Merge branch 'jt/index-fd-wo-repo-regression-fix-maint' into HEAD * jt/index-fd-wo-repo-regression-fix-maint: object-file: avoid ODB transaction when not writing objects
diff --git a/.editorconfig b/.editorconfig index 2d3929b..82e121a 100644 --- a/.editorconfig +++ b/.editorconfig
@@ -8,6 +8,10 @@ indent_style = tab tab_width = 8 +[templates/hooks/*.sample] +indent_style = tab +tab_width = 8 + [*.py] indent_style = space indent_size = 4
diff --git a/.gitattributes b/.gitattributes index 3258314..556322b 100644 --- a/.gitattributes +++ b/.gitattributes
@@ -1,13 +1,13 @@ -* whitespace=!indent,trail,space -*.[ch] whitespace=indent,trail,space diff=cpp -*.sh whitespace=indent,trail,space text eol=lf +* whitespace=trail,space +*.[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 *.pm text eol=lf diff=perl *.py text eol=lf diff=python *.bat text eol=crlf CODE_OF_CONDUCT.md -whitespace -/Documentation/**/*.adoc text eol=lf +/Documentation/**/*.adoc text eol=lf whitespace=trail,space,incomplete /command-list.txt text eol=lf /GIT-VERSION-GEN text eol=lf /mergetools/* text eol=lf @@ -17,3 +17,5 @@ /Documentation/gitk.adoc conflict-marker-size=32 /Documentation/user-manual.adoc conflict-marker-size=32 /t/t????-*.sh conflict-marker-size=32 +/t/unit-tests/clar/test/expected/* whitespace=-blank-at-eof +/templates/hooks/*.sample whitespace=indent,trail,space,incomplete text eol=lf
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index c8755e3..9304212 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md
@@ -10,7 +10,8 @@ Please read ["A note from the maintainer"](https://git.kernel.org/pub/scm/git/git.git/plain/MaintNotes?h=todo) to learn how the Git project is managed, and how you can work with it. -In addition, we highly recommend you to read [our submission guidelines](../Documentation/SubmittingPatches). +In addition, we highly recommend you to read +[our submission guidelines](https://git-scm.com/docs/SubmittingPatches). If you prefer video, then [this talk](https://www.youtube.com/watch?v=Q7i_qQW__q4&feature=youtu.be&t=6m4s) might be useful to you as the presenter walks you through the contribution
diff --git a/.github/workflows/l10n.yml b/.github/workflows/l10n.yml index e2c3dbd..95e5513 100644 --- a/.github/workflows/l10n.yml +++ b/.github/workflows/l10n.yml
@@ -63,7 +63,7 @@ origin \ ${{ github.ref }} \ $args - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: go-version: '>=1.16' cache: false
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 816d5a3..826f2f5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml
@@ -150,7 +150,7 @@ - uses: git-for-windows/setup-git-for-windows-sdk@v1 - name: test shell: bash - run: . /etc/profile && ci/run-test-slice.sh ${{matrix.nr}} 10 + run: . /etc/profile && ci/run-test-slice.sh $((${{matrix.nr}} + 1)) 10 - name: print test failures if: failure() && env.FAILED_TEST_ARTIFACTS != '' shell: bash @@ -237,7 +237,7 @@ shell: bash env: NO_SVN_TESTS: 1 - run: . /etc/profile && ci/run-test-slice.sh ${{matrix.nr}} 10 + run: . /etc/profile && ci/run-test-slice.sh $((${{matrix.nr}} + 1)) 10 - name: print test failures if: failure() && env.FAILED_TEST_ARTIFACTS != '' shell: bash @@ -297,8 +297,18 @@ name: windows-meson-artifacts path: build - name: Test - shell: pwsh - run: meson test -C build --no-rebuild --print-errorlogs --slice "$(1+${{ matrix.nr }})/10" + shell: bash + run: ci/run-test-slice-meson.sh build $((${{matrix.nr}} + 1)) 10 + - name: print test failures + if: failure() && env.FAILED_TEST_ARTIFACTS != '' + shell: bash + run: ci/print-test-failures.sh + - name: Upload failed tests' directories + if: failure() && env.FAILED_TEST_ARTIFACTS != '' + uses: actions/upload-artifact@v4 + with: + name: failed-tests-windows-meson-${{ matrix.nr }} + path: ${{env.FAILED_TEST_ARTIFACTS}} regular: name: ${{matrix.vector.jobname}} (${{matrix.vector.pool}}) @@ -433,7 +443,7 @@ - run: ci/install-dependencies.sh - run: useradd builder --create-home - run: chown -R builder . - - run: sudo --preserve-env --set-home --user=builder ci/run-build-and-tests.sh + - run: chmod a+w $GITHUB_ENV && sudo --preserve-env --set-home --user=builder ci/run-build-and-tests.sh - name: print test failures if: failure() && env.FAILED_TEST_ARTIFACTS != '' run: sudo --preserve-env --set-home --user=builder ci/print-test-failures.sh
diff --git a/.gitignore b/.gitignore index 78a45cb..24635cf 100644 --- a/.gitignore +++ b/.gitignore
@@ -79,6 +79,7 @@ /git-grep /git-hash-object /git-help +/git-history /git-hook /git-http-backend /git-http-fetch
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b419a84..83ec786 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml
@@ -101,13 +101,13 @@ parallel: matrix: - jobname: osx-clang - image: macos-14-xcode-15 + image: macos-15-xcode-16 CC: clang - jobname: osx-reftable - image: macos-14-xcode-15 + image: macos-15-xcode-16 CC: clang - jobname: osx-meson - image: macos-14-xcode-15 + image: macos-15-xcode-16 CC: clang artifacts: paths: @@ -157,6 +157,8 @@ parallel: 10 .msvc-meson: + variables: + TEST_OUTPUT_DIRECTORY: "C:/Git-Test" tags: - saas-windows-medium-amd64 before_script: @@ -164,12 +166,13 @@ - choco install -y git meson ninja rust-ms - Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1 - refreshenv + - New-Item -Path $env:TEST_OUTPUT_DIRECTORY -ItemType Directory build:msvc-meson: extends: .msvc-meson stage: build script: - - meson setup build --vsenv -Dperl=disabled -Dbackend_max_links=1 -Dcredential_helpers=wincred + - meson setup build --vsenv -Dperl=disabled -Dbackend_max_links=1 -Dcredential_helpers=wincred -Dtest_output_directory="$TEST_OUTPUT_DIRECTORY" - meson compile -C build artifacts: paths: @@ -183,11 +186,21 @@ - job: "build:msvc-meson" artifacts: true script: - - meson test -C build --no-rebuild --print-errorlogs --slice $Env:CI_NODE_INDEX/$Env:CI_NODE_TOTAL + - | + & "C:/Program Files/Git/usr/bin/bash.exe" -l -c 'ci/run-test-slice-meson.sh build $CI_NODE_INDEX $CI_NODE_TOTAL' + after_script: + - | + if ($env:CI_JOB_STATUS -ne "success") { + & "C:/Program Files/Git/usr/bin/bash.exe" -l -c 'ci/print-test-failures.sh' + Move-Item -Path "$env:TEST_OUTPUT_DIRECTORY/failed-test-artifacts" -Destination t/ + } parallel: 10 artifacts: + paths: + - t/failed-test-artifacts reports: junit: build/meson-logs/testlog.junit.xml + when: on_failure test:fuzz-smoke-tests: image: ubuntu:latest
diff --git a/.mailmap b/.mailmap index 7b31981..c2e3939 100644 --- a/.mailmap +++ b/.mailmap
@@ -107,6 +107,9 @@ Jay Soffian <jaysoffian@gmail.com> <jaysoffian+git@gmail.com> Jean-Noël Avila <jn.avila@free.fr> Jean-Noel Avila Jean-Noël Avila <jn.avila@free.fr> Jean-Noël AVILA +Jean-Noël Avila <jn.avila@free.fr> Jean-Noel Avila <jean-noel.avila@scantech.fr> +Jean-Noël Avila <jn.avila@free.fr> Jean-Noël AVILA <avila.jn@gmail.com> +Jean-Noël Avila <jn.avila@free.fr> Jean-Noël Avila via GitGitGadget <gitgitgadget@gmail.com> Jeff King <peff@peff.net> <peff@github.com> Jeff Muizelaar <jmuizelaar@mozilla.com> <jeff@infidigm.net> Jens Axboe <axboe@kernel.dk> <axboe@suse.de> @@ -140,8 +143,8 @@ Kaartic Sivaraam <kaartic.sivaraam@gmail.com> <kaarticsivaraam91196@gmail.com> Karl Wiberg <kha@treskal.com> Karl Hasselström Karl Wiberg <kha@treskal.com> <kha@yoghurt.hemma.treskal.com> -Karsten Blees <blees@dcon.de> <karsten.blees@dcon.de> -Karsten Blees <blees@dcon.de> <karsten.blees@gmail.com> +Karsten Blees <karsten.blees@gmail.com> <karsten.blees@dcon.de> +Karsten Blees <karsten.blees@gmail.com> <blees@dcon.de> Kay Sievers <kay.sievers@vrfy.org> <kay.sievers@suse.de> Kay Sievers <kay.sievers@vrfy.org> <kay@mam.(none)> Kazuki Saitoh <ksaitoh560@gmail.com> kazuki saitoh <ksaitoh560@gmail.com> @@ -221,11 +224,14 @@ Peter Krefting <peter@softwolves.pp.se> <peter@svarten.intern.softwolves.pp.se> Petr Baudis <pasky@ucw.cz> <pasky@suse.cz> Petr Baudis <pasky@ucw.cz> <xpasky@machine> -Phil Hord <hordp@cisco.com> <phil.hord@gmail.com> +Phil Hord <phil.hord@gmail.com> <hordp@cisco.com> +Phil Hord <phil.hord@gmail.com> <phord@purestorage.com> Philip Jägenstedt <philip@foolip.org> <philip.jagenstedt@gmail.com> Philip Oakley <philipoakley@iee.email> <philipoakley@iee.org> # secondary <philipoakley@dunelm.org.uk> Philipp A. Hartmann <pah@qo.cx> <ph@sorgh.de> Philippe Bruhat <book@cpan.org> +Phillip Wood <phillip.wood@dunelm.org.uk> <phillip.wood123@gmail.com> +Phillip Wood <phillip.wood@dunelm.org.uk> <phillip.wood@talktalk.net> Ralf Thielow <ralf.thielow@gmail.com> <ralf.thielow@googlemail.com> Ramsay Jones <ramsay@ramsayjones.plus.com> <ramsay@ramsay1.demon.co.uk> Ramkumar Ramachandra <r@artagnon.com> <artagnon@gmail.com> @@ -277,6 +283,7 @@ Thomas Rast <tr@thomasrast.ch> <trast@student.ethz.ch> Thomas Rast <tr@thomasrast.ch> <trast@inf.ethz.ch> Thomas Rast <tr@thomasrast.ch> <trast@google.com> +Tian Yuchen <cat@malon.dev> <a3205153416@gmail.com> Timo Hirvonen <tihirvon@gmail.com> <tihirvon@ee.oulu.fi> Toby Allsopp <Toby.Allsopp@navman.co.nz> <toby.allsopp@navman.co.nz> Tom Grennan <tmgrennan@gmail.com> <tgrennan@redback.com>
diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index df72fe0..b867075 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines
@@ -33,6 +33,16 @@ achieve and why the changes were necessary (more on this in the accompanying SubmittingPatches document). + - A label "NEEDSWORK:" followed by a description of the things to + be done is a way to leave in-code comments to document design + decisions yet to be made. 80% of the work to resolve a NEEDSWORK + comment is to decide if it still makes sense to do so, since the + situation around the codebase may have changed since the comment + was written. It can be a very valid change to remove an existing + NEEDSWORK comment without doing anything else, with the commit log + message describing a good argument why it does not make sense to do + the thing the NEEDSWORK comment mentioned. + Make your code readable and sensible, and don't try to be clever. As for more concrete guidelines, just imitate the existing code @@ -430,6 +440,8 @@ */ _("Here is a translatable string explained by the above."); + We do not use // comments. + - Double negation is often harder to understand than no negation at all. @@ -656,6 +668,19 @@ unsigned other_field:1; unsigned field_with_longer_name:1; + - Array names should be named in the singular form if the individual items are + subject of use. E.g.: + + char *dog[] = ...; + walk_dog(dog[0]); + walk_dog(dog[1]); + + Cases where the array is employed as a whole rather than as its unit parts, + the plural forms is preferable. E.g: + + char *dogs[] = ...; + walk_all_dogs(dogs); + For Perl programs: - Most of the C guidelines above apply.
diff --git a/Documentation/Makefile b/Documentation/Makefile index 04e9e10..2699f0b 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile
@@ -53,6 +53,7 @@ MAN7_TXT += gitcore-tutorial.adoc MAN7_TXT += gitcredentials.adoc MAN7_TXT += gitcvs-migration.adoc +MAN7_TXT += gitdatamodel.adoc MAN7_TXT += gitdiffcore.adoc MAN7_TXT += giteveryday.adoc MAN7_TXT += gitfaq.adoc @@ -142,6 +143,7 @@ TECH_DOCS += technical/sparse-checkout TECH_DOCS += technical/sparse-index TECH_DOCS += technical/trivial-merge +TECH_DOCS += technical/unambiguous-types TECH_DOCS += technical/unit-tests SP_ARTICLES += $(TECH_DOCS) SP_ARTICLES += technical/api-index
diff --git a/Documentation/MyFirstContribution.adoc b/Documentation/MyFirstContribution.adoc index f186dfb..b9fdefc 100644 --- a/Documentation/MyFirstContribution.adoc +++ b/Documentation/MyFirstContribution.adoc
@@ -331,7 +331,8 @@ for you, try `cd Documentation/ && ../bin-wrappers/git psuh`). That's not so helpful. So what other context can we get? -Add a line to `#include "config.h"` and `#include "repository.h"`. +Add a line to `#include "config.h"`, `#include "repository.h"` and +`#include "environment.h"`. Then, add the following bits to the function body: function body: @@ -351,7 +352,7 @@ apply standard precedence rules. `repo_config_get_string_tmp()` will look up a specific key ("user.name") and give you the value. There are a number of single-key lookup functions like this one; you can see them all (and more info -about how to use `repo_config()`) in `Documentation/technical/api-config.adoc`. +about how to use `repo_config()`) in `config.h`. You should see that the name printed matches the one you see when you run: @@ -429,6 +430,7 @@ ---- #include "commit.h" #include "pretty.h" +#include "strbuf.h" ---- Then, add the following lines within your implementation of `cmd_psuh()` near @@ -503,8 +505,8 @@ SYNOPSIS -------- -[verse] -'git-psuh [<arg>...]' +[synopsis] +git psuh [<arg>...] DESCRIPTION ----------- @@ -726,9 +728,10 @@ ---- NOTE: You can also do this with `make test` or use any testing harness which can -speak TAP. `prove` can run concurrently. `shuffle` randomizes the order the -tests are run in, which makes them resilient against unwanted inter-test -dependencies. `prove` also makes the output nicer. +speak TAP. `prove` can run concurrently. `-j$(nproc)` runs tests using all +available CPUs in parallel, but the job count can be adjusted as needed. +`shuffle` randomizes the order the tests are run in, which makes them resilient +against unwanted inter-test dependencies. `prove` also makes the output nicer. Go ahead and commit this change, as well.
diff --git a/Documentation/RelNotes/2.53.0.adoc b/Documentation/RelNotes/2.53.0.adoc new file mode 100644 index 0000000..2668c2e --- /dev/null +++ b/Documentation/RelNotes/2.53.0.adoc
@@ -0,0 +1,336 @@ +Git v2.53 Release Notes +======================= + +UI, Workflows & Features +------------------------ + + * "git maintenance" command learned "is-needed" subcommand to tell if + it is necessary to perform various maintenance tasks. + + * "git replay" (experimental) learned to perform ref updates itself + in a transaction by default, instead of emitting where each refs + should point at and leaving the actual update to another command. + + * "git blame" learns "--diff-algorithm=<algo>" option. + + * "git repo info" learned "--all" option. + + * Both "git apply" and "git diff" learn a new whitespace error class, + "incomplete-line". + + * Add a new manual that describes the data model. + + * "git fast-import" learns "--signed-commits=strip-if-invalid" option + to drop invalid cryptographic signature from objects. + + * The use of "revision" (a connected set of commits) has been + clarified in the "git replay" documentation. + + * A help message from "git branch" now mentions "git help" instead of + "man" when suggesting to read some documentation. + + * "git repo struct" learned to take "-z" as a synonym to "--format=nul". + + * More object database related information are shown in "git repo + structure" output. + + * Improve the error message when a bad argument is given to the + `--onto` option of "git replay". Test coverage of "git replay" has + been improved. + + * The iconv library on macOS fails to correctly handle stateful + ISO/IEC 2022:1994 encoded strings. Work it around instead of + replacing it wholesale from homebrew. + + * Upstream symbolic link support on Windows from Git-for-Windows. + + +Performance, Internal Implementation, Development Support etc. +-------------------------------------------------------------- + + * The list of packfiles used in a running Git process is moved from + the packed_git structure into the packfile store. + + * Some ref backend storage can hold not just the object name of an + annotated tag, but the object name of the object the tag points at. + The code to handle this information has been streamlined. + + * As "git diff --quiet" only cares about the existence of any + changes, disable rename/copy detection to skip more expensive + processing whose result will be discarded anyway. + + * A part of code paths that deals with loose objects has been cleaned + up. + + * "make strip" has been taught to strip "scalar" as well as "git". + + * Dockerized jobs at the GitHub Actions CI have been taught to show + more details of failed tests. + + * Code refactoring around object database sources. + + * Halve the memory consumed by artificial filepairs created during + "git diff --find-copies-harder", also making the operation run + faster. + + * The "git_istream" abstraction has been revamped to make it easier + to interface with pluggable object database design. + + * Rewrite the only use of "mktemp()" that is subject to TOCTOU race + and Stop using the insecure "mktemp()" function. + (merge 10bba537c4 rs/ban-mktemp later to maint). + + * In-code comment update to clarify that single-letter options are + outside of the scope of command line completion script. + (merge dc8a00fafe jc/completion-no-single-letter-options later to maint). + + * MEMZERO_ARRAY() helper is introduced to avoid clearing only the + first N bytes of an N-element array whose elements are larger than + a byte. + + * "git diff-files -R --find-copies-harder" has been taught to use + the potential copy sources from the index correctly. + + * Require C99 style flexible array member support from all platforms. + + * The code path that enumerates promisor objects have been optimized + to skip pointlessly parsing blob objects. + + * Prepare test suite for Git for Windows that supports symbolic + links. + + * Import newer version of "clar", unit testing framework. + (merge 84071a6dea ps/clar-integers later to maint). + + * The packfile_store data structure is moved from object store to odb + source. + + * The object-info API has been cleaned up. + + * Further preparation to upstream symbolic link support on Windows. + + * Remove implicit reliance on the_repository global in the APIs + around tree objects and make it explicit which repository to work + in. + + * "git bugreport" and "git version --build-options" learned to + include use of 'gettext' feature, to make it easier to diagnose + problems around l10n. + + * Dscho observed that SVN tests are taking too much time in CI leak + checking tasks, but most time is spent not in our code but in libsvn + code (which happen to be written in Perl), whose leaks have little + value to discover for us. Skip SVN, P4, and CVS tests in the leak + checking tasks. + (merge 047bd7dfe3 js/ci-leak-skip-svn later to maint). + + +Fixes since v2.52 +----------------- + + * Ever since we added whitespace rules for this project, we misspelt + an entry, which has been corrected. + (merge 358e94dc70 jc/gitattributes-whitespace-no-indent-fix later to maint). + + * The code to expand attribute macros has been rewritten to avoid + recursion to avoid running out of stack space in an uncontrolled + way. + (merge 42ed046866 jk/attr-macroexpand-wo-recursion later to maint). + + * Adding a repository that uses a different hash function is a no-no, + but "git submodule add" did not prevent it, which has been corrected. + (merge 6fe288bfbc bc/submodule-force-same-hash later to maint). + + * An earlier check added to osx keychain credential helper to avoid + storing the credential itself supplied was overeager and rejected + credential material supplied by other helper backends that it would + have wanted to store, which has been corrected. + (merge 4580bcd235 kn/osxkeychain-idempotent-store-fix later to maint). + + * The "git repo structure" subcommand tried to align its output but + mixed up byte count and display column width, which has been + corrected. + (merge 7a03a10a3a jx/repo-struct-utf8width-fix later to maint). + + * Yet another corner case fix around renames in the "ort" merge + strategy. + (merge a562d90a35 en/ort-rename-another-fix later to maint). + + * Test leakfix. + (merge 14b561e768 jk/test-mktemp-leakfix later to maint). + + * Update a version of action used at the GitHub Actions CI. + (merge cd99203f86 js/ci-github-setup-go-update later to maint). + + * The "return errno = EFOO, -1" construct, which is heavily used in + compat/mingw.c and triggers warnings under "-Wcomma", has been + rewritten to avoid the warnings. + (merge af3919816f js/mingw-assign-comma-fix later to maint). + + * Makefile based build have recently been updated to build a + libgit.a that also has reftable and xdiff objects; CMake based + build procedure has been updated to match. + (merge b0d5c88cca js/cmake-libgit-fix later to maint). + + * Under-allocation fix. + (merge d22a488482 js/wincred-get-credential-alloc-fix later to maint). + + * "git worktree list" attempts to show paths to worktrees while + aligning them, but miscounted display columns for the paths when + non-ASCII characters were involved, which has been corrected. + (merge 08dfa59835 pw/worktree-list-display-width-fix later to maint). + + * "Windows+meson" job at the GitHub Actions CI was hard to debug, as + it did not show and save failed test artifacts, which has been + corrected. + (merge 17bd1108ea jk/ci-windows-meson-test-fix later to maint). + + * Emulation code clean-up. + (merge 2367c6bcd6 gf/win32-pthread-cond-wait-err later to maint). + + * Various issues detected by Asan have been corrected. + (merge a031b6181a jk/asan-bonanza later to maint). + + * "git config get --path" segfaulted on an ":(optional)path" that + does not exist, which has been corrected. + (merge 0bd16856ff jc/optional-path later to maint). + + * The "--committer-date-is-author-date" option of "git am/rebase" is + a misguided one. The documentation is updated to discourage its + use. + (merge fbf3d0669f kh/doc-committer-date-is-author-date later to maint). + + * The option help text given by "git config unset -h" described + the "--all" option to "replace", not "unset", multiple variables, + which has been corrected. + (merge 18bf67b753 rs/config-unset-opthelp-fix later to maint). + + * The error message given by "git config set", when the variable + being updated has more than one values defined, used old style "git + config" syntax with an incorrect option in its hint, both of which + have been corrected. + (merge df963f0df4 rs/config-set-multi-error-message-fix later to maint). + + * "git replay" forgot to omit the "gpgsig-sha256" extended header + from the resulting commit the same way it omits "gpgsig", which has + been corrected. + (merge 9f3a115087 pw/replay-exclude-gpgsig-fix later to maint). + + * A few tests have been updated to work under the shell compatible + mode of zsh. + (merge a92f243a94 bc/zsh-testsuite later to maint). + + * The way patience diff finds LCS has been optimized. + (merge c7e3b8085b yc/xdiff-patience-optim later to maint). + + * Recent optimization to "last-modified" command introduced use of + uninitialized block of memory, which has been corrected. + (merge fe4e60759b tc/last-modified-active-paths-optimization later to maint). + + * "git last-modified" used to mishandle "--" to mark the beginning of + pathspec, which has been corrected. + (merge 05491b90ce js/last-modified-with-sparse-checkouts later to maint). + + * Emulation code clean-up. + (merge 42aa7603aa gf/win32-pthread-cond-init later to maint). + + * "git submodule add" to add a submodule under <name> segfaulted, + when a submodule.<name>.something is already in .gitmodules file + without defining where its submodule.<name>.path is, which has been + corrected. + (merge dd8e8c786e jc/submodule-add later to maint). + + * "git fetch" that involves fetching tags, when a tag being fetched + needs to overwrite existing one, failed to fetch other tags, which + has been corrected. + (merge b7b17ec8a6 kn/fix-fetch-backfill-tag-with-batched-ref-updates later to maint). + + * Document "rev-list --filter-provided-objects" better. + (merge 6d8dc99478 jt/doc-rev-list-filter-provided-objects later to maint). + + * Even when there is no changes in the packfile and no need to + recompute bitmaps, "git repack" recomputed and updated the MIDX + file, which has been corrected. + (merge 6ce9d558ce ps/repack-avoid-noop-midx-rewrite later to maint). + + * Update HTTP tests to adjust for changes in curl 8.18.0 + (merge 17f4b01da7 jk/test-curl-updates later to maint). + + * Workaround the "iconv" shipped as part of macOS, which is broken + handling stateful ISO/IEC 2022 encoded strings. + (merge cee341e9dd rs/macos-iconv-workaround later to maint). + + * Running "git diff" with "--name-only" and other options that allows + us not to look at the blob contents, while objects that are lazily + fetched from a promisor remote, caused use-after-free, which has + been corrected. + + * The ort merge machinery hit an assertion failure in a history with + criss-cross merges renamed a directory and a non-directory, which + has been corrected. + (merge 979ee83e8a en/ort-recursive-d-f-conflict-fix later to maint). + + * Diagnose invalid bundle-URI that lack the URI entry, instead of + crashing. + (merge 7796c14a1a sb/bundle-uri-without-uri later to maint). + + * Mailmap update for Karsten + (merge e97678c4ef js/mailmap-karsten-blees later to maint). + + * Perf-test fixes. + (merge 79d301c767 jk/t-perf-fixes later to maint). + + * Fix for a performance regression in "git cat-file". + (merge 9e8b448dd8 jk/cat-file-avoid-bitmap-when-unneeded later to maint). + + * Update a FAQ entry on synching two separate repositories using the + "git stash export/import" recently introduced. + (merge 02fc44a989 bc/doc-stash-import-export later to maint). + + * "git fsck" used inconsistent set of refs to show a confused + warning, which has been corrected. + + * Some error messages from the http transport layer lacked the + terminating newline, which has been corrected. + (merge a8227ae8d5 kt/http-backend-errors later to maint). + + * "git repack --geometric" did not work with promisor packs, which + has been corrected. + + * The logic that avoids reusing MIDX files with a wrong checksum was + broken, which has been corrected. + + * Other code cleanup, docfix, build fix, etc. + (merge 46207a54cc qj/doc-http-bad-want-response later to maint). + (merge df90eccd93 kh/doc-commit-extra-references later to maint). + (merge f18aa68861 rs/xmkstemp-simplify later to maint). + (merge fddba8f737 ja/doc-synopsis-style later to maint). + (merge 22ce0cb639 en/xdiff-cleanup-2 later to maint). + (merge 8ef7355a8f je/doc-pull later to maint). + (merge 48176f953f jc/capability-leak later to maint). + (merge 8cbbdc92f7 kh/doc-pre-commit-fix later to maint). + (merge d4bc39a4d9 mh/doc-config-gui-gcwarning later to maint). + (merge 41d425008a kh/doc-send-email-paragraph-fix later to maint). + (merge d4b732899e jc/macports-darwinports later to maint). + (merge bab391761d kj/pull-options-decl-cleanup later to maint). + (merge 007b8994d4 rs/t4014-git-version-string-fix later to maint). + (merge 4ce170c522 ds/doc-scalar-config later to maint). + (merge a0c813951a jc/doc-commit-signoff-config later to maint). + (merge 8ee262985a ja/doc-misc-fixes later to maint). + (merge 1722c2244b mh/doc-core-attributesfile later to maint). + (merge c469ca26c5 dk/ci-rust-fix later to maint). + (merge 12f0be0857 gf/clear-path-cache-cleanup later to maint). + (merge 949df6ed6b js/test-func-comment-fix later to maint). + (merge 93f894c001 bc/checkout-error-message-fix later to maint). + (merge abf05d856f rs/show-branch-prio-queue later to maint). + (merge 06188ea5f3 rs/parse-config-expiry-simplify later to maint). + (merge 861dbb1586 dd/t5403-modernise later to maint). + (merge acffc5e9e5 ja/doc-synopsis-style-more later to maint). + (merge 6c5c7e7071 ac/t1420-use-more-direct-check later to maint). + (merge 2ac93bfcbc ds/builtin-doc-update later to maint). + (merge 3f051fc9c9 kh/doc-patch-id later to maint). + (merge 555c8464e5 je/doc-reset later to maint). + (merge 220f888d7e ps/t1410-cleanup later to maint). + (merge 5814b04c02 ps/config-doc-get-urlmatch-fix later to maint). + (merge 5ae594f30b sb/doc-update-ref-markup-fix later to maint). + (merge bc8556d066 ty/t1005-test-path-is-helpers later to maint).
diff --git a/Documentation/RelNotes/2.54.0.adoc b/Documentation/RelNotes/2.54.0.adoc new file mode 100644 index 0000000..c692ddd --- /dev/null +++ b/Documentation/RelNotes/2.54.0.adoc
@@ -0,0 +1,534 @@ +Git v2.54 Release Notes +======================= + +UI, Workflows & Features +------------------------ + + * "git add -p" and friends note what the current status of the hunk + being shown is. + + * "git history" history rewriting (experimental) command has been + added. + + * "git replay" is taught to drop commits that become empty (not the + ones that are empty in the original). + + * The help text and the documentation for the "--expire" option of + "git worktree [list|prune]" have been improved. + + * When "git show-index" is run outside a repository, it silently + defaults to SHA-1; the tool now warns when this happens. + + * "git merge-file" can be run outside a repository, but it ignored + all configuration, even the per-user ones. The command now uses + available configuration files to find its customization. + + * "auto filter" logic for large-object promisor remote. + + * "git rev-list" and friends learn "--maximal-only" to show only the + commits that are not reachable by other commits. + + * Command line completion (in contrib/) update for + "stash import/export". + + * "git repo info" learns "--keys" action to list known keys. + + * Extend the alias configuration syntax to allow aliases using + characters outside ASCII alphanumeric (plus '-'). + + * A signature on a commit that was GPG signed long time ago ought to + be still valid after the key that was used to sign it has expired, + but we showed them in alarming red. + + * "git subtree split --prefix=P <commit>" now checks the prefix P + against the tree of the (potentially quite different from the + current working tree) given commit. + + * "git add -p" learned a new mode that allows the user to revisit a + file that was already dealt with. + + * Allow the directory in which reference backends store their data to + be specified. + + * "gitweb" has been taught to be mobile friendly. + + * "git apply --directory=./un/../normalized/path" now normalizes the + given path before using it. + + * "git maintenance" starts using the "geometric" strategy by default. + + * "git config list" is taught to show the values interpreted for + specific type with "--type=<X>" option. + + * "git add <submodule>" has been taught to honor + submodule.<name>.ignore that is set to "all" (and requires "git add + -f" to override it). + + * Hook commands are now allowed to be defined (possibly centrally) + in the configuration files, and run multiple of them for the same + hook event. + + * The way end-users can add their own "git <cmd>" subcommand by + storing "git-<cmd>" in a directory on their $PATH has not been + documented clearly, which has been corrected. + + * "git send-email" learns to pass hostname/port to Authen::SASL + module. + + * "git send-email" learns to support use of client-side certificates. + + * "git send-email" has learned to be a bit more careful when it + accepts charset to use from the end-user, to avoid 'y' (mistaken + 'yes' when expecting a charset like 'UTF-8') and other nonsense. + + * "git status" learned to show comparison between the current branch + and various other branches listed on status.compareBranches + configuration. + + * "git repo structure" command learns to report maximum values on + various aspects of objects it inspects. + + * "git rebase" learns "--trailer" command to drive the + interpret-trailers machinery. + + * "git fast-import" learned to optionally replace signature on + commits whose signatures get invalidated due to replaying by + signing afresh. + + * "git history" learned the "split" subcommand. + + * The reference-transaction hook was taught to be triggered before + taking locks on references in the "preparing" phase. + + * "git apply" now reports the name of the input file along with the + line number when it encounters a corrupt patch, and correctly + resets the line counter when processing multiple patch files. + + * The HTTP transport learned to react to "429 Too Many Requests". + + * "git repo info -h" and "git repo structure -h" limit their help output + to the part that is specific to the subcommand. + + * "git format-patch --cover-letter" learns to use a simpler format + instead of the traditional shortlog format to list its commits with + a new --commit-list-format option and format.commitListFormat + configuration variable. + + * `git backfill` learned to accept revision and pathspec arguments. + + * "git replay" (experimental) learns, in addition to "pick" and + "replay", a new operating mode "revert". + + * git replay now supports replaying down to the root commit. + + +Performance, Internal Implementation, Development Support etc. +-------------------------------------------------------------- + + * Avoid local submodule repository directory paths overlapping with + each other by encoding submodule names before using them as path + components. + + * The string_list API gains a new helper, string_list_sort_u(), and + new unit tests to extend coverage. + + * Improve set-up time of a perf test. + + * ISO C23 redefines strchr and friends that traditionally took + a const pointer and returned a non-const pointer derived from it to + preserve constness (i.e., if you ask for a substring in a const + string, you get a const pointer to the substring). Update code + paths that used non-const pointer to receive their results that did + not have to be non-const to adjust. + + * Rename three functions around the commit_list data structure. + + * Transaction to create objects (or not) is currently tied to the + repository, but in the future a repository can have multiple object + sources, which may have different transaction mechanisms. Make the + odb transaction API per object source. + + * "git merge-ours" is taught to work better in a sparse checkout. + + * Allow recording process ID of the process that holds the lock next + to a lockfile for diagnosis. + + * Reduce dependency on the_repository of xdiff-interface layer. + + * Code clean-up to use the commit_stack API. + + * "git diff --anchored=<text>" has been optimized. + + * A CodingGuidelines update. + + * Add process ancestry data to trace2 on macOS to match what we + already do on Linux and Windows. Also adjust the way Windows + implementation reports this information to match the other two. + + * A handful of places used refs_for_each_ref_in() API incorrectly, + which has been corrected. + + * Some tests assumed "iconv" is available without honoring ICONV + prerequisite, which has been corrected. + + * Revamp object enumeration API around odb. + + * Additional tests were introduced to see the interaction with netrc + auth with auth failure on the http transport. + + * A couple of bugs in use of flag bits around odb API has been + corrected, and the flag bits reordered. + + * Plumb gitk/git-gui build and install procedure in meson based + builds. + + * The code to accept shallow "git push" has been optimized. + + * Simplify build procedure for oxskeychain (in contrib/). + + * Fix dependency screw-up in meson-based builds. + + * Wean the mailmap code off of the_repository dependency. + + * API clean-up for the worktree subsystem. + + * The last uses of the_repository in "tree-diff.c" have been + eradicated. + + * Clean-up the code around "git repo info" command. + + * Mark the merge-ort codebase to prevent more uses of the_repository + from getting added. + + * The core.attributesfile is intended to be set per repository, but + were kept track of by a single global variable in-core, which has + been corrected by moving it to per-repository data structure. + + * Use the hook API to replace ad-hoc invocation of hook scripts via + the run_command() API. + + * Code refactoring around refs-for-each-* API functions. + + * The parse-options API learned to notice an options[] array with + duplicated long options. + (merge 237e520d81 rs/parse-options-duplicated-long-options later to maint). + + * The code to maintain mapping between object names in multiple hash + functions is being added, written in Rust. + + * A bit of OIDmap API enhancement and cleanup. + + * Move gitlab CI from macOS 14 images that are being deprecated. + + * The object source API is getting restructured to allow plugging new + backends. + + * Reduce dependence on the global the_hash_algo and the_repository + variables of wt-status code path. + + * The way combined list-object filter options are parsed has been + revamped. + + * Editorconfig filename patterns were specified incorrectly, making + many source files inside subdirectories unaffected, which has been + corrected. + + * The run_command() API lost its implicit dependency on the singleton + `the_repository` instance. + + * The unit test helper function was taught to use backslash + + mnemonic notation for certain control characters like "\t", instead + of octal notation like "\011". + + * Adjust test-lint to allow "sed -E" to use ERE in the patterns. + + * Clar (unit testing framework) update from the upstream. + + * Reduce system overhead "git upload-pack" spends on relaying "git + pack-objects" output to the "git fetch" running on the other end of + the connection. + + * Add a coccinelle rule to break the build when "struct strbuf" gets + passed by value. + + * Further work on incremental repacking using MIDX/bitmap + + * The logic to count objects has been cleaned up. + + * Tweak the build infrastructure by moving tools around. + + * Uses of prio_queue as a LIFO stack of commits have been written + with commit_stack. + + * The cleanup of remaining bitmaps in "ahead_behind()" has been + simplified. + + * split-index.c has been updated to not use the global the_repository + and the_hash_algo variables. + + * The unsigned integer that is used as an bitset to specify the kind + of branches interpret_branch_name() function has been changed to + use a dedicated enum type. + + * Various updates to contrib/diff-highlight, including documentation + updates, test improvements, and color configuration handling. + + * Code paths that loop over another array to push each element into a + strvec have been rewritten to use strvec_pushv() instead. + + * In case homebrew breaks REG_ENHANCED again, leave a in-code comment + to suggest use of our replacement regex as a workaround. + + * MinGW build updates. + + * The way dash 0.5.13 handles non-ASCII contents in here-doc + is buggy and breaks our existing tests, which unfortunately + have been rewritten to avoid triggering the bug. + + * Object name handling (disambiguation and abbreviation) has been + refactored to be backend-generic, moving logic into the respective + object database backends. + + * pack-objects's --stdin-packs=follow mode learns to handle + excluded-but-open packs. + + * A few code paths that spawned child processes for network + connection weren't wait(2)ing for their children and letting "init" + reap them instead; they have been tightened. + + * Adjust the codebase for C23 that changes functions like strchr() + that discarded constness when they return a pointer into a const + string to preserve constness. + + +Fixes since v2.53 +----------------- + + * HTTP transport failed to authenticate in some code paths, which has + been corrected. + (merge ed0f7a62f7 ap/http-probe-rpc-use-auth later to maint). + + * The computation of column width made by "git diff --stat" was + confused when pathnames contain non-ASCII characters. + (merge 04f5d95ef7 lp/diff-stat-utf8-display-width-fix later to maint). + + * The "-z" and "--max-depth" documentation (and implementation of + "-z") in the "git last-modified" command have been updated. + (merge 9dcc09bed1 tc/last-modified-options-cleanup later to maint). + + * A handful of code paths that started using batched ref update API + (after Git 2.51 or so) lost detailed error output, which have been + corrected. + (merge eff9299eac kn/ref-batch-output-error-reporting-fix later to maint). + + * "git blame --ignore-revs=... --color-lines" did not account for + ignored revisions passing blame to the same commit an adjacent line + gets blamed for. + (merge d519082d4e rs/blame-ignore-colors-fix later to maint). + + * Coccinelle rules update. + (merge 60614838a4 tc/memzero-array later to maint). + + * Giving "git last-modified" a tree (not a commit-ish) died an + uncontrolled death, which has been corrected. + (merge 525ef52301 tc/last-modified-not-a-tree later to maint). + + * Test contrib/ things in CI to catch breakages before they enter the + "next" branch. + (merge c591c3ceff jc/ci-test-contrib-too later to maint). + + * A handful of documentation pages have been modernized to use the + "synopsis" style. + (merge a34d1d53a6 ja/doc-synopsis-style-even-more later to maint). + + * Small clean-up of xdiff library to remove unnecessary data + duplication. + (merge 5086213bd2 pw/xdiff-cleanups later to maint). + + * Update sample commit-msg hook to complain when a log message has + material mailinfo considers the end of log message in the middle. + (merge 83804c361b pw/commit-msg-sample-hook later to maint). + + * "git pack-objects --stdin-packs" with "--exclude-promisor-objects" + fetched objects that are promised, which was not wanted. This has + been fixed. + (merge f4eff7116d ps/pack-concat-wo-backfill later to maint). + + * "git switch <name>", in an attempt to create a local branch <name> + after a remote tracking branch of the same name gave an advise + message to disambiguate using "git checkout", which has been + updated to use "git switch". + (merge 12fee11f21 jc/checkout-switch-restore later to maint). + + * It does not make much sense to apply the "incomplete-line" + whitespace rule to symbolic links, whose contents almost always + lack the final newline. "git apply" and "git diff" are now taught + to exclude them for a change to symbolic links. + (merge 6a41481c6d jc/whitespace-incomplete-line later to maint). + + * "git format-patch --from=<me>" did not honor the command line + option when writing out the cover letter, which has been corrected. + + * Update build precedure for mergetool documentation in meson-based builds. + (merge 58e4eeeeb5 pw/meson-doc-mergetool later to maint). + + * An earlier attempt to optimize "git subtree" discarded too much + relevant histories, which has been corrected. + + * A prefetch call can be triggered to access a stale diff_queue entry + after diffcore-break breaks a filepair into two and freed the + original entry that is no longer used, leading to a segfault, which + has been corrected. + (merge 2d88ab078d hy/diff-lazy-fetch-with-break-fix later to maint). + + * "git fetch --deepen" that tries to go beyond merged branch used to + get confused where the updated shallow points are, which has been + corrected. + (merge 3ef68ff40e sp/shallow-deepen-relative-fix later to maint). + + * "fsck" iterates over packfiles and its access to pack data caused + the list to be permuted, which caused it to loop forever; the code + to access pack data by "fsck" has been updated to avoid this. + (merge 13eb65d366 ps/fsck-stream-from-the-right-object-instance later to maint). + + * "git log --graph --stat" did not count the display width of colored + graph part of its own output correctly, which has been corrected. + (merge 064b869efc lp/diff-stat-utf8-display-width-fix later to maint). + + * The configuration variable format.noprefix did not behave as a + proper boolean variable, which has now been fixed and documented. + (merge ea3a62c40e kh/format-patch-noprefix-is-boolean later to maint). + + * CI fix. + (merge eb35167dd4 ps/ci-reduce-gitlab-envsize later to maint). + + * "git diff --no-index --find-object=<object-name>" outside a + repository of course wouldn't be able to find the object and died + while parsing the command line. The command is made to die in a + bit more user-friendly way. + (merge b0ddc7947c mm/diff-no-index-find-object later to maint). + + * Fix typo-induced breakages in fsmonitor-watchman sample hook. + (merge 41366e4677 pt/fsmonitor-watchman-sample-fix later to maint). + + * "git for-each-repo" started from a secondary worktree did not work + as expected, which has been corrected. + (merge e87493b9b4 ds/for-each-repo-w-worktree later to maint). + + * The construct 'test "$(command)" = expectation' loses the exit + status from the command, which has been fixed by breaking up the + statement into pieces. + (merge d3edca979a fp/t3310-unhide-git-failures later to maint). + + * While discovering a ".git" directory, the code treats any stat() + failure as a sign that a filesystem entity .git does not exist + there, and ignores ".git" that is not a "gitdir" file or a + directory. The code has been tightened to notice and report + filesystem corruption better. + (merge 1dd27bfbfd ty/setup-error-tightening later to maint). + + * Plug a few leaks where mmap'ed memory regions are not unmapped. + (merge a8a69bbb64 jk/unleak-mmap later to maint). + + * A test now uses the symbolic constant $ZERO_OID instead of 40 "0" to + work better with SHA-256 as well as SHA-1. + (merge 30310f3cc4 ss/t3200-test-zero-oid later to maint). + + * Instead of hardcoded 'origin', use the configured default remote + when fetching from submodules. + (merge 3b5fb32da8 ng/submodule-default-remote later to maint). + + * The code in "git help" that shows configuration items in sorted + order was awkwardly organized and prone to bugs. + + * "imap-send" used to use functions whose use is going to be removed + with OpenSSL 4.0; rewrite them using public API that has been + available since OpenSSL 1.1 since 2016 or so. + (merge 6392a0b75d bb/imap-send-openssl-4.0-prep later to maint). + + * Fix an example in the user-manual. + (merge 5514f14617 gj/user-manual-fix-grep-example later to maint). + + * The final clean-up phase of the diff output could turn the result of + histogram diff algorithm suboptimal, which has been corrected. + (merge e417277ae9 yc/histogram-hunk-shift-fix later to maint). + + * "git diff -U<num>" was too lenient in its command line parsing and + took an empty string as a valid <num>. + (merge 4f6a803aba ty/doc-diff-u-wo-number later to maint). + + * The handling of the incomplete lines at the end by "git + diff-highlight" has been fixed. + + * merge-file --object-id used to trigger a BUG when run in a linked + worktree, which has been fixed. + (merge 57246b7c62 mr/merge-file-object-id-worktree-fix later to maint). + + * "git apply -p<n>" parses <n> more carefully now. + (merge d05d84c5f5 mf/apply-p-no-atoi later to maint). + + * A test to run a .bat file with whitespaces in the name with arguments + with whitespaces in them was flaky in that sometimes it got killed + before it produced expected side effects, which has been rewritten to + make it more robust. + (merge 3ad4921838 jk/t0061-bat-test-update later to maint). + + * "git ls-remote '+refs/tags/*:refs/tags/*' https://..." run outside a + repository would dereference a NULL while trying to see if the given + refspec is a single-object refspec, which has been corrected. + (merge 4e5dc601dd kj/refspec-parsing-outside-repository later to maint). + + * Other code cleanup, docfix, build fix, etc. + (merge d79fff4a11 jk/remote-tracking-ref-leakfix later to maint). + (merge 7a747f972d dd/t5403-modernise later to maint). + (merge 81021871ea sp/myfirstcontribution-include-update later to maint). + (merge 49223593fd ac/sparse-checkout-string-list-cleanup later to maint). + (merge a824421d36 sp/t5500-cleanup later to maint). + (merge df1c5d7ed7 kh/doc-shortlog-fix later to maint). + (merge 2d45507f15 am/doc-github-contributiong-link-to-submittingpatches later to maint). + (merge 68060b9262 hs/t9160-test-paths later to maint). + (merge 486386c687 cs/subtree-reftable-testfix later to maint). + (merge 0728012c53 jc/diff-highlight-main-master-testfix later to maint). + (merge 831989ef38 mc/doc-send-email-signed-off-by-cc later to maint). + (merge c44b3f3203 sd/doc-my1c-api-config-reference-fix later to maint). + (merge 6c21e53bad rs/version-wo-the-repository later to maint). + (merge 10c68d2577 rs/clean-includes later to maint). + (merge 168d575719 bk/t2003-modernise later to maint). + (merge 6bfef81c9a kh/doc-rerere-options-xref later to maint). + (merge aaf3cc3d8d sd/t7003-test-path-is-helpers later to maint). + (merge 2668b6bdc4 jc/doc-rerere-update later to maint). + (merge 2f99f50f2d jc/doc-cg-c-comment later to maint). + (merge a454cdca42 kh/doc-am-format-sendmail later to maint). + (merge 8b0061b5c5 jk/ref-filter-lrstrip-optim later to maint). + (merge 5133837392 ps/ci-gitlab-msvc-updates later to maint). + (merge 143e84958c db/doc-fetch-jobs-auto later to maint). + (merge 0678e01f02 ap/use-test-seq-f-more later to maint). + (merge 96286f14b0 ty/symlinks-use-unsigned-for-bitset later to maint). + (merge b10e0cb1f3 kh/doc-am-xref later to maint). + (merge ed84bc1c0d kh/doc-patch-id-4 later to maint). + (merge 7451864bfa sc/pack-redundant-leakfix later to maint). + (merge f87593ab1a cx/fetch-display-ubfix later to maint). + (merge a66c8c7f91 jk/repo-structure-cleanup later to maint). + (merge 5ee8782f87 ss/test-that-that-typofix later to maint). + (merge f31b322008 fp/t3310-test-path-is-helpers later to maint). + (merge b22ed4c4f9 kj/path-micro-code-cleanup later to maint). + (merge a56fa1ca05 lp/doc-gitprotocol-pack-fixes later to maint). + (merge 0d6bb8b541 ss/t3700-modernize later to maint). + (merge 63c00a677b ss/t9123-setup-inside-test-expect-success later to maint). + (merge beca0ca4be os/doc-git-custom-commands later to maint). + (merge 4c223571be ty/patch-ids-document-lazy-eval later to maint). + (merge 476365ac85 jc/doc-wholesale-replace-before-next later to maint). + (merge 35f220b639 ss/submodule--helper-use-xmalloc later to maint). + (merge 02cbae61df cf/constness-fixes later to maint). + (merge 69efd53c81 ms/t7605-test-path-is-helpers later to maint). + (merge d39cef3a1a ss/t0410-delete-object-cleanup later to maint). + (merge 2f05039717 rj/pack-refs-tests-path-is-helpers later to maint). + (merge 2594747ad1 jk/transport-color-leakfix later to maint). + (merge 48430e44ac mf/t0008-cleanup later to maint). + (merge fc8a4f15e7 gi/doc-boolean-config-typofix later to maint). + (merge 37182267a0 kh/doc-interpret-trailers-1 later to maint). + (merge f64c50e768 jc/rerere-modern-strbuf-handling later to maint). + (merge 699248d89e th/t8003-unhide-git-failures later to maint). + (merge d8e34f971b za/t2000-modernise later to maint). + (merge 849988bc74 th/t6101-unhide-git-failures later to maint). + (merge 0f0ce07625 sp/doc-gitignore-oowt later to maint).
diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index e270ccb..d570184 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches
@@ -37,11 +37,36 @@ they have no obligation to help you (i.e. you ask them for help, you don't demand). +git log -p {litdd} _$area_you_are_modifying_+ would help you find out who they are. ++ +It is also a good idea to check whether your topic has been discussed +previously on the mailing list, or whether similar work is already in +progress. Prior discussions may contain useful context, design +considerations, or earlier attempts at solving the same problem. Being +aware of such discussions can help you avoid duplicating work and may +allow you to coordinate with other contributors working in the same +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. ++ +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. + +. These early update iterations are expected to be full replacements, + not incremental updates on top of what you posted already. If you + are correcting mistakes you made in the previous iteration that a + reviewer noticed and pointed out in their review, you _fix_ that + mistake by rewriting your history (e.g., by using "git rebase -i") + to pretend that you never made the mistake in the first place. In + other words, this is a chance to pretend to be a perfect developer, + and you are expected to take advantage of that. In the larger + picture, nobody is interested in your earlier mistakes. Just + present a logical progression made by a perfect developer who makes + no mistakes while working on the topic. . Polish, refine, and re-send your patches to the list and to the people who spent their time to improve your patch. Go back to step (2).
diff --git a/Documentation/asciidoc.conf.in b/Documentation/asciidoc.conf.in index ff9ea0a..31b883a 100644 --- a/Documentation/asciidoc.conf.in +++ b/Documentation/asciidoc.conf.in
@@ -81,12 +81,18 @@ ifdef::backend-docbook[] ifdef::doctype-manpage[] +[blockdef-open] +synopsis-style=template="verseparagraph",filter="sed 's!…\\(\\]\\|$\\)!<phrase>\\0</phrase>!g;s!\\([\\[ |()]\\|^\\|\\]\\|>\\)\\([-=a-zA-Z0-9:+@,\\/_^\\$.\\\\\\*]\\+\\|…\\)!\\1<literal>\\2</literal>!g;s!<[-a-zA-Z0-9.]\\+>!<emphasis>\\0</emphasis>!g'" + [paradef-default] synopsis-style=template="verseparagraph",filter="sed 's!…\\(\\]\\|$\\)!<phrase>\\0</phrase>!g;s!\\([\\[ |()]\\|^\\|\\]\\|>\\)\\([-=a-zA-Z0-9:+@,\\/_^\\$.\\\\\\*]\\+\\|…\\)!\\1<literal>\\2</literal>!g;s!<[-a-zA-Z0-9.]\\+>!<emphasis>\\0</emphasis>!g'" endif::doctype-manpage[] endif::backend-docbook[] ifdef::backend-xhtml11[] +[blockdef-open] +synopsis-style=template="verseparagraph",filter="sed 's!…\\(\\]\\|$\\)!<span>\\0</span>!g;s!\\([\\[ |()]\\|^\\|\\]\\|>\\)\\([-=a-zA-Z0-9:+@,\\/_^\\$.\\\\\\*]\\+\\|…\\)!\\1<code>\\2</code>!g;s!<[-a-zA-Z0-9.]\\+>!<em>\\0</em>!g'" + [paradef-default] synopsis-style=template="verseparagraph",filter="sed 's!…\\(\\]\\|$\\)!<span>\\0</span>!g;s!\\([\\[ |()]\\|^\\|\\]\\|>\\)\\([-=a-zA-Z0-9:+@,\\/_^\\$.\\\\\\*]\\+\\|…\\)!\\1<code>\\2</code>!g;s!<[-a-zA-Z0-9.]\\+>!<em>\\0</em>!g'" endif::backend-xhtml11[]
diff --git a/Documentation/blame-options.adoc b/Documentation/blame-options.adoc index 1fb948f..1ae1222 100644 --- a/Documentation/blame-options.adoc +++ b/Documentation/blame-options.adoc
@@ -1,105 +1,105 @@ --b:: +`-b`:: Show blank SHA-1 for boundary commits. This can also be controlled via the `blame.blankBoundary` config option. ---root:: +`--root`:: Do not treat root commits as boundaries. This can also be controlled via the `blame.showRoot` config option. ---show-stats:: +`--show-stats`:: Include additional statistics at the end of blame output. --L <start>,<end>:: --L :<funcname>:: - Annotate only the line range given by '<start>,<end>', - or by the function name regex '<funcname>'. +`-L <start>,<end>`:: +`-L :<funcname>`:: + Annotate only the line range given by `<start>,<end>`, + or by the function name regex _<funcname>_. May be specified multiple times. Overlapping ranges are allowed. + -'<start>' and '<end>' are optional. `-L <start>` or `-L <start>,` spans from -'<start>' to end of file. `-L ,<end>` spans from start of file to '<end>'. +_<start>_ and _<end>_ are optional. `-L <start>` or `-L <start>,` spans from +_<start>_ to end of file. `-L ,<end>` spans from start of file to _<end>_. + include::line-range-format.adoc[] --l:: +`-l`:: Show long rev (Default: off). --t:: +`-t`:: Show raw timestamp (Default: off). --S <revs-file>:: - Use revisions from revs-file instead of calling linkgit:git-rev-list[1]. +`-S <revs-file>`:: + Use revisions from _<revs-file>_ instead of calling + linkgit:git-rev-list[1]. ---reverse <rev>..<rev>:: +`--reverse <start>..<end>`:: Walk history forward instead of backward. Instead of showing the revision in which a line appeared, this shows the last revision in which a line has existed. This requires a range of - revision like START..END where the path to blame exists in - START. `git blame --reverse START` is taken as `git blame - --reverse START..HEAD` for convenience. + revision like `<start>..<end>` where the path to blame exists in + _<start>_. `git blame --reverse <start>` is taken as `git blame + --reverse <start>..HEAD` for convenience. ---first-parent:: +`--first-parent`:: Follow only the first parent commit upon seeing a merge commit. This option can be used to determine when a line was introduced to a particular integration branch, rather than when it was introduced to the history overall. --p:: ---porcelain:: +`-p`:: +`--porcelain`:: Show in a format designed for machine consumption. ---line-porcelain:: +`--line-porcelain`:: Show the porcelain format, but output commit information for each line, not just the first time a commit is referenced. - Implies --porcelain. + Implies `--porcelain`. ---incremental:: +`--incremental`:: Show the result incrementally in a format designed for machine consumption. ---encoding=<encoding>:: - Specifies the encoding used to output author names +`--encoding=<encoding>`:: + Specify the encoding used to output author names and commit summaries. Setting it to `none` makes blame output unconverted data. For more information see the discussion about encoding in the linkgit:git-log[1] manual page. ---contents <file>:: - Annotate using the contents from the named file, starting from <rev> - if it is specified, and HEAD otherwise. You may specify '-' to make +`--contents <file>`:: + Annotate using the contents from _<file>_, starting from _<rev>_ + if it is specified, and `HEAD` otherwise. You may specify `-` to make the command read from the standard input for the file contents. ---date <format>:: - Specifies the format used to output dates. If --date is not - provided, the value of the blame.date config variable is - used. If the blame.date config variable is also not set, the +`--date <format>`:: + Specify the format used to output dates. If `--date` is not + provided, the value of the `blame.date` config variable is + used. If the `blame.date` config variable is also not set, the iso format is used. For supported values, see the discussion - of the --date option at linkgit:git-log[1]. + of the `--date` option at linkgit:git-log[1]. ---progress:: ---no-progress:: - Progress status is reported on the standard error stream - by default when it is attached to a terminal. This flag - enables progress reporting even if not attached to a - terminal. Can't use `--progress` together with `--porcelain` - or `--incremental`. +`--progress`:: +`--no-progress`:: + Enable progress reporting on the standard error stream even if + not attached to a terminal. By default, progress status is + reported only when it is attached. You can't use `--progress` + together with `--porcelain` or `--incremental`. --M[<num>]:: +`-M[<num>]`:: Detect moved or copied lines within a file. When a commit moves or copies a block of lines (e.g. the original file - has A and then B, and the commit changes it to B and then - A), the traditional 'blame' algorithm notices only half of + has _A_ and then _B_, and the commit changes it to _B_ and then + _A_), the traditional `blame` algorithm notices only half of the movement and typically blames the lines that were moved - up (i.e. B) to the parent and assigns blame to the lines that - were moved down (i.e. A) to the child commit. With this + up (i.e. _B_) to the parent and assigns blame to the lines that + were moved down (i.e. _A_) to the child commit. With this option, both groups of lines are blamed on the parent by running extra passes of inspection. + -<num> is optional but it is the lower bound on the number of +_<num>_ is optional, but it is the lower bound on the number of alphanumeric characters that Git must detect as moving/copying within a file for it to associate those lines with the parent commit. The default value is 20. --C[<num>]:: +`-C[<num>]`:: In addition to `-M`, detect lines moved or copied from other files that were modified in the same commit. This is useful when you reorganize your program and move code @@ -109,14 +109,14 @@ option is given three times, the command additionally looks for copies from other files in any commit. + -<num> is optional but it is the lower bound on the number of +_<num>_ is optional, but it is the lower bound on the number of alphanumeric characters that Git must detect as moving/copying between files for it to associate those lines with the parent commit. And the default value is 40. If there are more than one -`-C` options given, the <num> argument of the last `-C` will +`-C` options given, the _<num>_ argument of the last `-C` will take effect. ---ignore-rev <rev>:: +`--ignore-rev <rev>`:: Ignore changes made by the revision when assigning blame, as if the change never happened. Lines that were changed or added by an ignored commit will be blamed on the previous commit that changed that line or @@ -126,26 +126,26 @@ another commit will be marked with a `?` in the blame output. If the `blame.markUnblamableLines` config option is set, then those lines touched by an ignored commit that we could not attribute to another revision are - marked with a '*'. In the porcelain modes, we print 'ignored' and - 'unblamable' on a newline respectively. + marked with a `*`. In the porcelain modes, we print `ignored` and + `unblamable` on a newline respectively. ---ignore-revs-file <file>:: - Ignore revisions listed in `file`, which must be in the same format as an +`--ignore-revs-file <file>`:: + Ignore revisions listed in _<file>_, which must be in the same format as an `fsck.skipList`. This option may be repeated, and these files will be processed after any files specified with the `blame.ignoreRevsFile` config option. An empty file name, `""`, will clear the list of revs from previously processed files. ---color-lines:: +`--color-lines`:: Color line annotations in the default format differently if they come from the same commit as the preceding line. This makes it easier to distinguish code blocks introduced by different commits. The color defaults to cyan and can be adjusted using the `color.blame.repeatedLines` config option. ---color-by-age:: - Color line annotations depending on the age of the line in the default format. - The `color.blame.highlightRecent` config option controls what color is used for - each range of age. +`--color-by-age`:: + Color line annotations depending on the age of the line in + the default format. The `color.blame.highlightRecent` config + option controls what color is used for each range of age. --h:: +`-h`:: Show help message.
diff --git a/Documentation/config/alias.adoc b/Documentation/config/alias.adoc index 80ce17d..dc6ca0e 100644 --- a/Documentation/config/alias.adoc +++ b/Documentation/config/alias.adoc
@@ -1,12 +1,47 @@ alias.*:: - Command aliases for the linkgit:git[1] command wrapper - e.g. - after defining `alias.last = cat-file commit HEAD`, the invocation - `git last` is equivalent to `git cat-file commit HEAD`. To avoid - confusion and troubles with script usage, aliases that - hide existing Git commands are ignored except for deprecated - commands. Arguments are split by - spaces, the usual shell quoting and escaping are supported. - A quote pair or a backslash can be used to quote them. +alias.*.command:: + Command aliases for the linkgit:git[1] command wrapper. Aliases + can be defined using two syntaxes: ++ +-- +1. Without a subsection, e.g., `[alias] co = checkout`. The alias + name ("co" in this example) is + limited to ASCII alphanumeric characters and `-`, + and is matched case-insensitively. +2. With a subsection, e.g., `[alias "co"] command = checkout`. The + alias name can contain any characters (except for newlines and NUL bytes), + including UTF-8, and is matched case-sensitively as raw bytes. + You define the action of the alias in the `command`. +-- ++ +Examples: ++ +---- +# Without subsection (ASCII alphanumeric and dash only) +[alias] + co = checkout + st = status + +# With subsection (allows any characters, including UTF-8) +[alias "hämta"] + command = fetch +[alias "rätta till"] + command = commit --amend +---- ++ +With a Git alias defined, e.g., ++ + $ git config --global alias.last "cat-file commit HEAD" + # Which is equivalent to + $ git config --global alias.last.command "cat-file commit HEAD" ++ +`git last` is equivalent to `git cat-file commit HEAD`. ++ +To avoid confusion and troubles with script usage, aliases that +hide existing Git commands are ignored except for deprecated +commands. Arguments are split by +spaces, the usual shell quoting and escaping are supported. +A quote pair or a backslash can be used to quote them. + Note that the first word of an alias does not necessarily have to be a command. It can be a command-line option that will be passed into the
diff --git a/Documentation/config/am.adoc b/Documentation/config/am.adoc index 5bcad2e..e9561e1 100644 --- a/Documentation/config/am.adoc +++ b/Documentation/config/am.adoc
@@ -1,14 +1,20 @@ am.keepcr:: - If true, git-am will call git-mailsplit for patches in mbox format - with parameter `--keep-cr`. In this case git-mailsplit will + If true, linkgit:git-am[1] will call linkgit:git-mailsplit[1] + for patches in mbox format with parameter `--keep-cr`. In this + case linkgit:git-mailsplit[1] will not remove `\r` from lines ending with `\r\n`. Can be overridden by giving `--no-keep-cr` from the command line. - See linkgit:git-am[1], linkgit:git-mailsplit[1]. am.threeWay:: - By default, `git am` will fail if the patch does not apply cleanly. When - set to true, this setting tells `git am` to fall back on 3-way merge if - the patch records the identity of blobs it is supposed to apply to and - we have those blobs available locally (equivalent to giving the `--3way` - option from the command line). Defaults to `false`. - See linkgit:git-am[1]. + By default, linkgit:git-am[1] will fail if the patch does not + apply cleanly. When set to true, this setting tells + linkgit:git-am[1] to fall back on 3-way merge if the patch + records the identity of blobs it is supposed to apply to and we + have those blobs available locally (equivalent to giving the + `--3way` option from the command line). Defaults to `false`. + +am.messageId:: + Add a `Message-ID` trailer based on the email header to the + commit when using linkgit:git-am[1] (see + linkgit:git-interpret-trailers[1]). See also the `--message-id` + and `--no-message-id` options.
diff --git a/Documentation/config/core.adoc b/Documentation/config/core.adoc index 11efad1..a0ebf03 100644 --- a/Documentation/config/core.adoc +++ b/Documentation/config/core.adoc
@@ -348,6 +348,17 @@ read-only snapshot of the same index to a location different from the repository's usual working tree). +core.lockfilePid:: + If true, Git will create a PID file alongside lock files. When a + lock acquisition fails and a PID file exists, Git can provide + additional diagnostic information about the process holding the + lock, including whether it is still running. Defaults to `false`. ++ +The PID file is named by inserting `~pid` before the `.lock` suffix. +For example, if the lock file is `index.lock`, the PID file will be +`index~pid.lock`. The file contains a single line in the format +`pid <value>` followed by a newline. + core.logAllRefUpdates:: Enable the reflog. Updates to a ref <ref> is logged to the file "`$GIT_DIR/logs/<ref>`", by appending the new and old @@ -492,10 +503,9 @@ command-line argument and write the password on its STDOUT. core.attributesFile:: - In addition to `.gitattributes` (per-directory) and - `.git/info/attributes`, Git looks into this file for attributes - (see linkgit:gitattributes[5]). Path expansions are made the same - way as for `core.excludesFile`. Its default value is + Specifies the pathname to the file that contains attributes (see + linkgit:gitattributes[5]), in addition to `.gitattributes` (per-directory) + and `.git/info/attributes`. Its default value is `$XDG_CONFIG_HOME/git/attributes`. If `$XDG_CONFIG_HOME` is either not set or empty, `$HOME/.config/git/attributes` is used instead. @@ -629,6 +639,8 @@ part of the line terminator, i.e. with it, `trailing-space` does not trigger if the character before such a carriage-return is not a whitespace (not enabled by default). +* `incomplete-line` treats the last line of a file that is missing the + newline at the end as an error (not enabled by default). * `tabwidth=<n>` tells how many character positions a tab occupies; this is relevant for `indent-with-non-tab` and when Git fixes `tab-in-indent` errors. The default tab width is 8. Allowed values are 1 to 63.
diff --git a/Documentation/config/extensions.adoc b/Documentation/config/extensions.adoc index 5324566..be6678b 100644 --- a/Documentation/config/extensions.adoc +++ b/Documentation/config/extensions.adoc
@@ -57,10 +57,24 @@ `core.repositoryFormatVersion` setting. refStorage::: - Specify the ref storage format to use. The acceptable values are: + Specify the ref storage format and a corresponding payload. The value + can be either a format name or a URI: + -- +* A format name alone (e.g., `reftable` or `files`). + +* A URI format `<format>://<payload>` explicitly specifies both the + format and payload (e.g., `reftable:///foo/bar`). + +Supported format names are: + include::../ref-storage-format.adoc[] + +The payload is passed directly to the reference backend. For the files and +reftable backends, this must be a filesystem path where the references will +be stored. Defaulting to the commondir when no payload is provided. Relative +paths are resolved relative to the `$GIT_DIR`. Future backends may support +other payload schemes, e.g., postgres://127.0.0.1:5432?database=myrepo. -- + Note that this setting should only be set by linkgit:git-init[1] or @@ -73,6 +87,35 @@ repaired with either the `--relative-paths` option or with the `worktree.useRelativePaths` config set to `true`. +submodulePathConfig::: + This extension is for the minority of users who: ++ +-- +* Encounter errors like `refusing to create ... in another submodule's git dir` + due to a number of reasons, like case-insensitive filesystem conflicts when + creating modules named `foo` and `Foo`. +* Require more flexible submodule layouts, for example due to nested names like + `foo`, `foo/bar` and `foo/baz` not supported by the default gitdir mechanism + which uses `.git/modules/<plain-name>` locations, causing further conflicts. +-- ++ +When `extensions.submodulePathConfig` is enabled, the `submodule.<name>.gitdir` +config becomes the single source of truth for all submodule gitdir paths and is +automatically set for all new submodules both during clone and init operations. ++ +Git will error out if a module does not have a corresponding +`submodule.<name>.gitdir` set. ++ +Existing (pre-extension) submodules need to be migrated by adding the missing +config entries. This can be done manually, e.g. for each submodule: +`git config submodule.<name>.gitdir .git/modules/<name>`, or via the +`git submodule--helper migrate-gitdir-configs` command which iterates over all +submodules and attempts to migrate them. ++ +The extension can be enabled automatically for new repositories by setting +`init.defaultSubmodulePathConfig` to `true`, for example by running +`git config --global init.defaultSubmodulePathConfig true`. + worktreeConfig::: If enabled, then worktrees will load config settings from the `$GIT_DIR/config.worktree` file in addition to the
diff --git a/Documentation/config/fetch.adoc b/Documentation/config/fetch.adoc index d7dc461..cd40db0 100644 --- a/Documentation/config/fetch.adoc +++ b/Documentation/config/fetch.adoc
@@ -1,32 +1,32 @@ -fetch.recurseSubmodules:: +`fetch.recurseSubmodules`:: This option controls whether `git fetch` (and the underlying fetch in `git pull`) will recursively fetch into populated submodules. - This option can be set either to a boolean value or to 'on-demand'. + This option can be set either to a boolean value or to `on-demand`. Setting it to a boolean changes the behavior of fetch and pull to recurse unconditionally into submodules when set to true or to not - recurse at all when set to false. When set to 'on-demand', fetch and + recurse at all when set to false. When set to `on-demand`, fetch and pull will only recurse into a populated submodule when its superproject retrieves a commit that updates the submodule's reference. - Defaults to 'on-demand', or to the value of 'submodule.recurse' if set. + Defaults to `on-demand`, or to the value of `submodule.recurse` if set. -fetch.fsckObjects:: +`fetch.fsckObjects`:: If it is set to true, git-fetch-pack will check all fetched objects. See `transfer.fsckObjects` for what's - checked. Defaults to false. If not set, the value of + checked. Defaults to `false`. If not set, the value of `transfer.fsckObjects` is used instead. -fetch.fsck.<msg-id>:: +`fetch.fsck.<msg-id>`:: Acts like `fsck.<msg-id>`, but is used by linkgit:git-fetch-pack[1] instead of linkgit:git-fsck[1]. See the `fsck.<msg-id>` documentation for details. -fetch.fsck.skipList:: +`fetch.fsck.skipList`:: Acts like `fsck.skipList`, but is used by linkgit:git-fetch-pack[1] instead of linkgit:git-fsck[1]. See the `fsck.skipList` documentation for details. -fetch.unpackLimit:: +`fetch.unpackLimit`:: If the number of objects fetched over the Git native transfer is below this limit, then the objects will be unpacked into loose object @@ -37,12 +37,12 @@ especially on slow filesystems. If not set, the value of `transfer.unpackLimit` is used instead. -fetch.prune:: +`fetch.prune`:: If true, fetch will automatically behave as if the `--prune` option was given on the command line. See also `remote.<name>.prune` and the PRUNING section of linkgit:git-fetch[1]. -fetch.pruneTags:: +`fetch.pruneTags`:: If true, fetch will automatically behave as if the `refs/tags/*:refs/tags/*` refspec was provided when pruning, if not set already. This allows for setting both this option @@ -50,41 +50,41 @@ refs. See also `remote.<name>.pruneTags` and the PRUNING section of linkgit:git-fetch[1]. -fetch.all:: +`fetch.all`:: If true, fetch will attempt to update all available remotes. This behavior can be overridden by passing `--no-all` or by explicitly specifying one or more remote(s) to fetch from. - Defaults to false. + Defaults to `false`. -fetch.output:: +`fetch.output`:: Control how ref update status is printed. Valid values are `full` and `compact`. Default value is `full`. See the OUTPUT section in linkgit:git-fetch[1] for details. -fetch.negotiationAlgorithm:: +`fetch.negotiationAlgorithm`:: Control how information about the commits in the local repository is sent when negotiating the contents of the packfile to be sent by - the server. Set to "consecutive" to use an algorithm that walks - over consecutive commits checking each one. Set to "skipping" to + the server. Set to `consecutive` to use an algorithm that walks + over consecutive commits checking each one. Set to `skipping` to use an algorithm that skips commits in an effort to converge faster, but may result in a larger-than-necessary packfile; or set - to "noop" to not send any information at all, which will almost + to `noop` to not send any information at all, which will almost certainly result in a larger-than-necessary packfile, but will skip - the negotiation step. Set to "default" to override settings made + the negotiation step. Set to `default` to override settings made previously and use the default behaviour. The default is normally - "consecutive", but if `feature.experimental` is true, then the - default is "skipping". Unknown values will cause 'git fetch' to + `consecutive`, but if `feature.experimental` is `true`, then the + default is `skipping`. Unknown values will cause `git fetch` to error out. + See also the `--negotiate-only` and `--negotiation-tip` options to linkgit:git-fetch[1]. -fetch.showForcedUpdates:: - Set to false to enable `--no-show-forced-updates` in +`fetch.showForcedUpdates`:: + Set to `false` to enable `--no-show-forced-updates` in linkgit:git-fetch[1] and linkgit:git-pull[1] commands. - Defaults to true. + Defaults to `true`. -fetch.parallel:: +`fetch.parallel`:: Specifies the maximal number of fetch operations to be run in parallel at a time (submodules, or remotes when the `--multiple` option of linkgit:git-fetch[1] is in effect). @@ -94,16 +94,16 @@ For submodules, this setting can be overridden using the `submodule.fetchJobs` config setting. -fetch.writeCommitGraph:: +`fetch.writeCommitGraph`:: Set to true to write a commit-graph after every `git fetch` command that downloads a pack-file from a remote. Using the `--split` option, most executions will create a very small commit-graph file on top of the existing commit-graph file(s). Occasionally, these files will merge and the write may take longer. Having an updated commit-graph file helps performance of many Git commands, including `git merge-base`, - `git push -f`, and `git log --graph`. Defaults to false. + `git push -f`, and `git log --graph`. Defaults to `false`. -fetch.bundleURI:: +`fetch.bundleURI`:: This value stores a URI for downloading Git object data from a bundle URI before performing an incremental fetch from the origin Git server. This is similar to how the `--bundle-uri` option behaves in @@ -115,9 +115,9 @@ value, then remove that `fetch.bundleCreationToken` value before fetching from the new bundle URI. -fetch.bundleCreationToken:: +`fetch.bundleCreationToken`:: When using `fetch.bundleURI` to fetch incrementally from a bundle - list that uses the "creationToken" heuristic, this config value + list that uses the "`creationToken`" heuristic, this config value stores the maximum `creationToken` value of the downloaded bundles. This value is used to prevent downloading bundles in the future if the advertised `creationToken` is not strictly larger than this
diff --git a/Documentation/config/format.adoc b/Documentation/config/format.adoc index ab0710e..dbd1862 100644 --- a/Documentation/config/format.adoc +++ b/Documentation/config/format.adoc
@@ -101,6 +101,11 @@ generate a cover-letter only when there's more than one patch. Default is false. +format.commitListFormat:: + When the `--cover-letter-format` option is not given, `format-patch` + uses the value of this variable to decide how to format the entry of + each commit. Defaults to `shortlog`. + format.outputDirectory:: Set a custom directory to store the resulting files instead of the current working directory. All directory components will be created.
diff --git a/Documentation/config/gui.adoc b/Documentation/config/gui.adoc index 171be77..1565c0a 100644 --- a/Documentation/config/gui.adoc +++ b/Documentation/config/gui.adoc
@@ -55,3 +55,8 @@ linkgit:gitk[1] for the selected commit, when the `Show History Context` menu item is invoked from 'git gui blame'. If this variable is set to zero, the whole history is shown. + +gui.GCWarning:: + Determines whether linkgit:git-gui[1] should prompt for garbage + collection when git detects a large number of loose objects in + the repository. The default value is "true".
diff --git a/Documentation/config/hook.adoc b/Documentation/config/hook.adoc new file mode 100644 index 0000000..9e78f26 --- /dev/null +++ b/Documentation/config/hook.adoc
@@ -0,0 +1,24 @@ +hook.<friendly-name>.command:: + The command to execute for `hook.<friendly-name>`. `<friendly-name>` + is a unique name that identifies this hook. The hook events that + trigger the command are configured with `hook.<friendly-name>.event`. + The value can be an executable path or a shell oneliner. If more than + one value is specified for the same `<friendly-name>`, only the last + value parsed is used. See linkgit:git-hook[1]. + +hook.<friendly-name>.event:: + The hook events that trigger `hook.<friendly-name>`. The value is the + name of a hook event, like "pre-commit" or "update". (See + linkgit:githooks[5] for a complete list of hook events.) On the + specified event, the associated `hook.<friendly-name>.command` is executed. + This is a multi-valued key. To run `hook.<friendly-name>` on multiple + events, specify the key more than once. An empty value resets + the list of events, clearing any previously defined events for + `hook.<friendly-name>`. See linkgit:git-hook[1]. + +hook.<friendly-name>.enabled:: + Whether the hook `hook.<friendly-name>` is enabled. Defaults to `true`. + Set to `false` to disable the hook without removing its + configuration. This is particularly useful when a hook is defined + in a system or global config file and needs to be disabled for a + specific repository. See linkgit:git-hook[1].
diff --git a/Documentation/config/http.adoc b/Documentation/config/http.adoc index 9da5c29..849c89f 100644 --- a/Documentation/config/http.adoc +++ b/Documentation/config/http.adoc
@@ -315,6 +315,32 @@ unset, curl's default value is used. Can be overridden by the `GIT_HTTP_KEEPALIVE_COUNT` environment variable. +http.retryAfter:: + Default wait time in seconds before retrying when a server returns + HTTP 429 (Too Many Requests) without a Retry-After header. + Defaults to 0 (retry immediately). When a Retry-After header is + present, its value takes precedence over this setting; however, + automatic use of the server-provided `Retry-After` header requires + libcurl 7.66.0 or later. On older versions, configure this setting + manually to control the retry delay. Can be overridden by the + `GIT_HTTP_RETRY_AFTER` environment variable. + See also `http.maxRetries` and `http.maxRetryTime`. + +http.maxRetries:: + Maximum number of times to retry after receiving HTTP 429 (Too Many + Requests) responses. Set to 0 (the default) to disable retries. + Can be overridden by the `GIT_HTTP_MAX_RETRIES` environment variable. + See also `http.retryAfter` and `http.maxRetryTime`. + +http.maxRetryTime:: + Maximum time in seconds to wait for a single retry attempt when + handling HTTP 429 (Too Many Requests) responses. If the server + requests a delay (via Retry-After header) or if `http.retryAfter` + is configured with a value that exceeds this maximum, Git will fail + immediately rather than waiting. Default is 300 seconds (5 minutes). + Can be overridden by the `GIT_HTTP_MAX_RETRY_TIME` environment + variable. See also `http.retryAfter` and `http.maxRetries`. + http.noEPSV:: A boolean which disables using of EPSV ftp command by curl. This can be helpful with some "poor" ftp servers which don't
diff --git a/Documentation/config/init.adoc b/Documentation/config/init.adoc index e45b2a8..7b4abda 100644 --- a/Documentation/config/init.adoc +++ b/Documentation/config/init.adoc
@@ -18,3 +18,9 @@ See `--ref-format=` in linkgit:git-init[1]. Both the command line option and the `GIT_DEFAULT_REF_FORMAT` environment variable take precedence over this config. + +init.defaultSubmodulePathConfig:: + A boolean that specifies if `git init` and `git clone` should + automatically set `extensions.submodulePathConfig` to `true`. This + allows all new repositories to automatically use the submodule path + extension. Defaults to `false` when unset.
diff --git a/Documentation/config/maintenance.adoc b/Documentation/config/maintenance.adoc index d0c38f0..b578856 100644 --- a/Documentation/config/maintenance.adoc +++ b/Documentation/config/maintenance.adoc
@@ -30,8 +30,7 @@ + * `none`: This strategy implies no tasks are run at all. This is the default strategy for scheduled maintenance. -* `gc`: This strategy runs the `gc` task. This is the default strategy for - manual maintenance. +* `gc`: This strategy runs the `gc` task. * `geometric`: This strategy performs geometric repacking of packfiles and keeps auxiliary data structures up-to-date. The strategy expires data in the reflog and removes worktrees that cannot be located anymore. When the @@ -40,7 +39,8 @@ are already part of a cruft pack will be expired. + This repacking strategy is a full replacement for the `gc` strategy and is -recommended for large repositories. +recommended for large repositories. This is the default strategy for manual +maintenance. * `incremental`: This setting optimizes for performing small maintenance activities that do not delete any data. This does not schedule the `gc` task, but runs the `prefetch` and `commit-graph` tasks hourly, the
diff --git a/Documentation/config/pack.adoc b/Documentation/config/pack.adoc index 75402d5..fa997c8 100644 --- a/Documentation/config/pack.adoc +++ b/Documentation/config/pack.adoc
@@ -160,12 +160,13 @@ processes. See linkgit:git-pack-objects[1] for full details. pack.preferBitmapTips:: + Specifies a ref hierarchy (e.g., "refs/heads/"); can be + given multiple times to specify more than one hierarchies. When selecting which commits will receive bitmaps, prefer a - commit at the tip of any reference that is a suffix of any value - of this configuration over any other commits in the "selection - window". + commit at the tip of a reference that is contained in any of + the configured hierarchies. + -Note that setting this configuration to `refs/foo` does not mean that +Note that setting this configuration to `refs/foo/` does not mean that the commits at the tips of `refs/foo/bar` and `refs/foo/baz` will necessarily be selected. This is because commits are selected for bitmaps from within a series of windows of variable length.
diff --git a/Documentation/config/promisor.adoc b/Documentation/config/promisor.adoc index 93e5e0d..b0fa43b 100644 --- a/Documentation/config/promisor.adoc +++ b/Documentation/config/promisor.adoc
@@ -89,3 +89,36 @@ `promisor.acceptFromServer` config variable is not set to "None". If set to "None", this config variable has no effect. See linkgit:gitprotocol-v2[5]. + +promisor.storeFields:: + A comma or space separated list of additional remote related + field names. If a client accepts an advertised remote, the + client will store the values associated with these field names + taken from the remote advertisement into its configuration, + and then reload its remote configuration. Currently, + "partialCloneFilter" and "token" are the only supported field + names. ++ +For example if a server advertises "partialCloneFilter=blob:limit=20k" +for remote "foo", and that remote is accepted, then "blob:limit=20k" +will be stored for the "remote.foo.partialCloneFilter" configuration +variable. ++ +If the new field value from an advertised remote is the same as the +existing field value for that remote on the client side, then no +change is made to the client configuration though. ++ +When a new value is stored, a message is printed to standard error to +let users know about this. ++ +Note that for security reasons, if the remote is not already +configured on the client side, nothing will be stored for that +remote. In any case, no new remote will be created and no URL will be +stored. ++ +Before storing a partial clone filter, it's parsed to check it's +valid. If it's not, a warning is emitted and it's not stored. ++ +Before storing a token, a check is performed to ensure it contains no +control character. If the check fails, a warning is emitted and it's +not stored.
diff --git a/Documentation/config/push.adoc b/Documentation/config/push.adoc index 0acbbea..d9112b2 100644 --- a/Documentation/config/push.adoc +++ b/Documentation/config/push.adoc
@@ -1,15 +1,15 @@ -push.autoSetupRemote:: - If set to "true" assume `--set-upstream` on default push when no +`push.autoSetupRemote`:: + If set to `true` assume `--set-upstream` on default push when no upstream tracking exists for the current branch; this option - takes effect with push.default options 'simple', 'upstream', - and 'current'. It is useful if by default you want new branches + takes effect with `push.default` options `simple`, `upstream`, + and `current`. It is useful if by default you want new branches to be pushed to the default remote (like the behavior of - 'push.default=current') and you also want the upstream tracking + `push.default=current`) and you also want the upstream tracking to be set. Workflows most likely to benefit from this option are - 'simple' central workflows where all branches are expected to + `simple` central workflows where all branches are expected to have the same name on the remote. -push.default:: +`push.default`:: Defines the action `git push` should take if no refspec is given (whether from the command-line, config, or elsewhere). Different values are well-suited for @@ -18,24 +18,28 @@ `upstream` is probably what you want. Possible values are: + -- +`nothing`;; +do not push anything (error out) unless a refspec is +given. This is primarily meant for people who want to +avoid mistakes by always being explicit. -* `nothing` - do not push anything (error out) unless a refspec is - given. This is primarily meant for people who want to - avoid mistakes by always being explicit. +`current`;; +push the current branch to update a branch with the same +name on the receiving end. Works in both central and non-central +workflows. -* `current` - push the current branch to update a branch with the same - name on the receiving end. Works in both central and non-central - workflows. +`upstream`;; +push the current branch back to the branch whose +changes are usually integrated into the current branch (which is +called `@{upstream}`). This mode only makes sense if you are +pushing to the same repository you would normally pull from +(i.e. central workflow). -* `upstream` - push the current branch back to the branch whose - changes are usually integrated into the current branch (which is - called `@{upstream}`). This mode only makes sense if you are - pushing to the same repository you would normally pull from - (i.e. central workflow). +`tracking`;; +this is a deprecated synonym for `upstream`. -* `tracking` - This is a deprecated synonym for `upstream`. - -* `simple` - push the current branch with the same name on the remote. +`simple`;; +push the current branch with the same name on the remote. + If you are working on a centralized workflow (pushing to the same repository you pull from, which is typically `origin`), then you need to configure an upstream @@ -44,16 +48,17 @@ This mode is the default since Git 2.0, and is the safest option suited for beginners. -* `matching` - push all branches having the same name on both ends. - This makes the repository you are pushing to remember the set of - branches that will be pushed out (e.g. if you always push 'maint' - and 'master' there and no other branches, the repository you push - to will have these two branches, and your local 'maint' and - 'master' will be pushed there). +`matching`;; +push all branches having the same name on both ends. +This makes the repository you are pushing to remember the set of +branches that will be pushed out (e.g. if you always push `maint` +and `master` there and no other branches, the repository you push +to will have these two branches, and your local `maint` and +`master` will be pushed there). + To use this mode effectively, you have to make sure _all_ the branches you would push out are ready to be pushed out before -running 'git push', as the whole point of this mode is to allow you +running `git push`, as the whole point of this mode is to allow you to push all of the branches in one go. If you usually finish work on only one branch and push out the result, while other branches are unfinished, this mode is not for you. Also this mode is not @@ -66,24 +71,24 @@ -- -push.followTags:: +`push.followTags`:: If set to true, enable `--follow-tags` option by default. You may override this configuration at time of push by specifying `--no-follow-tags`. -push.gpgSign:: - May be set to a boolean value, or the string 'if-asked'. A true +`push.gpgSign`:: + May be set to a boolean value, or the string `if-asked`. A true value causes all pushes to be GPG signed, as if `--signed` is - passed to linkgit:git-push[1]. The string 'if-asked' causes + passed to linkgit:git-push[1]. The string `if-asked` causes pushes to be signed if the server supports it, as if - `--signed=if-asked` is passed to 'git push'. A false value may + `--signed=if-asked` is passed to `git push`. A false value may override a value from a lower-priority config file. An explicit command-line flag always overrides this config option. -push.pushOption:: +`push.pushOption`:: When no `--push-option=<option>` argument is given from the - command line, `git push` behaves as if each <value> of - this variable is given as `--push-option=<value>`. + command line, `git push` behaves as if each _<option>_ of + this variable is given as `--push-option=<option>`. + This is a multi-valued variable, and an empty value can be used in a higher priority configuration file (e.g. `.git/config` in a @@ -109,26 +114,26 @@ ---- -push.recurseSubmodules:: - May be "check", "on-demand", "only", or "no", with the same behavior - as that of "push --recurse-submodules". - If not set, 'no' is used by default, unless 'submodule.recurse' is - set (in which case a 'true' value means 'on-demand'). +`push.recurseSubmodules`:: + May be `check`, `on-demand`, `only`, or `no`, with the same behavior + as that of `push --recurse-submodules`. + If not set, `no` is used by default, unless `submodule.recurse` is + set (in which case a `true` value means `on-demand`). -push.useForceIfIncludes:: - If set to "true", it is equivalent to specifying +`push.useForceIfIncludes`:: + If set to `true`, it is equivalent to specifying `--force-if-includes` as an option to linkgit:git-push[1] in the command line. Adding `--no-force-if-includes` at the time of push overrides this configuration setting. -push.negotiate:: - If set to "true", attempt to reduce the size of the packfile +`push.negotiate`:: + If set to `true`, attempt to reduce the size of the packfile sent by rounds of negotiation in which the client and the - server attempt to find commits in common. If "false", Git will + server attempt to find commits in common. If `false`, Git will rely solely on the server's ref advertisement to find commits in common. -push.useBitmaps:: - If set to "false", disable use of bitmaps for "git push" even if - `pack.useBitmaps` is "true", without preventing other git operations - from using bitmaps. Default is true. +`push.useBitmaps`:: + If set to `false`, disable use of bitmaps for `git push` even if + `pack.useBitmaps` is `true`, without preventing other git operations + from using bitmaps. Default is `true`.
diff --git a/Documentation/config/replay.adoc b/Documentation/config/replay.adoc new file mode 100644 index 0000000..7d549d2 --- /dev/null +++ b/Documentation/config/replay.adoc
@@ -0,0 +1,11 @@ +replay.refAction:: + Specifies the default mode for handling reference updates in + `git replay`. The value can be: ++ +-- + * `update`: Update refs directly using an atomic transaction (default behavior). + * `print`: Output update-ref commands for pipeline use. +-- ++ +This setting can be overridden with the `--ref-action` command-line option. +When not configured, `git replay` defaults to `update` mode.
diff --git a/Documentation/config/sendemail.adoc b/Documentation/config/sendemail.adoc index 90164c7..6560ecc 100644 --- a/Documentation/config/sendemail.adoc +++ b/Documentation/config/sendemail.adoc
@@ -12,6 +12,22 @@ Path to ca-certificates (either a directory or a single file). Set it to an empty string to disable certificate verification. +sendemail.smtpSSLClientCert:: + Path to the client certificate file to present if requested by the + server. This is required when the server is set up to verify client + certificates. If the corresponding private key is not included in the + file, it must be supplied using `sendemail.smtpSSLClientKey` or the + `--smtp-ssl-client-key` option. + +sendemail.smtpSSLClientKey:: + Path to the client private key file that corresponds to the client + certificate. To avoid misconfiguration, this configuration must be used + in conjunction with `sendemail.smtpSSLClientKey` or the + `--smtp-ssl-client-cert` option. If the client key is included in the + client certificate, the choice of private key depends on the format of + the certificate. Visit https://metacpan.org/pod/IO::Socket::SSL for more + details. + sendemail.<identity>.*:: Identity-specific versions of the `sendemail.*` parameters found below, taking precedence over those when this
diff --git a/Documentation/config/status.adoc b/Documentation/config/status.adoc index 8caf90f..b5dd85b 100644 --- a/Documentation/config/status.adoc +++ b/Documentation/config/status.adoc
@@ -17,6 +17,31 @@ `--no-ahead-behind` by default in linkgit:git-status[1] for non-porcelain status formats. Defaults to true. +status.compareBranches:: + A space-separated list of branch comparison specifiers to use in + linkgit:git-status[1]. Currently, only `@{upstream}` and `@{push}` + are supported. They are interpreted as `branch@{upstream}` and + `branch@{push}` for the current branch. ++ +If not set, the default behavior is equivalent to `@{upstream}`, which +compares against the configured upstream tracking branch. ++ +The entries are shown in the order they appear in the configuration. +Duplicate entries that resolve to the same ref are suppressed after +their first occurrence, so `@{push} @{upstream} @{push}` shows at +most two comparisons. When `@{upstream}` and `@{push}` resolve to +the same remote-tracking branch, only one comparison is shown. ++ +Example: ++ +---- +[status] + compareBranches = @{upstream} @{push} +---- ++ +This would show comparisons against both the configured upstream and push +tracking branches for the current branch. + status.displayCommentPrefix:: If set to true, linkgit:git-status[1] will insert a comment prefix before each output line (starting with
diff --git a/Documentation/config/submodule.adoc b/Documentation/config/submodule.adoc index 0672d99..8dacb85 100644 --- a/Documentation/config/submodule.adoc +++ b/Documentation/config/submodule.adoc
@@ -32,15 +32,16 @@ submodule.<name>.ignore:: Defines under what circumstances "git status" and the diff family show - a submodule as modified. When set to "all", it will never be considered - modified (but it will nonetheless show up in the output of status and - commit when it has been staged), "dirty" will ignore all changes - to the submodule's work tree and + a submodule as modified. + When set to "all" will never consider the submodule modified. It can + nevertheless be staged using the option --force and it will then show up + in the output of status. + When set to "dirty" will ignore all changes to the submodule's work tree and takes only differences between the HEAD of the submodule and the commit recorded in the superproject into account. "untracked" will additionally let submodules with modified tracked files in their work tree show up. - Using "none" (the default when this option is not set) also shows - submodules that have untracked files in their work tree as changed. + When set to "none"(default) It also show submodules as changed if they have + untracked files in their work tree. This setting overrides any setting made in .gitmodules for this submodule, both settings can be overridden on the command line by using the "--ignore-submodules" option. The 'git submodule' commands are not @@ -52,6 +53,13 @@ submodule.active config option. See linkgit:gitsubmodules[7] for details. +submodule.<name>.gitdir:: + This sets the gitdir path for submodule <name>. This configuration is + respected when `extensions.submodulePathConfig` is enabled, otherwise it + has no effect. When enabled, this config becomes the single source of + truth for submodule gitdir paths and Git will error if it is missing. + See linkgit:git-config[1] for details. + submodule.active:: A repeated field which contains a pathspec used to match against a submodule's path to determine if the submodule is of interest to git
diff --git a/Documentation/config/trailer.adoc b/Documentation/config/trailer.adoc index 60bc221..1bc7019 100644 --- a/Documentation/config/trailer.adoc +++ b/Documentation/config/trailer.adoc
@@ -1,21 +1,21 @@ -trailer.separators:: +`trailer.separators`:: This option tells which characters are recognized as trailer - separators. By default only ':' is recognized as a trailer - separator, except that '=' is always accepted on the command + separators. By default only `:` is recognized as a trailer + separator, except that `=` is always accepted on the command line for compatibility with other git commands. + The first character given by this option will be the default character used when another separator is not specified in the config for this trailer. + -For example, if the value for this option is "%=$", then only lines -using the format '<key><sep><value>' with <sep> containing '%', '=' -or '$' and then spaces will be considered trailers. And '%' will be +For example, if the value for this option is `%=$`, then only lines +using the format _<key><sep><value>_ with _<sep>_ containing `%`, `=` +or `$` and then spaces will be considered trailers. And `%` will be the default separator used, so by default trailers will appear like: -'<key>% <value>' (one percent sign and one space will appear between +`<key>% <value>` (one percent sign and one space will appear between the key and the value). -trailer.where:: +`trailer.where`:: This option tells where a new trailer will be added. + This can be `end`, which is the default, `start`, `after` or `before`. @@ -27,41 +27,41 @@ instead of the end, of the existing trailers. + If it is `after`, then each new trailer will appear just after the -last trailer with the same <key>. +last trailer with the same _<key>_. + If it is `before`, then each new trailer will appear just before the -first trailer with the same <key>. +first trailer with the same _<key>_. -trailer.ifexists:: +`trailer.ifexists`:: This option makes it possible to choose what action will be performed when there is already at least one trailer with the - same <key> in the input. + same _<key>_ in the input. + The valid values for this option are: `addIfDifferentNeighbor` (this is the default), `addIfDifferent`, `add`, `replace` or `doNothing`. + With `addIfDifferentNeighbor`, a new trailer will be added only if no -trailer with the same (<key>, <value>) pair is above or below the line +trailer with the same (_<key>_, _<value>_) pair is above or below the line where the new trailer will be added. + With `addIfDifferent`, a new trailer will be added only if no trailer -with the same (<key>, <value>) pair is already in the input. +with the same (_<key>_, _<value>_) pair is already in the input. + With `add`, a new trailer will be added, even if some trailers with -the same (<key>, <value>) pair are already in the input. +the same (_<key>_, _<value>_) pair are already in the input. + -With `replace`, an existing trailer with the same <key> will be +With `replace`, an existing trailer with the same _<key>_ will be deleted and the new trailer will be added. The deleted trailer will be -the closest one (with the same <key>) to the place where the new one +the closest one (with the same _<key>_) to the place where the new one will be added. + With `doNothing`, nothing will be done; that is no new trailer will be -added if there is already one with the same <key> in the input. +added if there is already one with the same _<key>_ in the input. -trailer.ifmissing:: +`trailer.ifmissing`:: This option makes it possible to choose what action will be performed when there is not yet any trailer with the same - <key> in the input. + _<key>_ in the input. + The valid values for this option are: `add` (this is the default) and `doNothing`. @@ -70,67 +70,68 @@ + With `doNothing`, nothing will be done. -trailer.<keyAlias>.key:: - Defines a <keyAlias> for the <key>. The <keyAlias> must be a - prefix (case does not matter) of the <key>. For example, in `git - config trailer.ack.key "Acked-by"` the "Acked-by" is the <key> and - the "ack" is the <keyAlias>. This configuration allows the shorter +`trailer.<key-alias>.key`:: + Defines a _<key-alias>_ for the _<key>_. The _<key-alias>_ must be a + prefix (case does not matter) of the _<key>_. For example, in `git + config trailer.ack.key "Acked-by"` the `Acked-by` is the _<key>_ and + the `ack` is the _<key-alias>_. This configuration allows the shorter `--trailer "ack:..."` invocation on the command line using the "ack" - <keyAlias> instead of the longer `--trailer "Acked-by:..."`. + `<key-alias>` instead of the longer `--trailer "Acked-by:..."`. + -At the end of the <key>, a separator can appear and then some -space characters. By default the only valid separator is ':', +At the end of the _<key>_, a separator can appear and then some +space characters. By default the only valid separator is `:`, but this can be changed using the `trailer.separators` config variable. + If there is a separator in the key, then it overrides the default separator when adding the trailer. -trailer.<keyAlias>.where:: - This option takes the same values as the 'trailer.where' +`trailer.<key-alias>.where`:: + This option takes the same values as the `trailer.where` configuration variable and it overrides what is specified by - that option for trailers with the specified <keyAlias>. + that option for trailers with the specified _<key-alias>_. -trailer.<keyAlias>.ifexists:: - This option takes the same values as the 'trailer.ifexists' +`trailer.<key-alias>.ifexists`:: + This option takes the same values as the `trailer.ifexists` configuration variable and it overrides what is specified by - that option for trailers with the specified <keyAlias>. + that option for trailers with the specified _<key-alias>_. -trailer.<keyAlias>.ifmissing:: - This option takes the same values as the 'trailer.ifmissing' +`trailer.<key-alias>.ifmissing`:: + This option takes the same values as the `trailer.ifmissing` configuration variable and it overrides what is specified by - that option for trailers with the specified <keyAlias>. + that option for trailers with the specified _<key-alias>_. -trailer.<keyAlias>.command:: - Deprecated in favor of 'trailer.<keyAlias>.cmd'. - This option behaves in the same way as 'trailer.<keyAlias>.cmd', except +`trailer.<key-alias>.command`:: + Deprecated in favor of `trailer.<key-alias>.cmd`. + This option behaves in the same way as `trailer.<key-alias>.cmd`, except that it doesn't pass anything as argument to the specified command. - Instead the first occurrence of substring $ARG is replaced by the - <value> that would be passed as argument. + Instead the first occurrence of substring `$ARG` is replaced by the + _<value>_ that would be passed as argument. + -Note that $ARG in the user's command is -only replaced once and that the original way of replacing $ARG is not safe. +Note that `$ARG` in the user's command is +only replaced once and that the original way of replacing `$ARG` is not safe. + -When both 'trailer.<keyAlias>.cmd' and 'trailer.<keyAlias>.command' are given -for the same <keyAlias>, 'trailer.<keyAlias>.cmd' is used and -'trailer.<keyAlias>.command' is ignored. +When both `trailer.<key-alias>.cmd` and `trailer.<key-alias>.command` are given +for the same _<key-alias>_, `trailer.<key-alias>.cmd` is used and +`trailer.<key-alias>.command` is ignored. -trailer.<keyAlias>.cmd:: +`trailer.<key-alias>.cmd`:: This option can be used to specify a shell command that will be called - once to automatically add a trailer with the specified <keyAlias>, and then - called each time a '--trailer <keyAlias>=<value>' argument is specified to - modify the <value> of the trailer that this option would produce. + once to automatically add a trailer with the specified _<key-alias>_, and then + called each time a `--trailer <key-alias>=<value>` argument is specified to + modify the _<value>_ of the trailer that this option would produce. + When the specified command is first called to add a trailer -with the specified <keyAlias>, the behavior is as if a special -'--trailer <keyAlias>=<value>' argument was added at the beginning -of the "git interpret-trailers" command, where <value> -is taken to be the standard output of the command with any -leading and trailing whitespace trimmed off. +with the specified _<key-alias>_, the behavior is as if a special +`--trailer <key-alias>=<value>` argument was added at the beginning +of linkgit:git-interpret-trailers[1], where _<value>_ is taken to be the +standard output of the command with any leading and trailing whitespace +trimmed off. + -If some '--trailer <keyAlias>=<value>' arguments are also passed +If some `--trailer <key-alias>=<value>` arguments are also passed on the command line, the command is called again once for each -of these arguments with the same <keyAlias>. And the <value> part +of these arguments with the same _<key-alias>_. And the _<value>_ part of these arguments, if any, will be passed to the command as its -first argument. This way the command can produce a <value> computed -from the <value> passed in the '--trailer <keyAlias>=<value>' argument. +first argument. This way the command can produce a _<value>_ computed +from the _<value>_ passed in the `--trailer <key-alias>=<value>` +argument.
diff --git a/Documentation/diff-algorithm-option.adoc b/Documentation/diff-algorithm-option.adoc new file mode 100644 index 0000000..8e3a0b6 --- /dev/null +++ b/Documentation/diff-algorithm-option.adoc
@@ -0,0 +1,20 @@ +`--diff-algorithm=(patience|minimal|histogram|myers)`:: + Choose a diff algorithm. The variants are as follows: ++ +-- + `default`;; + `myers`;; + The basic greedy diff algorithm. Currently, this is the default. + `minimal`;; + Spend extra time to make sure the smallest possible diff is + produced. + `patience`;; + Use "patience diff" algorithm when generating patches. + `histogram`;; + This algorithm extends the patience algorithm to "support + low-occurrence common elements". +-- ++ +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.
diff --git a/Documentation/diff-context-options.adoc b/Documentation/diff-context-options.adoc index e161260..b9ace2a 100644 --- a/Documentation/diff-context-options.adoc +++ b/Documentation/diff-context-options.adoc
@@ -1,7 +1,9 @@ `-U<n>`:: `--unified=<n>`:: - Generate diffs with _<n>_ lines of context. Defaults to `diff.context` - or 3 if the config option is unset. + Generate diffs with _<n>_ lines of context. The number of context + lines defaults to `diff.context` or 3 if the configuration variable + is unset. (`-U` without `<n>` is silently accepted as a synonym for + `-p` due to a historical accident). `--inter-hunk-context=<n>`:: Show the context between diff hunks, up to the specified _<number>_
diff --git a/Documentation/diff-options.adoc b/Documentation/diff-options.adoc index ae31520..8a63b5e 100644 --- a/Documentation/diff-options.adoc +++ b/Documentation/diff-options.adoc
@@ -127,8 +127,10 @@ `-U<n>`:: `--unified=<n>`:: - Generate diffs with _<n>_ lines of context instead of - the usual three. + Generate diffs with _<n>_ lines of context. The number of context + lines defaults to `diff.context` or 3 if the configuration variable + is unset. (`-U` without `<n>` is silently accepted as a synonym for + `-p` due to a historical accident). ifndef::git-format-patch[] Implies `--patch`. endif::git-format-patch[] @@ -197,26 +199,7 @@ appearing as a deletion or addition in the output. It uses the "patience diff" algorithm internally. -`--diff-algorithm=(patience|minimal|histogram|myers)`:: - Choose a diff algorithm. The variants are as follows: -+ --- - `default`;; - `myers`;; - The basic greedy diff algorithm. Currently, this is the default. - `minimal`;; - Spend extra time to make sure the smallest possible diff is - produced. - `patience`;; - Use "patience diff" algorithm when generating patches. - `histogram`;; - This algorithm extends the patience algorithm to "support - low-occurrence common elements". --- -+ -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. +include::diff-algorithm-option.adoc[] `--stat[=<width>[,<name-width>[,<count>]]]`:: Generate a diffstat. By default, as much space as necessary @@ -878,10 +861,18 @@ Do not show any source or destination prefix. `--default-prefix`:: +ifdef::git-format-patch[] + Use the default source and destination prefixes ("a/" and "b/"). + This overrides configuration variables such as `format.noprefix`, + `diff.srcPrefix`, `diff.dstPrefix`, and `diff.mnemonicPrefix` + (see linkgit:git-config[1]). +endif::git-format-patch[] +ifndef::git-format-patch[] Use the default source and destination prefixes ("a/" and "b/"). This overrides configuration variables such as `diff.noprefix`, `diff.srcPrefix`, `diff.dstPrefix`, and `diff.mnemonicPrefix` (see linkgit:git-config[1]). +endif::git-format-patch[] `--line-prefix=<prefix>`:: Prepend an additional _<prefix>_ to every line of output.
diff --git a/Documentation/fetch-options.adoc b/Documentation/fetch-options.adoc index ad1e1f4..81a9d7f 100644 --- a/Documentation/fetch-options.adoc +++ b/Documentation/fetch-options.adoc
@@ -1,41 +1,41 @@ ---all:: ---no-all:: +`--all`:: +`--no-all`:: Fetch all remotes, except for the ones that has the `remote.<name>.skipFetchAll` configuration variable set. This overrides the configuration variable `fetch.all`. --a:: ---append:: +`-a`:: +`--append`:: Append ref names and object names of fetched refs to the existing contents of `.git/FETCH_HEAD`. Without this option old data in `.git/FETCH_HEAD` will be overwritten. ---atomic:: +`--atomic`:: Use an atomic transaction to update local refs. Either all refs are updated, or on error, no refs are updated. ---depth=<depth>:: +`--depth=<depth>`:: Limit fetching to the specified number of commits from the tip of each remote branch history. If fetching to a 'shallow' repository created by `git clone` with `--depth=<depth>` option (see linkgit:git-clone[1]), deepen or shorten the history to the specified number of commits. Tags for the deepened commits are not fetched. ---deepen=<depth>:: - Similar to --depth, except it specifies the number of commits +`--deepen=<depth>`:: + Similar to `--depth`, except it specifies the number of commits from the current shallow boundary instead of from the tip of each remote branch history. ---shallow-since=<date>:: +`--shallow-since=<date>`:: Deepen or shorten the history of a shallow repository to - include all reachable commits after <date>. + include all reachable commits after _<date>_. ---shallow-exclude=<ref>:: +`--shallow-exclude=<ref>`:: Deepen or shorten the history of a shallow repository to exclude commits reachable from a specified remote branch or tag. This option can be specified multiple times. ---unshallow:: +`--unshallow`:: If the source repository is complete, convert a shallow repository to a complete one, removing all the limitations imposed by shallow repositories. @@ -43,13 +43,13 @@ If the source repository is shallow, fetch as much as possible so that the current repository has the same history as the source repository. ---update-shallow:: +`--update-shallow`:: By default when fetching from a shallow repository, `git fetch` refuses refs that require updating - .git/shallow. This option updates .git/shallow and accepts such + `.git/shallow`. This option updates `.git/shallow` and accepts such refs. ---negotiation-tip=<commit|glob>:: +`--negotiation-tip=(<commit>|<glob>)`:: By default, Git will report, to the server, commits reachable from all local refs to find common commits in an attempt to reduce the size of the to-be-received packfile. If specified, @@ -69,28 +69,47 @@ configuration variables documented in linkgit:git-config[1], and the `--negotiate-only` option below. ---negotiate-only:: +`--negotiate-only`:: Do not fetch anything from the server, and instead print the - ancestors of the provided `--negotiation-tip=*` arguments, + ancestors of the provided `--negotiation-tip=` arguments, which we have in common with the server. + -This is incompatible with `--recurse-submodules=[yes|on-demand]`. +This is incompatible with `--recurse-submodules=(yes|on-demand)`. Internally this is used to implement the `push.negotiate` option, see linkgit:git-config[1]. ---dry-run:: +`--dry-run`:: Show what would be done, without making any changes. ---porcelain:: +`--porcelain`:: Print the output to standard output in an easy-to-parse format for scripts. See section OUTPUT in linkgit:git-fetch[1] for details. + -This is incompatible with `--recurse-submodules=[yes|on-demand]` and takes +This is incompatible with `--recurse-submodules=(yes|on-demand)` and takes precedence over the `fetch.output` config option. +`--filter=<filter-spec>`:: + Use the partial clone feature and request that the server sends + a subset of reachable objects according to a given object filter. + When using `--filter`, the supplied _<filter-spec>_ is used for + the partial fetch. ++ +If `--filter=auto` is used, the filter specification is determined +automatically by combining the filter specifications advertised by +the server for the promisor remotes that the client accepts (see +linkgit:gitprotocol-v2[5] and the `promisor.acceptFromServer` +configuration option in linkgit:git-config[1]). ++ +For details on all other available filter specifications, see the +`--filter=<filter-spec>` option in linkgit:git-rev-list[1]. ++ +For example, `--filter=blob:none` will filter out all blobs (file +contents) until needed by Git. Also, `--filter=blob:limit=<size>` will +filter out all blobs of size at least _<size>_. + ifndef::git-pull[] ---write-fetch-head:: ---no-write-fetch-head:: +`--write-fetch-head`:: +`--no-write-fetch-head`:: Write the list of remote refs fetched in the `FETCH_HEAD` file directly under `$GIT_DIR`. This is the default. Passing `--no-write-fetch-head` from the command line tells @@ -98,64 +117,65 @@ file is never written. endif::git-pull[] --f:: ---force:: - When 'git fetch' is used with `<src>:<dst>` refspec, it may - refuse to update the local branch as discussed +`-f`:: +`--force`:: ifdef::git-pull[] - in the `<refspec>` part of the linkgit:git-fetch[1] - documentation. +When `git fetch` is used with `<src>:<dst>` refspec, it may +refuse to update the local branch as discussed +in the _<refspec>_ part of the linkgit:git-fetch[1] +documentation. endif::git-pull[] ifndef::git-pull[] - in the `<refspec>` part below. +When `git fetch` is used with `<src>:<dst>` refspec, it may +refuse to update the local branch as discussed in the _<refspec>_ part below. endif::git-pull[] - This option overrides that check. +This option overrides that check. --k:: ---keep:: +`-k`:: +`--keep`:: Keep downloaded pack. ifndef::git-pull[] ---multiple:: - Allow several <repository> and <group> arguments to be - specified. No <refspec>s may be specified. +`--multiple`:: + Allow several _<repository>_ and _<group>_ arguments to be + specified. No __<refspec>__s may be specified. ---auto-maintenance:: ---no-auto-maintenance:: ---auto-gc:: ---no-auto-gc:: +`--auto-maintenance`:: +`--no-auto-maintenance`:: +`--auto-gc`:: +`--no-auto-gc`:: Run `git maintenance run --auto` at the end to perform automatic - repository maintenance if needed. (`--[no-]auto-gc` is a synonym.) + repository maintenance if needed. This is enabled by default. ---write-commit-graph:: ---no-write-commit-graph:: +`--write-commit-graph`:: +`--no-write-commit-graph`:: Write a commit-graph after fetching. This overrides the config setting `fetch.writeCommitGraph`. endif::git-pull[] ---prefetch:: +`--prefetch`:: Modify the configured refspec to place all refs into the `refs/prefetch/` namespace. See the `prefetch` task in linkgit:git-maintenance[1]. --p:: ---prune:: +`-p`:: +`--prune`:: Before fetching, remove any remote-tracking references that no longer exist on the remote. Tags are not subject to pruning if they are fetched only because of the default tag - auto-following or due to a --tags option. However, if tags + auto-following or due to a `--tags` option. However, if tags are fetched due to an explicit refspec (either on the command line or in the remote configuration, for example if the remote - was cloned with the --mirror option), then they are also + was cloned with the `--mirror` option), then they are also subject to pruning. Supplying `--prune-tags` is a shorthand for providing the tag refspec. ifndef::git-pull[] + See the PRUNING section below for more details. --P:: ---prune-tags:: +`-P`:: +`--prune-tags`:: Before fetching, remove any local tags that no longer exist on the remote if `--prune` is enabled. This option should be used more carefully, unlike `--prune` it will remove any local @@ -168,17 +188,17 @@ endif::git-pull[] ifndef::git-pull[] --n:: +`-n`:: endif::git-pull[] ---no-tags:: +`--no-tags`:: By default, tags that point at objects that are downloaded from the remote repository are fetched and stored locally. This option disables this automatic tag following. The default - behavior for a remote may be specified with the remote.<name>.tagOpt + behavior for a remote may be specified with the `remote.<name>.tagOpt` setting. See linkgit:git-config[1]. ifndef::git-pull[] ---refetch:: +`--refetch`:: Instead of negotiating with the server to avoid transferring commits and associated objects that are already present locally, this option fetches all objects as a fresh clone would. Use this to reapply a partial clone @@ -187,29 +207,29 @@ object database pack consolidation to remove any duplicate objects. endif::git-pull[] ---refmap=<refspec>:: +`--refmap=<refspec>`:: When fetching refs listed on the command line, use the specified refspec (can be given more than once) to map the refs to remote-tracking branches, instead of the values of - `remote.*.fetch` configuration variables for the remote - repository. Providing an empty `<refspec>` to the + `remote.<name>.fetch` configuration variables for the remote + repository. Providing an empty _<refspec>_ to the `--refmap` option causes Git to ignore the configured refspecs and rely entirely on the refspecs supplied as command-line arguments. See section on "Configured Remote-tracking Branches" for details. --t:: ---tags:: +`-t`:: +`--tags`:: Fetch all tags from the remote (i.e., fetch remote tags `refs/tags/*` into local tags with the same name), in addition to whatever else would otherwise be fetched. Using this - option alone does not subject tags to pruning, even if --prune + option alone does not subject tags to pruning, even if `--prune` is used (though tags may be pruned anyway if they are also the destination of an explicit refspec; see `--prune`). ifndef::git-pull[] ---recurse-submodules[=(yes|on-demand|no)]:: - This option controls if and under what conditions new commits of +`--recurse-submodules[=(yes|on-demand|no)]`:: + Control if and under what conditions new commits of submodules should be fetched too. When recursing through submodules, `git fetch` always attempts to fetch "changed" submodules, that is, a submodule that has commits that are referenced by a newly fetched @@ -219,19 +239,21 @@ adds a new submodule, that submodule cannot be fetched until it is cloned e.g. by `git submodule update`. + -When set to 'on-demand', only changed submodules are fetched. When set -to 'yes', all populated submodules are fetched and submodules that are -both unpopulated and changed are fetched. When set to 'no', submodules +When set to `on-demand`, only changed submodules are fetched. When set +to `yes`, all populated submodules are fetched and submodules that are +both unpopulated and changed are fetched. When set to `no`, submodules are never fetched. + When unspecified, this uses the value of `fetch.recurseSubmodules` if it -is set (see linkgit:git-config[1]), defaulting to 'on-demand' if unset. -When this option is used without any value, it defaults to 'yes'. +is set (see linkgit:git-config[1]), defaulting to `on-demand` if unset. +When this option is used without any value, it defaults to `yes`. endif::git-pull[] --j:: ---jobs=<n>:: - Number of parallel children to be used for all forms of fetching. +`-j <n>`:: +`--jobs=<n>`:: + Parallelize all forms of fetching up to _<n>_ jobs at a time. ++ +A value of 0 will use some reasonable default. + If the `--multiple` option was specified, the different remotes will be fetched in parallel. If multiple submodules are fetched, they will be fetched in @@ -242,12 +264,12 @@ default fetches are performed sequentially, not in parallel. ifndef::git-pull[] ---no-recurse-submodules:: +`--no-recurse-submodules`:: Disable recursive fetching of submodules (this has the same effect as using the `--recurse-submodules=no` option). endif::git-pull[] ---set-upstream:: +`--set-upstream`:: If the remote is fetched successfully, add upstream (tracking) reference, used by argument-less linkgit:git-pull[1] and other commands. For more information, @@ -255,57 +277,57 @@ linkgit:git-config[1]. ifndef::git-pull[] ---submodule-prefix=<path>:: - Prepend <path> to paths printed in informative messages +`--submodule-prefix=<path>`:: + Prepend _<path>_ to paths printed in informative messages such as "Fetching submodule foo". This option is used internally when recursing over submodules. ---recurse-submodules-default=[yes|on-demand]:: +`--recurse-submodules-default=(yes|on-demand)`:: This option is used internally to temporarily provide a - non-negative default value for the --recurse-submodules + non-negative default value for the `--recurse-submodules` option. All other methods of configuring fetch's submodule recursion (such as settings in linkgit:gitmodules[5] and linkgit:git-config[1]) override this option, as does - specifying --[no-]recurse-submodules directly. + specifying `--[no-]recurse-submodules` directly. --u:: ---update-head-ok:: - By default 'git fetch' refuses to update the head which +`-u`:: +`--update-head-ok`:: + By default `git fetch` refuses to update the head which corresponds to the current branch. This flag disables the - check. This is purely for the internal use for 'git pull' - to communicate with 'git fetch', and unless you are + check. This is purely for the internal use for `git pull` + to communicate with `git fetch`, and unless you are implementing your own Porcelain you are not supposed to use it. endif::git-pull[] ---upload-pack <upload-pack>:: +`--upload-pack <upload-pack>`:: When given, and the repository to fetch from is handled - by 'git fetch-pack', `--exec=<upload-pack>` is passed to + by `git fetch-pack`, `--exec=<upload-pack>` is passed to the command to specify non-default path for the command run on the other end. ifndef::git-pull[] --q:: ---quiet:: - Pass --quiet to git-fetch-pack and silence any other internally +`-q`:: +`--quiet`:: + Pass `--quiet` to `git-fetch-pack` and silence any other internally used git commands. Progress is not reported to the standard error stream. --v:: ---verbose:: +`-v`:: +`--verbose`:: Be verbose. endif::git-pull[] ---progress:: +`--progress`:: Progress status is reported on the standard error stream - by default when it is attached to a terminal, unless -q + by default when it is attached to a terminal, unless `-q` is specified. This flag forces progress status even if the standard error stream is not directed to a terminal. --o <option>:: ---server-option=<option>:: +`-o <option>`:: +`--server-option=<option>`:: Transmit the given string to the server when communicating using - protocol version 2. The given string must not contain a NUL or LF + protocol version 2. The given string must not contain a _NUL_ or _LF_ character. The server's handling of server options, including unknown ones, is server-specific. When multiple `--server-option=<option>` are given, they are all @@ -314,23 +336,23 @@ the values of configuration variable `remote.<name>.serverOption` are used instead. ---show-forced-updates:: +`--show-forced-updates`:: By default, git checks if a branch is force-updated during - fetch. This can be disabled through fetch.showForcedUpdates, but - the --show-forced-updates option guarantees this check occurs. + fetch. This can be disabled through `fetch.showForcedUpdates`, but + the `--show-forced-updates` option guarantees this check occurs. See linkgit:git-config[1]. ---no-show-forced-updates:: +`--no-show-forced-updates`:: By default, git checks if a branch is force-updated during - fetch. Pass --no-show-forced-updates or set fetch.showForcedUpdates + fetch. Pass `--no-show-forced-updates` or set `fetch.showForcedUpdates` to false to skip this check for performance reasons. If used during - 'git-pull' the --ff-only option will still check for forced updates + `git-pull` the `--ff-only` option will still check for forced updates before attempting a fast-forward update. See linkgit:git-config[1]. --4:: ---ipv4:: +`-4`:: +`--ipv4`:: Use IPv4 addresses only, ignoring IPv6 addresses. --6:: ---ipv6:: +`-6`:: +`--ipv6`:: Use IPv6 addresses only, ignoring IPv4 addresses.
diff --git a/Documentation/for-each-ref-options.adoc b/Documentation/for-each-ref-options.adoc index f13efb5..54e2fa9 100644 --- a/Documentation/for-each-ref-options.adoc +++ b/Documentation/for-each-ref-options.adoc
@@ -30,8 +30,8 @@ `--color[=<when>]`:: Respect any colors specified in the `--format` option. The - _<when__ field must be one of `always`, `never`, or `auto` (if - `<when>` is absent, behave as if `always` was given). + _<when>_ field must be one of `always`, `never`, or `auto` (if + _<when>_ is absent, behave as if `always` was given). `--shell`:: `--perl`::
diff --git a/Documentation/format-patch-caveats.adoc b/Documentation/format-patch-caveats.adoc new file mode 100644 index 0000000..807a65b --- /dev/null +++ b/Documentation/format-patch-caveats.adoc
@@ -0,0 +1,33 @@ +The output from linkgit:git-format-patch[1] can lead to a different +commit message when applied with linkgit:git-am[1]. The patch that is +applied may also be different from the one that was generated, or patch +application may fail outright. +ifdef::git-am[] +See the <<discussion,DISCUSSION>> section above for the syntactic rules. +endif::git-am[] + +ifndef::git-am[] +include::format-patch-end-of-commit-message.adoc[] +endif::git-am[] + +Note that this is especially problematic for unindented diffs that occur +in the commit message; the diff in the commit message might get applied +along with the patch section, or the patch application machinery might +trip up because the patch target doesn't apply. This could for example +be caused by a diff in a Markdown code block. + +The solution for this is to indent the diff or other text that could +cause problems. + +This loss of fidelity might be simple to notice if you are applying +patches directly from a mailbox. However, changes originating from Git +could be applied in bulk, in which case this would be much harder to +notice. This could for example be a Linux distribution which uses patch +files to apply changes on top of the commits from the upstream +repositories. This goes to show that this behavior does not only impact +email workflows. + +Given these limitations, one might be tempted to use a general-purpose +utility like patch(1) instead. However, patch(1) will not only look for +unindented diffs (like linkgit:git-am[1]) but will try to apply indented +diffs as well.
diff --git a/Documentation/format-patch-end-of-commit-message.adoc b/Documentation/format-patch-end-of-commit-message.adoc new file mode 100644 index 0000000..ec1ef79 --- /dev/null +++ b/Documentation/format-patch-end-of-commit-message.adoc
@@ -0,0 +1,8 @@ +Any line that is of the form: + +* three-dashes and end-of-line, or +* a line that begins with "diff -", or +* a line that begins with "Index: " + +is taken as the beginning of a patch, and the commit log message +is terminated before the first occurrence of such a line.
diff --git a/Documentation/fsck-msgids.adoc b/Documentation/fsck-msgids.adoc index acac968..6a4db3a 100644 --- a/Documentation/fsck-msgids.adoc +++ b/Documentation/fsck-msgids.adoc
@@ -13,6 +13,9 @@ `badGpgsig`:: (ERROR) A tag contains a bad (truncated) signature (e.g., `gpgsig`) header. +`badHeadTarget`:: + (ERROR) The `HEAD` ref is a symref that does not refer to a branch. + `badHeaderContinuation`:: (ERROR) A continuation header (such as for `gpgsig`) is unexpectedly truncated. @@ -41,6 +44,9 @@ `badRefName`:: (ERROR) A ref has an invalid format. +`badRefOid`:: + (ERROR) A ref points to an invalid object ID. + `badReferentName`:: (ERROR) The referent name of a symref is invalid.
diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc index 6192dae..941135d 100644 --- a/Documentation/git-add.adoc +++ b/Documentation/git-add.adoc
@@ -75,7 +75,10 @@ `-f`:: `--force`:: - Allow adding otherwise ignored files. + Allow adding otherwise ignored files. The option is also used when + `submodule.<name>.ignore=all` is set, but you want to stage an + update of the submodule. The `path` to the submodule must be explicitly + specified. `--sparse`:: Allow updating index entries outside of the sparse-checkout cone.
diff --git a/Documentation/git-am.adoc b/Documentation/git-am.adoc index b23b4fb..384e0cd 100644 --- a/Documentation/git-am.adoc +++ b/Documentation/git-am.adoc
@@ -9,7 +9,7 @@ SYNOPSIS -------- [verse] -'git am' [--signoff] [--keep] [--[no-]keep-cr] [--[no-]utf8] [--no-verify] +'git am' [--signoff] [--keep] [--[no-]keep-cr] [--[no-]utf8] [--[no-]verify] [--[no-]3way] [--interactive] [--committer-date-is-author-date] [--ignore-date] [--ignore-space-change | --ignore-whitespace] [--whitespace=<action>] [-C<n>] [-p<n>] [--directory=<dir>] @@ -37,20 +37,21 @@ -s:: --signoff:: - Add a `Signed-off-by` trailer to the commit message, using - the committer identity of yourself. - See the signoff option in linkgit:git-commit[1] for more information. + Add a `Signed-off-by` trailer to the commit message (see + linkgit:git-interpret-trailers[1]), using the committer identity + of yourself. See the signoff option in linkgit:git-commit[1] + for more information. -k:: --keep:: - Pass `-k` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]). + Pass `-k` flag to linkgit:git-mailinfo[1]. --keep-non-patch:: - Pass `-b` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]). + Pass `-b` flag to linkgit:git-mailinfo[1]. --keep-cr:: --no-keep-cr:: - With `--keep-cr`, call 'git mailsplit' (see linkgit:git-mailsplit[1]) + With `--keep-cr`, call linkgit:git-mailsplit[1] with the same option, to prevent it from stripping CR at the end of lines. `am.keepcr` configuration variable can be used to specify the default behaviour. `--no-keep-cr` is useful to override `am.keepcr`. @@ -65,7 +66,7 @@ Ignore scissors lines (see linkgit:git-mailinfo[1]). --quoted-cr=<action>:: - This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]). + This flag will be passed down to linkgit:git-mailinfo[1]. --empty=(drop|keep|stop):: How to handle an e-mail message lacking a patch: @@ -83,10 +84,11 @@ -m:: --message-id:: - Pass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]), - so that the Message-ID header is added to the commit message. - The `am.messageid` configuration variable can be used to specify - the default behaviour. + Pass the `-m` flag to linkgit:git-mailinfo[1], so that the + `Message-ID` header is added as a trailer (see + linkgit:git-interpret-trailers[1]). The `am.messageid` + configuration variable can be used to specify the default + behaviour. --no-message-id:: Do not add the Message-ID header to the commit message. @@ -98,7 +100,7 @@ -u:: --utf8:: - Pass `-u` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]). + Pass `-u` flag to linkgit:git-mailinfo[1]. The proposed commit log message taken from the e-mail is re-coded into UTF-8 encoding (configuration variable `i18n.commitEncoding` can be used to specify the project's @@ -108,8 +110,7 @@ default. You can use `--no-utf8` to override this. --no-utf8:: - Pass `-n` flag to 'git mailinfo' (see - linkgit:git-mailinfo[1]). + Pass `-n` flag to linkgit:git-mailinfo[1]. -3:: --3way:: @@ -132,9 +133,8 @@ --exclude=<path>:: --include=<path>:: --reject:: - These flags are passed to the 'git apply' (see linkgit:git-apply[1]) - program that applies - the patch. + These flags are passed to the linkgit:git-apply[1] program that + applies the patch. + Valid <action> for the `--whitespace` option are: `nowarn`, `warn`, `fix`, `error`, and `error-all`. @@ -150,11 +150,14 @@ --interactive:: Run interactively. +--verify:: -n:: --no-verify:: - By default, the pre-applypatch and applypatch-msg hooks are run. - When any of `--no-verify` or `-n` is given, these are bypassed. - See also linkgit:githooks[5]. + Run the `pre-applypatch` and `applypatch-msg` hooks. This is the + default. Skip these hooks with `-n` or `--no-verify`. See also + linkgit:githooks[5]. ++ +Note that `post-applypatch` cannot be skipped. --committer-date-is-author-date:: By default the command records the date from the e-mail @@ -162,6 +165,13 @@ commit creation as the committer date. This allows the user to lie about the committer date by using the same value as the author date. ++ +WARNING: The history walking machinery assumes that commits have +non-decreasing commit timestamps. You should consider if you really need +to use this option. Then you should only use this option to override the +committer date when applying commits on top of a base which commit is +older (in terms of the commit date) than the oldest patch you are +applying. --ignore-date:: By default the command records the date from the e-mail @@ -198,7 +208,8 @@ to the screen before exiting. This overrides the standard message informing you to use `--continue` or `--skip` to handle the failure. This is solely - for internal use between 'git rebase' and 'git am'. + for internal use between linkgit:git-rebase[1] and + linkgit:git-am[1]. --abort:: Restore the original branch and abort the patching operation. @@ -216,7 +227,7 @@ failure again. --show-current-patch[=(diff|raw)]:: - Show the message at which `git am` has stopped due to + Show the message at which linkgit:git-am[1] has stopped due to conflicts. If `raw` is specified, show the raw contents of the e-mail message; if `diff`, show the diff portion only. Defaults to `raw`. @@ -226,6 +237,7 @@ create an empty commit with the contents of the e-mail message as its log message. +[[discussion]] DISCUSSION ---------- @@ -245,16 +257,13 @@ line is automatically stripped. The patch is expected to be inline, directly following the -message. Any line that is of the form: +message. +include::format-patch-end-of-commit-message.adoc[] -* three-dashes and end-of-line, or -* a line that begins with "diff -", or -* a line that begins with "Index: " +This means that the contents of the commit message can inadvertently +interrupt the processing (see the <<caveats,CAVEATS>> section below). -is taken as the beginning of a patch, and the commit log message -is terminated before the first occurrence of such a line. - -When initially invoking `git am`, you give it the names of the mailboxes +When initially invoking linkgit:git-am[1], you give it the names of the mailboxes to process. Upon seeing the first patch that does not apply, it aborts in the middle. You can recover from this in one of two ways: @@ -272,16 +281,25 @@ Before any patches are applied, ORIG_HEAD is set to the tip of the current branch. This is useful if you have problems with multiple -commits, like running 'git am' on the wrong branch or an error in the -commits that is more easily fixed by changing the mailbox (e.g. +commits, like running linkgit:git-am[1] on the wrong branch or an error +in the commits that is more easily fixed by changing the mailbox (e.g. errors in the "From:" lines). +[[caveats]] +CAVEATS +------- + +:git-am: 1 +include::format-patch-caveats.adoc[] + HOOKS ----- This command can run `applypatch-msg`, `pre-applypatch`, and `post-applypatch` hooks. See linkgit:githooks[5] for more information. +See the `--verify`/`-n`/`--no-verify` options. + CONFIGURATION -------------
diff --git a/Documentation/git-backfill.adoc b/Documentation/git-backfill.adoc index b8394dc..246ab41 100644 --- a/Documentation/git-backfill.adoc +++ b/Documentation/git-backfill.adoc
@@ -63,9 +63,12 @@ current sparse-checkout. If the sparse-checkout feature is enabled, then `--sparse` is assumed and can be disabled with `--no-sparse`. +You may also specify the commit limiting options from linkgit:git-rev-list[1]. + SEE ALSO -------- -linkgit:git-clone[1]. +linkgit:git-clone[1], +linkgit:git-rev-list[1] GIT ---
diff --git a/Documentation/git-blame.adoc b/Documentation/git-blame.adoc index e438d28..8808009 100644 --- a/Documentation/git-blame.adoc +++ b/Documentation/git-blame.adoc
@@ -7,12 +7,12 @@ SYNOPSIS -------- -[verse] -'git blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental] - [-L <range>] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>] - [--ignore-rev <rev>] [--ignore-revs-file <file>] - [--color-lines] [--color-by-age] [--progress] [--abbrev=<n>] - [ --contents <file> ] [<rev> | --reverse <rev>..<rev>] [--] <file> +[synopsis] +git blame [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-e] [-p] [-w] [--incremental] + [-L <range>] [-S <revs-file>] [-M] [-C] [-C] [-C] [--since=<date>] + [--ignore-rev <rev>] [--ignore-revs-file <file>] + [--color-lines] [--color-by-age] [--progress] [--abbrev=<n>] + [ --contents <file> ] [<rev> | --reverse <rev>..<rev>] [--] <file> DESCRIPTION ----------- @@ -30,7 +30,7 @@ `-C` and `-M` options. The report does not tell you anything about lines which have been deleted or -replaced; you need to use a tool such as 'git diff' or the "pickaxe" +replaced; you need to use a tool such as `git diff` or the "pickaxe" interface briefly mentioned in the following paragraph. Apart from supporting file annotation, Git also supports searching the @@ -50,45 +50,47 @@ ------- include::blame-options.adoc[] --c:: +`-c`:: Use the same output mode as linkgit:git-annotate[1] (Default: off). ---score-debug:: +`--score-debug`:: Include debugging information related to the movement of lines between files (see `-C`) and lines moved within a file (see `-M`). The first number listed is the score. This is the number of alphanumeric characters detected as having been moved between or within files. This must be above - a certain threshold for 'git blame' to consider those lines + a certain threshold for `git blame` to consider those lines of code to have been moved. --f:: ---show-name:: +`-f`:: +`--show-name`:: Show the filename in the original commit. By default the filename is shown if there is any line that came from a file with a different name, due to rename detection. --n:: ---show-number:: +`-n`:: +`--show-number`:: Show the line number in the original commit (Default: off). --s:: +`-s`:: Suppress the author name and timestamp from the output. --e:: ---show-email:: +`-e`:: +`--show-email`:: Show the author email instead of the author name (Default: off). This can also be controlled via the `blame.showEmail` config option. --w:: +`-w`:: Ignore whitespace when comparing the parent's version and the child's to find where the lines came from. ---abbrev=<n>:: - Instead of using the default 7+1 hexadecimal digits as the - abbreviated object name, use <m>+1 digits, where <m> is at - least <n> but ensures the commit object names are unique. +include::diff-algorithm-option.adoc[] + +`--abbrev=<n>`:: + Instead of using the default _7+1_ hexadecimal digits as the + abbreviated object name, use _<m>+1_ digits, where _<m>_ is at + least _<n>_ but ensures the commit object names are unique. Note that 1 column is used for a caret to mark the boundary commit. @@ -122,21 +124,21 @@ This header line is followed by the following information at least once for each commit: -- the author name ("author"), email ("author-mail"), time - ("author-time"), and time zone ("author-tz"); similarly +- the author name (`author`), email (`author-mail`), time + (`author-time`), and time zone (`author-tz`); similarly for committer. - the filename in the commit that the line is attributed to. -- the first line of the commit log message ("summary"). +- the first line of the commit log message (`summary`). The contents of the actual line are output after the above -header, prefixed by a TAB. This is to allow adding more +header, prefixed by a _TAB_. This is to allow adding more header elements later. The porcelain format generally suppresses commit information that has already been seen. For example, two lines that are blamed to the same commit will both be shown, but the details for that commit will be shown only once. Information which is specific to individual lines will not be -grouped together, like revs to be marked 'ignored' or 'unblamable'. This +grouped together, like revs to be marked `ignored` or `unblamable`. This is more efficient, but may require more state be kept by the reader. The `--line-porcelain` option can be used to output full commit information for each line, allowing simpler (but less efficient) usage like: @@ -150,7 +152,7 @@ SPECIFYING RANGES ----------------- -Unlike 'git blame' and 'git annotate' in older versions of git, the extent +Unlike `git blame` and `git annotate` in older versions of git, the extent of the annotation can be limited to both line ranges and revision ranges. The `-L` option, which limits annotation to a range of lines, may be specified multiple times. @@ -171,7 +173,7 @@ When you are not interested in changes older than version v2.6.18, or changes older than 3 weeks, you can use revision -range specifiers similar to 'git rev-list': +range specifiers similar to `git rev-list`: git blame v2.6.18.. -- foo git blame --since=3.weeks -- foo @@ -210,8 +212,9 @@ annotated. . Each blame entry always starts with a line of: - - <40-byte-hex-sha1> <sourceline> <resultline> <num-lines> ++ +[synopsis] +<40-byte-hex-sha1> <sourceline> <resultline> <num-lines> + Line numbers count from 1. @@ -222,16 +225,17 @@ . Unlike the Porcelain format, the filename information is always given and terminates the entry: - - "filename" <whitespace-quoted-filename-goes-here> ++ +[synopsis] +filename <whitespace-quoted-filename-goes-here> + and thus it is really quite easy to parse for some line- and word-oriented parser (which should be quite natural for most scripting languages). + [NOTE] For people who do parsing: to make it more robust, just ignore any -lines between the first and last one ("<sha1>" and "filename" lines) -where you do not recognize the tag words (or care about that particular +lines between the first and last one (_<40-byte-hex-sha1>_ and `filename` +lines) where you do not recognize the tag words (or care about that particular one) at the beginning of the "extended information" lines. That way, if there is ever added information (like the commit encoding or extended commit commentary), a blame viewer will not care.
diff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc index 6f281b2..43ccf47 100644 --- a/Documentation/git-checkout.adoc +++ b/Documentation/git-checkout.adoc
@@ -509,7 +509,7 @@ ----------------------- When you run `git checkout <something>`, Git tries to guess whether -`<something>` is intended to be a branch, a commit, or a set of file(s), +_<something>_ is intended to be a branch, a commit, or a set of file(s), and then either switches to that branch or commit, or restores the specified files.
diff --git a/Documentation/git-clone.adoc b/Documentation/git-clone.adoc index 57cdfb7..b6e1f8a 100644 --- a/Documentation/git-clone.adoc +++ b/Documentation/git-clone.adoc
@@ -84,7 +84,7 @@ with the source repository. The resulting repository starts out without any object of its own. + -*NOTE*: this is a possibly dangerous operation; do *not* use +NOTE: this is a possibly dangerous operation; do *not* use it unless you understand what it does. If you clone your repository using this option and then delete branches (or use any other Git command that makes any existing commit unreferenced) in the @@ -104,7 +104,8 @@ its source repository, you can simply run `git repack -a` to copy all objects from the source repository into a pack in the cloned repository. -`--reference[-if-able] <repository>`:: +`--reference=<repository>`:: +`--reference-if-able=<repository>`:: If the reference _<repository>_ is on the local machine, automatically setup `.git/objects/info/alternates` to obtain objects from the reference _<repository>_. Using @@ -115,7 +116,7 @@ directory is skipped with a warning instead of aborting the clone. + -*NOTE*: see the NOTE for the `--shared` option, and also the +NOTE: see the NOTE for the `--shared` option, and also the `--dissociate` option. `--dissociate`:: @@ -140,27 +141,28 @@ to the standard error stream. `--progress`:: - Progress status is reported on the standard error stream - by default when it is attached to a terminal, unless `--quiet` + Report progress status on the standard error stream + by default when attached to a terminal, unless `--quiet` is specified. This flag forces progress status even if the standard error stream is not directed to a terminal. `--server-option=<option>`:: Transmit the given string to the server when communicating using - protocol version 2. The given string must not contain a NUL or LF + protocol version 2. The given string must not contain a _NUL_ or _LF_ character. The server's handling of server options, including unknown ones, is server-specific. When multiple `--server-option=<option>` are given, they are all sent to the other side in the order listed on the command line. - When no ++--server-option=++__<option>__ is given from the command + When no `--server-option=<option>` is given from the command line, the values of configuration variable `remote.<name>.serverOption` are used instead. `-n`:: `--no-checkout`:: - No checkout of `HEAD` is performed after the clone is complete. + Do not checkout `HEAD` after the clone is complete. -`--`[`no-`]`reject-shallow`:: +`--no-reject-shallow`:: +`--reject-shallow`:: Fail if the source repository is a shallow repository. The `clone.rejectShallow` configuration variable can be used to specify the default. @@ -187,11 +189,26 @@ Use the partial clone feature and request that the server sends a subset of reachable objects according to a given object filter. When using `--filter`, the supplied _<filter-spec>_ is used for - the partial clone filter. For example, `--filter=blob:none` will - filter out all blobs (file contents) until needed by Git. Also, - `--filter=blob:limit=<size>` will filter out all blobs of size - at least _<size>_. For more details on filter specifications, see - the `--filter` option in linkgit:git-rev-list[1]. + the partial clone filter. ++ +If `--filter=auto` is used the filter specification is determined +automatically through the 'promisor-remote' protocol (see +linkgit:gitprotocol-v2[5]) by combining the filter specifications +advertised by the server for the promisor remotes that the client +accepts (see the `promisor.acceptFromServer` configuration option in +linkgit:git-config[1]). This allows the server to suggest the optimal +filter for the available promisor remotes. ++ +As with other filter specifications, the "auto" value is persisted in +the configuration. This ensures that future fetches will continue to +adapt to the server's current recommendation. ++ +For details on all other available filter specifications, see the +`--filter=<filter-spec>` option in linkgit:git-rev-list[1]. ++ +For example, `--filter=blob:none` will filter out all blobs (file +contents) until needed by Git. Also, `--filter=blob:limit=<size>` will +filter out all blobs of size at least _<size>_. `--also-filter-submodules`:: Also apply the partial clone filter to any submodules in the repository. @@ -206,18 +223,17 @@ that all these refs are overwritten by a `git remote update` in the target repository. -`-o` _<name>_:: -`--origin` _<name>_:: +`-o<name>`:: +`--origin=<name>`:: Instead of using the remote name `origin` to keep track of the upstream repository, use _<name>_. Overrides `clone.defaultRemoteName` from the config. -`-b` _<name>_:: -`--branch` _<name>_:: - Instead of pointing the newly created `HEAD` to the branch pointed - to by the cloned repository's `HEAD`, point to _<name>_ branch - instead. In a non-bare repository, this is the branch that will - be checked out. +`-b<name>`:: +`--branch=<name>`:: + Point the newly created `HEAD` to _<name>_ branch instead of the branch + pointed to by the cloned repository's `HEAD`. In a non-bare repository, + this is the branch that will be checked out. `--branch` can also take tags and detaches the `HEAD` at that commit in the resulting repository. @@ -230,18 +246,17 @@ name. This option is incompatible with `--branch` and `--mirror`. -`-u` _<upload-pack>_:: -`--upload-pack` _<upload-pack>_:: - When given, and the repository to clone from is accessed - via ssh, this specifies a non-default path for the command - run on the other end. +`-u<upload-pack>`:: +`--upload-pack=<upload-pack>`:: + Specify a non-default path for the command run on the other end when the + repository to clone from is accessed via ssh. `--template=<template-directory>`:: Specify the directory from which templates will be used; (See the "TEMPLATE DIRECTORY" section of linkgit:git-init[1].) -`-c` `<key>=<value>`:: -`--config` `<key>=<value>`:: +`-c<key>=<value>`:: +`--config=<key>=<value>`:: Set a configuration variable in the newly-created repository; this takes effect immediately after the repository is initialized, but before the remote history is fetched or any @@ -257,7 +272,7 @@ `remote.<name>.mirror` and `remote.<name>.tagOpt`. Use the corresponding `--mirror` and `--no-tags` options instead. -`--depth <depth>`:: +`--depth=<depth>`:: Create a 'shallow' clone with a history truncated to the specified number of commits. Implies `--single-branch` unless `--no-single-branch` is given to fetch the histories near the @@ -339,8 +354,8 @@ + include::ref-storage-format.adoc[] -`-j` _<n>_:: -`--jobs` _<n>_:: +`-j<n>`:: +`--jobs=<n>`:: The number of submodules fetched at the same time. Defaults to the `submodule.fetchJobs` option.
diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc index 54c207a..8329c10 100644 --- a/Documentation/git-commit.adoc +++ b/Documentation/git-commit.adoc
@@ -146,7 +146,8 @@ linkgit:git-status[1] for details. Implies `--dry-run`. `--branch`:: - Show the branch and tracking info even in short-format. + Show the branch and tracking info even in short-format. See + linkgit:git-status[1] for details. `--porcelain`:: When doing a dry-run, give the output in a porcelain-ready @@ -154,12 +155,13 @@ `--dry-run`. `--long`:: - When doing a dry-run, give the output in the long-format. - Implies `--dry-run`. + When doing a dry-run, give the output in the long-format. This + is the default output of linkgit:git-status[1]. Implies + `--dry-run`. `-z`:: `--null`:: - When showing `short` or `porcelain` status output, print the + When showing `short` or `porcelain` linkgit:git-status[1] output, print the filename verbatim and terminate the entries with _NUL_, instead of _LF_. If no format is given, implies the `--porcelain` output format. Without the `-z` option, filenames with "unusual" characters are
diff --git a/Documentation/git-config.adoc b/Documentation/git-config.adoc index cc054fa..00545b2 100644 --- a/Documentation/git-config.adoc +++ b/Documentation/git-config.adoc
@@ -221,7 +221,7 @@ + Valid `<type>`'s include: + -- 'bool': canonicalize values `true`, `yes`,`on`, and positive +- 'bool': canonicalize values `true`, `yes`, `on`, and positive numbers as "true", and values `false`, `no`, `off` and `0` as "false". - 'int': canonicalize values as simple decimal numbers. An optional suffix of @@ -240,6 +240,9 @@ that the given value is canonicalize-able as an ANSI color, but it is written as-is. + +If the command is in `list` mode, then the `--type <type>` argument will apply +to each listed config value. If the value does not successfully parse in that +format, then it will be omitted from the list. --bool:: --int:: @@ -332,7 +335,7 @@ Replaced by `git config get --all --show-names --regexp <name-regexp>`. --get-urlmatch <name> <URL>:: - Replaced by `git config get --all --show-names --url=<URL> <name>`. + Replaced by `git config get --url=<URL> <name>`. --get-color <name> [<default>]:: Replaced by `git config get --type=color [--default=<default>] <name>`.
diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc index b74179a..b3f42d4 100644 --- a/Documentation/git-fast-import.adoc +++ b/Documentation/git-fast-import.adoc
@@ -66,15 +66,30 @@ remote-helpers that use the `import` capability, as they are already trusted to run their own code. ---signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort):: - Specify how to handle signed tags. Behaves in the same way - as the same option in linkgit:git-fast-export[1], except that - default is 'verbatim' (instead of 'abort'). +`--signed-tags=(verbatim|warn-verbatim|warn-strip|strip|abort)`:: + Specify how to handle signed tags. Behaves in the same way as + the `--signed-commits=<mode>` below, except that the + `strip-if-invalid` mode is not yet supported. Like for signed + commits, the default mode is `verbatim`. ---signed-commits=(verbatim|warn-verbatim|warn-strip|strip|abort):: - Specify how to handle signed commits. Behaves in the same way - as the same option in linkgit:git-fast-export[1], except that - default is 'verbatim' (instead of 'abort'). +`--signed-commits=<mode>`:: + Specify how to handle signed commits. The following <mode>s + are supported: ++ +* `verbatim`, which is the default, will silently import commit + signatures. +* `warn-verbatim` will import them, but will display a warning. +* `abort` will make this program die when encountering a signed + commit. +* `strip` will silently make the commits unsigned. +* `warn-strip` will make them unsigned, but will display a warning. +* `strip-if-invalid` will check signatures and, if they are invalid, + will strip them and display a warning. The validation is performed + in the same way as linkgit:git-verify-commit[1] does it. +* `sign-if-invalid[=<keyid>]`, similar to `strip-if-invalid`, verifies + commit signatures and replaces invalid signatures with newly created ones. + Valid signatures are left unchanged. If `<keyid>` is provided, that key is + used for signing; otherwise the configured default signing key is used. Options for Frontends ~~~~~~~~~~~~~~~~~~~~~
diff --git a/Documentation/git-fetch.adoc b/Documentation/git-fetch.adoc index 16f5d9d..db03541 100644 --- a/Documentation/git-fetch.adoc +++ b/Documentation/git-fetch.adoc
@@ -8,11 +8,11 @@ SYNOPSIS -------- -[verse] -'git fetch' [<options>] [<repository> [<refspec>...]] -'git fetch' [<options>] <group> -'git fetch' --multiple [<options>] [(<repository> | <group>)...] -'git fetch' --all [<options>] +[synopsis] +git fetch [<options>] [<repository> [<refspec>...]] +git fetch [<options>] <group> +git fetch --multiple [<options>] [(<repository>|<group>)...] +git fetch --all [<options>] DESCRIPTION @@ -20,19 +20,19 @@ Fetch branches and/or tags (collectively, "refs") from one or more other repositories, along with the objects necessary to complete their histories. Remote-tracking branches are updated (see the description -of <refspec> below for ways to control this behavior). +of _<refspec>_ below for ways to control this behavior). By default, any tag that points into the histories being fetched is also fetched; the effect is to fetch tags that point at branches that you are interested in. This default behavior -can be changed by using the --tags or --no-tags options or by -configuring remote.<name>.tagOpt. By using a refspec that fetches tags +can be changed by using the `--tags` or `--no-tags` options or by +configuring `remote.<name>.tagOpt`. By using a refspec that fetches tags explicitly, you can fetch tags that do not point into branches you are interested in as well. -'git fetch' can fetch from either a single named repository or URL, -or from several repositories at once if <group> is given and -there is a remotes.<group> entry in the configuration file. +`git fetch` can fetch from either a single named repository or URL, +or from several repositories at once if _<group>_ is given and +there is a `remotes.<group>` entry in the configuration file. (See linkgit:git-config[1]). When no remote is specified, by default the `origin` remote will be used, @@ -48,15 +48,15 @@ include::pull-fetch-param.adoc[] ---stdin:: +`--stdin`:: Read refspecs, one per line, from stdin in addition to those provided - as arguments. The "tag <name>" format is not supported. + as arguments. The "tag _<name>_" format is not supported. include::urls-remotes.adoc[] - -CONFIGURED REMOTE-TRACKING BRANCHES[[CRTB]] -------------------------------------------- +[[CRTB]] +CONFIGURED REMOTE-TRACKING BRANCHES +----------------------------------- You often interact with the same remote repository by regularly and repeatedly fetching from it. In order to keep track @@ -84,13 +84,13 @@ * When `git fetch` is run with explicit branches and/or tags to fetch on the command line, e.g. `git fetch origin master`, the - <refspec>s given on the command line determine what are to be + _<refspec>s_ given on the command line determine what are to be fetched (e.g. `master` in the example, which is a short-hand for `master:`, which in turn means - "fetch the 'master' branch but I do not explicitly say what + "fetch the `master` branch but I do not explicitly say what remote-tracking branch to update with it from the command line"), and the example command will - fetch _only_ the 'master' branch. The `remote.<repository>.fetch` + fetch _only_ the `master` branch. The `remote.<repository>.fetch` values determine which remote-tracking branch, if any, is updated. When used in this way, the `remote.<repository>.fetch` values do not have any @@ -144,9 +144,9 @@ exist on the remote. This might not be what you expect, i.e. you want to prune remote -`<name>`, but also explicitly fetch tags from it, so when you fetch +_<name>_, but also explicitly fetch tags from it, so when you fetch from it you delete all your local tags, most of which may not have -come from the `<name>` remote in the first place. +come from the _<name>_ remote in the first place. So be careful when using this with a refspec like `refs/tags/*:refs/tags/*`, or any other refspec which might map @@ -213,11 +213,11 @@ <flag> <old-object-id> <new-object-id> <local-reference> ------------------------------- -The status of up-to-date refs is shown only if the --verbose option is +The status of up-to-date refs is shown only if the `--verbose` option is used. In compact output mode, specified with configuration variable -fetch.output, if either entire `<from>` or `<to>` is found in the +fetch.output, if either entire _<from>_ or _<to>_ is found in the other string, it will be substituted with `*` in the other string. For example, `master -> origin/master` becomes `master -> origin/*`. @@ -303,7 +303,7 @@ BUGS ---- -Using --recurse-submodules can only fetch new commits in submodules that are +Using `--recurse-submodules` can only fetch new commits in submodules that are present locally e.g. in `$GIT_DIR/modules/`. If the upstream adds a new submodule, that submodule cannot be fetched until it is cloned e.g. by `git submodule update`. This is expected to be fixed in a future Git version.
diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc index 9a7807c..5662382 100644 --- a/Documentation/git-format-patch.adoc +++ b/Documentation/git-format-patch.adoc
@@ -24,6 +24,7 @@ [(--reroll-count|-v) <n>] [--to=<email>] [--cc=<email>] [--[no-]cover-letter] [--quiet] + [--commit-list-format=<format-spec>] [--[no-]encode-email-headers] [--no-notes | --notes[=<ref>]] [--interdiff=<previous>] @@ -282,11 +283,12 @@ --from:: --from=<ident>:: - Use `ident` in the `From:` header of each commit email. If the - author ident of the commit is not textually identical to the - provided `ident`, place a `From:` header in the body of the - message with the original author. If no `ident` is given, use - the committer ident. + Use `ident` in the `From:` header of each email. In case of a + commit email, if the author ident of the commit is not textually + identical to the provided `ident`, place a `From:` header in the + body of the message with the original author. If no `ident` is + given, or if the option is not passed at all, use the ident of + the current committer. + Note that this option is only useful if you are actually sending the emails and want to identify yourself as the sender, but retain the @@ -317,9 +319,21 @@ --cover-letter:: --no-cover-letter:: - In addition to the patches, generate a cover letter file - containing the branch description, shortlog and the overall diffstat. You can - fill in a description in the file before sending it out. + In addition to the patches, generate a cover letter file containing the + branch description, commit list and the overall diffstat. You can fill + in a description in the file before sending it out. + +--commit-list-format=<format-spec>:: + Specify the format in which to generate the commit list of the patch + series. The accepted values for format-spec are `shortlog`, `modern` or + a format-string prefixed with `log:`. E.g. `log: %s (%an)`. + `modern` is the same as `log:%w(72)[%(count)/%(total)] %s`. + The `log:` prefix can be omitted if the format-string has a `%` in it + (expecting that it is part of `%<placeholder>`). + Defaults to the `format.commitListFormat` configuration variable, if + set, or `shortlog`. + This option given from the command-line implies the use of + `--cover-letter` unless `--no-cover-letter` is given. --encode-email-headers:: --no-encode-email-headers:: @@ -452,6 +466,7 @@ signOff = true outputDirectory = <directory> coverLetter = auto + commitListFormat = shortlog coverFromDescription = auto ------------ @@ -798,6 +813,10 @@ include enough information for the receiving end to reproduce the same merge commit. +=== PATCH APPLICATION + +include::format-patch-caveats.adoc[] + SEE ALSO -------- linkgit:git-am[1], linkgit:git-send-email[1]
diff --git a/Documentation/git-history.adoc b/Documentation/git-history.adoc new file mode 100644 index 0000000..24dc907 --- /dev/null +++ b/Documentation/git-history.adoc
@@ -0,0 +1,139 @@ +git-history(1) +============== + +NAME +---- +git-history - EXPERIMENTAL: Rewrite history + +SYNOPSIS +-------- +[synopsis] +git history reword <commit> [--dry-run] [--update-refs=(branches|head)] +git history split <commit> [--dry-run] [--update-refs=(branches|head)] [--] [<pathspec>...] + +DESCRIPTION +----------- + +Rewrite history by rearranging or modifying specific commits in the +history. + +THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE. + +This command is related to linkgit:git-rebase[1] in that both commands can be +used to rewrite history. There are a couple of major differences though: + +* linkgit:git-history[1] can work in a bare repository as it does not need to + touch either the index or the worktree. +* linkgit:git-history[1] does not execute any linkgit:githooks[5] at the + current point in time. This may change in the future. +* linkgit:git-history[1] by default updates all branches that are descendants + of the original commit to point to the rewritten commit. + +Overall, linkgit:git-history[1] aims to provide a more opinionated way to modify +your commit history that is simpler to use compared to linkgit:git-rebase[1] in +general. + +Use linkgit:git-rebase[1] if you want to reapply a range of commits onto a +different base, or interactive rebases if you want to edit a range of commits +at once. + +LIMITATIONS +----------- + +This command does not (yet) work with histories that contain merges. You +should use linkgit:git-rebase[1] with the `--rebase-merges` flag instead. + +Furthermore, the command does not support operations that can result in merge +conflicts. This limitation is by design as history rewrites are not intended to +be stateful operations. The limitation can be lifted once (if) Git learns about +first-class conflicts. + +COMMANDS +-------- + +The following commands are available to rewrite history in different ways: + +`reword <commit>`:: + Rewrite the commit message of the specified commit. All the other + details of this commit remain unchanged. This command will spawn an + editor with the current message of that commit. + +`split <commit> [--] [<pathspec>...]`:: + Interactively split up <commit> into two commits by choosing + hunks introduced by it that will be moved into the new split-out + commit. These hunks will then be written into a new commit that + becomes the parent of the previous commit. The original commit + stays intact, except that its parent will be the newly split-out + commit. ++ +The commit messages of the split-up commits will be asked for by launching +the configured editor. Authorship of the commit will be the same as for the +original commit. ++ +If passed, _<pathspec>_ can be used to limit which changes shall be split out +of the original commit. Files not matching any of the pathspecs will remain +part of the original commit. For more details, see the 'pathspec' entry in +linkgit:gitglossary[7]. ++ +It is invalid to select either all or no hunks, as that would lead to +one of the commits becoming empty. + +OPTIONS +------- + +`--dry-run`:: + Do not update any references, but instead print any ref updates in a + format that can be consumed by linkgit:git-update-ref[1]. Necessary new + objects will be written into the repository, so applying these printed + ref updates is generally safe. + +`--update-refs=(branches|head)`:: + Control which references will be updated by the command, if any. With + `branches`, all local branches that point to commits which are + descendants of the original commit will be rewritten. With `head`, only + the current `HEAD` reference will be rewritten. Defaults to `branches`. + +EXAMPLES +-------- + +Split a commit +~~~~~~~~~~~~~~ + +---------- +$ git log --stat --oneline +3f81232 (HEAD -> main) original + bar | 1 + + foo | 1 + + 2 files changed, 2 insertions(+) + +$ git history split HEAD +diff --git a/bar b/bar +new file mode 100644 +index 0000000..5716ca5 +--- /dev/null ++++ b/bar +@@ -0,0 +1 @@ ++bar +(1/1) Stage addition [y,n,q,a,d,p,?]? y + +diff --git a/foo b/foo +new file mode 100644 +index 0000000..257cc56 +--- /dev/null ++++ b/foo +@@ -0,0 +1 @@ ++foo +(1/1) Stage addition [y,n,q,a,d,p,?]? n + +$ git log --stat --oneline +7cebe64 (HEAD -> main) original + foo | 1 + + 1 file changed, 1 insertion(+) +d1582f3 split-out commit + bar | 1 + + 1 file changed, 1 insertion(+) +---------- + +GIT +--- +Part of the linkgit:git[1] suite
diff --git a/Documentation/git-hook.adoc b/Documentation/git-hook.adoc index f6cc72d..318c637 100644 --- a/Documentation/git-hook.adoc +++ b/Documentation/git-hook.adoc
@@ -8,7 +8,8 @@ SYNOPSIS -------- [verse] -'git hook' run [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>] +'git hook' run [--allow-unknown-hook-name] [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>] +'git hook' list [--allow-unknown-hook-name] [-z] [--show-scope] <hook-name> DESCRIPTION ----------- @@ -16,21 +17,117 @@ A command interface for running git hooks (see linkgit:githooks[5]), for use by other scripted git commands. +This command parses the default configuration files for sets of configs like +so: + + [hook "linter"] + event = pre-commit + command = ~/bin/linter --cpp20 + +In this example, `[hook "linter"]` represents one script - `~/bin/linter +--cpp20` - which can be shared by many repos, and even by many hook events, if +appropriate. + +To add an unrelated hook which runs on a different event, for example a +spell-checker for your commit messages, you would write a configuration like so: + + [hook "linter"] + event = pre-commit + command = ~/bin/linter --cpp20 + [hook "spellcheck"] + event = commit-msg + command = ~/bin/spellchecker + +With this config, when you run 'git commit', first `~/bin/linter --cpp20` will +have a chance to check your files to be committed (during the `pre-commit` hook +event`), and then `~/bin/spellchecker` will have a chance to check your commit +message (during the `commit-msg` hook event). + +Commands are run in the order Git encounters their associated +`hook.<friendly-name>.event` configs during the configuration parse (see +linkgit:git-config[1]). Although multiple `hook.linter.event` configs can be +added, only one `hook.linter.command` event is valid - Git uses "last-one-wins" +to determine which command to run. + +So if you wanted your linter to run when you commit as well as when you push, +you would configure it like so: + + [hook "linter"] + event = pre-commit + event = pre-push + command = ~/bin/linter --cpp20 + +With this config, `~/bin/linter --cpp20` would be run by Git before a commit is +generated (during `pre-commit`) as well as before a push is performed (during +`pre-push`). + +And if you wanted to run your linter as well as a secret-leak detector during +only the "pre-commit" hook event, you would configure it instead like so: + + [hook "linter"] + event = pre-commit + command = ~/bin/linter --cpp20 + [hook "no-leaks"] + event = pre-commit + command = ~/bin/leak-detector + +With this config, before a commit is generated (during `pre-commit`), Git would +first start `~/bin/linter --cpp20` and second start `~/bin/leak-detector`. It +would evaluate the output of each when deciding whether to proceed with the +commit. + +For a full list of hook events which you can set your `hook.<friendly-name>.event` to, +and how hooks are invoked during those events, see linkgit:githooks[5]. + +Git will ignore any `hook.<friendly-name>.event` that specifies an event it doesn't +recognize. This is intended so that tools which wrap Git can use the hook +infrastructure to run their own hooks; see "WRAPPERS" for more guidance. + +In general, when instructions suggest adding a script to +`.git/hooks/<hook-event>`, you can specify it in the config instead by running: + +---- +git config set hook.<some-name>.command <path-to-script> +git config set --append hook.<some-name>.event <hook-event> +---- + +This way you can share the script between multiple repos. That is, `cp +~/my-script.sh ~/project/.git/hooks/pre-commit` would become: + +---- +git config set hook.my-script.command ~/my-script.sh +git config set --append hook.my-script.event pre-commit +---- + SUBCOMMANDS ----------- run:: - Run the `<hook-name>` hook. See linkgit:githooks[5] for - supported hook names. + Runs hooks configured for `<hook-name>`, in the order they are + discovered during the config parse. The default `<hook-name>` from + the hookdir is run last. See linkgit:githooks[5] for supported + hook names. + Any positional arguments to the hook should be passed after a mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See linkgit:githooks[5] for arguments hooks might expect (if any). +list [-z] [--show-scope]:: + Print a list of hooks which will be run on `<hook-name>` event. If no + hooks are configured for that event, print a warning and return 1. + Use `-z` to terminate output lines with NUL instead of newlines. + OPTIONS ------- +--allow-unknown-hook-name:: + By default `git hook run` and `git hook list` will bail out when + `<hook-name>` is not a hook event known to Git (see linkgit:githooks[5] + for the list of known hooks). This is meant to help catch typos + such as `prereceive` when `pre-receive` was intended. Pass this + flag to allow unknown hook names. + --to-stdin:: For "run"; specify a file which will be streamed into the hook's stdin. The hook will receive the entire file from @@ -41,6 +138,55 @@ tools that want to do a blind one-shot run of a hook that may or may not be present. +-z:: + Terminate "list" output lines with NUL instead of newlines. + +--show-scope:: + For "list"; prefix each configured hook's friendly name with a + tab-separated config scope (e.g. `local`, `global`, `system`), + mirroring the output style of `git config --show-scope`. Traditional + hooks from the hookdir are unaffected. + +WRAPPERS +-------- + +`git hook run` has been designed to make it easy for tools which wrap Git to +configure and execute hooks using the Git hook infrastructure. It is possible to +provide arguments and stdin via the command line, as well as specifying parallel +or series execution if the user has provided multiple hooks. + +Assuming your wrapper wants to support a hook named "mywrapper-start-tests", you +can have your users specify their hooks like so: + + [hook "setup-test-dashboard"] + event = mywrapper-start-tests + command = ~/mywrapper/setup-dashboard.py --tap + +Then, in your 'mywrapper' tool, you can invoke any users' configured hooks by +running: + +---- +git hook run --allow-unknown-hook-name mywrapper-start-tests \ + # providing something to stdin + --stdin some-tempfile-123 \ + # execute hooks in serial + # plus some arguments of your own... + -- \ + --testname bar \ + baz +---- + +Take care to name your wrapper's hook events in a way which is unlikely to +overlap with Git's native hooks (see linkgit:githooks[5]) - a hook event named +`mywrappertool-validate-commit` is much less likely to be added to native Git +than a hook event named `validate-commit`. If Git begins to use a hook event +named the same thing as your wrapper hook, it may invoke your users' hooks in +unintended and unsupported ways. + +CONFIGURATION +------------- +include::config/hook.adoc[] + SEE ALSO -------- linkgit:githooks[5]
diff --git a/Documentation/git-interpret-trailers.adoc b/Documentation/git-interpret-trailers.adoc index fd335fe..77b4f63 100644 --- a/Documentation/git-interpret-trailers.adoc +++ b/Documentation/git-interpret-trailers.adoc
@@ -7,14 +7,14 @@ SYNOPSIS -------- -[verse] -'git interpret-trailers' [--in-place] [--trim-empty] +[synopsis] +git interpret-trailers [--in-place] [--trim-empty] [(--trailer (<key>|<key-alias>)[(=|:)<value>])...] [--parse] [<file>...] DESCRIPTION ----------- -Add or parse 'trailer' lines that look similar to RFC 822 e-mail +Add or parse _trailer_ lines that look similar to RFC 822 e-mail headers, at the end of the otherwise free-form part of a commit message. For example, in the following commit message @@ -27,23 +27,24 @@ Signed-off-by: Bob <bob@example.com> ------------------------------------------------ -the last two lines starting with "Signed-off-by" are trailers. +the last two lines starting with `Signed-off-by` are trailers. This command reads commit messages from either the -<file> arguments or the standard input if no <file> is specified. +_<file>_ arguments or the standard input if no _<file>_ is specified. If `--parse` is specified, the output consists of the parsed trailers coming from the input, without influencing them with any command line options or configuration variables. -Otherwise, this command applies `trailer.*` configuration variables -(which could potentially add new trailers, as well as reposition them), -as well as any command line arguments that can override configuration -variables (such as `--trailer=...` which could also add new trailers), -to each input file. The result is emitted on the standard output. +Otherwise, this command applies `trailer.<key-alias>` configuration +variables (which could potentially add new trailers, as well as +reposition them), as well as any command line arguments that can +override configuration variables (such as `--trailer=...` which could +also add new trailers), to each input file. The result is emitted on the +standard output. This command can also operate on the output of linkgit:git-format-patch[1], which is more elaborate than a plain commit message. Namely, such output -includes a commit message (as above), a "---" divider line, and a patch part. +includes a commit message (as above), a `---` divider line, and a patch part. For these inputs, the divider and patch parts are not modified by this command and are emitted as is on the output, unless `--no-divider` is specified. @@ -53,24 +54,24 @@ the input is changed. They also make it possible to automatically add some trailers. -By default, a '<key>=<value>' or '<key>:<value>' argument given +By default, a `<key>=<value>` or `<key>:<value>` argument given using `--trailer` will be appended after the existing trailers only if -the last trailer has a different (<key>, <value>) pair (or if there -is no existing trailer). The <key> and <value> parts will be trimmed +the last trailer has a different (_<key>_, _<value>_) pair (or if there +is no existing trailer). The _<key>_ and _<value>_ parts will be trimmed to remove starting and trailing whitespace, and the resulting trimmed -<key> and <value> will appear in the output like this: +_<key>_ and _<value>_ will appear in the output like this: ------------------------------------------------ key: value ------------------------------------------------ -This means that the trimmed <key> and <value> will be separated by -`': '` (one colon followed by one space). +This means that the trimmed _<key>_ and _<value>_ will be separated by +"`:`{nbsp}" (one colon followed by one space). -For convenience, a <key-alias> can be configured to make using `--trailer` +For convenience, a _<key-alias>_ can be configured to make using `--trailer` shorter to type on the command line. This can be configured using the -'trailer.<key-alias>.key' configuration variable. The <keyAlias> must be a prefix -of the full <key> string, although case sensitivity does not matter. For +`trailer.<key-alias>.key` configuration variable. The _<key-alias>_ must be a prefix +of the full _<key>_ string, although case sensitivity does not matter. For example, if you have ------------------------------------------------ @@ -91,13 +92,13 @@ least 25% trailers. The group must be preceded by one or more empty (or whitespace-only) lines. The group must either be at the end of the input or be the last -non-whitespace lines before a line that starts with '---' (followed by a +non-whitespace lines before a line that starts with `---` (followed by a space or the end of the line). When reading trailers, there can be no whitespace before or inside the -<key>, but any number of regular space and tab characters are allowed -between the <key> and the separator. There can be whitespaces before, -inside or after the <value>. The <value> may be split over multiple lines +_<key>_, but any number of regular space and tab characters are allowed +between the _<key>_ and the separator. There can be whitespaces before, +inside or after the _<value>_. The _<value>_ may be split over multiple lines with each subsequent line starting with at least one whitespace, like the "folding" in RFC 822. Example: @@ -111,77 +112,97 @@ OPTIONS ------- ---in-place:: - Edit the files in place. +`--in-place`:: +`--no-in-place`:: + Edit the files in place. The default is `--no-in-place`. ---trim-empty:: - If the <value> part of any trailer contains only whitespace, +`--trim-empty`:: +`--no-trim-empty`:: + If the _<value>_ part of any trailer contains only whitespace, the whole trailer will be removed from the output. This applies to existing trailers as well as new trailers. ++ +The default is `--no-trim-empty`. ---trailer <key>[(=|:)<value>]:: - Specify a (<key>, <value>) pair that should be applied as a - trailer to the inputs. See the description of this - command. +`--trailer=<key>[(=|:)<value>]`:: +`--no-trailer`:: + Specify a (_<key>_, _<value>_) pair that should be applied as a + trailer to the inputs. See the description of this command. Can + be given multiple times. ++ +Use `--no-trailer` to reset the list. ---where <placement>:: ---no-where:: +`--where=<placement>`:: +`--no-where`:: Specify where all new trailers will be added. A setting - provided with '--where' overrides the `trailer.where` and any - applicable `trailer.<keyAlias>.where` configuration variables - and applies to all '--trailer' options until the next occurrence of - '--where' or '--no-where'. Upon encountering '--no-where', clear the - effect of any previous use of '--where', such that the relevant configuration - variables are no longer overridden. Possible placements are `after`, + provided with `--where` overrides the `trailer.where` and any + applicable `trailer.<key-alias>.where` configuration variables + and applies to all `--trailer` options until the next occurrence of + `--where` or `--no-where`. Possible placements are `after`, `before`, `end` or `start`. ++ +Use `--no-where` to clear the effect of any previous use of `--where`, +such that the relevant configuration variables are no longer overridden. ---if-exists <action>:: ---no-if-exists:: +`--if-exists=<action>`:: +`--no-if-exists`:: Specify what action will be performed when there is already at - least one trailer with the same <key> in the input. A setting - provided with '--if-exists' overrides the `trailer.ifExists` and any - applicable `trailer.<keyAlias>.ifExists` configuration variables - and applies to all '--trailer' options until the next occurrence of - '--if-exists' or '--no-if-exists'. Upon encountering '--no-if-exists', clear the - effect of any previous use of '--if-exists', such that the relevant configuration - variables are no longer overridden. Possible actions are `addIfDifferent`, + least one trailer with the same _<key>_ in the input. A setting + provided with `--if-exists` overrides the `trailer.ifExists` and any + applicable `trailer.<key-alias>.ifExists` configuration variables + and applies to all `--trailer` options until the next occurrence of + `--if-exists` or `--no-if-exists`. Possible actions are `addIfDifferent`, `addIfDifferentNeighbor`, `add`, `replace` and `doNothing`. ++ +Use `--no-if-exists` to clear the effect of any previous use of +`--if-exists`, such that the relevant configuration variables are no +longer overridden. ---if-missing <action>:: ---no-if-missing:: +`--if-missing=<action>`:: +`--no-if-missing`:: Specify what action will be performed when there is no other - trailer with the same <key> in the input. A setting - provided with '--if-missing' overrides the `trailer.ifMissing` and any - applicable `trailer.<keyAlias>.ifMissing` configuration variables - and applies to all '--trailer' options until the next occurrence of - '--if-missing' or '--no-if-missing'. Upon encountering '--no-if-missing', - clear the effect of any previous use of '--if-missing', such that the relevant - configuration variables are no longer overridden. Possible actions are `doNothing` - or `add`. + trailer with the same _<key>_ in the input. A setting + provided with `--if-missing` overrides the `trailer.ifMissing` and any + applicable `trailer.<key-alias>.ifMissing` configuration variables + and applies to all `--trailer` options until the next occurrence of + `--if-missing` or `--no-if-missing`. Possible actions are + `doNothing` or `add`. ++ +Use `--no-if-missing` to clear the effect of any previous use of +`--if-missing`, such that the relevant configuration variables are no +longer overridden. ---only-trailers:: - Output only the trailers, not any other parts of the input. +`--only-trailers`:: +`--no-only-trailers`:: + Output only the trailers, not any other parts of the + input. The default is `--no-only-trailers`. ---only-input:: +`--only-input`:: +`--no-only-input`:: Output only trailers that exist in the input; do not add any - from the command-line or by applying `trailer.*` configuration - variables. + from the command-line or by applying `trailer.<key-alias>` configuration + variables. The default is `--no-only-input`. ---unfold:: +`--unfold`:: +`--no-unfold`:: If a trailer has a value that runs over multiple lines (aka "folded"), - reformat the value into a single line. + reformat the value into a single line. The default is `--no-unfold`. ---parse:: +`--parse`:: A convenience alias for `--only-trailers --only-input --unfold`. This makes it easier to only see the trailers coming from the input without influencing them with any command line options or configuration variables, while also making the output machine-friendly with - --unfold. + `--unfold`. ++ +There is no convenience alias to negate this alias. ---no-divider:: - Do not treat `---` as the end of the commit message. Use this - when you know your input contains just the commit message itself - (and not an email or the output of `git format-patch`). +`--divider`:: +`--no-divider`:: + Treat `---` as the end of the commit message. This is the default. + Use `--no-divider` when you know your input contains just the + commit message itself (and not an email or the output of + linkgit:git-format-patch[1]). CONFIGURATION VARIABLES ----------------------- @@ -193,7 +214,7 @@ EXAMPLES -------- -* Configure a 'sign' trailer with a 'Signed-off-by' key, and then +* Configure a `sign` trailer with a `Signed-off-by` key, and then add two of these trailers to a commit message file: + ------------ @@ -230,8 +251,8 @@ Acked-by: Alice <alice@example.com> ------------ -* Extract the last commit as a patch, and add a 'Cc' and a - 'Reviewed-by' trailer to it: +* Extract the last commit as a patch, and add a `Cc` and a + `Reviewed-by` trailer to it: + ------------ $ git format-patch -1 @@ -239,9 +260,9 @@ $ git interpret-trailers --trailer 'Cc: Alice <alice@example.com>' --trailer 'Reviewed-by: Bob <bob@example.com>' 0001-foo.patch >0001-bar.patch ------------ -* Configure a 'sign' trailer with a command to automatically add a - 'Signed-off-by: ' with the author information only if there is no - 'Signed-off-by: ' already, and show how it works: +* Configure a `sign` trailer with a command to automatically add a + "`Signed-off-by:`{nbsp}" with the author information only if there is no + "`Signed-off-by:`{nbsp}" already, and show how it works: + ------------ $ cat msg1.txt @@ -272,7 +293,7 @@ Signed-off-by: Alice <alice@example.com> ------------ -* Configure a 'fix' trailer with a key that contains a '#' and no +* Configure a `fix` trailer with a key that contains a `#` and no space after this character, and show how it works: + ------------ @@ -284,7 +305,7 @@ Fix #42 ------------ -* Configure a 'help' trailer with a cmd use a script `glog-find-author` +* Configure a `help` trailer with a cmd use a script `glog-find-author` which search specified author identity from git log in git repository and show how it works: + @@ -308,7 +329,7 @@ Helped-by: Christian Couder <christian.couder@gmail.com> ------------ -* Configure a 'ref' trailer with a cmd use a script `glog-grep` +* Configure a `ref` trailer with a cmd use a script `glog-grep` to grep last relevant commit from git log in the git repository and show how it works: + @@ -331,7 +352,7 @@ Reference-to: 8bc9a0c769 (Add copyright notices., 2005-04-07) ------------ -* Configure a 'see' trailer with a command to show the subject of a +* Configure a `see` trailer with a command to show the subject of a commit that is related, and show how it works: + ------------ @@ -359,8 +380,8 @@ * Configure a commit template with some trailers with empty values (using sed to show and keep the trailing spaces at the end of the trailers), then configure a commit-msg hook that uses - 'git interpret-trailers' to remove trailers with empty values and - to add a 'git-version' trailer: + git-interpret-trailers(1) to remove trailers with empty values and to + add a `git-version` trailer: + ------------ $ cat temp.txt
diff --git a/Documentation/git-last-modified.adoc b/Documentation/git-last-modified.adoc index 602843e..d7d16fc 100644 --- a/Documentation/git-last-modified.adoc +++ b/Documentation/git-last-modified.adoc
@@ -9,7 +9,8 @@ SYNOPSIS -------- [synopsis] -git last-modified [--recursive] [--show-trees] [<revision-range>] [[--] <path>...] +git last-modified [--recursive] [--show-trees] [--max-depth=<depth>] [-z] + [<revision-range>] [[--] <pathspec>...] DESCRIPTION ----------- @@ -24,13 +25,23 @@ `-r`:: `--recursive`:: - Instead of showing tree entries, step into subtrees and show all entries - inside them recursively. + Recursively traverse into all subtrees. By default, the command only + shows tree entries matching the `<pathspec>`. With this option, it + descends into subtrees and displays all entries within them. + Equivalent to `--max-depth=-1`. `-t`:: `--show-trees`:: - Show tree entries even when recursing into them. It has no effect - without `--recursive`. + Show tree entries even when recursing into them. + +`--max-depth=<depth>`:: + For each pathspec given on the command line, traverse at most `<depth>` + levels into subtrees. A negative value means no limit. + The default is 0, which shows all paths matching the pathspec + without descending into subtrees. + +`-z`:: + Terminate each line with a _NUL_ character rather than a newline. `<revision-range>`:: Only traverse commits in the specified revision range. When no @@ -39,10 +50,26 @@ spell `<revision-range>`, see the 'Specifying Ranges' section of linkgit:gitrevisions[7]. -`[--] <path>...`:: - For each _<path>_ given, the commit which last modified it is returned. - Without an optional path parameter, all files and subdirectories - in path traversal the are included in the output. +`[--] <pathspec>...`:: + Show the commit that last modified each path matching _<pathspec>_. + If no _<pathspec>_ is given, all files and subdirectories are included. + See linkgit:gitglossary[7] for details on pathspec syntax. + +OUTPUT +------ + +The output is in the format: + +------------ + <oid> TAB <path> LF +------------ + +If a path contains any special characters, the path is C-style quoted. To +avoid quoting, pass option `-z` to terminate each line with a NUL. + +------------ + <oid> TAB <path> NUL +------------ SEE ALSO --------
diff --git a/Documentation/git-maintenance.adoc b/Documentation/git-maintenance.adoc index 540b5cf..bda616f 100644 --- a/Documentation/git-maintenance.adoc +++ b/Documentation/git-maintenance.adoc
@@ -12,6 +12,7 @@ 'git maintenance' run [<options>] 'git maintenance' start [--scheduler=<scheduler>] 'git maintenance' (stop|register|unregister) [<options>] +'git maintenance' is-needed [<options>] DESCRIPTION @@ -84,6 +85,16 @@ is not already registered. Use the `--force` option to return success even when the current repository is not registered. +is-needed:: + Check whether maintenance needs to be run without actually running it. + Exits with a 0 status code if maintenance needs to be run, 1 otherwise. + Ideally used with the '--auto' flag. ++ +If one or more `--task` options are specified, then those tasks are checked +in that order. Otherwise, the tasks are determined by which +`maintenance.<task>.enabled` config options are true. By default, only +`maintenance.gc.enabled` is true. + TASKS ----- @@ -183,6 +194,8 @@ in the `gc.auto` config setting, or when the number of pack-files exceeds the `gc.autoPackLimit` config setting. Not compatible with the `--schedule` option. + When combined with the `is-needed` subcommand, check if the required + thresholds are met without actually running maintenance. --schedule:: When combined with the `run` subcommand, run maintenance tasks
diff --git a/Documentation/git-merge-file.adoc b/Documentation/git-merge-file.adoc index 71915a0..9dc5d8a 100644 --- a/Documentation/git-merge-file.adoc +++ b/Documentation/git-merge-file.adoc
@@ -85,6 +85,9 @@ --zdiff3:: Show conflicts in "zdiff3" style. ++ +The `--diff3` and `--zdiff3` options default to the value of the +`merge.conflictStyle` configuration variable (see linkgit:git-config[1]). --ours:: --theirs::
diff --git a/Documentation/git-multi-pack-index.adoc b/Documentation/git-multi-pack-index.adoc index 2f64269..6125683 100644 --- a/Documentation/git-multi-pack-index.adoc +++ b/Documentation/git-multi-pack-index.adoc
@@ -9,7 +9,14 @@ SYNOPSIS -------- [verse] -'git multi-pack-index' [--object-dir=<dir>] [--[no-]bitmap] <sub-command> +'git multi-pack-index' [<options>] write [--preferred-pack=<pack>] + [--[no-]bitmap] [--[no-]incremental] [--[no-]stdin-packs] + [--refs-snapshot=<path>] +'git multi-pack-index' [<options>] compact [--[no-]incremental] + [--[no-]bitmap] <from> <to> +'git multi-pack-index' [<options>] verify +'git multi-pack-index' [<options>] expire +'git multi-pack-index' [<options>] repack [--batch-size=<size>] DESCRIPTION ----------- @@ -18,6 +25,8 @@ OPTIONS ------- +The following command-line options are applicable to all sub-commands: + --object-dir=<dir>:: Use given directory for the location of Git objects. We check `<dir>/packs/multi-pack-index` for the current MIDX file, and @@ -73,7 +82,21 @@ Write an incremental MIDX file containing only objects and packs not present in an existing MIDX layer. Migrates non-incremental MIDXs to incremental ones when - necessary. Incompatible with `--bitmap`. + necessary. +-- + +compact:: + Write a new MIDX layer containing only objects and packs present + in the range `<from>` to `<to>`, where both arguments are + checksums of existing layers in the MIDX chain. ++ +-- + --incremental:: + Write the result to a MIDX chain instead of writing a + stand-alone MIDX. + + --[no-]bitmap:: + Control whether or not a multi-pack bitmap is written. -- verify::
diff --git a/Documentation/git-pack-objects.adoc b/Documentation/git-pack-objects.adoc index 71b9682..b78175f 100644 --- a/Documentation/git-pack-objects.adoc +++ b/Documentation/git-pack-objects.adoc
@@ -94,13 +94,24 @@ included packs (those not beginning with `^`), excluding any objects listed in the excluded packs (beginning with `^`). + -When `mode` is "follow", objects from packs not listed on stdin receive -special treatment. Objects within unlisted packs will be included if -those objects are (1) reachable from the included packs, and (2) not -found in any excluded packs. This mode is useful, for example, to -resurrect once-unreachable objects found in cruft packs to generate -packs which are closed under reachability up to the boundary set by the -excluded packs. +When `mode` is "follow" packs may additionally be prefixed with `!`, +indicating that they are excluded but not necessarily closed under +reachability. In addition to objects in included packs, the resulting +pack may include additional objects based on the following: ++ +-- +* If any packs are marked with `!`, then objects reachable from such + packs or included ones via objects outside of excluded-closed packs + will be included. In this case, all `^` packs are treated as closed + under reachability. +* Otherwise (if there are no `!` packs), objects within unlisted packs + will be included if those objects are (1) reachable from the + included packs, and (2) not found in any excluded packs. +-- ++ +This mode is useful, for example, to resurrect once-unreachable +objects found in cruft packs to generate packs which are closed under +reachability up to the boundary set by the excluded packs. + Incompatible with `--revs`, or options that imply `--revs` (such as `--all`), with the exception of `--unpacked`, which is compatible.
diff --git a/Documentation/git-patch-id.adoc b/Documentation/git-patch-id.adoc index 92a1af3..0585999 100644 --- a/Documentation/git-patch-id.adoc +++ b/Documentation/git-patch-id.adoc
@@ -3,7 +3,7 @@ NAME ---- -git-patch-id - Compute unique ID for a patch +git-patch-id - Compute unique IDs for patches SYNOPSIS -------- @@ -12,7 +12,7 @@ DESCRIPTION ----------- -Read a patch from the standard input and compute the patch ID for it. +Read patches from standard input and compute the patch IDs. A "patch ID" is nothing but a sum of SHA-1 of the file diffs associated with a patch, with line numbers ignored. As such, it's "reasonably stable", but at @@ -21,18 +21,19 @@ The main usecase for this command is to look for likely duplicate commits. -When dealing with `git diff-tree` output, it takes advantage of +When dealing with `git diff-tree --patch` output, it takes advantage of the fact that the patch is prefixed with the object name of the commit, and outputs two 40-byte hexadecimal strings. The first string is the patch ID, and the second string is the commit ID. -This can be used to make a mapping from patch ID to commit ID. +This can be used to make a mapping from patch ID to commit ID for a +set or range of commits. OPTIONS ------- `--verbatim`:: - Calculate the patch-id of the input as it is given, do not strip - any whitespace. + Calculate the patch ID of the input as it is given, do not strip + any whitespace. Implies `--stable` and forbids `--unstable`. + This is the default if `patchid.verbatim` is `true`. @@ -45,28 +46,72 @@ with two different settings for `-O<orderfile>` result in the same patch ID signature, thereby allowing the computed result to be used as a key to index some meta-information about the change between - the two trees; + the two trees. -- Result is different from the value produced by git 1.9 and older +- The result is different from the value produced by Git 1.9 and older or produced when an "unstable" hash (see `--unstable` below) is configured - even when used on a diff output taken without any use of `-O<orderfile>`, thereby making existing databases storing such - "unstable" or historical patch-ids unusable. + "unstable" or historical patch IDs unusable. -- All whitespace within the patch is ignored and does not affect the id. +- All whitespace within the patch is ignored and does not affect the ID. -- + This is the default if `patchid.stable` is set to `true`. `--unstable`:: Use an "unstable" hash as the patch ID. With this option, - the result produced is compatible with the patch-id value produced - by git 1.9 and older and whitespace is ignored. Users with pre-existing - databases storing patch-ids produced by git 1.9 and older (who do not deal + the result produced is compatible with the patch ID value produced + by Git 1.9 and older and whitespace is ignored. Users with pre-existing + databases storing patch IDs produced by Git 1.9 and older (who do not deal with reordered patches) may want to use this option. + This is the default. +EXAMPLES +-------- + +linkgit:git-cherry[1] shows what commits from a branch have patch ID +equivalent commits in some upstream branch. But it only tells you +whether such a commit exists or not. What if you wanted to know the +relevant commits in the upstream? We can use this command to make a +mapping between your branch and the upstream branch: + +---- +#!/bin/sh + +upstream="$1" +branch="$2" +test -z "$branch" && branch=HEAD +limit="$3" +if test -n "$limit" +then + tail_opts="$limit".."$upstream" +else + since=$(git log --format=%aI "$upstream".."$branch" | tail -1) + tail_opts=--since="$since"' '"$upstream" +fi +for_branch=$(mktemp) +for_upstream=$(mktemp) + +git rev-list --no-merges "$upstream".."$branch" | + git diff-tree --patch --stdin | + git patch-id --stable | sort >"$for_branch" +git rev-list --no-merges $tail_opts | + git diff-tree --patch --stdin | + git patch-id --stable | sort >"$for_upstream" +join -a1 "$for_branch" "$for_upstream" | cut -d' ' -f2,3 +rm "$for_branch" +rm "$for_upstream" +---- + +Now the first column shows the commit from your branch and the second +column shows the patch ID equivalent commit, if it exists. + +SEE ALSO +-------- +linkgit:git-cherry[1] + GIT --- Part of the linkgit:git[1] suite
diff --git a/Documentation/git-pull.adoc b/Documentation/git-pull.adoc index cd3bbc9..88f4fd3 100644 --- a/Documentation/git-pull.adoc +++ b/Documentation/git-pull.adoc
@@ -8,8 +8,8 @@ SYNOPSIS -------- -[verse] -'git pull' [<options>] [<repository> [<refspec>...]] +[synopsis] +git pull [<options>] [<repository> [<refspec>...]] DESCRIPTION @@ -37,13 +37,13 @@ or `pull.ff` with your preferred behaviour. If there's a merge conflict during the merge or rebase that you don't -want to handle, you can safely abort it with `git merge --abort` or `git ---rebase abort`. +want to handle, you can safely abort it with `git merge --abort` or +`git rebase --abort`. OPTIONS ------- -<repository>:: +_<repository>_:: The "remote" repository to pull from. This can be either a URL (see the section <<URLS,GIT URLS>> below) or the name of a remote (see the section <<REMOTES,REMOTES>> below). @@ -52,29 +52,29 @@ See <<UPSTREAM-BRANCHES,UPSTREAM BRANCHES>> below for more on how to configure upstreams. -<refspec>:: +_<refspec>_:: Which branch or other reference(s) to fetch and integrate into the current branch, for example `main` in `git pull origin main`. Defaults to the configured upstream for the current branch. + This can be a branch, tag, or other collection of reference(s). -See <<fetch-refspec,<refspec>>> below under "Options related to fetching" +See <<fetch-refspec,_<refspec>_>> below under "Options related to fetching" for the full syntax, and <<DEFAULT-BEHAVIOUR,DEFAULT BEHAVIOUR>> below for how `git pull` uses this argument to determine which remote branch to integrate. --q:: ---quiet:: +`-q`:: +`--quiet`:: This is passed to both underlying git-fetch to squelch reporting of during transfer, and underlying git-merge to squelch output during merging. --v:: ---verbose:: - Pass --verbose to git-fetch and git-merge. +`-v`:: +`--verbose`:: + Pass `--verbose` to git-fetch and git-merge. ---recurse-submodules[=(yes|on-demand|no)]:: ---no-recurse-submodules:: +`--recurse-submodules[=(yes|on-demand|no)]`:: +`--no-recurse-submodules`:: This option controls if new commits of populated submodules should be fetched, and if the working trees of active submodules should be updated, too (see linkgit:git-fetch[1], linkgit:git-config[1] and @@ -91,21 +91,20 @@ include::merge-options.adoc[] --r:: ---rebase[=(false|true|merges|interactive)]:: - When true, rebase the current branch on top of the upstream +`-r`:: +`--rebase[=(true|merges|false|interactive)]`:: +`true`;; rebase the current branch on top of the upstream branch after fetching. If there is a remote-tracking branch corresponding to the upstream branch and the upstream branch was rebased since last fetched, the rebase uses that information - to avoid rebasing non-local changes. -+ -When set to `merges`, rebase using `git rebase --rebase-merges` so that + to avoid rebasing non-local changes. This is the default. + +`merges`;; rebase using `git rebase --rebase-merges` so that the local merge commits are included in the rebase (see linkgit:git-rebase[1] for details). -+ -When false, merge the upstream branch into the current branch. -+ -When `interactive`, enable the interactive mode of rebase. +`false`;; merge the upstream branch into the current branch. +`interactive`;; enable the interactive mode of rebase. + + See `pull.rebase`, `branch.<name>.rebase` and `branch.autoSetupRebase` in linkgit:git-config[1] if you want to make `git pull` always use @@ -117,8 +116,8 @@ published that history already. Do *not* use this option unless you have read linkgit:git-rebase[1] carefully. ---no-rebase:: - This is shorthand for --rebase=false. +`--no-rebase`:: + This is shorthand for `--rebase=false`. Options related to fetching ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -178,7 +177,7 @@ rules apply: . If `branch.<name>.merge` configuration for the current - branch `<name>` exists, that is the name of the branch at the + branch _<name>_ exists, that is the name of the branch at the remote site that is merged. . If the refspec is a globbing one, nothing is merged. @@ -198,9 +197,9 @@ $ git pull origin ------------------------------------------------ + -Normally the branch merged in is the HEAD of the remote repository, -but the choice is determined by the branch.<name>.remote and -branch.<name>.merge options; see linkgit:git-config[1] for details. +Normally the branch merged in is the `HEAD` of the remote repository, +but the choice is determined by the `branch.<name>.remote` and +`branch.<name>.merge` options; see linkgit:git-config[1] for details. * Merge into the current branch the remote branch `next`: + @@ -208,7 +207,7 @@ $ git pull origin next ------------------------------------------------ + -This leaves a copy of `next` temporarily in FETCH_HEAD, and +This leaves a copy of `next` temporarily in `FETCH_HEAD`, and updates the remote-tracking branch `origin/next`. The same can be done by invoking fetch and merge: + @@ -219,14 +218,14 @@ If you tried a pull which resulted in complex conflicts and -would want to start over, you can recover with 'git reset'. +would want to start over, you can recover with `git reset`. include::transfer-data-leaks.adoc[] BUGS ---- -Using --recurse-submodules can only fetch new commits in already checked +Using `--recurse-submodules` can only fetch new commits in already checked out submodules right now. When e.g. upstream added a new submodule in the just fetched commits of the superproject the submodule itself cannot be fetched, making it impossible to check out that submodule later without
diff --git a/Documentation/git-push.adoc b/Documentation/git-push.adoc index 864b0d0..e5ba3a6 100644 --- a/Documentation/git-push.adoc +++ b/Documentation/git-push.adoc
@@ -8,13 +8,13 @@ SYNOPSIS -------- -[verse] -'git push' [--all | --branches | --mirror | --tags] [--follow-tags] [--atomic] [-n | --dry-run] [--receive-pack=<git-receive-pack>] - [--repo=<repository>] [-f | --force] [-d | --delete] [--prune] [-q | --quiet] [-v | --verbose] - [-u | --set-upstream] [-o <string> | --push-option=<string>] - [--[no-]signed|--signed=(true|false|if-asked)] - [--force-with-lease[=<refname>[:<expect>]] [--force-if-includes]] - [--no-verify] [<repository> [<refspec>...]] +[synopsis] +git push [--all | --branches | --mirror | --tags] [--follow-tags] [--atomic] [-n | --dry-run] [--receive-pack=<git-receive-pack>] + [--repo=<repository>] [-f | --force] [-d | --delete] [--prune] [-q | --quiet] [-v | --verbose] + [-u | --set-upstream] [-o <string> | --push-option=<string>] + [--[no-]signed | --signed=(true|false|if-asked)] + [--force-with-lease[=<refname>[:<expect>]] [--force-if-includes]] + [--no-verify] [<repository> [<refspec>...]] DESCRIPTION ----------- @@ -35,7 +35,7 @@ 1. The `<refspec>` argument(s) (for example `main` in `git push origin main`) or the `--all`, `--mirror`, or `--tags` options -2. The `remote.*.push` configuration for the repository being pushed to +2. The `remote.<name>.push` configuration for the repository being pushed to 3. The `push.default` configuration. The default is `push.default=simple`, which will push to a branch with the same name as the current branch. See the <<CONFIGURATION,CONFIGURATION>> section below for more on `push.default`. @@ -49,25 +49,25 @@ every time you push into it, by setting up 'hooks' there. See documentation for linkgit:git-receive-pack[1]. - -OPTIONS[[OPTIONS]] ------------------- -<repository>:: +[[OPTIONS]] +OPTIONS +------- +_<repository>_:: The "remote" repository that is the destination of a push operation. This parameter can be either a URL (see the section <<URLS,GIT URLS>> below) or the name of a remote (see the section <<REMOTES,REMOTES>> below). -<refspec>...:: +`<refspec>...`:: Specify what destination ref to update with what source object. + -The format for a refspec is [+]<src>[:<dst>], for example `main`, +The format for a refspec is `[+]<src>[:<dst>]`, for example `main`, `main:other`, or `HEAD^:refs/heads/main`. + -The `<src>` is often the name of the local branch to push, but it can be +The _<src>_ is often the name of the local branch to push, but it can be any arbitrary "SHA-1 expression" (see linkgit:gitrevisions[7]). + -The `<dst>` determines what ref to update on the remote side. It must be the +The _<dst>_ determines what ref to update on the remote side. It must be the name of a branch, tag, or other ref, not an arbitrary expression. + The `+` is optional and does the same thing as `--force`. @@ -78,23 +78,23 @@ `main:other`). Here are the rules for how refspecs are expanded, as well as various other special refspec forms: + - * `<src>` without a `:<dst>` means to update the same ref as the - `<src>`, unless the `remote.<repository>.push` configuration specifies a - different <dst>. For example, if `main` is a branch, then the refspec + * _<src>_ without a `:<dst>` means to update the same ref as the + _<src>_, unless the `remote.<repository>.push` configuration specifies a + different _<dst>_. For example, if `main` is a branch, then the refspec `main` expands to `main:refs/heads/main`. - * If `<dst>` unambiguously refers to a ref on the <repository> remote, + * If _<dst>_ unambiguously refers to a ref on the <repository> remote, then expand it to that ref. For example, if `v1.0` is a tag on the remote, then `HEAD:v1.0` expands to `HEAD:refs/tags/v1.0`. - * If `<src>` resolves to a ref starting with `refs/heads/` or `refs/tags/`, + * If _<src>_ resolves to a ref starting with `refs/heads/` or `refs/tags/`, then prepend that to <dst>. For example, if `main` is a branch, then `main:other` expands to `main:refs/heads/other` * The special refspec `:` (or `+:` to allow non-fast-forward updates) directs Git to push "matching" branches: for every branch that exists on the local side, the remote side is updated if a branch of the same name already exists on the remote side. - * <src> may contain a * to indicate a simple pattern match. + * _<src>_ may contain a `*` to indicate a simple pattern match. This works like a glob that matches any ref matching the pattern. - There must be only one * in both the `<src>` and `<dst>`. + There must be only one `*` in both the `<src>` and `<dst>`. It will map refs to the destination by replacing the * with the contents matched from the source. For example, `refs/heads/*:refs/heads/*` will push all branches. @@ -102,11 +102,11 @@ This specifies refs to exclude. A ref will be considered to match if it matches at least one positive refspec, and does not match any negative refspec. Negative refspecs can be pattern refspecs. - They must only contain a `<src>`. + They must only contain a _<src>_. Fully spelled out hex object names are also not supported. For example, `git push origin 'refs/heads/*' '^refs/heads/dev-*'` will push all branches except for those starting with `dev-` - * If `<src>` is empty, it deletes the `<dst>` ref from the remote + * If _<src>_ is empty, it deletes the _<dst>_ ref from the remote repository. For example, `git push origin :dev` will delete the `dev` branch. * `tag <tag>` expands to `refs/tags/<tag>:refs/tags/<tag>`. @@ -121,12 +121,12 @@ Not all updates are allowed: see PUSH RULES below for the details. ---all:: ---branches:: +`--all`:: +`--branches`:: Push all branches (i.e. refs under `refs/heads/`); cannot be used with other <refspec>. ---prune:: +`--prune`:: Remove remote branches that don't have a local counterpart. For example a remote branch `tmp` will be removed if a local branch with the same name doesn't exist any more. This also respects refspecs, e.g. @@ -134,7 +134,7 @@ make sure that remote `refs/tmp/foo` will be removed if `refs/heads/foo` doesn't exist. ---mirror:: +`--mirror`:: Instead of naming each ref to push, specifies that all refs under `refs/` (which includes but is not limited to `refs/heads/`, `refs/remotes/`, and `refs/tags/`) @@ -145,26 +145,26 @@ if the configuration option `remote.<remote>.mirror` is set. --n:: ---dry-run:: +`-n`:: +`--dry-run`:: Do everything except actually send the updates. ---porcelain:: +`--porcelain`:: Produce machine-readable output. The output status line for each ref will be tab-separated and sent to stdout instead of stderr. The full symbolic names of the refs will be given. --d:: ---delete:: +`-d`:: +`--delete`:: All listed refs are deleted from the remote repository. This is the same as prefixing all refs with a colon. ---tags:: +`--tags`:: All refs under `refs/tags` are pushed, in addition to refspecs explicitly listed on the command line. ---follow-tags:: +`--follow-tags`:: Push all the refs that would be pushed without this option, and also push annotated tags in `refs/tags` that are missing from the remote but are pointing at commit-ish that are @@ -172,29 +172,34 @@ with configuration variable `push.followTags`. For more information, see `push.followTags` in linkgit:git-config[1]. ---signed:: ---no-signed:: ---signed=(true|false|if-asked):: +`--signed`:: +`--no-signed`:: +`--signed=(true|false|if-asked)`:: GPG-sign the push request to update refs on the receiving side, to allow it to be checked by the hooks and/or be - logged. If `false` or `--no-signed`, no signing will be - attempted. If `true` or `--signed`, the push will fail if the - server does not support signed pushes. If set to `if-asked`, - sign if and only if the server supports signed pushes. The push - will also fail if the actual call to `gpg --sign` fails. See - linkgit:git-receive-pack[1] for the details on the receiving end. + logged. Possible values are: +`false`;; +`--no-signed`;; +no signing will be attempted. +`true`;; +`--signed`;; +the push will fail if the server does not support signed pushes. +`if-asked`;; +sign if and only if the server supports signed pushes. The push +will also fail if the actual call to `gpg --sign` fails. See +linkgit:git-receive-pack[1] for the details on the receiving end. ---atomic:: ---no-atomic:: +`--atomic`:: +`--no-atomic`:: Use an atomic transaction on the remote side if available. Either all refs are updated, or on error, no refs are updated. If the server does not support atomic pushes the push will fail. --o <option>:: ---push-option=<option>:: +`-o <option>`:: +`--push-option=<option>`:: Transmit the given string to the server, which passes them to the pre-receive as well as the post-receive hook. The given string - must not contain a NUL or LF character. + must not contain a _NUL_ or _LF_ character. When multiple `--push-option=<option>` are given, they are all sent to the other side in the order listed on the command line. @@ -202,22 +207,22 @@ line, the values of configuration variable `push.pushOption` are used instead. ---receive-pack=<git-receive-pack>:: ---exec=<git-receive-pack>:: +`--receive-pack=<git-receive-pack>`:: +`--exec=<git-receive-pack>`:: Path to the 'git-receive-pack' program on the remote end. Sometimes useful when pushing to a remote repository over ssh, and you do not have the program in - a directory on the default $PATH. + a directory on the default `$PATH`. ---force-with-lease:: ---no-force-with-lease:: ---force-with-lease=<refname>:: ---force-with-lease=<refname>:<expect>:: - Usually, "git push" refuses to update a remote ref that is +`--force-with-lease`:: +`--no-force-with-lease`:: +`--force-with-lease=<refname>`:: +`--force-with-lease=<refname>:<expect>`:: + Usually, `git push` refuses to update a remote ref that is not an ancestor of the local ref used to overwrite it. + This option overrides this restriction if the current value of the -remote ref is the expected value. "git push" fails otherwise. +remote ref is the expected value. `git push` fails otherwise. + Imagine that you have to rebase what you have already published. You will have to bypass the "must fast-forward" rule in order to @@ -239,16 +244,16 @@ for them. + `--force-with-lease=<refname>`, without specifying the expected value, will -protect the named ref (alone), if it is going to be updated, by +protect _<refname>_ (alone), if it is going to be updated, by requiring its current value to be the same as the remote-tracking branch we have for it. + -`--force-with-lease=<refname>:<expect>` will protect the named ref (alone), +`--force-with-lease=<refname>:<expect>` will protect _<refname>_ (alone), if it is going to be updated, by requiring its current value to be -the same as the specified value `<expect>` (which is allowed to be +the same as the specified value _<expect>_ (which is allowed to be different from the remote-tracking branch we have for the refname, or we do not even have to have such a remote-tracking branch when -this form is used). If `<expect>` is the empty string, then the named ref +this form is used). If _<expect>_ is the empty string, then the named ref must not already exist. + Note that all forms other than `--force-with-lease=<refname>:<expect>` @@ -256,7 +261,7 @@ still experimental and their semantics may change as we gain experience with this feature. + -"--no-force-with-lease" will cancel all the previous --force-with-lease on the +`--no-force-with-lease` will cancel all the previous `--force-with-lease` on the command line. + A general note on safety: supplying this option without an expected @@ -276,23 +281,29 @@ background for you a way to mitigate this is to simply set up another remote: + - git remote add origin-push $(git config remote.origin.url) - git fetch origin-push +---- +git remote add origin-push $(git config remote.origin.url) +git fetch origin-push +---- + Now when the background process runs `git fetch origin` the references on `origin-push` won't be updated, and thus commands like: + - git push --force-with-lease origin-push +---- +git push --force-with-lease origin-push +---- + Will fail unless you manually run `git fetch origin-push`. This method is of course entirely defeated by something that runs `git fetch --all`, in that case you'd need to either disable it or do something more tedious like: + - git fetch # update 'master' from remote - git tag base master # mark our base point - git rebase -i master # rewrite some commits - git push --force-with-lease=master:base master:master +---- +git fetch # update 'master' from remote +git tag base master # mark our base point +git rebase -i master # rewrite some commits +git push --force-with-lease=master:base master:master +---- + I.e. create a `base` tag for versions of the upstream code that you've seen and are willing to overwrite, then rewrite history, and finally @@ -308,26 +319,26 @@ implicitly updated in the background are integrated locally before allowing a forced update. --f:: ---force:: +`-f`:: +`--force`:: Usually, `git push` will refuse to update a branch that is not an ancestor of the commit being pushed. + This flag disables that check, the other safety checks in PUSH RULES -below, and the checks in --force-with-lease. It can cause the remote +below, and the checks in `--force-with-lease`. It can cause the remote repository to lose commits; use it with care. + Note that `--force` applies to all the refs that are pushed, hence using it with `push.default` set to `matching` or with multiple push -destinations configured with `remote.*.push` may overwrite refs +destinations configured with `remote.<name>.push` may overwrite refs other than the current branch (including local refs that are strictly behind their remote counterpart). To force a push to only one branch, use a `+` in front of the refspec to push (e.g `git push origin +master` to force a push to the `master` branch). See the `<refspec>...` section above for details. ---force-if-includes:: ---no-force-if-includes:: +`--force-if-includes`:: +`--no-force-if-includes`:: Force an update only if the tip of the remote-tracking ref has been integrated locally. + @@ -343,72 +354,78 @@ + Specifying `--no-force-if-includes` disables this behavior. ---repo=<repository>:: - This option is equivalent to the <repository> argument. If both +`--repo=<repository>`:: + This option is equivalent to the _<repository>_ argument. If both are specified, the command-line argument takes precedence. --u:: ---set-upstream:: +`-u`:: +`--set-upstream`:: For every branch that is up to date or successfully pushed, add upstream (tracking) reference, used by argument-less linkgit:git-pull[1] and other commands. For more information, see `branch.<name>.merge` in linkgit:git-config[1]. ---thin:: ---no-thin:: +`--thin`:: +`--no-thin`:: These options are passed to linkgit:git-send-pack[1]. A thin transfer significantly reduces the amount of sent data when the sender and receiver share many of the same objects in common. The default is `--thin`. --q:: ---quiet:: +`-q`:: +`--quiet`:: Suppress all output, including the listing of updated refs, unless an error occurs. Progress is not reported to the standard error stream. --v:: ---verbose:: +`-v`:: +`--verbose`:: Run verbosely. ---progress:: +`--progress`:: Progress status is reported on the standard error stream - by default when it is attached to a terminal, unless -q + by default when it is attached to a terminal, unless `-q` is specified. This flag forces progress status even if the standard error stream is not directed to a terminal. ---no-recurse-submodules:: ---recurse-submodules=check|on-demand|only|no:: +`--no-recurse-submodules`:: +`--recurse-submodules=(check|on-demand|only|no)`:: May be used to make sure all submodule commits used by the revisions to be pushed are available on a remote-tracking branch. - If 'check' is used Git will verify that all submodule commits that + Possible values are: +`check`;; + Git will verify that all submodule commits that changed in the revisions to be pushed are available on at least one remote of the submodule. If any commits are missing the push will - be aborted and exit with non-zero status. If 'on-demand' is used + be aborted and exit with non-zero status. +`on-demand`;; all submodules that changed in the revisions to be pushed will be - pushed. If on-demand was not able to push all necessary revisions it will - also be aborted and exit with non-zero status. If 'only' is used all - submodules will be pushed while the superproject is left - unpushed. A value of 'no' or using `--no-recurse-submodules` can be used - to override the push.recurseSubmodules configuration variable when no - submodule recursion is required. + pushed. If `on-demand` was not able to push all necessary revisions it will + also be aborted and exit with non-zero status. +`only`;; + all submodules will be pushed while the superproject is left + unpushed. +`no`;; + override the `push.recurseSubmodules` configuration variable when no + submodule recursion is required. Similar to using `--no-recurse-submodules`. + + -When using 'on-demand' or 'only', if a submodule has a -"push.recurseSubmodules={on-demand,only}" or "submodule.recurse" configuration, -further recursion will occur. In this case, "only" is treated as "on-demand". +When using `on-demand` or `only`, if a submodule has a +`push.recurseSubmodules=(on-demand|only)` or `submodule.recurse` configuration, +further recursion will occur. In this case, `only` is treated as `on-demand`. ---verify:: ---no-verify:: +`--verify`:: +`--no-verify`:: Toggle the pre-push hook (see linkgit:githooks[5]). The - default is --verify, giving the hook a chance to prevent the - push. With --no-verify, the hook is bypassed completely. + default is `--verify`, giving the hook a chance to prevent the + push. With `--no-verify`, the hook is bypassed completely. --4:: ---ipv4:: +`-4`:: +`--ipv4`:: Use IPv4 addresses only, ignoring IPv6 addresses. --6:: ---ipv6:: +`-6`:: +`--ipv6`:: Use IPv6 addresses only, ignoring IPv4 addresses. include::urls-remotes.adoc[] @@ -427,16 +444,16 @@ <flag> <summary> <from> -> <to> (<reason>) ------------------------------- -If --porcelain is used, then each line of the output is of the form: +If `--porcelain` is used, then each line of the output is of the form: ------------------------------- <flag> \t <from>:<to> \t <summary> (<reason>) ------------------------------- -The status of up-to-date refs is shown only if --porcelain or --verbose +The status of up-to-date refs is shown only if `--porcelain` or `--verbose` option is used. -flag:: +_<flag>_:: A single character indicating the status of the ref: (space);; for a successfully pushed fast-forward; `+`;; for a successful forced update; @@ -445,7 +462,7 @@ `!`;; for a ref that was rejected or failed to push; and `=`;; for a ref that was up to date and did not need pushing. -summary:: +_<summary>_:: For a successfully pushed ref, the summary shows the old and new values of the ref in a form suitable for using as an argument to `git log` (this is `<old>..<new>` in most cases, and @@ -586,7 +603,7 @@ push will be accepted. Alternatively, you can rebase your change between X and B on top of A, -with "git pull --rebase", and push the result back. The rebase will +with `git pull --rebase`, and push the result back. The rebase will create a new commit D that builds the change between X and B on top of A. @@ -604,12 +621,12 @@ There is another common situation where you may encounter non-fast-forward rejection when you try to push, and it is possible even when you are pushing into a repository nobody else pushes into. After you push commit -A yourself (in the first picture in this section), replace it with "git -commit --amend" to produce commit B, and you try to push it out, because +A yourself (in the first picture in this section), replace it with `git +commit --amend` to produce commit B, and you try to push it out, because forgot that you have pushed A out already. In such a case, and only if you are certain that nobody in the meantime fetched your earlier commit A -(and started building on top of it), you can run "git push --force" to -overwrite it. In other words, "git push --force" is a method reserved for +(and started building on top of it), you can run `git push --force` to +overwrite it. In other words, `git push --force` is a method reserved for a case where you do mean to lose history. @@ -627,18 +644,18 @@ variable) if it has the same name as the current branch, and errors out without pushing otherwise. + -The default behavior of this command when no <refspec> is given can be +The default behavior of this command when no _<refspec>_ is given can be configured by setting the `push` option of the remote, or the `push.default` configuration variable. + For example, to default to pushing only the current branch to `origin` -use `git config remote.origin.push HEAD`. Any valid <refspec> (like +use `git config remote.origin.push HEAD`. Any valid _<refspec>_ (like the ones in the examples below) can be configured as the default for `git push origin`. `git push origin :`:: Push "matching" branches to `origin`. See - <refspec> in the <<OPTIONS,OPTIONS>> section above for a + _<refspec>_ in the <<OPTIONS,OPTIONS>> section above for a description of "matching" branches. `git push origin master`::
diff --git a/Documentation/git-rebase.adoc b/Documentation/git-rebase.adoc index 005caf6..f6c22d1 100644 --- a/Documentation/git-rebase.adoc +++ b/Documentation/git-rebase.adoc
@@ -87,7 +87,7 @@ point to that commit at the end of the rebase if other commands that change `ORIG_HEAD` (like `git reset`) are used during the rebase. The previous branch tip, however, is accessible using the reflog of the current branch (i.e. `@{1}`, -see linkgit:gitrevisions[7]. +see linkgit:gitrevisions[7]). TRANSPLANTING A TOPIC BRANCH WITH --ONTO ---------------------------------------- @@ -474,6 +474,13 @@ Instead of using the current time as the committer date, use the author date of the commit being rebased as the committer date. This option implies `--force-rebase`. ++ +WARNING: The history walking machinery assumes that commits have +non-decreasing commit timestamps. You should consider if you really need +to use this option. Then you should only use this option to override the +committer date when rebasing commits on top of a base which commit is +older (in terms of the commit date) than the oldest commit you are +applying (in terms of the author date). --ignore-date:: --reset-author-date:: @@ -490,6 +497,13 @@ + See also INCOMPATIBLE OPTIONS below. +--trailer=<trailer>:: + Append the given trailer to every rebased commit message, processed + via linkgit:git-interpret-trailers[1]. This option implies + `--force-rebase`. ++ +See also INCOMPATIBLE OPTIONS below. + -i:: --interactive:: Make a list of the commits which are about to be rebased. Let the @@ -646,6 +660,7 @@ * --[no-]reapply-cherry-picks when used without --keep-base * --update-refs * --root when used without --onto + * --trailer In addition, the following pairs of options are incompatible:
diff --git a/Documentation/git-remote.adoc b/Documentation/git-remote.adoc index 932a5c3..eaae30a 100644 --- a/Documentation/git-remote.adoc +++ b/Documentation/git-remote.adoc
@@ -8,20 +8,20 @@ SYNOPSIS -------- -[verse] -'git remote' [-v | --verbose] -'git remote add' [-t <branch>] [-m <master>] [-f] [--[no-]tags] [--mirror=(fetch|push)] <name> <URL> -'git remote rename' [--[no-]progress] <old> <new> -'git remote remove' <name> -'git remote set-head' <name> (-a | --auto | -d | --delete | <branch>) -'git remote set-branches' [--add] <name> <branch>... -'git remote get-url' [--push] [--all] <name> -'git remote set-url' [--push] <name> <newurl> [<oldurl>] -'git remote set-url --add' [--push] <name> <newurl> -'git remote set-url --delete' [--push] <name> <URL> -'git remote' [-v | --verbose] 'show' [-n] <name>... -'git remote prune' [-n | --dry-run] <name>... -'git remote' [-v | --verbose] 'update' [-p | --prune] [(<group> | <remote>)...] +[synopsis] +git remote [-v | --verbose] +git remote add [-t <branch>] [-m <master>] [-f] [--[no-]tags] [--mirror=(fetch|push)] <name> <URL> +git remote rename [--[no-]progress] <old> <new> +git remote remove <name> +git remote set-head <name> (-a | --auto | -d | --delete | <branch>) +git remote set-branches [--add] <name> <branch>... +git remote get-url [--push] [--all] <name> +git remote set-url [--push] <name> <newurl> [<oldurl>] +git remote set-url --add [--push] <name> <newurl> +git remote set-url --delete [--push] <name> <URL> +git remote [-v | --verbose] show [-n] <name>... +git remote prune [-n | --dry-run] <name>... +git remote [-v | --verbose] update [-p | --prune] [(<group> | <remote>)...] DESCRIPTION ----------- @@ -32,8 +32,8 @@ OPTIONS ------- --v:: ---verbose:: +`-v`:: +`--verbose`:: Be a little more verbose and show remote url after name. For promisor remotes, also show which filters (`blob:none` etc.) are configured. @@ -43,14 +43,14 @@ COMMANDS -------- -With no arguments, shows a list of existing remotes. Several +With no arguments, show a list of existing remotes. Several subcommands are available to perform operations on the remotes. -'add':: +`add`:: -Add a remote named <name> for the repository at -<URL>. The command `git fetch <name>` can then be used to create and -update remote-tracking branches <name>/<branch>. +Add a remote named _<name>_ for the repository at +_<URL>_. The command `git fetch <name>` can then be used to create and +update remote-tracking branches `<name>/<branch>`. + With `-f` option, `git fetch <name>` is run immediately after the remote information is set up. @@ -66,40 +66,40 @@ + With `-t <branch>` option, instead of the default glob refspec for the remote to track all branches under -the `refs/remotes/<name>/` namespace, a refspec to track only `<branch>` +the `refs/remotes/<name>/` namespace, a refspec to track only _<branch>_ is created. You can give more than one `-t <branch>` to track multiple branches without grabbing all branches. + With `-m <master>` option, a symbolic-ref `refs/remotes/<name>/HEAD` is set -up to point at remote's `<master>` branch. See also the set-head command. +up to point at remote's _<master>_ branch. See also the set-head command. + When a fetch mirror is created with `--mirror=fetch`, the refs will not -be stored in the 'refs/remotes/' namespace, but rather everything in -'refs/' on the remote will be directly mirrored into 'refs/' in the +be stored in the `refs/remotes/` namespace, but rather everything in +`refs/` on the remote will be directly mirrored into `refs/` in the local repository. This option only makes sense in bare repositories, because a fetch would overwrite any local commits. + When a push mirror is created with `--mirror=push`, then `git push` will always behave as if `--mirror` was passed. -'rename':: +`rename`:: -Rename the remote named <old> to <new>. All remote-tracking branches and +Rename the remote named _<old>_ to _<new>_. All remote-tracking branches and configuration settings for the remote are updated. + -In case <old> and <new> are the same, and <old> is a file under +In case _<old>_ and _<new>_ are the same, and _<old>_ is a file under `$GIT_DIR/remotes` or `$GIT_DIR/branches`, the remote is converted to the configuration file format. -'remove':: -'rm':: +`remove`:: +`rm`:: -Remove the remote named <name>. All remote-tracking branches and +Remove the remote named _<name>_. All remote-tracking branches and configuration settings for the remote are removed. -'set-head':: +`set-head`:: -Sets or deletes the default branch (i.e. the target of the +Set or delete the default branch (i.e. the target of the symbolic-ref `refs/remotes/<name>/HEAD`) for the named remote. Having a default branch for a remote is not required, but allows the name of the remote to be specified in lieu of a specific @@ -116,15 +116,15 @@ only work if `refs/remotes/origin/next` already exists; if not it must be fetched first. + -Use `<branch>` to set the symbolic-ref `refs/remotes/<name>/HEAD` explicitly. e.g., `git +Use _<branch>_ to set the symbolic-ref `refs/remotes/<name>/HEAD` explicitly. e.g., `git remote set-head origin master` will set the symbolic-ref `refs/remotes/origin/HEAD` to `refs/remotes/origin/master`. This will only work if `refs/remotes/origin/master` already exists; if not it must be fetched first. + -'set-branches':: +`set-branches`:: -Changes the list of branches tracked by the named remote. +Change the list of branches tracked by the named remote. This can be used to track a subset of the available remote branches after the initial setup for a remote. + @@ -134,7 +134,7 @@ With `--add`, instead of replacing the list of currently tracked branches, adds to that list. -'get-url':: +`get-url`:: Retrieves the URLs for a remote. Configurations for `insteadOf` and `pushInsteadOf` are expanded here. By default, only the first URL is listed. @@ -143,18 +143,18 @@ + With `--all`, all URLs for the remote will be listed. -'set-url':: +`set-url`:: -Changes URLs for the remote. Sets first URL for remote <name> that matches -regex <oldurl> (first URL if no <oldurl> is given) to <newurl>. If -<oldurl> doesn't match any URL, an error occurs and nothing is changed. +Change URLs for the remote. Sets first URL for remote _<name>_ that matches +regex _<oldurl>_ (first URL if no _<oldurl>_ is given) to _<newurl>_. If +_<oldurl>_ doesn't match any URL, an error occurs and nothing is changed. + With `--push`, push URLs are manipulated instead of fetch URLs. + With `--add`, instead of changing existing URLs, new URL is added. + With `--delete`, instead of changing existing URLs, all URLs matching -regex <URL> are deleted for remote <name>. Trying to delete all +regex _<URL>_ are deleted for remote _<name>_. Trying to delete all non-push URLs is an error. + Note that the push URL and the fetch URL, even though they can @@ -165,17 +165,17 @@ your publishing repository), use two separate remotes. -'show':: +`show`:: -Gives some information about the remote <name>. +Give some information about the remote _<name>_. + With `-n` option, the remote heads are not queried first with `git ls-remote <name>`; cached information is used instead. -'prune':: +`prune`:: -Deletes stale references associated with <name>. By default, stale -remote-tracking branches under <name> are deleted, but depending on +Delete stale references associated with _<name>_. By default, stale +remote-tracking branches under _<name>_ are deleted, but depending on global configuration and the configuration of the remote we might even prune local tags that haven't been pushed there. Equivalent to `git fetch --prune <name>`, except that no new references will be fetched. @@ -186,13 +186,13 @@ With `--dry-run` option, report what branches would be pruned, but do not actually prune them. -'update':: +`update`:: Fetch updates for remotes or remote groups in the repository as defined by `remotes.<group>`. If neither group nor remote is specified on the command line, -the configuration parameter remotes.default will be used; if -remotes.default is not defined, all remotes which do not have the -configuration parameter `remote.<name>.skipDefaultUpdate` set to true will +the configuration parameter `remotes.default` will be used; if +`remotes.default` is not defined, all remotes which do not have the +configuration parameter `remote.<name>.skipDefaultUpdate` set to `true` will be updated. (See linkgit:git-config[1]). + With `--prune` option, run pruning against all the remotes that are updated. @@ -210,7 +210,7 @@ On success, the exit status is `0`. -When subcommands such as 'add', 'rename', and 'remove' can't find the +When subcommands such as `add`, `rename`, and `remove` can't find the remote in question, the exit status is `2`. When the remote already exists, the exit status is `3`. @@ -247,7 +247,7 @@ ... ------------ -* Imitate 'git clone' but track only selected branches +* Imitate `git clone` but track only selected branches + ------------ $ mkdir project.git
diff --git a/Documentation/git-repack.adoc b/Documentation/git-repack.adoc index d12c498..673ce91 100644 --- a/Documentation/git-repack.adoc +++ b/Documentation/git-repack.adoc
@@ -77,14 +77,14 @@ Only useful with `--cruft -d`. --max-cruft-size=<n>:: - Overrides `--max-pack-size` for cruft packs. Inherits the value of + Override `--max-pack-size` for cruft packs. Inherits the value of `--max-pack-size` (if any) by default. See the documentation for `--max-pack-size` for more details. --combine-cruft-below-size=<n>:: When generating cruft packs without pruning, only repack - existing cruft packs whose size is strictly less than `<n>`, - where `<n>` represents a number of bytes, which can optionally + existing cruft packs whose size is strictly less than `<n>` + bytes, which can optionally be suffixed with "k", "m", or "g". Cruft packs whose size is greater than or equal to `<n>` are left as-is and not repacked. Useful when you want to avoid repacking large cruft pack(s) in
diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc index 0b12bf8..997097e 100644 --- a/Documentation/git-replay.adoc +++ b/Documentation/git-replay.adoc
@@ -9,16 +9,17 @@ SYNOPSIS -------- [verse] -(EXPERIMENTAL!) 'git replay' ([--contained] --onto <newbase> | --advance <branch>) <revision-range>... +(EXPERIMENTAL!) 'git replay' ([--contained] --onto <newbase> | --advance <branch> | --revert <branch>) [--ref-action[=<mode>]] <revision-range> DESCRIPTION ----------- -Takes ranges of commits and replays them onto a new location. Leaves -the working tree and the index untouched, and updates no references. -The output of this command is meant to be used as input to -`git update-ref --stdin`, which would update the relevant branches -(see the OUTPUT section below). +Takes a range of commits and replays them onto a new location. Leaves +the working tree and the index untouched. By default, updates the +relevant references using an atomic transaction (all refs update or +none). Use `--ref-action=print` to avoid automatic ref updates and +instead get update commands that can be piped to `git update-ref --stdin` +(see the <<output,OUTPUT>> section below). THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE. @@ -29,43 +30,89 @@ Starting point at which to create the new commits. May be any valid commit, and not just an existing branch name. + -When `--onto` is specified, the update-ref command(s) in the output will -update the branch(es) in the revision range to point at the new -commits, similar to the way how `git rebase --update-refs` updates -multiple branches in the affected range. +When `--onto` is specified, the branch(es) in the revision range will be +updated to point at the new commits, similar to the way `git rebase --update-refs` +updates multiple branches in the affected range. --advance <branch>:: Starting point at which to create the new commits; must be a branch name. + -When `--advance` is specified, the update-ref command(s) in the output -will update the branch passed as an argument to `--advance` to point at -the new commits (in other words, this mimics a cherry-pick operation). +The history is replayed on top of the <branch> and <branch> is updated to +point at the tip of the resulting history. This is different from `--onto`, +which uses the target only as a starting point without updating it. + +--revert <branch>:: + Starting point at which to create the reverted commits; must be a + branch name. ++ +When `--revert` is specified, the commits in the revision range are reverted +(their changes are undone) and the reverted commits are created on top of +<branch>. The <branch> is then updated to point at the new commits. This is +the same as running `git revert <revision-range>` but does not update the +working tree. ++ +The commit messages follow `git revert` conventions: they are prefixed with +"Revert" and include "This reverts commit <hash>." When reverting a commit +whose message starts with "Revert", the new message uses "Reapply" instead. +Unlike cherry-pick which preserves the original author, revert commits use +the current user as the author, matching the behavior of `git revert`. ++ +This option is mutually exclusive with `--onto` and `--advance`. It is also +incompatible with `--contained` (which is a modifier for `--onto` only). + +--contained:: + Update all branches that point at commits in + <revision-range>. Requires `--onto`. + +--ref-action[=<mode>]:: + Control how references are updated. The mode can be: ++ +-- + * `update` (default): Update refs directly using an atomic transaction. + All refs are updated or none are (all-or-nothing behavior). + * `print`: Output update-ref commands for pipeline use. This is the + traditional behavior where output can be piped to `git update-ref --stdin`. +-- ++ +The default mode can be configured via the `replay.refAction` configuration variable. <revision-range>:: - Range of commits to replay. More than one <revision-range> can - be passed, but in `--advance <branch>` mode, they should have - a single tip, so that it's clear where <branch> should point - to. See "Specifying Ranges" in linkgit:git-rev-parse[1] and the - "Commit Limiting" options below. + Range of commits to replay; see "Specifying Ranges" in + linkgit:git-rev-parse[1]. In `--advance <branch>` or + `--revert <branch>` mode, the range should have a single tip, + so that it's clear to which tip the advanced or reverted + <branch> should point. Any commits in the range whose changes + are already present in the branch the commits are being + replayed onto will be dropped. +:git-replay: 1 include::rev-list-options.adoc[] +[[output]] OUTPUT ------ -When there are no conflicts, the output of this command is usable as -input to `git update-ref --stdin`. It is of the form: +By default, or with `--ref-action=update`, this command produces no output on +success, as refs are updated directly using an atomic transaction. + +When using `--ref-action=print`, the output is usable as input to +`git update-ref --stdin`. It is of the form: update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH} update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH} update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH} where the number of refs updated depends on the arguments passed and -the shape of the history being replayed. When using `--advance`, the -number of refs updated is always one, but for `--onto`, it can be one -or more (rebasing multiple branches simultaneously is supported). +the shape of the history being replayed. When using `--advance` or +`--revert`, the number of refs updated is always one, but for `--onto`, +it can be one or more (rebasing multiple branches simultaneously is +supported). +There is no stderr output on conflicts; see the <<exit-status,EXIT +STATUS>> section below. + +[[exit-status]] EXIT STATUS ----------- @@ -81,6 +128,14 @@ ------------ $ git replay --onto target origin/main..mybranch +------------ + +The refs are updated atomically and no output is produced on success. + +To see what would be updated without actually updating: + +------------ +$ git replay --ref-action=print --onto target origin/main..mybranch update refs/heads/mybranch ${NEW_mybranch_HASH} ${OLD_mybranch_HASH} ------------ @@ -88,33 +143,29 @@ ------------ $ git replay --advance target origin/main..mybranch -update refs/heads/target ${NEW_target_HASH} ${OLD_target_HASH} ------------ Note that the first two examples replay the exact same commits and on top of the exact same new base, they only differ in that the first -provides instructions to make mybranch point at the new commits and -the second provides instructions to make target point at them. +updates mybranch to point at the new commits and the second updates +target to point at them. What if you have a stack of branches, one depending upon another, and you'd really like to rebase the whole set? ------------ $ git replay --contained --onto origin/main origin/main..tipbranch -update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH} -update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH} -update refs/heads/tipbranch ${NEW_tipbranch_HASH} ${OLD_tipbranch_HASH} ------------ +All three branches (`branch1`, `branch2`, and `tipbranch`) are updated +atomically. + When calling `git replay`, one does not need to specify a range of commits to replay using the syntax `A..B`; any range expression will do: ------------ $ git replay --onto origin/main ^base branch1 branch2 branch3 -update refs/heads/branch1 ${NEW_branch1_HASH} ${OLD_branch1_HASH} -update refs/heads/branch2 ${NEW_branch2_HASH} ${OLD_branch2_HASH} -update refs/heads/branch3 ${NEW_branch3_HASH} ${OLD_branch3_HASH} ------------ This will simultaneously rebase `branch1`, `branch2`, and `branch3`, @@ -122,6 +173,21 @@ `origin/main`. These three branches may have commits on top of `base` that they have in common, but that does not need to be the case. +To revert commits on a branch: + +------------ +$ git replay --revert main topic~2..topic +------------ + +This reverts the last two commits from `topic`, creating revert commits on +top of `main`, and updates `main` to point at the result. This is useful when +commits from `topic` were previously merged or cherry-picked into `main` and +need to be undone. + +NOTE: For reverting an entire merge request as a single commit (rather than +commit-by-commit), consider using `git merge-tree --merge-base $TIP HEAD $BASE` +which can avoid unnecessary merge conflicts. + GIT --- Part of the linkgit:git[1] suite
diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc index ce43cb1..42262c1 100644 --- a/Documentation/git-repo.adoc +++ b/Documentation/git-repo.adoc
@@ -8,8 +8,9 @@ SYNOPSIS -------- [synopsis] -git repo info [--format=(keyvalue|nul)] [-z] [<key>...] -git repo structure [--format=(table|keyvalue|nul)] +git repo info [--format=(lines|nul) | -z] [--all | <key>...] +git repo info --keys [--format=(lines|nul) | -z] +git repo structure [--format=(table|lines|nul) | -z] DESCRIPTION ----------- @@ -19,38 +20,51 @@ COMMANDS -------- -`info [--format=(keyvalue|nul)] [-z] [<key>...]`:: +`info [--format=(lines|nul) | -z] [--all | <key>...]`:: Retrieve metadata-related information about the current repository. Only the requested data will be returned based on their keys (see "INFO KEYS" section below). + The values are returned in the same order in which their respective keys were -requested. +requested. The `--all` flag requests the values for all the available keys. + The output format can be chosen through the flag `--format`. Two formats are supported: + -`keyvalue`::: - output key-value pairs one per line using the `=` character as + +`lines`::: + Output key-value pairs one per line using the `=` character as the delimiter between the key and the value. Values containing "unusual" characters are quoted as explained for the configuration variable `core.quotePath` (see linkgit:git-config[1]). This is the default. `nul`::: - similar to `keyvalue`, but using a newline character as the delimiter - between the key and the value and using a NUL character after each value. + Similar to `lines`, but using a newline character as the delimiter + between the key and the value and using a _NUL_ character after each value. This format is better suited for being parsed by another applications than - `keyvalue`. Unlike in the `keyvalue` format, the values are never quoted. + `lines`. Unlike in the `lines` format, the values are never quoted. + `-z` is an alias for `--format=nul`. -`structure [--format=(table|keyvalue|nul)]`:: +`info --keys [--format=(lines|nul) | -z]`:: + List all the available keys, one per line. The output format can be chosen + through the flag `--format`. The following formats are supported: ++ +`lines`::: + Output the keys one per line. This is the default. + +`nul`::: + Similar to `lines`, but using a _NUL_ character after each value. + +`structure [--format=(table|lines|nul) | -z]`:: Retrieve statistics about the current repository structure. The following kinds of information are reported: + * Reference counts categorized by type * Reachable object counts categorized by type - +* Total inflated size of reachable objects by type +* Total disk size of reachable objects by type +* Largest reachable objects in the repository by type + The output format can be chosen through the flag `--format`. Three formats are supported: @@ -60,18 +74,20 @@ change and is not intended for machine parsing. This is the default format. -`keyvalue`::: +`lines`::: Each line of output contains a key-value pair for a repository stat. The '=' character is used to delimit between the key and the value. Values containing "unusual" characters are quoted as explained for the configuration variable `core.quotePath` (see linkgit:git-config[1]). `nul`::: - Similar to `keyvalue`, but uses a NUL character to delimit between + Similar to `lines`, but uses a _NUL_ character to delimit between key-value pairs instead of a newline. Also uses a newline character as the delimiter between the key and value instead of '='. Unlike the - `keyvalue` format, values containing "unusual" characters are never + `lines` format, values containing "unusual" characters are never quoted. ++ +`-z` is an alias for `--format=nul`. INFO KEYS ---------
diff --git a/Documentation/git-rerere.adoc b/Documentation/git-rerere.adoc index 992b469..99f967b 100644 --- a/Documentation/git-rerere.adoc +++ b/Documentation/git-rerere.adoc
@@ -32,7 +32,7 @@ -------- Normally, 'git rerere' is run without arguments or user-intervention. -However, it has several commands that allow it to interact with +However, it has several commands that allow users to interact with its working state. 'clear':: @@ -44,7 +44,7 @@ 'forget' <pathspec>:: Reset the conflict resolutions which rerere has recorded for the current -conflict in <pathspec>. +conflict in paths that match <pathspec>. 'diff'::
diff --git a/Documentation/git-reset.adoc b/Documentation/git-reset.adoc index 3b9ba9a..5023b50 100644 --- a/Documentation/git-reset.adoc +++ b/Documentation/git-reset.adoc
@@ -3,86 +3,67 @@ NAME ---- -git-reset - Reset current HEAD to the specified state +git-reset - Set `HEAD` or the index to a known state SYNOPSIS -------- [synopsis] +git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>] git reset [-q] [<tree-ish>] [--] <pathspec>... git reset [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>] git reset (--patch | -p) [<tree-ish>] [--] [<pathspec>...] -git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [<commit>] DESCRIPTION ----------- -In the first three forms, copy entries from _<tree-ish>_ to the index. -In the last form, set the current branch head (`HEAD`) to _<commit>_, -optionally modifying index and working tree to match. -The _<tree-ish>_/_<commit>_ defaults to `HEAD` in all forms. +`git reset` does either of the following: -`git reset [-q] [<tree-ish>] [--] <pathspec>...`:: -`git reset [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]`:: - These forms reset the index entries for all paths that match the - _<pathspec>_ to their state at _<tree-ish>_. (It does not affect - the working tree or the current branch.) -+ -This means that `git reset <pathspec>` is the opposite of `git add -<pathspec>`. This command is equivalent to -`git restore [--source=<tree-ish>] --staged <pathspec>...`. -+ -After running `git reset <pathspec>` to update the index entry, you can -use linkgit:git-restore[1] to check the contents out of the index to -the working tree. Alternatively, using linkgit:git-restore[1] -and specifying a commit with `--source`, you -can copy the contents of a path out of a commit to the index and to the -working tree in one go. - -`git reset (--patch | -p) [<tree-ish>] [--] [<pathspec>...]`:: - Interactively select hunks in the difference between the index - and _<tree-ish>_ (defaults to `HEAD`). The chosen hunks are applied - in reverse to the index. -+ -This means that `git reset -p` is the opposite of `git add -p`, i.e. -you can use it to selectively reset hunks. See the "Interactive Mode" -section of linkgit:git-add[1] to learn how to operate the `--patch` mode. +1. `git reset [<mode>] <commit>` changes which commit `HEAD` points to. This + makes it possible to undo various Git operations, for example commit, merge, + rebase, and pull. +2. When you specify files or directories or pass `--patch`, `git reset` updates + the staged version of the specified files. `git reset [<mode>] [<commit>]`:: - This form resets the current branch head to _<commit>_ and - possibly updates the index (resetting it to the tree of _<commit>_) and - the working tree depending on _<mode>_. Before the operation, `ORIG_HEAD` - is set to the tip of the current branch. If _<mode>_ is omitted, - defaults to `--mixed`. The _<mode>_ must be one of the following: + Set the current branch head (`HEAD`) to point at _<commit>_. + Depending on _<mode>_, also update the working directory and/or index + to match the contents of _<commit>_. + _<commit>_ defaults to `HEAD`. + Before the operation, `ORIG_HEAD` is set to the tip of the current branch. + --- -`--soft`:: - Does not touch the index file or the working tree at all (but - resets the head to _<commit>_, just like all modes do). This leaves - all your changed files "Changes to be committed", as `git status` - would put it. +The _<mode>_ must be one of the following (default `--mixed`): ++ +-- `--mixed`:: - Resets the index but not the working tree (i.e., the changed files - are preserved but not marked for commit) and reports what has not - been updated. This is the default action. + Leave your working directory unchanged. + Update the index to match the new `HEAD`, so nothing will be staged. + -If `-N` is specified, removed paths are marked as intent-to-add (see +If `-N` is specified, mark removed paths as intent-to-add (see linkgit:git-add[1]). +`--soft`:: + Leave your working tree files and the index unchanged. + For example, if you have no staged changes, you can use + `git reset --soft HEAD~5; git commit` + to combine the last 5 commits into 1 commit. This works even with + changes in the working tree, which are left untouched, but such usage + can lead to confusion. + `--hard`:: - Resets the index and working tree. Any changes to tracked files in the - working tree since _<commit>_ are discarded. Any untracked files or - directories in the way of writing any tracked files are simply deleted. + Overwrite all files and directories with the version from _<commit>_, + and may overwrite untracked files. Tracked files not in _<commit>_ are + removed so that the working tree matches _<commit>_. + Update the index to match the new `HEAD`, so nothing will be staged. `--merge`:: - Resets the index and updates the files in the working tree that are - different between _<commit>_ and `HEAD`, but keeps those which are + Reset the index and update the files in the working tree that are + different between _<commit>_ and `HEAD`, but keep those which are different between the index and working tree (i.e. which have changes which have not been added). + Mainly exists to reset unmerged index entries, like those left behind by + `git am -3` or `git switch -m` in certain situations. If a file that is different between _<commit>_ and the index has unstaged changes, reset is aborted. -+ -In other words, `--merge` does something like a `git read-tree -u -m <commit>`, -but carries forward unmerged index entries. `--keep`:: Resets index entries and updates files in the working tree that are @@ -98,6 +79,28 @@ the submodules' `HEAD` to be detached at that commit. -- +`git reset [-q] [<tree-ish>] [--] <pathspec>...`:: +`git reset [-q] [--pathspec-from-file=<file> [--pathspec-file-nul]] [<tree-ish>]`:: + For all specified files or directories, set the staged version to + the version from the given commit or tree (which defaults to `HEAD`). ++ +This means that `git reset <pathspec>` is the opposite of `git add +<pathspec>`: it unstages all changes to the specified file(s) or +directories. This is equivalent to `git restore --staged <pathspec>...`. ++ +In this mode, `git reset` updates only the index (without updating the `HEAD` or +working tree files). If you want to update the files as well as the index +entries, use linkgit:git-restore[1]. + +`git reset (--patch | -p) [<tree-ish>] [--] [<pathspec>...]`:: + Interactively select changes from the difference between the index + and the specified commit or tree (which defaults to `HEAD`). + The index is modified using the chosen changes. ++ +This means that `git reset -p` is the opposite of `git add -p`, i.e. +you can use it to selectively unstage changes. See the "Interactive Mode" +section of linkgit:git-add[1] to learn how to use the `--patch` option. + See "Reset, restore and revert" in linkgit:git[1] for the differences between the three commands.
diff --git a/Documentation/git-send-email.adoc b/Documentation/git-send-email.adoc index 263b977..dea3b86 100644 --- a/Documentation/git-send-email.adoc +++ b/Documentation/git-send-email.adoc
@@ -208,7 +208,7 @@ for your own case. Default is the value of `sendemail.smtpEncryption`. --smtp-domain=<FQDN>:: - Specifies the Fully Qualified Domain Name (FQDN) used in the + Specify the Fully Qualified Domain Name (FQDN) used in the HELO/EHLO command to the SMTP server. Some servers require the FQDN to match your IP address. If not set, `git send-email` attempts to determine your FQDN automatically. Default is the value of @@ -245,7 +245,7 @@ Disable SMTP authentication. Short hand for `--smtp-auth=none`. --smtp-server=<host>:: - If set, specifies the outgoing SMTP server to use (e.g. + Specify the outgoing SMTP server to use (e.g. `smtp.example.com` or a raw IP address). If unspecified, and if `--sendmail-cmd` is also unspecified, the default is to search for `sendmail` in `/usr/sbin`, `/usr/lib` and `$PATH` if such a @@ -258,7 +258,7 @@ instead. --smtp-server-port=<port>:: - Specifies a port different from the default port (SMTP + Specify a port different from the default port (SMTP servers typically listen to smtp port 25, but may also listen to submission port 587, or the common SSL smtp port 465); symbolic port names (e.g. `submission` instead of 587) @@ -266,7 +266,7 @@ `sendemail.smtpServerPort` configuration variable. --smtp-server-option=<option>:: - If set, specifies the outgoing SMTP server option to use. + Specify the outgoing SMTP server option to use. Default value can be specified by the `sendemail.smtpServerOption` configuration option. + @@ -277,7 +277,7 @@ --smtp-ssl:: Legacy alias for `--smtp-encryption ssl`. ---smtp-ssl-cert-path:: +--smtp-ssl-cert-path <path>:: Path to a store of trusted CA certificates for SMTP SSL/TLS certificate validation (either a directory that has been processed by `c_rehash`, or a single file containing one or more PEM format @@ -290,6 +290,25 @@ variable, if set, or the backing SSL library's compiled-in default otherwise (which should be the best choice on most platforms). +--smtp-ssl-client-cert <path>:: + Path to the client certificate file to present if requested by the + server. This option is required when the server is set up to verify + client certificates. If the corresponding private key is not included in + the file, it must be supplied using the `sendemail.smtpSSLClientKey` + configuration variable or the `--smtp-ssl-client-key` option. Defaults + to the value of the `sendemail.smtpSSLClientCert` configuration + variable, if set. + +--smtp-ssl-client-key <path>:: + Path to the client private key file that corresponds to the client + certificate. To avoid misconfiguration, this option must be used in + conjunction with the `sendemail.smtpSSLClientKey` configuration variable + or the `--smtp-ssl-client-cert` option. If the client key is included in + the client certificate, the choice of private key depends on the format + of the certificate. Visit https://metacpan.org/pod/IO::Socket::SSL for + more details. Defaults to the value of the `sendemail.smtpSSLClientKey` + configuration variable, if set. + --smtp-user=<user>:: Username for SMTP-AUTH. Default is the value of `sendemail.smtpUser`; if a username is not specified (with `--smtp-user` or `sendemail.smtpUser`), @@ -321,7 +340,6 @@ If disabled with `--no-use-imap-only`, the emails will be sent like usual. Disabled by default, but the `sendemail.useImapOnly` configuration variable can be used to enable it. - + This feature requires setting up `git imap-send`. See linkgit:git-imap-send[1] for instructions. @@ -347,11 +365,11 @@ --no-to:: --no-cc:: --no-bcc:: - Clears any list of `To:`, `Cc:`, `Bcc:` addresses previously + Clear any list of `To:`, `Cc:`, `Bcc:` addresses previously set via config. --no-identity:: - Clears the previously read value of `sendemail.identity` set + Clear the previously read value of `sendemail.identity` set via config, if any. --to-cmd=<command>:: @@ -436,7 +454,7 @@ + Default is the value of `sendemail.suppressCc` configuration value; if that is unspecified, default to `self` if `--suppress-from` is -specified, as well as `body` if `--no-signed-off-cc` is specified. +specified, as well as `body` if `--no-signed-off-by-cc` is specified. --suppress-from:: --no-suppress-from:: @@ -510,12 +528,12 @@ Currently, validation means the following: + -- - * Invoke the sendemail-validate hook if present (see linkgit:githooks[5]). - * Warn of patches that contain lines longer than - 998 characters unless a suitable transfer encoding - (`auto`, `base64`, or `quoted-printable`) is used; - this is due to SMTP limits as described by - https://www.ietf.org/rfc/rfc5322.txt. +* Invoke the sendemail-validate hook if present (see linkgit:githooks[5]). +* Warn of patches that contain lines longer than + 998 characters unless a suitable transfer encoding + (`auto`, `base64`, or `quoted-printable`) is used; + this is due to SMTP limits as described by + https://www.ietf.org/rfc/rfc5322.txt. -- + Default is the value of `sendemail.validate`; if this is not set, @@ -693,6 +711,11 @@ - https://github.com/AdityaGarg8/git-credential-email[git-msgraph] (cross platform client that can send emails using the Microsoft Graph API) +CAVEATS +------- + +include::format-patch-caveats.adoc[] + SEE ALSO -------- linkgit:git-format-patch[1], linkgit:git-imap-send[1], mbox(5)
diff --git a/Documentation/git-shortlog.adoc b/Documentation/git-shortlog.adoc index aa92800..a11b57c 100644 --- a/Documentation/git-shortlog.adoc +++ b/Documentation/git-shortlog.adoc
@@ -64,9 +64,6 @@ example, if your project uses `Reviewed-by` trailers, you might want to see who has been reviewing with `git shortlog -ns --group=trailer:reviewed-by`. - - `format:<format>`, any string accepted by the `--format` option of - 'git log'. (See the "PRETTY FORMATS" section of - linkgit:git-log[1].) + Note that commits that do not include the trailer will not be counted. Likewise, commits with multiple trailers (e.g., multiple signoffs) may @@ -77,6 +74,10 @@ identity. If successful, the mailmap is applied and the email is omitted unless the `--email` option is specified. If the value cannot be parsed as an identity, it will be taken literally and completely. + + - `format:<format>`, any string accepted by the `--format` option of + 'git log'. (See the "PRETTY FORMATS" section of + linkgit:git-log[1].) -- + If `--group` is specified multiple times, commits are counted under each
diff --git a/Documentation/git-show.adoc b/Documentation/git-show.adoc index 51044c8..3b180e8 100644 --- a/Documentation/git-show.adoc +++ b/Documentation/git-show.adoc
@@ -8,8 +8,8 @@ SYNOPSIS -------- -[verse] -'git show' [<options>] [<object>...] +[synopsis] +git show [<options>] [<object>...] DESCRIPTION ----------- @@ -17,16 +17,16 @@ For commits it shows the log message and textual diff. It also presents the merge commit in a special format as produced by -'git diff-tree --cc'. +`git diff-tree --cc`. For tags, it shows the tag message and the referenced objects. -For trees, it shows the names (equivalent to 'git ls-tree' -with --name-only). +For trees, it shows the names (equivalent to `git ls-tree` +with `--name-only`). For plain blobs, it shows the plain contents. -Some options that 'git log' command understands can be used to +Some options that `git log` command understands can be used to control how the changes the commit introduces are shown. This manual page describes only the most frequently used options. @@ -34,8 +34,8 @@ OPTIONS ------- -<object>...:: - The names of objects to show (defaults to 'HEAD'). +`<object>...`:: + The names of objects to show (defaults to `HEAD`). For a more complete list of ways to spell object names, see "SPECIFYING REVISIONS" section in linkgit:gitrevisions[7].
diff --git a/Documentation/git-stage.adoc b/Documentation/git-stage.adoc index 2f6aaa7..753a817 100644 --- a/Documentation/git-stage.adoc +++ b/Documentation/git-stage.adoc
@@ -8,8 +8,8 @@ SYNOPSIS -------- -[verse] -'git stage' <arg>... +[synopsis] +git stage <arg>... DESCRIPTION
diff --git a/Documentation/git-stash.adoc b/Documentation/git-stash.adoc index 235d57d..b05c990 100644 --- a/Documentation/git-stash.adoc +++ b/Documentation/git-stash.adoc
@@ -14,10 +14,10 @@ git stash pop [--index] [-q | --quiet] [<stash>] git stash apply [--index] [-q | --quiet] [<stash>] git stash branch <branchname> [<stash>] -git stash [push [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet] +git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet] [-u | --include-untracked] [-a | --all] [(-m | --message) <message>] [--pathspec-from-file=<file> [--pathspec-file-nul]] - [--] [<pathspec>...]] + [--] [<pathspec>...] git stash save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet] [-u | --include-untracked] [-a | --all] [<message>] git stash clear @@ -60,10 +60,8 @@ the description along with the stashed state. + For quickly making a snapshot, you can omit "push". In this mode, -non-option arguments are not allowed to prevent a misspelled -subcommand from making an unwanted stash entry. The two exceptions to this -are `stash -p` which acts as alias for `stash push -p` and pathspec elements, -which are allowed after a double hyphen `--` for disambiguation. +pathspec elements are only allowed after a double hyphen `--` +to prevent a misspelled subcommand from making an unwanted stash entry. `save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-u | --include-untracked] [-a | --all] [-q | --quiet] [<message>]`::
diff --git a/Documentation/git-status.adoc b/Documentation/git-status.adoc index 9a37688..9acca52 100644 --- a/Documentation/git-status.adoc +++ b/Documentation/git-status.adoc
@@ -8,8 +8,9 @@ SYNOPSIS -------- -[verse] -'git status' [<options>] [--] [<pathspec>...] + +[synopsis] +git status [<options>] [--] [<pathspec>...] DESCRIPTION ----------- @@ -18,57 +19,57 @@ tree and the index file, and paths in the working tree that are not tracked by Git (and are not ignored by linkgit:gitignore[5]). The first are what you _would_ commit by running `git commit`; the second and -third are what you _could_ commit by running 'git add' before running +third are what you _could_ commit by running `git add` before running `git commit`. OPTIONS ------- --s:: ---short:: +`-s`:: +`--short`:: Give the output in the short-format. --b:: ---branch:: +`-b`:: +`--branch`:: Show the branch and tracking info even in short-format. ---show-stash:: +`--show-stash`:: Show the number of entries currently stashed away. ---porcelain[=<version>]:: +`--porcelain[=<version>]`:: Give the output in an easy-to-parse format for scripts. This is similar to the short output, but will remain stable across Git versions and regardless of user configuration. See below for details. + -The version parameter is used to specify the format version. -This is optional and defaults to the original version 'v1' format. +The _<version>_ parameter is used to specify the format version. +This is optional and defaults to the original version `v1` format. ---long:: +`--long`:: Give the output in the long-format. This is the default. --v:: ---verbose:: +`-v`:: +`--verbose`:: In addition to the names of files that have been changed, also show the textual changes that are staged to be committed (i.e., like the output of `git diff --cached`). If `-v` is specified twice, then also show the changes in the working tree that have not yet been staged (i.e., like the output of `git diff`). --u[<mode>]:: ---untracked-files[=<mode>]:: +`-u[<mode>]`:: +`--untracked-files[=<mode>]`:: Show untracked files. + -- The mode parameter is used to specify the handling of untracked files. -It is optional: it defaults to 'all', and if specified, it must be +It is optional: it defaults to `all`, and if specified, it must be stuck to the option (e.g. `-uno`, but not `-u no`). The possible options are: - - 'no' - Show no untracked files. - - 'normal' - Shows untracked files and directories. - - 'all' - Also shows individual files in untracked directories. +`no`:: Show no untracked files. +`normal`:: Show untracked files and directories. +`all`:: Also show individual files in untracked directories. When `-u` option is not used, untracked files and directories are shown (i.e. the same as specifying `normal`), to help you avoid @@ -82,76 +83,78 @@ All usual spellings for Boolean value `true` are taken as `normal` and `false` as `no`. -The default can be changed using the status.showUntrackedFiles +The default can be changed using the `status.showUntrackedFiles` configuration variable documented in linkgit:git-config[1]. -- ---ignore-submodules[=<when>]:: - Ignore changes to submodules when looking for changes. <when> can be - either "none", "untracked", "dirty" or "all", which is the default. - Using "none" will consider the submodule modified when it either contains +`--ignore-submodules[=<when>]`:: + Ignore changes to submodules when looking for changes. _<when>_ can be + either `none`, `untracked`, `dirty` or `all`, which is the default. +`none`;; will consider the submodule modified when it either contains untracked or modified files or its HEAD differs from the commit recorded in the superproject and can be used to override any settings of the - 'ignore' option in linkgit:git-config[1] or linkgit:gitmodules[5]. When - "untracked" is used submodules are not considered dirty when they only + `ignore` option in linkgit:git-config[1] or linkgit:gitmodules[5]. +`untracked`;; submodules are not considered dirty when they only contain untracked content (but they are still scanned for modified - content). Using "dirty" ignores all changes to the work tree of submodules, + content). +`dirty`;; ignore all changes to the work tree of submodules, only changes to the commits stored in the superproject are shown (this was - the behavior before 1.7.0). Using "all" hides all changes to submodules + the behavior before 1.7.0). +`all`;; hide all changes to submodules (and suppresses the output of submodule summaries when the config option `status.submoduleSummary` is set). ---ignored[=<mode>]:: +`--ignored[=<mode>]`:: Show ignored files as well. + -- The mode parameter is used to specify the handling of ignored files. -It is optional: it defaults to 'traditional'. +It is optional: it defaults to `traditional`. The possible options are: - - 'traditional' - Shows ignored files and directories, unless - --untracked-files=all is specified, in which case - individual files in ignored directories are - displayed. - - 'no' - Show no ignored files. - - 'matching' - Shows ignored files and directories matching an - ignore pattern. - -When 'matching' mode is specified, paths that explicitly match an +`traditional`:: Show ignored files and directories, unless +`--untracked-files=all` is specified, in which case + individual files in ignored directories are + displayed. +`no`:: Show no ignored files. +`matching`:: Show ignored files and directories matching an +ignore pattern. ++ +Paths that explicitly match an ignored pattern are shown. If a directory matches an ignore pattern, then it is shown, but not paths contained in the ignored directory. If a directory does not match an ignore pattern, but all contents are ignored, then the directory is not shown, but all contents are shown. -- --z:: - Terminate entries with NUL, instead of LF. This implies +`-z`:: + Terminate entries with _NUL_, instead of _LF_. This implies the `--porcelain=v1` output format if no other format is given. ---column[=<options>]:: ---no-column:: +`--column[=<options>]`:: +`--no-column`:: Display untracked files in columns. See configuration variable `column.status` for option syntax. `--column` and `--no-column` - without options are equivalent to 'always' and 'never' + without options are equivalent to `always` and `never` respectively. ---ahead-behind:: ---no-ahead-behind:: +`--ahead-behind`:: +`--no-ahead-behind`:: Display or do not display detailed ahead/behind counts for the - branch relative to its upstream branch. Defaults to true. + branch relative to its upstream branch. Defaults to `true`. ---renames:: ---no-renames:: +`--renames`:: +`--no-renames`:: Turn on/off rename detection regardless of user configuration. See also linkgit:git-diff[1] `--no-renames`. ---find-renames[=<n>]:: +`--find-renames[=<n>]`:: Turn on rename detection, optionally setting the similarity threshold. See also linkgit:git-diff[1] `--find-renames`. -<pathspec>...:: +`<pathspec>...`:: See the 'pathspec' entry in linkgit:gitglossary[7]. OUTPUT @@ -173,12 +176,12 @@ In the short-format, the status of each path is shown as one of these forms - XY PATH - XY ORIG_PATH -> PATH + <xy> <path> + <xy> <orig-path> -> <path> -where `ORIG_PATH` is where the renamed/copied contents came -from. `ORIG_PATH` is only shown when the entry is renamed or -copied. The `XY` is a two-letter status code. +where _<orig-path>_ is where the renamed/copied contents came +from. _<orig-path>_ is only shown when the entry is renamed or +copied. The _<xy>_ is a two-letter status code `XY`. The fields (including the `->`) are separated from each other by a single space. If a filename contains whitespace or other nonprintable @@ -187,7 +190,7 @@ interior special characters backslash-escaped. There are three different types of states that are shown using this format, and -each one uses the `XY` syntax differently: +each one uses the _<xy>_ syntax differently: * When a merge is occurring and the merge was successful, or outside of a merge situation, `X` shows the status of the index and `Y` shows the status of the @@ -207,60 +210,59 @@ these characters are used for `X` and `Y` fields for the first two sections that show tracked paths: -* ' ' = unmodified -* 'M' = modified -* 'T' = file type changed (regular file, symbolic link or submodule) -* 'A' = added -* 'D' = deleted -* 'R' = renamed -* 'C' = copied (if config option status.renames is set to "copies") -* 'U' = updated but unmerged +' ':: unmodified +`M`:: modified +`T`:: file type changed (regular file, symbolic link or submodule) +`A`:: added +`D`:: deleted +`R`:: renamed +`C`:: copied (if config option status.renames is set to "copies") +`U`:: updated but unmerged -.... -X Y Meaning -------------------------------------------------- - [AMD] not updated -M [ MTD] updated in index -T [ MTD] type changed in index -A [ MTD] added to index -D deleted from index -R [ MTD] renamed in index -C [ MTD] copied in index -[MTARC] index and work tree matches -[ MTARC] M work tree changed since index -[ MTARC] T type changed in work tree since index -[ MTARC] D deleted in work tree - R renamed in work tree - C copied in work tree -------------------------------------------------- -D D unmerged, both deleted -A U unmerged, added by us -U D unmerged, deleted by them -U A unmerged, added by them -D U unmerged, deleted by us -A A unmerged, both added -U U unmerged, both modified -------------------------------------------------- -? ? untracked -! ! ignored -------------------------------------------------- -.... +[cols="^1m,^1m,<2",options="header"] +|=== +|X | Y |Meaning +| |[AMD] |not updated +|M |[ MTD] |updated in index +|T |[ MTD] |type changed in index +|A |[ MTD] |added to index +|D | |deleted from index +|R |[ MTD] |renamed in index +|C |[ MTD] |copied in index +|[MTARC] | |index and work tree matches +|[ MTARC] |M |work tree changed since index +|[ MTARC] |T |type changed in work tree since index +|[ MTARC] |D |deleted in work tree +| |R |renamed in work tree +| |C |copied in work tree +|D |D |unmerged, both deleted +|A |U |unmerged, added by us +|U |D |unmerged, deleted by them +|U |A |unmerged, added by them +|D |U |unmerged, deleted by us +|A |A |unmerged, both added +|U |U |unmerged, both modified +|? |? |untracked +|! |! |ignored +|=== Submodules have more state and instead report -* 'M' = the submodule has a different HEAD than recorded in the index -* 'm' = the submodule has modified content -* '?' = the submodule has untracked files +`M`:: the submodule has a different HEAD than recorded in the index +`m`:: the submodule has modified content +`?`:: the submodule has untracked files This is since modified content or untracked files in a submodule cannot be added via `git add` in the superproject to prepare a commit. -'m' and '?' are applied recursively. For example if a nested submodule -in a submodule contains an untracked file, this is reported as '?' as well. +`m` and `?` are applied recursively. For example if a nested submodule +in a submodule contains an untracked file, this is reported as `?` as well. -If -b is used the short-format status is preceded by a line +If `-b` is used the short-format status is preceded by a line - ## branchname tracking info +[synopsis] +{empty}## <branchname> <tracking-info> + Porcelain Format Version 1 ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -271,16 +273,16 @@ The description of the short format above also describes the porcelain format, with a few exceptions: -1. The user's color.status configuration is not respected; color will +1. The user's `color.status` configuration is not respected; color will always be off. -2. The user's status.relativePaths configuration is not respected; paths +2. The user's `status.relativePaths` configuration is not respected; paths shown will always be relative to the repository root. -There is also an alternate -z format recommended for machine parsing. In +There is also an alternate `-z` format recommended for machine parsing. In that format, the status field is the same, but some other things -change. First, the '\->' is omitted from rename entries and the field -order is reversed (e.g 'from \-> to' becomes 'to from'). Second, a NUL +change. First, the `->` is omitted from rename entries and the field +order is reversed (e.g `from -> to` becomes `to from`). Second, a _NUL_ (ASCII 0) follows each filename, replacing space as a field separator and the terminating newline (but a space still separates the status field from the first filename). Third, filenames containing special @@ -296,7 +298,7 @@ the worktree and changed items. Version 2 also defines an extensible set of easy to parse optional headers. -Header lines start with "#" and are added in response to specific +Header lines start with `#` and are added in response to specific command line arguments. Parsers should ignore headers they don't recognize. @@ -306,16 +308,15 @@ If `--branch` is given, a series of header lines are printed with information about the current branch. -.... -Line Notes ------------------------------------------------------------- -# branch.oid <commit> | (initial) Current commit. -# branch.head <branch> | (detached) Current branch. -# branch.upstream <upstream-branch> If upstream is set. -# branch.ab +<ahead> -<behind> If upstream is set and - the commit is present. ------------------------------------------------------------- -.... +[cols="<1,<1",options="header"] +|=== +|Line |Notes +|`# branch.oid <commit> \| (initial)` |Current commit. +|`# branch.head <branch> \| (detached)` |Current branch. +|`# branch.upstream <upstream-branch>` |If upstream is set. +|`# branch.ab +<ahead> -<behind>` |If upstream is set and + the commit is present. +|=== Stash Information ^^^^^^^^^^^^^^^^^ @@ -336,66 +337,73 @@ Ordinary changed entries have the following format: - 1 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <path> +[synopsis] +1 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <path> Renamed or copied entries have the following format: - 2 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <X><score> <path><sep><origPath> +[synopsis] +2 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <X><score> <path><sep><origPath> -.... -Field Meaning --------------------------------------------------------- -<XY> A 2 character field containing the staged and - unstaged XY values described in the short format, - with unchanged indicated by a "." rather than - a space. -<sub> A 4 character field describing the submodule state. - "N..." when the entry is not a submodule. - "S<c><m><u>" when the entry is a submodule. - <c> is "C" if the commit changed; otherwise ".". - <m> is "M" if it has tracked changes; otherwise ".". - <u> is "U" if there are untracked changes; otherwise ".". -<mH> The octal file mode in HEAD. -<mI> The octal file mode in the index. -<mW> The octal file mode in the worktree. -<hH> The object name in HEAD. -<hI> The object name in the index. -<X><score> The rename or copy score (denoting the percentage - of similarity between the source and target of the - move or copy). For example "R100" or "C75". -<path> The pathname. In a renamed/copied entry, this - is the target path. -<sep> When the `-z` option is used, the 2 pathnames are separated - with a NUL (ASCII 0x00) byte; otherwise, a tab (ASCII 0x09) - byte separates them. -<origPath> The pathname in the commit at HEAD or in the index. - This is only present in a renamed/copied entry, and - tells where the renamed/copied contents came from. --------------------------------------------------------- -.... +[cols="<1,<1a",options="header"] +|=== +|Field | Meaning + +|_<XY>_ +|A 2 character field containing the staged and +unstaged XY values described in the short format, +with unchanged indicated by a "." rather than +a space. +|_<sub>_ +|A 4 character field describing the submodule state. +"N..." when the entry is not a submodule. +`S<c><m><u>` when the entry is a submodule. + +* _<c>_ is "C" if the commit changed; otherwise ".". +* _<m>_ is "M" if it has tracked changes; otherwise ".". +* _<u>_ is "U" if there are untracked changes; otherwise ".". +|_<mH>_ |The octal file mode in HEAD. +|_<mI>_ |The octal file mode in the index. +|_<mW>_ |The octal file mode in the worktree. +|_<hH>_ |The object name in HEAD. +|_<hI>_ |The object name in the index. +|_<X><score>_ |The rename or copy score (denoting the percentage +of similarity between the source and target of the +move or copy). For example "R100" or "C75". +|_<path>_ +|The pathname. In a renamed/copied entry, this is the target path. +|_<sep>_ +|When the `-z` option is used, the 2 pathnames are separated +with a _NUL_ (ASCII 0x00) byte; otherwise, a _TAB_ (ASCII 0x09) +byte separates them. +|_<origPath>_ +|The pathname in the commit at HEAD or in the index. +This is only present in a renamed/copied entry, and +tells where the renamed/copied contents came from. +|=== Unmerged entries have the following format; the first character is a "u" to distinguish from ordinary changed entries. - u <XY> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path> +[synopsis] +u <XY> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path> -.... -Field Meaning --------------------------------------------------------- -<XY> A 2 character field describing the conflict type +[cols="<1,<1a",options="header"] +|=== +|Field |Meaning +|_<XY>_ |A 2 character field describing the conflict type as described in the short format. -<sub> A 4 character field describing the submodule state +|_<sub>_ |A 4 character field describing the submodule state as described above. -<m1> The octal file mode in stage 1. -<m2> The octal file mode in stage 2. -<m3> The octal file mode in stage 3. -<mW> The octal file mode in the worktree. -<h1> The object name in stage 1. -<h2> The object name in stage 2. -<h3> The object name in stage 3. -<path> The pathname. --------------------------------------------------------- -.... +|_<m1>_ |The octal file mode in stage 1. +|_<m2>_ |The octal file mode in stage 2. +|_<m3>_ |The octal file mode in stage 3. +|_<mW>_ |The octal file mode in the worktree. +|_<h1>_ |The object name in stage 1. +|_<h2>_ |The object name in stage 2. +|_<h3>_ |The object name in stage 3. +|_<path>_ |The pathname. +|=== Other Items ^^^^^^^^^^^ @@ -416,7 +424,7 @@ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ When the `-z` option is given, pathnames are printed as is and -without any quoting and lines are terminated with a NUL (ASCII 0x00) +without any quoting and lines are terminated with a _NUL_ (ASCII 0x00) byte. Without the `-z` option, pathnames with "unusual" characters are @@ -439,11 +447,11 @@ If `status.submoduleSummary` is set to a non zero number or true (identical to -1 or an unlimited number), the submodule summary will be enabled for the long format and a summary of commits for modified submodules will be -shown (see --summary-limit option of linkgit:git-submodule[1]). Please note +shown (see `--summary-limit` option of linkgit:git-submodule[1]). Please note that the summary output from the status command will be suppressed for all -submodules when `diff.ignoreSubmodules` is set to 'all' or only for those +submodules when `diff.ignoreSubmodules` is set to `all` or only for those submodules where `submodule.<name>.ignore=all`. To also view the summary for -ignored submodules you can either use the --ignore-submodules=dirty command +ignored submodules you can either use the `--ignore-submodules=dirty` command line option or the 'git submodule summary' command, which shows a similar output but does not honor these settings. @@ -484,7 +492,7 @@ setting this variable to `false` disables the warning message given when enumerating untracked files takes more than 2 seconds. In a large project, it may take longer and the user - may have already accepted the trade off (e.g. using "-uno" may + may have already accepted the trade off (e.g. using `-uno` may not be an acceptable option for the user), in which case, there is no point issuing the warning message, and in such a case, disabling the warning may be the best.
diff --git a/Documentation/git-submodule.adoc b/Documentation/git-submodule.adoc index 95beaee..722d827 100644 --- a/Documentation/git-submodule.adoc +++ b/Documentation/git-submodule.adoc
@@ -8,19 +8,19 @@ SYNOPSIS -------- -[verse] -'git submodule' [--quiet] [--cached] -'git submodule' [--quiet] add [<options>] [--] <repository> [<path>] -'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...] -'git submodule' [--quiet] init [--] [<path>...] -'git submodule' [--quiet] deinit [-f|--force] (--all|[--] <path>...) -'git submodule' [--quiet] update [<options>] [--] [<path>...] -'git submodule' [--quiet] set-branch [<options>] [--] <path> -'git submodule' [--quiet] set-url [--] <path> <newurl> -'git submodule' [--quiet] summary [<options>] [--] [<path>...] -'git submodule' [--quiet] foreach [--recursive] <command> -'git submodule' [--quiet] sync [--recursive] [--] [<path>...] -'git submodule' [--quiet] absorbgitdirs [--] [<path>...] +[synopsis] +git submodule [--quiet] [--cached] +git submodule [--quiet] add [<options>] [--] <repository> [<path>] +git submodule [--quiet] status [--cached] [--recursive] [--] [<path>...] +git submodule [--quiet] init [--] [<path>...] +git submodule [--quiet] deinit [-f|--force] (--all|[--] <path>...) +git submodule [--quiet] update [<options>] [--] [<path>...] +git submodule [--quiet] set-branch [<options>] [--] <path> +git submodule [--quiet] set-url [--] <path> <newurl> +git submodule [--quiet] summary [<options>] [--] [<path>...] +git submodule [--quiet] foreach [--recursive] <command> +git submodule [--quiet] sync [--recursive] [--] [<path>...] +git submodule [--quiet] absorbgitdirs [--] [<path>...] DESCRIPTION @@ -34,34 +34,34 @@ With no arguments, shows the status of existing submodules. Several subcommands are available to perform operations on the submodules. -add [-b <branch>] [-f|--force] [--name <name>] [--reference <repository>] [--ref-format <format>] [--depth <depth>] [--] <repository> [<path>]:: +`add [-b <branch>] [-f | --force] [--name <name>] [--reference <repository>] [--ref-format <format>] [--depth <depth>] [--] <repository> [<path>]`:: Add the given repository as a submodule at the given path to the changeset to be committed next to the current project: the current project is termed the "superproject". + -<repository> is the URL of the new submodule's origin repository. -This may be either an absolute URL, or (if it begins with ./ -or ../), the location relative to the superproject's default remote -repository (Please note that to specify a repository 'foo.git' -which is located right next to a superproject 'bar.git', you'll +_<repository>_ is the URL of the new submodule's `origin` repository. +This may be either an absolute URL, or (if it begins with `./` +or `../`), the location relative to the superproject's default remote +repository (Please note that to specify a repository `foo.git` +which is located right next to a superproject `bar.git`, you'll have to use `../foo.git` instead of `./foo.git` - as one might expect when following the rules for relative URLs - because the evaluation of relative URLs in Git is identical to that of relative directories). + The default remote is the remote of the remote-tracking branch of the current branch. If no such remote-tracking branch exists or -the HEAD is detached, "origin" is assumed to be the default remote. +the `HEAD` is detached, `origin` is assumed to be the default remote. If the superproject doesn't have a default remote configured the superproject is its own authoritative upstream and the current working directory is used instead. + -The optional argument <path> is the relative location for the cloned -submodule to exist in the superproject. If <path> is not given, the -canonical part of the source repository is used ("repo" for -"/path/to/repo.git" and "foo" for "host.xz:foo/.git"). If <path> +The optional argument _<path>_ is the relative location for the cloned +submodule to exist in the superproject. If _<path>_ is not given, the +canonical part of the source repository is used (`repo` for +`/path/to/repo.git` and `foo` for `host.xz:foo/.git`). If _<path>_ exists and is already a valid Git repository, then it is staged -for commit without cloning. The <path> is also used as the submodule's -logical name in its configuration entries unless `--name` is used +for commit without cloning. The _<path>_ is also used as the submodule's +logical name in its configuration entries unless `--name <name>` is used to specify a logical name. + The given URL is recorded into `.gitmodules` for use by subsequent users @@ -75,10 +75,10 @@ If `--ref-format <format>` is specified, the ref storage format of newly cloned submodules will be set accordingly. -status [--cached] [--recursive] [--] [<path>...]:: +`status [--cached] [--recursive] [--] [<path>...]`:: Show the status of the submodules. This will print the SHA-1 of the currently checked out commit for each submodule, along with the - submodule path and the output of 'git describe' for the + submodule path and the output of linkgit:git-describe[1] for the SHA-1. Each SHA-1 will possibly be prefixed with `-` if the submodule is not initialized, `+` if the currently checked out submodule commit does not match the SHA-1 found in the index of the containing @@ -91,11 +91,11 @@ submodules, and show their status as well. + If you are only interested in changes of the currently initialized -submodules with respect to the commit recorded in the index or the HEAD, +submodules with respect to the commit recorded in the index or the `HEAD`, linkgit:git-status[1] and linkgit:git-diff[1] will provide that information too (and can also report changes to a submodule's work tree). -init [--] [<path>...]:: +`init [--] [<path>...]`:: Initialize the submodules recorded in the index (which were added and committed elsewhere) by setting `submodule.$name.url` in `.git/config`, using the same setting from `.gitmodules` as @@ -103,7 +103,7 @@ the default remote. If there is no default remote, the current repository will be assumed to be upstream. + -Optional <path> arguments limit which submodules will be initialized. +Optional _<path>_ arguments limit which submodules will be initialized. If no path is specified and submodule.active has been configured, submodules configured to be active will be initialized, otherwise all submodules are initialized. @@ -116,12 +116,12 @@ You can then customize the submodule clone URLs in `.git/config` for your local setup and proceed to `git submodule update`; you can also just use `git submodule update --init` without -the explicit 'init' step if you do not intend to customize +the explicit `init` step if you do not intend to customize any submodule locations. + See the add subcommand for the definition of default remote. -deinit [-f|--force] (--all|[--] <path>...):: +`deinit [-f | --force] (--all|[--] <path>...)`:: Unregister the given submodules, i.e. remove the whole `submodule.$name` section from .git/config together with their work tree. Further calls to `git submodule update`, `git submodule foreach` @@ -139,7 +139,7 @@ that use linkgit:git-rm[1] instead. See linkgit:gitsubmodules[7] for removal options. -update [--init] [--remote] [-N|--no-fetch] [--[no-]recommend-shallow] [-f|--force] [--checkout|--rebase|--merge] [--reference <repository>] [--ref-format <format>] [--depth <depth>] [--recursive] [--jobs <n>] [--[no-]single-branch] [--filter <filter-spec>] [--] [<path>...]:: +`update [--init] [--remote] [-N | --no-fetch] [--[no-]recommend-shallow] [-f | --force] [--checkout | --rebase | --merge] [--reference=<repository>] [--ref-format=<format>] [--depth=<depth>] [--recursive] [--jobs <n>] [--[no-]single-branch] [--filter=<filter-spec>] [--] [<path>...]`:: + -- Update the registered submodules to match what the superproject @@ -148,38 +148,38 @@ the submodules. The "updating" can be done in several ways depending on command line options and the value of `submodule.<name>.update` configuration variable. The command line option takes precedence over -the configuration variable. If neither is given, a 'checkout' is performed. +the configuration variable. If neither is given, a `checkout` is performed. (note: what is in `.gitmodules` file is irrelevant at this point; see `git submodule init` above for how `.gitmodules` is used). -The 'update' procedures supported both from the command line as well as +The `update` procedures supported both from the command line as well as through the `submodule.<name>.update` configuration are: - checkout;; the commit recorded in the superproject will be - checked out in the submodule on a detached HEAD. +`checkout`;; the commit recorded in the superproject will be +checked out in the submodule on a detached `HEAD`. + If `--force` is specified, the submodule will be checked out (using `git checkout --force`), even if the commit specified in the index of the containing repository already matches the commit checked out in the submodule. - rebase;; the current branch of the submodule will be rebased - onto the commit recorded in the superproject. +`rebase`;; the current branch of the submodule will be rebased +onto the commit recorded in the superproject. - merge;; the commit recorded in the superproject will be merged - into the current branch in the submodule. +`merge`;; the commit recorded in the superproject will be merged +into the current branch in the submodule. The following update procedures have additional limitations: - custom command;; mechanism for running arbitrary commands with the - commit ID as an argument. Specifically, if the - `submodule.<name>.update` configuration variable is set to - `!custom command`, the object name of the commit recorded in the - superproject for the submodule is appended to the `custom command` - string and executed. Note that this mechanism is not supported in - the `.gitmodules` file or on the command line. +`!<custom-command>`;; mechanism for running arbitrary commands with the +commit ID as an argument. Specifically, if the +`submodule.<name>.update` configuration variable is set to +`!<custom-command>`, the object name of the commit recorded in the +superproject for the submodule is appended to the _<custom-command>_ +string and executed. Note that this mechanism is not supported in +the `.gitmodules` file or on the command line. - none;; the submodule is not updated. This update procedure is not - allowed on the command line. +`none`;; the submodule is not updated. This update procedure is not +allowed on the command line. If the submodule is not yet initialized, and you just want to use the setting as stored in `.gitmodules`, you can automatically initialize the @@ -195,20 +195,20 @@ applied to the submodule. See linkgit:git-rev-list[1] for details on filter specifications. -- -set-branch (-b|--branch) <branch> [--] <path>:: -set-branch (-d|--default) [--] <path>:: - Sets the default remote tracking branch for the submodule. The +`set-branch (-b|--branch) <branch> [--] <path>`:: +`set-branch (-d|--default) [--] <path>`:: + Set the default remote tracking branch for the submodule. The `--branch` option allows the remote branch to be specified. The - `--default` option removes the submodule.<name>.branch configuration - key, which causes the tracking branch to default to the remote 'HEAD'. + `--default` option removes the `submodule.<name>.branch` configuration + key, which causes the tracking branch to default to the remote `HEAD`. -set-url [--] <path> <newurl>:: - Sets the URL of the specified submodule to <newurl>. Then, it will +`set-url [--] <path> <newurl>`:: + Set the URL of the specified submodule to _<newurl>_. Then, it will automatically synchronize the submodule's new remote URL configuration. -summary [--cached|--files] [(-n|--summary-limit) <n>] [commit] [--] [<path>...]:: - Show commit summary between the given commit (defaults to HEAD) and +`summary [--cached | --files] [(-n|--summary-limit) <n>] [commit] [--] [<path>...]`:: + Show commit summary between the given commit (defaults to `HEAD`) and working tree/index. For a submodule in question, a series of commits in the submodule between the given super project commit and the index or working tree (switched by `--cached`) are shown. If the option @@ -220,27 +220,31 @@ Using the `--submodule=log` option with linkgit:git-diff[1] will provide that information too. -foreach [--recursive] <command>:: - Evaluates an arbitrary shell command in each checked out submodule. - The command has access to the variables $name, $sm_path, $displaypath, - $sha1 and $toplevel: - $name is the name of the relevant submodule section in `.gitmodules`, - $sm_path is the path of the submodule as recorded in the immediate - superproject, $displaypath contains the relative path from the - current working directory to the submodules root directory, - $sha1 is the commit as recorded in the immediate - superproject, and $toplevel is the absolute path to the top-level - of the immediate superproject. - Note that to avoid conflicts with '$PATH' on Windows, the '$path' - variable is now a deprecated synonym of '$sm_path' variable. - Any submodules defined in the superproject but not checked out are - ignored by this command. Unless given `--quiet`, foreach prints the name - of each submodule before evaluating the command. - If `--recursive` is given, submodules are traversed recursively (i.e. - the given shell command is evaluated in nested submodules as well). - A non-zero return from the command in any submodule causes - the processing to terminate. This can be overridden by adding '|| :' - to the end of the command. +`foreach [--recursive] <command>`:: + Evaluate an arbitrary shell _<command>_ in each checked out submodule. + The command has access to the variables `$name`, `$sm_path`, `$displaypath`, + `$sha1` and `$toplevel`: ++ +-- +`$name`;; the name of the relevant submodule section in `.gitmodules` +`$sm_path`;; the path of the submodule as recorded in the immediate + superproject +`$displaypath`;; the relative path from the + current working directory to the submodules root directory +`$sha1`;; the commit as recorded in the immediate superproject +`$toplevel`;; the absolute path to the top-level of the immediate superproject. +-- ++ +Note that to avoid conflicts with `$PATH` on Windows, the `$path` +variable is now a deprecated synonym of `$sm_path` variable. +Any submodules defined in the superproject but not checked out are +ignored by this command. Unless given `--quiet`, foreach prints the name +of each submodule before evaluating the command. +If `--recursive` is given, submodules are traversed recursively (i.e. +the given shell command is evaluated in nested submodules as well). +A non-zero return from the command in any submodule causes +the processing to terminate. This can be overridden by adding ++||:++ +to the end of the command. + As an example, the command below will show the path and currently checked out commit for each submodule: @@ -249,26 +253,26 @@ git submodule foreach 'echo $sm_path `git rev-parse HEAD`' -------------- -sync [--recursive] [--] [<path>...]:: - Synchronizes submodules' remote URL configuration setting +`sync [--recursive] [--] [<path>...]`:: + Synchronize submodules' remote URL configuration setting to the value specified in `.gitmodules`. It will only affect those - submodules which already have a URL entry in .git/config (that is the + submodules which already have a URL entry in `.git/config` (that is the case when they are initialized or freshly added). This is useful when submodule URLs change upstream and you need to update your local repositories accordingly. + `git submodule sync` synchronizes all submodules while -`git submodule sync -- A` synchronizes submodule "A" only. +`git submodule sync -- A` synchronizes submodule `A` only. + If `--recursive` is specified, this command will recurse into the registered submodules, and sync any nested submodules within. -absorbgitdirs:: +`absorbgitdirs`:: If a git directory of a submodule is inside the submodule, move the git directory of the submodule into its superproject's `$GIT_DIR/modules` path and then connect the git directory and its working directory by setting the `core.worktree` and adding - a .git file pointing to the git directory embedded in the + a `.git` file pointing to the git directory embedded in the superprojects git directory. + A repository that was cloned independently and later added as a submodule or @@ -279,72 +283,70 @@ OPTIONS ------- --q:: ---quiet:: +`-q`:: +`--quiet`:: Only print error messages. ---progress:: - This option is only valid for add and update commands. - Progress status is reported on the standard error stream - by default when it is attached to a terminal, unless -q +`--progress`:: + Report progress status on the standard error stream + by default when it is attached to a terminal, unless `-q` is specified. This flag forces progress status even if the - standard error stream is not directed to a terminal. + standard error stream is not directed to a terminal. It is + only valid for `add` and `update` commands. ---all:: - This option is only valid for the deinit command. Unregister all - submodules in the working tree. +`--all`:: + Unregister all submodules in the working tree. This option is only + valid for the `deinit` command. --b <branch>:: ---branch <branch>:: +`-b<branch>`:: +`--branch=<branch>`:: Branch of repository to add as submodule. The name of the branch is recorded as `submodule.<name>.branch` in `.gitmodules` for `update --remote`. A special value of `.` is used to indicate that the name of the branch in the submodule should be the same name as the current branch in the current repository. If the - option is not specified, it defaults to the remote 'HEAD'. + option is not specified, it defaults to the remote `HEAD`. --f:: ---force:: - This option is only valid for add, deinit and update commands. - When running add, allow adding an otherwise ignored submodule path. - This option is also used to bypass a check that the submodule's name - is not already in use. By default, 'git submodule add' will fail if - the proposed name (which is derived from the path) is already registered - for another submodule in the repository. Using '--force' allows the command - to proceed by automatically generating a unique name by appending a number - to the conflicting name (e.g., if a submodule named 'child' exists, it will - try 'child1', and so on). - When running deinit the submodule working trees will be removed even - if they contain local changes. - When running update (only effective with the checkout procedure), - throw away local changes in submodules when switching to a - different commit; and always run a checkout operation in the - submodule, even if the commit listed in the index of the - containing repository matches the commit checked out in the - submodule. +`-f`:: +`--force`:: + Force the command to proceed, even if it would otherwise fail. + This option is only valid for `add`, `deinit` and `update` commands. +`add`;; allow adding an otherwise ignored submodule path. +This option is also used to bypass a check that the submodule's name +is not already in use. By default, `git submodule add` will fail if +the proposed name (which is derived from the path) is already registered +for another submodule in the repository. Using `--force` allows the command +to proceed by automatically generating a unique name by appending a number +to the conflicting name (e.g., if a submodule named 'child' exists, it will +try 'child1', and so on). +`deinit`;; the submodule working trees will be removed even +if they contain local changes. +`update`;; (only effective with the checkout procedure), +throw away local changes in submodules when switching to a +different commit; and always run a checkout operation in the +submodule, even if the commit listed in the index of the +containing repository matches the commit checked out in the +submodule. ---cached:: - This option is only valid for status and summary commands. These - commands typically use the commit found in the submodule HEAD, but - with this option, the commit stored in the index is used instead. +`--cached`:: + Use the index to determine the commit instead of the `HEAD`. + This option is only valid for `status` and `summary` commands. ---files:: - This option is only valid for the summary command. This command - compares the commit in the index with that in the submodule HEAD - when this option is used. +`--files`:: + Make the `summary` command compare the commit in the index with that in + the submodule `HEAD`. --n:: ---summary-limit:: - This option is only valid for the summary command. - Limit the summary size (number of commits shown in total). +`-n<n>`:: +`--summary-limit=<n>`:: + Limit the `summary` size (number of commits shown in total) to _<n>_. Giving 0 will disable the summary; a negative number means unlimited (the default). This limit only applies to modified submodules. The size is always limited to 1 for added/deleted/typechanged submodules. ---remote:: - This option is only valid for the update command. Instead of using - the superproject's recorded SHA-1 to update the submodule, use the - status of the submodule's remote-tracking branch. The remote used +`--remote`:: + Instead of using the superproject's recorded SHA-1 to update the + submodule, use the status of the submodule's remote-tracking branch. + This option is only valid for the `update` command. The remote used is branch's remote (`branch.<name>.remote`), defaulting to `origin`. The remote branch used defaults to the remote `HEAD`, but the branch name may be overridden by setting the `submodule.<name>.branch` @@ -363,7 +365,7 @@ --remote --no-fetch`. + Use this option to integrate changes from the upstream subproject with -your submodule's current HEAD. Alternatively, you can run `git pull` +your submodule's current `HEAD`. Alternatively, you can run `git pull` from the submodule, which is equivalent except for the remote branch name: `update --remote` uses the default upstream repository and `submodule.<name>.branch`, while `git pull` uses the submodule's @@ -372,105 +374,106 @@ `branch.<name>.merge` if you want a more native feel while working in the submodule itself. --N:: ---no-fetch:: - This option is only valid for the update command. +`-N`:: +`--no-fetch`:: Don't fetch new objects from the remote site. + This option is only valid for the `update` command. ---checkout:: - This option is only valid for the update command. - Checkout the commit recorded in the superproject on a detached HEAD - in the submodule. This is the default behavior, the main use of - this option is to override `submodule.$name.update` when set to +`--checkout`:: + Checkout the commit recorded in the superproject on a detached `HEAD` + in the submodule. This option is only valid for the `update` command. + This is the default behavior, the main use of + this option is to override `submodule.<name>.update` when set to a value other than `checkout`. - If the key `submodule.$name.update` is either not explicitly set or + If the key `submodule.<name>.update` is either not explicitly set or set to `checkout`, this option is implicit. ---merge:: - This option is only valid for the update command. +`--merge`:: Merge the commit recorded in the superproject into the current branch - of the submodule. If this option is given, the submodule's HEAD will + of the submodule. This option is only valid for the `update` command. + If this option is given, the submodule's `HEAD` will not be detached. If a merge failure prevents this process, you will have to resolve the resulting conflicts within the submodule with the usual conflict resolution tools. - If the key `submodule.$name.update` is set to `merge`, this option is + If the key `submodule.<name>.update` is set to `merge`, this option is implicit. ---rebase:: - This option is only valid for the update command. - Rebase the current branch onto the commit recorded in the - superproject. If this option is given, the submodule's HEAD will not +`--rebase`:: + Rebase the current branch onto the commit recorded in the superproject. + This option is only valid for the `update` command. The submodule's `HEAD` will not be detached. If a merge failure prevents this process, you will have to resolve these failures with linkgit:git-rebase[1]. - If the key `submodule.$name.update` is set to `rebase`, this option is + If the key `submodule.<name>.update` is set to `rebase`, this option is implicit. ---init:: - This option is only valid for the update command. - Initialize all submodules for which "git submodule init" has not been - called so far before updating. +`--init`:: + Initialize all submodules for which `git submodule init` has not been + called so far before updating. This option is only valid for the `update` + command. ---name:: - This option is only valid for the add command. It sets the submodule's - name to the given string instead of defaulting to its path. The name - must be valid as a directory name and may not end with a '/'. ---reference <repository>:: - This option is only valid for add and update commands. These - commands sometimes need to clone a remote repository. In this case, +`--name=<name>`:: + Set the submodule's name to the given string instead of defaulting to its path. _<name>_ + must be valid as a directory name and may not end with a `/`. + +`--reference=<repository>`:: + Pass the local _<repository>_ as a reference when cloning the submodule. + This option is only valid for `add` and `update` commands. + These commands sometimes need to clone a remote repository. In this case, this option will be passed to the linkgit:git-clone[1] command. + -*NOTE*: Do *not* use this option unless you have read the note +NOTE: Do *not* use this option unless you have read the note for linkgit:git-clone[1]'s `--reference`, `--shared`, and `--dissociate` options carefully. ---dissociate:: - This option is only valid for add and update commands. These - commands sometimes need to clone a remote repository. In this case, +`--dissociate`:: + After using a reference repository to clone from, do not rely on it anymore. + This option is only valid for `add` and `update` commands. + These commands sometimes need to clone a remote repository. In this case, this option will be passed to the linkgit:git-clone[1] command. + -*NOTE*: see the NOTE for the `--reference` option. +NOTE: See the NOTE above for the `--reference` option. ---recursive:: - This option is only valid for foreach, update, status and sync commands. - Traverse submodules recursively. The operation is performed not +`--recursive`:: + Traverse submodules recursively. This option is only valid for `foreach`, + `update`, `status` and `sync` commands. The operation is performed not only in the submodules of the current repo, but also in any nested submodules inside those submodules (and so on). ---depth:: - This option is valid for add and update commands. Create a 'shallow' - clone with a history truncated to the specified number of revisions. - See linkgit:git-clone[1] +`--depth=<depth>`:: + Create a 'shallow' clone with a history truncated to the _<depth>_ revisions. + This option is valid for `add` and `update` commands. See linkgit:git-clone[1] ---recommend-shallow:: ---no-recommend-shallow:: - This option is only valid for the update command. +`--recommend-shallow`:: +`--no-recommend-shallow`:: + Recommend or not shallow cloning of submodules. + This option is only valid for the `update` command. The initial clone of a submodule will use the recommended `submodule.<name>.shallow` as provided by the `.gitmodules` file by default. To ignore the suggestions use `--no-recommend-shallow`. --j <n>:: ---jobs <n>:: - This option is only valid for the update command. - Clone new submodules in parallel with as many jobs. +`-j<n>`:: +`--jobs=<n>`:: + Clone new submodules in parallel with _<n>_ jobs. + This option is only valid for the `update` command. Defaults to the `submodule.fetchJobs` option. ---single-branch:: ---no-single-branch:: - This option is only valid for the update command. - Clone only one branch during update: HEAD or one specified by --branch. +`--single-branch`:: +`--no-single-branch`:: + Clone only one branch during update: `HEAD` or one specified by `--branch`. + This option is only valid for the `update` command. -<path>...:: +`<path>...`:: Paths to submodule(s). When specified this will restrict the command to only operate on the submodules found at the specified paths. - (This argument is required with add). + (This argument is required with `add`). FILES ----- When initializing submodules, a `.gitmodules` file in the top-level directory -of the containing repository is used to find the url of each submodule. +of the containing repository is used to find the URL of each submodule. This file should be formatted in the same way as `$GIT_DIR/config`. The key -to each submodule url is "submodule.$name.url". See linkgit:gitmodules[5] +to each submodule URL is `submodule.<name>.url`. See linkgit:gitmodules[5] for details. SEE ALSO
diff --git a/Documentation/git-update-ref.adoc b/Documentation/git-update-ref.adoc index 9310ce9..37a5019 100644 --- a/Documentation/git-update-ref.adoc +++ b/Documentation/git-update-ref.adoc
@@ -119,7 +119,7 @@ Verify <ref> against <old-oid> but do not change it. If <old-oid> is zero or missing, the ref must not exist. -symref-create: +symref-create:: Create symbolic ref <ref> with <new-target> after verifying that it does not exist.
diff --git a/Documentation/git-worktree.adoc b/Documentation/git-worktree.adoc index f272f79..fbf8426 100644 --- a/Documentation/git-worktree.adoc +++ b/Documentation/git-worktree.adoc
@@ -104,7 +104,7 @@ passed to the command. In the event the repository has a remote and `--guess-remote` is used, but no remote or local branches exist, then the command fails with a warning reminding the user to fetch from their remote -first (or override by using `-f/--force`). +first (or override by using `-f`/`--force`). `list`:: @@ -131,7 +131,13 @@ `prune`:: -Prune worktree information in `$GIT_DIR/worktrees`. +Remove worktree information in `$GIT_DIR/worktrees` for worktrees +whose working trees are missing. Useful after manually removing +a working tree that is no longer needed (but use "git worktree +remove" next time you want to do so). Also, if you _moved_ a +working tree elsewhere causing the worktree information to become +dangling, see "git worktree repair" to reconnect the worktree to +the new working tree location. `remove`:: @@ -271,7 +277,7 @@ With `list`, output additional information about worktrees (see below). `--expire <time>`:: - With `prune`, only expire unused worktrees older than _<time>_. + With `prune`, only prune missing worktrees if older than _<time>_. + With `list`, annotate missing worktrees as prunable if they are older than _<time>_.
diff --git a/Documentation/git.adoc b/Documentation/git.adoc index ce099e7..8a5cdd3 100644 --- a/Documentation/git.adoc +++ b/Documentation/git.adoc
@@ -235,7 +235,10 @@ ------------ We divide Git into high level ("porcelain") commands and low level -("plumbing") commands. +("plumbing") commands. For defining command aliases, see +linkgit:git-config[1] and look for descriptions of `alias.*`. +For installing custom "git" subcommands, see the description for +the 'PATH' environment variable in this manual. High-level commands (porcelain) ------------------------------- @@ -487,6 +490,14 @@ `$HOMEDRIVE$HOMEPATH` if both `$HOMEDRIVE` and `$HOMEPATH` exist; otherwise `$USERPROFILE` if `$USERPROFILE` exists. +`PATH`:: + When a user runs 'git <command>' that is not part of the core Git programs + (installed in GIT_EXEC_PATH), 'git-<command>' that is runnable by the user + in a directory on `$PATH` is invoked. Argument passed after the command + name are passed as-is to the program. To execute `git <foo>`, `git` finds + command `<foo>` (either a core Git program found in 'GIT_EXEC_PATH', or a + custom one in a directory on 'PATH'), before trying `foo` as an alias. + The Git Repository ~~~~~~~~~~~~~~~~~~ These environment variables apply to 'all' core Git commands. Nb: it @@ -584,6 +595,11 @@ repositories will be set to this value. The default is "files". See `--ref-format` in linkgit:git-init[1]. +`GIT_REFERENCE_BACKEND`:: + Specify which reference backend to be used along with its URI. + See `extensions.refStorage` option in linkgit:git-config[1] for more + details. Overrides the config variable when used. + Git Commits ~~~~~~~~~~~ `GIT_AUTHOR_NAME`::
diff --git a/Documentation/gitdatamodel.adoc b/Documentation/gitdatamodel.adoc new file mode 100644 index 0000000..dcfdff0 --- /dev/null +++ b/Documentation/gitdatamodel.adoc
@@ -0,0 +1,305 @@ +gitdatamodel(7) +=============== + +NAME +---- +gitdatamodel - Git's core data model + +SYNOPSIS +-------- +gitdatamodel + +DESCRIPTION +----------- + +It's not necessary to understand Git's data model to use Git, but it's +very helpful when reading Git's documentation so that you know what it +means when the documentation says "object", "reference" or "index". + +Git's core operations use 4 kinds of data: + +1. <<objects,Objects>>: commits, trees, blobs, and tag objects +2. <<references,References>>: branches, tags, + remote-tracking branches, etc +3. <<index,The index>>, also known as the staging area +4. <<reflogs,Reflogs>>: logs of changes to references ("ref log") + +[[objects]] +OBJECTS +------- + +All of the commits and files in a Git repository are stored as "Git objects". +Git objects never change after they're created, and every object has an ID, +like `1b61de420a21a2f1aaef93e38ecd0e45e8bc9f0a`. + +This means that if you have an object's ID, you can always recover its +exact contents as long as the object hasn't been deleted. + +Every object has: + +[[object-id]] +1. an *ID* (aka "object name"), which is a cryptographic hash of its + type and contents. + It's fast to look up a Git object using its ID. + This is usually represented in hexadecimal, like + `1b61de420a21a2f1aaef93e38ecd0e45e8bc9f0a`. +2. a *type*. There are 4 types of objects: + <<commit,commits>>, <<tree,trees>>, <<blob,blobs>>, + and <<tag-object,tag objects>>. +3. *contents*. The structure of the contents depends on the type. + +Here's how each type of object is structured: + +[[commit]] +commit:: + A commit contains these required fields + (though there are other optional fields): ++ +1. The full directory structure of all the files in that version of the + repository and each file's contents, stored as the *<<tree,tree>>* ID + of the commit's top-level directory +2. Its *parent commit ID(s)*. The first commit in a repository has 0 parents, + regular commits have 1 parent, merge commits have 2 or more parents +3. An *author* and the time the commit was authored +4. A *committer* and the time the commit was committed +5. A *commit message* ++ +Here's how an example commit is stored: ++ +---- +tree 1b61de420a21a2f1aaef93e38ecd0e45e8bc9f0a +parent 4ccb6d7b8869a86aae2e84c56523f8705b50c647 +author Maya <maya@example.com> 1759173425 -0400 +committer Maya <maya@example.com> 1759173425 -0400 + +Add README +---- ++ +Like all other objects, commits can never be changed after they're created. +For example, "amending" a commit with `git commit --amend` creates a new +commit with the same parent. ++ +Git does not store the diff for a commit: when you ask Git to show +the commit with linkgit:git-show[1], it calculates the diff from its +parent on the fly. + +[[tree]] +tree:: + A tree is how Git represents a directory. + It can contain files or other trees (which are subdirectories). + It lists, for each item in the tree: ++ +1. The *filename*, for example `hello.py` +2. The *file type*, which must be one of these five types: + - *regular file* + - *executable file* + - *symbolic link* + - *directory* + - *gitlink* (for use with submodules) +3. The <<object-id,*object ID*>> with the contents of the file, directory, + or gitlink. ++ +For example, this is how a tree containing one directory (`src`) and one file +(`README.md`) is stored: ++ +---- +100644 blob 8728a858d9d21a8c78488c8b4e70e531b659141f README.md +040000 tree 89b1d2e0495f66d6929f4ff76ff1bb07fc41947d src +---- + +NOTE: In the output above, Git displays the file type of each tree entry +using a format that's loosely modelled on Unix file modes (`100644` is +"regular file", `100755` is "executable file", `120000` is "symbolic +link", `040000` is "directory", and `160000` is "gitlink"). It also +displays the object's type: `blob` for files and symlinks, `tree` for +directories, and `commit` for gitlinks. + +[[blob]] +blob:: + A blob object contains a file's contents. ++ +When you make a commit, Git stores the full contents of each file that +you changed as a blob. +For example, if you have a commit that changes 2 files in a repository +with 1000 files, that commit will create 2 new blobs, and use the +previous blob ID for the other 998 files. +This means that commits can use relatively little disk space even in a +very large repository. + +[[tag-object]] +tag object:: + Tag objects contain these required fields + (though there are other optional fields): ++ +1. The *ID* of the object it references +2. The *type* of the object it references +3. The *tagger* and tag date +4. A *tag message*, similar to a commit message + +Here's how an example tag object is stored: + +---- +object 750b4ead9c87ceb3ddb7a390e6c7074521797fb3 +type commit +tag v1.0.0 +tagger Maya <maya@example.com> 1759927359 -0400 + +Release version 1.0.0 +---- + +NOTE: All of the examples in this section were generated with +`git cat-file -p <object-id>`. + +[[references]] +REFERENCES +---------- + +References are a way to give a name to a commit. +It's easier to remember "the changes I'm working on are on the `turtle` +branch" than "the changes are in commit bb69721404348e". +Git often uses "ref" as shorthand for "reference". + +References can either refer to: + +1. An object ID, usually a <<commit,commit>> ID +2. Another reference. This is called a "symbolic reference" + +References are stored in a hierarchy, and Git handles references +differently based on where they are in the hierarchy. +Most references are under `refs/`. Here are the main types: + +[[branch]] +branches: `refs/heads/<name>`:: + A branch refers to a commit ID. + That commit is the latest commit on the branch. ++ +To get the history of commits on a branch, Git will start at the commit +ID the branch references, and then look at the commit's parent(s), +the parent's parent, etc. + +[[tag]] +tags: `refs/tags/<name>`:: + A tag refers to a commit ID, tag object ID, or other object ID. + There are two types of tags: + 1. "Annotated tags", which reference a <<tag-object,tag object>> ID + which contains a tag message + 2. "Lightweight tags", which reference a commit, blob, or tree ID + directly ++ +Even though branches and tags both refer to a commit ID, Git +treats them very differently. +Branches are expected to change over time: when you make a commit, Git +will update your <<HEAD,current branch>> to point to the new commit. +Tags are usually not changed after they're created. + +[[HEAD]] +HEAD: `HEAD`:: + `HEAD` is where Git stores your current <<branch,branch>>, + if there is a current branch. `HEAD` can either be: ++ +1. A symbolic reference to your current branch, for example `ref: + refs/heads/main` if your current branch is `main`. +2. A direct reference to a commit ID. In this case there is no current branch. + This is called "detached HEAD state", see the DETACHED HEAD section + of linkgit:git-checkout[1] for more. + +[[remote-tracking-branch]] +remote-tracking branches: `refs/remotes/<remote>/<branch>`:: + A remote-tracking branch refers to a commit ID. + It's how Git stores the last-known state of a branch in a remote + repository. `git fetch` updates remote-tracking branches. When + `git status` says "you're up to date with origin/main", it's looking at + this. ++ +`refs/remotes/<remote>/HEAD` is a symbolic reference to the remote's +default branch. This is the branch that `git clone` checks out by default. + +[[other-refs]] +Other references:: + Git tools may create references anywhere under `refs/`. + For example, linkgit:git-stash[1], linkgit:git-bisect[1], + and linkgit:git-notes[1] all create their own references + in `refs/stash`, `refs/bisect`, etc. + Third-party Git tools may also create their own references. ++ +Git may also create references other than `HEAD` at the base of the +hierarchy, like `ORIG_HEAD`. + +NOTE: Git may delete objects that aren't "reachable" from any reference +or <<reflogs,reflog>>. +An object is "reachable" if we can find it by following tags to whatever +they tag, commits to their parents or trees, and trees to the trees or +blobs that they contain. +For example, if you amend a commit with `git commit --amend`, +there will no longer be a branch that points at the old commit. +The old commit is recorded in the current branch's <<reflogs,reflog>>, +so it is still "reachable", but when the reflog entry expires it may +become unreachable and get deleted. +Reachable objects will never be deleted. + +[[index]] +THE INDEX +--------- +The index, also known as the "staging area", is a list of files and +the contents of each file, stored as a <<blob,blob>>. +You can add files to the index or update the contents of a file in the +index with linkgit:git-add[1]. This is called "staging" the file for commit. + +Unlike a <<tree,tree>>, the index is a flat list of files. +When you commit, Git converts the list of files in the index to a +directory <<tree,tree>> and uses that tree in the new <<commit,commit>>. + +Each index entry has 4 fields: + +1. The *file type*, which must be one of: + - *regular file* + - *executable file* + - *symbolic link* + - *gitlink* (for use with submodules) +2. The *<<blob,blob>>* ID of the file, + or (rarely) the *<<commit,commit>>* ID of the submodule +3. The *stage number*, either 0, 1, 2, or 3. This is normally 0, but if + there's a merge conflict there can be multiple versions of the same + filename in the index. +4. The *file path*, for example `src/hello.py` + +It's extremely uncommon to look at the index directly: normally you'd +run `git status` to see a list of changes between the index and <<HEAD,HEAD>>. +But you can use `git ls-files --stage` to see the index. +Here's the output of `git ls-files --stage` in a repository with 2 files: + +---- +100644 8728a858d9d21a8c78488c8b4e70e531b659141f 0 README.md +100644 665c637a360874ce43bf74018768a96d2d4d219a 0 src/hello.py +---- + +[[reflogs]] +REFLOGS +------- + +Every time a branch, remote-tracking branch, or HEAD is updated, Git +updates a log called a "reflog" for that <<references,reference>>. +This means that if you make a mistake and "lose" a commit, you can +generally recover the commit ID by running `git reflog <reference>`. + +A reflog is a list of log entries. Each entry has: + +1. The *commit ID* +2. *Timestamp* when the change was made +3. *Log message*, for example `pull: Fast-forward` + +Reflogs only log changes made in your local repository. +They are not shared with remotes. + +You can view a reflog with `git reflog <reference>`. +For example, here's the reflog for a `main` branch which has changed twice: + +---- +$ git reflog main --date=iso --no-decorate +750b4ea main@{2025-09-29 15:17:05 -0400}: commit: Add README +4ccb6d7 main@{2025-09-29 15:16:48 -0400}: commit (initial): Initial commit +---- + +GIT +--- +Part of the linkgit:git[1] suite
diff --git a/Documentation/gitfaq.adoc b/Documentation/gitfaq.adoc index f2917d1..f6c9b9d 100644 --- a/Documentation/gitfaq.adoc +++ b/Documentation/gitfaq.adoc
@@ -83,6 +83,25 @@ which quotes the filename with spaces and specifies the `--nofork` option to avoid backgrounding the process. +[[sign-off]] +Why not have `commit.signoff` and other configuration variables?:: + Git intentionally does not (and will not) provide a + configuration variable, such as `commit.signoff`, to + automatically add `--signoff` by default. The reason is to + protect the legal and intentional significance of a sign-off. + If there were more automated and widely publicized ways for + sign-offs to be appended, it would become easier for someone + to argue later that a "Signed-off-by" trailer was just added + out of habit or by automation, without the committer's full + awareness or intent to certify their agreement with the + Developer Certificate of Origin (DCO) or a similar statement. + This could undermine the sign-off’s credibility in legal or + contractual situations. ++ +There exists `format.signoff`, but that is a historical mistake, and +it is not an excuse to add more mistakes of the same kind on top. + + Credentials ----------- @@ -214,14 +233,30 @@ the other doesn't have. This can result in important objects becoming unreferenced and possibly pruned by `git gc`, causing data loss. + -Therefore, it's better to push your work to either the other system or a central -server using the normal push and pull mechanism. However, this doesn't always -preserve important data, like stashes, so some people prefer to share a working -tree across systems. +Therefore, it's better to push your work to either the other system or a +central server using the normal push and pull mechanism. In Git 2.51, Git +learned to import and export stashes, so it's possible to synchronize the state +of the working tree by stashing it with `git stash`, then exporting either all +stashes with `git stash export --to-ref refs/heads/stashes` (assuming you want +to export to the `stashes` branch) or selecting stashes by adding their numbers +to the end of that command. It's also possible to include untracked files by +using the `--include-untracked` argument when stashing the data in the first +place, but be careful not to do this if any of these contain sensitive +information. + -If you do this, the recommended approach is to use `rsync -a --delete-after` -(ideally with an encrypted connection such as with `ssh`) on the root of -repository. You should ensure several things when you do this: +You can then push the `stashes` branch (or whatever branch you've exported to), +fetch them to the local system (such as with `git fetch origin ++stashes:stashes`), and import the stashes on the other system with `git stash +import stashes` (again, changing the name as necessary). Applying the changes +to the working tree can be done with `git stash pop` or `git stash apply`. +This is the approach that is most robust and most likely to avoid unintended +problems. ++ +Having said that, there are some cases where people nevertheless prefer to +share a working tree across systems. If you do this, the recommended approach +is to use `rsync -a --delete-after` (ideally with an encrypted connection such +as with `ssh`) on the root of repository. You should ensure several things +when you do this: + * If you have additional worktrees or a separate Git directory, they must be synced at the same time as the main working tree and repository. @@ -232,10 +267,11 @@ any sort are taking place on it, including background operations like `git gc` and operations invoked by your editor). + -Be aware that even with these recommendations, syncing in this way has some risk -since it bypasses Git's normal integrity checking for repositories, so having -backups is advised. You may also wish to do a `git fsck` to verify the -integrity of your data on the destination system after syncing. +Be aware that even with these recommendations, syncing working trees in this +way has some risk since it bypasses Git's normal integrity checking for +repositories, so having backups is advised. You may also wish to do a `git +fsck` to verify the integrity of your data on the destination system after +syncing. Common Issues -------------
diff --git a/Documentation/gitformat-loose.adoc b/Documentation/gitformat-loose.adoc index 9479936..b0b5697 100644 --- a/Documentation/gitformat-loose.adoc +++ b/Documentation/gitformat-loose.adoc
@@ -10,6 +10,7 @@ -------- [verse] $GIT_DIR/objects/[0-9a-f][0-9a-f]/* +$GIT_DIR/objects/object-map/map-*.map DESCRIPTION ----------- @@ -48,6 +49,83 @@ Similarly, a blob containing the contents `abc` would have the uncompressed data of `blob 3\0abc`. +== Loose object mapping + +When the `compatObjectFormat` option is used, Git needs to store a mapping +between the repository's main algorithm and the compatibility algorithm for +loose objects as well as some auxiliary information. + +The mapping consists of a set of files under `$GIT_DIR/objects/object-map` +ending in `.map`. The portion of the filename before the extension is that of +the main hash checksum (that is, the one specified in +`extensions.objectformat`) in hex format. + +`git gc` will repack existing entries into one file, removing any unnecessary +objects, such as obsolete shallow entries or loose objects that have been +packed. + +The file format is as follows. All values are in network byte order and all +4-byte and 8-byte values must be 4-byte aligned in the file, so the NUL padding +may be required in some cases. Git always uses the smallest number of NUL +bytes (including zero) that is required for the padding in order to make +writing files deterministic. + +- A header appears at the beginning and consists of the following: + * A 4-byte mapping signature: `LMAP` + * 4-byte version number: 1 + * 4-byte length of the header section (including reserved entries but + excluding any NUL padding). + * 4-byte number of objects declared in this map file. + * 4-byte number of object formats declared in this map file. + * For each object format: + ** 4-byte format identifier (e.g., `sha1` for SHA-1) + ** 4-byte length in bytes of shortened object names (that is, prefixes of + the full object names). This is the shortest possible length needed to + make names in the shortened object name table unambiguous. + ** 8-byte integer, recording where tables relating to this format + are stored in this index file, as an offset from the beginning. + * 8-byte offset to the trailer from the beginning of this file. + * The remainder of the header section is reserved for future use. + Readers must ignore unrecognized data here. +- Zero or more NUL bytes. These are used to improve the alignment of the + 4-byte quantities below. +- Tables for the first object format: + * A sorted table of shortened object names. These are prefixes of the names + of all objects in this file, packed together to reduce the cache footprint + of the binary search for a specific object name. + * A sorted table of full object names. + * A table of 4-byte metadata values. +- Zero or more NUL bytes. +- Tables for subsequent object formats: + * A sorted table of shortened object names. These are prefixes of the names + of all objects in this file, packed together without offset values to + reduce the cache footprint of the binary search for a specific object name. + * A table of full object names in the order specified by the first object format. + * A table of 4-byte values mapping object name order to the order of the + first object format. For an object in the table of sorted shortened object + names, the value at the corresponding index in this table is the index in + the previous table for that same object. + * Zero or more NUL bytes. +- The trailer consists of the following: + * Hash checksum of all of the above using the main hash. + +The lower six bits of each metadata table contain a type field indicating the +reason that this object is stored: + +0:: + Reserved. +1:: + This object is stored as a loose object in the repository. +2:: + This object is a shallow entry. The mapping refers to a shallow value + returned by a remote server. +3:: + This object is a submodule entry. The mapping refers to the commit stored + representing a submodule. + +Other data may be stored in this field in the future. Bits that are not used +must be zero. + GIT --- Part of the linkgit:git[1] suite
diff --git a/Documentation/gitformat-pack.adoc b/Documentation/gitformat-pack.adoc index 1b4db4a..3416edc 100644 --- a/Documentation/gitformat-pack.adoc +++ b/Documentation/gitformat-pack.adoc
@@ -374,7 +374,9 @@ The signature is: {'M', 'I', 'D', 'X'} 1-byte version number: - Git only writes or recognizes version 1. + Git writes the version specified by the "midx.version" + configuration option, which defaults to 2. It recognizes + both versions 1 and 2. 1-byte Object Id Version We infer the length of object IDs (OIDs) from this value: @@ -413,7 +415,9 @@ strings. There is no extra padding between the filenames, and they are listed in lexicographic order. The chunk itself is padded at the end with between 0 and 3 NUL bytes to make the - chunk size a multiple of 4 bytes. + chunk size a multiple of 4 bytes. Version 1 MIDXs are required to + list their packs in lexicographic order, but version 2 MIDXs may + list their packs in any arbitrary order. Bitmapped Packfiles (ID: {'B', 'T', 'M', 'P'}) Stores a table of two 4-byte unsigned integers in network order.
diff --git a/Documentation/githooks.adoc b/Documentation/githooks.adoc index 0397dec..ed04594 100644 --- a/Documentation/githooks.adoc +++ b/Documentation/githooks.adoc
@@ -103,17 +103,14 @@ making a commit. Exiting with a non-zero status from this script causes the `git commit` command to abort before creating a commit. -The default 'pre-commit' hook, when enabled, catches introduction -of lines with trailing whitespaces and aborts the commit when -such a line is found. - All the `git commit` hooks are invoked with the environment variable `GIT_EDITOR=:` if the command will not bring up an editor to modify the commit message. -The default 'pre-commit' hook, when enabled--and with the -`hooks.allownonascii` config option unset or set to false--prevents -the use of non-ASCII filenames. +The default 'pre-commit' hook, when enabled, prevents the introduction +of non-ASCII filenames and lines with trailing whitespace. The non-ASCII +check can be turned off by setting the `hooks.allownonascii` config +option to `true`. pre-merge-commit ~~~~~~~~~~~~~~~~ @@ -487,13 +484,16 @@ ~~~~~~~~~~~~~~~~~~~~~ This hook is invoked by any Git command that performs reference -updates. It executes whenever a reference transaction is prepared, -committed or aborted and may thus get called multiple times. The hook -also supports symbolic reference updates. +updates. It executes whenever a reference transaction is preparing, +prepared, committed or aborted and may thus get called multiple times. +The hook also supports symbolic reference updates. The hook takes exactly one argument, which is the current state the given reference transaction is in: + - "preparing": All reference updates have been queued to the + transaction but references are not yet locked on disk. + - "prepared": All reference updates have been queued to the transaction and references were locked on disk. @@ -514,16 +514,18 @@ the reference regardless of its current value or when the reference is to be created anew, `<old-value>` is the all-zeroes object name. To distinguish these cases, you can inspect the current value of -`<ref-name>` via `git rev-parse`. +`<ref-name>` via `git rev-parse`. During the "preparing" state, symbolic +references are not resolved: `<ref-name>` will reflect the symbolic reference +itself rather than the object it points to. For symbolic reference updates the `<old_value>` and `<new-value>` fields could denote references instead of objects. A reference will be denoted with a 'ref:' prefix, like `ref:<ref-target>`. The exit status of the hook is ignored for any state except for the -"prepared" state. In the "prepared" state, a non-zero exit status will -cause the transaction to be aborted. The hook will not be called with -"aborted" state in that case. +"preparing" and "prepared" states. In these states, a non-zero exit +status will cause the transaction to be aborted. The hook will not be +called with "aborted" state in that case. push-to-checkout ~~~~~~~~~~~~~~~~
diff --git a/Documentation/gitignore.adoc b/Documentation/gitignore.adoc index 9fccab4..a3d24e5 100644 --- a/Documentation/gitignore.adoc +++ b/Documentation/gitignore.adoc
@@ -96,6 +96,11 @@ particular `.gitignore` file itself. Otherwise the pattern may also match at any level below the `.gitignore` level. + - Patterns read from exclude sources that are outside the working tree, + such as $GIT_DIR/info/exclude and core.excludesFile, are treated as if + they are specified at the root of the working tree, i.e. a leading "/" + in such patterns anchors the match at the root of the repository. + - If there is a separator at the end of the pattern then the pattern will only match directories, otherwise the pattern can match both files and directories.
diff --git a/Documentation/gitmodules.adoc b/Documentation/gitmodules.adoc index d9bec8b..3792da9 100644 --- a/Documentation/gitmodules.adoc +++ b/Documentation/gitmodules.adoc
@@ -70,7 +70,10 @@ -- all;; The submodule will never be considered modified (but will nonetheless show up in the output of status and commit when it has - been staged). + been staged). Add `(new commits)` can be overruled using the + `git add --force <submodule.path>`. + The setting affects `status`, `update-index`, `diff` and `log`(due + to underlaying `diff`). dirty;; All changes to the submodule's work tree will be ignored, only committed differences between the `HEAD` of the submodule and its
diff --git a/Documentation/gitprotocol-http.adoc b/Documentation/gitprotocol-http.adoc index d024010..e2ef7f0 100644 --- a/Documentation/gitprotocol-http.adoc +++ b/Documentation/gitprotocol-http.adoc
@@ -443,7 +443,8 @@ TODO: Define error if no "want" lines are requested. If any "want" object is not reachable, send an error: -TODO: Define error if an invalid "want" is requested. +When a Git server receives an invalid or malformed `want` line, it +responds with an error message that includes the offending object name. Create an empty list, `s_common`.
diff --git a/Documentation/gitprotocol-pack.adoc b/Documentation/gitprotocol-pack.adoc index 837b691..633deec 100644 --- a/Documentation/gitprotocol-pack.adoc +++ b/Documentation/gitprotocol-pack.adoc
@@ -47,7 +47,9 @@ Transports ---------- There are three transports over which the packfile protocol is -initiated. The Git transport is a simple, unauthenticated server that +initiated. + +The Git transport is a simple, unauthenticated server that takes the command (almost always 'upload-pack', though Git servers can be configured to be globally writable, in which 'receive- pack' initiation is also allowed) with which the client wishes to @@ -65,7 +67,7 @@ ---------------- The protocol provides a mechanism in which clients can send additional -information in its first message to the server. These are called "Extra +information in their first message to the server. These are called "Extra Parameters", and are supported by the Git, SSH, and HTTP protocols. Each Extra Parameter takes the form of `<key>=<value>` or `<key>`. @@ -115,7 +117,7 @@ SSH Transport ------------- -Initiating the upload-pack or receive-pack processes over SSH is +Initiating the 'upload-pack' or 'receive-pack' processes over SSH is executing the binary on the server via SSH remote execution. It is basically equivalent to running this: @@ -129,7 +131,7 @@ In an ssh:// format URI, it's absolute in the URI, so the '/' after the host name (or port number) is sent as an argument, which is then -read by the remote git-upload-pack exactly as is, so it's effectively +read by the remote 'git-upload-pack' exactly as is, so it's effectively an absolute path in the remote filesystem. git clone ssh://user@example.com/project.git @@ -161,7 +163,7 @@ A few things to remember here: -- The "command name" is spelled with dash (e.g. git-upload-pack), but +- The "command name" is spelled with dash (e.g. 'git-upload-pack'), but this can be overridden by the client; - The repository path is always quoted with single quotes. @@ -277,7 +279,7 @@ filter-request = PKT-LINE("filter" SP filter-spec) ---- -Clients MUST send all the obj-ids it wants from the reference +Clients MUST send all the obj-ids they want from the reference discovery phase as 'want' lines. Clients MUST send at least one 'want' command in the request body. Clients MUST NOT mention an obj-id in a 'want' command which did not appear in the response @@ -375,10 +377,10 @@ Without either multi_ack or multi_ack_detailed: - * upload-pack sends "ACK obj-id" on the first common object it finds. + * 'upload-pack' sends "ACK obj-id" on the first common object it finds. After that it says nothing until the client gives it a "done". - * upload-pack sends "NAK" on a flush-pkt if no common object + * 'upload-pack' sends "NAK" on a flush-pkt if no common object has been found yet. If one has been found, and thus an ACK was already sent, it's silent on the flush-pkt.
diff --git a/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc index c7db103..f985cb4 100644 --- a/Documentation/gitprotocol-v2.adoc +++ b/Documentation/gitprotocol-v2.adoc
@@ -812,10 +812,15 @@ After these mandatory fields, the server MAY advertise the following optional fields in any order: -`partialCloneFilter`:: The filter specification used by the remote. +`partialCloneFilter`:: The filter specification for the remote. It +corresponds to the "remote.<name>.partialCloneFilter" config setting. Clients can use this to determine if the remote's filtering strategy -is compatible with their needs (e.g., checking if both use "blob:none"). -It corresponds to the "remote.<name>.partialCloneFilter" config setting. +is compatible with their needs (e.g., checking if both use +"blob:none"). Additionally they can use this through the +`--filter=auto` option in linkgit:git-clone[1]. With that option, the +filter specification of the clone will be automatically computed by +combining the filter specifications of the promisor remotes the client +accepts. `token`:: An authentication token that clients can use when connecting to the remote. It corresponds to the "remote.<name>.token" @@ -826,9 +831,11 @@ above. Clients MUST ignore fields they don't recognize to allow for future protocol extensions. -For now, the client can only use information transmitted through these -fields to decide if it accepts the advertised promisor remote. In the -future that information might be used for other purposes though. +The client can use information transmitted through these fields to +decide if it accepts the advertised promisor remote. Also, the client +can be configured to store the values of these fields or use them +to automatically configure the repository (see "promisor.storeFields" +in linkgit:git-config[1] and `--filter=auto` in linkgit:git-clone[1]). Field values MUST be urlencoded. @@ -856,8 +863,9 @@ 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. See the documentation of these -configuration options for more information. +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/glossary-content.adoc b/Documentation/glossary-content.adoc index e423e47..20ba121 100644 --- a/Documentation/glossary-content.adoc +++ b/Documentation/glossary-content.adoc
@@ -297,8 +297,8 @@ identified by its <<def_object_name,object name>>. The objects usually live in `$GIT_DIR/objects/`. -[[def_object_identifier]]object identifier (oid):: - Synonym for <<def_object_name,object name>>. +[[def_object_identifier]]object identifier, object ID, oid:: + Synonyms for <<def_object_name,object name>>. [[def_object_name]]object name:: The unique identifier of an <<def_object,object>>. The
diff --git a/Documentation/lint-gitlink.perl b/Documentation/lint-gitlink.perl index f183a18..a92e887 100755 --- a/Documentation/lint-gitlink.perl +++ b/Documentation/lint-gitlink.perl
@@ -41,10 +41,11 @@ @ARGV = $to_check; while (<>) { my $line = $_; + next if $line =~ /^\s*(ifn?def|endif)::/; while ($line =~ m/(.{,8})((git[-a-z]+|scalar)\[(\d)*\])/g) { my $pos = pos $line; my ($macro, $target, $page, $section) = ($1, $2, $3, $4); - if ( $macro ne "linkgit:" && $macro !~ "ifn?def::" && $macro ne "endif::" ) { + if ( $macro ne "linkgit:" ) { report($pos, $line, $target, "linkgit: macro expected"); } }
diff --git a/Documentation/merge-options.adoc b/Documentation/merge-options.adoc index 9d43326..952cb85 100644 --- a/Documentation/merge-options.adoc +++ b/Documentation/merge-options.adoc
@@ -56,7 +56,7 @@ `--ff-only`:: Only update to the new history if there is no divergent local history. This is the default when no method for reconciling - divergent histories is provided (via the --rebase=* flags). + divergent histories is provided (via the `--rebase` flags). `--ff`:: `--no-ff`::
diff --git a/Documentation/meson.build b/Documentation/meson.build index c00c9fe..d6365b8 100644 --- a/Documentation/meson.build +++ b/Documentation/meson.build
@@ -64,6 +64,7 @@ 'git-gui.adoc' : 1, 'git-hash-object.adoc' : 1, 'git-help.adoc' : 1, + 'git-history.adoc' : 1, 'git-hook.adoc' : 1, 'git-http-backend.adoc' : 1, 'git-http-fetch.adoc' : 1, @@ -193,6 +194,7 @@ 'gitcore-tutorial.adoc' : 7, 'gitcredentials.adoc' : 7, 'gitcvs-migration.adoc' : 7, + 'gitdatamodel.adoc' : 7, 'gitdiffcore.adoc' : 7, 'giteveryday.adoc' : 7, 'gitfaq.adoc' : 7, @@ -352,13 +354,10 @@ command: [ shell, '@INPUT@', - '..', + meson.project_source_root(), mode, '@OUTPUT@' ], - env: [ - 'MERGE_TOOLS_DIR=' + meson.project_source_root() / 'mergetools', - ], input: 'generate-mergetool-list.sh', output: 'mergetools-' + mode + '.adoc', )
diff --git a/Documentation/pretty-formats.adoc b/Documentation/pretty-formats.adoc index 2121e8e..2ae0eb1 100644 --- a/Documentation/pretty-formats.adoc +++ b/Documentation/pretty-formats.adoc
@@ -18,54 +18,72 @@ linkgit:git-config[1]). Here are the details of the built-in formats: -* `oneline` - - <hash> <title-line> +`oneline`:: ++ +[synopsis] +-- +<hash> <title-line> +-- + This is designed to be as compact as possible. -* `short` +`short`:: ++ +[synopsis] +-- +commit <hash> +Author: <author> - commit <hash> - Author: <author> + <title-line> +-- - <title-line> +`medium`:: ++ +[synopsis] +-- +commit <hash> +Author: <author> +Date: <author-date> -* `medium` + <title-line> - commit <hash> - Author: <author> - Date: <author-date> + <full-commit-message> +-- - <title-line> +`full`:: ++ +[synopsis] +-- +commit <hash> +Author: <author> +Commit: <committer> - <full-commit-message> + <title-line> -* `full` + <full-commit-message> +-- - commit <hash> - Author: <author> - Commit: <committer> +`fuller`:: ++ +[synopsis] +-- +commit <hash> +Author: <author> +AuthorDate: <author-date> +Commit: <committer> +CommitDate: <committer-date> - <title-line> + <title-line> - <full-commit-message> + <full-commit-message> +-- -* `fuller` - - commit <hash> - Author: <author> - AuthorDate: <author-date> - Commit: <committer> - CommitDate: <committer-date> - - <title-line> - - <full-commit-message> - -* `reference` - - <abbrev-hash> (<title-line>, <short-author-date>) +`reference`:: ++ +[synopsis] +-- +<abbrev-hash> (<title-line>, <short-author-date>) +-- + This format is used to refer to another commit in a commit message and is the same as ++--pretty=\'format:%C(auto)%h (%s, %ad)'++. By default, @@ -74,23 +92,24 @@ placeholders, its output is not affected by other options like `--decorate` and `--walk-reflogs`. -* `email` - - From <hash> <date> - From: <author> - Date: <author-date> - Subject: [PATCH] <title-line> - - <full-commit-message> - -* `mboxrd` +`email`:: + +[synopsis] +-- +From <hash> <date> +From: <author> +Date: <author-date> +Subject: [PATCH] <title-line> + +<full-commit-message> +-- + +`mboxrd`:: Like `email`, but lines in the commit message starting with "From " (preceded by zero or more ">") are quoted with ">" so they aren't confused as starting a new commit. -* `raw` -+ +`raw`:: The `raw` format shows the entire commit exactly as stored in the commit object. Notably, the hashes are displayed in full, regardless of whether `--abbrev` or @@ -101,8 +120,7 @@ `git log --raw`. To get full object names in a raw diff format, use `--no-abbrev`. -* `format:<format-string>` -+ +`format:<format-string>`:: The `format:<format-string>` format allows you to specify which information you want to show. It works a little bit like printf format, with the notable exception that you get a newline with `%n` @@ -120,13 +138,18 @@ The placeholders are: - Placeholders that expand to a single literal character: ++ +-- ++%n++:: newline ++%%++:: a raw ++%++ ++%x00++:: ++%x++ followed by two hexadecimal digits is replaced with a byte with the hexadecimal digits' value (we will call this "literal formatting code" in the rest of this document). +-- - Placeholders that affect formatting of later placeholders: ++ +-- ++%Cred++:: switch color to red ++%Cgreen++:: switch color to green ++%Cblue++:: switch color to blue @@ -181,8 +204,11 @@ ++%><|(++_<m>_++)++:: similar to ++%<(++_<n>_++)++, ++%<|(++_<m>_++)++ respectively, but padding both sides (i.e. the text is centered) +-- - Placeholders that expand to information extracted from the commit: ++ +-- +%H+:: commit hash +%h+:: abbreviated commit hash +%T+:: tree hash @@ -227,26 +253,29 @@ linkgit:git-rev-list[1]) +%d+:: ref names, like the --decorate option of linkgit:git-log[1] +%D+:: ref names without the " (", ")" wrapping. ++%(count)+:: the number of a patch within a patch series. Used only in + `--commit-list-format` in `format-patch` ++%(total)+:: the total number of patches in a patch series. Used only in + `--commit-list-format` in `format-patch` ++%(decorate++`[:<option>,...]`++)++:: ref names with custom decorations. The `decorate` string may be followed by a colon and zero or more comma-separated options. Option values may contain literal formatting codes. These must be used for commas (`%x2C`) and closing parentheses (`%x29`), due to their role in the option syntax. -** `prefix=<value>`: Shown before the list of ref names. Defaults to "{nbsp}++(++". -** `suffix=<value>`: Shown after the list of ref names. Defaults to "+)+". -** `separator=<value>`: Shown between ref names. Defaults to "+,+{nbsp}". -** `pointer=<value>`: Shown between HEAD and the branch it points to, if any. - Defaults to "{nbsp}++->++{nbsp}". -** `tag=<value>`: Shown before tag names. Defaults to "`tag:`{nbsp}". +`prefix=<value>`;; Shown before the list of ref names. Defaults to "{nbsp}++(++". +`suffix=<value>`;; Shown after the list of ref names. Defaults to "+)+". +`separator=<value>`;; Shown between ref names. Defaults to "+,+{nbsp}". +`pointer=<value>`;; Shown between HEAD and the branch it points to, if any. + Defaults to "{nbsp}->{nbsp}". +`tag=<value>`;; Shown before tag names. Defaults to "`tag:`{nbsp}". + --- For example, to produce decorations with no wrapping or tag annotations, and spaces as separators: - -++%(decorate:prefix=,suffix=,tag=,separator= )++ --- +--------------------- + %(decorate:prefix=,suffix=,tag=,separator= ) +--------------------- ++%(describe++`[:<option>,...]`++)++:: human-readable name, like linkgit:git-describe[1]; empty string for @@ -254,15 +283,15 @@ zero or more comma-separated options. Descriptions can be inconsistent when tags are added or removed at the same time. + -** `tags[=<bool-value>]`: Instead of only considering annotated tags, +`tags[=<bool-value>]`;; Instead of only considering annotated tags, consider lightweight tags as well. -** `abbrev=<number>`: Instead of using the default number of hexadecimal digits +`abbrev=<number>`;; Instead of using the default number of hexadecimal digits (which will vary according to the number of objects in the repository with a - default of 7) of the abbreviated object name, use <number> digits, or as many + default of 7) of the abbreviated object name, use _<number>_ digits, or as many digits as needed to form a unique object name. -** `match=<pattern>`: Only consider tags matching the given +`match=<pattern>`;; Only consider tags matching the given `glob(7)` _<pattern>_, excluding the `refs/tags/` prefix. -** `exclude=<pattern>`: Do not consider tags matching the given +`exclude=<pattern>`;; Do not consider tags matching the given `glob(7)` _<pattern>_, excluding the `refs/tags/` prefix. +%S+:: ref name given on the command line by which the commit was reached @@ -311,7 +340,7 @@ a colon and zero or more comma-separated options. If any option is provided multiple times, the last occurrence wins. + -** `key=<key>`: only show trailers with specified <key>. Matching is done +`key=<key>`;; only show trailers with specified <key>. Matching is done case-insensitively and trailing colon is optional. If option is given multiple times trailer lines matching any of the keys are shown. This option automatically enables the `only` option so that @@ -319,21 +348,21 @@ desired it can be disabled with `only=false`. E.g., +%(trailers:key=Reviewed-by)+ shows trailer lines with key `Reviewed-by`. -** `only[=<bool>]`: select whether non-trailer lines from the trailer +`only[=<bool>]`;; select whether non-trailer lines from the trailer block should be included. -** `separator=<sep>`: specify the separator inserted between trailer + `separator=<sep>`;; specify the separator inserted between trailer lines. Defaults to a line feed character. The string <sep> may contain the literal formatting codes described above. To use comma as separator one must use `%x2C` as it would otherwise be parsed as next option. E.g., +%(trailers:key=Ticket,separator=%x2C )+ - shows all trailer lines whose key is "Ticket" separated by a comma + shows all trailer lines whose key is `Ticket` separated by a comma and a space. -** `unfold[=<bool>]`: make it behave as if interpret-trailer's `--unfold` +`unfold[=<bool>]`;; make it behave as if interpret-trailer's `--unfold` option was given. E.g., +%(trailers:only,unfold=true)+ unfolds and shows all trailer lines. -** `keyonly[=<bool>]`: only show the key part of the trailer. -** `valueonly[=<bool>]`: only show the value part of the trailer. -** `key_value_separator=<sep>`: specify the separator inserted between +`keyonly[=<bool>]`;; only show the key part of the trailer. +`valueonly[=<bool>]`;; only show the value part of the trailer. +`key_value_separator=<sep>`;; specify the separator inserted between the key and value of each trailer. Defaults to ": ". Otherwise it shares the same semantics as `separator=<sep>` above. @@ -360,9 +389,9 @@ If you add a `' '` (space) after +%+ of a placeholder, a space is inserted immediately before the expansion if and only if the placeholder expands to a non-empty string. +-- -* `tformat:` -+ +`tformat:`:: The `tformat:` format works exactly like `format:`, except that it provides "terminator" semantics instead of "separator" semantics. In other words, each commit has the message terminator character (usually a
diff --git a/Documentation/pull-fetch-param.adoc b/Documentation/pull-fetch-param.adoc index bb2cf6a..d903dc8 100644 --- a/Documentation/pull-fetch-param.adoc +++ b/Documentation/pull-fetch-param.adoc
@@ -1,20 +1,20 @@ -<repository>:: +_<repository>_:: The "remote" repository that is the source of a fetch or pull operation. This parameter can be either a URL (see the section <<URLS,GIT URLS>> below) or the name of a remote (see the section <<REMOTES,REMOTES>> below). ifndef::git-pull[] -<group>:: +_<group>_:: A name referring to a list of repositories as the value - of remotes.<group> in the configuration file. + of `remotes.<group>` in the configuration file. (See linkgit:git-config[1]). endif::git-pull[] [[fetch-refspec]] -<refspec>:: +_<refspec>_:: Specifies which refs to fetch and which local refs to update. - When no <refspec>s appear on the command line, the refs to fetch + When no __<refspec>__s appear on the command line, the refs to fetch are read from `remote.<repository>.fetch` variables instead ifndef::git-pull[] (see <<CRTB,CONFIGURED REMOTE-TRACKING BRANCHES>> below). @@ -24,18 +24,18 @@ in linkgit:git-fetch[1]). endif::git-pull[] + -The format of a <refspec> parameter is an optional plus -`+`, followed by the source <src>, followed -by a colon `:`, followed by the destination <dst>. -The colon can be omitted when <dst> is empty. <src> is +The format of a _<refspec>_ parameter is an optional plus +`+`, followed by the source _<src>_, followed +by a colon `:`, followed by the destination _<dst>_. +The colon can be omitted when _<dst>_ is empty. _<src>_ is typically a ref, or a glob pattern with a single `*` that is used to match a set of refs, but it can also be a fully spelled hex object name. + -A <refspec> may contain a `*` in its <src> to indicate a simple pattern +A _<refspec>_ may contain a `*` in its _<src>_ to indicate a simple pattern match. Such a refspec functions like a glob that matches any ref with the -pattern. A pattern <refspec> must have one and only one `*` in both the <src> and -<dst>. It will map refs to the destination by replacing the `*` with the +pattern. A pattern _<refspec>_ must have one and only one `*` in both the _<src>_ and +_<dst>_. It will map refs to the destination by replacing the `*` with the contents matched from the source. + If a refspec is prefixed by `^`, it will be interpreted as a negative @@ -45,14 +45,14 @@ not match any negative refspec. Negative refspecs can be useful to restrict the scope of a pattern refspec so that it will not include specific refs. Negative refspecs can themselves be pattern refspecs. However, they may only -contain a <src> and do not specify a <dst>. Fully spelled out hex object +contain a _<src>_ and do not specify a _<dst>_. Fully spelled out hex object names are also not supported. + `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`; it requests fetching everything up to the given tag. + -The remote ref that matches <src> -is fetched, and if <dst> is not an empty string, an attempt +The remote ref that matches _<src>_ +is fetched, and if _<dst>_ is not an empty string, an attempt is made to update the local ref that matches it. + Whether that update is allowed without `--force` depends on the ref @@ -60,7 +60,7 @@ whether the update is considered to be a fast-forward. Generally, the same rules apply for fetching as when pushing, see the `<refspec>...` section of linkgit:git-push[1] for what those are. Exceptions to those -rules particular to 'git fetch' are noted below. +rules particular to `git fetch` are noted below. + Until Git version 2.20, and unlike when pushing with linkgit:git-push[1], any updates to `refs/tags/*` would be accepted @@ -101,19 +101,19 @@ ifdef::git-pull[] + [NOTE] -There is a difference between listing multiple <refspec> -directly on 'git pull' command line and having multiple +There is a difference between listing multiple _<refspec>_ +directly on `git pull` command line and having multiple `remote.<repository>.fetch` entries in your configuration -for a <repository> and running a -'git pull' command without any explicit <refspec> parameters. -<refspec>s listed explicitly on the command line are always +for a _<repository>_ and running a +`git pull` command without any explicit _<refspec>_ parameters. +__<refspec>__s listed explicitly on the command line are always merged into the current branch after fetching. In other words, -if you list more than one remote ref, 'git pull' will create +if you list more than one remote ref, `git pull` will create an Octopus merge. On the other hand, if you do not list any -explicit <refspec> parameter on the command line, 'git pull' -will fetch all the <refspec>s it finds in the +explicit _<refspec>_ parameter on the command line, `git pull` +will fetch all the __<refspec>__s it finds in the `remote.<repository>.fetch` configuration and merge -only the first <refspec> found into the current branch. +only the first _<refspec>_ found into the current branch. This is because making an Octopus from remote refs is rarely done, while keeping track of multiple remote heads in one-go by fetching more than one
diff --git a/Documentation/ref-storage-format.adoc b/Documentation/ref-storage-format.adoc index 14fff8a..6a8db47 100644 --- a/Documentation/ref-storage-format.adoc +++ b/Documentation/ref-storage-format.adoc
@@ -1,3 +1,3 @@ -* `files` for loose files with packed-refs. This is the default. -* `reftable` for the reftable format. This format is experimental and its +`files`;; for loose files with packed-refs. This is the default. +`reftable`;; for the reftable format. This format is experimental and its internals are subject to change.
diff --git a/Documentation/rerere-options.adoc b/Documentation/rerere-options.adoc index b0b9201..4395fe0 100644 --- a/Documentation/rerere-options.adoc +++ b/Documentation/rerere-options.adoc
@@ -4,6 +4,6 @@ the current conflict to update the files in the working tree, allow it to also update the index with the result of resolution. `--no-rerere-autoupdate` is a good way to - double-check what `rerere` did and catch potential + double-check what linkgit:git-rerere[1] did and catch potential mismerges, before committing the result to the index with a - separate `git add`. + separate linkgit:git-add[1].
diff --git a/Documentation/rev-list-options.adoc b/Documentation/rev-list-options.adoc index d9665d8..2d195a1 100644 --- a/Documentation/rev-list-options.adoc +++ b/Documentation/rev-list-options.adoc
@@ -148,6 +148,10 @@ from the point where it diverged from the remote branch, given that arbitrary merges can be valid topic branch changes. +`--maximal-only`:: + Restrict the output commits to be those that are not reachable + from any other commits in the revision range. + `--not`:: Reverses the meaning of the '{caret}' prefix (or lack thereof) for all following revision specifiers, up to the next `--not`. @@ -983,7 +987,9 @@ is the same as 'blob:limit=1024'. + The form `--filter=object:type=(tag|commit|tree|blob)` omits all objects -which are not of the requested type. +which are not of the requested type. Note that explicitly provided objects +ignore filters and are always printed unless `--filter-provided-objects` is +also specified. + The form `--filter=sparse:oid=<blob-ish>` uses a sparse-checkout specification contained in the blob (or blob-expression) _<blob-ish>_ @@ -1094,7 +1100,7 @@ Overrides a previous `--no-walk`. endif::git-shortlog[] -ifndef::git-shortlog[] +ifndef::git-shortlog,git-replay[] Commit Formatting ~~~~~~~~~~~~~~~~~ @@ -1263,4 +1269,4 @@ counts and print the count for equivalent commits separated by a tab. endif::git-rev-list[] -endif::git-shortlog[] +endif::git-shortlog,git-replay[]
diff --git a/Documentation/scalar.adoc b/Documentation/scalar.adoc index f81b283..5252fb1 100644 --- a/Documentation/scalar.adoc +++ b/Documentation/scalar.adoc
@@ -197,6 +197,170 @@ This subcommand lets you delete an existing Scalar enlistment from your local file system, unregistering the repository. +RECOMMENDED CONFIG VALUES +------------------------- + +As part of both `scalar clone` and `scalar register`, certain Git config +values are set to optimize for large repositories or cross-platform support. +These options are updated in new Git versions according to the best known +advice for large repositories, and users can get the latest recommendations +by running `scalar reconfigure [--all]`. + +This section lists justifications for the config values that are set in the +latest version. + +am.keepCR=true:: + This setting is important for cross-platform development across Windows + and non-Windows platforms and keeping carriage return (`\r`) characters + in certain workflows. + +commitGraph.changedPaths=true:: + This setting helps the background maintenance steps that compute the + serialized commit-graph to also store changed-path Bloom filters. This + accelerates file history commands and allows users to automatically + benefit without running a foreground command. + +commitGraph.generationVersion=1:: + While the preferred version is 2 for performance reasons, existing users + that had version 1 by default will need special care in upgrading to + version 2. This is likely to change in the future as the upgrade story + solidifies. + +core.autoCRLF=false:: + This removes the transformation of worktree files to add CRLF line + endings when only LF line endings exist. This is removed for performance + reasons. Repositories that use tools that care about CRLF line endings + should commit the necessary files with those line endings instead. + +core.logAllRefUpdates=true:: + This enables the reflog on all branches. While this is a performance + cost for large repositories, it is frequently an important data source + for users to get out of bad situations or to seek support from experts. + +core.safeCRLF=false:: + Similar to `core.autoCRLF=false`, this disables checks around whether + the CRLF conversion is reversible. This is a performance improvement, + but can be dangerous if `core.autoCRLF` is reenabled by the user. + +credential.https://dev.azure.com.useHttpPath=true:: + This setting enables the `credential.useHttpPath` feature only for web + URLs for Azure DevOps. This is important for users interacting with that + service using multiple organizations and thus multiple credential + tokens. + +feature.experimental=false:: + This disables the "experimental" optimizations grouped under this + feature config. The expectation is that all valuable optimizations are + also set explicitly by Scalar config, and any differences are + intentional. Notable differences include several bitmap-related config + options which are disabled for client-focused Scalar repos. + +feature.manyFiles=false:: + This disables the "many files" optimizations grouped under this feature + config. The expectation is that all valuable optimizations are also set + explicitly by Scalar config, and any differences are intentional. + +fetch.showForcedUpdates=false:: + This disables the check at the end of `git fetch` that notifies the user + if the ref update was a forced update (one where the previous position + is not reachable from the latest position). This check can be very + expensive in large repositories, so is disabled and replaced with an + advice message. Set `advice.fetchShowForcedUpdates=false` to disable + this advice message. + +fetch.unpackLimit=1:: + This setting prevents Git from unpacking packfiles into loose objects + as they are downloaded from the server. The default limit of 100 was + intended as a way to prevent performance issues from too many packfiles, + but Scalar uses background maintenance to group packfiles and cover them + with a multi-pack-index, removing this issue. + +fetch.writeCommitGraph=false:: + This config setting was created to help users automatically update their + commit-graph files as they perform fetches. However, this takes time + from foreground fetches and pulls and Scalar uses background maintenance + for this function instead. + +gc.auto=0:: + This disables automatic garbage collection, since Scalar uses background + maintenance to keep the repository data in good shape. + +gui.GCWarning=false:: + Since Scalar disables garbage collection by setting `gc.auto=0`, the + `git-gui` tool may start to warn about this setting. Disable this + warning as Scalar's background maintenance configuration makes the + warning irrelevant. + +index.skipHash=true:: + Disable computing the hash of the index contents as it is being written. + This assists with performance, especially for large index files. + +index.threads=true:: + This tells Git to automatically detect how many threads it should use + when reading the index due to the default value of `core.preloadIndex`, + which enables parallel index reads. This explicit setting also enables + `index.recordOffsetTable=true` to speed up parallel index reads. + +index.version=4:: + This index version adds compression to the path names, reducing the size + of the index in a significant way for large repos. This is an important + performance boost. + +log.excludeDecoration=refs/prefetch/*:: + Since Scalar enables background maintenance with the `incremental` + strategy, this setting avoids polluting `git log` output with refs + stored by the background prefetch operations. + +merge.renames=true:: + When computing merges in large repos, it is particularly important to + detect renames to maximize the potential for a result that will validate + correctly. Users performing merges locally are more likely to be doing + so because a server-side merge (via pull request or similar) resulted in + conflicts. While this is the default setting, it is set specifically to + override a potential change to `diff.renames` which a user may set for + performance reasons. + +merge.stat=false:: + This disables a diff output after computing a merge. This improves + performance of `git merge` for large repos while reducing noisy output. + +pack.useBitmaps=false:: + This disables the use of `.bitmap` files attached to packfiles. Bitmap + files are optimized for server-side use, not client-side use. Scalar + disables this to avoid some performance issues that can occur if a user + accidentally creates `.bitmap` files. + +pack.usePathWalk=true:: + This enables the `--path-walk` option to `git pack-objects` by default. + This can accelerate the computation and compression of packfiles created + by `git push` and other repack operations. + +receive.autoGC=false:: + Similar to `gc.auto`, this setting is disabled in preference of + background maintenance. + +status.aheadBehind=false:: + This disables the ahead/behind calculation that would normally happen + during a `git status` command. This information is frequently ignored by + users but can be expensive to calculate in large repos that receive + thousands of commits per day. The calculation is replaced with an advice + message that can be disabled by disabling the `advice.statusAheadBehind` + config. + +The following settings are different based on which platform is in use: + +core.untrackedCache=(true|false):: + The untracked cache feature is important for performance benefits on + large repositories, but has demonstrated some bugs on Windows + filesystems. Thus, this is set for other platforms but disabled on + Windows. + +http.sslBackend=schannel:: + On Windows, the `openssl` backend has some issues with certain types of + remote providers and certificate types. Override the default setting to + avoid these common problems. + + SEE ALSO -------- linkgit:git-clone[1], linkgit:git-maintenance[1].
diff --git a/Documentation/signoff-option.adoc b/Documentation/signoff-option.adoc index cddfb22..6fc2769 100644 --- a/Documentation/signoff-option.adoc +++ b/Documentation/signoff-option.adoc
@@ -16,3 +16,7 @@ + The `--no-signoff` option can be used to countermand an earlier `--signoff` option on the command line. ++ +Git does not (and will not) have a configuration variable to enable +the `--signoff` command line option by default; see the +`commit.signoff` entry in linkgit:gitfaq[7] for more details.
diff --git a/Documentation/technical/meson.build b/Documentation/technical/meson.build index faff396..ec07088 100644 --- a/Documentation/technical/meson.build +++ b/Documentation/technical/meson.build
@@ -32,6 +32,7 @@ 'sparse-checkout.adoc', 'sparse-index.adoc', 'trivial-merge.adoc', + 'unambiguous-types.adoc', 'unit-tests.adoc', ]
diff --git a/Documentation/technical/unambiguous-types.adoc b/Documentation/technical/unambiguous-types.adoc new file mode 100644 index 0000000..9a49908 --- /dev/null +++ b/Documentation/technical/unambiguous-types.adoc
@@ -0,0 +1,224 @@ += Unambiguous types + +Most of these mappings are obvious, but there are some nuances and gotchas with +Rust FFI (Foreign Function Interface). + +This document defines clear, one-to-one mappings between primitive types in C, +Rust (and possible other languages in the future). Its purpose is to eliminate +ambiguity in type widths, signedness, and binary representation across +platforms and languages. + +For Git, the only header required to use these unambiguous types in C is +`git-compat-util.h`. + +== Boolean types +[cols="1,1", options="header"] +|=== +| C Type | Rust Type +| bool^1^ | bool +|=== + +== Integer types + +In C, `<stdint.h>` (or an equivalent) must be included. + +[cols="1,1", options="header"] +|=== +| C Type | Rust Type +| uint8_t | u8 +| uint16_t | u16 +| uint32_t | u32 +| uint64_t | u64 + +| int8_t | i8 +| int16_t | i16 +| int32_t | i32 +| int64_t | i64 +|=== + +== Floating-point types + +Rust requires IEEE-754 semantics. +In C, that is typically true, but not guaranteed by the standard. + +[cols="1,1", options="header"] +|=== +| C Type | Rust Type +| float^2^ | f32 +| double^2^ | f64 +|=== + +== Size types + +These types represent pointer-sized integers and are typically defined in +`<stddef.h>` or an equivalent header. + +Size types should be used any time pointer arithmetic is performed e.g. +indexing an array, describing the number of elements in memory, etc... + +[cols="1,1", options="header"] +|=== +| C Type | Rust Type +| size_t^3^ | usize +| ptrdiff_t^3^ | isize +|=== + +== Character types + +This is where C and Rust don't have a clean one-to-one mapping. + +A C `char` and a Rust `u8` share the same bit width, so any C struct containing +a `char` will have the same size as the corresponding Rust struct using `u8`. +In that sense, such structs are safe to pass over the FFI boundary, because +their fields will be laid out identically. However, beyond bit width, C `char` +has additional semantics and platform-dependent behavior that can cause +problems, as discussed below. + +The C language leaves the signedness of `char` implementation defined. Because +our developer build enables -Wsign-compare, comparison of a value of `char` +type with either signed or unsigned integers may trigger warnings from the +compiler. + +Note: Rust's `char` type is an unsigned 32-bit integer that is used to describe +Unicode code points. + +=== Notes +^1^ This is only true if stdbool.h (or equivalent) is used. + +^2^ C does not enforce IEEE-754 compatibility, but Rust expects it. If the +platform/arch for C does not follow IEEE-754 then this equivalence does not +hold. Also, it's assumed that `float` is 32 bits and `double` is 64, but +there may be a strange platform/arch where even this isn't true. + +^3^ C also defines uintptr_t, ssize_t and intptr_t, but these types are +discouraged for FFI purposes. For functions like `read()` and `write()` ssize_t +should be cast to a different, and unambiguous, type before being passed over +the FFI boundary. + + +== Problems with std::ffi::c_* types in Rust +TL;DR: In practice, Rust's `c_*` types aren't guaranteed to match C types for +all possible C compilers, platforms, or architectures, because Rust only +ensures correctness of C types on officially supported targets. These +definitions have changed over time to match more targets which means that the +c_* definitions will differ based on which Rust version Git chooses to use. + +Current list of safe, Rust side, FFI types in Git: + + +* `c_void` +* `CStr` +* `CString` + +Even then, they should be used sparingly, and only where the semantics match +exactly. + +The std::os::raw::c_* directly inherits the problems of core::ffi, which +changes over time and seems to make a best guess at the correct definition for +a given platform/target. This probably isn't a problem for all other platforms +that Rust supports currently, but can anyone say that Rust got it right for all +C compilers of all platforms/targets? + +To give an example: c_long is defined in +footnote:[https://doc.rust-lang.org/1.63.0/src/core/ffi/mod.rs.html#175-189[c_long in 1.63.0]] +footnote:[https://doc.rust-lang.org/1.89.0/src/core/ffi/primitives.rs.html#135-151[c_long in 1.89.0]] + +=== Rust version 1.63.0 + +``` +mod c_long_definition { + cfg_if! { + if #[cfg(all(target_pointer_width = "64", not(windows)))] { + pub type c_long = i64; + pub type NonZero_c_long = crate::num::NonZeroI64; + pub type c_ulong = u64; + pub type NonZero_c_ulong = crate::num::NonZeroU64; + } else { + // The minimal size of `long` in the C standard is 32 bits + pub type c_long = i32; + pub type NonZero_c_long = crate::num::NonZeroI32; + pub type c_ulong = u32; + pub type NonZero_c_ulong = crate::num::NonZeroU32; + } + } +} +``` + +=== Rust version 1.89.0 + +``` +mod c_long_definition { + crate::cfg_select! { + any( + all(target_pointer_width = "64", not(windows)), + // wasm32 Linux ABI uses 64-bit long + all(target_arch = "wasm32", target_os = "linux") + ) => { + pub(super) type c_long = i64; + pub(super) type c_ulong = u64; + } + _ => { + // The minimal size of `long` in the C standard is 32 bits + pub(super) type c_long = i32; + pub(super) type c_ulong = u32; + } + } +} +``` + +Even for the cases where C types are correctly mapped to Rust types via +std::ffi::c_* there are still problems. Let's take c_char for example. On some +platforms it's u8 on others it's i8. + +=== Subtraction underflow in debug mode + +The following code will panic in debug on platforms that define c_char as u8, +but won't if it's an i8. + +``` +let mut x: std::ffi::c_char = 0; +x -= 1; +``` + +=== Inconsistent shift behavior + +`x` will be 0xC0 for platforms that use i8, but will be 0x40 where it's u8. + +``` +let mut x: std::ffi::c_char = 0x80; +x >>= 1; +``` + +=== Equality fails to compile on some platforms + +The following will not compile on platforms that define c_char as i8, but will +if it's u8. You can cast x e.g. `assert_eq!(x as u8, b'a');`, but then you get +a warning on platforms that use u8 and a clean compilation where i8 is used. + +``` +let mut x: std::ffi::c_char = 0x61; +assert_eq!(x, b'a'); +``` + +== Enum types +Rust enum types should not be used as FFI types. Rust enum types are more like +C union types than C enum's. For something like: + +``` +#[repr(C, u8)] +enum Fruit { + Apple, + Banana, + Cherry, +} +``` + +It's easy enough to make sure the Rust enum matches what C would expect, but a +more complex type like. + +``` +enum HashResult { + SHA1([u8; 20]), + SHA256([u8; 32]), +} +``` + +The Rust compiler has to add a discriminant to the enum to distinguish between +the variants. The width, location, and values for that discriminant is up to +the Rust compiler and is not ABI stable.
diff --git a/Documentation/urls-remotes.adoc b/Documentation/urls-remotes.adoc index 57b1646..6878bbe 100644 --- a/Documentation/urls-remotes.adoc +++ b/Documentation/urls-remotes.adoc
@@ -4,7 +4,7 @@ ------------------ The name of one of the following can be used instead -of a URL as `<repository>` argument: +of a URL as _<repository>_ argument: * a remote in the Git configuration file: `$GIT_DIR/config`, * a file in the `$GIT_DIR/remotes` directory, or @@ -32,8 +32,8 @@ fetch = <refspec> ------------ -The `<pushurl>` is used for pushes only. It is optional and defaults -to `<URL>`. Pushing to a remote affects all defined pushurls or all +The _<pushurl>_ is used for pushes only. It is optional and defaults +to _<URL>_. Pushing to a remote affects all defined pushurls or all defined urls if no pushurls are defined. Fetch, however, will only fetch from the first defined url if multiple urls are defined. @@ -54,8 +54,8 @@ ------------ -`Push:` lines are used by 'git push' and -`Pull:` lines are used by 'git pull' and 'git fetch'. +`Push:` lines are used by `git push` and +`Pull:` lines are used by `git pull` and `git fetch`. Multiple `Push:` and `Pull:` lines may be specified for additional branch mappings. @@ -72,12 +72,12 @@ <URL>#<head> ------------ -`<URL>` is required; `#<head>` is optional. +_<URL>_ is required; `#<head>` is optional. Depending on the operation, git will use one of the following refspecs, if you don't provide one on the command line. -`<branch>` is the name of this file in `$GIT_DIR/branches` and -`<head>` defaults to `master`. +_<branch>_ is the name of this file in `$GIT_DIR/branches` and +_<head>_ defaults to `master`. git fetch uses: @@ -111,7 +111,7 @@ 'origin/main' have diverged, and have 2 and 3 different commits each respectively". -The upstream is stored in `.git/config`, in the "remote" and "merge" +The upstream is stored in `.git/config`, in the "`remote`" and "`merge`" fields. For example, if `main`'s upstream is `origin/main`: ------------
diff --git a/Documentation/user-manual.adoc b/Documentation/user-manual.adoc index 7696987..64009ba 100644 --- a/Documentation/user-manual.adoc +++ b/Documentation/user-manual.adoc
@@ -4466,7 +4466,7 @@ $ git diff v2.6.15..v2.6.16 # diff between two tagged versions $ git diff v2.6.15..HEAD # diff with current head $ git grep "foo()" # search working directory for "foo()" -$ git grep v2.6.15 "foo()" # search old tree for "foo()" +$ git grep "foo()" v2.6.15 # search old tree for "foo()" $ git show v2.6.15:a.txt # look at old version of a.txt -----------------------------------------------
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 8d5bbf7..92ea811 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN
@@ -1,6 +1,6 @@ #!/bin/sh -DEF_VER=v2.52.0 +DEF_VER=v2.54.0-rc0 LF=' '
diff --git a/Makefile b/Makefile index 7e0f77e..5d22394 100644 --- a/Makefile +++ b/Makefile
@@ -95,11 +95,21 @@ # and LDFLAGS appropriately. # # Define NO_DARWIN_PORTS if you are building on Darwin/Mac OS X, -# have DarwinPorts installed in /opt/local, but don't want GIT to +# have DarwinPorts (which is an old name for MacPorts) installed +# in /opt/local, but don't want GIT to # link against any libraries installed there. If defined you may # specify your own (or DarwinPort's) include directories and # library directories by defining CFLAGS and LDFLAGS appropriately. # +# Define NO_HOMEBREW if you don't want to use gettext, libiconv and +# msgfmt installed by Homebrew. +# +# Define HOMEBREW_PREFIX if you have Homebrew installed in a non-default +# location on macOS or on Linux and want to use it. +# +# Define USE_HOMEBREW_LIBICONV to link against libiconv installed by +# Homebrew, if present. +# # Define NO_APPLE_COMMON_CRYPTO if you are building on Darwin/Mac OS X # and do not want to use Apple's CommonCrypto library. This allows you # to provide your own OpenSSL library, for example from MacPorts. @@ -332,6 +342,9 @@ # If it isn't set, fallback to $LC_ALL, $LANG or use the first utf-8 # locale returned by "locale -a". # +# Define TEST_CONTRIB_TOO to make "make test" run tests in contrib/ +# directories. +# # Define HAVE_CLOCK_GETTIME if your platform has clock_gettime. # # Define HAVE_CLOCK_MONOTONIC if your platform has CLOCK_MONOTONIC. @@ -981,7 +994,7 @@ SANITIZE_ADDRESS = # For the 'coccicheck' target -SPATCH_INCLUDE_FLAGS = --all-includes +SPATCH_INCLUDE_FLAGS = --all-includes $(addprefix -I ,compat ewah refs sha256 trace2 win32 xdiff) SPATCH_FLAGS = SPATCH_TEST_FLAGS = @@ -992,8 +1005,8 @@ # COMPUTE_HEADER_DEPENDENCIES=no this will be unset too. SPATCH_USE_O_DEPENDENCIES = YesPlease -# Set SPATCH_CONCAT_COCCI to concatenate the contrib/cocci/*.cocci -# files into a single contrib/cocci/ALL.cocci before running +# Set SPATCH_CONCAT_COCCI to concatenate the tools/coccinelle/*.cocci +# files into a single tools/coccinelle/ALL.cocci before running # "coccicheck". # # Pros: @@ -1012,7 +1025,7 @@ # generate a specific patch, e.g. this will always use strbuf.cocci, # not ALL.cocci: # -# make contrib/coccinelle/strbuf.cocci.patch +# make tools/coccinelle/strbuf.cocci.patch SPATCH_CONCAT_COCCI = YesPlease # Rebuild 'coccicheck' if $(SPATCH), its flags etc. change @@ -1053,11 +1066,13 @@ '*.sh' \ ':!*[tp][0-9][0-9][0-9][0-9]*' \ ':!contrib' \ + ':!tools' \ 2>/dev/null || \ $(FIND) . \ \( -name .git -type d -prune \) \ -o \( -name '[tp][0-9][0-9][0-9][0-9]*' -prune \) \ -o \( -name contrib -type d -prune \) \ + -o \( -name tools -type d -prune \) \ -o \( -name build -type d -prune \) \ -o \( -name .build -type d -prune \) \ -o \( -name 'trash*' -type d -prune \) \ @@ -1201,6 +1216,9 @@ LIB_OBJS += object-name.o LIB_OBJS += object.o LIB_OBJS += odb.o +LIB_OBJS += odb/source.o +LIB_OBJS += odb/source-files.o +LIB_OBJS += odb/streaming.o LIB_OBJS += oid-array.o LIB_OBJS += oidmap.o LIB_OBJS += oidset.o @@ -1274,6 +1292,7 @@ LIB_OBJS += repack-midx.o LIB_OBJS += repack-promisor.o LIB_OBJS += replace-object.o +LIB_OBJS += replay.o LIB_OBJS += repo-settings.o LIB_OBJS += repository.o LIB_OBJS += rerere.o @@ -1294,7 +1313,6 @@ LIB_OBJS += stable-qsort.o LIB_OBJS += statinfo.o LIB_OBJS += strbuf.o -LIB_OBJS += streaming.o LIB_OBJS += string-list.o LIB_OBJS += strmap.o LIB_OBJS += strvec.o @@ -1407,6 +1425,7 @@ BUILTIN_OBJS += builtin/grep.o BUILTIN_OBJS += builtin/hash-object.o BUILTIN_OBJS += builtin/help.o +BUILTIN_OBJS += builtin/history.o BUILTIN_OBJS += builtin/hook.o BUILTIN_OBJS += builtin/index-pack.o BUILTIN_OBJS += builtin/init-db.o @@ -1506,6 +1525,7 @@ CLAR_TEST_SUITES += u-example-decorate CLAR_TEST_SUITES += u-hash CLAR_TEST_SUITES += u-hashmap +CLAR_TEST_SUITES += u-list-objects-filter-options CLAR_TEST_SUITES += u-mem-pool CLAR_TEST_SUITES += u-oid-array CLAR_TEST_SUITES += u-oidmap @@ -1525,6 +1545,7 @@ CLAR_TEST_SUITES += u-strvec CLAR_TEST_SUITES += u-trailer CLAR_TEST_SUITES += u-urlmatch-normalization +CLAR_TEST_SUITES += u-utf8-width CLAR_TEST_PROG = $(UNIT_TEST_BIN)/unit-tests$(X) CLAR_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(CLAR_TEST_SUITES)) CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/clar/clar.o @@ -1534,7 +1555,10 @@ UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o +RUST_SOURCES += src/csum_file.rs +RUST_SOURCES += src/hash.rs RUST_SOURCES += src/lib.rs +RUST_SOURCES += src/loose.rs RUST_SOURCES += src/varint.rs GIT-VERSION-FILE: FORCE @@ -1583,10 +1607,12 @@ endif ifneq ($(filter leak,$(SANITIZERS)),) BASIC_CFLAGS += -O0 +NO_MMAP = CatchMapLeaks SANITIZE_LEAK = YesCompiledWithIt endif ifneq ($(filter address,$(SANITIZERS)),) NO_REGEX = NeededForASAN +NO_MMAP = NeededForASAN SANITIZE_ADDRESS = YesCompiledWithIt endif endif @@ -1675,11 +1701,21 @@ BASIC_CFLAGS += -I/sw/include BASIC_LDFLAGS += -L/sw/lib endif + ifeq ($(shell test -d /opt/sw/lib && echo y),y) + BASIC_CFLAGS += -I/opt/sw/include + BASIC_LDFLAGS += -L/opt/sw/lib + ifeq ($(shell test -e /opt/sw/lib/libiconv.dylib && echo y),y) + HAS_GOOD_LIBICONV = Yes + endif + endif endif ifndef NO_DARWIN_PORTS ifeq ($(shell test -d /opt/local/lib && echo y),y) BASIC_CFLAGS += -I/opt/local/include BASIC_LDFLAGS += -L/opt/local/lib + ifeq ($(shell test -e /opt/local/lib/libiconv.dylib && echo y),y) + HAS_GOOD_LIBICONV = Yes + endif endif endif ifndef NO_APPLE_COMMON_CRYPTO @@ -1690,6 +1726,24 @@ PTHREAD_LIBS = endif +ifndef NO_HOMEBREW +ifdef HOMEBREW_PREFIX +ifeq ($(shell test -d $(HOMEBREW_PREFIX)/opt/gettext && echo y),y) + BASIC_CFLAGS += -I$(HOMEBREW_PREFIX)/opt/gettext/include + BASIC_LDFLAGS += -L$(HOMEBREW_PREFIX)/opt/gettext/lib +endif +ifeq ($(shell test -x $(HOMEBREW_PREFIX)/opt/gettext/msgfmt && echo y),y) + MSGFMT = $(HOMEBREW_PREFIX)/opt/gettext/msgfmt +endif +ifdef USE_HOMEBREW_LIBICONV +ifeq ($(shell test -d $(HOMEBREW_PREFIX)/opt/libiconv && echo y),y) + ICONVDIR ?= $(HOMEBREW_PREFIX)/opt/libiconv + HAS_GOOD_LIBICONV = Yes +endif +endif +endif +endif + ifdef NO_LIBGEN_H COMPAT_CFLAGS += -DNO_LIBGEN_H COMPAT_OBJS += compat/basename.o @@ -1830,6 +1884,11 @@ endif EXTLIBS += $(ICONV_LINK) -liconv endif + ifdef NEEDS_GOOD_LIBICONV + ifndef HAS_GOOD_LIBICONV + BASIC_CFLAGS += -DICONV_RESTART_RESET + endif + endif endif ifdef ICONV_OMITS_BOM BASIC_CFLAGS += -DICONV_OMITS_BOM @@ -1917,7 +1976,6 @@ endif ifdef NO_MKDTEMP COMPAT_CFLAGS += -DNO_MKDTEMP - COMPAT_OBJS += compat/mkdtemp.o endif ifdef MKDIR_WO_TRAILING_SLASH COMPAT_CFLAGS += -DMKDIR_WO_TRAILING_SLASH @@ -1971,6 +2029,10 @@ COMPAT_CFLAGS += -DNO_PREAD COMPAT_OBJS += compat/pread.o endif +ifdef NO_WRITEV + COMPAT_CFLAGS += -DNO_WRITEV + COMPAT_OBJS += compat/writev.o +endif ifdef NO_FAST_WORKING_DIRECTORY BASIC_CFLAGS += -DNO_FAST_WORKING_DIRECTORY endif @@ -2565,7 +2627,7 @@ shell_compatibility_test: please_set_SHELL_PATH_to_a_more_modern_shell -strip: $(PROGRAMS) git$X +strip: $(PROGRAMS) git$X scalar$X $(STRIP) $(STRIP_OPTS) $^ ### Target-specific flags and dependencies @@ -2613,6 +2675,7 @@ help.sp help.s help.o: command-list.h builtin/bugreport.sp builtin/bugreport.s builtin/bugreport.o: hook-list.h +builtin/hook.sp builtin/hook.s builtin/hook.o: hook-list.h builtin/help.sp builtin/help.s builtin/help.o: config-list.h GIT-PREFIX builtin/help.sp builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \ @@ -2637,20 +2700,21 @@ ln -s $< $@ 2>/dev/null || \ cp $< $@ -config-list.h: generate-configlist.sh +config-list.h: tools/generate-configlist.sh + @mkdir -p .depend + $(QUIET_GEN)$(SHELL_PATH) ./tools/generate-configlist.sh . $@ .depend/config-list.h.d -config-list.h: Documentation/*config.adoc Documentation/config/*.adoc - $(QUIET_GEN)$(SHELL_PATH) ./generate-configlist.sh . $@ +-include .depend/config-list.h.d -command-list.h: generate-cmdlist.sh command-list.txt +command-list.h: tools/generate-cmdlist.sh command-list.txt command-list.h: $(wildcard Documentation/git*.adoc) - $(QUIET_GEN)$(SHELL_PATH) ./generate-cmdlist.sh \ + $(QUIET_GEN)$(SHELL_PATH) ./tools/generate-cmdlist.sh \ $(patsubst %,--exclude-program %,$(EXCLUDED_PROGRAMS)) \ . $@ -hook-list.h: generate-hooklist.sh Documentation/githooks.adoc - $(QUIET_GEN)$(SHELL_PATH) ./generate-hooklist.sh . $@ +hook-list.h: tools/generate-hooklist.sh Documentation/githooks.adoc + $(QUIET_GEN)$(SHELL_PATH) ./tools/generate-hooklist.sh . $@ SCRIPT_DEFINES = $(SHELL_PATH_SQ):$(DIFF_SQ):\ $(localedir_SQ):$(USE_GETTEXT_SCHEME):$(SANE_TOOL_PATH_SQ):\ @@ -2663,8 +2727,8 @@ echo "$$FLAGS" >$@; \ fi -$(SCRIPT_SH_GEN) $(SCRIPT_LIB) : % : %.sh generate-script.sh GIT-BUILD-OPTIONS GIT-SCRIPT-DEFINES - $(QUIET_GEN)./generate-script.sh "$<" "$@+" ./GIT-BUILD-OPTIONS && \ +$(SCRIPT_SH_GEN) $(SCRIPT_LIB) : % : %.sh tools/generate-script.sh GIT-BUILD-OPTIONS GIT-SCRIPT-DEFINES + $(QUIET_GEN)./tools/generate-script.sh "$<" "$@+" ./GIT-BUILD-OPTIONS && \ mv $@+ $@ git.rc: git.rc.in GIT-VERSION-GEN GIT-VERSION-FILE @@ -2704,8 +2768,8 @@ PERL_DEFINES += $(gitexecdir) $(perllibdir) $(localedir) -$(SCRIPT_PERL_GEN): % : %.perl generate-perl.sh GIT-PERL-DEFINES GIT-PERL-HEADER GIT-VERSION-FILE - $(QUIET_GEN)$(SHELL_PATH) generate-perl.sh ./GIT-BUILD-OPTIONS ./GIT-VERSION-FILE GIT-PERL-HEADER "$<" "$@+" && \ +$(SCRIPT_PERL_GEN): % : %.perl tools/generate-perl.sh GIT-PERL-DEFINES GIT-PERL-HEADER GIT-VERSION-FILE + $(QUIET_GEN)$(SHELL_PATH) tools/generate-perl.sh ./GIT-BUILD-OPTIONS ./GIT-VERSION-FILE GIT-PERL-HEADER "$<" "$@+" && \ mv $@+ $@ PERL_DEFINES := $(subst $(space),:,$(PERL_DEFINES)) @@ -2733,8 +2797,8 @@ perllibdir: @echo '$(perllibdir_SQ)' -git-instaweb: git-instaweb.sh generate-script.sh GIT-BUILD-OPTIONS GIT-SCRIPT-DEFINES - $(QUIET_GEN)./generate-script.sh "$<" "$@+" ./GIT-BUILD-OPTIONS && \ +git-instaweb: git-instaweb.sh tools/generate-script.sh GIT-BUILD-OPTIONS GIT-SCRIPT-DEFINES + $(QUIET_GEN)./tools/generate-script.sh "$<" "$@+" ./GIT-BUILD-OPTIONS && \ chmod +x $@+ && \ mv $@+ $@ else # NO_PERL @@ -2751,9 +2815,9 @@ $(SCRIPT_PYTHON_GEN): GIT-BUILD-OPTIONS ifndef NO_PYTHON -$(SCRIPT_PYTHON_GEN): generate-python.sh +$(SCRIPT_PYTHON_GEN): tools/generate-python.sh $(SCRIPT_PYTHON_GEN): % : %.py - $(QUIET_GEN)$(SHELL_PATH) generate-python.sh ./GIT-BUILD-OPTIONS "$<" "$@" + $(QUIET_GEN)$(SHELL_PATH) tools/generate-python.sh ./GIT-BUILD-OPTIONS "$<" "$@" else # NO_PYTHON $(SCRIPT_PYTHON_GEN): % : unimplemented.sh $(QUIET_GEN) \ @@ -2830,6 +2894,10 @@ dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d) dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS)))) +ifeq ($(uname_S),Darwin) + dep_dirs += $(addsuffix .depend,$(sort $(dir contrib/credential/osxkeychain/git-credential-osxkeychain.o))) +endif + ifeq ($(COMPUTE_HEADER_DEPENDENCIES),yes) $(dep_dirs): @mkdir -p $@ @@ -2963,7 +3031,7 @@ $(LIB_FILE): $(LIB_OBJS) $(QUIET_AR)$(RM) $@ && $(AR) $(ARFLAGS) $@ $^ -$(RUST_LIB): Cargo.toml $(RUST_SOURCES) +$(RUST_LIB): Cargo.toml $(RUST_SOURCES) $(LIB_FILE) $(QUIET_CARGO)cargo build $(CARGO_ARGS) .PHONY: rust @@ -3169,9 +3237,9 @@ NO_PERL_CPAN_FALLBACKS_SQ = $(subst ','\'',$(NO_PERL_CPAN_FALLBACKS)) endif -perl/build/lib/%.pm: perl/%.pm generate-perl.sh GIT-BUILD-OPTIONS GIT-VERSION-FILE GIT-PERL-DEFINES +perl/build/lib/%.pm: perl/%.pm tools/generate-perl.sh GIT-BUILD-OPTIONS GIT-VERSION-FILE GIT-PERL-DEFINES $(call mkdir_p_parent_template) - $(QUIET_GEN)$(SHELL_PATH) generate-perl.sh ./GIT-BUILD-OPTIONS ./GIT-VERSION-FILE GIT-PERL-HEADER "$<" "$@" + $(QUIET_GEN)$(SHELL_PATH) tools/generate-perl.sh ./GIT-BUILD-OPTIONS ./GIT-VERSION-FILE GIT-PERL-HEADER "$<" "$@" perl/build/man/man3/Git.3pm: perl/Git.pm $(call mkdir_p_parent_template) @@ -3325,6 +3393,9 @@ test: all $(MAKE) -C t/ all +ifdef TEST_CONTRIB_TOO + $(MAKE) -C contrib/ test +endif perf: all $(MAKE) -C t/perf/ all @@ -3397,15 +3468,15 @@ exit 1; \ fi -COCCI_GEN_ALL = .build/contrib/coccinelle/ALL.cocci -COCCI_GLOB = $(wildcard contrib/coccinelle/*.cocci) +COCCI_GEN_ALL = .build/tools/coccinelle/ALL.cocci +COCCI_GLOB = $(wildcard tools/coccinelle/*.cocci) COCCI_RULES_TRACKED = $(COCCI_GLOB:%=.build/%) COCCI_RULES_TRACKED_NO_PENDING = $(filter-out %.pending.cocci,$(COCCI_RULES_TRACKED)) COCCI_RULES = COCCI_RULES += $(COCCI_GEN_ALL) COCCI_RULES += $(COCCI_RULES_TRACKED) COCCI_NAMES = -COCCI_NAMES += $(COCCI_RULES:.build/contrib/coccinelle/%.cocci=%) +COCCI_NAMES += $(COCCI_RULES:.build/tools/coccinelle/%.cocci=%) COCCICHECK_PENDING = $(filter %.pending.cocci,$(COCCI_RULES)) COCCICHECK = $(filter-out $(COCCICHECK_PENDING),$(COCCI_RULES)) @@ -3420,20 +3491,20 @@ # on $(MAKECMDGOALS) that match these $(COCCI_RULES) COCCI_RULES_GLOB = COCCI_RULES_GLOB += cocci% -COCCI_RULES_GLOB += .build/contrib/coccinelle/% +COCCI_RULES_GLOB += .build/tools/coccinelle/% COCCI_RULES_GLOB += $(COCCICHECK_PATCHES) COCCI_RULES_GLOB += $(COCCICHEC_PATCHES_PENDING) COCCI_RULES_GLOB += $(COCCICHECK_PATCHES_INTREE) COCCI_RULES_GLOB += $(COCCICHECK_PATCHES_PENDING_INTREE) COCCI_GOALS = $(filter $(COCCI_RULES_GLOB),$(MAKECMDGOALS)) -COCCI_TEST_RES = $(wildcard contrib/coccinelle/tests/*.res) +COCCI_TEST_RES = $(wildcard tools/coccinelle/tests/*.res) $(COCCI_RULES_TRACKED): .build/% : % $(call mkdir_p_parent_template) $(QUIET_CP)cp $< $@ -.build/contrib/coccinelle/FOUND_H_SOURCES: $(FOUND_H_SOURCES) +.build/tools/coccinelle/FOUND_H_SOURCES: $(FOUND_H_SOURCES) $(call mkdir_p_parent_template) $(QUIET_GEN) >$@ @@ -3447,12 +3518,12 @@ define cocci-rule ## Rule for .build/$(1).patch/$(2); Params: -# $(1) = e.g. ".build/contrib/coccinelle/free.cocci" +# $(1) = e.g. ".build/tools/coccinelle/free.cocci" # $(2) = e.g. "grep.c" # $(3) = e.g. "grep.o" -COCCI_$(1:.build/contrib/coccinelle/%.cocci=%) += $(1).d/$(2).patch +COCCI_$(1:.build/tools/coccinelle/%.cocci=%) += $(1).d/$(2).patch $(1).d/$(2).patch: GIT-SPATCH-DEFINES -$(1).d/$(2).patch: $(if $(and $(SPATCH_USE_O_DEPENDENCIES),$(wildcard $(3))),$(3),.build/contrib/coccinelle/FOUND_H_SOURCES) +$(1).d/$(2).patch: $(if $(and $(SPATCH_USE_O_DEPENDENCIES),$(wildcard $(3))),$(3),.build/tools/coccinelle/FOUND_H_SOURCES) $(1).d/$(2).patch: $(1) $(1).d/$(2).patch: $(1).d/%.patch : % $$(call mkdir_p_parent_template) @@ -3478,13 +3549,13 @@ define spatch-rule -.build/contrib/coccinelle/$(1).cocci.patch: $$(COCCI_$(1)) +.build/tools/coccinelle/$(1).cocci.patch: $$(COCCI_$(1)) $$(QUIET_SPATCH_CAT)cat $$^ >$$@ && \ if test -s $$@; \ then \ echo ' ' SPATCH result: $$@; \ fi -contrib/coccinelle/$(1).cocci.patch: .build/contrib/coccinelle/$(1).cocci.patch +tools/coccinelle/$(1).cocci.patch: .build/tools/coccinelle/$(1).cocci.patch $$(QUIET_CP)cp $$< $$@ endef @@ -3498,9 +3569,9 @@ $(COCCI_TEST_RES_GEN): .build/%.res : %.c $(COCCI_TEST_RES_GEN): .build/%.res : %.res ifdef SPATCH_CONCAT_COCCI -$(COCCI_TEST_RES_GEN): .build/contrib/coccinelle/tests/%.res : $(COCCI_GEN_ALL) +$(COCCI_TEST_RES_GEN): .build/tools/coccinelle/tests/%.res : $(COCCI_GEN_ALL) else -$(COCCI_TEST_RES_GEN): .build/contrib/coccinelle/tests/%.res : contrib/coccinelle/%.cocci +$(COCCI_TEST_RES_GEN): .build/tools/coccinelle/tests/%.res : tools/coccinelle/%.cocci endif $(call mkdir_p_parent_template) $(QUIET_SPATCH_TEST)$(SPATCH) $(SPATCH_TEST_FLAGS) \ @@ -3516,14 +3587,14 @@ coccicheck: coccicheck-test ifdef SPATCH_CONCAT_COCCI -COCCICHECK_PATCH_MUST_BE_EMPTY_FILES = contrib/coccinelle/ALL.cocci.patch +COCCICHECK_PATCH_MUST_BE_EMPTY_FILES = tools/coccinelle/ALL.cocci.patch else COCCICHECK_PATCH_MUST_BE_EMPTY_FILES = $(COCCICHECK_PATCHES_INTREE) endif coccicheck: $(COCCICHECK_PATCH_MUST_BE_EMPTY_FILES) - ! grep -q ^ $(COCCICHECK_PATCH_MUST_BE_EMPTY_FILES) /dev/null + ! grep ^ $(COCCICHECK_PATCH_MUST_BE_EMPTY_FILES) /dev/null -# See contrib/coccinelle/README +# See tools/coccinelle/README coccicheck-pending: coccicheck-test coccicheck-pending: $(COCCICHECK_PATCHES_PENDING_INTREE) @@ -3797,8 +3868,8 @@ cocciclean: $(RM) GIT-SPATCH-DEFINES - $(RM) -r .build/contrib/coccinelle - $(RM) contrib/coccinelle/*.cocci.patch + $(RM) -r .build/tools/coccinelle + $(RM) tools/coccinelle/*.cocci.patch clean: profile-clean coverage-clean cocciclean $(RM) -r .build $(UNIT_TEST_BIN) @@ -3876,7 +3947,7 @@ ### Make sure built-ins do not have dups and listed in git.c # check-builtins:: - ./check-builtins.sh + ./tools/check-builtins.sh ### Test suite coverage testing # @@ -4014,3 +4085,20 @@ 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 + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \ + $(filter %.o,$^) $(LIB_FILE) $(EXTLIBS) -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) $< + +install-git-credential-osxkeychain: contrib/credential/osxkeychain/git-credential-osxkeychain + $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' + $(INSTALL) $(INSTALL_STRIP) $< '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' + +.PHONY: clean-git-credential-osxkeychain +clean-git-credential-osxkeychain: + $(RM) \ + contrib/credential/osxkeychain/git-credential-osxkeychain \ + contrib/credential/osxkeychain/git-credential-osxkeychain.o
diff --git a/RelNotes b/RelNotes index 6d16c00..84ef738 120000 --- a/RelNotes +++ b/RelNotes
@@ -1 +1 @@ -Documentation/RelNotes/2.52.0.adoc \ No newline at end of file +Documentation/RelNotes/2.54.0.adoc \ No newline at end of file
diff --git a/add-interactive.c b/add-interactive.c index 68fc095..3cf8a1d 100644 --- a/add-interactive.c +++ b/add-interactive.c
@@ -3,7 +3,6 @@ #include "git-compat-util.h" #include "add-interactive.h" #include "color.h" -#include "config.h" #include "diffcore.h" #include "gettext.h" #include "hash.h" @@ -20,119 +19,18 @@ #include "prompt.h" #include "tree.h" -static void init_color(struct repository *r, enum git_colorbool use_color, - const char *section_and_slot, char *dst, - const char *default_color) -{ - char *key = xstrfmt("color.%s", section_and_slot); - const char *value; - - if (!want_color(use_color)) - dst[0] = '\0'; - else if (repo_config_get_value(r, key, &value) || - color_parse(value, dst)) - strlcpy(dst, default_color, COLOR_MAXLEN); - - free(key); -} - -static enum git_colorbool check_color_config(struct repository *r, const char *var) -{ - const char *value; - enum git_colorbool ret; - - if (repo_config_get_value(r, var, &value)) - ret = GIT_COLOR_UNKNOWN; - else - ret = git_config_colorbool(var, value); - - /* - * Do not rely on want_color() to fall back to color.ui for us. It uses - * the value parsed by git_color_config(), which may not have been - * called by the main command. - */ - if (ret == GIT_COLOR_UNKNOWN && - !repo_config_get_value(r, "color.ui", &value)) - ret = git_config_colorbool("color.ui", value); - - return ret; -} - void init_add_i_state(struct add_i_state *s, struct repository *r, - struct add_p_opt *add_p_opt) + struct interactive_options *opts) { s->r = r; - s->context = -1; - s->interhunkcontext = -1; - - s->use_color_interactive = check_color_config(r, "color.interactive"); - - init_color(r, s->use_color_interactive, "interactive.header", - s->header_color, GIT_COLOR_BOLD); - init_color(r, s->use_color_interactive, "interactive.help", - s->help_color, GIT_COLOR_BOLD_RED); - init_color(r, s->use_color_interactive, "interactive.prompt", - s->prompt_color, GIT_COLOR_BOLD_BLUE); - init_color(r, s->use_color_interactive, "interactive.error", - s->error_color, GIT_COLOR_BOLD_RED); - strlcpy(s->reset_color_interactive, - want_color(s->use_color_interactive) ? GIT_COLOR_RESET : "", COLOR_MAXLEN); - - s->use_color_diff = check_color_config(r, "color.diff"); - - init_color(r, s->use_color_diff, "diff.frag", s->fraginfo_color, - diff_get_color(s->use_color_diff, DIFF_FRAGINFO)); - init_color(r, s->use_color_diff, "diff.context", s->context_color, - "fall back"); - if (!strcmp(s->context_color, "fall back")) - init_color(r, s->use_color_diff, "diff.plain", - s->context_color, - diff_get_color(s->use_color_diff, DIFF_CONTEXT)); - init_color(r, s->use_color_diff, "diff.old", s->file_old_color, - diff_get_color(s->use_color_diff, DIFF_FILE_OLD)); - init_color(r, s->use_color_diff, "diff.new", s->file_new_color, - diff_get_color(s->use_color_diff, DIFF_FILE_NEW)); - strlcpy(s->reset_color_diff, - want_color(s->use_color_diff) ? GIT_COLOR_RESET : "", COLOR_MAXLEN); - - FREE_AND_NULL(s->interactive_diff_filter); - repo_config_get_string(r, "interactive.difffilter", - &s->interactive_diff_filter); - - FREE_AND_NULL(s->interactive_diff_algorithm); - repo_config_get_string(r, "diff.algorithm", - &s->interactive_diff_algorithm); - - if (!repo_config_get_int(r, "diff.context", &s->context)) - if (s->context < 0) - die(_("%s cannot be negative"), "diff.context"); - if (!repo_config_get_int(r, "diff.interHunkContext", &s->interhunkcontext)) - if (s->interhunkcontext < 0) - die(_("%s cannot be negative"), "diff.interHunkContext"); - - repo_config_get_bool(r, "interactive.singlekey", &s->use_single_key); - if (s->use_single_key) - setbuf(stdin, NULL); - - if (add_p_opt->context != -1) { - if (add_p_opt->context < 0) - die(_("%s cannot be negative"), "--unified"); - s->context = add_p_opt->context; - } - if (add_p_opt->interhunkcontext != -1) { - if (add_p_opt->interhunkcontext < 0) - die(_("%s cannot be negative"), "--inter-hunk-context"); - s->interhunkcontext = add_p_opt->interhunkcontext; - } + interactive_config_init(&s->cfg, r, opts); } void clear_add_i_state(struct add_i_state *s) { - FREE_AND_NULL(s->interactive_diff_filter); - FREE_AND_NULL(s->interactive_diff_algorithm); + interactive_config_clear(&s->cfg); memset(s, 0, sizeof(*s)); - s->use_color_interactive = GIT_COLOR_UNKNOWN; - s->use_color_diff = GIT_COLOR_UNKNOWN; + interactive_config_clear(&s->cfg); } /* @@ -286,7 +184,7 @@ static void list(struct add_i_state *s, struct string_list *list, int *selected, return; if (opts->header) - color_fprintf_ln(stdout, s->header_color, + color_fprintf_ln(stdout, s->cfg.header_color, "%s", opts->header); for (i = 0; i < list->nr; i++) { @@ -354,7 +252,7 @@ static ssize_t list_and_choose(struct add_i_state *s, list(s, &items->items, items->selected, &opts->list_opts); - color_fprintf(stdout, s->prompt_color, "%s", opts->prompt); + color_fprintf(stdout, s->cfg.prompt_color, "%s", opts->prompt); fputs(singleton ? "> " : ">> ", stdout); fflush(stdout); @@ -432,7 +330,7 @@ static ssize_t list_and_choose(struct add_i_state *s, if (from < 0 || from >= items->items.nr || (singleton && from + 1 != to)) { - color_fprintf_ln(stderr, s->error_color, + color_fprintf_ln(stderr, s->cfg.error_color, _("Huh (%s)?"), p); break; } else if (singleton) { @@ -840,7 +738,7 @@ static int run_revert(struct add_i_state *s, const struct pathspec *ps, if (is_initial) oidcpy(&oid, s->r->hash_algo->empty_tree); else { - tree = parse_tree_indirect(&oid); + tree = repo_parse_tree_indirect(s->r, &oid); if (!tree) { res = error(_("Could not parse HEAD^{tree}")); goto finish_revert; @@ -992,7 +890,7 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps, free(files->items.items[i].string); } else if (item->index.unmerged || item->worktree.unmerged) { - color_fprintf_ln(stderr, s->error_color, + color_fprintf_ln(stderr, s->cfg.error_color, _("ignoring unmerged: %s"), files->items.items[i].string); free(item); @@ -1014,9 +912,10 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps, opts->prompt = N_("Patch update"); count = list_and_choose(s, files, opts); if (count > 0) { - struct add_p_opt add_p_opt = { - .context = s->context, - .interhunkcontext = s->interhunkcontext, + struct interactive_options opts = { + .context = s->cfg.context, + .interhunkcontext = s->cfg.interhunkcontext, + .auto_advance = s->cfg.auto_advance, }; struct strvec args = STRVEC_INIT; struct pathspec ps_selected = { 0 }; @@ -1028,7 +927,7 @@ static int run_patch(struct add_i_state *s, const struct pathspec *ps, parse_pathspec(&ps_selected, PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL, PATHSPEC_LITERAL_PATH, "", args.v); - res = run_add_p(s->r, ADD_P_ADD, &add_p_opt, NULL, &ps_selected); + res = run_add_p(s->r, ADD_P_ADD, &opts, NULL, &ps_selected, 0); strvec_clear(&args); clear_pathspec(&ps_selected); } @@ -1064,10 +963,10 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps, struct child_process cmd = CHILD_PROCESS_INIT; strvec_pushl(&cmd.args, "git", "diff", "-p", "--cached", NULL); - if (s->context != -1) - strvec_pushf(&cmd.args, "--unified=%i", s->context); - if (s->interhunkcontext != -1) - strvec_pushf(&cmd.args, "--inter-hunk-context=%i", s->interhunkcontext); + if (s->cfg.context != -1) + strvec_pushf(&cmd.args, "--unified=%i", s->cfg.context); + if (s->cfg.interhunkcontext != -1) + strvec_pushf(&cmd.args, "--inter-hunk-context=%i", s->cfg.interhunkcontext); strvec_pushl(&cmd.args, oid_to_hex(!is_initial ? &oid : s->r->hash_algo->empty_tree), "--", NULL); for (i = 0; i < files->items.nr; i++) @@ -1085,17 +984,17 @@ static int run_help(struct add_i_state *s, const struct pathspec *ps UNUSED, struct prefix_item_list *files UNUSED, struct list_and_choose_options *opts UNUSED) { - color_fprintf_ln(stdout, s->help_color, "status - %s", + color_fprintf_ln(stdout, s->cfg.help_color, "status - %s", _("show paths with changes")); - color_fprintf_ln(stdout, s->help_color, "update - %s", + color_fprintf_ln(stdout, s->cfg.help_color, "update - %s", _("add working tree state to the staged set of changes")); - color_fprintf_ln(stdout, s->help_color, "revert - %s", + color_fprintf_ln(stdout, s->cfg.help_color, "revert - %s", _("revert staged set of changes back to the HEAD version")); - color_fprintf_ln(stdout, s->help_color, "patch - %s", + color_fprintf_ln(stdout, s->cfg.help_color, "patch - %s", _("pick hunks and update selectively")); - color_fprintf_ln(stdout, s->help_color, "diff - %s", + color_fprintf_ln(stdout, s->cfg.help_color, "diff - %s", _("view diff between HEAD and index")); - color_fprintf_ln(stdout, s->help_color, "add untracked - %s", + color_fprintf_ln(stdout, s->cfg.help_color, "add untracked - %s", _("add contents of untracked files to the staged set of changes")); return 0; @@ -1103,21 +1002,21 @@ static int run_help(struct add_i_state *s, const struct pathspec *ps UNUSED, static void choose_prompt_help(struct add_i_state *s) { - color_fprintf_ln(stdout, s->help_color, "%s", + color_fprintf_ln(stdout, s->cfg.help_color, "%s", _("Prompt help:")); - color_fprintf_ln(stdout, s->help_color, "1 - %s", + color_fprintf_ln(stdout, s->cfg.help_color, "1 - %s", _("select a single item")); - color_fprintf_ln(stdout, s->help_color, "3-5 - %s", + color_fprintf_ln(stdout, s->cfg.help_color, "3-5 - %s", _("select a range of items")); - color_fprintf_ln(stdout, s->help_color, "2-3,6-9 - %s", + color_fprintf_ln(stdout, s->cfg.help_color, "2-3,6-9 - %s", _("select multiple ranges")); - color_fprintf_ln(stdout, s->help_color, "foo - %s", + color_fprintf_ln(stdout, s->cfg.help_color, "foo - %s", _("select item based on unique prefix")); - color_fprintf_ln(stdout, s->help_color, "-... - %s", + color_fprintf_ln(stdout, s->cfg.help_color, "-... - %s", _("unselect specified items")); - color_fprintf_ln(stdout, s->help_color, "* - %s", + color_fprintf_ln(stdout, s->cfg.help_color, "* - %s", _("choose all items")); - color_fprintf_ln(stdout, s->help_color, " - %s", + color_fprintf_ln(stdout, s->cfg.help_color, " - %s", _("(empty) finish selecting")); } @@ -1152,7 +1051,7 @@ static void print_command_item(int i, int selected UNUSED, static void command_prompt_help(struct add_i_state *s) { - const char *help_color = s->help_color; + const char *help_color = s->cfg.help_color; color_fprintf_ln(stdout, help_color, "%s", _("Prompt help:")); color_fprintf_ln(stdout, help_color, "1 - %s", _("select a numbered item")); @@ -1163,7 +1062,7 @@ static void command_prompt_help(struct add_i_state *s) } int run_add_i(struct repository *r, const struct pathspec *ps, - struct add_p_opt *add_p_opt) + struct interactive_options *interactive_opts) { struct add_i_state s = { NULL }; struct print_command_item_data data = { "[", "]" }; @@ -1206,15 +1105,15 @@ int run_add_i(struct repository *r, const struct pathspec *ps, ->util = util; } - init_add_i_state(&s, r, add_p_opt); + init_add_i_state(&s, r, interactive_opts); /* * When color was asked for, use the prompt color for * highlighting, otherwise use square brackets. */ - if (want_color(s.use_color_interactive)) { - data.color = s.prompt_color; - data.reset = s.reset_color_interactive; + if (want_color(s.cfg.use_color_interactive)) { + data.color = s.cfg.prompt_color; + data.reset = s.cfg.reset_color_interactive; } print_file_item_data.color = data.color; print_file_item_data.reset = data.reset;
diff --git a/add-interactive.h b/add-interactive.h index da49502..eefa2ed 100644 --- a/add-interactive.h +++ b/add-interactive.h
@@ -1,55 +1,21 @@ #ifndef ADD_INTERACTIVE_H #define ADD_INTERACTIVE_H -#include "color.h" +#include "add-patch.h" -struct add_p_opt { - int context; - int interhunkcontext; -}; - -#define ADD_P_OPT_INIT { .context = -1, .interhunkcontext = -1 } +struct pathspec; +struct repository; struct add_i_state { struct repository *r; - enum git_colorbool use_color_interactive; - enum git_colorbool use_color_diff; - char header_color[COLOR_MAXLEN]; - char help_color[COLOR_MAXLEN]; - char prompt_color[COLOR_MAXLEN]; - char error_color[COLOR_MAXLEN]; - char reset_color_interactive[COLOR_MAXLEN]; - - char fraginfo_color[COLOR_MAXLEN]; - char context_color[COLOR_MAXLEN]; - char file_old_color[COLOR_MAXLEN]; - char file_new_color[COLOR_MAXLEN]; - char reset_color_diff[COLOR_MAXLEN]; - - int use_single_key; - char *interactive_diff_filter, *interactive_diff_algorithm; - int context, interhunkcontext; + struct interactive_config cfg; }; void init_add_i_state(struct add_i_state *s, struct repository *r, - struct add_p_opt *add_p_opt); + struct interactive_options *opts); void clear_add_i_state(struct add_i_state *s); -struct repository; -struct pathspec; int run_add_i(struct repository *r, const struct pathspec *ps, - struct add_p_opt *add_p_opt); - -enum add_p_mode { - ADD_P_ADD, - ADD_P_STASH, - ADD_P_RESET, - ADD_P_CHECKOUT, - ADD_P_WORKTREE, -}; - -int run_add_p(struct repository *r, enum add_p_mode mode, - struct add_p_opt *o, const char *revision, - const struct pathspec *ps); + struct interactive_options *opts); #endif
diff --git a/add-patch.c b/add-patch.c index 173a532..4e28e5c 100644 --- a/add-patch.c +++ b/add-patch.c
@@ -2,11 +2,15 @@ #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" -#include "add-interactive.h" +#include "add-patch.h" #include "advice.h" +#include "commit.h" +#include "config.h" +#include "diff.h" #include "editor.h" #include "environment.h" #include "gettext.h" +#include "hex.h" #include "object-name.h" #include "pager.h" #include "read-cache-ll.h" @@ -42,10 +46,10 @@ static struct patch_mode patch_mode_add = { .apply_args = { "--cached", NULL }, .apply_check_args = { "--cached", NULL }, .prompt_mode = { - N_("Stage mode change [y,n,q,a,d%s,?]? "), - N_("Stage deletion [y,n,q,a,d%s,?]? "), - N_("Stage addition [y,n,q,a,d%s,?]? "), - N_("Stage this hunk [y,n,q,a,d%s,?]? ") + N_("Stage mode change%s [y,n,q,a,d%s,?]? "), + N_("Stage deletion%s [y,n,q,a,d%s,?]? "), + N_("Stage addition%s [y,n,q,a,d%s,?]? "), + N_("Stage this hunk%s [y,n,q,a,d%s,?]? ") }, .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk " "will immediately be marked for staging."), @@ -64,10 +68,10 @@ static struct patch_mode patch_mode_stash = { .apply_args = { "--cached", NULL }, .apply_check_args = { "--cached", NULL }, .prompt_mode = { - N_("Stash mode change [y,n,q,a,d%s,?]? "), - N_("Stash deletion [y,n,q,a,d%s,?]? "), - N_("Stash addition [y,n,q,a,d%s,?]? "), - N_("Stash this hunk [y,n,q,a,d%s,?]? "), + N_("Stash mode change%s [y,n,q,a,d%s,?]? "), + N_("Stash deletion%s [y,n,q,a,d%s,?]? "), + N_("Stash addition%s [y,n,q,a,d%s,?]? "), + N_("Stash this hunk%s [y,n,q,a,d%s,?]? "), }, .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk " "will immediately be marked for stashing."), @@ -88,10 +92,10 @@ static struct patch_mode patch_mode_reset_head = { .is_reverse = 1, .index_only = 1, .prompt_mode = { - N_("Unstage mode change [y,n,q,a,d%s,?]? "), - N_("Unstage deletion [y,n,q,a,d%s,?]? "), - N_("Unstage addition [y,n,q,a,d%s,?]? "), - N_("Unstage this hunk [y,n,q,a,d%s,?]? "), + N_("Unstage mode change%s [y,n,q,a,d%s,?]? "), + N_("Unstage deletion%s [y,n,q,a,d%s,?]? "), + N_("Unstage addition%s [y,n,q,a,d%s,?]? "), + N_("Unstage this hunk%s [y,n,q,a,d%s,?]? "), }, .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk " "will immediately be marked for unstaging."), @@ -111,10 +115,10 @@ static struct patch_mode patch_mode_reset_nothead = { .apply_check_args = { "--cached", NULL }, .index_only = 1, .prompt_mode = { - N_("Apply mode change to index [y,n,q,a,d%s,?]? "), - N_("Apply deletion to index [y,n,q,a,d%s,?]? "), - N_("Apply addition to index [y,n,q,a,d%s,?]? "), - N_("Apply this hunk to index [y,n,q,a,d%s,?]? "), + N_("Apply mode change to index%s [y,n,q,a,d%s,?]? "), + N_("Apply deletion to index%s [y,n,q,a,d%s,?]? "), + N_("Apply addition to index%s [y,n,q,a,d%s,?]? "), + N_("Apply this hunk to index%s [y,n,q,a,d%s,?]? "), }, .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk " "will immediately be marked for applying."), @@ -134,10 +138,10 @@ static struct patch_mode patch_mode_checkout_index = { .apply_check_args = { "-R", NULL }, .is_reverse = 1, .prompt_mode = { - N_("Discard mode change from worktree [y,n,q,a,d%s,?]? "), - N_("Discard deletion from worktree [y,n,q,a,d%s,?]? "), - N_("Discard addition from worktree [y,n,q,a,d%s,?]? "), - N_("Discard this hunk from worktree [y,n,q,a,d%s,?]? "), + N_("Discard mode change from worktree%s [y,n,q,a,d%s,?]? "), + N_("Discard deletion from worktree%s [y,n,q,a,d%s,?]? "), + N_("Discard addition from worktree%s [y,n,q,a,d%s,?]? "), + N_("Discard this hunk from worktree%s [y,n,q,a,d%s,?]? "), }, .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk " "will immediately be marked for discarding."), @@ -157,10 +161,10 @@ static struct patch_mode patch_mode_checkout_head = { .apply_check_args = { "-R", NULL }, .is_reverse = 1, .prompt_mode = { - N_("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "), - N_("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "), - N_("Discard addition from index and worktree [y,n,q,a,d%s,?]? "), - N_("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "), + N_("Discard mode change from index and worktree%s [y,n,q,a,d%s,?]? "), + N_("Discard deletion from index and worktree%s [y,n,q,a,d%s,?]? "), + N_("Discard addition from index and worktree%s [y,n,q,a,d%s,?]? "), + N_("Discard this hunk from index and worktree%s [y,n,q,a,d%s,?]? "), }, .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk " "will immediately be marked for discarding."), @@ -179,10 +183,10 @@ static struct patch_mode patch_mode_checkout_nothead = { .apply_for_checkout = 1, .apply_check_args = { NULL }, .prompt_mode = { - N_("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "), - N_("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "), - N_("Apply addition to index and worktree [y,n,q,a,d%s,?]? "), - N_("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "), + N_("Apply mode change to index and worktree%s [y,n,q,a,d%s,?]? "), + N_("Apply deletion to index and worktree%s [y,n,q,a,d%s,?]? "), + N_("Apply addition to index and worktree%s [y,n,q,a,d%s,?]? "), + N_("Apply this hunk to index and worktree%s [y,n,q,a,d%s,?]? "), }, .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk " "will immediately be marked for applying."), @@ -202,10 +206,10 @@ static struct patch_mode patch_mode_worktree_head = { .apply_check_args = { "-R", NULL }, .is_reverse = 1, .prompt_mode = { - N_("Discard mode change from worktree [y,n,q,a,d%s,?]? "), - N_("Discard deletion from worktree [y,n,q,a,d%s,?]? "), - N_("Discard addition from worktree [y,n,q,a,d%s,?]? "), - N_("Discard this hunk from worktree [y,n,q,a,d%s,?]? "), + N_("Discard mode change from worktree%s [y,n,q,a,d%s,?]? "), + N_("Discard deletion from worktree%s [y,n,q,a,d%s,?]? "), + N_("Discard addition from worktree%s [y,n,q,a,d%s,?]? "), + N_("Discard this hunk from worktree%s [y,n,q,a,d%s,?]? "), }, .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk " "will immediately be marked for discarding."), @@ -224,10 +228,10 @@ static struct patch_mode patch_mode_worktree_nothead = { .apply_args = { NULL }, .apply_check_args = { NULL }, .prompt_mode = { - N_("Apply mode change to worktree [y,n,q,a,d%s,?]? "), - N_("Apply deletion to worktree [y,n,q,a,d%s,?]? "), - N_("Apply addition to worktree [y,n,q,a,d%s,?]? "), - N_("Apply this hunk to worktree [y,n,q,a,d%s,?]? "), + N_("Apply mode change to worktree%s [y,n,q,a,d%s,?]? "), + N_("Apply deletion to worktree%s [y,n,q,a,d%s,?]? "), + N_("Apply addition to worktree%s [y,n,q,a,d%s,?]? "), + N_("Apply this hunk to worktree%s [y,n,q,a,d%s,?]? "), }, .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk " "will immediately be marked for applying."), @@ -260,7 +264,10 @@ struct hunk { }; struct add_p_state { - struct add_i_state s; + struct repository *r; + struct index_state *index; + const char *index_file; + struct interactive_config cfg; struct strbuf answer, buf; /* parsed diff */ @@ -278,6 +285,123 @@ struct add_p_state { const char *revision; }; +static void init_color(struct repository *r, + enum git_colorbool use_color, + const char *section_and_slot, char *dst, + const char *default_color) +{ + char *key = xstrfmt("color.%s", section_and_slot); + const char *value; + + if (!want_color(use_color)) + dst[0] = '\0'; + else if (repo_config_get_value(r, key, &value) || + color_parse(value, dst)) + strlcpy(dst, default_color, COLOR_MAXLEN); + + free(key); +} + +static enum git_colorbool check_color_config(struct repository *r, const char *var) +{ + const char *value; + enum git_colorbool ret; + + if (repo_config_get_value(r, var, &value)) + ret = GIT_COLOR_UNKNOWN; + else + ret = git_config_colorbool(var, value); + + /* + * Do not rely on want_color() to fall back to color.ui for us. It uses + * the value parsed by git_color_config(), which may not have been + * called by the main command. + */ + if (ret == GIT_COLOR_UNKNOWN && + !repo_config_get_value(r, "color.ui", &value)) + ret = git_config_colorbool("color.ui", value); + + return ret; +} + +void interactive_config_init(struct interactive_config *cfg, + struct repository *r, + struct interactive_options *opts) +{ + cfg->context = -1; + cfg->interhunkcontext = -1; + cfg->auto_advance = opts->auto_advance; + + cfg->use_color_interactive = check_color_config(r, "color.interactive"); + + init_color(r, cfg->use_color_interactive, "interactive.header", + cfg->header_color, GIT_COLOR_BOLD); + init_color(r, cfg->use_color_interactive, "interactive.help", + cfg->help_color, GIT_COLOR_BOLD_RED); + init_color(r, cfg->use_color_interactive, "interactive.prompt", + cfg->prompt_color, GIT_COLOR_BOLD_BLUE); + init_color(r, cfg->use_color_interactive, "interactive.error", + cfg->error_color, GIT_COLOR_BOLD_RED); + strlcpy(cfg->reset_color_interactive, + want_color(cfg->use_color_interactive) ? GIT_COLOR_RESET : "", COLOR_MAXLEN); + + cfg->use_color_diff = check_color_config(r, "color.diff"); + + init_color(r, cfg->use_color_diff, "diff.frag", cfg->fraginfo_color, + diff_get_color(cfg->use_color_diff, DIFF_FRAGINFO)); + init_color(r, cfg->use_color_diff, "diff.context", cfg->context_color, + "fall back"); + if (!strcmp(cfg->context_color, "fall back")) + init_color(r, cfg->use_color_diff, "diff.plain", + cfg->context_color, + diff_get_color(cfg->use_color_diff, DIFF_CONTEXT)); + init_color(r, cfg->use_color_diff, "diff.old", cfg->file_old_color, + diff_get_color(cfg->use_color_diff, DIFF_FILE_OLD)); + init_color(r, cfg->use_color_diff, "diff.new", cfg->file_new_color, + diff_get_color(cfg->use_color_diff, DIFF_FILE_NEW)); + strlcpy(cfg->reset_color_diff, + want_color(cfg->use_color_diff) ? GIT_COLOR_RESET : "", COLOR_MAXLEN); + + FREE_AND_NULL(cfg->interactive_diff_filter); + repo_config_get_string(r, "interactive.difffilter", + &cfg->interactive_diff_filter); + + FREE_AND_NULL(cfg->interactive_diff_algorithm); + repo_config_get_string(r, "diff.algorithm", + &cfg->interactive_diff_algorithm); + + if (!repo_config_get_int(r, "diff.context", &cfg->context)) + if (cfg->context < 0) + die(_("%s cannot be negative"), "diff.context"); + if (!repo_config_get_int(r, "diff.interHunkContext", &cfg->interhunkcontext)) + if (cfg->interhunkcontext < 0) + die(_("%s cannot be negative"), "diff.interHunkContext"); + + repo_config_get_bool(r, "interactive.singlekey", &cfg->use_single_key); + if (cfg->use_single_key) + setbuf(stdin, NULL); + + if (opts->context != -1) { + if (opts->context < 0) + die(_("%s cannot be negative"), "--unified"); + cfg->context = opts->context; + } + if (opts->interhunkcontext != -1) { + if (opts->interhunkcontext < 0) + die(_("%s cannot be negative"), "--inter-hunk-context"); + cfg->interhunkcontext = opts->interhunkcontext; + } +} + +void interactive_config_clear(struct interactive_config *cfg) +{ + FREE_AND_NULL(cfg->interactive_diff_filter); + FREE_AND_NULL(cfg->interactive_diff_algorithm); + memset(cfg, 0, sizeof(*cfg)); + cfg->use_color_interactive = GIT_COLOR_UNKNOWN; + cfg->use_color_diff = GIT_COLOR_UNKNOWN; +} + static void add_p_state_clear(struct add_p_state *s) { size_t i; @@ -289,7 +413,7 @@ static void add_p_state_clear(struct add_p_state *s) for (i = 0; i < s->file_diff_nr; i++) free(s->file_diff[i].hunk); free(s->file_diff); - clear_add_i_state(&s->s); + interactive_config_clear(&s->cfg); } __attribute__((format (printf, 2, 3))) @@ -298,9 +422,9 @@ static void err(struct add_p_state *s, const char *fmt, ...) va_list args; va_start(args, fmt); - fputs(s->s.error_color, stdout); + fputs(s->cfg.error_color, stdout); vprintf(fmt, args); - puts(s->s.reset_color_interactive); + puts(s->cfg.reset_color_interactive); va_end(args); } @@ -318,7 +442,7 @@ static void setup_child_process(struct add_p_state *s, cp->git_cmd = 1; strvec_pushf(&cp->env, - INDEX_ENVIRONMENT "=%s", s->s.r->index_file); + INDEX_ENVIRONMENT "=%s", s->index_file); } static int parse_range(const char **p, @@ -342,7 +466,7 @@ static int parse_hunk_header(struct add_p_state *s, struct hunk *hunk) { struct hunk_header *header = &hunk->header; const char *line = s->plain.buf + hunk->start, *p = line; - char *eol = memchr(p, '\n', s->plain.len - hunk->start); + const char *eol = memchr(p, '\n', s->plain.len - hunk->start); if (!eol) eol = s->plain.buf + s->plain.len; @@ -423,12 +547,12 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) int res; strvec_pushv(&args, s->mode->diff_cmd); - if (s->s.context != -1) - strvec_pushf(&args, "--unified=%i", s->s.context); - if (s->s.interhunkcontext != -1) - strvec_pushf(&args, "--inter-hunk-context=%i", s->s.interhunkcontext); - if (s->s.interactive_diff_algorithm) - strvec_pushf(&args, "--diff-algorithm=%s", s->s.interactive_diff_algorithm); + if (s->cfg.context != -1) + strvec_pushf(&args, "--unified=%i", s->cfg.context); + if (s->cfg.interhunkcontext != -1) + strvec_pushf(&args, "--inter-hunk-context=%i", s->cfg.interhunkcontext); + if (s->cfg.interactive_diff_algorithm) + strvec_pushf(&args, "--diff-algorithm=%s", s->cfg.interactive_diff_algorithm); if (s->revision) { struct object_id oid; strvec_push(&args, @@ -457,9 +581,9 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) } strbuf_complete_line(plain); - if (want_color_fd(1, s->s.use_color_diff)) { + if (want_color_fd(1, s->cfg.use_color_diff)) { struct child_process colored_cp = CHILD_PROCESS_INIT; - const char *diff_filter = s->s.interactive_diff_filter; + const char *diff_filter = s->cfg.interactive_diff_filter; setup_child_process(s, &colored_cp, NULL); xsnprintf((char *)args.v[color_arg_index], 8, "--color"); @@ -692,7 +816,7 @@ static void render_hunk(struct add_p_state *s, struct hunk *hunk, hunk->colored_end - hunk->colored_start); return; } else { - strbuf_addstr(out, s->s.fraginfo_color); + strbuf_addstr(out, s->cfg.fraginfo_color); p = s->colored.buf + header->colored_extra_start; len = header->colored_extra_end - header->colored_extra_start; @@ -714,7 +838,7 @@ static void render_hunk(struct add_p_state *s, struct hunk *hunk, if (len) strbuf_add(out, p, len); else if (colored) - strbuf_addf(out, "%s\n", s->s.reset_color_diff); + strbuf_addf(out, "%s\n", s->cfg.reset_color_diff); else strbuf_addch(out, '\n'); } @@ -1103,12 +1227,12 @@ static void recolor_hunk(struct add_p_state *s, struct hunk *hunk) strbuf_addstr(&s->colored, plain[current] == '-' ? - s->s.file_old_color : + s->cfg.file_old_color : plain[current] == '+' ? - s->s.file_new_color : - s->s.context_color); + s->cfg.file_new_color : + s->cfg.context_color); strbuf_add(&s->colored, plain + current, eol - current); - strbuf_addstr(&s->colored, s->s.reset_color_diff); + strbuf_addstr(&s->colored, s->cfg.reset_color_diff); if (next > eol) strbuf_add(&s->colored, plain + eol, next - eol); current = next; @@ -1237,7 +1361,7 @@ static int run_apply_check(struct add_p_state *s, static int read_single_character(struct add_p_state *s) { - if (s->s.use_single_key) { + if (s->cfg.use_single_key) { int res = read_key_without_echo(&s->answer); printf("%s\n", res == EOF ? "" : s->answer.buf); return res; @@ -1251,7 +1375,7 @@ static int read_single_character(struct add_p_state *s) static int prompt_yesno(struct add_p_state *s, const char *prompt) { for (;;) { - color_fprintf(stdout, s->s.prompt_color, "%s", _(prompt)); + color_fprintf(stdout, s->cfg.prompt_color, "%s", _(prompt)); fflush(stdout); if (read_single_character(s) == EOF) return -1; @@ -1418,7 +1542,46 @@ N_("j - go to the next undecided hunk, roll over at the bottom\n" "e - manually edit the current hunk\n" "p - print the current hunk\n" "P - print the current hunk using the pager\n" - "? - print help\n"); + "> - go to the next file, roll over at the bottom\n" + "< - go to the previous file, roll over at the top\n" + "? - print help\n" + "HUNKS SUMMARY - Hunks: %d, USE: %d, SKIP: %d\n"); + +static void apply_patch(struct add_p_state *s, struct file_diff *file_diff) +{ + struct child_process cp = CHILD_PROCESS_INIT; + size_t j; + + /* Any hunk to be used? */ + for (j = 0; j < file_diff->hunk_nr; j++) + if (file_diff->hunk[j].use == USE_HUNK) + break; + + if (j < file_diff->hunk_nr || + (!file_diff->hunk_nr && file_diff->head.use == USE_HUNK)) { + /* At least one hunk selected: apply */ + strbuf_reset(&s->buf); + reassemble_patch(s, file_diff, 0, &s->buf); + + discard_index(s->index); + if (s->mode->apply_for_checkout) + apply_for_checkout(s, &s->buf, + s->mode->is_reverse); + else { + setup_child_process(s, &cp, "apply", NULL); + strvec_pushv(&cp.args, s->mode->apply_args); + if (pipe_command(&cp, s->buf.buf, s->buf.len, + NULL, 0, NULL, 0)) + error(_("'git apply' failed")); + } + if (read_index_from(s->index, s->index_file, s->r->gitdir) >= 0 && + s->index == s->r->index) { + repo_refresh_and_write_index(s->r, REFRESH_QUIET, 0, + 1, NULL, NULL, NULL); + } + } + +} static size_t dec_mod(size_t a, size_t m) { @@ -1441,25 +1604,29 @@ static bool get_first_undecided(const struct file_diff *file_diff, size_t *idx) return false; } -static int patch_update_file(struct add_p_state *s, - struct file_diff *file_diff) +static size_t patch_update_file(struct add_p_state *s, + size_t idx, + unsigned flags) { size_t hunk_index = 0; ssize_t i, undecided_previous, undecided_next, rendered_hunk_index = -1; struct hunk *hunk; char ch; - struct child_process cp = CHILD_PROCESS_INIT; - int colored = !!s->colored.len, quit = 0, use_pager = 0; + int colored = !!s->colored.len, use_pager = 0; enum prompt_mode_type prompt_mode_type; + int all_decided = 0; + struct file_diff *file_diff = s->file_diff + idx; + size_t patch_update_resp = idx; /* Empty added files have no hunks */ if (!file_diff->hunk_nr && !file_diff->added) - return 0; + return patch_update_resp + 1; strbuf_reset(&s->buf); render_diff_header(s, file_diff, colored, &s->buf); fputs(s->buf.buf, stdout); for (;;) { + const char *hunk_use_decision = ""; enum { ALLOW_GOTO_PREVIOUS_HUNK = 1 << 0, ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK = 1 << 1, @@ -1467,7 +1634,9 @@ static int patch_update_file(struct add_p_state *s, ALLOW_GOTO_NEXT_UNDECIDED_HUNK = 1 << 3, ALLOW_SEARCH_AND_GOTO = 1 << 4, ALLOW_SPLIT = 1 << 5, - ALLOW_EDIT = 1 << 6 + ALLOW_EDIT = 1 << 6, + ALLOW_GOTO_PREVIOUS_FILE = 1 << 7, + ALLOW_GOTO_NEXT_FILE = 1 << 8 } permitted = 0; if (hunk_index >= file_diff->hunk_nr) @@ -1498,9 +1667,14 @@ static int patch_update_file(struct add_p_state *s, /* Everything decided? */ if (undecided_previous < 0 && undecided_next < 0 && - hunk->use != UNDECIDED_HUNK) - break; - + hunk->use != UNDECIDED_HUNK) { + if (!s->cfg.auto_advance) + all_decided = 1; + else { + patch_update_resp++; + break; + } + } strbuf_reset(&s->buf); if (file_diff->hunk_nr) { if (rendered_hunk_index != hunk_index) { @@ -1543,11 +1717,20 @@ static int patch_update_file(struct add_p_state *s, permitted |= ALLOW_SPLIT; strbuf_addstr(&s->buf, ",s"); } - if (hunk_index + 1 > file_diff->mode_change && + if (!(flags & ADD_P_DISALLOW_EDIT) && + hunk_index + 1 > file_diff->mode_change && !file_diff->deleted) { permitted |= ALLOW_EDIT; strbuf_addstr(&s->buf, ",e"); } + if (!s->cfg.auto_advance && s->file_diff_nr > 1) { + permitted |= ALLOW_GOTO_NEXT_FILE; + strbuf_addstr(&s->buf, ",>"); + } + if (!s->cfg.auto_advance && s->file_diff_nr > 1) { + permitted |= ALLOW_GOTO_PREVIOUS_FILE; + strbuf_addstr(&s->buf, ",<"); + } strbuf_addstr(&s->buf, ",p,P"); } if (file_diff->deleted) @@ -1559,18 +1742,24 @@ static int patch_update_file(struct add_p_state *s, else prompt_mode_type = PROMPT_HUNK; - printf("%s(%"PRIuMAX"/%"PRIuMAX") ", s->s.prompt_color, + printf("%s(%"PRIuMAX"/%"PRIuMAX") ", s->cfg.prompt_color, (uintmax_t)hunk_index + 1, (uintmax_t)(file_diff->hunk_nr ? file_diff->hunk_nr : 1)); + if (hunk->use != UNDECIDED_HUNK) { + if (hunk->use == USE_HUNK) + hunk_use_decision = _(" (was: y)"); + else + hunk_use_decision = _(" (was: n)"); + } printf(_(s->mode->prompt_mode[prompt_mode_type]), - s->buf.buf); - if (*s->s.reset_color_interactive) - fputs(s->s.reset_color_interactive, stdout); + hunk_use_decision, s->buf.buf); + if (*s->cfg.reset_color_interactive) + fputs(s->cfg.reset_color_interactive, stdout); fflush(stdout); if (read_single_character(s) == EOF) { - quit = 1; + patch_update_resp = s->file_diff_nr; break; } @@ -1616,8 +1805,30 @@ static int patch_update_file(struct add_p_state *s, hunk->use = SKIP_HUNK; } } else if (ch == 'q') { - quit = 1; + patch_update_resp = s->file_diff_nr; break; + } else if (!s->cfg.auto_advance && s->answer.buf[0] == '>') { + if (permitted & ALLOW_GOTO_NEXT_FILE) { + if (patch_update_resp == s->file_diff_nr - 1) + patch_update_resp = 0; + else + patch_update_resp++; + break; + } else { + err(s, _("No next file")); + continue; + } + } else if (!s->cfg.auto_advance && s->answer.buf[0] == '<') { + if (permitted & ALLOW_GOTO_PREVIOUS_FILE) { + if (patch_update_resp == 0) + patch_update_resp = s->file_diff_nr - 1; + else + patch_update_resp--; + break; + } else { + err(s, _("No previous file")); + continue; + } } else if (s->answer.buf[0] == 'K') { if (permitted & ALLOW_GOTO_PREVIOUS_HUNK) hunk_index = dec_mod(hunk_index, @@ -1730,7 +1941,7 @@ static int patch_update_file(struct add_p_state *s, err(s, _("Sorry, cannot split this hunk")); } else if (!split_hunk(s, file_diff, hunk - file_diff->hunk)) { - color_fprintf_ln(stdout, s->s.header_color, + color_fprintf_ln(stdout, s->cfg.header_color, _("Split into %d hunks."), (int)splittable_into); rendered_hunk_index = -1; @@ -1748,7 +1959,7 @@ static int patch_update_file(struct add_p_state *s, } else if (s->answer.buf[0] == '?') { const char *p = _(help_patch_remainder), *eol = p; - color_fprintf(stdout, s->s.help_color, "%s", + color_fprintf(stdout, s->cfg.help_color, "%s", _(s->mode->help_patch_text)); /* @@ -1763,10 +1974,22 @@ static int patch_update_file(struct add_p_state *s, * commands shown in the prompt that are not * always available. */ + if (all_decided && !strncmp(p, "HUNKS SUMMARY", 13)) { + int total = file_diff->hunk_nr, used = 0, skipped = 0; + + for (i = 0; i < file_diff->hunk_nr; i++) { + if (file_diff->hunk[i].use == USE_HUNK) + used += 1; + if (file_diff->hunk[i].use == SKIP_HUNK) + skipped += 1; + } + color_fprintf_ln(stdout, s->cfg.help_color, _(p), + total, used, skipped); + } if (*p != '?' && !strchr(s->buf.buf, *p)) continue; - color_fprintf_ln(stdout, s->s.help_color, + color_fprintf_ln(stdout, s->cfg.help_color, "%.*s", (int)(eol - p), p); } } else { @@ -1775,47 +1998,62 @@ static int patch_update_file(struct add_p_state *s, } } - /* Any hunk to be used? */ - for (i = 0; i < file_diff->hunk_nr; i++) - if (file_diff->hunk[i].use == USE_HUNK) - break; - - if (i < file_diff->hunk_nr || - (!file_diff->hunk_nr && file_diff->head.use == USE_HUNK)) { - /* At least one hunk selected: apply */ - strbuf_reset(&s->buf); - reassemble_patch(s, file_diff, 0, &s->buf); - - discard_index(s->s.r->index); - if (s->mode->apply_for_checkout) - apply_for_checkout(s, &s->buf, - s->mode->is_reverse); - else { - setup_child_process(s, &cp, "apply", NULL); - strvec_pushv(&cp.args, s->mode->apply_args); - if (pipe_command(&cp, s->buf.buf, s->buf.len, - NULL, 0, NULL, 0)) - error(_("'git apply' failed")); - } - if (repo_read_index(s->s.r) >= 0) - repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0, - 1, NULL, NULL, NULL); - } + if (s->cfg.auto_advance) + apply_patch(s, file_diff); putchar('\n'); - return quit; + return patch_update_resp; +} + +static int run_add_p_common(struct add_p_state *state, + const struct pathspec *ps, + unsigned flags) +{ + size_t binary_count = 0; + size_t i; + + if (parse_diff(state, ps) < 0) + return -1; + + for (i = 0; i < state->file_diff_nr;) { + if (state->file_diff[i].binary && !state->file_diff[i].hunk_nr) { + binary_count++; + i++; + continue; + } + if ((i = patch_update_file(state, i, flags)) == state->file_diff_nr) + break; + } + + if (!state->cfg.auto_advance) + for (i = 0; i < state->file_diff_nr; i++) + apply_patch(state, state->file_diff + i); + + if (state->file_diff_nr == 0) + err(state, _("No changes.")); + else if (binary_count == state->file_diff_nr) + err(state, _("Only binary files changed.")); + + return 0; } int run_add_p(struct repository *r, enum add_p_mode mode, - struct add_p_opt *o, const char *revision, - const struct pathspec *ps) + struct interactive_options *opts, const char *revision, + const struct pathspec *ps, + unsigned flags) { struct add_p_state s = { - { r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT + .r = r, + .index = r->index, + .index_file = r->index_file, + .answer = STRBUF_INIT, + .buf = STRBUF_INIT, + .plain = STRBUF_INIT, + .colored = STRBUF_INIT, }; - size_t i, binary_count = 0; + int ret; - init_add_i_state(&s.s, r, o); + interactive_config_init(&s.cfg, r, opts); if (mode == ADD_P_STASH) s.mode = &patch_mode_stash; @@ -1846,23 +2084,91 @@ int run_add_p(struct repository *r, enum add_p_mode mode, if (repo_read_index(r) < 0 || (!s.mode->index_only && repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1, - NULL, NULL, NULL) < 0) || - parse_diff(&s, ps) < 0) { - add_p_state_clear(&s); - return -1; + NULL, NULL, NULL) < 0)) { + ret = -1; + goto out; } - for (i = 0; i < s.file_diff_nr; i++) - if (s.file_diff[i].binary && !s.file_diff[i].hunk_nr) - binary_count++; - else if (patch_update_file(&s, s.file_diff + i)) - break; + ret = run_add_p_common(&s, ps, flags); + if (ret < 0) + goto out; - if (s.file_diff_nr == 0) - err(&s, _("No changes.")); - else if (binary_count == s.file_diff_nr) - err(&s, _("Only binary files changed.")); + ret = 0; +out: add_p_state_clear(&s); - return 0; + return ret; +} + +int run_add_p_index(struct repository *r, + struct index_state *index, + const char *index_file, + struct interactive_options *opts, + const char *revision, + const struct pathspec *ps, + unsigned flags) +{ + struct patch_mode mode = { + .apply_args = { "--cached", NULL }, + .apply_check_args = { "--cached", NULL }, + .prompt_mode = { + N_("Stage mode change [y,n,q,a,d%s,?]? "), + N_("Stage deletion [y,n,q,a,d%s,?]? "), + N_("Stage addition [y,n,q,a,d%s,?]? "), + N_("Stage this hunk [y,n,q,a,d%s,?]? ") + }, + .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk " + "will immediately be marked for staging."), + .help_patch_text = + N_("y - stage this hunk\n" + "n - do not stage this hunk\n" + "q - quit; do not stage this hunk or any of the remaining " + "ones\n" + "a - stage this hunk and all later hunks in the file\n" + "d - do not stage this hunk or any of the later hunks in " + "the file\n"), + .index_only = 1, + }; + struct add_p_state s = { + .r = r, + .index = index, + .index_file = index_file, + .answer = STRBUF_INIT, + .buf = STRBUF_INIT, + .plain = STRBUF_INIT, + .colored = STRBUF_INIT, + .mode = &mode, + .revision = revision, + }; + char parent_tree_oid[GIT_MAX_HEXSZ + 1]; + struct commit *commit; + int ret; + + interactive_config_init(&s.cfg, r, opts); + + commit = lookup_commit_reference_by_name(revision); + if (!commit) { + err(&s, _("Revision does not refer to a commit")); + ret = -1; + goto out; + } + + if (commit->parents) + oid_to_hex_r(parent_tree_oid, get_commit_tree_oid(commit->parents->item)); + else + oid_to_hex_r(parent_tree_oid, r->hash_algo->empty_tree); + + mode.diff_cmd[0] = "diff-tree"; + mode.diff_cmd[1] = "-r"; + mode.diff_cmd[2] = parent_tree_oid; + + ret = run_add_p_common(&s, ps, flags); + if (ret < 0) + goto out; + + ret = 0; + +out: + add_p_state_clear(&s); + return ret; }
diff --git a/add-patch.h b/add-patch.h new file mode 100644 index 0000000..fb6d975 --- /dev/null +++ b/add-patch.h
@@ -0,0 +1,74 @@ +#ifndef ADD_PATCH_H +#define ADD_PATCH_H + +#include "color.h" + +struct index_state; +struct pathspec; +struct repository; + +struct interactive_options { + int context; + int interhunkcontext; + int auto_advance; +}; + +#define INTERACTIVE_OPTIONS_INIT { \ + .context = -1, \ + .interhunkcontext = -1, \ + .auto_advance = 1, \ +} + +struct interactive_config { + enum git_colorbool use_color_interactive; + enum git_colorbool use_color_diff; + char header_color[COLOR_MAXLEN]; + char help_color[COLOR_MAXLEN]; + char prompt_color[COLOR_MAXLEN]; + char error_color[COLOR_MAXLEN]; + char reset_color_interactive[COLOR_MAXLEN]; + + char fraginfo_color[COLOR_MAXLEN]; + char context_color[COLOR_MAXLEN]; + char file_old_color[COLOR_MAXLEN]; + char file_new_color[COLOR_MAXLEN]; + char reset_color_diff[COLOR_MAXLEN]; + + int use_single_key; + char *interactive_diff_filter, *interactive_diff_algorithm; + int context, interhunkcontext; + int auto_advance; +}; + +void interactive_config_init(struct interactive_config *cfg, + struct repository *r, + struct interactive_options *opts); +void interactive_config_clear(struct interactive_config *cfg); + +enum add_p_mode { + ADD_P_ADD, + ADD_P_STASH, + ADD_P_RESET, + ADD_P_CHECKOUT, + ADD_P_WORKTREE, +}; + +enum add_p_flags { + /* Disallow "editing" hunks. */ + ADD_P_DISALLOW_EDIT = (1 << 0), +}; + +int run_add_p(struct repository *r, enum add_p_mode mode, + struct interactive_options *opts, const char *revision, + const struct pathspec *ps, + unsigned flags); + +int run_add_p_index(struct repository *r, + struct index_state *index, + const char *index_file, + struct interactive_options *opts, + const char *revision, + const struct pathspec *ps, + unsigned flags); + +#endif
diff --git a/alias.c b/alias.c index 1a1a141..ec9833d 100644 --- a/alias.c +++ b/alias.c
@@ -13,23 +13,57 @@ struct config_alias_data { struct string_list *list; }; -static int config_alias_cb(const char *key, const char *value, +static int config_alias_cb(const char *var, const char *value, const struct config_context *ctx UNUSED, void *d) { struct config_alias_data *data = d; - const char *p; + const char *subsection, *key; + size_t subsection_len; - if (!skip_prefix(key, "alias.", &p)) + if (parse_config_key(var, "alias", &subsection, &subsection_len, + &key) < 0) + return 0; + + /* + * Two config syntaxes: + * - alias.name = value (without subsection, case-insensitive) + * - [alias "name"] + * command = value (with subsection, case-sensitive) + */ + /* Treat [alias ""] (empty subsection) the same as plain [alias]. */ + if (subsection && !subsection_len) + subsection = NULL; + + if (subsection && strcmp(key, "command")) return 0; if (data->alias) { - if (!strcasecmp(p, data->alias)) { + int match; + + if (subsection) + match = (strlen(data->alias) == subsection_len && + !strncmp(data->alias, subsection, + subsection_len)); + else + match = !strcasecmp(data->alias, key); + + if (match) { FREE_AND_NULL(data->v); return git_config_string(&data->v, - key, value); + var, value); } } else if (data->list) { - string_list_append(data->list, p); + struct string_list_item *item; + + if (!value) + return config_error_nonbool(var); + + if (subsection) + item = string_list_append_nodup(data->list, + xmemdupz(subsection, subsection_len)); + else + item = string_list_append(data->list, key); + item->util = xstrdup(value); } return 0;
diff --git a/apply.c b/apply.c index a2ceb3f..63d5e3c 100644 --- a/apply.c +++ b/apply.c
@@ -42,6 +42,7 @@ struct gitdiff_data { struct strbuf *root; + const char *patch_input_file; int linenr; int p_value; }; @@ -900,7 +901,8 @@ static int parse_traditional_patch(struct apply_state *state, } } if (!name) - return error(_("unable to find filename in patch at line %d"), state->linenr); + return error(_("unable to find filename in patch at %s:%d"), + state->patch_input_file, state->linenr); return 0; } @@ -937,20 +939,35 @@ static int gitdiff_verify_name(struct gitdiff_data *state, if (*name) { char *another; - if (isnull) + if (isnull) { + if (state->patch_input_file) + return error(_("git apply: bad git-diff - expected /dev/null, got %s at %s:%d"), + *name, state->patch_input_file, state->linenr); return error(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"), *name, state->linenr); + } another = find_name(state->root, line, NULL, state->p_value, TERM_TAB); if (!another || strcmp(another, *name)) { free(another); + if (state->patch_input_file) + return error((side == DIFF_NEW_NAME) ? + _("git apply: bad git-diff - inconsistent new filename at %s:%d") : + _("git apply: bad git-diff - inconsistent old filename at %s:%d"), + state->patch_input_file, state->linenr); return error((side == DIFF_NEW_NAME) ? - _("git apply: bad git-diff - inconsistent new filename on line %d") : - _("git apply: bad git-diff - inconsistent old filename on line %d"), state->linenr); + _("git apply: bad git-diff - inconsistent new filename on line %d") : + _("git apply: bad git-diff - inconsistent old filename on line %d"), + state->linenr); } free(another); } else { - if (!is_dev_null(line)) - return error(_("git apply: bad git-diff - expected /dev/null on line %d"), state->linenr); + if (!is_dev_null(line)) { + if (state->patch_input_file) + return error(_("git apply: bad git-diff - expected /dev/null at %s:%d"), + state->patch_input_file, state->linenr); + return error(_("git apply: bad git-diff - expected /dev/null on line %d"), + state->linenr); + } } return 0; @@ -974,12 +991,19 @@ static int gitdiff_newname(struct gitdiff_data *state, DIFF_NEW_NAME); } -static int parse_mode_line(const char *line, int linenr, unsigned int *mode) +static int parse_mode_line(const char *line, + const char *patch_input_file, + int linenr, + unsigned int *mode) { char *end; *mode = strtoul(line, &end, 8); - if (end == line || !isspace(*end)) + if (end == line || !isspace(*end)) { + if (patch_input_file) + return error(_("invalid mode at %s:%d: %s"), + patch_input_file, linenr, line); return error(_("invalid mode on line %d: %s"), linenr, line); + } *mode = canon_mode(*mode); return 0; } @@ -988,14 +1012,16 @@ static int gitdiff_oldmode(struct gitdiff_data *state, const char *line, struct patch *patch) { - return parse_mode_line(line, state->linenr, &patch->old_mode); + return parse_mode_line(line, state->patch_input_file, state->linenr, + &patch->old_mode); } static int gitdiff_newmode(struct gitdiff_data *state, const char *line, struct patch *patch) { - return parse_mode_line(line, state->linenr, &patch->new_mode); + return parse_mode_line(line, state->patch_input_file, state->linenr, + &patch->new_mode); } static int gitdiff_delete(struct gitdiff_data *state, @@ -1314,6 +1340,7 @@ static int check_header_line(int linenr, struct patch *patch) } int parse_git_diff_header(struct strbuf *root, + const char *patch_input_file, int *linenr, int p_value, const char *line, @@ -1345,6 +1372,7 @@ int parse_git_diff_header(struct strbuf *root, size -= len; (*linenr)++; parse_hdr_state.root = root; + parse_hdr_state.patch_input_file = patch_input_file; parse_hdr_state.linenr = *linenr; parse_hdr_state.p_value = p_value; @@ -1382,6 +1410,7 @@ int parse_git_diff_header(struct strbuf *root, int res; if (len < oplen || memcmp(p->str, line, oplen)) continue; + parse_hdr_state.linenr = *linenr; res = p->fn(&parse_hdr_state, line + oplen, patch); if (res < 0) return -1; @@ -1396,12 +1425,20 @@ int parse_git_diff_header(struct strbuf *root, done: if (!patch->old_name && !patch->new_name) { if (!patch->def_name) { - error(Q_("git diff header lacks filename information when removing " - "%d leading pathname component (line %d)", - "git diff header lacks filename information when removing " - "%d leading pathname components (line %d)", - parse_hdr_state.p_value), - parse_hdr_state.p_value, *linenr); + if (patch_input_file) + error(Q_("git diff header lacks filename information when removing " + "%d leading pathname component at %s:%d", + "git diff header lacks filename information when removing " + "%d leading pathname components at %s:%d", + parse_hdr_state.p_value), + parse_hdr_state.p_value, patch_input_file, *linenr); + else + error(Q_("git diff header lacks filename information when removing " + "%d leading pathname component (line %d)", + "git diff header lacks filename information when removing " + "%d leading pathname components (line %d)", + parse_hdr_state.p_value), + parse_hdr_state.p_value, *linenr); return -128; } patch->old_name = xstrdup(patch->def_name); @@ -1409,8 +1446,12 @@ int parse_git_diff_header(struct strbuf *root, } if ((!patch->new_name && !patch->is_delete) || (!patch->old_name && !patch->is_new)) { - error(_("git diff header lacks filename information " - "(line %d)"), *linenr); + if (patch_input_file) + error(_("git diff header lacks filename information at %s:%d"), + patch_input_file, *linenr); + else + error(_("git diff header lacks filename information (line %d)"), + *linenr); return -128; } patch->is_toplevel_relative = 1; @@ -1577,8 +1618,9 @@ static int find_header(struct apply_state *state, struct fragment dummy; if (parse_fragment_header(line, len, &dummy) < 0) continue; - error(_("patch fragment without header at line %d: %.*s"), - state->linenr, (int)len-1, line); + error(_("patch fragment without header at %s:%d: %.*s"), + state->patch_input_file, state->linenr, + (int)len-1, line); return -128; } @@ -1590,7 +1632,9 @@ static int find_header(struct apply_state *state, * or mode change, so we handle that specially */ if (!memcmp("diff --git ", line, 11)) { - int git_hdr_len = parse_git_diff_header(&state->root, &state->linenr, + int git_hdr_len = parse_git_diff_header(&state->root, + state->patch_input_file, + &state->linenr, state->p_value, line, len, size, patch); if (git_hdr_len < 0) @@ -1640,6 +1684,14 @@ static void record_ws_error(struct apply_state *state, state->squelch_whitespace_errors < state->whitespace_error) return; + /* + * line[len] for an incomplete line points at the "\n" at the end + * of patch input line, so "%.*s" would drop the last letter on line; + * compensate for it. + */ + if (result & WS_INCOMPLETE_LINE) + len++; + err = whitespace_error_string(result); if (state->apply_verbosity > verbosity_silent) fprintf(stderr, "%s:%d: %s.\n%.*s\n", @@ -1671,6 +1723,35 @@ static void check_old_for_crlf(struct patch *patch, const char *line, int len) /* + * Just saw a single line in a fragment. If it is a part of this hunk + * that is a context " ", an added "+", or a removed "-" line, it may + * be followed by "\\ No newline..." to signal that the last "\n" on + * this line needs to be dropped. Depending on locale settings when + * the patch was produced we don't know what this line would exactly + * say. The only thing we do know is that it begins with "\ ". + * Checking for 12 is just for sanity check; "\ No newline..." would + * be at least that long in any l10n. + * + * Return 0 if the line we saw is not followed by "\ No newline...", + * or length of that line. The caller will use it to skip over the + * "\ No newline..." line. + */ +static int adjust_incomplete(const char *line, int len, + unsigned long size) +{ + int nextlen; + + if (*line != '\n' && *line != ' ' && *line != '+' && *line != '-') + return 0; + if (size - len < 12 || memcmp(line + len, "\\ ", 2)) + return 0; + nextlen = linelen(line + len, size - len); + if (nextlen < 12) + return 0; + return nextlen; +} + +/* * Parse a unified diff. Note that this really needs to parse each * fragment separately, since the only way to know the difference * between a "---" that is part of a patch, and a "---" that starts @@ -1684,9 +1765,30 @@ static int parse_fragment(struct apply_state *state, { int added, deleted; int len = linelen(line, size), offset; + int skip_len = 0; unsigned long oldlines, newlines; unsigned long leading, trailing; + /* do not complain a symbolic link being an incomplete line */ + if (patch->ws_rule & WS_INCOMPLETE_LINE) { + /* + * We want to figure out if the postimage is a + * symbolic link when applying the patch normally, or + * if the preimage is a symbolic link when applying + * the patch in reverse. A normal patch only has + * old_mode without new_mode. If it changes the + * filemode, new_mode has value, which is different + * from old_mode. + */ + unsigned mode = (state->apply_in_reverse + ? patch->old_mode + : patch->new_mode + ? patch->new_mode + : patch->old_mode); + if (mode && S_ISLNK(mode)) + patch->ws_rule &= ~WS_INCOMPLETE_LINE; + } + offset = parse_fragment_header(line, len, fragment); if (offset < 0) return -1; @@ -1710,6 +1812,22 @@ static int parse_fragment(struct apply_state *state, len = linelen(line, size); if (!len || line[len-1] != '\n') return -1; + + /* + * For an incomplete line, skip_len counts the bytes + * on "\\ No newline..." marker line that comes next + * to the current line. + * + * Reduce "len" to drop the newline at the end of + * line[], but add one to "skip_len", which will be + * added back to "len" for the next iteration, to + * compensate. + */ + skip_len = adjust_incomplete(line, len, size); + if (skip_len) { + len--; + skip_len++; + } switch (*line) { default: return -1; @@ -1745,19 +1863,12 @@ static int parse_fragment(struct apply_state *state, newlines--; trailing = 0; break; + } - /* - * We allow "\ No newline at end of file". Depending - * on locale settings when the patch was produced we - * don't know what this line looks like. The only - * thing we do know is that it begins with "\ ". - * Checking for 12 is just for sanity check -- any - * l10n of "\ No newline..." is at least that long. - */ - case '\\': - if (len < 12 || memcmp(line, "\\ ", 2)) - return -1; - break; + /* eat the "\\ No newline..." as well, if exists */ + if (skip_len) { + len += skip_len; + state->linenr++; } } if (oldlines || newlines) @@ -1768,14 +1879,6 @@ static int parse_fragment(struct apply_state *state, fragment->leading = leading; fragment->trailing = trailing; - /* - * If a fragment ends with an incomplete line, we failed to include - * it in the above loop because we hit oldlines == newlines == 0 - * before seeing it. - */ - if (12 < size && !memcmp(line, "\\ ", 2)) - offset += linelen(line, size); - patch->lines_added += added; patch->lines_deleted += deleted; @@ -1816,7 +1919,8 @@ static int parse_single_patch(struct apply_state *state, len = parse_fragment(state, line, size, patch, fragment); if (len <= 0) { free(fragment); - return error(_("corrupt patch at line %d"), state->linenr); + return error(_("corrupt patch at %s:%d"), + state->patch_input_file, state->linenr); } fragment->patch = line; fragment->size = len; @@ -2006,8 +2110,8 @@ static struct fragment *parse_binary_hunk(struct apply_state *state, corrupt: free(data); *status_p = -1; - error(_("corrupt binary patch at line %d: %.*s"), - state->linenr-1, llen-1, buffer); + error(_("corrupt binary patch at %s:%d: %.*s"), + state->patch_input_file, state->linenr-1, llen-1, buffer); return NULL; } @@ -2043,7 +2147,8 @@ static int parse_binary(struct apply_state *state, forward = parse_binary_hunk(state, &buffer, &size, &status, &used); if (!forward && !status) /* there has to be one hunk (forward hunk) */ - return error(_("unrecognized binary patch at line %d"), state->linenr-1); + return error(_("unrecognized binary patch at %s:%d"), + state->patch_input_file, state->linenr-1); if (status) /* otherwise we already gave an error message */ return status; @@ -2205,7 +2310,8 @@ static int parse_chunk(struct apply_state *state, char *buffer, unsigned long si */ if ((state->apply || state->check) && (!patch->is_binary && !metadata_changes(patch))) { - error(_("patch with only garbage at line %d"), state->linenr); + error(_("patch with only garbage at %s:%d"), + state->patch_input_file, state->linenr); return -128; } } @@ -3529,9 +3635,9 @@ static int three_way_merge(struct apply_state *state, else if (oideq(base, theirs) || oideq(ours, theirs)) return resolve_to(image, ours); - read_mmblob(&base_file, base); - read_mmblob(&our_file, ours); - read_mmblob(&their_file, theirs); + read_mmblob(&base_file, the_repository->objects, base); + read_mmblob(&our_file, the_repository->objects, ours); + read_mmblob(&their_file, the_repository->objects, theirs); merge_opts.variant = state->merge_variant; status = ll_merge(&result, path, &base_file, "base", @@ -3779,7 +3885,7 @@ static int check_preimage(struct apply_state *state, if (*ce && !(*ce)->ce_mode) BUG("ce_mode == 0 for path '%s'", old_name); - if (trust_executable_bit) + if (trust_executable_bit || !S_ISREG(st->st_mode)) st_mode = ce_mode_from_stat(*ce, st->st_mode); else if (*ce) st_mode = (*ce)->ce_mode; @@ -4105,7 +4211,7 @@ static int preimage_oid_in_gitlink_patch(struct patch *p, struct object_id *oid) */ struct fragment *hunk = p->fragments; static const char heading[] = "-Subproject commit "; - char *preimage; + const char *preimage; if (/* does the patch have only one hunk? */ hunk && !hunk->next && @@ -4766,6 +4872,7 @@ static int apply_patch(struct apply_state *state, int flush_attributes = 0; state->patch_input_file = filename; + state->linenr = 1; if (read_patch_file(&buf, fd) < 0) return -128; offset = 0; @@ -4922,7 +5029,8 @@ static int apply_option_parse_p(const struct option *opt, BUG_ON_OPT_NEG(unset); - state->p_value = atoi(arg); + if (strtol_i(arg, 10, &state->p_value) < 0 || state->p_value < 0) + die(_("option -p expects a non-negative integer, got '%s'"), arg); state->p_value_known = 1; return 0; } @@ -4963,6 +5071,10 @@ static int apply_option_parse_directory(const struct option *opt, strbuf_reset(&state->root); strbuf_addstr(&state->root, arg); + + if (strbuf_normalize_path(&state->root) < 0) + return error(_("unable to normalize directory: '%s'"), arg); + strbuf_complete(&state->root, '/'); return 0; }
diff --git a/apply.h b/apply.h index 90e887e..5f2f03d 100644 --- a/apply.h +++ b/apply.h
@@ -167,6 +167,7 @@ int check_apply_state(struct apply_state *state, int force_apply); * Returns -1 on failure, the length of the parsed header otherwise. */ int parse_git_diff_header(struct strbuf *root, + const char *patch_input_file, int *linenr, int p_value, const char *line,
diff --git a/archive-tar.c b/archive-tar.c index 73b63dd..0fc70d1 100644 --- a/archive-tar.c +++ b/archive-tar.c
@@ -12,8 +12,8 @@ #include "tar.h" #include "archive.h" #include "odb.h" +#include "odb/streaming.h" #include "strbuf.h" -#include "streaming.h" #include "run-command.h" #include "write-or-die.h" @@ -129,22 +129,20 @@ static void write_trailer(void) */ static int stream_blocked(struct repository *r, const struct object_id *oid) { - struct git_istream *st; - enum object_type type; - unsigned long sz; + struct odb_read_stream *st; char buf[BLOCKSIZE]; ssize_t readlen; - st = open_istream(r, oid, &type, &sz, NULL); + st = odb_read_stream_open(r->objects, oid, NULL); if (!st) return error(_("cannot stream blob %s"), oid_to_hex(oid)); for (;;) { - readlen = read_istream(st, buf, sizeof(buf)); + readlen = odb_read_stream_read(st, buf, sizeof(buf)); if (readlen <= 0) break; do_write_blocked(buf, readlen); } - close_istream(st); + odb_read_stream_close(st); if (!readlen) finish_record(); return readlen;
diff --git a/archive-zip.c b/archive-zip.c index bea5bdd..97ea8d6 100644 --- a/archive-zip.c +++ b/archive-zip.c
@@ -10,9 +10,9 @@ #include "gettext.h" #include "git-zlib.h" #include "hex.h" -#include "streaming.h" #include "utf8.h" #include "odb.h" +#include "odb/streaming.h" #include "strbuf.h" #include "userdiff.h" #include "write-or-die.h" @@ -309,7 +309,7 @@ static int write_zip_entry(struct archiver_args *args, enum zip_method method; unsigned char *out; void *deflated = NULL; - struct git_istream *stream = NULL; + struct odb_read_stream *stream = NULL; unsigned long flags = 0; int is_binary = -1; const char *path_without_prefix = path + args->baselen; @@ -347,12 +347,11 @@ static int write_zip_entry(struct archiver_args *args, method = ZIP_METHOD_DEFLATE; if (!buffer) { - enum object_type type; - stream = open_istream(args->repo, oid, &type, &size, - NULL); + stream = odb_read_stream_open(args->repo->objects, oid, NULL); if (!stream) return error(_("cannot stream blob %s"), oid_to_hex(oid)); + size = stream->size; flags |= ZIP_STREAM; out = NULL; } else { @@ -429,7 +428,7 @@ static int write_zip_entry(struct archiver_args *args, ssize_t readlen; for (;;) { - readlen = read_istream(stream, buf, sizeof(buf)); + readlen = odb_read_stream_read(stream, buf, sizeof(buf)); if (readlen <= 0) break; crc = crc32(crc, buf, readlen); @@ -439,7 +438,7 @@ static int write_zip_entry(struct archiver_args *args, buf, readlen); write_or_die(1, buf, readlen); } - close_istream(stream); + odb_read_stream_close(stream); if (readlen) return readlen; @@ -462,7 +461,7 @@ static int write_zip_entry(struct archiver_args *args, zstream.avail_out = sizeof(compressed); for (;;) { - readlen = read_istream(stream, buf, sizeof(buf)); + readlen = odb_read_stream_read(stream, buf, sizeof(buf)); if (readlen <= 0) break; crc = crc32(crc, buf, readlen); @@ -486,7 +485,7 @@ static int write_zip_entry(struct archiver_args *args, } } - close_istream(stream); + odb_read_stream_close(stream); if (readlen) return readlen;
diff --git a/archive.c b/archive.c index 310672b..fcd474c 100644 --- a/archive.c +++ b/archive.c
@@ -519,7 +519,7 @@ static void parse_treeish_arg(const char **argv, if (ar_args->mtime_option) archive_time = approxidate(ar_args->mtime_option); - tree = parse_tree_indirect(&oid); + tree = repo_parse_tree_indirect(the_repository, &oid); if (!tree) die(_("not a tree object: %s"), oid_to_hex(&oid));
diff --git a/attr.c b/attr.c index d1daeb0..7536954 100644 --- a/attr.c +++ b/attr.c
@@ -881,10 +881,11 @@ const char *git_attr_system_file(void) const char *git_attr_global_file(void) { - if (!git_attributes_file) - git_attributes_file = xdg_config_home("attributes"); + struct repo_config_values *cfg = repo_config_values(the_repository); + if (!cfg->attributes_file) + cfg->attributes_file = xdg_config_home("attributes"); - return git_attributes_file; + return cfg->attributes_file; } int git_attr_system_is_enabled(void) @@ -1064,24 +1065,52 @@ static int path_matches(const char *pathname, int pathlen, pattern, prefix, pat->patternlen); } -static int macroexpand_one(struct all_attrs_item *all_attrs, int nr, int rem); +struct attr_state_queue { + const struct attr_state **items; + size_t alloc, nr; +}; + +static void attr_state_queue_push(struct attr_state_queue *t, + const struct match_attr *a) +{ + for (size_t i = 0; i < a->num_attr; i++) { + ALLOC_GROW(t->items, t->nr + 1, t->alloc); + t->items[t->nr++] = &a->state[i]; + } +} + +static const struct attr_state *attr_state_queue_pop(struct attr_state_queue *t) +{ + return t->nr ? t->items[--t->nr] : NULL; +} + +static void attr_state_queue_release(struct attr_state_queue *t) +{ + free(t->items); +} static int fill_one(struct all_attrs_item *all_attrs, const struct match_attr *a, int rem) { - size_t i; + struct attr_state_queue todo = { 0 }; + const struct attr_state *state; - for (i = a->num_attr; rem > 0 && i > 0; i--) { - const struct git_attr *attr = a->state[i - 1].attr; + attr_state_queue_push(&todo, a); + while (rem > 0 && (state = attr_state_queue_pop(&todo))) { + const struct git_attr *attr = state->attr; const char **n = &(all_attrs[attr->attr_nr].value); - const char *v = a->state[i - 1].setto; + const char *v = state->setto; if (*n == ATTR__UNKNOWN) { + const struct all_attrs_item *item = + &all_attrs[attr->attr_nr]; *n = v; rem--; - rem = macroexpand_one(all_attrs, attr->attr_nr, rem); + if (item->macro && item->value == ATTR__TRUE) + attr_state_queue_push(&todo, item->macro); } } + attr_state_queue_release(&todo); return rem; } @@ -1106,16 +1135,6 @@ static int fill(const char *path, int pathlen, int basename_offset, return rem; } -static int macroexpand_one(struct all_attrs_item *all_attrs, int nr, int rem) -{ - const struct all_attrs_item *item = &all_attrs[nr]; - - if (item->macro && item->value == ATTR__TRUE) - return fill_one(all_attrs, item->macro, rem); - else - return rem; -} - /* * Marks the attributes which are macros based on the attribute stack. * This prevents having to search through the attribute stack each time
diff --git a/banned.h b/banned.h index 44e76bd..2b934c8 100644 --- a/banned.h +++ b/banned.h
@@ -41,4 +41,7 @@ #undef asctime_r #define asctime_r(t, buf) BANNED(asctime_r) +#undef mktemp +#define mktemp(x) BANNED(mktemp) + #endif /* BANNED_H */
diff --git a/bisect.c b/bisect.c index a6dc76b..ef17a44 100644 --- a/bisect.c +++ b/bisect.c
@@ -257,7 +257,7 @@ static struct commit_list *best_bisection_sorted(struct commit_list *list, int n p = p->next; } if (p) { - free_commit_list(p->next); + commit_list_free(p->next); p->next = NULL; } strbuf_release(&buf); @@ -438,7 +438,7 @@ void find_bisection(struct commit_list **commit_list, int *reaches, if (best) { if (!(bisect_flags & FIND_BISECTION_ALL)) { list->item = best->item; - free_commit_list(list->next); + commit_list_free(list->next); best = list; best->next = NULL; } @@ -450,21 +450,20 @@ void find_bisection(struct commit_list **commit_list, int *reaches, clear_commit_weight(&commit_weight); } -static int register_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid, - int flags UNUSED, void *cb_data UNUSED) +static int register_ref(const struct reference *ref, void *cb_data UNUSED) { struct strbuf good_prefix = STRBUF_INIT; strbuf_addstr(&good_prefix, term_good); strbuf_addstr(&good_prefix, "-"); - if (!strcmp(refname, term_bad)) { + if (!strcmp(ref->name, term_bad)) { free(current_bad_oid); current_bad_oid = xmalloc(sizeof(*current_bad_oid)); - oidcpy(current_bad_oid, oid); - } else if (starts_with(refname, good_prefix.buf)) { - oid_array_append(&good_revs, oid); - } else if (starts_with(refname, "skip-")) { - oid_array_append(&skipped_revs, oid); + oidcpy(current_bad_oid, ref->oid); + } else if (starts_with(ref->name, good_prefix.buf)) { + oid_array_append(&good_revs, ref->oid); + } else if (starts_with(ref->name, "skip-")) { + oid_array_append(&skipped_revs, ref->oid); } strbuf_release(&good_prefix); @@ -474,8 +473,12 @@ static int register_ref(const char *refname, const char *referent UNUSED, const static int read_bisect_refs(void) { - return refs_for_each_ref_in(get_main_ref_store(the_repository), - "refs/bisect/", register_ref, NULL); + struct refs_for_each_ref_options opts = { + .prefix = "refs/bisect/", + .trim_prefix = strlen("refs/bisect/"), + }; + return refs_for_each_ref_ext(get_main_ref_store(the_repository), + register_ref, NULL, &opts); } static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES") @@ -560,8 +563,8 @@ struct commit_list *filter_skipped(struct commit_list *list, } else { if (!show_all) { if (!skipped_first || !*skipped_first) { - free_commit_list(next); - free_commit_list(filtered); + commit_list_free(next); + commit_list_free(filtered); return list; } } else if (skipped_first && !*skipped_first) { @@ -880,7 +883,7 @@ static enum bisect_error check_merge_bases(size_t rev_nr, struct commit **rev, i } } - free_commit_list(result); + commit_list_free(result); return res; } @@ -1143,7 +1146,7 @@ enum bisect_error bisect_next_all(struct repository *r, const char *prefix) res = bisect_checkout(bisect_rev, no_checkout); cleanup: - free_commit_list(tried); + commit_list_free(tried); release_revisions(&revs); strvec_clear(&rev_argv); return res; @@ -1178,32 +1181,29 @@ int estimate_bisect_steps(int all) return (e < 3 * x) ? n : n - 1; } -static int mark_for_removal(const char *refname, - const char *referent UNUSED, - const struct object_id *oid UNUSED, - int flag UNUSED, void *cb_data) +static int mark_for_removal(const struct reference *ref, void *cb_data) { struct string_list *refs = cb_data; - char *ref = xstrfmt("refs/bisect%s", refname); - string_list_append(refs, ref); + string_list_append(refs, ref->name); return 0; } int bisect_clean_state(void) { + struct refs_for_each_ref_options opts = { + .prefix = "refs/bisect/", + }; int result = 0; /* There may be some refs packed during bisection */ - struct string_list refs_for_removal = STRING_LIST_INIT_NODUP; - refs_for_each_ref_in(get_main_ref_store(the_repository), - "refs/bisect", mark_for_removal, - (void *) &refs_for_removal); - string_list_append(&refs_for_removal, xstrdup("BISECT_HEAD")); - string_list_append(&refs_for_removal, xstrdup("BISECT_EXPECTED_REV")); + struct string_list refs_for_removal = STRING_LIST_INIT_DUP; + refs_for_each_ref_ext(get_main_ref_store(the_repository), + mark_for_removal, &refs_for_removal, &opts); + string_list_append(&refs_for_removal, "BISECT_HEAD"); + string_list_append(&refs_for_removal, "BISECT_EXPECTED_REV"); result = refs_delete_refs(get_main_ref_store(the_repository), "bisect: remove", &refs_for_removal, REF_NO_DEREF); - refs_for_removal.strdup_strings = 1; string_list_clear(&refs_for_removal, 0); unlink_or_warn(git_path_bisect_ancestors_ok()); unlink_or_warn(git_path_bisect_log());
diff --git a/blame.c b/blame.c index cb0b083..a3c49d1 100644 --- a/blame.c +++ b/blame.c
@@ -2368,7 +2368,7 @@ static struct commit_list *first_scapegoat(struct rev_info *revs, struct commit if (revs->first_parent_only && commit->parents && commit->parents->next) { - free_commit_list(commit->parents->next); + commit_list_free(commit->parents->next); commit->parents->next = NULL; } return commit->parents;
diff --git a/bloom.c b/bloom.c index 2d7b951..a805ac0 100644 --- a/bloom.c +++ b/bloom.c
@@ -354,7 +354,7 @@ static void init_truncated_large_filter(struct bloom_filter *filter, static int has_entries_with_high_bit(struct repository *r, struct tree *t) { - if (parse_tree(t)) + if (repo_parse_tree(r, t)) return 1; if (!(t->object.flags & VISITED)) { @@ -501,7 +501,7 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, struct hashmap_iter iter; for (i = 0; i < diff_queued_diff.nr; i++) { - const char *path = diff_queued_diff.queue[i]->two->path; + char *path = diff_queued_diff.queue[i]->two->path; /* * Add each leading directory of the changed file, i.e. for @@ -523,7 +523,7 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, free(e); if (!last_slash) - last_slash = (char*)path; + last_slash = path; *last_slash = '\0'; } while (*path);
diff --git a/branch.c b/branch.c index 26be358..243db7d 100644 --- a/branch.c +++ b/branch.c
@@ -375,7 +375,7 @@ int validate_branchname(const char *name, struct strbuf *ref) if (check_branch_ref(ref, name)) { int code = die_message(_("'%s' is not a valid branch name"), name); advise_if_enabled(ADVICE_REF_SYNTAX, - _("See `man git check-ref-format`")); + _("See 'git help check-ref-format'")); exit(code); }
diff --git a/branch.h b/branch.h index ec2f35f..3dc6e2a 100644 --- a/branch.h +++ b/branch.h
@@ -15,8 +15,6 @@ enum branch_track { BRANCH_TRACK_SIMPLE, }; -extern enum branch_track git_branch_track; - /* Functions for acting on the information about branches. */ /**
diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..3724b3a --- /dev/null +++ b/build.rs
@@ -0,0 +1,17 @@ +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation: version 2 of the License, dated June 1991. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see <https://www.gnu.org/licenses/>. + +fn main() { + println!("cargo:rustc-link-search=."); + println!("cargo:rustc-link-lib=git"); + println!("cargo:rustc-link-lib=z"); +}
diff --git a/builtin.h b/builtin.h index 1b35565..235c51f 100644 --- a/builtin.h +++ b/builtin.h
@@ -17,7 +17,8 @@ * . Define the implementation of the built-in command `foo` with * signature: * - * int cmd_foo(int argc, const char **argv, const char *prefix); + * int cmd_foo(int argc, const char **argv, + * const char *prefix, struct repository *repo); * * . Add the external declaration for the function to `builtin.h`. * @@ -29,12 +30,14 @@ * where options is the bitwise-or of: * * `RUN_SETUP`: + * * If there is not a Git directory to work on, abort. If there * is a work tree, chdir to the top of it if the command was * invoked in a subdirectory. If there is no work tree, no * chdir() is done. * * `RUN_SETUP_GENTLY`: + * * If there is a Git directory, chdir as per RUN_SETUP, otherwise, * don't chdir anywhere. * @@ -57,6 +60,12 @@ * more informed decision, e.g., by ignoring `pager.<cmd>` for * certain subcommands. * + * `NO_PARSEOPT`: + * + * Most Git builtins use the parseopt library for parsing options. + * This flag indicates that a custom parser is used and thus the + * builtin would not appear in 'git --list-cmds=parseopt'. + * * . Add `builtin/foo.o` to `BUILTIN_OBJS` in `Makefile`. * * Additionally, if `foo` is a new command, there are 4 more things to do: @@ -69,6 +78,21 @@ * * . Add an entry for `/git-foo` to `.gitignore`. * + * As you work on implementing your builtin, be mindful that the + * following tests will check different aspects of the builtin's + * readiness and adherence to matching the documentation: + * + * * t0012-help.sh checks that the builtin can handle -h, which comes + * automatically with the parseopt API. + * + * * t0450-txt-doc-vs-help.sh checks that the -h help output matches the + * SYNOPSIS in the documentation for the builtin. + * + * * t1517-outside-repo.sh checks that the builtin can handle -h when + * run outside of the context of a repository. Note that this test + * requires that the usage has a space after the builtin name, so some + * minimum description of options is required. + * * * How a built-in is called * ------------------------ @@ -172,6 +196,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix, struc int cmd_grep(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_hash_object(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_help(int argc, const char **argv, const char *prefix, struct repository *repo); +int cmd_history(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_hook(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_index_pack(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_init_db(int argc, const char **argv, const char *prefix, struct repository *repo);
diff --git a/builtin/add.c b/builtin/add.c index 3270979..7737ab8 100644 --- a/builtin/add.c +++ b/builtin/add.c
@@ -31,7 +31,7 @@ static const char * const builtin_add_usage[] = { NULL }; static int patch_interactive, add_interactive, edit_interactive; -static struct add_p_opt add_p_opt = ADD_P_OPT_INIT; +static struct interactive_options interactive_opts = INTERACTIVE_OPTIONS_INIT; static int take_worktree_changes; static int add_renormalize; static int pathspec_file_nul; @@ -160,7 +160,7 @@ static int refresh(struct repository *repo, int verbose, const struct pathspec * int interactive_add(struct repository *repo, const char **argv, const char *prefix, - int patch, struct add_p_opt *add_p_opt) + int patch, struct interactive_options *interactive_opts) { struct pathspec pathspec; int ret; @@ -172,9 +172,9 @@ int interactive_add(struct repository *repo, prefix, argv); if (patch) - ret = !!run_add_p(repo, ADD_P_ADD, add_p_opt, NULL, &pathspec); + ret = !!run_add_p(repo, ADD_P_ADD, interactive_opts, NULL, &pathspec, 0); else - ret = !!run_add_i(repo, &pathspec, add_p_opt); + ret = !!run_add_i(repo, &pathspec, interactive_opts); clear_pathspec(&pathspec); return ret; @@ -256,8 +256,10 @@ static struct option builtin_add_options[] = { OPT_GROUP(""), OPT_BOOL('i', "interactive", &add_interactive, N_("interactive picking")), OPT_BOOL('p', "patch", &patch_interactive, N_("select hunks interactively")), - OPT_DIFF_UNIFIED(&add_p_opt.context), - OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext), + OPT_BOOL(0, "auto-advance", &interactive_opts.auto_advance, + N_("auto advance to the next file when selecting hunks interactively")), + OPT_DIFF_UNIFIED(&interactive_opts.context), + OPT_DIFF_INTERHUNK_CONTEXT(&interactive_opts.interhunkcontext), OPT_BOOL('e', "edit", &edit_interactive, N_("edit current diff and apply")), OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files"), 0), OPT_BOOL('u', "update", &take_worktree_changes, N_("update tracked files")), @@ -400,9 +402,9 @@ int cmd_add(int argc, prepare_repo_settings(repo); repo->settings.command_requires_full_index = 0; - if (add_p_opt.context < -1) + if (interactive_opts.context < -1) die(_("'%s' cannot be negative"), "--unified"); - if (add_p_opt.interhunkcontext < -1) + if (interactive_opts.interhunkcontext < -1) die(_("'%s' cannot be negative"), "--inter-hunk-context"); if (patch_interactive) @@ -412,12 +414,14 @@ int cmd_add(int argc, die(_("options '%s' and '%s' cannot be used together"), "--dry-run", "--interactive/--patch"); if (pathspec_from_file) die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--interactive/--patch"); - exit(interactive_add(repo, argv + 1, prefix, patch_interactive, &add_p_opt)); + exit(interactive_add(repo, argv + 1, prefix, patch_interactive, &interactive_opts)); } else { - if (add_p_opt.context != -1) + if (interactive_opts.context != -1) die(_("the option '%s' requires '%s'"), "--unified", "--interactive/--patch"); - if (add_p_opt.interhunkcontext != -1) + if (interactive_opts.interhunkcontext != -1) die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--interactive/--patch"); + if (!interactive_opts.auto_advance) + die(_("the option '%s' requires '%s'"), "--no-auto-advance", "--interactive/--patch"); } if (edit_interactive) { @@ -584,7 +588,7 @@ int cmd_add(int argc, else exit_status |= add_files_to_cache(repo, prefix, &pathspec, ps_matched, - include_sparse, flags); + include_sparse, flags, ignored_too); if (take_worktree_changes && !add_renormalize && !ignore_add_errors && report_path_error(ps_matched, &pathspec))
diff --git a/builtin/am.c b/builtin/am.c index 277c2e7..fe6e087 100644 --- a/builtin/am.c +++ b/builtin/am.c
@@ -1188,7 +1188,7 @@ static void am_append_signoff(struct am_state *state) { struct strbuf sb = STRBUF_INIT; - strbuf_attach(&sb, state->msg, state->msg_len, state->msg_len); + strbuf_attach(&sb, state->msg, state->msg_len, state->msg_len + 1); append_signoff(&sb, 0, 0); state->msg = strbuf_detach(&sb, &state->msg_len); } @@ -1726,7 +1726,7 @@ static void do_commit(const struct am_state *state) run_hooks(the_repository, "post-applypatch"); - free_commit_list(parents); + commit_list_free(parents); strbuf_release(&sb); } @@ -1937,7 +1937,7 @@ static void am_run(struct am_state *state, int resume) */ if (!state->rebasing) { am_destroy(state); - run_auto_maintenance(state->quiet); + run_auto_maintenance(the_repository, state->quiet); } } @@ -1998,7 +1998,7 @@ static int fast_forward_to(struct tree *head, struct tree *remote, int reset) struct unpack_trees_options opts; struct tree_desc t[2]; - if (parse_tree(head) || parse_tree(remote)) + if (repo_parse_tree(the_repository, head) || repo_parse_tree(the_repository, remote)) return -1; repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR); @@ -2038,7 +2038,7 @@ static int merge_tree(struct tree *tree) struct unpack_trees_options opts; struct tree_desc t[1]; - if (parse_tree(tree)) + if (repo_parse_tree(the_repository, tree)) return -1; repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR); @@ -2071,11 +2071,11 @@ static int clean_index(const struct object_id *head, const struct object_id *rem struct tree *head_tree, *remote_tree, *index_tree; struct object_id index; - head_tree = parse_tree_indirect(head); + head_tree = repo_parse_tree_indirect(the_repository, head); if (!head_tree) return error(_("Could not parse object '%s'."), oid_to_hex(head)); - remote_tree = parse_tree_indirect(remote); + remote_tree = repo_parse_tree_indirect(the_repository, remote); if (!remote_tree) return error(_("Could not parse object '%s'."), oid_to_hex(remote)); @@ -2089,7 +2089,7 @@ static int clean_index(const struct object_id *head, const struct object_id *rem 0, NULL)) return -1; - index_tree = parse_tree_indirect(&index); + index_tree = repo_parse_tree_indirect(the_repository, &index); if (!index_tree) return error(_("Could not parse object '%s'."), oid_to_hex(&index));
diff --git a/builtin/backfill.c b/builtin/backfill.c index e80fc1b..2c5ce56 100644 --- a/builtin/backfill.c +++ b/builtin/backfill.c
@@ -35,6 +35,7 @@ struct backfill_context { struct oid_array current_batch; size_t min_batch_size; int sparse; + struct rev_info revs; }; static void backfill_context_clear(struct backfill_context *ctx) @@ -67,8 +68,7 @@ static int fill_missing_blobs(const char *path UNUSED, return 0; for (size_t i = 0; i < list->nr; i++) { - if (!odb_has_object(ctx->repo->objects, &list->oid[i], - OBJECT_INFO_FOR_PREFETCH)) + if (!odb_has_object(ctx->repo->objects, &list->oid[i], 0)) oid_array_append(&ctx->current_batch, &list->oid[i]); } @@ -80,7 +80,6 @@ static int fill_missing_blobs(const char *path UNUSED, static int do_backfill(struct backfill_context *ctx) { - struct rev_info revs; struct path_walk_info info = PATH_WALK_INFO_INIT; int ret; @@ -92,13 +91,14 @@ static int do_backfill(struct backfill_context *ctx) } } - repo_init_revisions(ctx->repo, &revs, ""); - handle_revision_arg("HEAD", &revs, 0, 0); + /* Walk from HEAD if otherwise unspecified. */ + if (!ctx->revs.pending.nr) + add_head_to_pending(&ctx->revs); info.blobs = 1; info.tags = info.commits = info.trees = 0; - info.revs = &revs; + info.revs = &ctx->revs; info.path_fn = fill_missing_blobs; info.path_fn_data = ctx; @@ -109,7 +109,6 @@ static int do_backfill(struct backfill_context *ctx) download_batch(ctx); path_walk_info_clear(&info); - release_revisions(&revs); return ret; } @@ -121,6 +120,7 @@ int cmd_backfill(int argc, const char **argv, const char *prefix, struct reposit .current_batch = OID_ARRAY_INIT, .min_batch_size = 50000, .sparse = 0, + .revs = REV_INFO_INIT, }; struct option options[] = { OPT_UNSIGNED(0, "min-batch-size", &ctx.min_batch_size, @@ -129,19 +129,29 @@ int cmd_backfill(int argc, const char **argv, const char *prefix, struct reposit N_("Restrict the missing objects to the current sparse-checkout")), OPT_END(), }; + struct repo_config_values *cfg = repo_config_values(the_repository); show_usage_with_options_if_asked(argc, argv, builtin_backfill_usage, options); argc = parse_options(argc, argv, prefix, options, builtin_backfill_usage, - 0); + PARSE_OPT_KEEP_UNKNOWN_OPT | + PARSE_OPT_KEEP_ARGV0 | + PARSE_OPT_KEEP_DASHDASH); + + repo_init_revisions(repo, &ctx.revs, prefix); + argc = setup_revisions(argc, argv, &ctx.revs, NULL); + + if (argc > 1) + die(_("unrecognized argument: %s"), argv[1]); repo_config(repo, git_default_config, NULL); if (ctx.sparse < 0) - ctx.sparse = core_apply_sparse_checkout; + ctx.sparse = cfg->apply_sparse_checkout; result = do_backfill(&ctx); backfill_context_clear(&ctx); + release_revisions(&ctx.revs); return result; }
diff --git a/builtin/bisect.c b/builtin/bisect.c index ccff4e1..4520e58 100644 --- a/builtin/bisect.c +++ b/builtin/bisect.c
@@ -363,10 +363,7 @@ static int check_and_set_terms(struct bisect_terms *terms, const char *cmd) return 0; } -static int inc_nr(const char *refname UNUSED, - const char *referent UNUSED, - const struct object_id *oid UNUSED, - int flag UNUSED, void *cb_data) +static int inc_nr(const struct reference *ref UNUSED, void *cb_data) { unsigned int *nr = (unsigned int *)cb_data; (*nr)++; @@ -425,13 +422,17 @@ static void bisect_status(struct bisect_state *state, { char *bad_ref = xstrfmt("refs/bisect/%s", terms->term_bad); char *good_glob = xstrfmt("%s-*", terms->term_good); + struct refs_for_each_ref_options opts = { + .pattern = good_glob, + .prefix = "refs/bisect/", + .trim_prefix = strlen("refs/bisect/"), + }; if (refs_ref_exists(get_main_ref_store(the_repository), bad_ref)) state->nr_bad = 1; - refs_for_each_glob_ref_in(get_main_ref_store(the_repository), inc_nr, - good_glob, "refs/bisect/", - (void *) &state->nr_good); + refs_for_each_ref_ext(get_main_ref_store(the_repository), + inc_nr, &state->nr_good, &opts); free(good_glob); free(bad_ref); @@ -554,18 +555,21 @@ static int bisect_append_log_quoted(const char **argv) return res; } -static int add_bisect_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid, - int flags UNUSED, void *cb) +static int add_bisect_ref(const struct reference *ref, void *cb) { struct add_bisect_ref_data *data = cb; - add_pending_oid(data->revs, refname, oid, data->object_flags); + add_pending_oid(data->revs, ref->name, ref->oid, data->object_flags); return 0; } static int prepare_revs(struct bisect_terms *terms, struct rev_info *revs) { + struct refs_for_each_ref_options opts = { + .prefix = "refs/bisect/", + .trim_prefix = strlen("refs/bisect/"), + }; int res = 0; struct add_bisect_ref_data cb = { revs }; char *good = xstrfmt("%s-*", terms->term_good); @@ -585,11 +589,16 @@ static int prepare_revs(struct bisect_terms *terms, struct rev_info *revs) reset_revision_walk(); repo_init_revisions(the_repository, revs, NULL); setup_revisions(0, NULL, revs, NULL); - refs_for_each_glob_ref_in(get_main_ref_store(the_repository), - add_bisect_ref, bad, "refs/bisect/", &cb); + + opts.pattern = bad; + refs_for_each_ref_ext(get_main_ref_store(the_repository), + add_bisect_ref, &cb, &opts); + cb.object_flags = UNINTERESTING; - refs_for_each_glob_ref_in(get_main_ref_store(the_repository), - add_bisect_ref, good, "refs/bisect/", &cb); + opts.pattern = good; + refs_for_each_ref_ext(get_main_ref_store(the_repository), + add_bisect_ref, &cb, &opts); + if (prepare_revision_walk(revs)) res = error(_("revision walk setup failed")); @@ -1170,12 +1179,9 @@ static int bisect_visualize(struct bisect_terms *terms, int argc, return run_command(&cmd); } -static int get_first_good(const char *refname UNUSED, - const char *referent UNUSED, - const struct object_id *oid, - int flag UNUSED, void *cb_data) +static int get_first_good(const struct reference *ref, void *cb_data) { - oidcpy(cb_data, oid); + oidcpy(cb_data, ref->oid); return 1; } @@ -1198,10 +1204,14 @@ static int verify_good(const struct bisect_terms *terms, const char *command) char *good_glob = xstrfmt("%s-*", terms->term_good); int no_checkout = refs_ref_exists(get_main_ref_store(the_repository), "BISECT_HEAD"); + struct refs_for_each_ref_options opts = { + .pattern = good_glob, + .prefix = "refs/bisect/", + .trim_prefix = strlen("refs/bisect/"), + }; - refs_for_each_glob_ref_in(get_main_ref_store(the_repository), - get_first_good, good_glob, "refs/bisect/", - &good_rev); + refs_for_each_ref_ext(get_main_ref_store(the_repository), + get_first_good, &good_rev, &opts); free(good_glob); if (refs_read_ref(get_main_ref_store(the_repository), no_checkout ? "BISECT_HEAD" : "HEAD", ¤t_rev))
diff --git a/builtin/blame.c b/builtin/blame.c index 2703820..f3a11ef 100644 --- a/builtin/blame.c +++ b/builtin/blame.c
@@ -10,7 +10,6 @@ #include "builtin.h" #include "config.h" #include "color.h" -#include "builtin.h" #include "environment.h" #include "gettext.h" #include "hex.h" @@ -454,7 +453,8 @@ static void determine_line_heat(struct commit_info *ci, const char **dest_color) *dest_color = colorfield[i].col; } -static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int opt) +static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, + int opt, struct blame_entry *prev_ent) { int cnt; const char *cp; @@ -485,7 +485,10 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int the_hash_algo->hexsz : (size_t) abbrev; if (opt & OUTPUT_COLOR_LINE) { - if (cnt > 0) { + if (cnt > 0 || + (prev_ent && + oideq(&suspect->commit->object.oid, + &prev_ent->suspect->commit->object.oid))) { color = repeated_meta_color; reset = GIT_COLOR_RESET; } else { @@ -571,7 +574,7 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int static void output(struct blame_scoreboard *sb, int option) { - struct blame_entry *ent; + struct blame_entry *ent, *prev_ent = NULL; if (option & OUTPUT_PORCELAIN) { for (ent = sb->ent; ent; ent = ent->next) { @@ -593,7 +596,8 @@ static void output(struct blame_scoreboard *sb, int option) if (option & OUTPUT_PORCELAIN) emit_porcelain(sb, ent, option); else { - emit_other(sb, ent, option); + emit_other(sb, ent, option, prev_ent); + prev_ent = ent; } } } @@ -739,7 +743,8 @@ static int git_blame_config(const char *var, const char *value, ret = git_config_pathname(&str, var, value); if (ret) return ret; - string_list_insert(&ignore_revs_file_list, str); + if (str) + string_list_insert(&ignore_revs_file_list, str); free(str); return 0; } @@ -779,6 +784,19 @@ static int git_blame_config(const char *var, const char *value, } } + if (!strcmp(var, "diff.algorithm")) { + long diff_algorithm; + if (!value) + return config_error_nonbool(var); + diff_algorithm = parse_algorithm_value(value); + if (diff_algorithm < 0) + return error(_("unknown value for config '%s': %s"), + var, value); + xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK; + xdl_opts |= diff_algorithm; + return 0; + } + if (git_diff_heuristic_config(var, value, cb) < 0) return -1; if (userdiff_config(var, value) < 0) @@ -824,6 +842,38 @@ static int blame_move_callback(const struct option *option, const char *arg, int return 0; } +static int blame_diff_algorithm_minimal(const struct option *option, + const char *arg, int unset) +{ + int *opt = option->value; + + BUG_ON_OPT_ARG(arg); + + *opt &= ~XDF_DIFF_ALGORITHM_MASK; + if (!unset) + *opt |= XDF_NEED_MINIMAL; + + return 0; +} + +static int blame_diff_algorithm_callback(const struct option *option, + const char *arg, int unset) +{ + int *opt = option->value; + long value = parse_algorithm_value(arg); + + BUG_ON_OPT_NEG(unset); + + if (value < 0) + return error(_("option diff-algorithm accepts \"myers\", " + "\"minimal\", \"patience\" and \"histogram\"")); + + *opt &= ~XDF_DIFF_ALGORITHM_MASK; + *opt |= value; + + return 0; +} + static int is_a_rev(const char *name) { struct object_id oid; @@ -915,11 +965,16 @@ int cmd_blame(int argc, OPT_BIT('s', NULL, &output_option, N_("suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR), OPT_BIT('e', "show-email", &output_option, N_("show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL), OPT_BIT('w', NULL, &xdl_opts, N_("ignore whitespace differences"), XDF_IGNORE_WHITESPACE), + OPT_CALLBACK_F(0, "diff-algorithm", &xdl_opts, N_("<algorithm>"), + N_("choose a diff algorithm"), + PARSE_OPT_NONEG, blame_diff_algorithm_callback), OPT_STRING_LIST(0, "ignore-rev", &ignore_rev_list, N_("rev"), N_("ignore <rev> when blaming")), OPT_STRING_LIST(0, "ignore-revs-file", &ignore_revs_file_list, N_("file"), N_("ignore revisions from <file>")), OPT_BIT(0, "color-lines", &output_option, N_("color redundant metadata from previous line differently"), OUTPUT_COLOR_LINE), OPT_BIT(0, "color-by-age", &output_option, N_("color lines by age"), OUTPUT_SHOW_AGE_WITH_COLOR), - OPT_BIT(0, "minimal", &xdl_opts, N_("spend extra cycles to find better match"), XDF_NEED_MINIMAL), + OPT_CALLBACK_F(0, "minimal", &xdl_opts, NULL, + N_("spend extra cycles to find a better match"), + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, blame_diff_algorithm_minimal), OPT_STRING('S', NULL, &revs_file, N_("file"), N_("use revisions from <file> instead of calling git-rev-list")), OPT_STRING(0, "contents", &contents_from, N_("file"), N_("use <file>'s contents as the final image")), OPT_CALLBACK_F('C', NULL, &opt, N_("score"), N_("find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback), @@ -1197,7 +1252,7 @@ int cmd_blame(int argc, sb.xdl_opts = xdl_opts; sb.no_whole_file_rename = no_whole_file_rename; - read_mailmap(&mailmap); + read_mailmap(the_repository, &mailmap); sb.found_guilty_entry = &found_guilty_entry; sb.found_guilty_entry_data = π
diff --git a/builtin/branch.c b/builtin/branch.c index 9fcf04b..1572a4f 100644 --- a/builtin/branch.c +++ b/builtin/branch.c
@@ -228,7 +228,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, int ret = 0; int remote_branch = 0; struct strbuf bname = STRBUF_INIT; - unsigned allowed_interpret; + enum interpret_branch_kind allowed_interpret; struct string_list refs_to_delete = STRING_LIST_INIT_DUP; struct string_list_item *item; int branch_name_pos; @@ -591,7 +591,7 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int else { int code = die_message(_("invalid branch name: '%s'"), oldname); advise_if_enabled(ADVICE_REF_SYNTAX, - _("See `man git check-ref-format`")); + _("See 'git help check-ref-format'")); exit(code); } } @@ -724,6 +724,7 @@ int cmd_branch(int argc, static struct ref_sorting *sorting; struct string_list sorting_options = STRING_LIST_INIT_DUP; struct ref_format format = REF_FORMAT_INIT; + struct repo_config_values *cfg = repo_config_values(the_repository); int ret; struct option options[] = { @@ -795,7 +796,7 @@ int cmd_branch(int argc, if (!sorting_options.nr) string_list_append(&sorting_options, "refname"); - track = git_branch_track; + track = cfg->branch_track; head = refs_resolve_refdup(get_main_ref_store(the_repository), "HEAD", 0, &head_oid, NULL);
diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 983ecec..cd13a3a 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c
@@ -18,13 +18,13 @@ #include "list-objects-filter-options.h" #include "parse-options.h" #include "userdiff.h" -#include "streaming.h" #include "oid-array.h" #include "packfile.h" #include "pack-bitmap.h" #include "object-file.h" #include "object-name.h" #include "odb.h" +#include "odb/streaming.h" #include "replace-object.h" #include "promisor-remote.h" #include "mailmap.h" @@ -95,7 +95,7 @@ static int filter_object(const char *path, unsigned mode, static int stream_blob(const struct object_id *oid) { - if (stream_blob_to_fd(1, oid, NULL, 0)) + if (odb_stream_blob_to_fd(the_repository->objects, 1, oid, NULL, 0)) die("unable to stream %s to stdout", oid_to_hex(oid)); return 0; } @@ -487,8 +487,7 @@ static void batch_object_write(const char *obj_name, data->info.sizep = &data->size; if (pack) - ret = packed_object_info(the_repository, pack, - offset, &data->info); + ret = packed_object_info(pack, offset, &data->info); else ret = odb_read_object_info_extended(the_repository->objects, &data->oid, &data->info, @@ -807,11 +806,14 @@ struct for_each_object_payload { void *payload; }; -static int batch_one_object_loose(const struct object_id *oid, - const char *path UNUSED, - void *_payload) +static int batch_one_object_oi(const struct object_id *oid, + struct object_info *oi, + 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, + payload->payload); return payload->callback(oid, NULL, 0, payload->payload); } @@ -846,12 +848,30 @@ static void batch_each_object(struct batch_options *opt, .callback = callback, .payload = _payload, }; - struct bitmap_index *bitmap = prepare_bitmap_git(the_repository); + struct odb_for_each_object_options opts = { + .flags = flags, + }; + struct bitmap_index *bitmap = NULL; + struct odb_source *source; - for_each_loose_object(the_repository->objects, batch_one_object_loose, &payload, 0); + /* + * 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 (bitmap && !for_each_bitmapped_object(bitmap, &opt->objects_filter, - batch_one_object_bitmapped, &payload)) { + 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) { @@ -862,8 +882,15 @@ static void batch_each_object(struct batch_options *opt, &payload, flags); } } else { - for_each_packed_object(the_repository, batch_one_object_packed, - &payload, flags); + 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); @@ -923,7 +950,7 @@ static int batch_objects(struct batch_options *opt) cb.seen = &seen; batch_each_object(opt, batch_unordered_object, - FOR_EACH_OBJECT_PACK_ORDER, &cb); + ODB_FOR_EACH_OBJECT_PACK_ORDER, &cb); oidset_clear(&seen); } else { @@ -1104,7 +1131,7 @@ int cmd_cat_file(int argc, opt_epts = (opt == 'e' || opt == 'p' || opt == 't' || opt == 's'); if (use_mailmap) - read_mailmap(&mailmap); + read_mailmap(the_repository, &mailmap); switch (batch.objects_filter.choice) { case LOFC_DISABLED:
diff --git a/builtin/check-mailmap.c b/builtin/check-mailmap.c index 9cc5c59..3f2a39c 100644 --- a/builtin/check-mailmap.c +++ b/builtin/check-mailmap.c
@@ -63,9 +63,9 @@ int cmd_check_mailmap(int argc, if (argc == 0 && !use_stdin) die(_("no contacts specified")); - read_mailmap(&mailmap); + read_mailmap(the_repository, &mailmap); if (mailmap_blob) - read_mailmap_blob(&mailmap, mailmap_blob); + read_mailmap_blob(the_repository, &mailmap, mailmap_blob); if (mailmap_file) read_mailmap_file(&mailmap, mailmap_file, 0);
diff --git a/builtin/checkout.c b/builtin/checkout.c index f945347..e031e61 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c
@@ -43,26 +43,11 @@ #include "parallel-checkout.h" #include "add-interactive.h" -static const char * const checkout_usage[] = { - N_("git checkout [<options>] <branch>"), - N_("git checkout [<options>] [<branch>] -- <file>..."), - NULL, -}; - -static const char * const switch_branch_usage[] = { - N_("git switch [<options>] [<branch>]"), - NULL, -}; - -static const char * const restore_usage[] = { - N_("git restore [<options>] [--source=<branch>] <file>..."), - NULL, -}; - struct checkout_opts { int patch_mode; int patch_context; int patch_interhunk_context; + int auto_advance; int quiet; int merge; int force; @@ -111,6 +96,7 @@ struct checkout_opts { .merge = -1, \ .patch_context = -1, \ .patch_interhunk_context = -1, \ + .auto_advance = 1, \ } struct branch_info { @@ -294,9 +280,9 @@ static int checkout_merged(int pos, const struct checkout *state, if (is_null_oid(&threeway[1]) || is_null_oid(&threeway[2])) return error(_("path '%s' does not have necessary versions"), path); - read_mmblob(&ancestor, &threeway[0]); - read_mmblob(&ours, &threeway[1]); - read_mmblob(&theirs, &threeway[2]); + read_mmblob(&ancestor, the_repository->objects, &threeway[0]); + read_mmblob(&ours, the_repository->objects, &threeway[1]); + read_mmblob(&theirs, the_repository->objects, &threeway[2]); repo_config_get_bool(the_repository, "merge.renormalize", &renormalize); ll_opts.renormalize = renormalize; @@ -546,9 +532,10 @@ static int checkout_paths(const struct checkout_opts *opts, if (opts->patch_mode) { enum add_p_mode patch_mode; - struct add_p_opt add_p_opt = { + struct interactive_options interactive_opts = { .context = opts->patch_context, .interhunkcontext = opts->patch_interhunk_context, + .auto_advance = opts->auto_advance }; const char *rev = new_branch_info->name; char rev_oid[GIT_MAX_HEXSZ + 1]; @@ -575,8 +562,8 @@ static int checkout_paths(const struct checkout_opts *opts, else BUG("either flag must have been set, worktree=%d, index=%d", opts->checkout_worktree, opts->checkout_index); - return !!run_add_p(the_repository, patch_mode, &add_p_opt, - rev, &opts->pathspec); + return !!run_add_p(the_repository, patch_mode, &interactive_opts, + rev, &opts->pathspec, 0); } repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR); @@ -724,7 +711,7 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o, init_checkout_metadata(&opts.meta, info->refname, info->commit ? &info->commit->object.oid : null_oid(the_hash_algo), NULL); - if (parse_tree(tree) < 0) + if (repo_parse_tree(the_repository, tree) < 0) return 128; init_tree_desc(&tree_desc, &tree->object.oid, tree->buffer, tree->size); switch (unpack_trees(1, &tree_desc, &opts)) { @@ -803,7 +790,8 @@ static int merge_working_tree(const struct checkout_opts *opts, if (opts->new_orphan_branch && opts->orphan_from_empty_tree) { if (new_branch_info->commit) BUG("'switch --orphan' should never accept a commit as starting point"); - new_tree = parse_tree_indirect(the_hash_algo->empty_tree); + new_tree = repo_parse_tree_indirect(the_repository, + the_hash_algo->empty_tree); if (!new_tree) BUG("unable to read empty tree"); } else { @@ -841,14 +829,15 @@ static int merge_working_tree(const struct checkout_opts *opts, old_commit_oid = old_branch_info->commit ? &old_branch_info->commit->object.oid : the_hash_algo->empty_tree; - tree = parse_tree_indirect(old_commit_oid); + tree = repo_parse_tree_indirect(the_repository, + old_commit_oid); if (!tree) die(_("unable to parse commit %s"), oid_to_hex(old_commit_oid)); init_tree_desc(&trees[0], &tree->object.oid, tree->buffer, tree->size); - if (parse_tree(new_tree) < 0) + if (repo_parse_tree(the_repository, new_tree) < 0) die(NULL); tree = new_tree; init_tree_desc(&trees[1], &tree->object.oid, @@ -899,10 +888,11 @@ static int merge_working_tree(const struct checkout_opts *opts, */ add_files_to_cache(the_repository, NULL, NULL, NULL, 0, - 0); + 0, 0); init_ui_merge_options(&o, the_repository); o.verbosity = 0; - work = write_in_core_index_as_tree(the_repository); + work = write_in_core_index_as_tree(the_repository, + the_repository->index); ret = reset_tree(new_tree, opts, 1, @@ -1063,11 +1053,9 @@ static void update_refs_for_switch(const struct checkout_opts *opts, report_tracking(new_branch_info); } -static int add_pending_uninteresting_ref(const char *refname, const char *referent UNUSED, - const struct object_id *oid, - int flags UNUSED, void *cb_data) +static int add_pending_uninteresting_ref(const struct reference *ref, void *cb_data) { - add_pending_oid(cb_data, refname, oid, UNINTERESTING); + add_pending_oid(cb_data, ref->name, ref->oid, UNINTERESTING); return 0; } @@ -1280,7 +1268,7 @@ static void setup_new_branch_info_and_source_tree( new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1); if (!new_branch_info->commit) { /* not a commit */ - *source_tree = parse_tree_indirect(rev); + *source_tree = repo_parse_tree_indirect(the_repository, rev); if (!*source_tree) die(_("unable to read tree (%s)"), oid_to_hex(rev)); } else { @@ -1293,9 +1281,17 @@ static void setup_new_branch_info_and_source_tree( } } + +enum checkout_command { + CHECKOUT_CHECKOUT = 1, + CHECKOUT_SWITCH = 2, + CHECKOUT_RESTORE = 3, +}; + static char *parse_remote_branch(const char *arg, struct object_id *rev, - int could_be_checkout_paths) + int could_be_checkout_paths, + enum checkout_command which_command) { int num_matches = 0; char *remote = unique_tracking_name(arg, rev, &num_matches); @@ -1308,14 +1304,30 @@ static char *parse_remote_branch(const char *arg, if (!remote && num_matches > 1) { if (advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME)) { + const char *cmdname; + + switch (which_command) { + case CHECKOUT_CHECKOUT: + cmdname = "checkout"; + break; + case CHECKOUT_SWITCH: + cmdname = "switch"; + break; + default: + BUG("command <%d> should not reach parse_remote_branch", + which_command); + break; + } + advise(_("If you meant to check out a remote tracking branch on, e.g. 'origin',\n" "you can do so by fully qualifying the name with the --track option:\n" "\n" - " git checkout --track origin/<name>\n" + " git %s --track origin/<name>\n" "\n" "If you'd like to always have checkouts of an ambiguous <name> prefer\n" "one remote, e.g. the 'origin' remote, consider setting\n" - "checkout.defaultRemote=origin in your config.")); + "checkout.defaultRemote=origin in your config."), + cmdname); } die(_("'%s' matched multiple (%d) remote tracking branches"), @@ -1327,6 +1339,7 @@ static char *parse_remote_branch(const char *arg, static int parse_branchname_arg(int argc, const char **argv, int dwim_new_local_branch_ok, + enum checkout_command which_command, struct branch_info *new_branch_info, struct checkout_opts *opts, struct object_id *rev) @@ -1436,7 +1449,8 @@ static int parse_branchname_arg(int argc, const char **argv, if (recover_with_dwim) { remote = parse_remote_branch(arg, rev, - could_be_checkout_paths); + could_be_checkout_paths, + which_command); if (remote) { *new_branch = arg; arg = remote; @@ -1590,6 +1604,7 @@ static void die_if_switching_to_a_branch_in_use(struct checkout_opts *opts, static int checkout_branch(struct checkout_opts *opts, struct branch_info *new_branch_info) { + struct repo_config_values *cfg = repo_config_values(the_repository); int noop_switch = (!new_branch_info->name && !opts->new_branch && !opts->force_detach); @@ -1633,7 +1648,7 @@ static int checkout_branch(struct checkout_opts *opts, if (opts->track != BRANCH_TRACK_UNSPECIFIED) die(_("'%s' cannot be used with '%s'"), "--detach", "-t"); } else if (opts->track == BRANCH_TRACK_UNSPECIFIED) - opts->track = git_branch_track; + opts->track = cfg->branch_track; if (new_branch_info->name && !new_branch_info->commit) die(_("Cannot switch branch to a non-commit '%s'"), @@ -1767,12 +1782,44 @@ static char cb_option = 'b'; static int checkout_main(int argc, const char **argv, const char *prefix, struct checkout_opts *opts, struct option *options, - const char * const usagestr[]) + enum checkout_command which_command) { int parseopt_flags = 0; struct branch_info new_branch_info = { 0 }; int ret; + static const char * const checkout_usage[] = { + N_("git checkout [<options>] <branch>"), + N_("git checkout [<options>] [<branch>] -- <file>..."), + NULL, + }; + + static const char * const switch_branch_usage[] = { + N_("git switch [<options>] [<branch>]"), + NULL, + }; + + static const char * const restore_usage[] = { + N_("git restore [<options>] [--source=<branch>] <file>..."), + NULL, + }; + + const char * const *usagestr; + + switch (which_command) { + case CHECKOUT_CHECKOUT: + usagestr = checkout_usage; + break; + case CHECKOUT_SWITCH: + usagestr = switch_branch_usage; + break; + case CHECKOUT_RESTORE: + usagestr = restore_usage; + break; + default: + BUG("no such checkout variant %d", which_command); + } + opts->overwrite_ignore = 1; opts->prefix = prefix; opts->show_progress = -1; @@ -1803,6 +1850,8 @@ static int checkout_main(int argc, const char **argv, const char *prefix, die(_("the option '%s' requires '%s'"), "--unified", "--patch"); if (opts->patch_interhunk_context != -1) die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch"); + if (!opts->auto_advance) + die(_("the option '%s' requires '%s'"), "--no-auto-advance", "--patch"); } if (opts->show_progress < 0) { @@ -1893,7 +1942,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix, opts->dwim_new_local_branch && opts->track == BRANCH_TRACK_UNSPECIFIED && !opts->new_branch; - int n = parse_branchname_arg(argc, argv, dwim_ok, + int n = parse_branchname_arg(argc, argv, dwim_ok, which_command, &new_branch_info, opts, &rev); argv += n; argc -= n; @@ -1901,7 +1950,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix, struct object_id rev; if (repo_get_oid_mb(the_repository, opts->from_treeish, &rev)) - die(_("could not resolve %s"), opts->from_treeish); + die(_("could not resolve '%s'"), opts->from_treeish); setup_new_branch_info_and_source_tree(&new_branch_info, opts, &rev, @@ -2001,6 +2050,8 @@ int cmd_checkout(int argc, OPT_BOOL(0, "guess", &opts.dwim_new_local_branch, N_("second guess 'git checkout <no-such-branch>' (default)")), OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode (default)")), + OPT_BOOL(0, "auto-advance", &opts.auto_advance, + N_("auto advance to the next file when selecting hunks interactively")), OPT_END() }; @@ -2032,7 +2083,7 @@ int cmd_checkout(int argc, options = add_checkout_path_options(&opts, options); return checkout_main(argc, argv, prefix, &opts, options, - checkout_usage); + CHECKOUT_CHECKOUT); } int cmd_switch(int argc, @@ -2071,7 +2122,7 @@ int cmd_switch(int argc, cb_option = 'c'; return checkout_main(argc, argv, prefix, &opts, options, - switch_branch_usage); + CHECKOUT_SWITCH); } int cmd_restore(int argc, @@ -2107,5 +2158,5 @@ int cmd_restore(int argc, options = add_checkout_path_options(&opts, options); return checkout_main(argc, argv, prefix, &opts, options, - restore_usage); + CHECKOUT_RESTORE); }
diff --git a/builtin/clone.c b/builtin/clone.c index c990f39..fba3c9c 100644 --- a/builtin/clone.c +++ b/builtin/clone.c
@@ -77,7 +77,6 @@ static struct string_list option_required_reference = STRING_LIST_INIT_NODUP; static struct string_list option_optional_reference = STRING_LIST_INIT_NODUP; static int max_jobs = -1; static struct string_list option_recurse_submodules = STRING_LIST_INIT_NODUP; -static struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT; static int config_filter_submodules = -1; /* unspecified */ static int option_remote_submodules; @@ -617,13 +616,15 @@ static int git_sparse_checkout_init(const char *repo) { struct child_process cmd = CHILD_PROCESS_INIT; int result = 0; + struct repo_config_values *cfg = repo_config_values(the_repository); + strvec_pushl(&cmd.args, "-C", repo, "sparse-checkout", "set", NULL); /* * We must apply the setting in the current process * for the later checkout to use the sparse-checkout file. */ - core_apply_sparse_checkout = 1; + cfg->apply_sparse_checkout = 1; cmd.git_cmd = 1; if (run_command(&cmd)) { @@ -634,7 +635,9 @@ static int git_sparse_checkout_init(const char *repo) return result; } -static int checkout(int submodule_progress, int filter_submodules, +static int checkout(int submodule_progress, + struct list_objects_filter_options *filter_options, + int filter_submodules, enum ref_storage_format ref_storage_format) { struct object_id oid; @@ -680,10 +683,10 @@ static int checkout(int submodule_progress, int filter_submodules, opts.dst_index = the_repository->index; init_checkout_metadata(&opts.meta, head, &oid, NULL); - tree = parse_tree_indirect(&oid); + tree = repo_parse_tree_indirect(the_repository, &oid); if (!tree) die(_("unable to parse commit %s"), oid_to_hex(&oid)); - if (parse_tree(tree) < 0) + if (repo_parse_tree(the_repository, tree) < 0) exit(128); init_tree_desc(&t, &tree->object.oid, tree->buffer, tree->size); if (unpack_trees(1, &t, &opts) < 0) @@ -723,9 +726,9 @@ static int checkout(int submodule_progress, int filter_submodules, strvec_pushf(&cmd.args, "--ref-format=%s", ref_storage_format_to_name(ref_storage_format)); - if (filter_submodules && filter_options.choice) + if (filter_submodules && filter_options->choice) strvec_pushf(&cmd.args, "--filter=%s", - expand_list_objects_filter_spec(&filter_options)); + expand_list_objects_filter_spec(filter_options)); if (option_single_branch >= 0) strvec_push(&cmd.args, option_single_branch ? @@ -903,6 +906,7 @@ int cmd_clone(int argc, enum transport_family family = TRANSPORT_FAMILY_ALL; struct string_list option_config = STRING_LIST_INIT_DUP; int option_dissociate = 0; + struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT; int option_filter_submodules = -1; /* unspecified */ struct string_list server_options = STRING_LIST_INIT_NODUP; const char *bundle_uri = NULL; @@ -999,6 +1003,8 @@ int cmd_clone(int argc, NULL }; + filter_options.allow_auto_filter = 1; + packet_trace_identity("clone"); repo_config(the_repository, git_clone_config, NULL); @@ -1136,8 +1142,7 @@ int cmd_clone(int argc, int val; /* remove duplicates */ - string_list_sort(&option_recurse_submodules); - string_list_remove_duplicates(&option_recurse_submodules, 0); + string_list_sort_u(&option_recurse_submodules, 0); /* * NEEDSWORK: In a multi-working-tree world, this needs to be @@ -1225,12 +1230,7 @@ int cmd_clone(int argc, initialize_repository_version(GIT_HASH_UNKNOWN, the_repository->ref_storage_format, 1); - strbuf_addf(&buf, "%s/HEAD", git_dir); - write_file(buf.buf, "ref: refs/heads/.invalid"); - - strbuf_reset(&buf); - strbuf_addf(&buf, "%s/refs", git_dir); - safe_create_dir(the_repository, buf.buf, 1); + refs_create_refdir_stubs(the_repository, git_dir, NULL); /* * additional config can be injected with -c, make sure it's included @@ -1442,7 +1442,7 @@ int cmd_clone(int argc, hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport)); initialize_repository_version(hash_algo, the_repository->ref_storage_format, 1); repo_set_hash_algo(the_repository, hash_algo); - create_reference_database(the_repository->ref_storage_format, NULL, 1); + create_reference_database(NULL, 1); /* * Before fetching from the remote, download and install bundle @@ -1617,7 +1617,7 @@ int cmd_clone(int argc, transport_disconnect(transport); if (option_dissociate) { - close_object_store(the_repository->objects); + odb_close(the_repository->objects); dissociate_from_references(); } @@ -1625,9 +1625,13 @@ int cmd_clone(int argc, return 1; junk_mode = JUNK_LEAVE_REPO; - err = checkout(submodule_progress, filter_submodules, + err = checkout(submodule_progress, + &filter_options, + filter_submodules, ref_storage_format); + list_objects_filter_release(&filter_options); + string_list_clear(&option_not, 0); string_list_clear(&option_config, 0); string_list_clear(&server_options, 0);
diff --git a/builtin/commit-tree.c b/builtin/commit-tree.c index 5189e68..30535db 100644 --- a/builtin/commit-tree.c +++ b/builtin/commit-tree.c
@@ -154,7 +154,7 @@ int cmd_commit_tree(int argc, ret = 0; out: - free_commit_list(parents); + commit_list_free(parents); strbuf_release(&buffer); return ret; }
diff --git a/builtin/commit.c b/builtin/commit.c index 0243f17..a3e52ac 100644 --- a/builtin/commit.c +++ b/builtin/commit.c
@@ -123,7 +123,7 @@ static const char *edit_message, *use_message; static char *fixup_message, *fixup_commit, *squash_message; static const char *fixup_prefix; static int all, also, interactive, patch_interactive, only, amend, signoff; -static struct add_p_opt add_p_opt = ADD_P_OPT_INIT; +static struct interactive_options interactive_opts = INTERACTIVE_OPTIONS_INIT; static int edit_flag = -1; /* unspecified */ static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; static int config_commit_verbose = -1; /* unspecified */ @@ -327,10 +327,11 @@ static void create_base_index(const struct commit *current_head) opts.dst_index = the_repository->index; opts.fn = oneway_merge; - tree = parse_tree_indirect(¤t_head->object.oid); + tree = repo_parse_tree_indirect(the_repository, + ¤t_head->object.oid); if (!tree) die(_("failed to unpack HEAD tree object")); - if (parse_tree(tree) < 0) + if (repo_parse_tree(the_repository, tree) < 0) exit(128); init_tree_desc(&t, &tree->object.oid, tree->buffer, tree->size); if (unpack_trees(1, &t, &opts)) @@ -356,9 +357,9 @@ static const char *prepare_index(const char **argv, const char *prefix, const char *ret; char *path = NULL; - if (add_p_opt.context < -1) + if (interactive_opts.context < -1) die(_("'%s' cannot be negative"), "--unified"); - if (add_p_opt.interhunkcontext < -1) + if (interactive_opts.interhunkcontext < -1) die(_("'%s' cannot be negative"), "--inter-hunk-context"); if (is_status) @@ -407,7 +408,7 @@ static const char *prepare_index(const char **argv, const char *prefix, old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT)); setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1); - if (interactive_add(the_repository, argv, prefix, patch_interactive, &add_p_opt) != 0) + if (interactive_add(the_repository, argv, prefix, patch_interactive, &interactive_opts) != 0) die(_("interactive add failed")); the_repository->index_file = old_repo_index_file; @@ -432,9 +433,9 @@ static const char *prepare_index(const char **argv, const char *prefix, ret = get_lock_file_path(&index_lock); goto out; } else { - if (add_p_opt.context != -1) + if (interactive_opts.context != -1) die(_("the option '%s' requires '%s'"), "--unified", "--interactive/--patch"); - if (add_p_opt.interhunkcontext != -1) + if (interactive_opts.interhunkcontext != -1) die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--interactive/--patch"); } @@ -455,7 +456,7 @@ static const char *prepare_index(const char **argv, const char *prefix, repo_hold_locked_index(the_repository, &index_lock, LOCK_DIE_ON_ERROR); add_files_to_cache(the_repository, also ? prefix : NULL, - &pathspec, ps_matched, 0, 0); + &pathspec, ps_matched, 0, 0, 0 ); if (!all && report_path_error(ps_matched, &pathspec)) exit(128); @@ -815,7 +816,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, logfile); hook_arg1 = "message"; } else if (use_message) { - char *buffer; + const char *buffer; buffer = strstr(use_message_buffer, "\n\n"); if (buffer) strbuf_addstr(&sb, skip_blank_lines(buffer + 2)); @@ -1154,7 +1155,7 @@ static const char *find_author_by_nickname(const char *name) setup_revisions(ac, av, &revs, NULL); revs.mailmap = xmalloc(sizeof(struct string_list)); string_list_init_nodup(revs.mailmap); - read_mailmap(revs.mailmap); + read_mailmap(the_repository, revs.mailmap); if (prepare_revision_walk(&revs)) die(_("revision walk setup failed")); @@ -1719,7 +1720,8 @@ int cmd_commit(int argc, OPT_STRING(0, "fixup", &fixup_message, N_("[(amend|reword):]commit"), N_("use autosquash formatted message to fixup or amend/reword specified commit")), OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")), OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")), - OPT_PASSTHRU_ARGV(0, "trailer", &trailer_args, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG), + OPT_STRVEC(0, "trailer", &trailer_args, N_("trailer"), + N_("add custom trailer(s)")), OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")), OPT_FILENAME('t', "template", &template_file, N_("use specified template file")), OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")), @@ -1742,8 +1744,8 @@ int cmd_commit(int argc, OPT_BOOL('i', "include", &also, N_("add specified files to index for commit")), OPT_BOOL(0, "interactive", &interactive, N_("interactively add files")), OPT_BOOL('p', "patch", &patch_interactive, N_("interactively add changes")), - OPT_DIFF_UNIFIED(&add_p_opt.context), - OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext), + OPT_DIFF_UNIFIED(&interactive_opts.context), + OPT_DIFF_INTERHUNK_CONTEXT(&interactive_opts.interhunkcontext), OPT_BOOL('o', "only", &only, N_("commit only specified files")), OPT_BOOL('n', "no-verify", &no_verify, N_("bypass pre-commit and commit-msg hooks")), OPT_BOOL(0, "dry-run", &dry_run, N_("show what would be committed")), @@ -1819,6 +1821,9 @@ int cmd_commit(int argc, argc = parse_and_validate_options(argc, argv, builtin_commit_options, builtin_commit_usage, prefix, current_head, &s); + if (trailer_args.nr) + trailer_config_init(); + if (verbose == -1) verbose = (config_commit_verbose < 0) ? 0 : config_commit_verbose; @@ -1849,7 +1854,7 @@ int cmd_commit(int argc, } else if (amend) { if (!reflog_msg) reflog_msg = "commit (amend)"; - parents = copy_commit_list(current_head->parents); + parents = commit_list_copy(current_head->parents); } else if (whence == FROM_MERGE) { struct strbuf m = STRBUF_INIT; FILE *fp; @@ -1957,7 +1962,7 @@ int cmd_commit(int argc, git_test_write_commit_graph_or_die(the_repository->objects->sources); repo_rerere(the_repository, 0); - run_auto_maintenance(quiet); + run_auto_maintenance(the_repository, quiet); run_commit_hook(use_editor, repo_get_index_file(the_repository), NULL, "post-commit", NULL); if (amend && !no_post_rewrite) { @@ -1978,7 +1983,7 @@ int cmd_commit(int argc, cleanup: free_commit_extra_headers(extra); - free_commit_list(parents); + commit_list_free(parents); strbuf_release(&author_ident); strbuf_release(&err); strbuf_release(&sb);
diff --git a/builtin/config.c b/builtin/config.c index 75852bd..cf4ba0f 100644 --- a/builtin/config.c +++ b/builtin/config.c
@@ -3,6 +3,7 @@ #include "abspath.h" #include "config.h" #include "color.h" +#include "date.h" #include "editor.h" #include "environment.h" #include "gettext.h" @@ -85,6 +86,17 @@ struct config_location_options { .respect_includes_opt = -1, \ } +enum config_type { + TYPE_NONE = 0, + TYPE_BOOL, + TYPE_INT, + TYPE_BOOL_OR_INT, + TYPE_PATH, + TYPE_EXPIRY_DATE, + TYPE_COLOR, + TYPE_BOOL_OR_STR, +}; + #define CONFIG_TYPE_OPTIONS(type) \ OPT_GROUP(N_("Type")), \ OPT_CALLBACK('t', "type", &type, N_("type"), N_("value is given this type"), option_parse_type), \ @@ -110,7 +122,7 @@ struct config_display_options { int show_origin; int show_scope; int show_keys; - int type; + enum config_type type; char *default_value; /* Populated via `display_options_init()`. */ int term; @@ -121,16 +133,9 @@ struct config_display_options { .term = '\n', \ .delim = '=', \ .key_delim = ' ', \ + .type = TYPE_NONE, \ } -#define TYPE_BOOL 1 -#define TYPE_INT 2 -#define TYPE_BOOL_OR_INT 3 -#define TYPE_PATH 4 -#define TYPE_EXPIRY_DATE 5 -#define TYPE_COLOR 6 -#define TYPE_BOOL_OR_STR 7 - #define OPT_CALLBACK_VALUE(s, l, v, h, i) { \ .type = OPTION_CALLBACK, \ .short_name = (s), \ @@ -231,95 +236,231 @@ static void show_config_scope(const struct config_display_options *opts, strbuf_addch(buf, term); } -static int show_all_config(const char *key_, const char *value_, - const struct config_context *ctx, - void *cb) -{ - const struct config_display_options *opts = cb; - const struct key_value_info *kvi = ctx->kvi; - - if (opts->show_origin || opts->show_scope) { - struct strbuf buf = STRBUF_INIT; - if (opts->show_scope) - show_config_scope(opts, kvi, &buf); - if (opts->show_origin) - show_config_origin(opts, kvi, &buf); - /* Use fwrite as "buf" can contain \0's if "end_null" is set. */ - fwrite(buf.buf, 1, buf.len, stdout); - strbuf_release(&buf); - } - if (!opts->omit_values && value_) - printf("%s%c%s%c", key_, opts->delim, value_, opts->term); - else - printf("%s%c", key_, opts->term); - return 0; -} - struct strbuf_list { struct strbuf *items; int nr; int alloc; }; +static int format_config_int64(struct strbuf *buf, + const char *key_, + const char *value_, + const struct key_value_info *kvi, + int gently) +{ + int64_t v = 0; + if (gently) { + if (!git_parse_int64(value_, &v)) + return -1; + } else { + /* may die() */ + v = git_config_int64(key_, value_ ? value_ : "", kvi); + } + + strbuf_addf(buf, "%"PRId64, v); + return 0; +} + +static int format_config_bool(struct strbuf *buf, + const char *key_, + const char *value_, + int gently) +{ + int v = 0; + if (gently) { + if ((v = git_parse_maybe_bool(value_)) < 0) + return -1; + } else { + /* may die() */ + v = git_config_bool(key_, value_); + } + + strbuf_addstr(buf, v ? "true" : "false"); + return 0; +} + +static int format_config_bool_or_int(struct strbuf *buf, + const char *key_, + const char *value_, + const struct key_value_info *kvi, + int gently) +{ + int v, is_bool = 0; + + if (gently) { + v = git_parse_maybe_bool_text(value_); + + if (v >= 0) + is_bool = 1; + else if (!git_parse_int(value_, &v)) + return -1; + } else { + v = git_config_bool_or_int(key_, value_, kvi, + &is_bool); + } + + if (is_bool) + strbuf_addstr(buf, v ? "true" : "false"); + else + strbuf_addf(buf, "%d", v); + + return 0; +} + +/* This mode is always gentle. */ +static int format_config_bool_or_str(struct strbuf *buf, + const char *value_) +{ + int v = git_parse_maybe_bool(value_); + if (v < 0) + strbuf_addstr(buf, value_); + else + strbuf_addstr(buf, v ? "true" : "false"); + return 0; +} + +static int format_config_path(struct strbuf *buf, + const char *key_, + const char *value_, + int gently) +{ + char *v; + + if (git_config_pathname(&v, key_, value_) < 0) + return -1; + + if (v) + strbuf_addstr(buf, v); + else + return gently ? -1 : 1; /* :(optional)no-such-file */ + + free(v); + return 0; +} + +static int format_config_expiry_date(struct strbuf *buf, + const char *key_, + const char *value_, + int quietly) +{ + timestamp_t t; + if (quietly) { + if (parse_expiry_date(value_, &t)) + return -1; + } else if (git_config_expiry_date(&t, key_, value_) < 0) { + return -1; + } + + strbuf_addf(buf, "%"PRItime, t); + return 0; +} + +static int format_config_color(struct strbuf *buf, + const char *key_, + const char *value_, + int gently) +{ + char v[COLOR_MAXLEN]; + + if (gently) { + if (color_parse_quietly(value_, v) < 0) + return -1; + } else if (git_config_color(v, key_, value_) < 0) { + return -1; + } + + strbuf_addstr(buf, v); + return 0; +} + +/* + * Format the configuration key-value pair (`key_`, `value_`) and + * append it into strbuf `buf`. Returns a negative value on failure, + * 0 on success, 1 on a missing optional value (i.e., telling the + * caller to pretend that <key_,value_> did not exist). + * + * Note: 'gently' is currently ignored, but will be implemented in + * a future change. + */ static int format_config(const struct config_display_options *opts, struct strbuf *buf, const char *key_, - const char *value_, const struct key_value_info *kvi) + const char *value_, const struct key_value_info *kvi, + int gently) { + int res = 0; if (opts->show_scope) show_config_scope(opts, kvi, buf); if (opts->show_origin) show_config_origin(opts, kvi, buf); if (opts->show_keys) strbuf_addstr(buf, key_); - if (!opts->omit_values) { - if (opts->show_keys) - strbuf_addch(buf, opts->key_delim); - if (opts->type == TYPE_INT) - strbuf_addf(buf, "%"PRId64, - git_config_int64(key_, value_ ? value_ : "", kvi)); - else if (opts->type == TYPE_BOOL) - strbuf_addstr(buf, git_config_bool(key_, value_) ? - "true" : "false"); - else if (opts->type == TYPE_BOOL_OR_INT) { - int is_bool, v; - v = git_config_bool_or_int(key_, value_, kvi, - &is_bool); - if (is_bool) - strbuf_addstr(buf, v ? "true" : "false"); - else - strbuf_addf(buf, "%d", v); - } else if (opts->type == TYPE_BOOL_OR_STR) { - int v = git_parse_maybe_bool(value_); - if (v < 0) - strbuf_addstr(buf, value_); - else - strbuf_addstr(buf, v ? "true" : "false"); - } else if (opts->type == TYPE_PATH) { - char *v; - if (git_config_pathname(&v, key_, value_) < 0) - return -1; - strbuf_addstr(buf, v); - free((char *)v); - } else if (opts->type == TYPE_EXPIRY_DATE) { - timestamp_t t; - if (git_config_expiry_date(&t, key_, value_) < 0) - return -1; - strbuf_addf(buf, "%"PRItime, t); - } else if (opts->type == TYPE_COLOR) { - char v[COLOR_MAXLEN]; - if (git_config_color(v, key_, value_) < 0) - return -1; - strbuf_addstr(buf, v); - } else if (value_) { + if (opts->omit_values) + goto terminator; + + if (opts->show_keys) + strbuf_addch(buf, opts->key_delim); + + switch (opts->type) { + case TYPE_INT: + res = format_config_int64(buf, key_, value_, kvi, gently); + break; + + case TYPE_BOOL: + res = format_config_bool(buf, key_, value_, gently); + break; + + case TYPE_BOOL_OR_INT: + res = format_config_bool_or_int(buf, key_, value_, kvi, gently); + break; + + case TYPE_BOOL_OR_STR: + res = format_config_bool_or_str(buf, value_); + break; + + case TYPE_PATH: + res = format_config_path(buf, key_, value_, gently); + break; + + case TYPE_EXPIRY_DATE: + res = format_config_expiry_date(buf, key_, value_, gently); + break; + + case TYPE_COLOR: + res = format_config_color(buf, key_, value_, gently); + break; + + case TYPE_NONE: + if (value_) { strbuf_addstr(buf, value_); } else { /* Just show the key name; back out delimiter */ if (opts->show_keys) strbuf_setlen(buf, buf->len - 1); } + break; + + default: + BUG("undefined type %d", opts->type); } + +terminator: strbuf_addch(buf, opts->term); + return res; +} + +static int show_all_config(const char *key_, const char *value_, + const struct config_context *ctx, + void *cb) +{ + const struct config_display_options *opts = cb; + const struct key_value_info *kvi = ctx->kvi; + struct strbuf formatted = STRBUF_INIT; + + if (format_config(opts, &formatted, key_, value_, kvi, 1) >= 0) + fwrite(formatted.buf, 1, formatted.len, stdout); + + strbuf_release(&formatted); return 0; } @@ -344,6 +485,7 @@ static int collect_config(const char *key_, const char *value_, struct collect_config_data *data = cb; struct strbuf_list *values = data->values; const struct key_value_info *kvi = ctx->kvi; + int status; if (!(data->get_value_flags & GET_VALUE_KEY_REGEXP) && strcmp(key_, data->key)) @@ -361,8 +503,15 @@ static int collect_config(const char *key_, const char *value_, ALLOC_GROW(values->items, values->nr + 1, values->alloc); strbuf_init(&values->items[values->nr], 0); - return format_config(data->display_opts, &values->items[values->nr++], - key_, value_, kvi); + status = format_config(data->display_opts, &values->items[values->nr++], + key_, value_, kvi, 0); + if (status < 0) + return status; + if (status) { + strbuf_release(&values->items[--values->nr]); + status = 0; + } + return status; } static int get_value(const struct config_location_options *opts, @@ -438,15 +587,23 @@ static int get_value(const struct config_location_options *opts, if (!values.nr && display_opts->default_value) { struct key_value_info kvi = KVI_INIT; struct strbuf *item; + int status; kvi_from_param(&kvi); ALLOC_GROW(values.items, values.nr + 1, values.alloc); item = &values.items[values.nr++]; strbuf_init(item, 0); - if (format_config(display_opts, item, key_, - display_opts->default_value, &kvi) < 0) + + status = format_config(display_opts, item, key_, + display_opts->default_value, &kvi, 0); + if (status < 0) die(_("failed to format default config value: %s"), display_opts->default_value); + if (status) { + /* default was a missing optional value */ + values.nr--; + strbuf_release(item); + } } ret = !values.nr; @@ -681,6 +838,7 @@ static int get_urlmatch(const struct config_location_options *opts, const char *var, const char *url) { int ret; + char *section; char *section_tail; struct config_display_options display_opts = *_display_opts; struct string_list_item *item; @@ -694,8 +852,8 @@ static int get_urlmatch(const struct config_location_options *opts, if (!url_normalize(url, &config.url)) die("%s", config.url.err); - config.section = xstrdup_tolower(var); - section_tail = strchr(config.section, '.'); + config.section = section = xstrdup_tolower(var); + section_tail = strchr(section, '.'); if (section_tail) { *section_tail = '\0'; config.key = section_tail + 1; @@ -714,11 +872,13 @@ static int get_urlmatch(const struct config_location_options *opts, for_each_string_list_item(item, &values) { struct urlmatch_current_candidate_value *matched = item->util; struct strbuf buf = STRBUF_INIT; + int status; - format_config(&display_opts, &buf, item->string, - matched->value_is_null ? NULL : matched->value.buf, - &matched->kvi); - fwrite(buf.buf, 1, buf.len, stdout); + status = format_config(&display_opts, &buf, item->string, + matched->value_is_null ? NULL : matched->value.buf, + &matched->kvi, 0); + if (!status) + fwrite(buf.buf, 1, buf.len, stdout); strbuf_release(&buf); strbuf_release(&matched->value); @@ -727,7 +887,7 @@ static int get_urlmatch(const struct config_location_options *opts, string_list_clear(&values, 1); free(config.url.url); - free((void *)config.section); + free(section); return ret; } @@ -841,6 +1001,19 @@ static void display_options_init(struct config_display_options *opts) } } +static void display_options_init_list(struct config_display_options *opts) +{ + opts->show_keys = 1; + + if (opts->end_nul) { + display_options_init(opts); + } else { + opts->term = '\n'; + opts->delim = ' '; + opts->key_delim = '='; + } +} + static int cmd_config_list(int argc, const char **argv, const char *prefix, struct repository *repo UNUSED) { @@ -859,7 +1032,7 @@ static int cmd_config_list(int argc, const char **argv, const char *prefix, check_argc(argc, 0, 0); location_options_init(&location_opts, prefix); - display_options_init(&display_opts); + display_options_init_list(&display_opts); setup_auto_pager("config", 1); @@ -985,7 +1158,7 @@ static int cmd_config_set(int argc, const char **argv, const char *prefix, argv[0], comment, value); if (ret == CONFIG_NOTHING_SET) error(_("cannot overwrite multiple values with a single value\n" - " Use a regexp, --add or --replace-all to change %s."), argv[0]); + " Use --value=<pattern>, --append or --all to change %s."), argv[0]); } location_options_release(&location_opts); @@ -1003,8 +1176,8 @@ static int cmd_config_unset(int argc, const char **argv, const char *prefix, struct option opts[] = { CONFIG_LOCATION_OPTIONS(location_opts), OPT_GROUP(N_("Filter")), - OPT_BIT(0, "all", &flags, N_("replace multi-valued config option with new value"), CONFIG_FLAGS_MULTI_REPLACE), - OPT_STRING(0, "value", &value_pattern, N_("pattern"), N_("show config with values matching the pattern")), + OPT_BIT(0, "all", &flags, N_("unset all multi-valued config options"), CONFIG_FLAGS_MULTI_REPLACE), + OPT_STRING(0, "value", &value_pattern, N_("pattern"), N_("unset multi-valued config options with matching values")), OPT_BIT(0, "fixed-value", &flags, N_("use string equality when comparing values to value pattern"), CONFIG_FLAGS_FIXED_VALUE), OPT_END(), }; @@ -1290,6 +1463,7 @@ static int cmd_config_actions(int argc, const char **argv, const char *prefix) if (actions == ACTION_LIST) { check_argc(argc, 0, 0); + display_options_init_list(&display_opts); if (config_with_options(show_all_config, &display_opts, &location_opts.source, the_repository, &location_opts.options) < 0) {
diff --git a/builtin/credential-store.c b/builtin/credential-store.c index b74e06c..bc1453c 100644 --- a/builtin/credential-store.c +++ b/builtin/credential-store.c
@@ -7,6 +7,7 @@ #include "path.h" #include "string-list.h" #include "parse-options.h" +#include "url.h" #include "write-or-die.h" static struct lock_file credential_lock; @@ -76,12 +77,6 @@ static void rewrite_credential_file(const char *fn, struct credential *c, die_errno("unable to write credential store"); } -static int is_rfc3986_unreserved(char ch) -{ - return isalnum(ch) || - ch == '-' || ch == '_' || ch == '.' || ch == '~'; -} - static int is_rfc3986_reserved_or_unreserved(char ch) { if (is_rfc3986_unreserved(ch))
diff --git a/builtin/describe.c b/builtin/describe.c index ffaf8d9..bffeed1 100644 --- a/builtin/describe.c +++ b/builtin/describe.c
@@ -112,13 +112,13 @@ static int replace_name(struct commit_name *e, if (!e->tag) { t = lookup_tag(the_repository, &e->oid); - if (!t || parse_tag(t)) + if (!t || parse_tag(the_repository, t)) return 1; e->tag = t; } t = lookup_tag(the_repository, oid); - if (!t || parse_tag(t)) + if (!t || parse_tag(the_repository, t)) return 0; *tag = t; @@ -154,20 +154,19 @@ static void add_to_known_names(const char *path, } } -static int get_name(const char *path, const char *referent UNUSED, const struct object_id *oid, - int flag UNUSED, void *cb_data UNUSED) +static int get_name(const struct reference *ref, void *cb_data UNUSED) { int is_tag = 0; struct object_id peeled; int is_annotated, prio; const char *path_to_match = NULL; - if (skip_prefix(path, "refs/tags/", &path_to_match)) { + if (skip_prefix(ref->name, "refs/tags/", &path_to_match)) { is_tag = 1; } else if (all) { if ((exclude_patterns.nr || patterns.nr) && - !skip_prefix(path, "refs/heads/", &path_to_match) && - !skip_prefix(path, "refs/remotes/", &path_to_match)) { + !skip_prefix(ref->name, "refs/heads/", &path_to_match) && + !skip_prefix(ref->name, "refs/remotes/", &path_to_match)) { /* Only accept reference of known type if there are match/exclude patterns */ return 0; } @@ -209,10 +208,10 @@ static int get_name(const char *path, const char *referent UNUSED, const struct } /* Is it annotated? */ - if (!peel_iterated_oid(the_repository, oid, &peeled)) { - is_annotated = !oideq(oid, &peeled); + if (!reference_get_peeled_oid(the_repository, ref, &peeled)) { + is_annotated = !oideq(ref->oid, &peeled); } else { - oidcpy(&peeled, oid); + oidcpy(&peeled, ref->oid); is_annotated = 0; } @@ -229,7 +228,8 @@ static int get_name(const char *path, const char *referent UNUSED, const struct else prio = 0; - add_to_known_names(all ? path + 5 : path + 10, &peeled, prio, oid); + add_to_known_names(all ? ref->name + 5 : ref->name + 10, + &peeled, prio, ref->oid); return 0; } @@ -335,7 +335,7 @@ static void append_name(struct commit_name *n, struct strbuf *dst) { if (n->prio == 2 && !n->tag) { n->tag = lookup_tag(the_repository, &n->oid); - if (!n->tag || parse_tag(n->tag)) + if (!n->tag || parse_tag(the_repository, n->tag)) die(_("annotated tag %s not available"), n->path); } if (n->tag && !n->name_checked) { @@ -558,7 +558,7 @@ static void process_object(struct object *obj, const char *path, void *data) describe_commit(pcd->current_commit, pcd->dst); strbuf_addf(pcd->dst, ":%s", path); } - free_commit_list(pcd->revs->commits); + commit_list_free(pcd->revs->commits); pcd->revs->commits = NULL; } } @@ -641,6 +641,9 @@ int cmd_describe(int argc, const char *prefix, struct repository *repo UNUSED ) { + struct refs_for_each_ref_options for_each_ref_opts = { + .flags = REFS_FOR_EACH_INCLUDE_BROKEN, + }; int contains = 0; struct option options[] = { OPT_BOOL(0, "contains", &contains, N_("find the tag that comes after the commit")), @@ -738,8 +741,8 @@ int cmd_describe(int argc, } hashmap_init(&names, commit_name_neq, NULL, 0); - refs_for_each_rawref(get_main_ref_store(the_repository), get_name, - NULL); + refs_for_each_ref_ext(get_main_ref_store(the_repository), + get_name, NULL, &for_each_ref_opts); if (!hashmap_get_size(&names) && !always) die(_("No names found, cannot describe anything."));
diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index 49dd4d0..8b8f8b5 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c
@@ -33,7 +33,7 @@ static int stdin_diff_commit(struct commit *commit, const char *p) struct commit *parent = lookup_commit(the_repository, &oid); if (!pptr) { /* Free the real parent list */ - free_commit_list(commit->parents); + commit_list_free(commit->parents); commit->parents = NULL; pptr = &(commit->parents); } @@ -52,7 +52,7 @@ static int stdin_diff_trees(struct tree *tree1, const char *p) if (!isspace(*p++) || parse_oid_hex(p, &oid, &p) || *p) return error("Need exactly two trees, separated by a space"); tree2 = lookup_tree(the_repository, &oid); - if (!tree2 || parse_tree(tree2)) + if (!tree2 || repo_parse_tree(the_repository, tree2)) return -1; printf("%s %s\n", oid_to_hex(&tree1->object.oid), oid_to_hex(&tree2->object.oid));
diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 0421360..13621b0 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c
@@ -64,7 +64,7 @@ static int parse_opt_sign_mode(const struct option *opt, if (unset) return 0; - if (parse_sign_mode(arg, val)) + if (parse_sign_mode(arg, val, NULL)) return error(_("unknown %s mode: %s"), opt->long_name, arg); return 0; @@ -797,10 +797,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, (int)(committer_end - committer), committer); if (signatures.nr) { switch (signed_commit_mode) { - case SIGN_ABORT: - die(_("encountered signed commit %s; use " - "--signed-commits=<mode> to handle it"), - oid_to_hex(&commit->object.oid)); + /* Exporting modes */ case SIGN_WARN_VERBATIM: warning(_("exporting %"PRIuMAX" signature(s) for commit %s"), (uintmax_t)signatures.nr, oid_to_hex(&commit->object.oid)); @@ -811,12 +808,28 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, print_signature(item->string, item->util); } break; + + /* Stripping modes */ case SIGN_WARN_STRIP: warning(_("stripping signature(s) from commit %s"), oid_to_hex(&commit->object.oid)); /* fallthru */ case SIGN_STRIP: break; + + /* Aborting modes */ + case SIGN_ABORT: + die(_("encountered signed commit %s; use " + "--signed-commits=<mode> to handle it"), + oid_to_hex(&commit->object.oid)); + case SIGN_STRIP_IF_INVALID: + die(_("'strip-if-invalid' is not a valid mode for " + "git fast-export with --signed-commits=<mode>")); + case SIGN_SIGN_IF_INVALID: + die(_("'sign-if-invalid' is not a valid mode for " + "git fast-export with --signed-commits=<mode>")); + default: + BUG("invalid signed_commit_mode value %d", signed_commit_mode); } string_list_clear(&signatures, 0); } @@ -935,16 +948,15 @@ static void handle_tag(const char *name, struct tag *tag) size_t sig_offset = parse_signed_buffer(message, message_size); if (sig_offset < message_size) switch (signed_tag_mode) { - case SIGN_ABORT: - die(_("encountered signed tag %s; use " - "--signed-tags=<mode> to handle it"), - oid_to_hex(&tag->object.oid)); + /* Exporting modes */ case SIGN_WARN_VERBATIM: warning(_("exporting signed tag %s"), oid_to_hex(&tag->object.oid)); /* fallthru */ case SIGN_VERBATIM: break; + + /* Stripping modes */ case SIGN_WARN_STRIP: warning(_("stripping signature from tag %s"), oid_to_hex(&tag->object.oid)); @@ -952,6 +964,20 @@ static void handle_tag(const char *name, struct tag *tag) case SIGN_STRIP: message_size = sig_offset; break; + + /* Aborting modes */ + case SIGN_ABORT: + die(_("encountered signed tag %s; use " + "--signed-tags=<mode> to handle it"), + oid_to_hex(&tag->object.oid)); + case SIGN_STRIP_IF_INVALID: + die(_("'strip-if-invalid' is not a valid mode for " + "git fast-export with --signed-tags=<mode>")); + case SIGN_SIGN_IF_INVALID: + die(_("'sign-if-invalid' is not a valid mode for " + "git fast-export with --signed-tags=<mode>")); + default: + BUG("invalid signed_commit_mode value %d", signed_commit_mode); } } @@ -1098,8 +1124,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info) free(full_name); } - string_list_sort(&extra_refs); - string_list_remove_duplicates(&extra_refs, 0); + string_list_sort_u(&extra_refs, 0); } static void handle_tags_and_duplicates(struct string_list *extras)
diff --git a/builtin/fast-import.c b/builtin/fast-import.c index 7c194e7..570fd04 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c
@@ -190,6 +190,7 @@ static const char *global_prefix; static enum sign_mode signed_tag_mode = SIGN_VERBATIM; static enum sign_mode signed_commit_mode = SIGN_VERBATIM; +static const char *signed_commit_keyid; /* Memory pools */ static struct mem_pool fi_mem_pool = { @@ -875,6 +876,7 @@ static void end_packfile(void) running = 1; clear_delta_base_cache(); if (object_count) { + struct odb_source_files *files = odb_source_files_downcast(pack_data->repo->objects->sources); struct packed_git *new_p; struct object_id cur_pack_oid; char *idx_name; @@ -900,8 +902,7 @@ static void end_packfile(void) idx_name = keep_pack(create_index()); /* Register the packfile with core git's machinery. */ - new_p = packfile_store_load_pack(pack_data->repo->objects->packfiles, - idx_name, 1); + new_p = packfile_store_load_pack(files->packed, idx_name, 1); if (!new_p) die(_("core Git rejected index %s"), idx_name); all_packs[pack_id] = new_p; @@ -955,7 +956,7 @@ static int store_object( struct object_id *oidout, uintmax_t mark) { - struct packfile_store *packs = the_repository->objects->packfiles; + struct odb_source *source; void *out, *delta; struct object_entry *e; unsigned char hdr[96]; @@ -979,7 +980,13 @@ static int store_object( if (e->idx.offset) { duplicate_count_by_type[type]++; return 1; - } else if (find_oid_pack(&oid, packfile_store_get_packs(packs))) { + } + + for (source = the_repository->objects->sources; source; source = source->next) { + struct odb_source_files *files = odb_source_files_downcast(source); + + if (!packfile_list_find_oid(packfile_store_get_packs(files->packed), &oid)) + continue; e->type = type; e->pack_id = MAX_PACK_ID; e->idx.offset = 1; /* just not zero! */ @@ -1096,10 +1103,10 @@ static void truncate_pack(struct hashfile_checkpoint *checkpoint) static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark) { - struct packfile_store *packs = the_repository->objects->packfiles; size_t in_sz = 64 * 1024, out_sz = 64 * 1024; unsigned char *in_buf = xmalloc(in_sz); unsigned char *out_buf = xmalloc(out_sz); + struct odb_source *source; struct object_entry *e; struct object_id oid; unsigned long hdrlen; @@ -1179,24 +1186,31 @@ static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark) if (e->idx.offset) { duplicate_count_by_type[OBJ_BLOB]++; truncate_pack(&checkpoint); + goto out; + } - } else if (find_oid_pack(&oid, packfile_store_get_packs(packs))) { + for (source = the_repository->objects->sources; source; source = source->next) { + struct odb_source_files *files = odb_source_files_downcast(source); + + if (!packfile_list_find_oid(packfile_store_get_packs(files->packed), &oid)) + continue; e->type = OBJ_BLOB; e->pack_id = MAX_PACK_ID; e->idx.offset = 1; /* just not zero! */ duplicate_count_by_type[OBJ_BLOB]++; truncate_pack(&checkpoint); - - } else { - e->depth = 0; - e->type = OBJ_BLOB; - e->pack_id = pack_id; - e->idx.offset = offset; - e->idx.crc32 = crc32_end(pack_file); - object_count++; - object_count_by_type[OBJ_BLOB]++; + goto out; } + e->depth = 0; + e->type = OBJ_BLOB; + e->pack_id = pack_id; + e->idx.offset = offset; + e->idx.crc32 = crc32_end(pack_file); + object_count++; + object_count_by_type[OBJ_BLOB]++; + +out: free(in_buf); free(out_buf); } @@ -2772,7 +2786,7 @@ static void add_gpgsig_to_commit(struct strbuf *commit_data, { struct string_list siglines = STRING_LIST_INIT_NODUP; - if (!sig->hash_algo) + if (!sig || !sig->hash_algo) return; strbuf_addstr(commit_data, header); @@ -2815,6 +2829,107 @@ static void import_one_signature(struct signature_data *sig_sha1, die(_("parse_one_signature() returned unknown hash algo")); } +static void finalize_commit_buffer(struct strbuf *new_data, + struct signature_data *sig_sha1, + struct signature_data *sig_sha256, + struct strbuf *msg) +{ + add_gpgsig_to_commit(new_data, "gpgsig ", sig_sha1); + add_gpgsig_to_commit(new_data, "gpgsig-sha256 ", sig_sha256); + + strbuf_addch(new_data, '\n'); + strbuf_addbuf(new_data, msg); +} + +static void warn_invalid_signature(struct signature_check *check, + const char *msg, enum sign_mode mode) +{ + const char *signer = check->signer ? check->signer : _("unknown"); + const char *subject; + int subject_len = find_commit_subject(msg, &subject); + + switch (mode) { + case SIGN_STRIP_IF_INVALID: + if (subject_len > 100) + warning(_("stripping invalid signature for commit '%.100s...'\n" + " allegedly by %s"), subject, signer); + else if (subject_len > 0) + warning(_("stripping invalid signature for commit '%.*s'\n" + " allegedly by %s"), subject_len, subject, signer); + else + warning(_("stripping invalid signature for commit\n" + " allegedly by %s"), signer); + break; + case SIGN_SIGN_IF_INVALID: + if (subject_len > 100) + warning(_("replacing invalid signature for commit '%.100s...'\n" + " allegedly by %s"), subject, signer); + else if (subject_len > 0) + warning(_("replacing invalid signature for commit '%.*s'\n" + " allegedly by %s"), subject_len, subject, signer); + else + warning(_("replacing invalid signature for commit\n" + " allegedly by %s"), signer); + break; + default: + BUG("unsupported signing mode"); + } +} + +static void handle_signature_if_invalid(struct strbuf *new_data, + struct signature_data *sig_sha1, + struct signature_data *sig_sha256, + struct strbuf *msg, + enum sign_mode mode) +{ + struct strbuf tmp_buf = STRBUF_INIT; + struct signature_check signature_check = { 0 }; + int ret; + + /* Check signature in a temporary commit buffer */ + strbuf_addbuf(&tmp_buf, new_data); + finalize_commit_buffer(&tmp_buf, sig_sha1, sig_sha256, msg); + ret = verify_commit_buffer(tmp_buf.buf, tmp_buf.len, &signature_check); + + if (ret) { + warn_invalid_signature(&signature_check, msg->buf, mode); + + if (mode == SIGN_SIGN_IF_INVALID) { + struct strbuf signature = STRBUF_INIT; + struct strbuf payload = STRBUF_INIT; + + /* + * NEEDSWORK: To properly support interoperability mode + * when signing commit signatures, the commit buffer + * must be provided in both the repository and + * compatibility object formats. As currently + * implemented, only the repository object format is + * considered meaning compatibility signatures cannot be + * generated. Thus, attempting to sign commit signatures + * in interoperability mode is currently unsupported. + */ + if (the_repository->compat_hash_algo) + die(_("signing commits in interoperability mode is unsupported")); + + strbuf_addstr(&payload, signature_check.payload); + if (sign_buffer(&payload, &signature, signed_commit_keyid, + SIGN_BUFFER_USE_DEFAULT_KEY)) + die(_("failed to sign commit object")); + add_header_signature(new_data, &signature, the_hash_algo); + + strbuf_release(&signature); + strbuf_release(&payload); + } + + finalize_commit_buffer(new_data, NULL, NULL, msg); + } else { + strbuf_swap(new_data, &tmp_buf); + } + + signature_check_clear(&signature_check); + strbuf_release(&tmp_buf); +} + static void parse_new_commit(const char *arg) { static struct strbuf msg = STRBUF_INIT; @@ -2866,6 +2981,8 @@ static void parse_new_commit(const char *arg) warning(_("importing a commit signature verbatim")); /* fallthru */ case SIGN_VERBATIM: + case SIGN_STRIP_IF_INVALID: + case SIGN_SIGN_IF_INVALID: import_one_signature(&sig_sha1, &sig_sha256, v); break; @@ -2950,11 +3067,14 @@ static void parse_new_commit(const char *arg) "encoding %s\n", encoding); - add_gpgsig_to_commit(&new_data, "gpgsig ", &sig_sha1); - add_gpgsig_to_commit(&new_data, "gpgsig-sha256 ", &sig_sha256); + if ((signed_commit_mode == SIGN_STRIP_IF_INVALID || + signed_commit_mode == SIGN_SIGN_IF_INVALID) && + (sig_sha1.hash_algo || sig_sha256.hash_algo)) + handle_signature_if_invalid(&new_data, &sig_sha1, &sig_sha256, + &msg, signed_commit_mode); + else + finalize_commit_buffer(&new_data, &sig_sha1, &sig_sha256, &msg); - strbuf_addch(&new_data, '\n'); - strbuf_addbuf(&new_data, &msg); free(author); free(committer); free(encoding); @@ -2975,9 +3095,6 @@ static void handle_tag_signature(struct strbuf *msg, const char *name) switch (signed_tag_mode) { /* First, modes that don't change anything */ - case SIGN_ABORT: - die(_("encountered signed tag; use " - "--signed-tags=<mode> to handle it")); case SIGN_WARN_VERBATIM: warning(_("importing a tag signature verbatim for tag '%s'"), name); /* fallthru */ @@ -2994,7 +3111,16 @@ static void handle_tag_signature(struct strbuf *msg, const char *name) strbuf_setlen(msg, sig_offset); break; - /* Third, BUG */ + /* Third, aborting modes */ + case SIGN_ABORT: + die(_("encountered signed tag; use " + "--signed-tags=<mode> to handle it")); + case SIGN_STRIP_IF_INVALID: + die(_("'strip-if-invalid' is not a valid mode for " + "git fast-import with --signed-tags=<mode>")); + case SIGN_SIGN_IF_INVALID: + die(_("'sign-if-invalid' is not a valid mode for " + "git fast-import with --signed-tags=<mode>")); default: BUG("invalid signed_tag_mode value %d from tag '%s'", signed_tag_mode, name); @@ -3181,7 +3307,7 @@ static void cat_blob(struct object_entry *oe, struct object_id *oid) cat_blob_write("\n", 1); if (oe && oe->pack_id == pack_id) { last_blob.offset = oe->idx.offset; - strbuf_attach(&last_blob.data, buf, size, size); + strbuf_attach(&last_blob.data, buf, size, size + 1); last_blob.depth = oe->depth; } else free(buf); @@ -3584,10 +3710,10 @@ static int parse_one_option(const char *option) } else if (skip_prefix(option, "export-pack-edges=", &option)) { option_export_pack_edges(option); } else if (skip_prefix(option, "signed-commits=", &option)) { - if (parse_sign_mode(option, &signed_commit_mode)) + if (parse_sign_mode(option, &signed_commit_mode, &signed_commit_keyid)) usagef(_("unknown --signed-commits mode '%s'"), option); } else if (skip_prefix(option, "signed-tags=", &option)) { - if (parse_sign_mode(option, &signed_tag_mode)) + if (parse_sign_mode(option, &signed_tag_mode, NULL)) usagef(_("unknown --signed-tags mode '%s'"), option); } else if (!strcmp(option, "quiet")) { show_stats = 0;
diff --git a/builtin/fetch.c b/builtin/fetch.c index c7ff348..4795b2a 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c
@@ -47,7 +47,7 @@ static const char * const builtin_fetch_usage[] = { N_("git fetch [<options>] [<repository> [<refspec>...]]"), N_("git fetch [<options>] <group>"), - N_("git fetch --multiple [<options>] [(<repository> | <group>)...]"), + N_("git fetch --multiple [<options>] [(<repository>|<group>)...]"), N_("git fetch --all [<options>]"), NULL }; @@ -97,7 +97,6 @@ static struct strbuf default_rla = STRBUF_INIT; static struct transport *gtransport; static struct transport *gsecondary; static struct refspec refmap = REFSPEC_INIT_FETCH; -static struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT; static struct string_list server_options = STRING_LIST_INIT_DUP; static struct string_list negotiation_tip = STRING_LIST_INIT_NODUP; @@ -289,13 +288,11 @@ static struct refname_hash_entry *refname_hash_add(struct hashmap *map, return ent; } -static int add_one_refname(const char *refname, const char *referent UNUSED, - const struct object_id *oid, - int flag UNUSED, void *cbdata) +static int add_one_refname(const struct reference *ref, void *cbdata) { struct hashmap *refname_map = cbdata; - (void) refname_hash_add(refname_map, refname, oid); + (void) refname_hash_add(refname_map, ref->name, ref->oid); return 0; } @@ -724,7 +721,7 @@ static void display_state_init(struct display_state *display_state, struct ref * display_state->url = xstrdup("foreign"); display_state->url_len = strlen(display_state->url); - for (i = display_state->url_len - 1; display_state->url[i] == '/' && 0 <= i; i--) + for (i = display_state->url_len - 1; 0 <= i && display_state->url[i] == '/'; i--) ; display_state->url_len = i + 1; if (4 < i && !strncmp(".git", display_state->url + i - 3, 4)) @@ -863,12 +860,87 @@ static void display_ref_update(struct display_state *display_state, char code, fputs(display_state->buf.buf, f); } +struct ref_update_display_info { + bool failed; + char success_code; + char fail_code; + char *summary; + char *fail_detail; + char *success_detail; + char *ref; + char *remote; + struct object_id old_oid; + struct object_id new_oid; +}; + +struct ref_update_display_info_array { + struct ref_update_display_info *info; + size_t alloc, nr; +}; + +static struct ref_update_display_info *ref_update_display_info_append( + struct ref_update_display_info_array *array, + char success_code, + char fail_code, + const char *summary, + const char *success_detail, + const char *fail_detail, + const char *ref, + const char *remote, + const struct object_id *old_oid, + const struct object_id *new_oid) +{ + struct ref_update_display_info *info; + + ALLOC_GROW(array->info, array->nr + 1, array->alloc); + info = &array->info[array->nr++]; + + info->failed = false; + info->success_code = success_code; + info->fail_code = fail_code; + info->summary = xstrdup(summary); + info->success_detail = xstrdup_or_null(success_detail); + info->fail_detail = xstrdup_or_null(fail_detail); + info->remote = xstrdup(remote); + info->ref = xstrdup(ref); + + oidcpy(&info->old_oid, old_oid); + oidcpy(&info->new_oid, new_oid); + + return info; +} + +static void ref_update_display_info_set_failed(struct ref_update_display_info *info) +{ + info->failed = true; +} + +static void ref_update_display_info_free(struct ref_update_display_info *info) +{ + free(info->summary); + free(info->success_detail); + free(info->fail_detail); + free(info->remote); + free(info->ref); +} + +static void ref_update_display_info_display(struct ref_update_display_info *info, + struct display_state *display_state, + int summary_width) +{ + display_ref_update(display_state, + info->failed ? info->fail_code : info->success_code, + info->summary, + info->failed ? info->fail_detail : info->success_detail, + info->remote, info->ref, &info->old_oid, + &info->new_oid, summary_width); +} + static int update_local_ref(struct ref *ref, struct ref_transaction *transaction, - struct display_state *display_state, const struct ref *remote_ref, - int summary_width, - const struct fetch_config *config) + const struct fetch_config *config, + struct ref_update_display_info_array *display_array) { struct commit *current = NULL, *updated; int fast_forward = 0; @@ -879,41 +951,56 @@ static int update_local_ref(struct ref *ref, if (oideq(&ref->old_oid, &ref->new_oid)) { if (verbosity > 0) - display_ref_update(display_state, '=', _("[up to date]"), NULL, - remote_ref->name, ref->name, - &ref->old_oid, &ref->new_oid, summary_width); + ref_update_display_info_append(display_array, '=', '=', + _("[up to date]"), NULL, + NULL, ref->name, + remote_ref->name, &ref->old_oid, + &ref->new_oid); return 0; } if (!update_head_ok && !is_null_oid(&ref->old_oid) && branch_checked_out(ref->name)) { + struct ref_update_display_info *info; /* * If this is the head, and it's not okay to update * the head, and the old value of the head isn't empty... */ - display_ref_update(display_state, '!', _("[rejected]"), - _("can't fetch into checked-out branch"), - remote_ref->name, ref->name, - &ref->old_oid, &ref->new_oid, summary_width); + info = ref_update_display_info_append(display_array, '!', '!', + _("[rejected]"), NULL, + _("can't fetch into checked-out branch"), + ref->name, remote_ref->name, + &ref->old_oid, &ref->new_oid); + ref_update_display_info_set_failed(info); return 1; } if (!is_null_oid(&ref->old_oid) && starts_with(ref->name, "refs/tags/")) { + struct ref_update_display_info *info; + if (force || ref->force) { int r; + r = s_update_ref("updating tag", ref, transaction, 0); - display_ref_update(display_state, r ? '!' : 't', _("[tag update]"), - r ? _("unable to update local ref") : NULL, - remote_ref->name, ref->name, - &ref->old_oid, &ref->new_oid, summary_width); + + info = ref_update_display_info_append(display_array, 't', '!', + _("[tag update]"), NULL, + _("unable to update local ref"), + ref->name, remote_ref->name, + &ref->old_oid, &ref->new_oid); + if (r) + ref_update_display_info_set_failed(info); + return r; } else { - display_ref_update(display_state, '!', _("[rejected]"), - _("would clobber existing tag"), - remote_ref->name, ref->name, - &ref->old_oid, &ref->new_oid, summary_width); + info = ref_update_display_info_append(display_array, '!', '!', + _("[rejected]"), NULL, + _("would clobber existing tag"), + ref->name, remote_ref->name, + &ref->old_oid, &ref->new_oid); + ref_update_display_info_set_failed(info); return 1; } } @@ -923,6 +1010,7 @@ static int update_local_ref(struct ref *ref, updated = lookup_commit_reference_gently(the_repository, &ref->new_oid, 1); if (!current || !updated) { + struct ref_update_display_info *info; const char *msg; const char *what; int r; @@ -943,10 +1031,15 @@ static int update_local_ref(struct ref *ref, } r = s_update_ref(msg, ref, transaction, 0); - display_ref_update(display_state, r ? '!' : '*', what, - r ? _("unable to update local ref") : NULL, - remote_ref->name, ref->name, - &ref->old_oid, &ref->new_oid, summary_width); + + info = ref_update_display_info_append(display_array, '*', '!', + what, NULL, + _("unable to update local ref"), + ref->name, remote_ref->name, + &ref->old_oid, &ref->new_oid); + if (r) + ref_update_display_info_set_failed(info); + return r; } @@ -962,6 +1055,7 @@ static int update_local_ref(struct ref *ref, } if (fast_forward) { + struct ref_update_display_info *info; struct strbuf quickref = STRBUF_INIT; int r; @@ -969,29 +1063,46 @@ static int update_local_ref(struct ref *ref, strbuf_addstr(&quickref, ".."); strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV); r = s_update_ref("fast-forward", ref, transaction, 1); - display_ref_update(display_state, r ? '!' : ' ', quickref.buf, - r ? _("unable to update local ref") : NULL, - remote_ref->name, ref->name, - &ref->old_oid, &ref->new_oid, summary_width); + + info = ref_update_display_info_append(display_array, ' ', '!', + quickref.buf, NULL, + _("unable to update local ref"), + ref->name, remote_ref->name, + &ref->old_oid, &ref->new_oid); + if (r) + ref_update_display_info_set_failed(info); + strbuf_release(&quickref); return r; } else if (force || ref->force) { + struct ref_update_display_info *info; struct strbuf quickref = STRBUF_INIT; int r; + strbuf_add_unique_abbrev(&quickref, ¤t->object.oid, DEFAULT_ABBREV); strbuf_addstr(&quickref, "..."); strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV); r = s_update_ref("forced-update", ref, transaction, 1); - display_ref_update(display_state, r ? '!' : '+', quickref.buf, - r ? _("unable to update local ref") : _("forced update"), - remote_ref->name, ref->name, - &ref->old_oid, &ref->new_oid, summary_width); + + info = ref_update_display_info_append(display_array, '+', '!', + quickref.buf, _("forced update"), + _("unable to update local ref"), + ref->name, remote_ref->name, + &ref->old_oid, &ref->new_oid); + + if (r) + ref_update_display_info_set_failed(info); + strbuf_release(&quickref); return r; } else { - display_ref_update(display_state, '!', _("[rejected]"), _("non-fast-forward"), - remote_ref->name, ref->name, - &ref->old_oid, &ref->new_oid, summary_width); + struct ref_update_display_info *info; + info = ref_update_display_info_append(display_array, '!', '!', + _("[rejected]"), NULL, + _("non-fast-forward"), + ref->name, remote_ref->name, + &ref->old_oid, &ref->new_oid); + ref_update_display_info_set_failed(info); return 1; } } @@ -1105,17 +1216,14 @@ static int store_updated_refs(struct display_state *display_state, int connectivity_checked, struct ref_transaction *transaction, struct ref *ref_map, struct fetch_head *fetch_head, - const struct fetch_config *config) + const struct fetch_config *config, + struct ref_update_display_info_array *display_array) { int rc = 0; struct strbuf note = STRBUF_INIT; const char *what, *kind; struct ref *rm; int want_status; - int summary_width = 0; - - if (verbosity >= 0) - summary_width = transport_summary_width(ref_map); if (!connectivity_checked) { struct check_connected_options opt = CHECK_CONNECTED_INIT; @@ -1220,8 +1328,8 @@ static int store_updated_refs(struct display_state *display_state, display_state->url_len); if (ref) { - rc |= update_local_ref(ref, transaction, display_state, - rm, summary_width, config); + rc |= update_local_ref(ref, transaction, rm, + config, display_array); free(ref); } else if (write_fetch_head || dry_run) { /* @@ -1229,12 +1337,12 @@ static int store_updated_refs(struct display_state *display_state, * would be written to FETCH_HEAD, if --dry-run * is set). */ - display_ref_update(display_state, '*', - *kind ? kind : "branch", NULL, - rm->name, - "FETCH_HEAD", - &rm->new_oid, &rm->old_oid, - summary_width); + + ref_update_display_info_append(display_array, '*', '*', + *kind ? kind : "branch", + NULL, NULL, "FETCH_HEAD", + rm->name, &rm->new_oid, + &rm->old_oid); } } } @@ -1302,7 +1410,8 @@ static int fetch_and_consume_refs(struct display_state *display_state, struct ref_transaction *transaction, struct ref *ref_map, struct fetch_head *fetch_head, - const struct fetch_config *config) + const struct fetch_config *config, + struct ref_update_display_info_array *display_array) { int connectivity_checked = 1; int ret; @@ -1324,7 +1433,8 @@ static int fetch_and_consume_refs(struct display_state *display_state, trace2_region_enter("fetch", "consume_refs", the_repository); ret = store_updated_refs(display_state, connectivity_checked, - transaction, ref_map, fetch_head, config); + transaction, ref_map, fetch_head, config, + display_array); trace2_region_leave("fetch", "consume_refs", the_repository); out: @@ -1416,14 +1526,11 @@ static void set_option(struct transport *transport, const char *name, const char } -static int add_oid(const char *refname UNUSED, - const char *referent UNUSED, - const struct object_id *oid, - int flags UNUSED, void *cb_data) +static int add_oid(const struct reference *ref, void *cb_data) { struct oid_array *oids = cb_data; - oid_array_append(oids, oid); + oid_array_append(oids, ref->oid); return 0; } @@ -1434,6 +1541,9 @@ static void add_negotiation_tips(struct git_transport_options *smart_options) for (i = 0; i < negotiation_tip.nr; i++) { const char *s = negotiation_tip.items[i].string; + struct refs_for_each_ref_options opts = { + .pattern = s, + }; int old_nr; if (!has_glob_specials(s)) { struct object_id oid; @@ -1445,8 +1555,8 @@ static void add_negotiation_tips(struct git_transport_options *smart_options) continue; } old_nr = oids->nr; - refs_for_each_glob_ref(get_main_ref_store(the_repository), - add_oid, s, oids); + refs_for_each_ref_ext(get_main_ref_store(the_repository), + add_oid, oids, &opts); if (old_nr == oids->nr) warning("ignoring --negotiation-tip=%s because it does not match any refs", s); @@ -1454,7 +1564,8 @@ static void add_negotiation_tips(struct git_transport_options *smart_options) smart_options->negotiation_tips = oids; } -static struct transport *prepare_transport(struct remote *remote, int deepen) +static struct transport *prepare_transport(struct remote *remote, int deepen, + struct list_objects_filter_options *filter_options) { struct transport *transport; @@ -1478,9 +1589,9 @@ static struct transport *prepare_transport(struct remote *remote, int deepen) set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes"); if (refetch) set_option(transport, TRANS_OPT_REFETCH, "yes"); - if (filter_options.choice) { + if (filter_options->choice) { const char *spec = - expand_list_objects_filter_spec(&filter_options); + expand_list_objects_filter_spec(filter_options); set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER, spec); set_option(transport, TRANS_OPT_FROM_PROMISOR, "1"); } @@ -1498,7 +1609,9 @@ static int backfill_tags(struct display_state *display_state, struct ref_transaction *transaction, struct ref *ref_map, struct fetch_head *fetch_head, - const struct fetch_config *config) + const struct fetch_config *config, + struct ref_update_display_info_array *display_array, + struct list_objects_filter_options *filter_options) { int retcode, cannot_reuse; @@ -1512,7 +1625,7 @@ static int backfill_tags(struct display_state *display_state, cannot_reuse = transport->cannot_reuse || deepen_since || deepen_not.nr; if (cannot_reuse) { - gsecondary = prepare_transport(transport->remote, 0); + gsecondary = prepare_transport(transport->remote, 0, filter_options); transport = gsecondary; } @@ -1520,7 +1633,7 @@ static int backfill_tags(struct display_state *display_state, transport_set_option(transport, TRANS_OPT_DEPTH, "0"); transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL); retcode = fetch_and_consume_refs(display_state, transport, transaction, ref_map, - fetch_head, config); + fetch_head, config, display_array); if (gsecondary) { transport_disconnect(gsecondary); @@ -1646,6 +1759,7 @@ struct ref_rejection_data { bool conflict_msg_shown; bool case_sensitive_msg_shown; const char *remote_name; + struct strmap *rejected_refs; }; static void ref_transaction_rejection_handler(const char *refname, @@ -1654,6 +1768,7 @@ static void ref_transaction_rejection_handler(const char *refname, const char *old_target UNUSED, const char *new_target UNUSED, enum ref_transaction_error err, + const char *details, void *cb_data) { struct ref_rejection_data *data = cb_data; @@ -1678,17 +1793,53 @@ static void ref_transaction_rejection_handler(const char *refname, "branches"), data->remote_name); data->conflict_msg_shown = true; } else { - const char *reason = ref_transaction_error_msg(err); - - error(_("fetching ref %s failed: %s"), refname, reason); + if (details) + error("%s", details); + else + error(_("fetching ref %s failed: %s"), + refname, ref_transaction_error_msg(err)); } + strmap_put(data->rejected_refs, refname, NULL); *data->retcode = 1; } +/* + * Commit the reference transaction. If it isn't an atomic transaction, handle + * rejected updates as part of using batched updates. + */ +static int commit_ref_transaction(struct ref_transaction **transaction, + bool is_atomic, const char *remote_name, + struct strmap *rejected_refs, + struct strbuf *err) +{ + int retcode = ref_transaction_commit(*transaction, err); + if (retcode) + goto out; + + if (!is_atomic) { + struct ref_rejection_data data = { + .conflict_msg_shown = 0, + .remote_name = remote_name, + .retcode = &retcode, + .rejected_refs = rejected_refs, + }; + + ref_transaction_for_each_rejected_update(*transaction, + ref_transaction_rejection_handler, + &data); + } + +out: + ref_transaction_free(*transaction); + *transaction = NULL; + return retcode; +} + static int do_fetch(struct transport *transport, struct refspec *rs, - const struct fetch_config *config) + const struct fetch_config *config, + struct list_objects_filter_options *filter_options) { struct ref_transaction *transaction = NULL; struct ref *ref_map = NULL; @@ -1701,6 +1852,9 @@ static int do_fetch(struct transport *transport, struct fetch_head fetch_head = { 0 }; struct strbuf err = STRBUF_INIT; int do_set_head = 0; + struct ref_update_display_info_array display_array = { 0 }; + struct strmap rejected_refs = STRMAP_INIT; + int summary_width = 0; if (tags == TAGS_DEFAULT) { if (transport->remote->fetch_tags == 2) @@ -1825,7 +1979,7 @@ static int do_fetch(struct transport *transport, } if (fetch_and_consume_refs(&display_state, transport, transaction, ref_map, - &fetch_head, config)) { + &fetch_head, config, &display_array)) { retcode = 1; goto cleanup; } @@ -1848,7 +2002,7 @@ static int do_fetch(struct transport *transport, * the transaction and don't commit anything. */ if (backfill_tags(&display_state, transport, transaction, tags_ref_map, - &fetch_head, config)) + &fetch_head, config, &display_array, filter_options)) retcode = 1; } @@ -1858,33 +2012,18 @@ static int do_fetch(struct transport *transport, if (retcode) goto cleanup; - retcode = ref_transaction_commit(transaction, &err); - if (retcode) { - /* - * Explicitly handle transaction cleanup to avoid - * aborting an already closed transaction. - */ - ref_transaction_free(transaction); - transaction = NULL; + if (verbosity >= 0) + summary_width = transport_summary_width(ref_map); + + retcode = commit_ref_transaction(&transaction, atomic_fetch, + transport->remote->name, + &rejected_refs, &err); + /* + * With '--atomic', bail out if the transaction fails. Without '--atomic', + * continue to fetch head and perform other post-fetch operations. + */ + if (retcode && atomic_fetch) goto cleanup; - } - - if (!atomic_fetch) { - struct ref_rejection_data data = { - .retcode = &retcode, - .conflict_msg_shown = 0, - .remote_name = transport->remote->name, - }; - - ref_transaction_for_each_rejected_update(transaction, - ref_transaction_rejection_handler, - &data); - if (retcode) { - ref_transaction_free(transaction); - transaction = NULL; - goto cleanup; - } - } commit_fetch_head(&fetch_head); @@ -1950,6 +2089,24 @@ static int do_fetch(struct transport *transport, } cleanup: + /* + * When using batched updates, we want to commit the non-rejected + * updates and also handle the rejections. + */ + if (retcode && !atomic_fetch && transaction) + commit_ref_transaction(&transaction, false, + transport->remote->name, + &rejected_refs, &err); + + for (size_t i = 0; i < display_array.nr; i++) { + struct ref_update_display_info *info = &display_array.info[i]; + + if (!info->failed && strmap_contains(&rejected_refs, info->ref)) + ref_update_display_info_set_failed(info); + ref_update_display_info_display(info, &display_state, summary_width); + ref_update_display_info_free(info); + } + if (retcode) { if (err.len) { error("%s", err.buf); @@ -1963,6 +2120,9 @@ static int do_fetch(struct transport *transport, if (transaction) ref_transaction_free(transaction); + + free(display_array.info); + strmap_clear(&rejected_refs, 0); display_state_release(&display_state); close_fetch_head(&fetch_head); strbuf_release(&err); @@ -2184,20 +2344,21 @@ static int fetch_multiple(struct string_list *list, int max_children, * Fetching from the promisor remote should use the given filter-spec * or inherit the default filter-spec from the config. */ -static inline void fetch_one_setup_partial(struct remote *remote) +static inline void fetch_one_setup_partial(struct remote *remote, + struct list_objects_filter_options *filter_options) { /* * Explicit --no-filter argument overrides everything, regardless * of any prior partial clones and fetches. */ - if (filter_options.no_filter) + if (filter_options->no_filter) return; /* * If no prior partial clone/fetch and the current fetch DID NOT * request a partial-fetch, do a normal fetch. */ - if (!repo_has_promisor_remote(the_repository) && !filter_options.choice) + if (!repo_has_promisor_remote(the_repository) && !filter_options->choice) return; /* @@ -2206,8 +2367,8 @@ static inline void fetch_one_setup_partial(struct remote *remote) * filter-spec as the default for subsequent fetches to this * remote if there is currently no default filter-spec. */ - if (filter_options.choice) { - partial_clone_register(remote->name, &filter_options); + if (filter_options->choice) { + partial_clone_register(remote->name, filter_options); return; } @@ -2216,14 +2377,15 @@ static inline void fetch_one_setup_partial(struct remote *remote) * explicitly given filter-spec or inherit the filter-spec from * the config. */ - if (!filter_options.choice) - partial_clone_get_default_filter_spec(&filter_options, remote->name); + if (!filter_options->choice) + partial_clone_get_default_filter_spec(filter_options, remote->name); return; } static int fetch_one(struct remote *remote, int argc, const char **argv, int prune_tags_ok, int use_stdin_refspecs, - const struct fetch_config *config) + const struct fetch_config *config, + struct list_objects_filter_options *filter_options) { struct refspec rs = REFSPEC_INIT_FETCH; int i; @@ -2235,7 +2397,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv, die(_("no remote repository specified; please specify either a URL or a\n" "remote name from which new revisions should be fetched")); - gtransport = prepare_transport(remote, 1); + gtransport = prepare_transport(remote, 1, filter_options); if (prune < 0) { /* no command line request */ @@ -2290,7 +2452,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv, sigchain_push_common(unlock_pack_on_signal); atexit(unlock_pack_atexit); sigchain_push(SIGPIPE, SIG_IGN); - exit_code = do_fetch(gtransport, &rs, config); + exit_code = do_fetch(gtransport, &rs, config, filter_options); sigchain_pop(SIGPIPE); refspec_clear(&rs); transport_disconnect(gtransport); @@ -2315,6 +2477,7 @@ int cmd_fetch(int argc, const char *submodule_prefix = ""; const char *bundle_uri; struct string_list list = STRING_LIST_INIT_DUP; + struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT; struct remote *remote = NULL; int all = -1, multiple = 0; int result = 0; @@ -2420,6 +2583,8 @@ int cmd_fetch(int argc, OPT_END() }; + filter_options.allow_auto_filter = 1; + packet_trace_identity("fetch"); /* Record the command line for the reflog */ @@ -2580,7 +2745,7 @@ int cmd_fetch(int argc, trace2_region_enter("fetch", "negotiate-only", the_repository); if (!remote) die(_("must supply remote when using --negotiate-only")); - gtransport = prepare_transport(remote, 1); + gtransport = prepare_transport(remote, 1, &filter_options); if (gtransport->smart_options) { gtransport->smart_options->acked_commits = &acked_commits; } else { @@ -2602,12 +2767,12 @@ int cmd_fetch(int argc, } else if (remote) { if (filter_options.choice || repo_has_promisor_remote(the_repository)) { trace2_region_enter("fetch", "setup-partial", the_repository); - fetch_one_setup_partial(remote); + fetch_one_setup_partial(remote, &filter_options); trace2_region_leave("fetch", "setup-partial", the_repository); } trace2_region_enter("fetch", "fetch-one", the_repository); result = fetch_one(remote, argc, argv, prune_tags_ok, stdin_refspecs, - &config); + &config, &filter_options); trace2_region_leave("fetch", "fetch-one", the_repository); } else { int max_children = max_jobs; @@ -2708,10 +2873,11 @@ int cmd_fetch(int argc, if (opt_val != 0) git_config_push_parameter("maintenance.incremental-repack.auto=-1"); } - run_auto_maintenance(verbosity < 0); + run_auto_maintenance(the_repository, verbosity < 0); } cleanup: string_list_clear(&list, 0); + list_objects_filter_release(&filter_options); return result; }
diff --git a/builtin/for-each-repo.c b/builtin/for-each-repo.c index 325a792..927d3d9 100644 --- a/builtin/for-each-repo.c +++ b/builtin/for-each-repo.c
@@ -2,6 +2,7 @@ #include "builtin.h" #include "config.h" +#include "environment.h" #include "gettext.h" #include "parse-options.h" #include "path.h" @@ -13,17 +14,16 @@ static const char * const for_each_repo_usage[] = { NULL }; -static int run_command_on_repo(const char *path, int argc, const char ** argv) +static int run_command_on_repo(const char *path, const char **argv) { - int i; struct child_process child = CHILD_PROCESS_INIT; char *abspath = interpolate_path(path, 0); + sanitize_repo_env(&child.env); + child.git_cmd = 1; strvec_pushl(&child.args, "-C", abspath, NULL); - - for (i = 0; i < argc; i++) - strvec_push(&child.args, argv[i]); + strvec_pushv(&child.args, argv); free(abspath); @@ -63,7 +63,7 @@ int cmd_for_each_repo(int argc, return 0; for (size_t i = 0; i < values->nr; i++) { - int ret = run_command_on_repo(values->items[i].string, argc, argv); + int ret = run_command_on_repo(values->items[i].string, argv); if (ret) { if (!keep_going) return ret;
diff --git a/builtin/fsck.c b/builtin/fsck.c index b1a650c..9bab32e 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c
@@ -13,11 +13,11 @@ #include "fsck.h" #include "parse-options.h" #include "progress.h" -#include "streaming.h" #include "packfile.h" #include "object-file.h" #include "object-name.h" #include "odb.h" +#include "odb/streaming.h" #include "path.h" #include "read-cache-ll.h" #include "replace-object.h" @@ -51,6 +51,7 @@ static int show_progress = -1; static int show_dangling = 1; static int name_objects; static int check_references = 1; +static timestamp_t now; #define ERROR_OBJECT 01 #define ERROR_REACHABLE 02 #define ERROR_PACK 04 @@ -161,7 +162,8 @@ static int mark_object(struct object *obj, enum object_type type, return 0; if (!(obj->flags & HAS_OBJ)) { - if (parent && !odb_has_object(the_repository->objects, &obj->oid, 1)) { + if (parent && !odb_has_object(the_repository->objects, &obj->oid, + HAS_OBJECT_RECHECK_PACKED)) { printf_ln(_("broken link from %7s %s\n" " to %7s %s"), printable_type(&parent->oid, parent->type), @@ -218,15 +220,17 @@ static int mark_used(struct object *obj, enum object_type type UNUSED, return 0; } -static void mark_unreachable_referents(const struct object_id *oid) +static int mark_unreachable_referents(const struct object_id *oid, + struct object_info *oi UNUSED, + void *data UNUSED) { struct fsck_options options = FSCK_OPTIONS_DEFAULT; struct object *obj = lookup_object(the_repository, oid); if (!obj || !(obj->flags & HAS_OBJ)) - return; /* not part of our original set */ + return 0; /* not part of our original set */ if (obj->flags & REACHABLE) - return; /* reachable objects already traversed */ + return 0; /* reachable objects already traversed */ /* * Avoid passing OBJ_NONE to fsck_walk, which will parse the object @@ -243,22 +247,7 @@ static void mark_unreachable_referents(const struct object_id *oid) fsck_walk(obj, NULL, &options); if (obj->type == OBJ_TREE) free_tree_buffer((struct tree *)obj); -} -static int mark_loose_unreachable_referents(const struct object_id *oid, - const char *path UNUSED, - void *data UNUSED) -{ - mark_unreachable_referents(oid); - return 0; -} - -static int mark_packed_unreachable_referents(const struct object_id *oid, - struct packed_git *pack UNUSED, - uint32_t pos UNUSED, - void *data UNUSED) -{ - mark_unreachable_referents(oid); return 0; } @@ -340,7 +329,8 @@ static void check_unreachable_object(struct object *obj) } f = xfopen(filename, "w"); if (obj->type == OBJ_BLOB) { - if (stream_blob_to_fd(fileno(f), &obj->oid, NULL, 1)) + if (odb_stream_blob_to_fd(the_repository->objects, fileno(f), + &obj->oid, NULL, 1)) die_errno(_("could not write '%s'"), filename); } else fprintf(f, "%s\n", describe_object(&obj->oid)); @@ -393,12 +383,8 @@ static void check_connectivity(void) * and ignore any that weren't present in our earlier * traversal. */ - for_each_loose_object(the_repository->objects, - mark_loose_unreachable_referents, NULL, 0); - for_each_packed_object(the_repository, - mark_packed_unreachable_referents, - NULL, - 0); + odb_for_each_object(the_repository->objects, NULL, + mark_unreachable_referents, NULL, 0); } /* Look up all the requirements, warn about missing objects.. */ @@ -509,6 +495,9 @@ static int fsck_handle_reflog_ent(const char *refname, timestamp_t timestamp, int tz UNUSED, const char *message UNUSED, void *cb_data UNUSED) { + if (now && timestamp > now) + return 0; + if (verbose) fprintf_ln(stderr, _("Checking reflog %s->%s"), oid_to_hex(ooid), oid_to_hex(noid)); @@ -530,14 +519,27 @@ static int fsck_handle_reflog(const char *logname, void *cb_data) return 0; } -static int fsck_handle_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid, - int flag UNUSED, void *cb_data UNUSED) +struct ref_snapshot { + char *refname; + struct object_id oid; + /* TODO: Maybe supplement with latest reflog entry info too? */ +}; + +struct snapshot { + size_t nr; + size_t alloc; + struct ref_snapshot *ref; + /* TODO: Consider also snapshotting the index of each worktree. */ +}; + +static int snapshot_ref(const struct reference *ref, void *cb_data) { + struct snapshot *snap = cb_data; struct object *obj; - obj = parse_object(the_repository, oid); + obj = parse_object(the_repository, ref->oid); if (!obj) { - if (is_promisor_object(the_repository, oid)) { + if (is_promisor_object(the_repository, ref->oid)) { /* * Increment default_refs anyway, because this is a * valid ref. @@ -546,54 +548,134 @@ static int fsck_handle_ref(const char *refname, const char *referent UNUSED, con return 0; } error(_("%s: invalid sha1 pointer %s"), - refname, oid_to_hex(oid)); + ref->name, oid_to_hex(ref->oid)); errors_found |= ERROR_REACHABLE; /* We'll continue with the rest despite the error.. */ return 0; } - if (obj->type != OBJ_COMMIT && is_branch(refname)) { - error(_("%s: not a commit"), refname); + if (obj->type != OBJ_COMMIT && is_branch(ref->name)) { + error(_("%s: not a commit"), ref->name); errors_found |= ERROR_REFS; } default_refs++; + + ALLOC_GROW(snap->ref, snap->nr + 1, snap->alloc); + snap->ref[snap->nr].refname = xstrdup(ref->name); + oidcpy(&snap->ref[snap->nr].oid, ref->oid); + snap->nr++; + + return 0; +} + +static int fsck_handle_ref(const struct reference *ref, void *cb_data UNUSED) +{ + struct object *obj; + + obj = parse_object(the_repository, ref->oid); obj->flags |= USED; fsck_put_object_name(&fsck_walk_options, - oid, "%s", refname); + ref->oid, "%s", ref->name); mark_object_reachable(obj); return 0; } -static int fsck_head_link(const char *head_ref_name, - const char **head_points_at, - struct object_id *head_oid); - -static void get_default_heads(void) +static void snapshot_refs(struct snapshot *snap, int argc, const char **argv) { + struct refs_for_each_ref_options opts = { + .flags = REFS_FOR_EACH_INCLUDE_BROKEN, + }; struct worktree **worktrees, **p; const char *head_points_at; struct object_id head_oid; - refs_for_each_rawref(get_main_ref_store(the_repository), - fsck_handle_ref, NULL); + for (int i = 0; i < argc; i++) { + const char *arg = argv[i]; + struct object_id oid; + if (!repo_get_oid(the_repository, arg, &oid)) { + struct reference ref = { + .name = arg, + .oid = &oid, + }; + + snapshot_ref(&ref, snap); + continue; + } + error(_("invalid parameter: expected sha1, got '%s'"), arg); + errors_found |= ERROR_OBJECT; + } + + if (argc) { + include_reflogs = 0; + return; + } + + refs_for_each_ref_ext(get_main_ref_store(the_repository), + snapshot_ref, snap, &opts); worktrees = get_worktrees(); for (p = worktrees; *p; p++) { struct worktree *wt = *p; - struct strbuf ref = STRBUF_INIT; + struct strbuf refname = STRBUF_INIT; - strbuf_worktree_ref(wt, &ref, "HEAD"); - fsck_head_link(ref.buf, &head_points_at, &head_oid); - if (head_points_at && !is_null_oid(&head_oid)) - fsck_handle_ref(ref.buf, NULL, &head_oid, 0, NULL); - strbuf_release(&ref); + strbuf_worktree_ref(wt, &refname, "HEAD"); - if (include_reflogs) - refs_for_each_reflog(get_worktree_ref_store(wt), - fsck_handle_reflog, wt); + head_points_at = refs_resolve_ref_unsafe(get_main_ref_store(the_repository), + refname.buf, 0, &head_oid, NULL); + + if (head_points_at && !is_null_oid(&head_oid)) { + struct reference ref = { + .name = refname.buf, + .oid = &head_oid, + }; + + snapshot_ref(&ref, snap); + } + strbuf_release(&refname); + + /* + * TODO: Could use refs_for_each_reflog(...) to find + * latest entry instead of using a global 'now' for that + * purpose. + */ } free_worktrees(worktrees); + /* Ignore reflogs newer than now */ + now = time(NULL); +} + + +static void free_snapshot_refs(struct snapshot *snap) +{ + for (size_t i = 0; i < snap->nr; i++) + free(snap->ref[i].refname); + free(snap->ref); +} + +static void process_refs(struct snapshot *snap) +{ + struct worktree **worktrees, **p; + + for (size_t i = 0; i < snap->nr; i++) { + struct reference ref = { + .name = snap->ref[i].refname, + .oid = &snap->ref[i].oid, + }; + fsck_handle_ref(&ref, NULL); + } + + if (include_reflogs) { + worktrees = get_worktrees(); + for (p = worktrees; *p; p++) { + struct worktree *wt = *p; + + refs_for_each_reflog(get_worktree_ref_store(wt), + fsck_handle_reflog, wt); + } + free_worktrees(worktrees); + } + /* * Not having any default heads isn't really fatal, but * it does mean that "--unreachable" no longer makes any @@ -707,43 +789,6 @@ static void fsck_source(struct odb_source *source) stop_progress(&progress); } -static int fsck_head_link(const char *head_ref_name, - const char **head_points_at, - struct object_id *head_oid) -{ - int null_is_error = 0; - - if (verbose) - fprintf_ln(stderr, _("Checking %s link"), head_ref_name); - - *head_points_at = refs_resolve_ref_unsafe(get_main_ref_store(the_repository), - head_ref_name, 0, head_oid, - NULL); - if (!*head_points_at) { - errors_found |= ERROR_REFS; - return error(_("invalid %s"), head_ref_name); - } - if (!strcmp(*head_points_at, head_ref_name)) - /* detached HEAD */ - null_is_error = 1; - else if (!starts_with(*head_points_at, "refs/heads/")) { - errors_found |= ERROR_REFS; - return error(_("%s points to something strange (%s)"), - head_ref_name, *head_points_at); - } - if (is_null_oid(head_oid)) { - if (null_is_error) { - errors_found |= ERROR_REFS; - return error(_("%s: detached HEAD points at nothing"), - head_ref_name); - } - fprintf_ln(stderr, - _("notice: %s points to an unborn branch (%s)"), - head_ref_name, *head_points_at + 11); - } - return 0; -} - static int fsck_cache_tree(struct cache_tree *it, const char *index_path) { int i; @@ -842,26 +887,12 @@ static void fsck_index(struct index_state *istate, const char *index_path, fsck_resolve_undo(istate, index_path); } -static void mark_object_for_connectivity(const struct object_id *oid) +static int mark_object_for_connectivity(const struct object_id *oid, + struct object_info *oi UNUSED, + void *cb_data UNUSED) { struct object *obj = lookup_unknown_object(the_repository, oid); obj->flags |= HAS_OBJ; -} - -static int mark_loose_for_connectivity(const struct object_id *oid, - const char *path UNUSED, - void *data UNUSED) -{ - mark_object_for_connectivity(oid); - return 0; -} - -static int mark_packed_for_connectivity(const struct object_id *oid, - struct packed_git *pack UNUSED, - uint32_t pos UNUSED, - void *data UNUSED) -{ - mark_object_for_connectivity(oid); return 0; } @@ -957,8 +988,12 @@ int cmd_fsck(int argc, const char *prefix, struct repository *repo UNUSED) { - int i; struct odb_source *source; + struct snapshot snap = { + .nr = 0, + .alloc = 0, + .ref = NULL + }; /* fsck knows how to handle missing promisor objects */ fetch_if_missing = 0; @@ -994,11 +1029,20 @@ int cmd_fsck(int argc, if (check_references) fsck_refs(the_repository); + /* + * Take a snapshot of the refs before walking objects to avoid looking + * at a set of refs that may be changed by the user while we are walking + * objects. We can still walk over new objects that are added during the + * execution of fsck but won't miss any objects that were reachable. + */ + snapshot_refs(&snap, argc, argv); + + /* Ensure we get a "fresh" view of the odb */ + odb_reprepare(the_repository->objects); + if (connectivity_only) { - for_each_loose_object(the_repository->objects, - mark_loose_for_connectivity, NULL, 0); - for_each_packed_object(the_repository, - mark_packed_for_connectivity, NULL, 0); + odb_for_each_object(the_repository->objects, NULL, + mark_object_for_connectivity, NULL, 0); } else { odb_prepare_alternates(the_repository->objects); for (source = the_repository->objects->sources; source; source = source->next) @@ -1035,42 +1079,18 @@ int cmd_fsck(int argc, errors_found |= ERROR_OBJECT; } - for (i = 0; i < argc; i++) { - const char *arg = argv[i]; - struct object_id oid; - if (!repo_get_oid(the_repository, arg, &oid)) { - struct object *obj = lookup_object(the_repository, - &oid); + /* Process the snapshotted refs and the reflogs. */ + process_refs(&snap); - if (!obj || !(obj->flags & HAS_OBJ)) { - if (is_promisor_object(the_repository, &oid)) - continue; - error(_("%s: object missing"), oid_to_hex(&oid)); - errors_found |= ERROR_OBJECT; - continue; - } - - obj->flags |= USED; - fsck_put_object_name(&fsck_walk_options, &oid, - "%s", arg); - mark_object_reachable(obj); - continue; - } - error(_("invalid parameter: expected sha1, got '%s'"), arg); - errors_found |= ERROR_OBJECT; - } - - /* - * If we've not been given any explicit head information, do the - * default ones from .git/refs. We also consider the index file - * in this case (ie this implies --cache). - */ - if (!argc) { - get_default_heads(); + /* If not given any explicit objects, process index files too. */ + if (!argc) keep_cache_objects = 1; - } - if (keep_cache_objects) { + /* + * TODO: Consider first walking these indexes in snapshot_refs, + * to snapshot where the index entries used to point, and then + * check those snapshotted locations here. + */ struct worktree **worktrees, **p; verify_index_checksum = 1; @@ -1088,7 +1108,7 @@ int cmd_fsck(int argc, * and may get overwritten by other calls * while we're examining the index. */ - path = xstrdup(worktree_git_path(the_repository, wt, "index")); + path = xstrdup(worktree_git_path(wt, "index")); wt_gitdir = get_worktree_git_dir(wt); read_index_from(&istate, path, wt_gitdir); @@ -1143,5 +1163,6 @@ int cmd_fsck(int argc, } } + free_snapshot_refs(&snap); return errors_found; }
diff --git a/builtin/gc.c b/builtin/gc.c index d212cbb..3a71e31 100644 --- a/builtin/gc.c +++ b/builtin/gc.c
@@ -36,6 +36,7 @@ #include "reflog.h" #include "repack.h" #include "rerere.h" +#include "revision.h" #include "blob.h" #include "tree.h" #include "promisor-remote.h" @@ -286,12 +287,26 @@ static void maintenance_run_opts_release(struct maintenance_run_opts *opts) static int pack_refs_condition(UNUSED struct gc_config *cfg) { - /* - * The auto-repacking logic for refs is handled by the ref backends and - * exposed via `git pack-refs --auto`. We thus always return truish - * here and let the backend decide for us. - */ - return 1; + struct string_list included_refs = STRING_LIST_INIT_NODUP; + struct ref_exclusions excludes = REF_EXCLUSIONS_INIT; + struct refs_optimize_opts optimize_opts = { + .exclusions = &excludes, + .includes = &included_refs, + .flags = REFS_OPTIMIZE_PRUNE | REFS_OPTIMIZE_AUTO, + }; + bool required; + + /* Check for all refs, similar to 'git refs optimize --all'. */ + string_list_append(optimize_opts.includes, "*"); + + if (refs_optimize_required(get_main_ref_store(the_repository), + &optimize_opts, &required)) + return 0; + + clear_ref_exclusions(&excludes); + string_list_clear(&included_refs, 0); + + return required; } static int maintenance_task_pack_refs(struct maintenance_run_opts *opts, @@ -452,37 +467,19 @@ static int rerere_gc_condition(struct gc_config *cfg UNUSED) static int too_many_loose_objects(int limit) { /* - * Quickly check if a "gc" is needed, by estimating how - * many loose objects there are. Because SHA-1 is evenly - * distributed, we can check only one and get a reasonable - * estimate. + * This is weird, but stems from legacy behaviour: the GC auto + * threshold was always essentially interpreted as if it was rounded up + * to the next multiple 256 of, so we retain this behaviour for now. */ - DIR *dir; - struct dirent *ent; - int auto_threshold; - int num_loose = 0; - int needed = 0; - const unsigned hexsz_loose = the_hash_algo->hexsz - 2; - char *path; + int auto_threshold = DIV_ROUND_UP(limit, 256) * 256; + unsigned long loose_count; - path = repo_git_path(the_repository, "objects/17"); - dir = opendir(path); - free(path); - if (!dir) + if (odb_source_loose_count_objects(the_repository->objects->sources, + ODB_COUNT_OBJECTS_APPROXIMATE, + &loose_count) < 0) return 0; - auto_threshold = DIV_ROUND_UP(limit, 256); - while ((ent = readdir(dir)) != NULL) { - if (strspn(ent->d_name, "0123456789abcdef") != hexsz_loose || - ent->d_name[hexsz_loose] != '\0') - continue; - if (++num_loose > auto_threshold) { - needed = 1; - break; - } - } - closedir(dir); - return needed; + return loose_count > auto_threshold; } static struct packed_git *find_base_packs(struct string_list *packs, @@ -577,9 +574,13 @@ static uint64_t total_ram(void) static uint64_t estimate_repack_memory(struct gc_config *cfg, struct packed_git *pack) { - unsigned long nr_objects = repo_approximate_object_count(the_repository); + unsigned long nr_objects; size_t os_cache, heap; + if (odb_count_objects(the_repository->objects, + ODB_COUNT_OBJECTS_APPROXIMATE, &nr_objects) < 0) + return 0; + if (!pack || !nr_objects) return 0; @@ -1015,7 +1016,7 @@ int cmd_gc(int argc, struct child_process repack_cmd = CHILD_PROCESS_INIT; repack_cmd.git_cmd = 1; - repack_cmd.close_object_store = 1; + repack_cmd.odb_to_close = the_repository->objects; strvec_pushv(&repack_cmd.args, repack_args.v); if (run_command(&repack_cmd)) die(FAILED_RUN, repack_args.v[0]); @@ -1048,7 +1049,7 @@ int cmd_gc(int argc, report_garbage = report_pack_garbage; odb_reprepare(the_repository->objects); if (pack_garbage.nr > 0) { - close_object_store(the_repository->objects); + odb_close(the_repository->objects); clean_pack_garbage(); } @@ -1095,34 +1096,30 @@ static int maintenance_opt_schedule(const struct option *opt, const char *arg, return 0; } -/* Remember to update object flag allocation in object.h */ -#define SEEN (1u<<0) - struct cg_auto_data { int num_not_in_graph; int limit; }; -static int dfs_on_ref(const char *refname UNUSED, - const char *referent UNUSED, - const struct object_id *oid, - int flags UNUSED, - void *cb_data) +static int dfs_on_ref(const struct reference *ref, void *cb_data) { struct cg_auto_data *data = (struct cg_auto_data *)cb_data; int result = 0; + const struct object_id *maybe_peeled = ref->oid; struct object_id peeled; struct commit_list *stack = NULL; struct commit *commit; - if (!peel_iterated_oid(the_repository, oid, &peeled)) - oid = &peeled; - if (odb_read_object_info(the_repository->objects, oid, NULL) != OBJ_COMMIT) + if (!reference_get_peeled_oid(the_repository, ref, &peeled)) + maybe_peeled = &peeled; + if (odb_read_object_info(the_repository->objects, maybe_peeled, NULL) != OBJ_COMMIT) return 0; - commit = lookup_commit(the_repository, oid); - if (!commit) + commit = lookup_commit(the_repository, maybe_peeled); + if (!commit || commit->object.flags & SEEN) return 0; + commit->object.flags |= SEEN; + if (repo_parse_commit(the_repository, commit) || commit_graph_position(commit) != COMMIT_NOT_FROM_GRAPH) return 0; @@ -1132,7 +1129,7 @@ static int dfs_on_ref(const char *refname UNUSED, if (data->num_not_in_graph >= data->limit) return 1; - commit_list_append(commit, &stack); + commit_list_insert(commit, &stack); while (!result && stack) { struct commit_list *parent; @@ -1153,11 +1150,11 @@ static int dfs_on_ref(const char *refname UNUSED, break; } - commit_list_append(parent->item, &stack); + commit_list_insert(parent->item, &stack); } } - free_commit_list(stack); + commit_list_free(stack); return result; } @@ -1188,7 +1185,8 @@ static int run_write_commit_graph(struct maintenance_run_opts *opts) { struct child_process child = CHILD_PROCESS_INIT; - child.git_cmd = child.close_object_store = 1; + child.git_cmd = 1; + child.odb_to_close = the_repository->objects; strvec_pushl(&child.args, "commit-graph", "write", "--split", "--reachable", NULL); @@ -1257,7 +1255,8 @@ static int maintenance_task_gc_background(struct maintenance_run_opts *opts, { struct child_process child = CHILD_PROCESS_INIT; - child.git_cmd = child.close_object_store = 1; + child.git_cmd = 1; + child.odb_to_close = the_repository->objects; strvec_push(&child.args, "gc"); if (opts->auto_flag) @@ -1473,7 +1472,8 @@ static int multi_pack_index_expire(struct maintenance_run_opts *opts) { struct child_process child = CHILD_PROCESS_INIT; - child.git_cmd = child.close_object_store = 1; + child.git_cmd = 1; + child.odb_to_close = the_repository->objects; strvec_pushl(&child.args, "multi-pack-index", "expire", NULL); if (opts->quiet) @@ -1531,7 +1531,8 @@ static int multi_pack_index_repack(struct maintenance_run_opts *opts) { struct child_process child = CHILD_PROCESS_INIT; - child.git_cmd = child.close_object_store = 1; + child.git_cmd = 1; + child.odb_to_close = the_repository->objects; strvec_pushl(&child.args, "multi-pack-index", "repack", NULL); if (opts->quiet) @@ -1969,7 +1970,7 @@ static void initialize_task_config(struct maintenance_run_opts *opts, strategy = none_strategy; type = MAINTENANCE_TYPE_SCHEDULED; } else { - strategy = gc_strategy; + strategy = geometric_strategy; type = MAINTENANCE_TYPE_MANUAL; } @@ -3447,7 +3448,67 @@ static int maintenance_stop(int argc, const char **argv, const char *prefix, return update_background_schedule(NULL, 0); } -static const char * const builtin_maintenance_usage[] = { +static const char *const builtin_maintenance_is_needed_usage[] = { + "git maintenance is-needed [--task=<task>] [--schedule]", + NULL +}; + +static int maintenance_is_needed(int argc, const char **argv, const char *prefix, + struct repository *repo UNUSED) +{ + struct maintenance_run_opts opts = MAINTENANCE_RUN_OPTS_INIT; + struct string_list selected_tasks = STRING_LIST_INIT_DUP; + struct gc_config cfg = GC_CONFIG_INIT; + struct option options[] = { + OPT_BOOL(0, "auto", &opts.auto_flag, + N_("run tasks based on the state of the repository")), + OPT_CALLBACK_F(0, "task", &selected_tasks, N_("task"), + N_("check a specific task"), + PARSE_OPT_NONEG, task_option_parse), + OPT_END() + }; + bool is_needed = false; + + argc = parse_options(argc, argv, prefix, options, + builtin_maintenance_is_needed_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + if (argc) + usage_with_options(builtin_maintenance_is_needed_usage, options); + + gc_config(&cfg); + initialize_task_config(&opts, &selected_tasks); + + if (opts.auto_flag) { + for (size_t i = 0; i < opts.tasks_nr; i++) { + if (tasks[opts.tasks[i]].auto_condition && + tasks[opts.tasks[i]].auto_condition(&cfg)) { + is_needed = true; + break; + } + } + } else { + /* + * When not using --auto we always require maintenance right now. + * + * TODO: this certainly is too eager, as some maintenance tasks may + * decide to not do anything because the data structures are already + * fully optimized. We may eventually want to extend the auto + * condition to also cover non-auto runs so that we can detect such + * cases. + */ + is_needed = true; + } + + string_list_clear(&selected_tasks, 0); + maintenance_run_opts_release(&opts); + gc_config_release(&cfg); + + if (is_needed) + return 0; + return 1; +} + +static const char *const builtin_maintenance_usage[] = { N_("git maintenance <subcommand> [<options>]"), NULL, }; @@ -3464,6 +3525,7 @@ int cmd_maintenance(int argc, OPT_SUBCOMMAND("stop", &fn, maintenance_stop), OPT_SUBCOMMAND("register", &fn, maintenance_register), OPT_SUBCOMMAND("unregister", &fn, maintenance_unregister), + OPT_SUBCOMMAND("is-needed", &fn, maintenance_is_needed), OPT_END(), };
diff --git a/builtin/grep.c b/builtin/grep.c index 53cccf2..e33285e 100644 --- a/builtin/grep.c +++ b/builtin/grep.c
@@ -482,7 +482,7 @@ static int grep_submodule(struct grep_opt *opt, * "forget" the sparse-index feature switch. As a result, the index * of these submodules are expanded unexpectedly. * - * 2. "core_apply_sparse_checkout" + * 2. "config_values_private_.apply_sparse_checkout" * When running `grep` in the superproject, this setting is * populated using the superproject's configs. However, once * initialized, this config is globally accessible and is read by @@ -1213,8 +1213,16 @@ int cmd_grep(int argc, */ if (recurse_submodules) repo_read_gitmodules(the_repository, 1); - if (startup_info->have_repository) - packfile_store_prepare(the_repository->objects->packfiles); + + 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); + } + } start_threads(&opt); } else {
diff --git a/builtin/help.c b/builtin/help.c index c09cbc8..c0aece4 100644 --- a/builtin/help.c +++ b/builtin/help.c
@@ -54,6 +54,7 @@ static enum help_action { HELP_ACTION_DEVELOPER_INTERFACES, HELP_ACTION_CONFIG_FOR_COMPLETION, HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION, + HELP_ACTION_ALIASES_FOR_COMPLETION, } cmd_mode; static char *html_path; @@ -90,6 +91,8 @@ static struct option builtin_help_options[] = { HELP_ACTION_CONFIG_FOR_COMPLETION, PARSE_OPT_HIDDEN), OPT_CMDMODE_F(0, "config-sections-for-completion", &cmd_mode, "", HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION, PARSE_OPT_HIDDEN), + OPT_CMDMODE_F(0, "aliases-for-completion", &cmd_mode, "", + HELP_ACTION_ALIASES_FOR_COMPLETION, PARSE_OPT_HIDDEN), OPT_END(), }; @@ -111,6 +114,49 @@ struct slot_expansion { int found; }; +static void set_config_vars(struct string_list *keys_uniq, struct string_list_item *var) +{ + struct strbuf sb = STRBUF_INIT; + const char *str = var->string; + const char *wildcard = strchr(str, '*'); + const char *tag = strchr(str, '<'); + const char *cut; + + if (wildcard && tag) + cut = wildcard < tag ? wildcard : tag; + else if (wildcard) + cut = wildcard; + else if (tag) + cut = tag; + else { + string_list_append(keys_uniq, str); + return; + } + + strbuf_add(&sb, str, cut - str); + string_list_append(keys_uniq, sb.buf); + strbuf_release(&sb); +} + +static void set_config_sections(struct string_list *keys_uniq, struct string_list_item *var) +{ + struct strbuf sb = STRBUF_INIT; + const char *str = var->string; + const char *dot = strchr(str, '.'); + const char *cut; + + if (dot) + cut = dot; + else { + set_config_vars(keys_uniq, var); + return; + } + + strbuf_add(&sb, str, cut - str); + string_list_append(keys_uniq, sb.buf); + strbuf_release(&sb); +} + static void list_config_help(enum show_config_type type) { struct slot_expansion slot_expansions[] = { @@ -131,13 +177,12 @@ static void list_config_help(enum show_config_type type) struct string_list keys = STRING_LIST_INIT_DUP; struct string_list keys_uniq = STRING_LIST_INIT_DUP; struct string_list_item *item; + struct strbuf sb = STRBUF_INIT; for (p = config_name_list; *p; p++) { const char *var = *p; - struct strbuf sb = STRBUF_INIT; for (e = slot_expansions; e->prefix; e++) { - strbuf_reset(&sb); strbuf_addf(&sb, "%s.%s", e->prefix, e->placeholder); if (!strcasecmp(var, sb.buf)) { @@ -146,60 +191,39 @@ static void list_config_help(enum show_config_type type) break; } } - strbuf_release(&sb); + if (!e->prefix) string_list_append(&keys, var); } + strbuf_release(&sb); + for (e = slot_expansions; e->prefix; e++) if (!e->found) BUG("slot_expansion %s.%s is not used", e->prefix, e->placeholder); - string_list_sort(&keys); for (size_t i = 0; i < keys.nr; i++) { - const char *var = keys.items[i].string; - const char *wildcard, *tag, *cut; - const char *dot = NULL; - struct strbuf sb = STRBUF_INIT; - switch (type) { case SHOW_CONFIG_HUMAN: - puts(var); - continue; + string_list_append(&keys_uniq, keys.items[i].string); + break; case SHOW_CONFIG_SECTIONS: - dot = strchr(var, '.'); + set_config_sections(&keys_uniq, &keys.items[i]); break; case SHOW_CONFIG_VARS: + set_config_vars(&keys_uniq, &keys.items[i]); break; + default: + BUG("%d: unexpected type", type); } - wildcard = strchr(var, '*'); - tag = strchr(var, '<'); - - if (!dot && !wildcard && !tag) { - string_list_append(&keys_uniq, var); - continue; - } - - if (dot) - cut = dot; - else if (wildcard && !tag) - cut = wildcard; - else if (!wildcard && tag) - cut = tag; - else - cut = wildcard < tag ? wildcard : tag; - - strbuf_add(&sb, var, cut - var); - string_list_append(&keys_uniq, sb.buf); - strbuf_release(&sb); - } - string_list_clear(&keys, 0); - string_list_remove_duplicates(&keys_uniq, 0); + + string_list_sort_u(&keys_uniq, 0); for_each_string_list_item(item, &keys_uniq) puts(item->string); string_list_clear(&keys_uniq, 0); + string_list_clear(&keys, 0); } static enum help_format parse_help_format(const char *format) @@ -691,6 +715,16 @@ int cmd_help(int argc, help_format); list_config_help(SHOW_CONFIG_SECTIONS); return 0; + case HELP_ACTION_ALIASES_FOR_COMPLETION: { + struct string_list alias_list = STRING_LIST_INIT_DUP; + opt_mode_usage(argc, "--aliases-for-completion", help_format); + list_aliases(&alias_list); + for (size_t i = 0; i < alias_list.nr; i++) + printf("%s%c%s%c", alias_list.items[i].string, '\n', + (char *)alias_list.items[i].util, '\0'); + string_list_clear(&alias_list, 1); + return 0; + } case HELP_ACTION_CONFIG: opt_mode_usage(argc, "--config", help_format); setup_pager(the_repository);
diff --git a/builtin/history.c b/builtin/history.c new file mode 100644 index 0000000..568dc75 --- /dev/null +++ b/builtin/history.c
@@ -0,0 +1,754 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "builtin.h" +#include "cache-tree.h" +#include "commit.h" +#include "commit-reach.h" +#include "config.h" +#include "editor.h" +#include "environment.h" +#include "gettext.h" +#include "hex.h" +#include "lockfile.h" +#include "oidmap.h" +#include "parse-options.h" +#include "path.h" +#include "read-cache.h" +#include "refs.h" +#include "replay.h" +#include "revision.h" +#include "sequencer.h" +#include "strvec.h" +#include "tree.h" +#include "unpack-trees.h" +#include "wt-status.h" + +#define GIT_HISTORY_REWORD_USAGE \ + N_("git history reword <commit> [--dry-run] [--update-refs=(branches|head)]") +#define GIT_HISTORY_SPLIT_USAGE \ + N_("git history split <commit> [--dry-run] [--update-refs=(branches|head)] [--] [<pathspec>...]") + +static void change_data_free(void *util, const char *str UNUSED) +{ + struct wt_status_change_data *d = util; + free(d->rename_source); + free(d); +} + +static int fill_commit_message(struct repository *repo, + const struct object_id *old_tree, + const struct object_id *new_tree, + const char *default_message, + const char *action, + struct strbuf *out) +{ + const char *path = git_path_commit_editmsg(); + const char *hint = + _("Please enter the commit message for the %s changes." + " Lines starting\nwith '%s' will be ignored, and an" + " 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; + s.commit_template = 1; + s.colopts = 0; + s.display_comment_prefix = 1; + s.hints = 0; + s.use_color = 0; + s.whence = FROM_COMMIT; + s.committable = 1; + + s.fp = fopen(git_path_commit_editmsg(), "a"); + if (!s.fp) + return error_errno(_("could not open '%s'"), git_path_commit_editmsg()); + + 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); + + strbuf_reset(out); + if (launch_editor(path, out, NULL)) { + fprintf(stderr, _("Aborting commit as launching the editor failed.\n")); + return -1; + } + strbuf_stripspace(out, comment_line_str); + + cleanup_message(out, COMMIT_MSG_CLEANUP_ALL, 0); + + if (!out->len) { + fprintf(stderr, _("Aborting commit due to empty commit message.\n")); + return -1; + } + + return 0; +} + +static int commit_tree_with_edited_message_ext(struct repository *repo, + const char *action, + struct commit *commit_with_message, + const struct commit_list *parents, + const struct object_id *old_tree, + const struct object_id *new_tree, + struct commit **out) +{ + const char *exclude_gpgsig[] = { + /* We reencode the message, so the encoding needs to be stripped. */ + "encoding", + /* We need to strip signatures as those will become invalid. */ + "gpgsig", + "gpgsig-sha256", + NULL, + }; + const char *original_message, *original_body, *ptr; + struct commit_extra_header *original_extra_headers = NULL; + struct strbuf commit_message = STRBUF_INIT; + struct object_id rewritten_commit_oid; + char *original_author = NULL; + size_t len; + int ret; + + /* We retain authorship of the original commit. */ + original_message = repo_logmsg_reencode(repo, commit_with_message, NULL, NULL); + ptr = find_commit_header(original_message, "author", &len); + if (ptr) + original_author = xmemdupz(ptr, len); + find_commit_subject(original_message, &original_body); + + ret = fill_commit_message(repo, old_tree, new_tree, + original_body, action, &commit_message); + if (ret < 0) + goto out; + + original_extra_headers = read_commit_extra_headers(commit_with_message, + exclude_gpgsig); + + ret = commit_tree_extended(commit_message.buf, commit_message.len, new_tree, + parents, &rewritten_commit_oid, original_author, + NULL, NULL, original_extra_headers); + if (ret < 0) + goto out; + + *out = lookup_commit_or_die(&rewritten_commit_oid, "rewritten commit"); + +out: + free_commit_extra_headers(original_extra_headers); + strbuf_release(&commit_message); + free(original_author); + return ret; +} + +static int commit_tree_with_edited_message(struct repository *repo, + const char *action, + struct commit *original, + struct commit **out) +{ + struct object_id parent_tree_oid; + const struct object_id *tree_oid; + struct commit *parent; + + tree_oid = &repo_get_commit_tree(repo, original)->object.oid; + + parent = original->parents ? original->parents->item : NULL; + if (parent) { + if (repo_parse_commit(repo, parent)) { + return error(_("unable to parse parent commit %s"), + oid_to_hex(&parent->object.oid)); + } + + parent_tree_oid = repo_get_commit_tree(repo, parent)->object.oid; + } else { + oidcpy(&parent_tree_oid, repo->hash_algo->empty_tree); + } + + return commit_tree_with_edited_message_ext(repo, action, original, original->parents, + &parent_tree_oid, tree_oid, out); +} + +enum ref_action { + REF_ACTION_DEFAULT, + REF_ACTION_BRANCHES, + REF_ACTION_HEAD, +}; + +static int parse_ref_action(const struct option *opt, const char *value, int unset) +{ + enum ref_action *action = opt->value; + + BUG_ON_OPT_NEG_NOARG(unset, value); + if (!strcmp(value, "branches")) { + *action = REF_ACTION_BRANCHES; + } else if (!strcmp(value, "head")) { + *action = REF_ACTION_HEAD; + } else { + return error(_("%s expects one of 'branches' or 'head'"), + opt->long_name); + } + + return 0; +} + +static int revwalk_contains_merges(struct repository *repo, + const struct strvec *revwalk_args) +{ + struct strvec args = STRVEC_INIT; + struct rev_info revs; + int ret; + + strvec_pushv(&args, revwalk_args->v); + strvec_push(&args, "--min-parents=2"); + + repo_init_revisions(repo, &revs, NULL); + + setup_revisions_from_strvec(&args, &revs, NULL); + if (args.nr != 1) + BUG("revisions were set up with invalid argument"); + + if (prepare_revision_walk(&revs) < 0) { + ret = error(_("error preparing revisions")); + goto out; + } + + if (get_revision(&revs)) { + ret = error(_("replaying merge commits is not supported yet!")); + goto out; + } + + reset_revision_walk(); + ret = 0; + +out: + release_revisions(&revs); + strvec_clear(&args); + return ret; +} + +static int setup_revwalk(struct repository *repo, + enum ref_action action, + struct commit *original, + struct rev_info *revs) +{ + struct strvec args = STRVEC_INIT; + int ret; + + repo_init_revisions(repo, revs, NULL); + strvec_push(&args, "ignored"); + strvec_push(&args, "--reverse"); + strvec_push(&args, "--topo-order"); + strvec_push(&args, "--full-history"); + + /* We only want to see commits that are descendants of the old commit. */ + strvec_pushf(&args, "--ancestry-path=%s", + oid_to_hex(&original->object.oid)); + + /* + * Ancestry path may also show ancestors of the old commit, but we + * don't want to see those, either. + */ + strvec_pushf(&args, "^%s", oid_to_hex(&original->object.oid)); + + /* + * When we're asked to update HEAD we need to verify that the commit + * that we want to rewrite is actually an ancestor of it and, if so, + * update it. Otherwise we'll update (or print) all descendant + * branches. + */ + if (action == REF_ACTION_HEAD) { + struct commit_list *from_list = NULL; + struct commit *head; + + head = lookup_commit_reference_by_name("HEAD"); + if (!head) { + ret = error(_("cannot look up HEAD")); + goto out; + } + + commit_list_insert(original, &from_list); + ret = repo_is_descendant_of(repo, head, from_list); + free_commit_list(from_list); + + if (ret < 0) { + ret = error(_("cannot determine descendance")); + goto out; + } else if (!ret) { + ret = error(_("rewritten commit must be an ancestor " + "of HEAD when using --update-refs=head")); + goto out; + } + + strvec_push(&args, "HEAD"); + } else { + strvec_push(&args, "--branches"); + strvec_push(&args, "HEAD"); + } + + ret = revwalk_contains_merges(repo, &args); + if (ret < 0) + goto out; + + setup_revisions_from_strvec(&args, revs, NULL); + if (args.nr != 1) + BUG("revisions were set up with invalid argument"); + + ret = 0; + +out: + strvec_clear(&args); + return ret; +} + +static int handle_ref_update(struct ref_transaction *transaction, + const char *refname, + const struct object_id *new_oid, + const struct object_id *old_oid, + const char *reflog_msg, + struct strbuf *err) +{ + if (!transaction) { + printf("update %s %s %s\n", + refname, oid_to_hex(new_oid), oid_to_hex(old_oid)); + return 0; + } + + return ref_transaction_update(transaction, refname, new_oid, old_oid, + 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) +{ + const struct name_decoration *decoration; + struct replay_revisions_options opts = { 0 }; + 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; + int ret; + + refs_read_ref_full(get_main_ref_store(revs->repo), "HEAD", + RESOLVE_REF_NO_RECURSE, NULL, &head_flags); + detached_head = !(head_flags & REF_ISSYMREF); + + opts.onto = oid_to_hex_r(hex, &rewritten->object.oid); + + ret = replay_revisions(revs, &opts, &result); + if (ret) + goto out; + + 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 + * handle updating references that point to `original`. + */ + for (decoration = get_name_decoration(&original->object); + decoration; + decoration = decoration->next) + { + if (decoration->type != DECORATION_REF_LOCAL && + decoration->type != DECORATION_REF_HEAD) + continue; + + if (action == REF_ACTION_HEAD && + decoration->type != DECORATION_REF_HEAD) + continue; + + /* + * We only need to update HEAD separately in case it's + * detached. If it's not we'd already update the branch + * it is pointing to. + */ + if (action == REF_ACTION_BRANCHES && + decoration->type == DECORATION_REF_HEAD && + !detached_head) + continue; + + ret = handle_ref_update(transaction, + decoration->name, + &rewritten->object.oid, + &original->object.oid, + reflog_msg, &err); + if (ret) { + ret = error(_("failed to update ref '%s': %s"), + decoration->name, err.buf); + goto out; + } + } + + if (transaction && ref_transaction_commit(transaction, &err)) { + ret = error(_("failed to commit ref transaction: %s"), err.buf); + goto out; + } + + ret = 0; + +out: + ref_transaction_free(transaction); + replay_result_release(&result); + strbuf_release(&err); + return ret; +} + +static int cmd_history_reword(int argc, + const char **argv, + const char *prefix, + struct repository *repo) +{ + const char * const usage[] = { + GIT_HISTORY_REWORD_USAGE, + NULL, + }; + enum ref_action action = REF_ACTION_DEFAULT; + int dry_run = 0; + struct option options[] = { + OPT_CALLBACK_F(0, "update-refs", &action, N_("<action>"), + N_("control which refs should be updated (branches|head)"), + PARSE_OPT_NONEG, parse_ref_action), + OPT_BOOL('n', "dry-run", &dry_run, + N_("perform a dry-run without updating any refs")), + OPT_END(), + }; + struct strbuf reflog_msg = STRBUF_INIT; + struct commit *original, *rewritten; + struct rev_info revs = { 0 }; + 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; + } + + ret = setup_revwalk(repo, action, original, &revs); + if (ret) + goto out; + + ret = commit_tree_with_edited_message(repo, "reworded", original, &rewritten); + if (ret < 0) { + ret = error(_("failed writing reworded commit")); + goto out; + } + + strbuf_addf(&reflog_msg, "reword: updating %s", argv[0]); + + ret = handle_reference_updates(&revs, action, original, rewritten, + reflog_msg.buf, dry_run); + if (ret < 0) { + ret = error(_("failed replaying descendants")); + goto out; + } + + ret = 0; + +out: + strbuf_release(&reflog_msg); + release_revisions(&revs); + return ret; +} + +static int write_ondisk_index(struct repository *repo, + struct object_id *oid, + const char *path) +{ + struct unpack_trees_options opts = { 0 }; + struct lock_file lock = LOCK_INIT; + struct tree_desc tree_desc; + struct index_state index; + struct tree *tree; + int ret; + + index_state_init(&index, repo); + + opts.head_idx = -1; + opts.src_index = &index; + opts.dst_index = &index; + + tree = repo_parse_tree_indirect(repo, oid); + init_tree_desc(&tree_desc, &tree->object.oid, tree->buffer, tree->size); + + if (unpack_trees(1, &tree_desc, &opts)) { + ret = error(_("unable to populate index with tree")); + goto out; + } + + prime_cache_tree(repo, &index, tree); + + if (hold_lock_file_for_update(&lock, path, 0) < 0) { + ret = error_errno(_("unable to acquire index lock")); + goto out; + } + + if (write_locked_index(&index, &lock, COMMIT_LOCK)) { + ret = error(_("unable to write new index file")); + goto out; + } + + ret = 0; + +out: + rollback_lock_file(&lock); + release_index(&index); + return ret; +} + +static int split_commit(struct repository *repo, + struct commit *original, + struct pathspec *pathspec, + struct commit **out) +{ + struct interactive_options interactive_opts = INTERACTIVE_OPTIONS_INIT; + struct strbuf index_file = STRBUF_INIT; + struct index_state index = INDEX_STATE_INIT(repo); + const struct object_id *original_commit_tree_oid; + const struct object_id *old_tree_oid, *new_tree_oid; + struct object_id parent_tree_oid; + char original_commit_oid[GIT_MAX_HEXSZ + 1]; + struct commit *first_commit, *second_commit; + struct commit_list *parents = NULL; + struct tree *split_tree; + int ret; + + if (original->parents) { + if (repo_parse_commit(repo, original->parents->item)) { + ret = error(_("unable to parse parent commit %s"), + oid_to_hex(&original->parents->item->object.oid)); + goto out; + } + + parent_tree_oid = *get_commit_tree_oid(original->parents->item); + } else { + oidcpy(&parent_tree_oid, repo->hash_algo->empty_tree); + } + original_commit_tree_oid = get_commit_tree_oid(original); + + /* + * Construct the first commit. This is done by taking the original + * commit parent's tree and selectively patching changes from the diff + * between that parent and its child. + */ + repo_git_path_replace(repo, &index_file, "%s", "history-split.index"); + + ret = write_ondisk_index(repo, &parent_tree_oid, index_file.buf); + if (ret < 0) + goto out; + + ret = read_index_from(&index, index_file.buf, repo->gitdir); + if (ret < 0) { + ret = error(_("failed reading temporary index")); + goto out; + } + + oid_to_hex_r(original_commit_oid, &original->object.oid); + ret = run_add_p_index(repo, &index, index_file.buf, &interactive_opts, + original_commit_oid, pathspec, ADD_P_DISALLOW_EDIT); + if (ret < 0) + goto out; + + split_tree = write_in_core_index_as_tree(repo, &index); + if (!split_tree) { + ret = error(_("failed split tree")); + goto out; + } + + unlink(index_file.buf); + strbuf_release(&index_file); + + /* + * We disallow the cases where either the split-out commit or the + * original commit would become empty. Consequently, if we see that the + * new tree ID matches either of those trees we abort. + */ + if (oideq(&split_tree->object.oid, &parent_tree_oid)) { + ret = error(_("split commit is empty")); + goto out; + } else if (oideq(&split_tree->object.oid, original_commit_tree_oid)) { + ret = error(_("split commit tree matches original commit")); + goto out; + } + + /* + * The first commit is constructed from the split-out tree. The base + * that shall be diffed against is the parent of the original commit. + */ + ret = commit_tree_with_edited_message_ext(repo, "split-out", original, + original->parents, &parent_tree_oid, + &split_tree->object.oid, &first_commit); + if (ret < 0) { + ret = error(_("failed writing first commit")); + goto out; + } + + /* + * The second commit is constructed from the original tree. The base to + * diff against and the parent in this case is the first split-out + * commit. + */ + commit_list_append(first_commit, &parents); + + old_tree_oid = &repo_get_commit_tree(repo, first_commit)->object.oid; + new_tree_oid = &repo_get_commit_tree(repo, original)->object.oid; + + ret = commit_tree_with_edited_message_ext(repo, "split-out", original, + parents, old_tree_oid, + new_tree_oid, &second_commit); + if (ret < 0) { + ret = error(_("failed writing second commit")); + goto out; + } + + *out = second_commit; + ret = 0; + +out: + if (index_file.len) + unlink(index_file.buf); + strbuf_release(&index_file); + free_commit_list(parents); + release_index(&index); + return ret; +} + +static int cmd_history_split(int argc, + const char **argv, + const char *prefix, + struct repository *repo) +{ + const char * const usage[] = { + GIT_HISTORY_SPLIT_USAGE, + NULL, + }; + enum ref_action action = REF_ACTION_DEFAULT; + int dry_run = 0; + struct option options[] = { + OPT_CALLBACK_F(0, "update-refs", &action, N_("<refs>"), + N_("control ref update behavior (branches|head|print)"), + PARSE_OPT_NONEG, parse_ref_action), + OPT_BOOL('n', "dry-run", &dry_run, + N_("perform a dry-run without updating any refs")), + OPT_END(), + }; + struct commit *original, *rewritten = NULL; + struct strbuf reflog_msg = STRBUF_INIT; + struct pathspec pathspec = { 0 }; + struct rev_info revs = { 0 }; + int ret; + + argc = parse_options(argc, argv, prefix, options, usage, 0); + if (argc < 1) { + ret = error(_("command expects a committish")); + goto out; + } + repo_config(repo, git_default_config, NULL); + + if (action == REF_ACTION_DEFAULT) + action = REF_ACTION_BRANCHES; + + parse_pathspec(&pathspec, 0, + PATHSPEC_PREFER_FULL | + PATHSPEC_SYMLINK_LEADING_PATH | + PATHSPEC_PREFIX_ORIGIN, + prefix, argv + 1); + + original = lookup_commit_reference_by_name(argv[0]); + if (!original) { + ret = error(_("commit cannot be found: %s"), argv[0]); + goto out; + } + + ret = setup_revwalk(repo, action, original, &revs); + if (ret < 0) + goto out; + + if (original->parents && original->parents->next) { + ret = error(_("cannot split up merge commit")); + goto out; + } + + ret = split_commit(repo, original, &pathspec, &rewritten); + if (ret < 0) + goto out; + + strbuf_addf(&reflog_msg, "split: updating %s", argv[0]); + + ret = handle_reference_updates(&revs, action, original, rewritten, + reflog_msg.buf, dry_run); + if (ret < 0) { + ret = error(_("failed replaying descendants")); + goto out; + } + + ret = 0; + +out: + strbuf_release(&reflog_msg); + clear_pathspec(&pathspec); + 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_REWORD_USAGE, + GIT_HISTORY_SPLIT_USAGE, + NULL, + }; + parse_opt_subcommand_fn *fn = NULL; + struct option options[] = { + OPT_SUBCOMMAND("reword", &fn, cmd_history_reword), + OPT_SUBCOMMAND("split", &fn, cmd_history_split), + OPT_END(), + }; + + argc = parse_options(argc, argv, prefix, options, usage, 0); + return fn(argc, argv, prefix, repo); +}
diff --git a/builtin/hook.c b/builtin/hook.c index 7afec38..c058558 100644 --- a/builtin/hook.c +++ b/builtin/hook.c
@@ -4,14 +4,26 @@ #include "environment.h" #include "gettext.h" #include "hook.h" +#include "hook-list.h" #include "parse-options.h" -#include "strvec.h" #define BUILTIN_HOOK_RUN_USAGE \ - N_("git hook run [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>]") + N_("git hook run [--allow-unknown-hook-name] [--ignore-missing] [--to-stdin=<path>] <hook-name> [-- <hook-args>]") +#define BUILTIN_HOOK_LIST_USAGE \ + N_("git hook list [--allow-unknown-hook-name] [-z] [--show-scope] <hook-name>") + +static int is_known_hook(const char *name) +{ + const char **p; + for (p = hook_name_list; *p; p++) + if (!strcmp(*p, name)) + return 1; + return 0; +} static const char * const builtin_hook_usage[] = { BUILTIN_HOOK_RUN_USAGE, + BUILTIN_HOOK_LIST_USAGE, NULL }; @@ -20,14 +32,102 @@ static const char * const builtin_hook_run_usage[] = { NULL }; +static int list(int argc, const char **argv, const char *prefix, + struct repository *repo) +{ + static const char *const builtin_hook_list_usage[] = { + BUILTIN_HOOK_LIST_USAGE, + NULL + }; + struct string_list *head; + struct string_list_item *item; + const char *hookname = NULL; + int line_terminator = '\n'; + int show_scope = 0; + int allow_unknown = 0; + int ret = 0; + + struct option list_options[] = { + OPT_SET_INT('z', NULL, &line_terminator, + N_("use NUL as line terminator"), '\0'), + OPT_BOOL(0, "show-scope", &show_scope, + N_("show the config scope that defined each hook")), + OPT_BOOL(0, "allow-unknown-hook-name", &allow_unknown, + N_("allow running a hook with a non-native hook name")), + OPT_END(), + }; + + argc = parse_options(argc, argv, prefix, list_options, + builtin_hook_list_usage, 0); + + /* + * The only unnamed argument provided should be the hook-name; if we add + * arguments later they probably should be caught by parse_options. + */ + if (argc != 1) + usage_msg_opt(_("you must specify a hook event name to list"), + builtin_hook_list_usage, list_options); + + hookname = argv[0]; + + if (!allow_unknown && !is_known_hook(hookname)) { + error(_("unknown hook event '%s';\n" + "use --allow-unknown-hook-name to allow non-native hook names"), + hookname); + return 1; + } + + head = list_hooks(repo, hookname, NULL); + + if (!head->nr) { + warning(_("no hooks found for event '%s'"), hookname); + ret = 1; /* no hooks found */ + goto cleanup; + } + + for_each_string_list_item(item, head) { + struct hook *h = item->util; + + switch (h->kind) { + case HOOK_TRADITIONAL: + printf("%s%c", _("hook from hookdir"), line_terminator); + break; + case HOOK_CONFIGURED: { + const char *name = h->u.configured.friendly_name; + const char *scope = show_scope ? + config_scope_name(h->u.configured.scope) : NULL; + if (scope) + printf("%s\t%s%s%c", scope, + h->u.configured.disabled ? "disabled\t" : "", + name, line_terminator); + else + printf("%s%s%c", + h->u.configured.disabled ? "disabled\t" : "", + name, line_terminator); + break; + } + default: + BUG("unknown hook kind"); + } + } + +cleanup: + string_list_clear_func(head, hook_free); + free(head); + return ret; +} + static int run(int argc, const char **argv, const char *prefix, struct repository *repo UNUSED) { int i; struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; int ignore_missing = 0; + int allow_unknown = 0; const char *hook_name; struct option run_options[] = { + OPT_BOOL(0, "allow-unknown-hook-name", &allow_unknown, + N_("allow running a hook with a non-native hook name")), OPT_BOOL(0, "ignore-missing", &ignore_missing, N_("silently ignore missing requested <hook-name>")), OPT_STRING(0, "to-stdin", &opt.path_to_stdin, N_("path"), @@ -59,6 +159,14 @@ static int run(int argc, const char **argv, const char *prefix, repo_config(the_repository, git_default_config, NULL); hook_name = argv[0]; + + if (!allow_unknown && !is_known_hook(hook_name)) { + error(_("unknown hook event '%s';\n" + "use --allow-unknown-hook-name to allow non-native hook names"), + hook_name); + return 1; + } + if (!ignore_missing) opt.error_if_missing = 1; ret = run_hooks_opt(the_repository, hook_name, &opt); @@ -77,6 +185,7 @@ int cmd_hook(int argc, parse_opt_subcommand_fn *fn = NULL; struct option builtin_hook_options[] = { OPT_SUBCOMMAND("run", &fn, run), + OPT_SUBCOMMAND("list", &fn, list), OPT_END(), };
diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 2b78ba7..d1e4727 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c
@@ -16,12 +16,12 @@ #include "progress.h" #include "fsck.h" #include "strbuf.h" -#include "streaming.h" #include "thread-utils.h" #include "packfile.h" #include "pack-revindex.h" #include "object-file.h" #include "odb.h" +#include "odb/streaming.h" #include "oid-array.h" #include "oidset.h" #include "path.h" @@ -762,7 +762,7 @@ static void find_ref_delta_children(const struct object_id *oid, struct compare_data { struct object_entry *entry; - struct git_istream *st; + struct odb_read_stream *st; unsigned char *buf; unsigned long buf_size; }; @@ -779,7 +779,7 @@ static int compare_objects(const unsigned char *buf, unsigned long size, } while (size) { - ssize_t len = read_istream(data->st, data->buf, size); + ssize_t len = odb_read_stream_read(data->st, data->buf, size); if (len == 0) die(_("SHA1 COLLISION FOUND WITH %s !"), oid_to_hex(&data->entry->idx.oid)); @@ -798,8 +798,6 @@ static int compare_objects(const unsigned char *buf, unsigned long size, static int check_collison(struct object_entry *entry) { struct compare_data data; - enum object_type type; - unsigned long size; if (entry->size <= repo_settings_get_big_file_threshold(the_repository) || entry->type != OBJ_BLOB) @@ -807,15 +805,14 @@ static int check_collison(struct object_entry *entry) memset(&data, 0, sizeof(data)); data.entry = entry; - data.st = open_istream(the_repository, &entry->idx.oid, &type, &size, - NULL); + data.st = odb_read_stream_open(the_repository->objects, &entry->idx.oid, NULL); if (!data.st) return -1; - if (size != entry->size || type != entry->type) + if (data.st->size != entry->size || data.st->type != entry->type) die(_("SHA1 COLLISION FOUND WITH %s !"), oid_to_hex(&entry->idx.oid)); unpack_data(entry, compare_objects, &data); - close_istream(data.st); + odb_read_stream_close(data.st); free(data.buf); return 0; } @@ -1640,9 +1637,11 @@ static void final(const char *final_pack_name, const char *curr_pack_name, rename_tmp_packfile(&final_index_name, curr_index_name, &index_name, hash, "idx", 1); - if (do_fsck_object) - packfile_store_load_pack(the_repository->objects->packfiles, - final_index_name, 0); + if (do_fsck_object && startup_info->have_repository) { + struct odb_source_files *files = + odb_source_files_downcast(the_repository->objects->sources); + packfile_store_load_pack(files->packed, final_index_name, 0); + } if (!from_stdin) { printf("%s\n", hash_to_hex(hash)); @@ -2110,8 +2109,23 @@ int cmd_index_pack(int argc, else close(input_fd); - if (do_fsck_object && fsck_finish(&fsck_options)) - die(_("fsck error in pack objects")); + if (do_fsck_object) { + /* + * We cannot perform queued consistency checks when running + * outside of a repository because those require us to read + * from the object database, which is uninitialized. + * + * TODO: we may eventually set up an in-memory object database, + * which would allow us to perform these queued checks. + */ + if (!startup_info->have_repository && + fsck_has_queued_checks(&fsck_options)) + die(_("cannot perform queued object checks outside " + "of a repository")); + + if (fsck_finish(&fsck_options)) + die(_("fsck error in pack objects")); + } free(opts.anomaly); free(objects);
diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c index 41b0750..e7e86e9 100644 --- a/builtin/interpret-trailers.c +++ b/builtin/interpret-trailers.c
@@ -93,37 +93,6 @@ static int parse_opt_parse(const struct option *opt, const char *arg, return 0; } -static struct tempfile *trailers_tempfile; - -static FILE *create_in_place_tempfile(const char *file) -{ - struct stat st; - struct strbuf filename_template = STRBUF_INIT; - const char *tail; - FILE *outfile; - - if (stat(file, &st)) - die_errno(_("could not stat %s"), file); - if (!S_ISREG(st.st_mode)) - die(_("file %s is not a regular file"), file); - if (!(st.st_mode & S_IWUSR)) - die(_("file %s is not writable by user"), file); - - /* Create temporary file in the same directory as the original */ - tail = strrchr(file, '/'); - if (tail) - strbuf_add(&filename_template, file, tail - file + 1); - strbuf_addstr(&filename_template, "git-interpret-trailers-XXXXXX"); - - trailers_tempfile = xmks_tempfile_m(filename_template.buf, st.st_mode); - strbuf_release(&filename_template); - outfile = fdopen_tempfile(trailers_tempfile, "w"); - if (!outfile) - die_errno(_("could not open temporary file")); - - return outfile; -} - static void read_input_file(struct strbuf *sb, const char *file) { if (file) { @@ -140,55 +109,31 @@ static void interpret_trailers(const struct process_trailer_options *opts, struct list_head *new_trailer_head, const char *file) { - LIST_HEAD(head); - struct strbuf sb = STRBUF_INIT; - struct strbuf trailer_block_sb = STRBUF_INIT; - struct trailer_block *trailer_block; - FILE *outfile = stdout; + struct strbuf input = STRBUF_INIT; + struct strbuf out = STRBUF_INIT; + struct tempfile *tempfile = NULL; + int fd = 1; trailer_config_init(); - read_input_file(&sb, file); + read_input_file(&input, file); - if (opts->in_place) - outfile = create_in_place_tempfile(file); - - trailer_block = parse_trailers(opts, sb.buf, &head); - - /* Print the lines before the trailer block */ - if (!opts->only_trailers) - fwrite(sb.buf, 1, trailer_block_start(trailer_block), outfile); - - if (!opts->only_trailers && !blank_line_before_trailer_block(trailer_block)) - fprintf(outfile, "\n"); - - - if (!opts->only_input) { - LIST_HEAD(config_head); - LIST_HEAD(arg_head); - parse_trailers_from_config(&config_head); - parse_trailers_from_command_line_args(&arg_head, new_trailer_head); - list_splice(&config_head, &arg_head); - process_trailers_lists(&head, &arg_head); + if (opts->in_place) { + tempfile = trailer_create_in_place_tempfile(file); + if (!tempfile) + die(NULL); + fd = tempfile->fd; } + process_trailers(opts, new_trailer_head, &input, &out); - /* Print trailer block. */ - format_trailers(opts, &head, &trailer_block_sb); - free_trailers(&head); - fwrite(trailer_block_sb.buf, 1, trailer_block_sb.len, outfile); - strbuf_release(&trailer_block_sb); - - /* Print the lines after the trailer block as is. */ - if (!opts->only_trailers) - fwrite(sb.buf + trailer_block_end(trailer_block), 1, - sb.len - trailer_block_end(trailer_block), outfile); - trailer_block_release(trailer_block); - + if (write_in_full(fd, out.buf, out.len) < 0) + die_errno(_("could not write to temporary file '%s'"), file); if (opts->in_place) - if (rename_tempfile(&trailers_tempfile, file)) + if (rename_tempfile(&tempfile, file)) die_errno(_("could not rename temporary file to %s"), file); - strbuf_release(&sb); + strbuf_release(&input); + strbuf_release(&out); } int cmd_interpret_trailers(int argc, @@ -211,7 +156,7 @@ int cmd_interpret_trailers(int argc, N_("action if trailer is missing"), option_parse_if_missing), OPT_BOOL(0, "only-trailers", &opts.only_trailers, N_("output only the trailers")), - OPT_BOOL(0, "only-input", &opts.only_input, N_("do not apply trailer.* configuration variables")), + OPT_BOOL(0, "only-input", &opts.only_input, N_("do not apply trailer.<key-alias> configuration variables")), OPT_BOOL(0, "unfold", &opts.unfold, N_("reformat multiline trailer values as single-line values")), OPT_CALLBACK_F(0, "parse", &opts, NULL, N_("alias for --only-trailers --only-input --unfold"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_parse),
diff --git a/builtin/last-modified.c b/builtin/last-modified.c index b0ecbdc..8900cee 100644 --- a/builtin/last-modified.c +++ b/builtin/last-modified.c
@@ -53,8 +53,9 @@ define_commit_slab(active_paths_for_commit, struct bitmap *); struct last_modified { struct hashmap paths; struct rev_info rev; - bool recursive; bool show_trees; + bool nul_termination; + int max_depth; const char **all_paths; size_t all_paths_nr; @@ -123,7 +124,7 @@ static void add_path_from_diff(struct diff_queue_struct *q, static int populate_paths_from_revs(struct last_modified *lm) { - int num_interesting = 0; + int num_interesting = 0, ret = 0; struct diff_options diffopt; /* @@ -145,16 +146,25 @@ static int populate_paths_from_revs(struct last_modified *lm) if (obj->item->flags & UNINTERESTING) continue; - if (num_interesting++) - return error(_("last-modified can only operate on one tree at a time")); + if (num_interesting++) { + ret = error(_("last-modified can only operate on one commit at a time")); + goto out; + } + + if (!repo_peel_to_type(lm->rev.repo, obj->path, 0, obj->item, OBJ_COMMIT)) { + ret = error(_("revision argument '%s' is a %s, not a commit-ish"), obj->name, type_name(obj->item->type)); + goto out; + } diff_tree_oid(lm->rev.repo->hash_algo->empty_tree, &obj->item->oid, "", &diffopt); diff_flush(&diffopt); } + +out: clear_pathspec(&diffopt.pathspec); - return 0; + return ret; } static void last_modified_emit(struct last_modified *lm, @@ -165,10 +175,10 @@ static void last_modified_emit(struct last_modified *lm, putchar('^'); printf("%s\t", oid_to_hex(&commit->object.oid)); - if (lm->rev.diffopt.line_termination) - write_name_quoted(path, stdout, '\n'); - else + if (lm->nul_termination) printf("%s%c", path, '\0'); + else + write_name_quoted(path, stdout, '\n'); } static void mark_path(const char *path, const struct object_id *oid, @@ -327,7 +337,7 @@ static void process_parent(struct last_modified *lm, if (!(parent->object.flags & PARENT1)) active_paths_free(lm, parent); - memset(lm->scratch->words, 0x0, lm->scratch->word_alloc); + MEMZERO_ARRAY(lm->scratch->words, lm->scratch->word_alloc); diff_queue_clear(&diff_queued_diff); } @@ -479,8 +489,10 @@ static int last_modified_init(struct last_modified *lm, struct repository *r, lm->rev.no_commit_id = 1; lm->rev.diff = 1; lm->rev.diffopt.flags.no_recursive_diff_tree_combined = 1; - lm->rev.diffopt.flags.recursive = lm->recursive; + lm->rev.diffopt.flags.recursive = 1; lm->rev.diffopt.flags.tree_in_recursive = lm->show_trees; + lm->rev.diffopt.max_depth = lm->max_depth; + lm->rev.diffopt.max_depth_valid = lm->max_depth >= 0; argc = setup_revisions(argc, argv, &lm->rev, NULL); if (argc > 1) { @@ -491,7 +503,7 @@ static int last_modified_init(struct last_modified *lm, struct repository *r, lm->rev.bloom_filter_settings = get_bloom_filter_settings(lm->rev.repo); if (populate_paths_from_revs(lm) < 0) - return error(_("unable to setup last-modified")); + return -1; CALLOC_ARRAY(lm->all_paths, hashmap_get_size(&lm->paths)); lm->all_paths_nr = 0; @@ -510,22 +522,27 @@ int cmd_last_modified(int argc, const char **argv, const char *prefix, struct last_modified lm = { 0 }; const char * const last_modified_usage[] = { - N_("git last-modified [--recursive] [--show-trees] " - "[<revision-range>] [[--] <path>...]"), + N_("git last-modified [--recursive] [--show-trees] [--max-depth=<depth>] [-z]\n" + " [<revision-range>] [[--] <pathspec>...]"), NULL }; struct option last_modified_options[] = { - OPT_BOOL('r', "recursive", &lm.recursive, - N_("recurse into subtrees")), + OPT_SET_INT('r', "recursive", &lm.max_depth, + N_("recurse into subtrees"), -1), OPT_BOOL('t', "show-trees", &lm.show_trees, N_("show tree entries when recursing into subtrees")), + OPT_INTEGER_F(0, "max-depth", &lm.max_depth, + N_("maximum tree depth to recurse"), PARSE_OPT_NONEG), + OPT_BOOL('z', NULL, &lm.nul_termination, + N_("lines are separated with NUL character")), OPT_END() }; argc = parse_options(argc, argv, prefix, last_modified_options, last_modified_usage, - PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT); + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT | + PARSE_OPT_KEEP_DASHDASH); repo_config(repo, git_default_config, NULL);
diff --git a/builtin/log.c b/builtin/log.c index c8319b8..8c0939d 100644 --- a/builtin/log.c +++ b/builtin/log.c
@@ -16,6 +16,7 @@ #include "refs.h" #include "object-name.h" #include "odb.h" +#include "odb/streaming.h" #include "pager.h" #include "color.h" #include "commit.h" @@ -23,7 +24,6 @@ #include "diff-merges.h" #include "revision.h" #include "log-tree.h" -#include "builtin.h" #include "oid-array.h" #include "tag.h" #include "reflog-walk.h" @@ -35,11 +35,12 @@ #include "parse-options.h" #include "line-log.h" #include "branch.h" -#include "streaming.h" #include "version.h" #include "mailmap.h" #include "progress.h" #include "commit-slab.h" +#include "advice.h" +#include "utf8.h" #include "commit-reach.h" #include "range-diff.h" @@ -337,7 +338,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, if (mailmap) { rev->mailmap = xmalloc(sizeof(struct string_list)); string_list_init_nodup(rev->mailmap); - read_mailmap(rev->mailmap); + read_mailmap(the_repository, rev->mailmap); } if (rev->pretty_given && rev->commit_format == CMIT_FMT_RAW) { @@ -424,7 +425,7 @@ static int cmd_log_walk_no_free(struct rev_info *rev) */ free_commit_buffer(the_repository->parsed_objects, commit); - free_commit_list(commit->parents); + commit_list_free(commit->parents); commit->parents = NULL; } if (saved_nrl < rev->diffopt.needed_rename_limit) @@ -584,7 +585,7 @@ static int show_blob_object(const struct object_id *oid, struct rev_info *rev, c fflush(rev->diffopt.file); if (!rev->diffopt.flags.textconv_set_via_cmdline || !rev->diffopt.flags.allow_textconv) - return stream_blob_to_fd(1, oid, NULL, 0); + return odb_stream_blob_to_fd(the_repository->objects, 1, oid, NULL, 0); if (get_oid_with_context(the_repository, obj_name, GET_OID_RECORD_PATH, @@ -594,7 +595,7 @@ static int show_blob_object(const struct object_id *oid, struct rev_info *rev, c !textconv_object(the_repository, obj_context.path, obj_context.mode, &oidc, 1, &buf, &size)) { object_context_release(&obj_context); - return stream_blob_to_fd(1, oid, NULL, 0); + return odb_stream_blob_to_fd(the_repository->objects, 1, oid, NULL, 0); } if (!buf) @@ -886,6 +887,7 @@ struct format_config { char *signature; char *signature_file; enum cover_setting config_cover_letter; + char *fmt_cover_letter_commit_list; char *config_output_directory; enum cover_from_description cover_from_description_mode; int show_notes; @@ -930,6 +932,7 @@ static void format_config_release(struct format_config *cfg) string_list_clear(&cfg->extra_cc, 0); strbuf_release(&cfg->sprefix); free(cfg->fmt_patch_suffix); + free(cfg->fmt_cover_letter_commit_list); } static enum cover_from_description parse_cover_from_description(const char *arg) @@ -1052,6 +1055,10 @@ static int git_format_config(const char *var, const char *value, cfg->config_cover_letter = git_config_bool(var, value) ? COVER_ON : COVER_OFF; return 0; } + if (!strcmp(var, "format.commitlistformat")) { + FREE_AND_NULL(cfg->fmt_cover_letter_commit_list); + return git_config_string(&cfg->fmt_cover_letter_commit_list, var, value); + } if (!strcmp(var, "format.outputdirectory")) { FREE_AND_NULL(cfg->config_output_directory); return git_config_string(&cfg->config_output_directory, var, value); @@ -1096,7 +1103,18 @@ static int git_format_config(const char *var, const char *value, return 0; } if (!strcmp(var, "format.noprefix")) { - format_no_prefix = 1; + format_no_prefix = git_parse_maybe_bool(value); + if (format_no_prefix < 0) { + int status = die_message( + _("bad boolean config value '%s' for '%s'"), + value, var); + advise(_("'%s' used to accept any value and " + "treat that as 'true'.\n" + "Now it only accepts boolean values, " + "like what '%s' does.\n"), + var, "diff.noprefix"); + exit(status); + } return 0; } @@ -1324,15 +1342,56 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev) } } +static void generate_shortlog_cover_letter(struct shortlog *log, + struct rev_info *rev, + struct commit **list, + int nr) +{ + shortlog_init(log); + log->wrap_lines = 1; + log->wrap = MAIL_DEFAULT_WRAP; + log->in1 = 2; + log->in2 = 4; + log->file = rev->diffopt.file; + log->groups = SHORTLOG_GROUP_AUTHOR; + shortlog_finish_setup(log); + for (int i = 0; i < nr; i++) + shortlog_add_commit(log, list[i]); + + shortlog_output(log); +} + +static void generate_commit_list_cover(FILE *cover_file, const char *format, + struct commit **list, int n) +{ + struct strbuf commit_line = STRBUF_INIT; + struct pretty_print_context ctx = {0}; + struct rev_info rev = REV_INFO_INIT; + + rev.total = n; + ctx.rev = &rev; + for (int i = 1; i <= n; i++) { + rev.nr = i; + repo_format_commit_message(the_repository, list[n - i], format, + &commit_line, &ctx); + fprintf(cover_file, "%s\n", commit_line.buf); + strbuf_reset(&commit_line); + } + fprintf(cover_file, "\n"); + + strbuf_release(&commit_line); +} + static void make_cover_letter(struct rev_info *rev, int use_separate_file, struct commit *origin, int nr, struct commit **list, const char *description_file, const char *branch_name, int quiet, - const struct format_config *cfg) + const struct format_config *cfg, + const char *format) { - const char *committer; + const char *from; struct shortlog log; struct strbuf sb = STRBUF_INIT; int i; @@ -1345,7 +1404,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file, if (!cmit_fmt_is_mail(rev->commit_format)) die(_("cover letter needs email format")); - committer = git_committer_info(0); + from = cfg->from ? cfg->from : git_committer_info(0); if (use_separate_file && open_next_file(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet)) @@ -1368,7 +1427,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file, pp.date_mode.type = DATE_RFC2822; pp.rev = rev; pp.encode_email_headers = rev->encode_email_headers; - pp_user_info(&pp, NULL, &sb, committer, encoding); + pp_user_info(&pp, NULL, &sb, from, encoding); prepare_cover_text(&pp, description_file, branch_name, &sb, encoding, need_8bit_cte, cfg); fprintf(rev->diffopt.file, "%s\n", sb.buf); @@ -1377,18 +1436,17 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file, free(pp.after_subject); strbuf_release(&sb); - shortlog_init(&log); - log.wrap_lines = 1; - log.wrap = MAIL_DEFAULT_WRAP; - log.in1 = 2; - log.in2 = 4; - log.file = rev->diffopt.file; - log.groups = SHORTLOG_GROUP_AUTHOR; - shortlog_finish_setup(&log); - for (i = 0; i < nr; i++) - shortlog_add_commit(&log, list[i]); - - shortlog_output(&log); + if (skip_prefix(format, "log:", &format)) + generate_commit_list_cover(rev->diffopt.file, format, list, nr); + else if (!strcmp(format, "shortlog")) + generate_shortlog_cover_letter(&log, rev, list, nr); + else if (!strcmp(format, "modern")) + generate_commit_list_cover(rev->diffopt.file, "%w(72)[%(count)/%(total)] %s", + list, nr); + else if (strchr(format, '%')) + generate_commit_list_cover(rev->diffopt.file, format, list, nr); + else + die(_("'%s' is not a valid format string"), format); /* We can only do diffstat with a unique reference point */ if (origin) @@ -1697,12 +1755,12 @@ static struct commit *get_base_commit(const struct format_config *cfg, if (die_on_failure) { die(_("could not find exact merge base")); } else { - free_commit_list(base_list); + commit_list_free(base_list); return NULL; } } base = base_list->item; - free_commit_list(base_list); + commit_list_free(base_list); } else { if (die_on_failure) die(_("failed to get upstream, if you want to record base commit automatically,\n" @@ -1732,14 +1790,14 @@ static struct commit *get_base_commit(const struct format_config *cfg, if (die_on_failure) { die(_("failed to find exact merge base")); } else { - free_commit_list(merge_base); + commit_list_free(merge_base); free(rev); return NULL; } } rev[i] = merge_base->item; - free_commit_list(merge_base); + commit_list_free(merge_base); } if (rev_nr % 2) @@ -1896,16 +1954,17 @@ int cmd_format_patch(int argc, { struct format_config cfg; struct commit *commit; - struct commit **list = NULL; + struct commit_stack list = COMMIT_STACK_INIT; struct rev_info rev; char *to_free = NULL; struct setup_revision_opt s_r_opt; - size_t nr = 0, total, i; + size_t total, i; int use_stdout = 0; int start_number = -1; int just_numbers = 0; int ignore_if_in_upstream = 0; int cover_letter = -1; + const char *cover_letter_fmt = NULL; int boundary_count = 0; int no_binary_diff = 0; int zero_commit = 0; @@ -1952,6 +2011,8 @@ int cmd_format_patch(int argc, N_("print patches to standard out")), OPT_BOOL(0, "cover-letter", &cover_letter, N_("generate a cover letter")), + OPT_STRING(0, "commit-list-format", &cover_letter_fmt, N_("format-spec"), + N_("format spec used for the commit list in the cover letter")), OPT_BOOL(0, "numbered-files", &just_numbers, N_("use simple number sequence for output file names")), OPT_STRING(0, "suffix", &fmt_patch_suffix, N_("sfx"), @@ -2283,14 +2344,21 @@ int cmd_format_patch(int argc, if (ignore_if_in_upstream && has_commit_patch_id(commit, &ids)) continue; - nr++; - REALLOC_ARRAY(list, nr); - list[nr - 1] = commit; + commit_stack_push(&list, commit); } - if (nr == 0) + if (!list.nr) /* nothing to do */ goto done; - total = nr; + total = list.nr; + + if (!cover_letter_fmt) { + cover_letter_fmt = cfg.fmt_cover_letter_commit_list; + if (!cover_letter_fmt) + cover_letter_fmt = "shortlog"; + } else if (cover_letter == -1) { + cover_letter = 1; + } + if (cover_letter == -1) { if (cfg.config_cover_letter == COVER_AUTO) cover_letter = (total > 1); @@ -2308,7 +2376,7 @@ int cmd_format_patch(int argc, if (!cover_letter && total != 1) die(_("--interdiff requires --cover-letter or single patch")); rev.idiff_oid1 = &idiff_prev.oid[idiff_prev.nr - 1]; - rev.idiff_oid2 = get_commit_tree_oid(list[0]); + rev.idiff_oid2 = get_commit_tree_oid(list.items[0]); rev.idiff_title = diff_title(&idiff_title, reroll_count, _("Interdiff:"), _("Interdiff against v%d:")); @@ -2324,7 +2392,7 @@ int cmd_format_patch(int argc, die(_("--range-diff requires --cover-letter or single patch")); infer_range_diff_ranges(&rdiff1, &rdiff2, rdiff_prev, - origin, list[0]); + origin, list.items[0]); rev.rdiff1 = rdiff1.buf; rev.rdiff2 = rdiff2.buf; rev.creation_factor = creation_factor; @@ -2360,11 +2428,11 @@ int cmd_format_patch(int argc, } memset(&bases, 0, sizeof(bases)); - base = get_base_commit(&cfg, list, nr); + base = get_base_commit(&cfg, list.items, list.nr); if (base) { reset_revision_walk(); clear_object_flags(the_repository, UNINTERESTING); - prepare_bases(&bases, base, list, nr); + prepare_bases(&bases, base, list.items, list.nr); } if (in_reply_to || cfg.thread || cover_letter) { @@ -2377,11 +2445,14 @@ int cmd_format_patch(int argc, } rev.numbered_files = just_numbers; rev.patch_suffix = fmt_patch_suffix; + if (cover_letter) { if (cfg.thread) gen_message_id(&rev, "cover"); make_cover_letter(&rev, !!output_directory, - origin, nr, list, description_file, branch_name, quiet, &cfg); + origin, list.nr, list.items, + description_file, branch_name, quiet, &cfg, + cover_letter_fmt); print_bases(&bases, rev.diffopt.file); print_signature(signature, rev.diffopt.file); total++; @@ -2395,12 +2466,12 @@ int cmd_format_patch(int argc, if (show_progress) progress = start_delayed_progress(the_repository, _("Generating patches"), total); - for (i = 0; i < nr; i++) { - size_t idx = nr - i - 1; + while (list.nr) { + size_t idx = list.nr - 1; int shown; display_progress(progress, total - idx); - commit = list[idx]; + commit = commit_stack_pop(&list); rev.nr = total - idx + (start_number - 1); /* Make the second and subsequent mails replies to the first */ @@ -2469,7 +2540,7 @@ int cmd_format_patch(int argc, } } stop_progress(&progress); - free(list); + commit_stack_clear(&list); if (ignore_if_in_upstream) free_patch_ids(&ids); @@ -2611,7 +2682,7 @@ int cmd_cherry(int argc, print_commit(sign, commit, verbose, abbrev, revs.diffopt.file); } - free_commit_list(list); + commit_list_free(list); free_patch_ids(&ids); return 0; }
diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index df09000..fe77829 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c
@@ -156,7 +156,7 @@ int cmd_ls_remote(int argc, continue; if (!tail_match(&pattern, ref->name)) continue; - item = ref_array_push(&ref_array, ref->name, &ref->old_oid); + item = ref_array_push(&ref_array, ref->name, &ref->old_oid, NULL); item->symref = xstrdup_or_null(ref->symref); }
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index ec6940f..113e4a9 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c
@@ -421,7 +421,7 @@ int cmd_ls_tree(int argc, for (i = 0; i < options.pathspec.nr; i++) options.pathspec.items[i].nowildcard_len = options.pathspec.items[i].len; options.pathspec.has_wildcard = 0; - tree = parse_tree_indirect(&oid); + tree = repo_parse_tree_indirect(the_repository, &oid); if (!tree) die("not a tree object"); /*
diff --git a/builtin/merge-base.c b/builtin/merge-base.c index 3f82781..c7ee97f 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c
@@ -15,7 +15,7 @@ static int show_merge_base(struct commit **rev, size_t rev_nr, int show_all) if (repo_get_merge_bases_many_dirty(the_repository, rev[0], rev_nr - 1, rev + 1, &result) < 0) { - free_commit_list(result); + commit_list_free(result); return -1; } @@ -28,7 +28,7 @@ static int show_merge_base(struct commit **rev, size_t rev_nr, int show_all) break; } - free_commit_list(result); + commit_list_free(result); return 0; } @@ -71,7 +71,7 @@ static int handle_independent(int count, const char **args) for (rev = revs; rev; rev = rev->next) printf("%s\n", oid_to_hex(&rev->item->object.oid)); - free_commit_list(revs); + commit_list_free(revs); return 0; } @@ -85,11 +85,11 @@ static int handle_octopus(int count, const char **args, int show_all) commit_list_insert(get_commit_reference(args[i]), &revs); if (get_octopus_merge_bases(revs, &result) < 0) { - free_commit_list(revs); - free_commit_list(result); + commit_list_free(revs); + commit_list_free(result); return 128; } - free_commit_list(revs); + commit_list_free(revs); reduce_heads_replace(&result); if (!result) @@ -101,7 +101,7 @@ static int handle_octopus(int count, const char **args, int show_all) break; } - free_commit_list(result); + commit_list_free(result); return 0; }
diff --git a/builtin/merge-file.c b/builtin/merge-file.c index 46775d0..59a9792 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c
@@ -60,7 +60,7 @@ static int diff_algorithm_cb(const struct option *opt, int cmd_merge_file(int argc, const char **argv, const char *prefix, - struct repository *repo UNUSED) + struct repository *repo) { const char *names[3] = { 0 }; mmfile_t mmfs[3] = { 0 }; @@ -95,12 +95,10 @@ int cmd_merge_file(int argc, xmp.style = 0; xmp.favor = 0; - if (startup_info->have_repository) { - /* Read the configuration file */ - repo_config(the_repository, git_xmerge_config, NULL); - if (0 <= git_xmerge_style) - xmp.style = git_xmerge_style; - } + /* Read the configuration file */ + repo_config(repo, git_xmerge_config, NULL); + if (0 <= git_xmerge_style) + xmp.style = git_xmerge_style; argc = parse_options(argc, argv, prefix, options, merge_file_usage, 0); if (argc != 3) @@ -110,7 +108,8 @@ int cmd_merge_file(int argc, return error_errno("failed to redirect stderr to /dev/null"); } - if (object_id) + if (!repo && object_id) + /* emit the correct "not a git repo" error in this case */ setup_git_directory(); for (i = 0; i < 3; i++) { @@ -128,7 +127,7 @@ int cmd_merge_file(int argc, ret = error(_("object '%s' does not exist"), argv[i]); else if (!oideq(&oid, the_hash_algo->empty_blob)) - read_mmblob(mmf, &oid); + read_mmblob(mmf, the_repository->objects, &oid); else read_mmfile(mmf, "/dev/null"); } else if (read_mmfile(mmf, fname)) {
diff --git a/builtin/merge-ours.c b/builtin/merge-ours.c index 97b8a79..405b298 100644 --- a/builtin/merge-ours.c +++ b/builtin/merge-ours.c
@@ -8,31 +8,34 @@ * Pretend we resolved the heads, but declare our tree trumps everybody else. */ -#define USE_THE_REPOSITORY_VARIABLE - #include "git-compat-util.h" #include "builtin.h" +#include "config.h" +#include "environment.h" #include "diff.h" - static const char builtin_merge_ours_usage[] = "git merge-ours <base>... -- HEAD <remote>..."; int cmd_merge_ours(int argc, const char **argv, const char *prefix UNUSED, - struct repository *repo UNUSED) + struct repository *repo) { show_usage_if_asked(argc, argv, builtin_merge_ours_usage); + repo_config(repo, git_default_config, NULL); + prepare_repo_settings(repo); + repo->settings.command_requires_full_index = 0; + /* * The contents of the current index becomes the tree we * commit. The index must match HEAD, or this merge cannot go * through. */ - if (repo_read_index(the_repository) < 0) + if (repo_read_index(repo) < 0) die_errno("read_cache failed"); - if (index_differs_from(the_repository, "HEAD", NULL, 0)) + if (index_differs_from(repo, "HEAD", NULL, 0)) return 2; return 0; }
diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index 1c063d9..312b595 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c
@@ -447,17 +447,20 @@ static int real_merge(struct merge_tree_options *o, if (repo_get_oid_treeish(the_repository, merge_base, &base_oid)) die(_("could not parse as tree '%s'"), merge_base); - base_tree = parse_tree_indirect(&base_oid); + base_tree = repo_parse_tree_indirect(the_repository, + &base_oid); if (!base_tree) die(_("unable to read tree (%s)"), oid_to_hex(&base_oid)); if (repo_get_oid_treeish(the_repository, branch1, &head_oid)) die(_("could not parse as tree '%s'"), branch1); - parent1_tree = parse_tree_indirect(&head_oid); + parent1_tree = repo_parse_tree_indirect(the_repository, + &head_oid); if (!parent1_tree) die(_("unable to read tree (%s)"), oid_to_hex(&head_oid)); if (repo_get_oid_treeish(the_repository, branch2, &merge_oid)) die(_("could not parse as tree '%s'"), branch2); - parent2_tree = parse_tree_indirect(&merge_oid); + parent2_tree = repo_parse_tree_indirect(the_repository, + &merge_oid); if (!parent2_tree) die(_("unable to read tree (%s)"), oid_to_hex(&merge_oid)); @@ -483,9 +486,9 @@ static int real_merge(struct merge_tree_options *o, exit(128); if (!merge_bases && !o->allow_unrelated_histories) die(_("refusing to merge unrelated histories")); - merge_bases = reverse_commit_list(merge_bases); + merge_bases = commit_list_reverse(merge_bases); merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result); - free_commit_list(merge_bases); + commit_list_free(merge_bases); } if (result.clean < 0)
diff --git a/builtin/merge.c b/builtin/merge.c index c421a11..2cbce56 100644 --- a/builtin/merge.c +++ b/builtin/merge.c
@@ -506,7 +506,7 @@ static void finish(struct commit *head_commit, * We ignore errors in 'gc --auto', since the * user should see them. */ - run_auto_maintenance(verbosity < 0); + run_auto_maintenance(the_repository, verbosity < 0); } } if (new_head && show_diffstat) { @@ -756,19 +756,19 @@ static int read_tree_trivial(struct object_id *common, struct object_id *head, opts.trivial_merges_only = 1; opts.merge = 1; opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */ - trees[nr_trees] = parse_tree_indirect(common); + trees[nr_trees] = repo_parse_tree_indirect(the_repository, common); if (!trees[nr_trees++]) return -1; - trees[nr_trees] = parse_tree_indirect(head); + trees[nr_trees] = repo_parse_tree_indirect(the_repository, head); if (!trees[nr_trees++]) return -1; - trees[nr_trees] = parse_tree_indirect(one); + trees[nr_trees] = repo_parse_tree_indirect(the_repository, one); if (!trees[nr_trees++]) return -1; opts.fn = threeway_merge; cache_tree_free(&the_repository->index->cache_tree); for (i = 0; i < nr_trees; i++) { - parse_tree(trees[i]); + repo_parse_tree(the_repository, trees[i]); init_tree_desc(t+i, &trees[i]->object.oid, trees[i]->buffer, trees[i]->size); } @@ -831,7 +831,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, LOCK_DIE_ON_ERROR); clean = merge_ort_recursive(&o, head, remoteheads->item, reversed, &result); - free_commit_list(reversed); + commit_list_free(reversed); strbuf_release(&o.obuf); if (clean < 0) { @@ -1006,7 +1006,7 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads) finish(head, remoteheads, &result_commit, "In-index merge"); remove_merge_branch_state(the_repository); - free_commit_list(parents); + commit_list_free(parents); return 0; } @@ -1022,7 +1022,7 @@ static int finish_automerge(struct commit *head, struct object_id result_commit; write_tree_trivial(result_tree); - free_commit_list(common); + commit_list_free(common); parents = remoteheads; if (!head_subsumed || fast_forward == FF_NO) commit_list_insert(head, &parents); @@ -1035,7 +1035,7 @@ static int finish_automerge(struct commit *head, strbuf_release(&buf); remove_merge_branch_state(the_repository); - free_commit_list(parents); + commit_list_free(parents); return 0; } @@ -1197,7 +1197,7 @@ static struct commit_list *reduce_parents(struct commit *head_commit, /* Find what parents to record by checking independent ones. */ parents = reduce_heads(remoteheads); - free_commit_list(remoteheads); + commit_list_free(remoteheads); remoteheads = NULL; remotes = &remoteheads; @@ -1748,7 +1748,7 @@ int cmd_merge(int argc, exit(128); common_item = common_one->item; - free_commit_list(common_one); + commit_list_free(common_one); if (!oideq(&common_item->object.oid, &j->item->object.oid)) { up_to_date = 0; break; @@ -1880,8 +1880,8 @@ int cmd_merge(int argc, done: if (!automerge_was_ok) { - free_commit_list(common); - free_commit_list(remoteheads); + commit_list_free(common); + commit_list_free(remoteheads); } strbuf_release(&buf); free(branch_to_free);
diff --git a/builtin/mktree.c b/builtin/mktree.c index 1277230..4084e32 100644 --- a/builtin/mktree.c +++ b/builtin/mktree.c
@@ -3,7 +3,6 @@ * * Copyright (c) Junio C Hamano, 2006, 2009 */ -#define USE_THE_REPOSITORY_VARIABLE #include "builtin.h" #include "gettext.h" #include "hex.h" @@ -46,7 +45,7 @@ static int ent_compare(const void *a_, const void *b_) b->name, b->len, b->mode); } -static void write_tree(struct object_id *oid) +static void write_tree(struct repository *repo, struct object_id *oid) { struct strbuf buf; size_t size; @@ -60,10 +59,10 @@ static void write_tree(struct object_id *oid) for (i = 0; i < used; i++) { struct treeent *ent = entries[i]; strbuf_addf(&buf, "%o %s%c", ent->mode, ent->name, '\0'); - strbuf_add(&buf, ent->oid.hash, the_hash_algo->rawsz); + strbuf_add(&buf, ent->oid.hash, repo->hash_algo->rawsz); } - odb_write_object(the_repository->objects, buf.buf, buf.len, OBJ_TREE, oid); + odb_write_object(repo->objects, buf.buf, buf.len, OBJ_TREE, oid); strbuf_release(&buf); } @@ -72,7 +71,7 @@ static const char *const mktree_usage[] = { NULL }; -static void mktree_line(char *buf, int nul_term_line, int allow_missing) +static void mktree_line(struct repository *repo, char *buf, int nul_term_line, int allow_missing) { char *ptr, *ntr; const char *p; @@ -93,7 +92,7 @@ static void mktree_line(char *buf, int nul_term_line, int allow_missing) die("input format error: %s", buf); ptr = ntr + 1; /* type */ ntr = strchr(ptr, ' '); - if (!ntr || parse_oid_hex(ntr + 1, &oid, &p) || + if (!ntr || parse_oid_hex_algop(ntr + 1, &oid, &p, repo->hash_algo) || *p != '\t') die("input format error: %s", buf); @@ -124,7 +123,7 @@ static void mktree_line(char *buf, int nul_term_line, int allow_missing) /* Check the type of object identified by oid without fetching objects */ oi.typep = &obj_type; - if (odb_read_object_info_extended(the_repository->objects, &oid, &oi, + if (odb_read_object_info_extended(repo->objects, &oid, &oi, OBJECT_INFO_LOOKUP_REPLACE | OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT) < 0) @@ -155,7 +154,7 @@ static void mktree_line(char *buf, int nul_term_line, int allow_missing) int cmd_mktree(int ac, const char **av, const char *prefix, - struct repository *repo UNUSED) + struct repository *repo) { struct strbuf sb = STRBUF_INIT; struct object_id oid; @@ -187,7 +186,7 @@ int cmd_mktree(int ac, break; die("input format error: (blank line only valid in batch mode)"); } - mktree_line(sb.buf, nul_term_line, allow_missing); + mktree_line(repo, sb.buf, nul_term_line, allow_missing); } if (is_batch_mode && got_eof && used < 1) { /* @@ -197,7 +196,7 @@ int cmd_mktree(int ac, */ ; /* skip creating an empty tree */ } else { - write_tree(&oid); + write_tree(repo, &oid); puts(oid_to_hex(&oid)); fflush(stdout); }
diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c index 5f364aa..0f72d96 100644 --- a/builtin/multi-pack-index.c +++ b/builtin/multi-pack-index.c
@@ -9,12 +9,18 @@ #include "strbuf.h" #include "trace2.h" #include "odb.h" +#include "odb/source.h" #include "replace-object.h" #include "repository.h" #define BUILTIN_MIDX_WRITE_USAGE \ - N_("git multi-pack-index [<options>] write [--preferred-pack=<pack>]" \ - "[--refs-snapshot=<path>]") + N_("git multi-pack-index [<options>] write [--preferred-pack=<pack>]\n" \ + " [--[no-]bitmap] [--[no-]incremental] [--[no-]stdin-packs]\n" \ + " [--refs-snapshot=<path>]") + +#define BUILTIN_MIDX_COMPACT_USAGE \ + N_("git multi-pack-index [<options>] compact [--[no-]incremental]\n" \ + " [--[no-]bitmap] <from> <to>") #define BUILTIN_MIDX_VERIFY_USAGE \ N_("git multi-pack-index [<options>] verify") @@ -29,6 +35,10 @@ static char const * const builtin_multi_pack_index_write_usage[] = { BUILTIN_MIDX_WRITE_USAGE, NULL }; +static char const * const builtin_multi_pack_index_compact_usage[] = { + BUILTIN_MIDX_COMPACT_USAGE, + NULL +}; static char const * const builtin_multi_pack_index_verify_usage[] = { BUILTIN_MIDX_VERIFY_USAGE, NULL @@ -43,6 +53,7 @@ static char const * const builtin_multi_pack_index_repack_usage[] = { }; static char const * const builtin_multi_pack_index_usage[] = { BUILTIN_MIDX_WRITE_USAGE, + BUILTIN_MIDX_COMPACT_USAGE, BUILTIN_MIDX_VERIFY_USAGE, BUILTIN_MIDX_EXPIRE_USAGE, BUILTIN_MIDX_REPACK_USAGE, @@ -84,6 +95,8 @@ static struct option common_opts[] = { N_("directory"), N_("object directory containing set of packfile and pack-index pairs"), parse_object_dir), + OPT_BIT(0, "progress", &opts.flags, N_("force progress reporting"), + MIDX_PROGRESS), OPT_END(), }; @@ -138,8 +151,6 @@ static int cmd_multi_pack_index_write(int argc, const char **argv, N_("pack for reuse when computing a multi-pack bitmap")), OPT_BIT(0, "bitmap", &opts.flags, N_("write multi-pack bitmap"), MIDX_WRITE_BITMAP | MIDX_WRITE_REV_INDEX), - OPT_BIT(0, "progress", &opts.flags, - N_("force progress reporting"), MIDX_PROGRESS), OPT_BIT(0, "incremental", &opts.flags, N_("write a new incremental MIDX"), MIDX_WRITE_INCREMENTAL), OPT_BOOL(0, "stdin-packs", &opts.stdin_packs, @@ -194,14 +205,78 @@ static int cmd_multi_pack_index_write(int argc, const char **argv, return ret; } +static int cmd_multi_pack_index_compact(int argc, const char **argv, + const char *prefix, + struct repository *repo) +{ + struct multi_pack_index *m, *cur; + struct multi_pack_index *from_midx = NULL; + struct multi_pack_index *to_midx = NULL; + struct odb_source *source; + int ret; + + struct option *options; + static struct option builtin_multi_pack_index_compact_options[] = { + OPT_BIT(0, "bitmap", &opts.flags, N_("write multi-pack bitmap"), + MIDX_WRITE_BITMAP | MIDX_WRITE_REV_INDEX), + OPT_BIT(0, "incremental", &opts.flags, + N_("write a new incremental MIDX"), MIDX_WRITE_INCREMENTAL), + OPT_END(), + }; + + repo_config(repo, git_multi_pack_index_write_config, NULL); + + options = add_common_options(builtin_multi_pack_index_compact_options); + + trace2_cmd_mode(argv[0]); + + if (isatty(2)) + opts.flags |= MIDX_PROGRESS; + argc = parse_options(argc, argv, prefix, + options, builtin_multi_pack_index_compact_usage, + 0); + + if (argc != 2) + usage_with_options(builtin_multi_pack_index_compact_usage, + options); + source = handle_object_dir_option(the_repository); + + FREE_AND_NULL(options); + + m = get_multi_pack_index(source); + + for (cur = m; cur && !(from_midx && to_midx); cur = cur->base_midx) { + const char *midx_csum = midx_get_checksum_hex(cur); + + if (!from_midx && !strcmp(midx_csum, argv[0])) + from_midx = cur; + if (!to_midx && !strcmp(midx_csum, argv[1])) + to_midx = cur; + } + + if (!from_midx) + die(_("could not find MIDX: %s"), argv[0]); + if (!to_midx) + die(_("could not find MIDX: %s"), argv[1]); + if (from_midx == to_midx) + die(_("MIDX compaction endpoints must be unique")); + + for (m = from_midx; m; m = m->base_midx) { + if (m == to_midx) + die(_("MIDX %s must be an ancestor of %s"), argv[0], argv[1]); + } + + ret = write_midx_file_compact(source, from_midx, to_midx, opts.flags); + + return ret; +} + static int cmd_multi_pack_index_verify(int argc, const char **argv, const char *prefix, struct repository *repo UNUSED) { struct option *options; static struct option builtin_multi_pack_index_verify_options[] = { - OPT_BIT(0, "progress", &opts.flags, - N_("force progress reporting"), MIDX_PROGRESS), OPT_END(), }; struct odb_source *source; @@ -231,8 +306,6 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv, { struct option *options; static struct option builtin_multi_pack_index_expire_options[] = { - OPT_BIT(0, "progress", &opts.flags, - N_("force progress reporting"), MIDX_PROGRESS), OPT_END(), }; struct odb_source *source; @@ -264,8 +337,6 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv, static struct option builtin_multi_pack_index_repack_options[] = { OPT_UNSIGNED(0, "batch-size", &opts.batch_size, N_("during repack, collect pack-files of smaller size into a batch that is larger than this size")), - OPT_BIT(0, "progress", &opts.flags, - N_("force progress reporting"), MIDX_PROGRESS), OPT_END(), }; struct odb_source *source; @@ -300,6 +371,7 @@ int cmd_multi_pack_index(int argc, struct option builtin_multi_pack_index_options[] = { OPT_SUBCOMMAND("repack", &fn, cmd_multi_pack_index_repack), OPT_SUBCOMMAND("write", &fn, cmd_multi_pack_index_write), + OPT_SUBCOMMAND("compact", &fn, cmd_multi_pack_index_compact), OPT_SUBCOMMAND("verify", &fn, cmd_multi_pack_index_verify), OPT_SUBCOMMAND("expire", &fn, cmd_multi_pack_index_expire), OPT_END(),
diff --git a/builtin/mv.c b/builtin/mv.c index d439250..2215d34 100644 --- a/builtin/mv.c +++ b/builtin/mv.c
@@ -238,6 +238,7 @@ int cmd_mv(int argc, struct hashmap moved_dirs = HASHMAP_INIT(pathmap_cmp, NULL); struct strbuf pathbuf = STRBUF_INIT; int ret; + struct repo_config_values *cfg = repo_config_values(the_repository); repo_config(the_repository, git_default_config, NULL); @@ -572,7 +573,7 @@ int cmd_mv(int argc, rename_index_entry_at(the_repository->index, pos, dst); if (ignore_sparse && - core_apply_sparse_checkout && + cfg->apply_sparse_checkout && core_sparse_checkout_cone) { /* * NEEDSWORK: we are *not* paying attention to
diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 74512e5..d6594ad 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c
@@ -12,7 +12,6 @@ #include "object-name.h" #include "pager.h" #include "parse-options.h" -#include "prio-queue.h" #include "hash-lookup.h" #include "commit-slab.h" #include "commit-graph.h" @@ -178,10 +177,9 @@ static void name_rev(struct commit *start_commit, const char *tip_name, timestamp_t taggerdate, int from_tag, int deref, struct mem_pool *string_pool) { - struct prio_queue queue; + struct commit_stack stack = COMMIT_STACK_INIT; struct commit *commit; - struct commit **parents_to_queue = NULL; - size_t parents_to_queue_nr, parents_to_queue_alloc = 0; + struct commit_stack parents_to_queue = COMMIT_STACK_INIT; struct rev_name *start_name; repo_parse_commit(the_repository, start_commit); @@ -198,15 +196,14 @@ static void name_rev(struct commit *start_commit, else start_name->tip_name = mem_pool_strdup(string_pool, tip_name); - memset(&queue, 0, sizeof(queue)); /* Use the prio_queue as LIFO */ - prio_queue_put(&queue, start_commit); + commit_stack_push(&stack, start_commit); - while ((commit = prio_queue_get(&queue))) { + while ((commit = commit_stack_pop(&stack))) { struct rev_name *name = get_commit_rev_name(commit); struct commit_list *parents; int parent_number = 1; - parents_to_queue_nr = 0; + parents_to_queue.nr = 0; for (parents = commit->parents; parents; @@ -238,22 +235,18 @@ static void name_rev(struct commit *start_commit, string_pool); else parent_name->tip_name = name->tip_name; - ALLOC_GROW(parents_to_queue, - parents_to_queue_nr + 1, - parents_to_queue_alloc); - parents_to_queue[parents_to_queue_nr] = parent; - parents_to_queue_nr++; + commit_stack_push(&parents_to_queue, parent); } } - /* The first parent must come out first from the prio_queue */ - while (parents_to_queue_nr) - prio_queue_put(&queue, - parents_to_queue[--parents_to_queue_nr]); + /* The first parent must come out first from the stack */ + while (parents_to_queue.nr) + commit_stack_push(&stack, + commit_stack_pop(&parents_to_queue)); } - clear_prio_queue(&queue); - free(parents_to_queue); + commit_stack_clear(&stack); + commit_stack_clear(&parents_to_queue); } static int subpath_matches(const char *path, const char *filter) @@ -339,10 +332,9 @@ static int cmp_by_tag_and_age(const void *a_, const void *b_) return a->taggerdate != b->taggerdate; } -static int name_ref(const char *path, const char *referent UNUSED, const struct object_id *oid, - int flags UNUSED, void *cb_data) +static int name_ref(const struct reference *ref, void *cb_data) { - struct object *o = parse_object(the_repository, oid); + struct object *o = parse_object(the_repository, ref->oid); struct name_ref_data *data = cb_data; int can_abbreviate_output = data->tags_only && data->name_only; int deref = 0; @@ -350,14 +342,14 @@ static int name_ref(const char *path, const char *referent UNUSED, const struct struct commit *commit = NULL; timestamp_t taggerdate = TIME_MAX; - if (data->tags_only && !starts_with(path, "refs/tags/")) + if (data->tags_only && !starts_with(ref->name, "refs/tags/")) return 0; if (data->exclude_filters.nr) { struct string_list_item *item; for_each_string_list_item(item, &data->exclude_filters) { - if (subpath_matches(path, item->string) >= 0) + if (subpath_matches(ref->name, item->string) >= 0) return 0; } } @@ -378,7 +370,7 @@ static int name_ref(const char *path, const char *referent UNUSED, const struct * shouldn't stop when seeing 'refs/tags/v1.4' matches * 'refs/tags/v*'. We should show it as 'v1.4'. */ - switch (subpath_matches(path, item->string)) { + switch (subpath_matches(ref->name, item->string)) { case -1: /* did not match */ break; case 0: /* matched fully */ @@ -406,13 +398,13 @@ static int name_ref(const char *path, const char *referent UNUSED, const struct } if (o && o->type == OBJ_COMMIT) { commit = (struct commit *)o; - from_tag = starts_with(path, "refs/tags/"); + from_tag = starts_with(ref->name, "refs/tags/"); if (taggerdate == TIME_MAX) taggerdate = commit->date; } - add_to_tip_table(oid, path, can_abbreviate_output, commit, taggerdate, - from_tag, deref); + add_to_tip_table(ref->oid, ref->name, can_abbreviate_output, + commit, taggerdate, from_tag, deref); return 0; }
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index b5454e5..dd2480a 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c
@@ -22,17 +22,18 @@ #include "pack-objects.h" #include "progress.h" #include "refs.h" -#include "streaming.h" #include "thread-utils.h" #include "pack-bitmap.h" #include "delta-islands.h" #include "reachable.h" #include "oid-array.h" #include "strvec.h" +#include "strmap.h" #include "list.h" #include "packfile.h" #include "object-file.h" #include "odb.h" +#include "odb/streaming.h" #include "replace-object.h" #include "dir.h" #include "midx.h" @@ -41,10 +42,10 @@ #include "promisor-remote.h" #include "pack-mtimes.h" #include "parse-options.h" +#include "pkt-line.h" #include "blob.h" #include "tree.h" #include "path-walk.h" -#include "trace2.h" /* * Objects we are going to pack are collected in the `to_pack` structure. @@ -217,6 +218,7 @@ static int have_non_local_packs; static int incremental; static int ignore_packed_keep_on_disk; static int ignore_packed_keep_in_core; +static int ignore_packed_keep_in_core_open; static int ignore_packed_keep_in_core_has_cruft; static int allow_ofs_delta; static struct pack_idx_option pack_idx_opts; @@ -404,7 +406,7 @@ static unsigned long do_compress(void **pptr, unsigned long size) return stream.total_out; } -static unsigned long write_large_blob_data(struct git_istream *st, struct hashfile *f, +static unsigned long write_large_blob_data(struct odb_read_stream *st, struct hashfile *f, const struct object_id *oid) { git_zstream stream; @@ -417,7 +419,7 @@ static unsigned long write_large_blob_data(struct git_istream *st, struct hashfi for (;;) { ssize_t readlen; int zret = Z_OK; - readlen = read_istream(st, ibuf, sizeof(ibuf)); + readlen = odb_read_stream_read(st, ibuf, sizeof(ibuf)); if (readlen == -1) die(_("unable to read %s"), oid_to_hex(oid)); @@ -513,17 +515,19 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent unsigned hdrlen; enum object_type type; void *buf; - struct git_istream *st = NULL; + struct odb_read_stream *st = NULL; const unsigned hashsz = the_hash_algo->rawsz; if (!usable_delta) { if (oe_type(entry) == OBJ_BLOB && oe_size_greater_than(&to_pack, entry, repo_settings_get_big_file_threshold(the_repository)) && - (st = open_istream(the_repository, &entry->idx.oid, &type, - &size, NULL)) != NULL) + (st = odb_read_stream_open(the_repository->objects, &entry->idx.oid, + NULL)) != NULL) { buf = NULL; - else { + type = st->type; + size = st->size; + } else { buf = odb_read_object(the_repository->objects, &entry->idx.oid, &type, &size); @@ -577,7 +581,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent dheader[--pos] = 128 | (--ofs & 127); if (limit && hdrlen + sizeof(dheader) - pos + datalen + hashsz >= limit) { if (st) - close_istream(st); + odb_read_stream_close(st); free(buf); return 0; } @@ -591,7 +595,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent */ if (limit && hdrlen + hashsz + datalen + hashsz >= limit) { if (st) - close_istream(st); + odb_read_stream_close(st); free(buf); return 0; } @@ -601,7 +605,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent } else { if (limit && hdrlen + datalen + hashsz >= limit) { if (st) - close_istream(st); + odb_read_stream_close(st); free(buf); return 0; } @@ -609,7 +613,7 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent } if (st) { datalen = write_large_blob_data(st, f, &entry->idx.oid); - close_istream(st); + odb_read_stream_close(st); } else { hashwrite(f, buf, datalen); free(buf); @@ -831,15 +835,14 @@ static enum write_one_status write_one(struct hashfile *f, return WRITE_ONE_WRITTEN; } -static int mark_tagged(const char *path UNUSED, const char *referent UNUSED, const struct object_id *oid, - int flag UNUSED, void *cb_data UNUSED) +static int mark_tagged(const struct reference *ref, void *cb_data UNUSED) { struct object_id peeled; - struct object_entry *entry = packlist_find(&to_pack, oid); + struct object_entry *entry = packlist_find(&to_pack, ref->oid); if (entry) entry->tagged = 1; - if (!peel_iterated_oid(the_repository, oid, &peeled)) { + if (!reference_get_peeled_oid(the_repository, ref, &peeled)) { entry = packlist_find(&to_pack, &peeled); if (entry) entry->tagged = 1; @@ -1330,11 +1333,25 @@ static void write_pack_file(void) unsigned char hash[GIT_MAX_RAWSZ]; char *pack_tmp_name = NULL; - if (pack_to_stdout) - f = hashfd_throughput(the_repository->hash_algo, 1, - "<stdout>", progress_state); - else + if (pack_to_stdout) { + /* + * This command is most often invoked via + * git-upload-pack(1), which will typically chunk data + * into pktlines. As such, we use the maximum data + * length of them as buffer length. + * + * Note that we need to subtract one though to + * accomodate for the sideband byte. + */ + struct hashfd_options opts = { + .progress = progress_state, + .buffer_len = LARGE_PACKET_DATA_MAX - 1, + }; + f = hashfd_ext(the_repository->hash_algo, 1, + "<stdout>", &opts); + } else { f = create_tmp_packfile(the_repository, &pack_tmp_name); + } offset = write_pack_header(f, nr_remaining); @@ -1528,49 +1545,54 @@ static int want_cruft_object_mtime(struct repository *r, const struct object_id *oid, unsigned flags, uint32_t mtime) { - struct packed_git **cache; + struct odb_source *source; - for (cache = kept_pack_cache(r, flags); *cache; cache++) { - struct packed_git *p = *cache; - off_t ofs; - uint32_t candidate_mtime; + for (source = r->objects->sources; source; source = source->next) { + struct odb_source_files *files = odb_source_files_downcast(source); + struct packed_git **cache = packfile_store_get_kept_pack_cache(files->packed, flags); - ofs = find_pack_entry_one(oid, p); - if (!ofs) - continue; + for (; *cache; cache++) { + struct packed_git *p = *cache; + off_t ofs; + uint32_t candidate_mtime; - /* - * We have a copy of the object 'oid' in a non-cruft - * pack. We can avoid packing an additional copy - * regardless of what the existing copy's mtime is since - * it is outside of a cruft pack. - */ - if (!p->is_cruft) - return 0; - - /* - * If we have a copy of the object 'oid' in a cruft - * pack, then either read the cruft pack's mtime for - * that object, or, if that can't be loaded, assume the - * pack's mtime itself. - */ - if (!load_pack_mtimes(p)) { - uint32_t pos; - if (offset_to_pack_pos(p, ofs, &pos) < 0) + ofs = find_pack_entry_one(oid, p); + if (!ofs) continue; - candidate_mtime = nth_packed_mtime(p, pos); - } else { - candidate_mtime = p->mtime; - } - /* - * We have a surviving copy of the object in a cruft - * pack whose mtime is greater than or equal to the one - * we are considering. We can thus avoid packing an - * additional copy of that object. - */ - if (mtime <= candidate_mtime) - return 0; + /* + * We have a copy of the object 'oid' in a non-cruft + * pack. We can avoid packing an additional copy + * regardless of what the existing copy's mtime is since + * it is outside of a cruft pack. + */ + if (!p->is_cruft) + return 0; + + /* + * If we have a copy of the object 'oid' in a cruft + * pack, then either read the cruft pack's mtime for + * that object, or, if that can't be loaded, assume the + * pack's mtime itself. + */ + if (!load_pack_mtimes(p)) { + uint32_t pos; + if (offset_to_pack_pos(p, ofs, &pos) < 0) + continue; + candidate_mtime = nth_packed_mtime(p, pos); + } else { + candidate_mtime = p->mtime; + } + + /* + * We have a surviving copy of the object in a cruft + * pack whose mtime is greater than or equal to the one + * we are considering. We can thus avoid packing an + * additional copy of that object. + */ + if (mtime <= candidate_mtime) + return 0; + } } return -1; @@ -1612,7 +1634,8 @@ static int want_found_object(const struct object_id *oid, int exclude, /* * Then handle .keep first, as we have a fast(er) path there. */ - if (ignore_packed_keep_on_disk || ignore_packed_keep_in_core) { + if (ignore_packed_keep_on_disk || ignore_packed_keep_in_core || + ignore_packed_keep_in_core_open) { /* * Set the flags for the kept-pack cache to be the ones we want * to ignore. @@ -1623,9 +1646,11 @@ static int want_found_object(const struct object_id *oid, int exclude, */ unsigned flags = 0; if (ignore_packed_keep_on_disk) - flags |= ON_DISK_KEEP_PACKS; + flags |= KEPT_PACK_ON_DISK; if (ignore_packed_keep_in_core) - flags |= IN_CORE_KEEP_PACKS; + flags |= KEPT_PACK_IN_CORE; + if (ignore_packed_keep_in_core_open) + flags |= KEPT_PACK_IN_CORE_OPEN; /* * If the object is in a pack that we want to ignore, *and* we @@ -1637,6 +1662,8 @@ static int want_found_object(const struct object_id *oid, int exclude, return 0; if (ignore_packed_keep_in_core && p->pack_keep_in_core) return 0; + if (ignore_packed_keep_in_core_open && p->pack_keep_in_core_open) + return 0; if (has_object_kept_pack(p->repo, oid, flags)) return 0; } else { @@ -1706,8 +1733,8 @@ static int want_object_in_pack_mtime(const struct object_id *oid, uint32_t found_mtime) { int want; + struct packfile_list_entry *e; struct odb_source *source; - struct list_head *pos; if (!exclude && local) { /* @@ -1716,7 +1743,7 @@ static int want_object_in_pack_mtime(const struct object_id *oid, */ struct odb_source *source = the_repository->objects->sources->next; for (; source; source = source->next) - if (has_loose_object(source, oid)) + if (odb_source_loose_has_object(source, oid)) return 0; } @@ -1748,14 +1775,17 @@ static int want_object_in_pack_mtime(const struct object_id *oid, } } - list_for_each(pos, packfile_store_get_packs_mru(the_repository->objects->packfiles)) { - struct packed_git *p = list_entry(pos, struct packed_git, mru); - want = want_object_in_pack_one(p, oid, exclude, found_pack, found_offset, found_mtime); - if (!exclude && want > 0) - list_move(&p->mru, - packfile_store_get_packs_mru(the_repository->objects->packfiles)); - if (want != -1) - return want; + for (source = the_repository->objects->sources; source; source = source->next) { + struct odb_source_files *files = odb_source_files_downcast(source); + + for (e = files->packed->packs.head; e; e = e->next) { + struct packed_git *p = e->pack; + want = want_object_in_pack_one(p, oid, exclude, found_pack, found_offset, found_mtime); + if (!exclude && want > 0) + packfile_list_prepend(&files->packed->packs, p); + if (want != -1) + return want; + } } if (uri_protocols.nr) { @@ -2411,7 +2441,7 @@ static void drop_reused_delta(struct object_entry *entry) oi.sizep = &size; oi.typep = &type; - if (packed_object_info(the_repository, IN_PACK(entry), entry->in_pack_offset, &oi) < 0) { + if (packed_object_info(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. @@ -3293,7 +3323,7 @@ static void add_tag_chain(const struct object_id *oid) tag = lookup_tag(the_repository, oid); while (1) { - if (!tag || parse_tag(tag) || !tag->tagged) + if (!tag || parse_tag(the_repository, tag) || !tag->tagged) die(_("unable to pack objects reachable from tag %s"), oid_to_hex(oid)); @@ -3306,13 +3336,13 @@ static void add_tag_chain(const struct object_id *oid) } } -static int add_ref_tag(const char *tag UNUSED, const char *referent UNUSED, const struct object_id *oid, - int flag UNUSED, void *cb_data UNUSED) +static int add_ref_tag(const struct reference *ref, void *cb_data UNUSED) { struct object_id peeled; - if (!peel_iterated_oid(the_repository, oid, &peeled) && obj_is_packed(&peeled)) - add_tag_chain(oid); + if (!reference_get_peeled_oid(the_repository, ref, &peeled) && + obj_is_packed(&peeled)) + add_tag_chain(ref->oid); return 0; } @@ -3733,6 +3763,7 @@ static int add_object_entry_from_pack(const struct object_id *oid, void *_data) { off_t ofs; + struct object_info oi = OBJECT_INFO_INIT; enum object_type type = OBJ_NONE; display_progress(progress_state, ++nr_seen); @@ -3740,29 +3771,34 @@ static int add_object_entry_from_pack(const struct object_id *oid, if (have_duplicate_entry(oid, 0)) return 0; + stdin_packs_found_nr++; + ofs = nth_packed_object_offset(p, pos); + + oi.typep = &type; + if (packed_object_info(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) { + struct rev_info *revs = _data; + /* + * commits in included packs are used as starting points + * for the subsequent revision walk + * + * Note that we do want to walk through commits that are + * present in excluded-open ('!') packs to pick up any + * objects reachable from them not present in the + * excluded-closed ('^') packs. + * + * However, we'll only add those objects to the packing + * list after checking `want_object_in_pack()` below. + */ + add_pending_oid(revs, NULL, oid, 0); + } + if (!want_object_in_pack(oid, 0, &p, &ofs)) return 0; - if (p) { - struct object_info oi = OBJECT_INFO_INIT; - - oi.typep = &type; - if (packed_object_info(the_repository, 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) { - struct rev_info *revs = _data; - /* - * commits in included packs are used as starting points for the - * subsequent revision walk - */ - add_pending_oid(revs, NULL, oid, 0); - } - - stdin_packs_found_nr++; - } - create_object_entry(oid, type, 0, 0, 0, p, ofs); return 0; @@ -3812,86 +3848,78 @@ static void show_commit_pack_hint(struct commit *commit, void *data) } +/* + * stdin_pack_info_kind specifies how a pack specified over stdin + * should be treated when pack-objects is invoked with --stdin-packs. + * + * - STDIN_PACK_INCLUDE: objects in any packs with this flag bit set + * should be included in the output pack, unless they appear in an + * excluded pack. + * + * - STDIN_PACK_EXCLUDE_CLOSED: objects in any packs with this flag + * bit set should be excluded from the output pack. + * + * - STDIN_PACK_EXCLUDE_OPEN: objects in any packs with this flag + * bit set should be excluded from the output pack, but are not + * guaranteed to be closed under reachability. + * + * Objects in packs whose 'kind' bits include STDIN_PACK_INCLUDE or + * STDIN_PACK_EXCLUDE_OPEN are used as traversal tips when invoked + * with --stdin-packs=follow. + */ +enum stdin_pack_info_kind { + STDIN_PACK_INCLUDE = (1<<0), + STDIN_PACK_EXCLUDE_CLOSED = (1<<1), + STDIN_PACK_EXCLUDE_OPEN = (1<<2), +}; + +struct stdin_pack_info { + struct packed_git *p; + enum stdin_pack_info_kind kind; +}; + static int pack_mtime_cmp(const void *_a, const void *_b) { - struct packed_git *a = ((const struct string_list_item*)_a)->util; - struct packed_git *b = ((const struct string_list_item*)_b)->util; + struct stdin_pack_info *a = ((const struct string_list_item*)_a)->util; + struct stdin_pack_info *b = ((const struct string_list_item*)_b)->util; /* * order packs by descending mtime so that objects are laid out * roughly as newest-to-oldest */ - if (a->mtime < b->mtime) + if (a->p->mtime < b->p->mtime) return 1; - else if (b->mtime < a->mtime) + else if (b->p->mtime < a->p->mtime) return -1; else return 0; } -static void read_packs_list_from_stdin(struct rev_info *revs) +static int stdin_packs_include_check_obj(struct object *obj, void *data UNUSED) { - struct strbuf buf = STRBUF_INIT; - struct string_list include_packs = STRING_LIST_INIT_DUP; - struct string_list exclude_packs = STRING_LIST_INIT_DUP; - struct string_list_item *item = NULL; - struct packed_git *p; + return !has_object_kept_pack(to_pack.repo, &obj->oid, + KEPT_PACK_IN_CORE); +} - while (strbuf_getline(&buf, stdin) != EOF) { - if (!buf.len) - continue; +static int stdin_packs_include_check(struct commit *commit, void *data) +{ + return stdin_packs_include_check_obj((struct object *)commit, data); +} - if (*buf.buf == '^') - string_list_append(&exclude_packs, buf.buf + 1); - else - string_list_append(&include_packs, buf.buf); +static void stdin_packs_add_pack_entries(struct strmap *packs, + struct rev_info *revs) +{ + struct string_list keys = STRING_LIST_INIT_NODUP; + struct string_list_item *item; + struct hashmap_iter iter; + struct strmap_entry *entry; - strbuf_reset(&buf); - } + strmap_for_each_entry(packs, &iter, entry) { + struct stdin_pack_info *info = entry->value; + if (!info->p) + die(_("could not find pack '%s'"), entry->key); - string_list_sort(&include_packs); - string_list_remove_duplicates(&include_packs, 0); - string_list_sort(&exclude_packs); - string_list_remove_duplicates(&exclude_packs, 0); - - repo_for_each_pack(the_repository, p) { - const char *pack_name = pack_basename(p); - - if ((item = string_list_lookup(&include_packs, pack_name))) - item->util = p; - if ((item = string_list_lookup(&exclude_packs, pack_name))) - item->util = p; - } - - /* - * Arguments we got on stdin may not even be packs. First - * check that to avoid segfaulting later on in - * e.g. pack_mtime_cmp(), excluded packs are handled below. - * - * Since we first parsed our STDIN and then sorted the input - * lines the pack we error on will be whatever line happens to - * sort first. This is lazy, it's enough that we report one - * bad case here, we don't need to report the first/last one, - * or all of them. - */ - for_each_string_list_item(item, &include_packs) { - struct packed_git *p = item->util; - if (!p) - die(_("could not find pack '%s'"), item->string); - if (!is_pack_valid(p)) - die(_("packfile %s cannot be accessed"), p->pack_name); - } - - /* - * Then, handle all of the excluded packs, marking them as - * kept in-core so that later calls to add_object_entry() - * discards any objects that are also found in excluded packs. - */ - for_each_string_list_item(item, &exclude_packs) { - struct packed_git *p = item->util; - if (!p) - die(_("could not find pack '%s'"), item->string); - p->pack_keep_in_core = 1; + string_list_append(&keys, entry->key)->util = info; } /* @@ -3899,27 +3927,134 @@ static void read_packs_list_from_stdin(struct rev_info *revs) * string_list_item's ->util pointer, which string_list_sort() does not * provide. */ - QSORT(include_packs.items, include_packs.nr, pack_mtime_cmp); + QSORT(keys.items, keys.nr, pack_mtime_cmp); - for_each_string_list_item(item, &include_packs) { - struct packed_git *p = item->util; - for_each_object_in_pack(p, - add_object_entry_from_pack, - revs, - FOR_EACH_OBJECT_PACK_ORDER); + for_each_string_list_item(item, &keys) { + struct stdin_pack_info *info = item->util; + + if (info->kind & STDIN_PACK_EXCLUDE_OPEN) { + /* + * When open-excluded packs ("!") are present, stop + * the parent walk at closed-excluded ("^") packs. + * Objects behind a "^" boundary are guaranteed to + * have closure and should not be rescued. + */ + revs->include_check = stdin_packs_include_check; + revs->include_check_obj = stdin_packs_include_check_obj; + } + + if ((info->kind & STDIN_PACK_INCLUDE) || + (info->kind & STDIN_PACK_EXCLUDE_OPEN)) + for_each_object_in_pack(info->p, + add_object_entry_from_pack, + revs, + ODB_FOR_EACH_OBJECT_PACK_ORDER); } + string_list_clear(&keys, 0); +} + +static void stdin_packs_read_input(struct rev_info *revs, + enum stdin_packs_mode mode) +{ + struct strbuf buf = STRBUF_INIT; + struct strmap packs = STRMAP_INIT; + struct packed_git *p; + + while (strbuf_getline(&buf, stdin) != EOF) { + struct stdin_pack_info *info; + enum stdin_pack_info_kind kind = STDIN_PACK_INCLUDE; + const char *key = buf.buf; + + if (!*key) + continue; + else if (*key == '^') + kind = STDIN_PACK_EXCLUDE_CLOSED; + else if (*key == '!' && mode == STDIN_PACKS_MODE_FOLLOW) + kind = STDIN_PACK_EXCLUDE_OPEN; + + if (kind != STDIN_PACK_INCLUDE) + key++; + + info = strmap_get(&packs, key); + if (!info) { + CALLOC_ARRAY(info, 1); + strmap_put(&packs, key, info); + } + + info->kind |= kind; + + strbuf_reset(&buf); + } + + repo_for_each_pack(the_repository, p) { + struct stdin_pack_info *info; + + info = strmap_get(&packs, pack_basename(p)); + if (!info) + continue; + + if (info->kind & STDIN_PACK_INCLUDE) { + if (exclude_promisor_objects && p->pack_promisor) + die(_("packfile %s is a promisor but --exclude-promisor-objects was given"), p->pack_name); + + /* + * Arguments we got on stdin may not even be + * packs. First check that to avoid segfaulting + * later on in e.g. pack_mtime_cmp(), excluded + * packs are handled below. + */ + if (!is_pack_valid(p)) + die(_("packfile %s cannot be accessed"), p->pack_name); + } + + if (info->kind & STDIN_PACK_EXCLUDE_CLOSED) { + /* + * Marking excluded packs as kept in-core so + * that later calls to add_object_entry() + * discards any objects that are also found in + * excluded packs. + */ + p->pack_keep_in_core = 1; + } + + if (info->kind & STDIN_PACK_EXCLUDE_OPEN) { + /* + * Marking excluded open packs as kept in-core + * (open) for the same reason as we marked + * exclude closed packs as kept in-core. + * + * Use a separate flag here to ensure we don't + * halt our traversal at these packs, since they + * are not guaranteed to have closure. + * + */ + p->pack_keep_in_core_open = 1; + } + + info->p = p; + } + + stdin_packs_add_pack_entries(&packs, revs); + strbuf_release(&buf); - string_list_clear(&include_packs, 0); - string_list_clear(&exclude_packs, 0); + strmap_clear(&packs, 1); } static void add_unreachable_loose_objects(struct rev_info *revs); static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked) { + int prev_fetch_if_missing = fetch_if_missing; struct rev_info revs; + /* + * The revision walk may hit objects that are promised, only. As the + * walk is best-effort though we don't want to perform backfill fetches + * for them. + */ + fetch_if_missing = 0; + repo_init_revisions(the_repository, &revs, NULL); /* * Use a revision walk to fill in the namehash of objects in the include @@ -3931,15 +4066,24 @@ static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked) * an optimization during delta selection. */ revs.no_kept_objects = 1; - revs.keep_pack_cache_flags |= IN_CORE_KEEP_PACKS; + revs.keep_pack_cache_flags |= KEPT_PACK_IN_CORE; revs.blob_objects = 1; revs.tree_objects = 1; revs.tag_objects = 1; revs.ignore_missing_links = 1; + revs.exclude_promisor_objects = exclude_promisor_objects; /* avoids adding objects in excluded packs */ ignore_packed_keep_in_core = 1; - read_packs_list_from_stdin(&revs); + if (mode == STDIN_PACKS_MODE_FOLLOW) { + /* + * In '--stdin-packs=follow' mode, additionally ignore + * objects in excluded-open packs to prevent them from + * appearing in the resulting pack. + */ + ignore_packed_keep_in_core_open = 1; + } + stdin_packs_read_input(&revs, mode); if (rev_list_unpacked) add_unreachable_loose_objects(&revs); @@ -3950,10 +4094,14 @@ static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked) show_object_pack_hint, &mode); + release_revisions(&revs); + trace2_data_intmax("pack-objects", the_repository, "stdin_packs_found", stdin_packs_found_nr); trace2_data_intmax("pack-objects", the_repository, "stdin_packs_hints", stdin_packs_hints_nr); + + fetch_if_missing = prev_fetch_if_missing; } static void add_cruft_object_entry(const struct object_id *oid, enum object_type type, @@ -3978,7 +4126,7 @@ static void add_cruft_object_entry(const struct object_id *oid, enum object_type int found = 0; for (; !found && source; source = source->next) - if (has_loose_object(source, oid)) + if (odb_source_loose_has_object(source, oid)) found = 1; /* @@ -4030,7 +4178,7 @@ static void show_cruft_commit(struct commit *commit, void *data) static int cruft_include_check_obj(struct object *obj, void *data UNUSED) { - return !has_object_kept_pack(to_pack.repo, &obj->oid, IN_CORE_KEEP_PACKS); + return !has_object_kept_pack(to_pack.repo, &obj->oid, KEPT_PACK_IN_CORE); } static int cruft_include_check(struct commit *commit, void *data) @@ -4308,25 +4456,12 @@ static void show_edge(struct commit *commit) } static int add_object_in_unpacked_pack(const struct object_id *oid, - struct packed_git *pack, - uint32_t pos, + struct object_info *oi, void *data UNUSED) { if (cruft) { - off_t offset; - time_t mtime; - - if (pack->is_cruft) { - if (load_pack_mtimes(pack) < 0) - die(_("could not load cruft pack .mtimes")); - mtime = nth_packed_mtime(pack, pos); - } else { - mtime = pack->mtime; - } - offset = nth_packed_object_offset(pack, pos); - - add_cruft_object_entry(oid, OBJ_NONE, pack, offset, - NULL, mtime); + add_cruft_object_entry(oid, OBJ_NONE, oi->u.packed.pack, + oi->u.packed.offset, NULL, *oi->mtimep); } else { add_object_entry(oid, OBJ_NONE, "", 0); } @@ -4335,14 +4470,29 @@ static int add_object_in_unpacked_pack(const struct object_id *oid, static void add_objects_in_unpacked_packs(void) { - if (for_each_packed_object(to_pack.repo, - add_object_in_unpacked_pack, - NULL, - FOR_EACH_OBJECT_PACK_ORDER | - FOR_EACH_OBJECT_LOCAL_ONLY | - FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS | - FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS)) - die(_("cannot open pack index")); + struct odb_source *source; + time_t mtime; + struct odb_for_each_object_options opts = { + .flags = ODB_FOR_EACH_OBJECT_PACK_ORDER | + ODB_FOR_EACH_OBJECT_LOCAL_ONLY | + ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS | + ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS, + }; + struct object_info oi = { + .mtimep = &mtime, + }; + + odb_prepare_alternates(to_pack.repo->objects); + for (source = to_pack.repo->objects->sources; source; source = source->next) { + struct odb_source_files *files = odb_source_files_downcast(source); + + if (!source->local) + continue; + + if (packfile_store_for_each_object(files->packed, &oi, + add_object_in_unpacked_pack, NULL, &opts)) + die(_("cannot open pack index")); + } } static int add_loose_object(const struct object_id *oid, const char *path, @@ -4389,27 +4539,27 @@ static void add_unreachable_loose_objects(struct rev_info *revs) static int has_sha1_pack_kept_or_nonlocal(const struct object_id *oid) { - struct packfile_store *packs = the_repository->objects->packfiles; - static struct packed_git *last_found = (void *)1; + static struct packed_git *last_found = NULL; struct packed_git *p; - p = (last_found != (void *)1) ? last_found : - packfile_store_get_packs(packs); + if (last_found && find_pack_entry_one(oid, last_found)) + return 1; - while (p) { - if ((!p->pack_local || p->pack_keep || - p->pack_keep_in_core) && - find_pack_entry_one(oid, p)) { + repo_for_each_pack(the_repository, p) { + /* + * We have already checked `last_found`, so there is no need to + * re-check here. + */ + if (p == last_found) + continue; + + if ((!p->pack_local || p->pack_keep || p->pack_keep_in_core) && + find_pack_entry_one(oid, p)) { last_found = p; return 1; } - if (p == last_found) - p = packfile_store_get_packs(packs); - else - p = p->next; - if (p == last_found) - p = p->next; } + return 0; } @@ -4528,41 +4678,22 @@ static void record_recent_commit(struct commit *commit, void *data UNUSED) oid_array_append(&recent_objects, &commit->object.oid); } -static int mark_bitmap_preferred_tip(const char *refname, - const char *referent UNUSED, - const struct object_id *oid, - int flags UNUSED, - void *data UNUSED) +static int mark_bitmap_preferred_tip(const struct reference *ref, void *data UNUSED) { + const struct object_id *maybe_peeled = ref->oid; struct object_id peeled; struct object *object; - if (!peel_iterated_oid(the_repository, oid, &peeled)) - oid = &peeled; + if (!reference_get_peeled_oid(the_repository, ref, &peeled)) + maybe_peeled = &peeled; - object = parse_object_or_die(the_repository, oid, refname); + object = parse_object_or_die(the_repository, maybe_peeled, ref->name); if (object->type == OBJ_COMMIT) object->flags |= NEEDS_BITMAP; return 0; } -static void mark_bitmap_preferred_tips(void) -{ - struct string_list_item *item; - const struct string_list *preferred_tips; - - preferred_tips = bitmap_preferred_tips(the_repository); - if (!preferred_tips) - return; - - for_each_string_list_item(item, preferred_tips) { - refs_for_each_ref_in(get_main_ref_store(the_repository), - item->string, mark_bitmap_preferred_tip, - NULL); - } -} - static inline int is_oid_uninteresting(struct repository *repo, struct object_id *oid) { @@ -4703,7 +4834,8 @@ static void get_object_list(struct rev_info *revs, struct strvec *argv) load_delta_islands(the_repository, progress); if (write_bitmap_index) - mark_bitmap_preferred_tips(); + for_each_preferred_bitmap_tip(the_repository, mark_bitmap_preferred_tip, + NULL); if (!fn_show_object) fn_show_object = show_object; @@ -4925,8 +5057,6 @@ int cmd_pack_objects(int argc, OPT_CALLBACK_F(0, "stdin-packs", &stdin_packs, N_("mode"), N_("read packs from stdin"), PARSE_OPT_OPTARG, parse_stdin_packs_mode), - OPT_BOOL(0, "stdin-packs", &stdin_packs, - N_("read packs from stdin")), OPT_BOOL(0, "stdout", &pack_to_stdout, N_("output pack to stdout")), OPT_BOOL(0, "include-tag", &include_tag, @@ -5095,9 +5225,13 @@ int cmd_pack_objects(int argc, exclude_promisor_objects_best_effort, "--exclude-promisor-objects-best-effort"); if (exclude_promisor_objects) { - use_internal_rev_list = 1; fetch_if_missing = 0; - strvec_push(&rp, "--exclude-promisor-objects"); + + /* --stdin-packs handles promisor objects separately. */ + if (!stdin_packs) { + use_internal_rev_list = 1; + strvec_push(&rp, "--exclude-promisor-objects"); + } } else if (exclude_promisor_objects_best_effort) { use_internal_rev_list = 1; fetch_if_missing = 0;
diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c index e4ecf77..86749bb 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c
@@ -546,8 +546,10 @@ static struct pack_list * add_pack(struct packed_git *p) l.pack = p; llist_init(&l.remaining_objects); - if (open_pack_index(p)) + if (open_pack_index(p)) { + llist_free(l.remaining_objects); return NULL; + } base = p->index_data; base += 256 * 4 + ((p->index_version < 2) ? 4 : 8);
diff --git a/builtin/patch-id.c b/builtin/patch-id.c index d26e9d0..2781598 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c
@@ -228,9 +228,9 @@ int cmd_patch_id(int argc, int opts = 0; struct option builtin_patch_id_options[] = { OPT_CMDMODE(0, "unstable", &opts, - N_("use the unstable patch-id algorithm"), 1), + N_("use the unstable patch ID algorithm"), 1), OPT_CMDMODE(0, "stable", &opts, - N_("use the stable patch-id algorithm"), 2), + N_("use the stable patch ID algorithm"), 2), OPT_CMDMODE(0, "verbatim", &opts, N_("don't strip whitespace from the patch"), 3), OPT_END()
diff --git a/builtin/pull.c b/builtin/pull.c index 5ebd529..7e67fdc 100644 --- a/builtin/pull.c +++ b/builtin/pull.c
@@ -119,148 +119,6 @@ static int opt_show_forced_updates = -1; static const char *set_upstream; static struct strvec opt_fetch = STRVEC_INIT; -static struct option pull_options[] = { - /* Shared options */ - OPT__VERBOSITY(&opt_verbosity), - OPT_PASSTHRU(0, "progress", &opt_progress, NULL, - N_("force progress reporting"), - PARSE_OPT_NOARG), - OPT_CALLBACK_F(0, "recurse-submodules", - &recurse_submodules_cli, N_("on-demand"), - N_("control for recursive fetching of submodules"), - PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules), - - /* Options passed to git-merge or git-rebase */ - OPT_GROUP(N_("Options related to merging")), - OPT_CALLBACK_F('r', "rebase", &opt_rebase, - "(false|true|merges|interactive)", - N_("incorporate changes by rebasing rather than merging"), - PARSE_OPT_OPTARG, parse_opt_rebase), - OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL, - N_("do not show a diffstat at the end of the merge"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG), - OPT_PASSTHRU(0, "stat", &opt_diffstat, NULL, - N_("show a diffstat at the end of the merge"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "summary", &opt_diffstat, NULL, - N_("(synonym to --stat)"), - PARSE_OPT_NOARG | PARSE_OPT_HIDDEN), - OPT_PASSTHRU(0, "compact-summary", &opt_diffstat, NULL, - N_("show a compact-summary at the end of the merge"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "log", &opt_log, N_("n"), - N_("add (at most <n>) entries from shortlog to merge commit message"), - PARSE_OPT_OPTARG), - OPT_PASSTHRU(0, "signoff", &opt_signoff, NULL, - N_("add a Signed-off-by trailer"), - PARSE_OPT_OPTARG), - OPT_PASSTHRU(0, "squash", &opt_squash, NULL, - N_("create a single commit instead of doing a merge"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "commit", &opt_commit, NULL, - N_("perform a commit if the merge succeeds (default)"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "edit", &opt_edit, NULL, - N_("edit message before committing"), - PARSE_OPT_NOARG), - OPT_CLEANUP(&cleanup_arg), - OPT_PASSTHRU(0, "ff", &opt_ff, NULL, - N_("allow fast-forward"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "ff-only", &opt_ff, NULL, - N_("abort if fast-forward is not possible"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG), - OPT_PASSTHRU(0, "verify", &opt_verify, NULL, - N_("control use of pre-merge-commit and commit-msg hooks"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "verify-signatures", &opt_verify_signatures, NULL, - N_("verify that the named commit has a valid GPG signature"), - PARSE_OPT_NOARG), - OPT_BOOL(0, "autostash", &opt_autostash, - N_("automatically stash/stash pop before and after")), - OPT_PASSTHRU_ARGV('s', "strategy", &opt_strategies, N_("strategy"), - N_("merge strategy to use"), - 0), - OPT_PASSTHRU_ARGV('X', "strategy-option", &opt_strategy_opts, - N_("option=value"), - N_("option for selected merge strategy"), - 0), - OPT_PASSTHRU('S', "gpg-sign", &opt_gpg_sign, N_("key-id"), - N_("GPG sign commit"), - PARSE_OPT_OPTARG), - OPT_SET_INT(0, "allow-unrelated-histories", - &opt_allow_unrelated_histories, - N_("allow merging unrelated histories"), 1), - - /* Options passed to git-fetch */ - OPT_GROUP(N_("Options related to fetching")), - OPT_PASSTHRU(0, "all", &opt_all, NULL, - N_("fetch from all remotes"), - PARSE_OPT_NOARG), - OPT_PASSTHRU('a', "append", &opt_append, NULL, - N_("append to .git/FETCH_HEAD instead of overwriting"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "upload-pack", &opt_upload_pack, N_("path"), - N_("path to upload pack on remote end"), - 0), - OPT__FORCE(&opt_force, N_("force overwrite of local branch"), 0), - OPT_PASSTHRU('t', "tags", &opt_tags, NULL, - N_("fetch all tags and associated objects"), - PARSE_OPT_NOARG), - OPT_PASSTHRU('p', "prune", &opt_prune, NULL, - N_("prune remote-tracking branches no longer on remote"), - PARSE_OPT_NOARG), - OPT_PASSTHRU('j', "jobs", &max_children, N_("n"), - N_("number of submodules pulled in parallel"), - PARSE_OPT_OPTARG), - OPT_BOOL(0, "dry-run", &opt_dry_run, - N_("dry run")), - OPT_PASSTHRU('k', "keep", &opt_keep, NULL, - N_("keep downloaded pack"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "depth", &opt_depth, N_("depth"), - N_("deepen history of shallow clone"), - 0), - OPT_PASSTHRU_ARGV(0, "shallow-since", &opt_fetch, N_("time"), - N_("deepen history of shallow repository based on time"), - 0), - OPT_PASSTHRU_ARGV(0, "shallow-exclude", &opt_fetch, N_("ref"), - N_("deepen history of shallow clone, excluding ref"), - 0), - OPT_PASSTHRU_ARGV(0, "deepen", &opt_fetch, N_("n"), - N_("deepen history of shallow clone"), - 0), - OPT_PASSTHRU(0, "unshallow", &opt_unshallow, NULL, - N_("convert to a complete repository"), - PARSE_OPT_NONEG | PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "update-shallow", &opt_update_shallow, NULL, - N_("accept refs that update .git/shallow"), - PARSE_OPT_NOARG), - OPT_PASSTHRU(0, "refmap", &opt_refmap, N_("refmap"), - N_("specify fetch refmap"), - PARSE_OPT_NONEG), - OPT_PASSTHRU_ARGV('o', "server-option", &opt_fetch, - N_("server-specific"), - N_("option to transmit"), - 0), - OPT_PASSTHRU('4', "ipv4", &opt_ipv4, NULL, - N_("use IPv4 addresses only"), - PARSE_OPT_NOARG), - OPT_PASSTHRU('6', "ipv6", &opt_ipv6, NULL, - N_("use IPv6 addresses only"), - PARSE_OPT_NOARG), - OPT_PASSTHRU_ARGV(0, "negotiation-tip", &opt_fetch, N_("revision"), - N_("report that we have only objects reachable from this object"), - 0), - OPT_BOOL(0, "show-forced-updates", &opt_show_forced_updates, - N_("check for forced-updates on all updated branches")), - OPT_PASSTHRU(0, "set-upstream", &set_upstream, NULL, - N_("set upstream for git pull/fetch"), - PARSE_OPT_NOARG), - - OPT_END() -}; - /** * Pushes "-q" or "-v" switches into arr to match the opt_verbosity level. */ @@ -596,7 +454,7 @@ static int run_fetch(const char *repo, const char **refspecs) } else if (*refspecs) BUG("refspecs without repo?"); cmd.git_cmd = 1; - cmd.close_object_store = 1; + cmd.odb_to_close = the_repository->objects; return run_command(&cmd); } @@ -846,14 +704,14 @@ static int get_octopus_merge_base(struct object_id *merge_base, if (get_octopus_merge_bases(revs, &result) < 0) exit(128); - free_commit_list(revs); + commit_list_free(revs); reduce_heads_replace(&result); if (!result) return 1; oidcpy(merge_base, &result->item->object.oid); - free_commit_list(result); + commit_list_free(result); return 0; } @@ -945,7 +803,7 @@ static int get_can_ff(struct object_id *orig_head, commit_list_insert(head, &list); merge_head = lookup_commit_reference(the_repository, orig_merge_head); ret = repo_is_descendant_of(the_repository, merge_head, list); - free_commit_list(list); + commit_list_free(list); if (ret < 0) exit(128); return ret; @@ -970,7 +828,7 @@ static int already_up_to_date(struct object_id *orig_head, theirs = lookup_commit_reference(the_repository, &merge_heads->oid[i]); commit_list_insert(theirs, &list); ok = repo_is_descendant_of(the_repository, ours, list); - free_commit_list(list); + commit_list_free(list); if (ok < 0) exit(128); if (!ok) @@ -1008,6 +866,147 @@ int cmd_pull(int argc, int can_ff; int divergent; int ret; + static struct option pull_options[] = { + /* Shared options */ + OPT__VERBOSITY(&opt_verbosity), + OPT_PASSTHRU(0, "progress", &opt_progress, NULL, + N_("force progress reporting"), + PARSE_OPT_NOARG), + OPT_CALLBACK_F(0, "recurse-submodules", + &recurse_submodules_cli, N_("on-demand"), + N_("control for recursive fetching of submodules"), + PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules), + + /* Options passed to git-merge or git-rebase */ + OPT_GROUP(N_("Options related to merging")), + OPT_CALLBACK_F('r', "rebase", &opt_rebase, + "(false|true|merges|interactive)", + N_("incorporate changes by rebasing rather than merging"), + PARSE_OPT_OPTARG, parse_opt_rebase), + OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL, + N_("do not show a diffstat at the end of the merge"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG), + OPT_PASSTHRU(0, "stat", &opt_diffstat, NULL, + N_("show a diffstat at the end of the merge"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "summary", &opt_diffstat, NULL, + N_("(synonym to --stat)"), + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN), + OPT_PASSTHRU(0, "compact-summary", &opt_diffstat, NULL, + N_("show a compact-summary at the end of the merge"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "log", &opt_log, N_("n"), + N_("add (at most <n>) entries from shortlog to merge commit message"), + PARSE_OPT_OPTARG), + OPT_PASSTHRU(0, "signoff", &opt_signoff, NULL, + N_("add a Signed-off-by trailer"), + PARSE_OPT_OPTARG), + OPT_PASSTHRU(0, "squash", &opt_squash, NULL, + N_("create a single commit instead of doing a merge"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "commit", &opt_commit, NULL, + N_("perform a commit if the merge succeeds (default)"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "edit", &opt_edit, NULL, + N_("edit message before committing"), + PARSE_OPT_NOARG), + OPT_CLEANUP(&cleanup_arg), + OPT_PASSTHRU(0, "ff", &opt_ff, NULL, + N_("allow fast-forward"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "ff-only", &opt_ff, NULL, + N_("abort if fast-forward is not possible"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG), + OPT_PASSTHRU(0, "verify", &opt_verify, NULL, + N_("control use of pre-merge-commit and commit-msg hooks"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "verify-signatures", &opt_verify_signatures, NULL, + N_("verify that the named commit has a valid GPG signature"), + PARSE_OPT_NOARG), + OPT_BOOL(0, "autostash", &opt_autostash, + N_("automatically stash/stash pop before and after")), + OPT_PASSTHRU_ARGV('s', "strategy", &opt_strategies, N_("strategy"), + N_("merge strategy to use"), + 0), + OPT_PASSTHRU_ARGV('X', "strategy-option", &opt_strategy_opts, + N_("option=value"), + N_("option for selected merge strategy"), + 0), + OPT_PASSTHRU('S', "gpg-sign", &opt_gpg_sign, N_("key-id"), + N_("GPG sign commit"), + PARSE_OPT_OPTARG), + OPT_SET_INT(0, "allow-unrelated-histories", + &opt_allow_unrelated_histories, + N_("allow merging unrelated histories"), 1), + + /* Options passed to git-fetch */ + OPT_GROUP(N_("Options related to fetching")), + OPT_PASSTHRU(0, "all", &opt_all, NULL, + N_("fetch from all remotes"), + PARSE_OPT_NOARG), + OPT_PASSTHRU('a', "append", &opt_append, NULL, + N_("append to .git/FETCH_HEAD instead of overwriting"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "upload-pack", &opt_upload_pack, N_("path"), + N_("path to upload pack on remote end"), + 0), + OPT__FORCE(&opt_force, N_("force overwrite of local branch"), 0), + OPT_PASSTHRU('t', "tags", &opt_tags, NULL, + N_("fetch all tags and associated objects"), + PARSE_OPT_NOARG), + OPT_PASSTHRU('p', "prune", &opt_prune, NULL, + N_("prune remote-tracking branches no longer on remote"), + PARSE_OPT_NOARG), + OPT_PASSTHRU('j', "jobs", &max_children, N_("n"), + N_("number of submodules pulled in parallel"), + PARSE_OPT_OPTARG), + OPT_BOOL(0, "dry-run", &opt_dry_run, + N_("dry run")), + OPT_PASSTHRU('k', "keep", &opt_keep, NULL, + N_("keep downloaded pack"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "depth", &opt_depth, N_("depth"), + N_("deepen history of shallow clone"), + 0), + OPT_PASSTHRU_ARGV(0, "shallow-since", &opt_fetch, N_("time"), + N_("deepen history of shallow repository based on time"), + 0), + OPT_PASSTHRU_ARGV(0, "shallow-exclude", &opt_fetch, N_("ref"), + N_("deepen history of shallow clone, excluding ref"), + 0), + OPT_PASSTHRU_ARGV(0, "deepen", &opt_fetch, N_("n"), + N_("deepen history of shallow clone"), + 0), + OPT_PASSTHRU(0, "unshallow", &opt_unshallow, NULL, + N_("convert to a complete repository"), + PARSE_OPT_NONEG | PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "update-shallow", &opt_update_shallow, NULL, + N_("accept refs that update .git/shallow"), + PARSE_OPT_NOARG), + OPT_PASSTHRU(0, "refmap", &opt_refmap, N_("refmap"), + N_("specify fetch refmap"), + PARSE_OPT_NONEG), + OPT_PASSTHRU_ARGV('o', "server-option", &opt_fetch, + N_("server-specific"), + N_("option to transmit"), + 0), + OPT_PASSTHRU('4', "ipv4", &opt_ipv4, NULL, + N_("use IPv4 addresses only"), + PARSE_OPT_NOARG), + OPT_PASSTHRU('6', "ipv6", &opt_ipv6, NULL, + N_("use IPv6 addresses only"), + PARSE_OPT_NOARG), + OPT_PASSTHRU_ARGV(0, "negotiation-tip", &opt_fetch, N_("revision"), + N_("report that we have only objects reachable from this object"), + 0), + OPT_BOOL(0, "show-forced-updates", &opt_show_forced_updates, + N_("check for forced-updates on all updated branches")), + OPT_PASSTHRU(0, "set-upstream", &set_upstream, NULL, + N_("set upstream for git pull/fetch"), + PARSE_OPT_NOARG), + + OPT_END() + }; if (!getenv("GIT_REFLOG_ACTION")) set_reflog_message(argc, argv);
diff --git a/builtin/push.c b/builtin/push.c index 5b6cebb..7100ffb 100644 --- a/builtin/push.c +++ b/builtin/push.c
@@ -151,6 +151,7 @@ static NORETURN void die_push_simple(struct branch *branch, const char *advice_pushdefault_maybe = ""; const char *advice_automergesimple_maybe = ""; const char *short_upstream = branch->merge[0]->src; + struct repo_config_values *cfg = repo_config_values(the_repository); skip_prefix(short_upstream, "refs/heads/", &short_upstream); @@ -162,7 +163,7 @@ static NORETURN void die_push_simple(struct branch *branch, advice_pushdefault_maybe = _("\n" "To choose either option permanently, " "see push.default in 'git help config'.\n"); - if (git_branch_track != BRANCH_TRACK_SIMPLE) + if (cfg->branch_track != BRANCH_TRACK_SIMPLE) advice_automergesimple_maybe = _("\n" "To avoid automatically configuring " "an upstream branch when its name\n"
diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 34f7a59..460b21e 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c
@@ -32,7 +32,7 @@ static int list_tree(struct object_id *oid) if (nr_trees >= MAX_UNPACK_TREES) die("I cannot read more than %d trees", MAX_UNPACK_TREES); - tree = parse_tree_indirect(oid); + tree = repo_parse_tree_indirect(the_repository, oid); if (!tree) return -1; trees[nr_trees++] = tree; @@ -268,7 +268,7 @@ int cmd_read_tree(int argc, cache_tree_free(&the_repository->index->cache_tree); for (i = 0; i < nr_trees; i++) { struct tree *tree = trees[i]; - if (parse_tree(tree) < 0) + if (repo_parse_tree(the_repository, tree) < 0) return 128; init_tree_desc(t+i, &tree->object.oid, tree->buffer, tree->size); }
diff --git a/builtin/rebase.c b/builtin/rebase.c index c468828..fa4f5d9 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c
@@ -36,6 +36,7 @@ #include "reset.h" #include "trace2.h" #include "hook.h" +#include "trailer.h" static char const * const builtin_rebase_usage[] = { N_("git rebase [-i] [options] [--exec <cmd>] " @@ -113,6 +114,7 @@ struct rebase_options { enum action action; char *reflog_action; int signoff; + struct strvec trailer_args; int allow_rerere_autoupdate; int keep_empty; int autosquash; @@ -143,6 +145,7 @@ struct rebase_options { .flags = REBASE_NO_QUIET, \ .git_am_opts = STRVEC_INIT, \ .exec = STRING_LIST_INIT_NODUP, \ + .trailer_args = STRVEC_INIT, \ .git_format_patch_opt = STRBUF_INIT, \ .fork_point = -1, \ .reapply_cherry_picks = -1, \ @@ -166,6 +169,7 @@ static void rebase_options_release(struct rebase_options *opts) free(opts->strategy); string_list_clear(&opts->strategy_opts, 0); strbuf_release(&opts->git_format_patch_opt); + strvec_clear(&opts->trailer_args); } static struct replay_opts get_replay_opts(const struct rebase_options *opts) @@ -177,6 +181,9 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts) sequencer_init_config(&replay); replay.signoff = opts->signoff; + + strvec_pushv(&replay.trailer_args, opts->trailer_args.v); + replay.allow_ff = !(opts->flags & REBASE_FORCE); if (opts->allow_rerere_autoupdate) replay.allow_rerere_auto = opts->allow_rerere_autoupdate; @@ -562,7 +569,9 @@ static int finish_rebase(struct rebase_options *opts) * We ignore errors in 'git maintenance run --auto', since the * user should see them. */ - run_auto_maintenance(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE))); + run_auto_maintenance(the_repository, + !(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE))); + if (opts->type == REBASE_MERGE) { struct replay_opts replay = REPLAY_OPTS_INIT; @@ -912,7 +921,7 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream, res = 1; done: - free_commit_list(merge_bases); + commit_list_free(merge_bases); return res && is_linear_history(onto, head); } @@ -929,7 +938,7 @@ static void fill_branch_base(struct rebase_options *options, else oidcpy(branch_base, &merge_bases->item->object.oid); - free_commit_list(merge_bases); + commit_list_free(merge_bases); } static int parse_opt_am(const struct option *opt, const char *arg, int unset) @@ -1132,6 +1141,8 @@ int cmd_rebase(int argc, .flags = PARSE_OPT_NOARG, .defval = REBASE_DIFFSTAT, }, + OPT_STRVEC(0, "trailer", &options.trailer_args, N_("trailer"), + N_("add custom trailer(s)")), OPT_BOOL(0, "signoff", &options.signoff, N_("add a Signed-off-by trailer to each commit")), OPT_BOOL(0, "committer-date-is-author-date", @@ -1285,6 +1296,12 @@ int cmd_rebase(int argc, builtin_rebase_options, builtin_rebase_usage, 0); + if (options.trailer_args.nr) { + if (validate_trailer_args(&options.trailer_args)) + die(NULL); + options.flags |= REBASE_FORCE; + } + if (preserve_merges_selected) die(_("--preserve-merges was replaced by --rebase-merges\n" "Note: Your `pull.rebase` configuration may also be set to 'preserve',\n" @@ -1542,6 +1559,9 @@ int cmd_rebase(int argc, if (options.root && !options.onto_name) imply_merge(&options, "--root without --onto"); + if (options.trailer_args.nr) + imply_merge(&options, "--trailer"); + if (isatty(2) && options.flags & REBASE_NO_QUIET) strbuf_addstr(&options.git_format_patch_opt, " --progress");
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index c9288a9..cb3656a 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c
@@ -3,46 +3,45 @@ #include "builtin.h" #include "abspath.h" - -#include "config.h" -#include "environment.h" -#include "gettext.h" -#include "hex.h" -#include "lockfile.h" -#include "pack.h" -#include "refs.h" -#include "pkt-line.h" -#include "sideband.h" -#include "run-command.h" -#include "hook.h" -#include "exec-cmd.h" #include "commit.h" -#include "object.h" -#include "remote.h" +#include "commit-reach.h" +#include "config.h" #include "connect.h" -#include "string-list.h" -#include "oid-array.h" #include "connected.h" -#include "strvec.h" -#include "version.h" -#include "gpg-interface.h" -#include "sigchain.h" +#include "environment.h" +#include "exec-cmd.h" #include "fsck.h" -#include "tmp-objdir.h" -#include "oidset.h" -#include "packfile.h" +#include "gettext.h" +#include "gpg-interface.h" +#include "hex.h" +#include "hook.h" +#include "lockfile.h" +#include "object.h" #include "object-file.h" #include "object-name.h" #include "odb.h" -#include "path.h" +#include "oid-array.h" +#include "oidset.h" +#include "pack.h" +#include "packfile.h" +#include "parse-options.h" +#include "pkt-line.h" #include "protocol.h" -#include "commit-reach.h" +#include "refs.h" +#include "remote.h" +#include "run-command.h" #include "server-info.h" +#include "setup.h" +#include "shallow.h" +#include "sideband.h" +#include "sigchain.h" +#include "string-list.h" +#include "strvec.h" +#include "tmp-objdir.h" #include "trace.h" #include "trace2.h" +#include "version.h" #include "worktree.h" -#include "shallow.h" -#include "parse-options.h" static const char * const receive_pack_usage[] = { N_("git receive-pack <git-dir>"), @@ -177,8 +176,9 @@ static int receive_pack_config(const char *var, const char *value, if (git_config_pathname(&path, var, value)) return -1; - strbuf_addf(&fsck_msg_types, "%cskiplist=%s", - fsck_msg_types.len ? ',' : '=', path); + if (path) + strbuf_addf(&fsck_msg_types, "%cskiplist=%s", + fsck_msg_types.len ? ',' : '=', path); free(path); return 0; } @@ -305,13 +305,12 @@ static void show_ref(const char *path, const struct object_id *oid) } } -static int show_ref_cb(const char *path_full, const char *referent UNUSED, const struct object_id *oid, - int flag UNUSED, void *data) +static int show_ref_cb(const struct reference *ref, void *data) { struct oidset *seen = data; - const char *path = strip_namespace(path_full); + const char *path = strip_namespace(ref->name); - if (ref_is_hidden(path, path_full, &hidden_refs)) + if (ref_is_hidden(path, ref->name, &hidden_refs)) return 0; /* @@ -320,13 +319,13 @@ static int show_ref_cb(const char *path_full, const char *referent UNUSED, const * transfer but will otherwise ignore them. */ if (!path) { - if (oidset_insert(seen, oid)) + if (oidset_insert(seen, ref->oid)) return 0; path = ".have"; } else { - oidset_insert(seen, oid); + oidset_insert(seen, ref->oid); } - show_ref(path, oid); + show_ref(path, ref->oid); return 0; } @@ -343,9 +342,9 @@ static void show_one_alternate_ref(const struct object_id *oid, static void write_head_info(void) { + struct refs_for_each_ref_options opts = { 0 }; static struct oidset seen = OIDSET_INIT; struct strvec excludes_vector = STRVEC_INIT; - const char **exclude_patterns; /* * We need access to the reference names both with and without their @@ -353,12 +352,12 @@ static void write_head_info(void) * thus have to adapt exclude patterns to carry the namespace prefix * ourselves. */ - exclude_patterns = get_namespaced_exclude_patterns( + opts.exclude_patterns = get_namespaced_exclude_patterns( hidden_refs_to_excludes(&hidden_refs), get_git_namespace(), &excludes_vector); - refs_for_each_fullref_in(get_main_ref_store(the_repository), "", - exclude_patterns, show_ref_cb, &seen); + refs_for_each_ref_ext(get_main_ref_store(the_repository), + show_ref_cb, &seen, &opts); odb_for_each_alternate_ref(the_repository->objects, show_one_alternate_ref, &seen); @@ -393,7 +392,7 @@ struct command { static void proc_receive_ref_append(const char *prefix) { struct proc_receive_ref *ref_pattern; - char *p; + const char *p; int len; CALLOC_ARRAY(ref_pattern, 1); @@ -561,6 +560,48 @@ static int copy_to_sideband(int in, int out UNUSED, void *arg UNUSED) return 0; } +/* + * Start an async thread which redirects hook stderr over the sideband. + * The original stderr fd is saved to `saved_stderr` and STDERR_FILENO is + * redirected to the async's input pipe. + */ +static void prepare_sideband_async(struct async *sideband_async, int *saved_stderr, int *started) +{ + *started = 0; + + if (!use_sideband) + return; + + memset(sideband_async, 0, sizeof(*sideband_async)); + sideband_async->proc = copy_to_sideband; + sideband_async->in = -1; + + if (!start_async(sideband_async)) { + *started = 1; + *saved_stderr = dup(STDERR_FILENO); + if (*saved_stderr >= 0) + dup2(sideband_async->in, STDERR_FILENO); + close(sideband_async->in); + } +} + +/* + * Restore the original stderr and wait for the async sideband thread to finish. + */ +static void finish_sideband_async(struct async *sideband_async, int saved_stderr, int started) +{ + if (!use_sideband) + return; + + if (saved_stderr >= 0) { + dup2(saved_stderr, STDERR_FILENO); + close(saved_stderr); + } + + if (started) + finish_async(sideband_async); +} + static void hmac_hash(unsigned char *out, const char *key_in, size_t key_len, const char *text, size_t text_len) @@ -749,7 +790,7 @@ static int check_cert_push_options(const struct string_list *push_options) return retval; } -static void prepare_push_cert_sha1(struct child_process *proc) +static void prepare_push_cert_sha1(struct run_hooks_opt *opt) { static int already_done; @@ -775,23 +816,23 @@ static void prepare_push_cert_sha1(struct child_process *proc) nonce_status = check_nonce(sigcheck.payload); } if (!is_null_oid(&push_cert_oid)) { - strvec_pushf(&proc->env, "GIT_PUSH_CERT=%s", + strvec_pushf(&opt->env, "GIT_PUSH_CERT=%s", oid_to_hex(&push_cert_oid)); - strvec_pushf(&proc->env, "GIT_PUSH_CERT_SIGNER=%s", + strvec_pushf(&opt->env, "GIT_PUSH_CERT_SIGNER=%s", sigcheck.signer ? sigcheck.signer : ""); - strvec_pushf(&proc->env, "GIT_PUSH_CERT_KEY=%s", + strvec_pushf(&opt->env, "GIT_PUSH_CERT_KEY=%s", sigcheck.key ? sigcheck.key : ""); - strvec_pushf(&proc->env, "GIT_PUSH_CERT_STATUS=%c", + strvec_pushf(&opt->env, "GIT_PUSH_CERT_STATUS=%c", sigcheck.result); if (push_cert_nonce) { - strvec_pushf(&proc->env, + strvec_pushf(&opt->env, "GIT_PUSH_CERT_NONCE=%s", push_cert_nonce); - strvec_pushf(&proc->env, + strvec_pushf(&opt->env, "GIT_PUSH_CERT_NONCE_STATUS=%s", nonce_status); if (nonce_status == NONCE_SLOP) - strvec_pushf(&proc->env, + strvec_pushf(&opt->env, "GIT_PUSH_CERT_NONCE_SLOP=%ld", nonce_stamp_slop); } @@ -803,94 +844,25 @@ struct receive_hook_feed_state { struct ref_push_report *report; int skip_broken; struct strbuf buf; - const struct string_list *push_options; }; -typedef int (*feed_fn)(void *, const char **, size_t *); -static int run_and_feed_hook(const char *hook_name, feed_fn feed, - struct receive_hook_feed_state *feed_state) +static int feed_receive_hook_cb(int hook_stdin_fd, void *pp_cb UNUSED, void *pp_task_cb) { - struct child_process proc = CHILD_PROCESS_INIT; - struct async muxer; - int code; - const char *hook_path = find_hook(the_repository, hook_name); - - if (!hook_path) - return 0; - - strvec_push(&proc.args, hook_path); - proc.in = -1; - proc.stdout_to_stderr = 1; - proc.trace2_hook_name = hook_name; - - if (feed_state->push_options) { - size_t i; - for (i = 0; i < feed_state->push_options->nr; i++) - strvec_pushf(&proc.env, - "GIT_PUSH_OPTION_%"PRIuMAX"=%s", - (uintmax_t)i, - feed_state->push_options->items[i].string); - strvec_pushf(&proc.env, "GIT_PUSH_OPTION_COUNT=%"PRIuMAX"", - (uintmax_t)feed_state->push_options->nr); - } else - strvec_pushf(&proc.env, "GIT_PUSH_OPTION_COUNT"); - - if (tmp_objdir) - strvec_pushv(&proc.env, tmp_objdir_env(tmp_objdir)); - - if (use_sideband) { - memset(&muxer, 0, sizeof(muxer)); - muxer.proc = copy_to_sideband; - muxer.in = -1; - code = start_async(&muxer); - if (code) - return code; - proc.err = muxer.in; - } - - prepare_push_cert_sha1(&proc); - - code = start_command(&proc); - if (code) { - if (use_sideband) - finish_async(&muxer); - return code; - } - - sigchain_push(SIGPIPE, SIG_IGN); - - while (1) { - const char *buf; - size_t n; - if (feed(feed_state, &buf, &n)) - break; - if (write_in_full(proc.in, buf, n) < 0) - break; - } - close(proc.in); - if (use_sideband) - finish_async(&muxer); - - sigchain_pop(SIGPIPE); - - return finish_command(&proc); -} - -static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep) -{ - struct receive_hook_feed_state *state = state_; + struct receive_hook_feed_state *state = pp_task_cb; struct command *cmd = state->cmd; + strbuf_reset(&state->buf); + while (cmd && state->skip_broken && (cmd->error_string || cmd->did_not_exist)) cmd = cmd->next; + if (!cmd) - return -1; /* EOF */ - if (!bufp) - return 0; /* OK, can feed something. */ - strbuf_reset(&state->buf); + return 1; /* no more commands left */ + if (!state->report) state->report = cmd->report; + if (state->report) { struct object_id *old_oid; struct object_id *new_oid; @@ -899,23 +871,56 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep) old_oid = state->report->old_oid ? state->report->old_oid : &cmd->old_oid; new_oid = state->report->new_oid ? state->report->new_oid : &cmd->new_oid; ref_name = state->report->ref_name ? state->report->ref_name : cmd->ref_name; + strbuf_addf(&state->buf, "%s %s %s\n", oid_to_hex(old_oid), oid_to_hex(new_oid), ref_name); + state->report = state->report->next; if (!state->report) - state->cmd = cmd->next; + cmd = cmd->next; } else { strbuf_addf(&state->buf, "%s %s %s\n", oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid), cmd->ref_name); - state->cmd = cmd->next; + cmd = cmd->next; } - if (bufp) { - *bufp = state->buf.buf; - *sizep = state->buf.len; + + state->cmd = cmd; + + if (state->buf.len > 0) { + int ret = write_in_full(hook_stdin_fd, state->buf.buf, state->buf.len); + if (ret < 0) { + if (errno == EPIPE) + return 1; /* child closed pipe */ + return ret; + } } - return 0; + + return state->cmd ? 0 : 1; /* 0 = more to come, 1 = EOF */ +} + +static void *receive_hook_feed_state_alloc(void *feed_pipe_ctx) +{ + struct receive_hook_feed_state *init_state = feed_pipe_ctx; + struct receive_hook_feed_state *data; + + CALLOC_ARRAY(data, 1); + data->report = init_state->report; + data->cmd = init_state->cmd; + data->skip_broken = init_state->skip_broken; + strbuf_init(&data->buf, 0); + + return data; +} + +static void receive_hook_feed_state_free(void *data) +{ + struct receive_hook_feed_state *d = data; + if (!d) + return; + strbuf_release(&d->buf); + free(d); } static int run_receive_hook(struct command *commands, @@ -923,47 +928,82 @@ static int run_receive_hook(struct command *commands, int skip_broken, const struct string_list *push_options) { - struct receive_hook_feed_state state; - int status; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + struct command *iter = commands; + struct receive_hook_feed_state feed_init_state = { + .cmd = commands, + .skip_broken = skip_broken, + .buf = STRBUF_INIT, + }; + struct async sideband_async; + int sideband_async_started = 0; + int saved_stderr = -1; + int ret; - strbuf_init(&state.buf, 0); - state.cmd = commands; - state.skip_broken = skip_broken; - state.report = NULL; - if (feed_receive_hook(&state, NULL, NULL)) + if (!hook_exists(the_repository, hook_name)) return 0; - state.cmd = commands; - state.push_options = push_options; - status = run_and_feed_hook(hook_name, feed_receive_hook, &state); - strbuf_release(&state.buf); - return status; + + /* if there are no valid commands, don't invoke the hook at all. */ + while (iter && skip_broken && (iter->error_string || iter->did_not_exist)) + iter = iter->next; + if (!iter) + return 0; + + if (push_options) { + for (int i = 0; i < push_options->nr; i++) + strvec_pushf(&opt.env, "GIT_PUSH_OPTION_%d=%s", i, + push_options->items[i].string); + strvec_pushf(&opt.env, "GIT_PUSH_OPTION_COUNT=%"PRIuMAX"", + (uintmax_t)push_options->nr); + } else { + strvec_push(&opt.env, "GIT_PUSH_OPTION_COUNT"); + } + + if (tmp_objdir) + strvec_pushv(&opt.env, tmp_objdir_env(tmp_objdir)); + + prepare_push_cert_sha1(&opt); + + prepare_sideband_async(&sideband_async, &saved_stderr, &sideband_async_started); + + /* set up stdin callback */ + opt.feed_pipe_ctx = &feed_init_state; + opt.feed_pipe = feed_receive_hook_cb; + opt.feed_pipe_cb_data_alloc = receive_hook_feed_state_alloc; + opt.feed_pipe_cb_data_free = receive_hook_feed_state_free; + + ret = run_hooks_opt(the_repository, hook_name, &opt); + + finish_sideband_async(&sideband_async, saved_stderr, sideband_async_started); + + return ret; } static int run_update_hook(struct command *cmd) { - struct child_process proc = CHILD_PROCESS_INIT; + static const char hook_name[] = "update"; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + struct async sideband_async; + int sideband_async_started = 0; + int saved_stderr = -1; int code; - const char *hook_path = find_hook(the_repository, "update"); - if (!hook_path) + if (!hook_exists(the_repository, hook_name)) return 0; - strvec_push(&proc.args, hook_path); - strvec_push(&proc.args, cmd->ref_name); - strvec_push(&proc.args, oid_to_hex(&cmd->old_oid)); - strvec_push(&proc.args, oid_to_hex(&cmd->new_oid)); + strvec_pushl(&opt.args, + cmd->ref_name, + oid_to_hex(&cmd->old_oid), + oid_to_hex(&cmd->new_oid), + NULL); - proc.no_stdin = 1; - proc.stdout_to_stderr = 1; - proc.err = use_sideband ? -1 : 0; - proc.trace2_hook_name = "update"; + prepare_sideband_async(&sideband_async, &saved_stderr, &sideband_async_started); - code = start_command(&proc); - if (code) - return code; - if (use_sideband) - copy_to_sideband(proc.err, -1, NULL); - return finish_command(&proc); + code = run_hooks_opt(the_repository, hook_name, &opt); + + finish_sideband_async(&sideband_async, saved_stderr, sideband_async_started); + + return code; } static struct command *find_command_by_refname(struct command *list, @@ -1639,34 +1679,29 @@ static const char *update(struct command *cmd, struct shallow_info *si) static void run_update_post_hook(struct command *commands) { + static const char hook_name[] = "post-update"; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + struct async sideband_async; struct command *cmd; - struct child_process proc = CHILD_PROCESS_INIT; - const char *hook; + int sideband_async_started = 0; + int saved_stderr = -1; - hook = find_hook(the_repository, "post-update"); - if (!hook) + if (!hook_exists(the_repository, hook_name)) return; for (cmd = commands; cmd; cmd = cmd->next) { if (cmd->error_string || cmd->did_not_exist) continue; - if (!proc.args.nr) - strvec_push(&proc.args, hook); - strvec_push(&proc.args, cmd->ref_name); + strvec_push(&opt.args, cmd->ref_name); } - if (!proc.args.nr) + if (!opt.args.nr) return; - proc.no_stdin = 1; - proc.stdout_to_stderr = 1; - proc.err = use_sideband ? -1 : 0; - proc.trace2_hook_name = "post-update"; + prepare_sideband_async(&sideband_async, &saved_stderr, &sideband_async_started); - if (!start_command(&proc)) { - if (use_sideband) - copy_to_sideband(proc.err, -1, NULL); - finish_command(&proc); - } + run_hooks_opt(the_repository, hook_name, &opt); + + finish_sideband_async(&sideband_async, saved_stderr, sideband_async_started); } static void check_aliased_update_internal(struct command *cmd, @@ -1853,10 +1888,14 @@ static void ref_transaction_rejection_handler(const char *refname, const char *old_target UNUSED, const char *new_target UNUSED, enum ref_transaction_error err, + const char *details, void *cb_data) { struct strmap *failed_refs = cb_data; + if (details) + rp_error("%s", details); + strmap_put(failed_refs, refname, (char *)ref_transaction_error_msg(err)); } @@ -1923,6 +1962,7 @@ static void execute_commands_non_atomic(struct command *commands, } ref_transaction_for_each_rejected_update(transaction, + ref_transaction_rejection_handler, &failed_refs); @@ -1934,7 +1974,7 @@ static void execute_commands_non_atomic(struct command *commands, if (reported_error) cmd->error_string = reported_error; else if (strmap_contains(&failed_refs, cmd->ref_name)) - cmd->error_string = strmap_get(&failed_refs, cmd->ref_name); + cmd->error_string = cmd->error_string_owned = xstrdup(strmap_get(&failed_refs, cmd->ref_name)); } cleanup: @@ -2691,7 +2731,7 @@ int cmd_receive_pack(int argc, if (auto_gc) { struct child_process proc = CHILD_PROCESS_INIT; - if (prepare_auto_maintenance(1, &proc)) { + if (prepare_auto_maintenance(the_repository, 1, &proc)) { proc.no_stdin = 1; proc.stdout_to_stderr = 1; proc.err = use_sideband ? -1 : 0;
diff --git a/builtin/remote.c b/builtin/remote.c index 8a7ed42..0fddaa1 100644 --- a/builtin/remote.c +++ b/builtin/remote.c
@@ -332,7 +332,7 @@ static int config_read_branches(const char *key, const char *value, info->remote_name = xstrdup(value); break; case MERGE: { - char *space = strchr(value, ' '); + const char *space = strchr(value, ' '); value = abbrev_branch(value); while (space) { char *merge; @@ -570,17 +570,14 @@ struct branches_for_remote { struct known_remotes *keep; }; -static int add_branch_for_removal(const char *refname, - const char *referent UNUSED, - const struct object_id *oid UNUSED, - int flags UNUSED, void *cb_data) +static int add_branch_for_removal(const struct reference *ref, void *cb_data) { struct branches_for_remote *branches = cb_data; struct refspec_item refspec; struct known_remote *kr; memset(&refspec, 0, sizeof(refspec)); - refspec.dst = (char *)refname; + refspec.dst = (char *)ref->name; if (remote_find_tracking(branches->remote, &refspec)) return 0; free(refspec.src); @@ -588,7 +585,7 @@ static int add_branch_for_removal(const char *refname, /* don't delete a branch if another remote also uses it */ for (kr = branches->keep->list; kr; kr = kr->next) { memset(&refspec, 0, sizeof(refspec)); - refspec.dst = (char *)refname; + refspec.dst = (char *)ref->name; if (!remote_find_tracking(kr->remote, &refspec)) { free(refspec.src); return 0; @@ -596,16 +593,16 @@ static int add_branch_for_removal(const char *refname, } /* don't delete non-remote-tracking refs */ - if (!starts_with(refname, "refs/remotes/")) { + if (!starts_with(ref->name, "refs/remotes/")) { /* advise user how to delete local branches */ - if (starts_with(refname, "refs/heads/")) + if (starts_with(ref->name, "refs/heads/")) string_list_append(branches->skipped, - abbrev_branch(refname)); + abbrev_branch(ref->name)); /* silently skip over other non-remote refs */ return 0; } - string_list_append(branches->branches, refname); + string_list_append(branches->branches, ref->name); return 0; } @@ -713,18 +710,18 @@ static int rename_one_reflog(const char *old_refname, return error; } -static int rename_one_ref(const char *old_refname, const char *referent, - const struct object_id *oid, - int flags, void *cb_data) +static int rename_one_ref(const struct reference *ref, void *cb_data) { struct strbuf new_referent = STRBUF_INIT; struct strbuf new_refname = STRBUF_INIT; struct rename_info *rename = cb_data; + const struct object_id *oid = ref->oid; + const char *referent = ref->target; int error; - compute_renamed_ref(rename, old_refname, &new_refname); + compute_renamed_ref(rename, ref->name, &new_refname); - if (flags & REF_ISSYMREF) { + if (ref->flags & REF_ISSYMREF) { /* * Stupidly enough `referent` is not pointing to the immediate * target of a symref, but it's the recursively resolved value. @@ -732,25 +729,25 @@ static int rename_one_ref(const char *old_refname, const char *referent, * unborn symrefs don't have any value for the `referent` at all. */ referent = refs_resolve_ref_unsafe(get_main_ref_store(the_repository), - old_refname, RESOLVE_REF_NO_RECURSE, + ref->name, RESOLVE_REF_NO_RECURSE, NULL, NULL); compute_renamed_ref(rename, referent, &new_referent); oid = NULL; } - error = ref_transaction_delete(rename->transaction, old_refname, + error = ref_transaction_delete(rename->transaction, ref->name, oid, referent, REF_NO_DEREF, NULL, rename->err); if (error < 0) goto out; error = ref_transaction_update(rename->transaction, new_refname.buf, oid, null_oid(the_hash_algo), - (flags & REF_ISSYMREF) ? new_referent.buf : NULL, NULL, + (ref->flags & REF_ISSYMREF) ? new_referent.buf : NULL, NULL, REF_SKIP_CREATE_REFLOG | REF_NO_DEREF | REF_SKIP_OID_VERIFICATION, NULL, rename->err); if (error < 0) goto out; - error = rename_one_reflog(old_refname, oid, rename); + error = rename_one_reflog(ref->name, oid, rename); if (error < 0) goto out; @@ -915,6 +912,9 @@ static int mv(int argc, const char **argv, const char *prefix, old_remote_context.buf); if (refspecs_need_update) { + struct refs_for_each_ref_options opts = { + .flags = REFS_FOR_EACH_INCLUDE_BROKEN, + }; rename.transaction = ref_store_transaction_begin(get_main_ref_store(the_repository), 0, &err); if (!rename.transaction) @@ -926,9 +926,10 @@ static int mv(int argc, const char **argv, const char *prefix, strbuf_reset(&buf); strbuf_addf(&buf, "refs/remotes/%s/", rename.old_name); + opts.prefix = buf.buf; - result = refs_for_each_rawref_in(get_main_ref_store(the_repository), buf.buf, - rename_one_ref, &rename); + result = refs_for_each_ref_ext(get_main_ref_store(the_repository), + rename_one_ref, &rename, &opts); if (result < 0) die(_("queueing remote ref renames failed: %s"), rename.err->buf); @@ -1125,19 +1126,16 @@ static void free_remote_ref_states(struct ref_states *states) string_list_clear_func(&states->push, clear_push_info); } -static int append_ref_to_tracked_list(const char *refname, - const char *referent UNUSED, - const struct object_id *oid UNUSED, - int flags, void *cb_data) +static int append_ref_to_tracked_list(const struct reference *ref, void *cb_data) { struct ref_states *states = cb_data; struct refspec_item refspec; - if (flags & REF_ISSYMREF) + if (ref->flags & REF_ISSYMREF) return 0; memset(&refspec, 0, sizeof(refspec)); - refspec.dst = (char *)refname; + refspec.dst = (char *)ref->name; if (!remote_find_tracking(states->remote, &refspec)) { string_list_append(&states->tracked, abbrev_branch(refspec.src)); free(refspec.src);
diff --git a/builtin/repack.c b/builtin/repack.c index cfdb4c0..4c5a82c 100644 --- a/builtin/repack.c +++ b/builtin/repack.c
@@ -332,6 +332,9 @@ int cmd_repack(int argc, !(pack_everything & PACK_CRUFT)) strvec_push(&cmd.args, "--pack-loose-unreachable"); } else if (geometry.split_factor) { + pack_geometry_repack_promisors(repo, &po_args, &geometry, + &names, packtmp); + if (midx_must_contain_cruft) strvec_push(&cmd.args, "--stdin-packs"); else @@ -366,8 +369,23 @@ int cmd_repack(int argc, */ for (i = 0; i < geometry.split; i++) fprintf(in, "%s\n", pack_basename(geometry.pack[i])); - for (i = geometry.split; i < geometry.pack_nr; i++) - fprintf(in, "^%s\n", pack_basename(geometry.pack[i])); + for (i = geometry.split; i < geometry.pack_nr; i++) { + const char *basename = pack_basename(geometry.pack[i]); + char marker = '^'; + + if (!midx_must_contain_cruft && + !string_list_has_string(&existing.midx_packs, + basename)) { + /* + * Assume non-MIDX'd packs are not + * necessarily closed under + * reachability. + */ + marker = '!'; + } + + fprintf(in, "%c%s\n", marker, basename); + } fclose(in); } @@ -488,7 +506,7 @@ int cmd_repack(int argc, string_list_sort(&names); - close_object_store(repo->objects); + odb_close(repo->objects); /* * Ok we have prepared all new packfiles.
diff --git a/builtin/replace.c b/builtin/replace.c index 900b560..4c62c5a 100644 --- a/builtin/replace.c +++ b/builtin/replace.c
@@ -47,30 +47,27 @@ struct show_data { enum replace_format format; }; -static int show_reference(const char *refname, - const char *referent UNUSED, - const struct object_id *oid, - int flag UNUSED, void *cb_data) +static int show_reference(const struct reference *ref, void *cb_data) { struct show_data *data = cb_data; - if (!wildmatch(data->pattern, refname, 0)) { + if (!wildmatch(data->pattern, ref->name, 0)) { if (data->format == REPLACE_FORMAT_SHORT) - printf("%s\n", refname); + printf("%s\n", ref->name); else if (data->format == REPLACE_FORMAT_MEDIUM) - printf("%s -> %s\n", refname, oid_to_hex(oid)); + printf("%s -> %s\n", ref->name, oid_to_hex(ref->oid)); else { /* data->format == REPLACE_FORMAT_LONG */ struct object_id object; enum object_type obj_type, repl_type; - if (repo_get_oid(data->repo, refname, &object)) - return error(_("failed to resolve '%s' as a valid ref"), refname); + if (repo_get_oid(data->repo, ref->name, &object)) + return error(_("failed to resolve '%s' as a valid ref"), ref->name); obj_type = odb_read_object_info(data->repo->objects, &object, NULL); - repl_type = odb_read_object_info(data->repo->objects, oid, NULL); + repl_type = odb_read_object_info(data->repo->objects, ref->oid, NULL); - printf("%s (%s) -> %s (%s)\n", refname, type_name(obj_type), - oid_to_hex(oid), type_name(repl_type)); + printf("%s (%s) -> %s (%s)\n", ref->name, type_name(obj_type), + oid_to_hex(ref->oid), type_name(repl_type)); } }
diff --git a/builtin/replay.c b/builtin/replay.c index 6172c8a..a0879b0 100644 --- a/builtin/replay.c +++ b/builtin/replay.c
@@ -2,286 +2,68 @@ * "git replay" builtin command */ -#define USE_THE_REPOSITORY_VARIABLE -#define DISABLE_SIGN_COMPARE_WARNINGS - #include "git-compat-util.h" #include "builtin.h" -#include "environment.h" +#include "config.h" #include "hex.h" -#include "lockfile.h" -#include "merge-ort.h" #include "object-name.h" #include "parse-options.h" #include "refs.h" +#include "replay.h" #include "revision.h" -#include "strmap.h" -#include <oidset.h> -#include <tree.h> -static const char *short_commit_name(struct repository *repo, - struct commit *commit) -{ - return repo_find_unique_abbrev(repo, &commit->object.oid, - DEFAULT_ABBREV); -} - -static struct commit *peel_committish(struct repository *repo, const char *name) -{ - struct object *obj; - struct object_id oid; - - if (repo_get_oid(repo, name, &oid)) - return NULL; - obj = parse_object(repo, &oid); - return (struct commit *)repo_peel_to_type(repo, name, 0, obj, - OBJ_COMMIT); -} - -static char *get_author(const char *message) -{ - size_t len; - const char *a; - - a = find_commit_header(message, "author", &len); - if (a) - return xmemdupz(a, len); - - return NULL; -} - -static struct commit *create_commit(struct repository *repo, - struct tree *tree, - struct commit *based_on, - struct commit *parent) -{ - struct object_id ret; - struct object *obj = NULL; - struct commit_list *parents = NULL; - char *author; - char *sign_commit = NULL; /* FIXME: cli users might want to sign again */ - struct commit_extra_header *extra = NULL; - struct strbuf msg = STRBUF_INIT; - const char *out_enc = get_commit_output_encoding(); - const char *message = repo_logmsg_reencode(repo, based_on, - NULL, out_enc); - const char *orig_message = NULL; - const char *exclude_gpgsig[] = { "gpgsig", NULL }; - - commit_list_insert(parent, &parents); - extra = read_commit_extra_headers(based_on, exclude_gpgsig); - find_commit_subject(message, &orig_message); - strbuf_addstr(&msg, orig_message); - author = get_author(message); - reset_ident_date(); - if (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents, - &ret, author, NULL, sign_commit, extra)) { - error(_("failed to write commit object")); - goto out; - } - - obj = parse_object(repo, &ret); - -out: - repo_unuse_commit_buffer(the_repository, based_on, message); - free_commit_extra_headers(extra); - free_commit_list(parents); - strbuf_release(&msg); - free(author); - return (struct commit *)obj; -} - -struct ref_info { - struct commit *onto; - struct strset positive_refs; - struct strset negative_refs; - int positive_refexprs; - int negative_refexprs; +enum ref_action_mode { + REF_ACTION_UPDATE, + REF_ACTION_PRINT, }; -static void get_ref_information(struct repository *repo, - struct rev_cmdline_info *cmd_info, - struct ref_info *ref_info) +static enum ref_action_mode parse_ref_action_mode(const char *ref_action, const char *source) { - int i; - - ref_info->onto = NULL; - strset_init(&ref_info->positive_refs); - strset_init(&ref_info->negative_refs); - ref_info->positive_refexprs = 0; - ref_info->negative_refexprs = 0; - - /* - * When the user specifies e.g. - * git replay origin/main..mybranch - * git replay ^origin/next mybranch1 mybranch2 - * we want to be able to determine where to replay the commits. In - * these examples, the branches are probably based on an old version - * of either origin/main or origin/next, so we want to replay on the - * newest version of that branch. In contrast we would want to error - * out if they ran - * git replay ^origin/master ^origin/next mybranch - * git replay mybranch~2..mybranch - * the first of those because there's no unique base to choose, and - * the second because they'd likely just be replaying commits on top - * of the same commit and not making any difference. - */ - for (i = 0; i < cmd_info->nr; i++) { - struct rev_cmdline_entry *e = cmd_info->rev + i; - struct object_id oid; - const char *refexpr = e->name; - char *fullname = NULL; - int can_uniquely_dwim = 1; - - if (*refexpr == '^') - refexpr++; - if (repo_dwim_ref(repo, refexpr, strlen(refexpr), &oid, &fullname, 0) != 1) - can_uniquely_dwim = 0; - - if (e->flags & BOTTOM) { - if (can_uniquely_dwim) - strset_add(&ref_info->negative_refs, fullname); - if (!ref_info->negative_refexprs) - ref_info->onto = lookup_commit_reference_gently(repo, - &e->item->oid, 1); - ref_info->negative_refexprs++; - } else { - if (can_uniquely_dwim) - strset_add(&ref_info->positive_refs, fullname); - ref_info->positive_refexprs++; - } - - free(fullname); - } + if (!ref_action || !strcmp(ref_action, "update")) + return REF_ACTION_UPDATE; + if (!strcmp(ref_action, "print")) + return REF_ACTION_PRINT; + die(_("invalid %s value: '%s'"), source, ref_action); } -static void determine_replay_mode(struct repository *repo, - struct rev_cmdline_info *cmd_info, - const char *onto_name, - char **advance_name, - struct commit **onto, - struct strset **update_refs) +static enum ref_action_mode get_ref_action_mode(struct repository *repo, const char *ref_action) { - struct ref_info rinfo; + const char *config_value = NULL; - get_ref_information(repo, cmd_info, &rinfo); - if (!rinfo.positive_refexprs) - die(_("need some commits to replay")); + /* Command line option takes precedence */ + if (ref_action) + return parse_ref_action_mode(ref_action, "--ref-action"); - die_for_incompatible_opt2(!!onto_name, "--onto", - !!*advance_name, "--advance"); - if (onto_name) { - *onto = peel_committish(repo, onto_name); - if (rinfo.positive_refexprs < - strset_get_size(&rinfo.positive_refs)) - die(_("all positive revisions given must be references")); - } else if (*advance_name) { - struct object_id oid; - char *fullname = NULL; + /* Check config value */ + if (!repo_config_get_string_tmp(repo, "replay.refAction", &config_value)) + return parse_ref_action_mode(config_value, "replay.refAction"); - *onto = peel_committish(repo, *advance_name); - if (repo_dwim_ref(repo, *advance_name, strlen(*advance_name), - &oid, &fullname, 0) == 1) { - free(*advance_name); - *advance_name = fullname; - } else { - die(_("argument to --advance must be a reference")); - } - if (rinfo.positive_refexprs > 1) - die(_("cannot advance target with multiple sources because ordering would be ill-defined")); - } else { - int positive_refs_complete = ( - rinfo.positive_refexprs == - strset_get_size(&rinfo.positive_refs)); - int negative_refs_complete = ( - rinfo.negative_refexprs == - strset_get_size(&rinfo.negative_refs)); - /* - * We need either positive_refs_complete or - * negative_refs_complete, but not both. - */ - if (rinfo.negative_refexprs > 0 && - positive_refs_complete == negative_refs_complete) - die(_("cannot implicitly determine whether this is an --advance or --onto operation")); - if (negative_refs_complete) { - struct hashmap_iter iter; - struct strmap_entry *entry; - const char *last_key = NULL; - - if (rinfo.negative_refexprs == 0) - die(_("all positive revisions given must be references")); - else if (rinfo.negative_refexprs > 1) - die(_("cannot implicitly determine whether this is an --advance or --onto operation")); - else if (rinfo.positive_refexprs > 1) - die(_("cannot advance target with multiple source branches because ordering would be ill-defined")); - - /* Only one entry, but we have to loop to get it */ - strset_for_each_entry(&rinfo.negative_refs, - &iter, entry) { - last_key = entry->key; - } - - free(*advance_name); - *advance_name = xstrdup_or_null(last_key); - } else { /* positive_refs_complete */ - if (rinfo.negative_refexprs > 1) - die(_("cannot implicitly determine correct base for --onto")); - if (rinfo.negative_refexprs == 1) - *onto = rinfo.onto; - } - } - if (!*advance_name) { - *update_refs = xcalloc(1, sizeof(**update_refs)); - **update_refs = rinfo.positive_refs; - memset(&rinfo.positive_refs, 0, sizeof(**update_refs)); - } - strset_clear(&rinfo.negative_refs); - strset_clear(&rinfo.positive_refs); + /* Default to update mode */ + return REF_ACTION_UPDATE; } -static struct commit *mapped_commit(kh_oid_map_t *replayed_commits, - struct commit *commit, - struct commit *fallback) +static int handle_ref_update(enum ref_action_mode mode, + struct ref_transaction *transaction, + const char *refname, + const struct object_id *new_oid, + const struct object_id *old_oid, + const char *reflog_msg, + struct strbuf *err) { - khint_t pos = kh_get_oid_map(replayed_commits, commit->object.oid); - if (pos == kh_end(replayed_commits)) - return fallback; - return kh_value(replayed_commits, pos); -} - -static struct commit *pick_regular_commit(struct repository *repo, - struct commit *pickme, - kh_oid_map_t *replayed_commits, - struct commit *onto, - struct merge_options *merge_opt, - struct merge_result *result) -{ - struct commit *base, *replayed_base; - struct tree *pickme_tree, *base_tree; - - base = pickme->parents->item; - replayed_base = mapped_commit(replayed_commits, base, onto); - - result->tree = repo_get_commit_tree(repo, replayed_base); - pickme_tree = repo_get_commit_tree(repo, pickme); - base_tree = repo_get_commit_tree(repo, base); - - merge_opt->branch1 = short_commit_name(repo, replayed_base); - merge_opt->branch2 = short_commit_name(repo, pickme); - merge_opt->ancestor = xstrfmt("parent of %s", merge_opt->branch2); - - merge_incore_nonrecursive(merge_opt, - base_tree, - result->tree, - pickme_tree, - result); - - free((char*)merge_opt->ancestor); - merge_opt->ancestor = NULL; - if (!result->clean) - return NULL; - return create_commit(repo, result->tree, pickme, replayed_base); + switch (mode) { + case REF_ACTION_PRINT: + printf("update %s %s %s\n", + refname, + oid_to_hex(new_oid), + oid_to_hex(old_oid)); + return 0; + case REF_ACTION_UPDATE: + return ref_transaction_update(transaction, refname, new_oid, old_oid, + NULL, NULL, 0, reflog_msg, err); + default: + BUG("unknown ref_action_mode %d", mode); + } } int cmd_replay(int argc, @@ -289,51 +71,68 @@ int cmd_replay(int argc, const char *prefix, struct repository *repo) { - const char *advance_name_opt = NULL; - char *advance_name = NULL; - struct commit *onto = NULL; - const char *onto_name = NULL; - int contained = 0; - + struct replay_revisions_options opts = { 0 }; + struct replay_result result = { 0 }; + const char *ref_action = NULL; + enum ref_action_mode ref_mode; struct rev_info revs; - struct commit *last_commit = NULL; - struct commit *commit; - struct merge_options merge_opt; - struct merge_result result; - struct strset *update_refs = NULL; - kh_oid_map_t *replayed_commits; + struct ref_transaction *transaction = NULL; + struct strbuf transaction_err = STRBUF_INIT; + struct strbuf reflog_msg = STRBUF_INIT; + int desired_reverse; int ret = 0; - const char * const replay_usage[] = { + const char *const replay_usage[] = { N_("(EXPERIMENTAL!) git replay " - "([--contained] --onto <newbase> | --advance <branch>) " - "<revision-range>..."), + "([--contained] --onto <newbase> | --advance <branch> | --revert <branch>) " + "[--ref-action[=<mode>]] <revision-range>"), NULL }; struct option replay_options[] = { - OPT_STRING(0, "advance", &advance_name_opt, + OPT_STRING(0, "advance", &opts.advance, N_("branch"), N_("make replay advance given branch")), - OPT_STRING(0, "onto", &onto_name, + OPT_STRING(0, "onto", &opts.onto, N_("revision"), N_("replay onto given commit")), - OPT_BOOL(0, "contained", &contained, - N_("advance all branches contained in revision-range")), + OPT_BOOL(0, "contained", &opts.contained, + N_("update all branches that point at commits in <revision-range>")), + OPT_STRING(0, "revert", &opts.revert, + N_("branch"), + N_("revert commits onto given branch")), + OPT_STRING(0, "ref-action", &ref_action, + N_("mode"), + N_("control ref update behavior (update|print)")), OPT_END() }; argc = parse_options(argc, argv, prefix, replay_options, replay_usage, PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT); - if (!onto_name && !advance_name_opt) { - error(_("option --onto or --advance is mandatory")); + /* Exactly one mode must be specified */ + if (!opts.onto && !opts.advance && !opts.revert) { + error(_("exactly one of --onto, --advance, or --revert is required")); usage_with_options(replay_usage, replay_options); } - if (advance_name_opt && contained) - die(_("options '%s' and '%s' cannot be used together"), - "--advance", "--contained"); - advance_name = xstrdup_or_null(advance_name_opt); + die_for_incompatible_opt3(!!opts.onto, "--onto", + !!opts.advance, "--advance", + !!opts.revert, "--revert"); + die_for_incompatible_opt2(!!opts.advance, "--advance", + opts.contained, "--contained"); + die_for_incompatible_opt2(!!opts.revert, "--revert", + opts.contained, "--contained"); + + /* Parse ref action mode from command line or config */ + ref_mode = get_ref_action_mode(repo, ref_action); + + /* + * Cherry-pick/rebase need oldest-first ordering so that each + * replayed commit can build on its already-replayed parent. + * Revert needs newest-first ordering (like git revert) to + * reduce conflicts by peeling off changes from the top. + */ + desired_reverse = !opts.revert; repo_init_revisions(repo, &revs, prefix); @@ -346,7 +145,7 @@ int cmd_replay(int argc, * some options changing these values if we think they could * be useful. */ - revs.reverse = 1; + revs.reverse = desired_reverse; revs.sort_order = REV_SORT_IN_GRAPH_ORDER; revs.topo_order = 1; revs.simplify_history = 0; @@ -361,11 +160,11 @@ int cmd_replay(int argc, * Detect and warn if we override some user specified rev * walking options. */ - if (revs.reverse != 1) { + if (revs.reverse != desired_reverse) { warning(_("some rev walking options will be overridden as " "'%s' bit in 'struct rev_info' will be forced"), "reverse"); - revs.reverse = 1; + revs.reverse = desired_reverse; } if (revs.sort_order != REV_SORT_IN_GRAPH_ORDER) { warning(_("some rev walking options will be overridden as " @@ -386,85 +185,65 @@ int cmd_replay(int argc, revs.simplify_history = 0; } - determine_replay_mode(repo, &revs.cmdline, onto_name, &advance_name, - &onto, &update_refs); - - if (!onto) /* FIXME: Should handle replaying down to root commit */ - die("Replaying down to root commit is not supported yet!"); - - if (prepare_revision_walk(&revs) < 0) { - ret = error(_("error preparing revisions")); + ret = replay_revisions(&revs, &opts, &result); + if (ret) goto cleanup; + + /* Build reflog message */ + if (opts.revert) { + strbuf_addf(&reflog_msg, "replay --revert %s", opts.revert); + } else if (opts.advance) { + strbuf_addf(&reflog_msg, "replay --advance %s", opts.advance); + } else { + struct object_id oid; + if (repo_get_oid_committish(repo, opts.onto, &oid)) + BUG("--onto commit should have been resolved beforehand already"); + strbuf_addf(&reflog_msg, "replay --onto %s", oid_to_hex(&oid)); } - init_basic_merge_options(&merge_opt, repo); - memset(&result, 0, sizeof(result)); - merge_opt.show_rename_progress = 0; - last_commit = onto; - replayed_commits = kh_init_oid_map(); - while ((commit = get_revision(&revs))) { - const struct name_decoration *decoration; - khint_t pos; - int hr; - - if (!commit->parents) - die(_("replaying down to root commit is not supported yet!")); - if (commit->parents->next) - die(_("replaying merge commits is not supported yet!")); - - last_commit = pick_regular_commit(repo, commit, replayed_commits, - onto, &merge_opt, &result); - if (!last_commit) - break; - - /* Record commit -> last_commit mapping */ - pos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr); - if (hr == 0) - BUG("Duplicate rewritten commit: %s\n", - oid_to_hex(&commit->object.oid)); - kh_value(replayed_commits, pos) = last_commit; - - /* Update any necessary branches */ - if (advance_name) - continue; - decoration = get_name_decoration(&commit->object); - if (!decoration) - continue; - while (decoration) { - if (decoration->type == DECORATION_REF_LOCAL && - (contained || strset_contains(update_refs, - decoration->name))) { - printf("update %s %s %s\n", - decoration->name, - oid_to_hex(&last_commit->object.oid), - oid_to_hex(&commit->object.oid)); - } - decoration = decoration->next; + /* Initialize ref transaction if using update mode */ + if (ref_mode == REF_ACTION_UPDATE) { + transaction = ref_store_transaction_begin(get_main_ref_store(repo), + 0, &transaction_err); + if (!transaction) { + ret = error(_("failed to begin ref transaction: %s"), + transaction_err.buf); + goto cleanup; } } - /* In --advance mode, advance the target ref */ - if (result.clean == 1 && advance_name) { - printf("update %s %s %s\n", - advance_name, - oid_to_hex(&last_commit->object.oid), - oid_to_hex(&onto->object.oid)); + for (size_t i = 0; i < result.updates_nr; i++) { + ret = handle_ref_update(ref_mode, transaction, result.updates[i].refname, + &result.updates[i].new_oid, &result.updates[i].old_oid, + reflog_msg.buf, &transaction_err); + if (ret) { + ret = error(_("failed to update ref '%s': %s"), + result.updates[i].refname, transaction_err.buf); + goto cleanup; + } } - merge_finalize(&merge_opt, &result); - kh_destroy_oid_map(replayed_commits); - if (update_refs) { - strset_clear(update_refs); - free(update_refs); + /* Commit the ref transaction if we have one */ + if (transaction) { + if (ref_transaction_commit(transaction, &transaction_err)) { + ret = error(_("failed to commit ref transaction: %s"), + transaction_err.buf); + goto cleanup; + } } - ret = result.clean; + + ret = 0; cleanup: + if (transaction) + ref_transaction_free(transaction); + replay_result_release(&result); + strbuf_release(&transaction_err); + strbuf_release(&reflog_msg); release_revisions(&revs); - free(advance_name); /* Return */ if (ret < 0) exit(128); - return ret ? 0 : 1; + return ret; }
diff --git a/builtin/repo.c b/builtin/repo.c index 9d4749f..71a5c1c 100644 --- a/builtin/repo.c +++ b/builtin/repo.c
@@ -1,7 +1,11 @@ #define USE_THE_REPOSITORY_VARIABLE #include "builtin.h" +#include "commit.h" #include "environment.h" +#include "hash.h" +#include "hex.h" +#include "odb.h" #include "parse-options.h" #include "path-walk.h" #include "progress.h" @@ -12,23 +16,42 @@ #include "strbuf.h" #include "string-list.h" #include "shallow.h" +#include "tree.h" +#include "tree-walk.h" #include "utf8.h" +#define REPO_INFO_USAGE \ + "git repo info [--format=(lines|nul) | -z] [--all | <key>...]", \ + "git repo info --keys [--format=(lines|nul) | -z]" + +#define REPO_STRUCTURE_USAGE \ + "git repo structure [--format=(table|lines|nul) | -z]" + static const char *const repo_usage[] = { - "git repo info [--format=(keyvalue|nul)] [-z] [<key>...]", - "git repo structure [--format=(table|keyvalue|nul)]", - NULL + REPO_INFO_USAGE, + REPO_STRUCTURE_USAGE, + NULL, +}; + +static const char *const repo_info_usage[] = { + REPO_INFO_USAGE, + NULL, +}; + +static const char *const repo_structure_usage[] = { + REPO_STRUCTURE_USAGE, + NULL, }; typedef int get_value_fn(struct repository *repo, struct strbuf *buf); enum output_format { FORMAT_TABLE, - FORMAT_KEYVALUE, + FORMAT_NEWLINE_TERMINATED, FORMAT_NUL_TERMINATED, }; -struct field { +struct repo_info_field { const char *key; get_value_fn *get_value; }; @@ -59,30 +82,49 @@ static int get_references_format(struct repository *repo, struct strbuf *buf) return 0; } -/* repo_info_fields keys must be in lexicographical order */ -static const struct field repo_info_fields[] = { +/* repo_info_field keys must be in lexicographical order */ +static const struct repo_info_field repo_info_field[] = { { "layout.bare", get_layout_bare }, { "layout.shallow", get_layout_shallow }, { "object.format", get_object_format }, { "references.format", get_references_format }, }; -static int repo_info_fields_cmp(const void *va, const void *vb) +static int repo_info_field_cmp(const void *va, const void *vb) { - const struct field *a = va; - const struct field *b = vb; + const struct repo_info_field *a = va; + const struct repo_info_field *b = vb; return strcmp(a->key, b->key); } -static get_value_fn *get_value_fn_for_key(const char *key) +static const struct repo_info_field *get_repo_info_field(const char *key) { - const struct field search_key = { key, NULL }; - const struct field *found = bsearch(&search_key, repo_info_fields, - ARRAY_SIZE(repo_info_fields), - sizeof(*found), - repo_info_fields_cmp); - return found ? found->get_value : NULL; + const struct repo_info_field search_key = { key, NULL }; + const struct repo_info_field *found = bsearch(&search_key, + repo_info_field, + ARRAY_SIZE(repo_info_field), + sizeof(*found), + repo_info_field_cmp); + + return found; +} + +static void print_field(enum output_format format, const char *key, + const char *value) +{ + switch (format) { + case FORMAT_NEWLINE_TERMINATED: + printf("%s=", key); + quote_c_style(value, NULL, stdout, 0); + putchar('\n'); + break; + case FORMAT_NUL_TERMINATED: + printf("%s\n%s%c", key, value, '\0'); + break; + default: + BUG("not a valid output format: %d", format); + } } static int print_fields(int argc, const char **argv, @@ -91,42 +133,65 @@ static int print_fields(int argc, const char **argv, { int ret = 0; struct strbuf valbuf = STRBUF_INIT; - struct strbuf quotbuf = STRBUF_INIT; for (int i = 0; i < argc; i++) { - get_value_fn *get_value; const char *key = argv[i]; + const struct repo_info_field *field = get_repo_info_field(key); - get_value = get_value_fn_for_key(key); - - if (!get_value) { + if (!field) { ret = error(_("key '%s' not found"), key); continue; } strbuf_reset(&valbuf); - strbuf_reset("buf); - - get_value(repo, &valbuf); - - switch (format) { - case FORMAT_KEYVALUE: - quote_c_style(valbuf.buf, "buf, NULL, 0); - printf("%s=%s\n", key, quotbuf.buf); - break; - case FORMAT_NUL_TERMINATED: - printf("%s\n%s%c", key, valbuf.buf, '\0'); - break; - default: - BUG("not a valid output format: %d", format); - } + field->get_value(repo, &valbuf); + print_field(format, key, valbuf.buf); } strbuf_release(&valbuf); - strbuf_release("buf); return ret; } +static int print_all_fields(struct repository *repo, + enum output_format format) +{ + struct strbuf valbuf = STRBUF_INIT; + + for (size_t i = 0; i < ARRAY_SIZE(repo_info_field); i++) { + const struct repo_info_field *field = &repo_info_field[i]; + + strbuf_reset(&valbuf); + field->get_value(repo, &valbuf); + print_field(format, field->key, valbuf.buf); + } + + strbuf_release(&valbuf); + return 0; +} + +static int print_keys(enum output_format format) +{ + char sep; + + switch (format) { + case FORMAT_NEWLINE_TERMINATED: + sep = '\n'; + break; + case FORMAT_NUL_TERMINATED: + sep = '\0'; + break; + default: + die(_("--keys can only be used with --format=lines or --format=nul")); + } + + for (size_t i = 0; i < ARRAY_SIZE(repo_info_field); i++) { + const struct repo_info_field *field = &repo_info_field[i]; + printf("%s%c", field->key, sep); + } + + return 0; +} + static int parse_format_cb(const struct option *opt, const char *arg, int unset UNUSED) { @@ -136,8 +201,8 @@ static int parse_format_cb(const struct option *opt, *format = FORMAT_NUL_TERMINATED; else if (!strcmp(arg, "nul")) *format = FORMAT_NUL_TERMINATED; - else if (!strcmp(arg, "keyvalue")) - *format = FORMAT_KEYVALUE; + else if (!strcmp(arg, "lines")) + *format = FORMAT_NEWLINE_TERMINATED; else if (!strcmp(arg, "table")) *format = FORMAT_TABLE; else @@ -149,7 +214,9 @@ static int parse_format_cb(const struct option *opt, static int cmd_repo_info(int argc, const char **argv, const char *prefix, struct repository *repo) { - enum output_format format = FORMAT_KEYVALUE; + enum output_format format = FORMAT_NEWLINE_TERMINATED; + int all_keys = 0; + int show_keys = 0; struct option options[] = { OPT_CALLBACK_F(0, "format", &format, N_("format"), N_("output format"), @@ -158,16 +225,46 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix, N_("synonym for --format=nul"), PARSE_OPT_NONEG | PARSE_OPT_NOARG, parse_format_cb), + OPT_BOOL(0, "all", &all_keys, N_("print all keys/values")), + OPT_BOOL(0, "keys", &show_keys, N_("show keys")), OPT_END() }; - argc = parse_options(argc, argv, prefix, options, repo_usage, 0); - if (format != FORMAT_KEYVALUE && format != FORMAT_NUL_TERMINATED) + argc = parse_options(argc, argv, prefix, options, repo_info_usage, 0); + + if (show_keys && (all_keys || argc)) + die(_("--keys cannot be used with a <key> or --all")); + + if (show_keys) + return print_keys(format); + + if (format != FORMAT_NEWLINE_TERMINATED && format != FORMAT_NUL_TERMINATED) die(_("unsupported output format")); - return print_fields(argc, argv, repo, format); + if (all_keys && argc) + die(_("--all and <key> cannot be used together")); + + if (all_keys) + return print_all_fields(repo, format); + else + return print_fields(argc, argv, repo, format); } +struct object_data { + struct object_id oid; + size_t value; +}; + +struct largest_objects { + struct object_data tag_size; + struct object_data commit_size; + struct object_data tree_size; + struct object_data blob_size; + + struct object_data parent_count; + struct object_data tree_entries; +}; + struct ref_stats { size_t branches; size_t remotes; @@ -175,13 +272,20 @@ struct ref_stats { size_t others; }; -struct object_stats { +struct object_values { size_t tags; size_t commits; size_t trees; size_t blobs; }; +struct object_stats { + struct object_values type_counts; + struct object_values inflated_sizes; + struct object_values disk_sizes; + struct largest_objects largest; +}; + struct repo_structure { struct ref_stats refs; struct object_stats objects; @@ -189,9 +293,11 @@ struct repo_structure { struct stats_table { struct string_list rows; + struct string_list annotations; int name_col_width; int value_col_width; + int unit_col_width; }; /* @@ -199,6 +305,9 @@ struct stats_table { */ struct stats_table_entry { char *value; + const char *unit; + size_t index; + struct object_id *oid; }; static void stats_table_vaddf(struct stats_table *table, @@ -219,11 +328,26 @@ static void stats_table_vaddf(struct stats_table *table, if (name_width > table->name_col_width) table->name_col_width = name_width; - if (entry) { + if (!entry) + return; + if (entry->oid) { + entry->index = table->annotations.nr + 1; + strbuf_addf(&buf, "[%" PRIuMAX "] %s", (uintmax_t)entry->index, + oid_to_hex(entry->oid)); + string_list_append_nodup(&table->annotations, strbuf_detach(&buf, NULL)); + } + if (entry->value) { int value_width = utf8_strwidth(entry->value); if (value_width > table->value_col_width) table->value_col_width = value_width; } + if (entry->unit) { + int unit_width = utf8_strwidth(entry->unit); + if (unit_width > table->unit_col_width) + table->unit_col_width = unit_width; + } + + strbuf_release(&buf); } static void stats_table_addf(struct stats_table *table, const char *format, ...) @@ -242,7 +366,63 @@ static void stats_table_count_addf(struct stats_table *table, size_t value, va_list ap; CALLOC_ARRAY(entry, 1); - entry->value = xstrfmt("%" PRIuMAX, (uintmax_t)value); + humanise_count(value, &entry->value, &entry->unit); + + va_start(ap, format); + stats_table_vaddf(table, entry, format, ap); + va_end(ap); +} + +static void stats_table_object_count_addf(struct stats_table *table, + struct object_id *oid, size_t value, + const char *format, ...) +{ + struct stats_table_entry *entry; + va_list ap; + + CALLOC_ARRAY(entry, 1); + humanise_count(value, &entry->value, &entry->unit); + + /* + * A NULL OID should not have a table annotation. + */ + if (!is_null_oid(oid)) + entry->oid = oid; + + va_start(ap, format); + stats_table_vaddf(table, entry, format, ap); + va_end(ap); +} + +static void stats_table_size_addf(struct stats_table *table, size_t value, + const char *format, ...) +{ + struct stats_table_entry *entry; + va_list ap; + + CALLOC_ARRAY(entry, 1); + humanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT); + + va_start(ap, format); + stats_table_vaddf(table, entry, format, ap); + va_end(ap); +} + +static void stats_table_object_size_addf(struct stats_table *table, + struct object_id *oid, size_t value, + const char *format, ...) +{ + struct stats_table_entry *entry; + va_list ap; + + CALLOC_ARRAY(entry, 1); + humanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT); + + /* + * A NULL OID should not have a table annotation. + */ + if (!is_null_oid(oid)) + entry->oid = oid; va_start(ap, format); stats_table_vaddf(table, entry, format, ap); @@ -254,9 +434,9 @@ static inline size_t get_total_reference_count(struct ref_stats *stats) return stats->branches + stats->remotes + stats->tags + stats->others; } -static inline size_t get_total_object_count(struct object_stats *stats) +static inline size_t get_total_object_values(struct object_values *values) { - return stats->tags + stats->commits + stats->trees + stats->blobs; + return values->tags + values->commits + values->trees + values->blobs; } static void stats_table_setup_structure(struct stats_table *table, @@ -264,7 +444,9 @@ static void stats_table_setup_structure(struct stats_table *table, { struct object_stats *objects = &stats->objects; struct ref_stats *refs = &stats->refs; - size_t object_total; + size_t inflated_object_total; + size_t object_count_total; + size_t disk_object_total; size_t ref_total; ref_total = get_total_reference_count(refs); @@ -275,51 +457,147 @@ static void stats_table_setup_structure(struct stats_table *table, stats_table_count_addf(table, refs->remotes, " * %s", _("Remotes")); stats_table_count_addf(table, refs->others, " * %s", _("Others")); - object_total = get_total_object_count(objects); + object_count_total = get_total_object_values(&objects->type_counts); stats_table_addf(table, ""); stats_table_addf(table, "* %s", _("Reachable objects")); - stats_table_count_addf(table, object_total, " * %s", _("Count")); - stats_table_count_addf(table, objects->commits, " * %s", _("Commits")); - stats_table_count_addf(table, objects->trees, " * %s", _("Trees")); - stats_table_count_addf(table, objects->blobs, " * %s", _("Blobs")); - stats_table_count_addf(table, objects->tags, " * %s", _("Tags")); + stats_table_count_addf(table, object_count_total, " * %s", _("Count")); + stats_table_count_addf(table, objects->type_counts.commits, + " * %s", _("Commits")); + stats_table_count_addf(table, objects->type_counts.trees, + " * %s", _("Trees")); + stats_table_count_addf(table, objects->type_counts.blobs, + " * %s", _("Blobs")); + stats_table_count_addf(table, objects->type_counts.tags, + " * %s", _("Tags")); + + inflated_object_total = get_total_object_values(&objects->inflated_sizes); + stats_table_size_addf(table, inflated_object_total, + " * %s", _("Inflated size")); + stats_table_size_addf(table, objects->inflated_sizes.commits, + " * %s", _("Commits")); + stats_table_size_addf(table, objects->inflated_sizes.trees, + " * %s", _("Trees")); + stats_table_size_addf(table, objects->inflated_sizes.blobs, + " * %s", _("Blobs")); + stats_table_size_addf(table, objects->inflated_sizes.tags, + " * %s", _("Tags")); + + disk_object_total = get_total_object_values(&objects->disk_sizes); + stats_table_size_addf(table, disk_object_total, + " * %s", _("Disk size")); + stats_table_size_addf(table, objects->disk_sizes.commits, + " * %s", _("Commits")); + stats_table_size_addf(table, objects->disk_sizes.trees, + " * %s", _("Trees")); + stats_table_size_addf(table, objects->disk_sizes.blobs, + " * %s", _("Blobs")); + stats_table_size_addf(table, objects->disk_sizes.tags, + " * %s", _("Tags")); + + stats_table_addf(table, ""); + stats_table_addf(table, "* %s", _("Largest objects")); + stats_table_addf(table, " * %s", _("Commits")); + stats_table_object_size_addf(table, + &objects->largest.commit_size.oid, + objects->largest.commit_size.value, + " * %s", _("Maximum size")); + stats_table_object_count_addf(table, + &objects->largest.parent_count.oid, + objects->largest.parent_count.value, + " * %s", _("Maximum parents")); + stats_table_addf(table, " * %s", _("Trees")); + stats_table_object_size_addf(table, + &objects->largest.tree_size.oid, + objects->largest.tree_size.value, + " * %s", _("Maximum size")); + stats_table_object_count_addf(table, + &objects->largest.tree_entries.oid, + objects->largest.tree_entries.value, + " * %s", _("Maximum entries")); + stats_table_addf(table, " * %s", _("Blobs")); + stats_table_object_size_addf(table, + &objects->largest.blob_size.oid, + objects->largest.blob_size.value, + " * %s", _("Maximum size")); + stats_table_addf(table, " * %s", _("Tags")); + stats_table_object_size_addf(table, + &objects->largest.tag_size.oid, + objects->largest.tag_size.value, + " * %s", _("Maximum size")); } +#define INDEX_WIDTH 4 + static void stats_table_print_structure(const struct stats_table *table) { const char *name_col_title = _("Repository structure"); const char *value_col_title = _("Value"); - int name_col_width = utf8_strwidth(name_col_title); - int value_col_width = utf8_strwidth(value_col_title); + int title_name_width = utf8_strwidth(name_col_title); + int title_value_width = utf8_strwidth(value_col_title); + int name_col_width = table->name_col_width; + int value_col_width = table->value_col_width; + int unit_col_width = table->unit_col_width; struct string_list_item *item; + struct strbuf buf = STRBUF_INIT; - if (table->name_col_width > name_col_width) - name_col_width = table->name_col_width; - if (table->value_col_width > value_col_width) - value_col_width = table->value_col_width; + if (title_name_width > name_col_width) + name_col_width = title_name_width; + if (title_value_width > value_col_width + unit_col_width + 1) + value_col_width = title_value_width - unit_col_width; - printf("| %-*s | %-*s |\n", name_col_width, name_col_title, - value_col_width, value_col_title); + strbuf_addstr(&buf, "| "); + strbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width + INDEX_WIDTH, + name_col_title); + strbuf_addstr(&buf, " | "); + strbuf_utf8_align(&buf, ALIGN_LEFT, + value_col_width + unit_col_width + 1, value_col_title); + strbuf_addstr(&buf, " |"); + printf("%s\n", buf.buf); + printf("| "); - for (int i = 0; i < name_col_width; i++) + for (int i = 0; i < name_col_width + INDEX_WIDTH; i++) putchar('-'); printf(" | "); - for (int i = 0; i < value_col_width; i++) + for (int i = 0; i < value_col_width + unit_col_width + 1; i++) putchar('-'); printf(" |\n"); for_each_string_list_item(item, &table->rows) { struct stats_table_entry *entry = item->util; const char *value = ""; + const char *unit = ""; if (entry) { - struct stats_table_entry *entry = item->util; value = entry->value; + if (entry->unit) + unit = entry->unit; } - printf("| %-*s | %*s |\n", name_col_width, item->string, - value_col_width, value); + strbuf_reset(&buf); + strbuf_addstr(&buf, "| "); + strbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width, item->string); + + if (entry && entry->oid) + strbuf_addf(&buf, " [%" PRIuMAX "]", + (uintmax_t)entry->index); + else + strbuf_addchars(&buf, ' ', INDEX_WIDTH); + + strbuf_addstr(&buf, " | "); + strbuf_utf8_align(&buf, ALIGN_RIGHT, value_col_width, value); + strbuf_addch(&buf, ' '); + strbuf_utf8_align(&buf, ALIGN_LEFT, unit_col_width, unit); + strbuf_addstr(&buf, " |"); + printf("%s\n", buf.buf); } + + if (table->annotations.nr) { + printf("\n"); + for_each_string_list_item(item, &table->annotations) + printf("%s\n", item->string); + } + + strbuf_release(&buf); } static void stats_table_clear(struct stats_table *table) @@ -334,28 +612,76 @@ static void stats_table_clear(struct stats_table *table) } string_list_clear(&table->rows, 1); + string_list_clear(&table->annotations, 1); +} + +static inline void print_keyvalue(const char *key, char key_delim, size_t value, + char value_delim) +{ + printf("%s%c%" PRIuMAX "%c", key, key_delim, (uintmax_t)value, + value_delim); +} + +static void print_object_data(const char *key, char key_delim, + struct object_data *data, char value_delim) +{ + print_keyvalue(key, key_delim, data->value, value_delim); + printf("%s_oid%c%s%c", key, key_delim, oid_to_hex(&data->oid), + value_delim); } static void structure_keyvalue_print(struct repo_structure *stats, char key_delim, char value_delim) { - printf("references.branches.count%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->refs.branches, value_delim); - printf("references.tags.count%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->refs.tags, value_delim); - printf("references.remotes.count%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->refs.remotes, value_delim); - printf("references.others.count%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->refs.others, value_delim); + print_keyvalue("references.branches.count", key_delim, + stats->refs.branches, value_delim); + print_keyvalue("references.tags.count", key_delim, + stats->refs.tags, value_delim); + print_keyvalue("references.remotes.count", key_delim, + stats->refs.remotes, value_delim); + print_keyvalue("references.others.count", key_delim, + stats->refs.others, value_delim); - printf("objects.commits.count%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.commits, value_delim); - printf("objects.trees.count%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.trees, value_delim); - printf("objects.blobs.count%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.blobs, value_delim); - printf("objects.tags.count%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.tags, value_delim); + print_keyvalue("objects.commits.count", key_delim, + stats->objects.type_counts.commits, value_delim); + print_keyvalue("objects.trees.count", key_delim, + stats->objects.type_counts.trees, value_delim); + print_keyvalue("objects.blobs.count", key_delim, + stats->objects.type_counts.blobs, value_delim); + print_keyvalue("objects.tags.count", key_delim, + stats->objects.type_counts.tags, value_delim); + + print_keyvalue("objects.commits.inflated_size", key_delim, + stats->objects.inflated_sizes.commits, value_delim); + print_keyvalue("objects.trees.inflated_size", key_delim, + stats->objects.inflated_sizes.trees, value_delim); + print_keyvalue("objects.blobs.inflated_size", key_delim, + stats->objects.inflated_sizes.blobs, value_delim); + print_keyvalue("objects.tags.inflated_size", key_delim, + stats->objects.inflated_sizes.tags, value_delim); + + print_keyvalue("objects.commits.disk_size", key_delim, + stats->objects.disk_sizes.commits, value_delim); + print_keyvalue("objects.trees.disk_size", key_delim, + stats->objects.disk_sizes.trees, value_delim); + print_keyvalue("objects.blobs.disk_size", key_delim, + stats->objects.disk_sizes.blobs, value_delim); + print_keyvalue("objects.tags.disk_size", key_delim, + stats->objects.disk_sizes.tags, value_delim); + + print_object_data("objects.commits.max_size", key_delim, + &stats->objects.largest.commit_size, value_delim); + print_object_data("objects.trees.max_size", key_delim, + &stats->objects.largest.tree_size, value_delim); + print_object_data("objects.blobs.max_size", key_delim, + &stats->objects.largest.blob_size, value_delim); + print_object_data("objects.tags.max_size", key_delim, + &stats->objects.largest.tag_size, value_delim); + + print_object_data("objects.commits.max_parents", key_delim, + &stats->objects.largest.parent_count, value_delim); + print_object_data("objects.trees.max_entries", key_delim, + &stats->objects.largest.tree_entries, value_delim); fflush(stdout); } @@ -366,16 +692,13 @@ struct count_references_data { struct progress *progress; }; -static int count_references(const char *refname, - const char *referent UNUSED, - const struct object_id *oid, - int flags UNUSED, void *cb_data) +static int count_references(const struct reference *ref, void *cb_data) { struct count_references_data *data = cb_data; struct ref_stats *stats = data->stats; size_t ref_count; - switch (ref_kind_from_refname(refname)) { + switch (ref_kind_from_refname(ref->name)) { case FILTER_REFS_BRANCHES: stats->branches++; break; @@ -396,7 +719,7 @@ static int count_references(const char *refname, * While iterating through references for counting, also add OIDs in * preparation for the path walk. */ - add_pending_oid(data->revs, NULL, oid, 0); + add_pending_oid(data->revs, NULL, ref->oid, 0); ref_count = get_total_reference_count(stats); display_progress(data->progress, ref_count); @@ -423,10 +746,34 @@ static void structure_count_references(struct ref_stats *stats, } struct count_objects_data { + struct object_database *odb; struct object_stats *stats; struct progress *progress; }; +static void check_largest(struct object_data *data, struct object_id *oid, + size_t value) +{ + if (value > data->value || is_null_oid(&data->oid)) { + oidcpy(&data->oid, oid); + data->value = value; + } +} + +static size_t count_tree_entries(struct object *obj) +{ + struct tree *t = object_as_type(obj, OBJ_TREE, 0); + struct name_entry entry; + struct tree_desc desc; + size_t count = 0; + + init_tree_desc(&desc, &t->object.oid, t->buffer, t->size); + while (tree_entry(&desc, &entry)) + count++; + + return count; +} + static int count_objects(const char *path UNUSED, struct oid_array *oids, enum object_type type, void *cb_data) { @@ -434,24 +781,70 @@ static int count_objects(const char *path UNUSED, struct oid_array *oids, struct object_stats *stats = data->stats; size_t object_count; - switch (type) { - case OBJ_TAG: - stats->tags += oids->nr; - break; - case OBJ_COMMIT: - stats->commits += oids->nr; - break; - case OBJ_TREE: - stats->trees += oids->nr; - break; - case OBJ_BLOB: - stats->blobs += oids->nr; - break; - default: - BUG("invalid object type"); + for (size_t i = 0; i < oids->nr; i++) { + struct object_info oi = OBJECT_INFO_INIT; + unsigned long inflated; + struct commit *commit; + struct object *obj; + void *content; + off_t disk; + int eaten; + + oi.sizep = &inflated; + oi.disk_sizep = &disk; + oi.contentp = &content; + + if (odb_read_object_info_extended(data->odb, &oids->oid[i], &oi, + OBJECT_INFO_SKIP_FETCH_OBJECT | + OBJECT_INFO_QUICK) < 0) + continue; + + obj = parse_object_buffer(the_repository, &oids->oid[i], type, + inflated, content, &eaten); + + switch (type) { + case OBJ_TAG: + stats->type_counts.tags++; + stats->inflated_sizes.tags += inflated; + stats->disk_sizes.tags += disk; + check_largest(&stats->largest.tag_size, &oids->oid[i], + inflated); + break; + case OBJ_COMMIT: + commit = object_as_type(obj, OBJ_COMMIT, 0); + stats->type_counts.commits++; + stats->inflated_sizes.commits += inflated; + stats->disk_sizes.commits += disk; + check_largest(&stats->largest.commit_size, &oids->oid[i], + inflated); + check_largest(&stats->largest.parent_count, &oids->oid[i], + commit_list_count(commit->parents)); + break; + case OBJ_TREE: + stats->type_counts.trees++; + stats->inflated_sizes.trees += inflated; + stats->disk_sizes.trees += disk; + check_largest(&stats->largest.tree_size, &oids->oid[i], + inflated); + check_largest(&stats->largest.tree_entries, &oids->oid[i], + count_tree_entries(obj)); + break; + case OBJ_BLOB: + stats->type_counts.blobs++; + stats->inflated_sizes.blobs += inflated; + stats->disk_sizes.blobs += disk; + check_largest(&stats->largest.blob_size, &oids->oid[i], + inflated); + break; + default: + BUG("invalid object type"); + } + + if (!eaten) + free(content); } - object_count = get_total_object_count(stats); + object_count = get_total_object_values(&stats->type_counts); display_progress(data->progress, object_count); return 0; @@ -463,6 +856,7 @@ static void structure_count_objects(struct object_stats *stats, { struct path_walk_info info = PATH_WALK_INFO_INIT; struct count_objects_data data = { + .odb = repo->objects, .stats = stats, }; @@ -483,6 +877,7 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix, { struct stats_table table = { .rows = STRING_LIST_INIT_DUP, + .annotations = STRING_LIST_INIT_DUP, }; enum output_format format = FORMAT_TABLE; struct repo_structure stats = { 0 }; @@ -492,11 +887,15 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix, OPT_CALLBACK_F(0, "format", &format, N_("format"), N_("output format"), PARSE_OPT_NONEG, parse_format_cb), + OPT_CALLBACK_F('z', NULL, &format, NULL, + N_("synonym for --format=nul"), + PARSE_OPT_NONEG | PARSE_OPT_NOARG, + parse_format_cb), OPT_BOOL(0, "progress", &show_progress, N_("show progress")), OPT_END() }; - argc = parse_options(argc, argv, prefix, options, repo_usage, 0); + argc = parse_options(argc, argv, prefix, options, repo_structure_usage, 0); if (argc) usage(_("too many arguments")); @@ -513,7 +912,7 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix, stats_table_setup_structure(&table, &stats); stats_table_print_structure(&table); break; - case FORMAT_KEYVALUE: + case FORMAT_NEWLINE_TERMINATED: structure_keyvalue_print(&stats, '=', '\n'); break; case FORMAT_NUL_TERMINATED:
diff --git a/builtin/reset.c b/builtin/reset.c index ed35802..3590be5 100644 --- a/builtin/reset.c +++ b/builtin/reset.c
@@ -118,7 +118,7 @@ static int reset_index(const char *ref, const struct object_id *oid, int reset_t goto out; if (reset_type == MIXED || reset_type == HARD) { - tree = parse_tree_indirect(oid); + tree = repo_parse_tree_indirect(the_repository, oid); if (!tree) { error(_("unable to read tree (%s)"), oid_to_hex(oid)); goto out; @@ -346,7 +346,7 @@ int cmd_reset(int argc, struct object_id oid; struct pathspec pathspec; int intent_to_add = 0; - struct add_p_opt add_p_opt = ADD_P_OPT_INIT; + struct interactive_options interactive_opts = INTERACTIVE_OPTIONS_INIT; const struct option options[] = { OPT__QUIET(&quiet, N_("be quiet, only report errors")), OPT_BOOL(0, "no-refresh", &no_refresh, @@ -371,8 +371,10 @@ int cmd_reset(int argc, PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater), OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")), - OPT_DIFF_UNIFIED(&add_p_opt.context), - OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext), + OPT_BOOL(0, "auto-advance", &interactive_opts.auto_advance, + N_("auto advance to the next file when selecting hunks interactively")), + OPT_DIFF_UNIFIED(&interactive_opts.context), + OPT_DIFF_INTERHUNK_CONTEXT(&interactive_opts.interhunkcontext), OPT_BOOL('N', "intent-to-add", &intent_to_add, N_("record only the fact that removed paths will be added later")), OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), @@ -417,15 +419,15 @@ int cmd_reset(int argc, struct tree *tree; if (repo_get_oid_treeish(the_repository, rev, &oid)) die(_("Failed to resolve '%s' as a valid tree."), rev); - tree = parse_tree_indirect(&oid); + tree = repo_parse_tree_indirect(the_repository, &oid); if (!tree) die(_("Could not parse object '%s'."), rev); oidcpy(&oid, &tree->object.oid); } - if (add_p_opt.context < -1) + if (interactive_opts.context < -1) die(_("'%s' cannot be negative"), "--unified"); - if (add_p_opt.interhunkcontext < -1) + if (interactive_opts.interhunkcontext < -1) die(_("'%s' cannot be negative"), "--inter-hunk-context"); prepare_repo_settings(the_repository); @@ -436,13 +438,15 @@ int cmd_reset(int argc, die(_("options '%s' and '%s' cannot be used together"), "--patch", "--{hard,mixed,soft}"); trace2_cmd_mode("patch-interactive"); update_ref_status = !!run_add_p(the_repository, ADD_P_RESET, - &add_p_opt, rev, &pathspec); + &interactive_opts, rev, &pathspec, 0); goto cleanup; } else { - if (add_p_opt.context != -1) + if (interactive_opts.context != -1) die(_("the option '%s' requires '%s'"), "--unified", "--patch"); - if (add_p_opt.interhunkcontext != -1) + if (interactive_opts.interhunkcontext != -1) die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch"); + if (!interactive_opts.auto_advance) + die(_("the option '%s' requires '%s'"), "--no-auto-advance", "--patch"); } /* git reset tree [--] paths... can be used to
diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 99f876b..854d82e 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c
@@ -88,9 +88,19 @@ static int arg_print_omitted; /* print objects omitted by filter */ struct missing_objects_map_entry { struct oidmap_entry entry; - const char *path; + char *path; unsigned type; }; + +static void missing_objects_map_entry_free(void *e) +{ + struct missing_objects_map_entry *entry = + container_of(e, struct missing_objects_map_entry, entry); + + free(entry->path); + free(entry); +} + static struct oidmap missing_objects; enum missing_action { MA_ERROR = 0, /* fail if any missing objects are encountered */ @@ -216,7 +226,7 @@ static inline void finish_object__ma(struct object *obj, const char *name) static void finish_commit(struct commit *commit) { - free_commit_list(commit->parents); + commit_list_free(commit->parents); commit->parents = NULL; free_commit_buffer(the_repository->parsed_objects, commit); @@ -935,10 +945,9 @@ int cmd_rev_list(int argc, while ((entry = oidmap_iter_next(&iter))) { print_missing_object(entry, arg_missing_action == MA_PRINT_INFO); - free((void *)entry->path); } - oidmap_clear(&missing_objects, true); + oidmap_clear_with_free(&missing_objects, missing_objects_map_entry_free); } stop_progress(&progress);
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 7b3711c..218b5f3 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c
@@ -217,19 +217,17 @@ static int show_default(void) return 0; } -static int show_reference(const char *refname, const char *referent UNUSED, const struct object_id *oid, - int flag UNUSED, void *cb_data UNUSED) +static int show_reference(const struct reference *ref, void *cb_data UNUSED) { - if (ref_excluded(&ref_excludes, refname)) + if (ref_excluded(&ref_excludes, ref->name)) return 0; - show_rev(NORMAL, oid, refname); + show_rev(NORMAL, ref->oid, ref->name); return 0; } -static int anti_reference(const char *refname, const char *referent UNUSED, const struct object_id *oid, - int flag UNUSED, void *cb_data UNUSED) +static int anti_reference(const struct reference *ref, void *cb_data UNUSED) { - show_rev(REVERSED, oid, refname); + show_rev(REVERSED, ref->oid, ref->name); return 0; } @@ -269,21 +267,20 @@ static int show_file(const char *arg, int output_prefix) static int try_difference(const char *arg) { - char *dotdot; + const char *dotdot; struct object_id start_oid; struct object_id end_oid; const char *end; const char *start; + char *to_free; int symmetric; static const char head_by_default[] = "HEAD"; if (!(dotdot = strstr(arg, ".."))) return 0; + start = to_free = xmemdupz(arg, dotdot - arg); end = dotdot + 2; - start = arg; symmetric = (*end == '.'); - - *dotdot = 0; end += symmetric; if (!*end) @@ -297,7 +294,7 @@ static int try_difference(const char *arg) * Just ".."? That is not a range but the * pathspec for the parent directory. */ - *dotdot = '.'; + free(to_free); return 0; } @@ -310,7 +307,7 @@ static int try_difference(const char *arg) a = lookup_commit_reference(the_repository, &start_oid); b = lookup_commit_reference(the_repository, &end_oid); if (!a || !b) { - *dotdot = '.'; + free(to_free); return 0; } if (repo_get_merge_bases(the_repository, a, b, &exclude) < 0) @@ -320,16 +317,16 @@ static int try_difference(const char *arg) show_rev(REVERSED, &commit->object.oid, NULL); } } - *dotdot = '.'; + free(to_free); return 1; } - *dotdot = '.'; + free(to_free); return 0; } static int try_parent_shorthands(const char *arg) { - char *dotdot; + const char *mark; struct object_id oid; struct commit *commit; struct commit_list *parents; @@ -337,38 +334,39 @@ static int try_parent_shorthands(const char *arg) int include_rev = 0; int include_parents = 0; int exclude_parent = 0; + char *to_free; - if ((dotdot = strstr(arg, "^!"))) { + if ((mark = strstr(arg, "^!"))) { include_rev = 1; - if (dotdot[2]) + if (mark[2]) return 0; - } else if ((dotdot = strstr(arg, "^@"))) { + } else if ((mark = strstr(arg, "^@"))) { include_parents = 1; - if (dotdot[2]) + if (mark[2]) return 0; - } else if ((dotdot = strstr(arg, "^-"))) { + } else if ((mark = strstr(arg, "^-"))) { include_rev = 1; exclude_parent = 1; - if (dotdot[2]) { + if (mark[2]) { char *end; - exclude_parent = strtoul(dotdot + 2, &end, 10); + exclude_parent = strtoul(mark + 2, &end, 10); if (*end != '\0' || !exclude_parent) return 0; } } else return 0; - *dotdot = 0; + arg = to_free = xmemdupz(arg, mark - arg); if (repo_get_oid_committish(the_repository, arg, &oid) || !(commit = lookup_commit_reference(the_repository, &oid))) { - *dotdot = '^'; + free(to_free); return 0; } if (exclude_parent && exclude_parent > commit_list_count(commit->parents)) { - *dotdot = '^'; + free(to_free); return 0; } @@ -389,7 +387,7 @@ static int try_parent_shorthands(const char *arg) free(name); } - *dotdot = '^'; + free(to_free); return 1; } @@ -615,13 +613,22 @@ static int opt_with_value(const char *arg, const char *opt, const char **value) static void handle_ref_opt(const char *pattern, const char *prefix) { - if (pattern) - refs_for_each_glob_ref_in(get_main_ref_store(the_repository), - show_reference, pattern, prefix, - NULL); - else - refs_for_each_ref_in(get_main_ref_store(the_repository), - prefix, show_reference, NULL); + if (pattern) { + struct refs_for_each_ref_options opts = { + .pattern = pattern, + .prefix = prefix, + .trim_prefix = prefix ? strlen(prefix) : 0, + }; + refs_for_each_ref_ext(get_main_ref_store(the_repository), + show_reference, NULL, &opts); + } else { + struct refs_for_each_ref_options opts = { + .prefix = prefix, + .trim_prefix = strlen(prefix), + }; + refs_for_each_ref_ext(get_main_ref_store(the_repository), + show_reference, NULL, &opts); + } clear_ref_exclusions(&ref_excludes); } @@ -933,14 +940,13 @@ int cmd_rev_parse(int argc, continue; } if (!strcmp(arg, "--bisect")) { - refs_for_each_fullref_in(get_main_ref_store(the_repository), - "refs/bisect/bad", - NULL, show_reference, - NULL); - refs_for_each_fullref_in(get_main_ref_store(the_repository), - "refs/bisect/good", - NULL, anti_reference, - NULL); + struct refs_for_each_ref_options opts = { 0 }; + opts.prefix = "refs/bisect/bad"; + refs_for_each_ref_ext(get_main_ref_store(the_repository), + show_reference, NULL, &opts); + opts.prefix = "refs/bisect/good"; + refs_for_each_ref_ext(get_main_ref_store(the_repository), + anti_reference, NULL, &opts); continue; } if (opt_with_value(arg, "--branches", &arg)) {
diff --git a/builtin/shortlog.c b/builtin/shortlog.c index b91acf4..6b2a0b9 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c
@@ -76,7 +76,7 @@ static void insert_one_record(struct shortlog *log, if (!eol) eol = oneline + strlen(oneline); if (starts_with(oneline, "[PATCH")) { - char *eob = strchr(oneline, ']'); + const char *eob = strchr(oneline, ']'); if (eob && (!eol || eob < eol)) oneline = eob + 1; } @@ -357,7 +357,7 @@ void shortlog_init(struct shortlog *log) { memset(log, 0, sizeof(*log)); - read_mailmap(&log->mailmap); + read_mailmap(the_repository, &log->mailmap); log->list.strdup_strings = 1; log->wrap = DEFAULT_WRAPLEN;
diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 441babf..f02831b 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c
@@ -18,6 +18,7 @@ #include "commit-slab.h" #include "date.h" #include "wildmatch.h" +#include "prio-queue.h" static const char*const show_branch_usage[] = { N_("git show-branch [-a | --all] [-r | --remotes] [--topo-order | --date-order]\n" @@ -59,11 +60,10 @@ static const char *get_color_reset_code(void) return ""; } -static struct commit *interesting(struct commit_list *list) +static struct commit *interesting(struct prio_queue *queue) { - while (list) { - struct commit *commit = list->item; - list = list->next; + for (size_t i = 0; i < queue->nr; i++) { + struct commit *commit = queue->array[i].data; if (commit->object.flags & UNINTERESTING) continue; return commit; @@ -222,17 +222,18 @@ static int mark_seen(struct commit *commit, struct commit_list **seen_p) return 0; } -static void join_revs(struct commit_list **list_p, +static void join_revs(struct prio_queue *queue, struct commit_list **seen_p, int num_rev, int extra) { int all_mask = ((1u << (REV_SHIFT + num_rev)) - 1); int all_revs = all_mask & ~((1u << REV_SHIFT) - 1); - while (*list_p) { + while (queue->nr) { struct commit_list *parents; - int still_interesting = !!interesting(*list_p); - struct commit *commit = pop_commit(list_p); + 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) @@ -253,8 +254,14 @@ static void join_revs(struct commit_list **list_p, if (mark_seen(p, seen_p) && !still_interesting) extra--; p->object.flags |= flags; - commit_list_insert_by_date(p, list_p); + if (get_pending) + prio_queue_replace(queue, p); + else + prio_queue_put(queue, p); + get_pending = false; } + if (get_pending) + prio_queue_get(queue); } /* @@ -413,34 +420,32 @@ static int append_ref(const char *refname, const struct object_id *oid, return 0; } -static int append_head_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid, - int flag UNUSED, void *cb_data UNUSED) +static int append_head_ref(const struct reference *ref, void *cb_data UNUSED) { struct object_id tmp; int ofs = 11; - if (!starts_with(refname, "refs/heads/")) + if (!starts_with(ref->name, "refs/heads/")) return 0; /* If both heads/foo and tags/foo exists, get_sha1 would * get confused. */ - if (repo_get_oid(the_repository, refname + ofs, &tmp) || !oideq(&tmp, oid)) + if (repo_get_oid(the_repository, ref->name + ofs, &tmp) || !oideq(&tmp, ref->oid)) ofs = 5; - return append_ref(refname + ofs, oid, 0); + return append_ref(ref->name + ofs, ref->oid, 0); } -static int append_remote_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid, - int flag UNUSED, void *cb_data UNUSED) +static int append_remote_ref(const struct reference *ref, void *cb_data UNUSED) { struct object_id tmp; int ofs = 13; - if (!starts_with(refname, "refs/remotes/")) + if (!starts_with(ref->name, "refs/remotes/")) return 0; /* If both heads/foo and tags/foo exists, get_sha1 would * get confused. */ - if (repo_get_oid(the_repository, refname + ofs, &tmp) || !oideq(&tmp, oid)) + if (repo_get_oid(the_repository, ref->name + ofs, &tmp) || !oideq(&tmp, ref->oid)) ofs = 5; - return append_ref(refname + ofs, oid, 0); + return append_ref(ref->name + ofs, ref->oid, 0); } static int append_tag_ref(const char *refname, const struct object_id *oid, @@ -454,27 +459,26 @@ static int append_tag_ref(const char *refname, const struct object_id *oid, static const char *match_ref_pattern = NULL; static int match_ref_slash = 0; -static int append_matching_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid, - int flag, void *cb_data) +static int append_matching_ref(const struct reference *ref, void *cb_data) { /* we want to allow pattern hold/<asterisk> to show all * branches under refs/heads/hold/, and v0.99.9? to show * refs/tags/v0.99.9a and friends. */ const char *tail; - int slash = count_slashes(refname); - for (tail = refname; *tail && match_ref_slash < slash; ) + int slash = count_slashes(ref->name); + for (tail = ref->name; *tail && match_ref_slash < slash; ) if (*tail++ == '/') slash--; if (!*tail) return 0; if (wildmatch(match_ref_pattern, tail, 0)) return 0; - if (starts_with(refname, "refs/heads/")) - return append_head_ref(refname, NULL, oid, flag, cb_data); - if (starts_with(refname, "refs/tags/")) - return append_tag_ref(refname, oid, flag, cb_data); - return append_ref(refname, oid, 0); + if (starts_with(ref->name, "refs/heads/")) + return append_head_ref(ref, cb_data); + if (starts_with(ref->name, "refs/tags/")) + return append_tag_ref(ref->name, ref->oid, ref->flags, cb_data); + return append_ref(ref->name, ref->oid, 0); } static void snarf_refs(int head, int remotes) @@ -642,7 +646,8 @@ int cmd_show_branch(int ac, { struct commit *rev[MAX_REVS], *commit; char *reflog_msg[MAX_REVS] = {0}; - struct commit_list *list = NULL, *seen = NULL; + struct commit_list *seen = NULL; + struct prio_queue queue = { compare_commits_by_commit_date }; unsigned int rev_mask[MAX_REVS]; int num_rev, i, extra = 0; int all_heads = 0, all_remotes = 0; @@ -886,14 +891,14 @@ int cmd_show_branch(int ac, */ commit->object.flags |= flag; if (commit->object.flags == flag) - commit_list_insert_by_date(commit, &list); + prio_queue_put(&queue, commit); rev[num_rev] = commit; } for (i = 0; i < num_rev; i++) rev_mask[i] = rev[i]->object.flags; if (0 <= extra) - join_revs(&list, &seen, num_rev, extra); + join_revs(&queue, &seen, num_rev, extra); commit_list_sort_by_date(&seen); @@ -1003,8 +1008,8 @@ int cmd_show_branch(int ac, out: for (size_t i = 0; i < ARRAY_SIZE(reflog_msg); i++) free(reflog_msg[i]); - free_commit_list(seen); - free_commit_list(list); + commit_list_free(seen); + clear_prio_queue(&queue); free(args_copy); free(head); return ret;
diff --git a/builtin/show-index.c b/builtin/show-index.c index 2c3e294..24f0230 100644 --- a/builtin/show-index.c +++ b/builtin/show-index.c
@@ -43,32 +43,35 @@ int cmd_show_index(int argc, /* * Fallback to SHA1 if we are running outside of a repository. * - * TODO: Figure out and implement a way to detect the hash algorithm in use by the - * the index file passed in and use that instead. + * TODO: If a future implementation of index file version encodes the hash + * algorithm in its header, enable show-index to infer it from the + * header rather than relying on repository context or a default fallback. */ - if (!the_hash_algo) + if (!the_hash_algo) { + warning(_("assuming SHA-1; use --object-format to override")); repo_set_hash_algo(the_repository, GIT_HASH_DEFAULT); + } hashsz = the_hash_algo->rawsz; if (fread(top_index, 2 * 4, 1, stdin) != 1) - die("unable to read header"); + die(_("unable to read header")); if (top_index[0] == htonl(PACK_IDX_SIGNATURE)) { version = ntohl(top_index[1]); if (version < 2 || version > 2) - die("unknown index version"); + die(_("unknown index version")); if (fread(top_index, 256 * 4, 1, stdin) != 1) - die("unable to read index"); + die(_("unable to read index")); } else { version = 1; if (fread(&top_index[2], 254 * 4, 1, stdin) != 1) - die("unable to read index"); + die(_("unable to read index")); } nr = 0; for (i = 0; i < 256; i++) { unsigned n = ntohl(top_index[i]); if (n < nr) - die("corrupt index file"); + die(_("corrupt index file")); nr = n; } if (version == 1) { @@ -76,7 +79,7 @@ int cmd_show_index(int argc, unsigned int offset, entry[(GIT_MAX_RAWSZ + 4) / sizeof(unsigned int)]; if (fread(entry, 4 + hashsz, 1, stdin) != 1) - die("unable to read entry %u/%u", i, nr); + die(_("unable to read entry %u/%u"), i, nr); offset = ntohl(entry[0]); printf("%u %s\n", offset, hash_to_hex((void *)(entry+1))); } @@ -90,15 +93,15 @@ int cmd_show_index(int argc, ALLOC_ARRAY(entries, nr); for (i = 0; i < nr; i++) { if (fread(entries[i].oid.hash, hashsz, 1, stdin) != 1) - die("unable to read sha1 %u/%u", i, nr); + die(_("unable to read sha1 %u/%u"), i, nr); entries[i].oid.algo = hash_algo_by_ptr(the_hash_algo); } for (i = 0; i < nr; i++) if (fread(&entries[i].crc, 4, 1, stdin) != 1) - die("unable to read crc %u/%u", i, nr); + die(_("unable to read crc %u/%u"), i, nr); for (i = 0; i < nr; i++) if (fread(&entries[i].off, 4, 1, stdin) != 1) - die("unable to read 32b offset %u/%u", i, nr); + die(_("unable to read 32b offset %u/%u"), i, nr); for (i = 0; i < nr; i++) { uint64_t offset; uint32_t off = ntohl(entries[i].off); @@ -107,9 +110,9 @@ int cmd_show_index(int argc, } else { uint32_t off64[2]; if ((off & 0x7fffffff) != off64_nr) - die("inconsistent 64b offset index"); + die(_("inconsistent 64b offset index")); if (fread(off64, 8, 1, stdin) != 1) - die("unable to read 64b offset %u", off64_nr); + die(_("unable to read 64b offset %u"), off64_nr); offset = (((uint64_t)ntohl(off64[0])) << 32) | ntohl(off64[1]); off64_nr++;
diff --git a/builtin/show-ref.c b/builtin/show-ref.c index 0b6f9ed..5d31ace 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c
@@ -31,31 +31,31 @@ struct show_one_options { }; static void show_one(const struct show_one_options *opts, - const char *refname, const struct object_id *oid) + const struct reference *ref) { const char *hex; struct object_id peeled; - if (!odb_has_object(the_repository->objects, oid, + if (!odb_has_object(the_repository->objects, ref->oid, HAS_OBJECT_RECHECK_PACKED | HAS_OBJECT_FETCH_PROMISOR)) - die("git show-ref: bad ref %s (%s)", refname, - oid_to_hex(oid)); + die("git show-ref: bad ref %s (%s)", ref->name, + oid_to_hex(ref->oid)); if (opts->quiet) return; - hex = repo_find_unique_abbrev(the_repository, oid, opts->abbrev); + hex = repo_find_unique_abbrev(the_repository, ref->oid, opts->abbrev); if (opts->hash_only) printf("%s\n", hex); else - printf("%s %s\n", hex, refname); + printf("%s %s\n", hex, ref->name); if (!opts->deref_tags) return; - if (!peel_iterated_oid(the_repository, oid, &peeled)) { + if (!reference_get_peeled_oid(the_repository, ref, &peeled)) { hex = repo_find_unique_abbrev(the_repository, &peeled, opts->abbrev); - printf("%s %s^{}\n", hex, refname); + printf("%s %s^{}\n", hex, ref->name); } } @@ -66,26 +66,25 @@ struct show_ref_data { int show_head; }; -static int show_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid, - int flag UNUSED, void *cbdata) +static int show_ref(const struct reference *ref, void *cbdata) { struct show_ref_data *data = cbdata; - if (data->show_head && !strcmp(refname, "HEAD")) + if (data->show_head && !strcmp(ref->name, "HEAD")) goto match; if (data->patterns) { - int reflen = strlen(refname); + int reflen = strlen(ref->name); const char **p = data->patterns, *m; while ((m = *p++) != NULL) { int len = strlen(m); if (len > reflen) continue; - if (memcmp(m, refname + reflen - len, len)) + if (memcmp(m, ref->name + reflen - len, len)) continue; if (len == reflen) goto match; - if (refname[reflen - len - 1] == '/') + if (ref->name[reflen - len - 1] == '/') goto match; } return 0; @@ -94,18 +93,15 @@ static int show_ref(const char *refname, const char *referent UNUSED, const stru match: data->found_match++; - show_one(data->show_one_opts, refname, oid); + show_one(data->show_one_opts, ref); return 0; } -static int add_existing(const char *refname, - const char *referent UNUSED, - const struct object_id *oid UNUSED, - int flag UNUSED, void *cbdata) +static int add_existing(const struct reference *ref, void *cbdata) { struct string_list *list = (struct string_list *)cbdata; - string_list_insert(list, refname); + string_list_insert(list, ref->name); return 0; } @@ -179,12 +175,18 @@ static int cmd_show_ref__verify(const struct show_one_options *show_one_opts, if ((starts_with(*refs, "refs/") || refname_is_safe(*refs)) && !refs_read_ref(get_main_ref_store(the_repository), *refs, &oid)) { - show_one(show_one_opts, *refs, &oid); - } - else if (!show_one_opts->quiet) + struct reference ref = { + .name = *refs, + .oid = &oid, + }; + + show_one(show_one_opts, &ref); + } else if (!show_one_opts->quiet) { die("'%s' - not a valid ref", *refs); - else + } else { return 1; + } + refs++; } @@ -213,14 +215,19 @@ static int cmd_show_ref__patterns(const struct patterns_options *opts, refs_head_ref(get_main_ref_store(the_repository), show_ref, &show_ref_data); if (opts->branches_only || opts->tags_only) { - if (opts->branches_only) - refs_for_each_fullref_in(get_main_ref_store(the_repository), - "refs/heads/", NULL, - show_ref, &show_ref_data); - if (opts->tags_only) - refs_for_each_fullref_in(get_main_ref_store(the_repository), - "refs/tags/", NULL, show_ref, - &show_ref_data); + struct refs_for_each_ref_options for_each_ref_opts = { 0 }; + + if (opts->branches_only) { + for_each_ref_opts.prefix = "refs/heads/"; + refs_for_each_ref_ext(get_main_ref_store(the_repository), + show_ref, &show_ref_data, &for_each_ref_opts); + } + + if (opts->tags_only) { + for_each_ref_opts.prefix = "refs/tags/"; + refs_for_each_ref_ext(get_main_ref_store(the_repository), + show_ref, &show_ref_data, &for_each_ref_opts); + } } else { refs_for_each_ref(get_main_ref_store(the_repository), show_ref, &show_ref_data);
diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index 15d51e6..f4aa405 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c
@@ -61,9 +61,10 @@ static int sparse_checkout_list(int argc, const char **argv, const char *prefix, struct pattern_list pl; char *sparse_filename; int res; + struct repo_config_values *cfg = repo_config_values(the_repository); setup_work_tree(); - if (!core_apply_sparse_checkout) + if (!cfg->apply_sparse_checkout) die(_("this worktree is not sparse")); argc = parse_options(argc, argv, prefix, @@ -91,10 +92,10 @@ static int sparse_checkout_list(int argc, const char **argv, const char *prefix, hashmap_for_each_entry(&pl.recursive_hashmap, &iter, pe, ent) { /* pe->pattern starts with "/", skip it */ - string_list_insert(&sl, pe->pattern + 1); + string_list_append(&sl, pe->pattern + 1); } - string_list_sort(&sl); + string_list_sort_u(&sl, 0); for (i = 0; i < sl.nr; i++) { quote_c_style(sl.items[i].string, NULL, stdout, 0); @@ -289,11 +290,10 @@ static void write_cone_to_file(FILE *fp, struct pattern_list *pl) if (!hashmap_contains_parent(&pl->recursive_hashmap, pe->pattern, &parent_pattern)) - string_list_insert(&sl, pe->pattern); + string_list_append(&sl, pe->pattern); } - string_list_sort(&sl); - string_list_remove_duplicates(&sl, 0); + string_list_sort_u(&sl, 0); fprintf(fp, "/*\n!/*/\n"); @@ -311,13 +311,12 @@ static void write_cone_to_file(FILE *fp, struct pattern_list *pl) if (!hashmap_contains_parent(&pl->recursive_hashmap, pe->pattern, &parent_pattern)) - string_list_insert(&sl, pe->pattern); + string_list_append(&sl, pe->pattern); } strbuf_release(&parent_pattern); - string_list_sort(&sl); - string_list_remove_duplicates(&sl, 0); + string_list_sort_u(&sl, 0); for (i = 0; i < sl.nr; i++) { char *pattern = escaped_pattern(sl.items[i].string); @@ -399,12 +398,14 @@ static int set_config(struct repository *repo, } static enum sparse_checkout_mode update_cone_mode(int *cone_mode) { + struct repo_config_values *cfg = repo_config_values(the_repository); + /* If not specified, use previous definition of cone mode */ - if (*cone_mode == -1 && core_apply_sparse_checkout) + if (*cone_mode == -1 && cfg->apply_sparse_checkout) *cone_mode = core_sparse_checkout_cone; /* Set cone/non-cone mode appropriately */ - core_apply_sparse_checkout = 1; + cfg->apply_sparse_checkout = 1; if (*cone_mode == 1 || *cone_mode == -1) { core_sparse_checkout_cone = 1; return MODE_CONE_PATTERNS; @@ -416,9 +417,10 @@ static enum sparse_checkout_mode update_cone_mode(int *cone_mode) { static int update_modes(struct repository *repo, int *cone_mode, int *sparse_index) { int mode, record_mode; + struct repo_config_values *cfg = repo_config_values(the_repository); /* Determine if we need to record the mode; ensure sparse checkout on */ - record_mode = (*cone_mode != -1) || !core_apply_sparse_checkout; + record_mode = (*cone_mode != -1) || !cfg->apply_sparse_checkout; mode = update_cone_mode(cone_mode); if (record_mode && set_config(repo, mode)) @@ -684,6 +686,7 @@ static int modify_pattern_list(struct repository *repo, int result; int changed_config = 0; struct pattern_list *pl = xcalloc(1, sizeof(*pl)); + struct repo_config_values *cfg = repo_config_values(the_repository); switch (m) { case ADD: @@ -699,9 +702,9 @@ static int modify_pattern_list(struct repository *repo, break; } - if (!core_apply_sparse_checkout) { + if (!cfg->apply_sparse_checkout) { set_config(repo, MODE_ALL_PATTERNS); - core_apply_sparse_checkout = 1; + cfg->apply_sparse_checkout = 1; changed_config = 1; } @@ -796,9 +799,10 @@ static int sparse_checkout_add(int argc, const char **argv, const char *prefix, }; struct strvec patterns = STRVEC_INIT; int ret; + struct repo_config_values *cfg = repo_config_values(the_repository); setup_work_tree(); - if (!core_apply_sparse_checkout) + if (!cfg->apply_sparse_checkout) die(_("no sparse-checkout to add to")); repo_read_index(repo); @@ -905,9 +909,10 @@ static int sparse_checkout_reapply(int argc, const char **argv, N_("toggle the use of a sparse index")), OPT_END(), }; + struct repo_config_values *cfg = repo_config_values(the_repository); setup_work_tree(); - if (!core_apply_sparse_checkout) + if (!cfg->apply_sparse_checkout) die(_("must be in a sparse-checkout to reapply sparsity patterns")); reapply_opts.cone_mode = -1; @@ -960,6 +965,7 @@ static int sparse_checkout_clean(int argc, const char **argv, size_t worktree_len; int force = 0, dry_run = 0, verbose = 0; int require_force = 1; + struct repo_config_values *cfg = repo_config_values(the_repository); struct option builtin_sparse_checkout_clean_options[] = { OPT__DRY_RUN(&dry_run, N_("dry run")), @@ -969,7 +975,7 @@ static int sparse_checkout_clean(int argc, const char **argv, }; setup_work_tree(); - if (!core_apply_sparse_checkout) + if (!cfg->apply_sparse_checkout) die(_("must be in a sparse-checkout to clean directories")); if (!core_sparse_checkout_cone) die(_("must be in a cone-mode sparse-checkout to clean directories")); @@ -1033,9 +1039,10 @@ static int sparse_checkout_disable(int argc, const char **argv, OPT_END(), }; struct pattern_list pl; + struct repo_config_values *cfg = repo_config_values(the_repository); /* - * We do not exit early if !core_apply_sparse_checkout; due to the + * We do not exit early if !repo->config_values.apply_sparse_checkout; due to the * ability for users to manually muck things up between * direct editing of .git/info/sparse-checkout * running read-tree -m u HEAD or update-index --skip-worktree @@ -1061,7 +1068,7 @@ static int sparse_checkout_disable(int argc, const char **argv, hashmap_init(&pl.recursive_hashmap, pl_hashmap_cmp, NULL, 0); hashmap_init(&pl.parent_hashmap, pl_hashmap_cmp, NULL, 0); pl.use_cone_patterns = 0; - core_apply_sparse_checkout = 1; + cfg->apply_sparse_checkout = 1; add_pattern("/*", empty_base, 0, &pl, 0);
diff --git a/builtin/stash.c b/builtin/stash.c index 948eba0..0d27b2f 100644 --- a/builtin/stash.c +++ b/builtin/stash.c
@@ -50,10 +50,10 @@ #define BUILTIN_STASH_STORE_USAGE \ N_("git stash store [(-m | --message) <message>] [-q | --quiet] <commit>") #define BUILTIN_STASH_PUSH_USAGE \ - N_("git stash [push [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n" \ + N_("git stash [push] [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n" \ " [-u | --include-untracked] [-a | --all] [(-m | --message) <message>]\n" \ " [--pathspec-from-file=<file> [--pathspec-file-nul]]\n" \ - " [--] [<pathspec>...]]") + " [--] [<pathspec>...]") #define BUILTIN_STASH_SAVE_USAGE \ N_("git stash save [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q | --quiet]\n" \ " [-u | --include-untracked] [-a | --all] [<message>]") @@ -347,8 +347,8 @@ static int reset_tree(struct object_id *i_tree, int update, int reset) memset(&opts, 0, sizeof(opts)); - tree = parse_tree_indirect(i_tree); - if (parse_tree(tree)) + tree = repo_parse_tree_indirect(the_repository, i_tree); + if (repo_parse_tree(the_repository, tree)) return -1; init_tree_desc(t, &tree->object.oid, tree->buffer, tree->size); @@ -940,8 +940,8 @@ static void diff_include_untracked(const struct stash_info *info, struct diff_op struct unpack_trees_options unpack_tree_opt = { 0 }; for (size_t i = 0; i < ARRAY_SIZE(oid); i++) { - tree[i] = parse_tree_indirect(oid[i]); - if (parse_tree(tree[i]) < 0) + tree[i] = repo_parse_tree_indirect(the_repository, oid[i]); + if (repo_parse_tree(the_repository, tree[i]) < 0) die(_("failed to parse tree")); init_tree_desc(&tree_desc[i], &tree[i]->object.oid, tree[i]->buffer, tree[i]->size); @@ -1232,7 +1232,7 @@ static int check_changes(const struct pathspec *ps, int include_untracked, } static int save_untracked_files(struct stash_info *info, struct strbuf *msg, - struct strbuf files) + struct strbuf *files) { int ret = 0; struct strbuf untracked_msg = STRBUF_INIT; @@ -1246,7 +1246,7 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg, stash_index_path.buf); strbuf_addf(&untracked_msg, "untracked files on %s\n", msg->buf); - if (pipe_command(&cp_upd_index, files.buf, files.len, NULL, 0, + if (pipe_command(&cp_upd_index, files->buf, files->len, NULL, 0, NULL, 0)) { ret = -1; goto done; @@ -1306,7 +1306,7 @@ static int stash_staged(struct stash_info *info, struct strbuf *out_patch, static int stash_patch(struct stash_info *info, const struct pathspec *ps, struct strbuf *out_patch, int quiet, - struct add_p_opt *add_p_opt) + struct interactive_options *interactive_opts) { int ret = 0; struct child_process cp_read_tree = CHILD_PROCESS_INIT; @@ -1331,7 +1331,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps, old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT)); setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1); - ret = !!run_add_p(the_repository, ADD_P_STASH, add_p_opt, NULL, ps); + ret = !!run_add_p(the_repository, ADD_P_STASH, interactive_opts, NULL, ps, 0); the_repository->index_file = old_repo_index_file; if (old_index_env && *old_index_env) @@ -1427,7 +1427,8 @@ static int stash_working_tree(struct stash_info *info, const struct pathspec *ps } static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf, - int include_untracked, int patch_mode, struct add_p_opt *add_p_opt, + int include_untracked, int patch_mode, + struct interactive_options *interactive_opts, int only_staged, struct stash_info *info, struct strbuf *patch, int quiet) { @@ -1495,11 +1496,11 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b goto done; } - free_commit_list(parents); + commit_list_free(parents); parents = NULL; if (include_untracked) { - if (save_untracked_files(info, &msg, untracked_files)) { + if (save_untracked_files(info, &msg, &untracked_files)) { if (!quiet) fprintf_ln(stderr, _("Cannot save " "the untracked files")); @@ -1509,7 +1510,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b untracked_commit_option = 1; } if (patch_mode) { - ret = stash_patch(info, ps, patch, quiet, add_p_opt); + ret = stash_patch(info, ps, patch, quiet, interactive_opts); if (ret < 0) { if (!quiet) fprintf_ln(stderr, _("Cannot save the current " @@ -1564,7 +1565,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b strbuf_release(&commit_tree_label); strbuf_release(&msg); strbuf_release(&untracked_files); - free_commit_list(parents); + commit_list_free(parents); free(branch_name_buf); return ret; } @@ -1595,7 +1596,8 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED, } static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet, - int keep_index, int patch_mode, struct add_p_opt *add_p_opt, + int keep_index, int patch_mode, + struct interactive_options *interactive_opts, int include_untracked, int only_staged) { int ret = 0; @@ -1667,7 +1669,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q if (stash_msg) strbuf_addstr(&stash_msg_buf, stash_msg); if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, - add_p_opt, only_staged, &info, &patch, quiet)) { + interactive_opts, only_staged, &info, &patch, quiet)) { ret = -1; goto done; } @@ -1841,7 +1843,7 @@ static int push_stash(int argc, const char **argv, const char *prefix, const char *stash_msg = NULL; char *pathspec_from_file = NULL; struct pathspec ps; - struct add_p_opt add_p_opt = ADD_P_OPT_INIT; + struct interactive_options interactive_opts = INTERACTIVE_OPTIONS_INIT; struct option options[] = { OPT_BOOL('k', "keep-index", &keep_index, N_("keep index")), @@ -1849,8 +1851,10 @@ static int push_stash(int argc, const char **argv, const char *prefix, N_("stash staged changes only")), OPT_BOOL('p', "patch", &patch_mode, N_("stash in patch mode")), - OPT_DIFF_UNIFIED(&add_p_opt.context), - OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext), + OPT_BOOL(0, "auto-advance", &interactive_opts.auto_advance, + N_("auto advance to the next file when selecting hunks interactively")), + OPT_DIFF_UNIFIED(&interactive_opts.context), + OPT_DIFF_INTERHUNK_CONTEXT(&interactive_opts.interhunkcontext), OPT__QUIET(&quiet, N_("quiet mode")), OPT_BOOL('u', "include-untracked", &include_untracked, N_("include untracked files in stash")), @@ -1907,19 +1911,21 @@ static int push_stash(int argc, const char **argv, const char *prefix, } if (!patch_mode) { - if (add_p_opt.context != -1) + if (interactive_opts.context != -1) die(_("the option '%s' requires '%s'"), "--unified", "--patch"); - if (add_p_opt.interhunkcontext != -1) + if (interactive_opts.interhunkcontext != -1) die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch"); + if (!interactive_opts.auto_advance) + die(_("the option '%s' requires '%s'"), "--no-auto-advance", "--patch"); } - if (add_p_opt.context < -1) + if (interactive_opts.context < -1) die(_("'%s' cannot be negative"), "--unified"); - if (add_p_opt.interhunkcontext < -1) + if (interactive_opts.interhunkcontext < -1) die(_("'%s' cannot be negative"), "--inter-hunk-context"); ret = do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode, - &add_p_opt, include_untracked, only_staged); + &interactive_opts, include_untracked, only_staged); clear_pathspec(&ps); free(pathspec_from_file); @@ -1944,7 +1950,7 @@ static int save_stash(int argc, const char **argv, const char *prefix, const char *stash_msg = NULL; struct pathspec ps; struct strbuf stash_msg_buf = STRBUF_INIT; - struct add_p_opt add_p_opt = ADD_P_OPT_INIT; + struct interactive_options interactive_opts = INTERACTIVE_OPTIONS_INIT; struct option options[] = { OPT_BOOL('k', "keep-index", &keep_index, N_("keep index")), @@ -1952,8 +1958,10 @@ static int save_stash(int argc, const char **argv, const char *prefix, N_("stash staged changes only")), OPT_BOOL('p', "patch", &patch_mode, N_("stash in patch mode")), - OPT_DIFF_UNIFIED(&add_p_opt.context), - OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext), + OPT_BOOL(0, "auto-advance", &interactive_opts.auto_advance, + N_("auto advance to the next file when selecting hunks interactively")), + OPT_DIFF_UNIFIED(&interactive_opts.context), + OPT_DIFF_INTERHUNK_CONTEXT(&interactive_opts.interhunkcontext), OPT__QUIET(&quiet, N_("quiet mode")), OPT_BOOL('u', "include-untracked", &include_untracked, N_("include untracked files in stash")), @@ -1973,20 +1981,22 @@ static int save_stash(int argc, const char **argv, const char *prefix, memset(&ps, 0, sizeof(ps)); - if (add_p_opt.context < -1) + if (interactive_opts.context < -1) die(_("'%s' cannot be negative"), "--unified"); - if (add_p_opt.interhunkcontext < -1) + if (interactive_opts.interhunkcontext < -1) die(_("'%s' cannot be negative"), "--inter-hunk-context"); if (!patch_mode) { - if (add_p_opt.context != -1) + if (interactive_opts.context != -1) die(_("the option '%s' requires '%s'"), "--unified", "--patch"); - if (add_p_opt.interhunkcontext != -1) + if (interactive_opts.interhunkcontext != -1) die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch"); + if (!interactive_opts.auto_advance) + die(_("the option '%s' requires '%s'"), "--no-auto-advance", "--patch"); } ret = do_push_stash(&ps, stash_msg, quiet, keep_index, - patch_mode, &add_p_opt, include_untracked, + patch_mode, &interactive_opts, include_untracked, only_staged); strbuf_release(&stash_msg_buf); @@ -2184,7 +2194,7 @@ static int do_import_stash(struct repository *r, const char *rev) out: if (this && buffer) repo_unuse_commit_buffer(r, this, buffer); - free_commit_list(items); + commit_list_free(items); free(msg); return res; @@ -2308,7 +2318,7 @@ static int do_export_stash(struct repository *r, * but where their first parents form a chain to our original empty * base commit. */ - items = reverse_commit_list(items); + items = commit_list_reverse(items); for (cur = items; cur; cur = cur->next) { struct commit_list *parents = NULL; struct commit_list **next = &parents; @@ -2318,7 +2328,7 @@ static int do_export_stash(struct repository *r, next = commit_list_append(prev, next); next = commit_list_append(stash, next); res = write_commit_with_parents(r, &out, &stash->object.oid, parents); - free_commit_list(parents); + commit_list_free(parents); if (res) goto out; prev = lookup_commit_reference(r, &out); @@ -2330,7 +2340,7 @@ static int do_export_stash(struct repository *r, puts(oid_to_hex(&prev->object.oid)); out: strbuf_release(&revision); - free_commit_list(items); + commit_list_free(items); return res; }
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index fcd73ab..2f589e3 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c
@@ -29,11 +29,13 @@ #include "object-file.h" #include "object-name.h" #include "odb.h" +#include "odb/source.h" #include "advice.h" #include "branch.h" #include "list-objects-filter-options.h" #include "wildmatch.h" #include "strbuf.h" +#include "url.h" #define OPT_QUIET (1 << 0) #define OPT_CACHED (1 << 1) @@ -112,6 +114,43 @@ static int get_default_remote_submodule(const char *module_path, char **default_ return 0; } +static int module_get_default_remote(int argc, const char **argv, const char *prefix, + struct repository *repo UNUSED) +{ + const char *path; + char *resolved_path = NULL; + char *default_remote = NULL; + int code; + struct option options[] = { + OPT_END() + }; + const char *const usage[] = { + N_("git submodule--helper get-default-remote <path>"), + NULL + }; + + argc = parse_options(argc, argv, prefix, options, usage, 0); + if (argc != 1) + usage_with_options(usage, options); + + path = argv[0]; + if (prefix && *prefix && !is_absolute_path(path)) { + resolved_path = xstrfmt("%s%s", prefix, path); + path = resolved_path; + } + + code = get_default_remote_submodule(path, &default_remote); + if (code) { + free(resolved_path); + return code; + } + + printf("%s\n", default_remote); + free(default_remote); + free(resolved_path); + return 0; +} + /* the result should be freed by the caller. */ static char *get_submodule_displaypath(const char *path, const char *prefix, const char *super_prefix) @@ -435,6 +474,102 @@ struct init_cb { }; #define INIT_CB_INIT { 0 } +static int validate_and_set_submodule_gitdir(struct strbuf *gitdir_path, + const char *submodule_name) +{ + const char *value; + char *key; + + if (validate_submodule_git_dir(gitdir_path->buf, submodule_name)) + return -1; + + key = xstrfmt("submodule.%s.gitdir", submodule_name); + + /* Nothing to do if the config already exists. */ + if (!repo_config_get_string_tmp(the_repository, key, &value)) { + free(key); + return 0; + } + + if (repo_config_set_gently(the_repository, key, gitdir_path->buf)) { + free(key); + return -1; + } + + free(key); + return 0; +} + +static void create_default_gitdir_config(const char *submodule_name) +{ + struct strbuf gitdir_path = STRBUF_INIT; + struct git_hash_ctx ctx; + char hex_name_hash[GIT_MAX_HEXSZ + 1], header[128]; + unsigned char raw_name_hash[GIT_MAX_RAWSZ]; + int header_len; + + /* Case 1: try the plain module name */ + repo_git_path_append(the_repository, &gitdir_path, "modules/%s", submodule_name); + if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name)) { + strbuf_release(&gitdir_path); + return; + } + + /* Case 2.1: Try URI-safe (RFC3986) encoding first, this fixes nested gitdirs */ + strbuf_reset(&gitdir_path); + repo_git_path_append(the_repository, &gitdir_path, "modules/"); + strbuf_addstr_urlencode(&gitdir_path, submodule_name, is_rfc3986_unreserved); + if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name)) { + strbuf_release(&gitdir_path); + return; + } + + /* Case 2.2: Try extended uppercase URI (RFC3986) encoding, to fix case-folding */ + strbuf_reset(&gitdir_path); + repo_git_path_append(the_repository, &gitdir_path, "modules/"); + strbuf_addstr_urlencode(&gitdir_path, submodule_name, is_casefolding_rfc3986_unreserved); + if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name)) + return; + + /* Case 2.3: Try some derived gitdir names, see if one sticks */ + for (char c = '0'; c <= '9'; c++) { + strbuf_reset(&gitdir_path); + repo_git_path_append(the_repository, &gitdir_path, "modules/"); + strbuf_addstr_urlencode(&gitdir_path, submodule_name, is_rfc3986_unreserved); + strbuf_addch(&gitdir_path, c); + if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name)) + return; + + strbuf_reset(&gitdir_path); + repo_git_path_append(the_repository, &gitdir_path, "modules/"); + strbuf_addstr_urlencode(&gitdir_path, submodule_name, is_casefolding_rfc3986_unreserved); + strbuf_addch(&gitdir_path, c); + if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name)) + return; + } + + /* 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); + 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); + if (!validate_and_set_submodule_gitdir(&gitdir_path, submodule_name)) { + strbuf_release(&gitdir_path); + return; + } + + /* Case 3: nothing worked, error out */ + die(_("failed to set a valid default config for 'submodule.%s.gitdir'. " + "Please ensure it is set, for example by running something like: " + "'git config submodule.%s.gitdir .git/modules/%s'"), + submodule_name, submodule_name, submodule_name); +} + static void init_submodule(const char *path, const char *prefix, const char *super_prefix, unsigned int flags) @@ -511,6 +646,10 @@ static void init_submodule(const char *path, const char *prefix, if (repo_config_set_gently(the_repository, sb.buf, upd)) die(_("Failed to register update mode for submodule path '%s'"), displaypath); } + + if (the_repository->repository_format_submodule_path_cfg) + create_default_gitdir_config(sub->name); + strbuf_release(&sb); free(displaypath); free(url); @@ -593,16 +732,12 @@ static void print_status(unsigned int flags, char state, const char *path, printf("\n"); } -static int handle_submodule_head_ref(const char *refname UNUSED, - const char *referent UNUSED, - const struct object_id *oid, - int flags UNUSED, - void *cb_data) +static int handle_submodule_head_ref(const struct reference *ref, void *cb_data) { struct object_id *output = cb_data; - if (oid) - oidcpy(output, oid); + if (ref->oid) + oidcpy(output, ref->oid); return 0; } @@ -1063,7 +1198,7 @@ static void submodule_summary_callback(struct diff_queue_struct *q, if (!S_ISGITLINK(p->one->mode) && !S_ISGITLINK(p->two->mode)) continue; - temp = (struct module_cb*)malloc(sizeof(struct module_cb)); + temp = xmalloc(sizeof(*temp)); temp->mod_src = p->one->mode; temp->mod_dst = p->two->mode; temp->oid_src = p->one->oid; @@ -1208,6 +1343,82 @@ static int module_summary(int argc, const char **argv, const char *prefix, return ret; } +static int module_gitdir(int argc, const char **argv, const char *prefix UNUSED, + struct repository *repo) +{ + struct strbuf gitdir = STRBUF_INIT; + + if (argc != 2) + usage(_("git submodule--helper gitdir <name>")); + + submodule_name_to_gitdir(&gitdir, repo, argv[1]); + + printf("%s\n", gitdir.buf); + + strbuf_release(&gitdir); + return 0; +} + +static int module_migrate(int argc UNUSED, const char **argv UNUSED, + const char *prefix UNUSED, struct repository *repo) +{ + struct strbuf module_dir = STRBUF_INIT; + DIR *dir; + struct dirent *de; + int repo_version = 0; + + repo_git_path_append(repo, &module_dir, "modules/"); + + dir = opendir(module_dir.buf); + if (!dir) + die(_("could not open '%s'"), module_dir.buf); + + while ((de = readdir(dir))) { + struct strbuf gitdir_path = STRBUF_INIT; + char *key; + const char *value; + + if (is_dot_or_dotdot(de->d_name)) + continue; + + strbuf_addf(&gitdir_path, "%s/%s", module_dir.buf, de->d_name); + if (!is_git_directory(gitdir_path.buf)) { + strbuf_release(&gitdir_path); + continue; + } + strbuf_release(&gitdir_path); + + key = xstrfmt("submodule.%s.gitdir", de->d_name); + if (!repo_config_get_string_tmp(repo, key, &value)) { + /* Already has a gitdir config, nothing to do. */ + free(key); + continue; + } + free(key); + + create_default_gitdir_config(de->d_name); + } + + closedir(dir); + strbuf_release(&module_dir); + + repo_config_get_int(the_repository, "core.repositoryformatversion", &repo_version); + if (repo_version == 0 && + repo_config_set_gently(repo, "core.repositoryformatversion", "1")) + die(_("could not set core.repositoryformatversion to 1.\n" + "Please set it for migration to work, for example:\n" + "git config core.repositoryformatversion 1")); + + if (repo_config_set_gently(repo, "extensions.submodulePathConfig", "true")) + die(_("could not enable submodulePathConfig extension. It is required\n" + "for migration to work. Please enable it in the root repo:\n" + "git config extensions.submodulePathConfig true")); + + repo->repository_format_submodule_path_cfg = 1; + + return 0; +} + struct sync_cb { const char *prefix; const char *super_prefix; @@ -1703,10 +1914,6 @@ static int clone_submodule(const struct module_clone_data *clone_data, clone_data_path = to_free = xstrfmt("%s/%s", repo_get_work_tree(the_repository), clone_data->path); - if (validate_submodule_git_dir(sm_gitdir, clone_data->name) < 0) - die(_("refusing to create/use '%s' in another submodule's " - "git dir"), sm_gitdir); - if (!file_exists(sm_gitdir)) { if (clone_data->require_init && !stat(clone_data_path, &st) && !is_empty_dir(clone_data_path)) @@ -1793,8 +2000,9 @@ static int clone_submodule(const struct module_clone_data *clone_data, char *head = xstrfmt("%s/HEAD", sm_gitdir); unlink(head); free(head); - die(_("refusing to create/use '%s' in another submodule's " - "git dir"), sm_gitdir); + die(_("refusing to create/use '%s' in another submodule's git dir. " + "Enabling extensions.submodulePathConfig should fix this."), + sm_gitdir); } connect_work_tree_and_git_dir(clone_data_path, sm_gitdir, 0); @@ -1907,6 +2115,13 @@ static int determine_submodule_update_strategy(struct repository *r, const char *val; int ret; + /* + * NEEDSWORK: audit and ensure that update_submodule() has right + * to assume that submodule_from_path() above will always succeed. + */ + if (!sub) + BUG("update_submodule assumes a submodule exists at path (%s)", + path); key = xstrfmt("submodule.%s.update", sub->name); if (update) { @@ -3123,9 +3338,10 @@ static int module_create_branch(int argc, const char **argv, const char *prefix, N_("git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] [-n|--dry-run] <name> <start-oid> <start-name>"), NULL }; + struct repo_config_values *cfg = repo_config_values(the_repository); repo_config(the_repository, git_default_config, NULL); - track = git_branch_track; + track = cfg->branch_track; argc = parse_options(argc, argv, prefix, options, usage, 0); if (argc != 3) @@ -3187,13 +3403,13 @@ static void append_fetch_remotes(struct strbuf *msg, const char *git_dir_path) static int add_submodule(const struct add_data *add_data) { - char *submod_gitdir_path; struct module_clone_data clone_data = MODULE_CLONE_DATA_INIT; struct string_list reference = STRING_LIST_INIT_NODUP; int ret = -1; /* perhaps the path already exists and is already a git repo, else clone it */ if (is_directory(add_data->sm_path)) { + char *submod_gitdir_path; struct strbuf sm_path = STRBUF_INIT; strbuf_addstr(&sm_path, add_data->sm_path); submod_gitdir_path = xstrfmt("%s/.git", add_data->sm_path); @@ -3207,10 +3423,11 @@ static int add_submodule(const struct add_data *add_data) free(submod_gitdir_path); } else { struct child_process cp = CHILD_PROCESS_INIT; + struct strbuf submod_gitdir = STRBUF_INIT; - submod_gitdir_path = xstrfmt(".git/modules/%s", add_data->sm_name); + submodule_name_to_gitdir(&submod_gitdir, the_repository, add_data->sm_name); - if (is_directory(submod_gitdir_path)) { + if (is_directory(submod_gitdir.buf)) { if (!add_data->force) { struct strbuf msg = STRBUF_INIT; char *die_msg; @@ -3219,8 +3436,8 @@ static int add_submodule(const struct add_data *add_data) "locally with remote(s):\n"), add_data->sm_name); - append_fetch_remotes(&msg, submod_gitdir_path); - free(submod_gitdir_path); + append_fetch_remotes(&msg, submod_gitdir.buf); + strbuf_release(&submod_gitdir); strbuf_addf(&msg, _("If you want to reuse this local git " "directory instead of cloning again from\n" @@ -3238,7 +3455,7 @@ static int add_submodule(const struct add_data *add_data) "submodule '%s'\n"), add_data->sm_name); } } - free(submod_gitdir_path); + strbuf_release(&submod_gitdir); clone_data.prefix = add_data->prefix; clone_data.path = add_data->sm_path; @@ -3531,14 +3748,15 @@ static int module_add(int argc, const char **argv, const char *prefix, } } - if(!add_data.sm_name) + if (!add_data.sm_name) add_data.sm_name = add_data.sm_path; existing = submodule_from_name(the_repository, null_oid(the_hash_algo), add_data.sm_name); - if (existing && strcmp(existing->path, add_data.sm_path)) { + if (existing && existing->path && + strcmp(existing->path, add_data.sm_path)) { if (!force) { die(_("submodule name '%s' already used for path '%s'"), add_data.sm_name, existing->path); @@ -3565,6 +3783,9 @@ static int module_add(int argc, const char **argv, const char *prefix, add_data.progress = !!progress; add_data.dissociate = !!dissociate; + if (the_repository->repository_format_submodule_path_cfg) + create_default_gitdir_config(add_data.sm_name); + if (add_submodule(&add_data)) goto cleanup; configure_added_submodule(&add_data); @@ -3590,6 +3811,8 @@ int cmd_submodule__helper(int argc, NULL }; struct option options[] = { + OPT_SUBCOMMAND("migrate-gitdir-configs", &fn, module_migrate), + OPT_SUBCOMMAND("gitdir", &fn, module_gitdir), OPT_SUBCOMMAND("clone", &fn, module_clone), OPT_SUBCOMMAND("add", &fn, module_add), OPT_SUBCOMMAND("update", &fn, module_update), @@ -3604,6 +3827,7 @@ int cmd_submodule__helper(int argc, OPT_SUBCOMMAND("set-url", &fn, module_set_url), OPT_SUBCOMMAND("set-branch", &fn, module_set_branch), OPT_SUBCOMMAND("create-branch", &fn, module_create_branch), + OPT_SUBCOMMAND("get-default-remote", &fn, module_get_default_remote), OPT_END() }; argc = parse_options(argc, argv, prefix, options, usage, 0);
diff --git a/builtin/tag.c b/builtin/tag.c index f0665af..d51c2e3 100644 --- a/builtin/tag.c +++ b/builtin/tag.c
@@ -149,11 +149,11 @@ static int verify_tag(const char *name, const char *ref UNUSED, if (format->format) flags = GPG_VERIFY_OMIT_STATUS; - if (gpg_verify_tag(oid, name, flags)) + if (gpg_verify_tag(the_repository, oid, name, flags)) return -1; if (format->format) - pretty_print_ref(name, oid, format); + pretty_print_ref(name, oid, NULL, format); return 0; } @@ -167,7 +167,7 @@ static int do_sign(struct strbuf *buffer, struct object_id **compat_oid, char *keyid = get_signing_key(); int ret = -1; - if (sign_buffer(buffer, &sig, keyid)) + if (sign_buffer(buffer, &sig, keyid, 0)) goto out; if (compat) { @@ -176,7 +176,7 @@ static int do_sign(struct strbuf *buffer, struct object_id **compat_oid, if (convert_object_file(the_repository ,&compat_buf, algo, compat, buffer->buf, buffer->len, OBJ_TAG, 1)) goto out; - if (sign_buffer(&compat_buf, &compat_sig, keyid)) + if (sign_buffer(&compat_buf, &compat_sig, keyid, 0)) goto out; add_header_signature(&compat_buf, &sig, algo); strbuf_addbuf(&compat_buf, &compat_sig); @@ -499,8 +499,8 @@ int cmd_tag(int argc, OPT_CALLBACK_F('m', "message", &msg, N_("message"), N_("tag message"), PARSE_OPT_NONEG, parse_msg_arg), OPT_FILENAME('F', "file", &msgfile, N_("read message from file")), - OPT_PASSTHRU_ARGV(0, "trailer", &trailer_args, N_("trailer"), - N_("add custom trailer(s)"), PARSE_OPT_NONEG), + OPT_STRVEC(0, "trailer", &trailer_args, N_("trailer"), + N_("add custom trailer(s)")), OPT_BOOL('e', "edit", &edit_flag, N_("force edit of tag message")), OPT_BOOL('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")), OPT_CLEANUP(&cleanup_arg), @@ -568,6 +568,9 @@ int cmd_tag(int argc, if (cmdmode == 'l') setup_auto_pager("tag", 1); + if (trailer_args.nr) + trailer_config_init(); + if (opt.sign == -1) opt.sign = cmdmode ? 0 : config_sign_tag > 0;
diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index ef79e43..6fc64e9 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c
@@ -363,7 +363,7 @@ struct input_zstream_data { int status; }; -static const void *feed_input_zstream(struct input_stream *in_stream, +static const void *feed_input_zstream(struct odb_write_stream *in_stream, unsigned long *readlen) { struct input_zstream_data *data = in_stream->data; @@ -393,7 +393,7 @@ static void stream_blob(unsigned long size, unsigned nr) { git_zstream zstream = { 0 }; struct input_zstream_data data = { 0 }; - struct input_stream in_stream = { + struct odb_write_stream in_stream = { .read = feed_input_zstream, .data = &data, }; @@ -402,8 +402,7 @@ static void stream_blob(unsigned long size, unsigned nr) data.zstream = &zstream; git_inflate_init(&zstream); - if (stream_loose_object(the_repository->objects->sources, - &in_stream, size, &info->oid)) + if (odb_write_object_stream(the_repository->objects, &in_stream, size, &info->oid)) die(_("failed to write object in stream")); if (data.status != Z_STREAM_END)
diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 195437e..2d68c40 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c
@@ -573,15 +573,18 @@ static void print_rejected_refs(const char *refname, const char *old_target, const char *new_target, enum ref_transaction_error err, + const char *details, void *cb_data UNUSED) { struct strbuf sb = STRBUF_INIT; - const char *reason = ref_transaction_error_msg(err); + + if (details && *details) + error("%s", details); strbuf_addf(&sb, "rejected %s %s %s %s\n", refname, new_oid ? oid_to_hex(new_oid) : new_target, old_oid ? oid_to_hex(old_oid) : old_target, - reason); + ref_transaction_error_msg(err)); fwrite(sb.buf, sb.len, 1, stdout); strbuf_release(&sb);
diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c index 97d7c95..25312bb 100644 --- a/builtin/upload-archive.c +++ b/builtin/upload-archive.c
@@ -4,8 +4,8 @@ #define USE_THE_REPOSITORY_VARIABLE #include "builtin.h" #include "archive.h" -#include "path.h" #include "pkt-line.h" +#include "setup.h" #include "sideband.h" #include "run-command.h" #include "strvec.h"
diff --git a/builtin/upload-pack.c b/builtin/upload-pack.c index c2bbc03..30498fa 100644 --- a/builtin/upload-pack.c +++ b/builtin/upload-pack.c
@@ -5,11 +5,11 @@ #include "gettext.h" #include "pkt-line.h" #include "parse-options.h" -#include "path.h" #include "protocol.h" #include "replace-object.h" #include "upload-pack.h" #include "serve.h" +#include "setup.h" #include "commit.h" #include "environment.h"
diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c index cd6bc11..4a261b2 100644 --- a/builtin/verify-tag.c +++ b/builtin/verify-tag.c
@@ -61,13 +61,13 @@ int cmd_verify_tag(int argc, continue; } - if (gpg_verify_tag(&oid, name, flags)) { + if (gpg_verify_tag(repo, &oid, name, flags)) { had_error = 1; continue; } if (format.format) - pretty_print_ref(name, &oid, &format); + pretty_print_ref(name, &oid, NULL, &format); } return had_error; }
diff --git a/builtin/worktree.c b/builtin/worktree.c index 812774a..4fd6f75 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c
@@ -252,7 +252,7 @@ static int prune(int ac, const char **av, const char *prefix, OPT__DRY_RUN(&show_only, N_("do not remove, show only")), OPT__VERBOSE(&verbose, N_("report pruned working trees")), OPT_EXPIRY_DATE(0, "expire", &expire, - N_("expire working trees older than <time>")), + N_("prune missing working trees older than <time>")), OPT_END() }; @@ -425,6 +425,39 @@ static int make_worktree_orphan(const char * ref, const struct add_opts *opts, return run_command(&cp); } +/* + * References for worktrees are generally stored in '$GIT_DIR/worktrees/<wt_id>'. + * But when using alternate reference directories, we want to store the worktree + * references in '$ALTERNATE_REFERENCE_DIR/worktrees/<wt_id>'. + * + * Create the necessary folder structure to facilitate the same. But to ensure + * that the former path is still considered a Git directory, add stubs. + */ +static void setup_alternate_ref_dir(struct worktree *wt, const char *wt_git_path) +{ + struct strbuf sb = STRBUF_INIT; + char *path; + + path = wt->repo->ref_storage_payload; + if (!path) + return; + + if (!is_absolute_path(path)) + strbuf_addf(&sb, "%s/", wt->repo->commondir); + + strbuf_addf(&sb, "%s/worktrees", path); + safe_create_dir(wt->repo, sb.buf, 1); + strbuf_addf(&sb, "/%s", wt->id); + safe_create_dir(wt->repo, sb.buf, 1); + strbuf_reset(&sb); + + strbuf_addf(&sb, "this worktree stores references in %s/worktrees/%s", + path, wt->id); + refs_create_refdir_stubs(wt->repo, wt_git_path, sb.buf); + + strbuf_release(&sb); +} + static int add_worktree(const char *path, const char *refname, const struct add_opts *opts) { @@ -440,6 +473,7 @@ static int add_worktree(const char *path, const char *refname, struct strbuf sb_name = STRBUF_INIT; struct worktree **worktrees, *wt = NULL; struct ref_store *wt_refs; + struct repo_config_values *cfg = repo_config_values(the_repository); worktrees = get_worktrees(); check_candidate_path(path, opts->force, worktrees, "add"); @@ -505,7 +539,7 @@ static int add_worktree(const char *path, const char *refname, strbuf_reset(&sb); strbuf_addf(&sb, "%s/gitdir", sb_repo.buf); - write_worktree_linking_files(sb_git, sb, opts->relative_paths); + write_worktree_linking_files(sb_git.buf, sb.buf, opts->relative_paths); strbuf_reset(&sb); strbuf_addf(&sb, "%s/commondir", sb_repo.buf); write_file(sb.buf, "../.."); @@ -518,6 +552,7 @@ static int add_worktree(const char *path, const char *refname, ret = error(_("could not find created worktree '%s'"), name); goto done; } + setup_alternate_ref_dir(wt, sb_repo.buf); wt_refs = get_worktree_ref_store(wt); ret = ref_store_create_on_disk(wt_refs, REF_STORE_CREATE_ON_DISK_IS_WORKTREE, &sb); @@ -536,7 +571,7 @@ static int add_worktree(const char *path, const char *refname, * If the current worktree has sparse-checkout enabled, then copy * the sparse-checkout patterns from the current worktree. */ - if (core_apply_sparse_checkout) + if (cfg->apply_sparse_checkout) copy_sparse_checkout(sb_repo.buf); /* @@ -635,11 +670,7 @@ static void print_preparing_worktree_line(int detach, * * Returns 0 on failure and non-zero on success. */ -static int first_valid_ref(const char *refname UNUSED, - const char *referent UNUSED, - const struct object_id *oid UNUSED, - int flags UNUSED, - void *cb_data UNUSED) +static int first_valid_ref(const struct reference *ref UNUSED, void *cb_data UNUSED) { return 1; } @@ -661,25 +692,8 @@ static int can_use_local_refs(const struct add_opts *opts) if (refs_head_ref(get_main_ref_store(the_repository), first_valid_ref, NULL)) { return 1; } else if (refs_for_each_branch_ref(get_main_ref_store(the_repository), first_valid_ref, NULL)) { - if (!opts->quiet) { - struct strbuf path = STRBUF_INIT; - struct strbuf contents = STRBUF_INIT; - char *wt_gitdir = get_worktree_git_dir(NULL); - - strbuf_add_real_path(&path, wt_gitdir); - strbuf_addstr(&path, "/HEAD"); - strbuf_read_file(&contents, path.buf, 64); - strbuf_stripspace(&contents, NULL); - strbuf_strip_suffix(&contents, "\n"); - - warning(_("HEAD points to an invalid (or orphaned) reference.\n" - "HEAD path: '%s'\n" - "HEAD contents: '%s'"), - path.buf, contents.buf); - strbuf_release(&path); - strbuf_release(&contents); - free(wt_gitdir); - } + if (!opts->quiet) + warning(_("HEAD points to an invalid (or orphaned) reference.\n")); return 1; } return 0; @@ -979,14 +993,18 @@ static void show_worktree_porcelain(struct worktree *wt, int line_terminator) fputc(line_terminator, stdout); } -static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len) +struct worktree_display { + char *path; + int width; +}; + +static void show_worktree(struct worktree *wt, struct worktree_display *display, + int path_maxwidth, int abbrev_len) { struct strbuf sb = STRBUF_INIT; - int cur_path_len = strlen(wt->path); - int path_adj = cur_path_len - utf8_strwidth(wt->path); const char *reason; - strbuf_addf(&sb, "%-*s ", 1 + path_maxlen + path_adj, wt->path); + strbuf_addf(&sb, "%s%*s", display->path, 1 + path_maxwidth - display->width, ""); if (wt->is_bare) strbuf_addstr(&sb, "(bare)"); else { @@ -1020,20 +1038,27 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len) strbuf_release(&sb); } -static void measure_widths(struct worktree **wt, int *abbrev, int *maxlen) +static void measure_widths(struct worktree **wt, int *abbrev, + struct worktree_display **d, int *maxwidth) { - int i; + int i, display_alloc = 0; + struct worktree_display *display = NULL; + struct strbuf buf = STRBUF_INIT; for (i = 0; wt[i]; i++) { int sha1_len; - int path_len = strlen(wt[i]->path); + ALLOC_GROW(display, i + 1, display_alloc); + quote_path(wt[i]->path, NULL, &buf, 0); + display[i].width = utf8_strwidth(buf.buf); + display[i].path = strbuf_detach(&buf, NULL); - if (path_len > *maxlen) - *maxlen = path_len; + if (display[i].width > *maxwidth) + *maxwidth = display[i].width; sha1_len = strlen(repo_find_unique_abbrev(the_repository, &wt[i]->head_oid, *abbrev)); if (sha1_len > *abbrev) *abbrev = sha1_len; } + *d = display; } static int pathcmp(const void *a_, const void *b_) @@ -1063,7 +1088,7 @@ static int list(int ac, const char **av, const char *prefix, OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")), OPT__VERBOSE(&verbose, N_("show extended annotations and reasons, if available")), OPT_EXPIRY_DATE(0, "expire", &expire, - N_("add 'prunable' annotation to worktrees older than <time>")), + N_("add 'prunable' annotation to missing worktrees older than <time>")), OPT_SET_INT('z', NULL, &line_terminator, N_("terminate records with a NUL character"), '\0'), OPT_END() @@ -1079,21 +1104,27 @@ static int list(int ac, const char **av, const char *prefix, die(_("the option '%s' requires '%s'"), "-z", "--porcelain"); else { struct worktree **worktrees = get_worktrees(); - int path_maxlen = 0, abbrev = DEFAULT_ABBREV, i; + int path_maxwidth = 0, abbrev = DEFAULT_ABBREV, i; + struct worktree_display *display = NULL; /* sort worktrees by path but keep main worktree at top */ pathsort(worktrees + 1); if (!porcelain) - measure_widths(worktrees, &abbrev, &path_maxlen); + measure_widths(worktrees, &abbrev, + &display, &path_maxwidth); for (i = 0; worktrees[i]; i++) { if (porcelain) show_worktree_porcelain(worktrees[i], line_terminator); else - show_worktree(worktrees[i], path_maxlen, abbrev); + show_worktree(worktrees[i], + &display[i], path_maxwidth, abbrev); } + for (i = 0; display && worktrees[i]; i++) + free(display[i].path); + free(display); free_worktrees(worktrees); } return 0; @@ -1178,14 +1209,14 @@ static void validate_no_submodules(const struct worktree *wt) wt_gitdir = get_worktree_git_dir(wt); - if (is_directory(worktree_git_path(the_repository, wt, "modules"))) { + if (is_directory(worktree_git_path(wt, "modules"))) { /* * There could be false positives, e.g. the "modules" * directory exists but is empty. But it's a rare case and * this simpler check is probably good enough for now. */ found_submodules = 1; - } else if (read_index_from(&istate, worktree_git_path(the_repository, wt, "index"), + } else if (read_index_from(&istate, worktree_git_path(wt, "index"), wt_gitdir) > 0) { for (i = 0; i < istate.cache_nr; i++) { struct cache_entry *ce = istate.cache[i];
diff --git a/bundle-uri.c b/bundle-uri.c index 57cccfc..3b2e347 100644 --- a/bundle-uri.c +++ b/bundle-uri.c
@@ -89,7 +89,10 @@ static int summarize_bundle(struct remote_bundle_info *info, void *data) { FILE *fp = data; fprintf(fp, "[bundle \"%s\"]\n", info->id); - fprintf(fp, "\turi = %s\n", info->uri); + if (info->uri) + fprintf(fp, "\turi = %s\n", info->uri); + else + fprintf(fp, "\t# uri = (missing)\n"); if (info->creationToken) fprintf(fp, "\tcreationToken = %"PRIu64"\n", info->creationToken); @@ -267,6 +270,19 @@ int bundle_uri_parse_config_format(const char *uri, result = 1; } + if (!result) { + struct hashmap_iter iter; + struct remote_bundle_info *bundle; + + hashmap_for_each_entry(&list->bundles, &iter, bundle, ent) { + if (!bundle->uri) { + error(_("bundle list at '%s': bundle '%s' has no uri"), + uri, bundle->id ? bundle->id : "<unknown>"); + result = 1; + } + } + } + return result; } @@ -751,6 +767,12 @@ static int fetch_bundle_uri_internal(struct repository *r, return -1; } + if (!bundle->uri) { + error(_("bundle '%s' has no uri"), + bundle->id ? bundle->id : "<unknown>"); + return -1; + } + if (!bundle->file && !(bundle->file = find_temp_filename())) { result = -1;
diff --git a/cache-tree.c b/cache-tree.c index 2aba470..60bcc07 100644 --- a/cache-tree.c +++ b/cache-tree.c
@@ -548,12 +548,41 @@ void cache_tree_write(struct strbuf *sb, struct cache_tree *root) trace2_region_leave("cache_tree", "write", the_repository); } +static int parse_int(const char **ptr, unsigned long *len_p, int *out) +{ + const char *s = *ptr; + unsigned long len = *len_p; + int ret = 0; + int sign = 1; + + while (len && *s == '-') { + sign *= -1; + s++; + len--; + } + + while (len) { + if (!isdigit(*s)) + break; + ret *= 10; + ret += *s - '0'; + s++; + len--; + } + + if (s == *ptr) + return -1; + + *ptr = s; + *len_p = len; + *out = sign * ret; + return 0; +} + static struct cache_tree *read_one(const char **buffer, unsigned long *size_p) { const char *buf = *buffer; unsigned long size = *size_p; - const char *cp; - char *ep; struct cache_tree *it; int i, subtree_nr; const unsigned rawsz = the_hash_algo->rawsz; @@ -569,19 +598,14 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p) buf++; size--; it = cache_tree(); - cp = buf; - it->entry_count = strtol(cp, &ep, 10); - if (cp == ep) + if (parse_int(&buf, &size, &it->entry_count) < 0) goto free_return; - cp = ep; - subtree_nr = strtol(cp, &ep, 10); - if (cp == ep) + if (!size || *buf != ' ') goto free_return; - while (size && *buf && *buf != '\n') { - size--; - buf++; - } - if (!size) + buf++; size--; + if (parse_int(&buf, &size, &subtree_nr) < 0) + goto free_return; + if (!size || *buf != '\n') goto free_return; buf++; size--; if (0 <= it->entry_count) { @@ -699,11 +723,11 @@ static int write_index_as_tree_internal(struct object_id *oid, return 0; } -struct tree* write_in_core_index_as_tree(struct repository *repo) { +struct tree *write_in_core_index_as_tree(struct repository *repo, + struct index_state *index_state) { struct object_id o; int was_valid, ret; - struct index_state *index_state = repo->index; was_valid = index_state->cache_tree && cache_tree_fully_valid(index_state->cache_tree); @@ -789,7 +813,7 @@ static void prime_cache_tree_rec(struct repository *r, struct cache_tree_sub *sub; struct tree *subtree = lookup_tree(r, &entry.oid); - if (parse_tree(subtree) < 0) + if (repo_parse_tree(the_repository, subtree) < 0) exit(128); sub = cache_tree_sub(it, entry.path); sub->cache_tree = cache_tree();
diff --git a/cache-tree.h b/cache-tree.h index b82c496..f8bddae 100644 --- a/cache-tree.h +++ b/cache-tree.h
@@ -47,7 +47,8 @@ int cache_tree_verify(struct repository *, struct index_state *); #define WRITE_TREE_UNMERGED_INDEX (-2) #define WRITE_TREE_PREFIX_ERROR (-3) -struct tree* write_in_core_index_as_tree(struct repository *repo); +struct tree *write_in_core_index_as_tree(struct repository *repo, + struct index_state *index_state); int write_index_as_tree(struct object_id *oid, struct index_state *index_state, const char *index_path, int flags, const char *prefix); void prime_cache_tree(struct repository *, struct index_state *, struct tree *);
diff --git a/cbtree.c b/cbtree.c index cf8cf75..4ab794b 100644 --- a/cbtree.c +++ b/cbtree.c
@@ -96,26 +96,28 @@ struct cb_node *cb_lookup(struct cb_tree *t, const uint8_t *k, size_t klen) return p && !memcmp(p->k, k, klen) ? p : NULL; } -static enum cb_next cb_descend(struct cb_node *p, cb_iter fn, void *arg) +static int cb_descend(struct cb_node *p, cb_iter fn, void *arg) { if (1 & (uintptr_t)p) { struct cb_node *q = cb_node_of(p); - enum cb_next n = cb_descend(q->child[0], fn, arg); - - return n == CB_BREAK ? n : cb_descend(q->child[1], fn, arg); + int ret = cb_descend(q->child[0], fn, arg); + if (ret) + return ret; + return cb_descend(q->child[1], fn, arg); } else { return fn(p, arg); } } -void cb_each(struct cb_tree *t, const uint8_t *kpfx, size_t klen, - cb_iter fn, void *arg) +int cb_each(struct cb_tree *t, const uint8_t *kpfx, size_t klen, + cb_iter fn, void *arg) { struct cb_node *p = t->root; struct cb_node *top = p; size_t i = 0; - if (!p) return; /* empty tree */ + if (!p) + return 0; /* empty tree */ /* Walk tree, maintaining top pointer */ while (1 & (uintptr_t)p) { @@ -130,7 +132,8 @@ void cb_each(struct cb_tree *t, const uint8_t *kpfx, size_t klen, for (i = 0; i < klen; i++) { if (p->k[i] != kpfx[i]) - return; /* "best" match failed */ + return 0; /* "best" match failed */ } - cb_descend(top, fn, arg); + + return cb_descend(top, fn, arg); }
diff --git a/cbtree.h b/cbtree.h index 43193ab..c374b1b 100644 --- a/cbtree.h +++ b/cbtree.h
@@ -30,11 +30,6 @@ struct cb_tree { struct cb_node *root; }; -enum cb_next { - CB_CONTINUE = 0, - CB_BREAK = 1 -}; - #define CBTREE_INIT { 0 } static inline void cb_init(struct cb_tree *t) @@ -46,9 +41,15 @@ static inline void cb_init(struct cb_tree *t) struct cb_node *cb_lookup(struct cb_tree *, const uint8_t *k, size_t klen); struct cb_node *cb_insert(struct cb_tree *, struct cb_node *, size_t klen); -typedef enum cb_next (*cb_iter)(struct cb_node *, void *arg); +/* + * Callback invoked by `cb_each()` for each node in the critbit tree. A return + * value of 0 will cause the iteration to continue, a non-zero return code will + * cause iteration to abort. The error code will be relayed back from + * `cb_each()` in that case. + */ +typedef int (*cb_iter)(struct cb_node *, void *arg); -void cb_each(struct cb_tree *, const uint8_t *kpfx, size_t klen, - cb_iter, void *arg); +int cb_each(struct cb_tree *, const uint8_t *kpfx, size_t klen, + cb_iter, void *arg); #endif /* CBTREE_H */
diff --git a/chdir-notify.c b/chdir-notify.c index 0d7bc04..f8bfe3c 100644 --- a/chdir-notify.c +++ b/chdir-notify.c
@@ -25,6 +25,24 @@ void chdir_notify_register(const char *name, list_add_tail(&e->list, &chdir_notify_entries); } +void chdir_notify_unregister(const char *name, chdir_notify_callback cb, + void *data) +{ + struct list_head *pos, *p; + + list_for_each_safe(pos, p, &chdir_notify_entries) { + struct chdir_notify_entry *e = + list_entry(pos, struct chdir_notify_entry, list); + + if (e->cb != cb || e->data != data || !e->name != !name || + (e->name && strcmp(e->name, name))) + continue; + + list_del(pos); + free(e); + } +} + static void reparent_cb(const char *name, const char *old_cwd, const char *new_cwd,
diff --git a/chdir-notify.h b/chdir-notify.h index 366e4c1..81eb69d 100644 --- a/chdir-notify.h +++ b/chdir-notify.h
@@ -41,6 +41,8 @@ typedef void (*chdir_notify_callback)(const char *name, const char *new_cwd, void *data); 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/ci/install-dependencies.sh b/ci/install-dependencies.sh index 6ee8216..c55441d 100755 --- a/ci/install-dependencies.sh +++ b/ci/install-dependencies.sh
@@ -76,6 +76,17 @@ sudo update-alternatives --set sudo /usr/bin/sudo.ws fi + # on uutils v0.2.2 from rust-coreutils, + # dirname "foo/." + # outputs "." instead of "foo" like it should. + # Use GNU coreutils to provide dirname instead. + # + # See <https://github.com/uutils/coreutils/issues/10508>. + if test -x /usr/bin/gnudirname + then + ln -sfT /usr/bin/gnudirname /usr/bin/dirname + fi + case "$distro" in ubuntu-*) mkdir --parents "$CUSTOM_PATH"
diff --git a/ci/lib.sh b/ci/lib.sh index f561884..42a2b6a 100755 --- a/ci/lib.sh +++ b/ci/lib.sh
@@ -231,6 +231,10 @@ distro=$(echo "$CI_JOB_IMAGE" | tr : -) elif test true = "$GITLAB_CI" then + # This environment is multiple kB in size and may cause us to exceed + # xargs(1) limits on Windows. + unset GITLAB_FEATURES + CI_TYPE=gitlab-ci CI_BRANCH="$CI_COMMIT_REF_NAME" CI_COMMIT="$CI_COMMIT_SHA" @@ -356,6 +360,9 @@ ;; linux-leaks|linux-reftable-leaks) export SANITIZE=leak + export NO_CVS_TESTS=LetsSaveSomeTime + export NO_SVN_TESTS=LetsSaveSomeTime + export NO_P4_TESTS=LetsSaveSomeTime ;; linux-asan-ubsan) export SANITIZE=address,undefined
diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh index 8bda62b..28cfe73 100755 --- a/ci/run-build-and-tests.sh +++ b/ci/run-build-and-tests.sh
@@ -5,6 +5,8 @@ . ${0%/*}/lib.sh +export TEST_CONTRIB_TOO=yes + case "$jobname" in fedora-breaking-changes-musl|linux-breaking-changes) export WITH_BREAKING_CHANGES=YesPlease @@ -36,6 +38,7 @@ linux-reftable|linux-reftable-leaks|osx-reftable) export GIT_TEST_DEFAULT_REF_FORMAT=reftable ;; + esac case "$jobname" in
diff --git a/ci/run-static-analysis.sh b/ci/run-static-analysis.sh index 9e9c726..ba67e80 100755 --- a/ci/run-static-analysis.sh +++ b/ci/run-static-analysis.sh
@@ -10,7 +10,7 @@ set +x fail= -for cocci_patch in contrib/coccinelle/*.patch +for cocci_patch in tools/coccinelle/*.patch do if test -s "$cocci_patch" then
diff --git a/ci/run-test-slice-meson.sh b/ci/run-test-slice-meson.sh new file mode 100755 index 0000000..a6df927 --- /dev/null +++ b/ci/run-test-slice-meson.sh
@@ -0,0 +1,13 @@ +#!/bin/sh + +# We must load the build options so we know where to find +# things like TEST_OUTPUT_DIRECTORY. This has to come before +# loading lib.sh, though, because it may clobber some CI lib +# variables like our custom GIT_TEST_OPTS. +. "$1"/GIT-BUILD-OPTIONS +. ${0%/*}/lib.sh + +group "Run tests" \ + meson test -C "$1" --no-rebuild --print-errorlogs \ + --test-args="$GIT_TEST_OPTS" --slice "$(($2))/$3" || +handle_failed_tests
diff --git a/ci/run-test-slice.sh b/ci/run-test-slice.sh index 0444c79..ff948e3 100755 --- a/ci/run-test-slice.sh +++ b/ci/run-test-slice.sh
@@ -5,9 +5,9 @@ . ${0%/*}/lib.sh -group "Run tests" make --quiet -C t T="$(cd t && - ./helper/test-tool path-utils slice-tests "$1" "$2" t[0-9]*.sh | - tr '\n' ' ')" || +TESTS=$(cd t && ./helper/test-tool path-utils slice-tests "$1" "$2" t[0-9]*.sh) + +group "Run tests" make --quiet -C t T="$(echo "$TESTS" | tr '\n' ' ')" || handle_failed_tests # We only have one unit test at the moment, so run it in the first slice
diff --git a/color.c b/color.c index 07ac8c9..00b53f9 100644 --- a/color.c +++ b/color.c
@@ -223,11 +223,6 @@ static int parse_attr(const char *name, size_t len) return -1; } -int color_parse(const char *value, char *dst) -{ - return color_parse_mem(value, strlen(value), dst); -} - /* * Write the ANSI color codes for "c" to "out"; the string should * already have the ANSI escape code in it. "out" should have enough @@ -264,7 +259,8 @@ static int color_empty(const struct color *c) return c->type <= COLOR_NORMAL; } -int color_parse_mem(const char *value, int value_len, char *dst) +static int color_parse_mem_1(const char *value, int value_len, + char *dst, int quiet) { const char *ptr = value; int len = value_len; @@ -365,10 +361,25 @@ int color_parse_mem(const char *value, int value_len, char *dst) OUT(0); return 0; bad: - return error(_("invalid color value: %.*s"), value_len, value); + return quiet ? -1 : error(_("invalid color value: %.*s"), value_len, value); #undef OUT } +int color_parse_mem(const char *value, int value_len, char *dst) +{ + return color_parse_mem_1(value, value_len, dst, 0); +} + +int color_parse(const char *value, char *dst) +{ + return color_parse_mem(value, strlen(value), dst); +} + +int color_parse_quietly(const char *value, char *dst) +{ + return color_parse_mem_1(value, strlen(value), dst, 1); +} + enum git_colorbool git_config_colorbool(const char *var, const char *value) { if (value) {
diff --git a/color.h b/color.h index 43e6c9a..0d72540 100644 --- a/color.h +++ b/color.h
@@ -118,6 +118,7 @@ bool want_color_fd(int fd, enum git_colorbool var); * terminal. */ int color_parse(const char *value, char *dst); +int color_parse_quietly(const char *value, char *dst); int color_parse_mem(const char *value, int len, char *dst); /*
diff --git a/command-list.txt b/command-list.txt index accd3d0..f9005cf 100644 --- a/command-list.txt +++ b/command-list.txt
@@ -115,6 +115,7 @@ git-gui mainporcelain git-hash-object plumbingmanipulators git-help ancillaryinterrogators complete +git-history mainporcelain history git-hook purehelpers git-http-backend synchingrepositories git-http-fetch synchelpers
diff --git a/commit-graph.c b/commit-graph.c index 474454d..df4b4a1 100644 --- a/commit-graph.c +++ b/commit-graph.c
@@ -965,7 +965,7 @@ static int fill_commit_in_graph(struct commit *item, do { if (g->chunk_extra_edges_size / sizeof(uint32_t) <= parent_data_pos) { error(_("commit-graph extra-edges pointer out of bounds")); - free_commit_list(item->parents); + commit_list_free(item->parents); item->parents = NULL; item->object.parsed = 0; return 0; @@ -1127,18 +1127,12 @@ struct tree *get_commit_tree_in_graph(struct repository *r, const struct commit return get_commit_tree_in_graph_one(r->objects->commit_graph, c); } -struct packed_commit_list { - struct commit **list; - size_t nr; - size_t alloc; -}; - struct write_commit_graph_context { struct repository *r; struct odb_source *odb_source; char *graph_name; struct oid_array oids; - struct packed_commit_list commits; + struct commit_stack commits; int num_extra_edges; int num_generation_data_overflows; unsigned long approx_nr_objects; @@ -1180,7 +1174,7 @@ static int write_graph_chunk_fanout(struct hashfile *f, { struct write_commit_graph_context *ctx = data; int i, count = 0; - struct commit **list = ctx->commits.list; + struct commit **list = ctx->commits.items; /* * Write the first-level table (the list is sorted, @@ -1206,7 +1200,7 @@ static int write_graph_chunk_oids(struct hashfile *f, void *data) { struct write_commit_graph_context *ctx = data; - struct commit **list = ctx->commits.list; + struct commit **list = ctx->commits.items; int count; for (count = 0; count < ctx->commits.nr; count++, list++) { display_progress(ctx->progress, ++ctx->progress_cnt); @@ -1226,8 +1220,8 @@ static int write_graph_chunk_data(struct hashfile *f, void *data) { struct write_commit_graph_context *ctx = data; - struct commit **list = ctx->commits.list; - struct commit **last = ctx->commits.list + ctx->commits.nr; + struct commit **list = ctx->commits.items; + struct commit **last = ctx->commits.items + ctx->commits.nr; uint32_t num_extra_edges = 0; while (list < last) { @@ -1249,7 +1243,7 @@ static int write_graph_chunk_data(struct hashfile *f, edge_value = GRAPH_PARENT_NONE; else { edge_value = oid_pos(&parent->item->object.oid, - ctx->commits.list, + ctx->commits.items, ctx->commits.nr, commit_to_oid); @@ -1280,7 +1274,7 @@ static int write_graph_chunk_data(struct hashfile *f, edge_value = GRAPH_EXTRA_EDGES_NEEDED | num_extra_edges; else { edge_value = oid_pos(&parent->item->object.oid, - ctx->commits.list, + ctx->commits.items, ctx->commits.nr, commit_to_oid); @@ -1332,7 +1326,7 @@ static int write_graph_chunk_generation_data(struct hashfile *f, int i, num_generation_data_overflows = 0; for (i = 0; i < ctx->commits.nr; i++) { - struct commit *c = ctx->commits.list[i]; + struct commit *c = ctx->commits.items[i]; timestamp_t offset; repo_parse_commit(ctx->r, c); offset = commit_graph_data_at(c)->generation - c->date; @@ -1355,7 +1349,7 @@ static int write_graph_chunk_generation_data_overflow(struct hashfile *f, struct write_commit_graph_context *ctx = data; int i; for (i = 0; i < ctx->commits.nr; i++) { - struct commit *c = ctx->commits.list[i]; + struct commit *c = ctx->commits.items[i]; timestamp_t offset = commit_graph_data_at(c)->generation - c->date; display_progress(ctx->progress, ++ctx->progress_cnt); @@ -1372,8 +1366,8 @@ static int write_graph_chunk_extra_edges(struct hashfile *f, void *data) { struct write_commit_graph_context *ctx = data; - struct commit **list = ctx->commits.list; - struct commit **last = ctx->commits.list + ctx->commits.nr; + struct commit **list = ctx->commits.items; + struct commit **last = ctx->commits.items + ctx->commits.nr; struct commit_list *parent; while (list < last) { @@ -1393,7 +1387,7 @@ static int write_graph_chunk_extra_edges(struct hashfile *f, /* Since num_parents > 2, this initializer is safe. */ for (parent = (*list)->parents->next; parent; parent = parent->next) { int edge_value = oid_pos(&parent->item->object.oid, - ctx->commits.list, + ctx->commits.items, ctx->commits.nr, commit_to_oid); @@ -1427,8 +1421,8 @@ static int write_graph_chunk_bloom_indexes(struct hashfile *f, void *data) { struct write_commit_graph_context *ctx = data; - struct commit **list = ctx->commits.list; - struct commit **last = ctx->commits.list + ctx->commits.nr; + struct commit **list = ctx->commits.items; + struct commit **last = ctx->commits.items + ctx->commits.nr; uint32_t cur_pos = 0; while (list < last) { @@ -1463,8 +1457,8 @@ static int write_graph_chunk_bloom_data(struct hashfile *f, void *data) { struct write_commit_graph_context *ctx = data; - struct commit **list = ctx->commits.list; - struct commit **last = ctx->commits.list + ctx->commits.nr; + struct commit **list = ctx->commits.items; + struct commit **last = ctx->commits.items + ctx->commits.nr; trace2_bloom_filter_settings(ctx); @@ -1485,24 +1479,16 @@ static int write_graph_chunk_bloom_data(struct hashfile *f, return 0; } -static int add_packed_commits(const struct object_id *oid, - struct packed_git *pack, - uint32_t pos, - void *data) +static int add_packed_commits_oi(const struct object_id *oid, + struct object_info *oi, + void *data) { struct write_commit_graph_context *ctx = (struct write_commit_graph_context*)data; - enum object_type type; - off_t offset = nth_packed_object_offset(pack, pos); - struct object_info oi = OBJECT_INFO_INIT; if (ctx->progress) display_progress(ctx->progress, ++ctx->progress_done); - oi.typep = &type; - if (packed_object_info(ctx->r, pack, offset, &oi) < 0) - die(_("unable to get type of object %s"), oid_to_hex(oid)); - - if (type != OBJ_COMMIT) + if (*oi->typep != OBJ_COMMIT) return 0; oid_array_append(&ctx->oids, oid); @@ -1511,6 +1497,22 @@ static int add_packed_commits(const struct object_id *oid, return 0; } +static int add_packed_commits(const struct object_id *oid, + struct packed_git *pack, + uint32_t pos, + void *data) +{ + enum object_type type; + off_t offset = nth_packed_object_offset(pack, pos); + struct object_info oi = OBJECT_INFO_INIT; + + oi.typep = &type; + if (packed_object_info(pack, offset, &oi) < 0) + die(_("unable to get type of object %s"), oid_to_hex(oid)); + + return add_packed_commits_oi(oid, &oi, data); +} + static void add_missing_parents(struct write_commit_graph_context *ctx, struct commit *commit) { struct commit_list *parent; @@ -1585,7 +1587,7 @@ static void close_reachable(struct write_commit_graph_context *ctx) struct compute_generation_info { struct repository *r; - struct packed_commit_list *commits; + struct commit_stack *commits; struct progress *progress; int progress_cnt; @@ -1622,7 +1624,7 @@ static void compute_reachable_generation_numbers( struct commit_list *list = NULL; for (i = 0; i < info->commits->nr; i++) { - struct commit *c = info->commits->list[i]; + struct commit *c = info->commits->items[i]; timestamp_t gen; repo_parse_commit(info->r, c); gen = info->get_generation(c, info->data); @@ -1729,7 +1731,7 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx) if (!ctx->trust_generation_numbers) { for (i = 0; i < ctx->commits.nr; i++) { - struct commit *c = ctx->commits.list[i]; + struct commit *c = ctx->commits.items[i]; repo_parse_commit(ctx->r, c); commit_graph_data_at(c)->generation = GENERATION_NUMBER_ZERO; } @@ -1738,7 +1740,7 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx) compute_reachable_generation_numbers(&info, 2); for (i = 0; i < ctx->commits.nr; i++) { - struct commit *c = ctx->commits.list[i]; + struct commit *c = ctx->commits.items[i]; timestamp_t offset = commit_graph_data_at(c)->generation - c->date; if (offset > GENERATION_NUMBER_V2_OFFSET_MAX) ctx->num_generation_data_overflows++; @@ -1760,8 +1762,8 @@ void ensure_generations_valid(struct repository *r, struct commit **commits, size_t nr) { int generation_version = get_configured_generation_version(r); - struct packed_commit_list list = { - .list = commits, + struct commit_stack list = { + .items = commits, .alloc = nr, .nr = nr, }; @@ -1804,7 +1806,7 @@ static void compute_bloom_filters(struct write_commit_graph_context *ctx) _("Computing commit changed paths Bloom filters"), ctx->commits.nr); - DUP_ARRAY(sorted_commits, ctx->commits.list, ctx->commits.nr); + DUP_ARRAY(sorted_commits, ctx->commits.items, ctx->commits.nr); if (ctx->order_by_pack) QSORT(sorted_commits, ctx->commits.nr, commit_pos_cmp); @@ -1851,18 +1853,16 @@ struct refs_cb_data { struct progress *progress; }; -static int add_ref_to_set(const char *refname UNUSED, - const char *referent UNUSED, - const struct object_id *oid, - int flags UNUSED, void *cb_data) +static int add_ref_to_set(const struct reference *ref, void *cb_data) { + const struct object_id *maybe_peeled = ref->oid; struct object_id peeled; struct refs_cb_data *data = (struct refs_cb_data *)cb_data; - if (!peel_iterated_oid(data->repo, oid, &peeled)) - oid = &peeled; - if (odb_read_object_info(data->repo->objects, oid, NULL) == OBJ_COMMIT) - oidset_insert(data->commits, oid); + if (!reference_get_peeled_oid(data->repo, ref, &peeled)) + maybe_peeled = &peeled; + if (odb_read_object_info(data->repo->objects, maybe_peeled, NULL) == OBJ_COMMIT) + oidset_insert(data->commits, maybe_peeled); display_progress(data->progress, oidset_size(data->commits)); @@ -1935,7 +1935,7 @@ static int fill_oids_from_packs(struct write_commit_graph_context *ctx, goto cleanup; } for_each_object_in_pack(p, add_packed_commits, ctx, - FOR_EACH_OBJECT_PACK_ORDER); + ODB_FOR_EACH_OBJECT_PACK_ORDER); close_pack(p); free(p); } @@ -1967,13 +1967,28 @@ static int fill_oids_from_commits(struct write_commit_graph_context *ctx, static void fill_oids_from_all_packs(struct write_commit_graph_context *ctx) { + struct odb_source *source; + enum object_type type; + struct odb_for_each_object_options opts = { + .flags = ODB_FOR_EACH_OBJECT_PACK_ORDER, + }; + struct object_info oi = { + .typep = &type, + }; + if (ctx->report_progress) ctx->progress = start_delayed_progress( ctx->r, _("Finding commits for commit graph among packed objects"), ctx->approx_nr_objects); - for_each_packed_object(ctx->r, add_packed_commits, ctx, - FOR_EACH_OBJECT_PACK_ORDER); + + 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); + } + if (ctx->progress_done < ctx->approx_nr_objects) display_progress(ctx->progress, ctx->approx_nr_objects); stop_progress(&ctx->progress); @@ -1994,26 +2009,26 @@ static void copy_oids_to_commits(struct write_commit_graph_context *ctx) oid_array_sort(&ctx->oids); for (i = 0; i < ctx->oids.nr; i = oid_array_next_unique(&ctx->oids, i)) { unsigned int num_parents; + struct commit *commit; display_progress(ctx->progress, i + 1); - ALLOC_GROW(ctx->commits.list, ctx->commits.nr + 1, ctx->commits.alloc); - ctx->commits.list[ctx->commits.nr] = lookup_commit(ctx->r, &ctx->oids.oid[i]); + commit = lookup_commit(ctx->r, &ctx->oids.oid[i]); if (ctx->split && flags != COMMIT_GRAPH_SPLIT_REPLACE && - commit_graph_position(ctx->commits.list[ctx->commits.nr]) != COMMIT_NOT_FROM_GRAPH) + commit_graph_position(commit) != COMMIT_NOT_FROM_GRAPH) continue; if (ctx->split && flags == COMMIT_GRAPH_SPLIT_REPLACE) - repo_parse_commit(ctx->r, ctx->commits.list[ctx->commits.nr]); + repo_parse_commit(ctx->r, commit); else - repo_parse_commit_no_graph(ctx->r, ctx->commits.list[ctx->commits.nr]); + repo_parse_commit_no_graph(ctx->r, commit); - num_parents = commit_list_count(ctx->commits.list[ctx->commits.nr]->parents); + num_parents = commit_list_count(commit->parents); if (num_parents > 2) ctx->num_extra_edges += num_parents - 1; - ctx->commits.nr++; + commit_stack_push(&ctx->commits, commit); } stop_progress(&ctx->progress); } @@ -2332,7 +2347,7 @@ static void merge_commit_graph(struct write_commit_graph_context *ctx, oid_to_hex(&g->oid), (uintmax_t)st_add(ctx->commits.nr, g->num_commits)); - ALLOC_GROW(ctx->commits.list, ctx->commits.nr + g->num_commits, ctx->commits.alloc); + commit_stack_grow(&ctx->commits, g->num_commits); for (i = 0; i < g->num_commits; i++) { struct object_id oid; @@ -2345,10 +2360,8 @@ static void merge_commit_graph(struct write_commit_graph_context *ctx, /* only add commits if they still exist in the repo */ result = lookup_commit_reference_gently(ctx->r, &oid, 1); - if (result) { - ctx->commits.list[ctx->commits.nr] = result; - ctx->commits.nr++; - } + if (result) + commit_stack_push(&ctx->commits, result); } } @@ -2369,14 +2382,14 @@ static void sort_and_scan_merged_commits(struct write_commit_graph_context *ctx) _("Scanning merged commits"), ctx->commits.nr); - QSORT(ctx->commits.list, ctx->commits.nr, commit_compare); + QSORT(ctx->commits.items, ctx->commits.nr, commit_compare); ctx->num_extra_edges = 0; for (i = 0; i < ctx->commits.nr; i++) { display_progress(ctx->progress, i + 1); - if (i && oideq(&ctx->commits.list[i - 1]->object.oid, - &ctx->commits.list[i]->object.oid)) { + if (i && oideq(&ctx->commits.items[i - 1]->object.oid, + &ctx->commits.items[i]->object.oid)) { /* * Silently ignore duplicates. These were likely * created due to a commit appearing in multiple @@ -2387,10 +2400,10 @@ static void sort_and_scan_merged_commits(struct write_commit_graph_context *ctx) } else { unsigned int num_parents; - ctx->commits.list[dedup_i] = ctx->commits.list[i]; + ctx->commits.items[dedup_i] = ctx->commits.items[i]; dedup_i++; - num_parents = commit_list_count(ctx->commits.list[i]->parents); + num_parents = commit_list_count(ctx->commits.items[i]->parents); if (num_parents > 2) ctx->num_extra_edges += num_parents - 1; } @@ -2597,7 +2610,8 @@ int write_commit_graph(struct odb_source *source, replace = ctx.opts->split_flags & COMMIT_GRAPH_SPLIT_REPLACE; } - ctx.approx_nr_objects = repo_approximate_object_count(r); + if (odb_count_objects(r->objects, ODB_COUNT_OBJECTS_APPROXIMATE, &ctx.approx_nr_objects) < 0) + ctx.approx_nr_objects = 0; if (ctx.append && g) { for (i = 0; i < g->num_commits; i++) { @@ -2668,7 +2682,7 @@ int write_commit_graph(struct odb_source *source, cleanup: free(ctx.graph_name); free(ctx.base_graph_name); - free(ctx.commits.list); + commit_stack_clear(&ctx.commits); oid_array_clear(&ctx.oids); clear_topo_level_slab(&topo_levels);
diff --git a/commit-reach.c b/commit-reach.c index cc18c86..d3a9b3e 100644 --- a/commit-reach.c +++ b/commit-reach.c
@@ -109,7 +109,7 @@ static int paint_down_to_common(struct repository *r, continue; if (repo_parse_commit(r, p)) { clear_prio_queue(&queue); - free_commit_list(*result); + commit_list_free(*result); *result = NULL; /* * At this stage, we know that the commit is @@ -166,7 +166,7 @@ static int merge_bases_many(struct repository *r, } if (paint_down_to_common(r, one, n, twos, 0, 0, &list)) { - free_commit_list(list); + commit_list_free(list); return -1; } @@ -195,8 +195,8 @@ int get_octopus_merge_bases(struct commit_list *in, struct commit_list **result) struct commit_list *bases = NULL; if (repo_get_merge_bases(the_repository, i->item, j->item, &bases) < 0) { - free_commit_list(bases); - free_commit_list(*result); + commit_list_free(bases); + commit_list_free(*result); *result = NULL; return -1; } @@ -207,7 +207,7 @@ int get_octopus_merge_bases(struct commit_list *in, struct commit_list **result) for (k = bases; k; k = k->next) end = k; } - free_commit_list(*result); + commit_list_free(*result); *result = new_commits; } return 0; @@ -249,7 +249,7 @@ static int remove_redundant_no_gen(struct repository *r, work, min_generation, 0, &common)) { clear_commit_marks(array[i], all_flags); clear_commit_marks_many(filled, work, all_flags); - free_commit_list(common); + commit_list_free(common); free(work); free(redundant); free(filled_index); @@ -262,7 +262,7 @@ static int remove_redundant_no_gen(struct repository *r, redundant[filled_index[j]] = 1; clear_commit_marks(array[i], all_flags); clear_commit_marks_many(filled, work, all_flags); - free_commit_list(common); + commit_list_free(common); } /* Now collect the result */ @@ -283,8 +283,8 @@ static int remove_redundant_with_gen(struct repository *r, { size_t i, count_non_stale = 0, count_still_independent = cnt; timestamp_t min_generation = GENERATION_NUMBER_INFINITY; - struct commit **walk_start, **sorted; - size_t walk_start_nr = 0, walk_start_alloc = cnt; + struct commit **sorted; + struct commit_stack walk_start = COMMIT_STACK_INIT; size_t min_gen_pos = 0; /* @@ -298,7 +298,7 @@ static int remove_redundant_with_gen(struct repository *r, QSORT(sorted, cnt, compare_commits_by_gen); min_generation = commit_graph_generation(sorted[0]); - ALLOC_ARRAY(walk_start, walk_start_alloc); + commit_stack_grow(&walk_start, cnt); /* Mark all parents of the input as STALE */ for (i = 0; i < cnt; i++) { @@ -312,18 +312,17 @@ static int remove_redundant_with_gen(struct repository *r, repo_parse_commit(r, parents->item); if (!(parents->item->object.flags & STALE)) { parents->item->object.flags |= STALE; - ALLOC_GROW(walk_start, walk_start_nr + 1, walk_start_alloc); - walk_start[walk_start_nr++] = parents->item; + commit_stack_push(&walk_start, parents->item); } parents = parents->next; } } - QSORT(walk_start, walk_start_nr, compare_commits_by_gen); + QSORT(walk_start.items, walk_start.nr, compare_commits_by_gen); /* remove STALE bit for now to allow walking through parents */ - for (i = 0; i < walk_start_nr; i++) - walk_start[i]->object.flags &= ~STALE; + for (i = 0; i < walk_start.nr; i++) + walk_start.items[i]->object.flags &= ~STALE; /* * Start walking from the highest generation. Hopefully, it will @@ -331,12 +330,12 @@ static int remove_redundant_with_gen(struct repository *r, * terminate early. Otherwise, we will do the same amount of work * as before. */ - for (i = walk_start_nr; i && count_still_independent > 1; i--) { + for (i = walk_start.nr; i && count_still_independent > 1; i--) { /* push the STALE bits up to min generation */ struct commit_list *stack = NULL; - commit_list_insert(walk_start[i - 1], &stack); - walk_start[i - 1]->object.flags |= STALE; + commit_list_insert(walk_start.items[i - 1], &stack); + walk_start.items[i - 1]->object.flags |= STALE; while (stack) { struct commit_list *parents; @@ -375,7 +374,7 @@ static int remove_redundant_with_gen(struct repository *r, if (!parents) pop_commit(&stack); } - free_commit_list(stack); + commit_list_free(stack); } free(sorted); @@ -390,8 +389,8 @@ static int remove_redundant_with_gen(struct repository *r, } /* clear marks */ - clear_commit_marks_many(walk_start_nr, walk_start, STALE); - free(walk_start); + clear_commit_marks_many(walk_start.nr, walk_start.items, STALE); + commit_stack_clear(&walk_start); *dedup_cnt = count_non_stale; return 0; @@ -452,7 +451,7 @@ static int get_merge_bases_many_0(struct repository *r, CALLOC_ARRAY(rslt, cnt); for (list = *result, i = 0; list; list = list->next) rslt[i++] = list->item; - free_commit_list(*result); + commit_list_free(*result); *result = NULL; clear_commit_marks(one, all_flags); @@ -511,7 +510,7 @@ int repo_is_descendant_of(struct repository *r, int result; commit_list_insert(commit, &from_list); result = can_all_from_reach(from_list, with_commit, 0); - free_commit_list(from_list); + commit_list_free(from_list); return result; } else { while (with_commit) { @@ -562,7 +561,7 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit, ret = 1; clear_commit_marks(commit, all_flags); clear_commit_marks_many(nr_reference, reference, all_flags); - free_commit_list(bases); + commit_list_free(bases); return ret; } @@ -579,7 +578,7 @@ int repo_in_merge_bases(struct repository *r, next = commit_list_append(commit, next); res = repo_is_descendant_of(r, reference, list); - free_commit_list(list); + commit_list_free(list); return res; } @@ -627,7 +626,7 @@ struct commit_list *reduce_heads(struct commit_list *heads) void reduce_heads_replace(struct commit_list **heads) { struct commit_list *result = reduce_heads(*heads); - free_commit_list(*heads); + commit_list_free(*heads); *heads = result; } @@ -662,7 +661,7 @@ int ref_newer(const struct object_id *new_oid, const struct object_id *old_oid) new_commit, old_commit_list); if (ret < 0) exit(128); - free_commit_list(old_commit_list); + commit_list_free(old_commit_list); return ret; } @@ -1118,10 +1117,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); - while (prio_queue_peek(&queue)) { - struct commit *c = prio_queue_get(&queue); - free_bit_array(c); - } + for (size_t i = 0; i < queue.nr; i++) + free_bit_array(queue.array[i].data); clear_bit_arrays(&bit_arrays); clear_prio_queue(&queue); } @@ -1237,7 +1234,7 @@ void tips_reachable_from_bases(struct repository *r, done: free(commits); repo_clear_commit_marks(r, SEEN); - free_commit_list(stack); + commit_list_free(stack); } /*
diff --git a/commit.c b/commit.c index 16d91b2..80d8d07 100644 --- a/commit.c +++ b/commit.c
@@ -31,7 +31,6 @@ #include "parse.h" #include "object-file.h" #include "object-file-convert.h" -#include "prio-queue.h" static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **); @@ -43,13 +42,35 @@ const char *commit_type = "commit"; struct commit *lookup_commit_reference_gently(struct repository *r, const struct object_id *oid, int quiet) { - struct object *obj = deref_tag(r, - parse_object(r, oid), - NULL, 0); + const struct object_id *maybe_peeled; + struct object_id peeled_oid; + struct commit *commit; + enum object_type type; - if (!obj) + switch (peel_object_ext(r, oid, &peeled_oid, 0, &type)) { + case PEEL_NON_TAG: + maybe_peeled = oid; + break; + case PEEL_PEELED: + maybe_peeled = &peeled_oid; + break; + default: return NULL; - return object_as_type(obj, OBJ_COMMIT, quiet); + } + + if (type != OBJ_COMMIT) { + if (!quiet) + error(_("object %s is a %s, not a %s"), + oid_to_hex(oid), type_name(type), + type_name(OBJ_COMMIT)); + return NULL; + } + + commit = lookup_commit(r, maybe_peeled); + if (!commit || repo_parse_commit_gently(r, commit, quiet) < 0) + return NULL; + + return commit; } struct commit *lookup_commit_reference(struct repository *r, const struct object_id *oid) @@ -191,7 +212,7 @@ void unparse_commit(struct repository *r, const struct object_id *oid) if (!c->object.parsed) return; - free_commit_list(c->parents); + commit_list_free(c->parents); c->parents = NULL; c->object.parsed = 0; } @@ -436,7 +457,7 @@ void release_commit_memory(struct parsed_object_pool *pool, struct commit *c) set_commit_tree(c, NULL); free_commit_buffer(pool, c); c->index = 0; - free_commit_list(c->parents); + commit_list_free(c->parents); c->object.parsed = 0; } @@ -480,7 +501,7 @@ int parse_commit_buffer(struct repository *r, struct commit *item, const void *b * same error, but that's good, since it lets our caller know * the result cannot be trusted. */ - free_commit_list(item->parents); + commit_list_free(item->parents); item->parents = NULL; tail += size; @@ -680,7 +701,7 @@ unsigned commit_list_count(const struct commit_list *l) return c; } -struct commit_list *copy_commit_list(const struct commit_list *list) +struct commit_list *commit_list_copy(const struct commit_list *list) { struct commit_list *head = NULL; struct commit_list **pp = &head; @@ -691,7 +712,7 @@ struct commit_list *copy_commit_list(const struct commit_list *list) return head; } -struct commit_list *reverse_commit_list(struct commit_list *list) +struct commit_list *commit_list_reverse(struct commit_list *list) { struct commit_list *next = NULL, *current, *backup; for (current = list; current; current = backup) { @@ -702,7 +723,7 @@ struct commit_list *reverse_commit_list(struct commit_list *list) return next; } -void free_commit_list(struct commit_list *list) +void commit_list_free(struct commit_list *list) { while (list) pop_commit(&list); @@ -977,7 +998,7 @@ void sort_in_topological_order(struct commit_list **list, enum rev_sort_order so prio_queue_reverse(&queue); /* We no longer need the commit list */ - free_commit_list(orig); + commit_list_free(orig); pptr = list; *list = NULL; @@ -1015,9 +1036,7 @@ void sort_in_topological_order(struct commit_list **list, enum rev_sort_order so } struct rev_collect { - struct commit **commit; - int nr; - int alloc; + struct commit_stack stack; unsigned int initial : 1; }; @@ -1034,8 +1053,7 @@ static void add_one_commit(struct object_id *oid, struct rev_collect *revs) repo_parse_commit(the_repository, commit)) return; - ALLOC_GROW(revs->commit, revs->nr + 1, revs->alloc); - revs->commit[revs->nr++] = commit; + commit_stack_push(&revs->stack, commit); commit->object.flags |= TMP_MARK; } @@ -1060,7 +1078,7 @@ struct commit *get_fork_point(const char *refname, struct commit *commit) struct object_id oid; struct rev_collect revs; struct commit_list *bases = NULL; - int i; + size_t i; struct commit *ret = NULL; char *full_refname; @@ -1074,19 +1092,19 @@ struct commit *get_fork_point(const char *refname, struct commit *commit) die("Ambiguous refname: '%s'", refname); } - memset(&revs, 0, sizeof(revs)); + commit_stack_init(&revs.stack); revs.initial = 1; refs_for_each_reflog_ent(get_main_ref_store(the_repository), full_refname, collect_one_reflog_ent, &revs); - if (!revs.nr) + if (!revs.stack.nr) add_one_commit(&oid, &revs); - for (i = 0; i < revs.nr; i++) - revs.commit[i]->object.flags &= ~TMP_MARK; + for (i = 0; i < revs.stack.nr; i++) + revs.stack.items[i]->object.flags &= ~TMP_MARK; - if (repo_get_merge_bases_many(the_repository, commit, revs.nr, - revs.commit, &bases) < 0) + if (repo_get_merge_bases_many(the_repository, commit, revs.stack.nr, + revs.stack.items, &bases) < 0) exit(128); /* @@ -1097,17 +1115,17 @@ struct commit *get_fork_point(const char *refname, struct commit *commit) goto cleanup_return; /* And the found one must be one of the reflog entries */ - for (i = 0; i < revs.nr; i++) - if (&bases->item->object == &revs.commit[i]->object) + for (i = 0; i < revs.stack.nr; i++) + if (&bases->item->object == &revs.stack.items[i]->object) break; /* found */ - if (revs.nr <= i) + if (revs.stack.nr <= i) goto cleanup_return; ret = bases->item; cleanup_return: - free(revs.commit); - free_commit_list(bases); + commit_stack_clear(&revs.stack); + commit_list_free(bases); free(full_refname); return ret; } @@ -1152,18 +1170,6 @@ int add_header_signature(struct strbuf *buf, struct strbuf *sig, const struct gi return 0; } -static int sign_commit_to_strbuf(struct strbuf *sig, struct strbuf *buf, const char *keyid) -{ - char *keyid_to_free = NULL; - int ret = 0; - if (!keyid || !*keyid) - keyid = keyid_to_free = get_signing_key(); - if (sign_buffer(buf, sig, keyid)) - ret = -1; - free(keyid_to_free); - return ret; -} - int parse_signed_commit(const struct commit *commit, struct strbuf *payload, struct strbuf *signature, const struct git_hash_algo *algop) @@ -1315,7 +1321,8 @@ static void handle_signed_tag(const struct commit *parent, struct commit_extra_h free(buf); } -int check_commit_signature(const struct commit *commit, struct signature_check *sigc) +int verify_commit_buffer(const char *buffer, size_t size, + struct signature_check *sigc) { struct strbuf payload = STRBUF_INIT; struct strbuf signature = STRBUF_INIT; @@ -1323,7 +1330,8 @@ int check_commit_signature(const struct commit *commit, struct signature_check * sigc->result = 'N'; - if (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0) + if (parse_buffer_signed_by_header(buffer, size, &payload, + &signature, the_hash_algo) <= 0) goto out; sigc->payload_type = SIGNATURE_PAYLOAD_COMMIT; @@ -1337,6 +1345,17 @@ int check_commit_signature(const struct commit *commit, struct signature_check * return ret; } +int check_commit_signature(const struct commit *commit, struct signature_check *sigc) +{ + unsigned long size; + const char *buffer = repo_get_commit_buffer(the_repository, commit, &size); + int ret = verify_commit_buffer(buffer, size, sigc); + + repo_unuse_commit_buffer(the_repository, commit, buffer); + + return ret; +} + void verify_merge_signature(struct commit *commit, int verbosity, int check_trust) { @@ -1728,7 +1747,8 @@ int commit_tree_extended(const char *msg, size_t msg_len, oidcpy(&parent_buf[i++], &p->item->object.oid); write_commit_tree(&buffer, msg, msg_len, tree, parent_buf, nparents, author, committer, extra); - if (sign_commit && sign_commit_to_strbuf(&sig, &buffer, sign_commit)) { + if (sign_commit && sign_buffer(&buffer, &sig, sign_commit, + SIGN_BUFFER_USE_DEFAULT_KEY)) { result = -1; goto out; } @@ -1760,7 +1780,9 @@ int commit_tree_extended(const char *msg, size_t msg_len, free_commit_extra_headers(compat_extra); free(mapped_parents); - if (sign_commit && sign_commit_to_strbuf(&compat_sig, &compat_buffer, sign_commit)) { + if (sign_commit && sign_buffer(&compat_buffer, &compat_sig, + sign_commit, + SIGN_BUFFER_USE_DEFAULT_KEY)) { result = -1; goto out; } @@ -1968,3 +1990,31 @@ int run_commit_hook(int editor_is_used, const char *index_file, opt.invoked_hook = invoked_hook; return run_hooks_opt(the_repository, name, &opt); } + +void commit_stack_init(struct commit_stack *stack) +{ + stack->items = NULL; + stack->nr = stack->alloc = 0; +} + +void commit_stack_grow(struct commit_stack *stack, size_t extra) +{ + ALLOC_GROW(stack->items, st_add(stack->nr, extra), stack->alloc); +} + +void commit_stack_push(struct commit_stack *stack, struct commit *commit) +{ + commit_stack_grow(stack, 1); + stack->items[stack->nr++] = commit; +} + +struct commit *commit_stack_pop(struct commit_stack *stack) +{ + return stack->nr ? stack->items[--stack->nr] : NULL; +} + +void commit_stack_clear(struct commit_stack *stack) +{ + free(stack->items); + commit_stack_init(stack); +}
diff --git a/commit.h b/commit.h index 1d6e0c7..5815004 100644 --- a/commit.h +++ b/commit.h
@@ -103,16 +103,26 @@ static inline int repo_parse_commit(struct repository *r, struct commit *item) return repo_parse_commit_gently(r, item, 0); } +void unparse_commit(struct repository *r, const struct object_id *oid); + static inline int repo_parse_commit_no_graph(struct repository *r, struct commit *commit) { + /* + * When the commit has been parsed but its tree wasn't populated then + * this is an indicator that it has been parsed via the commit-graph. + * We cannot read the tree via the commit-graph, as we're explicitly + * told not to use it. We thus have to first un-parse the object so + * that we can re-parse it without the graph. + */ + if (commit->object.parsed && !commit->maybe_tree) + unparse_commit(r, &commit->object.oid); + return repo_parse_commit_internal(r, commit, 0, 0); } void parse_commit_or_die(struct commit *item); -void unparse_commit(struct repository *r, const struct object_id *oid); - struct buffer_slab; struct buffer_slab *allocate_commit_buffer_slab(void); void free_commit_buffer_slab(struct buffer_slab *bs); @@ -186,12 +196,31 @@ struct commit_list *commit_list_insert_by_date(struct commit *item, void commit_list_sort_by_date(struct commit_list **list); /* Shallow copy of the input list */ -struct commit_list *copy_commit_list(const struct commit_list *list); +struct commit_list *commit_list_copy(const struct commit_list *list); /* Modify list in-place to reverse it, returning new head; list will be tail */ -struct commit_list *reverse_commit_list(struct commit_list *list); +struct commit_list *commit_list_reverse(struct commit_list *list); -void free_commit_list(struct commit_list *list); +void commit_list_free(struct commit_list *list); + +/* + * Deprecated compatibility functions for `struct commit_list`, to be removed + * once Git 2.53 is released. + */ +static inline struct commit_list *copy_commit_list(struct commit_list *l) +{ + return commit_list_copy(l); +} + +static inline struct commit_list *reverse_commit_list(struct commit_list *l) +{ + return commit_list_reverse(l); +} + +static inline void free_commit_list(struct commit_list *l) +{ + commit_list_free(l); +} struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */ @@ -258,7 +287,7 @@ int for_each_commit_graft(each_commit_graft_fn, void *); int interactive_add(struct repository *repo, const char **argv, const char *prefix, - int patch, struct add_p_opt *add_p_opt); + int patch, struct interactive_options *opts); struct commit_extra_header { struct commit_extra_header *next; @@ -333,6 +362,13 @@ int remove_signature(struct strbuf *buf); */ int check_commit_signature(const struct commit *commit, struct signature_check *sigc); +/* + * Same as check_commit_signature() but accepts a commit buffer and + * its size, instead of a `struct commit *`. + */ +int verify_commit_buffer(const char *buffer, size_t size, + struct signature_check *sigc); + /* record author-date for each commit object */ struct author_date_slab; void record_author_date(struct author_date_slab *author_date, @@ -364,8 +400,6 @@ LAST_ARG_MUST_BE_NULL int run_commit_hook(int editor_is_used, const char *index_file, int *invoked_hook, const char *name, ...); -/* Sign a commit or tag buffer, storing the result in a header. */ -int sign_with_header(struct strbuf *buf, const char *keyid); /* Parse the signature out of a header. */ int parse_buffer_signed_by_header(const char *buffer, unsigned long size, @@ -374,4 +408,16 @@ int parse_buffer_signed_by_header(const char *buffer, const struct git_hash_algo *algop); int add_header_signature(struct strbuf *buf, struct strbuf *sig, const struct git_hash_algo *algo); +struct commit_stack { + struct commit **items; + size_t nr, alloc; +}; +#define COMMIT_STACK_INIT { 0 } + +void commit_stack_init(struct commit_stack *); +void commit_stack_grow(struct commit_stack *, size_t); +void commit_stack_push(struct commit_stack *, struct commit *); +struct commit *commit_stack_pop(struct commit_stack *); +void commit_stack_clear(struct commit_stack *); + #endif /* COMMIT_H */
diff --git a/compat/darwin/procinfo.c b/compat/darwin/procinfo.c new file mode 100644 index 0000000..c8954f0 --- /dev/null +++ b/compat/darwin/procinfo.c
@@ -0,0 +1,97 @@ +#include "git-compat-util.h" +#include "strbuf.h" +#include "strvec.h" +#include "trace2.h" +#include <sys/sysctl.h> + +/* + * An arbitrarily chosen value to limit the depth of the ancestor chain. + */ +#define NR_PIDS_LIMIT 10 + +/* + * Get the process name and parent PID for a given PID using sysctl(). + * Returns 0 on success, -1 on failure. + */ +static int get_proc_info(pid_t pid, struct strbuf *name, pid_t *ppid) +{ + int mib[4]; + struct kinfo_proc proc; + size_t size = sizeof(proc); + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = pid; + + if (sysctl(mib, 4, &proc, &size, NULL, 0) < 0) + return -1; + + if (size == 0) + return -1; + + strbuf_addstr(name, proc.kp_proc.p_comm); + *ppid = proc.kp_eproc.e_ppid; + + return 0; +} + +/* + * Recursively push process names onto the ancestry array. + * We guard against cycles by limiting the depth to NR_PIDS_LIMIT. + */ +static void push_ancestry_name(struct strvec *names, pid_t pid, int depth) +{ + struct strbuf name = STRBUF_INIT; + pid_t ppid; + + if (depth >= NR_PIDS_LIMIT) + return; + + if (pid <= 0) + return; + + if (get_proc_info(pid, &name, &ppid) < 0) + goto cleanup; + + strvec_push(names, name.buf); + + /* + * Recurse to the parent process. Stop if ppid not valid + * or if we've reached ourselves (cycle). + */ + if (ppid && ppid != pid) + push_ancestry_name(names, ppid, depth + 1); + +cleanup: + strbuf_release(&name); +} + +void trace2_collect_process_info(enum trace2_process_info_reason reason) +{ + struct strvec names = STRVEC_INIT; + + if (!trace2_is_enabled()) + return; + + switch (reason) { + case TRACE2_PROCESS_INFO_STARTUP: + push_ancestry_name(&names, getppid(), 0); + if (names.nr) + trace2_cmd_ancestry(names.v); + + strvec_clear(&names); + break; + + case TRACE2_PROCESS_INFO_EXIT: + /* + * The Windows version of this calls its + * get_peak_memory_info() here. We may want to insert + * similar process-end statistics here in the future. + */ + break; + + default: + BUG("trace2_collect_process_info: unknown reason '%d'", reason); + } +}
diff --git a/compat/mingw-posix.h b/compat/mingw-posix.h index 631a208..2d989fd 100644 --- a/compat/mingw-posix.h +++ b/compat/mingw-posix.h
@@ -121,10 +121,6 @@ struct utsname { * trivial stubs */ -static inline int readlink(const char *path UNUSED, char *buf UNUSED, size_t bufsiz UNUSED) -{ errno = ENOSYS; return -1; } -static inline int symlink(const char *oldpath UNUSED, const char *newpath UNUSED) -{ errno = ENOSYS; return -1; } static inline int fchmod(int fildes UNUSED, mode_t mode UNUSED) { errno = ENOSYS; return -1; } #ifndef __MINGW64_VERSION_MAJOR @@ -197,6 +193,8 @@ int setitimer(int type, struct itimerval *in, struct itimerval *out); int sigaction(int sig, struct sigaction *in, struct sigaction *out); int link(const char *oldpath, const char *newpath); int uname(struct utsname *buf); +int symlink(const char *target, const char *link); +int readlink(const char *path, char *buf, size_t bufsiz); /* * replacements of existing functions @@ -241,9 +239,6 @@ int mingw_chdir(const char *dirname); int mingw_chmod(const char *filename, int mode); #define chmod mingw_chmod -char *mingw_mktemp(char *template); -#define mktemp mingw_mktemp - char *mingw_getcwd(char *pointer, int len); #define getcwd mingw_getcwd
diff --git a/compat/mingw.c b/compat/mingw.c index 736a07a..2023c16 100644 --- a/compat/mingw.c +++ b/compat/mingw.c
@@ -21,14 +21,13 @@ #define SECURITY_WIN32 #include <sspi.h> #include <wchar.h> +#include <winioctl.h> #include <winternl.h> #define STATUS_DELETE_PENDING ((NTSTATUS) 0xC0000056) #define HCAST(type, handle) ((type)(intptr_t)handle) -static const int delay[] = { 0, 1, 10, 20, 40 }; - void open_in_gdb(void) { static struct child_process cp = CHILD_PROCESS_INIT; @@ -103,6 +102,7 @@ int err_win_to_posix(DWORD winerr) case ERROR_INVALID_PARAMETER: error = EINVAL; break; case ERROR_INVALID_PASSWORD: error = EPERM; break; case ERROR_INVALID_PRIMARY_GROUP: error = EINVAL; break; + case ERROR_INVALID_REPARSE_DATA: error = EINVAL; break; case ERROR_INVALID_SIGNAL_NUMBER: error = EINVAL; break; case ERROR_INVALID_TARGET_HANDLE: error = EIO; break; case ERROR_INVALID_WORKSTATION: error = EACCES; break; @@ -117,6 +117,7 @@ int err_win_to_posix(DWORD winerr) case ERROR_NEGATIVE_SEEK: error = ESPIPE; break; case ERROR_NOACCESS: error = EFAULT; break; case ERROR_NONE_MAPPED: error = EINVAL; break; + case ERROR_NOT_A_REPARSE_POINT: error = EINVAL; break; case ERROR_NOT_ENOUGH_MEMORY: error = ENOMEM; break; case ERROR_NOT_READY: error = EAGAIN; break; case ERROR_NOT_SAME_DEVICE: error = EXDEV; break; @@ -137,6 +138,9 @@ int err_win_to_posix(DWORD winerr) case ERROR_PIPE_NOT_CONNECTED: error = EPIPE; break; case ERROR_PRIVILEGE_NOT_HELD: error = EACCES; break; case ERROR_READ_FAULT: error = EIO; break; + case ERROR_REPARSE_ATTRIBUTE_CONFLICT: error = EINVAL; break; + case ERROR_REPARSE_TAG_INVALID: error = EINVAL; break; + case ERROR_REPARSE_TAG_MISMATCH: error = EINVAL; break; case ERROR_SEEK: error = EIO; break; case ERROR_SEEK_ON_DEVICE: error = ESPIPE; break; case ERROR_SHARING_BUFFER_EXCEEDED: error = ENFILE; break; @@ -204,15 +208,12 @@ static int read_yes_no_answer(void) return -1; } -static int ask_yes_no_if_possible(const char *format, ...) +static int ask_yes_no_if_possible(const char *format, va_list args) { char question[4096]; const char *retry_hook; - va_list args; - va_start(args, format); vsnprintf(question, sizeof(question), format, args); - va_end(args); retry_hook = mingw_getenv("GIT_ASK_YESNO"); if (retry_hook) { @@ -237,6 +238,31 @@ static int ask_yes_no_if_possible(const char *format, ...) } } +static int retry_ask_yes_no(int *tries, const char *format, ...) +{ + static const int delay[] = { 0, 1, 10, 20, 40 }; + va_list args; + int result, saved_errno = errno; + + if ((*tries) < ARRAY_SIZE(delay)) { + /* + * We assume that some other process had the file open at the wrong + * moment and retry. In order to give the other process a higher + * chance to complete its operation, we give up our time slice now. + * If we have to retry again, we do sleep a bit. + */ + Sleep(delay[*tries]); + (*tries)++; + return 1; + } + + va_start(args, format); + result = ask_yes_no_if_possible(format, args); + va_end(args); + errno = saved_errno; + return result; +} + /* Windows only */ enum hide_dotfiles_type { HIDE_DOTFILES_FALSE = 0, @@ -270,6 +296,134 @@ int mingw_core_config(const char *var, const char *value, return 0; } +static inline int is_wdir_sep(wchar_t wchar) +{ + return wchar == L'/' || wchar == L'\\'; +} + +static const wchar_t *make_relative_to(const wchar_t *path, + const wchar_t *relative_to, wchar_t *out, + size_t size) +{ + size_t i = wcslen(relative_to), len; + + /* Is `path` already absolute? */ + if (is_wdir_sep(path[0]) || + (iswalpha(path[0]) && path[1] == L':' && is_wdir_sep(path[2]))) + return path; + + while (i > 0 && !is_wdir_sep(relative_to[i - 1])) + i--; + + /* Is `relative_to` in the current directory? */ + if (!i) + return path; + + len = wcslen(path); + if (i + len + 1 > size) { + error("Could not make '%ls' relative to '%ls' (too large)", + path, relative_to); + return NULL; + } + + memcpy(out, relative_to, i * sizeof(wchar_t)); + wcscpy(out + i, path); + return out; +} + +static DWORD symlink_file_flags = 0, symlink_directory_flags = 1; + +enum phantom_symlink_result { + PHANTOM_SYMLINK_RETRY, + PHANTOM_SYMLINK_DONE, + PHANTOM_SYMLINK_DIRECTORY +}; + +/* + * Changes a file symlink to a directory symlink if the target exists and is a + * directory. + */ +static enum phantom_symlink_result +process_phantom_symlink(const wchar_t *wtarget, const wchar_t *wlink) +{ + HANDLE hnd; + BY_HANDLE_FILE_INFORMATION fdata; + wchar_t relative[MAX_PATH]; + const wchar_t *rel; + + /* check that wlink is still a file symlink */ + if ((GetFileAttributesW(wlink) + & (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY)) + != FILE_ATTRIBUTE_REPARSE_POINT) + return PHANTOM_SYMLINK_DONE; + + /* make it relative, if necessary */ + rel = make_relative_to(wtarget, wlink, relative, ARRAY_SIZE(relative)); + if (!rel) + return PHANTOM_SYMLINK_DONE; + + /* let Windows resolve the link by opening it */ + hnd = CreateFileW(rel, 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (hnd == INVALID_HANDLE_VALUE) { + errno = err_win_to_posix(GetLastError()); + return PHANTOM_SYMLINK_RETRY; + } + + if (!GetFileInformationByHandle(hnd, &fdata)) { + errno = err_win_to_posix(GetLastError()); + CloseHandle(hnd); + return PHANTOM_SYMLINK_RETRY; + } + CloseHandle(hnd); + + /* if target exists and is a file, we're done */ + if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + return PHANTOM_SYMLINK_DONE; + + /* otherwise recreate the symlink with directory flag */ + if (DeleteFileW(wlink) && + CreateSymbolicLinkW(wlink, wtarget, symlink_directory_flags)) + return PHANTOM_SYMLINK_DIRECTORY; + + errno = err_win_to_posix(GetLastError()); + return PHANTOM_SYMLINK_RETRY; +} + +/* keep track of newly created symlinks to non-existing targets */ +struct phantom_symlink_info { + struct phantom_symlink_info *next; + wchar_t *wlink; + wchar_t *wtarget; +}; + +static struct phantom_symlink_info *phantom_symlinks = NULL; +static CRITICAL_SECTION phantom_symlinks_cs; + +static void process_phantom_symlinks(void) +{ + struct phantom_symlink_info *current, **psi; + EnterCriticalSection(&phantom_symlinks_cs); + /* process phantom symlinks list */ + psi = &phantom_symlinks; + while ((current = *psi)) { + enum phantom_symlink_result result = process_phantom_symlink( + current->wtarget, current->wlink); + if (result == PHANTOM_SYMLINK_RETRY) { + psi = ¤t->next; + } else { + /* symlink was processed, remove from list */ + *psi = current->next; + free(current); + /* if symlink was a directory, start over */ + if (result == PHANTOM_SYMLINK_DIRECTORY) + psi = &phantom_symlinks; + } + } + LeaveCriticalSection(&phantom_symlinks_cs); +} + /* Normalizes NT paths as returned by some low-level APIs. */ static wchar_t *normalize_ntpath(wchar_t *wbuf) { @@ -297,7 +451,7 @@ static wchar_t *normalize_ntpath(wchar_t *wbuf) int mingw_unlink(const char *pathname, int handle_in_use_error) { - int ret, tries = 0; + int tries = 0; wchar_t wpathname[MAX_PATH]; if (xutftowcs_path(wpathname, pathname) < 0) return -1; @@ -305,29 +459,26 @@ int mingw_unlink(const char *pathname, int handle_in_use_error) if (DeleteFileW(wpathname)) return 0; - /* read-only files cannot be removed */ - _wchmod(wpathname, 0666); - while ((ret = _wunlink(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) { + do { + /* read-only files cannot be removed */ + _wchmod(wpathname, 0666); + if (!_wunlink(wpathname)) + return 0; if (!is_file_in_use_error(GetLastError())) break; - if (!handle_in_use_error) - return ret; - /* - * We assume that some other process had the source or - * destination file open at the wrong moment and retry. - * In order to give the other process a higher chance to - * complete its operation, we give up our time slice now. - * If we have to retry again, we do sleep a bit. + * _wunlink() / DeleteFileW() for directory symlinks fails with + * ERROR_ACCESS_DENIED (EACCES), so try _wrmdir() as well. This is the + * same error we get if a file is in use (already checked above). */ - Sleep(delay[tries]); - tries++; - } - while (ret == -1 && is_file_in_use_error(GetLastError()) && - ask_yes_no_if_possible("Unlink of file '%s' failed. " - "Should I try again?", pathname)) - ret = _wunlink(wpathname); - return ret; + if (!_wrmdir(wpathname)) + return 0; + + if (!handle_in_use_error) + return -1; + } while (retry_ask_yes_no(&tries, "Unlink of file '%s' failed. " + "Should I try again?", pathname)); + return -1; } static int is_dir_empty(const wchar_t *wpath) @@ -354,7 +505,7 @@ static int is_dir_empty(const wchar_t *wpath) int mingw_rmdir(const char *pathname) { - int ret, tries = 0; + int tries = 0; wchar_t wpathname[MAX_PATH]; struct stat st; @@ -380,7 +531,11 @@ int mingw_rmdir(const char *pathname) if (xutftowcs_path(wpathname, pathname) < 0) return -1; - while ((ret = _wrmdir(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) { + do { + if (!_wrmdir(wpathname)) { + invalidate_lstat_cache(); + return 0; + } if (!is_file_in_use_error(GetLastError())) errno = err_win_to_posix(GetLastError()); if (errno != EACCES) @@ -389,23 +544,9 @@ int mingw_rmdir(const char *pathname) errno = ENOTEMPTY; break; } - /* - * We assume that some other process had the source or - * destination file open at the wrong moment and retry. - * In order to give the other process a higher chance to - * complete its operation, we give up our time slice now. - * If we have to retry again, we do sleep a bit. - */ - Sleep(delay[tries]); - tries++; - } - while (ret == -1 && errno == EACCES && is_file_in_use_error(GetLastError()) && - ask_yes_no_if_possible("Deletion of directory '%s' failed. " - "Should I try again?", pathname)) - ret = _wrmdir(wpathname); - if (!ret) - invalidate_lstat_cache(); - return ret; + } while (retry_ask_yes_no(&tries, "Deletion of directory '%s' failed. " + "Should I try again?", pathname)); + return -1; } static inline int needs_hiding(const char *path) @@ -466,6 +607,8 @@ int mingw_mkdir(const char *path, int mode UNUSED) if (xutftowcs_path(wpath, path) < 0) return -1; ret = _wmkdir(wpath); + if (!ret) + process_phantom_symlinks(); if (!ret && needs_hiding(path)) return set_hidden_flag(wpath, 1); return ret; @@ -491,8 +634,10 @@ static int mingw_open_append(wchar_t const *wfilename, int oflags, ...) DWORD create = (oflags & O_CREAT) ? OPEN_ALWAYS : OPEN_EXISTING; /* only these flags are supported */ - if ((oflags & ~O_CREAT) != (O_WRONLY | O_APPEND)) - return errno = ENOSYS, -1; + if ((oflags & ~O_CREAT) != (O_WRONLY | O_APPEND)) { + errno = ENOSYS; + return -1; + } /* * FILE_SHARE_WRITE is required to permit child processes @@ -627,6 +772,7 @@ int mingw_open (const char *filename, int oflags, ...) int fd, create = (oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL); wchar_t wfilename[MAX_PATH]; open_fn_t open_fn; + WIN32_FILE_ATTRIBUTE_DATA fdata; DECLARE_PROC_ADDR(ntdll.dll, NTSTATUS, NTAPI, RtlGetLastNtStatus, void); @@ -651,6 +797,19 @@ int mingw_open (const char *filename, int oflags, ...) else if (xutftowcs_path(wfilename, filename) < 0) return -1; + /* + * When `symlink` exists and is a symbolic link pointing to a + * non-existing file, `_wopen(symlink, O_CREAT | O_EXCL)` would + * create that file. Not what we want: Linux would say `EEXIST` + * in that instance, which is therefore what Git expects. + */ + if (create && + GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata) && + (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { + errno = EEXIST; + return -1; + } + fd = open_fn(wfilename, oflags, mode); /* @@ -837,9 +996,27 @@ int mingw_access(const char *filename, int mode) int mingw_chdir(const char *dirname) { wchar_t wdirname[MAX_PATH]; + if (xutftowcs_path(wdirname, dirname) < 0) return -1; - return _wchdir(wdirname); + + if (has_symlinks) { + HANDLE hnd = CreateFileW(wdirname, 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (hnd == INVALID_HANDLE_VALUE) { + errno = err_win_to_posix(GetLastError()); + return -1; + } + if (!GetFinalPathNameByHandleW(hnd, wdirname, ARRAY_SIZE(wdirname), 0)) { + errno = err_win_to_posix(GetLastError()); + CloseHandle(hnd); + return -1; + } + CloseHandle(hnd); + } + + return _wchdir(normalize_ntpath(wdirname)); } int mingw_chmod(const char *filename, int mode) @@ -901,53 +1078,139 @@ static int has_valid_directory_prefix(wchar_t *wfilename) return 1; } -/* We keep the do_lstat code in a separate function to avoid recursion. - * When a path ends with a slash, the stat will fail with ENOENT. In - * this case, we strip the trailing slashes and stat again. - * - * If follow is true then act like stat() and report on the link - * target. Otherwise report on the link itself. +#ifndef _WINNT_H +/* + * The REPARSE_DATA_BUFFER structure is defined in the Windows DDK (in + * ntifs.h) and in MSYS1's winnt.h (which defines _WINNT_H). So define + * it ourselves if we are on MSYS2 (whose winnt.h defines _WINNT_). */ -static int do_lstat(int follow, const char *file_name, struct stat *buf) +typedef struct _REPARSE_DATA_BUFFER { + DWORD ReparseTag; + WORD ReparseDataLength; + WORD Reserved; +#ifndef _MSC_VER + _ANONYMOUS_UNION +#endif + union { + struct { + WORD SubstituteNameOffset; + WORD SubstituteNameLength; + WORD PrintNameOffset; + WORD PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLinkReparseBuffer; + struct { + WORD SubstituteNameOffset; + WORD SubstituteNameLength; + WORD PrintNameOffset; + WORD PrintNameLength; + WCHAR PathBuffer[1]; + } MountPointReparseBuffer; + struct { + BYTE DataBuffer[1]; + } GenericReparseBuffer; + } DUMMYUNIONNAME; +} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; +#endif + +static int read_reparse_point(const WCHAR *wpath, BOOL fail_on_unknown_tag, + char *tmpbuf, int *plen, DWORD *ptag) +{ + HANDLE handle; + WCHAR *wbuf; + REPARSE_DATA_BUFFER *b = alloca(MAXIMUM_REPARSE_DATA_BUFFER_SIZE); + DWORD dummy; + + /* read reparse point data */ + handle = CreateFileW(wpath, 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, NULL); + if (handle == INVALID_HANDLE_VALUE) { + errno = err_win_to_posix(GetLastError()); + return -1; + } + if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, NULL, 0, b, + MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &dummy, NULL)) { + errno = err_win_to_posix(GetLastError()); + CloseHandle(handle); + return -1; + } + CloseHandle(handle); + + /* get target path for symlinks or mount points (aka 'junctions') */ + switch ((*ptag = b->ReparseTag)) { + case IO_REPARSE_TAG_SYMLINK: + wbuf = (WCHAR*) (((char*) b->SymbolicLinkReparseBuffer.PathBuffer) + + b->SymbolicLinkReparseBuffer.SubstituteNameOffset); + *(WCHAR*) (((char*) wbuf) + + b->SymbolicLinkReparseBuffer.SubstituteNameLength) = 0; + break; + case IO_REPARSE_TAG_MOUNT_POINT: + wbuf = (WCHAR*) (((char*) b->MountPointReparseBuffer.PathBuffer) + + b->MountPointReparseBuffer.SubstituteNameOffset); + *(WCHAR*) (((char*) wbuf) + + b->MountPointReparseBuffer.SubstituteNameLength) = 0; + break; + default: + if (fail_on_unknown_tag) { + errno = EINVAL; + return -1; + } else { + *plen = MAX_PATH; + return 0; + } + } + + if ((*plen = + xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_PATH)) < 0) + return -1; + return 0; +} + +int mingw_lstat(const char *file_name, struct stat *buf) { WIN32_FILE_ATTRIBUTE_DATA fdata; + DWORD reparse_tag = 0; + int link_len = 0; wchar_t wfilename[MAX_PATH]; - if (xutftowcs_path(wfilename, file_name) < 0) + int wlen = xutftowcs_path(wfilename, file_name); + if (wlen < 0) return -1; + /* strip trailing '/', or GetFileAttributes will fail */ + while (wlen && is_dir_sep(wfilename[wlen - 1])) + wfilename[--wlen] = 0; + if (!wlen) { + errno = ENOENT; + return -1; + } + if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) { + /* for reparse points, get the link tag and length */ + if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + char tmpbuf[MAX_PATH]; + + if (read_reparse_point(wfilename, FALSE, tmpbuf, + &link_len, &reparse_tag) < 0) + return -1; + } buf->st_ino = 0; buf->st_gid = 0; buf->st_uid = 0; buf->st_nlink = 1; - buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes); - buf->st_size = fdata.nFileSizeLow | - (((off_t)fdata.nFileSizeHigh)<<32); + buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes, + reparse_tag); + buf->st_size = S_ISLNK(buf->st_mode) ? link_len : + fdata.nFileSizeLow | (((off_t) fdata.nFileSizeHigh) << 32); buf->st_dev = buf->st_rdev = 0; /* not used by Git */ filetime_to_timespec(&(fdata.ftLastAccessTime), &(buf->st_atim)); filetime_to_timespec(&(fdata.ftLastWriteTime), &(buf->st_mtim)); filetime_to_timespec(&(fdata.ftCreationTime), &(buf->st_ctim)); - if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { - WIN32_FIND_DATAW findbuf; - HANDLE handle = FindFirstFileW(wfilename, &findbuf); - if (handle != INVALID_HANDLE_VALUE) { - if ((findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && - (findbuf.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) { - if (follow) { - char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; - buf->st_size = readlink(file_name, buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE); - } else { - buf->st_mode = S_IFLNK; - } - buf->st_mode |= S_IREAD; - if (!(findbuf.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) - buf->st_mode |= S_IWRITE; - } - FindClose(handle); - } - } return 0; } + switch (GetLastError()) { case ERROR_ACCESS_DENIED: case ERROR_SHARING_VIOLATION: @@ -974,39 +1237,6 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf) return -1; } -/* We provide our own lstat/fstat functions, since the provided - * lstat/fstat functions are so slow. These stat functions are - * tailored for Git's usage (read: fast), and are not meant to be - * complete. Note that Git stat()s are redirected to mingw_lstat() - * too, since Windows doesn't really handle symlinks that well. - */ -static int do_stat_internal(int follow, const char *file_name, struct stat *buf) -{ - size_t namelen; - char alt_name[PATH_MAX]; - - if (!do_lstat(follow, file_name, buf)) - return 0; - - /* if file_name ended in a '/', Windows returned ENOENT; - * try again without trailing slashes - */ - if (errno != ENOENT) - return -1; - - namelen = strlen(file_name); - if (namelen && file_name[namelen-1] != '/') - return -1; - while (namelen && file_name[namelen-1] == '/') - --namelen; - if (!namelen || namelen >= PATH_MAX) - return -1; - - memcpy(alt_name, file_name, namelen); - alt_name[namelen] = 0; - return do_lstat(follow, alt_name, buf); -} - static int get_file_info_by_handle(HANDLE hnd, struct stat *buf) { BY_HANDLE_FILE_INFORMATION fdata; @@ -1020,7 +1250,7 @@ static int get_file_info_by_handle(HANDLE hnd, struct stat *buf) buf->st_gid = 0; buf->st_uid = 0; buf->st_nlink = 1; - buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes); + buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes, 0); buf->st_size = fdata.nFileSizeLow | (((off_t)fdata.nFileSizeHigh)<<32); buf->st_dev = buf->st_rdev = 0; /* not used by Git */ @@ -1030,13 +1260,37 @@ static int get_file_info_by_handle(HANDLE hnd, struct stat *buf) return 0; } -int mingw_lstat(const char *file_name, struct stat *buf) -{ - return do_stat_internal(0, file_name, buf); -} int mingw_stat(const char *file_name, struct stat *buf) { - return do_stat_internal(1, file_name, buf); + wchar_t wfile_name[MAX_PATH]; + HANDLE hnd; + int result; + + /* open the file and let Windows resolve the links */ + if (xutftowcs_path(wfile_name, file_name) < 0) + return -1; + hnd = CreateFileW(wfile_name, 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (hnd == INVALID_HANDLE_VALUE) { + DWORD err = GetLastError(); + + if (err == ERROR_ACCESS_DENIED && + !mingw_lstat(file_name, buf) && + !S_ISLNK(buf->st_mode)) + /* + * POSIX semantics state to still try to fill + * information, even if permission is denied to create + * a file handle. + */ + return 0; + + errno = err_win_to_posix(err); + return -1; + } + result = get_file_info_by_handle(hnd, buf); + CloseHandle(hnd); + return result; } int mingw_fstat(int fd, struct stat *buf) @@ -1140,6 +1394,9 @@ int mingw_utime (const char *file_name, const struct utimbuf *times) size_t mingw_strftime(char *s, size_t max, const char *format, const struct tm *tm) { +#ifdef _UCRT + size_t ret = strftime(s, max, format, tm); +#else /* a pointer to the original strftime in case we can't find the UCRT version */ static size_t (*fallback)(char *, size_t, const char *, const struct tm *) = strftime; size_t ret; @@ -1150,6 +1407,7 @@ size_t mingw_strftime(char *s, size_t max, ret = strftime(s, max, format, tm); else ret = fallback(s, max, format, tm); +#endif if (!ret && errno == EINVAL) die("invalid strftime format: '%s'", format); @@ -1162,18 +1420,6 @@ unsigned int sleep (unsigned int seconds) return 0; } -char *mingw_mktemp(char *template) -{ - wchar_t wtemplate[MAX_PATH]; - if (xutftowcs_path(wtemplate, template) < 0) - return NULL; - if (!_wmktemp(wtemplate)) - return NULL; - if (xwcstoutf(template, wtemplate, strlen(template) + 1) < 0) - return NULL; - return template; -} - int mkstemp(char *template) { return git_mkstemp_mode(template, 0600); @@ -1235,18 +1481,16 @@ char *mingw_getcwd(char *pointer, int len) { wchar_t cwd[MAX_PATH], wpointer[MAX_PATH]; DWORD ret = GetCurrentDirectoryW(ARRAY_SIZE(cwd), cwd); + HANDLE hnd; if (!ret || ret >= ARRAY_SIZE(cwd)) { errno = ret ? ENAMETOOLONG : err_win_to_posix(GetLastError()); return NULL; } - ret = GetLongPathNameW(cwd, wpointer, ARRAY_SIZE(wpointer)); - if (!ret && GetLastError() == ERROR_ACCESS_DENIED) { - HANDLE hnd = CreateFileW(cwd, 0, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, - OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); - if (hnd == INVALID_HANDLE_VALUE) - return NULL; + hnd = CreateFileW(cwd, 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + if (hnd != INVALID_HANDLE_VALUE) { ret = GetFinalPathNameByHandleW(hnd, wpointer, ARRAY_SIZE(wpointer), 0); CloseHandle(hnd); if (!ret || ret >= ARRAY_SIZE(wpointer)) @@ -1255,13 +1499,11 @@ char *mingw_getcwd(char *pointer, int len) return NULL; return pointer; } - if (!ret || ret >= ARRAY_SIZE(wpointer)) - return NULL; - if (GetFileAttributesW(wpointer) == INVALID_FILE_ATTRIBUTES) { + if (GetFileAttributesW(cwd) == INVALID_FILE_ATTRIBUTES) { errno = ENOENT; return NULL; } - if (xwcstoutf(pointer, wpointer, len) < 0) + if (xwcstoutf(pointer, cwd, len) < 0) return NULL; convert_slashes(pointer); return pointer; @@ -1982,6 +2224,16 @@ int mingw_kill(pid_t pid, int sig) CloseHandle(h); return 0; } + /* + * OpenProcess returns ERROR_INVALID_PARAMETER for + * non-existent PIDs. Map this to ESRCH for POSIX + * compatibility with kill(pid, 0). + */ + if (GetLastError() == ERROR_INVALID_PARAMETER) + errno = ESRCH; + else + errno = err_win_to_posix(GetLastError()); + return -1; } errno = EINVAL; @@ -2197,7 +2449,7 @@ int mingw_accept(int sockfd1, struct sockaddr *sa, socklen_t *sz) int mingw_rename(const char *pold, const char *pnew) { static int supports_file_rename_info_ex = 1; - DWORD attrs, gle; + DWORD attrs = INVALID_FILE_ATTRIBUTES, gle; int tries = 0; wchar_t wpold[MAX_PATH], wpnew[MAX_PATH]; int wpnew_len; @@ -2208,20 +2460,11 @@ int mingw_rename(const char *pold, const char *pnew) if (wpnew_len < 0) return -1; - /* - * Try native rename() first to get errno right. - * It is based on MoveFile(), which cannot overwrite existing files. - */ - if (!_wrename(wpold, wpnew)) - return 0; - if (errno != EEXIST) - return -1; - repeat: if (supports_file_rename_info_ex) { /* * Our minimum required Windows version is still set to Windows - * Vista. We thus have to declare required infrastructure for + * 8.1. We thus have to declare required infrastructure for * FileRenameInfoEx ourselves until we bump _WIN32_WINNT to * 0x0A00. Furthermore, we have to handle cases where the * FileRenameInfoEx call isn't supported yet. @@ -2292,13 +2535,22 @@ int mingw_rename(const char *pold, const char *pnew) * to retry. */ } else { - if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING)) + if (MoveFileExW(wpold, wpnew, + MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED)) return 0; gle = GetLastError(); } - /* TODO: translate more errors */ - if (gle == ERROR_ACCESS_DENIED && + /* revert file attributes on failure */ + if (attrs != INVALID_FILE_ATTRIBUTES) + SetFileAttributesW(wpnew, attrs); + + if (!is_file_in_use_error(gle)) { + errno = err_win_to_posix(gle); + return -1; + } + + if (attrs == INVALID_FILE_ATTRIBUTES && (attrs = GetFileAttributesW(wpnew)) != INVALID_FILE_ATTRIBUTES) { if (attrs & FILE_ATTRIBUTE_DIRECTORY) { DWORD attrsold = GetFileAttributesW(wpold); @@ -2310,28 +2562,10 @@ int mingw_rename(const char *pold, const char *pnew) return -1; } if ((attrs & FILE_ATTRIBUTE_READONLY) && - SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY)) { - if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING)) - return 0; - gle = GetLastError(); - /* revert file attributes on failure */ - SetFileAttributesW(wpnew, attrs); - } + SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY)) + goto repeat; } - if (tries < ARRAY_SIZE(delay) && gle == ERROR_ACCESS_DENIED) { - /* - * We assume that some other process had the source or - * destination file open at the wrong moment and retry. - * In order to give the other process a higher chance to - * complete its operation, we give up our time slice now. - * If we have to retry again, we do sleep a bit. - */ - Sleep(delay[tries]); - tries++; - goto repeat; - } - if (gle == ERROR_ACCESS_DENIED && - ask_yes_no_if_possible("Rename from '%s' to '%s' failed. " + if (retry_ask_yes_no(&tries, "Rename from '%s' to '%s' failed. " "Should I try again?", pold, pnew)) goto repeat; @@ -2450,12 +2684,14 @@ static int start_timer_thread(void) timer_event = CreateEvent(NULL, FALSE, FALSE, NULL); if (timer_event) { timer_thread = (HANDLE) _beginthreadex(NULL, 0, ticktack, NULL, 0, NULL); - if (!timer_thread ) - return errno = ENOMEM, - error("cannot start timer thread"); - } else - return errno = ENOMEM, - error("cannot allocate resources for timer"); + if (!timer_thread ) { + errno = ENOMEM; + return error("cannot start timer thread"); + } + } else { + errno = ENOMEM; + return error("cannot allocate resources for timer"); + } return 0; } @@ -2488,13 +2724,15 @@ int setitimer(int type UNUSED, struct itimerval *in, struct itimerval *out) static const struct timeval zero; static int atexit_done; - if (out) - return errno = EINVAL, - error("setitimer param 3 != NULL not implemented"); + if (out) { + errno = EINVAL; + return error("setitimer param 3 != NULL not implemented"); + } if (!is_timeval_eq(&in->it_interval, &zero) && - !is_timeval_eq(&in->it_interval, &in->it_value)) - return errno = EINVAL, - error("setitimer: it_interval must be zero or eq it_value"); + !is_timeval_eq(&in->it_interval, &in->it_value)) { + errno = EINVAL; + return error("setitimer: it_interval must be zero or eq it_value"); + } if (timer_thread) stop_timer_thread(); @@ -2516,12 +2754,14 @@ int sigaction(int sig, struct sigaction *in, struct sigaction *out) { if (sig == SIGCHLD) return -1; - else if (sig != SIGALRM) - return errno = EINVAL, - error("sigaction only implemented for SIGALRM"); - if (out) - return errno = EINVAL, - error("sigaction: param 3 != NULL not implemented"); + else if (sig != SIGALRM) { + errno = EINVAL; + return error("sigaction only implemented for SIGALRM"); + } + if (out) { + errno = EINVAL; + return error("sigaction: param 3 != NULL not implemented"); + } timer_fn = in->sa_handler; return 0; @@ -2614,6 +2854,94 @@ int link(const char *oldpath, const char *newpath) return 0; } +int symlink(const char *target, const char *link) +{ + wchar_t wtarget[MAX_PATH], wlink[MAX_PATH]; + int len; + + /* fail if symlinks are disabled or API is not supported (WinXP) */ + if (!has_symlinks) { + errno = ENOSYS; + return -1; + } + + if ((len = xutftowcs_path(wtarget, target)) < 0 + || xutftowcs_path(wlink, link) < 0) + return -1; + + /* convert target dir separators to backslashes */ + while (len--) + if (wtarget[len] == '/') + wtarget[len] = '\\'; + + /* create file symlink */ + if (!CreateSymbolicLinkW(wlink, wtarget, symlink_file_flags)) { + errno = err_win_to_posix(GetLastError()); + return -1; + } + + /* convert to directory symlink if target exists */ + switch (process_phantom_symlink(wtarget, wlink)) { + case PHANTOM_SYMLINK_RETRY: { + /* if target doesn't exist, add to phantom symlinks list */ + wchar_t wfullpath[MAX_PATH]; + struct phantom_symlink_info *psi; + + /* convert to absolute path to be independent of cwd */ + len = GetFullPathNameW(wlink, MAX_PATH, wfullpath, NULL); + if (!len || len >= MAX_PATH) { + errno = err_win_to_posix(GetLastError()); + return -1; + } + + /* over-allocate and fill phantom_symlink_info structure */ + psi = xmalloc(sizeof(struct phantom_symlink_info) + + sizeof(wchar_t) * (len + wcslen(wtarget) + 2)); + psi->wlink = (wchar_t *)(psi + 1); + wcscpy(psi->wlink, wfullpath); + psi->wtarget = psi->wlink + len + 1; + wcscpy(psi->wtarget, wtarget); + + EnterCriticalSection(&phantom_symlinks_cs); + psi->next = phantom_symlinks; + phantom_symlinks = psi; + LeaveCriticalSection(&phantom_symlinks_cs); + break; + } + case PHANTOM_SYMLINK_DIRECTORY: + /* if we created a dir symlink, process other phantom symlinks */ + process_phantom_symlinks(); + break; + default: + break; + } + return 0; +} + +int readlink(const char *path, char *buf, size_t bufsiz) +{ + WCHAR wpath[MAX_PATH]; + char tmpbuf[MAX_PATH]; + int len; + DWORD tag; + + if (xutftowcs_path(wpath, path) < 0) + return -1; + + if (read_reparse_point(wpath, TRUE, tmpbuf, &len, &tag) < 0) + return -1; + + /* + * Adapt to strange readlink() API: Copy up to bufsiz *bytes*, potentially + * cutting off a UTF-8 sequence. Insufficient bufsize is *not* a failure + * condition. There is no conversion function that produces invalid UTF-8, + * so convert to a (hopefully large enough) temporary buffer, then memcpy + * the requested number of bytes (including '\0' for robustness). + */ + memcpy(buf, tmpbuf, min(bufsiz, len + 1)); + return min(bufsiz, len); +} + pid_t waitpid(pid_t pid, int *status, int options) { HANDLE h = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION, @@ -2802,6 +3130,15 @@ static void setup_windows_environment(void) if (!tmp && (tmp = getenv("USERPROFILE"))) setenv("HOME", tmp, 1); } + + /* + * Change 'core.symlinks' default to false, unless native symlinks are + * enabled in MSys2 (via 'MSYS=winsymlinks:nativestrict'). Thus we can + * run the test suite (which doesn't obey config files) with or without + * symlink support. + */ + if (!(tmp = getenv("MSYS")) || !strstr(tmp, "winsymlinks:nativestrict")) + has_symlinks = 0; } static void get_current_user_sid(PSID *sid, HANDLE *linked_token) @@ -3215,6 +3552,24 @@ static void maybe_redirect_std_handles(void) GENERIC_WRITE, FILE_FLAG_NO_BUFFERING); } +static void adjust_symlink_flags(void) +{ + /* + * Starting with Windows 10 Build 14972, symbolic links can be created + * using CreateSymbolicLink() without elevation by passing the flag + * SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE (0x02) as last + * parameter, provided the Developer Mode has been enabled. Some + * earlier Windows versions complain about this flag with an + * ERROR_INVALID_PARAMETER, hence we have to test the build number + * specifically. + */ + if (GetVersion() >= 14972 << 16) { + symlink_file_flags |= 2; + symlink_directory_flags |= 2; + } + +} + #ifdef _MSC_VER #ifdef _DEBUG #include <crtdbg.h> @@ -3250,6 +3605,7 @@ int wmain(int argc, const wchar_t **wargv) #endif maybe_redirect_std_handles(); + adjust_symlink_flags(); /* determine size of argv and environ conversion buffer */ maxlen = wcslen(wargv[0]); @@ -3279,6 +3635,7 @@ int wmain(int argc, const wchar_t **wargv) /* initialize critical section for waitpid pinfo_t list */ InitializeCriticalSection(&pinfo_cs); + InitializeCriticalSection(&phantom_symlinks_cs); /* set up default file mode and file modes for stdin/out/err */ _fmode = _O_BINARY;
diff --git a/compat/mkdtemp.c b/compat/mkdtemp.c deleted file mode 100644 index 1136119..0000000 --- a/compat/mkdtemp.c +++ /dev/null
@@ -1,8 +0,0 @@ -#include "../git-compat-util.h" - -char *gitmkdtemp(char *template) -{ - if (!*mktemp(template) || mkdir(template, 0700)) - return NULL; - return template; -}
diff --git a/compat/mmap.c b/compat/mmap.c index 2fe1c77..1a11871 100644 --- a/compat/mmap.c +++ b/compat/mmap.c
@@ -38,7 +38,7 @@ void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t of return start; } -int git_munmap(void *start, size_t length) +int git_munmap(void *start, size_t length UNUSED) { free(start); return 0;
diff --git a/compat/nedmalloc/malloc.c.h b/compat/nedmalloc/malloc.c.h index 814845d..e0c5675 100644 --- a/compat/nedmalloc/malloc.c.h +++ b/compat/nedmalloc/malloc.c.h
@@ -500,7 +500,7 @@ MAX_RELEASE_CHECK_RATE default: 4095 unless not HAVE_MMAP #ifdef WIN32 #define WIN32_LEAN_AND_MEAN #ifndef _WIN32_WINNT -#define _WIN32_WINNT 0x403 +#define _WIN32_WINNT 0x603 #endif #include <windows.h> #define HAVE_MMAP 1
diff --git a/compat/poll/poll.c b/compat/poll/poll.c index a2becd1..ea362b4 100644 --- a/compat/poll/poll.c +++ b/compat/poll/poll.c
@@ -20,7 +20,7 @@ #define DISABLE_SIGN_COMPARE_WARNINGS -/* To bump the minimum Windows version to Windows Vista */ +/* To bump the minimum Windows version to Windows 8.1 */ #include "git-compat-util.h" /* Tell gcc not to warn about the (nfd < 0) tests, below. */ @@ -41,7 +41,7 @@ #if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ # define WIN32_NATIVE # if defined (_MSC_VER) && !defined(_WIN32_WINNT) -# define _WIN32_WINNT 0x0502 +# define _WIN32_WINNT 0x0603 # endif # include <winsock2.h> # include <windows.h>
diff --git a/compat/posix.h b/compat/posix.h index 067a00f..94699a0 100644 --- a/compat/posix.h +++ b/compat/posix.h
@@ -76,7 +76,7 @@ #if defined(WIN32) && !defined(__CYGWIN__) /* Both MinGW and MSVC */ # if !defined(_WIN32_WINNT) -# define _WIN32_WINNT 0x0600 +# define _WIN32_WINNT 0x0603 # endif #define WIN32_LEAN_AND_MEAN /* stops windows.h including winsock.h */ #include <winsock2.h> @@ -137,6 +137,9 @@ #include <sys/socket.h> #include <sys/ioctl.h> #include <sys/statvfs.h> +#ifndef NO_WRITEV +#include <sys/uio.h> +#endif #include <termios.h> #ifndef NO_SYS_SELECT_H #include <sys/select.h> @@ -323,14 +326,24 @@ int git_lstat(const char *, struct stat *); ssize_t git_pread(int fd, void *buf, size_t count, off_t offset); #endif +#ifdef NO_WRITEV +#define writev git_writev +#define iovec git_iovec +struct git_iovec { + void *iov_base; + size_t iov_len; +}; + +ssize_t git_writev(int fd, const struct iovec *iov, int iovcnt); +#endif + #ifdef NO_SETENV #define setenv gitsetenv int gitsetenv(const char *, const char *, int); #endif #ifdef NO_MKDTEMP -#define mkdtemp gitmkdtemp -char *gitmkdtemp(char *); +#define mkdtemp git_mkdtemp #endif #ifdef NO_UNSETENV
diff --git a/compat/regcomp_enhanced.c b/compat/regcomp_enhanced.c index 84193ce..29c74ee 100644 --- a/compat/regcomp_enhanced.c +++ b/compat/regcomp_enhanced.c
@@ -3,6 +3,11 @@ int git_regcomp(regex_t *preg, const char *pattern, int cflags) { + /* + * If you are on macOS with clang and fail to compile this line, + * https://lore.kernel.org/git/458ad3c1-96df-4575-ee42-e6eb754f25f6@gmx.de/ + * might be relevant. + */ if (!(cflags & REG_EXTENDED)) cflags |= REG_ENHANCED; return regcomp(preg, pattern, cflags);
diff --git a/compat/simple-ipc/ipc-win32.c b/compat/simple-ipc/ipc-win32.c index a8fc812..4a3e7df 100644 --- a/compat/simple-ipc/ipc-win32.c +++ b/compat/simple-ipc/ipc-win32.c
@@ -686,7 +686,7 @@ static LPSECURITY_ATTRIBUTES get_sa(struct my_sa_data *d) goto fail; } - memset(ea, 0, NR_EA * sizeof(EXPLICIT_ACCESS)); + MEMZERO_ARRAY(ea, NR_EA); ea[0].grfAccessPermissions = GENERIC_READ | GENERIC_WRITE; ea[0].grfAccessMode = SET_ACCESS;
diff --git a/compat/win32.h b/compat/win32.h index a97e880..671bcc8 100644 --- a/compat/win32.h +++ b/compat/win32.h
@@ -6,10 +6,12 @@ #include <windows.h> #endif -static inline int file_attr_to_st_mode (DWORD attr) +static inline int file_attr_to_st_mode (DWORD attr, DWORD tag) { int fMode = S_IREAD; - if (attr & FILE_ATTRIBUTE_DIRECTORY) + if ((attr & FILE_ATTRIBUTE_REPARSE_POINT) && tag == IO_REPARSE_TAG_SYMLINK) + fMode |= S_IFLNK; + else if (attr & FILE_ATTRIBUTE_DIRECTORY) fMode |= S_IFDIR; else fMode |= S_IFREG;
diff --git a/compat/win32/dirent.c b/compat/win32/dirent.c index 52420ec..24ee9b8 100644 --- a/compat/win32/dirent.c +++ b/compat/win32/dirent.c
@@ -12,7 +12,10 @@ static inline void finddata2dirent(struct dirent *ent, WIN32_FIND_DATAW *fdata) xwcstoutf(ent->d_name, fdata->cFileName, sizeof(ent->d_name)); /* Set file type, based on WIN32_FIND_DATA */ - if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + if ((fdata->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + && fdata->dwReserved0 == IO_REPARSE_TAG_SYMLINK) + ent->d_type = DT_LNK; + else if (fdata->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ent->d_type = DT_DIR; else ent->d_type = DT_REG;
diff --git a/compat/win32/flush.c b/compat/win32/flush.c index 291f90e..7244ff6 100644 --- a/compat/win32/flush.c +++ b/compat/win32/flush.c
@@ -6,7 +6,9 @@ int win32_fsync_no_flush(int fd) { IO_STATUS_BLOCK io_status; +#ifndef FLUSH_FLAGS_FILE_DATA_ONLY #define FLUSH_FLAGS_FILE_DATA_ONLY 1 +#endif DECLARE_PROC_ADDR(ntdll.dll, NTSTATUS, NTAPI, NtFlushBuffersFileEx, HANDLE FileHandle, ULONG Flags, PVOID Parameters, ULONG ParameterSize,
diff --git a/compat/win32/pthread.c b/compat/win32/pthread.c index 58980a5..7e93146 100644 --- a/compat/win32/pthread.c +++ b/compat/win32/pthread.c
@@ -59,3 +59,10 @@ pthread_t pthread_self(void) t.tid = GetCurrentThreadId(); return t; } + +int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) +{ + if (SleepConditionVariableCS(cond, mutex, INFINITE) == 0) + return err_win_to_posix(GetLastError()); + return 0; +}
diff --git a/compat/win32/pthread.h b/compat/win32/pthread.h index e2b5c4f..ccacc5a 100644 --- a/compat/win32/pthread.h +++ b/compat/win32/pthread.h
@@ -34,9 +34,8 @@ typedef int pthread_mutexattr_t; #define pthread_cond_t CONDITION_VARIABLE -#define pthread_cond_init(a,b) InitializeConditionVariable((a)) +#define pthread_cond_init(a,b) return_0((InitializeConditionVariable((a)), 0)) #define pthread_cond_destroy(a) do {} while (0) -#define pthread_cond_wait(a,b) return_0(SleepConditionVariableCS((a), (b), INFINITE)) #define pthread_cond_signal WakeConditionVariable #define pthread_cond_broadcast WakeAllConditionVariable @@ -64,6 +63,8 @@ int win32_pthread_join(pthread_t *thread, void **value_ptr); #define pthread_equal(t1, t2) ((t1).tid == (t2).tid) pthread_t pthread_self(void); +int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); + static inline void NORETURN pthread_exit(void *ret) { _endthreadex((unsigned)(uintptr_t)ret);
diff --git a/compat/win32/trace2_win32_process_info.c b/compat/win32/trace2_win32_process_info.c index f147da7..6a6a396 100644 --- a/compat/win32/trace2_win32_process_info.c +++ b/compat/win32/trace2_win32_process_info.c
@@ -3,6 +3,7 @@ #include "../../git-compat-util.h" #include "../../json-writer.h" #include "../../repository.h" +#include "../../strvec.h" #include "../../trace2.h" #include "lazyload.h" #include <psapi.h> @@ -32,12 +33,7 @@ static int find_pid(DWORD pid, HANDLE hSnapshot, PROCESSENTRY32 *pe32) } /* - * Accumulate JSON array of our parent processes: - * [ - * exe-name-parent, - * exe-name-grand-parent, - * ... - * ] + * Accumulate array of our parent process names. * * Note: we only report the filename of the process executable; the * only way to get its full pathname is to use OpenProcess() @@ -73,7 +69,7 @@ static int find_pid(DWORD pid, HANDLE hSnapshot, PROCESSENTRY32 *pe32) * simple and avoid the alloc/realloc overhead. It is OK if we * truncate the search and return a partial answer. */ -static void get_processes(struct json_writer *jw, HANDLE hSnapshot) +static void get_processes(struct strvec *names, HANDLE hSnapshot) { PROCESSENTRY32 pe32; DWORD pid; @@ -82,19 +78,19 @@ static void get_processes(struct json_writer *jw, HANDLE hSnapshot) pid = GetCurrentProcessId(); while (find_pid(pid, hSnapshot, &pe32)) { - /* Only report parents. Omit self from the JSON output. */ + /* Only report parents. Omit self from the output. */ if (nr_pids) - jw_array_string(jw, pe32.szExeFile); + strvec_push(names, pe32.szExeFile); /* Check for cycle in snapshot. (Yes, it happened.) */ for (k = 0; k < nr_pids; k++) if (pid == pid_list[k]) { - jw_array_string(jw, "(cycle)"); + strvec_push(names, "(cycle)"); return; } if (nr_pids == NR_PIDS_LIMIT) { - jw_array_string(jw, "(truncated)"); + strvec_push(names, "(truncated)"); return; } @@ -105,24 +101,14 @@ static void get_processes(struct json_writer *jw, HANDLE hSnapshot) } /* - * Emit JSON data for the current and parent processes. Individual - * trace2 targets can decide how to actually print it. + * Collect the list of parent process names. */ -static void get_ancestry(void) +static void get_ancestry(struct strvec *names) { HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnapshot != INVALID_HANDLE_VALUE) { - struct json_writer jw = JSON_WRITER_INIT; - - jw_array_begin(&jw, 0); - get_processes(&jw, hSnapshot); - jw_end(&jw); - - trace2_data_json("process", the_repository, "windows/ancestry", - &jw); - - jw_release(&jw); + get_processes(names, hSnapshot); CloseHandle(hSnapshot); } } @@ -176,13 +162,35 @@ static void get_peak_memory_info(void) void trace2_collect_process_info(enum trace2_process_info_reason reason) { + struct strvec names = STRVEC_INIT; + if (!trace2_is_enabled()) return; switch (reason) { case TRACE2_PROCESS_INFO_STARTUP: get_is_being_debugged(); - get_ancestry(); + get_ancestry(&names); + if (names.nr) { + /* + Emit the ancestry data as a data_json event to + maintain compatibility for consumers of the older + "windows/ancestry" event. + */ + struct json_writer jw = JSON_WRITER_INIT; + jw_array_begin(&jw, 0); + for (size_t i = 0; i < names.nr; i++) + jw_array_string(&jw, names.v[i]); + jw_end(&jw); + trace2_data_json("process", the_repository, + "windows/ancestry", &jw); + jw_release(&jw); + + /* Emit the ancestry data with the new event. */ + trace2_cmd_ancestry(names.v); + } + + strvec_clear(&names); return; case TRACE2_PROCESS_INFO_EXIT:
diff --git a/compat/winansi.c b/compat/winansi.c index ac2ffb7..3ce1900 100644 --- a/compat/winansi.c +++ b/compat/winansi.c
@@ -32,47 +32,18 @@ static int non_ascii_used = 0; static HANDLE hthread, hread, hwrite; static HANDLE hconsole1, hconsole2; -#ifdef __MINGW32__ -#if !defined(__MINGW64_VERSION_MAJOR) || __MINGW64_VERSION_MAJOR < 5 -typedef struct _CONSOLE_FONT_INFOEX { - ULONG cbSize; - DWORD nFont; - COORD dwFontSize; - UINT FontFamily; - UINT FontWeight; - WCHAR FaceName[LF_FACESIZE]; -} CONSOLE_FONT_INFOEX, *PCONSOLE_FONT_INFOEX; -#endif -#endif - static void warn_if_raster_font(void) { DWORD fontFamily = 0; - DECLARE_PROC_ADDR(kernel32.dll, BOOL, WINAPI, - GetCurrentConsoleFontEx, HANDLE, BOOL, - PCONSOLE_FONT_INFOEX); + CONSOLE_FONT_INFOEX cfi; /* don't bother if output was ascii only */ if (!non_ascii_used) return; - /* GetCurrentConsoleFontEx is available since Vista */ - if (INIT_PROC_ADDR(GetCurrentConsoleFontEx)) { - CONSOLE_FONT_INFOEX cfi; - cfi.cbSize = sizeof(cfi); - if (GetCurrentConsoleFontEx(console, 0, &cfi)) - fontFamily = cfi.FontFamily; - } else { - /* pre-Vista: check default console font in registry */ - HKEY hkey; - if (ERROR_SUCCESS == RegOpenKeyExA(HKEY_CURRENT_USER, "Console", - 0, KEY_READ, &hkey)) { - DWORD size = sizeof(fontFamily); - RegQueryValueExA(hkey, "FontFamily", NULL, NULL, - (LPVOID) &fontFamily, &size); - RegCloseKey(hkey); - } - } + cfi.cbSize = sizeof(cfi); + if (GetCurrentConsoleFontEx(console, 0, &cfi)) + fontFamily = cfi.FontFamily; if (!(fontFamily & TMPF_TRUETYPE)) { const wchar_t *msg = L"\nWarning: Your console font probably "
diff --git a/compat/writev.c b/compat/writev.c new file mode 100644 index 0000000..3a94870 --- /dev/null +++ b/compat/writev.c
@@ -0,0 +1,44 @@ +#include "../git-compat-util.h" +#include "../wrapper.h" + +ssize_t git_writev(int fd, const struct iovec *iov, int iovcnt) +{ + size_t total_written = 0; + size_t sum = 0; + + /* + * According to writev(3p), the syscall shall error with EINVAL in case + * the sum of `iov_len` overflows `ssize_t`. + */ + for (int i = 0; i < iovcnt; i++) { + if (iov[i].iov_len > maximum_signed_value_of_type(ssize_t) || + iov[i].iov_len + sum > maximum_signed_value_of_type(ssize_t)) { + errno = EINVAL; + return -1; + } + + sum += iov[i].iov_len; + } + + for (int i = 0; i < iovcnt; i++) { + const char *bytes = iov[i].iov_base; + size_t iovec_written = 0; + + while (iovec_written < iov[i].iov_len) { + ssize_t bytes_written = xwrite(fd, bytes + iovec_written, + iov[i].iov_len - iovec_written); + if (bytes_written < 0) { + if (total_written) + goto out; + return bytes_written; + } + if (!bytes_written) + goto out; + iovec_written += bytes_written; + total_written += bytes_written; + } + } + +out: + return (ssize_t) total_written; +}
diff --git a/config.c b/config.c index f1def0d..156f2a2 100644 --- a/config.c +++ b/config.c
@@ -160,7 +160,7 @@ static int handle_path_include(const struct key_value_info *kvi, * based on the including config file. */ if (!is_absolute_path(path)) { - char *slash; + const char *slash; if (!kvi || kvi->origin_type != CONFIG_ORIGIN_FILE) { ret = error(_("relative config includes must come from files")); @@ -1291,6 +1291,7 @@ int git_config_pathname(char **dest, const char *var, const char *value) if (is_optional && is_missing_file(path)) { free(path); + *dest = NULL; return 0; } @@ -1953,7 +1954,7 @@ int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *d return 1; } -int git_configset_get_pathname(struct config_set *set, const char *key, char **dest) +static int git_configset_get_pathname(struct config_set *set, const char *key, char **dest) { const char *value; if (!git_configset_get_value(set, key, &value, NULL)) @@ -2434,14 +2435,14 @@ int repo_config_get_expiry_in_days(struct repository *r, const char *key, timestamp_t *expiry, timestamp_t now) { const char *expiry_string; - intmax_t days; + int days; timestamp_t when; if (repo_config_get_string_tmp(r, key, &expiry_string)) return 1; /* no such thing */ - if (git_parse_signed(expiry_string, &days, maximum_signed_value_of_type(int))) { - const int scale = 86400; + if (git_parse_int(expiry_string, &days)) { + const intmax_t scale = 86400; *expiry = now - days * scale; return 0; }
diff --git a/config.h b/config.h index 19c87fc..ba426a9 100644 --- a/config.h +++ b/config.h
@@ -564,7 +564,6 @@ int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned lon int git_configset_get_bool(struct config_set *cs, const char *key, int *dest); int git_configset_get_bool_or_int(struct config_set *cs, const char *key, int *is_bool, int *dest); int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest); -int git_configset_get_pathname(struct config_set *cs, const char *key, char **dest); /** * Run only the discover part of the repo_config_get_*() functions
diff --git a/config.mak.dev b/config.mak.dev index e86b6e1..c8dcf78 100644 --- a/config.mak.dev +++ b/config.mak.dev
@@ -1,5 +1,5 @@ ifndef COMPILER_FEATURES -COMPILER_FEATURES := $(shell ./detect-compiler $(CC)) +COMPILER_FEATURES := $(shell ./tools/detect-compiler $(CC)) endif ifeq ($(filter no-error,$(DEVOPTS)),)
diff --git a/config.mak.uname b/config.mak.uname index 1691c6a..ccb3f71 100644 --- a/config.mak.uname +++ b/config.mak.uname
@@ -124,6 +124,7 @@ # - MacOS 10.0.* and MacOS 10.1.0 = Darwin 1.* # - MacOS 10.x.* = Darwin (x+4).* for (1 <= x) # i.e. "begins with [15678] and a dot" means "10.4.* or older". + DARWIN_MAJOR_VERSION = $(shell expr "$(uname_R)" : '\([0-9]*\)\.') ifeq ($(shell expr "$(uname_R)" : '[15678]\.'),2) OLD_ICONV = UnfortunatelyYes NO_APPLE_COMMON_CRYPTO = YesPlease @@ -148,29 +149,17 @@ HAVE_NS_GET_EXECUTABLE_PATH = YesPlease CSPRNG_METHOD = arc4random USE_ENHANCED_BASIC_REGULAR_EXPRESSIONS = YesPlease + HAVE_PLATFORM_PROCINFO = YesPlease + COMPAT_OBJS += compat/darwin/procinfo.o - # Workaround for `gettext` being keg-only and not even being linked via - # `brew link --force gettext`, should be obsolete as of - # https://github.com/Homebrew/homebrew-core/pull/53489 - ifeq ($(shell test -d /usr/local/opt/gettext/ && echo y),y) - BASIC_CFLAGS += -I/usr/local/include -I/usr/local/opt/gettext/include - BASIC_LDFLAGS += -L/usr/local/lib -L/usr/local/opt/gettext/lib - ifeq ($(shell test -x /usr/local/opt/gettext/bin/msgfmt && echo y),y) - MSGFMT = /usr/local/opt/gettext/bin/msgfmt - endif - # On newer ARM-based machines the default installation path has changed to - # /opt/homebrew. Include it in our search paths so that the user does not - # have to configure this manually. - # - # Note that we do not employ the same workaround as above where we manually - # add gettext. The issue was fixed more than three years ago by now, and at - # that point there haven't been any ARM-based Macs yet. - else ifeq ($(shell test -d /opt/homebrew/ && echo y),y) - BASIC_CFLAGS += -I/opt/homebrew/include - BASIC_LDFLAGS += -L/opt/homebrew/lib - ifeq ($(shell test -x /opt/homebrew/bin/msgfmt && echo y),y) - MSGFMT = /opt/homebrew/bin/msgfmt - endif + ifeq ($(uname_M),arm64) + HOMEBREW_PREFIX = /opt/homebrew + else + HOMEBREW_PREFIX = /usr/local + endif + ifeq ($(shell test "$(DARWIN_MAJOR_VERSION)" -ge 24 && echo 1),1) + USE_HOMEBREW_LIBICONV = UnfortunatelyYes + NEEDS_GOOD_LIBICONV = UnfortunatelyYes endif # The builtin FSMonitor on MacOS builds upon Simple-IPC. Both require @@ -470,6 +459,7 @@ SANE_TOOL_PATH ?= $(msvc_bin_dir_msys) HAVE_ALLOCA_H = YesPlease NO_PREAD = YesPlease + NO_WRITEV = YesPlease NEEDS_CRYPTO_WITH_SSL = YesPlease NO_LIBGEN_H = YesPlease NO_POLL = YesPlease @@ -685,6 +675,7 @@ pathsep = ; HAVE_ALLOCA_H = YesPlease NO_PREAD = YesPlease + NO_WRITEV = YesPlease NEEDS_CRYPTO_WITH_SSL = YesPlease NO_LIBGEN_H = YesPlease NO_POLL = YesPlease
diff --git a/connect.c b/connect.c index 8352b71..fcd35c5 100644 --- a/connect.c +++ b/connect.c
@@ -240,6 +240,8 @@ static void process_capabilities(struct packet_reader *reader, size_t *linelen) size_t nul_location = strlen(line); if (nul_location == *linelen) return; + + free(server_capabilities_v1); server_capabilities_v1 = xstrdup(line + nul_location + 1); *linelen = nul_location; @@ -503,7 +505,8 @@ static void send_capabilities(int fd_out, struct packet_reader *reader) reader->hash_algo = &hash_algos[GIT_HASH_SHA1_LEGACY]; } if (server_feature_v2("promisor-remote", &promisor_remote_info)) { - char *reply = promisor_remote_reply(promisor_remote_info); + char *reply; + promisor_remote_reply(promisor_remote_info, &reply); if (reply) { packet_write_fmt(fd_out, "promisor-remote=%s", reply); free(reply); @@ -1051,6 +1054,8 @@ static struct child_process *git_proxy_connect(int fd[2], char *host) strvec_push(&proxy->args, port); proxy->in = -1; proxy->out = -1; + proxy->clean_on_exit = 1; + proxy->wait_after_clean = 1; if (start_command(proxy)) die(_("cannot start proxy %s"), git_proxy_command); fd[0] = proxy->out; /* read from proxy stdout */ @@ -1512,6 +1517,8 @@ struct child_process *git_connect(int fd[2], const char *url, } strvec_push(&conn->args, cmd.buf); + conn->clean_on_exit = 1; + conn->wait_after_clean = 1; if (start_command(conn)) die(_("unable to fork"));
diff --git a/connected.c b/connected.c index 7940310..6718503 100644 --- a/connected.c +++ b/connected.c
@@ -45,20 +45,6 @@ int check_connected(oid_iterate_fn fn, void *cb_data, return err; } - if (transport && transport->smart_options && - transport->smart_options->self_contained_and_connected && - transport->pack_lockfiles.nr == 1 && - strip_suffix(transport->pack_lockfiles.items[0].string, - ".keep", &base_len)) { - struct strbuf idx_file = STRBUF_INIT; - strbuf_add(&idx_file, transport->pack_lockfiles.items[0].string, - base_len); - strbuf_addstr(&idx_file, ".idx"); - new_pack = add_packed_git(the_repository, idx_file.buf, - idx_file.len, 1); - strbuf_release(&idx_file); - } - if (repo_has_promisor_remote(the_repository)) { /* * For partial clones, we don't want to have to do a regular @@ -90,7 +76,6 @@ int check_connected(oid_iterate_fn fn, void *cb_data, promisor_pack_found: ; } while ((oid = fn(cb_data)) != NULL); - free(new_pack); return 0; } @@ -127,15 +112,27 @@ int check_connected(oid_iterate_fn fn, void *cb_data, else rev_list.no_stderr = opt->quiet; - if (start_command(&rev_list)) { - free(new_pack); + if (start_command(&rev_list)) return error(_("Could not run 'git rev-list'")); - } sigchain_push(SIGPIPE, SIG_IGN); rev_list_in = xfdopen(rev_list.in, "w"); + if (transport && transport->smart_options && + transport->smart_options->self_contained_and_connected && + transport->pack_lockfiles.nr == 1 && + strip_suffix(transport->pack_lockfiles.items[0].string, + ".keep", &base_len)) { + struct strbuf idx_file = STRBUF_INIT; + strbuf_add(&idx_file, transport->pack_lockfiles.items[0].string, + base_len); + strbuf_addstr(&idx_file, ".idx"); + new_pack = add_packed_git(the_repository, idx_file.buf, + idx_file.len, 1); + strbuf_release(&idx_file); + } + do { /* * If index-pack already checked that: @@ -162,6 +159,9 @@ int check_connected(oid_iterate_fn fn, void *cb_data, err = error_errno(_("failed to close rev-list's stdin")); sigchain_pop(SIGPIPE); - free(new_pack); + if (new_pack) { + close_pack(new_pack); + free(new_pack); + } return finish_command(&rev_list) || err; }
diff --git a/contrib/Makefile b/contrib/Makefile new file mode 100644 index 0000000..787cd07 --- /dev/null +++ b/contrib/Makefile
@@ -0,0 +1,10 @@ +all:: + +test:: + $(MAKE) -C diff-highlight $@ + $(MAKE) -C subtree $@ + +clean:: + $(MAKE) -C contacts $@ + $(MAKE) -C diff-highlight $@ + $(MAKE) -C subtree $@
diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt index edb0fc0..d7a087e 100644 --- a/contrib/buildsystems/CMakeLists.txt +++ b/contrib/buildsystems/CMakeLists.txt
@@ -274,6 +274,8 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") add_compile_definitions(PROCFS_EXECUTABLE_PATH="/proc/self/exe" HAVE_DEV_TTY ) list(APPEND compat_SOURCES unix-socket.c unix-stream-server.c compat/linux/procinfo.c) +elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + list(APPEND compat_SOURCES compat/darwin/procinfo.c) endif() if(CMAKE_SYSTEM_NAME STREQUAL "Windows") @@ -374,7 +376,7 @@ #function checks set(function_checks strcasestr memmem strlcpy strtoimax strtoumax strtoull - setenv mkdtemp poll pread memmem) + setenv mkdtemp poll pread memmem writev) #unsetenv,hstrerror are incompatible with windows build if(NOT WIN32) @@ -411,10 +413,6 @@ list(APPEND compat_SOURCES compat/setenv.c) endif() -if(NOT HAVE_MKDTEMP) - list(APPEND compat_SOURCES compat/mkdtemp.c) -endif() - if(NOT HAVE_PREAD) list(APPEND compat_SOURCES compat/pread.c) endif() @@ -423,6 +421,10 @@ list(APPEND compat_SOURCES compat/memmem.c) endif() +if(NOT HAVE_WRITEV) + list(APPEND compat_SOURCES compat/writev.c) +endif() + if(NOT WIN32) if(NOT HAVE_UNSETENV) list(APPEND compat_SOURCES compat/unsetenv.c) @@ -638,7 +640,7 @@ if(NOT EXISTS ${CMAKE_BINARY_DIR}/command-list.h OR NOT EXCLUSION_PROGS_CACHE STREQUAL EXCLUSION_PROGS) list(REMOVE_ITEM EXCLUSION_PROGS empty) message("Generating command-list.h") - execute_process(COMMAND "${SH_EXE}" "${CMAKE_SOURCE_DIR}/generate-cmdlist.sh" + execute_process(COMMAND "${SH_EXE}" "${CMAKE_SOURCE_DIR}/tools/generate-cmdlist.sh" ${EXCLUSION_PROGS} "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}/command-list.h") @@ -646,14 +648,14 @@ if(NOT EXISTS ${CMAKE_BINARY_DIR}/config-list.h) message("Generating config-list.h") - execute_process(COMMAND "${SH_EXE}" "${CMAKE_SOURCE_DIR}/generate-configlist.sh" + execute_process(COMMAND "${SH_EXE}" "${CMAKE_SOURCE_DIR}/tools/generate-configlist.sh" "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}/config-list.h") endif() if(NOT EXISTS ${CMAKE_BINARY_DIR}/hook-list.h) message("Generating hook-list.h") - execute_process(COMMAND "${SH_EXE}" ${CMAKE_SOURCE_DIR}/generate-hooklist.sh + execute_process(COMMAND "${SH_EXE}" ${CMAKE_SOURCE_DIR}/tools/generate-hooklist.sh "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}/hook-list.h") endif() @@ -679,18 +681,6 @@ add_library(libgit ${libgit_SOURCES} ${compat_SOURCES}) -#libxdiff -parse_makefile_for_sources(libxdiff_SOURCES ${CMAKE_SOURCE_DIR}/Makefile "XDIFF_OBJS") - -list(TRANSFORM libxdiff_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/") -add_library(xdiff STATIC ${libxdiff_SOURCES}) - -#reftable -parse_makefile_for_sources(reftable_SOURCES ${CMAKE_SOURCE_DIR}/Makefile "REFTABLE_OBJS") - -list(TRANSFORM reftable_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/") -add_library(reftable STATIC ${reftable_SOURCES}) - if(WIN32) add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/git.rc COMMAND "${SH_EXE}" "${CMAKE_SOURCE_DIR}/GIT-VERSION-GEN" @@ -720,7 +710,7 @@ #link all required libraries to common-main add_library(common-main OBJECT ${CMAKE_SOURCE_DIR}/common-main.c) -target_link_libraries(common-main libgit xdiff reftable ${ZLIB_LIBRARIES}) +target_link_libraries(common-main libgit ${ZLIB_LIBRARIES}) if(Intl_FOUND) target_link_libraries(common-main ${Intl_LIBRARIES}) endif() @@ -846,11 +836,11 @@ endif() add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/${shell_gen_path}" - COMMAND "${SH_EXE}" "${CMAKE_SOURCE_DIR}/generate-script.sh" + COMMAND "${SH_EXE}" "${CMAKE_SOURCE_DIR}/tools/generate-script.sh" "${CMAKE_SOURCE_DIR}/${script}.sh" "${CMAKE_BINARY_DIR}/${shell_gen_path}" "${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS" - DEPENDS "${CMAKE_SOURCE_DIR}/generate-script.sh" + DEPENDS "${CMAKE_SOURCE_DIR}/tools/generate-script.sh" "${CMAKE_SOURCE_DIR}/${script}.sh" VERBATIM) list(APPEND shell_gen ${CMAKE_BINARY_DIR}/${shell_gen_path}) @@ -889,13 +879,13 @@ file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/${perl_gen_dir}") add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/${perl_gen_path}" - COMMAND "${SH_EXE}" "${CMAKE_SOURCE_DIR}/generate-perl.sh" + COMMAND "${SH_EXE}" "${CMAKE_SOURCE_DIR}/tools/generate-perl.sh" "${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS" "${CMAKE_BINARY_DIR}/GIT-VERSION-FILE" "${CMAKE_BINARY_DIR}/GIT-PERL-HEADER" "${CMAKE_SOURCE_DIR}/${script}" "${CMAKE_BINARY_DIR}/${perl_gen_path}" - DEPENDS "${CMAKE_SOURCE_DIR}/generate-perl.sh" + DEPENDS "${CMAKE_SOURCE_DIR}/tools/generate-perl.sh" "${CMAKE_SOURCE_DIR}/${script}" "${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS" "${CMAKE_BINARY_DIR}/GIT-VERSION-FILE" @@ -906,11 +896,11 @@ # Python script add_custom_command(OUTPUT "${CMAKE_BINARY_DIR}/git-p4" - COMMAND "${SH_EXE}" "${CMAKE_SOURCE_DIR}/generate-python.sh" + COMMAND "${SH_EXE}" "${CMAKE_SOURCE_DIR}/tools/generate-python.sh" "${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS" "${CMAKE_SOURCE_DIR}/git-p4.py" "${CMAKE_BINARY_DIR}/git-p4" - DEPENDS "${CMAKE_SOURCE_DIR}/generate-python.sh" + DEPENDS "${CMAKE_SOURCE_DIR}/tools/generate-python.sh" "${CMAKE_SOURCE_DIR}/git-p4.py" "${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS" VERBATIM)
diff --git a/contrib/coccinelle/the_repository.cocci b/contrib/coccinelle/the_repository.cocci deleted file mode 100644 index ea7fe1c..0000000 --- a/contrib/coccinelle/the_repository.cocci +++ /dev/null
@@ -1,123 +0,0 @@ -// Fully migrated "the_repository" additions -@@ -@@ -( -// cache.h -- get_oid -+ repo_get_oid -| -- get_oid_commit -+ repo_get_oid_commit -| -- get_oid_committish -+ repo_get_oid_committish -| -- get_oid_tree -+ repo_get_oid_tree -| -- get_oid_treeish -+ repo_get_oid_treeish -| -- get_oid_blob -+ repo_get_oid_blob -| -- get_oid_mb -+ repo_get_oid_mb -| -- find_unique_abbrev -+ repo_find_unique_abbrev -| -- find_unique_abbrev_r -+ repo_find_unique_abbrev_r -| -- for_each_abbrev -+ repo_for_each_abbrev -| -- interpret_branch_name -+ repo_interpret_branch_name -| -- peel_to_type -+ repo_peel_to_type -// commit-reach.h -| -- get_merge_bases -+ repo_get_merge_bases -| -- get_merge_bases_many -+ repo_get_merge_bases_many -| -- get_merge_bases_many_dirty -+ repo_get_merge_bases_many_dirty -| -- in_merge_bases -+ repo_in_merge_bases -| -- in_merge_bases_many -+ repo_in_merge_bases_many -// commit.h -| -- parse_commit_internal -+ repo_parse_commit_internal -| -- parse_commit -+ repo_parse_commit -| -- get_commit_buffer -+ repo_get_commit_buffer -| -- unuse_commit_buffer -+ repo_unuse_commit_buffer -| -- logmsg_reencode -+ repo_logmsg_reencode -| -- get_commit_tree -+ repo_get_commit_tree -// diff.h -| -- diff_setup -+ repo_diff_setup -// odb.h -| -- read_object_file -+ repo_read_object_file -| -- has_object_file -+ repo_has_object_file -| -- has_object_file_with_flags -+ repo_has_object_file_with_flags -// pretty.h -| -- format_commit_message -+ repo_format_commit_message -// packfile.h -| -- approximate_object_count -+ repo_approximate_object_count -// promisor-remote.h -| -- promisor_remote_reinit -+ repo_promisor_remote_reinit -| -- promisor_remote_find -+ repo_promisor_remote_find -| -- has_promisor_remote -+ repo_has_promisor_remote -// refs.h -| -- dwim_ref -+ repo_dwim_ref -// rerere.h -| -- rerere -+ repo_rerere -// revision.h -| -- init_revisions -+ repo_init_revisions -) - ( -+ the_repository, - ...)
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 73abea3..a8e7c6d 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash
@@ -13,7 +13,8 @@ # *) git email aliases for git-send-email # *) tree paths within 'ref:path/to/file' expressions # *) file paths within current working directory and index -# *) common --long-options +# *) common --long-options but not single-letter options +# *) arguments to long and single-letter options # # To use these routines: # @@ -3464,7 +3465,7 @@ _git_stash () { - local subcommands='push list show apply clear drop pop create branch' + local subcommands='push list show apply clear drop pop create branch import export' local subcommand="$(__git_find_on_cmdline "$subcommands save")" if [ -z "$subcommand" ]; then @@ -3490,6 +3491,9 @@ show,--*) __gitcomp_builtin stash_show "$__git_diff_common_options" ;; + export,--*) + __gitcomp_builtin stash_export "--print --to-ref" + ;; *,--*) __gitcomp_builtin "stash_$subcommand" ;; @@ -3501,7 +3505,10 @@ | sed -n -e 's/:.*//p')" fi ;; - show,*|apply,*|drop,*|pop,*) + import,*) + __git_complete_refs + ;; + show,*|apply,*|drop,*|pop,*|export,*) __gitcomp_nl "$(__git stash list \ | sed -n -e 's/:.*//p')" ;;
diff --git a/contrib/completion/git-completion.zsh b/contrib/completion/git-completion.zsh index f5877bd..c32186a 100644 --- a/contrib/completion/git-completion.zsh +++ b/contrib/completion/git-completion.zsh
@@ -202,7 +202,7 @@ __git_zsh_cmd_alias () { local -a list - list=(${${(0)"$(git config -z --get-regexp '^alias\.*')"}#alias.}) + list=(${(0)"$(git help --aliases-for-completion)"}) list=(${(f)"$(printf "%s:alias for '%s'\n" ${(f@)list})"}) _describe -t alias-commands 'aliases' list && _ret=0 }
diff --git a/contrib/credential/osxkeychain/Makefile b/contrib/credential/osxkeychain/Makefile index 9680717..219b0d7 100644 --- a/contrib/credential/osxkeychain/Makefile +++ b/contrib/credential/osxkeychain/Makefile
@@ -1,29 +1,13 @@ # The default target of this Makefile is... all:: git-credential-osxkeychain --include ../../../config.mak.autogen --include ../../../config.mak +git-credential-osxkeychain: + $(MAKE) -C ../../.. contrib/credential/osxkeychain/git-credential-osxkeychain -prefix ?= /usr/local -gitexecdir ?= $(prefix)/libexec/git-core - -CC ?= gcc -CFLAGS ?= -g -O2 -Wall -INSTALL ?= install -RM ?= rm -f - -%.o: %.c - $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ -c $< - -git-credential-osxkeychain: git-credential-osxkeychain.o - $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) \ - -framework Security -framework CoreFoundation - -install: git-credential-osxkeychain - $(INSTALL) -d -m 755 $(DESTDIR)$(gitexecdir) - $(INSTALL) -m 755 $< $(DESTDIR)$(gitexecdir) +install: + $(MAKE) -C ../../.. install-git-credential-osxkeychain clean: - $(RM) git-credential-osxkeychain git-credential-osxkeychain.o + $(MAKE) -C ../../.. clean-git-credential-osxkeychain -.PHONY: all install clean +.PHONY: all git-credential-osxkeychain install clean
diff --git a/contrib/credential/osxkeychain/git-credential-osxkeychain.c b/contrib/credential/osxkeychain/git-credential-osxkeychain.c index 611c979..b180267 100644 --- a/contrib/credential/osxkeychain/git-credential-osxkeychain.c +++ b/contrib/credential/osxkeychain/git-credential-osxkeychain.c
@@ -2,6 +2,9 @@ #include <string.h> #include <stdlib.h> #include <Security/Security.h> +#include "git-compat-util.h" +#include "strbuf.h" +#include "wrapper.h" #define ENCODING kCFStringEncodingUTF8 static CFStringRef protocol; /* Stores constant strings - not memory managed */ @@ -12,7 +15,7 @@ static CFStringRef username; static CFDataRef password; static CFDataRef password_expiry_utc; static CFDataRef oauth_refresh_token; -static int state_seen; +static char *state_seen; static void clear_credential(void) { @@ -48,27 +51,6 @@ static void clear_credential(void) #define STRING_WITH_LENGTH(s) s, sizeof(s) - 1 -__attribute__((format (printf, 1, 2), __noreturn__)) -static void die(const char *err, ...) -{ - char msg[4096]; - va_list params; - va_start(params, err); - vsnprintf(msg, sizeof(msg), err, params); - fprintf(stderr, "%s\n", msg); - va_end(params); - clear_credential(); - exit(1); -} - -static void *xmalloc(size_t len) -{ - void *ret = malloc(len); - if (!ret) - die("Out of memory"); - return ret; -} - static CFDictionaryRef create_dictionary(CFAllocatorRef allocator, ...) { va_list args; @@ -112,6 +94,66 @@ static void write_item(const char *what, const char *buf, size_t len) putchar('\n'); } +static void write_item_strbuf(struct strbuf *sb, const char *what, const char *buf, int n) +{ + char s[32]; + + xsnprintf(s, sizeof(s), "__%s=", what); + strbuf_add(sb, s, strlen(s)); + strbuf_add(sb, buf, n); +} + +static void write_item_strbuf_cfstring(struct strbuf *sb, const char *what, CFStringRef ref) +{ + char *buf; + int len; + + if (!ref) + return; + len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(ref), ENCODING) + 1; + buf = xmalloc(len); + if (CFStringGetCString(ref, buf, len, ENCODING)) + write_item_strbuf(sb, what, buf, strlen(buf)); + free(buf); +} + +static void write_item_strbuf_cfnumber(struct strbuf *sb, const char *what, CFNumberRef ref) +{ + short n; + char buf[32]; + + if (!ref) + return; + if (!CFNumberGetValue(ref, kCFNumberShortType, &n)) + return; + xsnprintf(buf, sizeof(buf), "%d", n); + write_item_strbuf(sb, what, buf, strlen(buf)); +} + +static void write_item_strbuf_cfdata(struct strbuf *sb, const char *what, CFDataRef ref) +{ + char *buf; + int len; + + if (!ref) + return; + buf = (char *)CFDataGetBytePtr(ref); + if (!buf || strlen(buf) == 0) + return; + len = CFDataGetLength(ref); + write_item_strbuf(sb, what, buf, len); +} + +static void encode_state_seen(struct strbuf *sb) +{ + strbuf_add(sb, "osxkeychain:seen=", strlen("osxkeychain:seen=")); + write_item_strbuf_cfstring(sb, "host", host); + write_item_strbuf_cfnumber(sb, "port", port); + write_item_strbuf_cfstring(sb, "path", path); + write_item_strbuf_cfstring(sb, "username", username); + write_item_strbuf_cfdata(sb, "password", password); +} + static void find_username_in_item(CFDictionaryRef item) { CFStringRef account_ref; @@ -124,6 +166,7 @@ static void find_username_in_item(CFDictionaryRef item) write_item("username", "", 0); return; } + username = CFStringCreateCopy(kCFAllocatorDefault, account_ref); username_buf = (char *)CFStringGetCStringPtr(account_ref, ENCODING); if (username_buf) @@ -163,6 +206,7 @@ static OSStatus find_internet_password(void) } data = CFDictionaryGetValue(item, kSecValueData); + password = CFDataCreateCopy(kCFAllocatorDefault, data); write_item("password", (const char *)CFDataGetBytePtr(data), @@ -173,7 +217,14 @@ static OSStatus find_internet_password(void) CFRelease(item); write_item("capability[]", "state", strlen("state")); - write_item("state[]", "osxkeychain:seen=1", strlen("osxkeychain:seen=1")); + { + struct strbuf sb; + + strbuf_init(&sb, 1024); + encode_state_seen(&sb); + write_item("state[]", sb.buf, strlen(sb.buf)); + strbuf_release(&sb); + } out: CFRelease(attrs); @@ -288,13 +339,22 @@ static OSStatus add_internet_password(void) CFDictionaryRef attrs; OSStatus result; - if (state_seen) - return errSecSuccess; - /* Only store complete credentials */ if (!protocol || !host || !username || !password) return -1; + if (state_seen) { + struct strbuf sb; + + strbuf_init(&sb, 1024); + encode_state_seen(&sb); + if (!strcmp(state_seen, sb.buf)) { + strbuf_release(&sb); + return errSecSuccess; + } + strbuf_release(&sb); + } + data = CFDataCreateMutableCopy(kCFAllocatorDefault, 0, password); if (password_expiry_utc) { CFDataAppendBytes(data, @@ -403,8 +463,9 @@ static void read_credential(void) (UInt8 *)v, strlen(v)); else if (!strcmp(buf, "state[]")) { - if (!strcmp(v, "osxkeychain:seen=1")) - state_seen = 1; + int len = strlen("osxkeychain:seen="); + if (!strncmp(v, "osxkeychain:seen=", len)) + state_seen = xstrdup(v); } /* * Ignore other lines; we don't know what they mean, but @@ -443,5 +504,8 @@ int main(int argc, const char **argv) clear_credential(); + if (state_seen) + free(state_seen); + return 0; }
diff --git a/contrib/credential/osxkeychain/meson.build b/contrib/credential/osxkeychain/meson.build index 3c7677f..ec91d0c 100644 --- a/contrib/credential/osxkeychain/meson.build +++ b/contrib/credential/osxkeychain/meson.build
@@ -1,6 +1,7 @@ executable('git-credential-osxkeychain', sources: 'git-credential-osxkeychain.c', dependencies: [ + libgit, dependency('CoreFoundation'), dependency('Security'), ],
diff --git a/contrib/credential/wincred/git-credential-wincred.c b/contrib/credential/wincred/git-credential-wincred.c index 5683846..73c2b9b 100644 --- a/contrib/credential/wincred/git-credential-wincred.c +++ b/contrib/credential/wincred/git-credential-wincred.c
@@ -165,7 +165,7 @@ static void get_credential(void) write_item("username", creds[i]->UserName, creds[i]->UserName ? wcslen(creds[i]->UserName) : 0); if (creds[i]->CredentialBlobSize > 0) { - secret = xmalloc(creds[i]->CredentialBlobSize); + secret = xmalloc(creds[i]->CredentialBlobSize + sizeof(WCHAR)); wcsncpy_s(secret, creds[i]->CredentialBlobSize, (LPCWSTR)creds[i]->CredentialBlob, creds[i]->CredentialBlobSize / sizeof(WCHAR)); line = wcstok_s(secret, L"\r\n", &remaining_lines); write_item("password", line, line ? wcslen(line) : 0);
diff --git a/contrib/diff-highlight/DiffHighlight.pm b/contrib/diff-highlight/DiffHighlight.pm index 3d061bc..abe4578 100644 --- a/contrib/diff-highlight/DiffHighlight.pm +++ b/contrib/diff-highlight/DiffHighlight.pm
@@ -1,6 +1,6 @@ package DiffHighlight; -require v5.26; +require v5.008; use warnings FATAL => 'all'; use strict; @@ -9,18 +9,11 @@ my $NULL = File::Spec->devnull(); -# Highlight by reversing foreground and background. You could do -# other things like bold or underline if you prefer. -my @OLD_HIGHLIGHT = ( - color_config('color.diff-highlight.oldnormal'), - color_config('color.diff-highlight.oldhighlight', "\x1b[7m"), - color_config('color.diff-highlight.oldreset', "\x1b[27m") -); -my @NEW_HIGHLIGHT = ( - color_config('color.diff-highlight.newnormal', $OLD_HIGHLIGHT[0]), - color_config('color.diff-highlight.newhighlight', $OLD_HIGHLIGHT[1]), - color_config('color.diff-highlight.newreset', $OLD_HIGHLIGHT[2]) -); +# The color theme is initially set to nothing here to allow outside callers +# to set the colors for their application. If nothing is sent in we use +# colors from git config in load_color_config(). +our @OLD_HIGHLIGHT = (); +our @NEW_HIGHLIGHT = (); my $RESET = "\x1b[m"; my $COLOR = qr/\x1b\[[0-9;]*m/; @@ -138,9 +131,21 @@ # of it being used in other settings. Let's handle our own # fallback, which means we will work even if git can't be run. sub color_config { + our $cached_config; my ($key, $default) = @_; - my $s = `git config --get-color $key 2>$NULL`; - return length($s) ? $s : $default; + + if (!defined $cached_config) { + $cached_config = {}; + my $data = `git config --type=color --get-regexp '^color\.diff-highlight\.' 2>$NULL`; + for my $line (split /\n/, $data) { + my ($key, $color) = split ' ', $line, 2; + $key =~ s/^color\.diff-highlight\.// or next; + $cached_config->{$key} = $color; + } + } + + my $s = $cached_config->{$key}; + return defined($s) ? $s : $default; } sub show_hunk { @@ -170,6 +175,29 @@ $line_cb->(@queue); } +sub load_color_config { + # If the colors were NOT set from outside this module we load them on-demand + # from the git config. Note that only one of elements 0 and 2 in each + # array is used (depending on whether you are doing set/unset on an + # attribute, or specifying normal vs highlighted coloring). So we use + # element 1 as our check for whether colors were passed in; it should + # always be set if you want highlighting to do anything. + if (!defined $OLD_HIGHLIGHT[1]) { + @OLD_HIGHLIGHT = ( + color_config('oldnormal'), + color_config('oldhighlight', "\x1b[7m"), + color_config('oldreset', "\x1b[27m") + ); + } + if (!defined $NEW_HIGHLIGHT[1]) { + @NEW_HIGHLIGHT = ( + color_config('newnormal', $OLD_HIGHLIGHT[0]), + color_config('newhighlight', $OLD_HIGHLIGHT[1]), + color_config('newreset', $OLD_HIGHLIGHT[2]) + ); + }; +} + sub highlight_pair { my @a = split_line(shift); my @b = split_line(shift); @@ -218,6 +246,7 @@ } if (is_pair_interesting(\@a, $pa, $sa, \@b, $pb, $sb)) { + load_color_config(); return highlight_line(\@a, $pa, $sa, \@OLD_HIGHLIGHT), highlight_line(\@b, $pb, $sb, \@NEW_HIGHLIGHT); } @@ -273,6 +302,18 @@ # or suffix (disregarding boring bits like whitespace and colorization). sub is_pair_interesting { my ($a, $pa, $sa, $b, $pb, $sb) = @_; + + # We hit this case if the prefix consumed the entire line, meaning + # that two lines are identical. This generally shouldn't happen, + # since it implies the diff isn't minimal (you could shrink the hunk by + # making this a context line). But you can see it when the line + # content is the same, but the trailing newline is dropped, like: + # + # -foo + # +foo + # \No newline at end of file + return 0 if $pa == @$a || $pb == @$b; + my $prefix_a = join('', @$a[0..($pa-1)]); my $prefix_b = join('', @$b[0..($pb-1)]); my $suffix_a = join('', @$a[($sa+1)..$#$a]);
diff --git a/contrib/diff-highlight/README b/contrib/diff-highlight/README index 1db4440..ed8d876 100644 --- a/contrib/diff-highlight/README +++ b/contrib/diff-highlight/README
@@ -39,10 +39,21 @@ preserved; the intent is that the output should look exactly the same as the input, except for the occasional highlight. +Build/Install +------------- + +You can build the `diff-highlight` script by running `make` from within +the diff-highlight directory. There is no `make install` target; you can +copy the built script to your $PATH. + +You can run diff-highlight's internal tests by running `make test`. Note +that you must also build Git itself first (by running `make` from the +top-level of the project). + Use --- -You can try out the diff-highlight program with: +You can try out the built diff-highlight program with: --------------------------------------------- git log -p --color | /path/to/diff-highlight @@ -127,6 +138,12 @@ processing a logical chunk of input). The default function flushes stdout. + - @DiffHighlight::OLD_HIGHLIGHT and @DiffHighlight::NEW_HIGHLIGHT - these + arrays specify the normal, highlighted, and reset colors (in that order) + for old/new lines. If unset, values will be retrieved by calling `git + config` (see "Color Config" above). Note that these should be the literal + color bytes (starting with an ANSI escape code), not color names. + The script may then feed lines, one at a time, to DiffHighlight::handle_line(). When lines are done processing, they will be fed to $line_cb. Note that DiffHighlight may queue up many input lines (to analyze a whole hunk)
diff --git a/contrib/diff-highlight/t/t9400-diff-highlight.sh b/contrib/diff-highlight/t/t9400-diff-highlight.sh index f6f5195..b38fe21 100755 --- a/contrib/diff-highlight/t/t9400-diff-highlight.sh +++ b/contrib/diff-highlight/t/t9400-diff-highlight.sh
@@ -7,9 +7,8 @@ TEST_DIRECTORY="$CURR_DIR"/../../../t DIFF_HIGHLIGHT="$CURR_DIR"/../diff-highlight -CW="$(printf "\033[7m")" # white -CR="$(printf "\033[27m")" # reset - +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . "$TEST_DIRECTORY"/test-lib.sh if ! test_have_prereq PERL @@ -39,8 +38,10 @@ git show >commit.raw } >/dev/null && - "$DIFF_HIGHLIGHT" <diff.raw | test_strip_patch_header >diff.act && - "$DIFF_HIGHLIGHT" <commit.raw | test_strip_patch_header >commit.act && + "$DIFF_HIGHLIGHT" <diff.raw >diff.hi && + test_strip_patch_header <diff.hi | test_decode_color >diff.act && + "$DIFF_HIGHLIGHT" <commit.raw >commit.hi && + test_strip_patch_header <commit.hi | test_decode_color >commit.act && test_cmp patch.exp diff.act && test_cmp patch.exp commit.act } @@ -122,8 +123,8 @@ dh_test a b <<-EOF @@ -1,3 +1,3 @@ aaa - -${CW}b${CR}bb - +${CW}0${CR}bb + -<REVERSE>b<NOREVERSE>bb + +<REVERSE>0<NOREVERSE>bb ccc EOF ' @@ -144,8 +145,8 @@ dh_test a b <<-EOF @@ -1,3 +1,3 @@ aaa - -bb${CW}b${CR} - +bb${CW}0${CR} + -bb<REVERSE>b<NOREVERSE> + +bb<REVERSE>0<NOREVERSE> ccc EOF ' @@ -166,8 +167,8 @@ dh_test a b <<-EOF @@ -1,3 +1,3 @@ aaa - -b${CW}b${CR}b - +b${CW}0${CR}b + -b<REVERSE>b<NOREVERSE>b + +b<REVERSE>0<NOREVERSE>b ccc EOF ' @@ -209,8 +210,8 @@ dh_test a b <<-EOF @@ -1,3 +1,3 @@ aaa - -b${CW}b${CR}b - +b${CW}0${CR}b + -b<REVERSE>b<NOREVERSE>b + +b<REVERSE>0<NOREVERSE>b +ccc EOF ' @@ -228,8 +229,8 @@ echo "unic${o_stroke}de" >b && dh_test a b <<-EOF @@ -1 +1 @@ - -unic${CW}${o_accent}${CR}de - +unic${CW}${o_stroke}${CR}de + -unic<REVERSE>${o_accent}<NOREVERSE>de + +unic<REVERSE>${o_stroke}<NOREVERSE>de EOF ' @@ -246,8 +247,8 @@ echo "unico${combine_circum}de" >b && dh_test a b <<-EOF @@ -1 +1 @@ - -unic${CW}o${combine_accent}${CR}de - +unic${CW}o${combine_circum}${CR}de + -unic<REVERSE>o${combine_accent}<NOREVERSE>de + +unic<REVERSE>o${combine_circum}<NOREVERSE>de EOF ' @@ -329,13 +330,52 @@ +++ b/file @@ -1,3 +1,3 @@ before - -the ${CW}old${CR} line - +the ${CW}new${CR} line + -the <REVERSE>old<NOREVERSE> line + +the <REVERSE>new<NOREVERSE> line -leading dash EOF git log --graph -p -1 | "$DIFF_HIGHLIGHT" >actual.raw && - trim_graph <actual.raw | sed -n "/^---/,\$p" >actual && + trim_graph <actual.raw | sed -n "/^---/,\$p" | test_decode_color >actual && test_cmp expect actual ' +test_expect_success 'highlight diff that removes final newline' ' + printf "content\n" >a && + printf "content" >b && + dh_test a b <<-\EOF + @@ -1 +1 @@ + -content + +content + \ No newline at end of file + EOF +' + +test_expect_success 'configure set/reset colors' ' + test_config color.diff-highlight.oldhighlight bold && + test_config color.diff-highlight.oldreset nobold && + test_config color.diff-highlight.newhighlight italic && + test_config color.diff-highlight.newreset noitalic && + echo "prefix a suffix" >a && + echo "prefix b suffix" >b && + dh_test a b <<-\EOF + @@ -1 +1 @@ + -prefix <BOLD>a<NORMAL_INTENSITY> suffix + +prefix <ITALIC>b<NOITALIC> suffix + EOF +' + +test_expect_success 'configure normal/highlight colors' ' + test_config color.diff-highlight.oldnormal red && + test_config color.diff-highlight.oldhighlight magenta && + test_config color.diff-highlight.newnormal green && + test_config color.diff-highlight.newhighlight yellow && + echo "prefix a suffix" >a && + echo "prefix b suffix" >b && + dh_test a b <<-\EOF + @@ -1 +1 @@ + <RED>-prefix <RESET><MAGENTA>a<RESET><RED> suffix<RESET> + <GREEN>+prefix <RESET><YELLOW>b<RESET><GREEN> suffix<RESET> + EOF +' + test_done
diff --git a/contrib/meson.build b/contrib/meson.build index a88c5df..569c23e 100644 --- a/contrib/meson.build +++ b/contrib/meson.build
@@ -2,5 +2,4 @@ subdir(feature) endforeach -subdir('coccinelle') subdir('credential')
diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh index 17106d1..791fd82 100755 --- a/contrib/subtree/git-subtree.sh +++ b/contrib/subtree/git-subtree.sh
@@ -257,6 +257,9 @@ test -e "$arg_prefix" && die "fatal: prefix '$arg_prefix' already exists." ;; + split) + # checked later against the commit, not the working tree + ;; *) test -e "$arg_prefix" || die "fatal: '$arg_prefix' does not exist; use 'git subtree add'" @@ -785,42 +788,6 @@ die "fatal: '$1' does not look like a ref" } -# Usage: should_ignore_subtree_split_commit REV -# -# Check if REV is a commit from another subtree and should be -# ignored from processing for splits -should_ignore_subtree_split_commit () { - assert test $# = 1 - - git show \ - --no-patch \ - --no-show-signature \ - --format='%(trailers:key=git-subtree-dir,key=git-subtree-mainline)' \ - "$1" | - ( - have_mainline= - subtree_dir= - - while read -r trailer val - do - case "$trailer" in - git-subtree-dir:) - subtree_dir="${val%/}" ;; - git-subtree-mainline:) - have_mainline=y ;; - esac - done - - if test -n "${subtree_dir}" && - test -z "${have_mainline}" && - test "${subtree_dir}" != "$arg_prefix" - then - return 0 - fi - return 1 - ) -} - # Usage: process_split_commit REV PARENTS process_split_commit () { assert test $# = 2 @@ -966,6 +933,12 @@ else die "fatal: you must provide exactly one revision, and optionally a repository. Got: '$*'" fi + + # Now validate prefix against the commit, not the working tree + if ! git cat-file -e "$rev:$dir" 2>/dev/null + then + die "fatal: '$dir' does not exist; use 'git subtree add'" + fi repository="" if test "$#" = 2 then @@ -1006,19 +979,7 @@ eval "$grl" | while read rev parents do - if should_ignore_subtree_split_commit "$rev" - then - continue - fi - parsedparents='' - for parent in $parents - do - if ! should_ignore_subtree_split_commit "$parent" - then - parsedparents="$parsedparents$parent " - fi - done - process_split_commit "$rev" "$parsedparents" + process_split_commit "$rev" "$parents" done || exit $? latest_new=$(cache_get latest_new) || exit $?
diff --git a/contrib/subtree/meson.build b/contrib/subtree/meson.build index 161435a..804c315 100644 --- a/contrib/subtree/meson.build +++ b/contrib/subtree/meson.build
@@ -3,7 +3,7 @@ output: 'git-subtree', command: [ shell, - meson.project_source_root() / 'generate-script.sh', + meson.project_source_root() / 'tools/generate-script.sh', '@INPUT@', '@OUTPUT@', meson.project_build_root() / 'GIT-BUILD-OPTIONS',
diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh index 316dc52..18d2b56 100755 --- a/contrib/subtree/t/t7900-subtree.sh +++ b/contrib/subtree/t/t7900-subtree.sh
@@ -368,6 +368,28 @@ ) ' +test_expect_success 'split works when prefix exists in commit but not in working tree' ' + subtree_test_create_repo "$test_count" && + ( + cd "$test_count" && + + # create subtree + mkdir pkg && + echo ok >pkg/file && + git add pkg && + git commit -m "add pkg" && + good=$(git rev-parse HEAD) && + + # remove it from working tree in later commit + git rm -r pkg && + git commit -m "remove pkg" && + + # must still be able to split using the old commit + git subtree split --prefix=pkg "$good" >out && + test -s out + ) +' + test_expect_success 'split rejects flags for add' ' subtree_test_create_repo "$test_count" && subtree_test_create_repo "$test_count/sub proj" && @@ -411,8 +433,9 @@ git fetch ./"sub proj" HEAD && git subtree merge --prefix="sub dir" FETCH_HEAD && split_hash=$(git subtree split --prefix="sub dir" --annotate="*") && - git subtree split --prefix="sub dir" --annotate="*" --rejoin && - test "$(last_commit_subject)" = "Split '\''sub dir/'\'' into commit '\''$split_hash'\''" + git subtree split --prefix="sub dir" --annotate="*" -b spl --rejoin && + test "$(last_commit_subject)" = "Split '\''sub dir/'\'' into commit '\''$split_hash'\''" && + test "$(git rev-list --count spl)" -eq 5 ) ' @@ -427,8 +450,7 @@ # - Perform 'split' on subtree B # - Create new commits with changes to subtree A and B # - Perform split on subtree A -# - Check that the commits in subtree B are not processed -# as part of the subtree A split +# - Check for expected history test_expect_success 'split with multiple subtrees' ' subtree_test_create_repo "$test_count" && subtree_test_create_repo "$test_count/subA" && @@ -442,18 +464,25 @@ git -C "$test_count" subtree add --prefix=subADir FETCH_HEAD && git -C "$test_count" fetch ./subB HEAD && git -C "$test_count" subtree add --prefix=subBDir FETCH_HEAD && + test "$(git -C "$test_count" rev-list --count main)" -eq 7 && test_create_commit "$test_count" subADir/main-subA1 && test_create_commit "$test_count" subBDir/main-subB1 && git -C "$test_count" subtree split --prefix=subADir \ - --squash --rejoin -m "Sub A Split 1" && + --squash --rejoin -m "Sub A Split 1" -b a1 && + test "$(git -C "$test_count" rev-list --count main..a1)" -eq 1 && git -C "$test_count" subtree split --prefix=subBDir \ - --squash --rejoin -m "Sub B Split 1" && + --squash --rejoin -m "Sub B Split 1" -b b1 && + test "$(git -C "$test_count" rev-list --count main..b1)" -eq 1 && test_create_commit "$test_count" subADir/main-subA2 && test_create_commit "$test_count" subBDir/main-subB2 && git -C "$test_count" subtree split --prefix=subADir \ - --squash --rejoin -m "Sub A Split 2" && - test "$(git -C "$test_count" subtree split --prefix=subBDir \ - --squash --rejoin -d -m "Sub B Split 1" 2>&1 | grep -w "\[1\]")" = "" + --squash --rejoin -m "Sub A Split 2" -b a2 && + test "$(git -C "$test_count" rev-list --count main..a2)" -eq 2 && + test "$(git -C "$test_count" rev-list --count a1..a2)" -eq 1 && + git -C "$test_count" subtree split --prefix=subBDir \ + --squash --rejoin -d -m "Sub B Split 1" -b b2 && + test "$(git -C "$test_count" rev-list --count main..b2)" -eq 2 && + test "$(git -C "$test_count" rev-list --count b1..b2)" -eq 1 ' # When subtree split-ing a directory that has other subtree @@ -477,6 +506,7 @@ test_path_is_file subA/file1.t && test_path_is_file subA/subB/file2.t && git subtree split --prefix=subA --branch=bsplit && + test "$(git rev-list --count bsplit)" -eq 2 && git checkout bsplit && test_path_is_file file1.t && test_path_is_file subB/file2.t && @@ -489,6 +519,7 @@ --prefix=subA/subB mksubtree && test_path_is_file subA/subB/file3.t && git subtree split --prefix=subA --branch=bsplit && + test "$(git rev-list --count bsplit)" -eq 3 && git checkout bsplit && test_path_is_file file1.t && test_path_is_file subB/file2.t && @@ -497,6 +528,67 @@ ' done +# Usually, +# +# git subtree merge -P subA --squash f00... +# +# makes two commits, in this order: +# +# 1. Squashed 'subA/' content from commit f00... +# 2. Merge commit (1) as 'subA' +# +# Commit 1 updates the subtree but does *not* rewrite paths. +# Commit 2 rewrites all trees to start with `subA/` +# +# Commit 1 either has no parents or depends only on other +# "Squashed 'subA/' content" commits. +# +# For merge without --squash, subtree produces just one commit: +# a merge commit with git-subtree trailers. +# +# In either case, if the user rebases these commits, they will +# still have the git-subtree-* trailers… but will NOT have +# the layout described above. +# +# Test that subsequent `git subtree split` are not confused by this. +test_expect_success 'split with rebased subtree commit' ' + subtree_test_create_repo "$test_count" && + ( + cd "$test_count" && + test_commit file0 && + test_create_subtree_add \ + . mksubtree subA file1 --squash && + test_path_is_file subA/file1.t && + mkdir subB && + test_commit subB/bfile && + git commit --amend -F - <<'EOF' && +Squashed '\''subB/'\'' content from commit '\''badf00da911bbe895347b4b236f5461d55dc9877'\'' + +Simulate a cherry-picked or rebased subtree commit. + +git-subtree-dir: subB +git-subtree-split: badf00da911bbe895347b4b236f5461d55dc9877 +EOF + test_commit subA/file2 && + test_commit subB/bfile2 && + git commit --amend -F - <<'EOF' && +Split '\''subB/'\'' into commit '\''badf00da911bbe895347b4b236f5461d55dc9877'\'' + +Simulate a cherry-picked or rebased subtree commit. + +git-subtree-dir: subB +git-subtree-mainline: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +git-subtree-split: badf00da911bbe895347b4b236f5461d55dc9877 +EOF + git subtree split --prefix=subA --branch=bsplit && + git checkout bsplit && + test_path_is_file file1.t && + test_path_is_file file2.t && + test "$(last_commit_subject)" = "subA/file2" && + test "$(git rev-list --count bsplit)" -eq 2 + ) +' + test_expect_success 'split sub dir/ with --rejoin from scratch' ' subtree_test_create_repo "$test_count" && test_create_commit "$test_count" main1 && @@ -1575,6 +1667,116 @@ ) ' +# --ignore-joins must ignore mainline content outside of the +# subtree. This test verifies that the logic in +# `find_existing_splits()` correctly handles a `git subtree add` +# In this test, the split history must not contain a commit titled +# +# Add 'sub/' from commit ... +# +# see: dd21d43b58 (subtree: make --ignore-joins pay +# attention to adds, 2018-09-28) +test_expect_success 'split --ignore-joins respects subtree add' ' + subtree_test_create_repo "$test_count" && + ( + cd "$test_count" && + test_commit main_must_not_be_in_subtree && + test_create_subtree_add . mksubtree sub sub1 && + test_commit sub/sub2 && + test_commit main_must_not_be_in_subtree2 && + git subtree split --prefix sub -b first_split --rejoin && + test_commit sub/sub3 && + no_ignore_joins="$(git subtree split --prefix sub -b no_ignore_joins)" && + ignore_joins="$(git subtree split --prefix sub --ignore-joins -b ignore_joins)" && + git checkout ignore_joins && + test_path_is_file sub1.t && + test_path_is_file sub2.t && + test_path_is_file sub3.t && + ! test_path_is_file main_must_not_be_in_subtree.t && + ! test_path_is_file main_must_not_be_in_subtree2.t && + test -z "$(git log -1 --grep "Add '''sub/''' from commit" ignore_joins)" && + test "$no_ignore_joins" = "$ignore_joins" && + test "$(git rev-list --count ignore_joins)" -eq 3; + ) +' + +# split excludes commits reachable from any previous --rejoin. +# These ignored commits can still be the basis for new work +# after the --rejoin. These commits must be processed, even +# if they are excluded. Otherwise, the split history will be +# incorrect. +# +# here, the merge +# +# git merge --no-ff new_work_based_on_prejoin +# +# doesn't contain any subtree changes and so should not end +# up in the split history. this subtree should be flat, +# with no merges. +# +# see: 315a84f9aa (subtree: use commits before rejoins for +# splits, 2018-09-28) +test_expect_success 'split links out-of-tree pre --rejoin commits with post --rejoin commits' ' + subtree_test_create_repo "$test_count" && + ( + cd "$test_count" && + test_commit main_must_not_be_in_subtree && + mkdir sub && + test_commit sub/sub1 && + test_commit sub/sub2 && + git subtree split --prefix sub --rejoin && + test "$(git rev-list --count HEAD)" -eq 6 && + git checkout sub/sub1 && + git checkout -b new_work_based_on_prejoin && + test_commit main_must_not_be_in_subtree2 && + git checkout main && + git merge --no-ff new_work_based_on_prejoin && + test_commit sub/sub3 && + git subtree split -d --prefix sub -b second_split && + git checkout second_split && + test_path_is_file sub1.t && + test_path_is_file sub2.t && + test_path_is_file sub3.t && + ! test_path_is_file main_must_not_be_in_subtree.t && + ! test_path_is_file main_must_not_be_in_subtree2.t && + test "$(git rev-list --count --merges second_split)" -eq 0 && + test "$(git rev-list --count second_split)" -eq 3; + ) +' + +# split must keep merge commits with unrelated histories, even +# if both parents are treesame. When deciding whether or not +# to eliminate a parent, copy_or_skip compares the merge-base +# of each parent. +# +# in the split_of_merges branch: +# +# * expect 4 commits +# * HEAD~ must be a merge +# +# see: 68f8ff8151 (subtree: improve decision on merges kept +# in split, 2018-09-28) +test_expect_success 'split preserves merges with unrelated history' ' + subtree_test_create_repo "$test_count" && + ( + cd "$test_count" && + test_commit main_must_not_be_in_subtree && + mkdir sub && + test_commit sub/sub1 && + git checkout --orphan new_history && + git checkout sub/sub1 -- . && + git add . && + git commit -m "treesame history but not a merge-base" && + git checkout main && + git merge --allow-unrelated-histories --no-ff new_history && + test "$(git rev-parse "HEAD^1^{tree}")" = "$(git rev-parse "HEAD^2^{tree}")" && + test_commit sub/sub2 && + git subtree split -d --prefix sub -b split_of_merges && + test "$(git rev-list --count split_of_merges)" -eq 4 && + test -n "$(git rev-list --merges HEAD~)"; + ) +' + # # This test covers 2 cases in subtree split copy_or_skip code # 1) Merges where one parent is a superset of the changes of the other @@ -1597,7 +1799,6 @@ test_expect_success 'subtree descendant check' ' subtree_test_create_repo "$test_count" && - defaultBranch=$(sed "s,ref: refs/heads/,," "$test_count/.git/HEAD") && test_create_commit "$test_count" folder_subtree/a && ( cd "$test_count" && @@ -1614,7 +1815,7 @@ ( cd "$test_count" && git cherry-pick $cherry && - git checkout $defaultBranch && + git checkout main && git merge -m "merge should be kept on subtree" branch && git branch no_subtree_work_branch ) && @@ -1626,10 +1827,10 @@ test_create_commit "$test_count" not_a_subtree_change && ( cd "$test_count" && - git checkout $defaultBranch && + git checkout main && git merge -m "merge should be skipped on subtree" no_subtree_work_branch && - git subtree split --prefix folder_subtree/ --branch subtree_tip $defaultBranch && + git subtree split --prefix folder_subtree/ --branch subtree_tip main && git subtree split --prefix folder_subtree/ --branch subtree_branch branch && test $(git rev-list --count subtree_tip..subtree_branch) = 0 )
diff --git a/convert.c b/convert.c index c7d6a85..a34ec6e 100644 --- a/convert.c +++ b/convert.c
@@ -1122,7 +1122,8 @@ static int count_ident(const char *cp, unsigned long size) static int ident_to_git(const char *src, size_t len, struct strbuf *buf, int ident) { - char *dst, *dollar; + char *dst; + const char *dollar; if (!ident || (src && !count_ident(src, len))) return 0;
diff --git a/csum-file.c b/csum-file.c index 6e21e3c..9558177 100644 --- a/csum-file.c +++ b/csum-file.c
@@ -110,7 +110,7 @@ void discard_hashfile(struct hashfile *f) free_hashfile(f); } -void hashwrite(struct hashfile *f, const void *buf, unsigned int count) +void hashwrite(struct hashfile *f, const void *buf, uint32_t count) { while (count) { unsigned left = f->buffer_len - f->offset; @@ -161,17 +161,16 @@ struct hashfile *hashfd_check(const struct git_hash_algo *algop, return f; } -static struct hashfile *hashfd_internal(const struct git_hash_algo *algop, - int fd, const char *name, - struct progress *tp, - size_t buffer_len) +struct hashfile *hashfd_ext(const struct git_hash_algo *algop, + int fd, const char *name, + const struct hashfd_options *opts) { struct hashfile *f = xmalloc(sizeof(*f)); f->fd = fd; f->check_fd = -1; f->offset = 0; f->total = 0; - f->tp = tp; + f->tp = opts->progress; f->name = name; f->do_crc = 0; f->skip_hash = 0; @@ -179,8 +178,8 @@ static struct hashfile *hashfd_internal(const struct git_hash_algo *algop, f->algop = unsafe_hash_algo(algop); f->algop->init_fn(&f->ctx); - f->buffer_len = buffer_len; - f->buffer = xmalloc(buffer_len); + f->buffer_len = opts->buffer_len ? opts->buffer_len : 128 * 1024; + f->buffer = xmalloc(f->buffer_len); f->check_buffer = NULL; return f; @@ -194,19 +193,8 @@ struct hashfile *hashfd(const struct git_hash_algo *algop, * measure the rate of data passing through this hashfile, * use a larger buffer size to reduce fsync() calls. */ - return hashfd_internal(algop, fd, name, NULL, 128 * 1024); -} - -struct hashfile *hashfd_throughput(const struct git_hash_algo *algop, - int fd, const char *name, struct progress *tp) -{ - /* - * Since we are expecting to report progress of the - * write into this hashfile, use a smaller buffer - * size so the progress indicators arrive at a more - * frequent rate. - */ - return hashfd_internal(algop, fd, name, tp, 8 * 1024); + struct hashfd_options opts = { 0 }; + return hashfd_ext(algop, fd, name, &opts); } void hashfile_checkpoint_init(struct hashfile *f,
diff --git a/csum-file.h b/csum-file.h index 07ae110..a9b390d 100644 --- a/csum-file.h +++ b/csum-file.h
@@ -45,12 +45,24 @@ int hashfile_truncate(struct hashfile *, struct hashfile_checkpoint *); #define CSUM_FSYNC 2 #define CSUM_HASH_IN_STREAM 4 +struct hashfd_options { + /* + * Throughput progress that counts the number of bytes that have been + * hashed. + */ + struct progress *progress; + + /* The length of the buffer that shall be used read read data. */ + size_t buffer_len; +}; + +struct hashfile *hashfd_ext(const struct git_hash_algo *algop, + int fd, const char *name, + const struct hashfd_options *opts); struct hashfile *hashfd(const struct git_hash_algo *algop, int fd, const char *name); struct hashfile *hashfd_check(const struct git_hash_algo *algop, const char *name); -struct hashfile *hashfd_throughput(const struct git_hash_algo *algop, - int fd, const char *name, struct progress *tp); /* * Free the hashfile without flushing its contents to disk. This only @@ -63,7 +75,7 @@ void free_hashfile(struct hashfile *f); */ int finalize_hashfile(struct hashfile *, unsigned char *, enum fsync_component, unsigned int); void discard_hashfile(struct hashfile *); -void hashwrite(struct hashfile *, const void *, unsigned int); +void hashwrite(struct hashfile *, const void *, uint32_t); void hashflush(struct hashfile *f); void crc32_begin(struct hashfile *); uint32_t crc32_end(struct hashfile *);
diff --git a/delta-islands.c b/delta-islands.c index 36c9479..f4d2468 100644 --- a/delta-islands.c +++ b/delta-islands.c
@@ -283,7 +283,7 @@ void resolve_tree_islands(struct repository *r, root_marks = kh_value(island_marks, pos); tree = lookup_tree(r, &ent->idx.oid); - if (!tree || parse_tree(tree) < 0) + if (!tree || repo_parse_tree(r, tree) < 0) die(_("bad tree object %s"), oid_to_hex(&ent->idx.oid)); init_tree_desc(&desc, &tree->object.oid, tree->buffer, tree->size); @@ -390,8 +390,7 @@ static void add_ref_to_island(kh_str_t *remote_islands, const char *island_name, rl->hash += sha_core; } -static int find_island_for_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid, - int flags UNUSED, void *cb) +static int find_island_for_ref(const struct reference *ref, void *cb) { struct island_load_data *ild = cb; @@ -406,7 +405,7 @@ static int find_island_for_ref(const char *refname, const char *referent UNUSED, /* walk backwards to get last-one-wins ordering */ for (i = ild->nr - 1; i >= 0; i--) { - if (!regexec(&ild->rx[i], refname, + if (!regexec(&ild->rx[i], ref->name, ARRAY_SIZE(matches), matches, 0)) break; } @@ -428,10 +427,10 @@ static int find_island_for_ref(const char *refname, const char *referent UNUSED, if (island_name.len) strbuf_addch(&island_name, '-'); - strbuf_add(&island_name, refname + match->rm_so, match->rm_eo - match->rm_so); + strbuf_add(&island_name, ref->name + match->rm_so, match->rm_eo - match->rm_so); } - add_ref_to_island(ild->remote_islands, island_name.buf, oid); + add_ref_to_island(ild->remote_islands, island_name.buf, ref->oid); strbuf_release(&island_name); return 0; }
diff --git a/diff-delta.c b/diff-delta.c index 71d3736..43c339f 100644 --- a/diff-delta.c +++ b/diff-delta.c
@@ -171,7 +171,7 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize) mem = hash + hsize; entry = mem; - memset(hash, 0, hsize * sizeof(*hash)); + MEMZERO_ARRAY(hash, hsize); /* allocate an array to count hash entries */ hash_count = calloc(hsize, sizeof(*hash_count));
diff --git a/diff-lib.c b/diff-lib.c index b8f8f3b..ae91027 100644 --- a/diff-lib.c +++ b/diff-lib.c
@@ -226,8 +226,12 @@ void run_diff_files(struct rev_info *revs, unsigned int option) continue; } - if (ce_uptodate(ce) || ce_skip_worktree(ce)) + if (ce_uptodate(ce) || ce_skip_worktree(ce)) { + if (revs->diffopt.flags.find_copies_harder) + diff_same(&revs->diffopt, ce->ce_mode, + &ce->oid, ce->name); continue; + } /* * When CE_VALID is set (via "update-index --assume-unchanged" @@ -272,8 +276,10 @@ void run_diff_files(struct rev_info *revs, unsigned int option) if (!changed && !dirty_submodule) { ce_mark_uptodate(ce); mark_fsmonitor_valid(istate, ce); - if (!revs->diffopt.flags.find_copies_harder) - continue; + if (revs->diffopt.flags.find_copies_harder) + diff_same(&revs->diffopt, newmode, + &ce->oid, ce->name); + continue; } oldmode = ce->ce_mode; old_oid = &ce->oid; @@ -418,13 +424,12 @@ static int show_modified(struct rev_info *revs, } oldmode = old_entry->ce_mode; - if (mode == oldmode && oideq(oid, &old_entry->oid) && !dirty_submodule && - !revs->diffopt.flags.find_copies_harder) - return 0; - - diff_change(&revs->diffopt, oldmode, mode, - &old_entry->oid, oid, 1, !is_null_oid(oid), - old_entry->name, 0, dirty_submodule); + if (mode != oldmode || !oideq(oid, &old_entry->oid) || dirty_submodule) + diff_change(&revs->diffopt, oldmode, mode, + &old_entry->oid, oid, 1, !is_null_oid(oid), + old_entry->name, 0, dirty_submodule); + else if (revs->diffopt.flags.find_copies_harder) + diff_same(&revs->diffopt, mode, oid, old_entry->name); return 0; } @@ -547,7 +552,7 @@ static int diff_cache(struct rev_info *revs, struct tree_desc t; struct unpack_trees_options opts; - tree = parse_tree_indirect(tree_oid); + tree = repo_parse_tree_indirect(the_repository, tree_oid); if (!tree) return error("bad tree object %s", tree_name ? tree_name : oid_to_hex(tree_oid)); @@ -610,7 +615,7 @@ void diff_get_merge_base(const struct rev_info *revs, struct object_id *mb) oidcpy(mb, &merge_bases->item->object.oid); - free_commit_list(merge_bases); + commit_list_free(merge_bases); } void run_diff_index(struct rev_info *revs, unsigned int option)
diff --git a/diff.c b/diff.c index a196152..e87847f 100644 --- a/diff.c +++ b/diff.c
@@ -601,6 +601,7 @@ struct emit_callback { int blank_at_eof_in_postimage; int lno_in_preimage; int lno_in_postimage; + int last_line_kind; const char **label_path; struct diff_words_data *diff_words; struct diff_options *opt; @@ -796,21 +797,23 @@ enum diff_symbol { DIFF_SYMBOL_CONTEXT_INCOMPLETE, DIFF_SYMBOL_PLUS, DIFF_SYMBOL_MINUS, - DIFF_SYMBOL_NO_LF_EOF, DIFF_SYMBOL_CONTEXT_FRAGINFO, DIFF_SYMBOL_CONTEXT_MARKER, DIFF_SYMBOL_SEPARATOR }; + /* * Flags for content lines: - * 0..12 are whitespace rules - * 13-15 are WSEH_NEW | WSEH_OLD | WSEH_CONTEXT - * 16 is marking if the line is blank at EOF + * 0..15 are whitespace rules (see ws.h) + * 16..18 are WSEH_NEW | WSEH_CONTEXT | WSEH_OLD + * 19 is marking if the line is blank at EOF + * 20..22 are used for color-moved. */ -#define DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF (1<<16) -#define DIFF_SYMBOL_MOVED_LINE (1<<17) -#define DIFF_SYMBOL_MOVED_LINE_ALT (1<<18) -#define DIFF_SYMBOL_MOVED_LINE_UNINTERESTING (1<<19) +#define DIFF_SYMBOL_CONTENT_BLANK_LINE_EOF (1<<19) +#define DIFF_SYMBOL_MOVED_LINE (1<<20) +#define DIFF_SYMBOL_MOVED_LINE_ALT (1<<21) +#define DIFF_SYMBOL_MOVED_LINE_UNINTERESTING (1<<22) + #define DIFF_SYMBOL_CONTENT_WS_MASK (WSEH_NEW | WSEH_OLD | WSEH_CONTEXT | WS_RULE_MASK) /* @@ -1318,20 +1321,25 @@ static void emit_line_ws_markup(struct diff_options *o, const char *ws = NULL; int sign = o->output_indicators[sign_index]; + if (diff_suppress_blank_empty && + sign_index == OUTPUT_INDICATOR_CONTEXT && + len == 1 && line[0] == '\n') + sign = 0; + if (o->ws_error_highlight & ws_rule) { ws = diff_get_color_opt(o, DIFF_WHITESPACE); if (!*ws) ws = NULL; } - if (!ws && !set_sign) + if (!ws && !set_sign) { emit_line_0(o, set, NULL, 0, reset, sign, line, len); - else if (!ws) { + } else if (!ws) { emit_line_0(o, set_sign, set, !!set_sign, reset, sign, line, len); - } else if (blank_at_eof) + } else if (blank_at_eof) { /* Blank line at EOF - paint '+' as well */ emit_line_0(o, ws, NULL, 0, reset, sign, line, len); - else { + } else { /* Emit just the prefix, then the rest. */ emit_line_0(o, set_sign ? set_sign : set, NULL, !!set_sign, reset, sign, "", 0); @@ -1343,7 +1351,6 @@ static void emit_line_ws_markup(struct diff_options *o, static void emit_diff_symbol_from_struct(struct diff_options *o, struct emitted_diff_symbol *eds) { - static const char *nneof = " No newline at end of file\n"; const char *context, *reset, *set, *set_sign, *meta, *fraginfo; enum diff_symbol s = eds->s; @@ -1355,13 +1362,6 @@ static void emit_diff_symbol_from_struct(struct diff_options *o, return; switch (s) { - case DIFF_SYMBOL_NO_LF_EOF: - context = diff_get_color_opt(o, DIFF_CONTEXT); - reset = diff_get_color_opt(o, DIFF_RESET); - putc('\n', o->file); - emit_line_0(o, context, NULL, 0, reset, '\\', - nneof, strlen(nneof)); - break; case DIFF_SYMBOL_SUBMODULE_HEADER: case DIFF_SYMBOL_SUBMODULE_ERROR: case DIFF_SYMBOL_SUBMODULE_PIPETHROUGH: @@ -1373,6 +1373,14 @@ static void emit_diff_symbol_from_struct(struct diff_options *o, emit_line(o, "", "", line, len); break; case DIFF_SYMBOL_CONTEXT_INCOMPLETE: + if ((flags & WS_INCOMPLETE_LINE) && + (flags & o->ws_error_highlight)) + set = diff_get_color_opt(o, DIFF_WHITESPACE); + else + set = diff_get_color_opt(o, DIFF_CONTEXT); + reset = diff_get_color_opt(o, DIFF_RESET); + emit_line(o, set, reset, line, len); + break; case DIFF_SYMBOL_CONTEXT_MARKER: context = diff_get_color_opt(o, DIFF_CONTEXT); reset = diff_get_color_opt(o, DIFF_RESET); @@ -1498,15 +1506,9 @@ static void emit_diff_symbol_from_struct(struct diff_options *o, case DIFF_SYMBOL_WORDS: context = diff_get_color_opt(o, DIFF_CONTEXT); reset = diff_get_color_opt(o, DIFF_RESET); - /* - * Skip the prefix character, if any. With - * diff_suppress_blank_empty, there may be - * none. - */ - if (line[0] != '\n') { - line++; - len--; - } + + /* Skip the prefix character */ + line++; len--; emit_line(o, context, reset, line, len); break; case DIFF_SYMBOL_FILEPAIR_PLUS: @@ -1668,6 +1670,19 @@ static void emit_context_line(struct emit_callback *ecbdata, emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_CONTEXT, line, len, flags); } +static void emit_incomplete_line_marker(struct emit_callback *ecbdata, + const char *line, int len) +{ + int last_line_kind = ecbdata->last_line_kind; + unsigned flags = (last_line_kind == '+' + ? WSEH_NEW + : last_line_kind == '-' + ? WSEH_OLD + : WSEH_CONTEXT) | ecbdata->ws_rule; + emit_diff_symbol(ecbdata->opt, DIFF_SYMBOL_CONTEXT_INCOMPLETE, + line, len, flags); +} + static void emit_hunk_header(struct emit_callback *ecbdata, const char *line, int len) { @@ -1769,28 +1784,44 @@ static void add_line_count(struct strbuf *out, int count) } } -static void emit_rewrite_lines(struct emit_callback *ecb, +static void emit_rewrite_lines(struct emit_callback *ecbdata, int prefix, const char *data, int size) { const char *endp = NULL; while (0 < size) { - int len; + int len, plen; + char *pdata = NULL; endp = memchr(data, '\n', size); - len = endp ? (endp - data + 1) : size; - if (prefix != '+') { - ecb->lno_in_preimage++; - emit_del_line(ecb, data, len); + + if (endp) { + len = endp - data + 1; + plen = len; } else { - ecb->lno_in_postimage++; - emit_add_line(ecb, data, len); + len = size; + plen = len + 1; + pdata = xmalloc(plen + 2); + memcpy(pdata, data, len); + pdata[len] = '\n'; + pdata[len + 1] = '\0'; } + if (prefix != '+') { + ecbdata->lno_in_preimage++; + emit_del_line(ecbdata, pdata ? pdata : data, plen); + } else { + ecbdata->lno_in_postimage++; + emit_add_line(ecbdata, pdata ? pdata : data, plen); + } + free(pdata); size -= len; data += len; } - if (!endp) - emit_diff_symbol(ecb->opt, DIFF_SYMBOL_NO_LF_EOF, NULL, 0, 0); + if (!endp) { + static const char nneof[] = "\\ No newline at end of file\n"; + ecbdata->last_line_kind = prefix; + emit_incomplete_line_marker(ecbdata, nneof, sizeof(nneof) - 1); + } } static void emit_rewrite_diff(const char *name_a, @@ -1806,6 +1837,7 @@ static void emit_rewrite_diff(const char *name_a, const char *a_prefix, *b_prefix; char *data_one, *data_two; size_t size_one, size_two; + unsigned ws_rule; struct emit_callback ecbdata; struct strbuf out = STRBUF_INIT; @@ -1828,9 +1860,15 @@ static void emit_rewrite_diff(const char *name_a, size_one = fill_textconv(o->repo, textconv_one, one, &data_one); size_two = fill_textconv(o->repo, textconv_two, two, &data_two); + ws_rule = whitespace_rule(o->repo->index, name_b); + + /* symlink being an incomplete line is not a news */ + if (DIFF_FILE_VALID(two) && S_ISLNK(two->mode)) + ws_rule &= ~WS_INCOMPLETE_LINE; + memset(&ecbdata, 0, sizeof(ecbdata)); ecbdata.color_diff = o->use_color; - ecbdata.ws_rule = whitespace_rule(o->repo->index, name_b); + ecbdata.ws_rule = ws_rule; ecbdata.opt = o; if (ecbdata.ws_rule & WS_BLANK_AT_EOF) { mmfile_t mf1, mf2; @@ -1930,7 +1968,7 @@ static int fn_out_diff_words_write_helper(struct diff_options *o, struct strbuf sb = STRBUF_INIT; while (count) { - char *p = memchr(buf, '\n', count); + const char *p = memchr(buf, '\n', count); if (print) strbuf_addstr(&sb, diff_line_prefix(o)); @@ -2375,12 +2413,6 @@ static int fn_out_consume(void *priv, char *line, unsigned long len) ecbdata->label_path[0] = ecbdata->label_path[1] = NULL; } - if (diff_suppress_blank_empty - && len == 2 && line[0] == ' ' && line[1] == '\n') { - line[0] = '\n'; - len = 1; - } - if (line[0] == '@') { if (ecbdata->diff_words) diff_words_flush(ecbdata); @@ -2431,13 +2463,24 @@ static int fn_out_consume(void *priv, char *line, unsigned long len) ecbdata->lno_in_preimage++; emit_context_line(ecbdata, line + 1, len - 1); break; - default: + case '\\': /* incomplete line at the end */ + switch (ecbdata->last_line_kind) { + case '+': + case '-': + case ' ': + break; + default: + BUG("fn_out_consume: '\\No newline' after unknown line (%c)", + ecbdata->last_line_kind); + } ecbdata->lno_in_preimage++; - emit_diff_symbol(o, DIFF_SYMBOL_CONTEXT_INCOMPLETE, - line, len, 0); + emit_incomplete_line_marker(ecbdata, line, len); break; + default: + BUG("fn_out_consume: unknown line '%s'", line); } + ecbdata->last_line_kind = line[0]; return 0; } @@ -2713,7 +2756,9 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) count = i; /* where we can stop scanning in data->files[] */ /* - * We have width = stat_width or term_columns() columns total. + * We have width = stat_width or term_columns() columns total minus the + * length of line_prefix skipping ANSI escape codes to get the display + * width (e.g., skip ANSI-colored strings in "log --graph --stat"). * We want a maximum of min(max_len, stat_name_width) for the name part. * We want a maximum of min(max_change, stat_graph_width) for the +- part. * We also need 1 for " " and 4 + decimal_width(max_change) @@ -2740,14 +2785,8 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) * separators and this message, this message will "overflow" * making the line longer than the maximum width. */ - - /* - * NEEDSWORK: line_prefix is often used for "log --graph" output - * and contains ANSI-colored string. utf8_strnwidth() should be - * used to correctly count the display width instead of strlen(). - */ if (options->stat_width == -1) - width = term_columns() - strlen(line_prefix); + width = term_columns() - utf8_strnwidth(line_prefix, strlen(line_prefix), 1); else width = options->stat_width ? options->stat_width : 80; number_width = decimal_width(max_change) > number_width ? @@ -2823,17 +2862,12 @@ static void show_stats(struct diffstat_t *data, struct diff_options *options) char *slash; prefix = "..."; len -= 3; - /* - * NEEDSWORK: (name_len - len) counts the display - * width, which would be shorter than the byte - * length of the corresponding substring. - * Advancing "name" by that number of bytes does - * *NOT* skip over that many columns, so it is - * very likely that chomping the pathname at the - * slash we will find starting from "name" will - * leave the resulting string still too long. - */ - name += name_len - len; + if (len < 0) + len = 0; + + while (name_len > len) + name_len -= utf8_width((const char**)&name, NULL); + slash = strchr(name, '/'); if (slash) name = slash; @@ -3013,7 +3047,7 @@ static long gather_dirstat(struct diff_options *opt, struct dirstat_dir *dir, struct dirstat_file *f = dir->files; int namelen = strlen(f->name); unsigned long changes; - char *slash; + const char *slash; if (namelen < baselen) break; @@ -3231,6 +3265,7 @@ struct checkdiff_t { struct diff_options *o; unsigned ws_rule; unsigned status; + int last_line_kind; }; static int is_conflict_marker(const char *line, int marker_size, unsigned long len) @@ -3269,6 +3304,7 @@ static void checkdiff_consume_hunk(void *priv, static int checkdiff_consume(void *priv, char *line, unsigned long len) { struct checkdiff_t *data = priv; + int last_line_kind; int marker_size = data->conflict_marker_size; const char *ws = diff_get_color(data->o->use_color, DIFF_WHITESPACE); const char *reset = diff_get_color(data->o->use_color, DIFF_RESET); @@ -3279,6 +3315,8 @@ static int checkdiff_consume(void *priv, char *line, unsigned long len) assert(data->o); line_prefix = diff_line_prefix(data->o); + last_line_kind = data->last_line_kind; + data->last_line_kind = line[0]; if (line[0] == '+') { unsigned bad; data->lineno++; @@ -3301,6 +3339,17 @@ static int checkdiff_consume(void *priv, char *line, unsigned long len) data->o->file, set, reset, ws); } else if (line[0] == ' ') { data->lineno++; + } else if (line[0] == '\\') { + /* no newline at the end of the line */ + if ((data->ws_rule & WS_INCOMPLETE_LINE) && + (last_line_kind == '+')) { + unsigned bad = WS_INCOMPLETE_LINE; + data->status |= bad; + err = whitespace_error_string(bad); + fprintf(data->o->file, "%s%s:%d: %s.\n", + line_prefix, data->filename, data->lineno, err); + free(err); + } } return 0; } @@ -3530,7 +3579,6 @@ static int set_diff_algorithm(struct diff_options *opts, return -1; /* clear out previous settings */ - DIFF_XDL_CLR(opts, NEED_MINIMAL); opts->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK; opts->xdl_opts |= value; @@ -3714,6 +3762,7 @@ static void builtin_diff(const char *name_a, xpparam_t xpp; xdemitconf_t xecfg; struct emit_callback ecbdata; + unsigned ws_rule; const struct userdiff_funcname *pe; if (must_show_header) { @@ -3725,6 +3774,12 @@ static void builtin_diff(const char *name_a, mf1.size = fill_textconv(o->repo, textconv_one, one, &mf1.ptr); mf2.size = fill_textconv(o->repo, textconv_two, two, &mf2.ptr); + ws_rule = whitespace_rule(o->repo->index, name_b); + + /* symlink being an incomplete line is not a news */ + if (DIFF_FILE_VALID(two) && S_ISLNK(two->mode)) + ws_rule &= ~WS_INCOMPLETE_LINE; + pe = diff_funcname_pattern(o, one); if (!pe) pe = diff_funcname_pattern(o, two); @@ -3736,7 +3791,7 @@ static void builtin_diff(const char *name_a, lbl[0] = NULL; ecbdata.label_path = lbl; ecbdata.color_diff = o->use_color; - ecbdata.ws_rule = whitespace_rule(o->repo->index, name_b); + ecbdata.ws_rule = ws_rule; if (ecbdata.ws_rule & WS_BLANK_AT_EOF) check_blank_at_eof(&mf1, &mf2, &ecbdata); ecbdata.opt = o; @@ -3943,6 +3998,10 @@ static void builtin_checkdiff(const char *name_a, const char *name_b, data.ws_rule = whitespace_rule(o->repo->index, attr_path); data.conflict_marker_size = ll_merge_marker_size(o->repo->index, attr_path); + /* symlink being an incomplete line is not a news */ + if (DIFF_FILE_VALID(two) && S_ISLNK(two->mode)) + data.ws_rule &= ~WS_INCOMPLETE_LINE; + if (fill_mmfile(o->repo, &mf1, one) < 0 || fill_mmfile(o->repo, &mf2, two) < 0) die("unable to read files to diff"); @@ -4987,6 +5046,8 @@ void diff_setup_done(struct diff_options *options) if (options->flags.quick) { options->output_format = DIFF_FORMAT_NO_OUTPUT; options->flags.exit_with_status = 1; + options->detect_rename = 0; + options->flags.find_copies_harder = 0; } /* @@ -5163,6 +5224,8 @@ static int diff_opt_find_object(const struct option *option, struct object_id oid; BUG_ON_OPT_NEG(unset); + if (!startup_info->have_repository) + return error(_("--find-object requires a git repository")); if (repo_get_oid(the_repository, arg, &oid)) return error(_("unable to resolve '%s'"), arg); @@ -7046,6 +7109,7 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt) if (!diffopt->flags.no_index) diffopt->skip_stat_unmatch++; diff_free_filepair(p); + q->queue[i] = NULL; } } free(q->queue); @@ -7089,6 +7153,10 @@ void diff_queued_diff_prefetch(void *repository) for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; + + if (!p) + continue; + diff_add_if_missing(repo, &to_fetch, p->one); diff_add_if_missing(repo, &to_fetch, p->two); } @@ -7124,7 +7192,7 @@ void diffcore_std(struct diff_options *options) * If no prefetching occurs, diffcore_rename() will prefetch if it * decides that it needs inexact rename detection. */ - if (options->repo == the_repository && repo_has_promisor_remote(the_repository) && + if (repo_has_promisor_remote(options->repo) && (options->output_format & output_formats_to_prefetch || options->pickaxe_opts & DIFF_PICKAXE_KINDS_MASK)) diff_queued_diff_prefetch(options->repo); @@ -7347,6 +7415,26 @@ void diff_change(struct diff_options *options, concatpath, old_dirty_submodule, new_dirty_submodule); } +void diff_same(struct diff_options *options, + unsigned mode, + const struct object_id *oid, + const char *concatpath) +{ + struct diff_filespec *one; + + if (S_ISGITLINK(mode) && is_submodule_ignored(concatpath, options)) + return; + + if (options->prefix && + strncmp(concatpath, options->prefix, options->prefix_length)) + return; + + one = alloc_filespec(concatpath); + fill_filespec(one, oid, 1, mode); + one->count++; + diff_queue(&diff_queued_diff, one, one); +} + struct diff_filepair *diff_unmerge(struct diff_options *options, const char *path) { struct diff_filepair *pair;
diff --git a/diff.h b/diff.h index 31eedd5..7eb84aa 100644 --- a/diff.h +++ b/diff.h
@@ -331,9 +331,9 @@ struct diff_options { int ita_invisible_in_index; /* white-space error highlighting */ -#define WSEH_NEW (1<<12) -#define WSEH_CONTEXT (1<<13) -#define WSEH_OLD (1<<14) +#define WSEH_NEW (1<<16) +#define WSEH_CONTEXT (1<<17) +#define WSEH_OLD (1<<18) unsigned ws_error_highlight; const char *prefix; int prefix_length; @@ -572,6 +572,11 @@ void diff_change(struct diff_options *, const char *fullpath, unsigned dirty_submodule1, unsigned dirty_submodule2); +void diff_same(struct diff_options *, + unsigned mode, + const struct object_id *oid, + const char *fullpath); + struct diff_filepair *diff_unmerge(struct diff_options *, const char *path); void compute_diffstat(struct diff_options *options, struct diffstat_t *diffstat,
diff --git a/diffcore-break.c b/diffcore-break.c index c4c2173..17b5ad1 100644 --- a/diffcore-break.c +++ b/diffcore-break.c
@@ -69,7 +69,7 @@ static int should_break(struct repository *r, oideq(&src->oid, &dst->oid)) return 0; /* they are the same */ - if (r == the_repository && repo_has_promisor_remote(the_repository)) { + if (repo_has_promisor_remote(r)) { options.missing_object_cb = diff_queued_diff_prefetch; options.missing_object_data = r; } @@ -222,6 +222,7 @@ void diffcore_break(struct repository *r, int break_score) free(p); /* not diff_free_filepair(), we are * reusing one and two here. */ + q->queue[i] = NULL; continue; } }
diff --git a/diffcore-delta.c b/diffcore-delta.c index ba6cbee..2b7db39 100644 --- a/diffcore-delta.c +++ b/diffcore-delta.c
@@ -56,7 +56,7 @@ static struct spanhash_top *spanhash_rehash(struct spanhash_top *orig) st_mult(sizeof(struct spanhash), sz))); new_spanhash->alloc_log2 = orig->alloc_log2 + 1; new_spanhash->free = INITIAL_FREE(new_spanhash->alloc_log2); - memset(new_spanhash->data, 0, sizeof(struct spanhash) * sz); + MEMZERO_ARRAY(new_spanhash->data, sz); for (i = 0; i < osz; i++) { struct spanhash *o = &(orig->data[i]); int bucket; @@ -135,7 +135,7 @@ static struct spanhash_top *hash_chars(struct repository *r, st_mult(sizeof(struct spanhash), (size_t)1 << i))); hash->alloc_log2 = i; hash->free = INITIAL_FREE(i); - memset(hash->data, 0, sizeof(struct spanhash) * ((size_t)1 << i)); + MEMZERO_ARRAY(hash->data, (size_t)1 << i); n = 0; accum1 = accum2 = 0;
diff --git a/diffcore-rename.c b/diffcore-rename.c index 7723bc3..c797d8e 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c
@@ -379,7 +379,7 @@ struct dir_rename_info { static char *get_dirname(const char *filename) { - char *slash = strrchr(filename, '/'); + const char *slash = strrchr(filename, '/'); return slash ? xstrndup(filename, slash - filename) : xstrdup(""); } @@ -987,7 +987,7 @@ static int find_basename_matches(struct diff_options *options, strintmap_set(&dests, base, i); } - if (options->repo == the_repository && repo_has_promisor_remote(the_repository)) { + if (repo_has_promisor_remote(options->repo)) { dpf_options.missing_object_cb = basename_prefetch; dpf_options.missing_object_data = &prefetch_options; } @@ -1574,7 +1574,7 @@ void diffcore_rename_extended(struct diff_options *options, /* Finish setting up dpf_options */ prefetch_options.skip_unmodified = skip_unmodified; - if (options->repo == the_repository && repo_has_promisor_remote(the_repository)) { + if (repo_has_promisor_remote(options->repo)) { dpf_options.missing_object_cb = inexact_prefetch; dpf_options.missing_object_data = &prefetch_options; }
diff --git a/dir.c b/dir.c index b00821f..fcb8f6d 100644 --- a/dir.c +++ b/dir.c
@@ -1551,7 +1551,9 @@ enum pattern_match_result path_matches_pattern_list( int init_sparse_checkout_patterns(struct index_state *istate) { - if (!core_apply_sparse_checkout) + struct repo_config_values *cfg = repo_config_values(the_repository); + + if (!cfg->apply_sparse_checkout) return 1; if (istate->sparse_checkout_patterns) return 0; @@ -3516,15 +3518,15 @@ int get_sparse_checkout_patterns(struct pattern_list *pl) int remove_path(const char *name) { - char *slash; + const char *last; if (unlink(name) && !is_missing_file_error(errno)) return -1; - slash = strrchr(name, '/'); - if (slash) { + last = strrchr(name, '/'); + if (last) { char *dirs = xstrdup(name); - slash = dirs + (slash - name); + char *slash = dirs + (last - name); do { *slash = '\0'; if (startup_info->original_cwd &&
diff --git a/entry.c b/entry.c index cae02eb..7817aee 100644 --- a/entry.c +++ b/entry.c
@@ -2,13 +2,13 @@ #include "git-compat-util.h" #include "odb.h" +#include "odb/streaming.h" #include "dir.h" #include "environment.h" #include "gettext.h" #include "hex.h" #include "name-hash.h" #include "sparse-index.h" -#include "streaming.h" #include "submodule.h" #include "symlinks.h" #include "progress.h" @@ -139,7 +139,7 @@ static int streaming_write_entry(const struct cache_entry *ce, char *path, if (fd < 0) return -1; - result |= stream_blob_to_fd(fd, &ce->oid, filter, 1); + result |= odb_stream_blob_to_fd(the_repository->objects, fd, &ce->oid, filter, 1); *fstat_done = fstat_checkout_output(fd, state, statbuf); result |= close(fd);
diff --git a/environment.c b/environment.c index a770b59..fc3ed8b 100644 --- a/environment.c +++ b/environment.c
@@ -21,6 +21,7 @@ #include "gettext.h" #include "git-zlib.h" #include "ident.h" +#include "lockfile.h" #include "mailmap.h" #include "object-name.h" #include "repository.h" @@ -53,7 +54,6 @@ char *git_commit_encoding; char *git_log_output_encoding; char *apply_default_whitespace; char *apply_default_ignorewhitespace; -char *git_attributes_file; int zlib_compression_level = Z_BEST_SPEED; int pack_compression_level = Z_DEFAULT_COMPRESSION; int fsync_object_files = -1; @@ -67,7 +67,6 @@ enum auto_crlf auto_crlf = AUTO_CRLF_FALSE; enum eol core_eol = EOL_UNSET; int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN; char *check_roundtrip_encoding; -enum branch_track git_branch_track = BRANCH_TRACK_REMOTE; enum rebase_setup_type autorebase = AUTOREBASE_NEVER; enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED; #ifndef OBJECT_CREATION_MODE @@ -75,35 +74,10 @@ 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_apply_sparse_checkout; 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; -int max_allowed_tree_depth = -#ifdef _MSC_VER - /* - * When traversing into too-deep trees, Visual C-compiled Git seems to - * run into some internal stack overflow detection in the - * `RtlpAllocateHeap()` function that is called from within - * `git_inflate_init()`'s call tree. The following value seems to be - * low enough to avoid that by letting Git exit with an error before - * the stack overflow can occur. - */ - 512; -#elif defined(GIT_WINDOWS_NATIVE) && defined(__clang__) && defined(__aarch64__) - /* - * Similar to Visual C, it seems that on Windows/ARM64 the clang-based - * builds have a smaller stack space available. When running out of - * that stack space, a `STATUS_STACK_OVERFLOW` is produced. When the - * Git command was run from an MSYS2 Bash, this unfortunately results - * in an exit code 127. Let's prevent that by lowering the maximal - * tree depth; This value seems to be low enough. - */ - 1280; -#else - 2048; -#endif #ifndef PROTECT_HFS_DEFAULT #define PROTECT_HFS_DEFAULT 0 @@ -324,9 +298,11 @@ static enum fsync_component parse_fsync_components(const char *var, const char * return (current & ~negative) | positive; } -static int git_default_core_config(const char *var, const char *value, - const struct config_context *ctx, void *cb) +int git_default_core_config(const char *var, const char *value, + const struct config_context *ctx, void *cb) { + struct repo_config_values *cfg = repo_config_values(the_repository); + /* This needs a better name */ if (!strcmp(var, "core.filemode")) { trust_executable_bit = git_config_bool(var, value); @@ -364,8 +340,8 @@ static int git_default_core_config(const char *var, const char *value, } if (!strcmp(var, "core.attributesfile")) { - FREE_AND_NULL(git_attributes_file); - return git_config_pathname(&git_attributes_file, var, value); + FREE_AND_NULL(cfg->attributes_file); + return git_config_pathname(&cfg->attributes_file, var, value); } if (!strcmp(var, "core.bare")) { @@ -532,6 +508,11 @@ static int git_default_core_config(const char *var, const char *value, return 0; } + if (!strcmp(var, "core.lockfilepid")) { + lockfile_pid_enabled = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "core.createobject")) { if (!value) return config_error_nonbool(var); @@ -545,7 +526,7 @@ static int git_default_core_config(const char *var, const char *value, } if (!strcmp(var, "core.sparsecheckout")) { - core_apply_sparse_checkout = git_config_bool(var, value); + cfg->apply_sparse_checkout = git_config_bool(var, value); return 0; } @@ -569,11 +550,6 @@ static int git_default_core_config(const char *var, const char *value, return 0; } - if (!strcmp(var, "core.maxtreedepth")) { - max_allowed_tree_depth = git_config_int(var, value, ctx->kvi); - return 0; - } - /* Add other config variables here and to Documentation/config.adoc. */ return platform_core_config(var, value, ctx, cb); } @@ -607,18 +583,20 @@ static int git_default_i18n_config(const char *var, const char *value) static int git_default_branch_config(const char *var, const char *value) { + struct repo_config_values *cfg = repo_config_values(the_repository); + if (!strcmp(var, "branch.autosetupmerge")) { if (value && !strcmp(value, "always")) { - git_branch_track = BRANCH_TRACK_ALWAYS; + cfg->branch_track = BRANCH_TRACK_ALWAYS; return 0; } else if (value && !strcmp(value, "inherit")) { - git_branch_track = BRANCH_TRACK_INHERIT; + cfg->branch_track = BRANCH_TRACK_INHERIT; return 0; } else if (value && !strcmp(value, "simple")) { - git_branch_track = BRANCH_TRACK_SIMPLE; + cfg->branch_track = BRANCH_TRACK_SIMPLE; return 0; } - git_branch_track = git_config_bool(var, value); + cfg->branch_track = git_config_bool(var, value); return 0; } if (!strcmp(var, "branch.autosetuprebase")) { @@ -670,22 +648,6 @@ static int git_default_push_config(const char *var, const char *value) return 0; } -static int git_default_mailmap_config(const char *var, const char *value) -{ - if (!strcmp(var, "mailmap.file")) { - FREE_AND_NULL(git_mailmap_file); - return git_config_pathname(&git_mailmap_file, var, value); - } - - if (!strcmp(var, "mailmap.blob")) { - FREE_AND_NULL(git_mailmap_blob); - return git_config_string(&git_mailmap_blob, var, value); - } - - /* Add other config variables here and to Documentation/config.adoc. */ - return 0; -} - static int git_default_attr_config(const char *var, const char *value) { if (!strcmp(var, "attr.tree")) { @@ -720,9 +682,6 @@ int git_default_config(const char *var, const char *value, if (starts_with(var, "push.")) return git_default_push_config(var, value); - if (starts_with(var, "mailmap.")) - return git_default_mailmap_config(var, value); - if (starts_with(var, "attr.")) return git_default_attr_config(var, value); @@ -756,3 +715,10 @@ int git_default_config(const char *var, const char *value, /* Add other config variables here and to Documentation/config.adoc. */ return 0; } + +void repo_config_values_init(struct repo_config_values *cfg) +{ + cfg->attributes_file = NULL; + cfg->apply_sparse_checkout = 0; + cfg->branch_track = BRANCH_TRACK_REMOTE; +}
diff --git a/environment.h b/environment.h index 51898c9..123a71c 100644 --- a/environment.h +++ b/environment.h
@@ -2,6 +2,7 @@ #define ENVIRONMENT_H #include "repo-settings.h" +#include "branch.h" /* Double-check local_repo_env below if you add to this list. */ #define GIT_DIR_ENVIRONMENT "GIT_DIR" @@ -42,6 +43,7 @@ #define GIT_OPTIONAL_LOCKS_ENVIRONMENT "GIT_OPTIONAL_LOCKS" #define GIT_TEXT_DOMAIN_DIR_ENVIRONMENT "GIT_TEXTDOMAINDIR" #define GIT_ATTR_SOURCE_ENVIRONMENT "GIT_ATTR_SOURCE" +#define GIT_REFERENCE_BACKEND_ENVIRONMENT "GIT_REFERENCE_BACKEND" /* * Environment variable used to propagate the --no-advice global option to the @@ -84,6 +86,18 @@ extern const char * const local_repo_env[]; struct strvec; +struct repository; +struct repo_config_values { + /* section "core" config values */ + char *attributes_file; + int apply_sparse_checkout; + + /* section "branch" config values */ + enum branch_track branch_track; +}; + +struct repo_config_values *repo_config_values(struct repository *repo); + /* * Wrapper of getenv() that returns a strdup value. This value is kept * in argv to be freed later. @@ -106,6 +120,10 @@ const char *strip_namespace(const char *namespaced_ref); int git_default_config(const char *, const char *, const struct config_context *, void *); +int git_default_core_config(const char *var, const char *value, + const struct config_context *ctx, void *cb); + +void repo_config_values_init(struct repo_config_values *cfg); /* * TODO: All the below state either explicitly or implicitly relies on @@ -152,17 +170,14 @@ extern int assume_unchanged; extern int warn_on_object_refname_ambiguity; extern char *apply_default_whitespace; extern char *apply_default_ignorewhitespace; -extern char *git_attributes_file; extern int zlib_compression_level; extern int pack_compression_level; extern unsigned long pack_size_limit_cfg; -extern int max_allowed_tree_depth; extern int precomposed_unicode; extern int protect_hfs; extern int protect_ntfs; -extern int core_apply_sparse_checkout; extern int core_sparse_checkout_cone; extern int sparse_expect_files_outside_of_patterns;
diff --git a/ewah/bitmap.c b/ewah/bitmap.c index 55928da..c378e0a 100644 --- a/ewah/bitmap.c +++ b/ewah/bitmap.c
@@ -46,8 +46,7 @@ static void bitmap_grow(struct bitmap *self, size_t word_alloc) { size_t old_size = self->word_alloc; ALLOC_GROW(self->words, word_alloc, self->word_alloc); - memset(self->words + old_size, 0x0, - (self->word_alloc - old_size) * sizeof(eword_t)); + MEMZERO_ARRAY(self->words + old_size, self->word_alloc - old_size); } void bitmap_set(struct bitmap *self, size_t pos) @@ -192,8 +191,8 @@ void bitmap_or_ewah(struct bitmap *self, struct ewah_bitmap *other) if (self->word_alloc < other_final) { self->word_alloc = other_final; REALLOC_ARRAY(self->words, self->word_alloc); - memset(self->words + original_size, 0x0, - (self->word_alloc - original_size) * sizeof(eword_t)); + MEMZERO_ARRAY(self->words + original_size, + self->word_alloc - original_size); } ewah_iterator_init(&it, other);
diff --git a/fetch-pack.c b/fetch-pack.c index fe7a84b..a32224e 100644 --- a/fetch-pack.c +++ b/fetch-pack.c
@@ -35,6 +35,7 @@ #include "sigchain.h" #include "mergesort.h" #include "prio-queue.h" +#include "promisor-remote.h" static int transfer_unpack_limit = -1; static int fetch_unpack_limit = -1; @@ -188,13 +189,9 @@ static int rev_list_insert_ref(struct fetch_negotiator *negotiator, return 0; } -static int rev_list_insert_ref_oid(const char *refname UNUSED, - const char *referent UNUSED, - const struct object_id *oid, - int flag UNUSED, - void *cb_data) +static int rev_list_insert_ref_oid(const struct reference *ref, void *cb_data) { - return rev_list_insert_ref(cb_data, oid); + return rev_list_insert_ref(cb_data, ref->oid); } enum ack_type { @@ -296,11 +293,14 @@ static int next_flush(int stateless_rpc, int count) static void mark_tips(struct fetch_negotiator *negotiator, const struct oid_array *negotiation_tips) { + struct refs_for_each_ref_options opts = { + .flags = REFS_FOR_EACH_INCLUDE_BROKEN, + }; int i; if (!negotiation_tips) { - refs_for_each_rawref(get_main_ref_store(the_repository), - rev_list_insert_ref_oid, negotiator); + refs_for_each_ref_ext(get_main_ref_store(the_repository), + rev_list_insert_ref_oid, negotiator, &opts); return; } @@ -616,13 +616,9 @@ static int mark_complete(const struct object_id *oid) return 0; } -static int mark_complete_oid(const char *refname UNUSED, - const char *referent UNUSED, - const struct object_id *oid, - int flag UNUSED, - void *cb_data UNUSED) +static int mark_complete_oid(const struct reference *ref, void *cb_data UNUSED) { - return mark_complete(oid); + return mark_complete(ref->oid); } static void mark_recent_complete_commits(struct fetch_pack_args *args, @@ -800,8 +796,12 @@ static void mark_complete_and_common_ref(struct fetch_negotiator *negotiator, */ trace2_region_enter("fetch-pack", "mark_complete_local_refs", NULL); if (!args->deepen) { - refs_for_each_rawref(get_main_ref_store(the_repository), - mark_complete_oid, NULL); + struct refs_for_each_ref_options opts = { + .flags = REFS_FOR_EACH_INCLUDE_BROKEN, + }; + + refs_for_each_ref_ext(get_main_ref_store(the_repository), + mark_complete_oid, NULL, &opts); for_each_cached_alternate(NULL, mark_alternate_complete); if (cutoff) mark_recent_complete_commits(args, cutoff); @@ -1024,12 +1024,8 @@ static int get_pack(struct fetch_pack_args *args, fsck_msg_types.buf); } - if (index_pack_args) { - int i; - - for (i = 0; i < cmd.args.nr; i++) - strvec_push(index_pack_args, cmd.args.v[i]); - } + if (index_pack_args) + strvec_pushv(index_pack_args, cmd.args.v); sigchain_push(SIGPIPE, SIG_IGN); @@ -1669,6 +1665,29 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args, struct string_list packfile_uris = STRING_LIST_INIT_DUP; int i; struct strvec index_pack_args = STRVEC_INIT; + const char *promisor_remote_config; + + if (server_feature_v2("promisor-remote", &promisor_remote_config)) + promisor_remote_reply(promisor_remote_config, NULL); + + if (args->filter_options.choice == LOFC_AUTO) { + struct strbuf errbuf = STRBUF_INIT; + char *constructed_filter = promisor_remote_construct_filter(r); + + list_objects_filter_release(&args->filter_options); + /* Disallow 'auto' as a result of the resolution of this 'auto' filter below */ + args->filter_options.allow_auto_filter = 0; + + if (constructed_filter && + gently_parse_list_objects_filter(&args->filter_options, + constructed_filter, + &errbuf)) + die(_("couldn't resolve 'auto' filter '%s': %s"), + constructed_filter, errbuf.buf); + + free(constructed_filter); + strbuf_release(&errbuf); + } negotiator = &negotiator_alloc; if (args->refetch) @@ -1873,8 +1892,9 @@ int fetch_pack_fsck_config(const char *var, const char *value, if (git_config_pathname(&path, var, value)) return -1; - strbuf_addf(msg_types, "%cskiplist=%s", - msg_types->len ? ',' : '=', path); + if (path) + strbuf_addf(msg_types, "%cskiplist=%s", + msg_types->len ? ',' : '=', path); free(path); return 0; }
diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c index c9085ed..45d8b20 100644 --- a/fmt-merge-msg.c +++ b/fmt-merge-msg.c
@@ -246,7 +246,8 @@ static void add_branch_desc(struct strbuf *out, const char *name) static void record_person_from_buf(int which, struct string_list *people, const char *buffer) { - char *name_buf, *name, *name_end; + char *name_buf; + const char *name, *name_end; struct string_list_item *elem; const char *field; @@ -421,7 +422,7 @@ static void shortlog(const char *name, clear_commit_marks((struct commit *)branch, flags); clear_commit_marks(head, flags); - free_commit_list(rev->commits); + commit_list_free(rev->commits); rev->commits = NULL; rev->pending.nr = 0;
diff --git a/fsck.c b/fsck.c index 341e100..0f02cf8 100644 --- a/fsck.c +++ b/fsck.c
@@ -360,7 +360,7 @@ static int fsck_walk_tree(struct tree *tree, void *data, struct fsck_options *op int res = 0; const char *name; - if (parse_tree(tree)) + if (repo_parse_tree(the_repository, tree)) return -1; name = fsck_get_object_name(options, &tree->object.oid); @@ -474,7 +474,7 @@ static int fsck_walk_tag(struct tag *tag, void *data, struct fsck_options *optio { const char *name = fsck_get_object_name(options, &tag->object.oid); - if (parse_tag(tag)) + if (parse_tag(the_repository, tag)) return -1; if (name) fsck_put_object_name(options, &tag->tagged->oid, "%s", name); @@ -860,31 +860,60 @@ static int verify_headers(const void *data, unsigned long size, FSCK_MSG_UNTERMINATED_HEADER, "unterminated header"); } -static int fsck_ident(const char **ident, +static timestamp_t parse_timestamp_from_buf(const char **start, const char *end) +{ + const char *p = *start; + char buf[24]; /* big enough for 2^64 */ + size_t i = 0; + + while (p < end && isdigit(*p)) { + if (i >= ARRAY_SIZE(buf) - 1) + return TIME_MAX; + buf[i++] = *p++; + } + buf[i] = '\0'; + *start = p; + return parse_timestamp(buf, NULL, 10); +} + +static int fsck_ident(const char **ident, const char *ident_end, const struct object_id *oid, enum object_type type, struct fsck_options *options) { const char *p = *ident; - char *end; + const char *nl; - *ident = strchrnul(*ident, '\n'); - if (**ident == '\n') - (*ident)++; + nl = memchr(p, '\n', ident_end - p); + if (!nl) + BUG("verify_headers() should have made sure we have a newline"); + *ident = nl + 1; if (*p == '<') return report(options, oid, type, FSCK_MSG_MISSING_NAME_BEFORE_EMAIL, "invalid author/committer line - missing space before email"); - p += strcspn(p, "<>\n"); - if (*p == '>') - return report(options, oid, type, FSCK_MSG_BAD_NAME, "invalid author/committer line - bad name"); - if (*p != '<') - return report(options, oid, type, FSCK_MSG_MISSING_EMAIL, "invalid author/committer line - missing email"); + for (;;) { + if (p >= ident_end || *p == '\n') + return report(options, oid, type, FSCK_MSG_MISSING_EMAIL, "invalid author/committer line - missing email"); + if (*p == '>') + return report(options, oid, type, FSCK_MSG_BAD_NAME, "invalid author/committer line - bad name"); + if (*p == '<') + break; /* end of name, beginning of email */ + + /* otherwise, skip past arbitrary name char */ + p++; + } if (p[-1] != ' ') return report(options, oid, type, FSCK_MSG_MISSING_SPACE_BEFORE_EMAIL, "invalid author/committer line - missing space before email"); - p++; - p += strcspn(p, "<>\n"); - if (*p != '>') - return report(options, oid, type, FSCK_MSG_BAD_EMAIL, "invalid author/committer line - bad email"); - p++; + p++; /* skip past '<' we found */ + for (;;) { + if (p >= ident_end || *p == '<' || *p == '\n') + return report(options, oid, type, FSCK_MSG_BAD_EMAIL, "invalid author/committer line - bad email"); + if (*p == '>') + break; /* end of email */ + + /* otherwise, skip past arbitrary email char */ + p++; + } + p++; /* skip past '>' we found */ if (*p != ' ') return report(options, oid, type, FSCK_MSG_MISSING_SPACE_BEFORE_DATE, "invalid author/committer line - missing space before date"); p++; @@ -904,11 +933,11 @@ static int fsck_ident(const char **ident, "invalid author/committer line - bad date"); if (*p == '0' && p[1] != ' ') return report(options, oid, type, FSCK_MSG_ZERO_PADDED_DATE, "invalid author/committer line - zero-padded date"); - if (date_overflows(parse_timestamp(p, &end, 10))) + if (date_overflows(parse_timestamp_from_buf(&p, ident_end))) return report(options, oid, type, FSCK_MSG_BAD_DATE_OVERFLOW, "invalid author/committer line - date causes integer overflow"); - if ((end == p || *end != ' ')) + if (*p != ' ') return report(options, oid, type, FSCK_MSG_BAD_DATE, "invalid author/committer line - bad date"); - p = end + 1; + p++; if ((*p != '+' && *p != '-') || !isdigit(p[1]) || !isdigit(p[2]) || @@ -958,7 +987,7 @@ static int fsck_commit(const struct object_id *oid, author_count = 0; while (buffer < buffer_end && skip_prefix(buffer, "author ", &buffer)) { author_count++; - err = fsck_ident(&buffer, oid, OBJ_COMMIT, options); + err = fsck_ident(&buffer, buffer_end, oid, OBJ_COMMIT, options); if (err) return err; } @@ -970,7 +999,7 @@ static int fsck_commit(const struct object_id *oid, return err; if (buffer >= buffer_end || !skip_prefix(buffer, "committer ", &buffer)) return report(options, oid, OBJ_COMMIT, FSCK_MSG_MISSING_COMMITTER, "invalid format - expected 'committer' line"); - err = fsck_ident(&buffer, oid, OBJ_COMMIT, options); + err = fsck_ident(&buffer, buffer_end, oid, OBJ_COMMIT, options); if (err) return err; if (memchr(buffer_begin, '\0', size)) { @@ -997,7 +1026,7 @@ int fsck_tag_standalone(const struct object_id *oid, const char *buffer, int *tagged_type) { int ret = 0; - char *eol; + const char *eol; struct strbuf sb = STRBUF_INIT; const char *buffer_end = buffer + size; const char *p; @@ -1065,7 +1094,7 @@ int fsck_tag_standalone(const struct object_id *oid, const char *buffer, goto done; } else - ret = fsck_ident(&buffer, oid, OBJ_TAG, options); + ret = fsck_ident(&buffer, buffer_end, oid, OBJ_TAG, options); if (buffer < buffer_end && (skip_prefix(buffer, "gpgsig ", &buffer) || skip_prefix(buffer, "gpgsig-sha256 ", &buffer))) { eol = memchr(buffer, '\n', buffer_end - buffer); @@ -1281,11 +1310,6 @@ int fsck_refs_error_function(struct fsck_options *options UNUSED, strbuf_addstr(&sb, report->path); - if (report->oid) - strbuf_addf(&sb, " -> (%s)", oid_to_hex(report->oid)); - else if (report->referent) - strbuf_addf(&sb, " -> (%s)", report->referent); - if (msg_type == FSCK_WARN) warning("%s: %s", sb.buf, message); else @@ -1350,6 +1374,12 @@ int fsck_finish(struct fsck_options *options) return ret; } +bool fsck_has_queued_checks(struct fsck_options *options) +{ + return !oidset_equal(&options->gitmodules_found, &options->gitmodules_done) || + !oidset_equal(&options->gitattributes_found, &options->gitattributes_done); +} + void fsck_options_clear(struct fsck_options *options) { free(options->msg_type); @@ -1369,14 +1399,16 @@ int git_fsck_config(const char *var, const char *value, if (strcmp(var, "fsck.skiplist") == 0) { char *path; - struct strbuf sb = STRBUF_INIT; if (git_config_pathname(&path, var, value)) return -1; - strbuf_addf(&sb, "skiplist=%s", path); - free(path); - fsck_set_msg_types(options, sb.buf); - strbuf_release(&sb); + if (path) { + struct strbuf sb = STRBUF_INIT; + strbuf_addf(&sb, "skiplist=%s", path); + free(path); + fsck_set_msg_types(options, sb.buf); + strbuf_release(&sb); + } return 0; }
diff --git a/fsck.h b/fsck.h index cb6ef32..65ecbb7 100644 --- a/fsck.h +++ b/fsck.h
@@ -30,6 +30,7 @@ enum fsck_msg_type { FUNC(BAD_DATE_OVERFLOW, ERROR) \ FUNC(BAD_EMAIL, ERROR) \ FUNC(BAD_GPGSIG, ERROR) \ + FUNC(BAD_HEAD_TARGET, ERROR) \ FUNC(BAD_NAME, ERROR) \ FUNC(BAD_OBJECT_SHA1, ERROR) \ FUNC(BAD_PACKED_REF_ENTRY, ERROR) \ @@ -39,6 +40,7 @@ enum fsck_msg_type { FUNC(BAD_REF_CONTENT, ERROR) \ FUNC(BAD_REF_FILETYPE, ERROR) \ FUNC(BAD_REF_NAME, ERROR) \ + FUNC(BAD_REF_OID, ERROR) \ FUNC(BAD_TIMEZONE, ERROR) \ FUNC(BAD_TREE, ERROR) \ FUNC(BAD_TREE_SHA1, ERROR) \ @@ -162,8 +164,6 @@ struct fsck_object_report { struct fsck_ref_report { const char *path; - const struct object_id *oid; - const char *referent; }; struct fsck_options { @@ -249,6 +249,13 @@ int fsck_tag_standalone(const struct object_id *oid, const char *buffer, int fsck_finish(struct fsck_options *options); /* + * Check whether there are any checks that have been queued up and that still + * need to be run. Returns `false` iff `fsck_finish()` wouldn't perform any + * actions, `true` otherwise. + */ +bool fsck_has_queued_checks(struct fsck_options *options); + +/* * Clear the fsck_options struct, freeing any allocated memory. */ void fsck_options_clear(struct fsck_options *options);
diff --git a/generate-configlist.sh b/generate-configlist.sh deleted file mode 100755 index 75c39ad..0000000 --- a/generate-configlist.sh +++ /dev/null
@@ -1,38 +0,0 @@ -#!/bin/sh - -SOURCE_DIR="$1" -OUTPUT="$2" - -if test -z "$SOURCE_DIR" || ! test -d "$SOURCE_DIR" || test -z "$OUTPUT" -then - echo >&2 "USAGE: $0 <SOURCE_DIR> <OUTPUT>" - exit 1 -fi - -print_config_list () { - cat <<EOF -static const char *config_name_list[] = { -EOF - sed -e ' - /^`*[a-zA-Z].*\..*`*::$/ { - /deprecated/d; - s/::$//; - s/`//g; - s/^.*$/ "&",/; - p;}; - d' \ - "$SOURCE_DIR"/Documentation/*config.adoc \ - "$SOURCE_DIR"/Documentation/config/*.adoc | - sort - cat <<EOF - NULL, -}; -EOF -} - -{ - echo "/* Automatically generated by generate-configlist.sh */" - echo - echo - print_config_list -} >"$OUTPUT"
diff --git a/git-compat-util.h b/git-compat-util.h index 398e0fa..4b4ea24 100644 --- a/git-compat-util.h +++ b/git-compat-util.h
@@ -34,41 +34,8 @@ struct strbuf; # define DISABLE_WARNING(warning) #endif -#ifdef DISABLE_SIGN_COMPARE_WARNINGS -DISABLE_WARNING(-Wsign-compare) -#endif - -#ifndef FLEX_ARRAY -/* - * See if our compiler is known to support flexible array members. - */ - -/* - * Check vendor specific quirks first, before checking the - * __STDC_VERSION__, as vendor compilers can lie and we need to be - * able to work them around. Note that by not defining FLEX_ARRAY - * here, we can fall back to use the "safer but a bit wasteful" one - * later. - */ -#if defined(__SUNPRO_C) && (__SUNPRO_C <= 0x580) -#elif defined(__GNUC__) -# if (__GNUC__ >= 3) -# define FLEX_ARRAY /* empty */ -# else -# define FLEX_ARRAY 0 /* older GNU extension */ -# endif -#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) -# define FLEX_ARRAY /* empty */ -#endif - -/* - * Otherwise, default to safer but a bit wasteful traditional style - */ -#ifndef FLEX_ARRAY -# define FLEX_ARRAY 1 -#endif -#endif - +#undef FLEX_ARRAY +#define FLEX_ARRAY /* empty - weather balloon to require C99 FAM */ /* * BUILD_ASSERT_OR_ZERO - assert a build-time dependency, as an expression. @@ -607,6 +574,30 @@ static inline bool strip_suffix(const char *str, const char *suffix, #define DEFAULT_PACKED_GIT_LIMIT \ ((1024L * 1024L) * (size_t)(sizeof(void*) >= 8 ? (32 * 1024L * 1024L) : 256)) +#ifdef _MSC_VER + /* + * When traversing into too-deep trees, Visual C-compiled Git seems to + * run into some internal stack overflow detection in the + * `RtlpAllocateHeap()` function that is called from within + * `git_inflate_init()`'s call tree. The following value seems to be + * low enough to avoid that by letting Git exit with an error before + * the stack overflow can occur. + */ +#define DEFAULT_MAX_ALLOWED_TREE_DEPTH 512 +#elif defined(GIT_WINDOWS_NATIVE) && defined(__clang__) && defined(__aarch64__) + /* + * Similar to Visual C, it seems that on Windows/ARM64 the clang-based + * builds have a smaller stack space available. When running out of + * that stack space, a `STATUS_STACK_OVERFLOW` is produced. When the + * Git command was run from an MSYS2 Bash, this unfortunately results + * in an exit code 127. Let's prevent that by lowering the maximal + * tree depth; This value seems to be low enough. + */ +#define DEFAULT_MAX_ALLOWED_TREE_DEPTH 1280 +#else +#define DEFAULT_MAX_ALLOWED_TREE_DEPTH 2048 +#endif + int git_open_cloexec(const char *name, int flags); #define git_open(name) git_open_cloexec(name, O_RDONLY) @@ -726,6 +717,7 @@ static inline uint64_t u64_add(uint64_t a, uint64_t b) #define ALLOC_ARRAY(x, alloc) (x) = xmalloc(st_mult(sizeof(*(x)), (alloc))) #define CALLOC_ARRAY(x, alloc) (x) = xcalloc((alloc), sizeof(*(x))) #define REALLOC_ARRAY(x, alloc) (x) = xrealloc((x), st_mult(sizeof(*(x)), (alloc))) +#define MEMZERO_ARRAY(x, alloc) memset((x), 0x0, st_mult(sizeof(*(x)), (alloc))) #define COPY_ARRAY(dst, src, n) copy_array((dst), (src), (n), sizeof(*(dst)) + \ BARF_UNLESS_COPYABLE((dst), (src))) @@ -1103,3 +1095,7 @@ extern int not_supposed_to_survive; #endif /* CHECK_ASSERTION_SIDE_EFFECTS */ #endif + +#ifdef DISABLE_SIGN_COMPARE_WARNINGS +DISABLE_WARNING(-Wsign-compare) +#endif
diff --git a/git-curl-compat.h b/git-curl-compat.h index 659e5a3..dccdd4d 100644 --- a/git-curl-compat.h +++ b/git-curl-compat.h
@@ -38,6 +38,14 @@ #endif /** + * CURLINFO_RETRY_AFTER was added in 7.66.0, released in September 2019. + * It allows curl to automatically parse Retry-After headers. + */ +#if LIBCURL_VERSION_NUM >= 0x074200 +#define GIT_CURL_HAVE_CURLINFO_RETRY_AFTER 1 +#endif + +/** * CURLOPT_PROTOCOLS_STR and CURLOPT_REDIR_PROTOCOLS_STR were added in 7.85.0, * released in August 2022. */
diff --git a/git-gui/.gitattributes b/git-gui/.gitattributes index 889d582..f03c0fe 100644 --- a/git-gui/.gitattributes +++ b/git-gui/.gitattributes
@@ -1,7 +1,7 @@ * whitespace=indent-with-non-tab,trailing-space,space-before-tab,tabwidth=4 * encoding=US-ASCII git-gui.sh encoding=UTF-8 -/po/*.po encoding=UTF-8 +*.po encoding=UTF-8 /GIT-VERSION-GEN eol=lf Makefile whitespace=!indent,trail,space meson.build whitespace=space
diff --git a/git-gui/.gitignore b/git-gui/.gitignore index 5130b4f..38a41eb 100644 --- a/git-gui/.gitignore +++ b/git-gui/.gitignore
@@ -5,4 +5,5 @@ GIT-VERSION-FILE git-gui git-gui--askpass +git-gui--askyesno lib/tclIndex
diff --git a/git-gui/GIT-VERSION-GEN b/git-gui/GIT-VERSION-GEN index c2767b4..2f729de 100755 --- a/git-gui/GIT-VERSION-GEN +++ b/git-gui/GIT-VERSION-GEN
@@ -5,19 +5,27 @@ LF=' ' -if test "$#" -ne 2 +if test "$#" -lt 2 then - echo >&2 "usage: $0 <SOURCE_DIR> <OUTPUT>" + echo >&2 "usage: $0 <SOURCE_DIR> <OUTPUT> [<PARENT_PROJECT_DIR>]" exit 1 fi SOURCE_DIR="$1" OUTPUT="$2" +PARENT_PROJECT_DIR="$3" # Protect us from reading Git version information outside of the Git directory # in case it is not a repository itself, but embedded in an unrelated -# repository. -GIT_CEILING_DIRECTORIES="$SOURCE_DIR/.." +# repository. The PARENT_PROJECT_DIR variable can be used to override this, for +# example when git-gui is included as a subproject. +if test -n "$PARENT_PROJECT_DIR" +then + GIT_CEILING_DIRECTORIES="$PARENT_PROJECT_DIR/.." +else + GIT_CEILING_DIRECTORIES="$SOURCE_DIR/.." +fi + export GIT_CEILING_DIRECTORIES tree_search ()
diff --git a/git-gui/Makefile b/git-gui/Makefile index 69b0b84..ca01068 100644 --- a/git-gui/Makefile +++ b/git-gui/Makefile
@@ -9,7 +9,7 @@ # GIT-VERSION-FILE: FORCE - @$(SHELL_PATH) ./GIT-VERSION-GEN . $@ + @$(SHELL_PATH) ./GIT-VERSION-GEN . $@ "$(PARENT_PROJECT_DIR)" uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not') @@ -177,10 +177,13 @@ git-gui--askpass: git-gui--askpass.sh GIT-GUI-BUILD-OPTIONS generate-script.sh $(QUIET_GEN)$(SHELL_PATH) generate-script.sh $@ $< ./GIT-GUI-BUILD-OPTIONS +git-gui--askyesno: git-gui--askyesno.sh GIT-GUI-BUILD-OPTIONS generate-script.sh + $(QUIET_GEN)$(SHELL_PATH) generate-script.sh $@ $< ./GIT-GUI-BUILD-OPTIONS + ifdef GITGUI_WINDOWS_WRAPPER all:: git-gui endif -all:: $(GITGUI_MAIN) git-gui--askpass lib/tclIndex $(ALL_MSGFILES) +all:: $(GITGUI_MAIN) git-gui--askpass git-gui--askyesno lib/tclIndex $(ALL_MSGFILES) install: all $(QUIET)$(INSTALL_D0)'$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL_D1) @@ -221,7 +224,7 @@ @sed 's|^GITGUI_VERSION=||' <GIT-VERSION-FILE >$(TARDIR)/version clean:: - $(RM_RF) $(GITGUI_MAIN) git-gui--askpass lib/tclIndex po/*.msg $(PO_TEMPLATE) + $(RM_RF) $(GITGUI_MAIN) git-gui--askpass git-gui--askyesno lib/tclIndex po/*.msg $(PO_TEMPLATE) $(RM_RF) GIT-VERSION-FILE GIT-GUI-BUILD-OPTIONS ifdef GITGUI_WINDOWS_WRAPPER $(RM_RF) git-gui
diff --git a/git-gui/git-gui--askyesno b/git-gui/git-gui--askyesno.sh similarity index 100% rename from git-gui/git-gui--askyesno rename to git-gui/git-gui--askyesno.sh
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index d3d3aa1..23fe76e 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh
@@ -3900,6 +3900,24 @@ backup_commit_buffer + # Grey out comment lines (which are stripped from the final commit message by + # wash_commit_message). + $ui_comm tag configure commit_comment -foreground gray + proc dim_commit_comment_lines {} { + global ui_comm comment_string + $ui_comm tag remove commit_comment 1.0 end + set text [$ui_comm get 1.0 end] + # See also cmt_rx in wash_commit_message + set cmt_rx [strcat {^} [regsub -all {\W} $comment_string {\\&}]] + set ranges [regexp -all -indices -inline -line -- $cmt_rx $text] + foreach pair $ranges { + set idx "1.0 + [lindex $pair 0] chars" + $ui_comm tag add commit_comment $idx "$idx lineend + 1 char" + } + } + dim_commit_comment_lines + bind $ui_comm <<Modified>> { after idle dim_commit_comment_lines } + # -- If the user has aspell available we can drive it # in pipe mode to spellcheck the commit message. #
diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl index 442737b..8be1a61 100644 --- a/git-gui/lib/diff.tcl +++ b/git-gui/lib/diff.tcl
@@ -385,6 +385,8 @@ # if {[string match {@@@ *} $line]} { set is_3way_diff 1 + apply_tab_size 2 + } elseif {[string match {@@ *} $line]} { apply_tab_size 1 }
diff --git a/git-gui/meson.build b/git-gui/meson.build index 320ba09..a8119aa 100644 --- a/git-gui/meson.build +++ b/git-gui/meson.build
@@ -4,7 +4,7 @@ fs = import('fs') -shell = find_program('sh') +shell = find_program('/bin/sh', 'sh') tclsh = find_program('tclsh') wish = find_program('wish') @@ -34,6 +34,7 @@ '@INPUT@', meson.current_source_dir(), '@OUTPUT@', + get_option('parent_project_dir'), ], build_always_stale: true, ) @@ -53,19 +54,21 @@ ) endif -custom_target( - output: 'git-gui--askpass', - input: 'git-gui--askpass.sh', - command: [ - shell, - meson.current_source_dir() / 'generate-script.sh', - '@OUTPUT@', - '@INPUT@', - meson.current_build_dir() / 'GIT-GUI-BUILD-OPTIONS', - ], - install: true, - install_dir: get_option('libexecdir') / 'git-core', -) +foreach script : [ 'git-gui--askpass', 'git-gui--askyesno' ] + custom_target( + output: script, + input: script + '.sh', + command: [ + shell, + meson.current_source_dir() / 'generate-script.sh', + '@OUTPUT@', + '@INPUT@', + meson.current_build_dir() / 'GIT-GUI-BUILD-OPTIONS', + ], + install: true, + install_dir: get_option('libexecdir') / 'git-core', + ) +endforeach custom_target( input: 'git-gui.sh',
diff --git a/git-gui/meson_options.txt b/git-gui/meson_options.txt new file mode 100644 index 0000000..7591a34 --- /dev/null +++ b/git-gui/meson_options.txt
@@ -0,0 +1,2 @@ +option('parent_project_dir', type: 'string', value: '', + description: 'The directory of the parent project. This is used so that the version can be determined even in case git-gui is included as a subtree.')
diff --git a/git-gui/po/bg.po b/git-gui/po/bg.po index 21f5bd5..ff5adb2 100644 --- a/git-gui/po/bg.po +++ b/git-gui/po/bg.po
@@ -1,7 +1,7 @@ # Bulgarian translation of git-gui po-file. -# Copyright (C) 2012, 2013, 2014, 2015, 2016, 2024, 2025 Alexander Shopov <ash@kambanaria.org>. +# Copyright (C) 2012, 2013, 2014, 2015, 2016, 2024, 2025, 2026 Alexander Shopov <ash@kambanaria.org>. # This file is distributed under the same license as the git package. -# Alexander Shopov <ash@kambanaria.org>, 2012, 2013, 2014, 2015, 2016, 2024, 2025. +# Alexander Shopov <ash@kambanaria.org>, 2012, 2013, 2014, 2015, 2016, 2024, 2025, 2026. # # msgid "" @@ -9,7 +9,7 @@ "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: 2025-07-28 11:56+0200\n" +"PO-Revision-Date: 2026-01-19 10:12+0100\n" "Last-Translator: Alexander Shopov <ash@kambanaria.org>\n" "Language-Team: Bulgarian <dict@fsa-bg.org>\n" "Language: bg\n" @@ -230,7 +230,7 @@ msgstr "Изтриване…" msgid "Reset..." -msgstr "Отмяна на промените…" +msgstr "Отмяна на промѐните…" msgid "Done" msgstr "Готово" @@ -322,7 +322,7 @@ msgstr "Промени в индекса (за подаване)" msgid "Stage Changed" -msgstr "Индексът е променен" +msgstr "Добавяне към индекса" msgid "Push" msgstr "Изтласкване" @@ -394,7 +394,7 @@ msgstr "Връщане към родителската версия" msgid "Visualize These Changes In The Submodule" -msgstr "Визуализиране на промените в подмодула" +msgstr "Визуализиране на промѐните в подмодула" msgid "Visualize Current Branch History In The Submodule" msgstr "Визуализация на историята на текущия клон в историята за подмодула" @@ -535,7 +535,7 @@ msgstr "Опции" msgid "Fetch Tracking Branch" -msgstr "Изтегляне на промените от следения клон" +msgstr "Изтегляне на промѐните от следения клон" msgid "Detach From Local Branch" msgstr "Изтриване от локалния клон" @@ -602,14 +602,14 @@ msgstr "Локални клони" msgid "Delete Only If Merged Into" -msgstr "Изтриване, само ако промените са слети и другаде" +msgstr "Изтриване, само ако промѐните са слети и другаде" msgid "Always (Do not perform merge checks)" msgstr "Винаги (без проверка за сливане)" #, tcl-format msgid "The following branches are not completely merged into %s:" -msgstr "Не всички промени в клоните са слети в „%s“:" +msgstr "Не всички промѐни в клоните са слети в „%s“:" msgid "" "Recovering deleted branches is difficult.\n" @@ -1035,10 +1035,37 @@ "\n" "You must stage at least 1 file before you can commit.\n" msgstr "" -"Няма промени за подаване.\n" +"Няма промѐни за подаване.\n" "\n" "Трябва да добавите поне един файл към индекса, за да подадете.\n" +msgid "Calling pre-commit hook..." +msgstr "Изпълняване на куката преди подаване…" + +msgid "Commit declined by pre-commit hook." +msgstr "Подаването е отхвърлено от куката преди подаване." + +msgid "" +"You are about to commit on a detached head. This is a potentially dangerous " +"thing to do because if you switch to another branch you will lose your " +"changes and it can be difficult to retrieve them later from the reflog. You " +"should probably cancel this commit and create a new branch to continue.\n" +" \n" +" Do you really want to proceed with your Commit?" +msgstr "" +"Ще подадете към несвързан, отделѐн указател „HEAD“. Това е опасно, защото " +"при преминаването към клон ще загубите промѐните си, като единственият начин " +"да ги върнете ще е чрез журнала на указателите (reflog). Най-вероятно трябва " +"да не правите това подаване, а да създадете нов клон, преди да продължите.\n" +" \n" +"Сигурни ли сте, че искате да извършите текущото подаване?" + +msgid "Calling commit-msg hook..." +msgstr "Изпълняване на куката за съобщението при подаване…" + +msgid "Commit declined by commit-msg hook." +msgstr "Подаването е отхвърлено от куката за съобщението при подаване." + msgid "" "Please supply a commit message.\n" "\n" @@ -1056,35 +1083,8 @@ "● Втори ред: празен.\n" "● Останалите редове: опишете защо се налага тази промяна.\n" -msgid "Calling pre-commit hook..." -msgstr "Изпълняване на куката преди подаване…" - -msgid "Commit declined by pre-commit hook." -msgstr "Подаването е отхвърлено от куката преди подаване." - -msgid "" -"You are about to commit on a detached head. This is a potentially dangerous " -"thing to do because if you switch to another branch you will lose your " -"changes and it can be difficult to retrieve them later from the reflog. You " -"should probably cancel this commit and create a new branch to continue.\n" -" \n" -" Do you really want to proceed with your Commit?" -msgstr "" -"Ще подадете към несвързан, отделѐн указател „HEAD“. Това е опасно, защото " -"при преминаването към клон ще загубите промените си, като единственият начин " -"да ги върнете ще е чрез журнала на указателите (reflog). Най-вероятно трябва " -"да не правите това подаване, а да създадете нов клон, преди да продължите.\n" -" \n" -"Сигурни ли сте, че искате да извършите текущото подаване?" - -msgid "Calling commit-msg hook..." -msgstr "Изпълняване на куката за съобщението при подаване…" - -msgid "Commit declined by commit-msg hook." -msgstr "Подаването е отхвърлено от куката за съобщението при подаване." - msgid "Committing changes..." -msgstr "Подаване на промените…" +msgstr "Подаване на промѐните…" msgid "write-tree failed:" msgstr "неуспешно запазване на дървото (write-tree):" @@ -1103,7 +1103,7 @@ "\n" "A rescan will be automatically started now.\n" msgstr "" -"Няма промени за подаване.\n" +"Няма промѐни за подаване.\n" "\n" "В това подаване не са променяни никакви файлове, а и не е подаване със " "сливане.\n" @@ -1111,7 +1111,7 @@ "Автоматично ще започне нова проверка.\n" msgid "No changes to commit." -msgstr "Няма промени за подаване." +msgstr "Няма промѐни за подаване." msgid "commit-tree failed:" msgstr "неуспешно подаване на дървото (commit-tree):" @@ -1191,7 +1191,7 @@ "* No differences detected; stage the file to de-list it from Unstaged " "Changes.\n" msgstr "" -"● Няма разлики. Добавете файла към индекса, за да се извади от промените " +"● Няма разлики. Добавете файла към индекса, за да се извади от промѐните " "извън индекса.\n" msgid "* Click to find other files that may have the same state.\n" @@ -1350,15 +1350,15 @@ #, tcl-format msgid "Revert changes in file %s?" -msgstr "Да се махнат ли промените във файла „%s“?" +msgstr "Да се махнат ли промѐните във файла „%s“?" #, tcl-format msgid "Revert changes in these %i files?" -msgstr "Да се махнат ли промените в тези %i файла?" +msgstr "Да се махнат ли промѐните в тези %i файла?" msgid "Any unstaged changes will be permanently lost by the revert." msgstr "" -"Всички промени, които не са били добавени в индекса, ще се загубят " +"Всички промѐни, които не са били добавени в индекса, ще се загубят " "безвъзвратно." msgid "Do Nothing" @@ -1393,11 +1393,11 @@ msgstr "%d от избраните %d файла не бяха изтрити." msgid "Reverting selected files" -msgstr "Махане на промените в избраните файлове" +msgstr "Махане на промѐните в избраните файлове" #, tcl-format msgid "Reverting %s" -msgstr "Махане на промените в „%s“" +msgstr "Махане на промѐните в „%s“" msgid "Goto Line:" msgstr "Към ред:" @@ -1507,7 +1507,7 @@ msgstr "" "Да се преустанови ли сливането?\n" "\n" -"В такъв случай ●ВСИЧКИ● неподадени промени ще се загубят безвъзвратно.\n" +"В такъв случай ●ВСИЧКИ● неподадени промѐни ще се загубят безвъзвратно.\n" "\n" "Наистина ли да се преустанови сливането?" @@ -1518,17 +1518,17 @@ "\n" "Continue with resetting the current changes?" msgstr "" -"Да се занулят ли промените?\n" +"Да се занулят ли промѐните?\n" "\n" -"В такъв случай ●ВСИЧКИ● неподадени промени ще се загубят безвъзвратно.\n" +"В такъв случай ●ВСИЧКИ● неподадени промѐни ще се загубят безвъзвратно.\n" "\n" -"Наистина ли да се занулят промените?" +"Наистина ли да се занулят промѐните?" msgid "Aborting" msgstr "Преустановяване" msgid "files reset" -msgstr "файла със занулени промени" +msgstr "файла със занулени промѐни" msgid "Abort failed." msgstr "Неуспешно преустановяване." @@ -1766,7 +1766,7 @@ msgstr "Незабавно доставяне" msgid "Initialize Remote Repository and Push" -msgstr "Инициализиране на отдалеченото хранилище и изтласкване на промените" +msgstr "Инициализиране на отдалеченото хранилище и изтласкване на промѐните" msgid "Do Nothing Else Now" msgstr "Да не се прави нищо" @@ -1896,6 +1896,10 @@ msgid "%s (%s): Create Desktop Icon" msgstr "%s (%s): Добавяне на икона на работния плот" +#, tcl-format +msgid "Replace existing shortcut: %s?" +msgstr "Да се замени ли съществуващата клавишна комбинация: %s?" + msgid "Cannot write shortcut:" msgstr "Клавишната комбинация не може да се запази:" @@ -2081,7 +2085,7 @@ #, tcl-format msgid "Fetching new changes from %s" -msgstr "Доставяне на промените от „%s“" +msgstr "Доставяне на промѐните от „%s“" #, tcl-format msgid "remote prune %s" @@ -2095,7 +2099,7 @@ msgstr "доставяне от всички отдалечени" msgid "Fetching new changes from all remotes" -msgstr "Доставяне на промените от всички отдалечени хранилища" +msgstr "Доставяне на промѐните от всички отдалечени хранилища" msgid "remote prune all remotes" msgstr "окастряне на следящите изтрити" @@ -2107,7 +2111,7 @@ #, tcl-format msgid "Pushing changes to %s" -msgstr "Изтласкване на промените към „%s“" +msgstr "Изтласкване на промѐните към „%s“" #, tcl-format msgid "Mirroring to %s" @@ -2131,7 +2135,7 @@ msgid "Force overwrite existing branch (may discard changes)" msgstr "" -"Изрично презаписване на съществуващ клон (някои промени може да се загубят)" +"Изрично презаписване на съществуващ клон (някои промѐни може да се загубят)" msgid "Use thin pack (for slow network connections)" msgstr "Максимална компресия (за бавни мрежови връзки)"
diff --git a/git-gui/po/glossary/bg.po b/git-gui/po/glossary/bg.po index 8b71fad..96563ef 100644 --- a/git-gui/po/glossary/bg.po +++ b/git-gui/po/glossary/bg.po
@@ -1,14 +1,14 @@ # Bulgarian translation of git-gui-glossary po-file. -# Copyright (C) 2012, 2013, 2014 Alexander Shopov <ash@kambanaria.org>. +# Copyright (C) 2012, 2013, 2014, 2026 Alexander Shopov <ash@kambanaria.org>. # This file is distributed under the same license as the git package. -# Alexander Shopov <ash@kambanaria.org>, 2012, 2013, 2014. +# Alexander Shopov <ash@kambanaria.org>, 2012, 2013, 2014, 2026. # # msgid "" msgstr "" "Project-Id-Version: git-gui-glossary master\n" -"POT-Creation-Date: 2014-01-13 21:39+0200\n" -"PO-Revision-Date: 2014-01-13 21:39+0200\n" +"POT-Creation-Date: 2020-01-26 22:26+0100\n" +"PO-Revision-Date: 2026-01-24 11:18+0100\n" "Last-Translator: Alexander Shopov <ash@kambanaria.org>\n" "Language-Team: Bulgarian <dict@fsa-bg.org>\n" "Language: bg\n" @@ -20,23 +20,55 @@ #. "English Definition (Dear translator: This file will never be visible to the user! It should only serve as a tool for you, the translator. Nothing more.)" msgid "" "English Term (Dear translator: This file will never be visible to the user!)" -msgstr "Термин" +msgstr "Български превод на термина" + +#. "prematurely stop and abandon an operation" +msgid "abort" +msgstr "преустановявам" #. "" msgid "amend" msgstr "поправям" +#. "a commit that succeeds the current one in git's graph of commits (not necessarily directly)" +msgid "ancestor" +msgstr "предшественик" + #. "" msgid "annotate" msgstr "анотирам" +#. "The person who initially created (authored) a commit" +msgid "author" +msgstr "автор" + +#. "a repository with only .git directory, without working directory" +msgid "bare repository" +msgstr "голо хранилище" + +#. "a parent version of the current file" +msgid "base" +msgstr "базова версия" + +#. "" +msgid "bisect" +msgstr "двоично търсене" + +#. "get the authors responsible for each line in a file" +msgid "blame" +msgstr "анотирам/анотиране" + +#. "" +msgid "blob" +msgstr "обект-BLOB" + #. "A 'branch' is an active line of development." msgid "branch [noun]" -msgstr "клон, разклонение [съществително]" +msgstr "клон [съществително]" #. "" msgid "branch [verb]" -msgstr "разклонявам [глагол]" +msgstr "създавам клон [глагол]" #. "" msgid "checkout [noun]" @@ -46,6 +78,18 @@ msgid "checkout [verb]" msgstr "изтегляне [глагол]" +#. "to select and apply a single commit to the current HEAD without merging" +msgid "cherry-pick" +msgstr "отбирам" + +#. "a commit that directly succeeds the current one in git's graph of commits" +msgid "child commit" +msgstr "подаване-наследник" + +#. "clean the state of the git repository, often after manually stopped operation" +msgid "cleanup" +msgstr "зачиствам" + #. "" msgid "clone [verb]" msgstr "клонирам [глагол]" @@ -56,180 +100,80 @@ #. "The action of storing a new snapshot of the project's state in the git history." msgid "commit [verb]" -msgstr "подавам [съществително]" +msgstr "подавам [глагол]" + +#. "a message that gets attached with any commit" +msgid "commit message" +msgstr "съобщение при подаване" + +#. "The person who committed a commit (to the current branch), which might be different than the author." +msgid "committer" +msgstr "подаващ" + +#. "a commit that precedes the current one in git's graph of commits (not necessarily directly)" +msgid "descendant" +msgstr "наследник" + +#. "checkout of a revision rather than some head" +msgid "detached HEAD" +msgstr "несвързан указател „HEAD“" + +#. "checkout of a revision rather than some head" +msgid "detached checkout" +msgstr "несвързано изтегляне" #. "" msgid "diff [noun]" -msgstr "разлика/промени [съществително]" +msgstr "разлика/промѐни [съществително]" #. "" msgid "diff [verb]" -msgstr "изчислявам разлика/промени [глагол]" +msgstr "изчислявам разлика/промѐни [глагол]" -#. "A fast-forward is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have." -msgid "fast forward merge" -msgstr "превъртащо/директно/тривиално сливане" +#. "" +msgid "directory" +msgstr "директория" + +#. "A fast-forward merge is a special type of merge where you have a revision and you are merging another branch's changes that happen to be a descendant of what you have." +msgid "fast-forward" +msgstr "превъртане" #. "Fetching a branch means to get the branch's head from a remote repository, to find out which objects are missing from the local object database, and to get them, too." msgid "fetch" msgstr "доставяне" +#. "any merge strategy that works on a file by file basis" +msgid "file level merging" +msgstr "пофайлово сливане" + +#. "" +msgid "file" +msgstr "файл" + +#. "the last revision in a branch" +msgid "head" +msgstr "връх (на клон, разработка)" + +#. "script that gets executed automatically on some event" +msgid "hook" +msgstr "кука" + #. "One context of consecutive lines in a whole patch, which consists of many such hunks" msgid "hunk" msgstr "парче" #. "A collection of files. The index is a stored version of your working tree." msgid "index (in git-gui: staging area)" -msgstr "скеле/индекс/изба" - -#. "A successful merge results in the creation of a new commit representing the result of the merge." -msgid "merge [noun]" -msgstr "сливане/обединяване [съществително]" - -#. "To bring the contents of another branch into the current branch." -msgid "merge [verb]" -msgstr "сливам/обединявам [глагол]" - -#. "" -msgid "message" -msgstr "съобщение" - -#. "Deletes all stale tracking branches under <name>. These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in 'remotes/<name>'." -msgid "prune" -msgstr "окастрям" - -#. "Pulling a branch means to fetch it and merge it." -msgid "pull" -msgstr "издърпвам" - -#. "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)" -msgid "push" -msgstr "изтласквам" - -#. "" -msgid "redo" -msgstr "повтарям/правя наново" - -#. "An other repository ('remote'). One might have a set of remotes whose branches one tracks." -msgid "remote" -msgstr "отдалечено хранилище" - -#. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)" -msgid "repository" -msgstr "хранилище" - -#. "" -msgid "reset" -msgstr "занулявам/отменям" - -#. "" -msgid "revert" -msgstr "връщам/отменям" - -#. "A particular state of files and directories which was stored in the object database." -msgid "revision" -msgstr "версия" - -#. "" -msgid "sign off" -msgstr "подписвам" - -#. "" -msgid "staging area" -msgstr "скеле/индекс/изба" - -#. "" -msgid "status" -msgstr "състояние" - -#. "A ref pointing to a tag or commit object" -msgid "tag [noun]" -msgstr "етикет [съществително]" - -#. "" -msgid "tag [verb]" -msgstr "задавам етикет [глагол]" - -#. "A regular git branch that is used to follow changes from another repository." -msgid "tracking branch" -msgstr "следящ клон" - -#. "" -msgid "undo" -msgstr "отменям" - -#. "" -msgid "update" -msgstr "обновявам" - -#. "" -msgid "verify" -msgstr "проверявам" - -#. "The tree of actual checked out files." -msgid "working copy, working tree" -msgstr "работно копие/работно дърво" - -#. "a commit that succeeds the current one in git's graph of commits (not necessarily directly)" -msgid "ancestor" -msgstr "предшественик" - -#. "prematurely stop and abandon an operation" -msgid "abort" -msgstr "преустановявам" - -#. "a repository with only .git directory, without working directory" -msgid "bare repository" -msgstr "голо хранилище" - -#. "a parent version of the current file" -msgid "base" -msgstr "родителска версия" - -#. "get the authors responsible for each line in a file" -msgid "blame" -msgstr "анотирам/анотиране" - -#. "to select and apply a single commit without merging" -msgid "cherry-pick" -msgstr "отбирам" - -#. "a commit that directly succeeds the current one in git's graph of commits" -msgid "child" -msgstr "дете" - -#. "clean the state of the git repository, often after manually stopped operation" -msgid "cleanup" -msgstr "зачиствам" - -#. "a message that gets attached with any commit" -msgid "commit message" -msgstr "съобщение при подаване" - -#. "a commit that precedes the current one in git's graph of commits (not necessarily directly)" -msgid "descendant" -msgstr "наследник" - -#. "checkout of a revision rather than a some head" -msgid "detached checkout" -msgstr "несвързано изтегляне" - -#. "any merge strategy that works on a file by file basis" -msgid "file level merging" -msgstr "пофайлово сливане" - -#. "the last revision in a branch" -msgid "head" -msgstr "глава/връх (на клон, разработка)" - -#. "script that gets executed automatically on some event" -msgid "hook" -msgstr "кука/автоматичен скрипт" +msgstr "индекс" #. "the first checkout during a clone operation" msgid "initial checkout" msgstr "първоначално изтегляне" +#. "The very first commit in a repository" +msgid "initial commit" +msgstr "първоначално подаване" + #. "a branch that resides in the local git repository" msgid "local branch" msgstr "локален клон" @@ -242,26 +186,112 @@ msgid "master branch" msgstr "основен клон" +#. "A successful merge results in the creation of a new commit representing the result of the merge." +msgid "merge [noun]" +msgstr "сливане [съществително]" + +#. "To bring the contents of another branch into the current branch." +msgid "merge [verb]" +msgstr "сливам [глагол]" + +#. "" +msgid "message" +msgstr "съобщение" + #. "a remote called by convention 'origin' that the current git repository has been cloned from" msgid "origin" msgstr "хранилище-източник" +#. "" +msgid "orphan commit" +msgstr "неродено подаване" + +#. "" +msgid "orphan reference" +msgstr "неродѐн указател" + #. "a file containing many git objects packed together" msgid "pack [noun]" -msgstr "етикет" +msgstr "пакет [глагол]" + +#. "the process of creating a pack file" +msgid "pack [verb]" +msgstr "пакетирам [глагол]" #. "a Git object part of some pack" msgid "packed object" msgstr "пакетиран обект" #. "a commit that directly precedes the current one in git's graph of commits" -msgid "parent" -msgstr "родител" +msgid "parent commit" +msgstr "подаване-родител" + +msgid "patch" +msgstr "кръпка" + +#. "The path to a file" +msgid "path" +msgstr "път" + +#. "Delete all stale tracking branches under <name>. These stale branches have already been removed from the remote repository referenced by <name>, but are still locally available in 'remotes/<name>'." +msgid "prune" +msgstr "окастрям" + +#. "Pulling a branch means to fetch it and merge it." +msgid "pull" +msgstr "издърпвам" + +#. "Pushing a branch means to get the branch's head ref from a remote repository, and ... (well, can someone please explain it for mere mortals?)" +msgid "push" +msgstr "изтласквам" + +#. "The process of rebasing one set of commits on top of another branch's head" +msgid "rebase [noun]" +msgstr "пребазиране [съществително]" + +#. "Re-apply one set of commits on top of another branch's head. Contrary to merge." +msgid "rebase [verb]" +msgstr "пребазирам [глагол]" + +#. "" +msgid "redo" +msgstr "повтарям/правя наново" + +#. "" +msgid "reference" +msgstr "указател" #. "the log file containing all states of the HEAD reference (in other words past pristine states of the working copy)" msgid "reflog" msgstr "журнал на указателите" +msgid "refmap" +msgstr "карта с указатели" + +#. "" +msgid "refspec" +msgstr "указател на версия" + +#. "The adjective for anything which is outside of the current (local) repository" +msgid "remote [adj]" +msgstr "отдалечен [прилагателно]" + +#. "A branch in any other ('remote') repository" +msgid "remote branch" +msgstr "отдалечен клон" + +#. "An other repository ('remote'). One might have a set of remotes whose branches one tracks." +msgid "remote repository" +msgstr "отдалечено/насрещно хранилище" + +#. "A collection of refs (?) together with an object database containing all objects which are reachable from the refs... (oops, you've lost me here. Again, please an explanation for mere mortals?)" +msgid "repository" +msgstr "хранилище" + +#. "" +msgid "reset" +msgstr "занулявам" + #. "decide which changes from alternative versions of a file should persist in Git" msgid "resolve (a conflict)" msgstr "коригирам (конфликт)" @@ -270,18 +300,102 @@ msgid "revert changes" msgstr "връщане на оригинала" +#. "" +msgid "revert" +msgstr "отменям" + #. "expression that signifies a revision in git" msgid "revision expression" msgstr "израз за версия" +#. "A particular state of files and directories which was stored in the object database." +msgid "revision" +msgstr "версия" + +#. "" +msgid "sign off" +msgstr "подписвам" + +#. "see: staging area. In some areas of git this is called 'index'." +msgid "stage [noun], index" +msgstr "индекс [съществително]" + #. "add some content of files and directories to the staging area in preparation for a commit" -msgid "stage/unstage" -msgstr "(добавяне) към скелето за подаване/изваждане от скелето за подаване" +msgid "stage [verb]" +msgstr "добавям в индекса [глагол]" + +#. "The place where changes from files are marked to be included for the next commit. In some areas of git this is called 'index'." +msgid "staging area" +msgstr "индекс" + +#. "The place (stack) where changes can be temporarily saved without committing" +msgid "stash [noun]" +msgstr "скатано [съществително]" #. "temporarily save changes in a stack without committing" -msgid "stash" -msgstr "скатавам промени" +msgid "stash [verb]" +msgstr "скатано [глагол]" + +#. "" +msgid "status" +msgstr "състояние" + +#. "" +msgid "submodule" +msgstr "подмодул" + +#. "A ref pointing to some commit object. In other words: A label on a specific commit." +msgid "tag [noun]" +msgstr "етикет [съществително]" + +#. "The process of creating a tag at a specific commit object" +msgid "tag [verb]" +msgstr "задавам етикет [глагол]" + +#. "The person who created a tag" +msgid "tagger" +msgstr "етикиращ" #. "file whose content is tracked/not tracked by git" msgid "tracked/untracked" msgstr "следен/неследен" + +#. "A regular git branch that is used to follow changes from another repository." +msgid "tracking branch" +msgstr "следящ клон" + +#. "" +msgid "trailer" +msgstr "епилог" + +#. "1. tree object, 2. directory tree" +msgid "tree" +msgstr "обект-дърво/дърво" + +#. "" +msgid "undo" +msgstr "отменям" + +#. "Remove content of files from the staging area again so that it will not be part of the next commit" +msgid "unstage" +msgstr "изваждено от индекса" + +#. "Retrieving the temporarily saved changes back again from the stash" +msgid "unstash [verb]" +msgstr "изваждам от индекса [глагол]" + +#. "" +msgid "update" +msgstr "обновявам" + +#. "" +msgid "upstream branch" +msgstr "следен клон" + +#. "" +msgid "verify" +msgstr "проверявам" + +#. "The tree of actual checked out files." +msgid "working directory, working copy, working tree" +msgstr "работна директория, работно копие, работно дърво"
diff --git a/git-send-email.perl b/git-send-email.perl index cd4b316..bb8ddd1 100755 --- a/git-send-email.perl +++ b/git-send-email.perl
@@ -23,6 +23,7 @@ use Git::LoadCPAN::Error qw(:try); use Git; use Git::I18N; +use Encode qw(find_encoding); Getopt::Long::Configure qw/ pass_through /; @@ -66,6 +67,8 @@ --smtp-ssl-cert-path <str> * Path to ca-certificates (either directory or file). Pass an empty string to disable certificate verification. + --smtp-ssl-client-cert <str> * Path to the client certificate file + --smtp-ssl-client-key <str> * Path to the private key file for the client certificate --smtp-domain <str> * The domain name sent to HELO/EHLO handshake --smtp-auth <str> * Space-separated list of allowed AUTH mechanisms, or "none" to disable authentication. @@ -279,6 +282,7 @@ my ($to_cmd, $cc_cmd, $header_cmd); my ($smtp_server, $smtp_server_port, @smtp_server_options); my ($smtp_authuser, $smtp_encryption, $smtp_ssl_cert_path); +my ($smtp_ssl_client_cert, $smtp_ssl_client_key); my ($batch_size, $relogin_delay); my ($identity, $aliasfiletype, @alias_files, $smtp_domain, $smtp_auth); my ($imap_sent_folder); @@ -350,6 +354,8 @@ my %config_path_settings = ( "aliasesfile" => \@alias_files, "smtpsslcertpath" => \$smtp_ssl_cert_path, + "smtpsslclientcert" => \$smtp_ssl_client_cert, + "smtpsslclientkey" => \$smtp_ssl_client_key, "mailmap.file" => \$mailmap_file, "mailmap.blob" => \$mailmap_blob, ); @@ -531,6 +537,8 @@ "smtp-ssl" => sub { $smtp_encryption = 'ssl' }, "smtp-encryption=s" => \$smtp_encryption, "smtp-ssl-cert-path=s" => \$smtp_ssl_cert_path, + "smtp-ssl-client-cert=s" => \$smtp_ssl_client_cert, + "smtp-ssl-client-key=s" => \$smtp_ssl_client_key, "smtp-debug:i" => \$debug_net_smtp, "smtp-domain:s" => \$smtp_domain, "smtp-auth=s" => \$smtp_auth, @@ -1044,9 +1052,27 @@ foreach my $f (sort keys %broken_encoding) { print " $f\n"; } - $auto_8bit_encoding = ask(__("Which 8bit encoding should I declare [UTF-8]? "), - valid_re => qr/.{4}/, confirm_only => 1, - default => "UTF-8"); + while (1) { + my $encoding = ask( + __("Declare which 8bit encoding to use [default: UTF-8]? "), + valid_re => qr/^\S+$/, + default => "UTF-8"); + next unless defined $encoding; + if (find_encoding($encoding)) { + $auto_8bit_encoding = $encoding; + last; + } + my $yesno = ask( + sprintf( + __("'%s' does not appear to be a valid charset name. Use it anyway [y/N]? "), + $encoding), + valid_re => qr/^(?:y|n)/i, + default => "n"); + if (defined $yesno && $yesno =~ /^y/i) { + $auto_8bit_encoding = $encoding; + last; + } + } } if (!$force) { @@ -1474,6 +1500,8 @@ user => $cred->{'username'}, pass => $cred->{'password'}, authname => $cred->{'username'}, + host => $smtp_server, + (defined $smtp_server_port ? (port => $smtp_server_port) : ()), } ); $result = $smtp->auth($sasl); @@ -1520,6 +1548,8 @@ } sub ssl_verify_params { + my %ret = (); + eval { require IO::Socket::SSL; IO::Socket::SSL->import(qw/SSL_VERIFY_PEER SSL_VERIFY_NONE/); @@ -1531,20 +1561,36 @@ if (!defined $smtp_ssl_cert_path) { # use the OpenSSL defaults - return (SSL_verify_mode => SSL_VERIFY_PEER()); + $ret{SSL_verify_mode} = SSL_VERIFY_PEER(); + } + else { + if ($smtp_ssl_cert_path eq "") { + $ret{SSL_verify_mode} = SSL_VERIFY_NONE(); + } elsif (-d $smtp_ssl_cert_path) { + $ret{SSL_verify_mode} = SSL_VERIFY_PEER(); + $ret{SSL_ca_path} = $smtp_ssl_cert_path; + } elsif (-f $smtp_ssl_cert_path) { + $ret{SSL_verify_mode} = SSL_VERIFY_PEER(); + $ret{SSL_ca_file} = $smtp_ssl_cert_path; + } else { + die sprintf(__("CA path \"%s\" does not exist"), $smtp_ssl_cert_path); + } } - if ($smtp_ssl_cert_path eq "") { - return (SSL_verify_mode => SSL_VERIFY_NONE()); - } elsif (-d $smtp_ssl_cert_path) { - return (SSL_verify_mode => SSL_VERIFY_PEER(), - SSL_ca_path => $smtp_ssl_cert_path); - } elsif (-f $smtp_ssl_cert_path) { - return (SSL_verify_mode => SSL_VERIFY_PEER(), - SSL_ca_file => $smtp_ssl_cert_path); - } else { - die sprintf(__("CA path \"%s\" does not exist"), $smtp_ssl_cert_path); + if (defined $smtp_ssl_client_cert) { + $ret{SSL_cert_file} = $smtp_ssl_client_cert; } + if (defined $smtp_ssl_client_key) { + if (!defined $smtp_ssl_client_cert) { + # Accept the client key only when a certificate is given. + # We die here because this case is a user error. + die sprintf(__("Only client key \"%s\" specified"), + $smtp_ssl_client_key); + } + $ret{SSL_key_file} = $smtp_ssl_client_key; + } + + return %ret; } sub file_name_is_absolute {
diff --git a/git.c b/git.c index c5fad56..5a40eab 100644 --- a/git.c +++ b/git.c
@@ -119,7 +119,7 @@ static int list_cmds(const char *spec) } for (size_t i = 0; i < list.nr; i++) puts(list.items[i].string); - string_list_clear(&list, 0); + string_list_clear(&list, 1); return 0; } @@ -586,7 +586,8 @@ static struct cmd_struct commands[] = { { "grep", cmd_grep, RUN_SETUP_GENTLY }, { "hash-object", cmd_hash_object }, { "help", cmd_help }, - { "hook", cmd_hook, RUN_SETUP }, + { "history", cmd_history, RUN_SETUP }, + { "hook", cmd_hook, RUN_SETUP_GENTLY }, { "index-pack", cmd_index_pack, RUN_SETUP_GENTLY | NO_PARSEOPT }, { "init", cmd_init_db }, { "init-db", cmd_init_db }, @@ -876,8 +877,7 @@ static int run_argv(struct strvec *args) commit_pager_choice(); strvec_push(&cmd.args, "git"); - for (size_t i = 0; i < args->nr; i++) - strvec_push(&cmd.args, args->v[i]); + strvec_pushv(&cmd.args, args->v); trace_argv_printf(cmd.args.v, "trace: exec:");
diff --git a/gitk-git/.gitignore b/gitk-git/.gitignore index d7ebcaf..15f96aa 100644 --- a/gitk-git/.gitignore +++ b/gitk-git/.gitignore
@@ -1,2 +1,3 @@ /GIT-TCLTK-VARS /gitk-wish +po/gitk.pot
diff --git a/gitk-git/Makefile b/gitk-git/Makefile index cc32dca..41116d8 100644 --- a/gitk-git/Makefile +++ b/gitk-git/Makefile
@@ -68,9 +68,12 @@ $(SHELL_PATH) ./generate-tcl.sh "$(TCLTK_PATH_SQ)" "$<" "$@" $(PO_TEMPLATE): gitk - $(XGETTEXT) -kmc -LTcl -o $@ gitk + $(XGETTEXT) -kmc -LTcl --package-name=Gitk -o $@ gitk update-po:: $(PO_TEMPLATE) - $(foreach p, $(ALL_POFILES), echo Updating $p ; msgmerge -U $p $(PO_TEMPLATE) ; ) + $(foreach p, $(ALL_POFILES), echo Updating $p ; msgmerge -U --add-location $p $(PO_TEMPLATE) ; ) + @echo "Before committing changes, ensure that a clean-filter is installed:"; \ + 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 $@) $<
diff --git a/gitk-git/gitk b/gitk-git/gitk index c02db01..2730274 100755 --- a/gitk-git/gitk +++ b/gitk-git/gitk
@@ -2131,12 +2131,14 @@ return $w } -proc make_transient {window origin} { +proc make_transient {window origin {geometry ""}} { wm transient $window $origin - # Windows fails to place transient windows normally, so - # schedule a callback to center them on the parent. - if {[tk windowingsystem] eq {win32}} { + if {$geometry ne ""} { + after idle [list wm geometry $window $geometry] + } elseif {[tk windowingsystem] eq {win32}} { + # Windows fails to place transient windows normally, so + # schedule a callback to center them on the parent. after idle [list tk::PlaceWindow $window widget $origin] } } @@ -2723,17 +2725,9 @@ .pwbottom add .bright .ctop add .pwbottom - # restore window width & height if known + # restore window position if known if {[info exists geometry(main)]} { - if {[scan $geometry(main) "%dx%d" w h] >= 2} { - if {$w > [winfo screenwidth .]} { - set w [winfo screenwidth .] - } - if {$h > [winfo screenheight .]} { - set h [winfo screenheight .] - } - wm geometry . "${w}x$h" - } + wm geometry . "$geometry(main)" } if {[info exists geometry(state)] && $geometry(state) eq "zoomed"} { @@ -3073,6 +3067,11 @@ puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sashpos 1] 1\"" puts $f "set geometry(botwidth) [winfo width .bleft]" puts $f "set geometry(botheight) [winfo height .bleft]" + unset -nocomplain geometry + global geometry + if {[info exists geometry(showrefs)]} { + puts $f "set geometry(showrefs) $geometry(showrefs)" + } array set view_save {} array set views {} @@ -3788,6 +3787,34 @@ "revision $diffid"] } +proc check_for_renames_in_diff {filepath} { # renames + global difffilestart ctext + + set filename [file tail $filepath] + set renames {} + + foreach loc $difffilestart { + set loclineend [string map {.0 .end} $loc] + set fromlineloc "$loc + 2 lines" + set tolineloc "$loc + 3 lines" + set renfromline [$ctext get $fromlineloc [string map {.0 .end} $fromlineloc]] + set rentoline [$ctext get $tolineloc [string map {.0 .end} $tolineloc]] + if {[string equal -length 12 "rename from " $renfromline] + && [string equal -length 10 "rename to " $rentoline]} { + set renfrom [string range $renfromline 12 end] + set rento [string range $rentoline 10 end] + if {[string first $filename $renfrom] != -1 + || [string first $filename $rento] != -1} { + lappend renames $renfrom + lappend renames $rento + break + } + } + } + + return $renames +} + proc external_diff {} { global nullid nullid2 global flist_menu_file @@ -3818,8 +3845,16 @@ if {$diffdir eq {}} return # gather files to diff - set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir] - set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir] + set renames [check_for_renames_in_diff $flist_menu_file] + set renamefrom [lindex $renames 0] + set renameto [lindex $renames 1] + if {$renamefrom ne {} && $renameto ne {}} { + set difffromfile [external_diff_get_one_file $diffidfrom $renamefrom $diffdir] + set difftofile [external_diff_get_one_file $diffidto $renameto $diffdir] + } else { + set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir] + set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir] + } if {$difffromfile ne {} && $difftofile ne {}} { set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile] @@ -6796,17 +6831,19 @@ } else { # draw a head or other ref if {[incr nheads -1] >= 0} { - set col $headbgcolor + set refoutlinecol $headoutlinecolor + set reffillcol $headbgcolor if {$tag eq $mainhead} { set font mainfontbold } } else { - set col "#ddddff" + set refoutlinecol black + set reffillcol "#ddddff" } set xl [expr {$xl - $delta/2}] $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \ - -width 1 -outline black -fill $col -tags tag.$id - if {[regexp {^(remotes/.*/|remotes/)} $tag match remoteprefix]} { + -width 1 -outline $refoutlinecol -fill $reffillcol -tags tag.$id + if {[regexp {^(remotes/[^/]*/|remotes/)} $tag match remoteprefix]} { set rwid [font measure mainfont $remoteprefix] set xi [expr {$x + 1}] set yti [expr {$yt + 1}] @@ -6815,7 +6852,8 @@ -width 0 -fill $remotebgcolor -tags tag.$id } } - set t [$canv create text $xl $y1 -anchor w -text $tag -fill $headfgcolor \ + set textfgcolor [expr {$ntags >= 0 ? $tagfgcolor : $headfgcolor}] + set t [$canv create text $xl $y1 -anchor w -text $tag -fill $textfgcolor \ -font $font -tags [list tag.$id text]] if {$ntags >= 0} { $canv bind $t <1> $tagclick @@ -8296,7 +8334,7 @@ if {![regexp {^diff (--cc|--git) } $line m type]} { set line [convertfrom utf-8 $line] $ctext insert end "$line\n" hunksep - continue + return } # start of a new file set diffinhdr 1 @@ -8401,6 +8439,7 @@ if {$i >= 0} { setinlist difffilestart $i $curdiffstart } + set line "rename from $fname" } elseif {![string compare -length 10 $line "rename to "] || ![string compare -length 8 $line "copy to "]} { set fname [string range $line [expr 4 + [string first " to " $line] ] end] @@ -8408,6 +8447,13 @@ set fname [lindex $fname 0] } makediffhdr $fname $ids + set line "[lindex $line 0] to $fname" + } elseif {![string compare -length 10 $line "copy from "]} { + set fname [string range $line 10 end] + if {[string index $fname 0] eq "\""} { + set fname [lindex $fname 0] + } + set line "copy from $fname" } elseif {[string compare -length 3 $line "---"] == 0} { # do nothing return @@ -10160,6 +10206,7 @@ proc showrefs {} { global showrefstop bgcolor fgcolor selectbgcolor global bglist fglist reflistfilter reflist maincursor + global geometry set top .showrefs set showrefstop $top @@ -10170,7 +10217,11 @@ } ttk_toplevel $top wm title $top [mc "Tags and heads: %s" [file tail [pwd]]] - make_transient $top . + if {[info exists geometry(showrefs)]} { + make_transient $top . $geometry(showrefs) + } else { + make_transient $top . + } text $top.list -background $bgcolor -foreground $fgcolor \ -selectbackground $selectbgcolor -font mainfont \ -xscrollcommand "$top.xsb set" -yscrollcommand "$top.ysb set" \ @@ -10206,6 +10257,9 @@ bind $top.list <ButtonRelease-1> {sel_reflist %W %x %y; break} set reflist {} refill_reflist + # avoid <Configure> being bound to child windows + bindtags $top [linsert [bindtags $top] 1 bind$top] + bind bind$top <Configure> {set geometry(showrefs) [wm geometry %W]} } proc sel_reflist {w x y} { @@ -11745,7 +11799,7 @@ proc prefspage_colors {notebook} { global bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor - global diffbgcolors + global diffbgcolors linkfgcolor global themeloader set page [create_prefs_page $notebook.colors] @@ -11822,6 +11876,11 @@ -command [list choosecolor selectbgcolor {} $page [mc "background"]] grid x $page.selbgbut $page.selbgsep -sticky w + label $page.linkfg -padx 40 -relief sunk -background $linkfgcolor + ttk::button $page.linkfgbut -text [mc "Link"] \ + -command [list choosecolor linkfgcolor {} $page [mc "link"]] + grid x $page.linkfgbut $page.linkfg -sticky w + grid columnconfigure $page 2 -weight 1 return $page @@ -11829,7 +11888,7 @@ proc prefspage_set_colorswatches {page} { global bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor - global diffbgcolors + global diffbgcolors linkfgcolor $page.bg configure -background $bgcolor $page.fg configure -background $fgcolor @@ -11840,6 +11899,7 @@ $page.hunksep configure -background [lindex $diffcolors 2] $page.markbgsep configure -background $markbgcolor $page.selbgsep configure -background $selectbgcolor + $page.linkfg configure -background $linkfgcolor } proc prefspage_fonts {notebook} {
diff --git a/gitk-git/meson.build b/gitk-git/meson.build index ca3c0ce..aecc068 100644 --- a/gitk-git/meson.build +++ b/gitk-git/meson.build
@@ -25,6 +25,6 @@ install_dir: get_option('bindir'), ) -if find_program('msgfmt').found() +if find_program('msgfmt', required: false).found() subdir('po') endif
diff --git a/gitk-git/po/.gitattributes b/gitk-git/po/.gitattributes new file mode 100644 index 0000000..938309e --- /dev/null +++ b/gitk-git/po/.gitattributes
@@ -0,0 +1 @@ +/*.po filter=gettext-no-location
diff --git a/gitk-git/po/bg.po b/gitk-git/po/bg.po index d1e7d92..e7e2f87 100644 --- a/gitk-git/po/bg.po +++ b/gitk-git/po/bg.po
@@ -6,7 +6,7 @@ # msgid "" msgstr "" -"Project-Id-Version: gitk master\n" +"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"
diff --git a/gitk-git/po/ca.po b/gitk-git/po/ca.po index 87dfc18..d588d09 100644 --- a/gitk-git/po/ca.po +++ b/gitk-git/po/ca.po
@@ -6,7 +6,7 @@ # msgid "" msgstr "" -"Project-Id-Version: gitk\n" +"Project-Id-Version: Gitk\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-05-17 14:32+1000\n" "PO-Revision-Date: 2015-10-05 22:23-0600\n" @@ -19,33 +19,26 @@ "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 1.8.5\n" -#: gitk:140 msgid "Couldn't get list of unmerged files:" msgstr "No s'ha pogut obtenir la llista de fitxers no fusionats:" -#: gitk:212 gitk:2381 msgid "Color words" msgstr "Colora les paraules" -#: gitk:217 gitk:2381 gitk:8220 gitk:8253 msgid "Markup words" msgstr "Marca les paraules" -#: gitk:324 msgid "Error parsing revisions:" msgstr "Error en analitzar les revisions:" -#: gitk:380 msgid "Error executing --argscmd command:" msgstr "Error en executar l'ordre --argscmd:" -#: gitk:393 msgid "No files selected: --merge specified but no files are unmerged." msgstr "" "No hi ha fitxers seleccionats: s'ha especificat --merge però cap fitxer està " "sense fusionar." -#: gitk:396 msgid "" "No files selected: --merge specified but no unmerged files are within file " "limit." @@ -53,314 +46,234 @@ "No hi ha fitxers seleccionats: s'ha especificat --merge però cap fitxer " "sense fusionar està dins del límit de fitxers." -#: gitk:418 gitk:566 msgid "Error executing git log:" msgstr "Error en executar git log:" -#: gitk:436 gitk:582 msgid "Reading" msgstr "Llegint" -#: gitk:496 gitk:4525 msgid "Reading commits..." msgstr "Llegint les revisions..." -#: gitk:499 gitk:1637 gitk:4528 msgid "No commits selected" msgstr "Cap comissió seleccionada" -#: gitk:1445 gitk:4045 gitk:12432 msgid "Command line" msgstr "Línia d'ordres" -#: gitk:1511 msgid "Can't parse git log output:" msgstr "No es pot analitzar la sortida del git log:" -#: gitk:1740 msgid "No commit information available" msgstr "Cap informació de comissió disponible" -#: gitk:1903 gitk:1932 gitk:4315 gitk:9669 gitk:11241 gitk:11521 msgid "OK" msgstr "D'acord" -#: gitk:1934 gitk:4317 gitk:9196 gitk:9275 gitk:9391 gitk:9440 gitk:9671 -#: gitk:11242 gitk:11522 msgid "Cancel" msgstr "Cancel·la" -#: gitk:2069 msgid "&Update" msgstr "Actualitza" -#: gitk:2070 msgid "&Reload" msgstr "Recarrega" -#: gitk:2071 msgid "Reread re&ferences" msgstr "Rellegeix les referències" -#: gitk:2072 msgid "&List references" msgstr "Llista les referències" -#: gitk:2074 msgid "Start git &gui" msgstr "Inicia el git gui" -#: gitk:2076 msgid "&Quit" msgstr "Surt" -#: gitk:2068 msgid "&File" msgstr "Fitxer" -#: gitk:2080 msgid "&Preferences" msgstr "Preferències" -#: gitk:2079 msgid "&Edit" msgstr "Edita" -#: gitk:2084 msgid "&New view..." msgstr "Vista nova..." -#: gitk:2085 msgid "&Edit view..." msgstr "Edita la vista..." -#: gitk:2086 msgid "&Delete view" msgstr "Suprimeix la vista" -#: gitk:2088 gitk:4043 msgid "&All files" msgstr "Tots els fitxers" -#: gitk:2083 gitk:4067 msgid "&View" msgstr "Vista" -#: gitk:2093 gitk:2103 gitk:3012 msgid "&About gitk" msgstr "Quant al gitk" -#: gitk:2094 gitk:2108 msgid "&Key bindings" msgstr "Associacions de tecles" -#: gitk:2092 gitk:2107 msgid "&Help" msgstr "Ajuda" -#: gitk:2185 gitk:8652 msgid "SHA1 ID:" msgstr "ID SHA1:" -#: gitk:2229 msgid "Row" msgstr "Fila" -#: gitk:2267 msgid "Find" msgstr "Cerca" -#: gitk:2295 msgid "commit" msgstr "comissió" -#: gitk:2299 gitk:2301 gitk:4687 gitk:4710 gitk:4734 gitk:6755 gitk:6827 -#: gitk:6912 msgid "containing:" msgstr "que contingui:" -#: gitk:2302 gitk:3526 gitk:3531 gitk:4763 msgid "touching paths:" msgstr "que toqui els camins:" -#: gitk:2303 gitk:4777 msgid "adding/removing string:" msgstr "que afegeixi/elimini la cadena:" -#: gitk:2304 gitk:4779 msgid "changing lines matching:" msgstr "que tingui línies canviades coincidents amb:" -#: gitk:2313 gitk:2315 gitk:4766 msgid "Exact" msgstr "Exacte" -#: gitk:2315 gitk:4854 gitk:6723 msgid "IgnCase" msgstr "Ignora majúscula i minúscula" -#: gitk:2315 gitk:4736 gitk:4852 gitk:6719 msgid "Regexp" msgstr "Regexp" -#: gitk:2317 gitk:2318 gitk:4874 gitk:4904 gitk:4911 gitk:6848 gitk:6916 msgid "All fields" msgstr "Tots els camps" -#: gitk:2318 gitk:4871 gitk:4904 gitk:6786 msgid "Headline" msgstr "Titular" -#: gitk:2319 gitk:4871 gitk:6786 gitk:6916 gitk:7389 msgid "Comments" msgstr "Comentaris" -#: gitk:2319 gitk:4871 gitk:4876 gitk:4911 gitk:6786 gitk:7324 gitk:8830 -#: gitk:8845 msgid "Author" msgstr "Autor" -#: gitk:2319 gitk:4871 gitk:6786 gitk:7326 msgid "Committer" msgstr "Comitent" -#: gitk:2350 msgid "Search" msgstr "Cerca" -#: gitk:2358 msgid "Diff" msgstr "Diferència" -#: gitk:2360 msgid "Old version" msgstr "Versió antiga" -#: gitk:2362 msgid "New version" msgstr "Versió nova" -#: gitk:2364 msgid "Lines of context" msgstr "Línies de context" -#: gitk:2374 msgid "Ignore space change" msgstr "Ignora canvis d'espai" -#: gitk:2378 gitk:2380 gitk:7959 gitk:8206 msgid "Line diff" msgstr "Diferència de línies" -#: gitk:2445 msgid "Patch" msgstr "Pedaç" -#: gitk:2447 msgid "Tree" msgstr "Arbre" -#: gitk:2617 gitk:2637 msgid "Diff this -> selected" msgstr "Diferencia aquesta -> la seleccionada" -#: gitk:2618 gitk:2638 msgid "Diff selected -> this" msgstr "Diferencia la seleccionada -> aquesta" -#: gitk:2619 gitk:2639 msgid "Make patch" msgstr "Fes pedaç" -#: gitk:2620 gitk:9254 msgid "Create tag" msgstr "Crea etiqueta" -#: gitk:2621 gitk:9371 msgid "Write commit to file" msgstr "Escriu la comissió a un fitxer" -#: gitk:2622 gitk:9428 msgid "Create new branch" msgstr "Crea una branca nova" -#: gitk:2623 msgid "Cherry-pick this commit" msgstr "Recull aquesta comissió com a cirera" -#: gitk:2624 msgid "Reset HEAD branch to here" msgstr "Restableix la branca HEAD aquí" -#: gitk:2625 msgid "Mark this commit" msgstr "Marca aquesta comissió" -#: gitk:2626 msgid "Return to mark" msgstr "Torna a la marca" -#: gitk:2627 msgid "Find descendant of this and mark" msgstr "Troba la descendent d'aquesta i marca-la" -#: gitk:2628 msgid "Compare with marked commit" msgstr "Compara amb la comissió marcada" -#: gitk:2629 gitk:2640 msgid "Diff this -> marked commit" msgstr "Diferencia aquesta -> la comissió marcada" -#: gitk:2630 gitk:2641 msgid "Diff marked commit -> this" msgstr "Diferencia la comissió seleccionada -> aquesta" -#: gitk:2631 msgid "Revert this commit" msgstr "Reverteix aquesta comissió" -#: gitk:2647 msgid "Check out this branch" msgstr "Agafa aquesta branca" -#: gitk:2648 msgid "Remove this branch" msgstr "Elimina aquesta branca" -#: gitk:2649 msgid "Copy branch name" msgstr "Copia el nom de branca" -#: gitk:2656 msgid "Highlight this too" msgstr "Ressalta aquest també" -#: gitk:2657 msgid "Highlight this only" msgstr "Ressalta només aquest" -#: gitk:2658 msgid "External diff" msgstr "Diferència externa" -#: gitk:2659 msgid "Blame parent commit" msgstr "Culpabilitat de la comissió mare" -#: gitk:2660 msgid "Copy path" msgstr "Copia el camí" -#: gitk:2667 msgid "Show origin of this line" msgstr "Mostra l'origen d'aquesta línia" -#: gitk:2668 msgid "Run git gui blame on this line" msgstr "Executa git gui blame en aquesta línia" -#: gitk:3014 msgid "" "\n" "Gitk - a commit viewer for git\n" @@ -376,317 +289,245 @@ "\n" "Useu-lo i redistribuïu-lo sota els termes de la Llicència Pública General GNU" -#: gitk:3022 gitk:3089 gitk:9857 msgid "Close" msgstr "Tanca" -#: gitk:3043 msgid "Gitk key bindings" msgstr "Associacions de tecles del Gitk" -#: gitk:3046 msgid "Gitk key bindings:" msgstr "Associacions de tecles del Gitk:" -#: gitk:3048 #, tcl-format msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\tSurt" -#: gitk:3049 #, tcl-format msgid "<%s-W>\t\tClose window" msgstr "<%s-W>\t\tTanca la finestra" -#: gitk:3050 msgid "<Home>\t\tMove to first commit" msgstr "<Inici>\t\tVés a la primera comissió" -#: gitk:3051 msgid "<End>\t\tMove to last commit" msgstr "<Fi>\t\tVés a l'última comissió" -#: gitk:3052 msgid "<Up>, p, k\tMove up one commit" msgstr "<Amunt>, p, k\tMou-te cap amunt per una comissió" -#: gitk:3053 msgid "<Down>, n, j\tMove down one commit" msgstr "<Avall>, n, j\tMou-te cap avall per una comissió" -#: gitk:3054 msgid "<Left>, z, h\tGo back in history list" msgstr "<Esquerra>, z, h\tRetrocedeix en la llista d'història" -#: gitk:3055 msgid "<Right>, x, l\tGo forward in history list" msgstr "<Dreta>, x, l\tAvança en la llista d'història" -#: gitk:3056 #, tcl-format msgid "<%s-n>\tGo to n-th parent of current commit in history list" msgstr "" "<%s-n>\tVés a l'enèsima mare de la comissió actual en la llista d'història" -#: gitk:3057 msgid "<PageUp>\tMove up one page in commit list" msgstr "<RePàg>\tMou-te cap amunt per una pàgina en la llista de comissions" -#: gitk:3058 msgid "<PageDown>\tMove down one page in commit list" msgstr "<AvPàg>\tMou-te cap avall per una pàgina en la llista de comissions" -#: gitk:3059 #, tcl-format msgid "<%s-Home>\tScroll to top of commit list" msgstr "<%s-Inici>\tDesplaça't a la part superior de la llista de comissions" -#: gitk:3060 #, tcl-format msgid "<%s-End>\tScroll to bottom of commit list" msgstr "<%s-Fi>\tDesplaça't a la part inferior de la llista de comissions" -#: gitk:3061 #, tcl-format msgid "<%s-Up>\tScroll commit list up one line" msgstr "<%s-Amunt>\tDesplaça la llista de comissions cap amunt per una línia" -#: gitk:3062 #, tcl-format msgid "<%s-Down>\tScroll commit list down one line" msgstr "<%s-Avall>\tDesplaça la llista de comissions cap avall per una línia" -#: gitk:3063 #, tcl-format msgid "<%s-PageUp>\tScroll commit list up one page" msgstr "<%s-RePàg>\tDesplaça la llista de comissions cap amunt per una pàgina" -#: gitk:3064 #, tcl-format msgid "<%s-PageDown>\tScroll commit list down one page" msgstr "<%s-AvPàg>\tDesplaça la llista de comissions cap avall per una pàgina" -#: gitk:3065 msgid "<Shift-Up>\tFind backwards (upwards, later commits)" msgstr "<Maj-Amunt>\tCerca cap enrere (cap amunt, les comissions més noves)" -#: gitk:3066 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" msgstr "<Maj-Avall>\tCerca cap endavant (cap avall, les comissions més velles)" -#: gitk:3067 msgid "<Delete>, b\tScroll diff view up one page" msgstr "<Supr>, b\tDesplaça la vista de diferència cap amunt per una pàgina" -#: gitk:3068 msgid "<Backspace>\tScroll diff view up one page" msgstr "<Retrocés>\tDesplaça la vista de diferència cap amunt per una pàgina" -#: gitk:3069 msgid "<Space>\t\tScroll diff view down one page" msgstr "<Espai>\t\tDesplaça la vista de diferència cap avall per una pàgina" -#: gitk:3070 msgid "u\t\tScroll diff view up 18 lines" msgstr "u\t\tDesplaça la vista de diferència cap amunt per 18 línies" -#: gitk:3071 msgid "d\t\tScroll diff view down 18 lines" msgstr "d\t\tDesplaça la vista de diferència cap avall per 18 línies" -#: gitk:3072 #, tcl-format msgid "<%s-F>\t\tFind" msgstr "<%s-F>\t\tCerca" -#: gitk:3073 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" msgstr "<%s-G>\t\tMou-te a la propera coincidència de la cerca" -#: gitk:3074 msgid "<Return>\tMove to next find hit" msgstr "<Retorn>\tMou-te a la propera coincidència de la cerca" -#: gitk:3075 msgid "g\t\tGo to commit" msgstr "g\t\tVés a l'última comissió" -#: gitk:3076 msgid "/\t\tFocus the search box" msgstr "/\t\tPosa el focus a la caixa de cerca" -#: gitk:3077 msgid "?\t\tMove to previous find hit" msgstr "?\t\tMou a la coincidència prèvia de la cerca" -#: gitk:3078 msgid "f\t\tScroll diff view to next file" msgstr "f\t\tDesplaça la vista de diferència al proper fitxer" -#: gitk:3079 #, tcl-format msgid "<%s-S>\t\tSearch for next hit in diff view" msgstr "<%s-S>\t\tCerca la propera coincidència en la vista de diferència" -#: gitk:3080 #, tcl-format msgid "<%s-R>\t\tSearch for previous hit in diff view" msgstr "<%s-R>\t\tCerca la coincidència prèvia en la vista de diferència" -#: gitk:3081 #, tcl-format msgid "<%s-KP+>\tIncrease font size" msgstr "<%s-KP+>\tAugmenta la mida de lletra" -#: gitk:3082 #, tcl-format msgid "<%s-plus>\tIncrease font size" msgstr "<%s-més>\tAugmenta la mida de lletra" -#: gitk:3083 #, tcl-format msgid "<%s-KP->\tDecrease font size" msgstr "<%s-KP->\tDisminueix la mida de lletra" -#: gitk:3084 #, tcl-format msgid "<%s-minus>\tDecrease font size" msgstr "<%s-menys>\tDisminueix la mida de lletra" -#: gitk:3085 msgid "<F5>\t\tUpdate" msgstr "<F5>\t\tActualitza" -#: gitk:3550 gitk:3559 #, tcl-format msgid "Error creating temporary directory %s:" msgstr "Error en crear el directori temporal %s:" -#: gitk:3572 #, tcl-format msgid "Error getting \"%s\" from %s:" msgstr "Error en obtenir \"%s\" de %s:" -#: gitk:3635 msgid "command failed:" msgstr "l'ordre ha fallat:" -#: gitk:3784 msgid "No such commit" msgstr "Cap comissió així" -#: gitk:3798 msgid "git gui blame: command failed:" msgstr "git gui blame: l'ordre ha fallat:" -#: gitk:3829 #, tcl-format msgid "Couldn't read merge head: %s" msgstr "No s'ha pogut llegir el cap de fusió: %s" -#: gitk:3837 #, tcl-format msgid "Error reading index: %s" msgstr "Error en llegir l'índex: %s" -#: gitk:3862 #, tcl-format msgid "Couldn't start git blame: %s" msgstr "No s'ha pogut iniciar el git blame: %s" -#: gitk:3865 gitk:6754 msgid "Searching" msgstr "Cercant" -#: gitk:3897 #, tcl-format msgid "Error running git blame: %s" msgstr "Error en executar el git blame: %s" -#: gitk:3925 #, tcl-format msgid "That line comes from commit %s, which is not in this view" msgstr "" "Aquella línia ve de la comissió %s, la qual no és en aquesta visualització" -#: gitk:3939 msgid "External diff viewer failed:" msgstr "El visualitzador de diferència extern ha fallat:" -#: gitk:4070 msgid "Gitk view definition" msgstr "Definició de vista del Gitk" -#: gitk:4074 msgid "Remember this view" msgstr "Recorda aquesta vista" -#: gitk:4075 msgid "References (space separated list):" msgstr "Referències (llista separada per espais)" -#: gitk:4076 msgid "Branches & tags:" msgstr "Branques i etiquetes:" -#: gitk:4077 msgid "All refs" msgstr "Totes les referències" -#: gitk:4078 msgid "All (local) branches" msgstr "Totes les branques (locals)" -#: gitk:4079 msgid "All tags" msgstr "Totes les etiquetes" -#: gitk:4080 msgid "All remote-tracking branches" msgstr "Totes les branques amb seguiment remot" -#: gitk:4081 msgid "Commit Info (regular expressions):" msgstr "Informació de comissió (expressions regulars):" -#: gitk:4082 msgid "Author:" msgstr "Autor:" -#: gitk:4083 msgid "Committer:" msgstr "Comitent:" -#: gitk:4084 msgid "Commit Message:" msgstr "Missatge de comissió:" -#: gitk:4085 msgid "Matches all Commit Info criteria" msgstr "Coincideix amb tots els criteris d'informació de comissió" -#: gitk:4086 msgid "Matches no Commit Info criteria" msgstr "No coincideix amb cap criteri d'informació de comissió" -#: gitk:4087 msgid "Changes to Files:" msgstr "Canvis als fitxers:" -#: gitk:4088 msgid "Fixed String" msgstr "Cadena fixa" -#: gitk:4089 msgid "Regular Expression" msgstr "Expressió regular" -#: gitk:4090 msgid "Search string:" msgstr "Cadena de cerca:" -#: gitk:4091 msgid "" "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" @@ -694,201 +535,153 @@ "Dates de comissió (\"fa 2 setmanes\", \"2009-03-17 15:27:38\", \"17 abr 2009 " "15:27:38\"):" -#: gitk:4092 msgid "Since:" msgstr "Des de:" -#: gitk:4093 msgid "Until:" msgstr "Fins:" -#: gitk:4094 msgid "Limit and/or skip a number of revisions (positive integer):" msgstr "Limita o salta un nombre de revisions (nombre enter positiu)" -#: gitk:4095 msgid "Number to show:" msgstr "Nombre a mostrar:" -#: gitk:4096 msgid "Number to skip:" msgstr "Nombre a saltar:" -#: gitk:4097 msgid "Miscellaneous options:" msgstr "Opcions miscel·lànies:" -#: gitk:4098 msgid "Strictly sort by date" msgstr "Ordena estrictament per data" -#: gitk:4099 msgid "Mark branch sides" msgstr "Marca els costats de les branques" -#: gitk:4100 msgid "Limit to first parent" msgstr "Limita a la primera mare" -#: gitk:4101 msgid "Simple history" msgstr "Història senzilla" -#: gitk:4102 msgid "Additional arguments to git log:" msgstr "Paràmetres addicionals al git log:" -#: gitk:4103 msgid "Enter files and directories to include, one per line:" msgstr "Introduïu els fitxers i directoris a incloure, un per línia:" -#: gitk:4104 msgid "Command to generate more commits to include:" msgstr "Ordre per a generar més comissions a incloure:" -#: gitk:4228 msgid "Gitk: edit view" msgstr "Gitk: vista d'edició" -#: gitk:4236 msgid "-- criteria for selecting revisions" msgstr "-- criteris per a seleccionar les revisions" -#: gitk:4241 msgid "View Name" msgstr "Nom de vista" -#: gitk:4316 msgid "Apply (F5)" msgstr "Aplica (F5)" -#: gitk:4354 msgid "Error in commit selection arguments:" msgstr "Error en els paràmetres de selecció de comissions:" -#: gitk:4409 gitk:4462 gitk:4924 gitk:4938 gitk:6208 gitk:12373 gitk:12374 msgid "None" msgstr "Cap" -#: gitk:5021 gitk:5026 msgid "Descendant" msgstr "Descendent" -#: gitk:5022 msgid "Not descendant" msgstr "No descendent" -#: gitk:5029 gitk:5034 msgid "Ancestor" msgstr "Avantpassat" -#: gitk:5030 msgid "Not ancestor" msgstr "No avantpassat" -#: gitk:5324 msgid "Local changes checked in to index but not committed" msgstr "Canvis locals registrats en l'índex però no comesos" -#: gitk:5360 msgid "Local uncommitted changes, not checked in to index" msgstr "Canvis locals sense cometre, no registrats en l'índex" -#: gitk:7134 msgid "and many more" msgstr "i moltes més" -#: gitk:7137 msgid "many" msgstr "moltes" -#: gitk:7328 msgid "Tags:" msgstr "Etiquetes:" -#: gitk:7345 gitk:7351 gitk:8825 msgid "Parent" msgstr "Mare" -#: gitk:7356 msgid "Child" msgstr "Filla" -#: gitk:7365 msgid "Branch" msgstr "Branca" -#: gitk:7368 msgid "Follows" msgstr "Segueix" -#: gitk:7371 msgid "Precedes" msgstr "Precedeix" -#: gitk:7966 #, tcl-format msgid "Error getting diffs: %s" msgstr "Error en obtenir les diferències: %s" -#: gitk:8650 msgid "Goto:" msgstr "Vés a:" -#: gitk:8671 #, tcl-format msgid "Short SHA1 id %s is ambiguous" msgstr "L'id SHA1 curta %s és ambigua" -#: gitk:8678 #, tcl-format msgid "Revision %s is not known" msgstr "La revisió %s és desconeguda" -#: gitk:8688 #, tcl-format msgid "SHA1 id %s is not known" msgstr "L'id SHA1 %s és desconeguda" -#: gitk:8690 #, tcl-format msgid "Revision %s is not in the current view" msgstr "La revisió %s no és en la vista actual" -#: gitk:8832 gitk:8847 msgid "Date" msgstr "Data" -#: gitk:8835 msgid "Children" msgstr "Filles" -#: gitk:8898 #, tcl-format msgid "Reset %s branch to here" msgstr "Restableix la branca %s aquí" -#: gitk:8900 msgid "Detached head: can't reset" msgstr "Cap separat: no es pot restablir" -#: gitk:9005 gitk:9011 msgid "Skipping merge commit " msgstr "Saltant la comissió de fusió " -#: gitk:9020 gitk:9025 msgid "Error getting patch ID for " msgstr "Error en obtenir l'ID de pedaç de " -#: gitk:9021 gitk:9026 msgid " - stopping\n" msgstr " - aturant\n" -#: gitk:9031 gitk:9034 gitk:9042 gitk:9056 gitk:9065 msgid "Commit " msgstr "Comissió " -#: gitk:9035 msgid "" " is the same patch as\n" " " @@ -896,7 +689,6 @@ " és el mateix pedaç que\n" " " -#: gitk:9043 msgid "" " differs from\n" " " @@ -904,7 +696,6 @@ " difereix de\n" " " -#: gitk:9045 msgid "" "Diff of commits:\n" "\n" @@ -912,131 +703,101 @@ "Diferència entre comissions:\n" "\n" -#: gitk:9057 gitk:9066 #, tcl-format msgid " has %s children - stopping\n" msgstr " té %s filles - aturant\n" -#: gitk:9085 #, tcl-format msgid "Error writing commit to file: %s" msgstr "Error en escriure la comissió al fitxer: %s" -#: gitk:9091 #, tcl-format msgid "Error diffing commits: %s" msgstr "Error en diferenciar les comissions: %s" -#: gitk:9137 msgid "Top" msgstr "Part superior" -#: gitk:9138 msgid "From" msgstr "De" -#: gitk:9143 msgid "To" msgstr "A" -#: gitk:9167 msgid "Generate patch" msgstr "Genera pedaç" -#: gitk:9169 msgid "From:" msgstr "De:" -#: gitk:9178 msgid "To:" msgstr "A:" -#: gitk:9187 msgid "Reverse" msgstr "Inverteix" -#: gitk:9189 gitk:9385 msgid "Output file:" msgstr "Fitxer de sortida:" -#: gitk:9195 msgid "Generate" msgstr "Genera" -#: gitk:9233 msgid "Error creating patch:" msgstr "Error en crear el pedaç:" -#: gitk:9256 gitk:9373 gitk:9430 msgid "ID:" msgstr "ID:" -#: gitk:9265 msgid "Tag name:" msgstr "Nom d'etiqueta:" -#: gitk:9268 msgid "Tag message is optional" msgstr "El missatge d'etiqueta és opcional" -#: gitk:9270 msgid "Tag message:" msgstr "Missatge d'etiqueta:" -#: gitk:9274 gitk:9439 msgid "Create" msgstr "Crea" -#: gitk:9292 msgid "No tag name specified" msgstr "No s'ha especificat cap nom d'etiqueta" -#: gitk:9296 #, tcl-format msgid "Tag \"%s\" already exists" msgstr "L'etiqueta \"%s\" ja existeix" -#: gitk:9306 msgid "Error creating tag:" msgstr "Error en crear l'etiqueta:" -#: gitk:9382 msgid "Command:" msgstr "Ordre:" -#: gitk:9390 msgid "Write" msgstr "Escriu" -#: gitk:9408 msgid "Error writing commit:" msgstr "Error en escriure la comissió:" -#: gitk:9435 msgid "Name:" msgstr "Nom:" -#: gitk:9458 msgid "Please specify a name for the new branch" msgstr "Si us plau, especifiqueu un nom per a la branca nova" -#: gitk:9463 #, tcl-format msgid "Branch '%s' already exists. Overwrite?" msgstr "La branca '%s' ja existeix. Voleu sobreescriure?" -#: gitk:9530 #, tcl-format msgid "Commit %s is already included in branch %s -- really re-apply it?" msgstr "" "La comissió %s ja està inclosa en la branca %s -- realment voleu tornar a " "aplicar-la?" -#: gitk:9535 msgid "Cherry-picking" msgstr "Recollint cireres" -#: gitk:9544 #, tcl-format msgid "" "Cherry-pick failed because of local changes to file '%s'.\n" @@ -1046,7 +807,6 @@ "Si us plau, cometeu, restabliu o emmagatzemeu els vostres canvis i torneu a " "intentar." -#: gitk:9550 msgid "" "Cherry-pick failed because of merge conflict.\n" "Do you wish to run git citool to resolve it?" @@ -1054,21 +814,17 @@ "El recull de cireres ha fallat a causa d'un conflicte de fusió.\n" "Voleu executar el git citool per a resoldre'l?" -#: gitk:9566 gitk:9624 msgid "No changes committed" msgstr "Cap canvi comès" -#: gitk:9593 #, tcl-format msgid "Commit %s is not included in branch %s -- really revert it?" msgstr "" "La comissió %s no s'inclou en la branca %s -- realment voleu revertir-la?" -#: gitk:9598 msgid "Reverting" msgstr "Revertint" -#: gitk:9606 #, tcl-format msgid "" "Revert failed because of local changes to the following files:%s Please " @@ -1078,7 +834,6 @@ "plau, cometeu, restabliu o emmagatzemeu els vostres canvis i torneu-ho a " "intentar." -#: gitk:9610 msgid "" "Revert failed because of merge conflict.\n" " Do you wish to run git citool to resolve it?" @@ -1086,28 +841,22 @@ "La reversió ha fallat a causa d'un conflicte de fusió.\n" " Voleu executar el git citool per a resoldre'l?" -#: gitk:9653 msgid "Confirm reset" msgstr "Confirma el restabliment" -#: gitk:9655 #, tcl-format msgid "Reset branch %s to %s?" msgstr "Voleu restablir la branca %s a %s?" -#: gitk:9657 msgid "Reset type:" msgstr "Tipus de restabliment:" -#: gitk:9660 msgid "Soft: Leave working tree and index untouched" msgstr "Suau: Deixa l'arbre de treball i l'índex sense tocar" -#: gitk:9663 msgid "Mixed: Leave working tree untouched, reset index" msgstr "Mixt: Deixa l'arbre de treball sense tocar, restableix l'índex" -#: gitk:9666 msgid "" "Hard: Reset working tree and index\n" "(discard ALL local changes)" @@ -1115,19 +864,15 @@ "Dur: Restableix l'arbre de treball i l'índex\n" "(descarta TOTS els canvis locals)" -#: gitk:9683 msgid "Resetting" msgstr "Restablint" -#: gitk:9743 msgid "Checking out" msgstr "Agafant" -#: gitk:9796 msgid "Cannot delete the currently checked-out branch" msgstr "No es pot suprimir la branca actualment agafada" -#: gitk:9802 #, tcl-format msgid "" "The commits on branch %s aren't on any other branch.\n" @@ -1136,16 +881,13 @@ "Les comissions en la branca %s no són en cap altra branca.\n" "Realment voleu suprimir la branca %s?" -#: gitk:9833 #, tcl-format msgid "Tags and heads: %s" msgstr "Etiquetes i caps: %s" -#: gitk:9850 msgid "Filter" msgstr "Filtre" -#: gitk:10146 msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." @@ -1153,201 +895,152 @@ "Error en llegir la informació de topologia de comissió; la informació sobre " "branques i etiquetes precedents/següents serà incompleta." -#: gitk:11123 msgid "Tag" msgstr "Etiqueta" -#: gitk:11127 msgid "Id" msgstr "Id" -#: gitk:11210 msgid "Gitk font chooser" msgstr "Selector de tipus de lletra del Gitk" -#: gitk:11227 msgid "B" msgstr "B" -#: gitk:11230 msgid "I" msgstr "I" -#: gitk:11348 msgid "Commit list display options" msgstr "Opcions de visualització de la llista de comissions" -#: gitk:11351 msgid "Maximum graph width (lines)" msgstr "Amplada màxima del gràfic (línies)" -#: gitk:11355 #, no-tcl-format msgid "Maximum graph width (% of pane)" msgstr "Amplada màxima del gràfic (% del panell)" -#: gitk:11358 msgid "Show local changes" msgstr "Mostra els canvis locals" -#: gitk:11361 msgid "Auto-select SHA1 (length)" msgstr "Selecciona automàticament l'SHA1 (longitud)" -#: gitk:11365 msgid "Hide remote refs" msgstr "Amaga les referències remotes" -#: gitk:11369 msgid "Diff display options" msgstr "Opcions de visualització de diferència" -#: gitk:11371 msgid "Tab spacing" msgstr "Espaiat de tabulació" -#: gitk:11374 msgid "Display nearby tags/heads" msgstr "Mostra etiquetes/caps propers" -#: gitk:11377 msgid "Maximum # tags/heads to show" msgstr "Nombre màxim d'etiquetes/caps a mostrar" -#: gitk:11380 msgid "Limit diffs to listed paths" msgstr "Limita les diferències als camins llistats" -#: gitk:11383 msgid "Support per-file encodings" msgstr "Admet codificacions específiques per a cada fitxer" -#: gitk:11389 gitk:11536 msgid "External diff tool" msgstr "Eina de diferència externa" -#: gitk:11390 msgid "Choose..." msgstr "Trieu..." -#: gitk:11395 msgid "General options" msgstr "Opcions generals" -#: gitk:11398 msgid "Use themed widgets" msgstr "Usa els ginys tematitzats" -#: gitk:11400 msgid "(change requires restart)" msgstr "(el canvi requereix reiniciar)" -#: gitk:11402 msgid "(currently unavailable)" msgstr "(actualment no disponible)" -#: gitk:11413 msgid "Colors: press to choose" msgstr "Colors: pressiona per a triar" -#: gitk:11416 msgid "Interface" msgstr "Interfície" -#: gitk:11417 msgid "interface" msgstr "interfície" -#: gitk:11420 msgid "Background" msgstr "Fons" -#: gitk:11421 gitk:11451 msgid "background" msgstr "fons" -#: gitk:11424 msgid "Foreground" msgstr "Primer pla" -#: gitk:11425 msgid "foreground" msgstr "primer pla" -#: gitk:11428 msgid "Diff: old lines" msgstr "Diferència: línies velles" -#: gitk:11429 msgid "diff old lines" msgstr "diferencia les línies velles" -#: gitk:11433 msgid "Diff: new lines" msgstr "Diferència: línies noves" -#: gitk:11434 msgid "diff new lines" msgstr "diferencia les línies noves" -#: gitk:11438 msgid "Diff: hunk header" msgstr "Diferència: capçalera de tros" -#: gitk:11440 msgid "diff hunk header" msgstr "diferencia la capçalera de tros" -#: gitk:11444 msgid "Marked line bg" msgstr "Fons de la línia marcada" -#: gitk:11446 msgid "marked line background" msgstr "fons de la línia marcada" -#: gitk:11450 msgid "Select bg" msgstr "Fons de la selecció" -#: gitk:11459 msgid "Fonts: press to choose" msgstr "Tipus de lletra: pressiona per a triar" -#: gitk:11461 msgid "Main font" msgstr "Tipus de lletra principal" -#: gitk:11462 msgid "Diff display font" msgstr "Tipus de lletra de visualització de diferència" -#: gitk:11463 msgid "User interface font" msgstr "Tipus de lletra de la interfície d'usuari" -#: gitk:11485 msgid "Gitk preferences" msgstr "Preferències del Gitk" -#: gitk:11494 msgid "General" msgstr "General" -#: gitk:11495 msgid "Colors" msgstr "Colors" -#: gitk:11496 msgid "Fonts" msgstr "Tipus de lletra" -#: gitk:11546 #, tcl-format msgid "Gitk: choose color for %s" msgstr "Gitk: tria el color per a %s" -#: gitk:12059 msgid "" "Sorry, gitk cannot run with this version of Tcl/Tk.\n" " Gitk requires at least Tcl/Tk 8.4." @@ -1355,15 +1048,12 @@ "Perdó, el gitk no pot executar-se amb aquesta versió de Tcl/Tk.\n" " El Gitk requereix com a mínim el Tcl/Tk 8.4." -#: gitk:12269 msgid "Cannot find a git repository here." msgstr "No es pot trobar cap dipòsit de git aquí." -#: gitk:12316 #, tcl-format msgid "Ambiguous argument '%s': both revision and filename" msgstr "Paràmetre ambigu '%s': és tant revisió com nom de fitxer" -#: gitk:12328 msgid "Bad arguments to gitk:" msgstr "Paràmetres dolents al gitk:"
diff --git a/gitk-git/po/de.po b/gitk-git/po/de.po index 5db3824..fea18fa 100644 --- a/gitk-git/po/de.po +++ b/gitk-git/po/de.po
@@ -6,7 +6,7 @@ # Frederik Schwarzer <schwarzerf@gmail.com>, 2008. msgid "" msgstr "" -"Project-Id-Version: git-gui\n" +"Project-Id-Version: Gitk\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-05-17 14:32+1000\n" "PO-Revision-Date: 2015-10-20 14:20+0200\n" @@ -17,33 +17,26 @@ "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: gitk:140 msgid "Couldn't get list of unmerged files:" msgstr "Liste der nicht zusammengeführten Dateien nicht gefunden:" -#: gitk:212 gitk:2381 msgid "Color words" msgstr "Wörter einfärben" -#: gitk:217 gitk:2381 gitk:8220 gitk:8253 msgid "Markup words" msgstr "Wörter kennzeichnen" -#: gitk:324 msgid "Error parsing revisions:" msgstr "Fehler beim Laden der Versionen:" -#: gitk:380 msgid "Error executing --argscmd command:" msgstr "Fehler beim Ausführen des --argscmd-Kommandos:" -#: gitk:393 msgid "No files selected: --merge specified but no files are unmerged." msgstr "" "Keine Dateien ausgewählt: Es wurde --merge angegeben, aber es existieren " "keine nicht zusammengeführten Dateien." -#: gitk:396 msgid "" "No files selected: --merge specified but no unmerged files are within file " "limit." @@ -51,314 +44,234 @@ "Keine Dateien ausgewählt: Es wurde --merge angegeben, aber es sind keine " "nicht zusammengeführten Dateien in der Dateiauswahl." -#: gitk:418 gitk:566 msgid "Error executing git log:" msgstr "Fehler beim Ausführen von »git log«:" -#: gitk:436 gitk:582 msgid "Reading" msgstr "Lesen" -#: gitk:496 gitk:4525 msgid "Reading commits..." msgstr "Versionen werden gelesen ..." -#: gitk:499 gitk:1637 gitk:4528 msgid "No commits selected" msgstr "Keine Versionen ausgewählt" -#: gitk:1445 gitk:4045 gitk:12432 msgid "Command line" msgstr "Kommandozeile" -#: gitk:1511 msgid "Can't parse git log output:" msgstr "Ausgabe von »git log« kann nicht erkannt werden:" -#: gitk:1740 msgid "No commit information available" msgstr "Keine Versionsinformation verfügbar" -#: gitk:1903 gitk:1932 gitk:4315 gitk:9669 gitk:11241 gitk:11521 msgid "OK" msgstr "Ok" -#: gitk:1934 gitk:4317 gitk:9196 gitk:9275 gitk:9391 gitk:9440 gitk:9671 -#: gitk:11242 gitk:11522 msgid "Cancel" msgstr "Abbrechen" -#: gitk:2069 msgid "&Update" msgstr "&Aktualisieren" -#: gitk:2070 msgid "&Reload" msgstr "&Neu laden" -#: gitk:2071 msgid "Reread re&ferences" msgstr "&Zweige neu laden" -#: gitk:2072 msgid "&List references" msgstr "Zweige/Markierungen auf&listen" -#: gitk:2074 msgid "Start git &gui" msgstr "»git &gui« starten" -#: gitk:2076 msgid "&Quit" msgstr "&Beenden" -#: gitk:2068 msgid "&File" msgstr "&Datei" -#: gitk:2080 msgid "&Preferences" msgstr "&Einstellungen" -#: gitk:2079 msgid "&Edit" msgstr "&Bearbeiten" -#: gitk:2084 msgid "&New view..." msgstr "&Neue Ansicht ..." -#: gitk:2085 msgid "&Edit view..." msgstr "Ansicht &bearbeiten ..." -#: gitk:2086 msgid "&Delete view" msgstr "Ansicht &entfernen" -#: gitk:2088 gitk:4043 msgid "&All files" msgstr "&Alle Dateien" -#: gitk:2083 gitk:4067 msgid "&View" msgstr "&Ansicht" -#: gitk:2093 gitk:2103 gitk:3012 msgid "&About gitk" msgstr "Über &gitk" -#: gitk:2094 gitk:2108 msgid "&Key bindings" msgstr "&Tastenkürzel" -#: gitk:2092 gitk:2107 msgid "&Help" msgstr "&Hilfe" -#: gitk:2185 gitk:8652 msgid "SHA1 ID:" msgstr "SHA1 ID:" -#: gitk:2229 msgid "Row" msgstr "Zeile" -#: gitk:2267 msgid "Find" msgstr "Suche" -#: gitk:2295 msgid "commit" msgstr "Version nach" -#: gitk:2299 gitk:2301 gitk:4687 gitk:4710 gitk:4734 gitk:6755 gitk:6827 -#: gitk:6912 msgid "containing:" msgstr "Beschreibung:" -#: gitk:2302 gitk:3526 gitk:3531 gitk:4763 msgid "touching paths:" msgstr "Dateien:" -#: gitk:2303 gitk:4777 msgid "adding/removing string:" msgstr "Änderungen:" -#: gitk:2304 gitk:4779 msgid "changing lines matching:" msgstr "Geänderte Zeilen entsprechen:" -#: gitk:2313 gitk:2315 gitk:4766 msgid "Exact" msgstr "Exakt" -#: gitk:2315 gitk:4854 gitk:6723 msgid "IgnCase" msgstr "Kein Groß/Klein" -#: gitk:2315 gitk:4736 gitk:4852 gitk:6719 msgid "Regexp" msgstr "Regexp" -#: gitk:2317 gitk:2318 gitk:4874 gitk:4904 gitk:4911 gitk:6848 gitk:6916 msgid "All fields" msgstr "Alle Felder" -#: gitk:2318 gitk:4871 gitk:4904 gitk:6786 msgid "Headline" msgstr "Überschrift" -#: gitk:2319 gitk:4871 gitk:6786 gitk:6916 gitk:7389 msgid "Comments" msgstr "Beschreibung" -#: gitk:2319 gitk:4871 gitk:4876 gitk:4911 gitk:6786 gitk:7324 gitk:8830 -#: gitk:8845 msgid "Author" msgstr "Autor" -#: gitk:2319 gitk:4871 gitk:6786 gitk:7326 msgid "Committer" msgstr "Eintragender" -#: gitk:2350 msgid "Search" msgstr "Suchen" -#: gitk:2358 msgid "Diff" msgstr "Vergleich" -#: gitk:2360 msgid "Old version" msgstr "Alte Version" -#: gitk:2362 msgid "New version" msgstr "Neue Version" -#: gitk:2364 msgid "Lines of context" msgstr "Kontextzeilen" -#: gitk:2374 msgid "Ignore space change" msgstr "Leerzeichenänderungen ignorieren" -#: gitk:2378 gitk:2380 gitk:7959 gitk:8206 msgid "Line diff" msgstr "Zeilenunterschied" -#: gitk:2445 msgid "Patch" msgstr "Patch" -#: gitk:2447 msgid "Tree" msgstr "Baum" -#: gitk:2617 gitk:2637 msgid "Diff this -> selected" msgstr "Vergleich: diese -> gewählte" -#: gitk:2618 gitk:2638 msgid "Diff selected -> this" msgstr "Vergleich: gewählte -> diese" -#: gitk:2619 gitk:2639 msgid "Make patch" msgstr "Patch erstellen" -#: gitk:2620 gitk:9254 msgid "Create tag" msgstr "Markierung erstellen" -#: gitk:2621 gitk:9371 msgid "Write commit to file" msgstr "Version in Datei schreiben" -#: gitk:2622 gitk:9428 msgid "Create new branch" msgstr "Neuen Zweig erstellen" -#: gitk:2623 msgid "Cherry-pick this commit" msgstr "Diese Version pflücken" -#: gitk:2624 msgid "Reset HEAD branch to here" msgstr "HEAD-Zweig auf diese Version zurücksetzen" -#: gitk:2625 msgid "Mark this commit" msgstr "Lesezeichen setzen" -#: gitk:2626 msgid "Return to mark" msgstr "Zum Lesezeichen" -#: gitk:2627 msgid "Find descendant of this and mark" msgstr "Abkömmling von Lesezeichen und dieser Version finden" -#: gitk:2628 msgid "Compare with marked commit" msgstr "Mit Lesezeichen vergleichen" -#: gitk:2629 gitk:2640 msgid "Diff this -> marked commit" msgstr "Vergleich: diese -> gewählte Version" -#: gitk:2630 gitk:2641 msgid "Diff marked commit -> this" msgstr "Vergleich: gewählte -> diese Version" -#: gitk:2631 msgid "Revert this commit" msgstr "Version umkehren" -#: gitk:2647 msgid "Check out this branch" msgstr "Auf diesen Zweig umstellen" -#: gitk:2648 msgid "Remove this branch" msgstr "Zweig löschen" -#: gitk:2649 msgid "Copy branch name" msgstr "Zweigname kopieren" -#: gitk:2656 msgid "Highlight this too" msgstr "Diesen auch hervorheben" -#: gitk:2657 msgid "Highlight this only" msgstr "Nur diesen hervorheben" -#: gitk:2658 msgid "External diff" msgstr "Externes Diff-Programm" -#: gitk:2659 msgid "Blame parent commit" msgstr "Annotieren der Elternversion" -#: gitk:2660 msgid "Copy path" msgstr "Pfad kopieren" -#: gitk:2667 msgid "Show origin of this line" msgstr "Herkunft dieser Zeile anzeigen" -#: gitk:2668 msgid "Run git gui blame on this line" msgstr "Diese Zeile annotieren (»git gui blame«)" -#: gitk:3014 msgid "" "\n" "Gitk - a commit viewer for git\n" @@ -375,517 +288,397 @@ "Benutzung und Weiterverbreitung gemäß den Bedingungen der GNU General Public " "License" -#: gitk:3022 gitk:3089 gitk:9857 msgid "Close" msgstr "Schließen" -#: gitk:3043 msgid "Gitk key bindings" msgstr "Gitk-Tastaturbelegung" -#: gitk:3046 msgid "Gitk key bindings:" msgstr "Gitk-Tastaturbelegung:" -#: gitk:3048 #, tcl-format msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\tBeenden" -#: gitk:3049 #, tcl-format msgid "<%s-W>\t\tClose window" msgstr "<%s-F>\t\tFenster schließen" -#: gitk:3050 msgid "<Home>\t\tMove to first commit" msgstr "<Pos1>\t\tZur neuesten Version springen" -#: gitk:3051 msgid "<End>\t\tMove to last commit" msgstr "<Ende>\t\tZur ältesten Version springen" -#: gitk:3052 msgid "<Up>, p, k\tMove up one commit" msgstr "<Hoch>, p, k\tNächste neuere Version" -#: gitk:3053 msgid "<Down>, n, j\tMove down one commit" msgstr "<Runter>, n, j\tNächste ältere Version" -#: gitk:3054 msgid "<Left>, z, h\tGo back in history list" msgstr "<Links>, z, h\tEine Version zurückgehen" -#: gitk:3055 msgid "<Right>, x, l\tGo forward in history list" msgstr "<Rechts>, x, l\tEine Version weitergehen" -#: gitk:3056 #, tcl-format msgid "<%s-n>\tGo to n-th parent of current commit in history list" msgstr "<%s-n>\tZu n-ter Elternversion in Versionshistorie springen" -#: gitk:3057 msgid "<PageUp>\tMove up one page in commit list" msgstr "<BildHoch>\tEine Seite nach oben blättern" -#: gitk:3058 msgid "<PageDown>\tMove down one page in commit list" msgstr "<BildRunter>\tEine Seite nach unten blättern" -#: gitk:3059 #, tcl-format msgid "<%s-Home>\tScroll to top of commit list" msgstr "<%s-Pos1>\tZum oberen Ende der Versionsliste blättern" -#: gitk:3060 #, tcl-format msgid "<%s-End>\tScroll to bottom of commit list" msgstr "<%s-Ende>\tZum unteren Ende der Versionsliste blättern" -#: gitk:3061 #, tcl-format msgid "<%s-Up>\tScroll commit list up one line" msgstr "<%s-Hoch>\tVersionsliste eine Zeile nach oben blättern" -#: gitk:3062 #, tcl-format msgid "<%s-Down>\tScroll commit list down one line" msgstr "<%s-Runter>\tVersionsliste eine Zeile nach unten blättern" -#: gitk:3063 #, tcl-format msgid "<%s-PageUp>\tScroll commit list up one page" msgstr "<%s-BildHoch>\tVersionsliste eine Seite nach oben blättern" -#: gitk:3064 #, tcl-format msgid "<%s-PageDown>\tScroll commit list down one page" msgstr "<%s-BildRunter>\tVersionsliste eine Seite nach unten blättern" -#: gitk:3065 msgid "<Shift-Up>\tFind backwards (upwards, later commits)" msgstr "<Umschalt-Hoch>\tRückwärts suchen (nach oben; neuere Versionen)" -#: gitk:3066 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" msgstr "<Umschalt-Runter> Suchen (nach unten; ältere Versionen)" -#: gitk:3067 msgid "<Delete>, b\tScroll diff view up one page" msgstr "<Entf>, b\t\tVergleich eine Seite nach oben blättern" -#: gitk:3068 msgid "<Backspace>\tScroll diff view up one page" msgstr "<Löschtaste>\tVergleich eine Seite nach oben blättern" -#: gitk:3069 msgid "<Space>\t\tScroll diff view down one page" msgstr "<Leertaste>\tVergleich eine Seite nach unten blättern" -#: gitk:3070 msgid "u\t\tScroll diff view up 18 lines" msgstr "u\t\tVergleich um 18 Zeilen nach oben blättern" -#: gitk:3071 msgid "d\t\tScroll diff view down 18 lines" msgstr "d\t\tVergleich um 18 Zeilen nach unten blättern" -#: gitk:3072 #, tcl-format msgid "<%s-F>\t\tFind" msgstr "<%s-F>\t\tSuchen" -#: gitk:3073 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" msgstr "<%s-G>\t\tWeitersuchen" -#: gitk:3074 msgid "<Return>\tMove to next find hit" msgstr "<Eingabetaste>\tWeitersuchen" -#: gitk:3075 msgid "g\t\tGo to commit" msgstr "g\t\tZu Version springen" -#: gitk:3076 msgid "/\t\tFocus the search box" msgstr "/\t\tTastaturfokus ins Suchfeld" -#: gitk:3077 msgid "?\t\tMove to previous find hit" msgstr "?\t\tRückwärts weitersuchen" -#: gitk:3078 msgid "f\t\tScroll diff view to next file" msgstr "f\t\tVergleich zur nächsten Datei blättern" -#: gitk:3079 #, tcl-format msgid "<%s-S>\t\tSearch for next hit in diff view" msgstr "<%s-S>\t\tWeitersuchen im Vergleich" -#: gitk:3080 #, tcl-format msgid "<%s-R>\t\tSearch for previous hit in diff view" msgstr "<%s-R>\t\tRückwärts weitersuchen im Vergleich" -#: gitk:3081 #, tcl-format msgid "<%s-KP+>\tIncrease font size" msgstr "<%s-Nummerblock-Plus>\tSchrift vergrößern" -#: gitk:3082 #, tcl-format msgid "<%s-plus>\tIncrease font size" msgstr "<%s-Plus>\tSchrift vergrößern" -#: gitk:3083 #, tcl-format msgid "<%s-KP->\tDecrease font size" msgstr "<%s-Nummernblock-Minus> Schrift verkleinern" -#: gitk:3084 #, tcl-format msgid "<%s-minus>\tDecrease font size" msgstr "<%s-Minus>\tSchrift verkleinern" -#: gitk:3085 msgid "<F5>\t\tUpdate" msgstr "<F5>\t\tAktualisieren" -#: gitk:3550 gitk:3559 #, tcl-format msgid "Error creating temporary directory %s:" msgstr "Fehler beim Erzeugen des temporären Verzeichnisses »%s«:" -#: gitk:3572 #, tcl-format msgid "Error getting \"%s\" from %s:" msgstr "Fehler beim Holen von »%s« von »%s«:" -#: gitk:3635 msgid "command failed:" msgstr "Kommando fehlgeschlagen:" -#: gitk:3784 msgid "No such commit" msgstr "Version nicht gefunden" -#: gitk:3798 msgid "git gui blame: command failed:" msgstr "git gui blame: Kommando fehlgeschlagen:" -#: gitk:3829 #, tcl-format msgid "Couldn't read merge head: %s" msgstr "Zusammenführungs-Spitze konnte nicht gelesen werden: %s" -#: gitk:3837 #, tcl-format msgid "Error reading index: %s" msgstr "Fehler beim Lesen der Bereitstellung (»index«): %s" -#: gitk:3862 #, tcl-format msgid "Couldn't start git blame: %s" msgstr "»git blame« konnte nicht gestartet werden: %s" -#: gitk:3865 gitk:6754 msgid "Searching" msgstr "Suchen" -#: gitk:3897 #, tcl-format msgid "Error running git blame: %s" msgstr "Fehler beim Ausführen von »git blame«: %s" -#: gitk:3925 #, tcl-format msgid "That line comes from commit %s, which is not in this view" msgstr "" "Diese Zeile stammt aus Version %s, die nicht in dieser Ansicht gezeigt wird" -#: gitk:3939 msgid "External diff viewer failed:" msgstr "Externes Diff-Programm fehlgeschlagen:" -#: gitk:4070 msgid "Gitk view definition" msgstr "Gitk-Ansichten" -#: gitk:4074 msgid "Remember this view" msgstr "Diese Ansicht speichern" -#: gitk:4075 msgid "References (space separated list):" msgstr "Zweige/Markierungen (durch Leerzeichen getrennte Liste):" -#: gitk:4076 msgid "Branches & tags:" msgstr "Zweige/Markierungen:" -#: gitk:4077 msgid "All refs" msgstr "Alle Markierungen und Zweige" -#: gitk:4078 msgid "All (local) branches" msgstr "Alle (lokalen) Zweige" -#: gitk:4079 msgid "All tags" msgstr "Alle Markierungen" -#: gitk:4080 msgid "All remote-tracking branches" msgstr "Alle Übernahmezweige" -#: gitk:4081 msgid "Commit Info (regular expressions):" msgstr "Versionsinformationen (reguläre Ausdrücke):" -#: gitk:4082 msgid "Author:" msgstr "Autor:" -#: gitk:4083 msgid "Committer:" msgstr "Eintragender:" -#: gitk:4084 msgid "Commit Message:" msgstr "Versionsbeschreibung:" -#: gitk:4085 msgid "Matches all Commit Info criteria" msgstr "Alle Versionsinformationen-Kriterien erfüllen" -#: gitk:4086 msgid "Matches no Commit Info criteria" msgstr "keine Versionsinformationen-Kriterien erfüllen" -#: gitk:4087 msgid "Changes to Files:" msgstr "Dateien:" -#: gitk:4088 msgid "Fixed String" msgstr "Zeichenkette" -#: gitk:4089 msgid "Regular Expression" msgstr "Regulärer Ausdruck" -#: gitk:4090 msgid "Search string:" msgstr "Suchausdruck:" -#: gitk:4091 msgid "" "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" msgstr "" "Datum (»2 weeks ago«, »2009-03-17 15:27:38«, »March 17, 2009 15:27:38«)" -#: gitk:4092 msgid "Since:" msgstr "Von:" -#: gitk:4093 msgid "Until:" msgstr "Bis:" -#: gitk:4094 msgid "Limit and/or skip a number of revisions (positive integer):" msgstr "Versionsanzahl begrenzen oder einige überspringen (ganzzahliger Wert):" -#: gitk:4095 msgid "Number to show:" msgstr "Anzeigen:" -#: gitk:4096 msgid "Number to skip:" msgstr "Überspringen:" -#: gitk:4097 msgid "Miscellaneous options:" msgstr "Sonstiges:" -#: gitk:4098 msgid "Strictly sort by date" msgstr "Streng nach Datum sortieren" -#: gitk:4099 msgid "Mark branch sides" msgstr "Zweig-Seiten markieren" -#: gitk:4100 msgid "Limit to first parent" msgstr "Auf erste Elternversion beschränken" -#: gitk:4101 msgid "Simple history" msgstr "Einfache Historie" -#: gitk:4102 msgid "Additional arguments to git log:" msgstr "Zusätzliche Argumente für »git log«:" -#: gitk:4103 msgid "Enter files and directories to include, one per line:" msgstr "Folgende Dateien und Verzeichnisse anzeigen (eine pro Zeile):" -#: gitk:4104 msgid "Command to generate more commits to include:" msgstr "Versionsliste durch folgendes Kommando erzeugen lassen:" -#: gitk:4228 msgid "Gitk: edit view" msgstr "Gitk: Ansicht bearbeiten" -#: gitk:4236 msgid "-- criteria for selecting revisions" msgstr "-- Auswahl der angezeigten Versionen" -#: gitk:4241 msgid "View Name" msgstr "Ansichtsname" -#: gitk:4316 msgid "Apply (F5)" msgstr "Anwenden (F5)" -#: gitk:4354 msgid "Error in commit selection arguments:" msgstr "Fehler in den ausgewählten Versionen:" -#: gitk:4409 gitk:4462 gitk:4924 gitk:4938 gitk:6208 gitk:12373 gitk:12374 msgid "None" msgstr "Keine" -#: gitk:5021 gitk:5026 msgid "Descendant" msgstr "Abkömmling" -#: gitk:5022 msgid "Not descendant" msgstr "Kein Abkömmling" -#: gitk:5029 gitk:5034 msgid "Ancestor" msgstr "Vorgänger" -#: gitk:5030 msgid "Not ancestor" msgstr "Kein Vorgänger" -#: gitk:5324 msgid "Local changes checked in to index but not committed" msgstr "Lokale Änderungen bereitgestellt, aber nicht eingetragen" -#: gitk:5360 msgid "Local uncommitted changes, not checked in to index" msgstr "Lokale Änderungen, nicht bereitgestellt" -#: gitk:7134 msgid "and many more" msgstr "und weitere" -#: gitk:7137 msgid "many" msgstr "viele" -#: gitk:7328 msgid "Tags:" msgstr "Markierungen:" -#: gitk:7345 gitk:7351 gitk:8825 msgid "Parent" msgstr "Eltern" -#: gitk:7356 msgid "Child" msgstr "Kind" -#: gitk:7365 msgid "Branch" msgstr "Zweig" -#: gitk:7368 msgid "Follows" msgstr "Folgt auf" -#: gitk:7371 msgid "Precedes" msgstr "Vorgänger von" -#: gitk:7966 #, tcl-format msgid "Error getting diffs: %s" msgstr "Fehler beim Laden des Vergleichs: %s" -#: gitk:8650 msgid "Goto:" msgstr "Gehe zu:" -#: gitk:8671 #, tcl-format msgid "Short SHA1 id %s is ambiguous" msgstr "Kurzer SHA1-Hashwert »%s« ist mehrdeutig" -#: gitk:8678 #, tcl-format msgid "Revision %s is not known" msgstr "Version »%s« ist unbekannt" -#: gitk:8688 #, tcl-format msgid "SHA1 id %s is not known" msgstr "SHA1-Hashwert »%s« ist unbekannt" -#: gitk:8690 #, tcl-format msgid "Revision %s is not in the current view" msgstr "Version »%s« wird in der aktuellen Ansicht nicht angezeigt" -#: gitk:8832 gitk:8847 msgid "Date" msgstr "Datum" -#: gitk:8835 msgid "Children" msgstr "Kinder" -#: gitk:8898 #, tcl-format msgid "Reset %s branch to here" msgstr "Zweig »%s« hierher zurücksetzen" -#: gitk:8900 msgid "Detached head: can't reset" msgstr "Zweigspitze ist abgetrennt: Zurücksetzen nicht möglich" -#: gitk:9005 gitk:9011 msgid "Skipping merge commit " msgstr "Überspringe Zusammenführungs-Version " -#: gitk:9020 gitk:9025 msgid "Error getting patch ID for " msgstr "Fehler beim Holen der Patch-ID für " -#: gitk:9021 gitk:9026 msgid " - stopping\n" msgstr " - Abbruch.\n" -#: gitk:9031 gitk:9034 gitk:9042 gitk:9056 gitk:9065 msgid "Commit " msgstr "Version " -#: gitk:9035 msgid "" " is the same patch as\n" " " @@ -893,7 +686,6 @@ " ist das gleiche Patch wie\n" " " -#: gitk:9043 msgid "" " differs from\n" " " @@ -901,7 +693,6 @@ " ist unterschiedlich von\n" " " -#: gitk:9045 msgid "" "Diff of commits:\n" "\n" @@ -909,131 +700,101 @@ "Vergleich der Versionen:\n" "\n" -#: gitk:9057 gitk:9066 #, tcl-format msgid " has %s children - stopping\n" msgstr " hat %s Kinder. Abbruch\n" -#: gitk:9085 #, tcl-format msgid "Error writing commit to file: %s" msgstr "Fehler beim Schreiben der Version in Datei: %s" -#: gitk:9091 #, tcl-format msgid "Error diffing commits: %s" msgstr "Fehler beim Vergleichen der Versionen: %s" -#: gitk:9137 msgid "Top" msgstr "Oben" -#: gitk:9138 msgid "From" msgstr "Von" -#: gitk:9143 msgid "To" msgstr "bis" -#: gitk:9167 msgid "Generate patch" msgstr "Patch erstellen" -#: gitk:9169 msgid "From:" msgstr "Von:" -#: gitk:9178 msgid "To:" msgstr "bis:" -#: gitk:9187 msgid "Reverse" msgstr "Umgekehrt" -#: gitk:9189 gitk:9385 msgid "Output file:" msgstr "Ausgabedatei:" -#: gitk:9195 msgid "Generate" msgstr "Erzeugen" -#: gitk:9233 msgid "Error creating patch:" msgstr "Fehler beim Erzeugen des Patches:" -#: gitk:9256 gitk:9373 gitk:9430 msgid "ID:" msgstr "ID:" -#: gitk:9265 msgid "Tag name:" msgstr "Markierungsname:" -#: gitk:9268 msgid "Tag message is optional" msgstr "Eine Markierungsbeschreibung ist optional" -#: gitk:9270 msgid "Tag message:" msgstr "Markierungsbeschreibung:" -#: gitk:9274 gitk:9439 msgid "Create" msgstr "Erstellen" -#: gitk:9292 msgid "No tag name specified" msgstr "Kein Markierungsname angegeben" -#: gitk:9296 #, tcl-format msgid "Tag \"%s\" already exists" msgstr "Markierung »%s« existiert bereits." -#: gitk:9306 msgid "Error creating tag:" msgstr "Fehler beim Erstellen der Markierung:" -#: gitk:9382 msgid "Command:" msgstr "Kommando:" -#: gitk:9390 msgid "Write" msgstr "Schreiben" -#: gitk:9408 msgid "Error writing commit:" msgstr "Fehler beim Schreiben der Version:" -#: gitk:9435 msgid "Name:" msgstr "Name:" -#: gitk:9458 msgid "Please specify a name for the new branch" msgstr "Bitte geben Sie einen Namen für den neuen Zweig an." -#: gitk:9463 #, tcl-format msgid "Branch '%s' already exists. Overwrite?" msgstr "Zweig »%s« existiert bereits. Soll er überschrieben werden?" -#: gitk:9530 #, tcl-format msgid "Commit %s is already included in branch %s -- really re-apply it?" msgstr "" "Version »%s« ist bereits im Zweig »%s« enthalten -- trotzdem erneut " "eintragen?" -#: gitk:9535 msgid "Cherry-picking" msgstr "Version pflücken" -#: gitk:9544 #, tcl-format msgid "" "Cherry-pick failed because of local changes to file '%s'.\n" @@ -1043,7 +804,6 @@ "vorliegen. Bitte diese Änderungen eintragen, zurücksetzen oder\n" "zwischenspeichern (»git stash«) und dann erneut versuchen." -#: gitk:9550 msgid "" "Cherry-pick failed because of merge conflict.\n" "Do you wish to run git citool to resolve it?" @@ -1052,21 +812,16 @@ "ist. Soll das Zusammenführungs-Werkzeug (»git citool«) aufgerufen\n" "werden, um diesen Konflikt aufzulösen?" -#: gitk:9566 gitk:9624 msgid "No changes committed" msgstr "Keine Änderungen eingetragen" -#: gitk:9593 #, tcl-format msgid "Commit %s is not included in branch %s -- really revert it?" -msgstr "" -"Version »%s« ist nicht im Zweig »%s« enthalten -- trotzdem umkehren?" +msgstr "Version »%s« ist nicht im Zweig »%s« enthalten -- trotzdem umkehren?" -#: gitk:9598 msgid "Reverting" msgstr "Umkehren" -#: gitk:9606 #, tcl-format msgid "" "Revert failed because of local changes to the following files:%s Please " @@ -1076,7 +831,6 @@ "vorliegen. Bitte diese Änderungen eintragen, zurücksetzen oder\n" "zwischenspeichern (»git stash«) und dann erneut versuchen." -#: gitk:9610 msgid "" "Revert failed because of merge conflict.\n" " Do you wish to run git citool to resolve it?" @@ -1085,30 +839,24 @@ "ist. Soll das Zusammenführungs-Werkzeug (»git citool«) aufgerufen\n" "werden, um diesen Konflikt aufzulösen?" -#: gitk:9653 msgid "Confirm reset" msgstr "Zurücksetzen bestätigen" -#: gitk:9655 #, tcl-format msgid "Reset branch %s to %s?" msgstr "Zweig »%s« auf »%s« zurücksetzen?" -#: gitk:9657 msgid "Reset type:" msgstr "Art des Zurücksetzens:" -#: gitk:9660 msgid "Soft: Leave working tree and index untouched" msgstr "Harmlos: Arbeitskopie und Bereitstellung unverändert" -#: gitk:9663 msgid "Mixed: Leave working tree untouched, reset index" msgstr "" "Gemischt: Arbeitskopie unverändert,\n" "Bereitstellung zurückgesetzt" -#: gitk:9666 msgid "" "Hard: Reset working tree and index\n" "(discard ALL local changes)" @@ -1116,21 +864,17 @@ "Hart: Arbeitskopie und Bereitstellung\n" "(Alle lokalen Änderungen werden gelöscht)" -#: gitk:9683 msgid "Resetting" msgstr "Zurücksetzen" -#: gitk:9743 msgid "Checking out" msgstr "Umstellen" -#: gitk:9796 msgid "Cannot delete the currently checked-out branch" msgstr "" "Der Zweig, auf den die Arbeitskopie momentan umgestellt ist, kann nicht " "gelöscht werden." -#: gitk:9802 #, tcl-format msgid "" "The commits on branch %s aren't on any other branch.\n" @@ -1139,16 +883,13 @@ "Die Versionen auf Zweig »%s« existieren auf keinem anderen Zweig.\n" "Zweig »%s« trotzdem löschen?" -#: gitk:9833 #, tcl-format msgid "Tags and heads: %s" msgstr "Markierungen und Zweige: %s" -#: gitk:9850 msgid "Filter" msgstr "Filtern" -#: gitk:10146 msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." @@ -1156,218 +897,167 @@ "Fehler beim Lesen der Strukturinformationen; Zweige und Informationen zu " "Vorgänger/Nachfolger werden unvollständig sein." -#: gitk:11123 msgid "Tag" msgstr "Markierung" -#: gitk:11127 msgid "Id" msgstr "Id" -#: gitk:11210 msgid "Gitk font chooser" msgstr "Gitk-Schriften wählen" -#: gitk:11227 msgid "B" msgstr "F" -#: gitk:11230 msgid "I" msgstr "K" -#: gitk:11348 msgid "Commit list display options" msgstr "Anzeige der Versionsliste" -#: gitk:11351 msgid "Maximum graph width (lines)" msgstr "Maximale Graphenbreite (Zeilen)" -#: gitk:11355 #, no-tcl-format msgid "Maximum graph width (% of pane)" msgstr "Maximale Graphenbreite (% des Fensters)" -#: gitk:11358 msgid "Show local changes" msgstr "Lokale Änderungen anzeigen" -#: gitk:11361 msgid "Auto-select SHA1 (length)" msgstr "SHA1-Hashwert (Länge) automatisch auswählen" -#: gitk:11365 msgid "Hide remote refs" msgstr "Entfernte Zweige/Markierungen ausblenden" -#: gitk:11369 msgid "Diff display options" msgstr "Anzeige des Vergleichs" -#: gitk:11371 msgid "Tab spacing" msgstr "Tabulatorbreite" -#: gitk:11374 msgid "Display nearby tags/heads" msgstr "Naheliegende Markierungen/Zweigspitzen anzeigen" -#: gitk:11377 msgid "Maximum # tags/heads to show" msgstr "Maximale Anzahl anzuzeigender Markierungen/Zweigspitzen" -#: gitk:11380 msgid "Limit diffs to listed paths" msgstr "Vergleich nur für angezeigte Pfade" -#: gitk:11383 msgid "Support per-file encodings" msgstr "Zeichenkodierung pro Datei ermitteln" -#: gitk:11389 gitk:11536 msgid "External diff tool" msgstr "Externes Diff-Programm" -#: gitk:11390 msgid "Choose..." msgstr "Wählen ..." -#: gitk:11395 msgid "General options" msgstr "Allgemeine Optionen" -#: gitk:11398 msgid "Use themed widgets" msgstr "Aussehen der Benutzeroberfläche durch Thema bestimmen" -#: gitk:11400 msgid "(change requires restart)" msgstr "(Änderungen werden erst nach Neustart wirksam)" -#: gitk:11402 msgid "(currently unavailable)" msgstr "(Momentan nicht verfügbar)" -#: gitk:11413 msgid "Colors: press to choose" msgstr "Farben: Klicken zum Wählen" -#: gitk:11416 msgid "Interface" msgstr "Benutzeroberfläche" -#: gitk:11417 msgid "interface" msgstr "Benutzeroberfläche" -#: gitk:11420 msgid "Background" msgstr "Hintergrund" -#: gitk:11421 gitk:11451 msgid "background" msgstr "Hintergrund" -#: gitk:11424 msgid "Foreground" msgstr "Vordergrund" -#: gitk:11425 msgid "foreground" msgstr "Vordergrund" -#: gitk:11428 msgid "Diff: old lines" msgstr "Vergleich: Alte Zeilen" -#: gitk:11429 msgid "diff old lines" msgstr "Vergleich - Alte Zeilen" -#: gitk:11433 msgid "Diff: new lines" msgstr "Vergleich: Neue Zeilen" -#: gitk:11434 msgid "diff new lines" msgstr "Vergleich - Neue Zeilen" -#: gitk:11438 msgid "Diff: hunk header" msgstr "Vergleich: Änderungstitel" -#: gitk:11440 msgid "diff hunk header" msgstr "Vergleich - Änderungstitel" -#: gitk:11444 msgid "Marked line bg" msgstr "Hintergrund für markierte Zeile" -#: gitk:11446 msgid "marked line background" msgstr "Hintergrund für markierte Zeile" -#: gitk:11450 msgid "Select bg" msgstr "Hintergrundfarbe auswählen" -#: gitk:11459 msgid "Fonts: press to choose" msgstr "Schriftart: Klicken zum Wählen" -#: gitk:11461 msgid "Main font" msgstr "Programmschriftart" -#: gitk:11462 msgid "Diff display font" msgstr "Schriftart für Vergleich" -#: gitk:11463 msgid "User interface font" msgstr "Beschriftungen" -#: gitk:11485 msgid "Gitk preferences" msgstr "Gitk-Einstellungen" -#: gitk:11494 msgid "General" msgstr "Allgemein" -#: gitk:11495 msgid "Colors" msgstr "Farben" -#: gitk:11496 msgid "Fonts" msgstr "Schriftarten" -#: gitk:11546 #, tcl-format msgid "Gitk: choose color for %s" msgstr "Gitk: Farbe wählen für %s" -#: gitk:12059 msgid "" "Sorry, gitk cannot run with this version of Tcl/Tk.\n" " Gitk requires at least Tcl/Tk 8.4." msgstr "" -"Entschuldigung, gitk kann nicht mit dieser Tcl/Tk Version ausgeführt werden.\n" +"Entschuldigung, gitk kann nicht mit dieser Tcl/Tk Version ausgeführt " +"werden.\n" " Gitk erfordert mindestens Tcl/Tk 8.4." -#: gitk:12269 msgid "Cannot find a git repository here." msgstr "Kein Git-Projektarchiv gefunden." -#: gitk:12316 #, tcl-format msgid "Ambiguous argument '%s': both revision and filename" msgstr "Mehrdeutige Angabe »%s«: Sowohl Version als auch Dateiname existiert." -#: gitk:12328 msgid "Bad arguments to gitk:" msgstr "Falsche Kommandozeilen-Parameter für gitk:"
diff --git a/gitk-git/po/es.po b/gitk-git/po/es.po index fef3bba..25260c6 100644 --- a/gitk-git/po/es.po +++ b/gitk-git/po/es.po
@@ -6,7 +6,7 @@ # msgid "" msgstr "" -"Project-Id-Version: gitk\n" +"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" @@ -17,34 +17,27 @@ "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: gitk:140 msgid "Couldn't get list of unmerged files:" msgstr "Imposible obtener la lista de archivos pendientes de fusión:" -#: gitk:212 gitk:2381 msgid "Color words" msgstr "" -#: gitk:217 gitk:2381 gitk:8220 gitk:8253 msgid "Markup words" msgstr "" -#: gitk:324 #, fuzzy msgid "Error parsing revisions:" msgstr "Error al leer las diferencias de fusión:" -#: gitk:380 msgid "Error executing --argscmd command:" msgstr "" -#: gitk:393 msgid "No files selected: --merge specified but no files are unmerged." msgstr "" "No hay archivos seleccionados: se seleccionó la opción --merge pero no hay " "archivos pendientes de fusión." -#: gitk:396 msgid "" "No files selected: --merge specified but no unmerged files are within file " "limit." @@ -52,319 +45,239 @@ "No hay archivos seleccionados: se seleccionó la opción --merge pero los " "archivos especificados no necesitan fusión." -#: gitk:418 gitk:566 #, fuzzy msgid "Error executing git log:" msgstr "Error al crear la etiqueta:" -#: gitk:436 gitk:582 msgid "Reading" msgstr "Leyendo" -#: gitk:496 gitk:4525 msgid "Reading commits..." msgstr "Leyendo revisiones..." -#: gitk:499 gitk:1637 gitk:4528 msgid "No commits selected" msgstr "No se seleccionaron revisiones" -#: gitk:1445 gitk:4045 gitk:12432 msgid "Command line" msgstr "Línea de comandos" -#: gitk:1511 msgid "Can't parse git log output:" msgstr "Error analizando la salida de git log:" -#: gitk:1740 msgid "No commit information available" msgstr "Falta información sobre las revisiones" -#: gitk:1903 gitk:1932 gitk:4315 gitk:9669 gitk:11241 gitk:11521 msgid "OK" msgstr "Aceptar" -#: gitk:1934 gitk:4317 gitk:9196 gitk:9275 gitk:9391 gitk:9440 gitk:9671 -#: gitk:11242 gitk:11522 msgid "Cancel" msgstr "Cancelar" -#: gitk:2069 msgid "&Update" msgstr "Actualizar" -#: gitk:2070 msgid "&Reload" msgstr "" -#: gitk:2071 msgid "Reread re&ferences" msgstr "Releer referencias" -#: gitk:2072 msgid "&List references" msgstr "Lista de referencias" -#: gitk:2074 msgid "Start git &gui" msgstr "" -#: gitk:2076 msgid "&Quit" msgstr "Salir" -#: gitk:2068 msgid "&File" msgstr "Archivo" -#: gitk:2080 msgid "&Preferences" msgstr "Preferencias" -#: gitk:2079 msgid "&Edit" msgstr "Editar" -#: gitk:2084 msgid "&New view..." msgstr "Nueva vista..." -#: gitk:2085 msgid "&Edit view..." msgstr "Modificar vista..." -#: gitk:2086 msgid "&Delete view" msgstr "Eliminar vista" -#: gitk:2088 gitk:4043 msgid "&All files" msgstr "Todos los archivos" -#: gitk:2083 gitk:4067 msgid "&View" msgstr "Vista" -#: gitk:2093 gitk:2103 gitk:3012 msgid "&About gitk" msgstr "Acerca de gitk" -#: gitk:2094 gitk:2108 msgid "&Key bindings" msgstr "Combinaciones de teclas" -#: gitk:2092 gitk:2107 msgid "&Help" msgstr "Ayuda" -#: gitk:2185 gitk:8652 msgid "SHA1 ID:" msgstr "SHA1 ID:" -#: gitk:2229 msgid "Row" msgstr "" -#: gitk:2267 msgid "Find" msgstr "Buscar" -#: gitk:2295 msgid "commit" msgstr "revisión" -#: gitk:2299 gitk:2301 gitk:4687 gitk:4710 gitk:4734 gitk:6755 gitk:6827 -#: gitk:6912 msgid "containing:" msgstr "que contiene:" -#: gitk:2302 gitk:3526 gitk:3531 gitk:4763 msgid "touching paths:" msgstr "que modifica la ruta:" -#: gitk:2303 gitk:4777 msgid "adding/removing string:" msgstr "que añade/elimina cadena:" -#: gitk:2304 gitk:4779 msgid "changing lines matching:" msgstr "" -#: gitk:2313 gitk:2315 gitk:4766 msgid "Exact" msgstr "Exacto" -#: gitk:2315 gitk:4854 gitk:6723 msgid "IgnCase" msgstr "NoMayús" -#: gitk:2315 gitk:4736 gitk:4852 gitk:6719 msgid "Regexp" msgstr "Regex" -#: gitk:2317 gitk:2318 gitk:4874 gitk:4904 gitk:4911 gitk:6848 gitk:6916 msgid "All fields" msgstr "Todos los campos" -#: gitk:2318 gitk:4871 gitk:4904 gitk:6786 msgid "Headline" msgstr "Título" -#: gitk:2319 gitk:4871 gitk:6786 gitk:6916 gitk:7389 msgid "Comments" msgstr "Comentarios" -#: gitk:2319 gitk:4871 gitk:4876 gitk:4911 gitk:6786 gitk:7324 gitk:8830 -#: gitk:8845 msgid "Author" msgstr "Autor" -#: gitk:2319 gitk:4871 gitk:6786 gitk:7326 msgid "Committer" msgstr "" -#: gitk:2350 msgid "Search" msgstr "Buscar" -#: gitk:2358 msgid "Diff" msgstr "Diferencia" -#: gitk:2360 msgid "Old version" msgstr "Versión antigua" -#: gitk:2362 msgid "New version" msgstr "Versión nueva" -#: gitk:2364 msgid "Lines of context" msgstr "Líneas de contexto" -#: gitk:2374 msgid "Ignore space change" msgstr "Ignora cambios de espaciado" -#: gitk:2378 gitk:2380 gitk:7959 gitk:8206 msgid "Line diff" msgstr "" -#: gitk:2445 msgid "Patch" msgstr "Parche" -#: gitk:2447 msgid "Tree" msgstr "Árbol" -#: gitk:2617 gitk:2637 msgid "Diff this -> selected" msgstr "Diferencia de esta -> seleccionada" -#: gitk:2618 gitk:2638 msgid "Diff selected -> this" msgstr "Diferencia de seleccionada -> esta" -#: gitk:2619 gitk:2639 msgid "Make patch" msgstr "Crear patch" -#: gitk:2620 gitk:9254 msgid "Create tag" msgstr "Crear etiqueta" -#: gitk:2621 gitk:9371 msgid "Write commit to file" msgstr "Escribir revisiones a archivo" -#: gitk:2622 gitk:9428 msgid "Create new branch" msgstr "Crear nueva rama" -#: gitk:2623 msgid "Cherry-pick this commit" msgstr "Añadir esta revisión a la rama actual (cherry-pick)" -#: gitk:2624 msgid "Reset HEAD branch to here" msgstr "Traer la rama HEAD aquí" -#: gitk:2625 #, fuzzy msgid "Mark this commit" msgstr "Añadir esta revisión a la rama actual (cherry-pick)" -#: gitk:2626 msgid "Return to mark" msgstr "" -#: gitk:2627 msgid "Find descendant of this and mark" msgstr "" -#: gitk:2628 msgid "Compare with marked commit" msgstr "" -#: gitk:2629 gitk:2640 #, fuzzy msgid "Diff this -> marked commit" msgstr "Diferencia de esta -> seleccionada" -#: gitk:2630 gitk:2641 #, fuzzy msgid "Diff marked commit -> this" msgstr "Diferencia de seleccionada -> esta" -#: gitk:2631 #, fuzzy msgid "Revert this commit" msgstr "Añadir esta revisión a la rama actual (cherry-pick)" -#: gitk:2647 msgid "Check out this branch" msgstr "Cambiar a esta rama" -#: gitk:2648 msgid "Remove this branch" msgstr "Eliminar esta rama" -#: gitk:2649 msgid "Copy branch name" msgstr "" -#: gitk:2656 msgid "Highlight this too" msgstr "Seleccionar también" -#: gitk:2657 msgid "Highlight this only" msgstr "Seleccionar sólo" -#: gitk:2658 msgid "External diff" msgstr "" -#: gitk:2659 msgid "Blame parent commit" msgstr "" -#: gitk:2660 msgid "Copy path" msgstr "" -#: gitk:2667 msgid "Show origin of this line" msgstr "" -#: gitk:2668 msgid "Run git gui blame on this line" msgstr "" -#: gitk:3014 #, fuzzy msgid "" "\n" @@ -382,733 +295,569 @@ "Uso y redistribución permitidos según los términos de la Licencia Pública " "General de GNU (GNU GPL)" -#: gitk:3022 gitk:3089 gitk:9857 msgid "Close" msgstr "Cerrar" -#: gitk:3043 msgid "Gitk key bindings" msgstr "Combinaciones de tecla de Gitk" -#: gitk:3046 msgid "Gitk key bindings:" msgstr "Combinaciones de tecla de Gitk:" -#: gitk:3048 #, tcl-format msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\tSalir" -#: gitk:3049 #, fuzzy, tcl-format msgid "<%s-W>\t\tClose window" msgstr "<%s-F>\t\tBuscar" -#: gitk:3050 msgid "<Home>\t\tMove to first commit" msgstr "<Home>\t\tIr a la primera revisión" -#: gitk:3051 msgid "<End>\t\tMove to last commit" msgstr "<End>\t\tIr a la última revisión" -#: gitk:3052 #, fuzzy msgid "<Up>, p, k\tMove up one commit" msgstr "<Up>, p, i\tSubir una revisión" -#: gitk:3053 #, fuzzy msgid "<Down>, n, j\tMove down one commit" msgstr "<Down>, n, k\tBajar una revisión" -#: gitk:3054 #, fuzzy msgid "<Left>, z, h\tGo back in history list" msgstr "<Left>, z, j\tRetroceder en la historia" -#: gitk:3055 msgid "<Right>, x, l\tGo forward in history list" msgstr "<Right>, x, l\tAvanzar en la historia" -#: gitk:3056 #, tcl-format msgid "<%s-n>\tGo to n-th parent of current commit in history list" msgstr "" -#: gitk:3057 msgid "<PageUp>\tMove up one page in commit list" msgstr "<PageUp>\tSubir una página en la lista de revisiones" -#: gitk:3058 msgid "<PageDown>\tMove down one page in commit list" msgstr "<PageDown>\tBajar una página en la lista de revisiones" -#: gitk:3059 #, tcl-format msgid "<%s-Home>\tScroll to top of commit list" msgstr "<%s-Home>\tDesplazarse al inicio de la lista de revisiones" -#: gitk:3060 #, tcl-format msgid "<%s-End>\tScroll to bottom of commit list" msgstr "<%s-End>\tDesplazarse al final de la lista de revisiones" -#: gitk:3061 #, tcl-format msgid "<%s-Up>\tScroll commit list up one line" msgstr "<%s-Up>\tDesplazar una línea hacia arriba la lista de revisiones" -#: gitk:3062 #, tcl-format msgid "<%s-Down>\tScroll commit list down one line" msgstr "<%s-Down>\tDesplazar una línea hacia abajo la lista de revisiones" -#: gitk:3063 #, tcl-format msgid "<%s-PageUp>\tScroll commit list up one page" msgstr "<%s-PageUp>\tDesplazar una página hacia arriba la lista de revisiones" -#: gitk:3064 #, tcl-format msgid "<%s-PageDown>\tScroll commit list down one page" msgstr "<%s-PageDown>\tDesplazar una página hacia abajo la lista de revisiones" -#: gitk:3065 msgid "<Shift-Up>\tFind backwards (upwards, later commits)" msgstr "<Shift-Up>\tBuscar hacia atrás (arriba, revisiones siguientes)" -#: gitk:3066 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" msgstr "<Shift-Down>\tBuscar hacia adelante (abajo, revisiones anteriores)" -#: gitk:3067 msgid "<Delete>, b\tScroll diff view up one page" msgstr "<Delete>, b\tDesplaza hacia arriba una página la vista de diferencias" -#: gitk:3068 msgid "<Backspace>\tScroll diff view up one page" msgstr "<Backspace>\tDesplaza hacia arriba una página la vista de diferencias" -#: gitk:3069 msgid "<Space>\t\tScroll diff view down one page" msgstr "<Space>\t\tDesplaza hacia abajo una página la vista de diferencias" -#: gitk:3070 msgid "u\t\tScroll diff view up 18 lines" msgstr "u\t\tDesplaza hacia arriba 18 líneas la vista de diferencias" -#: gitk:3071 msgid "d\t\tScroll diff view down 18 lines" msgstr "d\t\tDesplaza hacia abajo 18 líneas la vista de diferencias" -#: gitk:3072 #, tcl-format msgid "<%s-F>\t\tFind" msgstr "<%s-F>\t\tBuscar" -#: gitk:3073 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" msgstr "<%s-G>\t\tBuscar el siguiente" -#: gitk:3074 msgid "<Return>\tMove to next find hit" msgstr "<Return>\tBuscar el siguiente" -#: gitk:3075 #, fuzzy msgid "g\t\tGo to commit" msgstr "<End>\t\tIr a la última revisión" -#: gitk:3076 msgid "/\t\tFocus the search box" msgstr "" -#: gitk:3077 msgid "?\t\tMove to previous find hit" msgstr "?\t\tBuscar el anterior" -#: gitk:3078 msgid "f\t\tScroll diff view to next file" msgstr "f\t\tDesplazar la vista de diferencias al archivo siguiente" -#: gitk:3079 #, tcl-format msgid "<%s-S>\t\tSearch for next hit in diff view" msgstr "<%s-S>\t\tBuscar siguiente en la vista de diferencias" -#: gitk:3080 #, tcl-format msgid "<%s-R>\t\tSearch for previous hit in diff view" msgstr "<%s-R>\t\tBuscar anterior en la vista de diferencias" -#: gitk:3081 #, tcl-format msgid "<%s-KP+>\tIncrease font size" msgstr "<%s-KP+>\tAumentar tamaño del texto" -#: gitk:3082 #, tcl-format msgid "<%s-plus>\tIncrease font size" msgstr "<%s-plus>\tAumentar tamaño del texto" -#: gitk:3083 #, tcl-format msgid "<%s-KP->\tDecrease font size" msgstr "<%s-KP->\tDisminuir tamaño del texto" -#: gitk:3084 #, tcl-format msgid "<%s-minus>\tDecrease font size" msgstr "<%s-minus>\tDisminuir tamaño del texto" -#: gitk:3085 msgid "<F5>\t\tUpdate" msgstr "<F5>\t\tActualizar" -#: gitk:3550 gitk:3559 #, fuzzy, tcl-format msgid "Error creating temporary directory %s:" msgstr "Error en la creación del parche:" -#: gitk:3572 #, fuzzy, tcl-format msgid "Error getting \"%s\" from %s:" msgstr "Error al leer las diferencias de fusión:" -#: gitk:3635 #, fuzzy msgid "command failed:" msgstr "Línea de comandos" -#: gitk:3784 #, fuzzy msgid "No such commit" msgstr "No se han guardado cambios" -#: gitk:3798 msgid "git gui blame: command failed:" msgstr "" -#: gitk:3829 #, tcl-format msgid "Couldn't read merge head: %s" msgstr "" -#: gitk:3837 #, fuzzy, tcl-format msgid "Error reading index: %s" msgstr "Error al crear la etiqueta:" -#: gitk:3862 #, tcl-format msgid "Couldn't start git blame: %s" msgstr "" -#: gitk:3865 gitk:6754 msgid "Searching" msgstr "Buscando" -#: gitk:3897 #, fuzzy, tcl-format msgid "Error running git blame: %s" msgstr "Error al crear la etiqueta:" -#: gitk:3925 #, tcl-format msgid "That line comes from commit %s, which is not in this view" msgstr "" -#: gitk:3939 #, fuzzy msgid "External diff viewer failed:" msgstr "f\t\tDesplazar la vista de diferencias al archivo siguiente" -#: gitk:4070 msgid "Gitk view definition" msgstr "Definición de vistas de Gitk" -#: gitk:4074 msgid "Remember this view" msgstr "Recordar esta vista" -#: gitk:4075 msgid "References (space separated list):" msgstr "" -#: gitk:4076 msgid "Branches & tags:" msgstr "" -#: gitk:4077 #, fuzzy msgid "All refs" msgstr "Todos los archivos" -#: gitk:4078 msgid "All (local) branches" msgstr "" -#: gitk:4079 msgid "All tags" msgstr "" -#: gitk:4080 msgid "All remote-tracking branches" msgstr "" -#: gitk:4081 msgid "Commit Info (regular expressions):" msgstr "" -#: gitk:4082 #, fuzzy msgid "Author:" msgstr "Autor" -#: gitk:4083 #, fuzzy msgid "Committer:" msgstr "revisión" -#: gitk:4084 msgid "Commit Message:" msgstr "" -#: gitk:4085 msgid "Matches all Commit Info criteria" msgstr "" -#: gitk:4086 msgid "Matches no Commit Info criteria" msgstr "" -#: gitk:4087 msgid "Changes to Files:" msgstr "" -#: gitk:4088 msgid "Fixed String" msgstr "" -#: gitk:4089 msgid "Regular Expression" msgstr "" -#: gitk:4090 #, fuzzy msgid "Search string:" msgstr "Buscando" -#: gitk:4091 msgid "" "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" msgstr "" -#: gitk:4092 msgid "Since:" msgstr "" -#: gitk:4093 msgid "Until:" msgstr "" -#: gitk:4094 msgid "Limit and/or skip a number of revisions (positive integer):" msgstr "" -#: gitk:4095 msgid "Number to show:" msgstr "" -#: gitk:4096 msgid "Number to skip:" msgstr "" -#: gitk:4097 msgid "Miscellaneous options:" msgstr "" -#: gitk:4098 msgid "Strictly sort by date" msgstr "" -#: gitk:4099 msgid "Mark branch sides" msgstr "" -#: gitk:4100 #, fuzzy msgid "Limit to first parent" msgstr "Limitar las diferencias a las rutas seleccionadas" -#: gitk:4101 msgid "Simple history" msgstr "" -#: gitk:4102 #, fuzzy msgid "Additional arguments to git log:" msgstr "Revisiones a incluir (argumentos a git log):" -#: gitk:4103 msgid "Enter files and directories to include, one per line:" msgstr "Introducir archivos y directorios a incluir, uno por línea:" -#: gitk:4104 msgid "Command to generate more commits to include:" msgstr "Comando que genera más revisiones a incluir:" -#: gitk:4228 msgid "Gitk: edit view" msgstr "" -#: gitk:4236 msgid "-- criteria for selecting revisions" msgstr "" -#: gitk:4241 #, fuzzy msgid "View Name" msgstr "Vista" -#: gitk:4316 msgid "Apply (F5)" msgstr "" -#: gitk:4354 msgid "Error in commit selection arguments:" msgstr "Error en los argumentos de selección de las revisiones:" -#: gitk:4409 gitk:4462 gitk:4924 gitk:4938 gitk:6208 gitk:12373 gitk:12374 msgid "None" msgstr "Ninguno" -#: gitk:5021 gitk:5026 msgid "Descendant" msgstr "Descendiente" -#: gitk:5022 msgid "Not descendant" msgstr "No descendiente" -#: gitk:5029 gitk:5034 msgid "Ancestor" msgstr "Antepasado" -#: gitk:5030 msgid "Not ancestor" msgstr "No antepasado" -#: gitk:5324 msgid "Local changes checked in to index but not committed" msgstr "Cambios locales añadidos al índice pero sin completar revisión" -#: gitk:5360 msgid "Local uncommitted changes, not checked in to index" msgstr "Cambios locales sin añadir al índice" -#: gitk:7134 msgid "and many more" msgstr "" -#: gitk:7137 msgid "many" msgstr "" -#: gitk:7328 msgid "Tags:" msgstr "Etiquetas:" -#: gitk:7345 gitk:7351 gitk:8825 msgid "Parent" msgstr "Padre" -#: gitk:7356 msgid "Child" msgstr "Hija" -#: gitk:7365 msgid "Branch" msgstr "Rama" -#: gitk:7368 msgid "Follows" msgstr "Sigue-a" -#: gitk:7371 msgid "Precedes" msgstr "Precede-a" -#: gitk:7966 #, fuzzy, tcl-format msgid "Error getting diffs: %s" msgstr "Error al leer las diferencias de fusión:" -#: gitk:8650 msgid "Goto:" msgstr "Ir a:" -#: gitk:8671 #, tcl-format msgid "Short SHA1 id %s is ambiguous" msgstr "La id SHA1 abreviada %s es ambigua" -#: gitk:8678 #, fuzzy, tcl-format msgid "Revision %s is not known" msgstr "La id SHA1 %s es desconocida" -#: gitk:8688 #, tcl-format msgid "SHA1 id %s is not known" msgstr "La id SHA1 %s es desconocida" -#: gitk:8690 #, tcl-format msgid "Revision %s is not in the current view" msgstr "" -#: gitk:8832 gitk:8847 msgid "Date" msgstr "Fecha" -#: gitk:8835 msgid "Children" msgstr "Hijas" -#: gitk:8898 #, tcl-format msgid "Reset %s branch to here" msgstr "Poner la rama %s en esta revisión" -#: gitk:8900 msgid "Detached head: can't reset" msgstr "" -#: gitk:9005 gitk:9011 msgid "Skipping merge commit " msgstr "" -#: gitk:9020 gitk:9025 #, fuzzy msgid "Error getting patch ID for " msgstr "Error en la creación del parche:" -#: gitk:9021 gitk:9026 msgid " - stopping\n" msgstr "" -#: gitk:9031 gitk:9034 gitk:9042 gitk:9056 gitk:9065 #, fuzzy msgid "Commit " msgstr "revisión" -#: gitk:9035 msgid "" " is the same patch as\n" " " msgstr "" -#: gitk:9043 msgid "" " differs from\n" " " msgstr "" -#: gitk:9045 msgid "" "Diff of commits:\n" "\n" msgstr "" -#: gitk:9057 gitk:9066 #, tcl-format msgid " has %s children - stopping\n" msgstr "" -#: gitk:9085 #, fuzzy, tcl-format msgid "Error writing commit to file: %s" msgstr "Error al escribir revisión:" -#: gitk:9091 #, fuzzy, tcl-format msgid "Error diffing commits: %s" msgstr "Error al escribir revisión:" -#: gitk:9137 msgid "Top" msgstr "Origen" -#: gitk:9138 msgid "From" msgstr "De" -#: gitk:9143 msgid "To" msgstr "A" -#: gitk:9167 msgid "Generate patch" msgstr "Generar parche" -#: gitk:9169 msgid "From:" msgstr "De:" -#: gitk:9178 msgid "To:" msgstr "Para:" -#: gitk:9187 msgid "Reverse" msgstr "Invertir" -#: gitk:9189 gitk:9385 msgid "Output file:" msgstr "Escribir a archivo:" -#: gitk:9195 msgid "Generate" msgstr "Generar" -#: gitk:9233 msgid "Error creating patch:" msgstr "Error en la creación del parche:" -#: gitk:9256 gitk:9373 gitk:9430 msgid "ID:" msgstr "ID:" -#: gitk:9265 msgid "Tag name:" msgstr "Nombre de etiqueta:" -#: gitk:9268 msgid "Tag message is optional" msgstr "" -#: gitk:9270 #, fuzzy msgid "Tag message:" msgstr "Nombre de etiqueta:" -#: gitk:9274 gitk:9439 msgid "Create" msgstr "Crear" -#: gitk:9292 msgid "No tag name specified" msgstr "No se ha especificado etiqueta" -#: gitk:9296 #, tcl-format msgid "Tag \"%s\" already exists" msgstr "La etiqueta \"%s\" ya existe" -#: gitk:9306 msgid "Error creating tag:" msgstr "Error al crear la etiqueta:" -#: gitk:9382 msgid "Command:" msgstr "Comando:" -#: gitk:9390 msgid "Write" msgstr "Escribir" -#: gitk:9408 msgid "Error writing commit:" msgstr "Error al escribir revisión:" -#: gitk:9435 msgid "Name:" msgstr "Nombre:" -#: gitk:9458 msgid "Please specify a name for the new branch" msgstr "Especifique un nombre para la nueva rama" -#: gitk:9463 #, fuzzy, tcl-format msgid "Branch '%s' already exists. Overwrite?" msgstr "La etiqueta \"%s\" ya existe" -#: gitk:9530 #, tcl-format msgid "Commit %s is already included in branch %s -- really re-apply it?" msgstr "La revisión %s ya está incluida en la rama %s -- ¿Volver a aplicarla?" -#: gitk:9535 msgid "Cherry-picking" msgstr "Eligiendo revisiones (cherry-picking)" -#: gitk:9544 #, tcl-format msgid "" "Cherry-pick failed because of local changes to file '%s'.\n" "Please commit, reset or stash your changes and try again." msgstr "" -#: gitk:9550 msgid "" "Cherry-pick failed because of merge conflict.\n" "Do you wish to run git citool to resolve it?" msgstr "" -#: gitk:9566 gitk:9624 msgid "No changes committed" msgstr "No se han guardado cambios" -#: gitk:9593 #, fuzzy, 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?" -#: gitk:9598 #, fuzzy msgid "Reverting" msgstr "Reponiendo" -#: gitk:9606 #, 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 "" -#: gitk:9610 msgid "" "Revert failed because of merge conflict.\n" " Do you wish to run git citool to resolve it?" msgstr "" -#: gitk:9653 msgid "Confirm reset" msgstr "Confirmar git reset" -#: gitk:9655 #, tcl-format msgid "Reset branch %s to %s?" msgstr "¿Reponer la rama %s a %s?" -#: gitk:9657 msgid "Reset type:" msgstr "Tipo de reposición:" -#: gitk:9660 msgid "Soft: Leave working tree and index untouched" msgstr "Suave: No altera la copia de trabajo ni el índice" -#: gitk:9663 msgid "Mixed: Leave working tree untouched, reset index" msgstr "Mixta: Actualiza el índice, no altera la copia de trabajo" -#: gitk:9666 msgid "" "Hard: Reset working tree and index\n" "(discard ALL local changes)" @@ -1116,19 +865,15 @@ "Dura: Actualiza el índice y la copia de trabajo\n" "(abandona TODAS las modificaciones locales)" -#: gitk:9683 msgid "Resetting" msgstr "Reponiendo" -#: gitk:9743 msgid "Checking out" msgstr "Creando copia de trabajo" -#: gitk:9796 msgid "Cannot delete the currently checked-out branch" msgstr "No se puede borrar la rama actual" -#: gitk:9802 #, tcl-format msgid "" "The commits on branch %s aren't on any other branch.\n" @@ -1137,16 +882,13 @@ "Las revisiones de la rama %s no están presentes en otras ramas.\n" "¿Borrar la rama %s?" -#: gitk:9833 #, tcl-format msgid "Tags and heads: %s" msgstr "Etiquetas y ramas: %s" -#: gitk:9850 msgid "Filter" msgstr "Filtro" -#: gitk:10146 msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." @@ -1154,211 +896,162 @@ "Error al leer la topología de revisiones: la información sobre las ramas y " "etiquetas precedentes y siguientes será incompleta." -#: gitk:11123 msgid "Tag" msgstr "Etiqueta" -#: gitk:11127 msgid "Id" msgstr "Id" -#: gitk:11210 msgid "Gitk font chooser" msgstr "Selector de tipografías gitk" -#: gitk:11227 msgid "B" msgstr "B" -#: gitk:11230 msgid "I" msgstr "I" -#: gitk:11348 msgid "Commit list display options" msgstr "Opciones de visualización de la lista de revisiones" -#: gitk:11351 msgid "Maximum graph width (lines)" msgstr "Ancho máximo del gráfico (en líneas)" -#: gitk:11355 #, no-tcl-format msgid "Maximum graph width (% of pane)" msgstr "Ancho máximo del gráfico (en % del panel)" -#: gitk:11358 msgid "Show local changes" msgstr "Mostrar cambios locales" -#: gitk:11361 #, fuzzy msgid "Auto-select SHA1 (length)" msgstr "Seleccionar automáticamente SHA1 hash" -#: gitk:11365 msgid "Hide remote refs" msgstr "" -#: gitk:11369 msgid "Diff display options" msgstr "Opciones de visualización de diferencias" -#: gitk:11371 msgid "Tab spacing" msgstr "Espaciado de tabulador" -#: gitk:11374 #, fuzzy msgid "Display nearby tags/heads" msgstr "Mostrar etiquetas cercanas" -#: gitk:11377 msgid "Maximum # tags/heads to show" msgstr "" -#: gitk:11380 msgid "Limit diffs to listed paths" msgstr "Limitar las diferencias a las rutas seleccionadas" -#: gitk:11383 msgid "Support per-file encodings" msgstr "" -#: gitk:11389 gitk:11536 msgid "External diff tool" msgstr "" -#: gitk:11390 msgid "Choose..." msgstr "" -#: gitk:11395 #, fuzzy msgid "General options" msgstr "Generar parche" -#: gitk:11398 msgid "Use themed widgets" msgstr "" -#: gitk:11400 msgid "(change requires restart)" msgstr "" -#: gitk:11402 msgid "(currently unavailable)" msgstr "" -#: gitk:11413 msgid "Colors: press to choose" msgstr "Colores: pulse para seleccionar" -#: gitk:11416 msgid "Interface" msgstr "" -#: gitk:11417 #, fuzzy msgid "interface" msgstr "Tipografía para interfaz de usuario" -#: gitk:11420 msgid "Background" msgstr "Fondo" -#: gitk:11421 gitk:11451 #, fuzzy msgid "background" msgstr "Fondo" -#: gitk:11424 msgid "Foreground" msgstr "Primer plano" -#: gitk:11425 #, fuzzy msgid "foreground" msgstr "Primer plano" -#: gitk:11428 msgid "Diff: old lines" msgstr "Diff: líneas viejas" -#: gitk:11429 #, fuzzy msgid "diff old lines" msgstr "Diff: líneas viejas" -#: gitk:11433 msgid "Diff: new lines" msgstr "Diff: líneas nuevas" -#: gitk:11434 #, fuzzy msgid "diff new lines" msgstr "Diff: líneas nuevas" -#: gitk:11438 msgid "Diff: hunk header" msgstr "Diff: cabecera de fragmento" -#: gitk:11440 #, fuzzy msgid "diff hunk header" msgstr "Diff: cabecera de fragmento" -#: gitk:11444 msgid "Marked line bg" msgstr "" -#: gitk:11446 msgid "marked line background" msgstr "" -#: gitk:11450 msgid "Select bg" msgstr "Color de fondo de la selección" -#: gitk:11459 msgid "Fonts: press to choose" msgstr "Tipografías: pulse para elegir" -#: gitk:11461 msgid "Main font" msgstr "Tipografía principal" -#: gitk:11462 msgid "Diff display font" msgstr "Tipografía para diferencias" -#: gitk:11463 msgid "User interface font" msgstr "Tipografía para interfaz de usuario" -#: gitk:11485 msgid "Gitk preferences" msgstr "Preferencias de gitk" -#: gitk:11494 #, fuzzy msgid "General" msgstr "Generar" -#: gitk:11495 msgid "Colors" msgstr "" -#: gitk:11496 msgid "Fonts" msgstr "" -#: gitk:11546 #, tcl-format msgid "Gitk: choose color for %s" msgstr "Gitk: elegir color para %s" -#: gitk:12059 msgid "" "Sorry, gitk cannot run with this version of Tcl/Tk.\n" " Gitk requires at least Tcl/Tk 8.4." @@ -1366,17 +1059,14 @@ "Esta versión de Tcl/Tk es demasiado antigua.\n" " Gitk requiere Tcl/Tk versión 8.4 o superior." -#: gitk:12269 msgid "Cannot find a git repository here." msgstr "No hay un repositorio git aquí." -#: gitk:12316 #, tcl-format msgid "Ambiguous argument '%s': both revision and filename" msgstr "" "Argumento ambiguo: '%s' es tanto una revisión como un nombre de archivo" -#: gitk:12328 msgid "Bad arguments to gitk:" msgstr "Argumentos incorrectos a Gitk:"
diff --git a/gitk-git/po/fr.po b/gitk-git/po/fr.po index e4fac93..f1c54b6 100644 --- a/gitk-git/po/fr.po +++ b/gitk-git/po/fr.po
@@ -6,7 +6,7 @@ # Jean-Noël Avila <jn.avila@free.fr> msgid "" msgstr "" -"Project-Id-Version: gitk\n" +"Project-Id-Version: Gitk\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-01-22 22:04+0100\n" "PO-Revision-Date: 2016-01-22 23:28+0100\n" @@ -19,355 +19,268 @@ "X-Poedit-Language: French\n" "X-Poedit-Country: FRANCE\n" -#: gitk:140 msgid "Couldn't get list of unmerged files:" msgstr "Impossible de récupérer la liste des fichiers non fusionnés :" -#: gitk:212 gitk:2399 msgid "Color words" msgstr "Colorier les mots différents" -#: gitk:217 gitk:2399 gitk:8239 gitk:8272 msgid "Markup words" msgstr "Marquer les mots différents" -#: gitk:324 msgid "Error parsing revisions:" msgstr "Erreur lors du parcours des révisions :" -#: gitk:380 msgid "Error executing --argscmd command:" msgstr "Erreur à l'exécution de la commande --argscmd :" -#: gitk:393 msgid "No files selected: --merge specified but no files are unmerged." msgstr "" "Aucun fichier sélectionné : --merge précisé, mais tous les fichiers sont " "fusionnés." # FIXME : améliorer la traduction de 'file limite' -#: gitk:396 msgid "" "No files selected: --merge specified but no unmerged files are within file " "limit." -msgstr "Aucun fichier sélectionné : --merge précisé mais aucun fichier non fusionné n'est dans la limite des fichiers." +msgstr "" +"Aucun fichier sélectionné : --merge précisé mais aucun fichier non fusionné " +"n'est dans la limite des fichiers." -#: gitk:418 gitk:566 msgid "Error executing git log:" msgstr "Erreur à l'exécution de git log :" -#: gitk:436 gitk:582 msgid "Reading" msgstr "Lecture en cours" -#: gitk:496 gitk:4544 msgid "Reading commits..." msgstr "Lecture des commits..." -#: gitk:499 gitk:1637 gitk:4547 msgid "No commits selected" msgstr "Aucun commit sélectionné" -#: gitk:1445 gitk:4064 gitk:12469 msgid "Command line" msgstr "Ligne de commande" -#: gitk:1511 msgid "Can't parse git log output:" msgstr "Impossible de lire la sortie de git log :" -#: gitk:1740 msgid "No commit information available" msgstr "Aucune information disponible sur le commit" -#: gitk:1903 gitk:1932 gitk:4334 gitk:9702 gitk:11274 gitk:11554 msgid "OK" msgstr "OK" -#: gitk:1934 gitk:4336 gitk:9215 gitk:9294 gitk:9424 gitk:9473 gitk:9704 -#: gitk:11275 gitk:11555 msgid "Cancel" msgstr "Annuler" -#: gitk:2083 msgid "&Update" msgstr "Mise à jour" -#: gitk:2084 msgid "&Reload" msgstr "&Recharger" -#: gitk:2085 msgid "Reread re&ferences" msgstr "Relire les ré&férences" -#: gitk:2086 msgid "&List references" msgstr "&Lister les références" -#: gitk:2088 msgid "Start git &gui" msgstr "Démarrer git &gui" -#: gitk:2090 msgid "&Quit" msgstr "&Quitter" -#: gitk:2082 msgid "&File" msgstr "&Fichier" -#: gitk:2094 msgid "&Preferences" msgstr "Préférences" -#: gitk:2093 msgid "&Edit" msgstr "&Éditer" -#: gitk:2098 msgid "&New view..." msgstr "&Nouvelle vue..." -#: gitk:2099 msgid "&Edit view..." msgstr "&Éditer la vue..." -#: gitk:2100 msgid "&Delete view" msgstr "Supprimer la vue" -#: gitk:2102 msgid "&All files" msgstr "Tous les fichiers" -#: gitk:2097 msgid "&View" msgstr "&Vue" -#: gitk:2107 gitk:2117 msgid "&About gitk" msgstr "À propos de gitk" -#: gitk:2108 gitk:2122 msgid "&Key bindings" msgstr "Raccourcis clavier" -#: gitk:2106 gitk:2121 msgid "&Help" msgstr "Aide" -#: gitk:2199 gitk:8671 msgid "SHA1 ID:" msgstr "Id SHA1 :" -#: gitk:2243 msgid "Row" msgstr "Colonne" -#: gitk:2281 msgid "Find" msgstr "Recherche" -#: gitk:2309 msgid "commit" msgstr "commit" -#: gitk:2313 gitk:2315 gitk:4706 gitk:4729 gitk:4753 gitk:6774 gitk:6846 -#: gitk:6931 msgid "containing:" msgstr "contient :" -#: gitk:2316 gitk:3545 gitk:3550 gitk:4782 msgid "touching paths:" msgstr "chemins modifiés :" -#: gitk:2317 gitk:4796 msgid "adding/removing string:" msgstr "ajoute/supprime la chaîne :" -#: gitk:2318 gitk:4798 msgid "changing lines matching:" msgstr "modifie les lignes vérifiant :" -#: gitk:2327 gitk:2329 gitk:4785 msgid "Exact" msgstr "Exact" -#: gitk:2329 gitk:4873 gitk:6742 msgid "IgnCase" msgstr "Ignorer la casse" -#: gitk:2329 gitk:4755 gitk:4871 gitk:6738 msgid "Regexp" msgstr "Expression régulière" -#: gitk:2331 gitk:2332 gitk:4893 gitk:4923 gitk:4930 gitk:6867 gitk:6935 msgid "All fields" msgstr "Tous les champs" -#: gitk:2332 gitk:4890 gitk:4923 gitk:6805 msgid "Headline" msgstr "Titre" -#: gitk:2333 gitk:4890 gitk:6805 gitk:6935 gitk:7408 msgid "Comments" msgstr "Commentaires" -#: gitk:2333 gitk:4890 gitk:4895 gitk:4930 gitk:6805 gitk:7343 gitk:8849 -#: gitk:8864 msgid "Author" msgstr "Auteur" -#: gitk:2333 gitk:4890 gitk:6805 gitk:7345 msgid "Committer" msgstr "Validateur" -#: gitk:2367 msgid "Search" msgstr "Rechercher" -#: gitk:2375 msgid "Diff" msgstr "Diff" -#: gitk:2377 msgid "Old version" msgstr "Ancienne version" -#: gitk:2379 msgid "New version" msgstr "Nouvelle version" -#: gitk:2382 msgid "Lines of context" msgstr "Lignes de contexte" -#: gitk:2392 msgid "Ignore space change" msgstr "Ignorer les modifications d'espace" -#: gitk:2396 gitk:2398 gitk:7978 gitk:8225 msgid "Line diff" msgstr "différence par ligne" -#: gitk:2463 msgid "Patch" msgstr "Patch" -#: gitk:2465 msgid "Tree" msgstr "Arbre" -#: gitk:2635 gitk:2656 msgid "Diff this -> selected" msgstr "Diff ceci -> la sélection" -#: gitk:2636 gitk:2657 msgid "Diff selected -> this" msgstr "Diff sélection -> ceci" -#: gitk:2637 gitk:2658 msgid "Make patch" msgstr "Créer patch" -#: gitk:2638 gitk:9273 msgid "Create tag" msgstr "Créer étiquette" -#: gitk:2639 msgid "Copy commit summary" msgstr "Copié le résumé du commit" -#: gitk:2640 gitk:9404 msgid "Write commit to file" msgstr "Écrire le commit dans un fichier" -#: gitk:2641 gitk:9461 msgid "Create new branch" msgstr "Créer une nouvelle branche" -#: gitk:2642 msgid "Cherry-pick this commit" msgstr "Cueillir (cherry-pick) ce commit" -#: gitk:2643 msgid "Reset HEAD branch to here" msgstr "Réinitialiser la branche HEAD vers cet état" -#: gitk:2644 msgid "Mark this commit" msgstr "Marquer ce commit" -#: gitk:2645 msgid "Return to mark" msgstr "Retourner à la marque" -#: gitk:2646 msgid "Find descendant of this and mark" msgstr "Chercher le descendant de ceci et le marquer" -#: gitk:2647 msgid "Compare with marked commit" msgstr "Comparer avec le commit marqué" -#: gitk:2648 gitk:2659 msgid "Diff this -> marked commit" msgstr "Diff ceci -> sélection" -#: gitk:2649 gitk:2660 msgid "Diff marked commit -> this" msgstr "Diff entre sélection -> ceci" -#: gitk:2650 msgid "Revert this commit" msgstr "Défaire ce commit" -#: gitk:2666 msgid "Check out this branch" msgstr "Récupérer cette branche" -#: gitk:2667 msgid "Remove this branch" msgstr "Supprimer cette branche" -#: gitk:2668 msgid "Copy branch name" msgstr "Copier la nom de la branche" -#: gitk:2675 msgid "Highlight this too" msgstr "Surligner également ceci" -#: gitk:2676 msgid "Highlight this only" msgstr "Surligner seulement ceci" -#: gitk:2677 msgid "External diff" msgstr "Diff externe" -#: gitk:2678 msgid "Blame parent commit" msgstr "Blâmer le commit parent" -#: gitk:2679 msgid "Copy path" msgstr "Copier le chemin" -#: gitk:2686 msgid "Show origin of this line" msgstr "Montrer l'origine de cette ligne" -#: gitk:2687 msgid "Run git gui blame on this line" msgstr "Exécuter git gui blame sur cette ligne" -#: gitk:3031 msgid "About gitk" msgstr "À propos de gitk" -#: gitk:3033 msgid "" "\n" "Gitk - a commit viewer for git\n" @@ -381,327 +294,254 @@ "\n" "Copyright \\u00a9 2005-2016 Paul Mackerras\n" "\n" -"Utilisation et redistribution soumises aux termes de la GNU General Public License" +"Utilisation et redistribution soumises aux termes de la GNU General Public " +"License" -#: gitk:3041 gitk:3108 gitk:9890 msgid "Close" msgstr "Fermer" -#: gitk:3062 msgid "Gitk key bindings" msgstr "Raccourcis clavier de Gitk" -#: gitk:3065 msgid "Gitk key bindings:" msgstr "Raccourcis clavier de Gitk :" -#: gitk:3067 #, tcl-format msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\tQuitter" -#: gitk:3068 #, tcl-format msgid "<%s-W>\t\tClose window" msgstr "<%s-W>\t\tFermer la fenêtre" -#: gitk:3069 msgid "<Home>\t\tMove to first commit" msgstr "<Début>\t\tAller au premier commit" -#: gitk:3070 msgid "<End>\t\tMove to last commit" msgstr "<Fin>\t\tAller au dernier commit" -#: gitk:3071 msgid "<Up>, p, k\tMove up one commit" msgstr "<Haut>, p, k\t Aller au commit précédent" -#: gitk:3072 msgid "<Down>, n, j\tMove down one commit" msgstr "<Bas>, n, j\t Aller au commit suivant" -#: gitk:3073 msgid "<Left>, z, h\tGo back in history list" msgstr "<Gauche>, z, h\tReculer dans l'historique" -#: gitk:3074 msgid "<Right>, x, l\tGo forward in history list" msgstr "<Droite>, x, l\tAvancer dans l'historique" -#: gitk:3075 #, tcl-format msgid "<%s-n>\tGo to n-th parent of current commit in history list" msgstr "<%s-n>\tAller sur le n-ième parent du commit dans l'historique" -#: gitk:3076 msgid "<PageUp>\tMove up one page in commit list" msgstr "<PageUp>\tMonter d'une page dans la liste des commits" -#: gitk:3077 msgid "<PageDown>\tMove down one page in commit list" msgstr "<PageDown>\tDescendre d'une page dans la liste des commits" -#: gitk:3078 #, tcl-format msgid "<%s-Home>\tScroll to top of commit list" msgstr "<%s-Début>\tAller en haut de la liste des commits" -#: gitk:3079 #, tcl-format msgid "<%s-End>\tScroll to bottom of commit list" msgstr "<%s-End>\tAller en bas de la liste des commits" -#: gitk:3080 #, tcl-format msgid "<%s-Up>\tScroll commit list up one line" msgstr "<%s-Up>\tMonter d'une ligne dans la liste des commits" -#: gitk:3081 #, tcl-format msgid "<%s-Down>\tScroll commit list down one line" msgstr "<%s-Down>\tDescendre d'une ligne dans la liste des commits" -#: gitk:3082 #, tcl-format msgid "<%s-PageUp>\tScroll commit list up one page" msgstr "<%s-PageUp>\tMonter d'une page dans la liste des commits" -#: gitk:3083 #, tcl-format msgid "<%s-PageDown>\tScroll commit list down one page" msgstr "<%s-PageDown>\tDescendre d'une page dans la liste des commits" -#: gitk:3084 msgid "<Shift-Up>\tFind backwards (upwards, later commits)" msgstr "" "<Shift-Up>\tRecherche en arrière (vers l'avant, commits les plus anciens)" -#: gitk:3085 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" msgstr "" "<Shift-Down>\tRecherche en avant (vers l'arrière, commit les plus récents)" -#: gitk:3086 msgid "<Delete>, b\tScroll diff view up one page" msgstr "<Supprimer>, b\tMonter d'une page dans la vue des diff" -#: gitk:3087 msgid "<Backspace>\tScroll diff view up one page" msgstr "<Backspace>\tMonter d'une page dans la vue des diff" -#: gitk:3088 msgid "<Space>\t\tScroll diff view down one page" msgstr "<Espace>\t\tDescendre d'une page dans la vue des diff" -#: gitk:3089 msgid "u\t\tScroll diff view up 18 lines" msgstr "u\t\tMonter de 18 lignes dans la vue des diff" -#: gitk:3090 msgid "d\t\tScroll diff view down 18 lines" msgstr "d\t\tDescendre de 18 lignes dans la vue des diff" -#: gitk:3091 #, tcl-format msgid "<%s-F>\t\tFind" msgstr "<%s-F>\t\tRechercher" -#: gitk:3092 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" msgstr "<%s-G>\t\tAller au résultat de recherche suivant" -#: gitk:3093 msgid "<Return>\tMove to next find hit" msgstr "<Return>\t\tAller au résultat de recherche suivant" -#: gitk:3094 msgid "g\t\tGo to commit" msgstr "g\t\tAller au commit" -#: gitk:3095 msgid "/\t\tFocus the search box" msgstr "/\t\tFocus sur la zone de recherche" -#: gitk:3096 msgid "?\t\tMove to previous find hit" msgstr "?\t\tAller au résultat de recherche précédent" -#: gitk:3097 msgid "f\t\tScroll diff view to next file" msgstr "f\t\tAller au prochain fichier dans la vue des diff" -#: gitk:3098 #, tcl-format msgid "<%s-S>\t\tSearch for next hit in diff view" msgstr "<%s-S>\t\tAller au résultat suivant dans la vue des diff" -#: gitk:3099 #, tcl-format msgid "<%s-R>\t\tSearch for previous hit in diff view" msgstr "<%s-R>\t\tAller au résultat précédent dans la vue des diff" -#: gitk:3100 #, tcl-format msgid "<%s-KP+>\tIncrease font size" msgstr "<%s-KP+>\tAugmenter la taille de la police" -#: gitk:3101 #, tcl-format msgid "<%s-plus>\tIncrease font size" msgstr "<%s-plus>\tAugmenter la taille de la police" -#: gitk:3102 #, tcl-format msgid "<%s-KP->\tDecrease font size" msgstr "<%s-KP->\tDiminuer la taille de la police" -#: gitk:3103 #, tcl-format msgid "<%s-minus>\tDecrease font size" msgstr "<%s-minus>\tDiminuer la taille de la police" -#: gitk:3104 msgid "<F5>\t\tUpdate" msgstr "<F5>\t\tMise à jour" -#: gitk:3569 gitk:3578 #, tcl-format msgid "Error creating temporary directory %s:" msgstr "Erreur lors de la création du répertoire temporaire %s :" -#: gitk:3591 #, tcl-format msgid "Error getting \"%s\" from %s:" msgstr "Erreur en obtenant \"%s\" de %s:" -#: gitk:3654 msgid "command failed:" msgstr "échec de la commande :" -#: gitk:3803 msgid "No such commit" msgstr "Commit inexistant" -#: gitk:3817 msgid "git gui blame: command failed:" msgstr "git gui blame : échec de la commande :" -#: gitk:3848 #, tcl-format msgid "Couldn't read merge head: %s" msgstr "Impossible de lire le head de la fusion : %s" -#: gitk:3856 #, tcl-format msgid "Error reading index: %s" msgstr "Erreur à la lecture de l'index : %s" -#: gitk:3881 #, tcl-format msgid "Couldn't start git blame: %s" msgstr "Impossible de démarrer git blame : %s" -#: gitk:3884 gitk:6773 msgid "Searching" msgstr "Recherche en cours" -#: gitk:3916 #, tcl-format msgid "Error running git blame: %s" msgstr "Erreur à l'exécution de git blame : %s" -#: gitk:3944 #, tcl-format msgid "That line comes from commit %s, which is not in this view" msgstr "Cette ligne est issue du commit %s, qui n'est pas dans cette vue" -#: gitk:3958 msgid "External diff viewer failed:" msgstr "Échec de l'outil externe de visualisation des diff :" -#: gitk:4062 msgid "All files" msgstr "Tous les fichiers" -#: gitk:4086 msgid "View" msgstr "Vue" -#: gitk:4089 msgid "Gitk view definition" msgstr "Définition des vues de Gitk" -#: gitk:4093 msgid "Remember this view" msgstr "Se souvenir de cette vue" -#: gitk:4094 msgid "References (space separated list):" msgstr "Références (liste d'éléments séparés par des espaces) :" -#: gitk:4095 msgid "Branches & tags:" msgstr "Branches & étiquettes :" -#: gitk:4096 msgid "All refs" msgstr "Toutes les références" -#: gitk:4097 msgid "All (local) branches" msgstr "Toutes les branches (locales)" -#: gitk:4098 msgid "All tags" msgstr "Toutes les étiquettes" -#: gitk:4099 msgid "All remote-tracking branches" msgstr "Toutes les branches de suivi à distance" -#: gitk:4100 msgid "Commit Info (regular expressions):" msgstr "Info sur les commits (expressions régulières) :" -#: gitk:4101 msgid "Author:" msgstr "Auteur :" -#: gitk:4102 msgid "Committer:" msgstr "Validateur :" -#: gitk:4103 msgid "Commit Message:" msgstr "Message de commit :" -#: gitk:4104 msgid "Matches all Commit Info criteria" msgstr "Correspond à tous les critères d'Info sur les commits" -#: gitk:4105 msgid "Matches no Commit Info criteria" msgstr "Ne correspond à aucun des critères d'Info sur les commits" -#: gitk:4106 msgid "Changes to Files:" msgstr "Changements des fichiers :" -#: gitk:4107 msgid "Fixed String" msgstr "Chaîne Figée" -#: gitk:4108 msgid "Regular Expression" msgstr "Expression Régulière" -#: gitk:4109 msgid "Search string:" msgstr "Recherche de la chaîne :" -#: gitk:4110 msgid "" "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" @@ -709,201 +549,153 @@ "Dates des commits (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, " "2009 15:27:38\") :" -#: gitk:4111 msgid "Since:" msgstr "Depuis :" -#: gitk:4112 msgid "Until:" msgstr "Jusqu'au :" -#: gitk:4113 msgid "Limit and/or skip a number of revisions (positive integer):" msgstr "Limiter et/ou sauter un certain nombre (entier positif) de révisions :" -#: gitk:4114 msgid "Number to show:" msgstr "Nombre à afficher :" -#: gitk:4115 msgid "Number to skip:" msgstr "Nombre à sauter :" -#: gitk:4116 msgid "Miscellaneous options:" msgstr "Options diverses :" -#: gitk:4117 msgid "Strictly sort by date" msgstr "Trier par date" -#: gitk:4118 msgid "Mark branch sides" msgstr "Indiquer les côtés des branches" -#: gitk:4119 msgid "Limit to first parent" msgstr "Limiter au premier ancêtre" -#: gitk:4120 msgid "Simple history" msgstr "Historique simple" -#: gitk:4121 msgid "Additional arguments to git log:" msgstr "Arguments supplémentaires de git log :" -#: gitk:4122 msgid "Enter files and directories to include, one per line:" msgstr "Saisir les fichiers et répertoires à inclure, un par ligne :" -#: gitk:4123 msgid "Command to generate more commits to include:" msgstr "Commande pour générer plus de commits à inclure :" -#: gitk:4247 msgid "Gitk: edit view" msgstr "Gitk : éditer la vue" -#: gitk:4255 msgid "-- criteria for selecting revisions" msgstr "-- critère pour la sélection des révisions" -#: gitk:4260 msgid "View Name" msgstr "Nom de la vue" -#: gitk:4335 msgid "Apply (F5)" msgstr "Appliquer (F5)" -#: gitk:4373 msgid "Error in commit selection arguments:" msgstr "Erreur dans les arguments de sélection des commits :" -#: gitk:4428 gitk:4481 gitk:4943 gitk:4957 gitk:6227 gitk:12410 gitk:12411 msgid "None" msgstr "Aucun" -#: gitk:5040 gitk:5045 msgid "Descendant" msgstr "Descendant" -#: gitk:5041 msgid "Not descendant" msgstr "Pas un descendant" -#: gitk:5048 gitk:5053 msgid "Ancestor" msgstr "Ancêtre" -#: gitk:5049 msgid "Not ancestor" msgstr "Pas un ancêtre" -#: gitk:5343 msgid "Local changes checked in to index but not committed" msgstr "Modifications locales enregistrées dans l'index mais non validées" -#: gitk:5379 msgid "Local uncommitted changes, not checked in to index" msgstr "Modifications locales non enregistrées dans l'index et non validées" -#: gitk:7153 msgid "and many more" msgstr "et beaucoup plus" -#: gitk:7156 msgid "many" msgstr "nombreux" -#: gitk:7347 msgid "Tags:" msgstr "Étiquettes :" -#: gitk:7364 gitk:7370 gitk:8844 msgid "Parent" msgstr "Parent" -#: gitk:7375 msgid "Child" msgstr "Enfant" -#: gitk:7384 msgid "Branch" msgstr "Branche" -#: gitk:7387 msgid "Follows" msgstr "Suit" -#: gitk:7390 msgid "Precedes" msgstr "Précède" -#: gitk:7985 #, tcl-format msgid "Error getting diffs: %s" msgstr "Erreur lors de la récupération des diff : %s" -#: gitk:8669 msgid "Goto:" msgstr "Aller à :" -#: gitk:8690 #, tcl-format msgid "Short SHA1 id %s is ambiguous" msgstr "L'id SHA1 court %s est ambigu" -#: gitk:8697 #, tcl-format msgid "Revision %s is not known" msgstr "La révision %s est inconnu" -#: gitk:8707 #, tcl-format msgid "SHA1 id %s is not known" msgstr "L'id SHA1 %s est inconnu" -#: gitk:8709 #, tcl-format msgid "Revision %s is not in the current view" msgstr "La révision %s n'est pas dans la vue courante" -#: gitk:8851 gitk:8866 msgid "Date" msgstr "Date" -#: gitk:8854 msgid "Children" msgstr "Enfants" -#: gitk:8917 #, tcl-format msgid "Reset %s branch to here" msgstr "Réinitialiser la branche %s vers cet état" -#: gitk:8919 msgid "Detached head: can't reset" msgstr "Head détaché : impossible de réinitialiser" -#: gitk:9024 gitk:9030 msgid "Skipping merge commit " msgstr "Éviter le commit de la fusion " -#: gitk:9039 gitk:9044 msgid "Error getting patch ID for " msgstr "Erreur à l'obtention de l'ID du patch pour " -#: gitk:9040 gitk:9045 msgid " - stopping\n" msgstr " - arrêt en cours\n" -#: gitk:9050 gitk:9053 gitk:9061 gitk:9075 gitk:9084 msgid "Commit " msgstr "Commit " -#: gitk:9054 msgid "" " is the same patch as\n" " " @@ -911,7 +703,6 @@ "est le même patch que \n" " " -#: gitk:9062 msgid "" " differs from\n" " " @@ -919,146 +710,118 @@ " diffère de\n" " " -#: gitk:9064 msgid "" "Diff of commits:\n" "\n" -msgstr "Diff des commits :\n\n" +msgstr "" +"Diff des commits :\n" +"\n" -#: gitk:9076 gitk:9085 #, tcl-format msgid " has %s children - stopping\n" msgstr " a %s enfants - arrêt en cours\n" -#: gitk:9104 #, tcl-format msgid "Error writing commit to file: %s" msgstr "Erreur à l'écriture du commit dans le fichier : %s" -#: gitk:9110 #, tcl-format msgid "Error diffing commits: %s" msgstr "Erreur à la différence des commits : %s" -#: gitk:9156 msgid "Top" msgstr "Haut" -#: gitk:9157 msgid "From" msgstr "De" -#: gitk:9162 msgid "To" msgstr "À" -#: gitk:9186 msgid "Generate patch" msgstr "Générer le patch" -#: gitk:9188 msgid "From:" msgstr "De :" -#: gitk:9197 msgid "To:" msgstr "À :" -#: gitk:9206 msgid "Reverse" msgstr "Inverser" -#: gitk:9208 gitk:9418 msgid "Output file:" msgstr "Fichier de sortie :" -#: gitk:9214 msgid "Generate" msgstr "Générer" -#: gitk:9252 msgid "Error creating patch:" msgstr "Erreur à la création du patch :" -#: gitk:9275 gitk:9406 gitk:9463 msgid "ID:" msgstr "ID :" -#: gitk:9284 msgid "Tag name:" msgstr "Nom de l'étiquette :" -#: gitk:9287 msgid "Tag message is optional" msgstr "Le message d'étiquette est optionnel" -#: gitk:9289 msgid "Tag message:" msgstr "Message d'étiquette :" -#: gitk:9293 gitk:9472 msgid "Create" msgstr "Créer" -#: gitk:9311 msgid "No tag name specified" msgstr "Aucun nom d'étiquette spécifié" -#: gitk:9315 #, tcl-format msgid "Tag \"%s\" already exists" msgstr "L'étiquette \"%s\" existe déjà" -#: gitk:9325 msgid "Error creating tag:" msgstr "Erreur à la création de l'étiquette :" -#: gitk:9415 msgid "Command:" msgstr "Commande :" -#: gitk:9423 msgid "Write" msgstr "Écrire" -#: gitk:9441 msgid "Error writing commit:" msgstr "Erreur à l'ecriture du commit :" -#: gitk:9468 msgid "Name:" msgstr "Nom :" -#: gitk:9491 msgid "Please specify a name for the new branch" msgstr "Veuillez spécifier un nom pour la nouvelle branche" -#: gitk:9496 #, tcl-format msgid "Branch '%s' already exists. Overwrite?" msgstr "La branche '%s' existe déjà. Écraser?" -#: gitk:9563 #, tcl-format msgid "Commit %s is already included in branch %s -- really re-apply it?" msgstr "" "Le Commit %s est déjà inclus dans la branche %s -- le ré-appliquer malgré " "tout?" -#: gitk:9568 msgid "Cherry-picking" msgstr "Picorer (Cherry-picking)" -#: gitk:9577 #, tcl-format msgid "" "Cherry-pick failed because of local changes to file '%s'.\n" "Please commit, reset or stash your changes and try again." msgstr "" -"Le picorage (cherry-pick) a échouée à cause de modifications locales du fichier '%s'.\n" -"Veuillez commiter, réinitialiser ou stasher vos changements et essayer de nouveau." +"Le picorage (cherry-pick) a échouée à cause de modifications locales du " +"fichier '%s'.\n" +"Veuillez commiter, réinitialiser ou stasher vos changements et essayer de " +"nouveau." -#: gitk:9583 msgid "" "Cherry-pick failed because of merge conflict.\n" "Do you wish to run git citool to resolve it?" @@ -1066,27 +829,26 @@ "Le picorage (cherry-pick) a échouée à cause d'un conflit lors d'une fusion.\n" "Souhaitez-vous exécuter git citool pour le résoudre ?" -#: gitk:9599 gitk:9657 msgid "No changes committed" msgstr "Aucune modification validée" -#: gitk:9626 #, tcl-format msgid "Commit %s is not included in branch %s -- really revert it?" -msgstr "Le Commit %s n'est pas inclus dans la branche %s -- le défaire malgré tout?" +msgstr "" +"Le Commit %s n'est pas inclus dans la branche %s -- le défaire malgré tout?" -#: gitk:9631 msgid "Reverting" msgstr "Commit défait" -#: gitk:9639 #, 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 "Échec en tentant de défaire le commit à cause de modifications locales des fichiers : %s. Veuillez valider, réinitialiser ou remiser vos modifications et essayer de nouveau." +msgstr "" +"Échec en tentant de défaire le commit à cause de modifications locales des " +"fichiers : %s. Veuillez valider, réinitialiser ou remiser vos modifications " +"et essayer de nouveau." -#: gitk:9643 msgid "" "Revert failed because of merge conflict.\n" " Do you wish to run git citool to resolve it?" @@ -1094,30 +856,24 @@ "Échec en tentant de défaire à cause d'un conflit de fusion.\n" "Souhaitez-vous exécuter git citool pour le résoudre ?" -#: gitk:9686 msgid "Confirm reset" msgstr "Confirmer la réinitialisation" -#: gitk:9688 #, tcl-format msgid "Reset branch %s to %s?" msgstr "Réinitialiser la branche %s à %s?" -#: gitk:9690 msgid "Reset type:" msgstr "Type de réinitialisation :" -#: gitk:9693 msgid "Soft: Leave working tree and index untouched" msgstr "Douce : Laisse le répertoire de travail et l'index intacts" -#: gitk:9696 msgid "Mixed: Leave working tree untouched, reset index" msgstr "" "Hybride : Laisse le répertoire de travail dans son état courant, " "réinitialise l'index" -#: gitk:9699 msgid "" "Hard: Reset working tree and index\n" "(discard ALL local changes)" @@ -1125,20 +881,16 @@ "Dure : Réinitialise le répertoire de travail et l'index\n" "(abandonne TOUTES les modifications locale)" -#: gitk:9716 msgid "Resetting" msgstr "Réinitialisation" # Fixme: Récupération est-il vraiment une mauvaise traduction? -#: gitk:9776 msgid "Checking out" msgstr "Extraction" -#: gitk:9829 msgid "Cannot delete the currently checked-out branch" msgstr "Impossible de supprimer la branche extraite" -#: gitk:9835 #, tcl-format msgid "" "The commits on branch %s aren't on any other branch.\n" @@ -1147,16 +899,13 @@ "Les commits de la branche %s ne sont dans aucune autre branche.\n" "Voulez-vous vraiment supprimer cette branche %s ?" -#: gitk:9866 #, tcl-format msgid "Tags and heads: %s" msgstr "Étiquettes et heads : %s" -#: gitk:9883 msgid "Filter" msgstr "Filtrer" -#: gitk:10179 msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." @@ -1165,202 +914,153 @@ "informations sur les branches et les tags précédents/suivants seront " "incomplètes." -#: gitk:11156 msgid "Tag" msgstr "Étiquette" -#: gitk:11160 msgid "Id" msgstr "Id" -#: gitk:11243 msgid "Gitk font chooser" msgstr "Sélecteur de police de Gitk" -#: gitk:11260 msgid "B" msgstr "B" -#: gitk:11263 msgid "I" msgstr "I" -#: gitk:11381 msgid "Commit list display options" msgstr "Options d'affichage de la liste des commits" -#: gitk:11384 msgid "Maximum graph width (lines)" msgstr "Longueur maximum du graphe (lignes)" # FIXME : Traduction standard de "pane"? -#: gitk:11388 #, no-tcl-format msgid "Maximum graph width (% of pane)" msgstr "Largeur maximum du graphe (% du panneau)" -#: gitk:11391 msgid "Show local changes" msgstr "Montrer les modifications locales" -#: gitk:11394 msgid "Auto-select SHA1 (length)" msgstr "Sélection auto. du SHA1 (longueur)" -#: gitk:11398 msgid "Hide remote refs" msgstr "Cacher les refs distantes" -#: gitk:11402 msgid "Diff display options" msgstr "Options d'affichage des diff" -#: gitk:11404 msgid "Tab spacing" msgstr "Taille des tabulations" -#: gitk:11407 msgid "Display nearby tags/heads" msgstr "Afficher les tags les plus proches" -#: gitk:11410 msgid "Maximum # tags/heads to show" msgstr "Nombre maximum d'étiquettes/heads à afficher" -#: gitk:11413 msgid "Limit diffs to listed paths" msgstr "Limiter les différences aux chemins listés" -#: gitk:11416 msgid "Support per-file encodings" msgstr "Support pour un encodage des caractères par fichier" -#: gitk:11422 gitk:11569 msgid "External diff tool" msgstr "Outil diff externe" -#: gitk:11423 msgid "Choose..." msgstr "Choisir..." -#: gitk:11428 msgid "General options" msgstr "Options générales" -#: gitk:11431 msgid "Use themed widgets" msgstr "Utiliser des widgets en thème" -#: gitk:11433 msgid "(change requires restart)" msgstr "(la modification nécessite un redémarrage)" -#: gitk:11435 msgid "(currently unavailable)" msgstr "(non disponible actuellement)" -#: gitk:11446 msgid "Colors: press to choose" msgstr "Couleurs : cliquer pour choisir" -#: gitk:11449 msgid "Interface" msgstr "Interface" -#: gitk:11450 msgid "interface" msgstr "interface" -#: gitk:11453 msgid "Background" msgstr "Arrière-plan" -#: gitk:11454 gitk:11484 msgid "background" msgstr "arrière-plan" -#: gitk:11457 msgid "Foreground" msgstr "Premier plan" -#: gitk:11458 msgid "foreground" msgstr "premier plan" -#: gitk:11461 msgid "Diff: old lines" msgstr "Diff : anciennes lignes" -#: gitk:11462 msgid "diff old lines" msgstr "diff anciennes lignes" -#: gitk:11466 msgid "Diff: new lines" msgstr "Diff : nouvelles lignes" -#: gitk:11467 msgid "diff new lines" msgstr "diff nouvelles lignes" -#: gitk:11471 msgid "Diff: hunk header" msgstr "Diff : entête du hunk" -#: gitk:11473 msgid "diff hunk header" msgstr "diff : entête du hunk" -#: gitk:11477 msgid "Marked line bg" msgstr "Fond de la ligne marquée" -#: gitk:11479 msgid "marked line background" msgstr "Fond de la ligne marquée" -#: gitk:11483 msgid "Select bg" msgstr "Sélectionner le fond" -#: gitk:11492 msgid "Fonts: press to choose" msgstr "Polices : cliquer pour choisir" -#: gitk:11494 msgid "Main font" msgstr "Police principale" -#: gitk:11495 msgid "Diff display font" msgstr "Police d'affichage des diff" -#: gitk:11496 msgid "User interface font" msgstr "Police de l'interface utilisateur" -#: gitk:11518 msgid "Gitk preferences" msgstr "Préférences de Gitk" -#: gitk:11527 msgid "General" msgstr "Général" -#: gitk:11528 msgid "Colors" msgstr "Couleurs" -#: gitk:11529 msgid "Fonts" msgstr "Polices" -#: gitk:11579 #, tcl-format msgid "Gitk: choose color for %s" msgstr "Gitk : choisir la couleur de %s" -#: gitk:12092 msgid "" "Sorry, gitk cannot run with this version of Tcl/Tk.\n" " Gitk requires at least Tcl/Tk 8.4." @@ -1368,16 +1068,13 @@ "Désolé, gitk ne peut être exécuté avec cette version de Tcl/Tk.\n" " Gitk requiert Tcl/Tk version 8.4 ou supérieur." -#: gitk:12302 msgid "Cannot find a git repository here." msgstr "Impossible de trouver un dépôt git ici." -#: gitk:12349 #, tcl-format msgid "Ambiguous argument '%s': both revision and filename" msgstr "Argument '%s' ambigu : à la fois une révision et un nom de fichier" -#: gitk:12361 msgid "Bad arguments to gitk:" msgstr "Arguments invalides pour gitk :"
diff --git a/gitk-git/po/hu.po b/gitk-git/po/hu.po index 79ec5a5..9ab3f96 100644 --- a/gitk-git/po/hu.po +++ b/gitk-git/po/hu.po
@@ -6,7 +6,7 @@ # msgid "" msgstr "" -"Project-Id-Version: git-gui\n" +"Project-Id-Version: Gitk\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-05-17 14:32+1000\n" "PO-Revision-Date: 2009-12-14 14:04+0100\n" @@ -17,32 +17,25 @@ "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: gitk:140 msgid "Couldn't get list of unmerged files:" msgstr "Nem sikerült letölteni az unmerged fájl listát:" -#: gitk:212 gitk:2381 msgid "Color words" msgstr "" -#: gitk:217 gitk:2381 gitk:8220 gitk:8253 msgid "Markup words" msgstr "" -#: gitk:324 msgid "Error parsing revisions:" msgstr "Hiba történt értelmezés közben:" -#: gitk:380 msgid "Error executing --argscmd command:" msgstr "Hiba történt a végrehajtáskor --argscmd parancs:" -#: gitk:393 msgid "No files selected: --merge specified but no files are unmerged." msgstr "" "Nincsen fájl kiválasztva: --merge megadve, de egyetlen fájl sem unmerged." -#: gitk:396 msgid "" "No files selected: --merge specified but no unmerged files are within file " "limit." @@ -50,317 +43,237 @@ "Nincsen fájl kiválasztva: --merge megadva, de nincsenek unmerged fájlok a " "fájlon belül limit." -#: gitk:418 gitk:566 msgid "Error executing git log:" msgstr "Hiba történt a git log végrehajtása közben:" -#: gitk:436 gitk:582 msgid "Reading" msgstr "Olvasás" -#: gitk:496 gitk:4525 msgid "Reading commits..." msgstr "Commitok olvasása ..." -#: gitk:499 gitk:1637 gitk:4528 msgid "No commits selected" msgstr "Nincsen commit kiválasztva" -#: gitk:1445 gitk:4045 gitk:12432 msgid "Command line" msgstr "Parancs sor" -#: gitk:1511 msgid "Can't parse git log output:" msgstr "Nem lehet értelmezni a git log kimenetét:" -#: gitk:1740 msgid "No commit information available" msgstr "Nincsen elérhető commit információ" -#: gitk:1903 gitk:1932 gitk:4315 gitk:9669 gitk:11241 gitk:11521 msgid "OK" msgstr "OK" -#: gitk:1934 gitk:4317 gitk:9196 gitk:9275 gitk:9391 gitk:9440 gitk:9671 -#: gitk:11242 gitk:11522 msgid "Cancel" msgstr "Visszavonás" -#: gitk:2069 msgid "&Update" msgstr "Frissités" -#: gitk:2070 msgid "&Reload" msgstr "Újratöltés" -#: gitk:2071 msgid "Reread re&ferences" msgstr "Referenciák újraolvasása" -#: gitk:2072 msgid "&List references" msgstr "Referenciák listázása" -#: gitk:2074 msgid "Start git &gui" msgstr "Git gui indítása" -#: gitk:2076 msgid "&Quit" msgstr "Kilépés" -#: gitk:2068 msgid "&File" msgstr "Fájl" -#: gitk:2080 msgid "&Preferences" msgstr "Beállítások" -#: gitk:2079 msgid "&Edit" msgstr "Szerkesztés" -#: gitk:2084 msgid "&New view..." msgstr "Új nézet ..." -#: gitk:2085 msgid "&Edit view..." msgstr "Nézet szerkesztése ..." -#: gitk:2086 msgid "&Delete view" msgstr "Nézet törlése" -#: gitk:2088 gitk:4043 msgid "&All files" msgstr "Minden fájl" -#: gitk:2083 gitk:4067 msgid "&View" msgstr "Nézet" -#: gitk:2093 gitk:2103 gitk:3012 msgid "&About gitk" msgstr "Gitk névjegy" -#: gitk:2094 gitk:2108 msgid "&Key bindings" msgstr "Billentyűkombináció" -#: gitk:2092 gitk:2107 msgid "&Help" msgstr "Segítség" -#: gitk:2185 gitk:8652 msgid "SHA1 ID:" msgstr "SHA1 ID:" -#: gitk:2229 msgid "Row" msgstr "Sor" -#: gitk:2267 msgid "Find" msgstr "Keresés" -#: gitk:2295 msgid "commit" msgstr "commit" -#: gitk:2299 gitk:2301 gitk:4687 gitk:4710 gitk:4734 gitk:6755 gitk:6827 -#: gitk:6912 msgid "containing:" msgstr "tartalmazás:" -#: gitk:2302 gitk:3526 gitk:3531 gitk:4763 msgid "touching paths:" msgstr "érintendő útvonalak:" -#: gitk:2303 gitk:4777 msgid "adding/removing string:" msgstr "string hozzáadása/törlése:" -#: gitk:2304 gitk:4779 msgid "changing lines matching:" msgstr "" -#: gitk:2313 gitk:2315 gitk:4766 msgid "Exact" msgstr "Pontos" -#: gitk:2315 gitk:4854 gitk:6723 msgid "IgnCase" msgstr "Kis/nagy betű nem számít" -#: gitk:2315 gitk:4736 gitk:4852 gitk:6719 msgid "Regexp" msgstr "Regexp" -#: gitk:2317 gitk:2318 gitk:4874 gitk:4904 gitk:4911 gitk:6848 gitk:6916 msgid "All fields" msgstr "Minden mező" -#: gitk:2318 gitk:4871 gitk:4904 gitk:6786 msgid "Headline" msgstr "Főcím" -#: gitk:2319 gitk:4871 gitk:6786 gitk:6916 gitk:7389 msgid "Comments" msgstr "Megjegyzések" -#: gitk:2319 gitk:4871 gitk:4876 gitk:4911 gitk:6786 gitk:7324 gitk:8830 -#: gitk:8845 msgid "Author" msgstr "Szerző" -#: gitk:2319 gitk:4871 gitk:6786 gitk:7326 msgid "Committer" msgstr "Commitoló" -#: gitk:2350 msgid "Search" msgstr "Keresés" -#: gitk:2358 msgid "Diff" msgstr "Diff" -#: gitk:2360 msgid "Old version" msgstr "Régi verzió" -#: gitk:2362 msgid "New version" msgstr "Új verzió" -#: gitk:2364 msgid "Lines of context" msgstr "Tartalmi sorok" -#: gitk:2374 msgid "Ignore space change" msgstr "Space váltás mellőzése" -#: gitk:2378 gitk:2380 gitk:7959 gitk:8206 msgid "Line diff" msgstr "" -#: gitk:2445 msgid "Patch" msgstr "Patch" -#: gitk:2447 msgid "Tree" msgstr "Tree" -#: gitk:2617 gitk:2637 msgid "Diff this -> selected" msgstr "Diff ezeket -> kiválasztott" -#: gitk:2618 gitk:2638 msgid "Diff selected -> this" msgstr "Diff kiválasztottakat -> ezt" -#: gitk:2619 gitk:2639 msgid "Make patch" msgstr "Patch készítése" -#: gitk:2620 gitk:9254 msgid "Create tag" msgstr "Tag készítése" -#: gitk:2621 gitk:9371 msgid "Write commit to file" msgstr "Commit fáljba írása" -#: gitk:2622 gitk:9428 msgid "Create new branch" msgstr "Új branch készítése" -#: gitk:2623 msgid "Cherry-pick this commit" msgstr "Cherry-pick erre a commitra" -#: gitk:2624 msgid "Reset HEAD branch to here" msgstr "HEAD branch újraindítása ide" -#: gitk:2625 msgid "Mark this commit" msgstr "Ezen commit megjelölése" -#: gitk:2626 msgid "Return to mark" msgstr "Visszatérés a megjelöléshez" -#: gitk:2627 msgid "Find descendant of this and mark" msgstr "Találd meg ezen utódokat és jelöld meg" -#: gitk:2628 msgid "Compare with marked commit" msgstr "Összehasonlítás a megjelölt commit-tal" -#: gitk:2629 gitk:2640 #, fuzzy msgid "Diff this -> marked commit" msgstr "Diff ezeket -> kiválasztott" -#: gitk:2630 gitk:2641 #, fuzzy msgid "Diff marked commit -> this" msgstr "Diff kiválasztottakat -> ezt" -#: gitk:2631 #, fuzzy msgid "Revert this commit" msgstr "Ezen commit megjelölése" -#: gitk:2647 msgid "Check out this branch" msgstr "Check out ezt a branchot" -#: gitk:2648 msgid "Remove this branch" msgstr "Töröld ezt a branch-ot" -#: gitk:2649 msgid "Copy branch name" msgstr "" -#: gitk:2656 msgid "Highlight this too" msgstr "Emeld ki ezt is" -#: gitk:2657 msgid "Highlight this only" msgstr "Csak ezt emeld ki" -#: gitk:2658 msgid "External diff" msgstr "Külső diff" -#: gitk:2659 msgid "Blame parent commit" msgstr "Blame szülő kommitra" -#: gitk:2660 msgid "Copy path" msgstr "" -#: gitk:2667 msgid "Show origin of this line" msgstr "Mutasd meg ennek a sornak az eredetét" -#: gitk:2668 msgid "Run git gui blame on this line" msgstr "Futtasd a git gui blame-t ezen a soron" -#: gitk:3014 #, fuzzy msgid "" "\n" @@ -377,321 +290,249 @@ "\n" "Használd és terjeszd a GNU General Public License feltételei mellett" -#: gitk:3022 gitk:3089 gitk:9857 msgid "Close" msgstr "Bezárás" -#: gitk:3043 msgid "Gitk key bindings" msgstr "Gitk-billentyű hozzárendelés" -#: gitk:3046 msgid "Gitk key bindings:" msgstr "Gitk-billentyű hozzaárendelés:" -#: gitk:3048 #, tcl-format msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\tKilépés" -#: gitk:3049 #, fuzzy, tcl-format msgid "<%s-W>\t\tClose window" msgstr "<%s-F>\t\tKeresés" -#: gitk:3050 msgid "<Home>\t\tMove to first commit" msgstr "<Pos1>\t\tElső commithoz" -#: gitk:3051 msgid "<End>\t\tMove to last commit" msgstr "<Ende>\t\tUtolsó commithoz" -#: gitk:3052 #, fuzzy msgid "<Up>, p, k\tMove up one commit" msgstr "<Hoch>, p, i\tEgy committal feljebb" -#: gitk:3053 #, fuzzy msgid "<Down>, n, j\tMove down one commit" msgstr "<Runter>, n, k\tEgy committal lejjebb" -#: gitk:3054 #, fuzzy msgid "<Left>, z, h\tGo back in history list" msgstr "<Links>, z, j\tVissza a history listába" -#: gitk:3055 msgid "<Right>, x, l\tGo forward in history list" msgstr "<Rechts>, x, l\tElőre a history listába" -#: gitk:3056 #, tcl-format msgid "<%s-n>\tGo to n-th parent of current commit in history list" msgstr "" -#: gitk:3057 msgid "<PageUp>\tMove up one page in commit list" msgstr "<BildHoch>\tEgy lappal feljebb a commit listába" -#: gitk:3058 msgid "<PageDown>\tMove down one page in commit list" msgstr "<BildRunter>\tEgy lappal lejjebb a commit listába" -#: gitk:3059 #, tcl-format msgid "<%s-Home>\tScroll to top of commit list" msgstr "<%s-Pos1>\tGörgetés a commit lista tetejéhez" -#: gitk:3060 #, tcl-format msgid "<%s-End>\tScroll to bottom of commit list" msgstr "<%s-Ende>\tGörgetés a commit lista aljához" -#: gitk:3061 #, tcl-format msgid "<%s-Up>\tScroll commit list up one line" msgstr "<%s-Hoch>\tEgy sorral feljebb görgetés a commit listában" -#: gitk:3062 #, tcl-format msgid "<%s-Down>\tScroll commit list down one line" msgstr "<%s-Runter>\tEgy sorral lejjebb görgetés a commit listában" -#: gitk:3063 #, tcl-format msgid "<%s-PageUp>\tScroll commit list up one page" msgstr "<%s-BildHoch>\tEgy lappal feljebb görgetés a commit listában" -#: gitk:3064 #, tcl-format msgid "<%s-PageDown>\tScroll commit list down one page" msgstr "<%s-BildRunter>\tEgy sorral lejjebb görgetés a commit listában" -#: gitk:3065 msgid "<Shift-Up>\tFind backwards (upwards, later commits)" msgstr "<Umschalt-Hoch>\tKeresés visszafele (felfele, utolsó commitok)" -#: gitk:3066 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" msgstr "<Umschalt-Runter>\tKeresés előre (lefelé; korábbi commitok)" -#: gitk:3067 msgid "<Delete>, b\tScroll diff view up one page" msgstr "<Entf>, b\t\tEgy lappal feljebb görgetés a diff nézetben" -#: gitk:3068 msgid "<Backspace>\tScroll diff view up one page" msgstr "<Löschtaste>\tEgy lappal feljebb görgetés a diff nézetben" -#: gitk:3069 msgid "<Space>\t\tScroll diff view down one page" msgstr "<Leertaste>\tEgy lappal lejjebb görgetés a diff nézetben" -#: gitk:3070 msgid "u\t\tScroll diff view up 18 lines" msgstr "u\t\t18 sorral felfelé görgetés diff nézetben" -#: gitk:3071 msgid "d\t\tScroll diff view down 18 lines" msgstr "d\t\t18 sorral lejjebb görgetés a diff nézetben" -#: gitk:3072 #, tcl-format msgid "<%s-F>\t\tFind" msgstr "<%s-F>\t\tKeresés" -#: gitk:3073 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" msgstr "<%s-G>\t\tKövetkező találathoz" -#: gitk:3074 msgid "<Return>\tMove to next find hit" msgstr "<Eingabetaste>\tKövetkező találathoz" -#: gitk:3075 #, fuzzy msgid "g\t\tGo to commit" msgstr "<Ende>\t\tUtolsó commithoz" -#: gitk:3076 msgid "/\t\tFocus the search box" msgstr "/\t\tLépj a keresési mezőre" -#: gitk:3077 msgid "?\t\tMove to previous find hit" msgstr "?\t\tElőző találathoz" -#: gitk:3078 msgid "f\t\tScroll diff view to next file" msgstr "f\t\tKövetkező fájlra görgetés diff nézetben" -#: gitk:3079 #, tcl-format msgid "<%s-S>\t\tSearch for next hit in diff view" msgstr "<%s-S>\t\tKövetkező találatra keresés diff nézetben" -#: gitk:3080 #, tcl-format msgid "<%s-R>\t\tSearch for previous hit in diff view" msgstr "<%s-R>\t\tElőző találatra keresés diff nézetben" -#: gitk:3081 #, tcl-format msgid "<%s-KP+>\tIncrease font size" msgstr "<%s-Nummerblock-Plus>\tBetűméret növelése" -#: gitk:3082 #, tcl-format msgid "<%s-plus>\tIncrease font size" msgstr "<%s-Plus>\tBetűméret növelése" -#: gitk:3083 #, tcl-format msgid "<%s-KP->\tDecrease font size" msgstr "<%s-Nummernblock-Minus> Betűméret csökkentése" -#: gitk:3084 #, tcl-format msgid "<%s-minus>\tDecrease font size" msgstr "<%s-Minus>\tBetűméret csökkentése" -#: gitk:3085 msgid "<F5>\t\tUpdate" msgstr "<F5>\t\tFrissítés" -#: gitk:3550 gitk:3559 #, tcl-format msgid "Error creating temporary directory %s:" msgstr "Hiba történt az ideiglenes könyvtár létrehozása közben %s:" -#: gitk:3572 #, tcl-format msgid "Error getting \"%s\" from %s:" msgstr "Hiba történt \"%s\" letöltése közben %s-ről:" -#: gitk:3635 msgid "command failed:" msgstr "parancs hiba:" -#: gitk:3784 msgid "No such commit" msgstr "Nincs ilyen commit" -#: gitk:3798 msgid "git gui blame: command failed:" msgstr "git gui blame: parancs hiba:" -#: gitk:3829 #, tcl-format msgid "Couldn't read merge head: %s" msgstr "Nem sikerült a Merge head olvasása: %s" -#: gitk:3837 #, tcl-format msgid "Error reading index: %s" msgstr "Hiba történt az index olvasása közben: %s" -#: gitk:3862 #, tcl-format msgid "Couldn't start git blame: %s" msgstr "Nem sikerült a git blame indítása: %s" -#: gitk:3865 gitk:6754 msgid "Searching" msgstr "Keresés" -#: gitk:3897 #, tcl-format msgid "Error running git blame: %s" msgstr "Hiba történt a git blame futtatása közben: %s" -#: gitk:3925 #, tcl-format msgid "That line comes from commit %s, which is not in this view" msgstr "" "A %s commitból származik az a sor, amelyik nem található ebben a nézetben" -#: gitk:3939 msgid "External diff viewer failed:" msgstr "Külső diff nézegető hiba:" -#: gitk:4070 msgid "Gitk view definition" msgstr "Gitk nézet meghatározása" -#: gitk:4074 msgid "Remember this view" msgstr "Maradj ennél a nézetnél" -#: gitk:4075 msgid "References (space separated list):" msgstr "Referenciák (szóközzel tagolt lista" -#: gitk:4076 msgid "Branches & tags:" msgstr "Branch-ek & tagek:" -#: gitk:4077 msgid "All refs" msgstr "Minden ref" -#: gitk:4078 msgid "All (local) branches" msgstr "Minden (helyi) branch" -#: gitk:4079 msgid "All tags" msgstr "Minden tag" -#: gitk:4080 msgid "All remote-tracking branches" msgstr "Minden távoli követő branch" -#: gitk:4081 msgid "Commit Info (regular expressions):" msgstr "Commit Infó (reguláris kifejezés):" -#: gitk:4082 msgid "Author:" msgstr "Szerző:" -#: gitk:4083 msgid "Committer:" msgstr "Commitoló:" -#: gitk:4084 msgid "Commit Message:" msgstr "Commit üzenet:" -#: gitk:4085 msgid "Matches all Commit Info criteria" msgstr "Egyezik minen Commit Infó feltétellel" -#: gitk:4086 #, fuzzy msgid "Matches no Commit Info criteria" msgstr "Egyezik minen Commit Infó feltétellel" -#: gitk:4087 msgid "Changes to Files:" msgstr "Fájl változások:" -#: gitk:4088 msgid "Fixed String" msgstr "Fix String" -#: gitk:4089 msgid "Regular Expression" msgstr "Reguláris kifejezés" -#: gitk:4090 msgid "Search string:" msgstr "Keresés szöveg:" -#: gitk:4091 msgid "" "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" @@ -699,203 +540,155 @@ "Commit Dátumok (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" -#: gitk:4092 msgid "Since:" msgstr "Ettől:" -#: gitk:4093 msgid "Until:" msgstr "Eddig:" -#: gitk:4094 msgid "Limit and/or skip a number of revisions (positive integer):" msgstr "Limitálva és/vagy kihagyva egy adott számú revíziót (pozitív egész):" -#: gitk:4095 msgid "Number to show:" msgstr "Mutatandó szám:" -#: gitk:4096 msgid "Number to skip:" msgstr "Kihagyandó szám:" -#: gitk:4097 msgid "Miscellaneous options:" msgstr "Különféle opciók:" -#: gitk:4098 msgid "Strictly sort by date" msgstr "Szigorú rendezás dátum alapján" -#: gitk:4099 msgid "Mark branch sides" msgstr "Jelölje meg az ágakat" -#: gitk:4100 msgid "Limit to first parent" msgstr "Korlátozás az első szülőre" -#: gitk:4101 msgid "Simple history" msgstr "Egyszerű history" -#: gitk:4102 msgid "Additional arguments to git log:" msgstr "További argumentok a git log-hoz:" -#: gitk:4103 msgid "Enter files and directories to include, one per line:" msgstr "Fájlok és könyvtárak bejegyzése amiket tartalmaz, soronként:" -#: gitk:4104 msgid "Command to generate more commits to include:" msgstr "Parancs több tartalmazó commit generálására:" -#: gitk:4228 msgid "Gitk: edit view" msgstr "Gitk: szerkesztés nézet" -#: gitk:4236 msgid "-- criteria for selecting revisions" msgstr "-- kritériumok a revíziók kiválasztásához" -#: gitk:4241 msgid "View Name" msgstr "Nézet neve" -#: gitk:4316 msgid "Apply (F5)" msgstr "Alkalmaz (F5)" -#: gitk:4354 msgid "Error in commit selection arguments:" msgstr "Hiba történt a commit argumentumok kiválasztása közben:" -#: gitk:4409 gitk:4462 gitk:4924 gitk:4938 gitk:6208 gitk:12373 gitk:12374 msgid "None" msgstr "Keine" -#: gitk:5021 gitk:5026 msgid "Descendant" msgstr "Leszármazott" -#: gitk:5022 msgid "Not descendant" msgstr "Nem leszármazott" -#: gitk:5029 gitk:5034 msgid "Ancestor" msgstr "Előd" -#: gitk:5030 msgid "Not ancestor" msgstr "Nem előd" -#: gitk:5324 msgid "Local changes checked in to index but not committed" msgstr "" "Lokális változtatások, melyek be vannak téve az indexbe, de még nincsenek " "commitolva" -#: gitk:5360 msgid "Local uncommitted changes, not checked in to index" msgstr "Lokális nem commitolt változások, nincsenek betéve az indexbe" -#: gitk:7134 msgid "and many more" msgstr "" -#: gitk:7137 msgid "many" msgstr "sok" -#: gitk:7328 msgid "Tags:" msgstr "Tagek:" -#: gitk:7345 gitk:7351 gitk:8825 msgid "Parent" msgstr "Eltern" -#: gitk:7356 msgid "Child" msgstr "Gyerek" -#: gitk:7365 msgid "Branch" msgstr "Ág" -#: gitk:7368 msgid "Follows" msgstr "Következők" -#: gitk:7371 msgid "Precedes" msgstr "Megelőzők" -#: gitk:7966 #, tcl-format msgid "Error getting diffs: %s" msgstr "Hiba történt a diff-ek letöltése közben: %s" -#: gitk:8650 msgid "Goto:" msgstr "Menj:" -#: gitk:8671 #, tcl-format msgid "Short SHA1 id %s is ambiguous" msgstr "Rövid SHA1 id %s félreérthető" -#: gitk:8678 #, tcl-format msgid "Revision %s is not known" msgstr "A(z) %s revízió nem ismert" -#: gitk:8688 #, tcl-format msgid "SHA1 id %s is not known" msgstr "SHA1 id %s nem ismert" -#: gitk:8690 #, tcl-format msgid "Revision %s is not in the current view" msgstr "A(z) %s revízió nincs a jelenlegi nézetben" -#: gitk:8832 gitk:8847 msgid "Date" msgstr "Dátum" -#: gitk:8835 msgid "Children" msgstr "Gyerekek" -#: gitk:8898 #, tcl-format msgid "Reset %s branch to here" msgstr "Állítsd vissza a %s branch-ot ide" -#: gitk:8900 msgid "Detached head: can't reset" msgstr "Elkülönített head: nem lehet visszaállítani" -#: gitk:9005 gitk:9011 msgid "Skipping merge commit " msgstr "Merge commit kihagyása " -#: gitk:9020 gitk:9025 msgid "Error getting patch ID for " msgstr "Hiba történt a patch ID megszerzése közben a következőnél " -#: gitk:9021 gitk:9026 msgid " - stopping\n" msgstr " - abbahagyás\n" -#: gitk:9031 gitk:9034 gitk:9042 gitk:9056 gitk:9065 msgid "Commit " msgstr "Commit " -#: gitk:9035 msgid "" " is the same patch as\n" " " @@ -903,7 +696,6 @@ " Ugyanaz a patch mint\n" " " -#: gitk:9043 msgid "" " differs from\n" " " @@ -911,7 +703,6 @@ " különbözik innentől\n" " " -#: gitk:9045 msgid "" "Diff of commits:\n" "\n" @@ -919,132 +710,102 @@ "A commitok diffje:\n" "\n" -#: gitk:9057 gitk:9066 #, tcl-format msgid " has %s children - stopping\n" msgstr " %s gyereke van. abbahagyás\n" -#: gitk:9085 #, tcl-format msgid "Error writing commit to file: %s" msgstr "Hiba történt a commit fájlba írása közben: %s" -#: gitk:9091 #, tcl-format msgid "Error diffing commits: %s" msgstr "Hiba történt a commitok diffelése közben: %s" -#: gitk:9137 msgid "Top" msgstr "Teteje" -#: gitk:9138 msgid "From" msgstr "Innen" -#: gitk:9143 msgid "To" msgstr "Ide" -#: gitk:9167 msgid "Generate patch" msgstr "Patch generálása" -#: gitk:9169 msgid "From:" msgstr "Innen:" -#: gitk:9178 msgid "To:" msgstr "Ide:" -#: gitk:9187 msgid "Reverse" msgstr "Visszafele" -#: gitk:9189 gitk:9385 msgid "Output file:" msgstr "Kimeneti fájl:" -#: gitk:9195 msgid "Generate" msgstr "Generálás" -#: gitk:9233 msgid "Error creating patch:" msgstr "Hiba törtét a patch készítése közben:" -#: gitk:9256 gitk:9373 gitk:9430 msgid "ID:" msgstr "ID:" -#: gitk:9265 msgid "Tag name:" msgstr "Tag név:" -#: gitk:9268 msgid "Tag message is optional" msgstr "" -#: gitk:9270 #, fuzzy msgid "Tag message:" msgstr "Tag név:" -#: gitk:9274 gitk:9439 msgid "Create" msgstr "Létrehozás" -#: gitk:9292 msgid "No tag name specified" msgstr "A tag neve nincsen megadva" -#: gitk:9296 #, tcl-format msgid "Tag \"%s\" already exists" msgstr "%s Tag már létezik" -#: gitk:9306 msgid "Error creating tag:" msgstr "Hiba történt a tag létrehozása közben:" -#: gitk:9382 msgid "Command:" msgstr "Parancs:" -#: gitk:9390 msgid "Write" msgstr "Írás" -#: gitk:9408 msgid "Error writing commit:" msgstr "Hiba történt a commit írása közben:" -#: gitk:9435 msgid "Name:" msgstr "Név:" -#: gitk:9458 msgid "Please specify a name for the new branch" msgstr "Kérem adja meg a nevét az új branchhoz" -#: gitk:9463 #, tcl-format msgid "Branch '%s' already exists. Overwrite?" msgstr "%s branch már létezik. Felülírja?" -#: gitk:9530 #, tcl-format msgid "Commit %s is already included in branch %s -- really re-apply it?" msgstr "" "%s commit már benne van a %s branchban -- biztos hogy újra csinálja ?" "eintragen?" -#: gitk:9535 msgid "Cherry-picking" msgstr "Cherry-picking" -#: gitk:9544 #, tcl-format msgid "" "Cherry-pick failed because of local changes to file '%s'.\n" @@ -1054,7 +815,6 @@ "Kérem commitolja, indítsa újra vagy rejtse el a változtatásait és próbálja " "újra." -#: gitk:9550 msgid "" "Cherry-pick failed because of merge conflict.\n" "Do you wish to run git citool to resolve it?" @@ -1062,23 +822,19 @@ "Cherry-pick hiba történt merge konfliktus miatt.\n" "Kívánja futtatni a git citool-t a probléma megoldásához?" -#: gitk:9566 gitk:9624 msgid "No changes committed" msgstr "Nincsen változás commitolva" -#: gitk:9593 #, fuzzy, tcl-format msgid "Commit %s is not included in branch %s -- really revert it?" msgstr "" "%s commit már benne van a %s branchban -- biztos hogy újra csinálja ?" "eintragen?" -#: gitk:9598 #, fuzzy msgid "Reverting" msgstr "Újraindítás" -#: gitk:9606 #, fuzzy, tcl-format msgid "" "Revert failed because of local changes to the following files:%s Please " @@ -1088,7 +844,6 @@ "Kérem commitolja, indítsa újra vagy rejtse el a változtatásait és próbálja " "újra." -#: gitk:9610 #, fuzzy msgid "" "Revert failed because of merge conflict.\n" @@ -1097,28 +852,22 @@ "Cherry-pick hiba történt merge konfliktus miatt.\n" "Kívánja futtatni a git citool-t a probléma megoldásához?" -#: gitk:9653 msgid "Confirm reset" msgstr "Újraindítás megerősítése" -#: gitk:9655 #, tcl-format msgid "Reset branch %s to %s?" msgstr "Újraindítja a %s branchot %s-ig?" -#: gitk:9657 msgid "Reset type:" msgstr "Újraindítás típusa:" -#: gitk:9660 msgid "Soft: Leave working tree and index untouched" msgstr "Soft: Hagyd a working tree-t és az indexet érintetlenül" -#: gitk:9663 msgid "Mixed: Leave working tree untouched, reset index" msgstr "Kevert: Hagyd a working tree-t érintetlenül, töröld az indexet" -#: gitk:9666 msgid "" "Hard: Reset working tree and index\n" "(discard ALL local changes)" @@ -1126,19 +875,15 @@ "Hard: Indítsd újra a working tree-t és az indexet\n" "(MINDEN lokális változás eldobása)" -#: gitk:9683 msgid "Resetting" msgstr "Újraindítás" -#: gitk:9743 msgid "Checking out" msgstr "Kivesz" -#: gitk:9796 msgid "Cannot delete the currently checked-out branch" msgstr "Nem lehet a jelenleg kivett branch-ot törölni" -#: gitk:9802 #, tcl-format msgid "" "The commits on branch %s aren't on any other branch.\n" @@ -1147,16 +892,13 @@ "A %s branchon található commit nem található meg semelyik másik branchon.\n" "Tényleg törli a %s branchot?" -#: gitk:9833 #, tcl-format msgid "Tags and heads: %s" msgstr "Tagek és headek: %s" -#: gitk:9850 msgid "Filter" msgstr "Szűrő" -#: gitk:10146 msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." @@ -1164,204 +906,155 @@ "Hiba történt a commit topológiai információ olvasása közben; branch ésa " "megelőző/következő információ nem lesz teljes." -#: gitk:11123 msgid "Tag" msgstr "Tag" -#: gitk:11127 msgid "Id" msgstr "Id" -#: gitk:11210 msgid "Gitk font chooser" msgstr "Gitk-betű kiválasztó" -#: gitk:11227 msgid "B" msgstr "F" -#: gitk:11230 msgid "I" msgstr "K" -#: gitk:11348 msgid "Commit list display options" msgstr "Commit lista kijelzési opciók" -#: gitk:11351 msgid "Maximum graph width (lines)" msgstr "Maximális grafikon szélesség (sorok)" -#: gitk:11355 #, no-tcl-format msgid "Maximum graph width (% of pane)" msgstr "Maximális grafikon szélesség (táble %-je)" -#: gitk:11358 msgid "Show local changes" msgstr "Mutasd a lokális változtatásokat" -#: gitk:11361 #, fuzzy msgid "Auto-select SHA1 (length)" msgstr "SHA1 Automatikus kiválasztása" -#: gitk:11365 msgid "Hide remote refs" msgstr "A távoli refek elrejtése" -#: gitk:11369 msgid "Diff display options" msgstr "Diff kijelző opciók" -#: gitk:11371 msgid "Tab spacing" msgstr "Tab sorköz" -#: gitk:11374 #, fuzzy msgid "Display nearby tags/heads" msgstr "Szomszédos tagek kijelzése" -#: gitk:11377 msgid "Maximum # tags/heads to show" msgstr "" -#: gitk:11380 msgid "Limit diffs to listed paths" msgstr "Korlátozott diffek a kilistázott útvonalakhoz" -#: gitk:11383 msgid "Support per-file encodings" msgstr "Fájlonkénti kódolás támgatása" -#: gitk:11389 gitk:11536 msgid "External diff tool" msgstr "Külső diff alkalmazás" -#: gitk:11390 msgid "Choose..." msgstr "Válaszd ..." -#: gitk:11395 msgid "General options" msgstr "Általános opciók" -#: gitk:11398 msgid "Use themed widgets" msgstr "Témázott vezérlők használata" -#: gitk:11400 msgid "(change requires restart)" msgstr "(a változás újraindítást igényel)" -#: gitk:11402 msgid "(currently unavailable)" msgstr "(jelenleg nem elérhető)" -#: gitk:11413 msgid "Colors: press to choose" msgstr "Színek: nyomja meg a kiválasztáshoz" -#: gitk:11416 msgid "Interface" msgstr "Interfész" -#: gitk:11417 msgid "interface" msgstr "interfész" -#: gitk:11420 msgid "Background" msgstr "Háttér" -#: gitk:11421 gitk:11451 msgid "background" msgstr "háttér" -#: gitk:11424 msgid "Foreground" msgstr "Előtér" -#: gitk:11425 msgid "foreground" msgstr "előtér" -#: gitk:11428 msgid "Diff: old lines" msgstr "Diff: régi sorok" -#: gitk:11429 msgid "diff old lines" msgstr "diff régi sorok" -#: gitk:11433 msgid "Diff: new lines" msgstr "Diff: új sorok" -#: gitk:11434 msgid "diff new lines" msgstr "diff - új sorok" -#: gitk:11438 msgid "Diff: hunk header" msgstr "Diff: nagy headerök" -#: gitk:11440 msgid "diff hunk header" msgstr "diff - nagy headerök" -#: gitk:11444 msgid "Marked line bg" msgstr "Megjelölt sor háttér" -#: gitk:11446 msgid "marked line background" msgstr "megjelölt sor háttér" -#: gitk:11450 msgid "Select bg" msgstr "Válasszon hátteret" -#: gitk:11459 msgid "Fonts: press to choose" msgstr "Betű: nyomja meg a kiválasztáshoz" -#: gitk:11461 msgid "Main font" msgstr "Fő betű" -#: gitk:11462 msgid "Diff display font" msgstr "Diff kijelző betű" -#: gitk:11463 msgid "User interface font" msgstr "Felhasználói interfész betű" -#: gitk:11485 msgid "Gitk preferences" msgstr "Gitk beállítások" -#: gitk:11494 #, fuzzy msgid "General" msgstr "Generálás" -#: gitk:11495 msgid "Colors" msgstr "" -#: gitk:11496 msgid "Fonts" msgstr "" -#: gitk:11546 #, tcl-format msgid "Gitk: choose color for %s" msgstr "Gitk: válasszon színt a %s-ra" -#: gitk:12059 msgid "" "Sorry, gitk cannot run with this version of Tcl/Tk.\n" " Gitk requires at least Tcl/Tk 8.4." @@ -1369,16 +1062,13 @@ "Sajnáljuk, de a gitk nem futtatható ezzel a Tcl/Tk verzióval.\n" "Gitk futtatásához legalább Tcl/Tk 8.4 szükséges." -#: gitk:12269 msgid "Cannot find a git repository here." msgstr "Nem találhatü git repository itt." -#: gitk:12316 #, tcl-format msgid "Ambiguous argument '%s': both revision and filename" msgstr "Félreérthető argumentum '%s': revízió és fájlnév is" -#: gitk:12328 msgid "Bad arguments to gitk:" msgstr "Rossz gitk argumentumok:"
diff --git a/gitk-git/po/it.po b/gitk-git/po/it.po index b58d23e..aa34a34 100644 --- a/gitk-git/po/it.po +++ b/gitk-git/po/it.po
@@ -6,7 +6,7 @@ # msgid "" msgstr "" -"Project-Id-Version: gitk\n" +"Project-Id-Version: Gitk\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-05-17 14:32+1000\n" "PO-Revision-Date: 2010-01-28 18:41+0100\n" @@ -17,33 +17,26 @@ "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: gitk:140 msgid "Couldn't get list of unmerged files:" msgstr "Impossibile ottenere l'elenco dei file in attesa di fusione:" -#: gitk:212 gitk:2381 msgid "Color words" msgstr "" -#: gitk:217 gitk:2381 gitk:8220 gitk:8253 msgid "Markup words" msgstr "" -#: gitk:324 msgid "Error parsing revisions:" msgstr "Errore nella lettura delle revisioni:" -#: gitk:380 msgid "Error executing --argscmd command:" msgstr "Errore nell'esecuzione del comando specificato con --argscmd:" -#: gitk:393 msgid "No files selected: --merge specified but no files are unmerged." msgstr "" "Nessun file selezionato: è stata specificata l'opzione --merge ma non ci " "sono file in attesa di fusione." -#: gitk:396 msgid "" "No files selected: --merge specified but no unmerged files are within file " "limit." @@ -51,317 +44,237 @@ "Nessun file selezionato: è stata specificata l'opzione --merge ma i file " "specificati non sono in attesa di fusione." -#: gitk:418 gitk:566 msgid "Error executing git log:" msgstr "Errore nell'esecuzione di git log:" -#: gitk:436 gitk:582 msgid "Reading" msgstr "Lettura in corso" -#: gitk:496 gitk:4525 msgid "Reading commits..." msgstr "Lettura delle revisioni in corso..." -#: gitk:499 gitk:1637 gitk:4528 msgid "No commits selected" msgstr "Nessuna revisione selezionata" -#: gitk:1445 gitk:4045 gitk:12432 msgid "Command line" msgstr "Linea di comando" -#: gitk:1511 msgid "Can't parse git log output:" msgstr "Impossibile elaborare i dati di git log:" -#: gitk:1740 msgid "No commit information available" msgstr "Nessuna informazione disponibile sulle revisioni" -#: gitk:1903 gitk:1932 gitk:4315 gitk:9669 gitk:11241 gitk:11521 msgid "OK" msgstr "OK" -#: gitk:1934 gitk:4317 gitk:9196 gitk:9275 gitk:9391 gitk:9440 gitk:9671 -#: gitk:11242 gitk:11522 msgid "Cancel" msgstr "Annulla" -#: gitk:2069 msgid "&Update" msgstr "Aggiorna" -#: gitk:2070 msgid "&Reload" msgstr "Ricarica" -#: gitk:2071 msgid "Reread re&ferences" msgstr "Rileggi riferimenti" -#: gitk:2072 msgid "&List references" msgstr "Elenca riferimenti" -#: gitk:2074 msgid "Start git &gui" msgstr "Avvia git gui" -#: gitk:2076 msgid "&Quit" msgstr "Esci" -#: gitk:2068 msgid "&File" msgstr "&File" -#: gitk:2080 msgid "&Preferences" msgstr "Preferenze" -#: gitk:2079 msgid "&Edit" msgstr "Modifica" -#: gitk:2084 msgid "&New view..." msgstr "Nuova vista..." -#: gitk:2085 msgid "&Edit view..." msgstr "Modifica vista..." -#: gitk:2086 msgid "&Delete view" msgstr "Elimina vista" -#: gitk:2088 gitk:4043 msgid "&All files" msgstr "Tutti i file" -#: gitk:2083 gitk:4067 msgid "&View" msgstr "Vista" -#: gitk:2093 gitk:2103 gitk:3012 msgid "&About gitk" msgstr "Informazioni su gitk" -#: gitk:2094 gitk:2108 msgid "&Key bindings" msgstr "Scorciatoie da tastiera" -#: gitk:2092 gitk:2107 msgid "&Help" msgstr "Aiuto" -#: gitk:2185 gitk:8652 msgid "SHA1 ID:" msgstr "SHA1 ID:" -#: gitk:2229 msgid "Row" msgstr "Riga" -#: gitk:2267 msgid "Find" msgstr "Trova" -#: gitk:2295 msgid "commit" msgstr "revisione" -#: gitk:2299 gitk:2301 gitk:4687 gitk:4710 gitk:4734 gitk:6755 gitk:6827 -#: gitk:6912 msgid "containing:" msgstr "contenente:" -#: gitk:2302 gitk:3526 gitk:3531 gitk:4763 msgid "touching paths:" msgstr "che riguarda i percorsi:" -#: gitk:2303 gitk:4777 msgid "adding/removing string:" msgstr "che aggiunge/rimuove la stringa:" -#: gitk:2304 gitk:4779 msgid "changing lines matching:" msgstr "" -#: gitk:2313 gitk:2315 gitk:4766 msgid "Exact" msgstr "Esatto" -#: gitk:2315 gitk:4854 gitk:6723 msgid "IgnCase" msgstr "" -#: gitk:2315 gitk:4736 gitk:4852 gitk:6719 msgid "Regexp" msgstr "" -#: gitk:2317 gitk:2318 gitk:4874 gitk:4904 gitk:4911 gitk:6848 gitk:6916 msgid "All fields" msgstr "Tutti i campi" -#: gitk:2318 gitk:4871 gitk:4904 gitk:6786 msgid "Headline" msgstr "Titolo" -#: gitk:2319 gitk:4871 gitk:6786 gitk:6916 gitk:7389 msgid "Comments" msgstr "Commenti" -#: gitk:2319 gitk:4871 gitk:4876 gitk:4911 gitk:6786 gitk:7324 gitk:8830 -#: gitk:8845 msgid "Author" msgstr "Autore" -#: gitk:2319 gitk:4871 gitk:6786 gitk:7326 msgid "Committer" msgstr "Revisione creata da" -#: gitk:2350 msgid "Search" msgstr "Cerca" -#: gitk:2358 msgid "Diff" msgstr "" -#: gitk:2360 msgid "Old version" msgstr "Vecchia versione" -#: gitk:2362 msgid "New version" msgstr "Nuova versione" -#: gitk:2364 msgid "Lines of context" msgstr "Linee di contesto" -#: gitk:2374 msgid "Ignore space change" msgstr "Ignora modifiche agli spazi" -#: gitk:2378 gitk:2380 gitk:7959 gitk:8206 msgid "Line diff" msgstr "" -#: gitk:2445 msgid "Patch" msgstr "Modifiche" -#: gitk:2447 msgid "Tree" msgstr "Directory" -#: gitk:2617 gitk:2637 msgid "Diff this -> selected" msgstr "Diff questo -> selezionato" -#: gitk:2618 gitk:2638 msgid "Diff selected -> this" msgstr "Diff selezionato -> questo" -#: gitk:2619 gitk:2639 msgid "Make patch" msgstr "Crea patch" -#: gitk:2620 gitk:9254 msgid "Create tag" msgstr "Crea etichetta" -#: gitk:2621 gitk:9371 msgid "Write commit to file" msgstr "Scrivi revisione in un file" -#: gitk:2622 gitk:9428 msgid "Create new branch" msgstr "Crea un nuovo ramo" -#: gitk:2623 msgid "Cherry-pick this commit" msgstr "Porta questa revisione in cima al ramo attuale" -#: gitk:2624 msgid "Reset HEAD branch to here" msgstr "Aggiorna il ramo HEAD a questa revisione" -#: gitk:2625 msgid "Mark this commit" msgstr "Segna questa revisione" -#: gitk:2626 msgid "Return to mark" msgstr "Torna alla revisione segnata" -#: gitk:2627 msgid "Find descendant of this and mark" msgstr "Trova il discendente di questa revisione e di quella segnata" -#: gitk:2628 msgid "Compare with marked commit" msgstr "Confronta con la revisione segnata" -#: gitk:2629 gitk:2640 #, fuzzy msgid "Diff this -> marked commit" msgstr "Diff questo -> selezionato" -#: gitk:2630 gitk:2641 #, fuzzy msgid "Diff marked commit -> this" msgstr "Diff selezionato -> questo" -#: gitk:2631 #, fuzzy msgid "Revert this commit" msgstr "Segna questa revisione" -#: gitk:2647 msgid "Check out this branch" msgstr "Attiva questo ramo" -#: gitk:2648 msgid "Remove this branch" msgstr "Elimina questo ramo" -#: gitk:2649 msgid "Copy branch name" msgstr "" -#: gitk:2656 msgid "Highlight this too" msgstr "Evidenzia anche questo" -#: gitk:2657 msgid "Highlight this only" msgstr "Evidenzia solo questo" -#: gitk:2658 msgid "External diff" msgstr "Visualizza differenze in un altro programma" -#: gitk:2659 msgid "Blame parent commit" msgstr "Annota la revisione precedente" -#: gitk:2660 msgid "Copy path" msgstr "" -#: gitk:2667 msgid "Show origin of this line" msgstr "Mostra la provenienza di questa riga" -#: gitk:2668 msgid "Run git gui blame on this line" msgstr "Esegui git gui blame su questa riga" -#: gitk:3014 #, fuzzy msgid "" "\n" @@ -379,321 +292,249 @@ "Utilizzo e redistribuzione permessi sotto i termini della GNU General Public " "License" -#: gitk:3022 gitk:3089 gitk:9857 msgid "Close" msgstr "Chiudi" -#: gitk:3043 msgid "Gitk key bindings" msgstr "Scorciatoie da tastiera di Gitk" -#: gitk:3046 msgid "Gitk key bindings:" msgstr "Scorciatoie da tastiera di Gitk:" -#: gitk:3048 #, tcl-format msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\tEsci" -#: gitk:3049 #, fuzzy, tcl-format msgid "<%s-W>\t\tClose window" msgstr "<%s-F>\t\tTrova" -#: gitk:3050 msgid "<Home>\t\tMove to first commit" msgstr "<Home>\t\tVai alla prima revisione" -#: gitk:3051 msgid "<End>\t\tMove to last commit" msgstr "<End>\t\tVai all'ultima revisione" -#: gitk:3052 #, fuzzy msgid "<Up>, p, k\tMove up one commit" msgstr "<Su>, p, i\tVai più in alto di una revisione" -#: gitk:3053 #, fuzzy msgid "<Down>, n, j\tMove down one commit" msgstr "<Giù>, n, k\tVai più in basso di una revisione" -#: gitk:3054 #, fuzzy msgid "<Left>, z, h\tGo back in history list" msgstr "<Sinistra>, z, j\tTorna indietro nella cronologia" -#: gitk:3055 msgid "<Right>, x, l\tGo forward in history list" msgstr "<Destra>, x, l\tVai avanti nella cronologia" -#: gitk:3056 #, tcl-format msgid "<%s-n>\tGo to n-th parent of current commit in history list" msgstr "" -#: gitk:3057 msgid "<PageUp>\tMove up one page in commit list" msgstr "<PaginaSu>\tVai più in alto di una pagina nella lista delle revisioni" -#: gitk:3058 msgid "<PageDown>\tMove down one page in commit list" msgstr "" "<PaginaGiù>\tVai più in basso di una pagina nella lista delle revisioni" -#: gitk:3059 #, tcl-format msgid "<%s-Home>\tScroll to top of commit list" msgstr "<%s-Home>\tScorri alla cima della lista delle revisioni" -#: gitk:3060 #, tcl-format msgid "<%s-End>\tScroll to bottom of commit list" msgstr "<%s-End>\tScorri alla fine della lista delle revisioni" -#: gitk:3061 #, tcl-format msgid "<%s-Up>\tScroll commit list up one line" msgstr "<%s-Su>\tScorri la lista delle revisioni in alto di una riga" -#: gitk:3062 #, tcl-format msgid "<%s-Down>\tScroll commit list down one line" msgstr "<%s-Giù>\tScorri la lista delle revisioni in basso di una riga" -#: gitk:3063 #, tcl-format msgid "<%s-PageUp>\tScroll commit list up one page" msgstr "<%s-PaginaSu>\tScorri la lista delle revisioni in alto di una pagina" -#: gitk:3064 #, tcl-format msgid "<%s-PageDown>\tScroll commit list down one page" msgstr "<%s-PaginaGiù>\tScorri la lista delle revisioni in basso di una pagina" -#: gitk:3065 msgid "<Shift-Up>\tFind backwards (upwards, later commits)" msgstr "<Shift-Su>\tTrova all'indietro (verso l'alto, revisioni successive)" -#: gitk:3066 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" msgstr "<Shift-Giù>\tTrova in avanti (verso il basso, revisioni precedenti)" -#: gitk:3067 msgid "<Delete>, b\tScroll diff view up one page" msgstr "<Delete>, b\tScorri la vista delle differenze in alto di una pagina" -#: gitk:3068 msgid "<Backspace>\tScroll diff view up one page" msgstr "<Backspace>\tScorri la vista delle differenze in alto di una pagina" -#: gitk:3069 msgid "<Space>\t\tScroll diff view down one page" msgstr "<Spazio>\t\tScorri la vista delle differenze in basso di una pagina" -#: gitk:3070 msgid "u\t\tScroll diff view up 18 lines" msgstr "u\t\tScorri la vista delle differenze in alto di 18 linee" -#: gitk:3071 msgid "d\t\tScroll diff view down 18 lines" msgstr "d\t\tScorri la vista delle differenze in basso di 18 linee" -#: gitk:3072 #, tcl-format msgid "<%s-F>\t\tFind" msgstr "<%s-F>\t\tTrova" -#: gitk:3073 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" msgstr "<%s-G>\t\tTrova in avanti" -#: gitk:3074 msgid "<Return>\tMove to next find hit" msgstr "<Invio>\tTrova in avanti" -#: gitk:3075 #, fuzzy msgid "g\t\tGo to commit" msgstr "<End>\t\tVai all'ultima revisione" -#: gitk:3076 msgid "/\t\tFocus the search box" msgstr "/\t\tCursore nel box di ricerca" -#: gitk:3077 msgid "?\t\tMove to previous find hit" msgstr "?\t\tTrova all'indietro" -#: gitk:3078 msgid "f\t\tScroll diff view to next file" msgstr "f\t\tScorri la vista delle differenze al file successivo" -#: gitk:3079 #, tcl-format msgid "<%s-S>\t\tSearch for next hit in diff view" msgstr "<%s-S>\t\tCerca in avanti nella vista delle differenze" -#: gitk:3080 #, tcl-format msgid "<%s-R>\t\tSearch for previous hit in diff view" msgstr "<%s-R>\t\tCerca all'indietro nella vista delle differenze" -#: gitk:3081 #, tcl-format msgid "<%s-KP+>\tIncrease font size" msgstr "<%s-KP+>\tAumenta dimensione carattere" -#: gitk:3082 #, tcl-format msgid "<%s-plus>\tIncrease font size" msgstr "<%s-più>\tAumenta dimensione carattere" -#: gitk:3083 #, tcl-format msgid "<%s-KP->\tDecrease font size" msgstr "<%s-KP->\tDiminuisci dimensione carattere" -#: gitk:3084 #, tcl-format msgid "<%s-minus>\tDecrease font size" msgstr "<%s-meno>\tDiminuisci dimensione carattere" -#: gitk:3085 msgid "<F5>\t\tUpdate" msgstr "<F5>\t\tAggiorna" -#: gitk:3550 gitk:3559 #, tcl-format msgid "Error creating temporary directory %s:" msgstr "Errore durante la creazione della directory temporanea %s:" -#: gitk:3572 #, tcl-format msgid "Error getting \"%s\" from %s:" msgstr "Errore nella lettura di \"%s\" da %s:" -#: gitk:3635 msgid "command failed:" msgstr "impossibile eseguire il comando:" -#: gitk:3784 msgid "No such commit" msgstr "Revisione inesistente" -#: gitk:3798 msgid "git gui blame: command failed:" msgstr "git gui blame: impossibile eseguire il comando:" -#: gitk:3829 #, tcl-format msgid "Couldn't read merge head: %s" msgstr "Impossibile leggere merge head: %s" -#: gitk:3837 #, tcl-format msgid "Error reading index: %s" msgstr "Errore nella lettura dell'indice: %s" -#: gitk:3862 #, tcl-format msgid "Couldn't start git blame: %s" msgstr "Impossibile eseguire git blame: %s" -#: gitk:3865 gitk:6754 msgid "Searching" msgstr "Ricerca in corso" -#: gitk:3897 #, tcl-format msgid "Error running git blame: %s" msgstr "Errore nell'esecuzione di git blame: %s" -#: gitk:3925 #, tcl-format msgid "That line comes from commit %s, which is not in this view" msgstr "Quella riga proviene dalla revisione %s, non presente in questa vista" -#: gitk:3939 msgid "External diff viewer failed:" msgstr "Impossibile eseguire il visualizzatore di differenze:" -#: gitk:4070 msgid "Gitk view definition" msgstr "Scelta vista Gitk" -#: gitk:4074 msgid "Remember this view" msgstr "Ricorda questa vista" -#: gitk:4075 msgid "References (space separated list):" msgstr "Riferimenti (lista di elementi separati da spazi)" -#: gitk:4076 msgid "Branches & tags:" msgstr "Rami ed etichette" -#: gitk:4077 msgid "All refs" msgstr "Tutti i riferimenti" -#: gitk:4078 msgid "All (local) branches" msgstr "Tutti i rami (locali)" -#: gitk:4079 msgid "All tags" msgstr "Tutte le etichette" -#: gitk:4080 msgid "All remote-tracking branches" msgstr "Tutti i rami remoti" -#: gitk:4081 msgid "Commit Info (regular expressions):" msgstr "Informazioni sulla revisione (espressioni regolari):" -#: gitk:4082 msgid "Author:" msgstr "Autore:" -#: gitk:4083 msgid "Committer:" msgstr "Revisione creata da:" -#: gitk:4084 msgid "Commit Message:" msgstr "Messaggio di revisione:" -#: gitk:4085 msgid "Matches all Commit Info criteria" msgstr "Risponde a tutti i criteri di ricerca sulle revisioni" -#: gitk:4086 #, fuzzy msgid "Matches no Commit Info criteria" msgstr "Risponde a tutti i criteri di ricerca sulle revisioni" -#: gitk:4087 msgid "Changes to Files:" msgstr "Modifiche ai file:" -#: gitk:4088 msgid "Fixed String" msgstr "Stringa fissa" -#: gitk:4089 msgid "Regular Expression" msgstr "Espressione regolare" -#: gitk:4090 msgid "Search string:" msgstr "Cerca stringa:" -#: gitk:4091 msgid "" "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" @@ -701,201 +542,153 @@ "Date di revisione (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, " "2009 15:27:38\"):" -#: gitk:4092 msgid "Since:" msgstr "Da:" -#: gitk:4093 msgid "Until:" msgstr "A:" -#: gitk:4094 msgid "Limit and/or skip a number of revisions (positive integer):" msgstr "Limita e/o salta N revisioni (intero positivo):" -#: gitk:4095 msgid "Number to show:" msgstr "Numero di revisioni da mostrare:" -#: gitk:4096 msgid "Number to skip:" msgstr "Numero di revisioni da saltare:" -#: gitk:4097 msgid "Miscellaneous options:" msgstr "Altre opzioni:" -#: gitk:4098 msgid "Strictly sort by date" msgstr "Ordina solo per data" -#: gitk:4099 msgid "Mark branch sides" msgstr "Segna i lati del ramo" -#: gitk:4100 msgid "Limit to first parent" msgstr "Limita al primo genitore" -#: gitk:4101 msgid "Simple history" msgstr "Cronologia semplificata" -#: gitk:4102 msgid "Additional arguments to git log:" msgstr "Ulteriori argomenti da passare a git log:" -#: gitk:4103 msgid "Enter files and directories to include, one per line:" msgstr "Inserire file e directory da includere, uno per riga:" -#: gitk:4104 msgid "Command to generate more commits to include:" msgstr "Comando che genera altre revisioni da visualizzare:" -#: gitk:4228 msgid "Gitk: edit view" msgstr "Gitk: modifica vista" -#: gitk:4236 msgid "-- criteria for selecting revisions" msgstr "-- criteri per la scelta delle revisioni" -#: gitk:4241 msgid "View Name" msgstr "Nome vista" -#: gitk:4316 msgid "Apply (F5)" msgstr "Applica (F5)" -#: gitk:4354 msgid "Error in commit selection arguments:" msgstr "Errore negli argomenti di selezione delle revisioni:" -#: gitk:4409 gitk:4462 gitk:4924 gitk:4938 gitk:6208 gitk:12373 gitk:12374 msgid "None" msgstr "Nessuno" -#: gitk:5021 gitk:5026 msgid "Descendant" msgstr "Discendente" -#: gitk:5022 msgid "Not descendant" msgstr "Non discendente" -#: gitk:5029 gitk:5034 msgid "Ancestor" msgstr "Ascendente" -#: gitk:5030 msgid "Not ancestor" msgstr "Non ascendente" -#: gitk:5324 msgid "Local changes checked in to index but not committed" msgstr "Modifiche locali presenti nell'indice ma non nell'archivio" -#: gitk:5360 msgid "Local uncommitted changes, not checked in to index" msgstr "Modifiche locali non presenti né nell'archivio né nell'indice" -#: gitk:7134 msgid "and many more" msgstr "" -#: gitk:7137 msgid "many" msgstr "molti" -#: gitk:7328 msgid "Tags:" msgstr "Etichette:" -#: gitk:7345 gitk:7351 gitk:8825 msgid "Parent" msgstr "Genitore" -#: gitk:7356 msgid "Child" msgstr "Figlio" -#: gitk:7365 msgid "Branch" msgstr "Ramo" -#: gitk:7368 msgid "Follows" msgstr "Segue" -#: gitk:7371 msgid "Precedes" msgstr "Precede" -#: gitk:7966 #, tcl-format msgid "Error getting diffs: %s" msgstr "Errore nella lettura delle differenze:" -#: gitk:8650 msgid "Goto:" msgstr "Vai a:" -#: gitk:8671 #, tcl-format msgid "Short SHA1 id %s is ambiguous" msgstr "La SHA1 id abbreviata %s è ambigua" -#: gitk:8678 #, tcl-format msgid "Revision %s is not known" msgstr "La revisione %s è sconosciuta" -#: gitk:8688 #, tcl-format msgid "SHA1 id %s is not known" msgstr "La SHA1 id %s è sconosciuta" -#: gitk:8690 #, tcl-format msgid "Revision %s is not in the current view" msgstr "La revisione %s non è presente nella vista attuale" -#: gitk:8832 gitk:8847 msgid "Date" msgstr "Data" -#: gitk:8835 msgid "Children" msgstr "Figli" -#: gitk:8898 #, tcl-format msgid "Reset %s branch to here" msgstr "Aggiorna il ramo %s a questa revisione" -#: gitk:8900 msgid "Detached head: can't reset" msgstr "Nessun ramo attivo: reset impossibile" -#: gitk:9005 gitk:9011 msgid "Skipping merge commit " msgstr "Salto la revisione di fusione " -#: gitk:9020 gitk:9025 msgid "Error getting patch ID for " msgstr "Errore nella identificazione della patch per " -#: gitk:9021 gitk:9026 msgid " - stopping\n" msgstr " - fine\n" -#: gitk:9031 gitk:9034 gitk:9042 gitk:9056 gitk:9065 msgid "Commit " msgstr "La revisione " -#: gitk:9035 msgid "" " is the same patch as\n" " " @@ -903,7 +696,6 @@ " ha le stesse differenze di\n" " " -#: gitk:9043 msgid "" " differs from\n" " " @@ -911,7 +703,6 @@ " è diversa da\n" " " -#: gitk:9045 msgid "" "Diff of commits:\n" "\n" @@ -919,129 +710,99 @@ "Differenze tra le revisioni:\n" "\n" -#: gitk:9057 gitk:9066 #, tcl-format msgid " has %s children - stopping\n" msgstr " ha %s figli - fine\n" -#: gitk:9085 #, tcl-format msgid "Error writing commit to file: %s" msgstr "Errore nella scrittura della revisione nel file: %s" -#: gitk:9091 #, tcl-format msgid "Error diffing commits: %s" msgstr "Errore nelle differenze tra le revisioni: %s" -#: gitk:9137 msgid "Top" msgstr "Inizio" -#: gitk:9138 msgid "From" msgstr "Da" -#: gitk:9143 msgid "To" msgstr "A" -#: gitk:9167 msgid "Generate patch" msgstr "Genera patch" -#: gitk:9169 msgid "From:" msgstr "Da:" -#: gitk:9178 msgid "To:" msgstr "A:" -#: gitk:9187 msgid "Reverse" msgstr "Inverti" -#: gitk:9189 gitk:9385 msgid "Output file:" msgstr "Scrivi sul file:" -#: gitk:9195 msgid "Generate" msgstr "Genera" -#: gitk:9233 msgid "Error creating patch:" msgstr "Errore nella creazione della patch:" -#: gitk:9256 gitk:9373 gitk:9430 msgid "ID:" msgstr "ID:" -#: gitk:9265 msgid "Tag name:" msgstr "Nome etichetta:" -#: gitk:9268 msgid "Tag message is optional" msgstr "Il messaggio dell'etichetta è opzionale" -#: gitk:9270 msgid "Tag message:" msgstr "Messaggio dell'etichetta:" -#: gitk:9274 gitk:9439 msgid "Create" msgstr "Crea" -#: gitk:9292 msgid "No tag name specified" msgstr "Nessuna etichetta specificata" -#: gitk:9296 #, tcl-format msgid "Tag \"%s\" already exists" msgstr "L'etichetta \"%s\" esiste già" -#: gitk:9306 msgid "Error creating tag:" msgstr "Errore nella creazione dell'etichetta:" -#: gitk:9382 msgid "Command:" msgstr "Comando:" -#: gitk:9390 msgid "Write" msgstr "Scrivi" -#: gitk:9408 msgid "Error writing commit:" msgstr "Errore nella scrittura della revisione:" -#: gitk:9435 msgid "Name:" msgstr "Nome:" -#: gitk:9458 msgid "Please specify a name for the new branch" msgstr "Specificare un nome per il nuovo ramo" -#: gitk:9463 #, tcl-format msgid "Branch '%s' already exists. Overwrite?" msgstr "Il ramo '%s' esiste già. Sovrascrivere?" -#: gitk:9530 #, tcl-format msgid "Commit %s is already included in branch %s -- really re-apply it?" msgstr "La revisione %s è già inclusa nel ramo %s -- applicarla di nuovo?" -#: gitk:9535 msgid "Cherry-picking" msgstr "" -#: gitk:9544 #, tcl-format msgid "" "Cherry-pick failed because of local changes to file '%s'.\n" @@ -1052,7 +813,6 @@ "Prima di riprovare, bisogna creare una nuova revisione, annullare le " "modifiche o usare 'git stash'." -#: gitk:9550 msgid "" "Cherry-pick failed because of merge conflict.\n" "Do you wish to run git citool to resolve it?" @@ -1060,21 +820,17 @@ "Impossibile eseguire cherry-pick a causa di un conflitto nella fusione.\n" "Vuoi avviare git citool per risolverlo?" -#: gitk:9566 gitk:9624 msgid "No changes committed" msgstr "Nessuna modifica archiviata" -#: gitk:9593 #, fuzzy, tcl-format msgid "Commit %s is not included in branch %s -- really revert it?" msgstr "La revisione %s è già inclusa nel ramo %s -- applicarla di nuovo?" -#: gitk:9598 #, fuzzy msgid "Reverting" msgstr "git reset in corso" -#: gitk:9606 #, fuzzy, tcl-format msgid "" "Revert failed because of local changes to the following files:%s Please " @@ -1085,7 +841,6 @@ "Prima di riprovare, bisogna creare una nuova revisione, annullare le " "modifiche o usare 'git stash'." -#: gitk:9610 #, fuzzy msgid "" "Revert failed because of merge conflict.\n" @@ -1094,28 +849,22 @@ "Impossibile eseguire cherry-pick a causa di un conflitto nella fusione.\n" "Vuoi avviare git citool per risolverlo?" -#: gitk:9653 msgid "Confirm reset" msgstr "Conferma git reset" -#: gitk:9655 #, tcl-format msgid "Reset branch %s to %s?" msgstr "Aggiornare il ramo %s a %s?" -#: gitk:9657 msgid "Reset type:" msgstr "Tipo di aggiornamento:" -#: gitk:9660 msgid "Soft: Leave working tree and index untouched" msgstr "Soft: Lascia la direcory di lavoro e l'indice come sono" -#: gitk:9663 msgid "Mixed: Leave working tree untouched, reset index" msgstr "Mixed: Lascia la directory di lavoro come è, aggiorna l'indice" -#: gitk:9666 msgid "" "Hard: Reset working tree and index\n" "(discard ALL local changes)" @@ -1123,19 +872,15 @@ "Hard: Aggiorna la directory di lavoro e l'indice\n" "(abbandona TUTTE le modifiche locali)" -#: gitk:9683 msgid "Resetting" msgstr "git reset in corso" -#: gitk:9743 msgid "Checking out" msgstr "Attivazione in corso" -#: gitk:9796 msgid "Cannot delete the currently checked-out branch" msgstr "Impossibile cancellare il ramo attualmente attivo" -#: gitk:9802 #, tcl-format msgid "" "The commits on branch %s aren't on any other branch.\n" @@ -1144,16 +889,13 @@ "Le revisioni nel ramo %s non sono presenti su altri rami.\n" "Cancellare il ramo %s?" -#: gitk:9833 #, tcl-format msgid "Tags and heads: %s" msgstr "Etichette e rami: %s" -#: gitk:9850 msgid "Filter" msgstr "Filtro" -#: gitk:10146 msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." @@ -1161,219 +903,167 @@ "Errore nella lettura della topologia delle revisioni: le informazioni sul " "ramo e le etichette precedenti e seguenti saranno incomplete." -#: gitk:11123 msgid "Tag" msgstr "Etichetta" -#: gitk:11127 msgid "Id" msgstr "Id" -#: gitk:11210 msgid "Gitk font chooser" msgstr "Scelta caratteri gitk" -#: gitk:11227 msgid "B" msgstr "B" -#: gitk:11230 msgid "I" msgstr "I" -#: gitk:11348 msgid "Commit list display options" msgstr "Opzioni visualizzazione dell'elenco revisioni" -#: gitk:11351 msgid "Maximum graph width (lines)" msgstr "Larghezza massima del grafico (in linee)" -#: gitk:11355 #, no-tcl-format msgid "Maximum graph width (% of pane)" msgstr "Larghezza massima del grafico (% del pannello)" -#: gitk:11358 msgid "Show local changes" msgstr "Mostra modifiche locali" -#: gitk:11361 #, fuzzy msgid "Auto-select SHA1 (length)" msgstr "Seleziona automaticamente SHA1 hash" -#: gitk:11365 msgid "Hide remote refs" msgstr "Nascondi i riferimenti remoti" -#: gitk:11369 msgid "Diff display options" msgstr "Opzioni di visualizzazione delle differenze" -#: gitk:11371 msgid "Tab spacing" msgstr "Spaziatura tabulazioni" -#: gitk:11374 #, fuzzy msgid "Display nearby tags/heads" msgstr "Mostra etichette vicine" -#: gitk:11377 msgid "Maximum # tags/heads to show" msgstr "" -#: gitk:11380 msgid "Limit diffs to listed paths" msgstr "Limita le differenze ai percorsi elencati" -#: gitk:11383 msgid "Support per-file encodings" msgstr "Attiva codifica file per file" -#: gitk:11389 gitk:11536 msgid "External diff tool" msgstr "Visualizzatore di differenze" -#: gitk:11390 msgid "Choose..." msgstr "Scegli..." -#: gitk:11395 msgid "General options" msgstr "Opzioni generali" -#: gitk:11398 msgid "Use themed widgets" msgstr "Utilizza interfaccia a tema" -#: gitk:11400 msgid "(change requires restart)" msgstr "(una modifica richiede il riavvio)" -#: gitk:11402 msgid "(currently unavailable)" msgstr "(momentaneamente non disponibile)" -#: gitk:11413 msgid "Colors: press to choose" msgstr "Colori: premere per scegliere" -#: gitk:11416 msgid "Interface" msgstr "Interfaccia" -#: gitk:11417 msgid "interface" msgstr "interfaccia" -#: gitk:11420 msgid "Background" msgstr "Sfondo" -#: gitk:11421 gitk:11451 msgid "background" msgstr "sfondo" -#: gitk:11424 msgid "Foreground" msgstr "Primo piano" -#: gitk:11425 msgid "foreground" msgstr "primo piano" -#: gitk:11428 msgid "Diff: old lines" msgstr "Diff: vecchie linee" -#: gitk:11429 msgid "diff old lines" msgstr "vecchie linee" -#: gitk:11433 msgid "Diff: new lines" msgstr "Diff: nuove linee" -#: gitk:11434 msgid "diff new lines" msgstr "nuove linee" -#: gitk:11438 msgid "Diff: hunk header" msgstr "Diff: intestazione della sezione" -#: gitk:11440 msgid "diff hunk header" msgstr "intestazione della sezione" -#: gitk:11444 msgid "Marked line bg" msgstr "Sfondo riga selezionata" -#: gitk:11446 msgid "marked line background" msgstr "sfondo riga selezionata" -#: gitk:11450 msgid "Select bg" msgstr "Sfondo" -#: gitk:11459 msgid "Fonts: press to choose" msgstr "Carattere: premere per scegliere" -#: gitk:11461 msgid "Main font" msgstr "Carattere principale" -#: gitk:11462 msgid "Diff display font" msgstr "Carattere per differenze" -#: gitk:11463 msgid "User interface font" msgstr "Carattere per interfaccia utente" -#: gitk:11485 msgid "Gitk preferences" msgstr "Preferenze gitk" -#: gitk:11494 #, fuzzy msgid "General" msgstr "Genera" -#: gitk:11495 msgid "Colors" msgstr "" -#: gitk:11496 msgid "Fonts" msgstr "" -#: gitk:11546 #, tcl-format msgid "Gitk: choose color for %s" msgstr "Gitk: scegliere un colore per %s" -#: gitk:12059 msgid "" "Sorry, gitk cannot run with this version of Tcl/Tk.\n" " Gitk requires at least Tcl/Tk 8.4." msgstr "" -#: gitk:12269 msgid "Cannot find a git repository here." msgstr "Archivio git non trovato." -#: gitk:12316 #, tcl-format msgid "Ambiguous argument '%s': both revision and filename" msgstr "Argomento ambiguo: '%s' è sia revisione che nome di file" -#: gitk:12328 msgid "Bad arguments to gitk:" msgstr "Gitk: argomenti errati:"
diff --git a/gitk-git/po/ja.po b/gitk-git/po/ja.po index ca3c29b..2c40b76 100644 --- a/gitk-git/po/ja.po +++ b/gitk-git/po/ja.po
@@ -8,7 +8,7 @@ # Satoshi Yasushima <s.yasushima@gmail.com>, 2016. msgid "" msgstr "" -"Project-Id-Version: gitk\n" +"Project-Id-Version: Gitk\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-05-17 14:32+1000\n" "PO-Revision-Date: 2015-11-12 13:00+0900\n" @@ -20,33 +20,26 @@ "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: gitk:140 msgid "Couldn't get list of unmerged files:" msgstr "マージされていないファイルのリストを取得できません:" -#: gitk:212 gitk:2381 msgid "Color words" msgstr "変更を着色" -#: gitk:217 gitk:2381 gitk:8221 gitk:8254 msgid "Markup words" msgstr "変更をマークアップ" -#: gitk:324 msgid "Error parsing revisions:" msgstr "リビジョン解析エラー:" -#: gitk:380 msgid "Error executing --argscmd command:" msgstr "--argscmd コマンド実行エラー:" -#: gitk:393 msgid "No files selected: --merge specified but no files are unmerged." msgstr "" "ファイル未選択: --merge が指定されましたが、マージされていないファイルはあり" "ません。" -#: gitk:396 msgid "" "No files selected: --merge specified but no unmerged files are within file " "limit." @@ -54,322 +47,240 @@ "ファイル未選択: --merge が指定されましたが、ファイル制限内にマージされていな" "いファイルはありません。" -#: gitk:418 gitk:566 msgid "Error executing git log:" msgstr "git log 実行エラー:" -#: gitk:436 gitk:582 msgid "Reading" msgstr "読み込み中" -#: gitk:496 gitk:4526 msgid "Reading commits..." msgstr "コミット読み込み中..." -#: gitk:499 gitk:1637 gitk:4529 msgid "No commits selected" msgstr "コミットが選択されていません" -#: gitk:1445 gitk:4046 gitk:12447 msgid "Command line" msgstr "コマンド行" -#: gitk:1511 msgid "Can't parse git log output:" msgstr "git log の出力を解析できません:" -#: gitk:1740 msgid "No commit information available" msgstr "有効なコミットの情報がありません" -#: gitk:1903 gitk:1932 gitk:4316 gitk:9684 gitk:11256 gitk:11536 msgid "OK" msgstr "OK" -#: gitk:1934 gitk:4318 gitk:9197 gitk:9276 gitk:9406 gitk:9455 gitk:9686 -#: gitk:11257 gitk:11537 msgid "Cancel" msgstr "キャンセル" -#: gitk:2069 msgid "&Update" msgstr "更新(&U)" -#: gitk:2070 msgid "&Reload" msgstr "リロード(&R)" -#: gitk:2071 msgid "Reread re&ferences" msgstr "リファレンスを再読み込み(&F)" -#: gitk:2072 msgid "&List references" msgstr "リファレンスリストを表示(&L)" -#: gitk:2074 msgid "Start git &gui" msgstr "git gui の開始(&G)" -#: gitk:2076 msgid "&Quit" msgstr "終了(&Q)" -#: gitk:2068 msgid "&File" msgstr "ファイル(&F)" -#: gitk:2080 msgid "&Preferences" msgstr "設定(&P)" -#: gitk:2079 msgid "&Edit" msgstr "編集(&E)" -#: gitk:2084 msgid "&New view..." msgstr "新規ビュー(&N)..." -#: gitk:2085 msgid "&Edit view..." msgstr "ビュー編集(&E)..." -#: gitk:2086 msgid "&Delete view" msgstr "ビュー削除(&D)" -#: gitk:2088 msgid "&All files" msgstr "全てのファイル(&A)" -#: gitk:2083 msgid "&View" msgstr "ビュー(&V)" -#: gitk:2093 gitk:2103 msgid "&About gitk" msgstr "gitk について(&A)" -#: gitk:2094 gitk:2108 msgid "&Key bindings" msgstr "キーバインディング(&K)" -#: gitk:2092 gitk:2107 msgid "&Help" msgstr "ヘルプ(&H)" -#: gitk:2185 gitk:8653 msgid "SHA1 ID:" msgstr "SHA1 ID:" -#: gitk:2229 msgid "Row" msgstr "行" -#: gitk:2267 msgid "Find" msgstr "検索" -#: gitk:2295 msgid "commit" msgstr "コミット" -#: gitk:2299 gitk:2301 gitk:4688 gitk:4711 gitk:4735 gitk:6756 gitk:6828 -#: gitk:6913 msgid "containing:" msgstr "含む:" -#: gitk:2302 gitk:3527 gitk:3532 gitk:4764 msgid "touching paths:" msgstr "パスの一部:" -#: gitk:2303 gitk:4778 msgid "adding/removing string:" msgstr "追加/除去される文字列:" -#: gitk:2304 gitk:4780 msgid "changing lines matching:" msgstr "変更される文字列" -#: gitk:2313 gitk:2315 gitk:4767 msgid "Exact" msgstr "英字の大小を区別する" -#: gitk:2315 gitk:4855 gitk:6724 msgid "IgnCase" msgstr "英字の大小を区別しない" -#: gitk:2315 gitk:4737 gitk:4853 gitk:6720 msgid "Regexp" msgstr "正規表現" -#: gitk:2317 gitk:2318 gitk:4875 gitk:4905 gitk:4912 gitk:6849 gitk:6917 msgid "All fields" msgstr "全ての項目" -#: gitk:2318 gitk:4872 gitk:4905 gitk:6787 msgid "Headline" msgstr "ヘッドライン" -#: gitk:2319 gitk:4872 gitk:6787 gitk:6917 gitk:7390 msgid "Comments" msgstr "コメント" -#: gitk:2319 gitk:4872 gitk:4877 gitk:4912 gitk:6787 gitk:7325 gitk:8831 -#: gitk:8846 msgid "Author" msgstr "作者" -#: gitk:2319 gitk:4872 gitk:6787 gitk:7327 msgid "Committer" msgstr "コミット者" -#: gitk:2350 msgid "Search" msgstr "検索" -#: gitk:2358 msgid "Diff" msgstr "Diff" -#: gitk:2360 msgid "Old version" msgstr "旧バージョン" -#: gitk:2362 msgid "New version" msgstr "新バージョン" -#: gitk:2364 msgid "Lines of context" msgstr "文脈行数" -#: gitk:2374 msgid "Ignore space change" msgstr "空白の違いを無視" -#: gitk:2378 gitk:2380 gitk:7960 gitk:8207 msgid "Line diff" msgstr "行毎のdiff" -#: gitk:2445 msgid "Patch" msgstr "パッチ" -#: gitk:2447 msgid "Tree" msgstr "ツリー" -#: gitk:2617 gitk:2638 msgid "Diff this -> selected" msgstr "これと選択したコミットのdiffを見る" -#: gitk:2618 gitk:2639 msgid "Diff selected -> this" msgstr "選択したコミットとこれのdiffを見る" -#: gitk:2619 gitk:2640 msgid "Make patch" msgstr "パッチ作成" -#: gitk:2620 gitk:9255 msgid "Create tag" msgstr "タグ生成" -#: gitk:2621 msgid "Copy commit summary" msgstr "コミットの要約をコピーする" -#: gitk:2622 gitk:9386 msgid "Write commit to file" msgstr "コミットをファイルに書き出す" -#: gitk:2623 gitk:9443 msgid "Create new branch" msgstr "新規ブランチ生成" -#: gitk:2624 msgid "Cherry-pick this commit" msgstr "このコミットをチェリーピックする" -#: gitk:2625 msgid "Reset HEAD branch to here" msgstr "ブランチのHEADをここにリセットする" -#: gitk:2626 msgid "Mark this commit" msgstr "このコミットにマークをつける" -#: gitk:2627 msgid "Return to mark" msgstr "マークを付けた所に戻る" -#: gitk:2628 msgid "Find descendant of this and mark" msgstr "これとマークをつけた所との子孫を見つける" -#: gitk:2629 msgid "Compare with marked commit" msgstr "マークを付けたコミットと比較する" -#: gitk:2630 gitk:2641 msgid "Diff this -> marked commit" msgstr "これとマークを付けたコミットのdiffを見る" -#: gitk:2631 gitk:2642 msgid "Diff marked commit -> this" msgstr "マークを付けたコミットとこれのdiffを見る" -#: gitk:2632 msgid "Revert this commit" msgstr "このコミットを撤回する" -#: gitk:2648 msgid "Check out this branch" msgstr "このブランチをチェックアウトする" -#: gitk:2649 msgid "Remove this branch" msgstr "このブランチを除去する" -#: gitk:2650 msgid "Copy branch name" msgstr "ブランチ名をコピーする" -#: gitk:2657 msgid "Highlight this too" msgstr "これもハイライトさせる" -#: gitk:2658 msgid "Highlight this only" msgstr "これだけをハイライトさせる" -#: gitk:2659 msgid "External diff" msgstr "外部diffツール" -#: gitk:2660 msgid "Blame parent commit" msgstr "親コミットから blame をかける" -#: gitk:2661 msgid "Copy path" msgstr "パス名をコピーする" -#: gitk:2668 msgid "Show origin of this line" msgstr "この行の出自を表示する" -#: gitk:2669 msgid "Run git gui blame on this line" msgstr "この行に git gui で blame をかける" -#: gitk:3013 msgid "About gitk" msgstr "gitk について" -#: gitk:3015 msgid "" "\n" "Gitk - a commit viewer for git\n" @@ -385,323 +296,251 @@ "\n" "使用および再配布は GNU General Public License に従ってください" -#: gitk:3023 gitk:3090 gitk:9872 msgid "Close" msgstr "閉じる" -#: gitk:3044 msgid "Gitk key bindings" msgstr "Gitk キーバインディング" -#: gitk:3047 msgid "Gitk key bindings:" msgstr "Gitk キーバインディング:" -#: gitk:3049 #, tcl-format msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\t終了" -#: gitk:3050 #, tcl-format msgid "<%s-W>\t\tClose window" msgstr "<%s-W>\t\tウィンドウを閉じる" -#: gitk:3051 msgid "<Home>\t\tMove to first commit" msgstr "<Home>\t\t最初のコミットに移動" -#: gitk:3052 msgid "<End>\t\tMove to last commit" msgstr "<End>\t\t最後のコミットに移動" -#: gitk:3053 msgid "<Up>, p, k\tMove up one commit" msgstr "<Up>, p, k\t一つ上のコミットに移動" -#: gitk:3054 msgid "<Down>, n, j\tMove down one commit" msgstr "<Down>, n, j\t一つ下のコミットに移動" -#: gitk:3055 msgid "<Left>, z, h\tGo back in history list" msgstr "<Left>, z, h\t履歴の前に戻る" -#: gitk:3056 msgid "<Right>, x, l\tGo forward in history list" msgstr "<Right>, x, l\t履歴の次へ進む" -#: gitk:3057 #, tcl-format msgid "<%s-n>\tGo to n-th parent of current commit in history list" -msgstr "<%s-n(数字)>\t履歴上で現在のコミットの親コミットの内のn(数字)番目のコミットへ移動" +msgstr "" +"<%s-n(数字)>\t履歴上で現在のコミットの親コミットの内のn(数字)番目のコミットへ" +"移動" -#: gitk:3058 msgid "<PageUp>\tMove up one page in commit list" msgstr "<PageUp>\tコミットリストの一つ上のページに移動" -#: gitk:3059 msgid "<PageDown>\tMove down one page in commit list" msgstr "<PageDown>\tコミットリストの一つ下のページに移動" -#: gitk:3060 #, tcl-format msgid "<%s-Home>\tScroll to top of commit list" msgstr "<%s-Home>\tコミットリストの一番上にスクロールする" -#: gitk:3061 #, tcl-format msgid "<%s-End>\tScroll to bottom of commit list" msgstr "<%s-End>\tコミットリストの一番下にスクロールする" -#: gitk:3062 #, tcl-format msgid "<%s-Up>\tScroll commit list up one line" msgstr "<%s-Up>\tコミットリストの一つ下の行にスクロールする" -#: gitk:3063 #, tcl-format msgid "<%s-Down>\tScroll commit list down one line" msgstr "<%s-Down>\tコミットリストの一つ下の行にスクロールする" -#: gitk:3064 #, tcl-format msgid "<%s-PageUp>\tScroll commit list up one page" msgstr "<%s-PageUp>\tコミットリストの上のページにスクロールする" -#: gitk:3065 #, tcl-format msgid "<%s-PageDown>\tScroll commit list down one page" msgstr "<%s-PageDown>\tコミットリストの下のページにスクロールする" -#: gitk:3066 msgid "<Shift-Up>\tFind backwards (upwards, later commits)" msgstr "<Shift-Up>\t後方を検索 (上方の・新しいコミット)" -#: gitk:3067 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" msgstr "<Shift-Down>\t前方を検索(下方の・古いコミット)" -#: gitk:3068 msgid "<Delete>, b\tScroll diff view up one page" msgstr "<Delete>, b\tdiff画面を上のページにスクロールする" -#: gitk:3069 msgid "<Backspace>\tScroll diff view up one page" msgstr "<Backspace>\tdiff画面を上のページにスクロールする" -#: gitk:3070 msgid "<Space>\t\tScroll diff view down one page" msgstr "<Space>\t\tdiff画面を下のページにスクロールする" -#: gitk:3071 msgid "u\t\tScroll diff view up 18 lines" msgstr "u\t\tdiff画面を上に18行スクロールする" -#: gitk:3072 msgid "d\t\tScroll diff view down 18 lines" msgstr "d\t\tdiff画面を下に18行スクロールする" -#: gitk:3073 #, tcl-format msgid "<%s-F>\t\tFind" msgstr "<%s-F>\t\t検索" -#: gitk:3074 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" msgstr "<%s-G>\t\t次を検索して移動" -#: gitk:3075 msgid "<Return>\tMove to next find hit" msgstr "<Return>\t次を検索して移動" -#: gitk:3076 msgid "g\t\tGo to commit" msgstr "g\t\t指定してコミットに移動" -#: gitk:3077 msgid "/\t\tFocus the search box" msgstr "/\t\t検索ボックスにフォーカス" -#: gitk:3078 msgid "?\t\tMove to previous find hit" msgstr "?\t\t前を検索して移動" -#: gitk:3079 msgid "f\t\tScroll diff view to next file" msgstr "f\t\t次のファイルにdiff画面をスクロールする" -#: gitk:3080 #, tcl-format msgid "<%s-S>\t\tSearch for next hit in diff view" msgstr "<%s-S>\t\tdiff画面の次を検索" -#: gitk:3081 #, tcl-format msgid "<%s-R>\t\tSearch for previous hit in diff view" msgstr "<%s-R>\t\tdiff画面の前を検索" -#: gitk:3082 #, tcl-format msgid "<%s-KP+>\tIncrease font size" msgstr "<%s-KP+>\t文字サイズを拡大" -#: gitk:3083 #, tcl-format msgid "<%s-plus>\tIncrease font size" msgstr "<%s-plus>\t文字サイズを拡大" -#: gitk:3084 #, tcl-format msgid "<%s-KP->\tDecrease font size" msgstr "<%s-KP->\t文字サイズを縮小" -#: gitk:3085 #, tcl-format msgid "<%s-minus>\tDecrease font size" msgstr "<%s-minus>\t文字サイズを縮小" -#: gitk:3086 msgid "<F5>\t\tUpdate" msgstr "<F5>\t\t更新" -#: gitk:3551 gitk:3560 #, tcl-format msgid "Error creating temporary directory %s:" msgstr "一時ディレクトリ %s 生成時エラー:" -#: gitk:3573 #, tcl-format msgid "Error getting \"%s\" from %s:" msgstr "\"%s\" のエラーが %s に発生:" -#: gitk:3636 msgid "command failed:" msgstr "コマンド失敗:" -#: gitk:3785 msgid "No such commit" msgstr "そのようなコミットはありません" -#: gitk:3799 msgid "git gui blame: command failed:" msgstr "git gui blame: コマンド失敗:" -#: gitk:3830 #, tcl-format msgid "Couldn't read merge head: %s" msgstr "マージする HEAD を読み込めません: %s" -#: gitk:3838 #, tcl-format msgid "Error reading index: %s" msgstr "インデックス読み込みエラー: %s" -#: gitk:3863 #, tcl-format msgid "Couldn't start git blame: %s" msgstr "git blame を始められません: %s" -#: gitk:3866 gitk:6755 msgid "Searching" msgstr "検索中" -#: gitk:3898 #, tcl-format msgid "Error running git blame: %s" msgstr "git blame 実行エラー: %s" -#: gitk:3926 #, tcl-format msgid "That line comes from commit %s, which is not in this view" msgstr "コミット %s に由来するその行は、このビューに表示されていません" -#: gitk:3940 msgid "External diff viewer failed:" msgstr "外部diffビューアが失敗:" -#: gitk:4044 msgid "All files" msgstr "全てのファイル" -#: gitk:4068 msgid "View" msgstr "ビュー" -#: gitk:4071 msgid "Gitk view definition" msgstr "Gitk ビュー定義" -#: gitk:4075 msgid "Remember this view" msgstr "このビューを記憶する" -#: gitk:4076 msgid "References (space separated list):" msgstr "リファレンス(スペース区切りのリスト):" -#: gitk:4077 msgid "Branches & tags:" msgstr "ブランチ&タグ:" -#: gitk:4078 msgid "All refs" msgstr "全てのリファレンス" -#: gitk:4079 msgid "All (local) branches" msgstr "全ての(ローカルな)ブランチ" -#: gitk:4080 msgid "All tags" msgstr "全てのタグ" -#: gitk:4081 msgid "All remote-tracking branches" msgstr "全てのリモート追跡ブランチ" -#: gitk:4082 msgid "Commit Info (regular expressions):" msgstr "コミット情報(正規表現):" -#: gitk:4083 msgid "Author:" msgstr "作者:" -#: gitk:4084 msgid "Committer:" msgstr "コミット者:" -#: gitk:4085 msgid "Commit Message:" msgstr "コミットメッセージ:" -#: gitk:4086 msgid "Matches all Commit Info criteria" msgstr "コミット情報の全ての条件に一致" -#: gitk:4087 msgid "Matches no Commit Info criteria" msgstr "コミット情報の全ての条件に不一致" -#: gitk:4088 msgid "Changes to Files:" msgstr "変更したファイル:" -#: gitk:4089 msgid "Fixed String" msgstr "固定文字列" -#: gitk:4090 msgid "Regular Expression" msgstr "正規表現" -#: gitk:4091 msgid "Search string:" msgstr "検索文字列:" -#: gitk:4092 msgid "" "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" @@ -709,201 +548,153 @@ "コミット日時 (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" -#: gitk:4093 msgid "Since:" msgstr "期間の始め:" -#: gitk:4094 msgid "Until:" msgstr "期間の終わり:" -#: gitk:4095 msgid "Limit and/or skip a number of revisions (positive integer):" msgstr "制限・省略するリビジョンの数(正の整数):" -#: gitk:4096 msgid "Number to show:" msgstr "表示する数:" -#: gitk:4097 msgid "Number to skip:" msgstr "省略する数:" -#: gitk:4098 msgid "Miscellaneous options:" msgstr "その他のオプション:" -#: gitk:4099 msgid "Strictly sort by date" msgstr "厳密に日付順で並び替え" -#: gitk:4100 msgid "Mark branch sides" msgstr "側枝マーク" -#: gitk:4101 msgid "Limit to first parent" msgstr "最初の親に制限" -#: gitk:4102 msgid "Simple history" msgstr "簡易な履歴" -#: gitk:4103 msgid "Additional arguments to git log:" msgstr "git log への追加の引数:" -#: gitk:4104 msgid "Enter files and directories to include, one per line:" msgstr "含まれるファイル・ディレクトリを一行ごとに入力:" -#: gitk:4105 msgid "Command to generate more commits to include:" msgstr "コミット追加コマンド:" -#: gitk:4229 msgid "Gitk: edit view" msgstr "Gitk: ビュー編集" -#: gitk:4237 msgid "-- criteria for selecting revisions" msgstr "― リビジョンの選択条件" -#: gitk:4242 msgid "View Name" msgstr "ビュー名:" -#: gitk:4317 msgid "Apply (F5)" msgstr "適用 (F5)" -#: gitk:4355 msgid "Error in commit selection arguments:" msgstr "コミット選択引数のエラー:" -#: gitk:4410 gitk:4463 gitk:4925 gitk:4939 gitk:6209 gitk:12388 gitk:12389 msgid "None" msgstr "無し" -#: gitk:5022 gitk:5027 msgid "Descendant" msgstr "子孫" -#: gitk:5023 msgid "Not descendant" msgstr "非子孫" -#: gitk:5030 gitk:5035 msgid "Ancestor" msgstr "祖先" -#: gitk:5031 msgid "Not ancestor" msgstr "非祖先" -#: gitk:5325 msgid "Local changes checked in to index but not committed" msgstr "ステージされた、コミット前のローカルな変更" -#: gitk:5361 msgid "Local uncommitted changes, not checked in to index" msgstr "ステージされていない、コミット前のローカルな変更" -#: gitk:7135 msgid "and many more" msgstr "他多数" -#: gitk:7138 msgid "many" msgstr "多数" -#: gitk:7329 msgid "Tags:" msgstr "タグ:" -#: gitk:7346 gitk:7352 gitk:8826 msgid "Parent" msgstr "親" -#: gitk:7357 msgid "Child" msgstr "子" -#: gitk:7366 msgid "Branch" msgstr "ブランチ" -#: gitk:7369 msgid "Follows" msgstr "下位" -#: gitk:7372 msgid "Precedes" msgstr "上位" -#: gitk:7967 #, tcl-format msgid "Error getting diffs: %s" msgstr "diff取得エラー: %s" -#: gitk:8651 msgid "Goto:" msgstr "Goto:" -#: gitk:8672 #, tcl-format msgid "Short SHA1 id %s is ambiguous" msgstr "%s を含む SHA1 ID は複数存在します" -#: gitk:8679 #, tcl-format msgid "Revision %s is not known" msgstr "リビジョン %s は不明です" -#: gitk:8689 #, tcl-format msgid "SHA1 id %s is not known" msgstr "SHA1 id %s は不明です" -#: gitk:8691 #, tcl-format msgid "Revision %s is not in the current view" msgstr "リビジョン %s は現在のビューにはありません" -#: gitk:8833 gitk:8848 msgid "Date" msgstr "日付" -#: gitk:8836 msgid "Children" msgstr "子" -#: gitk:8899 #, tcl-format msgid "Reset %s branch to here" msgstr "%s ブランチをここにリセットする" -#: gitk:8901 msgid "Detached head: can't reset" msgstr "切り離されたHEAD: リセットできません" -#: gitk:9006 gitk:9012 msgid "Skipping merge commit " msgstr "コミットマージをスキップ: " -#: gitk:9021 gitk:9026 msgid "Error getting patch ID for " msgstr "パッチ取得エラー: ID " -#: gitk:9022 gitk:9027 msgid " - stopping\n" msgstr " - 停止\n" -#: gitk:9032 gitk:9035 gitk:9043 gitk:9057 gitk:9066 msgid "Commit " msgstr "コミット " -#: gitk:9036 msgid "" " is the same patch as\n" " " @@ -911,7 +702,6 @@ " は下記のパッチと同等\n" " " -#: gitk:9044 msgid "" " differs from\n" " " @@ -919,7 +709,6 @@ " 下記からのdiff\n" " " -#: gitk:9046 msgid "" "Diff of commits:\n" "\n" @@ -927,130 +716,100 @@ "コミットのdiff:\n" "\n" -#: gitk:9058 gitk:9067 #, tcl-format msgid " has %s children - stopping\n" msgstr " には %s の子があります - 停止\n" -#: gitk:9086 #, tcl-format msgid "Error writing commit to file: %s" msgstr "ファイルへのコミット書き出しエラー: %s" -#: gitk:9092 #, tcl-format msgid "Error diffing commits: %s" msgstr "コミットのdiff実行エラー: %s" -#: gitk:9138 msgid "Top" msgstr "Top" -#: gitk:9139 msgid "From" msgstr "From" -#: gitk:9144 msgid "To" msgstr "To" -#: gitk:9168 msgid "Generate patch" msgstr "パッチ生成" -#: gitk:9170 msgid "From:" msgstr "From:" -#: gitk:9179 msgid "To:" msgstr "To:" -#: gitk:9188 msgid "Reverse" msgstr "逆" -#: gitk:9190 gitk:9400 msgid "Output file:" msgstr "出力ファイル:" -#: gitk:9196 msgid "Generate" msgstr "生成" -#: gitk:9234 msgid "Error creating patch:" msgstr "パッチ生成エラー:" -#: gitk:9257 gitk:9388 gitk:9445 msgid "ID:" msgstr "ID:" -#: gitk:9266 msgid "Tag name:" msgstr "タグ名:" -#: gitk:9269 msgid "Tag message is optional" msgstr "タグメッセージを付ける事も出来ます" -#: gitk:9271 msgid "Tag message:" msgstr "タグメッセージ:" -#: gitk:9275 gitk:9454 msgid "Create" msgstr "生成" -#: gitk:9293 msgid "No tag name specified" msgstr "タグの名称が指定されていません" -#: gitk:9297 #, tcl-format msgid "Tag \"%s\" already exists" msgstr "タグ \"%s\" は既に存在します" -#: gitk:9307 msgid "Error creating tag:" msgstr "タグ生成エラー:" -#: gitk:9397 msgid "Command:" msgstr "コマンド:" -#: gitk:9405 msgid "Write" msgstr "書き出し" -#: gitk:9423 msgid "Error writing commit:" msgstr "コミット書き出しエラー:" -#: gitk:9450 msgid "Name:" msgstr "名前:" -#: gitk:9473 msgid "Please specify a name for the new branch" msgstr "新しいブランチの名前を指定してください" -#: gitk:9478 #, tcl-format msgid "Branch '%s' already exists. Overwrite?" msgstr "ブランチ '%s' は既に存在します。上書きしますか?" -#: gitk:9545 #, tcl-format msgid "Commit %s is already included in branch %s -- really re-apply it?" msgstr "" "コミット %s は既にブランチ %s に含まれています ― 本当にこれを再適用しますか?" -#: gitk:9550 msgid "Cherry-picking" msgstr "チェリーピック中" -#: gitk:9559 #, tcl-format msgid "" "Cherry-pick failed because of local changes to file '%s'.\n" @@ -1060,7 +819,6 @@ "あなたの変更に commit, reset, stash のいずれかを行ってからやり直してくださ" "い。" -#: gitk:9565 msgid "" "Cherry-pick failed because of merge conflict.\n" "Do you wish to run git citool to resolve it?" @@ -1068,27 +826,25 @@ "マージの衝突によってチェリーピックは失敗しました。\n" "この解決のために git citool を実行したいですか?" -#: gitk:9581 gitk:9639 msgid "No changes committed" msgstr "何の変更もコミットされていません" -#: gitk:9608 #, tcl-format msgid "Commit %s is not included in branch %s -- really revert it?" -msgstr "コミット %s は既にブランチ %s に含まれています ― 本当にこれを撤回しますか?" +msgstr "" +"コミット %s は既にブランチ %s に含まれています ― 本当にこれを撤回しますか?" -#: gitk:9613 msgid "Reverting" msgstr "撤回中" -#: gitk:9621 #, 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 "ファイル '%s' のローカルな変更のために撤回は失敗しました。 あなたの変更に commit, reset, stash のいずれかを行ってからやり直してください。" +msgstr "" +"ファイル '%s' のローカルな変更のために撤回は失敗しました。 あなたの変更に " +"commit, reset, stash のいずれかを行ってからやり直してください。" -#: gitk:9625 msgid "" "Revert failed because of merge conflict.\n" " Do you wish to run git citool to resolve it?" @@ -1096,28 +852,22 @@ "マージの衝突によって撤回は失敗しました。\n" "この解決のために git citool を実行したいですか?" -#: gitk:9668 msgid "Confirm reset" msgstr "確認を取り消す" -#: gitk:9670 #, tcl-format msgid "Reset branch %s to %s?" msgstr "ブランチ %s を %s にリセットしますか?" -#: gitk:9672 msgid "Reset type:" msgstr "Reset タイプ:" -#: gitk:9675 msgid "Soft: Leave working tree and index untouched" msgstr "Soft: 作業ツリーもインデックスもそのままにする" -#: gitk:9678 msgid "Mixed: Leave working tree untouched, reset index" msgstr "Mixed: 作業ツリーをそのままにして、インデックスをリセット" -#: gitk:9681 msgid "" "Hard: Reset working tree and index\n" "(discard ALL local changes)" @@ -1125,19 +875,15 @@ "Hard: 作業ツリーやインデックスをリセット\n" "(「全ての」ローカルな変更を破棄)" -#: gitk:9698 msgid "Resetting" msgstr "リセット中" -#: gitk:9758 msgid "Checking out" msgstr "チェックアウト" -#: gitk:9811 msgid "Cannot delete the currently checked-out branch" msgstr "現在チェックアウトされているブランチを削除することはできません" -#: gitk:9817 #, tcl-format msgid "" "The commits on branch %s aren't on any other branch.\n" @@ -1146,16 +892,13 @@ "ブランチ %s には他のブランチに存在しないコミットがあります。\n" "本当にブランチ %s を削除しますか?" -#: gitk:9848 #, tcl-format msgid "Tags and heads: %s" msgstr "タグとHEAD: %s" -#: gitk:9865 msgid "Filter" msgstr "フィルター" -#: gitk:10161 msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." @@ -1163,201 +906,152 @@ "コミット構造情報読み込みエラー; ブランチ及び上位/下位のタグ情報が不完全である" "ようです。" -#: gitk:11138 msgid "Tag" msgstr "タグ" -#: gitk:11142 msgid "Id" msgstr "ID" -#: gitk:11225 msgid "Gitk font chooser" msgstr "Gitk フォント選択" -#: gitk:11242 msgid "B" msgstr "B" -#: gitk:11245 msgid "I" msgstr "I" -#: gitk:11363 msgid "Commit list display options" msgstr "コミットリスト表示オプション" -#: gitk:11366 msgid "Maximum graph width (lines)" msgstr "最大グラフ幅(線の本数)" -#: gitk:11370 #, no-tcl-format msgid "Maximum graph width (% of pane)" msgstr "最大グラフ幅(ペインに対する%)" -#: gitk:11373 msgid "Show local changes" msgstr "ローカルな変更を表示" -#: gitk:11376 msgid "Auto-select SHA1 (length)" msgstr "SHA1 の自動選択 (選択文字数指定)" -#: gitk:11380 msgid "Hide remote refs" msgstr "リモートリファレンスを隠す" -#: gitk:11384 msgid "Diff display options" msgstr "diff表示オプション" -#: gitk:11386 msgid "Tab spacing" msgstr "タブ空白幅" -#: gitk:11389 msgid "Display nearby tags/heads" msgstr "近くの タグ/head を表示する" -#: gitk:11392 msgid "Maximum # tags/heads to show" msgstr "タグ/head の最大表示数" -#: gitk:11395 msgid "Limit diffs to listed paths" msgstr "diff をリストのパスに制限" -#: gitk:11398 msgid "Support per-file encodings" msgstr "ファイルごとのエンコーディングのサポート" -#: gitk:11404 gitk:11551 msgid "External diff tool" msgstr "外部diffツール" -#: gitk:11405 msgid "Choose..." msgstr "選択..." -#: gitk:11410 msgid "General options" msgstr "全体設定" -#: gitk:11413 msgid "Use themed widgets" msgstr "テーマウィジェットを使用する" -#: gitk:11415 msgid "(change requires restart)" msgstr "(変更には再起動が必要です)" -#: gitk:11417 msgid "(currently unavailable)" msgstr "(現在は使用出来ません)" -#: gitk:11428 msgid "Colors: press to choose" msgstr "色: ボタンを押して選択" -#: gitk:11431 msgid "Interface" msgstr "インターフェイス" -#: gitk:11432 msgid "interface" msgstr "インターフェイス" -#: gitk:11435 msgid "Background" msgstr "背景" -#: gitk:11436 gitk:11466 msgid "background" msgstr "背景" -#: gitk:11439 msgid "Foreground" msgstr "前景" -#: gitk:11440 msgid "foreground" msgstr "前景" -#: gitk:11443 msgid "Diff: old lines" msgstr "Diff: 旧バージョン" -#: gitk:11444 msgid "diff old lines" msgstr "diff 旧バージョン" -#: gitk:11448 msgid "Diff: new lines" msgstr "Diff: 新バージョン" -#: gitk:11449 msgid "diff new lines" msgstr "diff 新バージョン" -#: gitk:11453 msgid "Diff: hunk header" msgstr "Diff: hunkヘッダ" -#: gitk:11455 msgid "diff hunk header" msgstr "diff hunkヘッダ" -#: gitk:11459 msgid "Marked line bg" msgstr "マーク行の背景" -#: gitk:11461 msgid "marked line background" msgstr "マーク行の背景" -#: gitk:11465 msgid "Select bg" msgstr "選択の背景" -#: gitk:11474 msgid "Fonts: press to choose" msgstr "フォント: ボタンを押して選択" -#: gitk:11476 msgid "Main font" msgstr "主フォント" -#: gitk:11477 msgid "Diff display font" msgstr "Diff表示用フォント" -#: gitk:11478 msgid "User interface font" msgstr "UI用フォント" -#: gitk:11500 msgid "Gitk preferences" msgstr "Gitk 設定" -#: gitk:11509 msgid "General" msgstr "一般" -#: gitk:11510 msgid "Colors" msgstr "色" -#: gitk:11511 msgid "Fonts" msgstr "フォント" -#: gitk:11561 #, tcl-format msgid "Gitk: choose color for %s" msgstr "Gitk: 「%s」 の色を選択" -#: gitk:12074 msgid "" "Sorry, gitk cannot run with this version of Tcl/Tk.\n" " Gitk requires at least Tcl/Tk 8.4." @@ -1365,15 +1059,12 @@ "申し訳ありませんが、このバージョンの Tcl/Tk では gitk を実行出来ません。\n" "Gitkの実行には Tcl/Tk 8.4 以上が必要です。" -#: gitk:12284 msgid "Cannot find a git repository here." msgstr "ここにはgitリポジトリがありません。" -#: gitk:12331 #, tcl-format msgid "Ambiguous argument '%s': both revision and filename" msgstr "あいまいな引数 '%s': リビジョンとファイル名の両方に解釈できます" -#: gitk:12343 msgid "Bad arguments to gitk:" msgstr "gitkへの不正な引数:"
diff --git a/gitk-git/po/pt_br.po b/gitk-git/po/pt_br.po index 1feb348..e78fb26 100644 --- a/gitk-git/po/pt_br.po +++ b/gitk-git/po/pt_br.po
@@ -7,7 +7,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: gitk\n" +"Project-Id-Version: Gitk\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-05-17 14:32+1000\n" "PO-Revision-Date: 2010-12-06 23:39-0200\n" @@ -18,33 +18,26 @@ "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: gitk:140 msgid "Couldn't get list of unmerged files:" msgstr "Não foi possível obter a lista dos arquivos não mesclados:" -#: gitk:212 gitk:2381 msgid "Color words" msgstr "" -#: gitk:217 gitk:2381 gitk:8220 gitk:8253 msgid "Markup words" msgstr "" -#: gitk:324 msgid "Error parsing revisions:" msgstr "Erro ao interpretar revisões:" -#: gitk:380 msgid "Error executing --argscmd command:" msgstr "Erro ao executar o comando--argscmd:" -#: gitk:393 msgid "No files selected: --merge specified but no files are unmerged." msgstr "" "Nenhum arquivo foi selecionado: --merge especificado mas não há arquivos não-" "mesclados." -#: gitk:396 msgid "" "No files selected: --merge specified but no unmerged files are within file " "limit." @@ -52,317 +45,237 @@ "Nenhum arquivo foi selecionado: --merge especificado mas não há arquivos não-" "mesclados dentro dos limites." -#: gitk:418 gitk:566 msgid "Error executing git log:" msgstr "Erro ao executar git log:" -#: gitk:436 gitk:582 msgid "Reading" msgstr "Lendo" -#: gitk:496 gitk:4525 msgid "Reading commits..." msgstr "Lendo revisões..." -#: gitk:499 gitk:1637 gitk:4528 msgid "No commits selected" msgstr "Nenhuma revisão foi selecionada" -#: gitk:1445 gitk:4045 gitk:12432 msgid "Command line" msgstr "Linha de comando" -#: gitk:1511 msgid "Can't parse git log output:" msgstr "Não foi possível interpretar a saída do \"git log\":" -#: gitk:1740 msgid "No commit information available" msgstr "Não há informações disponíveis sobre a revisão" -#: gitk:1903 gitk:1932 gitk:4315 gitk:9669 gitk:11241 gitk:11521 msgid "OK" msgstr "Ok" -#: gitk:1934 gitk:4317 gitk:9196 gitk:9275 gitk:9391 gitk:9440 gitk:9671 -#: gitk:11242 gitk:11522 msgid "Cancel" msgstr "Cancelar" -#: gitk:2069 msgid "&Update" msgstr "Atualizar" -#: gitk:2070 msgid "&Reload" msgstr "Recarregar" -#: gitk:2071 msgid "Reread re&ferences" msgstr "Ler as referências novamente" -#: gitk:2072 msgid "&List references" msgstr "Listar referências" -#: gitk:2074 msgid "Start git &gui" msgstr "Iniciar Git GUI" -#: gitk:2076 msgid "&Quit" msgstr "Sair" -#: gitk:2068 msgid "&File" msgstr "Arquivo" -#: gitk:2080 msgid "&Preferences" msgstr "Preferências" -#: gitk:2079 msgid "&Edit" msgstr "Editar" -#: gitk:2084 msgid "&New view..." msgstr "Nova vista..." -#: gitk:2085 msgid "&Edit view..." msgstr "Editar vista..." -#: gitk:2086 msgid "&Delete view" msgstr "Apagar vista" -#: gitk:2088 gitk:4043 msgid "&All files" msgstr "Todos os arquivos" -#: gitk:2083 gitk:4067 msgid "&View" msgstr "Exibir" -#: gitk:2093 gitk:2103 gitk:3012 msgid "&About gitk" msgstr "Sobre o gitk" -#: gitk:2094 gitk:2108 msgid "&Key bindings" msgstr "Atalhos de teclado" -#: gitk:2092 gitk:2107 msgid "&Help" msgstr "Ajuda" -#: gitk:2185 gitk:8652 msgid "SHA1 ID:" msgstr "SHA1 ID:" -#: gitk:2229 msgid "Row" msgstr "Linha" -#: gitk:2267 msgid "Find" msgstr "Encontrar" -#: gitk:2295 msgid "commit" msgstr "Revisão" -#: gitk:2299 gitk:2301 gitk:4687 gitk:4710 gitk:4734 gitk:6755 gitk:6827 -#: gitk:6912 msgid "containing:" msgstr "contendo:" -#: gitk:2302 gitk:3526 gitk:3531 gitk:4763 msgid "touching paths:" msgstr "envolvendo os caminhos:" -#: gitk:2303 gitk:4777 msgid "adding/removing string:" msgstr "Adicionando/removendo texto:" -#: gitk:2304 gitk:4779 msgid "changing lines matching:" msgstr "" -#: gitk:2313 gitk:2315 gitk:4766 msgid "Exact" msgstr "Exatamente" -#: gitk:2315 gitk:4854 gitk:6723 msgid "IgnCase" msgstr "Ignorar maiúsculas/minúsculas" -#: gitk:2315 gitk:4736 gitk:4852 gitk:6719 msgid "Regexp" msgstr "Expressão regular" -#: gitk:2317 gitk:2318 gitk:4874 gitk:4904 gitk:4911 gitk:6848 gitk:6916 msgid "All fields" msgstr "Todos os campos" -#: gitk:2318 gitk:4871 gitk:4904 gitk:6786 msgid "Headline" msgstr "Assunto" -#: gitk:2319 gitk:4871 gitk:6786 gitk:6916 gitk:7389 msgid "Comments" msgstr "Descrição da revisão" -#: gitk:2319 gitk:4871 gitk:4876 gitk:4911 gitk:6786 gitk:7324 gitk:8830 -#: gitk:8845 msgid "Author" msgstr "Autor" -#: gitk:2319 gitk:4871 gitk:6786 gitk:7326 msgid "Committer" msgstr "Revisor" -#: gitk:2350 msgid "Search" msgstr "Buscar" -#: gitk:2358 msgid "Diff" msgstr "Diferenças" -#: gitk:2360 msgid "Old version" msgstr "Versão antiga" -#: gitk:2362 msgid "New version" msgstr "Versão nova" -#: gitk:2364 msgid "Lines of context" msgstr "Número de linhas de contexto" -#: gitk:2374 msgid "Ignore space change" msgstr "Ignorar mudanças de caixa" -#: gitk:2378 gitk:2380 gitk:7959 gitk:8206 msgid "Line diff" msgstr "" -#: gitk:2445 msgid "Patch" msgstr "Diferenças" -#: gitk:2447 msgid "Tree" msgstr "Árvore" -#: gitk:2617 gitk:2637 msgid "Diff this -> selected" msgstr "Comparar esta revisão com a selecionada" -#: gitk:2618 gitk:2638 msgid "Diff selected -> this" msgstr "Comparar a revisão selecionada com esta" -#: gitk:2619 gitk:2639 msgid "Make patch" msgstr "Criar patch" -#: gitk:2620 gitk:9254 msgid "Create tag" msgstr "Criar etiqueta" -#: gitk:2621 gitk:9371 msgid "Write commit to file" msgstr "Salvar revisão para um arquivo" -#: gitk:2622 gitk:9428 msgid "Create new branch" msgstr "Criar novo ramo" -#: gitk:2623 msgid "Cherry-pick this commit" msgstr "Fazer cherry-pick desta revisão" -#: gitk:2624 msgid "Reset HEAD branch to here" msgstr "Redefinir HEAD para cá" -#: gitk:2625 msgid "Mark this commit" msgstr "Marcar esta revisão" -#: gitk:2626 msgid "Return to mark" msgstr "Voltar à marca" -#: gitk:2627 msgid "Find descendant of this and mark" msgstr "Encontrar descendente e marcar" -#: gitk:2628 msgid "Compare with marked commit" msgstr "Comparar com a revisão marcada" -#: gitk:2629 gitk:2640 #, fuzzy msgid "Diff this -> marked commit" msgstr "Comparar esta revisão com a selecionada" -#: gitk:2630 gitk:2641 #, fuzzy msgid "Diff marked commit -> this" msgstr "Comparar a revisão selecionada com esta" -#: gitk:2631 #, fuzzy msgid "Revert this commit" msgstr "Marcar esta revisão" -#: gitk:2647 msgid "Check out this branch" msgstr "Efetuar checkout deste ramo" -#: gitk:2648 msgid "Remove this branch" msgstr "Excluir este ramo" -#: gitk:2649 msgid "Copy branch name" msgstr "" -#: gitk:2656 msgid "Highlight this too" msgstr "Marcar este também" -#: gitk:2657 msgid "Highlight this only" msgstr "Marcar apenas este" -#: gitk:2658 msgid "External diff" msgstr "Diff externo" -#: gitk:2659 msgid "Blame parent commit" msgstr "Anotar revisão anterior" -#: gitk:2660 msgid "Copy path" msgstr "" -#: gitk:2667 msgid "Show origin of this line" msgstr "Exibir origem desta linha" -#: gitk:2668 msgid "Run git gui blame on this line" msgstr "Executar 'git blame' nesta linha" -#: gitk:3014 #, fuzzy msgid "" "\n" @@ -379,320 +292,248 @@ "\n" "Uso e distribuição segundo os termos da Licença Pública Geral GNU" -#: gitk:3022 gitk:3089 gitk:9857 msgid "Close" msgstr "Fechar" -#: gitk:3043 msgid "Gitk key bindings" msgstr "Atalhos de teclado" -#: gitk:3046 msgid "Gitk key bindings:" msgstr "Atalhos de teclado:" -#: gitk:3048 #, tcl-format msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\tSair" -#: gitk:3049 #, tcl-format msgid "<%s-W>\t\tClose window" msgstr "<%s-W>\t\tFechar janela" -#: gitk:3050 msgid "<Home>\t\tMove to first commit" msgstr "<Home>\t\tIr para a primeira revisão" -#: gitk:3051 msgid "<End>\t\tMove to last commit" msgstr "<End>\t\tIr para a última revisão" -#: gitk:3052 #, fuzzy msgid "<Up>, p, k\tMove up one commit" msgstr "<Up>, p, i\tIr para uma revisão acima" -#: gitk:3053 #, fuzzy msgid "<Down>, n, j\tMove down one commit" msgstr "<Down>, n, k\tIr para uma revisão abaixo" -#: gitk:3054 #, fuzzy msgid "<Left>, z, h\tGo back in history list" msgstr "<Left>, z, j\tVoltar no histórico" -#: gitk:3055 msgid "<Right>, x, l\tGo forward in history list" msgstr "<Right>, x, l\tAvançar no histórico" -#: gitk:3056 #, tcl-format msgid "<%s-n>\tGo to n-th parent of current commit in history list" msgstr "" -#: gitk:3057 msgid "<PageUp>\tMove up one page in commit list" msgstr "<PageUp>\tSubir uma página na lista de revisões" -#: gitk:3058 msgid "<PageDown>\tMove down one page in commit list" msgstr "<PageDown>\tDescer uma página na lista de revisões" -#: gitk:3059 #, tcl-format msgid "<%s-Home>\tScroll to top of commit list" msgstr "<%s-Home>\tRolar para o início da lista de revisões" -#: gitk:3060 #, tcl-format msgid "<%s-End>\tScroll to bottom of commit list" msgstr "<%s-End>\tRolar para o final da lista de revisões" -#: gitk:3061 #, tcl-format msgid "<%s-Up>\tScroll commit list up one line" msgstr "<%s-Up>\tRolar uma linha acima na lista de revisões" -#: gitk:3062 #, tcl-format msgid "<%s-Down>\tScroll commit list down one line" msgstr "<%s-Down>\tRolar uma linha abaixo na lista de revisões" -#: gitk:3063 #, tcl-format msgid "<%s-PageUp>\tScroll commit list up one page" msgstr "<%s-PageUp>\tRolar uma página acima na lista de revisões" -#: gitk:3064 #, tcl-format msgid "<%s-PageDown>\tScroll commit list down one page" msgstr "<%s-PageDown>\tRolar uma página abaixo na lista de revisões" -#: gitk:3065 msgid "<Shift-Up>\tFind backwards (upwards, later commits)" msgstr "<Shift-Up>\tProcurar próxima (revisões mas recentes)" -#: gitk:3066 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" msgstr "<Shift-Down>\tProcurar anterior (revisões mais antigas)" -#: gitk:3067 msgid "<Delete>, b\tScroll diff view up one page" msgstr "<Delete>, b\tRola alterações uma página acima" -#: gitk:3068 msgid "<Backspace>\tScroll diff view up one page" msgstr "<Backspace>\tRolar alterações uma página abaixo" -#: gitk:3069 msgid "<Space>\t\tScroll diff view down one page" msgstr "<Space>\t\tRolar alterações uma página abaixo" -#: gitk:3070 msgid "u\t\tScroll diff view up 18 lines" msgstr "u\t\tRolar alterações 18 linhas acima" -#: gitk:3071 msgid "d\t\tScroll diff view down 18 lines" msgstr "d\t\tRolar alterações 18 linhas abaixo" -#: gitk:3072 #, tcl-format msgid "<%s-F>\t\tFind" msgstr "<%s-F>\t\tProcurar" -#: gitk:3073 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" msgstr "<%s-G>\t\tIr para a próxima ocorrência" -#: gitk:3074 msgid "<Return>\tMove to next find hit" msgstr "<Return>\tIr para a próxima ocorrência" -#: gitk:3075 #, fuzzy msgid "g\t\tGo to commit" msgstr "<End>\t\tIr para a última revisão" -#: gitk:3076 msgid "/\t\tFocus the search box" msgstr "/\t\tPor foco na caixa de busca" -#: gitk:3077 msgid "?\t\tMove to previous find hit" msgstr "?\t\tIr para a ocorrência anterior" -#: gitk:3078 msgid "f\t\tScroll diff view to next file" msgstr "f\t\tRolar alterações para o próximo arquivo" -#: gitk:3079 #, tcl-format msgid "<%s-S>\t\tSearch for next hit in diff view" msgstr "<%s-S>\t\tProcurar a próxima ocorrência na lista de alterações" -#: gitk:3080 #, tcl-format msgid "<%s-R>\t\tSearch for previous hit in diff view" msgstr "<%s-R>\t\tProcurar ocorrência anterior na lista de alterações" -#: gitk:3081 #, tcl-format msgid "<%s-KP+>\tIncrease font size" msgstr "<%s-KP+>\tAumentar tamanho da fonte" -#: gitk:3082 #, tcl-format msgid "<%s-plus>\tIncrease font size" msgstr "<%s-plus>\tAumentar tamanho da fonte" -#: gitk:3083 #, tcl-format msgid "<%s-KP->\tDecrease font size" msgstr "<%s-KP->\tReduzir tamanho da fonte" -#: gitk:3084 #, tcl-format msgid "<%s-minus>\tDecrease font size" msgstr "<%s-minus>\tReduzir tamanho da fonte" -#: gitk:3085 msgid "<F5>\t\tUpdate" msgstr "<F5>\t\tAtualizar" -#: gitk:3550 gitk:3559 #, tcl-format msgid "Error creating temporary directory %s:" msgstr "Erro ao criar o diretório temporário %s:" -#: gitk:3572 #, tcl-format msgid "Error getting \"%s\" from %s:" msgstr "Erro ao ler \"%s\" de %s:" -#: gitk:3635 msgid "command failed:" msgstr "O comando falhou:" -#: gitk:3784 msgid "No such commit" msgstr "Revisão não encontrada" -#: gitk:3798 msgid "git gui blame: command failed:" msgstr "Comando 'git gui blame' falhou:" -#: gitk:3829 #, tcl-format msgid "Couldn't read merge head: %s" msgstr "Impossível ler merge head: %s" -#: gitk:3837 #, tcl-format msgid "Error reading index: %s" msgstr "Erro ao ler o índice: %s" -#: gitk:3862 #, tcl-format msgid "Couldn't start git blame: %s" msgstr "Não foi possível inciar o 'git blame': %s" -#: gitk:3865 gitk:6754 msgid "Searching" msgstr "Procurando" -#: gitk:3897 #, tcl-format msgid "Error running git blame: %s" msgstr "Erro ao executar 'git blame': %s" -#: gitk:3925 #, tcl-format msgid "That line comes from commit %s, which is not in this view" msgstr "Esta linha vem da revisão %s, que não está nesta vista" -#: gitk:3939 msgid "External diff viewer failed:" msgstr "Erro do visualizador de alterações externo:" -#: gitk:4070 msgid "Gitk view definition" msgstr "Definir vista" -#: gitk:4074 msgid "Remember this view" msgstr "Lembrar esta vista" -#: gitk:4075 msgid "References (space separated list):" msgstr "Referências (separar a lista com um espaço):" -#: gitk:4076 msgid "Branches & tags:" msgstr "Ramos & etiquetas:" -#: gitk:4077 msgid "All refs" msgstr "Todas as referências" -#: gitk:4078 msgid "All (local) branches" msgstr "Todos os ramos locais" -#: gitk:4079 msgid "All tags" msgstr "Todas as etiquetas" -#: gitk:4080 msgid "All remote-tracking branches" msgstr "Todos os ramos de rastreio" -#: gitk:4081 msgid "Commit Info (regular expressions):" msgstr "Informações da revisão (expressões regulares):" -#: gitk:4082 msgid "Author:" msgstr "Autor:" -#: gitk:4083 msgid "Committer:" msgstr "Revisor:" -#: gitk:4084 msgid "Commit Message:" msgstr "Descrição da revisão:" -#: gitk:4085 msgid "Matches all Commit Info criteria" msgstr "Coincidir todos os critérios de informações da revisão" -#: gitk:4086 #, fuzzy msgid "Matches no Commit Info criteria" msgstr "Coincidir todos os critérios de informações da revisão" -#: gitk:4087 msgid "Changes to Files:" msgstr "Mudanças para os arquivos:" -#: gitk:4088 msgid "Fixed String" msgstr "Texto fixo" -#: gitk:4089 msgid "Regular Expression" msgstr "Expressão regular" -#: gitk:4090 msgid "Search string:" msgstr "Texto de busca" -#: gitk:4091 msgid "" "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" @@ -700,201 +541,153 @@ "Datas de revisão (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" -#: gitk:4092 msgid "Since:" msgstr "Desde:" -#: gitk:4093 msgid "Until:" msgstr "Até:" -#: gitk:4094 msgid "Limit and/or skip a number of revisions (positive integer):" msgstr "Limitar e/ou ignorar um número de revisões (inteiro positivo):" -#: gitk:4095 msgid "Number to show:" msgstr "Número para mostrar:" -#: gitk:4096 msgid "Number to skip:" msgstr "Número para ignorar:" -#: gitk:4097 msgid "Miscellaneous options:" msgstr "Opções diversas:" -#: gitk:4098 msgid "Strictly sort by date" msgstr "Ordenar estritamente pela data" -#: gitk:4099 msgid "Mark branch sides" msgstr "Marcar os dois lados do ramo" -#: gitk:4100 msgid "Limit to first parent" msgstr "Limitar ao primeiro antecessor" -#: gitk:4101 msgid "Simple history" msgstr "Histórico simplificado" -#: gitk:4102 msgid "Additional arguments to git log:" msgstr "Argumentos adicionais para o 'git log':" -#: gitk:4103 msgid "Enter files and directories to include, one per line:" msgstr "Arquivos e diretórios para incluir, um por linha" -#: gitk:4104 msgid "Command to generate more commits to include:" msgstr "Comando para gerar mais revisões para incluir:" -#: gitk:4228 msgid "Gitk: edit view" msgstr "Gitk: editar vista" -#: gitk:4236 msgid "-- criteria for selecting revisions" msgstr "-- critérios para selecionar revisões" -#: gitk:4241 msgid "View Name" msgstr "Nome da vista" -#: gitk:4316 msgid "Apply (F5)" msgstr "Aplicar (F5)" -#: gitk:4354 msgid "Error in commit selection arguments:" msgstr "Erro nos argumentos de seleção de revisões:" -#: gitk:4409 gitk:4462 gitk:4924 gitk:4938 gitk:6208 gitk:12373 gitk:12374 msgid "None" msgstr "Nenhum" -#: gitk:5021 gitk:5026 msgid "Descendant" msgstr "Descendente de" -#: gitk:5022 msgid "Not descendant" msgstr "Não descendente de" -#: gitk:5029 gitk:5034 msgid "Ancestor" msgstr "Antecessor de" -#: gitk:5030 msgid "Not ancestor" msgstr "Não antecessor de" -#: gitk:5324 msgid "Local changes checked in to index but not committed" msgstr "Mudanças locais marcadas, porém não salvas" -#: gitk:5360 msgid "Local uncommitted changes, not checked in to index" msgstr "Mudanças locais não marcadas" -#: gitk:7134 msgid "and many more" msgstr "" -#: gitk:7137 msgid "many" msgstr "muitas" -#: gitk:7328 msgid "Tags:" msgstr "Etiquetas:" -#: gitk:7345 gitk:7351 gitk:8825 msgid "Parent" msgstr "Antecessor" -#: gitk:7356 msgid "Child" msgstr "Descendente" -#: gitk:7365 msgid "Branch" msgstr "Ramo" -#: gitk:7368 msgid "Follows" msgstr "Segue" -#: gitk:7371 msgid "Precedes" msgstr "Precede" -#: gitk:7966 #, tcl-format msgid "Error getting diffs: %s" msgstr "Erro ao obter diferenças: %s" -#: gitk:8650 msgid "Goto:" msgstr "Ir para:" -#: gitk:8671 #, tcl-format msgid "Short SHA1 id %s is ambiguous" msgstr "O id SHA1 %s é ambíguo" -#: gitk:8678 #, tcl-format msgid "Revision %s is not known" msgstr "Revisão %s desconhecida" -#: gitk:8688 #, tcl-format msgid "SHA1 id %s is not known" msgstr "Id SHA1 %s desconhecido" -#: gitk:8690 #, tcl-format msgid "Revision %s is not in the current view" msgstr "A revisão %s não está na vista atual" -#: gitk:8832 gitk:8847 msgid "Date" msgstr "Data" -#: gitk:8835 msgid "Children" msgstr "Descendentes" -#: gitk:8898 #, tcl-format msgid "Reset %s branch to here" msgstr "Redefinir ramo %s para este ponto" -#: gitk:8900 msgid "Detached head: can't reset" msgstr "Detached head: impossível redefinir" -#: gitk:9005 gitk:9011 msgid "Skipping merge commit " msgstr "Saltando revisão de mesclagem" -#: gitk:9020 gitk:9025 msgid "Error getting patch ID for " msgstr "Erro ao obter patch ID para" -#: gitk:9021 gitk:9026 msgid " - stopping\n" msgstr "- parando\n" -#: gitk:9031 gitk:9034 gitk:9042 gitk:9056 gitk:9065 msgid "Commit " msgstr "Revisão" -#: gitk:9035 msgid "" " is the same patch as\n" " " @@ -902,13 +695,11 @@ "é o mesmo patch que\n" " " -#: gitk:9043 msgid "" " differs from\n" " " msgstr "difere de" -#: gitk:9045 msgid "" "Diff of commits:\n" "\n" @@ -916,129 +707,99 @@ "Diferença de revisões:\n" "\n" -#: gitk:9057 gitk:9066 #, tcl-format msgid " has %s children - stopping\n" msgstr "possui %s descendentes - parando\n" -#: gitk:9085 #, tcl-format msgid "Error writing commit to file: %s" msgstr "Erro ao salvar revisão para o arquivo: %s" -#: gitk:9091 #, tcl-format msgid "Error diffing commits: %s" msgstr "Erro ao comparar revisões: %s" -#: gitk:9137 msgid "Top" msgstr "Início" -#: gitk:9138 msgid "From" msgstr "De" -#: gitk:9143 msgid "To" msgstr "Para" -#: gitk:9167 msgid "Generate patch" msgstr "Gerar patch" -#: gitk:9169 msgid "From:" msgstr "De:" -#: gitk:9178 msgid "To:" msgstr "Para:" -#: gitk:9187 msgid "Reverse" msgstr "Inverter" -#: gitk:9189 gitk:9385 msgid "Output file:" msgstr "Arquivo de saída:" -#: gitk:9195 msgid "Generate" msgstr "Gerar" -#: gitk:9233 msgid "Error creating patch:" msgstr "Erro ao criar patch:" -#: gitk:9256 gitk:9373 gitk:9430 msgid "ID:" msgstr "ID:" -#: gitk:9265 msgid "Tag name:" msgstr "Nome da etiqueta:" -#: gitk:9268 msgid "Tag message is optional" msgstr "A descrição da etiqueta é opcional" -#: gitk:9270 msgid "Tag message:" msgstr "Descrição da etiqueta" -#: gitk:9274 gitk:9439 msgid "Create" msgstr "Criar" -#: gitk:9292 msgid "No tag name specified" msgstr "Nome da etiqueta não indicado" -#: gitk:9296 #, tcl-format msgid "Tag \"%s\" already exists" msgstr "Etiqueta \"%s\" já existe" -#: gitk:9306 msgid "Error creating tag:" msgstr "Erro ao criar etiqueta:" -#: gitk:9382 msgid "Command:" msgstr "Comando:" -#: gitk:9390 msgid "Write" msgstr "Exportar" -#: gitk:9408 msgid "Error writing commit:" msgstr "Erro ao exportar revisão" -#: gitk:9435 msgid "Name:" msgstr "Nome:" -#: gitk:9458 msgid "Please specify a name for the new branch" msgstr "Indique um nome para o novo ramo" -#: gitk:9463 #, tcl-format msgid "Branch '%s' already exists. Overwrite?" msgstr "O ramo \"%s\" já existe. Sobrescrever?" -#: gitk:9530 #, tcl-format msgid "Commit %s is already included in branch %s -- really re-apply it?" msgstr "Revisão %s já inclusa no ramo %s -- você realmente deseja reaplicá-la?" -#: gitk:9535 msgid "Cherry-picking" msgstr "Cherry-picking" -#: gitk:9544 #, tcl-format msgid "" "Cherry-pick failed because of local changes to file '%s'.\n" @@ -1048,7 +809,6 @@ "Salve a uma revisão, redefina ou armazene (stash) suas mudanças e tente " "novamente." -#: gitk:9550 msgid "" "Cherry-pick failed because of merge conflict.\n" "Do you wish to run git citool to resolve it?" @@ -1056,21 +816,17 @@ "O cherry-pick falhou porque houve um conflito na mesclagem.\n" "Executar o 'git citool' para resolvê-lo?" -#: gitk:9566 gitk:9624 msgid "No changes committed" msgstr "Nenhuma revisão foi salva" -#: gitk:9593 #, fuzzy, tcl-format msgid "Commit %s is not included in branch %s -- really revert it?" msgstr "Revisão %s já inclusa no ramo %s -- você realmente deseja reaplicá-la?" -#: gitk:9598 #, fuzzy msgid "Reverting" msgstr "Redefinindo" -#: gitk:9606 #, fuzzy, tcl-format msgid "" "Revert failed because of local changes to the following files:%s Please " @@ -1080,7 +836,6 @@ "Salve a uma revisão, redefina ou armazene (stash) suas mudanças e tente " "novamente." -#: gitk:9610 #, fuzzy msgid "" "Revert failed because of merge conflict.\n" @@ -1089,28 +844,22 @@ "O cherry-pick falhou porque houve um conflito na mesclagem.\n" "Executar o 'git citool' para resolvê-lo?" -#: gitk:9653 msgid "Confirm reset" msgstr "Confirmar redefinição" -#: gitk:9655 #, tcl-format msgid "Reset branch %s to %s?" msgstr "Você realmente deseja redefinir o ramo %s para %s?" -#: gitk:9657 msgid "Reset type:" msgstr "Tipo de redefinição" -#: gitk:9660 msgid "Soft: Leave working tree and index untouched" msgstr "Soft: deixa a árvore de trabalho e o índice intocados" -#: gitk:9663 msgid "Mixed: Leave working tree untouched, reset index" msgstr "Misto: Deixa a árvore de trabalho intocada, redefine o índice" -#: gitk:9666 msgid "" "Hard: Reset working tree and index\n" "(discard ALL local changes)" @@ -1118,19 +867,15 @@ "Hard: Redefine a árvore de trabalho e o índice\n" "(descarta TODAS as mudanças locais)" -#: gitk:9683 msgid "Resetting" msgstr "Redefinindo" -#: gitk:9743 msgid "Checking out" msgstr "Abrindo" -#: gitk:9796 msgid "Cannot delete the currently checked-out branch" msgstr "Impossível excluir o ramo atualmente aberto" -#: gitk:9802 #, tcl-format msgid "" "The commits on branch %s aren't on any other branch.\n" @@ -1139,16 +884,13 @@ "As revisões do ramo \"%s\" não existem em nenhum outro ramo.\n" "Você realmente deseja excluir ramo \"%s\"?" -#: gitk:9833 #, tcl-format msgid "Tags and heads: %s" msgstr "Referências: %s" -#: gitk:9850 msgid "Filter" msgstr "Filtro" -#: gitk:10146 msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." @@ -1156,221 +898,169 @@ "Erro ao ler a topologia das revisões; as informações dos ramos e etiquetas " "antecessoras/sucessoras estarão incompletas" -#: gitk:11123 msgid "Tag" msgstr "Etiqueta" -#: gitk:11127 msgid "Id" msgstr "Id" -#: gitk:11210 msgid "Gitk font chooser" msgstr "Selecionar fontes do Gitk" -#: gitk:11227 msgid "B" msgstr "B" -#: gitk:11230 msgid "I" msgstr "I" -#: gitk:11348 msgid "Commit list display options" msgstr "Opções da lista de revisões" -#: gitk:11351 msgid "Maximum graph width (lines)" msgstr "Largura máxima do grafo (linhas)" -#: gitk:11355 #, no-tcl-format msgid "Maximum graph width (% of pane)" msgstr "Largura máxima do grafo (% do painel)" -#: gitk:11358 msgid "Show local changes" msgstr "Exibir mudanças locais" -#: gitk:11361 #, fuzzy msgid "Auto-select SHA1 (length)" msgstr "Selecionar o SHA1 automaticamente" -#: gitk:11365 msgid "Hide remote refs" msgstr "Ocultar referências remotas" -#: gitk:11369 msgid "Diff display options" msgstr "Opções de exibição das alterações" -#: gitk:11371 msgid "Tab spacing" msgstr "Espaços por tabulação" -#: gitk:11374 #, fuzzy msgid "Display nearby tags/heads" msgstr "Exibir etiquetas próximas" -#: gitk:11377 msgid "Maximum # tags/heads to show" msgstr "" -#: gitk:11380 msgid "Limit diffs to listed paths" msgstr "Limitar diferenças aos caminhos listados" -#: gitk:11383 msgid "Support per-file encodings" msgstr "Usar codificações distintas por arquivo" -#: gitk:11389 gitk:11536 msgid "External diff tool" msgstr "Ferramenta 'diff' externa" -#: gitk:11390 msgid "Choose..." msgstr "Selecionar..." -#: gitk:11395 msgid "General options" msgstr "Opções gerais" -#: gitk:11398 msgid "Use themed widgets" msgstr "Usar temas para as janelas" -#: gitk:11400 msgid "(change requires restart)" msgstr "(exige reinicialização)" -#: gitk:11402 msgid "(currently unavailable)" msgstr "(atualmente indisponível)" -#: gitk:11413 msgid "Colors: press to choose" msgstr "Cores: clique para escolher" -#: gitk:11416 msgid "Interface" msgstr "Interface" -#: gitk:11417 msgid "interface" msgstr "interface" -#: gitk:11420 msgid "Background" msgstr "Segundo plano" -#: gitk:11421 gitk:11451 msgid "background" msgstr "segundo plano" -#: gitk:11424 msgid "Foreground" msgstr "Primeiro plano" -#: gitk:11425 msgid "foreground" msgstr "primeiro plano" -#: gitk:11428 msgid "Diff: old lines" msgstr "Diff: linhas excluídas" -#: gitk:11429 msgid "diff old lines" msgstr "linhas excluídas" -#: gitk:11433 msgid "Diff: new lines" msgstr "Diff: linhas adicionadas" -#: gitk:11434 msgid "diff new lines" msgstr "linhas adicionadas" -#: gitk:11438 msgid "Diff: hunk header" msgstr "Diff: cabeçalho do bloco" -#: gitk:11440 msgid "diff hunk header" msgstr "cabeçalho do bloco" -#: gitk:11444 msgid "Marked line bg" msgstr "2º plano da linha marcada" -#: gitk:11446 msgid "marked line background" msgstr "segundo plano da linha marcada" -#: gitk:11450 msgid "Select bg" msgstr "2º plano da seleção" -#: gitk:11459 msgid "Fonts: press to choose" msgstr "Fontes: clique para escolher" -#: gitk:11461 msgid "Main font" msgstr "Fonte principal" -#: gitk:11462 msgid "Diff display font" msgstr "Fonte da lista de mudanças" -#: gitk:11463 msgid "User interface font" msgstr "Fonte da interface" -#: gitk:11485 msgid "Gitk preferences" msgstr "Preferências do Gitk" -#: gitk:11494 #, fuzzy msgid "General" msgstr "Gerar" -#: gitk:11495 msgid "Colors" msgstr "" -#: gitk:11496 msgid "Fonts" msgstr "" -#: gitk:11546 #, tcl-format msgid "Gitk: choose color for %s" msgstr "Gitk: selecionar cor para %s" -#: gitk:12059 msgid "" "Sorry, gitk cannot run with this version of Tcl/Tk.\n" " Gitk requires at least Tcl/Tk 8.4." msgstr "" -#: gitk:12269 msgid "Cannot find a git repository here." msgstr "Não há nenhum repositório git aqui." -#: gitk:12316 #, tcl-format msgid "Ambiguous argument '%s': both revision and filename" msgstr "" "O argumento \"%s\" é ambíguo (especifica tanto uma revisão e um nome de " "arquivo)" -#: gitk:12328 msgid "Bad arguments to gitk:" msgstr "Argumentos incorretos para o gitk:"
diff --git a/gitk-git/po/pt_pt.po b/gitk-git/po/pt_pt.po index f680ea8..66d3159 100644 --- a/gitk-git/po/pt_pt.po +++ b/gitk-git/po/pt_pt.po
@@ -4,7 +4,7 @@ # Vasco Almeida <vascomalmeida@sapo.pt>, 2016. msgid "" msgstr "" -"Project-Id-Version: gitk\n" +"Project-Id-Version: Gitk\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-04-15 16:52+0000\n" "PO-Revision-Date: 2016-05-06 15:35+0000\n" @@ -17,33 +17,26 @@ "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Virtaal 0.7.1\n" -#: gitk:140 msgid "Couldn't get list of unmerged files:" msgstr "Não foi possível obter lista de ficheiros não integrados:" -#: gitk:212 gitk:2399 msgid "Color words" msgstr "Colorir palavras" -#: gitk:217 gitk:2399 gitk:8239 gitk:8272 msgid "Markup words" msgstr "Marcar palavras" -#: gitk:324 msgid "Error parsing revisions:" msgstr "Erro ao analisar revisões:" -#: gitk:380 msgid "Error executing --argscmd command:" msgstr "Erro ao executar o comando de --argscmd:" -#: gitk:393 msgid "No files selected: --merge specified but no files are unmerged." msgstr "" "Nenhum ficheiro selecionado: --merge especificado mas não há ficheiros por " "integrar." -#: gitk:396 msgid "" "No files selected: --merge specified but no unmerged files are within file " "limit." @@ -51,322 +44,240 @@ "Nenhum ficheiro selecionado: --merge especificado mas não há ficheiros por " "integrar ao nível de ficheiro." -#: gitk:418 gitk:566 msgid "Error executing git log:" msgstr "Erro ao executar git log:" -#: gitk:436 gitk:582 msgid "Reading" msgstr "A ler" -#: gitk:496 gitk:4544 msgid "Reading commits..." msgstr "A ler commits..." -#: gitk:499 gitk:1637 gitk:4547 msgid "No commits selected" msgstr "Nenhum commit selecionado" -#: gitk:1445 gitk:4064 gitk:12469 msgid "Command line" msgstr "Linha de comandos" -#: gitk:1511 msgid "Can't parse git log output:" msgstr "Não é possível analisar a saída de git log:" -#: gitk:1740 msgid "No commit information available" msgstr "Não há informação disponível sobre o commit" -#: gitk:1903 gitk:1932 gitk:4334 gitk:9702 gitk:11274 gitk:11554 msgid "OK" msgstr "OK" -#: gitk:1934 gitk:4336 gitk:9215 gitk:9294 gitk:9424 gitk:9473 gitk:9704 -#: gitk:11275 gitk:11555 msgid "Cancel" msgstr "Cancelar" -#: gitk:2083 msgid "&Update" msgstr "At&ualizar" -#: gitk:2084 msgid "&Reload" msgstr "&Recarregar" -#: gitk:2085 msgid "Reread re&ferences" msgstr "Reler re&ferências" -#: gitk:2086 msgid "&List references" msgstr "&Listar referências" -#: gitk:2088 msgid "Start git &gui" msgstr "Iniciar git &gui" -#: gitk:2090 msgid "&Quit" msgstr "&Sair" -#: gitk:2082 msgid "&File" msgstr "&Ficheiro" -#: gitk:2094 msgid "&Preferences" msgstr "&Preferências" -#: gitk:2093 msgid "&Edit" msgstr "&Editar" -#: gitk:2098 msgid "&New view..." msgstr "&Nova vista..." -#: gitk:2099 msgid "&Edit view..." msgstr "&Editar vista..." -#: gitk:2100 msgid "&Delete view" msgstr "Elimina&r vista" -#: gitk:2102 msgid "&All files" msgstr "&Todos os ficheiros" -#: gitk:2097 msgid "&View" msgstr "&Ver" -#: gitk:2107 gitk:2117 msgid "&About gitk" msgstr "&Sobre gitk" -#: gitk:2108 gitk:2122 msgid "&Key bindings" msgstr "&Atalhos" -#: gitk:2106 gitk:2121 msgid "&Help" msgstr "&Ajuda" -#: gitk:2199 gitk:8671 msgid "SHA1 ID:" msgstr "ID SHA1:" -#: gitk:2243 msgid "Row" msgstr "Linha" -#: gitk:2281 msgid "Find" msgstr "Procurar" -#: gitk:2309 msgid "commit" msgstr "commit" -#: gitk:2313 gitk:2315 gitk:4706 gitk:4729 gitk:4753 gitk:6774 gitk:6846 -#: gitk:6931 msgid "containing:" msgstr "contendo:" -#: gitk:2316 gitk:3545 gitk:3550 gitk:4782 msgid "touching paths:" msgstr "altera os caminhos:" -#: gitk:2317 gitk:4796 msgid "adding/removing string:" msgstr "adiciona/remove a cadeia:" -#: gitk:2318 gitk:4798 msgid "changing lines matching:" msgstr "altera linhas com:" -#: gitk:2327 gitk:2329 gitk:4785 msgid "Exact" msgstr "Exato" -#: gitk:2329 gitk:4873 gitk:6742 msgid "IgnCase" msgstr "IgnMaiúsculas" -#: gitk:2329 gitk:4755 gitk:4871 gitk:6738 msgid "Regexp" msgstr "Expr. regular" -#: gitk:2331 gitk:2332 gitk:4893 gitk:4923 gitk:4930 gitk:6867 gitk:6935 msgid "All fields" msgstr "Todos os campos" -#: gitk:2332 gitk:4890 gitk:4923 gitk:6805 msgid "Headline" msgstr "Cabeçalho" -#: gitk:2333 gitk:4890 gitk:6805 gitk:6935 gitk:7408 msgid "Comments" msgstr "Comentários" -#: gitk:2333 gitk:4890 gitk:4895 gitk:4930 gitk:6805 gitk:7343 gitk:8849 -#: gitk:8864 msgid "Author" msgstr "Autor" -#: gitk:2333 gitk:4890 gitk:6805 gitk:7345 msgid "Committer" msgstr "Committer" -#: gitk:2367 msgid "Search" msgstr "Pesquisar" -#: gitk:2375 msgid "Diff" msgstr "Diff" -#: gitk:2377 msgid "Old version" msgstr "Versão antiga" -#: gitk:2379 msgid "New version" msgstr "Versão nova" -#: gitk:2382 msgid "Lines of context" msgstr "Linhas de contexto" -#: gitk:2392 msgid "Ignore space change" msgstr "Ignorar espaços" -#: gitk:2396 gitk:2398 gitk:7978 gitk:8225 msgid "Line diff" msgstr "Diff de linha" -#: gitk:2463 msgid "Patch" msgstr "Patch" -#: gitk:2465 msgid "Tree" msgstr "Árvore" -#: gitk:2635 gitk:2656 msgid "Diff this -> selected" msgstr "Diff este -> seleção" -#: gitk:2636 gitk:2657 msgid "Diff selected -> this" msgstr "Diff seleção -> este" -#: gitk:2637 gitk:2658 msgid "Make patch" msgstr "Gerar patch" -#: gitk:2638 gitk:9273 msgid "Create tag" msgstr "Criar tag" -#: gitk:2639 msgid "Copy commit summary" msgstr "Copiar sumário do commit" -#: gitk:2640 gitk:9404 msgid "Write commit to file" msgstr "Escrever commit num ficheiro" -#: gitk:2641 gitk:9461 msgid "Create new branch" msgstr "Criar novo ramo" -#: gitk:2642 msgid "Cherry-pick this commit" msgstr "Efetuar cherry-pick deste commit" -#: gitk:2643 msgid "Reset HEAD branch to here" msgstr "Repor ramo HEAD para aqui" -#: gitk:2644 msgid "Mark this commit" msgstr "Marcar este commit" -#: gitk:2645 msgid "Return to mark" msgstr "Voltar à marca" -#: gitk:2646 msgid "Find descendant of this and mark" msgstr "Encontrar descendeste deste e da marca" -#: gitk:2647 msgid "Compare with marked commit" msgstr "Comparar com o commit marcado" -#: gitk:2648 gitk:2659 msgid "Diff this -> marked commit" msgstr "Diff este -> commit marcado" -#: gitk:2649 gitk:2660 msgid "Diff marked commit -> this" msgstr "Diff commit marcado -> este" -#: gitk:2650 msgid "Revert this commit" msgstr "Reverter este commit" -#: gitk:2666 msgid "Check out this branch" msgstr "Extrair este ramo" -#: gitk:2667 msgid "Remove this branch" msgstr "Remover este ramo" -#: gitk:2668 msgid "Copy branch name" msgstr "Copiar nome do ramo" -#: gitk:2675 msgid "Highlight this too" msgstr "Realçar este também" -#: gitk:2676 msgid "Highlight this only" msgstr "Realçar apenas este" -#: gitk:2677 msgid "External diff" msgstr "Diff externo" -#: gitk:2678 msgid "Blame parent commit" msgstr "Culpar commit pai" -#: gitk:2679 msgid "Copy path" msgstr "Copiar caminho" -#: gitk:2686 msgid "Show origin of this line" msgstr "Mostrar origem deste ficheiro" -#: gitk:2687 msgid "Run git gui blame on this line" msgstr "Executar git gui blame sobre esta linha" -#: gitk:3031 msgid "About gitk" msgstr "Sobre gitk" -#: gitk:3033 msgid "" "\n" "Gitk - a commit viewer for git\n" @@ -382,323 +293,249 @@ "\n" "Use e redistribua sob os termos da GNU General Public License" -#: gitk:3041 gitk:3108 gitk:9890 msgid "Close" msgstr "Fechar" -#: gitk:3062 msgid "Gitk key bindings" msgstr "Atalhos do gitk" -#: gitk:3065 msgid "Gitk key bindings:" msgstr "Atalhos do gitk:" -#: gitk:3067 #, tcl-format msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\tSair" -#: gitk:3068 #, tcl-format msgid "<%s-W>\t\tClose window" msgstr "<%s-W>\t\tFechar janela" -#: gitk:3069 msgid "<Home>\t\tMove to first commit" msgstr "<Home>\t\tMover para o primeiro commit" -#: gitk:3070 msgid "<End>\t\tMove to last commit" msgstr "<End>\t\tMover para o último commit" -#: gitk:3071 msgid "<Up>, p, k\tMove up one commit" msgstr "<Cima>, p, k\tMover para o commit acima" -#: gitk:3072 msgid "<Down>, n, j\tMove down one commit" msgstr "<Baixo>, n, j\tMover para o commit abaixo" -#: gitk:3073 msgid "<Left>, z, h\tGo back in history list" msgstr "<Esquerda>, z, h\tRecuar no histórico" -#: gitk:3074 msgid "<Right>, x, l\tGo forward in history list" msgstr "<Direita>, x, l\tAvançar no histórico" -#: gitk:3075 #, tcl-format msgid "<%s-n>\tGo to n-th parent of current commit in history list" msgstr "<%s-n>\tIr para o n-ésimo pai do commit atual no histórico" -#: gitk:3076 msgid "<PageUp>\tMove up one page in commit list" msgstr "<PageUp>\tMover a lista de commits uma página para cima" -#: gitk:3077 msgid "<PageDown>\tMove down one page in commit list" msgstr "<PageDown>\tMover a lista de commits uma página para baixo" -#: gitk:3078 #, tcl-format msgid "<%s-Home>\tScroll to top of commit list" msgstr "<%s-Home>\tDeslocar para o topo da lista" -#: gitk:3079 #, tcl-format msgid "<%s-End>\tScroll to bottom of commit list" msgstr "<%s-End>\tDeslocar para o fim da lista" -#: gitk:3080 #, tcl-format msgid "<%s-Up>\tScroll commit list up one line" msgstr "<%s-Cima>\tDeslocar a lista de commits uma linha para cima" -#: gitk:3081 #, tcl-format msgid "<%s-Down>\tScroll commit list down one line" msgstr "<%s-Baixo>\tDeslocar a lista de commits uma linha para baixo" -#: gitk:3082 #, tcl-format msgid "<%s-PageUp>\tScroll commit list up one page" msgstr "<%s-PageUp>\tDeslocar a lista de commits uma página para cima" -#: gitk:3083 #, tcl-format msgid "<%s-PageDown>\tScroll commit list down one page" msgstr "<%s-PageDown>\tDeslocar a lista de commits uma página para baixo" -#: gitk:3084 msgid "<Shift-Up>\tFind backwards (upwards, later commits)" msgstr "<Shift-Cima>\tProcurar para trás (para cima, commits posteriores)" -#: gitk:3085 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" msgstr "<Shift-Baixo>\tProcurar para a frente (para baixo, commits anteriores)" -#: gitk:3086 msgid "<Delete>, b\tScroll diff view up one page" msgstr "<Delete>, b\tDeslocar vista diff uma página para cima" -#: gitk:3087 msgid "<Backspace>\tScroll diff view up one page" msgstr "<Retrocesso>\tDeslocar vista diff uma página para cima" -#: gitk:3088 msgid "<Space>\t\tScroll diff view down one page" msgstr "<Espaço>\tDeslocar vista diff uma página para baixo" -#: gitk:3089 msgid "u\t\tScroll diff view up 18 lines" msgstr "u\t\tDeslocar vista diff 18 linhas para cima" -#: gitk:3090 msgid "d\t\tScroll diff view down 18 lines" msgstr "d\t\tDeslocar vista diff 18 linhas para baixo" -#: gitk:3091 #, tcl-format msgid "<%s-F>\t\tFind" msgstr "<%s-F>\t\tProcurar" -#: gitk:3092 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" msgstr "<%s-G>\t\tMover para a ocorrência seguinte" -#: gitk:3093 msgid "<Return>\tMove to next find hit" msgstr "<Return>\tMover para a ocorrência seguinte" -#: gitk:3094 msgid "g\t\tGo to commit" msgstr "g\t\tIr para o commit" -#: gitk:3095 msgid "/\t\tFocus the search box" msgstr "/\t\tFocar a caixa de pesquisa" -#: gitk:3096 msgid "?\t\tMove to previous find hit" msgstr "?\t\tMover para a ocorrência anterior" -#: gitk:3097 msgid "f\t\tScroll diff view to next file" msgstr "f\t\tDeslocar vista diff para o ficheiro seguinte" -#: gitk:3098 #, tcl-format msgid "<%s-S>\t\tSearch for next hit in diff view" msgstr "<%s-S>\t\tProcurar pela ocorrência seguinte na vista diff" -#: gitk:3099 #, tcl-format msgid "<%s-R>\t\tSearch for previous hit in diff view" msgstr "<%s-R>\t\tProcurar pela ocorrência anterior na vista diff" -#: gitk:3100 #, tcl-format msgid "<%s-KP+>\tIncrease font size" msgstr "<%s-KP+>\tAumentar o tamanho da letra" -#: gitk:3101 #, tcl-format msgid "<%s-plus>\tIncrease font size" msgstr "<%s-mais>\tAumentar o tamanho da letra" -#: gitk:3102 #, tcl-format msgid "<%s-KP->\tDecrease font size" msgstr "<%s-KP->\tDiminuir o tamanho da letra" -#: gitk:3103 #, tcl-format msgid "<%s-minus>\tDecrease font size" msgstr "<%s-menos>\tDiminuir o tamanho da letra" -#: gitk:3104 msgid "<F5>\t\tUpdate" msgstr "<F5>\t\tAtualizar" -#: gitk:3569 gitk:3578 #, tcl-format msgid "Error creating temporary directory %s:" msgstr "Erro ao criar ficheiro temporário %s:" -#: gitk:3591 #, tcl-format msgid "Error getting \"%s\" from %s:" msgstr "Erro ao obter \"%s\" de %s:" -#: gitk:3654 msgid "command failed:" msgstr "o comando falhou:" -#: gitk:3803 msgid "No such commit" msgstr "Commit inexistente" -#: gitk:3817 msgid "git gui blame: command failed:" msgstr "git gui blame: o comando falhou:" -#: gitk:3848 #, tcl-format msgid "Couldn't read merge head: %s" msgstr "Não foi possível ler a cabeça de integração: %s" -#: gitk:3856 #, tcl-format msgid "Error reading index: %s" msgstr "Erro ao ler o índice: %s" -#: gitk:3881 #, tcl-format msgid "Couldn't start git blame: %s" msgstr "Não foi possível iniciar git blame: %s" -#: gitk:3884 gitk:6773 msgid "Searching" msgstr "A procurar" -#: gitk:3916 #, tcl-format msgid "Error running git blame: %s" msgstr "Erro ao executar git blame: %s" -#: gitk:3944 #, tcl-format msgid "That line comes from commit %s, which is not in this view" msgstr "Essa linha provém do commit %s, que não está nesta vista" -#: gitk:3958 msgid "External diff viewer failed:" msgstr "Visualizador diff externo falhou:" -#: gitk:4062 msgid "All files" msgstr "Todos os ficheiros" -#: gitk:4086 msgid "View" msgstr "Vista" -#: gitk:4089 msgid "Gitk view definition" msgstr "Definição de vistas do gitk" -#: gitk:4093 msgid "Remember this view" msgstr "Recordar esta vista" -#: gitk:4094 msgid "References (space separated list):" msgstr "Referências (lista separada por espaço):" -#: gitk:4095 msgid "Branches & tags:" msgstr "Ramos e tags:" -#: gitk:4096 msgid "All refs" msgstr "Todas as referências" -#: gitk:4097 msgid "All (local) branches" msgstr "Todos os ramos (locais)" -#: gitk:4098 msgid "All tags" msgstr "Todas as tags" -#: gitk:4099 msgid "All remote-tracking branches" msgstr "Todos os ramos remotos de monitorização" -#: gitk:4100 msgid "Commit Info (regular expressions):" msgstr "Informação Sobre o Commit (expressões regulares):" -#: gitk:4101 msgid "Author:" msgstr "Autor:" -#: gitk:4102 msgid "Committer:" msgstr "Committer:" -#: gitk:4103 msgid "Commit Message:" msgstr "Mensagem de Commit:" -#: gitk:4104 msgid "Matches all Commit Info criteria" msgstr "Corresponde a todos os critérios da Informação Sobre o Commit" -#: gitk:4105 msgid "Matches no Commit Info criteria" msgstr "Não corresponde a nenhum critério da Informação Sobre o Commit" -#: gitk:4106 msgid "Changes to Files:" msgstr "Alterações nos Ficheiros:" -#: gitk:4107 msgid "Fixed String" msgstr "Cadeia Fixa" -#: gitk:4108 msgid "Regular Expression" msgstr "Expressão Regular" -#: gitk:4109 msgid "Search string:" msgstr "Procurar pela cadeia:" -#: gitk:4110 msgid "" "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" @@ -706,201 +543,153 @@ "Datas de Commit (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" -#: gitk:4111 msgid "Since:" msgstr "Desde:" -#: gitk:4112 msgid "Until:" msgstr "Até:" -#: gitk:4113 msgid "Limit and/or skip a number of revisions (positive integer):" msgstr "Limitar e/ou ignorar um número de revisões (inteiro positivo):" -#: gitk:4114 msgid "Number to show:" msgstr "Número a mostrar:" -#: gitk:4115 msgid "Number to skip:" msgstr "Número a ignorar:" -#: gitk:4116 msgid "Miscellaneous options:" msgstr "Opções diversas:" -#: gitk:4117 msgid "Strictly sort by date" msgstr "Ordenar estritamente pela data" -#: gitk:4118 msgid "Mark branch sides" msgstr "Marcar lado dos ramos" -#: gitk:4119 msgid "Limit to first parent" msgstr "Restringir ao primeiro pai" -#: gitk:4120 msgid "Simple history" msgstr "Histórico simples" -#: gitk:4121 msgid "Additional arguments to git log:" msgstr "Argumentos adicionais ao git log:" -#: gitk:4122 msgid "Enter files and directories to include, one per line:" msgstr "Introduza ficheiros e diretórios para incluir, um por linha:" -#: gitk:4123 msgid "Command to generate more commits to include:" msgstr "Comando para gerar mais commits para incluir:" -#: gitk:4247 msgid "Gitk: edit view" msgstr "Gitk: editar vista" -#: gitk:4255 msgid "-- criteria for selecting revisions" msgstr "-- critério para selecionar revisões" -#: gitk:4260 msgid "View Name" msgstr "Nome da Vista" -#: gitk:4335 msgid "Apply (F5)" msgstr "Aplicar (F5)" -#: gitk:4373 msgid "Error in commit selection arguments:" msgstr "Erro nos argumentos de seleção de commits:" -#: gitk:4428 gitk:4481 gitk:4943 gitk:4957 gitk:6227 gitk:12410 gitk:12411 msgid "None" msgstr "Nenhum" -#: gitk:5040 gitk:5045 msgid "Descendant" msgstr "Descendente" -#: gitk:5041 msgid "Not descendant" msgstr "Não descendente" -#: gitk:5048 gitk:5053 msgid "Ancestor" msgstr "Antecessor" -#: gitk:5049 msgid "Not ancestor" msgstr "Não antecessor" -#: gitk:5343 msgid "Local changes checked in to index but not committed" msgstr "Alterações locais preparadas no índice mas não submetidas" -#: gitk:5379 msgid "Local uncommitted changes, not checked in to index" msgstr "Alterações locais não submetidas, não preparadas no índice" -#: gitk:7153 msgid "and many more" msgstr "e muitos mais" -#: gitk:7156 msgid "many" msgstr "muitos" -#: gitk:7347 msgid "Tags:" msgstr "Tags:" -#: gitk:7364 gitk:7370 gitk:8844 msgid "Parent" msgstr "Pai" -#: gitk:7375 msgid "Child" msgstr "Filho" -#: gitk:7384 msgid "Branch" msgstr "Ramo" -#: gitk:7387 msgid "Follows" msgstr "Sucede" -#: gitk:7390 msgid "Precedes" msgstr "Precede" -#: gitk:7985 #, tcl-format msgid "Error getting diffs: %s" msgstr "Erro ao obter diferenças: %s" -#: gitk:8669 msgid "Goto:" msgstr "Ir para:" -#: gitk:8690 #, tcl-format msgid "Short SHA1 id %s is ambiguous" msgstr "O id SHA1 abreviado %s é ambíguo" -#: gitk:8697 #, tcl-format msgid "Revision %s is not known" msgstr "A revisão %s não é conhecida" -#: gitk:8707 #, tcl-format msgid "SHA1 id %s is not known" msgstr "O id SHA1 %s não é conhecido" -#: gitk:8709 #, tcl-format msgid "Revision %s is not in the current view" msgstr "A revisão %s não se encontra na vista atual" -#: gitk:8851 gitk:8866 msgid "Date" msgstr "Data" -#: gitk:8854 msgid "Children" msgstr "Filhos" -#: gitk:8917 #, tcl-format msgid "Reset %s branch to here" msgstr "Repor o ramo %s para aqui" -#: gitk:8919 msgid "Detached head: can't reset" msgstr "Cabeça destacada: não é possível repor" -#: gitk:9024 gitk:9030 msgid "Skipping merge commit " msgstr "A ignorar commit de integração " -#: gitk:9039 gitk:9044 msgid "Error getting patch ID for " msgstr "Erro ao obter ID de patch de " -#: gitk:9040 gitk:9045 msgid " - stopping\n" msgstr " - a interromper\n" -#: gitk:9050 gitk:9053 gitk:9061 gitk:9075 gitk:9084 msgid "Commit " msgstr "Commit " -#: gitk:9054 msgid "" " is the same patch as\n" " " @@ -908,7 +697,6 @@ " é o mesmo patch que\n" " " -#: gitk:9062 msgid "" " differs from\n" " " @@ -916,7 +704,6 @@ " difere de\n" " " -#: gitk:9064 msgid "" "Diff of commits:\n" "\n" @@ -924,129 +711,99 @@ "Diferença dos commits:\n" "\n" -#: gitk:9076 gitk:9085 #, tcl-format msgid " has %s children - stopping\n" msgstr " tem %s filhos - a interromper\n" -#: gitk:9104 #, tcl-format msgid "Error writing commit to file: %s" msgstr "Erro ao escrever commit no ficheiro: %s" -#: gitk:9110 #, tcl-format msgid "Error diffing commits: %s" msgstr "Erro ao calcular as diferenças dos commits: %s" -#: gitk:9156 msgid "Top" msgstr "Topo" -#: gitk:9157 msgid "From" msgstr "De" -#: gitk:9162 msgid "To" msgstr "Para" -#: gitk:9186 msgid "Generate patch" msgstr "Gerar patch" -#: gitk:9188 msgid "From:" msgstr "De:" -#: gitk:9197 msgid "To:" msgstr "Para:" -#: gitk:9206 msgid "Reverse" msgstr "Reverter" -#: gitk:9208 gitk:9418 msgid "Output file:" msgstr "Ficheiro de saída:" -#: gitk:9214 msgid "Generate" msgstr "Gerar" -#: gitk:9252 msgid "Error creating patch:" msgstr "Erro ao criar patch:" -#: gitk:9275 gitk:9406 gitk:9463 msgid "ID:" msgstr "ID:" -#: gitk:9284 msgid "Tag name:" msgstr "Nome da tag:" -#: gitk:9287 msgid "Tag message is optional" msgstr "A mensagem da tag é opcional" -#: gitk:9289 msgid "Tag message:" msgstr "Mensagem da tag:" -#: gitk:9293 gitk:9472 msgid "Create" msgstr "Criar" -#: gitk:9311 msgid "No tag name specified" msgstr "Nenhum nome de tag especificado" -#: gitk:9315 #, tcl-format msgid "Tag \"%s\" already exists" msgstr "A tag \"%s\" já existe" -#: gitk:9325 msgid "Error creating tag:" msgstr "Erro ao criar tag:" -#: gitk:9415 msgid "Command:" msgstr "Comando:" -#: gitk:9423 msgid "Write" msgstr "Escrever" -#: gitk:9441 msgid "Error writing commit:" msgstr "Erro ao escrever commit:" -#: gitk:9468 msgid "Name:" msgstr "Nome:" -#: gitk:9491 msgid "Please specify a name for the new branch" msgstr "Especifique um nome para o novo ramo" -#: gitk:9496 #, tcl-format msgid "Branch '%s' already exists. Overwrite?" msgstr "O ramo '%s' já existe. Substituí-lo?" -#: gitk:9563 #, tcl-format msgid "Commit %s is already included in branch %s -- really re-apply it?" msgstr "O commit %s já está incluído no ramo %s -- reaplicá-lo mesmo assim?" -#: gitk:9568 msgid "Cherry-picking" msgstr "A efetuar cherry-pick" -#: gitk:9577 #, tcl-format msgid "" "Cherry-pick failed because of local changes to file '%s'.\n" @@ -1055,7 +812,6 @@ "Falha ao efetuar cherry-pick devido a alterações locais no ficheiro '%s'.\n" "Submeta, empilhe ou reponha as alterações e tente de novo." -#: gitk:9583 msgid "" "Cherry-pick failed because of merge conflict.\n" "Do you wish to run git citool to resolve it?" @@ -1063,20 +819,16 @@ "Falha ao efetuar cherry-pick devido a conflito de integração.\n" "Deseja executar git citool para resolvê-lo?" -#: gitk:9599 gitk:9657 msgid "No changes committed" msgstr "Não foi submetida nenhum alteração" -#: gitk:9626 #, tcl-format msgid "Commit %s is not included in branch %s -- really revert it?" msgstr "O commit %s não está incluído no ramo %s -- revertê-lo mesmo assim?" -#: gitk:9631 msgid "Reverting" msgstr "A reverter" -#: gitk:9639 #, tcl-format msgid "" "Revert failed because of local changes to the following files:%s Please " @@ -1085,7 +837,6 @@ "Falha ao reverter devido a alterações locais nos seguintes ficheiros:%s " "Submeta, empilhe ou reponha as alterações e tente de novo." -#: gitk:9643 msgid "" "Revert failed because of merge conflict.\n" " Do you wish to run git citool to resolve it?" @@ -1093,28 +844,22 @@ "Falha ao reverter devido a conflito de integração.\n" "Deseja executar git citool para resolvê-lo?" -#: gitk:9686 msgid "Confirm reset" msgstr "Confirmar reposição" -#: gitk:9688 #, tcl-format msgid "Reset branch %s to %s?" msgstr "Repor o ramo %s para %s?" -#: gitk:9690 msgid "Reset type:" msgstr "Tipo de reposição:" -#: gitk:9693 msgid "Soft: Leave working tree and index untouched" msgstr "Suave: Deixar a árvore de trabalho e o índice intactos" -#: gitk:9696 msgid "Mixed: Leave working tree untouched, reset index" msgstr "Misto: Deixar a árvore de trabalho intacta, repor índice" -#: gitk:9699 msgid "" "Hard: Reset working tree and index\n" "(discard ALL local changes)" @@ -1122,19 +867,15 @@ "Forte: Repor árvore de trabalho e índice\n" "(descartar TODAS as alterações locais)" -#: gitk:9716 msgid "Resetting" msgstr "A repor" -#: gitk:9776 msgid "Checking out" msgstr "A extrair" -#: gitk:9829 msgid "Cannot delete the currently checked-out branch" msgstr "Não é possível eliminar o ramo atual extraído" -#: gitk:9835 #, tcl-format msgid "" "The commits on branch %s aren't on any other branch.\n" @@ -1143,16 +884,13 @@ "Os commits no ramo %s não estão presentes em mais nenhum ramo.\n" "Eliminar o ramo %s mesmo assim?" -#: gitk:9866 #, tcl-format msgid "Tags and heads: %s" msgstr "Tags e cabeças: %s" -#: gitk:9883 msgid "Filter" msgstr "Filtrar" -#: gitk:10179 msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." @@ -1160,201 +898,152 @@ "Erro ao ler informação de topologia do commit; a informação do ramo e da tag " "precedente/seguinte ficará incompleta." -#: gitk:11156 msgid "Tag" msgstr "Tag" -#: gitk:11160 msgid "Id" msgstr "Id" -#: gitk:11243 msgid "Gitk font chooser" msgstr "Escolha de tipo de letra do gitk" -#: gitk:11260 msgid "B" msgstr "B" -#: gitk:11263 msgid "I" msgstr "I" -#: gitk:11381 msgid "Commit list display options" msgstr "Opções de visualização da lista de commits" -#: gitk:11384 msgid "Maximum graph width (lines)" msgstr "Largura máxima do gráfico (linhas)" -#: gitk:11388 #, no-tcl-format msgid "Maximum graph width (% of pane)" msgstr "Largura máxima do gráfico (% do painel)" -#: gitk:11391 msgid "Show local changes" msgstr "Mostrar alterações locais" -#: gitk:11394 msgid "Auto-select SHA1 (length)" msgstr "Selecionar automaticamente SHA1 (largura)" -#: gitk:11398 msgid "Hide remote refs" msgstr "Ocultar referências remotas" -#: gitk:11402 msgid "Diff display options" msgstr "Opções de visualização de diferenças" -#: gitk:11404 msgid "Tab spacing" msgstr "Espaçamento da tabulação" -#: gitk:11407 msgid "Display nearby tags/heads" msgstr "Mostrar tags/cabeças próximas" -#: gitk:11410 msgid "Maximum # tags/heads to show" msgstr "Nº máximo de tags/cabeças a mostrar" -#: gitk:11413 msgid "Limit diffs to listed paths" msgstr "Limitar diferenças aos caminhos listados" -#: gitk:11416 msgid "Support per-file encodings" msgstr "Suportar codificação por cada ficheiro" -#: gitk:11422 gitk:11569 msgid "External diff tool" msgstr "Ferramenta diff externa" -#: gitk:11423 msgid "Choose..." msgstr "Escolher..." -#: gitk:11428 msgid "General options" msgstr "Opções gerais" -#: gitk:11431 msgid "Use themed widgets" msgstr "Usar widgets com estilo" -#: gitk:11433 msgid "(change requires restart)" msgstr "(alteração exige reiniciar)" -#: gitk:11435 msgid "(currently unavailable)" msgstr "(não disponível de momento)" -#: gitk:11446 msgid "Colors: press to choose" msgstr "Cores: pressione para escolher" -#: gitk:11449 msgid "Interface" msgstr "Interface" -#: gitk:11450 msgid "interface" msgstr "interface" -#: gitk:11453 msgid "Background" msgstr "Fundo" -#: gitk:11454 gitk:11484 msgid "background" msgstr "fundo" -#: gitk:11457 msgid "Foreground" msgstr "Primeiro plano" -#: gitk:11458 msgid "foreground" msgstr "primeiro plano" -#: gitk:11461 msgid "Diff: old lines" msgstr "Diff: linhas antigas" -#: gitk:11462 msgid "diff old lines" msgstr "diff linhas antigas" -#: gitk:11466 msgid "Diff: new lines" msgstr "Diff: linhas novas" -#: gitk:11467 msgid "diff new lines" msgstr "diff linhas novas" -#: gitk:11471 msgid "Diff: hunk header" msgstr "Diff: cabeçalho do excerto" -#: gitk:11473 msgid "diff hunk header" msgstr "diff cabeçalho do excerto" -#: gitk:11477 msgid "Marked line bg" msgstr "Fundo da linha marcada" -#: gitk:11479 msgid "marked line background" msgstr "fundo da linha marcada" -#: gitk:11483 msgid "Select bg" msgstr "Selecionar fundo" -#: gitk:11492 msgid "Fonts: press to choose" msgstr "Tipo de letra: pressione para escolher" -#: gitk:11494 msgid "Main font" msgstr "Tipo de letra principal" -#: gitk:11495 msgid "Diff display font" msgstr "Tipo de letra ao mostrar diferenças" -#: gitk:11496 msgid "User interface font" msgstr "Tipo de letra da interface de utilizador" -#: gitk:11518 msgid "Gitk preferences" msgstr "Preferências do gitk" -#: gitk:11527 msgid "General" msgstr "Geral" -#: gitk:11528 msgid "Colors" msgstr "Cores" -#: gitk:11529 msgid "Fonts" msgstr "Tipos de letra" -#: gitk:11579 #, tcl-format msgid "Gitk: choose color for %s" msgstr "Gitk: escolher cor de %s" -#: gitk:12092 msgid "" "Sorry, gitk cannot run with this version of Tcl/Tk.\n" " Gitk requires at least Tcl/Tk 8.4." @@ -1362,15 +1051,12 @@ "Não é possível executar o gitk com esta versão do Tcl/Tk.\n" "O gitk requer pelo menos Tcl/Tk 8.4." -#: gitk:12302 msgid "Cannot find a git repository here." msgstr "Não foi encontrado nenhum repositório git aqui." -#: gitk:12349 #, tcl-format msgid "Ambiguous argument '%s': both revision and filename" msgstr "Argumento '%s' ambíguo: pode ser uma revisão ou um ficheiro" -#: gitk:12361 msgid "Bad arguments to gitk:" msgstr "Argumentos do gitk incorretos:"
diff --git a/gitk-git/po/ru.po b/gitk-git/po/ru.po index 9b08c26..e102fb7 100644 --- a/gitk-git/po/ru.po +++ b/gitk-git/po/ru.po
@@ -8,368 +8,285 @@ # Skip <bsvskip@rambler.ru>, 2011 msgid "" msgstr "" -"Project-Id-Version: Git Russian Localization Project\n" +"Project-Id-Version: Gitk Russian Localization Project\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-12-15 00:18+0200\n" "PO-Revision-Date: 2016-12-14 22:23+0000\n" "Last-Translator: Dimitriy Ryazantcev <DJm00n@mail.ru>\n" -"Language-Team: Russian (http://www.transifex.com/djm00n/git-po-ru/language/ru/)\n" +"Language-Team: Russian (http://www.transifex.com/djm00n/git-po-ru/language/" +"ru/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: ru\n" -"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" +"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || " +"(n%100>=11 && n%100<=14)? 2 : 3);\n" -#: gitk:140 msgid "Couldn't get list of unmerged files:" msgstr "Невозможно получить список файлов незавершённой операции слияния:" -#: gitk:212 gitk:2403 msgid "Color words" msgstr "Цветные слова" -#: gitk:217 gitk:2403 gitk:8249 gitk:8282 msgid "Markup words" msgstr "Помеченые слова" -#: gitk:324 msgid "Error parsing revisions:" msgstr "Ошибка при разборе редакции:" -#: gitk:380 msgid "Error executing --argscmd command:" msgstr "Ошибка выполнения команды заданной --argscmd:" -#: gitk:393 msgid "No files selected: --merge specified but no files are unmerged." -msgstr "Файлы не выбраны: указан --merge, но не было найдено ни одного файла где эта операция должна быть завершена." +msgstr "" +"Файлы не выбраны: указан --merge, но не было найдено ни одного файла где эта " +"операция должна быть завершена." -#: gitk:396 msgid "" "No files selected: --merge specified but no unmerged files are within file " "limit." -msgstr "Файлы не выбраны: указан --merge, но в рамках указанного ограничения на имена файлов нет ни одного где эта операция должна быть завершена." +msgstr "" +"Файлы не выбраны: указан --merge, но в рамках указанного ограничения на " +"имена файлов нет ни одного где эта операция должна быть завершена." -#: gitk:418 gitk:566 msgid "Error executing git log:" msgstr "Ошибка запуска git log:" -#: gitk:436 gitk:582 msgid "Reading" msgstr "Чтение" -#: gitk:496 gitk:4549 msgid "Reading commits..." msgstr "Чтение коммитов..." -#: gitk:499 gitk:1641 gitk:4552 msgid "No commits selected" msgstr "Ничего не выбрано" -#: gitk:1449 gitk:4069 gitk:12583 msgid "Command line" msgstr "Командная строка" -#: gitk:1515 msgid "Can't parse git log output:" msgstr "Ошибка обработки вывода команды git log:" -#: gitk:1744 msgid "No commit information available" msgstr "Нет информации о коммите" -#: gitk:1907 gitk:1936 gitk:4339 gitk:9789 gitk:11388 gitk:11668 msgid "OK" msgstr "Ok" -#: gitk:1938 gitk:4341 gitk:9225 gitk:9304 gitk:9434 gitk:9520 gitk:9791 -#: gitk:11389 gitk:11669 msgid "Cancel" msgstr "Отмена" -#: gitk:2087 msgid "&Update" msgstr "Обновить" -#: gitk:2088 msgid "&Reload" msgstr "Перечитать" -#: gitk:2089 msgid "Reread re&ferences" msgstr "Обновить список ссылок" -#: gitk:2090 msgid "&List references" msgstr "Список ссылок" -#: gitk:2092 msgid "Start git &gui" msgstr "Запустить git gui" -#: gitk:2094 msgid "&Quit" msgstr "Завершить" -#: gitk:2086 msgid "&File" msgstr "Файл" -#: gitk:2098 msgid "&Preferences" msgstr "Настройки" -#: gitk:2097 msgid "&Edit" msgstr "Редактировать" -#: gitk:2102 msgid "&New view..." msgstr "Новое представление..." -#: gitk:2103 msgid "&Edit view..." msgstr "Редактировать представление..." -#: gitk:2104 msgid "&Delete view" msgstr "Удалить представление" -#: gitk:2106 msgid "&All files" msgstr "Все файлы" -#: gitk:2101 msgid "&View" msgstr "Представление" -#: gitk:2111 gitk:2121 msgid "&About gitk" msgstr "О gitk" -#: gitk:2112 gitk:2126 msgid "&Key bindings" msgstr "Назначения клавиатуры" -#: gitk:2110 gitk:2125 msgid "&Help" msgstr "Подсказка" -#: gitk:2203 gitk:8681 msgid "SHA1 ID:" msgstr "SHA1 ID:" -#: gitk:2247 msgid "Row" msgstr "Строка" -#: gitk:2285 msgid "Find" msgstr "Поиск" -#: gitk:2313 msgid "commit" msgstr "коммит" -#: gitk:2317 gitk:2319 gitk:4711 gitk:4734 gitk:4758 gitk:6779 gitk:6851 -#: gitk:6936 msgid "containing:" msgstr "содержащее:" -#: gitk:2320 gitk:3550 gitk:3555 gitk:4787 msgid "touching paths:" msgstr "касательно файлов:" -#: gitk:2321 gitk:4801 msgid "adding/removing string:" msgstr "добавив/удалив строку:" -#: gitk:2322 gitk:4803 msgid "changing lines matching:" msgstr "изменяя совпадающие строки:" -#: gitk:2331 gitk:2333 gitk:4790 msgid "Exact" msgstr "Точно" -#: gitk:2333 gitk:4878 gitk:6747 msgid "IgnCase" msgstr "Игнорировать большие/маленькие" -#: gitk:2333 gitk:4760 gitk:4876 gitk:6743 msgid "Regexp" msgstr "Регулярные выражения" -#: gitk:2335 gitk:2336 gitk:4898 gitk:4928 gitk:4935 gitk:6872 gitk:6940 msgid "All fields" msgstr "Во всех полях" -#: gitk:2336 gitk:4895 gitk:4928 gitk:6810 msgid "Headline" msgstr "Заголовок" -#: gitk:2337 gitk:4895 gitk:6810 gitk:6940 gitk:7413 msgid "Comments" msgstr "Комментарии" -#: gitk:2337 gitk:4895 gitk:4900 gitk:4935 gitk:6810 gitk:7348 gitk:8859 -#: gitk:8874 msgid "Author" msgstr "Автор" -#: gitk:2337 gitk:4895 gitk:6810 gitk:7350 msgid "Committer" msgstr "Коммитер" -#: gitk:2371 msgid "Search" msgstr "Найти" -#: gitk:2379 msgid "Diff" msgstr "Сравнить" -#: gitk:2381 msgid "Old version" msgstr "Старая версия" -#: gitk:2383 msgid "New version" msgstr "Новая версия" -#: gitk:2386 msgid "Lines of context" msgstr "Строк контекста" -#: gitk:2396 msgid "Ignore space change" msgstr "Игнорировать пробелы" -#: gitk:2400 gitk:2402 gitk:7983 gitk:8235 msgid "Line diff" msgstr "Изменения строк" -#: gitk:2467 msgid "Patch" msgstr "Патч" -#: gitk:2469 msgid "Tree" msgstr "Файлы" -#: gitk:2639 gitk:2660 msgid "Diff this -> selected" msgstr "Сравнить этот коммит с выделенным" -#: gitk:2640 gitk:2661 msgid "Diff selected -> this" msgstr "Сравнить выделенный с этим коммитом" -#: gitk:2641 gitk:2662 msgid "Make patch" msgstr "Создать патч" -#: gitk:2642 gitk:9283 msgid "Create tag" msgstr "Создать метку" -#: gitk:2643 msgid "Copy commit summary" msgstr "Копировать информацию о коммите" -#: gitk:2644 gitk:9414 msgid "Write commit to file" msgstr "Сохранить коммит в файл" -#: gitk:2645 msgid "Create new branch" msgstr "Создать ветку" -#: gitk:2646 msgid "Cherry-pick this commit" msgstr "Копировать этот коммит в текущую ветку" -#: gitk:2647 msgid "Reset HEAD branch to here" msgstr "Установить HEAD на этот коммит" -#: gitk:2648 msgid "Mark this commit" msgstr "Пометить этот коммит" -#: gitk:2649 msgid "Return to mark" msgstr "Вернуться на пометку" -#: gitk:2650 msgid "Find descendant of this and mark" msgstr "Найти и пометить потомка этого коммита" -#: gitk:2651 msgid "Compare with marked commit" msgstr "Сравнить с помеченным коммитом" -#: gitk:2652 gitk:2663 msgid "Diff this -> marked commit" msgstr "Сравнить выделенное с помеченным коммитом" -#: gitk:2653 gitk:2664 msgid "Diff marked commit -> this" msgstr "Сравнить помеченный с этим коммитом" -#: gitk:2654 msgid "Revert this commit" msgstr "Обратить изменения этого коммита" -#: gitk:2670 msgid "Check out this branch" msgstr "Перейти на эту ветку" -#: gitk:2671 msgid "Rename this branch" msgstr "Переименовать эту ветку" -#: gitk:2672 msgid "Remove this branch" msgstr "Удалить эту ветку" -#: gitk:2673 msgid "Copy branch name" msgstr "Копировать имя ветки" -#: gitk:2680 msgid "Highlight this too" msgstr "Подсветить этот тоже" -#: gitk:2681 msgid "Highlight this only" msgstr "Подсветить только этот" -#: gitk:2682 msgid "External diff" msgstr "Программа сравнения" -#: gitk:2683 msgid "Blame parent commit" msgstr "Авторы изменений родительского коммита" -#: gitk:2684 msgid "Copy path" msgstr "Копировать путь" -#: gitk:2691 msgid "Show origin of this line" msgstr "Показать источник этой строки" -#: gitk:2692 msgid "Run git gui blame on this line" msgstr "Запустить git gui blame для этой строки" -#: gitk:3036 msgid "About gitk" msgstr "О gitk" -#: gitk:3038 msgid "" "\n" "Gitk - a commit viewer for git\n" @@ -377,995 +294,797 @@ "Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" -msgstr "\nGitk — программа просмотра истории репозиториев git\n\n© 2005-2016 Paul Mackerras\n\nИспользование и распространение согласно условиям GNU General Public License" +msgstr "" +"\n" +"Gitk — программа просмотра истории репозиториев git\n" +"\n" +"© 2005-2016 Paul Mackerras\n" +"\n" +"Использование и распространение согласно условиям GNU General Public License" -#: gitk:3046 gitk:3113 gitk:10004 msgid "Close" msgstr "Закрыть" -#: gitk:3067 msgid "Gitk key bindings" msgstr "Назначения клавиатуры в Gitk" -#: gitk:3070 msgid "Gitk key bindings:" msgstr "Назначения клавиатуры в Gitk:" -#: gitk:3072 #, tcl-format msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\tЗавершить" -#: gitk:3073 #, tcl-format msgid "<%s-W>\t\tClose window" msgstr "<%s-W>\t\tЗакрыть окно" -#: gitk:3074 msgid "<Home>\t\tMove to first commit" msgstr "<Home>\t\tПерейти к первому коммиту" -#: gitk:3075 msgid "<End>\t\tMove to last commit" msgstr "<End>\t\tПерейти к последнему коммиту" -#: gitk:3076 msgid "<Up>, p, k\tMove up one commit" msgstr "<Up>, p, k\tПерейти на один коммит вверх" -#: gitk:3077 msgid "<Down>, n, j\tMove down one commit" msgstr "<Down>, n, j\tПерейти на один коммит вниз" -#: gitk:3078 msgid "<Left>, z, h\tGo back in history list" msgstr "<Left>, z, h\tПоказать ранее посещённое состояние" -#: gitk:3079 msgid "<Right>, x, l\tGo forward in history list" msgstr "<Right>, x, l\tПоказать следующий посещённый коммит" -#: gitk:3080 #, tcl-format msgid "<%s-n>\tGo to n-th parent of current commit in history list" msgstr "<%s-n>\tПерейти на n родителя от текущего коммита" -#: gitk:3081 msgid "<PageUp>\tMove up one page in commit list" msgstr "<PageUp>\tПерейти на страницу выше в списке коммитов" -#: gitk:3082 msgid "<PageDown>\tMove down one page in commit list" msgstr "<PageDown>\tПерейти на страницу ниже в списке коммитов" -#: gitk:3083 #, tcl-format msgid "<%s-Home>\tScroll to top of commit list" msgstr "<%s-Home>\tПерейти на начало списка коммитов" -#: gitk:3084 #, tcl-format msgid "<%s-End>\tScroll to bottom of commit list" msgstr "<%s-End>\tПерейти на конец списка коммитов" -#: gitk:3085 #, tcl-format msgid "<%s-Up>\tScroll commit list up one line" msgstr "<%s-Up>\tПровернуть список коммитов вверх" -#: gitk:3086 #, tcl-format msgid "<%s-Down>\tScroll commit list down one line" msgstr "<%s-Down>\tПровернуть список коммитов вниз" -#: gitk:3087 #, tcl-format msgid "<%s-PageUp>\tScroll commit list up one page" msgstr "<%s-PageUp>\tПровернуть список коммитов на страницу вверх" -#: gitk:3088 #, tcl-format msgid "<%s-PageDown>\tScroll commit list down one page" msgstr "<%s-PageDown>\tПровернуть список коммитов на страницу вниз" -#: gitk:3089 msgid "<Shift-Up>\tFind backwards (upwards, later commits)" msgstr "<Shift-Up>\tПоиск в обратном порядке (вверх, среди новых коммитов)" -#: gitk:3090 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" msgstr "<Shift-Down>\tПоиск (вниз, среди старых коммитов)" -#: gitk:3091 msgid "<Delete>, b\tScroll diff view up one page" msgstr "<Delete>, b\tПрокрутить список изменений на страницу выше" -#: gitk:3092 msgid "<Backspace>\tScroll diff view up one page" msgstr "<Backspace>\tПрокрутить список изменений на страницу выше" -#: gitk:3093 msgid "<Space>\t\tScroll diff view down one page" msgstr "<Leertaste>\t\tПрокрутить список изменений на страницу ниже" -#: gitk:3094 msgid "u\t\tScroll diff view up 18 lines" msgstr "u\t\tПрокрутить список изменений на 18 строк вверх" -#: gitk:3095 msgid "d\t\tScroll diff view down 18 lines" msgstr "d\t\tПрокрутить список изменений на 18 строк вниз" -#: gitk:3096 #, tcl-format msgid "<%s-F>\t\tFind" msgstr "<%s-F>\t\tПоиск" -#: gitk:3097 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" msgstr "<%s-G>\t\tПерейти к следующему найденному коммиту" -#: gitk:3098 msgid "<Return>\tMove to next find hit" msgstr "<Return>\tПерейти к следующему найденному коммиту" -#: gitk:3099 msgid "g\t\tGo to commit" msgstr "g\t\tПерейти на коммит" -#: gitk:3100 msgid "/\t\tFocus the search box" msgstr "/\t\tПерейти к полю поиска" -#: gitk:3101 msgid "?\t\tMove to previous find hit" msgstr "?\t\tПерейти к предыдущему найденному коммиту" -#: gitk:3102 msgid "f\t\tScroll diff view to next file" msgstr "f\t\tПрокрутить список изменений к следующему файлу" -#: gitk:3103 #, tcl-format msgid "<%s-S>\t\tSearch for next hit in diff view" msgstr "<%s-S>\t\tПродолжить поиск в списке изменений" -#: gitk:3104 #, tcl-format msgid "<%s-R>\t\tSearch for previous hit in diff view" msgstr "<%s-R>\t\tПерейти к предыдущему найденному тексту в списке изменений" -#: gitk:3105 #, tcl-format msgid "<%s-KP+>\tIncrease font size" msgstr "<%s-KP+>\tУвеличить размер шрифта" -#: gitk:3106 #, tcl-format msgid "<%s-plus>\tIncrease font size" msgstr "<%s-plus>\tУвеличить размер шрифта" -#: gitk:3107 #, tcl-format msgid "<%s-KP->\tDecrease font size" msgstr "<%s-KP->\tУменьшить размер шрифта" -#: gitk:3108 #, tcl-format msgid "<%s-minus>\tDecrease font size" msgstr "<%s-minus>\tУменьшить размер шрифта" -#: gitk:3109 msgid "<F5>\t\tUpdate" msgstr "<F5>\t\tОбновить" -#: gitk:3574 gitk:3583 #, tcl-format msgid "Error creating temporary directory %s:" msgstr "Ошибка создания временного каталога %s:" -#: gitk:3596 #, tcl-format msgid "Error getting \"%s\" from %s:" msgstr "Ошибка получения «%s» из %s:" -#: gitk:3659 msgid "command failed:" msgstr "ошибка выполнения команды:" -#: gitk:3808 msgid "No such commit" msgstr "Коммит не найден" -#: gitk:3822 msgid "git gui blame: command failed:" msgstr "git gui blame: ошибка выполнения команды:" -#: gitk:3853 #, tcl-format msgid "Couldn't read merge head: %s" msgstr "Ошибка чтения MERGE_HEAD: %s" -#: gitk:3861 #, tcl-format msgid "Error reading index: %s" msgstr "Ошибка чтения индекса: %s" -#: gitk:3886 #, tcl-format msgid "Couldn't start git blame: %s" msgstr "Ошибка запуска git blame: %s" -#: gitk:3889 gitk:6778 msgid "Searching" msgstr "Поиск" -#: gitk:3921 #, tcl-format msgid "Error running git blame: %s" msgstr "Ошибка выполнения git blame: %s" -#: gitk:3949 #, tcl-format msgid "That line comes from commit %s, which is not in this view" -msgstr "Эта строка принадлежит коммиту %s, который не показан в этом представлении" +msgstr "" +"Эта строка принадлежит коммиту %s, который не показан в этом представлении" -#: gitk:3963 msgid "External diff viewer failed:" msgstr "Ошибка выполнения программы сравнения:" -#: gitk:4067 msgid "All files" msgstr "Все файлы" -#: gitk:4091 msgid "View" msgstr "Представление" -#: gitk:4094 msgid "Gitk view definition" msgstr "Gitk определение представлений" -#: gitk:4098 msgid "Remember this view" msgstr "Запомнить представление" -#: gitk:4099 msgid "References (space separated list):" msgstr "Ссылки (разделённые пробелом):" -#: gitk:4100 msgid "Branches & tags:" msgstr "Ветки и метки" -#: gitk:4101 msgid "All refs" msgstr "Все ссылки" -#: gitk:4102 msgid "All (local) branches" msgstr "Все (локальные) ветки" -#: gitk:4103 msgid "All tags" msgstr "Все метки" -#: gitk:4104 msgid "All remote-tracking branches" msgstr "Все внешние отслеживаемые ветки" -#: gitk:4105 msgid "Commit Info (regular expressions):" msgstr "Информация о коммите (регулярные выражения):" -#: gitk:4106 msgid "Author:" msgstr "Автор:" -#: gitk:4107 msgid "Committer:" msgstr "Коммитер:" -#: gitk:4108 msgid "Commit Message:" msgstr "Сообщение коммита:" -#: gitk:4109 msgid "Matches all Commit Info criteria" msgstr "Совпадает со всеми условиями информации о коммите" -#: gitk:4110 msgid "Matches no Commit Info criteria" msgstr "Не совпадает с условиями информации о коммите" -#: gitk:4111 msgid "Changes to Files:" msgstr "Изменения файлов:" -#: gitk:4112 msgid "Fixed String" msgstr "Обычная строка" -#: gitk:4113 msgid "Regular Expression" msgstr "Регулярное выражение:" -#: gitk:4114 msgid "Search string:" msgstr "Строка для поиска:" -#: gitk:4115 msgid "" "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" -msgstr "Даты коммита («2 недели назад», «2009-03-17 15:27:38», «17 марта 2009 15:27:38»):" +msgstr "" +"Даты коммита («2 недели назад», «2009-03-17 15:27:38», «17 марта 2009 " +"15:27:38»):" -#: gitk:4116 msgid "Since:" msgstr "С даты:" -#: gitk:4117 msgid "Until:" msgstr "По дату:" -#: gitk:4118 msgid "Limit and/or skip a number of revisions (positive integer):" msgstr "Ограничить и/или пропустить количество редакций (положительное число):" -#: gitk:4119 msgid "Number to show:" msgstr "Показать количество:" -#: gitk:4120 msgid "Number to skip:" msgstr "Пропустить количество:" -#: gitk:4121 msgid "Miscellaneous options:" msgstr "Различные опции:" -#: gitk:4122 msgid "Strictly sort by date" msgstr "Строгая сортировка по дате" -#: gitk:4123 msgid "Mark branch sides" msgstr "Отметить стороны веток" -#: gitk:4124 msgid "Limit to first parent" msgstr "Ограничить первым предком" -#: gitk:4125 msgid "Simple history" msgstr "Упрощенная история" -#: gitk:4126 msgid "Additional arguments to git log:" msgstr "Дополнительные аргументы для git log:" -#: gitk:4127 msgid "Enter files and directories to include, one per line:" msgstr "Файлы и каталоги для ограничения истории, по одному на строку:" -#: gitk:4128 msgid "Command to generate more commits to include:" msgstr "Дополнительная команда для списка коммитов:" -#: gitk:4252 msgid "Gitk: edit view" msgstr "Gitk: изменить представление" -#: gitk:4260 msgid "-- criteria for selecting revisions" msgstr "— критерий поиска редакций" -#: gitk:4265 msgid "View Name" msgstr "Имя представления" -#: gitk:4340 msgid "Apply (F5)" msgstr "Применить (F5)" -#: gitk:4378 msgid "Error in commit selection arguments:" msgstr "Ошибка в параметрах выбора коммитов:" -#: gitk:4433 gitk:4486 gitk:4948 gitk:4962 gitk:6232 gitk:12524 gitk:12525 msgid "None" msgstr "Ни одного" -#: gitk:5045 gitk:5050 msgid "Descendant" msgstr "Порождённое" -#: gitk:5046 msgid "Not descendant" msgstr "Не порождённое" -#: gitk:5053 gitk:5058 msgid "Ancestor" msgstr "Предок" -#: gitk:5054 msgid "Not ancestor" msgstr "Не предок" -#: gitk:5348 msgid "Local changes checked in to index but not committed" msgstr "Проиндексированные изменения" -#: gitk:5384 msgid "Local uncommitted changes, not checked in to index" msgstr "Непроиндексированные изменения" -#: gitk:7158 msgid "and many more" msgstr "и многое другое" -#: gitk:7161 msgid "many" msgstr "много" -#: gitk:7352 msgid "Tags:" msgstr "Метки:" -#: gitk:7369 gitk:7375 gitk:8854 msgid "Parent" msgstr "Предок" -#: gitk:7380 msgid "Child" msgstr "Потомок" -#: gitk:7389 msgid "Branch" msgstr "Ветка" -#: gitk:7392 msgid "Follows" msgstr "Следует за" -#: gitk:7395 msgid "Precedes" msgstr "Предшествует" -#: gitk:7990 #, tcl-format msgid "Error getting diffs: %s" msgstr "Ошибка получения изменений: %s" -#: gitk:8679 msgid "Goto:" msgstr "Перейти к:" -#: gitk:8700 #, tcl-format msgid "Short SHA1 id %s is ambiguous" msgstr "Сокращённый SHA1 идентификатор %s неоднозначен" -#: gitk:8707 #, tcl-format msgid "Revision %s is not known" msgstr "Редакция %s не найдена" -#: gitk:8717 #, tcl-format msgid "SHA1 id %s is not known" msgstr "SHA1 идентификатор %s не найден" -#: gitk:8719 #, tcl-format msgid "Revision %s is not in the current view" msgstr "Редакция %s не найдена в текущем представлении" -#: gitk:8861 gitk:8876 msgid "Date" msgstr "Дата" -#: gitk:8864 msgid "Children" msgstr "Потомки" -#: gitk:8927 #, tcl-format msgid "Reset %s branch to here" msgstr "Сбросить ветку %s на этот коммит" -#: gitk:8929 msgid "Detached head: can't reset" msgstr "Коммит не принадлежит ни одной ветке, сбросить невозможно" -#: gitk:9034 gitk:9040 msgid "Skipping merge commit " msgstr "Пропускаю коммит-слияние" -#: gitk:9049 gitk:9054 msgid "Error getting patch ID for " msgstr "Не удалось получить идентификатор патча для " -#: gitk:9050 gitk:9055 msgid " - stopping\n" msgstr " — останов\n" -#: gitk:9060 gitk:9063 gitk:9071 gitk:9085 gitk:9094 msgid "Commit " msgstr "Коммит" -#: gitk:9064 msgid "" " is the same patch as\n" " " -msgstr " такой же патч, как и\n " +msgstr "" +" такой же патч, как и\n" +" " -#: gitk:9072 msgid "" " differs from\n" " " -msgstr " отличается от\n " +msgstr "" +" отличается от\n" +" " -#: gitk:9074 msgid "" "Diff of commits:\n" "\n" -msgstr "Различия коммитов:\n\n" +msgstr "" +"Различия коммитов:\n" +"\n" -#: gitk:9086 gitk:9095 #, tcl-format msgid " has %s children - stopping\n" msgstr " является %s потомком — останов\n" -#: gitk:9114 #, tcl-format msgid "Error writing commit to file: %s" msgstr "Произошла ошибка при записи коммита в файл: %s" -#: gitk:9120 #, tcl-format msgid "Error diffing commits: %s" msgstr "Произошла ошибка при выводе различий коммитов: %s" -#: gitk:9166 msgid "Top" msgstr "Верх" -#: gitk:9167 msgid "From" msgstr "От" -#: gitk:9172 msgid "To" msgstr "До" -#: gitk:9196 msgid "Generate patch" msgstr "Создать патч" -#: gitk:9198 msgid "From:" msgstr "От:" -#: gitk:9207 msgid "To:" msgstr "До:" -#: gitk:9216 msgid "Reverse" msgstr "В обратном порядке" -#: gitk:9218 gitk:9428 msgid "Output file:" msgstr "Файл для сохранения:" -#: gitk:9224 msgid "Generate" msgstr "Создать" -#: gitk:9262 msgid "Error creating patch:" msgstr "Ошибка создания патча:" -#: gitk:9285 gitk:9416 gitk:9504 msgid "ID:" msgstr "ID:" -#: gitk:9294 msgid "Tag name:" msgstr "Имя метки:" -#: gitk:9297 msgid "Tag message is optional" msgstr "Описание метки указывать не обязательно" -#: gitk:9299 msgid "Tag message:" msgstr "Описание метки:" -#: gitk:9303 gitk:9474 msgid "Create" msgstr "Создать" -#: gitk:9321 msgid "No tag name specified" msgstr "Не задано имя метки" -#: gitk:9325 #, tcl-format msgid "Tag \"%s\" already exists" msgstr "Метка «%s» уже существует" -#: gitk:9335 msgid "Error creating tag:" msgstr "Ошибка создания метки:" -#: gitk:9425 msgid "Command:" msgstr "Команда:" -#: gitk:9433 msgid "Write" msgstr "Запись" -#: gitk:9451 msgid "Error writing commit:" msgstr "Произошла ошибка при записи коммита:" -#: gitk:9473 msgid "Create branch" msgstr "Создать ветку" -#: gitk:9489 #, tcl-format msgid "Rename branch %s" msgstr "Переименовать ветку %s" -#: gitk:9490 msgid "Rename" msgstr "Переименовать" -#: gitk:9514 msgid "Name:" msgstr "Имя:" -#: gitk:9538 msgid "Please specify a name for the new branch" msgstr "Укажите имя для новой ветки" -#: gitk:9543 #, tcl-format msgid "Branch '%s' already exists. Overwrite?" msgstr "Ветка «%s» уже существует. Переписать?" -#: gitk:9587 msgid "Please specify a new name for the branch" msgstr "Укажите имя для новой ветки" -#: gitk:9650 #, tcl-format msgid "Commit %s is already included in branch %s -- really re-apply it?" msgstr "Коммит %s уже включён в ветку %s. Продолжить операцию?" -#: gitk:9655 msgid "Cherry-picking" msgstr "Копирование коммита" -#: gitk:9664 #, tcl-format msgid "" "Cherry-pick failed because of local changes to file '%s'.\n" "Please commit, reset or stash your changes and try again." -msgstr "Копирование коммита невозможно из-за изменений в файле «%s».\nЗакоммитьте, сбросьте или спрячьте изменения и повторите операцию." +msgstr "" +"Копирование коммита невозможно из-за изменений в файле «%s».\n" +"Закоммитьте, сбросьте или спрячьте изменения и повторите операцию." -#: gitk:9670 msgid "" "Cherry-pick failed because of merge conflict.\n" "Do you wish to run git citool to resolve it?" -msgstr "Копирование изменений невозможно из-за незавершённой операции слияния.\nЗапустить git citool для завершения этой операции?" +msgstr "" +"Копирование изменений невозможно из-за незавершённой операции слияния.\n" +"Запустить git citool для завершения этой операции?" -#: gitk:9686 gitk:9744 msgid "No changes committed" msgstr "Изменения не закоммичены" -#: gitk:9713 #, tcl-format msgid "Commit %s is not included in branch %s -- really revert it?" msgstr "Коммит %s не включён в ветку %s. Продолжить операцию?" -#: gitk:9718 msgid "Reverting" msgstr "Обращение изменений" -#: gitk:9726 #, 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 "Возврат изменений коммита не удался из-за локальных изменений в указанных файлах: %s\nЗакоммитьте, сбросьте или спрячьте изменения и повторите операцию." +msgstr "" +"Возврат изменений коммита не удался из-за локальных изменений в указанных " +"файлах: %s\n" +"Закоммитьте, сбросьте или спрячьте изменения и повторите операцию." -#: gitk:9730 msgid "" "Revert failed because of merge conflict.\n" " Do you wish to run git citool to resolve it?" -msgstr "Возврат изменений невозможен из-за незавершённой операции слияния.\nЗапустить git citool для завершения этой операции?" +msgstr "" +"Возврат изменений невозможен из-за незавершённой операции слияния.\n" +"Запустить git citool для завершения этой операции?" -#: gitk:9773 msgid "Confirm reset" msgstr "Подтвердите операцию перехода" -#: gitk:9775 #, tcl-format msgid "Reset branch %s to %s?" msgstr "Сбросить ветку %s на коммит %s?" -#: gitk:9777 msgid "Reset type:" msgstr "Тип операции перехода:" -#: gitk:9780 msgid "Soft: Leave working tree and index untouched" msgstr "Лёгкий: оставить рабочий каталог и индекс неизменными" -#: gitk:9783 msgid "Mixed: Leave working tree untouched, reset index" msgstr "Смешанный: оставить рабочий каталог неизменным, установить индекс" -#: gitk:9786 msgid "" "Hard: Reset working tree and index\n" "(discard ALL local changes)" -msgstr "Жесткий: переписать индекс и рабочий каталог\n(все изменения в рабочем каталоге будут потеряны)" +msgstr "" +"Жесткий: переписать индекс и рабочий каталог\n" +"(все изменения в рабочем каталоге будут потеряны)" -#: gitk:9803 msgid "Resetting" msgstr "Сброс" -#: gitk:9876 #, tcl-format msgid "A local branch named %s exists already" msgstr "Локальная ветка с именем %s уже существует" -#: gitk:9884 msgid "Checking out" msgstr "Переход" -#: gitk:9943 msgid "Cannot delete the currently checked-out branch" msgstr "Активная ветка не может быть удалена" -#: gitk:9949 #, tcl-format msgid "" "The commits on branch %s aren't on any other branch.\n" "Really delete branch %s?" -msgstr "Коммиты из ветки %s не принадлежат больше никакой другой ветке.\nДействительно удалить ветку %s?" +msgstr "" +"Коммиты из ветки %s не принадлежат больше никакой другой ветке.\n" +"Действительно удалить ветку %s?" -#: gitk:9980 #, tcl-format msgid "Tags and heads: %s" msgstr "Метки и ветки: %s" -#: gitk:9997 msgid "Filter" msgstr "Фильтровать" -#: gitk:10293 msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." -msgstr "Ошибка чтения истории проекта; информация о ветках и коммитах вокруг меток (до/после) может быть неполной." +msgstr "" +"Ошибка чтения истории проекта; информация о ветках и коммитах вокруг меток " +"(до/после) может быть неполной." -#: gitk:11270 msgid "Tag" msgstr "Метка" -#: gitk:11274 msgid "Id" msgstr "Id" -#: gitk:11357 msgid "Gitk font chooser" msgstr "Шрифт Gitk" -#: gitk:11374 msgid "B" msgstr "Ж" -#: gitk:11377 msgid "I" msgstr "К" -#: gitk:11495 msgid "Commit list display options" msgstr "Параметры показа списка коммитов" -#: gitk:11498 msgid "Maximum graph width (lines)" msgstr "Макс. ширина графа (строк)" -#: gitk:11502 #, no-tcl-format msgid "Maximum graph width (% of pane)" msgstr "Макс. ширина графа (% ширины панели)" -#: gitk:11505 msgid "Show local changes" msgstr "Показывать изменения в рабочем каталоге" -#: gitk:11508 msgid "Auto-select SHA1 (length)" msgstr "Автоматически выделить SHA1 (длинна)" -#: gitk:11512 msgid "Hide remote refs" msgstr "Скрыть внешние ссылки" -#: gitk:11516 msgid "Diff display options" msgstr "Параметры показа изменений" -#: gitk:11518 msgid "Tab spacing" msgstr "Ширина табуляции" -#: gitk:11521 msgid "Display nearby tags/heads" msgstr "Показывать близкие метки/ветки" -#: gitk:11524 msgid "Maximum # tags/heads to show" msgstr "Показывать максимальное количество меток/веток" -#: gitk:11527 msgid "Limit diffs to listed paths" msgstr "Ограничить показ изменений выбранными файлами" -#: gitk:11530 msgid "Support per-file encodings" msgstr "Поддержка кодировок в отдельных файлах" -#: gitk:11536 gitk:11683 msgid "External diff tool" msgstr "Программа для показа изменений" -#: gitk:11537 msgid "Choose..." msgstr "Выберите..." -#: gitk:11542 msgid "General options" msgstr "Общие опции" -#: gitk:11545 msgid "Use themed widgets" msgstr "Использовать стили виджетов" -#: gitk:11547 msgid "(change requires restart)" msgstr "(изменение потребует перезапуск)" -#: gitk:11549 msgid "(currently unavailable)" msgstr "(недоступно в данный момент)" -#: gitk:11560 msgid "Colors: press to choose" msgstr "Цвета: нажмите для выбора" -#: gitk:11563 msgid "Interface" msgstr "Интерфейс" -#: gitk:11564 msgid "interface" msgstr "интерфейс" -#: gitk:11567 msgid "Background" msgstr "Фон" -#: gitk:11568 gitk:11598 msgid "background" msgstr "фон" -#: gitk:11571 msgid "Foreground" msgstr "Передний план" -#: gitk:11572 msgid "foreground" msgstr "передний план" -#: gitk:11575 msgid "Diff: old lines" msgstr "Изменения: старый текст" -#: gitk:11576 msgid "diff old lines" msgstr "старый текст изменения" -#: gitk:11580 msgid "Diff: new lines" msgstr "Изменения: новый текст" -#: gitk:11581 msgid "diff new lines" msgstr "новый текст изменения" -#: gitk:11585 msgid "Diff: hunk header" msgstr "Изменения: заголовок блока" -#: gitk:11587 msgid "diff hunk header" msgstr "заголовок блока изменений" -#: gitk:11591 msgid "Marked line bg" msgstr "Фон выбранной строки" -#: gitk:11593 msgid "marked line background" msgstr "фон выбранной строки" -#: gitk:11597 msgid "Select bg" msgstr "Выберите фон" -#: gitk:11606 msgid "Fonts: press to choose" msgstr "Шрифт: нажмите для выбора" -#: gitk:11608 msgid "Main font" msgstr "Основной шрифт" -#: gitk:11609 msgid "Diff display font" msgstr "Шрифт показа изменений" -#: gitk:11610 msgid "User interface font" msgstr "Шрифт интерфейса" -#: gitk:11632 msgid "Gitk preferences" msgstr "Настройки Gitk" -#: gitk:11641 msgid "General" msgstr "Общие" -#: gitk:11642 msgid "Colors" msgstr "Цвета" -#: gitk:11643 msgid "Fonts" msgstr "Шрифты" -#: gitk:11693 #, tcl-format msgid "Gitk: choose color for %s" msgstr "Gitk: выберите цвет для %s" -#: gitk:12206 msgid "" "Sorry, gitk cannot run with this version of Tcl/Tk.\n" " Gitk requires at least Tcl/Tk 8.4." -msgstr "К сожалению gitk не может работать с этой версий Tcl/Tk.\nТребуется как минимум Tcl/Tk 8.4." +msgstr "" +"К сожалению gitk не может работать с этой версий Tcl/Tk.\n" +"Требуется как минимум Tcl/Tk 8.4." -#: gitk:12416 msgid "Cannot find a git repository here." msgstr "Git-репозитарий не найден в текущем каталоге." -#: gitk:12463 #, tcl-format msgid "Ambiguous argument '%s': both revision and filename" msgstr "Неоднозначный аргумент «%s»: существует как редакция и как имя файла" -#: gitk:12475 msgid "Bad arguments to gitk:" msgstr "Неправильные аргументы для gitk:"
diff --git a/gitk-git/po/sv.po b/gitk-git/po/sv.po index 5afbe6d..c929062 100644 --- a/gitk-git/po/sv.po +++ b/gitk-git/po/sv.po
@@ -7,7 +7,7 @@ # msgid "" msgstr "" -"Project-Id-Version: sv\n" +"Project-Id-Version: Gitk\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2023-10-26 21:39+0100\n" "PO-Revision-Date: 2023-10-26 21:42+0100\n" @@ -20,33 +20,26 @@ "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Gtranslator 3.38.0\n" -#: gitk:139 msgid "Couldn't get list of unmerged files:" msgstr "Kunde inte hämta lista över ej sammanslagna filer:" -#: gitk:211 gitk:2406 msgid "Color words" msgstr "Färga ord" -#: gitk:216 gitk:2406 gitk:8307 gitk:8340 msgid "Markup words" msgstr "Märk upp ord" -#: gitk:323 msgid "Error parsing revisions:" msgstr "Fel vid tolkning av revisioner:" -#: gitk:379 msgid "Error executing --argscmd command:" msgstr "Fel vid körning av --argscmd-kommando:" -#: gitk:392 msgid "No files selected: --merge specified but no files are unmerged." msgstr "" "Inga filer valdes: --merge angavs men det finns inga filer som inte har " "slagits samman." -#: gitk:395 msgid "" "No files selected: --merge specified but no unmerged files are within file " "limit." @@ -54,326 +47,243 @@ "Inga filer valdes: --merge angavs men det finns inga filer inom " "filbegränsningen." -#: gitk:417 gitk:565 msgid "Error executing git log:" msgstr "Fel vid körning av git log:" -#: gitk:435 gitk:581 msgid "Reading" msgstr "Läser" -#: gitk:495 gitk:4572 msgid "Reading commits..." msgstr "Läser incheckningar..." -#: gitk:498 gitk:1640 gitk:4575 msgid "No commits selected" msgstr "Inga incheckningar markerade" -#: gitk:1448 gitk:4092 gitk:12674 msgid "Command line" msgstr "Kommandorad" -#: gitk:1514 msgid "Can't parse git log output:" msgstr "Kan inte tolka utdata från git log:" -#: gitk:1743 msgid "No commit information available" msgstr "Ingen incheckningsinformation är tillgänglig" -#: gitk:1910 gitk:1939 gitk:4362 gitk:9847 gitk:11451 gitk:11751 msgid "OK" msgstr "OK" -#: gitk:1941 gitk:4364 gitk:9283 gitk:9362 gitk:9492 gitk:9578 gitk:9849 -#: gitk:11452 gitk:11752 msgid "Cancel" msgstr "Avbryt" -#: gitk:2090 msgid "&Update" msgstr "&Uppdatera" -#: gitk:2091 msgid "&Reload" msgstr "Läs &om" -#: gitk:2092 msgid "Reread re&ferences" msgstr "Läs om &referenser" -#: gitk:2093 msgid "&List references" msgstr "&Visa referenser" -#: gitk:2095 msgid "Start git &gui" msgstr "Starta git &gui" -#: gitk:2097 msgid "&Quit" msgstr "&Avsluta" -#: gitk:2089 msgid "&File" msgstr "&Arkiv" -#: gitk:2101 msgid "&Preferences" msgstr "&Inställningar" -#: gitk:2100 msgid "&Edit" msgstr "&Redigera" -#: gitk:2105 msgid "&New view..." msgstr "&Ny vy..." -#: gitk:2106 msgid "&Edit view..." msgstr "&Ändra vy..." -#: gitk:2107 msgid "&Delete view" msgstr "&Ta bort vy" -#: gitk:2109 msgid "&All files" msgstr "&Alla filer" -#: gitk:2104 msgid "&View" msgstr "&Visa" -#: gitk:2114 gitk:2124 msgid "&About gitk" msgstr "&Om gitk" -#: gitk:2115 gitk:2129 msgid "&Key bindings" msgstr "&Tangentbordsbindningar" -#: gitk:2113 gitk:2128 msgid "&Help" msgstr "&Hjälp" -#: gitk:2206 gitk:8739 msgid "SHA1 ID:" msgstr "SHA1-id:" -#: gitk:2250 msgid "Row" msgstr "Rad" -#: gitk:2288 msgid "Find" msgstr "Sök" -#: gitk:2316 msgid "commit" msgstr "incheckning" -#: gitk:2320 gitk:2322 gitk:4734 gitk:4757 gitk:4781 gitk:6802 gitk:6874 -#: gitk:6959 msgid "containing:" msgstr "som innehåller:" -#: gitk:2323 gitk:3573 gitk:3578 gitk:4810 msgid "touching paths:" msgstr "som rör sökväg:" -#: gitk:2324 gitk:4824 msgid "adding/removing string:" msgstr "som lägger/till tar bort sträng:" -#: gitk:2325 gitk:4826 msgid "changing lines matching:" msgstr "ändrar rader som matchar:" -#: gitk:2334 gitk:2336 gitk:4813 msgid "Exact" msgstr "Exakt" -#: gitk:2336 gitk:4901 gitk:6770 msgid "IgnCase" msgstr "IgnVersaler" -#: gitk:2336 gitk:4783 gitk:4899 gitk:6766 msgid "Regexp" msgstr "Reg.uttr." -#: gitk:2338 gitk:2339 gitk:4921 gitk:4951 gitk:4958 gitk:6895 gitk:6963 msgid "All fields" msgstr "Alla fält" -#: gitk:2339 gitk:4918 gitk:4951 gitk:6833 msgid "Headline" msgstr "Rubrik" -#: gitk:2340 gitk:4918 gitk:6833 gitk:6963 gitk:7471 msgid "Comments" msgstr "Kommentarer" -#: gitk:2340 gitk:4918 gitk:4923 gitk:4958 gitk:6833 gitk:7406 gitk:8917 -#: gitk:8932 msgid "Author" msgstr "Författare" -#: gitk:2340 gitk:4918 gitk:6833 gitk:7408 msgid "Committer" msgstr "Incheckare" -#: gitk:2374 msgid "Search" msgstr "Sök" -#: gitk:2382 msgid "Diff" msgstr "Diff" -#: gitk:2384 msgid "Old version" msgstr "Gammal version" -#: gitk:2386 msgid "New version" msgstr "Ny version" -#: gitk:2389 msgid "Lines of context" msgstr "Rader sammanhang" -#: gitk:2399 msgid "Ignore space change" msgstr "Ignorera ändringar i blanksteg" -#: gitk:2403 gitk:2405 gitk:8041 gitk:8293 msgid "Line diff" msgstr "Rad-diff" -#: gitk:2478 msgid "Patch" msgstr "Patch" -#: gitk:2480 msgid "Tree" msgstr "Träd" -#: gitk:2650 gitk:2671 msgid "Diff this -> selected" msgstr "Diff denna -> markerad" -#: gitk:2651 gitk:2672 msgid "Diff selected -> this" msgstr "Diff markerad -> denna" -#: gitk:2652 gitk:2673 msgid "Make patch" msgstr "Skapa patch" -#: gitk:2653 gitk:9341 msgid "Create tag" msgstr "Skapa tagg" -#: gitk:2654 msgid "Copy commit reference" msgstr "Kopiera incheckningsreferens" -#: gitk:2655 gitk:9472 msgid "Write commit to file" msgstr "Skriv incheckning till fil" -#: gitk:2656 msgid "Create new branch" msgstr "Skapa ny gren" -#: gitk:2657 msgid "Cherry-pick this commit" msgstr "Plocka denna incheckning" -#: gitk:2658 msgid "Reset HEAD branch to here" msgstr "Återställ HEAD-grenen hit" -#: gitk:2659 msgid "Mark this commit" msgstr "Markera denna incheckning" -#: gitk:2660 msgid "Return to mark" msgstr "Återgå till markering" -#: gitk:2661 msgid "Find descendant of this and mark" msgstr "Hitta efterföljare till denna och markera" -#: gitk:2662 msgid "Compare with marked commit" msgstr "Jämför med markerad incheckning" -#: gitk:2663 gitk:2674 msgid "Diff this -> marked commit" msgstr "Diff denna -> markerad incheckning" -#: gitk:2664 gitk:2675 msgid "Diff marked commit -> this" msgstr "Diff markerad incheckning -> denna" -#: gitk:2665 msgid "Revert this commit" msgstr "Ångra denna incheckning" -#: gitk:2681 msgid "Check out this branch" msgstr "Checka ut denna gren" -#: gitk:2682 msgid "Rename this branch" msgstr "Byt namn på denna gren" -#: gitk:2683 msgid "Remove this branch" msgstr "Ta bort denna gren" -#: gitk:2684 msgid "Copy branch name" msgstr "Kopiera namn på gren" -#: gitk:2691 msgid "Highlight this too" msgstr "Markera även detta" -#: gitk:2692 msgid "Highlight this only" msgstr "Markera bara detta" -#: gitk:2693 msgid "External diff" msgstr "Extern diff" -#: gitk:2694 msgid "Blame parent commit" msgstr "Klandra föräldraincheckning" -#: gitk:2695 msgid "Copy path" msgstr "Kopiera sökväg" -#: gitk:2702 msgid "Show origin of this line" msgstr "Visa ursprunget för den här raden" -#: gitk:2703 msgid "Run git gui blame on this line" msgstr "Kör git gui blame på den här raden" -#: gitk:3057 msgid "About gitk" msgstr "Om gitk" -#: gitk:3059 msgid "" "\n" "Gitk - a commit viewer for git\n" @@ -389,323 +299,249 @@ "\n" "Använd och vidareförmedla enligt villkoren i GNU General Public License" -#: gitk:3067 gitk:3134 gitk:10062 msgid "Close" msgstr "Stäng" -#: gitk:3088 msgid "Gitk key bindings" msgstr "Tangentbordsbindningar för Gitk" -#: gitk:3091 msgid "Gitk key bindings:" msgstr "Tangentbordsbindningar för Gitk:" -#: gitk:3093 #, tcl-format msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\tAvsluta" -#: gitk:3094 #, tcl-format msgid "<%s-W>\t\tClose window" msgstr "<%s-W>\t\tStäng fönster" -#: gitk:3095 msgid "<Home>\t\tMove to first commit" msgstr "<Home>\t\tGå till första incheckning" -#: gitk:3096 msgid "<End>\t\tMove to last commit" msgstr "<End>\t\tGå till sista incheckning" -#: gitk:3097 msgid "<Up>, p, k\tMove up one commit" msgstr "<Upp>, p, k\tGå en incheckning upp" -#: gitk:3098 msgid "<Down>, n, j\tMove down one commit" msgstr "<Ned>, n, j\tGå en incheckning ned" -#: gitk:3099 msgid "<Left>, z, h\tGo back in history list" msgstr "<Vänster>, z, h\tGå bakåt i historiken" -#: gitk:3100 msgid "<Right>, x, l\tGo forward in history list" msgstr "<Höger>, x, l\tGå framåt i historiken" -#: gitk:3101 #, tcl-format msgid "<%s-n>\tGo to n-th parent of current commit in history list" msgstr "<%s-n>\tGå till aktuell inchecknings n:te förälder i historielistan" -#: gitk:3102 msgid "<PageUp>\tMove up one page in commit list" msgstr "<PageUp>\tGå upp en sida i incheckningslistan" -#: gitk:3103 msgid "<PageDown>\tMove down one page in commit list" msgstr "<PageDown>\tGå ned en sida i incheckningslistan" -#: gitk:3104 #, tcl-format msgid "<%s-Home>\tScroll to top of commit list" msgstr "<%s-Home>\tRulla till början av incheckningslistan" -#: gitk:3105 #, tcl-format msgid "<%s-End>\tScroll to bottom of commit list" msgstr "<%s-End>\tRulla till slutet av incheckningslistan" -#: gitk:3106 #, tcl-format msgid "<%s-Up>\tScroll commit list up one line" msgstr "<%s-Upp>\tRulla incheckningslistan upp ett steg" -#: gitk:3107 #, tcl-format msgid "<%s-Down>\tScroll commit list down one line" msgstr "<%s-Ned>\tRulla incheckningslistan ned ett steg" -#: gitk:3108 #, tcl-format msgid "<%s-PageUp>\tScroll commit list up one page" msgstr "<%s-PageUp>\tRulla incheckningslistan upp en sida" -#: gitk:3109 #, tcl-format msgid "<%s-PageDown>\tScroll commit list down one page" msgstr "<%s-PageDown>\tRulla incheckningslistan ned en sida" -#: gitk:3110 msgid "<Shift-Up>\tFind backwards (upwards, later commits)" msgstr "<Skift-Upp>\tSök bakåt (uppåt, senare incheckningar)" -#: gitk:3111 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" msgstr "<Skift-Ned>\tSök framåt (nedåt, tidigare incheckningar)" -#: gitk:3112 msgid "<Delete>, b\tScroll diff view up one page" msgstr "<Delete>, b\tRulla diffvisningen upp en sida" -#: gitk:3113 msgid "<Backspace>\tScroll diff view up one page" msgstr "<Baksteg>\tRulla diffvisningen upp en sida" -#: gitk:3114 msgid "<Space>\t\tScroll diff view down one page" msgstr "<Blanksteg>\tRulla diffvisningen ned en sida" -#: gitk:3115 msgid "u\t\tScroll diff view up 18 lines" msgstr "u\t\tRulla diffvisningen upp 18 rader" -#: gitk:3116 msgid "d\t\tScroll diff view down 18 lines" msgstr "d\t\tRulla diffvisningen ned 18 rader" -#: gitk:3117 #, tcl-format msgid "<%s-F>\t\tFind" msgstr "<%s-F>\t\tSök" -#: gitk:3118 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" msgstr "<%s-G>\t\tGå till nästa sökträff" -#: gitk:3119 msgid "<Return>\tMove to next find hit" msgstr "<Return>\t\tGå till nästa sökträff" -#: gitk:3120 msgid "g\t\tGo to commit" msgstr "g\t\tGå till incheckning" -#: gitk:3121 msgid "/\t\tFocus the search box" msgstr "/\t\tFokusera sökrutan" -#: gitk:3122 msgid "?\t\tMove to previous find hit" msgstr "?\t\tGå till föregående sökträff" -#: gitk:3123 msgid "f\t\tScroll diff view to next file" msgstr "f\t\tRulla diffvisningen till nästa fil" -#: gitk:3124 #, tcl-format msgid "<%s-S>\t\tSearch for next hit in diff view" msgstr "<%s-S>\t\tGå till nästa sökträff i diffvisningen" -#: gitk:3125 #, tcl-format msgid "<%s-R>\t\tSearch for previous hit in diff view" msgstr "<%s-R>\t\tGå till föregående sökträff i diffvisningen" -#: gitk:3126 #, tcl-format msgid "<%s-KP+>\tIncrease font size" msgstr "<%s-Num+>\tÖka teckenstorlek" -#: gitk:3127 #, tcl-format msgid "<%s-plus>\tIncrease font size" msgstr "<%s-plus>\tÖka teckenstorlek" -#: gitk:3128 #, tcl-format msgid "<%s-KP->\tDecrease font size" msgstr "<%s-Num->\tMinska teckenstorlek" -#: gitk:3129 #, tcl-format msgid "<%s-minus>\tDecrease font size" msgstr "<%s-minus>\tMinska teckenstorlek" -#: gitk:3130 msgid "<F5>\t\tUpdate" msgstr "<F5>\t\tUppdatera" -#: gitk:3597 gitk:3606 #, tcl-format msgid "Error creating temporary directory %s:" msgstr "Fel vid skapande av temporär katalog %s:" -#: gitk:3619 #, tcl-format msgid "Error getting \"%s\" from %s:" msgstr "Fel vid hämtning av ”%s” från %s:" -#: gitk:3682 msgid "command failed:" msgstr "kommando misslyckades:" -#: gitk:3831 msgid "No such commit" msgstr "Incheckning saknas" -#: gitk:3845 msgid "git gui blame: command failed:" msgstr "git gui blame: kommando misslyckades:" -#: gitk:3876 #, tcl-format msgid "Couldn't read merge head: %s" msgstr "Kunde inte läsa sammanslagningshuvud: %s" -#: gitk:3884 #, tcl-format msgid "Error reading index: %s" msgstr "Fel vid läsning av index: %s" -#: gitk:3909 #, tcl-format msgid "Couldn't start git blame: %s" msgstr "Kunde inte starta git blame: %s" -#: gitk:3912 gitk:6801 msgid "Searching" msgstr "Söker" -#: gitk:3944 #, tcl-format msgid "Error running git blame: %s" msgstr "Fel vid körning av git blame: %s" -#: gitk:3972 #, tcl-format msgid "That line comes from commit %s, which is not in this view" msgstr "Raden kommer från incheckningen %s, som inte finns i denna vy" -#: gitk:3986 msgid "External diff viewer failed:" msgstr "Externt diff-verktyg misslyckades:" -#: gitk:4090 msgid "All files" msgstr "Alla filer" -#: gitk:4114 msgid "View" msgstr "Visa" -#: gitk:4117 msgid "Gitk view definition" msgstr "Definition av Gitk-vy" -#: gitk:4121 msgid "Remember this view" msgstr "Spara denna vy" -#: gitk:4122 msgid "References (space separated list):" msgstr "Referenser (blankstegsavdelad lista):" -#: gitk:4123 msgid "Branches & tags:" msgstr "Grenar & taggar:" -#: gitk:4124 msgid "All refs" msgstr "Alla referenser" -#: gitk:4125 msgid "All (local) branches" msgstr "Alla (lokala) grenar" -#: gitk:4126 msgid "All tags" msgstr "Alla taggar" -#: gitk:4127 msgid "All remote-tracking branches" msgstr "Alla fjärrspårande grenar" -#: gitk:4128 msgid "Commit Info (regular expressions):" msgstr "Incheckningsinfo (reguljära uttryck):" -#: gitk:4129 msgid "Author:" msgstr "Författare:" -#: gitk:4130 msgid "Committer:" msgstr "Incheckare:" -#: gitk:4131 msgid "Commit Message:" msgstr "Incheckningsmeddelande:" -#: gitk:4132 msgid "Matches all Commit Info criteria" msgstr "Motsvarar alla kriterier för incheckningsinfo" -#: gitk:4133 msgid "Matches no Commit Info criteria" msgstr "Motsvarar inga kriterier för incheckningsinfo" -#: gitk:4134 msgid "Changes to Files:" msgstr "Ändringar av filer:" -#: gitk:4135 msgid "Fixed String" msgstr "Fast sträng" -#: gitk:4136 msgid "Regular Expression" msgstr "Reguljärt uttryck" -#: gitk:4137 msgid "Search string:" msgstr "Söksträng:" -#: gitk:4138 msgid "" "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" @@ -713,205 +549,156 @@ "Incheckningsdatum (”2 weeks ago”, ”2009-03-17 15:27:38”, ”March 17, 2009 " "15:27:38”):" -#: gitk:4139 msgid "Since:" msgstr "Från:" -#: gitk:4140 msgid "Until:" msgstr "Till:" -#: gitk:4141 msgid "Limit and/or skip a number of revisions (positive integer):" msgstr "Begränsa och/eller hoppa över ett antal revisioner (positivt heltal):" -#: gitk:4142 msgid "Number to show:" msgstr "Antal att visa:" -#: gitk:4143 msgid "Number to skip:" msgstr "Antal att hoppa över:" -#: gitk:4144 msgid "Miscellaneous options:" msgstr "Diverse alternativ:" -#: gitk:4145 msgid "Strictly sort by date" msgstr "Strikt datumsortering" -#: gitk:4146 msgid "Mark branch sides" msgstr "Markera sidogrenar" -#: gitk:4147 msgid "Limit to first parent" msgstr "Begränsa till första förälder" -#: gitk:4148 msgid "Simple history" msgstr "Enkel historik" -#: gitk:4149 msgid "Additional arguments to git log:" msgstr "Ytterligare argument till git log:" -#: gitk:4150 msgid "Enter files and directories to include, one per line:" msgstr "Ange filer och kataloger att ta med, en per rad:" -#: gitk:4151 msgid "Command to generate more commits to include:" msgstr "Kommando för att generera fler incheckningar att ta med:" -#: gitk:4275 msgid "Gitk: edit view" msgstr "Gitk: redigera vy" -#: gitk:4283 msgid "-- criteria for selecting revisions" msgstr " - kriterier för val av revisioner" -#: gitk:4288 msgid "View Name" msgstr "Namn på vy" -#: gitk:4363 msgid "Apply (F5)" msgstr "Använd (F5)" -#: gitk:4401 msgid "Error in commit selection arguments:" msgstr "Fel i argument för val av incheckningar:" -#: gitk:4456 gitk:4509 gitk:4971 gitk:4985 gitk:6255 gitk:12615 gitk:12616 msgid "None" msgstr "Inget" -#: gitk:5068 gitk:5073 msgid "Descendant" msgstr "Avkomling" -#: gitk:5069 msgid "Not descendant" msgstr "Inte avkomling" -#: gitk:5076 gitk:5081 msgid "Ancestor" msgstr "Förfader" -#: gitk:5077 msgid "Not ancestor" msgstr "Inte förfader" -#: gitk:5371 msgid "Local changes checked in to index but not committed" msgstr "Lokala ändringar sparade i indexet men inte incheckade" -#: gitk:5407 msgid "Local uncommitted changes, not checked in to index" msgstr "Lokala ändringar, ej sparade i indexet" -#: gitk:7155 msgid "Error starting web browser:" msgstr "Fel när webbläsaren skulle startas:" -#: gitk:7216 msgid "and many more" msgstr "med många flera" -#: gitk:7219 msgid "many" msgstr "många" -#: gitk:7410 msgid "Tags:" msgstr "Taggar:" -#: gitk:7427 gitk:7433 gitk:8912 msgid "Parent" msgstr "Förälder" -#: gitk:7438 msgid "Child" msgstr "Barn" -#: gitk:7447 msgid "Branch" msgstr "Gren" -#: gitk:7450 msgid "Follows" msgstr "Följer" -#: gitk:7453 msgid "Precedes" msgstr "Föregår" -#: gitk:8048 #, tcl-format msgid "Error getting diffs: %s" msgstr "Fel vid hämtning av diff: %s" -#: gitk:8737 msgid "Goto:" msgstr "Gå till:" -#: gitk:8758 #, tcl-format msgid "Short SHA1 id %s is ambiguous" msgstr "Förkortat SHA1-id %s är tvetydigt" -#: gitk:8765 #, tcl-format msgid "Revision %s is not known" msgstr "Revisionen %s är inte känd" -#: gitk:8775 #, tcl-format msgid "SHA1 id %s is not known" msgstr "SHA-id:t %s är inte känt" -#: gitk:8777 #, tcl-format msgid "Revision %s is not in the current view" msgstr "Revisionen %s finns inte i den nuvarande vyn" -#: gitk:8919 gitk:8934 msgid "Date" msgstr "Datum" -#: gitk:8922 msgid "Children" msgstr "Barn" -#: gitk:8985 #, tcl-format msgid "Reset %s branch to here" msgstr "Återställ grenen %s hit" -#: gitk:8987 msgid "Detached head: can't reset" msgstr "Frånkopplad head: kan inte återställa" -#: gitk:9092 gitk:9098 msgid "Skipping merge commit " msgstr "Hoppar över sammanslagningsincheckning " -#: gitk:9107 gitk:9112 msgid "Error getting patch ID for " msgstr "Fel vid hämtning av patch-id för " -#: gitk:9108 gitk:9113 msgid " - stopping\n" msgstr " - stannar\n" -#: gitk:9118 gitk:9121 gitk:9129 gitk:9143 gitk:9152 msgid "Commit " msgstr "Incheckning " -#: gitk:9122 msgid "" " is the same patch as\n" " " @@ -919,7 +706,6 @@ " är samma patch som\n" " " -#: gitk:9130 msgid "" " differs from\n" " " @@ -927,7 +713,6 @@ " skiljer sig från\n" " " -#: gitk:9132 msgid "" "Diff of commits:\n" "\n" @@ -935,148 +720,114 @@ "Skillnad mellan incheckningar:\n" "\n" -#: gitk:9144 gitk:9153 #, tcl-format msgid " has %s children - stopping\n" msgstr " har %s barn - stannar\n" -#: gitk:9172 #, tcl-format msgid "Error writing commit to file: %s" msgstr "Fel vid skrivning av incheckning till fil: %s" -#: gitk:9178 #, tcl-format msgid "Error diffing commits: %s" msgstr "Fel vid jämförelse av incheckningar: %s" -#: gitk:9224 msgid "Top" msgstr "Topp" -#: gitk:9225 msgid "From" msgstr "Från" -#: gitk:9230 msgid "To" msgstr "Till" -#: gitk:9254 msgid "Generate patch" msgstr "Generera patch" -#: gitk:9256 msgid "From:" msgstr "Från:" -#: gitk:9265 msgid "To:" msgstr "Till:" -#: gitk:9274 msgid "Reverse" msgstr "Vänd" -#: gitk:9276 gitk:9486 msgid "Output file:" msgstr "Utdatafil:" -#: gitk:9282 msgid "Generate" msgstr "Generera" -#: gitk:9320 msgid "Error creating patch:" msgstr "Fel vid generering av patch:" -#: gitk:9343 gitk:9474 gitk:9562 msgid "ID:" msgstr "Id:" -#: gitk:9352 msgid "Tag name:" msgstr "Taggnamn:" -#: gitk:9355 msgid "Tag message is optional" msgstr "Taggmeddelandet är valfritt" -#: gitk:9357 msgid "Tag message:" msgstr "Taggmeddelande:" -#: gitk:9361 gitk:9532 msgid "Create" msgstr "Skapa" -#: gitk:9379 msgid "No tag name specified" msgstr "Inget taggnamn angavs" -#: gitk:9383 #, tcl-format msgid "Tag \"%s\" already exists" msgstr "Taggen ”%s” finns redan" -#: gitk:9393 msgid "Error creating tag:" msgstr "Fel vid skapande av tagg:" -#: gitk:9483 msgid "Command:" msgstr "Kommando:" -#: gitk:9491 msgid "Write" msgstr "Skriv" -#: gitk:9509 msgid "Error writing commit:" msgstr "Fel vid skrivning av incheckning:" -#: gitk:9531 msgid "Create branch" msgstr "Skapa gren" -#: gitk:9547 #, tcl-format msgid "Rename branch %s" msgstr "Byt namn på grenen %s" -#: gitk:9548 msgid "Rename" msgstr "Byt namn" -#: gitk:9572 msgid "Name:" msgstr "Namn:" -#: gitk:9596 msgid "Please specify a name for the new branch" msgstr "Ange ett namn för den nya grenen" -#: gitk:9601 #, tcl-format msgid "Branch '%s' already exists. Overwrite?" msgstr "Grenen ”%s” finns redan. Skriva över?" -#: gitk:9645 msgid "Please specify a new name for the branch" msgstr "Ange ett nytt namn för grenen" -#: gitk:9708 #, tcl-format msgid "Commit %s is already included in branch %s -- really re-apply it?" msgstr "" "Incheckningen %s finns redan på grenen %s -- skall den verkligen appliceras " "på nytt?" -#: gitk:9713 msgid "Cherry-picking" msgstr "Plockar" -#: gitk:9722 #, tcl-format msgid "" "Cherry-pick failed because of local changes to file '%s'.\n" @@ -1086,7 +837,6 @@ "Checka in, återställ eller spara undan (stash) dina ändringar och försök " "igen." -#: gitk:9728 msgid "" "Cherry-pick failed because of merge conflict.\n" "Do you wish to run git citool to resolve it?" @@ -1094,20 +844,16 @@ "Cherry-pick misslyckades på grund av en sammanslagningskonflikt.\n" "Vill du köra git citool för att lösa den?" -#: gitk:9744 gitk:9802 msgid "No changes committed" msgstr "Inga ändringar incheckade" -#: gitk:9771 #, tcl-format msgid "Commit %s is not included in branch %s -- really revert it?" msgstr "Incheckningen %s finns inte på grenen %s -- vill du verkligen ångra?" -#: gitk:9776 msgid "Reverting" msgstr "Ångrar" -#: gitk:9784 #, tcl-format msgid "" "Revert failed because of local changes to the following files:%s Please " @@ -1117,7 +863,6 @@ "Checka in, återställ eller spara undan (stash) dina ändringar och försök " "igen." -#: gitk:9788 msgid "" "Revert failed because of merge conflict.\n" " Do you wish to run git citool to resolve it?" @@ -1125,28 +870,22 @@ "Misslyckades med att ångra på grund av en sammanslagningskonflikt.\n" " Vill du köra git citool för att lösa den?" -#: gitk:9831 msgid "Confirm reset" msgstr "Bekräfta återställning" -#: gitk:9833 #, tcl-format msgid "Reset branch %s to %s?" msgstr "Återställa grenen %s till %s?" -#: gitk:9835 msgid "Reset type:" msgstr "Typ av återställning:" -#: gitk:9838 msgid "Soft: Leave working tree and index untouched" msgstr "Mjuk: Rör inte utcheckning och index" -#: gitk:9841 msgid "Mixed: Leave working tree untouched, reset index" msgstr "Blandad: Rör inte utcheckning, återställ index" -#: gitk:9844 msgid "" "Hard: Reset working tree and index\n" "(discard ALL local changes)" @@ -1154,24 +893,19 @@ "Hård: Återställ utcheckning och index\n" "(förkastar ALLA lokala ändringar)" -#: gitk:9861 msgid "Resetting" msgstr "Återställer" -#: gitk:9934 #, tcl-format msgid "A local branch named %s exists already" msgstr "Det finns redan en lokal gren som heter %s" -#: gitk:9942 msgid "Checking out" msgstr "Checkar ut" -#: gitk:10001 msgid "Cannot delete the currently checked-out branch" msgstr "Kan inte ta bort den just nu utcheckade grenen" -#: gitk:10007 #, tcl-format msgid "" "The commits on branch %s aren't on any other branch.\n" @@ -1180,16 +914,13 @@ "Incheckningarna på grenen %s existerar inte på någon annan gren.\n" "Vill du verkligen ta bort grenen %s?" -#: gitk:10038 #, tcl-format msgid "Tags and heads: %s" msgstr "Taggar och huvuden: %s" -#: gitk:10055 msgid "Filter" msgstr "Filter" -#: gitk:10356 msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." @@ -1197,221 +928,167 @@ "Fel vid läsning av information om incheckningstopologi; information om " "grenar och föregående/senare taggar kommer inte vara komplett." -#: gitk:11333 msgid "Tag" msgstr "Tagg" -#: gitk:11337 msgid "Id" msgstr "Id" -#: gitk:11420 msgid "Gitk font chooser" msgstr "Teckensnittsväljare för Gitk" -#: gitk:11437 msgid "B" msgstr "F" -#: gitk:11440 msgid "I" msgstr "K" -#: gitk:11558 msgid "Commit list display options" msgstr "Alternativ för incheckningslistvy" -#: gitk:11561 msgid "Maximum graph width (lines)" msgstr "Maximal grafbredd (rader)" -#: gitk:11565 #, no-tcl-format msgid "Maximum graph width (% of pane)" msgstr "Maximal grafbredd (% av ruta)" -#: gitk:11568 msgid "Show local changes" msgstr "Visa lokala ändringar" -#: gitk:11571 msgid "Auto-select SHA1 (length)" msgstr "Välj SHA1 (längd) automatiskt" -#: gitk:11575 msgid "Hide remote refs" msgstr "Dölj fjärr-referenser" -#: gitk:11579 msgid "Diff display options" msgstr "Alternativ för diffvy" -#: gitk:11581 msgid "Tab spacing" msgstr "Blanksteg för tabulatortecken" -#: gitk:11584 msgid "Display nearby tags/heads" msgstr "Visa närliggande taggar/huvuden" -#: gitk:11587 msgid "Maximum # tags/heads to show" msgstr "Maximalt antal taggar/huvuden att visa" -#: gitk:11590 msgid "Limit diffs to listed paths" msgstr "Begränsa diff till listade sökvägar" -#: gitk:11593 msgid "Support per-file encodings" msgstr "Stöd för filspecifika teckenkodningar" -#: gitk:11599 gitk:11766 msgid "External diff tool" msgstr "Externt diff-verktyg" -#: gitk:11600 msgid "Choose..." msgstr "Välj..." -#: gitk:11607 msgid "Web browser" msgstr "Webbläsare" -#: gitk:11612 msgid "General options" msgstr "Allmänna inställningar" -#: gitk:11615 msgid "Use themed widgets" msgstr "Använd tema på fönsterelement" -#: gitk:11617 msgid "(change requires restart)" msgstr "(ändringen kräver omstart)" -#: gitk:11619 msgid "(currently unavailable)" msgstr "(för närvarande inte tillgängligt)" -#: gitk:11631 msgid "Colors: press to choose" msgstr "Färger: tryck för att välja" -#: gitk:11634 msgid "Interface" msgstr "Gränssnitt" -#: gitk:11635 msgid "interface" msgstr "gränssnitt" -#: gitk:11638 msgid "Background" msgstr "Bakgrund" -#: gitk:11639 gitk:11681 msgid "background" msgstr "bakgrund" -#: gitk:11642 msgid "Foreground" msgstr "Förgrund" -#: gitk:11643 msgid "foreground" msgstr "förgrund" -#: gitk:11646 msgid "Diff: old lines" msgstr "Diff: gamla rader" -#: gitk:11647 msgid "diff old lines" msgstr "diff gamla rader" -#: gitk:11651 msgid "Diff: old lines bg" msgstr "Diff: gamla rader bg" -#: gitk:11653 msgid "diff old lines bg" msgstr "diff gamla rader bg" -#: gitk:11657 msgid "Diff: new lines" msgstr "Diff: nya rader" -#: gitk:11658 msgid "diff new lines" msgstr "diff nya rader" -#: gitk:11662 msgid "Diff: new lines bg" msgstr "Diff: nya rader bg" -#: gitk:11664 msgid "diff new lines bg" msgstr "diff nya rader bg" -#: gitk:11668 msgid "Diff: hunk header" msgstr "Diff: delhuvud" -#: gitk:11670 msgid "diff hunk header" msgstr "diff delhuvud" -#: gitk:11674 msgid "Marked line bg" msgstr "Markerad rad bakgrund" -#: gitk:11676 msgid "marked line background" msgstr "markerad rad bakgrund" -#: gitk:11680 msgid "Select bg" msgstr "Markerad bakgrund" -#: gitk:11689 msgid "Fonts: press to choose" msgstr "Teckensnitt: tryck för att välja" -#: gitk:11691 msgid "Main font" msgstr "Huvudteckensnitt" -#: gitk:11692 msgid "Diff display font" msgstr "Teckensnitt för diffvisning" -#: gitk:11693 msgid "User interface font" msgstr "Teckensnitt för användargränssnitt" -#: gitk:11715 msgid "Gitk preferences" msgstr "Inställningar för Gitk" -#: gitk:11724 msgid "General" msgstr "Allmänt" -#: gitk:11725 msgid "Colors" msgstr "Färger" -#: gitk:11726 msgid "Fonts" msgstr "Teckensnitt" -#: gitk:11776 #, tcl-format msgid "Gitk: choose color for %s" msgstr "Gitk: välj färg för %s" -#: gitk:12289 msgid "" "Sorry, gitk cannot run with this version of Tcl/Tk.\n" " Gitk requires at least Tcl/Tk 8.4." @@ -1419,15 +1096,12 @@ "Gitk kan tyvärr inte köra med denna version av Tcl/Tk.\n" " Gitk kräver åtminstone Tcl/Tk 8.4." -#: gitk:12507 msgid "Cannot find a git repository here." msgstr "Hittar inget git-arkiv här." -#: gitk:12554 #, tcl-format msgid "Ambiguous argument '%s': both revision and filename" msgstr "Tvetydigt argument ”%s”: både revision och filnamn" -#: gitk:12566 msgid "Bad arguments to gitk:" msgstr "Felaktiga argument till gitk:"
diff --git a/gitk-git/po/ta.po b/gitk-git/po/ta.po index 0e390c5..9b9176a 100644 --- a/gitk-git/po/ta.po +++ b/gitk-git/po/ta.po
@@ -6,7 +6,7 @@ # msgid "" msgstr "" -"Project-Id-Version: gitk\n" +"Project-Id-Version: Gitk\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-05-07 08:01+0530\n" "PO-Revision-Date: 2025-05-07 09:17\n" @@ -17,360 +17,270 @@ "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: gitk:274 msgid "Couldn't get list of unmerged files:" msgstr "ஒருங்கிணைக்கப்படாத கோப்புகளின் பட்டியலைப் பெற முடியவில்லை:" -#: gitk:346 gitk:2565 msgid "Color words" msgstr "வண்ண சொற்கள்" -#: gitk:351 gitk:2565 gitk:8476 gitk:8509 msgid "Markup words" msgstr "குறிக்கப்பட்ட சொற்கள்" -#: gitk:458 msgid "Error parsing revisions:" msgstr "பிழைகளை பாகுபடுத்துதல்:" -#: gitk:524 msgid "Error executing --argscmd command:" msgstr "--argscmd கட்டளையை இயக்குவதில் பிழை:" -#: gitk:537 msgid "No files selected: --merge specified but no files are unmerged." msgstr "" -"கோப்புகள் எதுவும் தேர்ந்தெடுக்கப்படவில்லை: --ஒன்றிணை குறிப்பிடப்பட்டுள்ளது, " -"ஆனால் கோப்புகள் எதுவும் அவிழ்க்கப்படவில்லை." +"கோப்புகள் எதுவும் தேர்ந்தெடுக்கப்படவில்லை: --ஒன்றிணை குறிப்பிடப்பட்டுள்ளது, ஆனால் கோப்புகள் " +"எதுவும் அவிழ்க்கப்படவில்லை." -#: gitk:540 msgid "" "No files selected: --merge specified but no unmerged files are within file " "limit." msgstr "" -"கோப்புகள் எதுவும் தேர்ந்தெடுக்கப்படவில்லை: --ஒன்றிணை குறிப்பிடப்பட்டுள்ளது, " -"ஆனால் அவிழ்க்கப்படாத கோப்புகள் எதுவும் கோப்பு வரம்பிற்குள் இல்லை." +"கோப்புகள் எதுவும் தேர்ந்தெடுக்கப்படவில்லை: --ஒன்றிணை குறிப்பிடப்பட்டுள்ளது, ஆனால் " +"அவிழ்க்கப்படாத கோப்புகள் எதுவும் கோப்பு வரம்பிற்குள் இல்லை." -#: gitk:565 gitk:720 msgid "Error executing git log:" msgstr "அறிவிலி பதிவை இயக்குவதில் பிழை:" -#: gitk:583 gitk:736 msgid "Reading" msgstr "படித்தல்" -#: gitk:643 gitk:4736 msgid "Reading commits..." msgstr "உறுதிமொழிகளைப் படித்தல்..." -#: gitk:646 gitk:1795 gitk:4739 msgid "No commits selected" msgstr "எந்த உறுதிமொழிகளும் தேர்ந்தெடுக்கப்படவில்லை" -#: gitk:1603 gitk:4256 gitk:12883 msgid "Command line" msgstr "கட்டளை வரி" -#: gitk:1669 msgid "Can't parse git log output:" msgstr "அறிவிலி பதிவு வெளியீட்டை அலச முடியாது:" -#: gitk:1898 msgid "No commit information available" msgstr "உறுதிமொழி செய்தி எதுவும் கிடைக்கவில்லை" -#: gitk:2065 gitk:2094 gitk:4526 gitk:10016 gitk:11626 gitk:11946 msgid "OK" msgstr "சரி" -#: gitk:2096 gitk:4528 gitk:9452 gitk:9531 gitk:9661 gitk:9747 gitk:10018 -#: gitk:11627 gitk:11947 msgid "Cancel" msgstr "நீக்கறல்" -#: gitk:2249 msgid "&Update" msgstr "புதுப்பித்தல்" -#: gitk:2250 msgid "&Reload" msgstr "மீண்டும் ஏற்று" -#: gitk:2251 msgid "Reread re&ferences" msgstr "குறிப்புகளை மீண்டும் படி" -#: gitk:2252 msgid "&List references" msgstr "பட்டியல் குறிப்புகள்" -#: gitk:2254 msgid "Start git &gui" msgstr "அறிவிலி இடைமுகத்தைத் தொடங்கு" -#: gitk:2256 msgid "&Quit" msgstr "வெளியேறு" -#: gitk:2248 msgid "&File" msgstr "கோப்பு" -#: gitk:2260 msgid "&Preferences" msgstr "விருப்பத்தேர்வுகள்" -#: gitk:2259 msgid "&Edit" msgstr "திருத்து" -#: gitk:2264 msgid "&New view..." msgstr "புதிய பார்வை..." -#: gitk:2265 msgid "&Edit view..." msgstr "பார்வையைத் திருத்து..." -#: gitk:2266 msgid "&Delete view" msgstr "பார்வையை நீக்கு" -#: gitk:2268 msgid "&All files" msgstr "அனைத்து கோப்புகளும்" -#: gitk:2263 msgid "&View" msgstr "காண்க" -#: gitk:2273 gitk:2283 msgid "&About gitk" msgstr "அறிவிலிகே பற்றி" -#: gitk:2274 gitk:2288 msgid "&Key bindings" msgstr "முக்கிய பிணைப்புகள்" -#: gitk:2272 gitk:2287 msgid "&Help" msgstr "உதவி" -#: gitk:2365 gitk:8908 msgid "Commit ID:" msgstr "உறுதிமொழி அடையாளம்:" -#: gitk:2409 msgid "Row" msgstr "நிரை" -#: gitk:2447 msgid "Find" msgstr "கண்டுபிடி" -#: gitk:2475 msgid "commit" msgstr "உறுதிமொழி" -#: gitk:2479 gitk:2481 gitk:4898 gitk:4921 gitk:4945 gitk:6966 gitk:7038 -#: gitk:7123 msgid "containing:" msgstr "கொண்டிருக்கிறது:" -#: gitk:2482 gitk:3737 gitk:3742 gitk:4974 msgid "touching paths:" msgstr "தொடும் பாதைகள்:" -#: gitk:2483 gitk:4988 msgid "adding/removing string:" msgstr "சரத்தைச் சேர்ப்பது/அகற்றுவது:" -#: gitk:2484 gitk:4990 msgid "changing lines matching:" msgstr "பொருந்தக்கூடிய வரிகளை மாற்றுதல்:" -#: gitk:2493 gitk:2495 gitk:4977 msgid "Exact" msgstr "சரியான" -#: gitk:2495 gitk:5065 gitk:6934 msgid "IgnCase" msgstr "வழக்குதவிர்" -#: gitk:2495 gitk:4947 gitk:5063 gitk:6930 msgid "Regexp" msgstr "வழக்கவெளி" -#: gitk:2497 gitk:2498 gitk:5085 gitk:5115 gitk:5122 gitk:7059 gitk:7127 msgid "All fields" msgstr "அனைத்து புலங்களும்" -#: gitk:2498 gitk:5082 gitk:5115 gitk:6997 msgid "Headline" msgstr "தலைப்பு" -#: gitk:2499 gitk:5082 gitk:6997 gitk:7127 gitk:7639 msgid "Comments" msgstr "கருத்துகள்" -#: gitk:2499 gitk:5082 gitk:5087 gitk:5122 gitk:6997 gitk:7574 gitk:9086 -#: gitk:9101 msgid "Author" msgstr "நூலாசிரியர்" -#: gitk:2499 gitk:5082 gitk:6997 gitk:7576 msgid "Committer" msgstr "உறுதிமொழிபவர்" -#: gitk:2533 msgid "Search" msgstr "தேடு" -#: gitk:2541 msgid "Diff" msgstr "வேறுபாடு" -#: gitk:2543 msgid "Old version" msgstr "பழைய பதிப்பு" -#: gitk:2545 msgid "New version" msgstr "புதிய பதிப்பு" -#: gitk:2548 msgid "Lines of context" msgstr "சூழலின் வரிகள்" -#: gitk:2558 msgid "Ignore space change" msgstr "இடைவெளி மாற்றத்தை புறக்கணி" -#: gitk:2562 gitk:2564 gitk:8209 gitk:8462 msgid "Line diff" msgstr "வரி வேறுபாடு" -#: gitk:2637 msgid "Patch" msgstr "ஒட்டு" -#: gitk:2639 msgid "Tree" msgstr "மரம்" -#: gitk:2814 gitk:2835 msgid "Diff this -> selected" msgstr "இதை வேறுபடுத்துங்கள் -> தேர்ந்தெடுக்கப்பட்டது" -#: gitk:2815 gitk:2836 msgid "Diff selected -> this" msgstr "வேறுபாடு தேர்ந்தெடுக்கப்பட்டது -> இது" -#: gitk:2816 gitk:2837 msgid "Make patch" msgstr "ஒட்டு செய்" -#: gitk:2817 gitk:9510 msgid "Create tag" msgstr "குறிச்சொல்லை உருவாக்கு" -#: gitk:2818 msgid "Copy commit reference" msgstr "உறுதிமொழி குறிப்பு நகலெடு" -#: gitk:2819 gitk:9641 msgid "Write commit to file" msgstr "கோப்பில் உறவை எழுதுங்கள்" -#: gitk:2820 msgid "Create new branch" msgstr "புதிய கிளையை உருவாக்கு" -#: gitk:2821 msgid "Cherry-pick this commit" msgstr "கனி-எடு இந்த உறுதிமொழி" -#: gitk:2822 msgid "Reset HEAD branch to here" msgstr "தலை கிளையை இங்கே மீட்டமை" -#: gitk:2823 msgid "Mark this commit" msgstr "இந்த உறுதிமொழியைக் குறி" -#: gitk:2824 msgid "Return to mark" msgstr "மார்க்குக்குத் திரும்பு" -#: gitk:2825 msgid "Find descendant of this and mark" msgstr "இதன் வழித்தோன்றலைக் கண்டுபிடித்து குறி" -#: gitk:2826 msgid "Compare with marked commit" msgstr "குறிக்கப்பட்ட உறுதிப்பாட்டுடன் ஒப்பிடுக" -#: gitk:2827 gitk:2838 msgid "Diff this -> marked commit" msgstr "இதை வேறுபடுத்துங்கள் -> குறிக்கப்பட்ட உறுதிமொழி" -#: gitk:2828 gitk:2839 msgid "Diff marked commit -> this" msgstr "வேறுபாடு குறிக்கப்பட்ட உறுதிமொழி -> இது" -#: gitk:2829 msgid "Revert this commit" msgstr "இந்த உறுதிப்பாட்டை மாற்றவும்" -#: gitk:2845 msgid "Check out this branch" msgstr "இந்த கிளையைப் பாருங்கள்" -#: gitk:2846 msgid "Rename this branch" msgstr "இந்த கிளையை மறுபெயரிடு" -#: gitk:2847 msgid "Remove this branch" msgstr "இந்த கிளையை அகற்று" -#: gitk:2848 msgid "Copy branch name" msgstr "கிளை பெயரை நகலெடு" -#: gitk:2855 msgid "Highlight this too" msgstr "இதை முன்னிலைப்படுத்து" -#: gitk:2856 msgid "Highlight this only" msgstr "இதை முன்னிலைப்படுத்து" -#: gitk:2857 msgid "External diff" msgstr "வெளிப்புற வேறுபாடு" -#: gitk:2858 msgid "Blame parent commit" msgstr "பெற்றோரை குற்றம் சாட்டு" -#: gitk:2859 msgid "Copy path" msgstr "நகல் பாதை" -#: gitk:2866 msgid "Show origin of this line" msgstr "இந்த வரியின் தோற்றத்தைக் காட்டு" -#: gitk:2867 msgid "Run git gui blame on this line" msgstr "இந்த வரியில் அறிவிலி இடைமுகம் பழியை இயக்கு" -#: gitk:3221 msgid "About gitk" msgstr "அறிவிலிகே பற்றி" -#: gitk:3223 msgid "" "\n" "Gitk - a commit viewer for git\n" @@ -386,697 +296,533 @@ "\n" "குனு பொது பொதுமக்கள் உரிமத்தின் விதிமுறைகளின் கீழ் பயன்படுத்தவும் மறுபகிர்வு செய்யவும்" -#: gitk:3231 gitk:3298 gitk:10231 msgid "Close" msgstr "மூடு" -#: gitk:3252 msgid "Gitk key bindings" msgstr "அறிவிலிகே விசை பிணைப்புகள்" -#: gitk:3255 msgid "Gitk key bindings:" msgstr "அறிவிலிகே விசை பிணைப்புகள்:" -#: gitk:3257 #, tcl-format msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\tவெளியேறு" -#: gitk:3258 #, tcl-format msgid "<%s-W>\t\tClose window" msgstr "<%s-w>\t\tசாளரத்தை மூடு" -#: gitk:3259 msgid "<Home>\t\tMove to first commit" msgstr "<வீடு> முதல் உறுதிமொழிக்கு நகர்த்து" -#: gitk:3260 msgid "<End>\t\tMove to last commit" msgstr "<முடி> கடைசி உறுதிமொழிக்கு நகர்த்து" -#: gitk:3261 msgid "<Up>, p, k\tMove up one commit" msgstr "<மேலே>, பி, கே\tஒரு உறுதிமொழியை மேலே நகர்த்து" -#: gitk:3262 msgid "<Down>, n, j\tMove down one commit" msgstr "<கீழ்>, n, j\tஒரு உறுதிமொழியை கீழே நகர்த்து" -#: gitk:3263 msgid "<Left>, z, h\tGo back in history list" msgstr "<இடது>, z, h\tவரலாற்று பட்டியலில் திரும்பிச் செல்" -#: gitk:3264 msgid "<Right>, x, l\tGo forward in history list" msgstr "<வலது>, x, l\tவரலாற்று பட்டியலில் முன்னோக்கி செல்" -#: gitk:3265 #, tcl-format msgid "<%s-n>\tGo to n-th parent of current commit in history list" -msgstr "" -"<%s-n> வரலாற்று பட்டியலில் தற்போதைய உறுதிப்பாட்டின் n- வது பெற்றோரிடம் " -"செல்" +msgstr "<%s-n> வரலாற்று பட்டியலில் தற்போதைய உறுதிப்பாட்டின் n- வது பெற்றோரிடம் செல்" -#: gitk:3266 msgid "<PageUp>\tMove up one page in commit list" msgstr "<பக்கம்மேல்>\tஉறுதிமொழி பட்டியலில் ஒரு பக்கத்தை நகர்த்து" -#: gitk:3267 msgid "<PageDown>\tMove down one page in commit list" msgstr "<பக்கம்கீழ்>\tஉறுதிமொழி பட்டியலில் ஒரு பக்கத்தை நகர்த்து" -#: gitk:3268 #, tcl-format msgid "<%s-Home>\tScroll to top of commit list" msgstr "<%s-வீடு>\tஉறுதிமொழி பட்டியலை மேல் பகுதிக்கு உருட்டவும்" -#: gitk:3269 #, tcl-format msgid "<%s-End>\tScroll to bottom of commit list" msgstr "<%s-முடி> உறுதிமொழி பட்டியலின் கீழ் பகுதிக்கு உருட்டவும்" -#: gitk:3270 #, tcl-format msgid "<%s-Up>\tScroll commit list up one line" msgstr "<%s-மேலே>\tஉறுதிமொழி பட்டியலை ஒரு வரி மேலே உருட்டவும்" -#: gitk:3271 #, tcl-format msgid "<%s-Down>\tScroll commit list down one line" msgstr "<%s-கீழ்>\tஉறுதிமொழி பட்டியலை ஒரு வரி கீழே உருட்டவும்" -#: gitk:3272 #, tcl-format msgid "<%s-PageUp>\tScroll commit list up one page" msgstr "<%s-பக்கம்மேலே>\tஉறுதிமொழி பட்டியலை ஒரு பக்கம் மேலே உருட்டவும்" -#: gitk:3273 #, tcl-format msgid "<%s-PageDown>\tScroll commit list down one page" msgstr "<%s-பக்கம்கீழ்>\tஉறுதிமொழி பட்டியலை ஒரு பக்கம் கீழே உருட்டவும்" -#: gitk:3274 msgid "<Shift-Up>\tFind backwards (upwards, later commits)" msgstr "<உயர்த்து-மேலே>\tபின்னோக்கி கண்டுபிடி (மேல்நோக்கி, பின்னர் உறுதிமொழிகள்)" -#: gitk:3275 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" -msgstr "" -"<உயர்த்து-கீழே>\tமுன்னோக்குகளைக் கண்டறியவும் (கீழ்நோக்கி, முந்தைய " -"உறுதிமொழிகள்)" +msgstr "<உயர்த்து-கீழே>\tமுன்னோக்குகளைக் கண்டறியவும் (கீழ்நோக்கி, முந்தைய உறுதிமொழிகள்)" -#: gitk:3276 msgid "<Delete>, b\tScroll diff view up one page" msgstr "<நீக்கு>, b\tசுருள் வேறுபாடு ஒரு பக்கத்தை மேலே காண்க" -#: gitk:3277 msgid "<Backspace>\tScroll diff view up one page" msgstr "<பின்வெளி>\tசுருள் வேறுபாடு ஒரு பக்கத்தை மேலே காண்க" -#: gitk:3278 msgid "<Space>\t\tScroll diff view down one page" msgstr "<Space>\t\tசுருள் வேறுபாடு ஒரு பக்கத்தைக் கீழே காண்க" -#: gitk:3279 msgid "u\t\tScroll diff view up 18 lines" msgstr "u\t\tசுருள் வேறுபாடு 18 வரிகளை மேலே காண்க" -#: gitk:3280 msgid "d\t\tScroll diff view down 18 lines" msgstr "d\t\tசுருள் வேறுபாடு 18 வரிகளைக் கீழே காண்க" -#: gitk:3281 #, tcl-format msgid "<%s-F>\t\tFind" msgstr "<%s-F>\t\tகண்டுபிடி" -#: gitk:3282 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" msgstr "<%s-G>\t\tஅடுத்த கண்டுபிடிப்பு வெற்றிக்கு செல்" -#: gitk:3283 msgid "<Return>\tMove to next find hit" msgstr "<திரும்பு>\tஅடுத்ததைக் கண்டுபிடி" -#: gitk:3284 msgid "g\t\tGo to commit" msgstr "g\t\tஉறுதிமொழிக்கு செல்" -#: gitk:3285 msgid "/\t\tFocus the search box" msgstr "/\t\tதேடல் பெட்டியில் கவனம் செலுத்து" -#: gitk:3286 msgid "?\t\tMove to previous find hit" msgstr "?\t\tமுந்தைய கண்டுபிடிப்பு வெற்றிக்கு செல்" -#: gitk:3287 msgid "f\t\tScroll diff view to next file" msgstr "f\t\tஅடுத்த கோப்பிற்கு உருள் வேறுபாடு பார்வை" -#: gitk:3288 #, tcl-format msgid "<%s-S>\t\tSearch for next hit in diff view" msgstr "<%s-S>\t\tவேறுபாடு பார்வையில் அடுத்த வெற்றியைத் தேடுங்கள்" -#: gitk:3289 #, tcl-format msgid "<%s-R>\t\tSearch for previous hit in diff view" msgstr "<%s-r> வேறுபட்ட பார்வையில் முந்தைய வெற்றியைத் தேடுங்கள்" -#: gitk:3290 #, tcl-format msgid "<%s-KP+>\tIncrease font size" msgstr "<%s-KP+>\tஎழுத்துரு அளவை அதிகரி" -#: gitk:3291 #, tcl-format msgid "<%s-plus>\tIncrease font size" msgstr "<%s-plus>\tஎழுத்துரு அளவை அதிகரி" -#: gitk:3292 #, tcl-format msgid "<%s-KP->\tDecrease font size" msgstr "<%s-KP->\tஎழுத்துரு அளவைக் குறை" -#: gitk:3293 #, tcl-format msgid "<%s-minus>\tDecrease font size" msgstr "<%s-minus>\tஎழுத்துரு அளவைக் குறை" -#: gitk:3294 msgid "<F5>\t\tUpdate" msgstr "<F5>\t\tபுதுப்பிப்பு" -#: gitk:3761 gitk:3770 #, tcl-format msgid "Error creating temporary directory %s:" msgstr "தற்காலிக அடைவு %s ஐ உருவாக்குவது பிழை:" -#: gitk:3783 #, tcl-format msgid "Error getting \"%s\" from %s:" msgstr "%s இலிருந்து \" %s\" பெறுவது பிழை:" -#: gitk:3846 msgid "command failed:" msgstr "கட்டளை தோல்வியுற்றது:" -#: gitk:3995 msgid "No such commit" msgstr "அத்தகைய உறுதிமொழி இல்லை" -#: gitk:4009 msgid "git gui blame: command failed:" msgstr "அறிவிலி இடைமுக பழி: கட்டளை தோல்வியுற்றது:" -#: gitk:4040 #, tcl-format msgid "Couldn't read merge head: %s" msgstr "ஒன்றிணைப்பு தலையைப் படிக்க முடியவில்லை: %s" -#: gitk:4048 #, tcl-format msgid "Error reading index: %s" msgstr "பிழை வாசிப்பு குறியீடு: %s" -#: gitk:4073 #, tcl-format msgid "Couldn't start git blame: %s" msgstr "அறிவிலி பழியைத் தொடங்க முடியவில்லை: %s" -#: gitk:4076 gitk:6965 msgid "Searching" msgstr "தேடுகிறது" -#: gitk:4108 #, tcl-format msgid "Error running git blame: %s" msgstr "பிழை இயங்கும் அறிவிலி பழி: %s" -#: gitk:4136 #, tcl-format msgid "That line comes from commit %s, which is not in this view" -msgstr "" -"அந்த வரி உறுதிமொழி %s என்பதிலிருந்து வருகிறது, இது இந்த பார்வையில் இல்லை" +msgstr "அந்த வரி உறுதிமொழி %s என்பதிலிருந்து வருகிறது, இது இந்த பார்வையில் இல்லை" -#: gitk:4150 msgid "External diff viewer failed:" msgstr "வெளிப்புற வேறுபாடு பார்வையாளர் தோல்வியுற்றது:" -#: gitk:4254 msgid "All files" msgstr "அனைத்து கோப்புகளும்" -#: gitk:4278 msgid "View" msgstr "காண்க" -#: gitk:4281 msgid "Gitk view definition" msgstr "அறிவிலிகே பார்வை வரையறை" -#: gitk:4285 msgid "Remember this view" msgstr "இந்த பார்வையை நினைவில் கொள்ளுங்கள்" -#: gitk:4286 msgid "References (space separated list):" msgstr "குறிப்புகள் (இடைவெளி பிரிக்கப்பட்ட பட்டியல்):" -#: gitk:4287 msgid "Branches & tags:" msgstr "கிளைகள் மற்றும் குறிச்சொற்கள்:" -#: gitk:4288 msgid "All refs" msgstr "அனைத்து குறிப்புகள்" -#: gitk:4289 msgid "All (local) branches" msgstr "அனைத்து (உள்ளக) கிளைகளும்" -#: gitk:4290 msgid "All tags" msgstr "அனைத்து குறிச்சொற்களும்" -#: gitk:4291 msgid "All remote-tracking branches" msgstr "அனைத்து தொலை-கண்காணிப்பு கிளைகளும்" -#: gitk:4292 msgid "Commit Info (regular expressions):" msgstr "உறுதிமொழி செய்தி (வழக்கமான வெளிப்பாடுகள்):" -#: gitk:4293 msgid "Author:" msgstr "ஆசிரியர்:" -#: gitk:4294 msgid "Committer:" msgstr "உறுதிமொழிபவர்:" -#: gitk:4295 msgid "Commit Message:" msgstr "உறுதிமொழி செய்தி:" -#: gitk:4296 msgid "Matches all Commit Info criteria" msgstr "அனைத்து உறுதிமொழி செய்தி அளவுகோல்களையும் பொருத்துகிறது" -#: gitk:4297 msgid "Matches no Commit Info criteria" msgstr "உறுதிமொழி செய்தி அளவுகோல்களுடன் பொருந்தவில்லை" -#: gitk:4298 msgid "Changes to Files:" msgstr "கோப்புகளில் மாற்றங்கள்:" -#: gitk:4299 msgid "Fixed String" msgstr "நிலையான சரம்" -#: gitk:4300 msgid "Regular Expression" msgstr "வழக்கமான வெளிப்பாடு" -#: gitk:4301 msgid "Search string:" msgstr "தேடல் சரம்:" -#: gitk:4302 msgid "" "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" msgstr "" -"உறுதிமொழி தேதிகள் (\"2 வாரங்களுக்கு முன்பு\", \"2009-01-16 15:27:38\", \"மார்ச் 17, " -"2009 15:27:38\"):" +"உறுதிமொழி தேதிகள் (\"2 வாரங்களுக்கு முன்பு\", \"2009-01-16 15:27:38\", \"மார்ச் " +"17, 2009 15:27:38\"):" -#: gitk:4303 msgid "Since:" msgstr "பின்னர்:" -#: gitk:4304 msgid "Until:" msgstr "வரை:" -#: gitk:4305 msgid "Limit and/or skip a number of revisions (positive integer):" -msgstr "" -"பல திருத்தங்களை (நேர்மறை முழு எண்) கட்டுப்படுத்து மற்றும்/அல்லது தவிர்:" +msgstr "பல திருத்தங்களை (நேர்மறை முழு எண்) கட்டுப்படுத்து மற்றும்/அல்லது தவிர்:" -#: gitk:4306 msgid "Number to show:" msgstr "காண்பிக்க எண்:" -#: gitk:4307 msgid "Number to skip:" msgstr "தவிர்க்க எண்:" -#: gitk:4308 msgid "Miscellaneous options:" msgstr "இதர விருப்பங்கள்:" -#: gitk:4309 msgid "Strictly sort by date" msgstr "கண்டிப்பாக தேதியின்படி வரிசைப்படுத்து" -#: gitk:4310 msgid "Mark branch sides" msgstr "கிளை பக்கங்களைக் குறி" -#: gitk:4311 msgid "Limit to first parent" msgstr "முதல் பெற்றோருக்கு வரம்பு" -#: gitk:4312 msgid "Simple history" msgstr "எளிய வரலாறு" -#: gitk:4313 msgid "Additional arguments to git log:" msgstr "அறிவிலி பதிவுக்கு கூடுதல் வாதங்கள்:" -#: gitk:4314 msgid "Enter files and directories to include, one per line:" msgstr "சேர்க்க கோப்புகள் மற்றும் கோப்பகங்களை உள்ளிடவும், ஒரு வரிக்கு ஒன்று:" -#: gitk:4315 msgid "Command to generate more commits to include:" msgstr "சேர்க்க கூடுதல் உறுதிமொழிகளை உருவாக்க கட்டளை:" -#: gitk:4439 msgid "Gitk: edit view" msgstr "அறிவிலிகே: திருத்து பார்வை" -#: gitk:4447 msgid "-- criteria for selecting revisions" msgstr "-- திருத்தங்களைத் தேர்ந்தெடுப்பதற்கான அளவுகோல்கள்" -#: gitk:4452 msgid "View Name" msgstr "பெயரைக் காண்க" -#: gitk:4527 msgid "Apply (F5)" msgstr "இடு (F5)" -#: gitk:4565 msgid "Error in commit selection arguments:" msgstr "உறுதிமொழி தேர்வு வாதங்களில் பிழை:" -#: gitk:4620 gitk:4673 gitk:5135 gitk:5149 gitk:6419 gitk:12820 gitk:12821 msgid "None" msgstr "எதுவுமில்லை" -#: gitk:5232 gitk:5237 msgid "Descendant" msgstr "வழித்தோன்றல்" -#: gitk:5233 msgid "Not descendant" msgstr "வழித்தோன்றல் அல்ல" -#: gitk:5240 gitk:5245 msgid "Ancestor" msgstr "மூதாதையர்" -#: gitk:5241 msgid "Not ancestor" msgstr "மூதாதையர் அல்ல" -#: gitk:5535 msgid "Local changes checked in to index but not committed" -msgstr "" -"உள்ளக மாற்றங்கள் குறியீட்டில் சரிபார்க்கப்பட்டன, ஆனால் உறுதிமொழியவில்லை" +msgstr "உள்ளக மாற்றங்கள் குறியீட்டில் சரிபார்க்கப்பட்டன, ஆனால் உறுதிமொழியவில்லை" -#: gitk:5571 msgid "Local uncommitted changes, not checked in to index" msgstr "உள்ளக உறுதிமொழியாத மாற்றங்கள், குறியீட்டில் சரிபார்க்கப்படவில்லை" -#: gitk:7319 msgid "Error starting web browser:" msgstr "வலை உலாவியைத் தொடங்குவதில் பிழை:" -#: gitk:7380 msgid "and many more" msgstr "மற்றும் மேலும் பல" -#: gitk:7383 msgid "many" msgstr "பல" -#: gitk:7578 msgid "Tags:" msgstr "குறிச்சொற்கள்:" -#: gitk:7595 gitk:7601 gitk:9081 msgid "Parent" msgstr "பெற்றோர்" -#: gitk:7606 msgid "Child" msgstr "குழந்தை" -#: gitk:7615 msgid "Branch" msgstr "கிளை" -#: gitk:7618 msgid "Follows" msgstr "பின்வருமாறு" -#: gitk:7621 msgid "Precedes" msgstr "முன்னால்" -#: gitk:8216 #, tcl-format msgid "Error getting diffs: %s" msgstr "வேறுபாடு பெறுவதில் பிழை: %s" -#: gitk:8906 msgid "Goto:" msgstr "செல்:" -#: gitk:8927 #, tcl-format msgid "Short commit ID %s is ambiguous" msgstr "குறுகிய உறுதிமொழி அடையாளம் %s தெளிவற்றவை" -#: gitk:8934 #, tcl-format msgid "Revision %s is not known" msgstr "திருத்தம் %s தெரியவில்லை" -#: gitk:8944 #, tcl-format msgid "Commit ID %s is not known" msgstr "உறுதிமொழி அடையாளம் %s அறியப்படவில்லை" -#: gitk:8946 #, tcl-format msgid "Revision %s is not in the current view" msgstr "திருத்தம் %s தற்போதைய பார்வையில் இல்லை" -#: gitk:9088 gitk:9103 msgid "Date" msgstr "திகதி" -#: gitk:9091 msgid "Children" msgstr "குழந்தைகள்" -#: gitk:9154 #, tcl-format msgid "Reset %s branch to here" msgstr "%s கிளையை இங்கே மீட்டமை" -#: gitk:9156 msgid "Detached head: can't reset" msgstr "பிரிக்கப்பட்ட தலை: மீட்டமைக்க முடியாது" -#: gitk:9261 gitk:9267 msgid "Skipping merge commit " msgstr "ஒன்றிணை உறுதிமொழியை தவர்கிறது " -#: gitk:9276 gitk:9281 msgid "Error getting patch ID for " msgstr "ஒட்டு அடையாளத்தைப் பெறுவதில் பிழை" -#: gitk:9277 gitk:9282 msgid " - stopping\n" msgstr "- நிறுத்துதல்\n" -#: gitk:9287 gitk:9290 gitk:9298 gitk:9312 gitk:9321 msgid "Commit " msgstr "உறுதிமொழி" -#: gitk:9291 msgid "" " is the same patch as\n" " " -msgstr "அதே ஒட்டு\n" +msgstr "" +"அதே ஒட்டு\n" " " -#: gitk:9299 msgid "" " differs from\n" " " -msgstr "இருந்து வேறுபடுகிறது\n" +msgstr "" +"இருந்து வேறுபடுகிறது\n" " " -#: gitk:9301 msgid "" "Diff of commits:\n" "\n" -msgstr "உறுதிமொழியின் வேறுபாடு:\n" +msgstr "" +"உறுதிமொழியின் வேறுபாடு:\n" "\n" -#: gitk:9313 gitk:9322 #, tcl-format msgid " has %s children - stopping\n" msgstr "%s குழந்தைகள் உள்ளனர் - நிறுத்துதல்\n" -#: gitk:9341 #, tcl-format msgid "Error writing commit to file: %s" msgstr "உறுதிமொழி கோப்பில் எழுதுதல் பிழை: %s" -#: gitk:9347 #, tcl-format msgid "Error diffing commits: %s" msgstr "உறுதிமொழிகள் வேறுபாடு பிழை: %s" -#: gitk:9393 msgid "Top" msgstr "மேலே" -#: gitk:9394 msgid "From" msgstr "இருந்து" -#: gitk:9399 msgid "To" msgstr "பெறுநர்" -#: gitk:9423 msgid "Generate patch" msgstr "ஒட்டை உருவாக்கு" -#: gitk:9425 msgid "From:" msgstr "இருந்து:" -#: gitk:9434 msgid "To:" msgstr "இதற்கு:" -#: gitk:9443 msgid "Reverse" msgstr "தலைகீழ்" -#: gitk:9445 gitk:9655 msgid "Output file:" msgstr "வெளியீட்டு கோப்பு:" -#: gitk:9451 msgid "Generate" msgstr "உருவாக்கு" -#: gitk:9489 msgid "Error creating patch:" msgstr "ஒட்டை உருவாக்கு பிழை:" -#: gitk:9512 gitk:9643 gitk:9731 msgid "ID:" msgstr "அடையாளம்:" -#: gitk:9521 msgid "Tag name:" msgstr "குறிச்சொல் பெயர்:" -#: gitk:9524 msgid "Tag message is optional" msgstr "குறிச்சொல் செய்தி விருப்பமானது" -#: gitk:9526 msgid "Tag message:" msgstr "குறிச்சொல் செய்தி:" -#: gitk:9530 gitk:9701 msgid "Create" msgstr "உருவாக்கு" -#: gitk:9548 msgid "No tag name specified" msgstr "குறிச்சொல் பெயர் குறிப்பிடப்படவில்லை" -#: gitk:9552 #, tcl-format msgid "Tag \"%s\" already exists" msgstr "குறிச்சொல் \"%s\" ஏற்கனவே உள்ளது" -#: gitk:9562 msgid "Error creating tag:" msgstr "குறிச்சொல்லை உருவாக்கு பிழை:" -#: gitk:9652 msgid "Command:" msgstr "கட்டளை:" -#: gitk:9660 msgid "Write" msgstr "எழுது" -#: gitk:9678 msgid "Error writing commit:" msgstr "பிழை எழுதுதல் உறுதிமொழி:" -#: gitk:9700 msgid "Create branch" msgstr "கிளையை உருவாக்கு" -#: gitk:9716 #, tcl-format msgid "Rename branch %s" msgstr "%s கிளையை மறுபெயரிடு" -#: gitk:9717 msgid "Rename" msgstr "மறுபெயரிடு" -#: gitk:9741 msgid "Name:" msgstr "பெயர்:" -#: gitk:9765 msgid "Please specify a name for the new branch" msgstr "புதிய கிளைக்கு ஒரு பெயரைக் குறிப்பிடு" -#: gitk:9770 #, tcl-format msgid "Branch '%s' already exists. Overwrite?" msgstr "கிளை '%s' ஏற்கனவே உள்ளது. மேலெழுதவா?" -#: gitk:9814 msgid "Please specify a new name for the branch" msgstr "கிளைக்கு ஒரு புதிய பெயரைக் குறிப்பிடு" -#: gitk:9877 #, tcl-format msgid "Commit %s is already included in branch %s -- really re-apply it?" -msgstr "" -"உறுதிமொழி %s ஏற்கனவே கிளை %s சேர்க்கப்பட்டுள்ளன-உண்மையில் அதை மீண்டும் இடவா?" +msgstr "உறுதிமொழி %s ஏற்கனவே கிளை %s சேர்க்கப்பட்டுள்ளன-உண்மையில் அதை மீண்டும் இடவா?" -#: gitk:9882 msgid "Cherry-picking" msgstr "கனி எடுக்கும்" -#: gitk:9891 #, tcl-format msgid "" "Cherry-pick failed because of local changes to file '%s'.\n" @@ -1085,7 +831,6 @@ "'%s' கோப்பில் உள்ளக மாற்றங்கள் காரணமாக கனி-எடு தோல்வியடைந்தது. \n" "தயவுசெய்து உங்கள் மாற்றங்களைச் உறுதிமொழி, மீட்டமை அல்லது சேமி பிறகு மீண்டும் முயற்சி." -#: gitk:9897 msgid "" "Cherry-pick failed because of merge conflict.\n" "Do you wish to run git citool to resolve it?" @@ -1093,31 +838,24 @@ "ஒன்றிணைக்கும் மோதல் காரணமாக கனி-எடு தோல்வியடைந்தது. \n" "அதை தீர்க்க அறிவிலி சிஐகருவியை இயக்க விரும்புகிறீர்களா?" -#: gitk:9913 gitk:9971 msgid "No changes committed" msgstr "எந்த மாற்றங்களும் உறுதிமொழியப்படவில்லை" -#: gitk:9940 #, tcl-format msgid "Commit %s is not included in branch %s -- really revert it?" -msgstr "" -"உறுதிமொழி %s கிளை %s சேர்க்கப்படவில்லை - உண்மையில் அதை மீட்டெடுக்கவா?" +msgstr "உறுதிமொழி %s கிளை %s சேர்க்கப்படவில்லை - உண்மையில் அதை மீட்டெடுக்கவா?" -#: gitk:9945 msgid "Reverting" msgstr "மீட்டெடுத்தல்" -#: gitk:9953 #, 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 "" -"பின்வரும் கோப்புகளில் உள்ளக மாற்றங்கள் காரணமாக மீட்டெடு தோல்வியுற்றது:%s " -"தயவுசெய்து உங்கள் மாற்றங்களைச் உறுதிமொழி, மீட்டமை அல்லது " -"சேமி மற்றும் மீண்டும் முயற்சி." +"பின்வரும் கோப்புகளில் உள்ளக மாற்றங்கள் காரணமாக மீட்டெடு தோல்வியுற்றது:%s தயவுசெய்து உங்கள் " +"மாற்றங்களைச் உறுதிமொழி, மீட்டமை அல்லது சேமி மற்றும் மீண்டும் முயற்சி." -#: gitk:9957 msgid "" "Revert failed because of merge conflict.\n" " Do you wish to run git citool to resolve it?" @@ -1125,30 +863,22 @@ "ஒன்றிணைக்கும் மோதல் காரணமாக மீட்டெடு தோல்வியடைந்தது. \n" "அதை தீர்க்க அறிவிலி சிஐகருவியை இயக்க விரும்புகிறீர்களா?" -#: gitk:10000 msgid "Confirm reset" msgstr "மீட்டமைப்பை உறுதிப்படுத்து" -#: gitk:10002 #, tcl-format msgid "Reset branch %s to %s?" msgstr "%s கிளையை %s க்கு மீட்டமைக்கவா?" -#: gitk:10004 msgid "Reset type:" msgstr "மீட்டமை வகை:" -#: gitk:10007 msgid "Soft: Leave working tree and index untouched" -msgstr "" -"மென்மை: வேலை செய்யும் மரம் மற்றும் குறியீட்டைத் தீண்டாமல் விடு" +msgstr "மென்மை: வேலை செய்யும் மரம் மற்றும் குறியீட்டைத் தீண்டாமல் விடு" -#: gitk:10010 msgid "Mixed: Leave working tree untouched, reset index" -msgstr "" -"கலப்பு: வேலை செய்யும் மரத்தை தீண்டாமல் விடு, குறியீட்டை மீட்டமை" +msgstr "கலப்பு: வேலை செய்யும் மரத்தை தீண்டாமல் விடு, குறியீட்டை மீட்டமை" -#: gitk:10013 msgid "" "Hard: Reset working tree and index\n" "(discard ALL local changes)" @@ -1156,24 +886,19 @@ "கடினம்: வேலை செய்யும் மரம் மற்றும் குறியீட்டை மீட்டமை \n" "(அனைத்து உள்ளக மாற்றங்களையும் நிராகரி)" -#: gitk:10030 msgid "Resetting" msgstr "மீட்டமைத்தல்" -#: gitk:10103 #, tcl-format msgid "A local branch named %s exists already" msgstr "%s என்ற உள்ளக கிளை ஏற்கனவே உள்ளது" -#: gitk:10111 msgid "Checking out" msgstr "சரிபார்" -#: gitk:10170 msgid "Cannot delete the currently checked-out branch" msgstr "தற்போது சரிபார்க்கப்பட்ட கிளையை நீக்க முடியாது" -#: gitk:10176 #, tcl-format msgid "" "The commits on branch %s aren't on any other branch.\n" @@ -1182,254 +907,193 @@ "கிளை %s மீதான உறுதிமொழிகள் வேறு எந்த கிளையிலும் இல்லை. \n" "உண்மையில் கிளை %s நீக்கவா?" -#: gitk:10207 #, tcl-format msgid "Tags and heads: %s" msgstr "குறிச்சொற்கள் மற்றும் தலைகள்: %s" -#: gitk:10224 msgid "Filter" msgstr "வடிப்பி" -#: gitk:10531 msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." msgstr "" -"உறுதிமொழி இடவியல் தகவலை படிப்பதில் பிழை; கிளை மற்றும் அதற்கு " -"முந்தைய/பின்வரும் குறிச்சொல் செய்தி முழுமையடையாது." +"உறுதிமொழி இடவியல் தகவலை படிப்பதில் பிழை; கிளை மற்றும் அதற்கு முந்தைய/பின்வரும் " +"குறிச்சொல் செய்தி முழுமையடையாது." -#: gitk:11508 msgid "Tag" msgstr "குறிச்சொல்" -#: gitk:11512 msgid "Id" msgstr "அடையாளம்" -#: gitk:11595 msgid "Gitk font chooser" msgstr "அறிவிலிகே எழுத்துரு தேர்வு" -#: gitk:11612 msgid "B" msgstr "பி" -#: gitk:11615 msgid "I" msgstr "ஐ" -#: gitk:11734 msgid "Commit list display options" msgstr "உறுதிமொழி பட்டியல் காட்சி விருப்பங்கள்" -#: gitk:11737 msgid "Maximum graph width (lines)" msgstr "அதிகபட்ச வரைபட அகலம் (கோடுகள்)" -#: gitk:11741 #, no-tcl-format msgid "Maximum graph width (% of pane)" msgstr "அதிகபட்ச வரைபட அகலம் (பலகத்தின் %)" -#: gitk:11744 msgid "Show local changes" msgstr "உள்ளக மாற்றங்களைக் காட்டு" -#: gitk:11747 msgid "Hide remote refs" msgstr "தொலை குறிகளை மறை" -#: gitk:11751 msgid "Copy commit ID to clipboard" msgstr "இடைநிலைப்பலகைக்கு அடையாளத்தை நகலெடு" -#: gitk:11755 msgid "Copy commit ID to X11 selection" msgstr "உறுதிமொழி அடையாளத்தை ஃ11 பகுதிக்கு நகலெடு" -#: gitk:11760 msgid "Length of commit ID to copy" msgstr "நகலெடுக்க உறுதிமொழி அடையாளத்தின் நீளம்" -#: gitk:11763 msgid "Diff display options" msgstr "வேறுபாடு காட்சி விருப்பங்கள்" -#: gitk:11765 msgid "Tab spacing" msgstr "தாவல் இடைவெளி" -#: gitk:11769 msgid "Wrap comment text" msgstr "கருத்து உரையை மடி" -#: gitk:11774 msgid "Wrap other text" msgstr "மற்ற உரையை மடி" -#: gitk:11779 msgid "Display nearby tags/heads" msgstr "அருகிலுள்ள குறிச்சொற்கள்/தலைகளைக் காண்பி" -#: gitk:11782 msgid "Maximum # tags/heads to show" msgstr "காண்பிக்க அதிகபட்ச # குறிச்சொற்கள்/தலைகள்" -#: gitk:11785 msgid "Limit diffs to listed paths" msgstr "பட்டியலிடப்பட்ட பாதைகளுக்கு வரம்பு வேறுபடுகிறது" -#: gitk:11788 msgid "Support per-file encodings" msgstr "ஒரு கோப்பு குறியீடுகளை ஆதரி" -#: gitk:11794 gitk:11961 msgid "External diff tool" msgstr "வெளிப்புற வேறுபாடு கருவி" -#: gitk:11795 msgid "Choose..." msgstr "தேர்வு..." -#: gitk:11802 msgid "Web browser" msgstr "வலை உலாவி" -#: gitk:11807 msgid "General options" msgstr "பொது விருப்பங்கள்" -#: gitk:11810 msgid "Use themed widgets" msgstr "கருப்பொருள் நிரல்பலகைகளைப் பயன்படுத்து" -#: gitk:11812 msgid "(change requires restart)" msgstr "(மாற்றத்திற்கு மறுதொடக்கம் தேவை)" -#: gitk:11814 msgid "(currently unavailable)" msgstr "(தற்போது கிடைக்கவில்லை)" -#: gitk:11826 msgid "Colors: press to choose" msgstr "நிறங்கள்: தேர்வு செய்ய அழுத்தவும்" -#: gitk:11829 msgid "Interface" msgstr "இடைமுகம்" -#: gitk:11830 msgid "interface" msgstr "இடைமுகம்" -#: gitk:11833 msgid "Background" msgstr "பின்னணி" -#: gitk:11834 gitk:11876 msgid "background" msgstr "பின்னணி" -#: gitk:11837 msgid "Foreground" msgstr "முன்புறம்" -#: gitk:11838 msgid "foreground" msgstr "முன்புறம்" -#: gitk:11841 msgid "Diff: old lines" msgstr "வேறுபாடு: பழைய வரிகள்" -#: gitk:11842 msgid "diff old lines" msgstr "பழைய வரிகள் வேறுபாடு" -#: gitk:11846 msgid "Diff: old lines bg" msgstr "வேறுபாடு: பழைய வரிகள் பின்ணனி" -#: gitk:11848 msgid "diff old lines bg" msgstr "பழைய வரிகள் பின்ணனி வேறுபாடு" -#: gitk:11852 msgid "Diff: new lines" msgstr "வேறுபாடு: புதிய கோடுகள்" -#: gitk:11853 msgid "diff new lines" msgstr "புதிய வரிகள் வேறுபாடு" -#: gitk:11857 msgid "Diff: new lines bg" msgstr "வேறுபாடு: புதிய வரிகள் பின்ணனி" -#: gitk:11859 msgid "diff new lines bg" msgstr "புதிய வரிகளை பின்ணனி வேறுபாடு" -#: gitk:11863 msgid "Diff: hunk header" msgstr "வேறுபாடு: அங்க் தலைப்பு" -#: gitk:11865 msgid "diff hunk header" msgstr "அங்க் தலைப்பு வேறுபாடு" -#: gitk:11869 msgid "Marked line bg" msgstr "குறிக்கப்பட்ட வரி பின்னணி" -#: gitk:11871 msgid "marked line background" msgstr "குறிக்கப்பட்ட வரி பின்னணி" -#: gitk:11875 msgid "Select bg" msgstr "பின்னணி தேர்வு" -#: gitk:11884 msgid "Fonts: press to choose" msgstr "எழுத்துருக்கள்: தேர்வு செய்ய அழுத்து" -#: gitk:11886 msgid "Main font" msgstr "முதன்மையான எழுத்துரு" -#: gitk:11887 msgid "Diff display font" msgstr "காட்சி எழுத்துரு வேறுபாடு" -#: gitk:11888 msgid "User interface font" msgstr "பயனர் இடைமுக எழுத்துரு" -#: gitk:11910 msgid "Gitk preferences" msgstr "அறிவிலிகே விருப்பத்தேர்வுகள்" -#: gitk:11919 msgid "General" msgstr "பொது" -#: gitk:11920 msgid "Colors" msgstr "நிறங்கள்" -#: gitk:11921 msgid "Fonts" msgstr "எழுத்துருக்கள்" -#: gitk:11971 #, tcl-format msgid "Gitk: choose color for %s" msgstr "அறிவிலிகே: %s க்கு வண்ணத்தைத் தேர்வுசெய்க" -#: gitk:12490 msgid "" "Sorry, gitk cannot run with this version of Tcl/Tk.\n" " Gitk requires at least Tcl/Tk 8.4." @@ -1437,16 +1101,13 @@ "மன்னிக்கவும், டிசிஎல்/டிகேயின் இந்த பதிப்பைக் கொண்டு அறிவிலிகே இயக்க முடியாது. \n" "அறிவிலிகேவுக்கு குறைந்தபட்சம் டிசிஎல்/டிகே 8.4 தேவைப்படுகிறது." -#: gitk:12711 msgid "Cannot find a git repository here." msgstr "இங்கே ஒரு அறிவிலி களஞ்சியத்தைக் கண்டுபிடிக்க முடியவில்லை." -#: gitk:12758 #, tcl-format msgid "Ambiguous argument '%s': both revision and filename" msgstr "தெளிவற்ற வாதம் '%s': திருத்தம் மற்றும் கோப்பு பெயர்" -#: gitk:12770 msgid "Bad arguments to gitk:" msgstr "அறிவிலிகேவிற்கு மோசமான வாதங்கள்:"
diff --git a/gitk-git/po/vi.po b/gitk-git/po/vi.po index 5967498..52c9d09 100644 --- a/gitk-git/po/vi.po +++ b/gitk-git/po/vi.po
@@ -5,7 +5,7 @@ # msgid "" msgstr "" -"Project-Id-Version: gitk @@GIT_VERSION@@\n" +"Project-Id-Version: Gitk @@GIT_VERSION@@\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-05-17 14:32+1000\n" "PO-Revision-Date: 2015-09-15 07:33+0700\n" @@ -18,32 +18,25 @@ "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Gtranslator 2.91.7\n" -#: gitk:140 msgid "Couldn't get list of unmerged files:" msgstr "Không thể lấy danh sách các tập-tin chưa được hòa trộn:" -#: gitk:212 gitk:2381 msgid "Color words" msgstr "Tô màu chữ" -#: gitk:217 gitk:2381 gitk:8220 gitk:8253 msgid "Markup words" msgstr "Đánh dấu chữ" -#: gitk:324 msgid "Error parsing revisions:" msgstr "Gặp lỗi khi phân tích điểm xét duyệt:" -#: gitk:380 msgid "Error executing --argscmd command:" msgstr "Gặp lỗi khi thực hiện lệnh --argscmd:" -#: gitk:393 msgid "No files selected: --merge specified but no files are unmerged." msgstr "" "Chưa chọn tập tin: --merge đã chỉ định nhưng không có tập tin chưa hòa trộn." -#: gitk:396 msgid "" "No files selected: --merge specified but no unmerged files are within file " "limit." @@ -51,314 +44,234 @@ "Chưa chọn tập tin: --merge đã chỉ định nhưng không có tập tin chưa hòa trộn " "trong giới hạn tập tin." -#: gitk:418 gitk:566 msgid "Error executing git log:" msgstr "Gặp lỗi khi thực hiện lệnh git log:" -#: gitk:436 gitk:582 msgid "Reading" msgstr "Đang đọc" -#: gitk:496 gitk:4525 msgid "Reading commits..." msgstr "Đang đọc các lần chuyển giao…" -#: gitk:499 gitk:1637 gitk:4528 msgid "No commits selected" msgstr "Chưa chọn các lần chuyển giao" -#: gitk:1445 gitk:4045 gitk:12432 msgid "Command line" msgstr "Dòng lệnh" -#: gitk:1511 msgid "Can't parse git log output:" msgstr "Không thể phân tích kết xuất từ lệnh git log:" -#: gitk:1740 msgid "No commit information available" msgstr "Không có thông tin về lần chuyển giao nào" -#: gitk:1903 gitk:1932 gitk:4315 gitk:9669 gitk:11241 gitk:11521 msgid "OK" msgstr "Đồng ý" -#: gitk:1934 gitk:4317 gitk:9196 gitk:9275 gitk:9391 gitk:9440 gitk:9671 -#: gitk:11242 gitk:11522 msgid "Cancel" msgstr "Thôi" -#: gitk:2069 msgid "&Update" msgstr "Cập nhật" -#: gitk:2070 msgid "&Reload" msgstr "Tải lại" -#: gitk:2071 msgid "Reread re&ferences" msgstr "Đọc lại tham chiếu" -#: gitk:2072 msgid "&List references" msgstr "Liệt kê các tham chiếu" -#: gitk:2074 msgid "Start git &gui" msgstr "Khởi chạy git gui" -#: gitk:2076 msgid "&Quit" msgstr "Thoát" -#: gitk:2068 msgid "&File" msgstr "Chính" -#: gitk:2080 msgid "&Preferences" msgstr "Tùy thích" -#: gitk:2079 msgid "&Edit" msgstr "Chỉnh sửa" -#: gitk:2084 msgid "&New view..." msgstr "Thêm trình bày mới…" -#: gitk:2085 msgid "&Edit view..." msgstr "Sửa cách trình bày…" -#: gitk:2086 msgid "&Delete view" msgstr "Xóa cách trình bày" -#: gitk:2088 gitk:4043 msgid "&All files" msgstr "Mọi tập tin" -#: gitk:2083 gitk:4067 msgid "&View" msgstr "Trình bày" -#: gitk:2093 gitk:2103 gitk:3012 msgid "&About gitk" msgstr "Giới thiệu về gitk" -#: gitk:2094 gitk:2108 msgid "&Key bindings" msgstr "Tổ hợp phím" -#: gitk:2092 gitk:2107 msgid "&Help" msgstr "Trợ giúp" -#: gitk:2185 gitk:8652 msgid "SHA1 ID:" msgstr "SHA1 ID:" -#: gitk:2229 msgid "Row" msgstr "Hàng" -#: gitk:2267 msgid "Find" msgstr "Tìm" -#: gitk:2295 msgid "commit" msgstr "lần chuyển giao" -#: gitk:2299 gitk:2301 gitk:4687 gitk:4710 gitk:4734 gitk:6755 gitk:6827 -#: gitk:6912 msgid "containing:" msgstr "có chứa:" -#: gitk:2302 gitk:3526 gitk:3531 gitk:4763 msgid "touching paths:" msgstr "đang chạm đường dẫn:" -#: gitk:2303 gitk:4777 msgid "adding/removing string:" msgstr "thêm/gỡ bỏ chuỗi:" -#: gitk:2304 gitk:4779 msgid "changing lines matching:" msgstr "những dòng thay đổi khớp mẫu:" -#: gitk:2313 gitk:2315 gitk:4766 msgid "Exact" msgstr "Chính xác" -#: gitk:2315 gitk:4854 gitk:6723 msgid "IgnCase" msgstr "BquaHt" -#: gitk:2315 gitk:4736 gitk:4852 gitk:6719 msgid "Regexp" msgstr "BTCQ" -#: gitk:2317 gitk:2318 gitk:4874 gitk:4904 gitk:4911 gitk:6848 gitk:6916 msgid "All fields" msgstr "Mọi trường" -#: gitk:2318 gitk:4871 gitk:4904 gitk:6786 msgid "Headline" msgstr "Nội dung chính" -#: gitk:2319 gitk:4871 gitk:6786 gitk:6916 gitk:7389 msgid "Comments" msgstr "Ghi chú" -#: gitk:2319 gitk:4871 gitk:4876 gitk:4911 gitk:6786 gitk:7324 gitk:8830 -#: gitk:8845 msgid "Author" msgstr "Tác giả" -#: gitk:2319 gitk:4871 gitk:6786 gitk:7326 msgid "Committer" msgstr "Người chuyển giao" -#: gitk:2350 msgid "Search" msgstr "Tìm kiếm" -#: gitk:2358 msgid "Diff" msgstr "So sánh" -#: gitk:2360 msgid "Old version" msgstr "Phiên bản cũ" -#: gitk:2362 msgid "New version" msgstr "Phiên bản mới" -#: gitk:2364 msgid "Lines of context" msgstr "Các dòng của nội dung" -#: gitk:2374 msgid "Ignore space change" msgstr "Không xét đến thay đổi do khoảng trắng" -#: gitk:2378 gitk:2380 gitk:7959 gitk:8206 msgid "Line diff" msgstr "Khác biệt theo dòng" -#: gitk:2445 msgid "Patch" msgstr "Vá" -#: gitk:2447 msgid "Tree" msgstr "Cây" -#: gitk:2617 gitk:2637 msgid "Diff this -> selected" msgstr "So sánh cái này -> cái đã chọn" -#: gitk:2618 gitk:2638 msgid "Diff selected -> this" msgstr "So sánh cái đã chọn -> cái này" -#: gitk:2619 gitk:2639 msgid "Make patch" msgstr "Tạo miếng vá" -#: gitk:2620 gitk:9254 msgid "Create tag" msgstr "Tạo thẻ" -#: gitk:2621 gitk:9371 msgid "Write commit to file" msgstr "Ghi lần chuyển giao ra tập tin" -#: gitk:2622 gitk:9428 msgid "Create new branch" msgstr "Tạo nhánh mới" -#: gitk:2623 msgid "Cherry-pick this commit" msgstr "Cherry-pick lần chuyển giao này" -#: gitk:2624 msgid "Reset HEAD branch to here" msgstr "Đặt lại HEAD của nhánh vào đây" -#: gitk:2625 msgid "Mark this commit" msgstr "Đánh dấu lần chuyển giao này" -#: gitk:2626 msgid "Return to mark" msgstr "Quay lại vị trí dấu" -#: gitk:2627 msgid "Find descendant of this and mark" msgstr "Tìm con cháu của cái này và cái đã đánh dấu" -#: gitk:2628 msgid "Compare with marked commit" msgstr "So sánh với lần chuyển giao đã đánh dấu" -#: gitk:2629 gitk:2640 msgid "Diff this -> marked commit" msgstr "So sánh cái này -> lần chuyển giao đã đánh dấu" -#: gitk:2630 gitk:2641 msgid "Diff marked commit -> this" msgstr "So sánh lần chuyển giao đã đánh dấu -> cái này" -#: gitk:2631 msgid "Revert this commit" msgstr "Hoàn lại lần chuyển giao này" -#: gitk:2647 msgid "Check out this branch" msgstr "Lấy ra nhánh này" -#: gitk:2648 msgid "Remove this branch" msgstr "Gỡ bỏ nhánh này" -#: gitk:2649 msgid "Copy branch name" msgstr "Chép tên nhánh" -#: gitk:2656 msgid "Highlight this too" msgstr "Cũng tô sáng nó" -#: gitk:2657 msgid "Highlight this only" msgstr "Chỉ tô sáng cái này" -#: gitk:2658 msgid "External diff" msgstr "diff từ bên ngoài" -#: gitk:2659 msgid "Blame parent commit" msgstr "Xem công trạng lần chuyển giao cha mẹ" -#: gitk:2660 msgid "Copy path" msgstr "Chép đường dẫn" -#: gitk:2667 msgid "Show origin of this line" msgstr "Hiển thị nguyên gốc của dòng này" -#: gitk:2668 msgid "Run git gui blame on this line" msgstr "Chạy lệnh git gui blame cho dòng này" -#: gitk:3014 msgid "" "\n" "Gitk - a commit viewer for git\n" @@ -374,318 +287,246 @@ "\n" "Dùng và phân phối lại phần mềm này theo các điều khoản của Giấy Phép Công GNU" -#: gitk:3022 gitk:3089 gitk:9857 msgid "Close" msgstr "Đóng" -#: gitk:3043 msgid "Gitk key bindings" msgstr "Tổ hợp phím gitk" -#: gitk:3046 msgid "Gitk key bindings:" msgstr "Tổ hợp phím gitk:" -#: gitk:3048 #, tcl-format msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\tThoát" -#: gitk:3049 #, tcl-format msgid "<%s-W>\t\tClose window" msgstr "<%s-W>\t\tĐóng cửa sổ" -#: gitk:3050 msgid "<Home>\t\tMove to first commit" msgstr "<Home>\t\tChuyển đến lần chuyển giao đầu tiên" -#: gitk:3051 msgid "<End>\t\tMove to last commit" msgstr "<End>\t\tChuyển đến lần chuyển giao cuối" -#: gitk:3052 msgid "<Up>, p, k\tMove up one commit" msgstr "<Up>, p, k\tDi chuyển lên một lần chuyển giao" -#: gitk:3053 msgid "<Down>, n, j\tMove down one commit" msgstr "<Down>, n, j\tDi chuyển xuống một lần chuyển giao" -#: gitk:3054 msgid "<Left>, z, h\tGo back in history list" msgstr "<Left>, z, h\tQuay trở lại danh sách lịch sử" -#: gitk:3055 msgid "<Right>, x, l\tGo forward in history list" msgstr "<Right>, x, l\tDi chuyển tiếp trong danh sách lịch sử" -#: gitk:3056 #, tcl-format msgid "<%s-n>\tGo to n-th parent of current commit in history list" msgstr "" "<%s-n>\tĐến cha thứ n của lần chuyển giao hiện tại trong danh sách lịch sử" -#: gitk:3057 msgid "<PageUp>\tMove up one page in commit list" msgstr "<PageUp>\tDi chuyển lên một trang trong danh sách lần chuyển giao" -#: gitk:3058 msgid "<PageDown>\tMove down one page in commit list" msgstr "<PageDown>\tDi chuyển xuống một trang trong danh sách lần chuyển giao" -#: gitk:3059 #, tcl-format msgid "<%s-Home>\tScroll to top of commit list" msgstr "<%s-Home>\tCuộn lên trên cùng của danh sách lần chuyển giao" -#: gitk:3060 #, tcl-format msgid "<%s-End>\tScroll to bottom of commit list" msgstr "<%s-End>\tCuộn xuống dưới cùng của danh sách lần chuyển giao" -#: gitk:3061 #, tcl-format msgid "<%s-Up>\tScroll commit list up one line" msgstr "<%s-Up>\tCuộn danh sách lần chuyển giao lên một dòng" -#: gitk:3062 #, tcl-format msgid "<%s-Down>\tScroll commit list down one line" msgstr "<%s-Down>\tCuộn danh sách lần chuyển giao xuống một dòng" -#: gitk:3063 #, tcl-format msgid "<%s-PageUp>\tScroll commit list up one page" msgstr "<%s-PageUp>\tCuộn danh sách lần chuyển giao lên một trang" -#: gitk:3064 #, tcl-format msgid "<%s-PageDown>\tScroll commit list down one page" msgstr "<%s-PageDown>\tCuộn danh sách lần chuyển giao xuống một trang" -#: gitk:3065 msgid "<Shift-Up>\tFind backwards (upwards, later commits)" msgstr "<Shift-Up>\tTìm về phía sau (hướng lên trên, lần chuyển giao sau này)" -#: gitk:3066 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" msgstr "" "<Shift-Down>\tTìm về phía trước (hướng xuống dưới, lần chuyển giao trước đây)" -#: gitk:3067 msgid "<Delete>, b\tScroll diff view up one page" msgstr "<Delete>, b\tCuộn phần trình bày diff lên một trang" -#: gitk:3068 msgid "<Backspace>\tScroll diff view up one page" msgstr "<Backspace>\tCuộn phần trình bày diff lên một trang" -#: gitk:3069 msgid "<Space>\t\tScroll diff view down one page" msgstr "<Space>\t\tCuộn phần trình bày diff xuống một trang" -#: gitk:3070 msgid "u\t\tScroll diff view up 18 lines" msgstr "u\t\tCuộn phần trình bày diff lên 18 dòng" -#: gitk:3071 msgid "d\t\tScroll diff view down 18 lines" msgstr "d\t\tCuộn phần trình bày diff xuống 18 dòng" -#: gitk:3072 #, tcl-format msgid "<%s-F>\t\tFind" msgstr "<%s-F>\t\tTìm kiếm" -#: gitk:3073 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" msgstr "<%s-G>\t\tDi chuyển đến chỗ gặp kế tiếp" -#: gitk:3074 msgid "<Return>\tMove to next find hit" msgstr "<Return>\t\tDi chuyển đến chỗ gặp kế tiếp" -#: gitk:3075 msgid "g\t\tGo to commit" msgstr "g\t\tChuyển đến lần chuyển giao" -#: gitk:3076 msgid "/\t\tFocus the search box" msgstr "/\t\tĐưa con trỏ chuột vào ô tìm kiếm" -#: gitk:3077 msgid "?\t\tMove to previous find hit" msgstr "?\t\tDi chuyển đến chỗ gặp kế trước" -#: gitk:3078 msgid "f\t\tScroll diff view to next file" msgstr "f\t\tCuộn phần trình bày diff sang tập-tin kế" -#: gitk:3079 #, tcl-format msgid "<%s-S>\t\tSearch for next hit in diff view" msgstr "<%s-S>\t\tTìm đến chỗ khác biệt kế tiếp" -#: gitk:3080 #, tcl-format msgid "<%s-R>\t\tSearch for previous hit in diff view" msgstr "<%s-R>\t\tTìm đến chỗ khác biệt kế trước" -#: gitk:3081 #, tcl-format msgid "<%s-KP+>\tIncrease font size" msgstr "<%s-KP+>\tTăng cỡ chữ" -#: gitk:3082 #, tcl-format msgid "<%s-plus>\tIncrease font size" msgstr "<%s-plus>\tTăng cỡ chữ" -#: gitk:3083 #, tcl-format msgid "<%s-KP->\tDecrease font size" msgstr "<%s-KP->\tGiảm cỡ chữ" -#: gitk:3084 #, tcl-format msgid "<%s-minus>\tDecrease font size" msgstr "<%s-minus>\tGiảm cỡ chữ" -#: gitk:3085 msgid "<F5>\t\tUpdate" msgstr "<F5>\t\tCập nhật" -#: gitk:3550 gitk:3559 #, tcl-format msgid "Error creating temporary directory %s:" msgstr "Gặp lỗi khi tạo thư mục tạm %s:" -#: gitk:3572 #, tcl-format msgid "Error getting \"%s\" from %s:" msgstr "Lỗi chào hỏi \"%s\" từ %s:" -#: gitk:3635 msgid "command failed:" msgstr "lệnh gặp lỗi:" -#: gitk:3784 msgid "No such commit" msgstr "Không có lần chuyển giao như vậy" -#: gitk:3798 msgid "git gui blame: command failed:" msgstr "git gui blame: lệnh gặp lỗi:" -#: gitk:3829 #, tcl-format msgid "Couldn't read merge head: %s" msgstr "Không thể độc đầu của hòa trộn: %s" # tcl-format -#: gitk:3837 #, tcl-format msgid "Error reading index: %s" msgstr "Gặp lỗi khi đọc chỉ mục: %s" -#: gitk:3862 #, tcl-format msgid "Couldn't start git blame: %s" msgstr "Không thể khởi chạy git blame: %s" -#: gitk:3865 gitk:6754 msgid "Searching" msgstr "Đang tìm kiếm" -#: gitk:3897 #, tcl-format msgid "Error running git blame: %s" msgstr "Gặp lỗi khi chạy git blame: %s" -#: gitk:3925 #, tcl-format msgid "That line comes from commit %s, which is not in this view" msgstr "Dòng đến từ lần chuyển giao %s, cái mà không trong trình bày này" -#: gitk:3939 msgid "External diff viewer failed:" msgstr "Bộ trình bày diff từ bên ngoài gặp lỗi:" -#: gitk:4070 msgid "Gitk view definition" msgstr "Định nghĩa cách trình bày gitk" -#: gitk:4074 msgid "Remember this view" msgstr "Nhớ cách trình bày này" -#: gitk:4075 msgid "References (space separated list):" msgstr "Tham chiếu (danh sách ngăn cách bằng dấu cách):" -#: gitk:4076 msgid "Branches & tags:" msgstr "Nhánh & thẻ:" -#: gitk:4077 msgid "All refs" msgstr "Mọi tham chiếu" -#: gitk:4078 msgid "All (local) branches" msgstr "Mọi nhánh (nội bộ)" -#: gitk:4079 msgid "All tags" msgstr "Mọi thẻ" -#: gitk:4080 msgid "All remote-tracking branches" msgstr "Mọi nhánh remote-tracking" -#: gitk:4081 msgid "Commit Info (regular expressions):" msgstr "Thông tin chuyển giao (biểu thức chính quy):" -#: gitk:4082 msgid "Author:" msgstr "Tác giả:" -#: gitk:4083 msgid "Committer:" msgstr "Người chuyển giao:" -#: gitk:4084 msgid "Commit Message:" msgstr "Chú thích của lần chuyển giao:" -#: gitk:4085 msgid "Matches all Commit Info criteria" msgstr "Khớp mọi điều kiện Thông tin Chuyển giao" -#: gitk:4086 msgid "Matches no Commit Info criteria" msgstr "Khớp không điều kiện Thông tin Chuyển giao" -#: gitk:4087 msgid "Changes to Files:" msgstr "Đổi thành Tập tin:" -#: gitk:4088 msgid "Fixed String" msgstr "Chuỗi cố định" -#: gitk:4089 msgid "Regular Expression" msgstr "Biểu thức chính quy" -#: gitk:4090 msgid "Search string:" msgstr "Chuỗi tìm kiếm:" -#: gitk:4091 msgid "" "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" @@ -693,203 +534,155 @@ "Ngày chuyển giao (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" -#: gitk:4092 msgid "Since:" msgstr "Kể từ:" -#: gitk:4093 msgid "Until:" msgstr "Đến:" -#: gitk:4094 msgid "Limit and/or skip a number of revisions (positive integer):" msgstr "Giới hạn và/hoặc bỏ số của điểm xét (số nguyên âm):" -#: gitk:4095 msgid "Number to show:" msgstr "Số lượng hiển thị:" -#: gitk:4096 msgid "Number to skip:" msgstr "Số lượng sẽ bỏ qua:" -#: gitk:4097 msgid "Miscellaneous options:" msgstr "Tùy chọn hỗn hợp:" -#: gitk:4098 msgid "Strictly sort by date" msgstr "Sắp xếp chặt chẽ theo ngày" -#: gitk:4099 msgid "Mark branch sides" msgstr "Đánh dấu các cạnh nhánh" -#: gitk:4100 msgid "Limit to first parent" msgstr "Giới hạn thành cha mẹ đầu tiên" -#: gitk:4101 msgid "Simple history" msgstr "Lịch sử dạng đơn giản" -#: gitk:4102 msgid "Additional arguments to git log:" msgstr "Đối số bổ xung cho lệnh git log:" -#: gitk:4103 msgid "Enter files and directories to include, one per line:" msgstr "Nhập vào các tập tin và thư mục bao gồm, mỗi dòng một cái:" -#: gitk:4104 msgid "Command to generate more commits to include:" msgstr "Lệnh tạo ra nhiều lần chuyển giao hơn bao gồm:" -#: gitk:4228 msgid "Gitk: edit view" msgstr "Gitk: sửa cách trình bày" -#: gitk:4236 msgid "-- criteria for selecting revisions" msgstr "-- tiêu chuẩn chọn điểm xét duyệt" -#: gitk:4241 msgid "View Name" msgstr "Tên cách trình bày" -#: gitk:4316 msgid "Apply (F5)" msgstr "Áp dụng (F5)" -#: gitk:4354 msgid "Error in commit selection arguments:" msgstr "Lỗi trong các đối số chọn chuyển giao:" -#: gitk:4409 gitk:4462 gitk:4924 gitk:4938 gitk:6208 gitk:12373 gitk:12374 msgid "None" msgstr "Không" -#: gitk:5021 gitk:5026 msgid "Descendant" msgstr "Con cháu" -#: gitk:5022 msgid "Not descendant" msgstr "Không có con cháu" -#: gitk:5029 gitk:5034 msgid "Ancestor" msgstr "Tổ tiên chung" -#: gitk:5030 msgid "Not ancestor" msgstr "Không có chung tổ tiên" -#: gitk:5324 msgid "Local changes checked in to index but not committed" msgstr "" "Có thay đổi nội bộ đã được đưa vào bảng mục lục, nhưng chưa được chuyển giao" -#: gitk:5360 msgid "Local uncommitted changes, not checked in to index" msgstr "Có thay đổi nội bộ, nhưng chưa được đưa vào bảng mục lục" -#: gitk:7134 msgid "and many more" msgstr "và nhiều nữa" -#: gitk:7137 msgid "many" msgstr "nhiều" -#: gitk:7328 msgid "Tags:" msgstr "Thẻ:" -#: gitk:7345 gitk:7351 gitk:8825 msgid "Parent" msgstr "Cha" -#: gitk:7356 msgid "Child" msgstr "Con" -#: gitk:7365 msgid "Branch" msgstr "Nhánh" -#: gitk:7368 msgid "Follows" msgstr "Đứng sau" -#: gitk:7371 msgid "Precedes" msgstr "Đứng trước" # tcl-format -#: gitk:7966 #, tcl-format msgid "Error getting diffs: %s" msgstr "Lỗi lấy diff: %s" -#: gitk:8650 msgid "Goto:" msgstr "Nhảy tới:" -#: gitk:8671 #, tcl-format msgid "Short SHA1 id %s is ambiguous" msgstr "Định danh SHA1 dạng ngắn %s là chưa đủ rõ ràng" -#: gitk:8678 #, tcl-format msgid "Revision %s is not known" msgstr "Không hiểu điểm xét duyệt %s" -#: gitk:8688 #, tcl-format msgid "SHA1 id %s is not known" msgstr "Không hiểu định danh SHA1 %s" -#: gitk:8690 #, tcl-format msgid "Revision %s is not in the current view" msgstr "Điểm %s không ở trong phần hiển thị hiện tại" -#: gitk:8832 gitk:8847 msgid "Date" msgstr "Ngày" -#: gitk:8835 msgid "Children" msgstr "Con cháu" -#: gitk:8898 #, tcl-format msgid "Reset %s branch to here" msgstr "Đặt lại nhánh %s tại đây" -#: gitk:8900 msgid "Detached head: can't reset" msgstr "Head đã bị tách rời: không thể đặt lại" -#: gitk:9005 gitk:9011 msgid "Skipping merge commit " msgstr "Bỏ qua lần chuyển giao hòa trộn " -#: gitk:9020 gitk:9025 msgid "Error getting patch ID for " msgstr "Gặp lỗi khi lấy ID miếng vá cho " -#: gitk:9021 gitk:9026 msgid " - stopping\n" msgstr " - dừng\n" -#: gitk:9031 gitk:9034 gitk:9042 gitk:9056 gitk:9065 msgid "Commit " msgstr "Commit " -#: gitk:9035 msgid "" " is the same patch as\n" " " @@ -897,7 +690,6 @@ " là cùng một miếng vá với\n" " " -#: gitk:9043 msgid "" " differs from\n" " " @@ -905,7 +697,6 @@ " khác biệt từ\n" " " -#: gitk:9045 msgid "" "Diff of commits:\n" "\n" @@ -913,131 +704,101 @@ "Khác biệt của lần chuyển giao (commit):\n" "\n" -#: gitk:9057 gitk:9066 #, tcl-format msgid " has %s children - stopping\n" msgstr " có %s con - dừng\n" -#: gitk:9085 #, tcl-format msgid "Error writing commit to file: %s" msgstr "Gặp lỗi trong quá trình ghi lần chuyển giao vào tập tin: %s" -#: gitk:9091 #, tcl-format msgid "Error diffing commits: %s" msgstr "Gặp lỗi khi so sánh sự khác biệt giữa các lần chuyển giao: %s" -#: gitk:9137 msgid "Top" msgstr "Đỉnh" -#: gitk:9138 msgid "From" msgstr "Từ" -#: gitk:9143 msgid "To" msgstr "Đến" -#: gitk:9167 msgid "Generate patch" msgstr "Tạo miếng vá" -#: gitk:9169 msgid "From:" msgstr "Từ:" -#: gitk:9178 msgid "To:" msgstr "Đến:" -#: gitk:9187 msgid "Reverse" msgstr "Đảo ngược" -#: gitk:9189 gitk:9385 msgid "Output file:" msgstr "Tập tin kết xuất:" -#: gitk:9195 msgid "Generate" msgstr "Tạo" -#: gitk:9233 msgid "Error creating patch:" msgstr "Gặp lỗi khi tạo miếng vá:" -#: gitk:9256 gitk:9373 gitk:9430 msgid "ID:" msgstr "Mã số:" -#: gitk:9265 msgid "Tag name:" msgstr "Tên thẻ:" -#: gitk:9268 msgid "Tag message is optional" msgstr "Ghi chú thẻ chỉ là tùy chọn" -#: gitk:9270 msgid "Tag message:" msgstr "Ghi chú cho thẻ:" -#: gitk:9274 gitk:9439 msgid "Create" msgstr "Tạo" -#: gitk:9292 msgid "No tag name specified" msgstr "Chưa chỉ ra tên của thẻ" -#: gitk:9296 #, tcl-format msgid "Tag \"%s\" already exists" msgstr "Thẻ “%s” đã có sẵn rồi" -#: gitk:9306 msgid "Error creating tag:" msgstr "Gặp lỗi khi tạo thẻ:" -#: gitk:9382 msgid "Command:" msgstr "Lệnh:" -#: gitk:9390 msgid "Write" msgstr "Ghi" -#: gitk:9408 msgid "Error writing commit:" msgstr "Gặp lỗi trong quá trình ghi chuyển giao:" -#: gitk:9435 msgid "Name:" msgstr "Tên:" -#: gitk:9458 msgid "Please specify a name for the new branch" msgstr "Vui lòng chỉ định tên cho nhánh mới" -#: gitk:9463 #, tcl-format msgid "Branch '%s' already exists. Overwrite?" msgstr "Nhánh “%s” đã có từ trước rồi. Ghi đè?" -#: gitk:9530 #, tcl-format msgid "Commit %s is already included in branch %s -- really re-apply it?" msgstr "" "Lần chuyển giao %s đã sẵn được bao gồm trong nhánh %s -- bạn có thực sự muốn " "áp dụng lại nó không?" -#: gitk:9535 msgid "Cherry-picking" msgstr "Đang cherry-pick" -#: gitk:9544 #, tcl-format msgid "" "Cherry-pick failed because of local changes to file '%s'.\n" @@ -1046,7 +807,6 @@ "Cherry-pick gặp lỗi bởi vì các thay đổi nội bộ tập tin “%s”.\n" "Xin hãy chuyển giao, reset hay stash các thay đổi của bạn sau đó thử lại." -#: gitk:9550 msgid "" "Cherry-pick failed because of merge conflict.\n" "Do you wish to run git citool to resolve it?" @@ -1054,22 +814,18 @@ "Cherry-pick gặp lỗi bởi vì xung đột trong hòa trộn.\n" "Bạn có muốn chạy lệnh “git citool” để giải quyết vấn đề này không?" -#: gitk:9566 gitk:9624 msgid "No changes committed" msgstr "Không có thay đổi nào cần chuyển giao" -#: gitk:9593 #, tcl-format msgid "Commit %s is not included in branch %s -- really revert it?" msgstr "" "Lần chuyển giao %s không được bao gồm trong nhánh %s -- bạn có thực sự muốn " "“revert” nó không?" -#: gitk:9598 msgid "Reverting" msgstr "Đang hoàn tác" -#: gitk:9606 #, tcl-format msgid "" "Revert failed because of local changes to the following files:%s Please " @@ -1078,7 +834,6 @@ "Revert gặp lỗi bởi vì tập tin sau đã được thay đổi nội bộ:%s\n" "Xin hãy chạy lệnh “commit”, “reset” hoặc “stash” rồi thử lại." -#: gitk:9610 msgid "" "Revert failed because of merge conflict.\n" " Do you wish to run git citool to resolve it?" @@ -1086,29 +841,23 @@ "Revert gặp lỗi bởi vì xung đột hòa trộn.\n" " Bạn có muốn chạy lệnh “git citool” để phân giải nó không?" -#: gitk:9653 msgid "Confirm reset" msgstr "Xác nhật đặt lại" -#: gitk:9655 #, tcl-format msgid "Reset branch %s to %s?" msgstr "Đặt lại nhánh “%s” thành “%s”?" -#: gitk:9657 msgid "Reset type:" msgstr "Kiểu đặt lại:" -#: gitk:9660 msgid "Soft: Leave working tree and index untouched" msgstr "Mềm: Không động đến thư mục làm việc và bảng mục lục" -#: gitk:9663 msgid "Mixed: Leave working tree untouched, reset index" msgstr "" "Pha trộn: Không động chạm đến thư mục làm việc nhưng đặt lại bảng mục lục" -#: gitk:9666 msgid "" "Hard: Reset working tree and index\n" "(discard ALL local changes)" @@ -1116,19 +865,15 @@ "Hard: Đặt lại cây làm việc và mục lục\n" "(hủy bỏ MỌI thay đổi nội bộ)" -#: gitk:9683 msgid "Resetting" msgstr "Đang đặt lại" -#: gitk:9743 msgid "Checking out" msgstr "Đang checkout" -#: gitk:9796 msgid "Cannot delete the currently checked-out branch" msgstr "Không thể xóa nhánh hiện tại đang được lấy ra" -#: gitk:9802 #, tcl-format msgid "" "The commits on branch %s aren't on any other branch.\n" @@ -1137,16 +882,13 @@ "Các lần chuyển giao trên nhánh %s không ở trên nhánh khác.\n" "Thực sự muốn xóa nhánh %s?" -#: gitk:9833 #, tcl-format msgid "Tags and heads: %s" msgstr "Thẻ và Đầu: %s" -#: gitk:9850 msgid "Filter" msgstr "Bộ lọc" -#: gitk:10146 msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." @@ -1154,201 +896,152 @@ "Gặp lỗi khi đọc thông tin hình học lần chuyển giao; thông tin nhánh và thẻ " "trước/sau sẽ không hoàn thiện." -#: gitk:11123 msgid "Tag" msgstr "Thẻ" -#: gitk:11127 msgid "Id" msgstr "Id" -#: gitk:11210 msgid "Gitk font chooser" msgstr "Hộp thoại chọn phông Gitk" -#: gitk:11227 msgid "B" msgstr "B" -#: gitk:11230 msgid "I" msgstr "I" -#: gitk:11348 msgid "Commit list display options" msgstr "Các tùy chọn về hiển thị danh sách lần chuyển giao" -#: gitk:11351 msgid "Maximum graph width (lines)" msgstr "Độ rộng biểu đồ tối đa (dòng)" -#: gitk:11355 #, no-tcl-format msgid "Maximum graph width (% of pane)" msgstr "Độ rộng đồ thị tối đa (% của bảng)" -#: gitk:11358 msgid "Show local changes" msgstr "Hiển thị các thay đổi nội bộ" -#: gitk:11361 msgid "Auto-select SHA1 (length)" msgstr "Tự chọn (độ dài) SHA1" -#: gitk:11365 msgid "Hide remote refs" msgstr "Ẩn tham chiếu đến máy chủ" -#: gitk:11369 msgid "Diff display options" msgstr "Các tùy chọn trình bày các khác biệt" -#: gitk:11371 msgid "Tab spacing" msgstr "Khoảng cách tab" -#: gitk:11374 msgid "Display nearby tags/heads" msgstr "Hiển thị các thẻ/đầu xung quanh" -#: gitk:11377 msgid "Maximum # tags/heads to show" msgstr "Số lượng thẻ/đầu tối đa sẽ hiển thị" -#: gitk:11380 msgid "Limit diffs to listed paths" msgstr "Giới hạn các khác biệt cho đường dẫn đã liệt kê" -#: gitk:11383 msgid "Support per-file encodings" msgstr "Hỗ trợ mã hóa mỗi-dòng" -#: gitk:11389 gitk:11536 msgid "External diff tool" msgstr "Công cụ so sánh từ bên ngoài" -#: gitk:11390 msgid "Choose..." msgstr "Chọn…" -#: gitk:11395 msgid "General options" msgstr "Các tùy chọn chung" -#: gitk:11398 msgid "Use themed widgets" msgstr "Dùng các widget chủ đề" -#: gitk:11400 msgid "(change requires restart)" msgstr "(để thay đổi cần khởi động lại)" -#: gitk:11402 msgid "(currently unavailable)" msgstr "(hiện tại không sẵn sàng)" -#: gitk:11413 msgid "Colors: press to choose" msgstr "Màu sắc: bấm vào nút phía dưới để chọn màu" -#: gitk:11416 msgid "Interface" msgstr "Giao diện" -#: gitk:11417 msgid "interface" msgstr "giao diện" -#: gitk:11420 msgid "Background" msgstr "Nền" -#: gitk:11421 gitk:11451 msgid "background" msgstr "nền" -#: gitk:11424 msgid "Foreground" msgstr "Tiền cảnh" -#: gitk:11425 msgid "foreground" msgstr "tiền cảnh" -#: gitk:11428 msgid "Diff: old lines" msgstr "So sánh: dòng cũ" -#: gitk:11429 msgid "diff old lines" msgstr "diff dòng cũ" -#: gitk:11433 msgid "Diff: new lines" msgstr "So sánh: dòng mới" -#: gitk:11434 msgid "diff new lines" msgstr "màu dòng mới" -#: gitk:11438 msgid "Diff: hunk header" msgstr "So sánh: phần đầu của đoạn" -#: gitk:11440 msgid "diff hunk header" msgstr "màu của phần đầu của đoạn khi so sánh" -#: gitk:11444 msgid "Marked line bg" msgstr "Nền dòng đánh dấu" -#: gitk:11446 msgid "marked line background" msgstr "nền dòng được đánh dấu" -#: gitk:11450 msgid "Select bg" msgstr "Màu nền" -#: gitk:11459 msgid "Fonts: press to choose" msgstr "Phông chữ: bấm vào các nút ở dưới để chọn" -#: gitk:11461 msgid "Main font" msgstr "Phông chữ chính" -#: gitk:11462 msgid "Diff display font" msgstr "Phông chữ dùng khi so sánh" -#: gitk:11463 msgid "User interface font" msgstr "Phông chữ giao diện" -#: gitk:11485 msgid "Gitk preferences" msgstr "Cá nhân hóa các cài đặt cho Gitk" -#: gitk:11494 msgid "General" msgstr "Chung" -#: gitk:11495 msgid "Colors" msgstr "Màu sắc" -#: gitk:11496 msgid "Fonts" msgstr "Phông chữ" -#: gitk:11546 #, tcl-format msgid "Gitk: choose color for %s" msgstr "Gitk: chọn màu cho %s" -#: gitk:12059 msgid "" "Sorry, gitk cannot run with this version of Tcl/Tk.\n" " Gitk requires at least Tcl/Tk 8.4." @@ -1356,16 +1049,13 @@ "Rất tiếc, gitk không thể chạy Tcl/Tk phiên bản này.\n" " Gitk cần ít nhất là Tcl/Tk 8.4." -#: gitk:12269 msgid "Cannot find a git repository here." msgstr "Không thể tìm thấy kho git ở đây." -#: gitk:12316 #, tcl-format msgid "Ambiguous argument '%s': both revision and filename" msgstr "Đối số “%s” chưa rõ ràng: vừa là điểm xét duyệt vừa là tên tập tin" -#: gitk:12328 msgid "Bad arguments to gitk:" msgstr "Đối số cho gitk không hợp lệ:"
diff --git a/gitk-git/po/zh_cn.po b/gitk-git/po/zh_cn.po index 17b7f89..71a9878 100644 --- a/gitk-git/po/zh_cn.po +++ b/gitk-git/po/zh_cn.po
@@ -2,10 +2,9 @@ # # Translators: # YanKe <imyanke@163.com>, 2017 - msgid "" msgstr "" -"Project-Id-Version: Git Chinese Localization Project\n" +"Project-Id-Version: Gitk Chinese Localization Project\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-02-28 23:11+0800\n" "PO-Revision-Date: 2017-03-11 02:27+0800\n" @@ -16,356 +15,266 @@ "Content-Transfer-Encoding: 8bit\n" "Language: zh_CN\n" -#: gitk:140 msgid "Couldn't get list of unmerged files:" msgstr "不能获取未合并文件列表:" -#: gitk:212 gitk:2403 msgid "Color words" msgstr "着色显示差异" -#: gitk:217 gitk:2403 gitk:8249 gitk:8282 msgid "Markup words" msgstr "标记显示差异" -#: gitk:324 msgid "Error parsing revisions:" msgstr "解析版本错误:" -#: gitk:380 msgid "Error executing --argscmd command:" msgstr "运行 --argscmd命令出错" -#: gitk:393 msgid "No files selected: --merge specified but no files are unmerged." msgstr "没有选中文件:--指定merge参数但没有未合并的文件。" -#: gitk:396 msgid "" "No files selected: --merge specified but no unmerged files are within file " "limit." msgstr "没有选中文件:--指定merge参数但没有未合并的文件在文件中" -#: gitk:418 gitk:566 msgid "Error executing git log:" msgstr "执行git log命令出错:" -#: gitk:436 gitk:582 msgid "Reading" msgstr "读取中" -#: gitk:496 gitk:4549 msgid "Reading commits..." msgstr "提交记录读取中..." -#: gitk:499 gitk:1641 gitk:4552 msgid "No commits selected" msgstr "未选中任何提交" -#: gitk:1449 gitk:4069 gitk:12583 msgid "Command line" msgstr "命令行" -#: gitk:1515 msgid "Can't parse git log output:" msgstr "不能解析git log输出:" -#: gitk:1744 msgid "No commit information available" msgstr "无可用提交信息" -#: gitk:1907 gitk:1936 gitk:4339 gitk:9789 gitk:11388 gitk:11668 msgid "OK" msgstr "确定" -#: gitk:1938 gitk:4341 gitk:9225 gitk:9304 gitk:9434 gitk:9520 gitk:9791 -#: gitk:11389 gitk:11669 msgid "Cancel" msgstr "取消" -#: gitk:2087 msgid "&Update" msgstr "更新" -#: gitk:2088 msgid "&Reload" msgstr "重新加载" -#: gitk:2089 msgid "Reread re&ferences" msgstr "重新读取引用" -#: gitk:2090 msgid "&List references" msgstr "列出引用(分支以及tag)" -#: gitk:2092 msgid "Start git &gui" msgstr "启动git gui客户端" -#: gitk:2094 msgid "&Quit" msgstr "退出" -#: gitk:2086 msgid "&File" msgstr "文件" -#: gitk:2098 msgid "&Preferences" msgstr "偏好设置" -#: gitk:2097 msgid "&Edit" msgstr "编辑" -#: gitk:2102 msgid "&New view..." msgstr "新视图..." -#: gitk:2103 msgid "&Edit view..." msgstr "编辑视图..." -#: gitk:2104 msgid "&Delete view" msgstr "删除视图" -#: gitk:2106 msgid "&All files" msgstr "所有文件" -#: gitk:2101 msgid "&View" msgstr "视图" -#: gitk:2111 gitk:2121 msgid "&About gitk" msgstr "关于gitk" -#: gitk:2112 gitk:2126 msgid "&Key bindings" msgstr "快捷键" -#: gitk:2110 gitk:2125 msgid "&Help" msgstr "帮助" -#: gitk:2203 gitk:8681 msgid "SHA1 ID:" msgstr "SHA1 ID:" -#: gitk:2247 msgid "Row" msgstr "行" -#: gitk:2285 msgid "Find" msgstr "查找" -#: gitk:2313 msgid "commit" msgstr "提交" -#: gitk:2317 gitk:2319 gitk:4711 gitk:4734 gitk:4758 gitk:6779 gitk:6851 -#: gitk:6936 msgid "containing:" msgstr "包含:" -#: gitk:2320 gitk:3550 gitk:3555 gitk:4787 msgid "touching paths:" msgstr "影响路径:" -#: gitk:2321 gitk:4801 msgid "adding/removing string:" msgstr "增加/删除字符串:" -#: gitk:2322 gitk:4803 msgid "changing lines matching:" msgstr "改变行匹配:" -#: gitk:2331 gitk:2333 gitk:4790 msgid "Exact" msgstr "精确匹配" -#: gitk:2333 gitk:4878 gitk:6747 msgid "IgnCase" msgstr "忽略大小写" -#: gitk:2333 gitk:4760 gitk:4876 gitk:6743 msgid "Regexp" msgstr "正则" -#: gitk:2335 gitk:2336 gitk:4898 gitk:4928 gitk:4935 gitk:6872 gitk:6940 msgid "All fields" msgstr "所有字段" -#: gitk:2336 gitk:4895 gitk:4928 gitk:6810 msgid "Headline" msgstr "标题" -#: gitk:2337 gitk:4895 gitk:6810 gitk:6940 gitk:7413 msgid "Comments" msgstr "提交注释" -#: gitk:2337 gitk:4895 gitk:4900 gitk:4935 gitk:6810 gitk:7348 gitk:8859 -#: gitk:8874 msgid "Author" msgstr "作者" -#: gitk:2337 gitk:4895 gitk:6810 gitk:7350 msgid "Committer" msgstr "提交者" -#: gitk:2371 msgid "Search" msgstr "搜索" -#: gitk:2379 msgid "Diff" msgstr "差异" -#: gitk:2381 msgid "Old version" msgstr "老版本" -#: gitk:2383 msgid "New version" msgstr "新版本" -#: gitk:2386 msgid "Lines of context" msgstr "Diff上下文显示行数" -#: gitk:2396 msgid "Ignore space change" msgstr "忽略空格修改" -#: gitk:2400 gitk:2402 gitk:7983 gitk:8235 msgid "Line diff" msgstr "按行显示差异" -#: gitk:2467 msgid "Patch" msgstr "补丁" -#: gitk:2469 msgid "Tree" msgstr "树" -#: gitk:2639 gitk:2660 msgid "Diff this -> selected" msgstr "比较从当前提交到选中提交的差异" -#: gitk:2640 gitk:2661 msgid "Diff selected -> this" msgstr "比较从选中提交到当前提交的差异" -#: gitk:2641 gitk:2662 msgid "Make patch" msgstr "制作补丁" -#: gitk:2642 gitk:9283 msgid "Create tag" msgstr "创建tag" -#: gitk:2643 msgid "Copy commit summary" msgstr "复制提交摘要" -#: gitk:2644 gitk:9414 msgid "Write commit to file" msgstr "写入提交到文件" -#: gitk:2645 msgid "Create new branch" msgstr "创建新分支" -#: gitk:2646 msgid "Cherry-pick this commit" msgstr "在此提交运用补丁(cherry-pick)命令" -#: gitk:2647 msgid "Reset HEAD branch to here" msgstr "将分支头(HEAD)重置到此处" -#: gitk:2648 msgid "Mark this commit" msgstr "标记此提交" -#: gitk:2649 msgid "Return to mark" msgstr "返回到标记" -#: gitk:2650 msgid "Find descendant of this and mark" msgstr "查找本次提交的子提交并标记" -#: gitk:2651 msgid "Compare with marked commit" msgstr "和已标记的提交作比较" -#: gitk:2652 gitk:2663 msgid "Diff this -> marked commit" msgstr "比较从当前提交到已标记提交的差异" -#: gitk:2653 gitk:2664 msgid "Diff marked commit -> this" msgstr "比较从已标记提交到当前提交的差异" -#: gitk:2654 msgid "Revert this commit" msgstr "撤销(revert)此提交" -#: gitk:2670 msgid "Check out this branch" msgstr "检出(checkout)此分支" -#: gitk:2671 msgid "Rename this branch" msgstr "重命名(Rename)此分支" -#: gitk:2672 msgid "Remove this branch" msgstr "删除(Remove)此分支" -#: gitk:2673 msgid "Copy branch name" msgstr "复制分支名称" -#: gitk:2680 msgid "Highlight this too" msgstr "高亮此处" -#: gitk:2681 msgid "Highlight this only" msgstr "只高亮此处" -#: gitk:2682 msgid "External diff" msgstr "外部diff" -#: gitk:2683 msgid "Blame parent commit" msgstr "Blame父提交" -#: gitk:2684 msgid "Copy path" msgstr "复制路径" -#: gitk:2691 msgid "Show origin of this line" msgstr "显示此行原始提交" -#: gitk:2692 msgid "Run git gui blame on this line" msgstr "在此行运行git gui客户端的blame" -#: gitk:3036 msgid "About gitk" msgstr "关于gitk" -#: gitk:3038 msgid "" "\n" "Gitk - a commit viewer for git\n" @@ -373,995 +282,792 @@ "Copyright © 2005-2016 Paul Mackerras\n" "\n" "Use and redistribute under the terms of the GNU General Public License" -msgstr "\nGitk — 一个git的提交查看器\n\n© 2005-2016 Paul Mackerras\n\n在GNU许可证下使用以及分发" +msgstr "" +"\n" +"Gitk — 一个git的提交查看器\n" +"\n" +"© 2005-2016 Paul Mackerras\n" +"\n" +"在GNU许可证下使用以及分发" -#: gitk:3046 gitk:3113 gitk:10004 msgid "Close" msgstr "关闭" -#: gitk:3067 msgid "Gitk key bindings" msgstr "Gitk快捷键" -#: gitk:3070 msgid "Gitk key bindings:" msgstr "Gitk快捷键:" -#: gitk:3072 #, tcl-format msgid "<%s-Q>\t\tQuit" msgstr "<%s-Q>\t\t退出" -#: gitk:3073 #, tcl-format msgid "<%s-W>\t\tClose window" msgstr "<%s-W>\t\t关闭窗口" -#: gitk:3074 msgid "<Home>\t\tMove to first commit" msgstr "<Home>\t\t移动到第一次提交" -#: gitk:3075 msgid "<End>\t\tMove to last commit" msgstr "<End>\t\t移动到最后一次提交" -#: gitk:3076 msgid "<Up>, p, k\tMove up one commit" msgstr "<Up>, p, k\t移动到上一次提交" -#: gitk:3077 msgid "<Down>, n, j\tMove down one commit" msgstr "<Down>, n, j\t移动到下一次提交" -#: gitk:3078 msgid "<Left>, z, h\tGo back in history list" msgstr "<Left>, z, h\t历史列表的上一项" -#: gitk:3079 msgid "<Right>, x, l\tGo forward in history list" msgstr "<Right>, x, l\t历史列表的下一项" -#: gitk:3080 #, tcl-format msgid "<%s-n>\tGo to n-th parent of current commit in history list" msgstr "<%s-n>\t在历史列表中前往本次提交的第n个父提交" -#: gitk:3081 msgid "<PageUp>\tMove up one page in commit list" msgstr "<PageUp>\t上一页提交列表" -#: gitk:3082 msgid "<PageDown>\tMove down one page in commit list" msgstr "<PageDown>\t下一页提交列表" -#: gitk:3083 #, tcl-format msgid "<%s-Home>\tScroll to top of commit list" msgstr "<%s-Home>\t滚动到提交列表顶部" -#: gitk:3084 #, tcl-format msgid "<%s-End>\tScroll to bottom of commit list" msgstr "<%s-End>\t滚动到提交列表底部" -#: gitk:3085 #, tcl-format msgid "<%s-Up>\tScroll commit list up one line" msgstr "<%s-Up>\t向上滚动一行提交列表" -#: gitk:3086 #, tcl-format msgid "<%s-Down>\tScroll commit list down one line" msgstr "<%s-Down>\t向下滚动一行提交列表" -#: gitk:3087 #, tcl-format msgid "<%s-PageUp>\tScroll commit list up one page" msgstr "<%s-PageUp>\t向上滚动一页提交列表" -#: gitk:3088 #, tcl-format msgid "<%s-PageDown>\tScroll commit list down one page" msgstr "<%s-PageDown>\t向下滚动一页提交列表" -#: gitk:3089 msgid "<Shift-Up>\tFind backwards (upwards, later commits)" msgstr "<Shift-Up>\t向后查找(向上的,更晚的提交)" -#: gitk:3090 msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)" msgstr "<Shift-Down>\t向前查找(向下的,更早的提交)" -#: gitk:3091 msgid "<Delete>, b\tScroll diff view up one page" msgstr "<Delete>, b\t向上滚动diff视图一页" -#: gitk:3092 msgid "<Backspace>\tScroll diff view up one page" msgstr "<Backspace>\t向上滚动diff视图一页" -#: gitk:3093 msgid "<Space>\t\tScroll diff view down one page" msgstr "<Space>\t\t向下滚动diff视图一页" -#: gitk:3094 msgid "u\t\tScroll diff view up 18 lines" msgstr "u\t\t向上滚动diff视图18行" -#: gitk:3095 msgid "d\t\tScroll diff view down 18 lines" msgstr "d\t\t向下滚动diff视图18行" -#: gitk:3096 #, tcl-format msgid "<%s-F>\t\tFind" msgstr "<%s-F>\t\t查找" -#: gitk:3097 #, tcl-format msgid "<%s-G>\t\tMove to next find hit" msgstr "<%s-G>\t\t移动到下一次查找命中" -#: gitk:3098 msgid "<Return>\tMove to next find hit" msgstr "<Return>\t\t移动到下一次查找命中" -#: gitk:3099 msgid "g\t\tGo to commit" msgstr "g\t\t转到提交" -#: gitk:3100 msgid "/\t\tFocus the search box" msgstr "/\t\t选中搜索框" -#: gitk:3101 msgid "?\t\tMove to previous find hit" msgstr "?\t\t移动到上一次查找命中" -#: gitk:3102 msgid "f\t\tScroll diff view to next file" msgstr "f\t\t滚动diff视图到下一个文件" -#: gitk:3103 #, tcl-format msgid "<%s-S>\t\tSearch for next hit in diff view" msgstr "<%s-S>\t\t在diff视图中查找下一此命中" -#: gitk:3104 #, tcl-format msgid "<%s-R>\t\tSearch for previous hit in diff view" msgstr "<%s-R>\t\t在diff视图中查找上一次命中" -#: gitk:3105 #, tcl-format msgid "<%s-KP+>\tIncrease font size" msgstr "<%s-KP+>\t增大字体大小" -#: gitk:3106 #, tcl-format msgid "<%s-plus>\tIncrease font size" msgstr "<%s-plus>\t增大字体大小" -#: gitk:3107 #, tcl-format msgid "<%s-KP->\tDecrease font size" msgstr "<%s-KP->\t减小字体大小" -#: gitk:3108 #, tcl-format msgid "<%s-minus>\tDecrease font size" msgstr "<%s-minus>\t减小字体大小" -#: gitk:3109 msgid "<F5>\t\tUpdate" msgstr "<F5>\t\t更新" -#: gitk:3574 gitk:3583 #, tcl-format msgid "Error creating temporary directory %s:" msgstr "创建临时目录出错%s:" -#: gitk:3596 #, tcl-format msgid "Error getting \"%s\" from %s:" msgstr "从%s获取\"%s\"出错:" -#: gitk:3659 msgid "command failed:" msgstr "执行命令失败:" -#: gitk:3808 msgid "No such commit" msgstr "无此提交" -#: gitk:3822 msgid "git gui blame: command failed:" msgstr "git gui blame:执行命令失败:" -#: gitk:3853 #, tcl-format msgid "Couldn't read merge head: %s" msgstr "不能读取合并头(merge head):%s" -#: gitk:3861 #, tcl-format msgid "Error reading index: %s" msgstr "读取索引出错:%s" -#: gitk:3886 #, tcl-format msgid "Couldn't start git blame: %s" msgstr "不能执行git blame:%s" -#: gitk:3889 gitk:6778 msgid "Searching" msgstr "搜索中" -#: gitk:3921 #, tcl-format msgid "Error running git blame: %s" msgstr "运行git blame出错:%s" -#: gitk:3949 #, tcl-format msgid "That line comes from commit %s, which is not in this view" msgstr "此行来自提交%s,不在此视图中" -#: gitk:3963 msgid "External diff viewer failed:" msgstr "外部diff查看器失败:" -#: gitk:4067 msgid "All files" msgstr "所有文件" -#: gitk:4091 msgid "View" msgstr "视图" -#: gitk:4094 msgid "Gitk view definition" msgstr "Gitk视图定义" -#: gitk:4098 msgid "Remember this view" msgstr "记住此视图" -#: gitk:4099 msgid "References (space separated list):" msgstr "引用(空格切分的列表):" -#: gitk:4100 msgid "Branches & tags:" msgstr "分支和tags" -#: gitk:4101 msgid "All refs" msgstr "所有引用" -#: gitk:4102 msgid "All (local) branches" msgstr "所有(本地)分支" -#: gitk:4103 msgid "All tags" msgstr "所有tag" -#: gitk:4104 msgid "All remote-tracking branches" msgstr "所有远程跟踪分支" -#: gitk:4105 msgid "Commit Info (regular expressions):" msgstr "提交信息 (正则表达式):" -#: gitk:4106 msgid "Author:" msgstr "作者:" -#: gitk:4107 msgid "Committer:" msgstr "提交者:" -#: gitk:4108 msgid "Commit Message:" msgstr "提交信息:" -#: gitk:4109 msgid "Matches all Commit Info criteria" msgstr "匹配所有提交信息标准" -#: gitk:4110 msgid "Matches no Commit Info criteria" msgstr "匹配无提交信息标准" -#: gitk:4111 msgid "Changes to Files:" msgstr "文件修改列表:" -#: gitk:4112 msgid "Fixed String" msgstr "固定字符串" -#: gitk:4113 msgid "Regular Expression" msgstr "正则表达式:" -#: gitk:4114 msgid "Search string:" msgstr "搜索字符串:" -#: gitk:4115 msgid "" "Commit Dates (\"2 weeks ago\", \"2009-03-17 15:27:38\", \"March 17, 2009 " "15:27:38\"):" -msgstr "提交日期 (\"2星期之前\", \"2009-03-17 15:27:38\", \"5月 17, 2009 15:27:38\"):" +msgstr "" +"提交日期 (\"2星期之前\", \"2009-03-17 15:27:38\", \"5月 17, 2009 15:27:38\"):" -#: gitk:4116 msgid "Since:" msgstr "自:" -#: gitk:4117 msgid "Until:" msgstr "到:" -#: gitk:4118 msgid "Limit and/or skip a number of revisions (positive integer):" msgstr "限制 且/或 跳过一定数量的版本(正整数):" -#: gitk:4119 msgid "Number to show:" msgstr "显示数量:" -#: gitk:4120 msgid "Number to skip:" msgstr "跳过数量:" -#: gitk:4121 msgid "Miscellaneous options:" msgstr "其他选项:" -#: gitk:4122 msgid "Strictly sort by date" msgstr "严格按日期整理" -#: gitk:4123 msgid "Mark branch sides" msgstr "标记分支边界" -#: gitk:4124 msgid "Limit to first parent" msgstr "限制到第一个父提交" -#: gitk:4125 msgid "Simple history" msgstr "简易历史" -#: gitk:4126 msgid "Additional arguments to git log:" msgstr "git log命令的额外参数:" -#: gitk:4127 msgid "Enter files and directories to include, one per line:" msgstr "输入文件和文件夹来引用,每行一个:" -#: gitk:4128 msgid "Command to generate more commits to include:" msgstr "命令产生更多的提交来引用:" -#: gitk:4252 msgid "Gitk: edit view" msgstr "Gitk: 编辑视图" -#: gitk:4260 msgid "-- criteria for selecting revisions" msgstr "-- 用来选择版本的规则" -#: gitk:4265 msgid "View Name" msgstr "视图名称" -#: gitk:4340 msgid "Apply (F5)" msgstr "应用(F5)" -#: gitk:4378 msgid "Error in commit selection arguments:" msgstr "提交选择参数错误:" -#: gitk:4433 gitk:4486 gitk:4948 gitk:4962 gitk:6232 gitk:12524 gitk:12525 msgid "None" msgstr "无" -#: gitk:5045 gitk:5050 msgid "Descendant" msgstr "子提交" -#: gitk:5046 msgid "Not descendant" msgstr "非子提交" -#: gitk:5053 gitk:5058 msgid "Ancestor" msgstr "父提交" -#: gitk:5054 msgid "Not ancestor" msgstr "非父提交" -#: gitk:5348 msgid "Local changes checked in to index but not committed" msgstr "已添加到索引但未提交的修改" -#: gitk:5384 msgid "Local uncommitted changes, not checked in to index" msgstr "未添加到索引且未提交的修改" -#: gitk:7158 msgid "and many more" msgstr "更多" -#: gitk:7161 msgid "many" msgstr "很多" -#: gitk:7352 msgid "Tags:" msgstr "Tags:" -#: gitk:7369 gitk:7375 gitk:8854 msgid "Parent" msgstr "父节点" -#: gitk:7380 msgid "Child" msgstr "子节点" -#: gitk:7389 msgid "Branch" msgstr "分支" -#: gitk:7392 msgid "Follows" msgstr "之后的tag" -#: gitk:7395 msgid "Precedes" msgstr "之前的tag" -#: gitk:7990 #, tcl-format msgid "Error getting diffs: %s" msgstr "获取差异错误:%s" -#: gitk:8679 msgid "Goto:" msgstr "转到:" -#: gitk:8700 #, tcl-format msgid "Short SHA1 id %s is ambiguous" msgstr "短格式的SHA1提交号%s不明确、有歧义" -#: gitk:8707 #, tcl-format msgid "Revision %s is not known" msgstr "版本%s未知" -#: gitk:8717 #, tcl-format msgid "SHA1 id %s is not known" msgstr "提交号(SHA1 id)%s未知" -#: gitk:8719 #, tcl-format msgid "Revision %s is not in the current view" msgstr "版本%s不在当前视图中" -#: gitk:8861 gitk:8876 msgid "Date" msgstr "日期" -#: gitk:8864 msgid "Children" msgstr "子节点" -#: gitk:8927 #, tcl-format msgid "Reset %s branch to here" msgstr "重置分支%s到此处" -#: gitk:8929 msgid "Detached head: can't reset" msgstr "分离的头(head):不能重置(reset)" -#: gitk:9034 gitk:9040 msgid "Skipping merge commit " msgstr "跳过合并提交" -#: gitk:9049 gitk:9054 msgid "Error getting patch ID for " msgstr "获取补丁ID出错" -#: gitk:9050 gitk:9055 msgid " - stopping\n" msgstr " — 停止中\n" -#: gitk:9060 gitk:9063 gitk:9071 gitk:9085 gitk:9094 msgid "Commit " msgstr "提交" -#: gitk:9064 msgid "" " is the same patch as\n" " " -msgstr " 是相同的补丁(patch)\n " +msgstr "" +" 是相同的补丁(patch)\n" +" " -#: gitk:9072 msgid "" " differs from\n" " " -msgstr " 差异来自\n " +msgstr "" +" 差异来自\n" +" " -#: gitk:9074 msgid "" "Diff of commits:\n" "\n" -msgstr "提交的差异(Diff):\n\n" +msgstr "" +"提交的差异(Diff):\n" +"\n" -#: gitk:9086 gitk:9095 #, tcl-format msgid " has %s children - stopping\n" msgstr "有%s子节点 — 停止中\n" -#: gitk:9114 #, tcl-format msgid "Error writing commit to file: %s" msgstr "写入提交到文件出错:%s" -#: gitk:9120 #, tcl-format msgid "Error diffing commits: %s" msgstr "比较提交差异出错:%s" -#: gitk:9166 msgid "Top" msgstr "顶部" -#: gitk:9167 msgid "From" msgstr "从" -#: gitk:9172 msgid "To" msgstr "到" -#: gitk:9196 msgid "Generate patch" msgstr "生成补丁(patch)" -#: gitk:9198 msgid "From:" msgstr "从:" -#: gitk:9207 msgid "To:" msgstr "到:" -#: gitk:9216 msgid "Reverse" msgstr "反向(Reverse)" -#: gitk:9218 gitk:9428 msgid "Output file:" msgstr "输出文件:" -#: gitk:9224 msgid "Generate" msgstr "生成" -#: gitk:9262 msgid "Error creating patch:" msgstr "创建补丁(patch)出错:" -#: gitk:9285 gitk:9416 gitk:9504 msgid "ID:" msgstr "ID:" -#: gitk:9294 msgid "Tag name:" msgstr "Tag名称:" -#: gitk:9297 msgid "Tag message is optional" msgstr "Tag信息是可选的" -#: gitk:9299 msgid "Tag message:" msgstr "Tag信息:" -#: gitk:9303 gitk:9474 msgid "Create" msgstr "创建" -#: gitk:9321 msgid "No tag name specified" msgstr "未指定tag名称" -#: gitk:9325 #, tcl-format msgid "Tag \"%s\" already exists" msgstr "Tag\"%s\"已经存在" -#: gitk:9335 msgid "Error creating tag:" msgstr "创建tag出错:" -#: gitk:9425 msgid "Command:" msgstr "命令:" -#: gitk:9433 msgid "Write" msgstr "写入" -#: gitk:9451 msgid "Error writing commit:" msgstr "写入提交出错:" -#: gitk:9473 msgid "Create branch" msgstr "创建分支" -#: gitk:9489 #, tcl-format msgid "Rename branch %s" msgstr "重命名分支%s" -#: gitk:9490 msgid "Rename" msgstr "重命名" -#: gitk:9514 msgid "Name:" msgstr "名称:" -#: gitk:9538 msgid "Please specify a name for the new branch" msgstr "请指定新分支的名称" -#: gitk:9543 #, tcl-format msgid "Branch '%s' already exists. Overwrite?" msgstr "分支\"%s\"已经存在。覆盖它?" -#: gitk:9587 msgid "Please specify a new name for the branch" msgstr "请重新指定新分支的名称" -#: gitk:9650 #, tcl-format msgid "Commit %s is already included in branch %s -- really re-apply it?" msgstr "提交%s已经存在于分支%s。确定重新应用它?" -#: gitk:9655 msgid "Cherry-picking" msgstr "打补丁中(Cherry-picking)" -#: gitk:9664 #, tcl-format msgid "" "Cherry-pick failed because of local changes to file '%s'.\n" "Please commit, reset or stash your changes and try again." -msgstr "打补丁(Cherry-pick)失败,因为本地修改了文件\"%s\"。\n请提交(commit)、重置(reset)或暂存(stash)修改后重试。" +msgstr "" +"打补丁(Cherry-pick)失败,因为本地修改了文件\"%s\"。\n" +"请提交(commit)、重置(reset)或暂存(stash)修改后重试。" -#: gitk:9670 msgid "" "Cherry-pick failed because of merge conflict.\n" "Do you wish to run git citool to resolve it?" -msgstr "打补丁(Cherry-pick)失败因为合并冲突。\n你是否希望运行git citool 来解决冲突?" +msgstr "" +"打补丁(Cherry-pick)失败因为合并冲突。\n" +"你是否希望运行git citool 来解决冲突?" -#: gitk:9686 gitk:9744 msgid "No changes committed" msgstr "无已经提交的修改" -#: gitk:9713 #, tcl-format msgid "Commit %s is not included in branch %s -- really revert it?" msgstr "提交%s不包含在分支%s中,确认回滚(revert)它?" -#: gitk:9718 msgid "Reverting" msgstr "回滚中(Reverting)" -#: gitk:9726 #, 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 "回滚(revert)失败,因为如下的本地文件修改:%s\n请提交(commit)、重置(reset)或者暂存(stash)改变后重试。" +msgstr "" +"回滚(revert)失败,因为如下的本地文件修改:%s\n" +"请提交(commit)、重置(reset)或者暂存(stash)改变后重试。" -#: gitk:9730 msgid "" "Revert failed because of merge conflict.\n" " Do you wish to run git citool to resolve it?" -msgstr "回滚(revert)失败,因为合并冲突。\n你是否希望运行git citool来解决冲突?" +msgstr "" +"回滚(revert)失败,因为合并冲突。\n" +"你是否希望运行git citool来解决冲突?" -#: gitk:9773 msgid "Confirm reset" msgstr "确认重置(reset)" -#: gitk:9775 #, tcl-format msgid "Reset branch %s to %s?" msgstr "重置(reset)分支%s到%s?" -#: gitk:9777 msgid "Reset type:" msgstr "重置(reset)类型:" -#: gitk:9780 msgid "Soft: Leave working tree and index untouched" msgstr "软性:离开工作树,索引未改变" -#: gitk:9783 msgid "Mixed: Leave working tree untouched, reset index" msgstr "混合:离开工作树(未改变),索引重置" -#: gitk:9786 msgid "" "Hard: Reset working tree and index\n" "(discard ALL local changes)" -msgstr "硬性:重置工作树和索引\n(丢弃所有的本地修改)" +msgstr "" +"硬性:重置工作树和索引\n" +"(丢弃所有的本地修改)" -#: gitk:9803 msgid "Resetting" msgstr "重置中(Resetting)" -#: gitk:9876 #, tcl-format msgid "A local branch named %s exists already" msgstr "本地分支%s已经存在" -#: gitk:9884 msgid "Checking out" msgstr "检出中(Checking out)" -#: gitk:9943 msgid "Cannot delete the currently checked-out branch" msgstr "不能删除当前检出(checkout)分支" -#: gitk:9949 #, tcl-format msgid "" "The commits on branch %s aren't on any other branch.\n" "Really delete branch %s?" -msgstr "在分支%s上的提交不在其他任何分支上。\n确认删除分支%s?" +msgstr "" +"在分支%s上的提交不在其他任何分支上。\n" +"确认删除分支%s?" -#: gitk:9980 #, tcl-format msgid "Tags and heads: %s" msgstr "Tags和头指针(heads):%s" -#: gitk:9997 msgid "Filter" msgstr "过滤器" -#: gitk:10293 msgid "" "Error reading commit topology information; branch and preceding/following " "tag information will be incomplete." msgstr "读取提交拓扑信息出错;分支和之前/之后的tag信息将不能完成。" -#: gitk:11270 msgid "Tag" msgstr "标签(Tag)" -#: gitk:11274 msgid "Id" msgstr "Id" -#: gitk:11357 msgid "Gitk font chooser" msgstr "Gitk字体选择" -#: gitk:11374 msgid "B" msgstr "粗体" -#: gitk:11377 msgid "I" msgstr "斜体" -#: gitk:11495 msgid "Commit list display options" msgstr "提交列表展示选项" -#: gitk:11498 msgid "Maximum graph width (lines)" msgstr "最大图宽度(行数)" -#: gitk:11502 #, no-tcl-format msgid "Maximum graph width (% of pane)" msgstr "最大图宽度(%窗口百分比)" -#: gitk:11505 msgid "Show local changes" msgstr "显示本地修改" -#: gitk:11508 msgid "Auto-select SHA1 (length)" msgstr "自动选择SHA1(长度)" -#: gitk:11512 msgid "Hide remote refs" msgstr "隐藏远程引用" -#: gitk:11516 msgid "Diff display options" msgstr "差异(Diff)展示选项" -#: gitk:11518 msgid "Tab spacing" msgstr "制表符宽度" -#: gitk:11521 msgid "Display nearby tags/heads" msgstr "显示临近的tags/heads" -#: gitk:11524 msgid "Maximum # tags/heads to show" msgstr "最大tags/heads展示数量" -#: gitk:11527 msgid "Limit diffs to listed paths" msgstr "diff中列出文件限制" -#: gitk:11530 msgid "Support per-file encodings" msgstr "单独文件编码支持" -#: gitk:11536 gitk:11683 msgid "External diff tool" msgstr "外部差异(diff)工具" -#: gitk:11537 msgid "Choose..." msgstr "选择..." -#: gitk:11542 msgid "General options" msgstr "常规选项" -#: gitk:11545 msgid "Use themed widgets" msgstr "使用主题小部件" -#: gitk:11547 msgid "(change requires restart)" msgstr "(需重启生效)" -#: gitk:11549 msgid "(currently unavailable)" msgstr "(当前不可用)" -#: gitk:11560 msgid "Colors: press to choose" msgstr "颜色:点击来选择" -#: gitk:11563 msgid "Interface" msgstr "界面" -#: gitk:11564 msgid "interface" msgstr "界面" -#: gitk:11567 msgid "Background" msgstr "背景" -#: gitk:11568 gitk:11598 msgid "background" msgstr "背景" -#: gitk:11571 msgid "Foreground" msgstr "前景" -#: gitk:11572 msgid "foreground" msgstr "前景" -#: gitk:11575 msgid "Diff: old lines" msgstr "差异(Diff):老代码行" -#: gitk:11576 msgid "diff old lines" msgstr "差异(diff)老代码行" -#: gitk:11580 msgid "Diff: new lines" msgstr "差异(Diff):新代码行" -#: gitk:11581 msgid "diff new lines" msgstr "差异(diff)新代码行" -#: gitk:11585 msgid "Diff: hunk header" msgstr "差异(Diff):补丁片段头信息" -#: gitk:11587 msgid "diff hunk header" msgstr "差异(diff)补丁片段头信息" -#: gitk:11591 msgid "Marked line bg" msgstr "已标记代码行背景" -#: gitk:11593 msgid "marked line background" msgstr "已标记代码行背景" -#: gitk:11597 msgid "Select bg" msgstr "选择背景" -#: gitk:11606 msgid "Fonts: press to choose" msgstr "字体:点击来选择" -#: gitk:11608 msgid "Main font" msgstr "主字体" -#: gitk:11609 msgid "Diff display font" msgstr "差异(Diff)显示字体" -#: gitk:11610 msgid "User interface font" msgstr "用户界面字体" -#: gitk:11632 msgid "Gitk preferences" msgstr "Gitk偏好设置" -#: gitk:11641 msgid "General" msgstr "常规" -#: gitk:11642 msgid "Colors" msgstr "颜色" -#: gitk:11643 msgid "Fonts" msgstr "字体" -#: gitk:11693 #, tcl-format msgid "Gitk: choose color for %s" msgstr "Gitk:选择颜色用于%s" -#: gitk:12206 msgid "" "Sorry, gitk cannot run with this version of Tcl/Tk.\n" " Gitk requires at least Tcl/Tk 8.4." -msgstr "对不起,gitk不能运行在当前版本的Tcl/Tk中。\nGitk运行需要最低版本为Tcl/Tk8.4。" +msgstr "" +"对不起,gitk不能运行在当前版本的Tcl/Tk中。\n" +"Gitk运行需要最低版本为Tcl/Tk8.4。" -#: gitk:12416 msgid "Cannot find a git repository here." msgstr "在此位置未发现git仓库。" -#: gitk:12463 #, tcl-format msgid "Ambiguous argument '%s': both revision and filename" msgstr "不明确有歧义的参数\"%s\":版本和文件名称" -#: gitk:12475 msgid "Bad arguments to gitk:" msgstr "运行gitk参数错误:"
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index b5490df..fde8045 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl
@@ -4214,6 +4214,7 @@ <head> <meta name="generator" content="gitweb/$version git/$git_version$mod_perl_version"/> <meta name="robots" content="index, nofollow"/> +<meta name="viewport" content="width=device-width, initial-scale=1"/> <title>$title</title> EOF # the stylesheet, favicon etc urls won't work correctly with path_info
diff --git a/gitweb/static/gitweb.css b/gitweb/static/gitweb.css index 48d2e51..e2e6dd9 100644 --- a/gitweb/static/gitweb.css +++ b/gitweb/static/gitweb.css
@@ -42,7 +42,7 @@ } div.page_header { - height: 25px; + min-height: 25px; padding: 8px; font-size: 150%; font-weight: bold; @@ -73,11 +73,17 @@ } div.page_footer { - height: 22px; + min-height: 22px; padding: 4px 8px; background-color: #d9d8d1; } +div.page_footer::after { + content: ""; + display: table; + clear: both; +} + div.page_footer_text { line-height: 22px; float: left; @@ -123,6 +129,7 @@ div.log_body { padding: 8px 8px 8px 150px; + overflow-wrap: anywhere; } span.age { @@ -684,3 +691,66 @@ .kwb { color:#830000; } .kwc { color:#000000; font-weight:bold; } .kwd { color:#010181; } + +@media (max-width: 768px) { + div.page_body { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + + div.page_body div.pre { + min-width: max-content; + } + + div.projsearch { + padding: 0 8px; + box-sizing: border-box; + } + + div.projsearch input[type="text"] { + max-width: 100%; + box-sizing: border-box; + } + + div.title_text { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + padding-left: 4px; + padding-right: 4px; + box-sizing: border-box; + } + + div.title_text table.object_header { + width: max-content; + } + + div.log_body { + padding: 8px; + clear: left; + } + + div.patchset div.patch { + width: max-content; + min-width: 100%; + } + + div.diff.header { + padding: 4px 8px 2px 8px; + white-space: nowrap; + overflow-wrap: normal; + } + + div.diff.extended_header { + padding: 2px 8px; + white-space: nowrap; + overflow-wrap: normal; + } + + div.diff.ctx, + div.diff.add, + div.diff.rem, + div.diff.chunk_header { + padding: 0 8px; + white-space: pre; + } +}
diff --git a/gpg-interface.c b/gpg-interface.c index f680ed3..d517425 100644 --- a/gpg-interface.c +++ b/gpg-interface.c
@@ -382,7 +382,8 @@ static int verify_gpg_signed_buffer(struct signature_check *sigc, delete_tempfile(&temp); - ret |= !strstr(gpg_stdout.buf, "\n[GNUPG:] GOODSIG "); + ret |= !strstr(gpg_stdout.buf, "\n[GNUPG:] GOODSIG ") && + !strstr(gpg_stdout.buf, "\n[GNUPG:] EXPKEYSIG "); sigc->output = strbuf_detach(&gpg_stderr, NULL); sigc->gpg_status = strbuf_detach(&gpg_stdout, NULL); @@ -398,7 +399,7 @@ static void parse_ssh_output(struct signature_check *sigc) { const char *line, *principal, *search; char *to_free; - char *key = NULL; + const char *key; /* * ssh-keygen output should be: @@ -680,7 +681,7 @@ int check_signature(struct signature_check *sigc, if (status && !sigc->output) return !!status; - status |= sigc->result != 'G'; + status |= sigc->result != 'G' && sigc->result != 'Y'; status |= sigc->trust_level < configured_min_trust_level; return !!status; @@ -794,8 +795,16 @@ static int git_gpg_config(const char *var, const char *value, fmtname = "ssh"; if (fmtname) { + char *program; + int status; + fmt = get_format_by_name(fmtname); - return git_config_pathname((char **) &fmt->program, var, value); + status = git_config_pathname(&program, var, value); + if (status) + return status; + if (program) + fmt->program = program; + return status; } return 0; @@ -965,11 +974,20 @@ const char *gpg_trust_level_to_str(enum signature_trust_level level) return sigcheck_gpg_trust_level[level].display_key; } -int sign_buffer(struct strbuf *buffer, struct strbuf *signature, const char *signing_key) +int sign_buffer(struct strbuf *buffer, struct strbuf *signature, + const char *signing_key, enum sign_buffer_flags flags) { + char *keyid_to_free = NULL; + int ret = 0; + gpg_interface_lazy_init(); - return use_format->sign_buffer(buffer, signature, signing_key); + if ((flags & SIGN_BUFFER_USE_DEFAULT_KEY) && (!signing_key || !*signing_key)) + signing_key = keyid_to_free = get_signing_key(); + + ret = use_format->sign_buffer(buffer, signature, signing_key); + free(keyid_to_free); + return ret; } /* @@ -1134,19 +1152,28 @@ static int sign_buffer_ssh(struct strbuf *buffer, struct strbuf *signature, return ret; } -int parse_sign_mode(const char *arg, enum sign_mode *mode) +int parse_sign_mode(const char *arg, enum sign_mode *mode, const char **keyid) { - if (!strcmp(arg, "abort")) + if (!strcmp(arg, "abort")) { *mode = SIGN_ABORT; - else if (!strcmp(arg, "verbatim") || !strcmp(arg, "ignore")) + } else if (!strcmp(arg, "verbatim") || !strcmp(arg, "ignore")) { *mode = SIGN_VERBATIM; - else if (!strcmp(arg, "warn-verbatim") || !strcmp(arg, "warn")) + } else if (!strcmp(arg, "warn-verbatim") || !strcmp(arg, "warn")) { *mode = SIGN_WARN_VERBATIM; - else if (!strcmp(arg, "warn-strip")) + } else if (!strcmp(arg, "warn-strip")) { *mode = SIGN_WARN_STRIP; - else if (!strcmp(arg, "strip")) + } else if (!strcmp(arg, "strip")) { *mode = SIGN_STRIP; - else + } else if (!strcmp(arg, "strip-if-invalid")) { + *mode = SIGN_STRIP_IF_INVALID; + } else if (!strcmp(arg, "sign-if-invalid")) { + *mode = SIGN_SIGN_IF_INVALID; + } else if (skip_prefix(arg, "sign-if-invalid=", &arg)) { + *mode = SIGN_SIGN_IF_INVALID; + if (keyid) + *keyid = arg; + } else { return -1; + } return 0; }
diff --git a/gpg-interface.h b/gpg-interface.h index ead1ed6..a365586 100644 --- a/gpg-interface.h +++ b/gpg-interface.h
@@ -74,6 +74,15 @@ int parse_signature(const char *buf, size_t size, struct strbuf *payload, struct */ size_t parse_signed_buffer(const char *buf, size_t size); +/* Flags for sign_buffer(). */ +enum sign_buffer_flags { + /* + * Use the default configured signing key as returned by `get_signing_key()` + * when the provided "signing_key" is NULL or empty. + */ + SIGN_BUFFER_USE_DEFAULT_KEY = (1 << 0), +}; + /* * Create a detached signature for the contents of "buffer" and append * it after "signature"; "buffer" and "signature" can be the same @@ -81,8 +90,7 @@ size_t parse_signed_buffer(const char *buf, size_t size); * at the end. Returns 0 on success, non-zero on failure. */ int sign_buffer(struct strbuf *buffer, struct strbuf *signature, - const char *signing_key); - + const char *signing_key, enum sign_buffer_flags flags); /* * Returns corresponding string in lowercase for a given member of @@ -111,12 +119,16 @@ enum sign_mode { SIGN_VERBATIM, SIGN_WARN_STRIP, SIGN_STRIP, + SIGN_STRIP_IF_INVALID, + SIGN_SIGN_IF_INVALID, }; /* * Return 0 if `arg` can be parsed into an `enum sign_mode`. Return -1 - * otherwise. + * otherwise. If the parsed mode is SIGN_SIGN_IF_INVALID and GPG key provided in + * the arguments in the form `sign-if-invalid=<keyid>`, the key-ID is parsed + * into `char **keyid`. */ -int parse_sign_mode(const char *arg, enum sign_mode *mode); +int parse_sign_mode(const char *arg, enum sign_mode *mode, const char **keyid); #endif
diff --git a/hash.c b/hash.c index 4a04ecb..e925b97 100644 --- a/hash.c +++ b/hash.c
@@ -241,7 +241,49 @@ const char *empty_tree_oid_hex(const struct git_hash_algo *algop) return oid_to_hex_r(buf, algop->empty_tree); } -int hash_algo_by_name(const char *name) +const struct git_hash_algo *hash_algo_ptr_by_number(uint32_t algo) +{ + if (algo >= GIT_HASH_NALGOS) + return NULL; + return &hash_algos[algo]; +} + +struct git_hash_ctx *git_hash_alloc(void) +{ + return xmalloc(sizeof(struct git_hash_ctx)); +} + +void git_hash_free(struct git_hash_ctx *ctx) +{ + free(ctx); +} + +void git_hash_init(struct git_hash_ctx *ctx, const struct git_hash_algo *algop) +{ + algop->init_fn(ctx); +} + +void git_hash_clone(struct git_hash_ctx *dst, const struct git_hash_ctx *src) +{ + src->algop->clone_fn(dst, src); +} + +void git_hash_update(struct git_hash_ctx *ctx, const void *in, size_t len) +{ + ctx->algop->update_fn(ctx, in, len); +} + +void git_hash_final(unsigned char *hash, struct git_hash_ctx *ctx) +{ + ctx->algop->final_fn(hash, ctx); +} + +void git_hash_final_oid(struct object_id *oid, struct git_hash_ctx *ctx) +{ + ctx->algop->final_oid_fn(oid, ctx); +} + +uint32_t hash_algo_by_name(const char *name) { if (!name) return GIT_HASH_UNKNOWN; @@ -251,7 +293,7 @@ int hash_algo_by_name(const char *name) return GIT_HASH_UNKNOWN; } -int hash_algo_by_id(uint32_t format_id) +uint32_t hash_algo_by_id(uint32_t format_id) { for (size_t i = 1; i < GIT_HASH_NALGOS; i++) if (format_id == hash_algos[i].format_id) @@ -259,7 +301,7 @@ int hash_algo_by_id(uint32_t format_id) return GIT_HASH_UNKNOWN; } -int hash_algo_by_length(size_t len) +uint32_t hash_algo_by_length(size_t len) { for (size_t i = 1; i < GIT_HASH_NALGOS; i++) if (len == hash_algos[i].rawsz) @@ -275,3 +317,21 @@ const struct git_hash_algo *unsafe_hash_algo(const struct git_hash_algo *algop) /* Otherwise use the default one. */ return algop; } + +unsigned oid_common_prefix_hexlen(const struct object_id *a, + const struct object_id *b) +{ + unsigned rawsz = hash_algos[a->algo].rawsz; + + for (unsigned i = 0; i < rawsz; i++) { + if (a->hash[i] == b->hash[i]) + continue; + + if ((a->hash[i] ^ b->hash[i]) & 0xf0) + return i * 2; + else + return i * 2 + 1; + } + + return rawsz * 2; +}
diff --git a/hash.h b/hash.h index fae966b..c082a53 100644 --- a/hash.h +++ b/hash.h
@@ -211,7 +211,7 @@ static inline void git_SHA256_Clone(git_SHA256_CTX *dst, const git_SHA256_CTX *s struct object_id { unsigned char hash[GIT_MAX_RAWSZ]; - int algo; /* XXX requires 4-byte alignment */ + uint32_t algo; /* XXX requires 4-byte alignment */ }; #define GET_OID_QUIETLY 01 @@ -320,37 +320,25 @@ struct git_hash_algo { }; extern const struct git_hash_algo hash_algos[GIT_HASH_NALGOS]; -static inline void git_hash_clone(struct git_hash_ctx *dst, const struct git_hash_ctx *src) -{ - src->algop->clone_fn(dst, src); -} - -static inline void git_hash_update(struct git_hash_ctx *ctx, const void *in, size_t len) -{ - ctx->algop->update_fn(ctx, in, len); -} - -static inline void git_hash_final(unsigned char *hash, struct git_hash_ctx *ctx) -{ - ctx->algop->final_fn(hash, ctx); -} - -static inline void git_hash_final_oid(struct object_id *oid, struct git_hash_ctx *ctx) -{ - ctx->algop->final_oid_fn(oid, ctx); -} - +void git_hash_init(struct git_hash_ctx *ctx, const struct git_hash_algo *algop); +void git_hash_clone(struct git_hash_ctx *dst, const struct git_hash_ctx *src); +void git_hash_update(struct git_hash_ctx *ctx, const void *in, size_t len); +void git_hash_final(unsigned char *hash, struct git_hash_ctx *ctx); +void git_hash_final_oid(struct object_id *oid, 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); /* * Return a GIT_HASH_* constant based on the name. Returns GIT_HASH_UNKNOWN if * the name doesn't match a known algorithm. */ -int hash_algo_by_name(const char *name); +uint32_t hash_algo_by_name(const char *name); /* Identical, except based on the format ID. */ -int hash_algo_by_id(uint32_t format_id); +uint32_t hash_algo_by_id(uint32_t format_id); /* Identical, except based on the length. */ -int hash_algo_by_length(size_t len); +uint32_t hash_algo_by_length(size_t len); /* Identical, except for a pointer to struct git_hash_algo. */ -static inline int hash_algo_by_ptr(const struct git_hash_algo *p) +static inline uint32_t hash_algo_by_ptr(const struct git_hash_algo *p) { size_t i; for (i = 0; i < GIT_HASH_NALGOS; i++) { @@ -408,6 +396,9 @@ static inline int oideq(const struct object_id *oid1, const struct object_id *oi return !memcmp(oid1->hash, oid2->hash, GIT_MAX_RAWSZ); } +unsigned oid_common_prefix_hexlen(const struct object_id *a, + const struct object_id *b); + static inline void oidcpy(struct object_id *dst, const struct object_id *src) { memcpy(dst->hash, src->hash, GIT_MAX_RAWSZ);
diff --git a/hashmap.c b/hashmap.c index a711377..3b5d6f1 100644 --- a/hashmap.c +++ b/hashmap.c
@@ -194,7 +194,7 @@ void hashmap_partial_clear_(struct hashmap *map, ssize_t entry_offset) return; if (entry_offset >= 0) /* called by hashmap_clear_entries */ free_individual_entries(map, entry_offset); - memset(map->table, 0, map->tablesize * sizeof(struct hashmap_entry *)); + MEMZERO_ARRAY(map->table, map->tablesize); map->shrink_at = 0; map->private_size = 0; }
diff --git a/help.c b/help.c index 5854dd4..3e59d07 100644 --- a/help.c +++ b/help.c
@@ -20,6 +20,8 @@ #include "prompt.h" #include "fsmonitor-ipc.h" #include "repository.h" +#include "alias.h" +#include "utf8.h" #ifndef NO_CURL #include "git-curl-compat.h" /* For LIBCURL_VERSION only */ @@ -107,7 +109,7 @@ static void print_command_list(const struct cmdname_help *cmds, for (i = 0; cmds[i].name; i++) { if (cmds[i].category & mask) { - size_t len = strlen(cmds[i].name); + size_t len = utf8_strwidth(cmds[i].name); printf(" %s ", cmds[i].name); if (longest > len) mput_char(' ', longest - len); @@ -420,8 +422,7 @@ void list_cmds_by_config(struct string_list *list) if (repo_config_get_string_tmp(the_repository, "completion.commands", &cmd_list)) return; - string_list_sort(list); - string_list_remove_duplicates(list, 0); + string_list_sort_u(list, 1); while (*cmd_list) { struct strbuf sb = STRBUF_INIT; @@ -469,20 +470,6 @@ void list_developer_interfaces_help(void) putchar('\n'); } -static int get_alias(const char *var, const char *value, - const struct config_context *ctx UNUSED, void *data) -{ - struct string_list *list = data; - - if (skip_prefix(var, "alias.", &var)) { - if (!value) - return config_error_nonbool(var); - string_list_append(list, var)->util = xstrdup(value); - } - - return 0; -} - static void list_all_cmds_help_external_commands(void) { struct string_list others = STRING_LIST_INIT_DUP; @@ -502,11 +489,11 @@ static void list_all_cmds_help_aliases(int longest) struct cmdname_help *aliases; int i; - repo_config(the_repository, get_alias, &alias_list); + list_aliases(&alias_list); string_list_sort(&alias_list); for (i = 0; i < alias_list.nr; i++) { - size_t len = strlen(alias_list.items[i].string); + size_t len = utf8_strwidth(alias_list.items[i].string); if (longest < len) longest = len; } @@ -587,7 +574,8 @@ static int git_unknown_cmd_config(const char *var, const char *value, void *cb) { struct help_unknown_cmd_config *cfg = cb; - const char *p; + const char *subsection, *key; + size_t subsection_len; if (!strcmp(var, "help.autocorrect")) { int v = parse_autocorrect(value); @@ -602,8 +590,18 @@ static int git_unknown_cmd_config(const char *var, const char *value, } /* Also use aliases for command lookup */ - if (skip_prefix(var, "alias.", &p)) - add_cmdname(&cfg->aliases, p, strlen(p)); + if (!parse_config_key(var, "alias", &subsection, &subsection_len, + &key)) { + if (subsection) { + /* [alias "name"] command = value */ + if (!strcmp(key, "command")) + add_cmdname(&cfg->aliases, subsection, + subsection_len); + } else { + /* alias.name = value */ + add_cmdname(&cfg->aliases, key, strlen(key)); + } + } return 0; } @@ -799,6 +797,9 @@ void get_version_info(struct strbuf *buf, int show_build_options) if (fsmonitor_ipc__is_supported()) strbuf_addstr(buf, "feature: fsmonitor--daemon\n"); +#if !defined NO_GETTEXT + strbuf_addstr(buf, "gettext: enabled\n"); +#endif #if defined LIBCURL_VERSION strbuf_addf(buf, "libcurl: %s\n", LIBCURL_VERSION); #endif @@ -851,18 +852,16 @@ struct similar_ref_cb { struct string_list *similar_refs; }; -static int append_similar_ref(const char *refname, const char *referent UNUSED, - const struct object_id *oid UNUSED, - int flags UNUSED, void *cb_data) +static int append_similar_ref(const struct reference *ref, void *cb_data) { struct similar_ref_cb *cb = (struct similar_ref_cb *)(cb_data); - char *branch = strrchr(refname, '/') + 1; + const char *branch = strrchr(ref->name, '/') + 1; /* A remote branch of the same name is deemed similar */ - if (starts_with(refname, "refs/remotes/") && + if (starts_with(ref->name, "refs/remotes/") && !strcmp(branch, cb->base_ref)) string_list_append_nodup(cb->similar_refs, - refs_shorten_unambiguous_ref(get_main_ref_store(the_repository), refname, 1)); + refs_shorten_unambiguous_ref(get_main_ref_store(the_repository), ref->name, 1)); return 0; }
diff --git a/hook.c b/hook.c index b3de104..cc23276 100644 --- a/hook.c +++ b/hook.c
@@ -1,14 +1,16 @@ #include "git-compat-util.h" #include "abspath.h" #include "advice.h" +#include "config.h" +#include "environment.h" #include "gettext.h" #include "hook.h" +#include "parse.h" #include "path.h" #include "run-command.h" -#include "config.h" -#include "strbuf.h" -#include "environment.h" #include "setup.h" +#include "strbuf.h" +#include "strmap.h" const char *find_hook(struct repository *r, const char *name) { @@ -16,6 +18,9 @@ const char *find_hook(struct repository *r, const char *name) int found_hook; + if (!r || !r->gitdir) + return NULL; + repo_git_path_replace(r, &path, "hooks/%s", name); found_hook = access(path.buf, X_OK) >= 0; #ifdef STRIP_EXTENSION @@ -47,42 +52,436 @@ const char *find_hook(struct repository *r, const char *name) return path.buf; } +void hook_free(void *p, const char *str UNUSED) +{ + struct hook *h = p; + + if (!h) + return; + + if (h->kind == HOOK_TRADITIONAL) { + free((void *)h->u.traditional.path); + } else if (h->kind == HOOK_CONFIGURED) { + free((void *)h->u.configured.friendly_name); + free((void *)h->u.configured.command); + } + + if (h->data_free && h->feed_pipe_cb_data) + h->data_free(h->feed_pipe_cb_data); + + free(h); +} + +/* Helper to detect and add default "traditional" hooks from the hookdir. */ +static void list_hooks_add_default(struct repository *r, const char *hookname, + struct string_list *hook_list, + struct run_hooks_opt *options) +{ + const char *hook_path = find_hook(r, hookname); + struct hook *h; + + if (!hook_path) + return; + + CALLOC_ARRAY(h, 1); + + /* + * If the hook is to run in a specific dir, a relative path can + * become invalid in that dir, so convert to an absolute path. + */ + if (options && options->dir) + hook_path = absolute_path(hook_path); + + /* + * Setup per-hook internal state callback data. + * When provided, the alloc/free callbacks are always provided + * together, so use them to alloc/free the internal hook state. + */ + if (options && options->feed_pipe_cb_data_alloc) { + h->feed_pipe_cb_data = options->feed_pipe_cb_data_alloc(options->feed_pipe_ctx); + h->data_free = options->feed_pipe_cb_data_free; + } + + h->kind = HOOK_TRADITIONAL; + h->u.traditional.path = xstrdup(hook_path); + + string_list_append(hook_list, hook_path)->util = h; +} + +/* + * Cache entry stored as the .util pointer of string_list items inside the + * hook config cache. + */ +struct hook_config_cache_entry { + char *command; + enum config_scope scope; + bool disabled; +}; + +/* + * Callback struct to collect all hook.* keys in a single config pass. + * commands: friendly-name to command map. + * event_hooks: event-name to list of friendly-names map. + * disabled_hooks: set of friendly-names with hook.<friendly-name>.enabled = false. + */ +struct hook_all_config_cb { + struct strmap commands; + struct strmap event_hooks; + struct string_list disabled_hooks; +}; + +/* repo_config() callback that collects all hook.* configuration in one pass. */ +static int hook_config_lookup_all(const char *key, const char *value, + const struct config_context *ctx, + void *cb_data) +{ + struct hook_all_config_cb *data = cb_data; + const char *name, *subkey; + char *hook_name; + size_t name_len = 0; + + if (parse_config_key(key, "hook", &name, &name_len, &subkey)) + return 0; + + if (!value) + return config_error_nonbool(key); + + /* Extract name, ensuring it is null-terminated. */ + hook_name = xmemdupz(name, name_len); + + if (!strcmp(subkey, "event")) { + if (!*value) { + /* Empty values reset previous events for this hook. */ + struct hashmap_iter iter; + struct strmap_entry *e; + + strmap_for_each_entry(&data->event_hooks, &iter, e) + unsorted_string_list_remove(e->value, hook_name, 0); + } else { + struct string_list *hooks = + strmap_get(&data->event_hooks, value); + + if (!hooks) { + CALLOC_ARRAY(hooks, 1); + string_list_init_dup(hooks); + strmap_put(&data->event_hooks, value, hooks); + } + + /* Re-insert if necessary to preserve last-seen order. */ + unsorted_string_list_remove(hooks, hook_name, 0); + + if (!ctx->kvi) + BUG("hook config callback called without key-value info"); + + /* + * Stash the config scope in the util pointer for + * later retrieval in build_hook_config_map(). This + * intermediate struct is transient and never leaves + * that function, so we pack the enum value into the + * pointer rather than heap-allocating a wrapper. + */ + string_list_append(hooks, hook_name)->util = + (void *)(uintptr_t)ctx->kvi->scope; + } + } else if (!strcmp(subkey, "command")) { + /* Store command overwriting the old value */ + char *old = strmap_put(&data->commands, hook_name, + xstrdup(value)); + free(old); + } else if (!strcmp(subkey, "enabled")) { + switch (git_parse_maybe_bool(value)) { + case 0: /* disabled */ + if (!unsorted_string_list_lookup(&data->disabled_hooks, + hook_name)) + string_list_append(&data->disabled_hooks, + hook_name); + break; + case 1: /* enabled: undo a prior disabled entry */ + unsorted_string_list_remove(&data->disabled_hooks, + hook_name, 0); + break; + default: + break; /* ignore unrecognised values */ + } + } + + free(hook_name); + return 0; +} + +/* + * The hook config cache maps each hook event name to a string_list where + * every item's string is the hook's friendly-name and its util pointer is + * the corresponding command string. Both strings are owned by the map. + * + * Disabled hooks are kept in the cache with entry->disabled set, so that + * "git hook list" can display them. A non-disabled hook missing a command + * is fatal; a disabled hook missing a command emits a warning and is kept + * in the cache with entry->command = NULL. + */ +void hook_cache_clear(struct strmap *cache) +{ + struct hashmap_iter iter; + struct strmap_entry *e; + + strmap_for_each_entry(cache, &iter, e) { + struct string_list *hooks = e->value; + for (size_t i = 0; i < hooks->nr; i++) { + struct hook_config_cache_entry *entry = hooks->items[i].util; + free(entry->command); + free(entry); + } + string_list_clear(hooks, 0); + free(hooks); + } + strmap_clear(cache, 0); +} + +/* Populate `cache` with the complete hook configuration */ +static void build_hook_config_map(struct repository *r, struct strmap *cache) +{ + struct hook_all_config_cb cb_data; + struct hashmap_iter iter; + struct strmap_entry *e; + + strmap_init(&cb_data.commands); + strmap_init(&cb_data.event_hooks); + string_list_init_dup(&cb_data.disabled_hooks); + + /* Parse all configs in one run. */ + repo_config(r, hook_config_lookup_all, &cb_data); + + /* Construct the cache from parsed configs. */ + strmap_for_each_entry(&cb_data.event_hooks, &iter, e) { + struct string_list *hook_names = e->value; + struct string_list *hooks; + + CALLOC_ARRAY(hooks, 1); + string_list_init_dup(hooks); + + for (size_t i = 0; i < hook_names->nr; i++) { + const char *hname = hook_names->items[i].string; + enum config_scope scope = + (enum config_scope)(uintptr_t)hook_names->items[i].util; + struct hook_config_cache_entry *entry; + char *command; + + bool is_disabled = + !!unsorted_string_list_lookup( + &cb_data.disabled_hooks, hname); + + command = strmap_get(&cb_data.commands, hname); + if (!command) { + if (is_disabled) + warning(_("disabled hook '%s' has no " + "command configured"), hname); + else + die(_("'hook.%s.command' must be configured or " + "'hook.%s.event' must be removed;" + " aborting."), hname, hname); + } + + /* util stores a cache entry; owned by the cache. */ + CALLOC_ARRAY(entry, 1); + entry->command = xstrdup_or_null(command); + entry->scope = scope; + entry->disabled = is_disabled; + string_list_append(hooks, hname)->util = entry; + } + + strmap_put(cache, e->key, hooks); + } + + strmap_clear(&cb_data.commands, 1); + string_list_clear(&cb_data.disabled_hooks, 0); + strmap_for_each_entry(&cb_data.event_hooks, &iter, e) { + string_list_clear(e->value, 0); + free(e->value); + } + strmap_clear(&cb_data.event_hooks, 0); +} + +/* + * Return the hook config map for `r`, populating it first if needed. + * + * Out-of-repo calls (r->gitdir == NULL) allocate and return a temporary + * cache map; the caller is responsible for freeing it with + * hook_cache_clear() + free(). + */ +static struct strmap *get_hook_config_cache(struct repository *r) +{ + struct strmap *cache = NULL; + + if (r && r->gitdir) { + /* + * For in-repo calls, the map is stored in r->hook_config_cache, + * so repeated invocations don't parse the configs, so allocate + * it just once on the first call. + */ + if (!r->hook_config_cache) { + CALLOC_ARRAY(r->hook_config_cache, 1); + strmap_init(r->hook_config_cache); + build_hook_config_map(r, r->hook_config_cache); + } + cache = r->hook_config_cache; + } else { + /* + * Out-of-repo calls (no gitdir) allocate and return a temporary + * cache which gets freed immediately by the caller. + */ + CALLOC_ARRAY(cache, 1); + strmap_init(cache); + build_hook_config_map(r, cache); + } + + return cache; +} + +static void list_hooks_add_configured(struct repository *r, + const char *hookname, + struct string_list *list, + struct run_hooks_opt *options) +{ + struct strmap *cache = get_hook_config_cache(r); + struct string_list *configured_hooks = strmap_get(cache, hookname); + + /* Iterate through configured hooks and initialize internal states */ + for (size_t i = 0; configured_hooks && i < configured_hooks->nr; i++) { + const char *friendly_name = configured_hooks->items[i].string; + struct hook_config_cache_entry *entry = configured_hooks->items[i].util; + struct hook *hook; + + CALLOC_ARRAY(hook, 1); + + /* + * When provided, the alloc/free callbacks are always provided + * together, so use them to alloc/free the internal hook state. + */ + if (options && options->feed_pipe_cb_data_alloc) { + hook->feed_pipe_cb_data = + options->feed_pipe_cb_data_alloc( + options->feed_pipe_ctx); + hook->data_free = options->feed_pipe_cb_data_free; + } + + hook->kind = HOOK_CONFIGURED; + hook->u.configured.friendly_name = xstrdup(friendly_name); + hook->u.configured.command = + entry->command ? xstrdup(entry->command) : NULL; + hook->u.configured.scope = entry->scope; + hook->u.configured.disabled = entry->disabled; + + string_list_append(list, friendly_name)->util = hook; + } + + /* + * Cleanup temporary cache for out-of-repo calls since they can't be + * stored persistently. Next out-of-repo calls will have to re-parse. + */ + if (!r || !r->gitdir) { + hook_cache_clear(cache); + free(cache); + } +} + +struct string_list *list_hooks(struct repository *r, const char *hookname, + struct run_hooks_opt *options) +{ + struct string_list *hook_head; + + if (!hookname) + BUG("null hookname was provided to hook_list()!"); + + CALLOC_ARRAY(hook_head, 1); + string_list_init_dup(hook_head); + + /* Add hooks from the config, e.g. hook.myhook.event = pre-commit */ + list_hooks_add_configured(r, hookname, hook_head, options); + + /* Add the default "traditional" hooks from hookdir. */ + list_hooks_add_default(r, hookname, hook_head, options); + + return hook_head; +} + int hook_exists(struct repository *r, const char *name) { - return !!find_hook(r, name); + struct string_list *hooks = list_hooks(r, name, NULL); + int exists = 0; + + for (size_t i = 0; i < hooks->nr; i++) { + struct hook *h = hooks->items[i].util; + if (h->kind == HOOK_TRADITIONAL || + !h->u.configured.disabled) { + exists = 1; + break; + } + } + string_list_clear_func(hooks, hook_free); + free(hooks); + return exists; } static int pick_next_hook(struct child_process *cp, struct strbuf *out UNUSED, void *pp_cb, - void **pp_task_cb UNUSED) + void **pp_task_cb) { struct hook_cb_data *hook_cb = pp_cb; - const char *hook_path = hook_cb->hook_path; + struct string_list *hook_list = hook_cb->hook_command_list; + struct hook *h; - if (!hook_path) - return 0; + do { + if (hook_cb->hook_to_run_index >= hook_list->nr) + return 0; + h = hook_list->items[hook_cb->hook_to_run_index++].util; + } while (h->kind == HOOK_CONFIGURED && h->u.configured.disabled); cp->no_stdin = 1; strvec_pushv(&cp->env, hook_cb->options->env.v); + + if (hook_cb->options->path_to_stdin && hook_cb->options->feed_pipe) + BUG("options path_to_stdin and feed_pipe are mutually exclusive"); + /* reopen the file for stdin; run_command closes it. */ if (hook_cb->options->path_to_stdin) { cp->no_stdin = 0; cp->in = xopen(hook_cb->options->path_to_stdin, O_RDONLY); } - cp->stdout_to_stderr = 1; + + if (hook_cb->options->feed_pipe) { + cp->no_stdin = 0; + /* start_command() will allocate a pipe / stdin fd for us */ + cp->in = -1; + } + + cp->stdout_to_stderr = hook_cb->options->stdout_to_stderr; cp->trace2_hook_name = hook_cb->hook_name; cp->dir = hook_cb->options->dir; - strvec_push(&cp->args, hook_path); + /* Add hook exec paths or commands */ + if (h->kind == HOOK_TRADITIONAL) { + strvec_push(&cp->args, h->u.traditional.path); + } else if (h->kind == HOOK_CONFIGURED) { + /* to enable oneliners, let config-specified hooks run in shell. */ + cp->use_shell = true; + if (!h->u.configured.command) + BUG("non-disabled HOOK_CONFIGURED hook has no command"); + strvec_push(&cp->args, h->u.configured.command); + } else { + BUG("unknown hook kind"); + } + + if (!cp->args.nr) + BUG("hook must have at least one command or exec path"); + strvec_pushv(&cp->args, hook_cb->options->args.v); /* - * This pick_next_hook() will be called again, we're only - * running one hook, so indicate that no more work will be - * done. + * Provide per-hook internal state via task_cb for easy access, so + * hook callbacks don't have to go through hook_cb->options. */ - hook_cb->hook_path = NULL; + *pp_task_cb = h->feed_pipe_cb_data; return 1; } @@ -123,23 +522,22 @@ static void run_hooks_opt_clear(struct run_hooks_opt *options) int run_hooks_opt(struct repository *r, const char *hook_name, struct run_hooks_opt *options) { - struct strbuf abs_path = STRBUF_INIT; struct hook_cb_data cb_data = { .rc = 0, .hook_name = hook_name, .options = options, }; - const char *const hook_path = find_hook(r, hook_name); int ret = 0; const struct run_process_parallel_opts opts = { .tr2_category = "hook", .tr2_label = hook_name, - .processes = 1, - .ungroup = 1, + .processes = options->jobs, + .ungroup = options->jobs == 1, .get_next_task = pick_next_hook, .start_failure = notify_start_failure, + .feed_pipe = options->feed_pipe, .task_finished = notify_hook_finished, .data = &cb_data, @@ -148,27 +546,34 @@ int run_hooks_opt(struct repository *r, const char *hook_name, if (!options) BUG("a struct run_hooks_opt must be provided to run_hooks"); + if (options->path_to_stdin && options->feed_pipe) + BUG("options path_to_stdin and feed_pipe are mutually exclusive"); + + if (!options->jobs) + BUG("run_hooks_opt must be called with options.jobs >= 1"); + + /* + * Ensure cb_data copy and free functions are either provided together, + * or neither one is provided. + */ + if (!options->feed_pipe_cb_data_alloc != !options->feed_pipe_cb_data_free) + BUG("feed_pipe_cb_data_alloc and feed_pipe_cb_data_free must be set together"); + if (options->invoked_hook) *options->invoked_hook = 0; - if (!hook_path && !options->error_if_missing) + cb_data.hook_command_list = list_hooks(r, hook_name, options); + if (!cb_data.hook_command_list->nr) { + if (options->error_if_missing) + ret = error("cannot find a hook named %s", hook_name); goto cleanup; - - if (!hook_path) { - ret = error("cannot find a hook named %s", hook_name); - goto cleanup; - } - - cb_data.hook_path = hook_path; - if (options->dir) { - strbuf_add_absolute_path(&abs_path, hook_path); - cb_data.hook_path = abs_path.buf; } run_processes_parallel(&opts); ret = cb_data.rc; cleanup: - strbuf_release(&abs_path); + string_list_clear_func(cb_data.hook_command_list, hook_free); + free(cb_data.hook_command_list); run_hooks_opt_clear(options); return ret; }
diff --git a/hook.h b/hook.h index 11863fa..5c5628d 100644 --- a/hook.h +++ b/hook.h
@@ -1,11 +1,63 @@ #ifndef HOOK_H #define HOOK_H +#include "config.h" +#include "run-command.h" +#include "string-list.h" +#include "strmap.h" #include "strvec.h" struct repository; -struct run_hooks_opt -{ +typedef void (*hook_data_free_fn)(void *data); +typedef void *(*hook_data_alloc_fn)(void *init_ctx); + +/** + * Represents a hook command to be run. + * Hooks can be: + * 1. "traditional" (found in the hooks directory) + * 2. "configured" (defined in Git's configuration via hook.<friendly-name>.event). + * The 'kind' field determines which part of the union 'u' is valid. + */ +struct hook { + enum { + HOOK_TRADITIONAL, + HOOK_CONFIGURED, + } kind; + union { + struct { + const char *path; + } traditional; + struct { + const char *friendly_name; + const char *command; + enum config_scope scope; + bool disabled; + } configured; + } u; + + /** + * Opaque data pointer used to keep internal state across callback calls. + * + * It can be accessed directly via the third hook callback arg: + * struct ... *state = pp_task_cb; + * + * The caller is responsible for managing the memory for this data by + * providing alloc/free callbacks to `run_hooks_opt`. + * + * Only useful when using `run_hooks_opt.feed_pipe`, otherwise ignore it. + */ + void *feed_pipe_cb_data; + + /** + * Callback to free `feed_pipe_cb_data`. + * + * It is called automatically and points to the `feed_pipe_cb_data_free` + * provided via the `run_hook_opt` parameter. + */ + hook_data_free_fn data_free; +}; + +struct run_hooks_opt { /* Environment vars to be set for each hook */ struct strvec env; @@ -16,6 +68,14 @@ struct run_hooks_opt unsigned int error_if_missing:1; /** + * Number of processes to parallelize across. + * + * If > 1, output will be buffered and de-interleaved (ungroup=0). + * If == 1, output will be real-time (ungroup=1). + */ + unsigned int jobs; + + /** * An optional initial working directory for the hook, * translates to "struct child_process"'s "dir" member. */ @@ -34,25 +94,120 @@ struct run_hooks_opt int *invoked_hook; /** + * Send the hook's stdout to stderr. + * + * This is the default behavior for all hooks except pre-push, + * which has separate stdout and stderr streams for backwards + * compatibility reasons. + */ + unsigned int stdout_to_stderr:1; + + /** * Path to file which should be piped to stdin for each hook. */ const char *path_to_stdin; + + /** + * Callback used to incrementally feed a child hook stdin pipe. + * + * Useful especially if a hook consumes large quantities of data + * (e.g. a list of all refs in a client push), so feeding it via + * in-memory strings or slurping to/from files is inefficient. + * 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 internal state to `feed_pipe_cb_data`. + * + */ + feed_pipe_fn feed_pipe; + + /** + * Opaque data pointer used to pass context to `feed_pipe_fn`. + * + * It can be accessed via the second callback arg 'pp_cb': + * ((struct hook_cb_data *) pp_cb)->hook_cb->options->feed_pipe_ctx; + * + * The caller is responsible for managing the memory for this data. + * Only useful when using `run_hooks_opt.feed_pipe`, otherwise ignore it. + */ + void *feed_pipe_ctx; + + /** + * Some hooks need to create a fresh `feed_pipe_cb_data` internal state, + * so they can keep track of progress without affecting one another. + * + * If provided, this function will be called to alloc & initialize the + * `feed_pipe_cb_data` for each hook. + * + * The `feed_pipe_ctx` pointer can be used to pass initialization data. + */ + hook_data_alloc_fn feed_pipe_cb_data_alloc; + + /** + * Called to free the memory initialized by `feed_pipe_cb_data_alloc`. + * + * Must always be provided when `feed_pipe_cb_data_alloc` is provided. + */ + hook_data_free_fn feed_pipe_cb_data_free; }; #define RUN_HOOKS_OPT_INIT { \ .env = STRVEC_INIT, \ .args = STRVEC_INIT, \ + .stdout_to_stderr = 1, \ + .jobs = 1, \ } struct hook_cb_data { /* rc reflects the cumulative failure state */ int rc; const char *hook_name; - const char *hook_path; + + /** + * A list of hook commands/paths to run for the 'hook_name' event. + * + * The 'string' member of each item holds the path (for traditional hooks) + * or the unique friendly-name for hooks specified in configs. + * The 'util' member of each item points to the corresponding struct hook. + */ + struct string_list *hook_command_list; + + /* Iterator/cursor for the above list, pointing to the next hook to run. */ + size_t hook_to_run_index; + struct run_hooks_opt *options; }; -/* +/** + * Provides a list of hook commands to run for the 'hookname' event. + * + * This function consolidates hooks from two sources: + * 1. The config-based hooks (not yet implemented). + * 2. The "traditional" hook found in the repository hooks directory + * (e.g., .git/hooks/pre-commit). + * + * The list is ordered by execution priority. + * + * The caller is responsible for freeing the memory of the returned list + * using string_list_clear() and free(). + */ +struct string_list *list_hooks(struct repository *r, const char *hookname, + struct run_hooks_opt *options); + +/** + * Frees a struct hook stored as the util pointer of a string_list_item. + * Suitable for use as a string_list_clear_func_t callback. + */ +void hook_free(void *p, const char *str); + +/** + * Frees the hook configuration cache stored in `struct repository`. + * Called by repo_clear(). + */ +void hook_cache_clear(struct strmap *cache); + +/** * Returns the path to the hook file, or NULL if the hook is missing * or disabled. Note that this points to static storage that will be * overwritten by further calls to find_hook and run_hook_*.
diff --git a/http-backend.c b/http-backend.c index 52f0483..1a171c5 100644 --- a/http-backend.c +++ b/http-backend.c
@@ -16,6 +16,7 @@ #include "run-command.h" #include "string-list.h" #include "url.h" +#include "setup.h" #include "strvec.h" #include "packfile.h" #include "odb.h" @@ -143,8 +144,10 @@ static NORETURN void not_found(struct strbuf *hdr, const char *err, ...) end_headers(hdr); va_start(params, err); - if (err && *err) + if (err && *err) { vfprintf(stderr, err, params); + putc('\n', stderr); + } va_end(params); exit(0); } @@ -159,8 +162,10 @@ static NORETURN void forbidden(struct strbuf *hdr, const char *err, ...) end_headers(hdr); va_start(params, err); - if (err && *err) + if (err && *err) { vfprintf(stderr, err, params); + putc('\n', stderr); + } va_end(params); exit(0); } @@ -513,18 +518,17 @@ static void run_service(const char **argv, int buffer_input) exit(1); } -static int show_text_ref(const char *name, const char *referent UNUSED, const struct object_id *oid, - int flag UNUSED, void *cb_data) +static int show_text_ref(const struct reference *ref, void *cb_data) { - const char *name_nons = strip_namespace(name); + const char *name_nons = strip_namespace(ref->name); struct strbuf *buf = cb_data; - struct object *o = parse_object(the_repository, oid); + struct object *o = parse_object(the_repository, ref->oid); if (!o) return 0; - strbuf_addf(buf, "%s\t%s\n", oid_to_hex(oid), name_nons); + strbuf_addf(buf, "%s\t%s\n", oid_to_hex(ref->oid), name_nons); if (o->type == OBJ_TAG) { - o = deref_tag(the_repository, o, name, 0); + o = deref_tag(the_repository, o, ref->name, 0); if (!o) return 0; strbuf_addf(buf, "%s\t%s^{}\n", oid_to_hex(&o->oid), @@ -561,29 +565,32 @@ static void get_info_refs(struct strbuf *hdr, char *arg UNUSED) run_service(argv, 0); } else { + struct refs_for_each_ref_options opts = { + .namespace = get_git_namespace(), + }; + select_getanyfile(hdr); - refs_for_each_namespaced_ref(get_main_ref_store(the_repository), - NULL, show_text_ref, &buf); + refs_for_each_ref_ext(get_main_ref_store(the_repository), + show_text_ref, &buf, &opts); send_strbuf(hdr, "text/plain", &buf); } strbuf_release(&buf); } -static int show_head_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid, - int flag, void *cb_data) +static int show_head_ref(const struct reference *ref, void *cb_data) { struct strbuf *buf = cb_data; - if (flag & REF_ISSYMREF) { + if (ref->flags & REF_ISSYMREF) { const char *target = refs_resolve_ref_unsafe(get_main_ref_store(the_repository), - refname, + ref->name, RESOLVE_REF_READING, NULL, NULL); if (target) strbuf_addf(buf, "ref: %s\n", strip_namespace(target)); } else { - strbuf_addf(buf, "%s\n", oid_to_hex(oid)); + strbuf_addf(buf, "%s\n", oid_to_hex(ref->oid)); } return 0;
diff --git a/http-push.c b/http-push.c index a1c01e3..9ae6062 100644 --- a/http-push.c +++ b/http-push.c
@@ -104,7 +104,7 @@ struct repo { int has_info_refs; int can_update_info_refs; int has_info_packs; - struct packed_git *packs; + struct packfile_list packs; struct remote_lock *locks; }; @@ -311,7 +311,7 @@ static void start_fetch_packed(struct transfer_request *request) struct transfer_request *check_request = request_queue_head; struct http_pack_request *preq; - target = find_oid_pack(&request->obj->oid, repo->packs); + target = packfile_list_find_oid(repo->packs.head, &request->obj->oid); if (!target) { fprintf(stderr, "Unable to fetch %s, will not be able to update server info refs\n", oid_to_hex(&request->obj->oid)); repo->can_update_info_refs = 0; @@ -683,7 +683,7 @@ static int add_send_request(struct object *obj, struct remote_lock *lock) get_remote_object_list(obj->oid.hash[0]); if (obj->flags & (REMOTE | PUSHING)) return 0; - target = find_oid_pack(&obj->oid, repo->packs); + target = packfile_list_find_oid(repo->packs.head, &obj->oid); if (target) { obj->flags |= REMOTE; return 0; @@ -1311,7 +1311,7 @@ static struct object_list **process_tree(struct tree *tree, if (obj->flags & (UNINTERESTING | SEEN)) return p; - if (parse_tree(tree) < 0) + if (repo_parse_tree(the_repository, tree) < 0) die("bad tree object %s", oid_to_hex(&obj->oid)); obj->flags |= SEEN; @@ -1725,6 +1725,7 @@ int cmd_main(int argc, const char **argv) int i; int new_refs; struct ref *ref, *local_refs = NULL; + const char *gitdir; CALLOC_ARRAY(repo, 1); @@ -1767,7 +1768,7 @@ int cmd_main(int argc, const char **argv) usage(http_push_usage); } if (!repo->url) { - char *path = strstr(arg, "//"); + const char *path = strstr(arg, "//"); str_end_url_with_slash(arg, &repo->url); repo->path_len = strlen(repo->url); if (path) { @@ -1787,7 +1788,7 @@ int cmd_main(int argc, const char **argv) if (delete_branch && rs.nr != 1) die("You must specify only one branch name when deleting a remote branch"); - setup_git_directory(); + gitdir = setup_git_directory(); memset(remote_dir_exists, -1, 256); @@ -1941,7 +1942,7 @@ int cmd_main(int argc, const char **argv) if (!push_all && !is_null_oid(&ref->old_oid)) strvec_pushf(&commit_argv, "^%s", oid_to_hex(&ref->old_oid)); - repo_init_revisions(the_repository, &revs, setup_git_directory()); + repo_init_revisions(the_repository, &revs, gitdir); setup_revisions_from_strvec(&commit_argv, &revs, NULL); revs.edge_hint = 0; /* just in case */
diff --git a/http-walker.c b/http-walker.c index 0f7ae46..e886e64 100644 --- a/http-walker.c +++ b/http-walker.c
@@ -15,7 +15,7 @@ struct alt_base { char *base; int got_indices; - struct packed_git *packs; + struct packfile_list packs; struct alt_base *next; }; @@ -324,11 +324,8 @@ static void process_alternates_response(void *callback_data) } else if (is_alternate_allowed(target.buf)) { warning("adding alternate object store: %s", target.buf); - newalt = xmalloc(sizeof(*newalt)); - newalt->next = NULL; + CALLOC_ARRAY(newalt, 1); newalt->base = strbuf_detach(&target, NULL); - newalt->got_indices = 0; - newalt->packs = NULL; while (tail->next != NULL) tail = tail->next; @@ -435,7 +432,7 @@ static int http_fetch_pack(struct walker *walker, struct alt_base *repo, if (fetch_indices(walker, repo)) return -1; - target = find_oid_pack(oid, repo->packs); + target = packfile_list_find_oid(repo->packs.head, oid); if (!target) return -1; close_pack_index(target); @@ -584,17 +581,15 @@ static void cleanup(struct walker *walker) if (data) { alt = data->alt; while (alt) { - struct packed_git *pack; + struct packfile_list_entry *e; alt_next = alt->next; - pack = alt->packs; - while (pack) { - struct packed_git *pack_next = pack->next; - close_pack(pack); - free(pack); - pack = pack_next; + for (e = alt->packs.head; e; e = e->next) { + close_pack(e->pack); + free(e->pack); } + packfile_list_clear(&alt->packs); free(alt->base); free(alt); @@ -612,14 +607,11 @@ struct walker *get_http_walker(const char *url) struct walker_data *data = xmalloc(sizeof(struct walker_data)); struct walker *walker = xmalloc(sizeof(struct walker)); - data->alt = xmalloc(sizeof(*data->alt)); + CALLOC_ARRAY(data->alt, 1); data->alt->base = xstrdup(url); for (s = data->alt->base + strlen(data->alt->base) - 1; *s == '/'; --s) *s = 0; - data->alt->got_indices = 0; - data->alt->packs = NULL; - data->alt->next = NULL; data->got_alternates = -1; walker->corrupt_object_found = 0;
diff --git a/http.c b/http.c index 1713082..d8d0168 100644 --- a/http.c +++ b/http.c
@@ -22,6 +22,8 @@ #include "object-file.h" #include "odb.h" #include "tempfile.h" +#include "date.h" +#include "trace2.h" static struct trace_key trace_curl = TRACE_KEY_INIT(CURL); static int trace_curl_data = 1; @@ -149,6 +151,11 @@ static char *cached_accept_language; static char *http_ssl_backend; static int http_schannel_check_revoke = 1; + +static long http_retry_after = 0; +static long http_max_retries = 0; +static long http_max_retry_time = 300; + /* * With the backend being set to `schannel`, setting sslCAinfo would override * the Certificate Store in cURL v7.60.0 and later, which is not what we want @@ -209,7 +216,7 @@ static inline int is_hdr_continuation(const char *ptr, const size_t size) return size && (*ptr == ' ' || *ptr == '\t'); } -static size_t fwrite_wwwauth(char *ptr, size_t eltsize, size_t nmemb, void *p UNUSED) +static size_t fwrite_wwwauth(char *ptr, size_t eltsize, size_t nmemb, void *p MAYBE_UNUSED) { size_t size = eltsize * nmemb; struct strvec *values = &http_auth.wwwauth_headers; @@ -575,6 +582,21 @@ static int http_options(const char *var, const char *value, return 0; } + if (!strcmp("http.retryafter", var)) { + http_retry_after = git_config_int(var, value, ctx->kvi); + return 0; + } + + if (!strcmp("http.maxretries", var)) { + http_max_retries = git_config_int(var, value, ctx->kvi); + return 0; + } + + if (!strcmp("http.maxretrytime", var)) { + http_max_retry_time = git_config_int(var, value, ctx->kvi); + return 0; + } + /* Fall back on the default ones */ return git_default_config(var, value, ctx, data); } @@ -1422,6 +1444,10 @@ void http_init(struct remote *remote, const char *url, int proactive_auth) set_long_from_env(&curl_tcp_keepintvl, "GIT_TCP_KEEPINTVL"); set_long_from_env(&curl_tcp_keepcnt, "GIT_TCP_KEEPCNT"); + set_long_from_env(&http_retry_after, "GIT_HTTP_RETRY_AFTER"); + set_long_from_env(&http_max_retries, "GIT_HTTP_MAX_RETRIES"); + set_long_from_env(&http_max_retry_time, "GIT_HTTP_MAX_RETRY_TIME"); + curl_default = get_curl_handle(); } @@ -1871,6 +1897,10 @@ static int handle_curl_result(struct slot_results *results) } return HTTP_REAUTH; } + } else if (results->http_code == 429) { + trace2_data_intmax("http", the_repository, "http/429-retry-after", + results->retry_after); + return HTTP_RATE_LIMITED; } else { if (results->http_connectcode == 407) credential_reject(the_repository, &proxy_auth); @@ -1886,6 +1916,7 @@ int run_one_slot(struct active_request_slot *slot, struct slot_results *results) { slot->results = results; + if (!start_active_slot(slot)) { xsnprintf(curl_errorstr, sizeof(curl_errorstr), "failed to start HTTP request"); @@ -2119,10 +2150,10 @@ static void http_opt_request_remainder(CURL *curl, off_t pos) static int http_request(const char *url, void *result, int target, - const struct http_get_options *options) + struct http_get_options *options) { struct active_request_slot *slot; - struct slot_results results; + struct slot_results results = { .retry_after = -1 }; struct curl_slist *headers = http_copy_default_headers(); struct strbuf buf = STRBUF_INIT; const char *accept_language; @@ -2156,22 +2187,19 @@ static int http_request(const char *url, headers = curl_slist_append(headers, accept_language); strbuf_addstr(&buf, "Pragma:"); - if (options && options->no_cache) + if (options->no_cache) strbuf_addstr(&buf, " no-cache"); - if (options && options->initial_request && + if (options->initial_request && http_follow_config == HTTP_FOLLOW_INITIAL) curl_easy_setopt(slot->curl, CURLOPT_FOLLOWLOCATION, 1L); headers = curl_slist_append(headers, buf.buf); /* Add additional headers here */ - if (options && options->extra_headers) { + if (options->extra_headers) { const struct string_list_item *item; - if (options && options->extra_headers) { - for_each_string_list_item(item, options->extra_headers) { - headers = curl_slist_append(headers, item->string); - } - } + for_each_string_list_item(item, options->extra_headers) + headers = curl_slist_append(headers, item->string); } headers = http_append_auth_header(&http_auth, headers); @@ -2183,7 +2211,18 @@ static int http_request(const char *url, ret = run_one_slot(slot, &results); - if (options && options->content_type) { +#ifdef GIT_CURL_HAVE_CURLINFO_RETRY_AFTER + if (ret == HTTP_RATE_LIMITED) { + curl_off_t retry_after; + if (curl_easy_getinfo(slot->curl, CURLINFO_RETRY_AFTER, + &retry_after) == CURLE_OK && retry_after > 0) + results.retry_after = (long)retry_after; + } +#endif + + options->retry_after = results.retry_after; + + if (options->content_type) { struct strbuf raw = STRBUF_INIT; curlinfo_strbuf(slot->curl, CURLINFO_CONTENT_TYPE, &raw); extract_content_type(&raw, options->content_type, @@ -2191,7 +2230,7 @@ static int http_request(const char *url, strbuf_release(&raw); } - if (options && options->effective_url) + if (options->effective_url) curlinfo_strbuf(slot->curl, CURLINFO_EFFECTIVE_URL, options->effective_url); @@ -2253,22 +2292,66 @@ static int update_url_from_redirect(struct strbuf *base, return 1; } -static int http_request_reauth(const char *url, +/* + * Compute the retry delay for an HTTP 429 response. + * Returns a negative value if configuration is invalid (delay exceeds + * http.maxRetryTime), otherwise returns the delay in seconds (>= 0). + */ +static long handle_rate_limit_retry(long slot_retry_after) +{ + /* Use the slot-specific retry_after value or configured default */ + if (slot_retry_after >= 0) { + /* Check if retry delay exceeds maximum allowed */ + if (slot_retry_after > http_max_retry_time) { + error(_("response requested a delay greater than http.maxRetryTime (%ld > %ld seconds)"), + slot_retry_after, http_max_retry_time); + trace2_data_string("http", the_repository, + "http/429-error", "exceeds-max-retry-time"); + trace2_data_intmax("http", the_repository, + "http/429-requested-delay", slot_retry_after); + return -1; + } + return slot_retry_after; + } else { + /* No Retry-After header provided, use configured default */ + if (http_retry_after > http_max_retry_time) { + error(_("configured http.retryAfter exceeds http.maxRetryTime (%ld > %ld seconds)"), + http_retry_after, http_max_retry_time); + trace2_data_string("http", the_repository, + "http/429-error", "config-exceeds-max-retry-time"); + return -1; + } + trace2_data_string("http", the_repository, + "http/429-retry-source", "config-default"); + return http_retry_after; + } +} + +static int http_request_recoverable(const char *url, void *result, int target, struct http_get_options *options) { + static struct http_get_options empty_opts; int i = 3; int ret; + int rate_limit_retries = http_max_retries; + + if (!options) + options = &empty_opts; if (always_auth_proactively()) credential_fill(the_repository, &http_auth, 1); ret = http_request(url, result, target, options); - if (ret != HTTP_OK && ret != HTTP_REAUTH) + if (ret != HTTP_OK && ret != HTTP_REAUTH && ret != HTTP_RATE_LIMITED) return ret; - if (options && options->effective_url && options->base_url) { + /* If retries are disabled and we got a 429, fail immediately */ + if (ret == HTTP_RATE_LIMITED && !http_max_retries) + return HTTP_ERROR; + + if (options->effective_url && options->base_url) { if (update_url_from_redirect(options->base_url, url, options->effective_url)) { credential_from_url(&http_auth, options->base_url->buf); @@ -2276,7 +2359,9 @@ static int http_request_reauth(const char *url, } } - while (ret == HTTP_REAUTH && --i) { + while ((ret == HTTP_REAUTH && --i) || + (ret == HTTP_RATE_LIMITED && --rate_limit_retries)) { + long retry_delay = -1; /* * The previous request may have put cruft into our output stream; we * should clear it out before making our next request. @@ -2301,11 +2386,28 @@ static int http_request_reauth(const char *url, default: BUG("Unknown http_request target"); } + if (ret == HTTP_RATE_LIMITED) { + retry_delay = handle_rate_limit_retry(options->retry_after); + if (retry_delay < 0) + return HTTP_ERROR; - credential_fill(the_repository, &http_auth, 1); + if (retry_delay > 0) { + warning(_("rate limited, waiting %ld seconds before retry"), retry_delay); + trace2_data_intmax("http", the_repository, + "http/retry-sleep-seconds", retry_delay); + sleep(retry_delay); + } + } else if (ret == HTTP_REAUTH) { + credential_fill(the_repository, &http_auth, 1); + } ret = http_request(url, result, target, options); } + if (ret == HTTP_RATE_LIMITED) { + trace2_data_string("http", the_repository, + "http/429-error", "retries-exhausted"); + return HTTP_RATE_LIMITED; + } return ret; } @@ -2313,7 +2415,7 @@ int http_get_strbuf(const char *url, struct strbuf *result, struct http_get_options *options) { - return http_request_reauth(url, result, HTTP_REQUEST_STRBUF, options); + return http_request_recoverable(url, result, HTTP_REQUEST_STRBUF, options); } /* @@ -2337,7 +2439,7 @@ int http_get_file(const char *url, const char *filename, goto cleanup; } - ret = http_request_reauth(url, result, HTTP_REQUEST_FILE, options); + ret = http_request_recoverable(url, result, HTTP_REQUEST_FILE, options); fclose(result); if (ret == HTTP_OK && finalize_object_file(the_repository, tmpfile.buf, filename)) @@ -2413,8 +2515,9 @@ static char *fetch_pack_index(unsigned char *hash, const char *base_url) return tmp; } -static int fetch_and_setup_pack_index(struct packed_git **packs_head, - unsigned char *sha1, const char *base_url) +static int fetch_and_setup_pack_index(struct packfile_list *packs, + unsigned char *sha1, + const char *base_url) { struct packed_git *new_pack, *p; char *tmp_idx = NULL; @@ -2448,12 +2551,11 @@ static int fetch_and_setup_pack_index(struct packed_git **packs_head, if (ret) return -1; - new_pack->next = *packs_head; - *packs_head = new_pack; + packfile_list_prepend(packs, new_pack); return 0; } -int http_get_info_packs(const char *base_url, struct packed_git **packs_head) +int http_get_info_packs(const char *base_url, struct packfile_list *packs) { struct http_get_options options = {0}; int ret = 0; @@ -2477,7 +2579,7 @@ int http_get_info_packs(const char *base_url, struct packed_git **packs_head) !parse_oid_hex(data, &oid, &data) && skip_prefix(data, ".pack", &data) && (*data == '\n' || *data == '\0')) { - fetch_and_setup_pack_index(packs_head, oid.hash, base_url); + fetch_and_setup_pack_index(packs, oid.hash, base_url); } else { data = strchrnul(data, '\n'); } @@ -2541,15 +2643,11 @@ int finish_http_pack_request(struct http_pack_request *preq) } void http_install_packfile(struct packed_git *p, - struct packed_git **list_to_remove_from) + struct packfile_list *list_to_remove_from) { - struct packed_git **lst = list_to_remove_from; - - while (*lst != p) - lst = &((*lst)->next); - *lst = (*lst)->next; - - packfile_store_add_pack(the_repository->objects->packfiles, p); + struct odb_source_files *files = odb_source_files_downcast(the_repository->objects->sources); + packfile_list_remove(list_to_remove_from, p); + packfile_store_add_pack(files->packed, p); } struct http_pack_request *new_http_pack_request(
diff --git a/http.h b/http.h index 553e162..f9ee888 100644 --- a/http.h +++ b/http.h
@@ -2,6 +2,7 @@ #define HTTP_H struct packed_git; +struct packfile_list; #include "git-zlib.h" @@ -19,6 +20,7 @@ struct slot_results { long http_code; long auth_avail; long http_connectcode; + long retry_after; }; struct active_request_slot { @@ -156,6 +158,13 @@ struct http_get_options { * request has completed. */ struct string_list *extra_headers; + + /* + * After a request completes, contains the Retry-After delay in seconds + * if the server returned HTTP 429 with a Retry-After header (requires + * libcurl 7.66.0 or later), or -1 if no such header was present. + */ + long retry_after; }; /* Return values for http_get_*() */ @@ -166,6 +175,7 @@ struct http_get_options { #define HTTP_REAUTH 4 #define HTTP_NOAUTH 5 #define HTTP_NOMATCHPUBLICKEY 6 +#define HTTP_RATE_LIMITED 7 /* * Requests a URL and stores the result in a strbuf. @@ -190,7 +200,7 @@ struct curl_slist *http_append_auth_header(const struct credential *c, /* Helpers for fetching packs */ int http_get_info_packs(const char *base_url, - struct packed_git **packs_head); + struct packfile_list *packs); /* Helper for getting Accept-Language header */ const char *http_get_accept_language_header(void); @@ -226,7 +236,7 @@ void release_http_pack_request(struct http_pack_request *preq); * from http_get_info_packs() and have chosen a specific pack to fetch. */ void http_install_packfile(struct packed_git *p, - struct packed_git **list_to_remove_from); + struct packfile_list *list_to_remove_from); /* Helpers for fetching object */ struct http_object_request {
diff --git a/imap-send.c b/imap-send.c index 26dda7f..af02c6a 100644 --- a/imap-send.c +++ b/imap-send.c
@@ -219,8 +219,14 @@ static int ssl_socket_connect(struct imap_socket *sock UNUSED, #else -static int host_matches(const char *host, const char *pattern) +static int host_matches(const char *host, const ASN1_STRING *asn1_str) { + const char *pattern = (const char *)ASN1_STRING_get0_data(asn1_str); + + /* embedded NUL characters may open a security hole */ + if (memchr(pattern, '\0', ASN1_STRING_length(asn1_str))) + return 0; + if (pattern[0] == '*' && pattern[1] == '.') { pattern += 2; if (!(host = strchr(host, '.'))) @@ -233,9 +239,13 @@ static int host_matches(const char *host, const char *pattern) static int verify_hostname(X509 *cert, const char *hostname) { - int len; +#if (OPENSSL_VERSION_NUMBER >= 0x40000000L) + const X509_NAME *subj; +#else X509_NAME *subj; - char cname[1000]; +#endif + const X509_NAME_ENTRY *cname_entry; + const ASN1_STRING *cname; int i, found; STACK_OF(GENERAL_NAME) *subj_alt_names; @@ -244,10 +254,11 @@ static int verify_hostname(X509 *cert, const char *hostname) if ((subj_alt_names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL))) { int num_subj_alt_names = sk_GENERAL_NAME_num(subj_alt_names); for (i = 0; !found && i < num_subj_alt_names; i++) { + int ntype; GENERAL_NAME *subj_alt_name = sk_GENERAL_NAME_value(subj_alt_names, i); - if (subj_alt_name->type == GEN_DNS && - strlen((const char *)subj_alt_name->d.ia5->data) == (size_t)subj_alt_name->d.ia5->length && - host_matches(hostname, (const char *)(subj_alt_name->d.ia5->data))) + ASN1_STRING *subj_alt_str = GENERAL_NAME_get0_value(subj_alt_name, &ntype); + + if (ntype == GEN_DNS && host_matches(hostname, subj_alt_str)) found = 1; } sk_GENERAL_NAME_pop_free(subj_alt_names, GENERAL_NAME_free); @@ -258,12 +269,14 @@ static int verify_hostname(X509 *cert, const char *hostname) /* try the common name */ if (!(subj = X509_get_subject_name(cert))) return error("cannot get certificate subject"); - if ((len = X509_NAME_get_text_by_NID(subj, NID_commonName, cname, sizeof(cname))) < 0) + if ((i = X509_NAME_get_index_by_NID(subj, NID_commonName, -1)) < 0 || + (cname_entry = X509_NAME_get_entry(subj, i)) == NULL || + (cname = X509_NAME_ENTRY_get_data(cname_entry)) == NULL) return error("cannot get certificate common name"); - if (strlen(cname) == (size_t)len && host_matches(hostname, cname)) + if (host_matches(hostname, cname)) return 0; return error("certificate owner '%s' does not match hostname '%s'", - cname, hostname); + ASN1_STRING_get0_data(cname), hostname); } static int ssl_socket_connect(struct imap_socket *sock,
diff --git a/line-log.c b/line-log.c index 8bd4221..eeaf684 100644 --- a/line-log.c +++ b/line-log.c
@@ -1239,7 +1239,7 @@ static int process_ranges_merge_commit(struct rev_info *rev, struct commit *comm * don't follow any other path in history */ add_line_range(rev, parent, cand[i]); - free_commit_list(commit->parents); + commit_list_free(commit->parents); commit_list_append(parent, &commit->parents); ret = 0;
diff --git a/linear-assignment.c b/linear-assignment.c index 5416cbc..97b4f75 100644 --- a/linear-assignment.c +++ b/linear-assignment.c
@@ -20,8 +20,8 @@ void compute_assignment(int column_count, int row_count, int *cost, int i, j, phase; if (column_count < 2) { - memset(column2row, 0, sizeof(int) * column_count); - memset(row2column, 0, sizeof(int) * row_count); + MEMZERO_ARRAY(column2row, column_count); + MEMZERO_ARRAY(row2column, row_count); return; }
diff --git a/list-objects-filter-options.c b/list-objects-filter-options.c index 7420bf8..cef67e5 100644 --- a/list-objects-filter-options.c +++ b/list-objects-filter-options.c
@@ -20,6 +20,8 @@ const char *list_object_filter_config_name(enum list_objects_filter_choice c) case LOFC_DISABLED: /* we have no name for "no filter at all" */ break; + case LOFC_AUTO: + return "auto"; case LOFC_BLOB_NONE: return "blob:none"; case LOFC_BLOB_LIMIT: @@ -52,7 +54,16 @@ int gently_parse_list_objects_filter( if (filter_options->choice) BUG("filter_options already populated"); - if (!strcmp(arg, "blob:none")) { + if (!strcmp(arg, "auto")) { + if (!filter_options->allow_auto_filter) { + strbuf_addstr(errbuf, + _("'auto' filter not supported by this command")); + return 1; + } + filter_options->choice = LOFC_AUTO; + return 0; + + } else if (!strcmp(arg, "blob:none")) { filter_options->choice = LOFC_BLOB_NONE; return 0; @@ -114,9 +125,9 @@ int gently_parse_list_objects_filter( static const char *RESERVED_NON_WS = "~`!@#$^&*()[]{}\\;'\",<>?"; static int has_reserved_character( - struct strbuf *sub_spec, struct strbuf *errbuf) + const char *sub_spec, struct strbuf *errbuf) { - const char *c = sub_spec->buf; + const char *c = sub_spec; while (*c) { if (*c <= ' ' || strchr(RESERVED_NON_WS, *c)) { strbuf_addf( @@ -133,7 +144,7 @@ static int has_reserved_character( static int parse_combine_subfilter( struct list_objects_filter_options *filter_options, - struct strbuf *subspec, + const char *subspec, struct strbuf *errbuf) { size_t new_index = filter_options->sub_nr; @@ -144,12 +155,24 @@ static int parse_combine_subfilter( filter_options->sub_alloc); list_objects_filter_init(&filter_options->sub[new_index]); - decoded = url_percent_decode(subspec->buf); + decoded = url_percent_decode(subspec); - result = has_reserved_character(subspec, errbuf) || - gently_parse_list_objects_filter( + result = has_reserved_character(subspec, errbuf); + if (result) + goto cleanup; + + result = gently_parse_list_objects_filter( &filter_options->sub[new_index], decoded, errbuf); + if (result) + goto cleanup; + result = (filter_options->sub[new_index].choice == LOFC_AUTO); + if (result) { + strbuf_addstr(errbuf, _("an 'auto' filter cannot be combined")); + goto cleanup; + } + +cleanup: free(decoded); return result; } @@ -159,34 +182,34 @@ static int parse_combine_filter( const char *arg, struct strbuf *errbuf) { - struct strbuf **subspecs = strbuf_split_str(arg, '+', 0); - size_t sub; + const char *p = arg; + struct strbuf sub = STRBUF_INIT; int result = 0; - if (!subspecs[0]) { + if (!*p) { strbuf_addstr(errbuf, _("expected something after combine:")); result = 1; goto cleanup; } - for (sub = 0; subspecs[sub] && !result; sub++) { - if (subspecs[sub + 1]) { - /* - * This is not the last subspec. Remove trailing "+" so - * we can parse it. - */ - size_t last = subspecs[sub]->len - 1; - assert(subspecs[sub]->buf[last] == '+'); - strbuf_remove(subspecs[sub], last, 1); - } - result = parse_combine_subfilter( - filter_options, subspecs[sub], errbuf); + while (*p && !result) { + const char *end = strchrnul(p, '+'); + + strbuf_reset(&sub); + strbuf_add(&sub, p, end - p); + + if (sub.len) + result = parse_combine_subfilter(filter_options, sub.buf, errbuf); + + if (!*end) + break; + p = end + 1; } + strbuf_release(&sub); filter_options->choice = LOFC_COMBINE; cleanup: - strbuf_list_free(subspecs); if (result) list_objects_filter_release(filter_options); return result; @@ -263,6 +286,9 @@ void parse_list_objects_filter( } else { struct list_objects_filter_options *sub; + if (filter_options->choice == LOFC_AUTO) + die(_("an 'auto' filter is incompatible with any other filter")); + /* * Make filter_options an LOFC_COMBINE spec so we can trivially * add subspecs to it. @@ -277,6 +303,9 @@ void parse_list_objects_filter( if (gently_parse_list_objects_filter(sub, arg, &errbuf)) die("%s", errbuf.buf); + if (sub->choice == LOFC_AUTO) + die(_("an 'auto' filter is incompatible with any other filter")); + strbuf_addch(&filter_options->filter_spec, '+'); filter_spec_append_urlencode(filter_options, arg); } @@ -317,15 +346,19 @@ void list_objects_filter_release( struct list_objects_filter_options *filter_options) { size_t sub; + unsigned int allow_auto_filter; if (!filter_options) return; + + allow_auto_filter = filter_options->allow_auto_filter; strbuf_release(&filter_options->filter_spec); free(filter_options->sparse_oid_name); for (sub = 0; sub < filter_options->sub_nr; sub++) list_objects_filter_release(&filter_options->sub[sub]); free(filter_options->sub); list_objects_filter_init(filter_options); + filter_options->allow_auto_filter = allow_auto_filter; } void partial_clone_register(
diff --git a/list-objects-filter-options.h b/list-objects-filter-options.h index 7b2108b..77d7bbc 100644 --- a/list-objects-filter-options.h +++ b/list-objects-filter-options.h
@@ -18,6 +18,7 @@ enum list_objects_filter_choice { LOFC_SPARSE_OID, LOFC_OBJECT_TYPE, LOFC_COMBINE, + LOFC_AUTO, LOFC__COUNT /* must be last */ }; @@ -51,6 +52,11 @@ struct list_objects_filter_options { unsigned int no_filter : 1; /* + * Is LOFC_AUTO a valid option? + */ + unsigned int allow_auto_filter : 1; + + /* * BEGIN choice-specific parsed values from within the filter-spec. Only * some values will be defined for any given choice. */
diff --git a/list-objects-filter.c b/list-objects-filter.c index acd65eb..78316e7 100644 --- a/list-objects-filter.c +++ b/list-objects-filter.c
@@ -745,6 +745,13 @@ static void filter_combine__init( filter->finalize_omits_fn = filter_combine__finalize_omits; } +static void filter_auto__init( + struct list_objects_filter_options *filter_options UNUSED, + struct filter *filter UNUSED) +{ + BUG("LOFC_AUTO should have been resolved before initializing the filter"); +} + typedef void (*filter_init_fn)( struct list_objects_filter_options *filter_options, struct filter *filter); @@ -760,6 +767,7 @@ static filter_init_fn s_filters[] = { filter_sparse_oid__init, filter_object_type__init, filter_combine__init, + filter_auto__init, }; struct filter *list_objects_filter__init(
diff --git a/list-objects.c b/list-objects.c index 42c17d9..91b23e2 100644 --- a/list-objects.c +++ b/list-objects.c
@@ -167,10 +167,10 @@ static void process_tree(struct traversal_context *ctx, !revs->include_check_obj(&tree->object, revs->include_check_data)) return; - if (ctx->depth > max_allowed_tree_depth) + if (ctx->depth > revs->repo->settings.max_allowed_tree_depth) die("exceeded maximum allowed tree depth"); - failed_parse = parse_tree_gently(tree, 1); + failed_parse = repo_parse_tree_gently(the_repository, tree, 1); if (failed_parse) { if (revs->ignore_missing_links) return;
diff --git a/lockfile.c b/lockfile.c index 1d5ed01..7add2f1 100644 --- a/lockfile.c +++ b/lockfile.c
@@ -6,6 +6,9 @@ #include "abspath.h" #include "gettext.h" #include "lockfile.h" +#include "parse.h" +#include "strbuf.h" +#include "wrapper.h" /* * path = absolute or relative path name @@ -19,14 +22,14 @@ static void trim_last_path_component(struct strbuf *path) int i = path->len; /* back up past trailing slashes, if any */ - while (i && path->buf[i - 1] == '/') + while (i && is_dir_sep(path->buf[i - 1])) i--; /* * then go backwards until a slash, or the beginning of the * string */ - while (i && path->buf[i - 1] != '/') + while (i && !is_dir_sep(path->buf[i - 1])) i--; strbuf_setlen(path, i); @@ -71,19 +74,115 @@ static void resolve_symlink(struct strbuf *path) strbuf_reset(&link); } +/* + * Lock PID file functions - write PID to a foo~pid.lock file alongside + * the lock file for debugging stale locks. The PID file is registered + * as a tempfile so it gets cleaned up by signal/atexit handlers. + * + * Naming: For "foo.lock", the PID file is "foo~pid.lock". The tilde is + * forbidden in refnames and allowed in Windows filenames, guaranteeing + * no collision with the refs namespace. + */ + +/* Global config variable, initialized from core.lockfilePid */ +int lockfile_pid_enabled; + +/* + * Path generation helpers. + * Given base path "foo", generate: + * - lock path: "foo.lock" + * - pid path: "foo-pid.lock" + */ +static void get_lock_path(struct strbuf *out, const char *path) +{ + strbuf_addstr(out, path); + strbuf_addstr(out, LOCK_SUFFIX); +} + +static void get_pid_path(struct strbuf *out, const char *path) +{ + strbuf_addstr(out, path); + strbuf_addstr(out, LOCK_PID_INFIX); + strbuf_addstr(out, LOCK_SUFFIX); +} + +static struct tempfile *create_lock_pid_file(const char *pid_path, int mode) +{ + struct strbuf content = STRBUF_INIT; + struct tempfile *pid_tempfile = NULL; + int fd; + + if (!lockfile_pid_enabled) + goto out; + + fd = open(pid_path, O_WRONLY | O_CREAT | O_EXCL, mode); + if (fd < 0) + goto out; + + strbuf_addf(&content, "pid %" PRIuMAX "\n", (uintmax_t)getpid()); + if (write_in_full(fd, content.buf, content.len) < 0) { + warning_errno(_("could not write lock pid file '%s'"), pid_path); + close(fd); + unlink(pid_path); + goto out; + } + + close(fd); + pid_tempfile = register_tempfile(pid_path); + +out: + strbuf_release(&content); + return pid_tempfile; +} + +static int read_lock_pid(const char *pid_path, uintmax_t *pid_out) +{ + struct strbuf content = STRBUF_INIT; + const char *val; + int ret = -1; + + if (strbuf_read_file(&content, pid_path, LOCK_PID_MAXLEN) <= 0) + goto out; + + strbuf_rtrim(&content); + + if (skip_prefix(content.buf, "pid ", &val)) { + char *endptr; + *pid_out = strtoumax(val, &endptr, 10); + if (*pid_out > 0 && !*endptr) + ret = 0; + } + + if (ret) + warning(_("malformed lock pid file '%s'"), pid_path); + +out: + strbuf_release(&content); + return ret; +} + /* Make sure errno contains a meaningful value on error */ static int lock_file(struct lock_file *lk, const char *path, int flags, int mode) { - struct strbuf filename = STRBUF_INIT; + struct strbuf base_path = STRBUF_INIT; + struct strbuf lock_path = STRBUF_INIT; + struct strbuf pid_path = STRBUF_INIT; - strbuf_addstr(&filename, path); + strbuf_addstr(&base_path, path); if (!(flags & LOCK_NO_DEREF)) - resolve_symlink(&filename); + resolve_symlink(&base_path); - strbuf_addstr(&filename, LOCK_SUFFIX); - lk->tempfile = create_tempfile_mode(filename.buf, mode); - strbuf_release(&filename); + get_lock_path(&lock_path, base_path.buf); + get_pid_path(&pid_path, base_path.buf); + + lk->tempfile = create_tempfile_mode(lock_path.buf, mode); + if (lk->tempfile) + lk->pid_tempfile = create_lock_pid_file(pid_path.buf, mode); + + strbuf_release(&base_path); + strbuf_release(&lock_path); + strbuf_release(&pid_path); return lk->tempfile ? lk->tempfile->fd : -1; } @@ -151,16 +250,49 @@ static int lock_file_timeout(struct lock_file *lk, const char *path, void unable_to_lock_message(const char *path, int err, struct strbuf *buf) { if (err == EEXIST) { - strbuf_addf(buf, _("Unable to create '%s.lock': %s.\n\n" - "Another git process seems to be running in this repository, e.g.\n" - "an editor opened by 'git commit'. Please make sure all processes\n" - "are terminated then try again. If it still fails, a git process\n" - "may have crashed in this repository earlier:\n" - "remove the file manually to continue."), - absolute_path(path), strerror(err)); - } else + const char *abs_path = absolute_path(path); + struct strbuf lock_path = STRBUF_INIT; + struct strbuf pid_path = STRBUF_INIT; + uintmax_t pid; + int pid_status = 0; /* 0 = unknown, 1 = running, -1 = stale */ + + get_lock_path(&lock_path, abs_path); + get_pid_path(&pid_path, abs_path); + + strbuf_addf(buf, _("Unable to create '%s': %s.\n\n"), + lock_path.buf, strerror(err)); + + /* + * Try to read PID file unconditionally - it may exist if + * core.lockfilePid was enabled. + */ + if (!read_lock_pid(pid_path.buf, &pid)) { + if (kill((pid_t)pid, 0) == 0 || errno == EPERM) + pid_status = 1; /* running (or no permission to signal) */ + else if (errno == ESRCH) + pid_status = -1; /* no such process - stale lock */ + } + + if (pid_status == 1) + strbuf_addf(buf, _("Lock may be held by process %" PRIuMAX "; " + "if no git process is running, the lock file " + "may be stale (PIDs can be reused)"), + pid); + else if (pid_status == -1) + strbuf_addf(buf, _("Lock was held by process %" PRIuMAX ", " + "which is no longer running; the lock file " + "appears to be stale"), + pid); + else + strbuf_addstr(buf, _("Another git process seems to be running in this repository, " + "or the lock file may be stale")); + + strbuf_release(&lock_path); + strbuf_release(&pid_path); + } else { strbuf_addf(buf, _("Unable to create '%s.lock': %s"), absolute_path(path), strerror(err)); + } } NORETURN void unable_to_lock_die(const char *path, int err) @@ -207,6 +339,8 @@ int commit_lock_file(struct lock_file *lk) { char *result_path = get_locked_file_path(lk); + delete_tempfile(&lk->pid_tempfile); + if (commit_lock_file_to(lk, result_path)) { int save_errno = errno; free(result_path); @@ -216,3 +350,9 @@ int commit_lock_file(struct lock_file *lk) free(result_path); return 0; } + +int rollback_lock_file(struct lock_file *lk) +{ + delete_tempfile(&lk->pid_tempfile); + return delete_tempfile(&lk->tempfile); +}
diff --git a/lockfile.h b/lockfile.h index 1bb9926..e7233f2 100644 --- a/lockfile.h +++ b/lockfile.h
@@ -119,6 +119,7 @@ struct lock_file { struct tempfile *tempfile; + struct tempfile *pid_tempfile; }; #define LOCK_INIT { 0 } @@ -127,6 +128,22 @@ struct lock_file { #define LOCK_SUFFIX ".lock" #define LOCK_SUFFIX_LEN 5 +/* + * PID file naming: for a lock file "foo.lock", the PID file is "foo~pid.lock". + * The tilde is forbidden in refnames and allowed in Windows filenames, avoiding + * namespace collisions (e.g., refs "foo" and "foo~pid" cannot both exist). + */ +#define LOCK_PID_INFIX "~pid" +#define LOCK_PID_INFIX_LEN 4 + +/* Maximum length for PID file content */ +#define LOCK_PID_MAXLEN 32 + +/* + * Whether to create PID files alongside lock files. + * Configured via core.lockfilePid (boolean). + */ +extern int lockfile_pid_enabled; /* * Flags @@ -169,12 +186,12 @@ struct lock_file { * handling, and mode are described above. */ int hold_lock_file_for_update_timeout_mode( - struct lock_file *lk, const char *path, - int flags, long timeout_ms, int mode); + struct lock_file *lk, const char *path, + int flags, long timeout_ms, int mode); static inline int hold_lock_file_for_update_timeout( - struct lock_file *lk, const char *path, - int flags, long timeout_ms) + struct lock_file *lk, const char *path, + int flags, long timeout_ms) { return hold_lock_file_for_update_timeout_mode(lk, path, flags, timeout_ms, 0666); @@ -186,15 +203,14 @@ static inline int hold_lock_file_for_update_timeout( * argument and error handling are described above. */ static inline int hold_lock_file_for_update( - struct lock_file *lk, const char *path, - int flags) + struct lock_file *lk, const char *path, int flags) { return hold_lock_file_for_update_timeout(lk, path, flags, 0); } static inline int hold_lock_file_for_update_mode( - struct lock_file *lk, const char *path, - int flags, int mode) + struct lock_file *lk, const char *path, + int flags, int mode) { return hold_lock_file_for_update_timeout_mode(lk, path, flags, 0, mode); } @@ -319,13 +335,10 @@ static inline int commit_lock_file_to(struct lock_file *lk, const char *path) /* * Roll back `lk`: close the file descriptor and/or file pointer and - * remove the lockfile. It is a NOOP to call `rollback_lock_file()` - * for a `lock_file` object that has already been committed or rolled - * back. No error will be returned in this case. + * remove the lockfile and any associated PID file. It is a NOOP to + * call `rollback_lock_file()` for a `lock_file` object that has already + * been committed or rolled back. No error will be returned in this case. */ -static inline int rollback_lock_file(struct lock_file *lk) -{ - return delete_tempfile(&lk->tempfile); -} +int rollback_lock_file(struct lock_file *lk); #endif /* LOCKFILE_H */
diff --git a/log-tree.c b/log-tree.c index 7d917f2..7e04870 100644 --- a/log-tree.c +++ b/log-tree.c
@@ -147,9 +147,7 @@ static int ref_filter_match(const char *refname, return 1; } -static int add_ref_decoration(const char *refname, const char *referent UNUSED, const struct object_id *oid, - int flags UNUSED, - void *cb_data) +static int add_ref_decoration(const struct reference *ref, void *cb_data) { int i; struct object *obj; @@ -158,16 +156,16 @@ static int add_ref_decoration(const char *refname, const char *referent UNUSED, struct decoration_filter *filter = (struct decoration_filter *)cb_data; const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref; - if (filter && !ref_filter_match(refname, filter)) + if (filter && !ref_filter_match(ref->name, filter)) return 0; - if (starts_with(refname, git_replace_ref_base)) { + if (starts_with(ref->name, git_replace_ref_base)) { struct object_id original_oid; if (!replace_refs_enabled(the_repository)) return 0; - if (get_oid_hex(refname + strlen(git_replace_ref_base), + if (get_oid_hex(ref->name + strlen(git_replace_ref_base), &original_oid)) { - warning("invalid replace ref %s", refname); + warning("invalid replace ref %s", ref->name); return 0; } obj = parse_object(the_repository, &original_oid); @@ -176,10 +174,10 @@ static int add_ref_decoration(const char *refname, const char *referent UNUSED, return 0; } - objtype = odb_read_object_info(the_repository->objects, oid, NULL); + objtype = odb_read_object_info(the_repository->objects, ref->oid, NULL); if (objtype < 0) return 0; - obj = lookup_object_by_type(the_repository, oid, objtype); + obj = lookup_object_by_type(the_repository, ref->oid, objtype); for (i = 0; i < ARRAY_SIZE(ref_namespace); i++) { struct ref_namespace_info *info = &ref_namespace[i]; @@ -187,24 +185,24 @@ static int add_ref_decoration(const char *refname, const char *referent UNUSED, if (!info->decoration) continue; if (info->exact) { - if (!strcmp(refname, info->ref)) { + if (!strcmp(ref->name, info->ref)) { deco_type = info->decoration; break; } - } else if (starts_with(refname, info->ref)) { + } else if (starts_with(ref->name, info->ref)) { deco_type = info->decoration; break; } } - add_name_decoration(deco_type, refname, obj); + add_name_decoration(deco_type, ref->name, obj); while (obj->type == OBJ_TAG) { if (!obj->parsed) parse_object(the_repository, &obj->oid); obj = ((struct tag *)obj)->tagged; if (!obj) break; - add_name_decoration(DECORATION_REF_TAG, refname, obj); + add_name_decoration(DECORATION_REF_TAG, ref->name, obj); } return 0; } @@ -1079,7 +1077,7 @@ static int do_remerge_diff(struct rev_info *opt, log_tree_diff_flush(opt); /* Cleanup */ - free_commit_list(bases); + commit_list_free(bases); cleanup_additional_headers(&opt->diffopt); strbuf_release(&parent1_desc); strbuf_release(&parent2_desc);
diff --git a/loose.c b/loose.c index e8ea6e7..07333be 100644 --- a/loose.c +++ b/loose.c
@@ -1,7 +1,9 @@ #include "git-compat-util.h" #include "hash.h" #include "path.h" +#include "object-file.h" #include "odb.h" +#include "odb/source-files.h" #include "hex.h" #include "repository.h" #include "wrapper.h" @@ -48,27 +50,29 @@ static int insert_loose_map(struct odb_source *source, const struct object_id *oid, const struct object_id *compat_oid) { - struct loose_object_map *map = source->loose_map; + struct odb_source_files *files = odb_source_files_downcast(source); + struct loose_object_map *map = files->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(source->loose_objects_cache, compat_oid); + oidtree_insert(files->loose->cache, compat_oid); return inserted; } static int load_one_loose_object_map(struct repository *repo, struct odb_source *source) { + struct odb_source_files *files = odb_source_files_downcast(source); struct strbuf buf = STRBUF_INIT, path = STRBUF_INIT; FILE *fp; - if (!source->loose_map) - loose_object_map_init(&source->loose_map); - if (!source->loose_objects_cache) { - ALLOC_ARRAY(source->loose_objects_cache, 1); - oidtree_init(source->loose_objects_cache); + 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); } insert_loose_map(source, repo->hash_algo->empty_tree, repo->compat_hash_algo->empty_tree); @@ -124,7 +128,8 @@ int repo_read_loose_object_map(struct repository *repo) int repo_write_loose_object_map(struct repository *repo) { - kh_oid_map_t *map = repo->objects->sources->loose_map->to_compat; + struct odb_source_files *files = odb_source_files_downcast(repo->objects->sources); + kh_oid_map_t *map = files->loose->map->to_compat; struct lock_file lock; int fd; khiter_t iter; @@ -230,7 +235,8 @@ int repo_loose_object_map_oid(struct repository *repo, khiter_t pos; for (source = repo->objects->sources; source; source = source->next) { - struct loose_object_map *loose_map = source->loose_map; + struct odb_source_files *files = odb_source_files_downcast(source); + struct loose_object_map *loose_map = files->loose->map; if (!loose_map) continue; map = (to == repo->compat_hash_algo) ?
diff --git a/ls-refs.c b/ls-refs.c index c47acde..9759826 100644 --- a/ls-refs.c +++ b/ls-refs.c
@@ -75,42 +75,42 @@ struct ls_refs_data { unsigned unborn : 1; }; -static int send_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid, - int flag, void *cb_data) +static int send_ref(const struct reference *ref, void *cb_data) { struct ls_refs_data *data = cb_data; - const char *refname_nons = strip_namespace(refname); + const char *refname_nons = strip_namespace(ref->name); strbuf_reset(&data->buf); - if (ref_is_hidden(refname_nons, refname, &data->hidden_refs)) + if (ref_is_hidden(refname_nons, ref->name, &data->hidden_refs)) return 0; if (!ref_match(&data->prefixes, refname_nons)) return 0; - if (oid) - strbuf_addf(&data->buf, "%s %s", oid_to_hex(oid), refname_nons); + if (ref->oid) + strbuf_addf(&data->buf, "%s %s", oid_to_hex(ref->oid), refname_nons); else strbuf_addf(&data->buf, "unborn %s", refname_nons); - if (data->symrefs && flag & REF_ISSYMREF) { + if (data->symrefs && ref->flags & REF_ISSYMREF) { + int unused_flag; struct object_id unused; const char *symref_target = refs_resolve_ref_unsafe(get_main_ref_store(the_repository), - refname, + ref->name, 0, &unused, - &flag); + &unused_flag); if (!symref_target) - die("'%s' is a symref but it is not?", refname); + die("'%s' is a symref but it is not?", ref->name); strbuf_addf(&data->buf, " symref-target:%s", strip_namespace(symref_target)); } - if (data->peel && oid) { + if (data->peel && ref->oid) { struct object_id peeled; - if (!peel_iterated_oid(the_repository, oid, &peeled)) + if (!reference_get_peeled_oid(the_repository, ref, &peeled)) strbuf_addf(&data->buf, " peeled:%s", oid_to_hex(&peeled)); } @@ -131,9 +131,17 @@ static void send_possibly_unborn_head(struct ls_refs_data *data) if (!refs_resolve_ref_unsafe(get_main_ref_store(the_repository), namespaced.buf, 0, &oid, &flag)) return; /* bad ref */ oid_is_null = is_null_oid(&oid); + if (!oid_is_null || - (data->unborn && data->symrefs && (flag & REF_ISSYMREF))) - send_ref(namespaced.buf, NULL, oid_is_null ? NULL : &oid, flag, data); + (data->unborn && data->symrefs && (flag & REF_ISSYMREF))) { + struct reference ref = { + .name = namespaced.buf, + .oid = oid_is_null ? NULL : &oid, + .flags = flag, + }; + + send_ref(&ref, data); + } strbuf_release(&namespaced); } @@ -152,6 +160,7 @@ static int ls_refs_config(const char *var, const char *value, int ls_refs(struct repository *r, struct packet_reader *request) { + struct refs_for_each_ref_options opts = { 0 }; struct ls_refs_data data; memset(&data, 0, sizeof(data)); @@ -193,10 +202,12 @@ int ls_refs(struct repository *r, struct packet_reader *request) send_possibly_unborn_head(&data); if (!data.prefixes.nr) strvec_push(&data.prefixes, ""); - refs_for_each_fullref_in_prefixes(get_main_ref_store(r), - get_git_namespace(), data.prefixes.v, - hidden_refs_to_excludes(&data.hidden_refs), - send_ref, &data); + + opts.exclude_patterns = hidden_refs_to_excludes(&data.hidden_refs); + opts.namespace = get_git_namespace(); + + refs_for_each_ref_in_prefixes(get_main_ref_store(r), data.prefixes.v, + &opts, send_ref, &data); packet_fflush(stdout); strvec_clear(&data.prefixes); strbuf_release(&data.buf);
diff --git a/mailinfo.c b/mailinfo.c index 99ac596..13949ff 100644 --- a/mailinfo.c +++ b/mailinfo.c
@@ -470,7 +470,7 @@ static int convert_to_utf8(struct mailinfo *mi, return error("cannot convert from %s to %s", charset, mi->metainfo_charset); } - strbuf_attach(line, out, out_len, out_len); + strbuf_attach(line, out, out_len, out_len + 1); return 0; } @@ -1141,7 +1141,7 @@ static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf { const char *sp = data->buf; while (1) { - char *ep = strchr(sp, '\n'); + const char *ep = strchr(sp, '\n'); int len; if (!ep) len = strlen(sp);
diff --git a/mailmap.c b/mailmap.c index 37fd158..3b26917 100644 --- a/mailmap.c +++ b/mailmap.c
@@ -7,9 +7,7 @@ #include "object-name.h" #include "odb.h" #include "setup.h" - -char *git_mailmap_file; -char *git_mailmap_blob; +#include "config.h" struct mailmap_info { char *name; @@ -183,7 +181,8 @@ static void read_mailmap_string(struct string_list *map, char *buf) } } -int read_mailmap_blob(struct string_list *map, const char *name) +int read_mailmap_blob(struct repository *repo, struct string_list *map, + const char *name) { struct object_id oid; char *buf; @@ -192,10 +191,10 @@ int read_mailmap_blob(struct string_list *map, const char *name) if (!name) return 0; - if (repo_get_oid(the_repository, name, &oid) < 0) + if (repo_get_oid(repo, name, &oid) < 0) return 0; - buf = odb_read_object(the_repository->objects, &oid, &type, &size); + buf = odb_read_object(repo->objects, &oid, &type, &size); if (!buf) return error("unable to read mailmap object at %s", name); if (type != OBJ_BLOB) { @@ -209,23 +208,32 @@ int read_mailmap_blob(struct string_list *map, const char *name) return 0; } -int read_mailmap(struct string_list *map) +int read_mailmap(struct repository *repo, struct string_list *map) { int err = 0; + char *mailmap_file = NULL, *mailmap_blob = NULL; + + repo_config_get_pathname(repo, "mailmap.file", &mailmap_file); + repo_config_get_string(repo, "mailmap.blob", &mailmap_blob); map->strdup_strings = 1; map->cmp = namemap_cmp; - if (!git_mailmap_blob && is_bare_repository()) - git_mailmap_blob = xstrdup("HEAD:.mailmap"); + if (!mailmap_blob && is_bare_repository()) + mailmap_blob = xstrdup("HEAD:.mailmap"); if (!startup_info->have_repository || !is_bare_repository()) err |= read_mailmap_file(map, ".mailmap", startup_info->have_repository ? MAILMAP_NOFOLLOW : 0); if (startup_info->have_repository) - err |= read_mailmap_blob(map, git_mailmap_blob); - err |= read_mailmap_file(map, git_mailmap_file, 0); + err |= read_mailmap_blob(repo, map, mailmap_blob); + + err |= read_mailmap_file(map, mailmap_file, 0); + + free(mailmap_file); + free(mailmap_blob); + return err; }
diff --git a/mailmap.h b/mailmap.h index 908365e..6866cb6 100644 --- a/mailmap.h +++ b/mailmap.h
@@ -1,19 +1,18 @@ #ifndef MAILMAP_H #define MAILMAP_H +struct repository; struct string_list; -extern char *git_mailmap_file; -extern char *git_mailmap_blob; - /* Flags for read_mailmap_file() */ #define MAILMAP_NOFOLLOW (1<<0) int read_mailmap_file(struct string_list *map, const char *filename, unsigned flags); -int read_mailmap_blob(struct string_list *map, const char *name); +int read_mailmap_blob(struct repository *repo, struct string_list *map, + const char *name); -int read_mailmap(struct string_list *map); +int read_mailmap(struct repository *repo, struct string_list *map); void clear_mailmap(struct string_list *map); int map_user(struct string_list *map,
diff --git a/mem-pool.c b/mem-pool.c index 62441dc..8bc77cb 100644 --- a/mem-pool.c +++ b/mem-pool.c
@@ -169,7 +169,7 @@ char *mem_pool_strdup(struct mem_pool *pool, const char *str) char *mem_pool_strndup(struct mem_pool *pool, const char *str, size_t len) { - char *p = memchr(str, '\0', len); + const char *p = memchr(str, '\0', len); size_t actual_len = (p ? p - str : len); char *ret = mem_pool_alloc(pool, actual_len+1);
diff --git a/merge-ort-wrappers.c b/merge-ort-wrappers.c index c54d56b..2110844 100644 --- a/merge-ort-wrappers.c +++ b/merge-ort-wrappers.c
@@ -120,7 +120,7 @@ int merge_ort_generic(struct merge_options *opt, repo_hold_locked_index(opt->repo, &lock, LOCK_DIE_ON_ERROR); clean = merge_ort_recursive(opt, head_commit, next_commit, ca, result); - free_commit_list(ca); + commit_list_free(ca); if (clean < 0) { rollback_lock_file(&lock); return clean;
diff --git a/merge-ort.c b/merge-ort.c index 2985807..00923ce 100644 --- a/merge-ort.c +++ b/merge-ort.c
@@ -54,6 +54,14 @@ #include "xdiff-interface.h" /* + * We technically need USE_THE_REPOSITORY_VARIABLE above for DEFAULT_ABBREV, + * but do not want more uses of the_repository. Prevent them. + * + * opt->repo is available; use it instead. + */ +#define the_repository DO_NOT_USE_THE_REPOSITORY + +/* * We have many arrays of size 3. Whenever we have such an array, the * indices refer to one of the sides of the three-way merge. This is so * pervasive that the constants 0, 1, and 2 are used in many places in the @@ -1502,11 +1510,44 @@ static void resolve_trivial_directory_merge(struct conflict_info *ci, int side) VERIFY_CI(ci); assert((side == 1 && ci->match_mask == 5) || (side == 2 && ci->match_mask == 3)); + + /* + * Since ci->stages[0] matches ci->stages[3-side], resolve merge in + * favor of ci->stages[side]. + */ oidcpy(&ci->merged.result.oid, &ci->stages[side].oid); ci->merged.result.mode = ci->stages[side].mode; ci->merged.is_null = is_null_oid(&ci->stages[side].oid); + + /* + * Because we resolved in favor of "side", we are no longer + * considering the paths which matched (i.e. had the same hash) any + * more. Strip the matching paths from both dirmask & filemask. + * Another consequence of merging in favor of side is that we can no + * longer have a directory/file conflict either..but there's a slight + * nuance we consider before clearing it. + * + * In most cases, resolving in favor of the other side means there's + * no conflict at all, but if we had a directory/file conflict to + * start, and the directory is resolved away, the remaining file could + * still be part of a rename. If the remaining file is part of a + * rename, then it may also be part of a rename conflict (e.g. + * rename/delete or rename/rename(1to2)), so we can't + * mark it as a clean merge if we started with a directory/file + * conflict and still have a file left. + * + * In contrast, if we started with a directory/file conflict and + * still have a directory left, no file under that directory can be + * part of a rename, otherwise we would have had to recurse into the + * directory and would have never ended up within + * resolve_trivial_directory_merge() for that directory. + */ + ci->dirmask &= (~ci->match_mask); + ci->filemask &= (~ci->match_mask); + assert(!ci->filemask || !ci->dirmask); ci->match_mask = 0; - ci->merged.clean = 1; /* (ci->filemask == 0); */ + ci->merged.clean = !ci->df_conflict || ci->dirmask; + ci->df_conflict = 0; } static int handle_deferred_entries(struct merge_options *opt, @@ -1699,9 +1740,9 @@ static int collect_merge_info(struct merge_options *opt, info.data = opt; info.show_all_errors = 1; - if (parse_tree(merge_base) < 0 || - parse_tree(side1) < 0 || - parse_tree(side2) < 0) + if (repo_parse_tree(opt->repo, merge_base) < 0 || + repo_parse_tree(opt->repo, side1) < 0 || + repo_parse_tree(opt->repo, side2) < 0) return -1; init_tree_desc(t + 0, &merge_base->object.oid, merge_base->buffer, merge_base->size); @@ -1824,7 +1865,7 @@ static int merge_submodule(struct merge_options *opt, BUG("submodule deleted on one side; this should be handled outside of merge_submodule()"); if ((sub_not_initialized = repo_submodule_init(&subrepo, - opt->repo, path, null_oid(the_hash_algo)))) { + opt->repo, path, null_oid(opt->repo->hash_algo)))) { path_msg(opt, CONFLICT_SUBMODULE_NOT_INITIALIZED, 0, path, NULL, NULL, NULL, _("Failed to merge submodule %s (not checked out)"), @@ -2103,9 +2144,9 @@ static int merge_3way(struct merge_options *opt, name2 = mkpathdup("%s:%s", opt->branch2, pathnames[2]); } - read_mmblob(&orig, o); - read_mmblob(&src1, a); - read_mmblob(&src2, b); + read_mmblob(&orig, opt->repo->objects, o); + read_mmblob(&src1, opt->repo->objects, a); + read_mmblob(&src2, opt->repo->objects, b); merge_status = ll_merge(result_buf, path, &orig, base, &src1, name1, &src2, name2, @@ -2207,7 +2248,7 @@ static int handle_content_merge(struct merge_options *opt, two_way = ((S_IFMT & o->mode) != (S_IFMT & a->mode)); merge_status = merge_3way(opt, path, - two_way ? null_oid(the_hash_algo) : &o->oid, + two_way ? null_oid(opt->repo->hash_algo) : &o->oid, &a->oid, &b->oid, pathnames, extra_marker_size, &result_buf); @@ -2221,7 +2262,7 @@ static int handle_content_merge(struct merge_options *opt, } if (!ret && record_object && - odb_write_object(the_repository->objects, result_buf.ptr, result_buf.size, + odb_write_object(opt->repo->objects, result_buf.ptr, result_buf.size, OBJ_BLOB, &result->oid)) { path_msg(opt, ERROR_OBJECT_WRITE_FAILED, 0, pathnames[0], pathnames[1], pathnames[2], NULL, @@ -2239,7 +2280,7 @@ static int handle_content_merge(struct merge_options *opt, } else if (S_ISGITLINK(a->mode)) { int two_way = ((S_IFMT & o->mode) != (S_IFMT & a->mode)); clean = merge_submodule(opt, pathnames[0], - two_way ? null_oid(the_hash_algo) : &o->oid, + two_way ? null_oid(opt->repo->hash_algo) : &o->oid, &a->oid, &b->oid, &result->oid); if (clean < 0) return -1; @@ -2698,7 +2739,7 @@ static void apply_directory_rename_modifications(struct merge_options *opt, while (1) { /* Find the parent directory of cur_path */ - char *last_slash = strrchr(cur_path, '/'); + const char *last_slash = strrchr(cur_path, '/'); if (last_slash) { parent_name = mem_pool_strndup(&opt->priv->pool, cur_path, @@ -2753,7 +2794,7 @@ static void apply_directory_rename_modifications(struct merge_options *opt, assert(!new_ci->match_mask); new_ci->dirmask = 0; new_ci->stages[1].mode = 0; - oidcpy(&new_ci->stages[1].oid, null_oid(the_hash_algo)); + oidcpy(&new_ci->stages[1].oid, null_oid(opt->repo->hash_algo)); /* * Now that we have the file information in new_ci, make sure @@ -2766,7 +2807,7 @@ static void apply_directory_rename_modifications(struct merge_options *opt, continue; /* zero out any entries related to files */ ci->stages[i].mode = 0; - oidcpy(&ci->stages[i].oid, null_oid(the_hash_algo)); + oidcpy(&ci->stages[i].oid, null_oid(opt->repo->hash_algo)); } /* Now we want to focus on new_ci, so reassign ci to it. */ @@ -2913,6 +2954,32 @@ static int process_renames(struct merge_options *opt, continue; /* + * Rename caching from a previous commit might give us an + * irrelevant rename for the current commit. + * + * Imagine: + * foo/A -> bar/A + * was a cached rename for the upstream side from the + * previous commit (without the directories being renamed), + * but the next commit being replayed + * * does NOT add or delete files + * * does NOT have directory renames + * * does NOT modify any files under bar/ + * * does NOT modify foo/A + * * DOES modify other files under foo/ (otherwise the + * !oldinfo check above would have already exited for + * us) + * In such a case, our trivial directory resolution will + * have already merged bar/, and our attempt to process + * the cached + * foo/A -> bar/A + * would be counterproductive, and lack the necessary + * information anyway. Skip such renames. + */ + if (!newinfo) + continue; + + /* * diff_filepairs have copies of pathnames, thus we have to * use standard 'strcmp()' (negated) instead of '=='. */ @@ -3155,7 +3222,7 @@ static int process_renames(struct merge_options *opt, if (type_changed) { /* rename vs. typechange */ /* Mark the original as resolved by removal */ - memcpy(&oldinfo->stages[0].oid, null_oid(the_hash_algo), + memcpy(&oldinfo->stages[0].oid, null_oid(opt->repo->hash_algo), sizeof(oldinfo->stages[0].oid)); oldinfo->stages[0].mode = 0; oldinfo->filemask &= 0x06; @@ -3438,7 +3505,7 @@ static int collect_renames(struct merge_options *opt, continue; } if (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_NONE && - p->status == 'R' && 1) { + p->status == 'R') { possibly_cache_new_pair(renames, p, side_index, NULL); goto skip_directory_renames; } @@ -3654,7 +3721,7 @@ static int read_oid_strbuf(struct merge_options *opt, void *buf; enum object_type type; unsigned long size; - buf = odb_read_object(the_repository->objects, oid, &type, &size); + buf = odb_read_object(opt->repo->objects, oid, &type, &size); if (!buf) { path_msg(opt, ERROR_OBJECT_READ_FAILED, 0, path, NULL, NULL, NULL, @@ -3763,15 +3830,16 @@ static int tree_entry_order(const void *a_, const void *b_) b->string, strlen(b->string), bmi->result.mode); } -static int write_tree(struct object_id *result_oid, +static int write_tree(struct repository *repo, + struct object_id *result_oid, struct string_list *versions, - unsigned int offset, - size_t hash_size) + unsigned int offset) { size_t maxlen = 0, extra; unsigned int nr; struct strbuf buf = STRBUF_INIT; int i, ret = 0; + size_t hash_size = repo->hash_algo->rawsz; assert(offset <= versions->nr); nr = versions->nr - offset; @@ -3797,7 +3865,7 @@ static int write_tree(struct object_id *result_oid, } /* Write this object file out, and record in result_oid */ - if (odb_write_object(the_repository->objects, buf.buf, + if (odb_write_object(repo->objects, buf.buf, buf.len, OBJ_TREE, result_oid)) ret = -1; strbuf_release(&buf); @@ -3967,8 +4035,8 @@ static int write_completed_directory(struct merge_options *opt, dir_info->is_null = 0; dir_info->result.mode = S_IFDIR; if (record_tree && - write_tree(&dir_info->result.oid, &info->versions, offset, - opt->repo->hash_algo->rawsz) < 0) + write_tree(opt->repo, &dir_info->result.oid, &info->versions, + offset) < 0) ret = -1; } @@ -4042,7 +4110,7 @@ static int process_entry(struct merge_options *opt, if (ci->filemask & (1 << i)) continue; ci->stages[i].mode = 0; - oidcpy(&ci->stages[i].oid, null_oid(the_hash_algo)); + oidcpy(&ci->stages[i].oid, null_oid(opt->repo->hash_algo)); } } else if (ci->df_conflict && ci->merged.result.mode != 0) { /* @@ -4089,7 +4157,7 @@ static int process_entry(struct merge_options *opt, continue; /* zero out any entries related to directories */ new_ci->stages[i].mode = 0; - oidcpy(&new_ci->stages[i].oid, null_oid(the_hash_algo)); + oidcpy(&new_ci->stages[i].oid, null_oid(opt->repo->hash_algo)); } /* @@ -4211,11 +4279,11 @@ static int process_entry(struct merge_options *opt, new_ci->merged.result.mode = ci->stages[2].mode; oidcpy(&new_ci->merged.result.oid, &ci->stages[2].oid); new_ci->stages[1].mode = 0; - oidcpy(&new_ci->stages[1].oid, null_oid(the_hash_algo)); + oidcpy(&new_ci->stages[1].oid, null_oid(opt->repo->hash_algo)); new_ci->filemask = 5; if ((S_IFMT & b_mode) != (S_IFMT & o_mode)) { new_ci->stages[0].mode = 0; - oidcpy(&new_ci->stages[0].oid, null_oid(the_hash_algo)); + oidcpy(&new_ci->stages[0].oid, null_oid(opt->repo->hash_algo)); new_ci->filemask = 4; } @@ -4223,11 +4291,11 @@ static int process_entry(struct merge_options *opt, ci->merged.result.mode = ci->stages[1].mode; oidcpy(&ci->merged.result.oid, &ci->stages[1].oid); ci->stages[2].mode = 0; - oidcpy(&ci->stages[2].oid, null_oid(the_hash_algo)); + oidcpy(&ci->stages[2].oid, null_oid(opt->repo->hash_algo)); ci->filemask = 3; if ((S_IFMT & a_mode) != (S_IFMT & o_mode)) { ci->stages[0].mode = 0; - oidcpy(&ci->stages[0].oid, null_oid(the_hash_algo)); + oidcpy(&ci->stages[0].oid, null_oid(opt->repo->hash_algo)); ci->filemask = 2; } @@ -4355,7 +4423,7 @@ static int process_entry(struct merge_options *opt, /* Deleted on both sides */ ci->merged.is_null = 1; ci->merged.result.mode = 0; - oidcpy(&ci->merged.result.oid, null_oid(the_hash_algo)); + oidcpy(&ci->merged.result.oid, null_oid(opt->repo->hash_algo)); assert(!ci->df_conflict); ci->merged.clean = !ci->path_conflict; } @@ -4379,7 +4447,7 @@ static void prefetch_for_content_merges(struct merge_options *opt, struct string_list_item *e; struct oid_array to_fetch = OID_ARRAY_INIT; - if (opt->repo != the_repository || !repo_has_promisor_remote(the_repository)) + if (!repo_has_promisor_remote(opt->repo)) return; for (e = &plist->items[plist->nr-1]; e >= plist->items; --e) { @@ -4514,8 +4582,7 @@ static int process_entries(struct merge_options *opt, BUG("dir_metadata accounting completely off; shouldn't happen"); } if (record_tree && - write_tree(result_oid, &dir_metadata.versions, 0, - opt->repo->hash_algo->rawsz) < 0) + write_tree(opt->repo, result_oid, &dir_metadata.versions, 0) < 0) ret = -1; cleanup: string_list_clear(&plist, 0); @@ -4560,10 +4627,10 @@ static int checkout(struct merge_options *opt, unpack_opts.verbose_update = (opt->verbosity > 2); unpack_opts.fn = twoway_merge; unpack_opts.preserve_ignored = 0; /* FIXME: !opts->overwrite_ignore */ - if (parse_tree(prev) < 0) + if (repo_parse_tree(opt->repo, prev) < 0) return -1; init_tree_desc(&trees[0], &prev->object.oid, prev->buffer, prev->size); - if (parse_tree(next) < 0) + if (repo_parse_tree(opt->repo, next) < 0) return -1; init_tree_desc(&trees[1], &next->object.oid, next->buffer, next->size); @@ -5118,7 +5185,8 @@ static void merge_check_renames_reusable(struct merge_options *opt, * optimization" comment near that case). * * This could be revisited in the future; see the commit message - * where this comment was added for some possible pointers. + * where this comment was added for some possible pointers, or the + * later commit where this comment was added. */ if (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_NONE) { renames->cached_pairs_valid_side = 0; /* neither side valid */ @@ -5220,7 +5288,8 @@ static void merge_ort_nonrecursive_internal(struct merge_options *opt, if (result->clean >= 0) { if (!opt->mergeability_only) { - result->tree = parse_tree_indirect(&working_tree_oid); + result->tree = repo_parse_tree_indirect(opt->repo, + &working_tree_oid); if (!result->tree) die(_("unable to read tree (%s)"), oid_to_hex(&working_tree_oid)); @@ -5241,20 +5310,20 @@ static void merge_ort_internal(struct merge_options *opt, struct commit *h2, struct merge_result *result) { - struct commit_list *merge_bases = copy_commit_list(_merge_bases); + struct commit_list *merge_bases = commit_list_copy(_merge_bases); struct commit *next; struct commit *merged_merge_bases; const char *ancestor_name; struct strbuf merge_base_abbrev = STRBUF_INIT; if (!merge_bases) { - if (repo_get_merge_bases(the_repository, h1, h2, + if (repo_get_merge_bases(opt->repo, h1, h2, &merge_bases) < 0) { result->clean = -1; goto out; } /* See merge-ort.h:merge_incore_recursive() declaration NOTE */ - merge_bases = reverse_commit_list(merge_bases); + merge_bases = commit_list_reverse(merge_bases); } merged_merge_bases = pop_commit(&merge_bases); @@ -5322,7 +5391,7 @@ static void merge_ort_internal(struct merge_options *opt, opt->ancestor = NULL; /* avoid accidental re-use of opt->ancestor */ out: - free_commit_list(merge_bases); + commit_list_free(merge_bases); } void merge_incore_nonrecursive(struct merge_options *opt, @@ -5379,20 +5448,20 @@ static void merge_recursive_config(struct merge_options *opt, int ui) { char *value = NULL; int renormalize = 0; - repo_config_get_int(the_repository, "merge.verbosity", &opt->verbosity); - repo_config_get_int(the_repository, "diff.renamelimit", &opt->rename_limit); - repo_config_get_int(the_repository, "merge.renamelimit", &opt->rename_limit); - repo_config_get_bool(the_repository, "merge.renormalize", &renormalize); + repo_config_get_int(opt->repo, "merge.verbosity", &opt->verbosity); + repo_config_get_int(opt->repo, "diff.renamelimit", &opt->rename_limit); + repo_config_get_int(opt->repo, "merge.renamelimit", &opt->rename_limit); + repo_config_get_bool(opt->repo, "merge.renormalize", &renormalize); opt->renormalize = renormalize; - if (!repo_config_get_string(the_repository, "diff.renames", &value)) { + if (!repo_config_get_string(opt->repo, "diff.renames", &value)) { opt->detect_renames = git_config_rename("diff.renames", value); free(value); } - if (!repo_config_get_string(the_repository, "merge.renames", &value)) { + if (!repo_config_get_string(opt->repo, "merge.renames", &value)) { opt->detect_renames = git_config_rename("merge.renames", value); free(value); } - if (!repo_config_get_string(the_repository, "merge.directoryrenames", &value)) { + if (!repo_config_get_string(opt->repo, "merge.directoryrenames", &value)) { int boolval = git_parse_maybe_bool(value); if (0 <= boolval) { opt->detect_directory_renames = boolval ? @@ -5405,7 +5474,7 @@ static void merge_recursive_config(struct merge_options *opt, int ui) free(value); } if (ui) { - if (!repo_config_get_string(the_repository, "diff.algorithm", &value)) { + if (!repo_config_get_string(opt->repo, "diff.algorithm", &value)) { long diff_algorithm = parse_algorithm_value(value); if (diff_algorithm < 0) die(_("unknown value for config '%s': %s"), "diff.algorithm", value); @@ -5413,7 +5482,7 @@ static void merge_recursive_config(struct merge_options *opt, int ui) free(value); } } - repo_config(the_repository, git_xmerge_config, NULL); + repo_config(opt->repo, git_xmerge_config, NULL); } static void init_merge_options(struct merge_options *opt, @@ -5496,7 +5565,6 @@ int parse_merge_opt(struct merge_options *opt, const char *s) if (value < 0) return -1; /* clear out previous settings */ - DIFF_XDL_CLR(opt, NEED_MINIMAL); opt->xdl_opts &= ~XDF_DIFF_ALGORITHM_MASK; opt->xdl_opts |= value; }
diff --git a/merge.c b/merge.c index 5ecaf50..0f5e823 100644 --- a/merge.c +++ b/merge.c
@@ -68,18 +68,18 @@ int checkout_fast_forward(struct repository *r, memset(&trees, 0, sizeof(trees)); memset(&t, 0, sizeof(t)); - trees[nr_trees] = parse_tree_indirect(head); + trees[nr_trees] = repo_parse_tree_indirect(the_repository, head); if (!trees[nr_trees++]) { rollback_lock_file(&lock_file); return -1; } - trees[nr_trees] = parse_tree_indirect(remote); + trees[nr_trees] = repo_parse_tree_indirect(the_repository, remote); if (!trees[nr_trees++]) { rollback_lock_file(&lock_file); return -1; } for (i = 0; i < nr_trees; i++) { - if (parse_tree(trees[i]) < 0) { + if (repo_parse_tree(the_repository, trees[i]) < 0) { rollback_lock_file(&lock_file); return -1; }
diff --git a/meson.build b/meson.build index 1f95a06..8309942 100644 --- a/meson.build +++ b/meson.build
@@ -239,7 +239,9 @@ sed = find_program('sed', dirs: program_path, native: true) shell = find_program('sh', dirs: program_path, native: true) tar = find_program('tar', dirs: program_path, native: true) +tclsh = find_program('tclsh', required: get_option('git_gui'), native: false) time = find_program('time', dirs: program_path, required: get_option('benchmarks')) +wish = find_program('wish', required: get_option('git_gui').enabled() or get_option('gitk').enabled(), native: false) # Detect the target shell that is used by Git at runtime. Note that we prefer # "/bin/sh" over a PATH-based lookup, which provides a working shell on most @@ -269,6 +271,13 @@ compiler = meson.get_compiler('c') +compat_sources = [ + 'compat/nonblock.c', + 'compat/obstack.c', + 'compat/open.c', + 'compat/terminal.c', +] + libgit_sources = [ 'abspath.c', 'add-interactive.c', @@ -302,10 +311,6 @@ 'commit.c', 'common-exit.c', 'common-init.c', - 'compat/nonblock.c', - 'compat/obstack.c', - 'compat/open.c', - 'compat/terminal.c', 'compiler-tricks/not-constant.c', 'config.c', 'connect.c', @@ -397,6 +402,9 @@ 'object-name.c', 'object.c', 'odb.c', + 'odb/source.c', + 'odb/source-files.c', + 'odb/streaming.c', 'oid-array.c', 'oidmap.c', 'oidset.c', @@ -470,6 +478,7 @@ 'repack-midx.c', 'repack-promisor.c', 'replace-object.c', + 'replay.c', 'repo-settings.c', 'repository.c', 'rerere.c', @@ -490,7 +499,6 @@ 'stable-qsort.c', 'statinfo.c', 'strbuf.c', - 'streaming.c', 'string-list.c', 'strmap.c', 'strvec.c', @@ -551,7 +559,7 @@ libgit_sources += custom_target( input: 'command-list.txt', output: 'command-list.h', - command: [shell, meson.current_source_dir() + '/generate-cmdlist.sh', meson.current_source_dir(), '@OUTPUT@'], + command: [shell, meson.current_source_dir() + '/tools/generate-cmdlist.sh', meson.current_source_dir(), '@OUTPUT@'], env: script_environment, ) @@ -609,6 +617,7 @@ 'builtin/grep.c', 'builtin/hash-object.c', 'builtin/help.c', + 'builtin/history.c', 'builtin/hook.c', 'builtin/index-pack.c', 'builtin/init-db.c', @@ -718,11 +727,14 @@ builtin_sources += custom_target( output: 'config-list.h', + depfile: 'config-list.h.d', + depend_files: [ 'tools/generate-configlist.sh' ], command: [ shell, - meson.current_source_dir() + '/generate-configlist.sh', + meson.current_source_dir() / 'tools/generate-configlist.sh', meson.current_source_dir(), '@OUTPUT@', + '@DEPFILE@', ], env: script_environment, ) @@ -732,7 +744,7 @@ output: 'hook-list.h', command: [ shell, - meson.current_source_dir() + '/generate-hooklist.sh', + meson.current_source_dir() + '/tools/generate-hooklist.sh', meson.current_source_dir(), '@OUTPUT@', ], @@ -1033,42 +1045,52 @@ have_old_iconv = true endif - iconv_omits_bom_source = '''# - #include <iconv.h> + if meson.can_run_host_binaries() + if compiler.run(''' + #include <iconv.h> - int main(int argc, const char **argv) - { - ''' - if have_old_iconv - iconv_omits_bom_source += ''' - typedef const char *iconv_ibp; - ''' - else - iconv_omits_bom_source += ''' - typedef char *iconv_ibp; - ''' - endif - iconv_omits_bom_source += ''' - int v; - iconv_t conv; - char in[] = "a"; iconv_ibp pin = in; - char out[20] = ""; char *pout = out; - size_t isz = sizeof in; - size_t osz = sizeof out; + int main(int argc, const char **argv) + { + char in[] = "a", *inpos = in; + char out[20] = "", *outpos = out; + size_t insz = sizeof(in), outsz = sizeof(out); + iconv_t conv = iconv_open("UTF-16", "UTF-8"); + iconv(conv, (void *) &inpos, &insz, &outpos, &outsz); + iconv_close(conv); + return (unsigned char)(out[0]) + (unsigned char)(out[1]) != 0xfe + 0xff; + } + ''', + dependencies: iconv, + name: 'iconv omits BOM', + ).returncode() != 0 + libgit_c_args += '-DICONV_OMITS_BOM' + endif - conv = iconv_open("UTF-16", "UTF-8"); - iconv(conv, &pin, &isz, &pout, &osz); - iconv_close(conv); - v = (unsigned char)(out[0]) + (unsigned char)(out[1]); - return v != 0xfe + 0xff; - } - ''' + if compiler.run(''' + #include <iconv.h> + #include <string.h> - if compiler.run(iconv_omits_bom_source, - dependencies: iconv, - name: 'iconv omits BOM', - ).returncode() != 0 - libgit_c_args += '-DICONV_OMITS_BOM' + int main(int argc, const char *argv[]) + { + char in[] = "\x1b\x24\x42\x24\x22\x24\x22\x1b\x28\x42", *inpos = in; + char out[7] = { 0 }, *outpos = out; + size_t insz = sizeof(in) - 1, outsz = 4; + iconv_t conv = iconv_open("UTF-8", "ISO-2022-JP"); + if (!conv) + return 1; + if (iconv(conv, (void *) &inpos, &insz, &outpos, &outsz) != (size_t) -1) + return 2; + outsz = sizeof(out) - (outpos - out); + if (iconv(conv, (void *) &inpos, &insz, &outpos, &outsz) == (size_t) -1) + return 3; + return strcmp("\343\201\202\343\201\202", out) ? 4 : 0; + } + ''', + dependencies: iconv, + name: 'iconv handles restarts properly', + ).returncode() != 0 + libgit_c_args += '-DICONV_RESTART_RESET' + endif endif else libgit_c_args += '-DNO_ICONV' @@ -1156,7 +1178,7 @@ if not has_poll_h and not has_sys_poll_h libgit_c_args += '-DNO_POLL' - libgit_sources += 'compat/poll/poll.c' + compat_sources += 'compat/poll/poll.c' libgit_include_directories += 'compat/poll' endif @@ -1172,7 +1194,7 @@ # implementation to threat things like drive prefixes specially. if host_machine.system() == 'windows' or not compiler.has_header('libgen.h') libgit_c_args += '-DNO_LIBGEN_H' - libgit_sources += 'compat/basename.c' + compat_sources += 'compat/basename.c' endif if compiler.has_header('paths.h') @@ -1202,7 +1224,7 @@ foreach symbol : ['inet_ntop', 'inet_pton', 'hstrerror'] if not compiler.has_function(symbol, dependencies: networking_dependencies) libgit_c_args += '-DNO_' + symbol.to_upper() - libgit_sources += 'compat/' + symbol + '.c' + compat_sources += 'compat/' + symbol + '.c' endif endforeach endif @@ -1244,18 +1266,18 @@ endif if host_machine.system() == 'darwin' - libgit_sources += 'compat/precompose_utf8.c' + compat_sources += 'compat/precompose_utf8.c' libgit_c_args += '-DPRECOMPOSE_UNICODE' libgit_c_args += '-DPROTECT_HFS_DEFAULT' endif # Configure general compatibility wrappers. if host_machine.system() == 'cygwin' - libgit_sources += [ + compat_sources += [ 'compat/win32/path-utils.c', ] elif host_machine.system() == 'windows' - libgit_sources += [ + compat_sources += [ 'compat/winansi.c', 'compat/win32/dirent.c', 'compat/win32/flush.c', @@ -1282,18 +1304,20 @@ libgit_include_directories += 'compat/win32' if compiler.get_id() == 'msvc' libgit_include_directories += 'compat/vcbuild/include' - libgit_sources += 'compat/msvc.c' + compat_sources += 'compat/msvc.c' else - libgit_sources += 'compat/mingw.c' + compat_sources += 'compat/mingw.c' endif endif if host_machine.system() == 'linux' - libgit_sources += 'compat/linux/procinfo.c' + compat_sources += 'compat/linux/procinfo.c' elif host_machine.system() == 'windows' - libgit_sources += 'compat/win32/trace2_win32_process_info.c' + compat_sources += 'compat/win32/trace2_win32_process_info.c' +elif host_machine.system() == 'darwin' + compat_sources += 'compat/darwin/procinfo.c' else - libgit_sources += 'compat/stub/procinfo.c' + compat_sources += 'compat/stub/procinfo.c' endif if host_machine.system() == 'cygwin' or host_machine.system() == 'windows' @@ -1306,13 +1330,13 @@ # Configure the simple-ipc subsystem required fro the fsmonitor. if host_machine.system() == 'windows' - libgit_sources += [ + compat_sources += [ 'compat/simple-ipc/ipc-shared.c', 'compat/simple-ipc/ipc-win32.c', ] libgit_c_args += '-DSUPPORTS_SIMPLE_IPC' else - libgit_sources += [ + compat_sources += [ 'compat/simple-ipc/ipc-shared.c', 'compat/simple-ipc/ipc-unix-socket.c', ] @@ -1330,7 +1354,7 @@ libgit_c_args += '-DHAVE_FSMONITOR_DAEMON_BACKEND' libgit_c_args += '-DHAVE_FSMONITOR_OS_SETTINGS' - libgit_sources += [ + compat_sources += [ 'compat/fsmonitor/fsm-health-' + fsmonitor_backend + '.c', 'compat/fsmonitor/fsm-ipc-' + fsmonitor_backend + '.c', 'compat/fsmonitor/fsm-listen-' + fsmonitor_backend + '.c', @@ -1346,7 +1370,7 @@ if compiler.get_define('REG_ENHANCED', prefix: '#include <regex.h>') != '' libgit_c_args += '-DUSE_ENHANCED_BASIC_REGULAR_EXPRESSIONS' - libgit_sources += 'compat/regcomp_enhanced.c' + compat_sources += 'compat/regcomp_enhanced.c' endif elif not get_option('regex').enabled() libgit_c_args += [ @@ -1355,7 +1379,7 @@ '-DNO_MBSUPPORT', ] build_options_config.set('NO_REGEX', '1') - libgit_sources += 'compat/regex/regex.c' + compat_sources += 'compat/regex/regex.c' libgit_include_directories += 'compat/regex' else error('Native regex support requested but not found') @@ -1401,29 +1425,36 @@ 'strlcpy' : ['strlcpy.c'], 'strtoull' : [], 'setenv' : ['setenv.c'], - 'mkdtemp' : ['mkdtemp.c'], + 'mkdtemp' : [], 'initgroups' : [], 'strtoumax' : ['strtoumax.c', 'strtoimax.c'], 'pread' : ['pread.c'], + 'writev' : ['writev.c'], } if host_machine.system() == 'windows' libgit_c_args += '-DUSE_WIN32_MMAP' else checkfuncs += { - 'mmap' : ['mmap.c'], # provided by compat/mingw.c. 'unsetenv' : ['unsetenv.c'], # provided by compat/mingw.c. 'getpagesize' : [], } + + if get_option('b_sanitize').contains('address') or get_option('b_sanitize').contains('leak') + libgit_c_args += '-DNO_MMAP' + compat_sources += 'compat/mmap.c' + else + checkfuncs += { 'mmap': ['mmap.c'] } + endif endif foreach func, impls : checkfuncs if not compiler.has_function(func) libgit_c_args += '-DNO_' + func.to_upper() foreach impl : impls - libgit_sources += 'compat/' + impl + compat_sources += 'compat/' + impl endforeach endif endforeach @@ -1434,13 +1465,13 @@ if not compiler.has_function('strdup') libgit_c_args += '-DOVERRIDE_STRDUP' - libgit_sources += 'compat/strdup.c' + compat_sources += 'compat/strdup.c' endif if not compiler.has_function('qsort') libgit_c_args += '-DINTERNAL_QSORT' endif -libgit_sources += 'compat/qsort_s.c' +compat_sources += 'compat/qsort_s.c' if compiler.has_function('getdelim') libgit_c_args += '-DHAVE_GETDELIM' @@ -1486,7 +1517,7 @@ endif endif -if not meson.is_cross_build() and compiler.run(''' +if meson.can_run_host_binaries() and compiler.run(''' #include <stdio.h> int main(int argc, const char **argv) @@ -1496,7 +1527,7 @@ } ''', name: 'fread reads directories').returncode() == 0 libgit_c_args += '-DFREAD_READS_DIRECTORIES' - libgit_sources += 'compat/fopen.c' + compat_sources += 'compat/fopen.c' endif if not meson.is_cross_build() and fs.exists('/dev/tty') @@ -1730,14 +1761,23 @@ endif libgit = declare_dependency( - link_with: static_library('git', - sources: libgit_sources, - c_args: libgit_c_args + [ - '-DGIT_VERSION_H="' + version_def_h.full_path() + '"', - ], - dependencies: libgit_dependencies, - include_directories: libgit_include_directories, - ), + link_with: [ + static_library('compat', + sources: compat_sources, + c_args: libgit_c_args, + dependencies: libgit_dependencies, + include_directories: libgit_include_directories, + ), + static_library('git', + sources: libgit_sources, + c_args: libgit_c_args + [ + '-DGIT_VERSION_H="' + version_def_h.full_path() + '"', + ], + c_pch: 'tools/precompiled.h', + dependencies: libgit_dependencies, + include_directories: libgit_include_directories, + ), + ], compile_args: libgit_c_args, dependencies: libgit_dependencies, include_directories: libgit_include_directories, @@ -1794,6 +1834,7 @@ git_builtin = executable('git', sources: builtin_sources + 'git.c', + c_pch: 'tools/precompiled.h', dependencies: [libgit_commonmain], install: true, install_dir: git_exec_path, @@ -1944,7 +1985,7 @@ output: fs.stem(script), command: [ shell, - meson.project_source_root() / 'generate-script.sh', + meson.project_source_root() / 'tools/generate-script.sh', '@INPUT@', '@OUTPUT@', meson.project_build_root() / 'GIT-BUILD-OPTIONS', @@ -1993,7 +2034,7 @@ generate_perl_command = [ shell, - meson.project_source_root() / 'generate-perl.sh', + meson.project_source_root() / 'tools/generate-perl.sh', meson.project_build_root() / 'GIT-BUILD-OPTIONS', git_version_file.full_path(), perl_header, @@ -2042,7 +2083,7 @@ output: fs.stem(script), command: [ shell, - meson.project_source_root() / 'generate-python.sh', + meson.project_source_root() / 'tools/generate-python.sh', meson.project_build_root() / 'GIT-BUILD-OPTIONS', '@INPUT@', '@OUTPUT@', @@ -2134,6 +2175,7 @@ endif subdir('contrib') +subdir('tools') # Note that the target is intentionally configured after including the # 'contrib' directory, as some tool there also have their own manpages. @@ -2244,6 +2286,16 @@ configuration: build_options_config, ) +gitk_option = get_option('gitk').disable_auto_if(not wish.found()) +if gitk_option.allowed() + subproject('gitk') +endif + +git_gui_option = get_option('git_gui').disable_auto_if(not tclsh.found() or not wish.found()) +if git_gui_option.allowed() + subproject('git-gui') +endif + # Development environments can be used via `meson devenv -C <builddir>`. This # allows you to execute test scripts directly with the built Git version and # puts the built version of Git in your PATH. @@ -2270,6 +2322,8 @@ 'curl': curl, 'expat': expat, 'gettext': intl, + 'gitk': gitk_option.allowed(), + 'git-gui': git_gui_option.allowed(), 'gitweb': gitweb_option.allowed(), 'iconv': iconv, 'pcre2': pcre2,
diff --git a/meson_options.txt b/meson_options.txt index e0be260..659cbb2 100644 --- a/meson_options.txt +++ b/meson_options.txt
@@ -43,6 +43,10 @@ description: 'Build helpers used to push to remotes with the HTTP transport.') option('gettext', type: 'feature', value: 'auto', description: 'Build translation files.') +option('gitk', type: 'feature', value: 'auto', + description: 'Build the Gitk graphical repository browser. Requires Tcl/Tk.') +option('git_gui', type: 'feature', value: 'auto', + description: 'Build the git-gui graphical user interface for Git. Requires Tcl/Tk.') option('gitweb', type: 'feature', value: 'auto', description: 'Build Git web interface. Requires Perl.') option('iconv', type: 'feature', value: 'auto',
diff --git a/midx-write.c b/midx-write.c index c73010d..0ff2e45 100644 --- a/midx-write.c +++ b/midx-write.c
@@ -36,10 +36,13 @@ extern int cmp_idx_or_pack_name(const char *idx_or_pack_name, static size_t write_midx_header(const struct git_hash_algo *hash_algo, struct hashfile *f, unsigned char num_chunks, - uint32_t num_packs) + uint32_t num_packs, int version) { + if (version != MIDX_VERSION_V1 && version != MIDX_VERSION_V2) + BUG("unexpected MIDX version: %d", version); + hashwrite_be32(f, MIDX_SIGNATURE); - hashwrite_u8(f, MIDX_VERSION); + hashwrite_u8(f, version); hashwrite_u8(f, oid_version(hash_algo)); hashwrite_u8(f, num_chunks); hashwrite_u8(f, 0); /* unused */ @@ -105,15 +108,29 @@ struct write_midx_context { uint32_t preferred_pack_idx; + int version; /* must be MIDX_VERSION_V1 or _V2 */ + int incremental; uint32_t num_multi_pack_indexes_before; + struct multi_pack_index *compact_from; + struct multi_pack_index *compact_to; + int compact; + struct string_list *to_include; struct repository *repo; struct odb_source *source; }; +static uint32_t midx_pack_perm(struct write_midx_context *ctx, + uint32_t orig_pack_int_id) +{ + if (ctx->compact) + orig_pack_int_id -= ctx->compact_from->num_packs_in_base; + return ctx->pack_perm[orig_pack_int_id]; +} + static int should_include_pack(const struct write_midx_context *ctx, const char *file_name) { @@ -257,18 +274,14 @@ static void midx_fanout_sort(struct midx_fanout *fanout) QSORT(fanout->entries, fanout->nr, midx_oid_compare); } -static void midx_fanout_add_midx_fanout(struct midx_fanout *fanout, - struct multi_pack_index *m, - uint32_t cur_fanout, - uint32_t preferred_pack) +static void midx_fanout_add_midx_fanout_1(struct midx_fanout *fanout, + struct multi_pack_index *m, + uint32_t cur_fanout, + uint32_t preferred_pack) { uint32_t start = m->num_objects_in_base, end; uint32_t cur_object; - if (m->base_midx) - midx_fanout_add_midx_fanout(fanout, m->base_midx, cur_fanout, - preferred_pack); - if (cur_fanout) start += ntohl(m->chunk_oid_fanout[cur_fanout - 1]); end = m->num_objects_in_base + ntohl(m->chunk_oid_fanout[cur_fanout]); @@ -292,6 +305,17 @@ static void midx_fanout_add_midx_fanout(struct midx_fanout *fanout, } } +static void midx_fanout_add_midx_fanout(struct midx_fanout *fanout, + struct multi_pack_index *m, + uint32_t cur_fanout, + uint32_t preferred_pack) +{ + if (m->base_midx) + midx_fanout_add_midx_fanout(fanout, m->base_midx, cur_fanout, + preferred_pack); + midx_fanout_add_midx_fanout_1(fanout, m, cur_fanout, preferred_pack); +} + static void midx_fanout_add_pack_fanout(struct midx_fanout *fanout, struct pack_info *info, uint32_t cur_pack, @@ -317,6 +341,45 @@ static void midx_fanout_add_pack_fanout(struct midx_fanout *fanout, } } +static void midx_fanout_add(struct midx_fanout *fanout, + struct write_midx_context *ctx, + uint32_t start_pack, + uint32_t cur_fanout) +{ + uint32_t cur_pack; + + if (ctx->m && !ctx->incremental) + midx_fanout_add_midx_fanout(fanout, ctx->m, cur_fanout, + ctx->preferred_pack_idx); + + for (cur_pack = start_pack; cur_pack < ctx->nr; cur_pack++) { + int preferred = cur_pack == ctx->preferred_pack_idx; + midx_fanout_add_pack_fanout(fanout, ctx->info, cur_pack, + preferred, cur_fanout); + } + + if (ctx->preferred_pack_idx != NO_PREFERRED_PACK && + ctx->preferred_pack_idx < start_pack) + midx_fanout_add_pack_fanout(fanout, ctx->info, + ctx->preferred_pack_idx, 1, + cur_fanout); +} + +static void midx_fanout_add_compact(struct midx_fanout *fanout, + struct write_midx_context *ctx, + uint32_t cur_fanout) +{ + struct multi_pack_index *m = ctx->compact_to; + + ASSERT(ctx->compact); + + while (m && m != ctx->compact_from->base_midx) { + midx_fanout_add_midx_fanout_1(fanout, m, cur_fanout, + NO_PREFERRED_PACK); + m = m->base_midx; + } +} + /* * It is possible to artificially get into a state where there are many * duplicate copies of objects. That can create high memory pressure if @@ -335,6 +398,9 @@ static void compute_sorted_entries(struct write_midx_context *ctx, size_t alloc_objects, total_objects = 0; struct midx_fanout fanout = { 0 }; + if (ctx->compact) + ASSERT(!start_pack); + for (cur_pack = start_pack; cur_pack < ctx->nr; cur_pack++) total_objects = st_add(total_objects, ctx->info[cur_pack].p->num_objects); @@ -353,23 +419,10 @@ static void compute_sorted_entries(struct write_midx_context *ctx, for (cur_fanout = 0; cur_fanout < 256; cur_fanout++) { fanout.nr = 0; - if (ctx->m && !ctx->incremental) - midx_fanout_add_midx_fanout(&fanout, ctx->m, cur_fanout, - ctx->preferred_pack_idx); - - for (cur_pack = start_pack; cur_pack < ctx->nr; cur_pack++) { - int preferred = cur_pack == ctx->preferred_pack_idx; - midx_fanout_add_pack_fanout(&fanout, - ctx->info, cur_pack, - preferred, cur_fanout); - } - - if (ctx->preferred_pack_idx != NO_PREFERRED_PACK && - ctx->preferred_pack_idx < start_pack) - midx_fanout_add_pack_fanout(&fanout, ctx->info, - ctx->preferred_pack_idx, 1, - cur_fanout); - + if (ctx->compact) + midx_fanout_add_compact(&fanout, ctx, cur_fanout); + else + midx_fanout_add(&fanout, ctx, start_pack, cur_fanout); midx_fanout_sort(&fanout); /* @@ -410,7 +463,9 @@ static int write_midx_pack_names(struct hashfile *f, void *data) if (ctx->info[i].expired) continue; - if (i && strcmp(ctx->info[i].pack_name, ctx->info[i - 1].pack_name) <= 0) + if (ctx->version == MIDX_VERSION_V1 && + i && strcmp(ctx->info[i].pack_name, + ctx->info[i - 1].pack_name) <= 0) BUG("incorrect pack-file order: %s before %s", ctx->info[i - 1].pack_name, ctx->info[i].pack_name); @@ -514,12 +569,12 @@ static int write_midx_object_offsets(struct hashfile *f, for (i = 0; i < ctx->entries_nr; i++) { struct pack_midx_entry *obj = list++; - if (ctx->pack_perm[obj->pack_int_id] == PACK_EXPIRED) + if (midx_pack_perm(ctx, obj->pack_int_id) == PACK_EXPIRED) BUG("object %s is in an expired pack with int-id %d", oid_to_hex(&obj->oid), obj->pack_int_id); - hashwrite_be32(f, ctx->pack_perm[obj->pack_int_id]); + hashwrite_be32(f, midx_pack_perm(ctx, obj->pack_int_id)); if (ctx->large_offsets_needed && obj->offset >> 31) hashwrite_be32(f, MIDX_LARGE_OFFSET_NEEDED | nr_large_offset++); @@ -620,8 +675,8 @@ static uint32_t *midx_pack_order(struct write_midx_context *ctx) for (i = 0; i < ctx->entries_nr; i++) { struct pack_midx_entry *e = &ctx->entries[i]; data[i].nr = i; - data[i].pack = ctx->pack_perm[e->pack_int_id]; - if (!e->preferred) + data[i].pack = midx_pack_perm(ctx, e->pack_int_id); + if (!e->preferred || ctx->compact) data[i].pack |= (1U << 31); data[i].offset = e->offset; } @@ -630,14 +685,14 @@ static uint32_t *midx_pack_order(struct write_midx_context *ctx) for (i = 0; i < ctx->entries_nr; i++) { struct pack_midx_entry *e = &ctx->entries[data[i].nr]; - struct pack_info *pack = &ctx->info[ctx->pack_perm[e->pack_int_id]]; + struct pack_info *pack = &ctx->info[midx_pack_perm(ctx, e->pack_int_id)]; if (pack->bitmap_pos == BITMAP_POS_UNKNOWN) pack->bitmap_pos = i + base_objects; pack->bitmap_nr++; pack_order[i] = data[i].nr; } for (i = 0; i < ctx->nr; i++) { - struct pack_info *pack = &ctx->info[ctx->pack_perm[i]]; + struct pack_info *pack = &ctx->info[i]; if (pack->bitmap_pos == BITMAP_POS_UNKNOWN) pack->bitmap_pos = 0; } @@ -691,42 +746,39 @@ static void prepare_midx_packing_data(struct packing_data *pdata, struct object_entry *to = packlist_alloc(pdata, &from->oid); oe_set_in_pack(pdata, to, - ctx->info[ctx->pack_perm[from->pack_int_id]].p); + ctx->info[midx_pack_perm(ctx, from->pack_int_id)].p); } trace2_region_leave("midx", "prepare_midx_packing_data", ctx->repo); } -static int add_ref_to_pending(const char *refname, const char *referent UNUSED, - const struct object_id *oid, - int flag, void *cb_data) +static int add_ref_to_pending(const struct reference *ref, void *cb_data) { struct rev_info *revs = (struct rev_info*)cb_data; + const struct object_id *maybe_peeled = ref->oid; struct object_id peeled; struct object *object; - if ((flag & REF_ISSYMREF) && (flag & REF_ISBROKEN)) { - warning("symbolic ref is dangling: %s", refname); + if ((ref->flags & REF_ISSYMREF) && (ref->flags & REF_ISBROKEN)) { + warning("symbolic ref is dangling: %s", ref->name); return 0; } - if (!peel_iterated_oid(revs->repo, oid, &peeled)) - oid = &peeled; + if (!reference_get_peeled_oid(revs->repo, ref, &peeled)) + maybe_peeled = &peeled; - object = parse_object_or_die(revs->repo, oid, refname); + object = parse_object_or_die(revs->repo, maybe_peeled, ref->name); if (object->type != OBJ_COMMIT) return 0; add_pending_object(revs, object, ""); - if (bitmap_is_preferred_refname(revs->repo, refname)) + if (bitmap_is_preferred_refname(revs->repo, ref->name)) object->flags |= NEEDS_BITMAP; return 0; } struct bitmap_commit_cb { - struct commit **commits; - size_t commits_nr, commits_alloc; - + struct commit_stack *commits; struct write_midx_context *ctx; }; @@ -746,8 +798,7 @@ static void bitmap_show_commit(struct commit *commit, void *_data) if (pos < 0) return; - ALLOC_GROW(data->commits, data->commits_nr + 1, data->commits_alloc); - data->commits[data->commits_nr++] = commit; + commit_stack_push(data->commits, commit); } static int read_refs_snapshot(const char *refs_snapshot, @@ -785,17 +836,15 @@ static int read_refs_snapshot(const char *refs_snapshot, return 0; } -static struct commit **find_commits_for_midx_bitmap(uint32_t *indexed_commits_nr_p, - const char *refs_snapshot, - struct write_midx_context *ctx) +static void find_commits_for_midx_bitmap(struct commit_stack *commits, + const char *refs_snapshot, + struct write_midx_context *ctx) { struct rev_info revs; - struct bitmap_commit_cb cb = {0}; + struct bitmap_commit_cb cb = { .commits = commits, .ctx = ctx }; trace2_region_enter("midx", "find_commits_for_midx_bitmap", ctx->repo); - cb.ctx = ctx; - repo_init_revisions(ctx->repo, &revs, NULL); if (refs_snapshot) { read_refs_snapshot(refs_snapshot, &revs); @@ -824,14 +873,10 @@ static struct commit **find_commits_for_midx_bitmap(uint32_t *indexed_commits_nr die(_("revision walk setup failed")); traverse_commit_list(&revs, bitmap_show_commit, NULL, &cb); - if (indexed_commits_nr_p) - *indexed_commits_nr_p = cb.commits_nr; release_revisions(&revs); trace2_region_leave("midx", "find_commits_for_midx_bitmap", ctx->repo); - - return cb.commits; } static int write_midx_bitmap(struct write_midx_context *ctx, @@ -910,6 +955,21 @@ static int write_midx_bitmap(struct write_midx_context *ctx, return ret; } +static int fill_pack_from_midx(struct pack_info *info, + struct multi_pack_index *m, + uint32_t pack_int_id) +{ + if (prepare_midx_pack(m, pack_int_id)) + return error(_("could not load pack %d"), pack_int_id); + + fill_pack_info(info, + m->packs[pack_int_id - m->num_packs_in_base], + m->pack_names[pack_int_id - m->num_packs_in_base], + pack_int_id); + + return 0; +} + static int fill_packs_from_midx(struct write_midx_context *ctx) { struct multi_pack_index *m; @@ -917,19 +977,88 @@ static int fill_packs_from_midx(struct write_midx_context *ctx) for (m = ctx->m; m; m = m->base_midx) { uint32_t i; - for (i = 0; i < m->num_packs; i++) { - if (prepare_midx_pack(m, m->num_packs_in_base + i)) - return error(_("could not load pack")); - + for (i = m->num_packs_in_base; + i < m->num_packs_in_base + m->num_packs; i++) { ALLOC_GROW(ctx->info, ctx->nr + 1, ctx->alloc); - fill_pack_info(&ctx->info[ctx->nr++], m->packs[i], - m->pack_names[i], - m->num_packs_in_base + i); + + if (fill_pack_from_midx(&ctx->info[ctx->nr], m, i) < 0) + return -1; + + ctx->nr++; } } return 0; } +static uint32_t compactible_packs_between(const struct multi_pack_index *from, + const struct multi_pack_index *to) +{ + uint32_t nr; + + ASSERT(from && to); + + if (unsigned_add_overflows(to->num_packs, to->num_packs_in_base)) + die(_("too many packs, unable to compact")); + + nr = to->num_packs + to->num_packs_in_base; + if (nr < from->num_packs_in_base) + BUG("unexpected number of packs in base during compaction: " + "%"PRIu32" < %"PRIu32, nr, from->num_packs_in_base); + + return nr - from->num_packs_in_base; +} + +static int fill_packs_from_midx_range(struct write_midx_context *ctx, + int bitmap_order) +{ + struct multi_pack_index *m = ctx->compact_to; + uint32_t packs_nr; + + ASSERT(ctx->compact && !ctx->nr); + ASSERT(ctx->compact_from); + ASSERT(ctx->compact_to); + + packs_nr = compactible_packs_between(ctx->compact_from, + ctx->compact_to); + + ALLOC_GROW(ctx->info, packs_nr, ctx->alloc); + + while (m != ctx->compact_from->base_midx) { + uint32_t pack_int_id, preferred_pack_id; + uint32_t i; + + if (bitmap_order) { + if (midx_preferred_pack(m, &preferred_pack_id) < 0) + die(_("could not determine preferred pack")); + } else { + preferred_pack_id = m->num_packs_in_base; + } + + pack_int_id = m->num_packs_in_base - ctx->compact_from->num_packs_in_base; + + if (fill_pack_from_midx(&ctx->info[pack_int_id++], m, + preferred_pack_id) < 0) + return -1; + + for (i = m->num_packs_in_base; + i < m->num_packs_in_base + m->num_packs; i++) { + if (preferred_pack_id == i) + continue; + + if (fill_pack_from_midx(&ctx->info[pack_int_id++], m, + i) < 0) + return -1; + } + + ctx->nr += m->num_packs; + m = m->base_midx; + } + + ASSERT(ctx->nr == packs_nr); + + return 0; +} + static struct { const char *non_split; const char *split; @@ -956,7 +1085,7 @@ static int link_midx_to_chain(struct multi_pack_index *m) } for (i = 0; i < ARRAY_SIZE(midx_exts); i++) { - const unsigned char *hash = get_midx_checksum(m); + const unsigned char *hash = midx_get_checksum_hash(m); get_midx_filename_ext(m->source, &from, hash, midx_exts[i].non_split); @@ -1015,14 +1144,113 @@ static void clear_midx_files(struct odb_source *source, strbuf_release(&buf); } -static int write_midx_internal(struct odb_source *source, - struct string_list *packs_to_include, - struct string_list *packs_to_drop, - const char *preferred_pack_name, - const char *refs_snapshot, - unsigned flags) +static bool midx_needs_update(struct multi_pack_index *midx, struct write_midx_context *ctx) { - struct repository *r = source->odb->repo; + struct strset packs = STRSET_INIT; + struct strbuf buf = STRBUF_INIT; + bool needed = true; + + /* + * Ensure that we have a valid checksum before consulting the + * exisiting MIDX in order to determine if we can avoid an + * update. + * + * This is necessary because the given MIDX is loaded directly + * from the object store (because we still compare our proposed + * update to any on-disk MIDX regardless of whether or not we + * have assigned "ctx.m") and is thus not guaranteed to have a + * valid checksum. + */ + if (!midx_checksum_valid(midx)) + goto out; + + /* + * If the version differs, we need to update. + */ + if (midx->version != ctx->version) + goto out; + + /* + * Ignore incremental updates for now. The assumption is that any + * incremental update would be either empty (in which case we will bail + * out later) or it would actually cover at least one new pack. + */ + if (ctx->incremental) + goto out; + + if (ctx->compact) + goto out; /* Compaction always requires an update. */ + + /* + * Otherwise, we need to verify that the packs covered by the existing + * MIDX match the packs that we already have. The logic to do so is way + * more complicated than it has any right to be. This is because: + * + * - We cannot assume any ordering. + * + * - The MIDX packs may not be loaded at all, and loading them would + * be wasteful. So we need to use the pack names tracked by the + * MIDX itself. + * + * - The MIDX pack names are tracking the ".idx" files, whereas the + * packs themselves are tracking the ".pack" files. So we need to + * strip suffixes. + */ + if (ctx->nr != midx->num_packs + midx->num_packs_in_base) + goto out; + + for (uint32_t i = 0; i < ctx->nr; i++) { + strbuf_reset(&buf); + strbuf_addstr(&buf, pack_basename(ctx->info[i].p)); + strbuf_strip_suffix(&buf, ".pack"); + + if (!strset_add(&packs, buf.buf)) + BUG("same pack added twice?"); + } + + for (uint32_t i = 0; i < ctx->nr; i++) { + strbuf_reset(&buf); + strbuf_addstr(&buf, midx->pack_names[i]); + strbuf_strip_suffix(&buf, ".idx"); + + if (!strset_contains(&packs, buf.buf)) + goto out; + strset_remove(&packs, buf.buf); + } + + needed = false; + +out: + strbuf_release(&buf); + strset_clear(&packs); + return needed; +} + +static int midx_hashcmp(const struct multi_pack_index *a, + const struct multi_pack_index *b, + const struct git_hash_algo *algop) +{ + return hashcmp(midx_get_checksum_hash(a), midx_get_checksum_hash(b), + algop); +} + +struct write_midx_opts { + struct odb_source *source; /* non-optional */ + + struct string_list *packs_to_include; + struct string_list *packs_to_drop; + + struct multi_pack_index *compact_from; + struct multi_pack_index *compact_to; + + const char *preferred_pack_name; + const char *refs_snapshot; + unsigned flags; +}; + +static int write_midx_internal(struct write_midx_opts *opts) +{ + struct repository *r = opts->source->odb->repo; struct strbuf midx_name = STRBUF_INIT; unsigned char midx_hash[GIT_MAX_RAWSZ]; uint32_t start_pack; @@ -1031,33 +1259,53 @@ static int write_midx_internal(struct odb_source *source, struct tempfile *incr; struct write_midx_context ctx = { .preferred_pack_idx = NO_PREFERRED_PACK, + .version = MIDX_VERSION_V2, }; + struct multi_pack_index *midx_to_free = NULL; int bitmapped_packs_concat_len = 0; int pack_name_concat_len = 0; int dropped_packs = 0; int result = -1; const char **keep_hashes = NULL; + size_t keep_hashes_nr = 0; struct chunkfile *cf; trace2_region_enter("midx", "write_midx_internal", r); ctx.repo = r; - ctx.source = source; + ctx.source = opts->source; - ctx.incremental = !!(flags & MIDX_WRITE_INCREMENTAL); + repo_config_get_int(ctx.repo, "midx.version", &ctx.version); + if (ctx.version != MIDX_VERSION_V1 && ctx.version != MIDX_VERSION_V2) + die(_("unknown MIDX version: %d"), ctx.version); + + ctx.incremental = !!(opts->flags & MIDX_WRITE_INCREMENTAL); + ctx.compact = !!(opts->flags & MIDX_WRITE_COMPACT); + + if (ctx.compact) { + if (ctx.version != MIDX_VERSION_V2) + die(_("cannot perform MIDX compaction with v1 format")); + if (!opts->compact_from) + BUG("expected non-NULL 'from' MIDX during compaction"); + if (!opts->compact_to) + BUG("expected non-NULL 'to' MIDX during compaction"); + + ctx.compact_from = opts->compact_from; + ctx.compact_to = opts->compact_to; + } if (ctx.incremental) strbuf_addf(&midx_name, "%s/pack/multi-pack-index.d/tmp_midx_XXXXXX", - source->path); + opts->source->path); else - get_midx_filename(source, &midx_name); + get_midx_filename(opts->source, &midx_name); if (safe_create_leading_directories(r, midx_name.buf)) die_errno(_("unable to create leading directories of %s"), midx_name.buf); - if (!packs_to_include || ctx.incremental) { - struct multi_pack_index *m = get_multi_pack_index(source); + if (!opts->packs_to_include || ctx.incremental) { + struct multi_pack_index *m = get_multi_pack_index(opts->source); if (m && !midx_checksum_valid(m)) { warning(_("ignoring existing multi-pack-index; checksum mismatch")); m = NULL; @@ -1072,11 +1320,18 @@ static int write_midx_internal(struct odb_source *source, */ if (ctx.incremental) ctx.base_midx = m; - else if (!packs_to_include) + if (!opts->packs_to_include) ctx.m = m; } } + /* + * If compacting MIDX layer(s) in the range [from, to], then the + * compacted MIDX will share the same base MIDX as 'from'. + */ + if (ctx.compact) + ctx.base_midx = ctx.compact_from->base_midx; + ctx.nr = 0; ctx.alloc = ctx.m ? ctx.m->num_packs + ctx.m->num_packs_in_base : 16; ctx.info = NULL; @@ -1085,54 +1340,74 @@ static int write_midx_internal(struct odb_source *source, if (ctx.incremental) { struct multi_pack_index *m = ctx.base_midx; while (m) { - if (flags & MIDX_WRITE_BITMAP && load_midx_revindex(m)) { + if (opts->flags & MIDX_WRITE_BITMAP && load_midx_revindex(m)) { error(_("could not load reverse index for MIDX %s"), - hash_to_hex_algop(get_midx_checksum(m), - m->source->odb->repo->hash_algo)); + midx_get_checksum_hex(m)); goto cleanup; } ctx.num_multi_pack_indexes_before++; m = m->base_midx; } - } else if (ctx.m && fill_packs_from_midx(&ctx)) { + } else if (ctx.m && !ctx.compact && fill_packs_from_midx(&ctx)) { goto cleanup; } start_pack = ctx.nr; ctx.pack_paths_checked = 0; - if (flags & MIDX_PROGRESS) + if (opts->flags & MIDX_PROGRESS) ctx.progress = start_delayed_progress(r, _("Adding packfiles to multi-pack-index"), 0); else ctx.progress = NULL; - ctx.to_include = packs_to_include; + if (ctx.compact) { + int bitmap_order = 0; + if (opts->preferred_pack_name) + bitmap_order |= 1; + else if (opts->flags & (MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP)) + bitmap_order |= 1; - for_each_file_in_pack_dir(source->path, add_pack_to_midx, &ctx); + 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); + } stop_progress(&ctx.progress); - if ((ctx.m && ctx.nr == ctx.m->num_packs + ctx.m->num_packs_in_base) && - !ctx.incremental && - !(packs_to_include || packs_to_drop)) { - struct bitmap_index *bitmap_git; - int bitmap_exists; - int want_bitmap = flags & MIDX_WRITE_BITMAP; + if (!opts->packs_to_drop) { + /* + * If there is no MIDX then either it doesn't exist, or we're + * doing a geometric repack. Try to load it from the source to + * tell these two cases apart. + */ + struct multi_pack_index *midx = ctx.m; + if (!midx) + midx = midx_to_free = load_multi_pack_index(ctx.source); - bitmap_git = prepare_midx_bitmap_git(ctx.m); - bitmap_exists = bitmap_git && bitmap_is_midx(bitmap_git); - free_bitmap_index(bitmap_git); + if (midx && !midx_needs_update(midx, &ctx)) { + struct bitmap_index *bitmap_git; + int bitmap_exists; + int want_bitmap = opts->flags & MIDX_WRITE_BITMAP; - if (bitmap_exists || !want_bitmap) { - /* - * The correct MIDX already exists, and so does a - * corresponding bitmap (or one wasn't requested). - */ - if (!want_bitmap) - clear_midx_files_ext(source, "bitmap", NULL); - result = 0; - goto cleanup; + bitmap_git = prepare_midx_bitmap_git(midx); + bitmap_exists = bitmap_git && bitmap_is_midx(bitmap_git); + free_bitmap_index(bitmap_git); + + if (bitmap_exists || !want_bitmap) { + /* + * The correct MIDX already exists, and so does a + * corresponding bitmap (or one wasn't requested). + */ + if (!want_bitmap) + clear_midx_files_ext(ctx.source, "bitmap", NULL); + result = 0; + goto cleanup; + } } + + close_midx(midx_to_free); + midx_to_free = NULL; } if (ctx.incremental && !ctx.nr) { @@ -1140,11 +1415,11 @@ static int write_midx_internal(struct odb_source *source, goto cleanup; /* nothing to do */ } - if (preferred_pack_name) { + if (opts->preferred_pack_name) { ctx.preferred_pack_idx = NO_PREFERRED_PACK; for (size_t i = 0; i < ctx.nr; i++) { - if (!cmp_idx_or_pack_name(preferred_pack_name, + if (!cmp_idx_or_pack_name(opts->preferred_pack_name, ctx.info[i].pack_name)) { ctx.preferred_pack_idx = i; break; @@ -1153,9 +1428,9 @@ static int write_midx_internal(struct odb_source *source, if (ctx.preferred_pack_idx == NO_PREFERRED_PACK) warning(_("unknown preferred pack: '%s'"), - preferred_pack_name); + opts->preferred_pack_name); } else if (ctx.nr && - (flags & (MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP))) { + (opts->flags & (MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP))) { struct packed_git *oldest = ctx.info[0].p; ctx.preferred_pack_idx = 0; @@ -1166,7 +1441,7 @@ static int write_midx_internal(struct odb_source *source, */ open_pack_index(oldest); - if (packs_to_drop && packs_to_drop->nr) + if (opts->packs_to_drop && opts->packs_to_drop->nr) BUG("cannot write a MIDX bitmap during expiration"); /* @@ -1226,22 +1501,30 @@ static int write_midx_internal(struct odb_source *source, ctx.large_offsets_needed = 1; } - QSORT(ctx.info, ctx.nr, pack_info_compare); + if (ctx.compact) { + if (ctx.version != MIDX_VERSION_V2) + BUG("performing MIDX compaction with v1 MIDX"); + } else { + QSORT(ctx.info, ctx.nr, pack_info_compare); + } - if (packs_to_drop && packs_to_drop->nr) { + if (opts->packs_to_drop && opts->packs_to_drop->nr) { size_t drop_index = 0; int missing_drops = 0; - for (size_t i = 0; i < ctx.nr && drop_index < packs_to_drop->nr; i++) { + ASSERT(!ctx.compact); + + for (size_t i = 0; + i < ctx.nr && drop_index < opts->packs_to_drop->nr; i++) { int cmp = strcmp(ctx.info[i].pack_name, - packs_to_drop->items[drop_index].string); + opts->packs_to_drop->items[drop_index].string); if (!cmp) { drop_index++; ctx.info[i].expired = 1; } else if (cmp > 0) { error(_("did not see pack-file %s to drop"), - packs_to_drop->items[drop_index].string); + opts->packs_to_drop->items[drop_index].string); drop_index++; missing_drops++; i--; @@ -1262,12 +1545,20 @@ static int write_midx_internal(struct odb_source *source, */ ALLOC_ARRAY(ctx.pack_perm, ctx.nr); for (size_t i = 0; i < ctx.nr; i++) { + uint32_t from = ctx.info[i].orig_pack_int_id; + uint32_t to; + if (ctx.info[i].expired) { + to = PACK_EXPIRED; dropped_packs++; - ctx.pack_perm[ctx.info[i].orig_pack_int_id] = PACK_EXPIRED; } else { - ctx.pack_perm[ctx.info[i].orig_pack_int_id] = i - dropped_packs; + to = i - dropped_packs; } + + if (ctx.compact) + from -= ctx.compact_from->num_packs_in_base; + + ctx.pack_perm[from] = to; } for (size_t i = 0; i < ctx.nr; i++) { @@ -1278,16 +1569,16 @@ static int write_midx_internal(struct odb_source *source, } /* Check that the preferred pack wasn't expired (if given). */ - if (preferred_pack_name) { - struct pack_info *preferred = bsearch(preferred_pack_name, + if (opts->preferred_pack_name) { + struct pack_info *preferred = bsearch(opts->preferred_pack_name, ctx.info, ctx.nr, sizeof(*ctx.info), idx_or_pack_name_cmp); if (preferred) { - uint32_t perm = ctx.pack_perm[preferred->orig_pack_int_id]; + uint32_t perm = midx_pack_perm(&ctx, preferred->orig_pack_int_id); if (perm == PACK_EXPIRED) warning(_("preferred pack '%s' is expired"), - preferred_pack_name); + opts->preferred_pack_name); } } @@ -1301,15 +1592,15 @@ static int write_midx_internal(struct odb_source *source, } if (!ctx.entries_nr) { - if (flags & MIDX_WRITE_BITMAP) + if (opts->flags & MIDX_WRITE_BITMAP) warning(_("refusing to write multi-pack .bitmap without any objects")); - flags &= ~(MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP); + opts->flags &= ~(MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP); } if (ctx.incremental) { struct strbuf lock_name = STRBUF_INIT; - get_midx_chain_filename(source, &lock_name); + get_midx_chain_filename(opts->source, &lock_name); hold_lock_file_for_update(&lk, lock_name.buf, LOCK_DIE_ON_ERROR); strbuf_release(&lock_name); @@ -1352,7 +1643,7 @@ static int write_midx_internal(struct odb_source *source, MIDX_CHUNK_LARGE_OFFSET_WIDTH), write_midx_large_offsets); - if (flags & (MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP)) { + if (opts->flags & (MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP)) { ctx.pack_order = midx_pack_order(&ctx); add_chunk(cf, MIDX_CHUNKID_REVINDEX, st_mult(ctx.entries_nr, sizeof(uint32_t)), @@ -1363,28 +1654,27 @@ static int write_midx_internal(struct odb_source *source, } write_midx_header(r->hash_algo, f, get_num_chunks(cf), - ctx.nr - dropped_packs); + ctx.nr - dropped_packs, ctx.version); write_chunkfile(cf, &ctx); finalize_hashfile(f, midx_hash, FSYNC_COMPONENT_PACK_METADATA, CSUM_FSYNC | CSUM_HASH_IN_STREAM); free_chunkfile(cf); - if (flags & MIDX_WRITE_REV_INDEX && + if (opts->flags & MIDX_WRITE_REV_INDEX && git_env_bool("GIT_TEST_MIDX_WRITE_REV", 0)) write_midx_reverse_index(&ctx, midx_hash); - if (flags & MIDX_WRITE_BITMAP) { + if (opts->flags & MIDX_WRITE_BITMAP) { struct packing_data pdata; - struct commit **commits; - uint32_t commits_nr; + struct commit_stack commits = COMMIT_STACK_INIT; if (!ctx.entries_nr) BUG("cannot write a bitmap without any objects"); prepare_midx_packing_data(&pdata, &ctx); - commits = find_commits_for_midx_bitmap(&commits_nr, refs_snapshot, &ctx); + find_commits_for_midx_bitmap(&commits, opts->refs_snapshot, &ctx); /* * The previous steps translated the information from @@ -1395,17 +1685,16 @@ static int write_midx_internal(struct odb_source *source, FREE_AND_NULL(ctx.entries); ctx.entries_nr = 0; - if (write_midx_bitmap(&ctx, - midx_hash, &pdata, commits, commits_nr, - flags) < 0) { + if (write_midx_bitmap(&ctx, midx_hash, &pdata, commits.items, + commits.nr, opts->flags) < 0) { error(_("could not write multi-pack bitmap")); clear_packing_data(&pdata); - free(commits); + commit_stack_clear(&commits); goto cleanup; } clear_packing_data(&pdata); - free(commits); + commit_stack_clear(&commits); } /* * NOTE: Do not use ctx.entries beyond this point, since it might @@ -1415,7 +1704,24 @@ static int write_midx_internal(struct odb_source *source, if (ctx.num_multi_pack_indexes_before == UINT32_MAX) die(_("too many multi-pack-indexes")); - CALLOC_ARRAY(keep_hashes, ctx.num_multi_pack_indexes_before + 1); + if (ctx.compact) { + struct multi_pack_index *m; + + /* + * Keep all MIDX layers excluding those in the range [from, to]. + */ + for (m = ctx.base_midx; m; m = m->base_midx) + keep_hashes_nr++; + for (m = ctx.m; + m && midx_hashcmp(m, ctx.compact_to, r->hash_algo); + m = m->base_midx) + keep_hashes_nr++; + + keep_hashes_nr++; /* include the compacted layer */ + } else { + keep_hashes_nr = ctx.num_multi_pack_indexes_before + 1; + } + CALLOC_ARRAY(keep_hashes, keep_hashes_nr); if (ctx.incremental) { FILE *chainf = fdopen_lock_file(&lk, "w"); @@ -1430,7 +1736,7 @@ static int write_midx_internal(struct odb_source *source, if (link_midx_to_chain(ctx.base_midx) < 0) goto cleanup; - get_split_midx_filename_ext(source, &final_midx_name, + get_split_midx_filename_ext(opts->source, &final_midx_name, midx_hash, MIDX_EXT_MIDX); if (rename_tempfile(&incr, final_midx_name.buf) < 0) { @@ -1440,18 +1746,47 @@ static int write_midx_internal(struct odb_source *source, strbuf_release(&final_midx_name); - keep_hashes[ctx.num_multi_pack_indexes_before] = - xstrdup(hash_to_hex_algop(midx_hash, r->hash_algo)); + if (ctx.compact) { + struct multi_pack_index *m; + uint32_t num_layers_before_from = 0; + uint32_t i; - for (uint32_t i = 0; i < ctx.num_multi_pack_indexes_before; i++) { - uint32_t j = ctx.num_multi_pack_indexes_before - i - 1; + for (m = ctx.base_midx; m; m = m->base_midx) + num_layers_before_from++; - keep_hashes[j] = xstrdup(hash_to_hex_algop(get_midx_checksum(m), + m = ctx.base_midx; + for (i = 0; i < num_layers_before_from; i++) { + uint32_t j = num_layers_before_from - i - 1; + + keep_hashes[j] = xstrdup(midx_get_checksum_hex(m)); + m = m->base_midx; + } + + keep_hashes[i] = xstrdup(hash_to_hex_algop(midx_hash, r->hash_algo)); - m = m->base_midx; + + i = 0; + for (m = ctx.m; + m && midx_hashcmp(m, ctx.compact_to, r->hash_algo); + m = m->base_midx) { + keep_hashes[keep_hashes_nr - i - 1] = + xstrdup(midx_get_checksum_hex(m)); + i++; + } + } else { + keep_hashes[ctx.num_multi_pack_indexes_before] = + xstrdup(hash_to_hex_algop(midx_hash, + r->hash_algo)); + + for (uint32_t i = 0; i < ctx.num_multi_pack_indexes_before; i++) { + uint32_t j = ctx.num_multi_pack_indexes_before - i - 1; + + keep_hashes[j] = xstrdup(midx_get_checksum_hex(m)); + m = m->base_midx; + } } - for (uint32_t i = 0; i <= ctx.num_multi_pack_indexes_before; i++) + for (uint32_t i = 0; i < keep_hashes_nr; i++) fprintf(get_lock_file_fp(&lk), "%s\n", keep_hashes[i]); } else { keep_hashes[ctx.num_multi_pack_indexes_before] = @@ -1459,13 +1794,12 @@ static int write_midx_internal(struct odb_source *source, } if (ctx.m || ctx.base_midx) - close_object_store(ctx.repo->objects); + odb_close(ctx.repo->objects); if (commit_lock_file(&lk) < 0) die_errno(_("could not write multi-pack-index")); - clear_midx_files(source, keep_hashes, - ctx.num_multi_pack_indexes_before + 1, + clear_midx_files(opts->source, keep_hashes, keep_hashes_nr, ctx.incremental); result = 0; @@ -1483,11 +1817,12 @@ static int write_midx_internal(struct odb_source *source, free(ctx.pack_perm); free(ctx.pack_order); if (keep_hashes) { - for (uint32_t i = 0; i <= ctx.num_multi_pack_indexes_before; i++) + for (uint32_t i = 0; i < keep_hashes_nr; i++) free((char *)keep_hashes[i]); free(keep_hashes); } strbuf_release(&midx_name); + close_midx(midx_to_free); trace2_region_leave("midx", "write_midx_internal", r); @@ -1498,9 +1833,14 @@ int write_midx_file(struct odb_source *source, const char *preferred_pack_name, const char *refs_snapshot, unsigned flags) { - return write_midx_internal(source, NULL, NULL, - preferred_pack_name, refs_snapshot, - flags); + struct write_midx_opts opts = { + .source = source, + .preferred_pack_name = preferred_pack_name, + .refs_snapshot = refs_snapshot, + .flags = flags, + }; + + return write_midx_internal(&opts); } int write_midx_file_only(struct odb_source *source, @@ -1508,8 +1848,30 @@ int write_midx_file_only(struct odb_source *source, const char *preferred_pack_name, const char *refs_snapshot, unsigned flags) { - return write_midx_internal(source, packs_to_include, NULL, - preferred_pack_name, refs_snapshot, flags); + struct write_midx_opts opts = { + .source = source, + .packs_to_include = packs_to_include, + .preferred_pack_name = preferred_pack_name, + .refs_snapshot = refs_snapshot, + .flags = flags, + }; + + return write_midx_internal(&opts); +} + +int write_midx_file_compact(struct odb_source *source, + struct multi_pack_index *from, + struct multi_pack_index *to, + unsigned flags) +{ + struct write_midx_opts opts = { + .source = source, + .compact_from = from, + .compact_to = to, + .flags = flags | MIDX_WRITE_COMPACT, + }; + + return write_midx_internal(&opts); } int expire_midx_packs(struct odb_source *source, unsigned flags) @@ -1568,9 +1930,14 @@ int expire_midx_packs(struct odb_source *source, unsigned flags) free(count); - if (packs_to_drop.nr) - result = write_midx_internal(source, NULL, - &packs_to_drop, NULL, NULL, flags); + if (packs_to_drop.nr) { + struct write_midx_opts opts = { + .source = source, + .packs_to_drop = &packs_to_drop, + .flags = flags & MIDX_PROGRESS, + }; + result = write_midx_internal(&opts); + } string_list_clear(&packs_to_drop, 0); @@ -1703,6 +2070,10 @@ int midx_repack(struct odb_source *source, size_t batch_size, unsigned flags) struct child_process cmd = CHILD_PROCESS_INIT; FILE *cmd_in; struct multi_pack_index *m = get_multi_pack_index(source); + struct write_midx_opts opts = { + .source = source, + .flags = flags, + }; /* * When updating the default for these configuration @@ -1777,8 +2148,7 @@ int midx_repack(struct odb_source *source, size_t batch_size, unsigned flags) goto cleanup; } - result = write_midx_internal(source, NULL, NULL, NULL, NULL, - flags); + result = write_midx_internal(&opts); cleanup: free(include_pack);
diff --git a/midx.c b/midx.c index 1d6269f..81d6ab1 100644 --- a/midx.c +++ b/midx.c
@@ -24,7 +24,13 @@ void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext int cmp_idx_or_pack_name(const char *idx_or_pack_name, const char *idx_name); -const unsigned char *get_midx_checksum(struct multi_pack_index *m) +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); +} + +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; } @@ -95,8 +101,9 @@ static int midx_read_object_offsets(const unsigned char *chunk_start, struct multi_pack_index *get_multi_pack_index(struct odb_source *source) { - packfile_store_prepare(source->odb->packfiles); - return source->midx; + struct odb_source_files *files = odb_source_files_downcast(source); + packfile_store_prepare(files->packed); + return files->packed->midx; } static struct multi_pack_index *load_multi_pack_index_one(struct odb_source *source, @@ -143,7 +150,7 @@ static struct multi_pack_index *load_multi_pack_index_one(struct odb_source *sou m->signature, MIDX_SIGNATURE); m->version = m->data[MIDX_BYTE_FILE_VERSION]; - if (m->version != MIDX_VERSION) + if (m->version != MIDX_VERSION_V1 && m->version != MIDX_VERSION_V2) die(_("multi-pack-index version %d not recognized"), m->version); @@ -204,7 +211,8 @@ static struct multi_pack_index *load_multi_pack_index_one(struct odb_source *sou die(_("multi-pack-index pack-name chunk is too short")); cur_pack_name = end + 1; - if (i && strcmp(m->pack_names[i], m->pack_names[i - 1]) <= 0) + if (m->version == MIDX_VERSION_V1 && + i && strcmp(m->pack_names[i], m->pack_names[i - 1]) <= 0) die(_("multi-pack-index pack names out of order: '%s' before '%s'"), m->pack_names[i - 1], m->pack_names[i]); @@ -405,6 +413,7 @@ void close_midx(struct multi_pack_index *m) } FREE_AND_NULL(m->packs); FREE_AND_NULL(m->pack_names); + FREE_AND_NULL(m->pack_names_sorted); free(m); } @@ -447,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 repository *r = m->source->odb->repo; + struct odb_source_files *files = odb_source_files_downcast(m->source); struct strbuf pack_name = STRBUF_INIT; struct packed_git *p; @@ -458,12 +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", m->source->path, + strbuf_addf(&pack_name, "%s/pack/%s", files->base.path, m->pack_names[pack_int_id]); - p = packfile_store_load_pack(r->objects->packfiles, - pack_name.buf, m->source->local); - if (p) - list_add_tail(&p->mru, &r->objects->packfiles->mru); + p = packfile_store_load_pack(files->packed, + pack_name.buf, files->base.local); strbuf_release(&pack_name); if (!p) { @@ -652,17 +659,40 @@ int cmp_idx_or_pack_name(const char *idx_or_pack_name, return strcmp(idx_or_pack_name, idx_name); } + +static int midx_pack_names_cmp(const void *a, const void *b, void *m_) +{ + struct multi_pack_index *m = m_; + return strcmp(m->pack_names[*(const size_t *)a], + m->pack_names[*(const size_t *)b]); +} + static int midx_contains_pack_1(struct multi_pack_index *m, const char *idx_or_pack_name) { uint32_t first = 0, last = m->num_packs; + if (m->version == MIDX_VERSION_V2 && !m->pack_names_sorted) { + uint32_t i; + + ALLOC_ARRAY(m->pack_names_sorted, m->num_packs); + + for (i = 0; i < m->num_packs; i++) + m->pack_names_sorted[i] = i; + + QSORT_S(m->pack_names_sorted, m->num_packs, midx_pack_names_cmp, + m); + } + while (first < last) { uint32_t mid = first + (last - first) / 2; const char *current; int cmp; - current = m->pack_names[mid]; + if (m->pack_names_sorted) + current = m->pack_names[m->pack_names_sorted[mid]]; + else + current = m->pack_names[mid]; cmp = cmp_idx_or_pack_name(idx_or_pack_name, current); if (!cmp) return 1; @@ -688,7 +718,7 @@ int midx_preferred_pack(struct multi_pack_index *m, uint32_t *pack_int_id) { if (m->preferred_pack_idx == -1) { uint32_t midx_pos; - if (load_midx_revindex(m) < 0) { + if (load_midx_revindex(m)) { m->preferred_pack_idx = -2; return -1; } @@ -706,18 +736,19 @@ int midx_preferred_pack(struct multi_pack_index *m, uint32_t *pack_int_id) int prepare_multi_pack_index_one(struct odb_source *source) { + struct odb_source_files *files = odb_source_files_downcast(source); struct repository *r = source->odb->repo; prepare_repo_settings(r); if (!r->settings.core_multi_pack_index) return 0; - if (source->midx) + if (files->packed->midx) return 1; - source->midx = load_multi_pack_index(source); + files->packed->midx = load_multi_pack_index(source); - return !!source->midx; + return !!files->packed->midx; } int midx_checksum_valid(struct multi_pack_index *m) @@ -806,9 +837,10 @@ void clear_midx_file(struct repository *r) struct odb_source *source; for (source = r->objects->sources; source; source = source->next) { - if (source->midx) - close_midx(source->midx); - source->midx = NULL; + struct odb_source_files *files = odb_source_files_downcast(source); + if (files->packed->midx) + close_midx(files->packed->midx); + files->packed->midx = NULL; } }
diff --git a/midx.h b/midx.h index 6e54d73..08f3728 100644 --- a/midx.h +++ b/midx.h
@@ -11,7 +11,8 @@ struct git_hash_algo; struct odb_source; #define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */ -#define MIDX_VERSION 1 +#define MIDX_VERSION_V1 1 +#define MIDX_VERSION_V2 2 #define MIDX_BYTE_FILE_VERSION 4 #define MIDX_BYTE_HASH_VERSION 5 #define MIDX_BYTE_NUM_CHUNKS 6 @@ -71,6 +72,7 @@ struct multi_pack_index { uint32_t num_packs_in_base; const char **pack_names; + size_t *pack_names_sorted; struct packed_git **packs; }; @@ -80,12 +82,14 @@ struct multi_pack_index { #define MIDX_WRITE_BITMAP_HASH_CACHE (1 << 3) #define MIDX_WRITE_BITMAP_LOOKUP_TABLE (1 << 4) #define MIDX_WRITE_INCREMENTAL (1 << 5) +#define MIDX_WRITE_COMPACT (1 << 6) #define MIDX_EXT_REV "rev" #define MIDX_EXT_BITMAP "bitmap" #define MIDX_EXT_MIDX "midx" -const unsigned char *get_midx_checksum(struct multi_pack_index *m); +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, const unsigned char *hash, const char *ext); @@ -128,6 +132,10 @@ int write_midx_file_only(struct odb_source *source, struct string_list *packs_to_include, const char *preferred_pack_name, const char *refs_snapshot, unsigned flags); +int write_midx_file_compact(struct odb_source *source, + struct multi_pack_index *from, + struct multi_pack_index *to, + unsigned flags); void clear_midx_file(struct repository *r); int verify_midx_file(struct odb_source *source, unsigned flags); int expire_midx_packs(struct odb_source *source, unsigned flags);
diff --git a/negotiator/default.c b/negotiator/default.c index c479da9..3cac047 100644 --- a/negotiator/default.c +++ b/negotiator/default.c
@@ -38,11 +38,10 @@ static void rev_list_push(struct negotiation_state *ns, } } -static int clear_marks(const char *refname, const char *referent UNUSED, const struct object_id *oid, - int flag UNUSED, - void *cb_data UNUSED) +static int clear_marks(const struct reference *ref, void *cb_data UNUSED) { - struct object *o = deref_tag(the_repository, parse_object(the_repository, oid), refname, 0); + struct object *o = deref_tag(the_repository, parse_object(the_repository, ref->oid), + ref->name, 0); if (o && o->type == OBJ_COMMIT) clear_commit_marks((struct commit *)o, @@ -58,19 +57,19 @@ static int clear_marks(const char *refname, const char *referent UNUSED, const s static void mark_common(struct negotiation_state *ns, struct commit *commit, int ancestors_only, int dont_parse) { - struct prio_queue queue = { NULL }; + struct commit_stack stack = COMMIT_STACK_INIT; if (!commit || (commit->object.flags & COMMON)) return; - prio_queue_put(&queue, commit); + commit_stack_push(&stack, commit); if (!ancestors_only) { commit->object.flags |= COMMON; if ((commit->object.flags & SEEN) && !(commit->object.flags & POPPED)) ns->non_common_revs--; } - while ((commit = prio_queue_get(&queue))) { + while ((commit = commit_stack_pop(&stack))) { struct object *o = (struct object *)commit; if (!(o->flags & SEEN)) @@ -95,12 +94,12 @@ static void mark_common(struct negotiation_state *ns, struct commit *commit, if ((p->object.flags & SEEN) && !(p->object.flags & POPPED)) ns->non_common_revs--; - prio_queue_put(&queue, parents->item); + commit_stack_push(&stack, parents->item); } } } - clear_prio_queue(&queue); + commit_stack_clear(&stack); } /*
diff --git a/negotiator/skipping.c b/negotiator/skipping.c index 616df6b..fe4126c 100644 --- a/negotiator/skipping.c +++ b/negotiator/skipping.c
@@ -75,11 +75,10 @@ static struct entry *rev_list_push(struct data *data, struct commit *commit, int return entry; } -static int clear_marks(const char *refname, const char *referent UNUSED, const struct object_id *oid, - int flag UNUSED, - void *cb_data UNUSED) +static int clear_marks(const struct reference *ref, void *cb_data UNUSED) { - struct object *o = deref_tag(the_repository, parse_object(the_repository, oid), refname, 0); + struct object *o = deref_tag(the_repository, parse_object(the_repository, ref->oid), + ref->name, 0); if (o && o->type == OBJ_COMMIT) clear_commit_marks((struct commit *)o, @@ -92,15 +91,15 @@ static int clear_marks(const char *refname, const char *referent UNUSED, const s */ static void mark_common(struct data *data, struct commit *seen_commit) { - struct prio_queue queue = { NULL }; + struct commit_stack stack = COMMIT_STACK_INIT; struct commit *c; if (seen_commit->object.flags & COMMON) return; - prio_queue_put(&queue, seen_commit); + commit_stack_push(&stack, seen_commit); seen_commit->object.flags |= COMMON; - while ((c = prio_queue_get(&queue))) { + while ((c = commit_stack_pop(&stack))) { struct commit_list *p; if (!(c->object.flags & POPPED)) @@ -114,11 +113,11 @@ static void mark_common(struct data *data, struct commit *seen_commit) continue; p->item->object.flags |= COMMON; - prio_queue_put(&queue, p->item); + commit_stack_push(&stack, p->item); } } - clear_prio_queue(&queue); + commit_stack_clear(&stack); } /*
diff --git a/notes-merge.c b/notes-merge.c index 5869399..b9322ab 100644 --- a/notes-merge.c +++ b/notes-merge.c
@@ -359,9 +359,9 @@ static int ll_merge_in_worktree(struct notes_merge_options *o, mmfile_t base, local, remote; enum ll_merge_result status; - read_mmblob(&base, &p->base); - read_mmblob(&local, &p->local); - read_mmblob(&remote, &p->remote); + read_mmblob(&base, the_repository->objects, &p->base); + read_mmblob(&local, the_repository->objects, &p->local); + read_mmblob(&remote, the_repository->objects, &p->remote); status = ll_merge(&result_buf, oid_to_hex(&p->obj), &base, NULL, &local, o->local_ref, &remote, o->remote_ref, @@ -668,11 +668,11 @@ int notes_merge(struct notes_merge_options *o, commit_list_insert(local, &parents); create_notes_commit(o->repo, local_tree, parents, o->commit_msg.buf, o->commit_msg.len, result_oid); - free_commit_list(parents); + commit_list_free(parents); } found_result: - free_commit_list(bases); + commit_list_free(bases); strbuf_release(&(o->commit_msg)); trace_printf("notes_merge(): result = %i, result_oid = %.7s\n", result, oid_to_hex(result_oid));
diff --git a/notes-utils.c b/notes-utils.c index 6a50c6d..5c1c75d 100644 --- a/notes-utils.c +++ b/notes-utils.c
@@ -40,7 +40,7 @@ void create_notes_commit(struct repository *r, NULL)) die("Failed to commit notes tree to database"); - free_commit_list(parents_to_free); + commit_list_free(parents_to_free); } void commit_notes(struct repository *r, struct notes_tree *t, const char *msg)
diff --git a/notes.c b/notes.c index 9a2e918..51a7ef9 100644 --- a/notes.c +++ b/notes.c
@@ -921,8 +921,7 @@ int combine_notes_cat_sort_uniq(struct object_id *cur_oid, if (string_list_add_note_lines(&sort_uniq_list, new_oid)) goto out; string_list_remove_empty_items(&sort_uniq_list, 0); - string_list_sort(&sort_uniq_list); - string_list_remove_duplicates(&sort_uniq_list, 0); + string_list_sort_u(&sort_uniq_list, 0); /* create a new blob object from sort_uniq_list */ if (for_each_string_list(&sort_uniq_list, @@ -938,13 +937,11 @@ int combine_notes_cat_sort_uniq(struct object_id *cur_oid, return ret; } -static int string_list_add_one_ref(const char *refname, const char *referent UNUSED, - const struct object_id *oid UNUSED, - int flag UNUSED, void *cb) +static int string_list_add_one_ref(const struct reference *ref, void *cb) { struct string_list *refs = cb; - if (!unsorted_string_list_has_string(refs, refname)) - string_list_append(refs, refname); + if (!unsorted_string_list_has_string(refs, ref->name)) + string_list_append(refs, ref->name); return 0; } @@ -955,8 +952,11 @@ void string_list_add_refs_by_glob(struct string_list *list, const char *glob) { assert(list->strdup_strings); if (has_glob_specials(glob)) { - refs_for_each_glob_ref(get_main_ref_store(the_repository), - string_list_add_one_ref, glob, list); + struct refs_for_each_ref_options opts = { + .pattern = glob, + }; + refs_for_each_ref_ext(get_main_ref_store(the_repository), + string_list_add_one_ref, list, &opts); } else { struct object_id oid; if (repo_get_oid(the_repository, glob, &oid))
diff --git a/object-file-convert.c b/object-file-convert.c index 7ab875a..63ee186 100644 --- a/object-file-convert.c +++ b/object-file-convert.c
@@ -6,14 +6,13 @@ #include "hex.h" #include "repository.h" #include "hash.h" -#include "hash.h" #include "object.h" #include "loose.h" #include "commit.h" #include "gpg-interface.h" #include "object-file-convert.h" -int repo_oid_to_algop(struct repository *repo, const struct object_id *src, +int repo_oid_to_algop(struct repository *repo, const struct object_id *srcoid, const struct git_hash_algo *to, struct object_id *dest) { /* @@ -21,9 +20,17 @@ int repo_oid_to_algop(struct repository *repo, const struct object_id *src, * default hash algorithm for that object. */ const struct git_hash_algo *from = - src->algo ? &hash_algos[src->algo] : repo->hash_algo; + srcoid->algo ? &hash_algos[srcoid->algo] : repo->hash_algo; + struct object_id temp; + const struct object_id *src = srcoid; - if (from == to) { + if (!srcoid->algo) { + oidcpy(&temp, srcoid); + temp.algo = hash_algo_by_ptr(repo->hash_algo); + src = &temp; + } + + if (from == to || !to) { if (src != dest) oidcpy(dest, src); return 0;
diff --git a/object-file.c b/object-file.c index 5d72e65..63408fc 100644 --- a/object-file.c +++ b/object-file.c
@@ -20,19 +20,22 @@ #include "object-file-convert.h" #include "object-file.h" #include "odb.h" +#include "odb/streaming.h" #include "oidtree.h" #include "pack.h" #include "packfile.h" #include "path.h" #include "read-cache-ll.h" #include "setup.h" -#include "streaming.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) @@ -99,8 +102,8 @@ static int check_and_freshen_source(struct odb_source *source, return check_and_freshen_file(path.buf, freshen); } -int has_loose_object(struct odb_source *source, - const struct object_id *oid) +int odb_source_loose_has_object(struct odb_source *source, + const struct object_id *oid) { return check_and_freshen_source(source, oid, 0); } @@ -129,32 +132,27 @@ int check_object_signature(struct repository *r, const struct object_id *oid, return !oideq(oid, &real_oid) ? -1 : 0; } -int stream_object_signature(struct repository *r, const struct object_id *oid) +int stream_object_signature(struct repository *r, + struct odb_read_stream *st, + const struct object_id *oid) { struct object_id real_oid; - unsigned long size; - enum object_type obj_type; - struct git_istream *st; struct git_hash_ctx c; char hdr[MAX_HEADER_LEN]; int hdrlen; - st = open_istream(r, oid, &obj_type, &size, NULL); - if (!st) - return -1; - /* Generate the header */ - hdrlen = format_object_header(hdr, sizeof(hdr), obj_type, size); + hdrlen = format_object_header(hdr, sizeof(hdr), st->type, st->size); /* Sha1.. */ r->hash_algo->init_fn(&c); git_hash_update(&c, hdr, hdrlen); for (;;) { char buf[1024 * 16]; - ssize_t readlen = read_istream(st, buf, sizeof(buf)); + ssize_t readlen = odb_read_stream_read(st, buf, sizeof(buf)); if (readlen < 0) { - close_istream(st); + odb_read_stream_close(st); return -1; } if (!readlen) @@ -162,71 +160,35 @@ int stream_object_signature(struct repository *r, const struct object_id *oid) git_hash_update(&c, buf, readlen); } git_hash_final_oid(&real_oid, &c); - close_istream(st); return !oideq(oid, &real_oid) ? -1 : 0; } /* - * Find "oid" as a loose object in the local repository or in an alternate. - * Returns 0 on success, negative on failure. + * 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 stat_loose_object(struct repository *r, const struct object_id *oid, - struct stat *st, const char **path) -{ - struct odb_source *source; - static struct strbuf buf = STRBUF_INIT; - - odb_prepare_alternates(r->objects); - for (source = r->objects->sources; source; source = source->next) { - *path = odb_loose_path(source, &buf, oid); - if (!lstat(*path, st)) - return 0; - } - - return -1; -} - -/* - * Like stat_loose_object(), but actually open the object and return the - * descriptor. See the caveats on the "path" parameter above. - */ -static int open_loose_object(struct repository *r, +static int open_loose_object(struct odb_source_loose *loose, const struct object_id *oid, const char **path) { - int fd; - struct odb_source *source; - int most_interesting_errno = ENOENT; static struct strbuf buf = STRBUF_INIT; + int fd; - odb_prepare_alternates(r->objects); - for (source = r->objects->sources; source; source = source->next) { - *path = odb_loose_path(source, &buf, oid); - fd = git_open(*path); - if (fd >= 0) - return fd; + *path = odb_loose_path(loose->source, &buf, oid); + fd = git_open(*path); + if (fd >= 0) + return fd; - if (most_interesting_errno == ENOENT) - most_interesting_errno = errno; - } - errno = most_interesting_errno; return -1; } -static int quick_has_loose(struct repository *r, +static int quick_has_loose(struct odb_source_loose *loose, const struct object_id *oid) { - struct odb_source *source; - - odb_prepare_alternates(r->objects); - for (source = r->objects->sources; source; source = source->next) { - if (oidtree_contains(odb_loose_cache(source, oid), oid)) - return 1; - } - return 0; + return !!oidtree_contains(odb_source_loose_cache(loose->source, oid), oid); } /* @@ -252,23 +214,42 @@ static void *map_fd(int fd, const char *path, unsigned long *size) return map; } -void *map_loose_object(struct repository *r, - const struct object_id *oid, - unsigned long *size) +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(r, oid, &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 unpack_loose_header(git_zstream *stream, - unsigned char *map, - unsigned long mapsize, - void *buffer, - unsigned long bufsiz) +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) { int status; @@ -347,11 +328,18 @@ static void *unpack_loose_rest(git_zstream *stream, } /* + * parse_loose_header() parses the starting "<type> <len>\0" of an + * object. If it doesn't follow that format -1 is returned. To check + * the validity of the <type> populate the "typep" in the "struct + * object_info". It will be OBJ_BAD if the object type is unknown. The + * parsed <len> can be retrieved via "oi->sizep", and from there + * passed to unpack_loose_rest(). + * * We used to just use "sscanf()", but that's actually way * too permissive for what we want to check. So do an anal * object header parse by hand. */ -int parse_loose_header(const char *hdr, struct object_info *oi) +static int parse_loose_header(const char *hdr, struct object_info *oi) { const char *type_buf = hdr; size_t size; @@ -407,22 +395,22 @@ int parse_loose_header(const char *hdr, struct object_info *oi) return 0; } -int loose_object_info(struct repository *r, - const struct object_id *oid, - struct object_info *oi, int flags) +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) { - int status = 0; + struct odb_source_files *files = odb_source_files_downcast(source); + int ret; int fd; unsigned long mapsize; - const char *path; - void *map; - git_zstream stream; + void *map = NULL; + git_zstream stream, *stream_to_end = NULL; char hdr[MAX_HEADER_LEN]; unsigned long size_scratch; enum object_type type_scratch; - - if (oi->delta_base_oid) - oidclr(oi->delta_base_oid, r->hash_algo); + struct stat st; /* * If we don't care about type or size, then we don't @@ -432,73 +420,143 @@ int loose_object_info(struct repository *r, * return value implicitly indicates whether the * object even exists. */ - if (!oi->typep && !oi->sizep && !oi->contentp) { + if (!oi || (!oi->typep && !oi->sizep && !oi->contentp)) { struct stat st; - if (!oi->disk_sizep && (flags & OBJECT_INFO_QUICK)) - return quick_has_loose(r, oid) ? 0 : -1; - if (stat_loose_object(r, oid, &st, &path) < 0) - return -1; - if (oi->disk_sizep) - *oi->disk_sizep = st.st_size; - return 0; + + 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 = open_loose_object(r, oid, &path); + fd = git_open(path); if (fd < 0) { if (errno != ENOENT) error_errno(_("unable to open loose object %s"), oid_to_hex(oid)); - return -1; + ret = -1; + goto out; } - map = map_fd(fd, path, &mapsize); - if (!map) - return -1; - if (!oi->sizep) - oi->sizep = &size_scratch; - if (!oi->typep) - oi->typep = &type_scratch; + 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 (parse_loose_header(hdr, oi) < 0) - status = error(_("unable to parse %s header"), oid_to_hex(oid)); - else if (*oi->typep < 0) + 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) - break; - *oi->contentp = unpack_loose_rest(&stream, hdr, *oi->sizep, oid); - if (*oi->contentp) - goto cleanup; + if (oi->contentp) { + *oi->contentp = unpack_loose_rest(&stream, hdr, *oi->sizep, oid); + if (!*oi->contentp) { + ret = -1; + goto corrupt; + } + } - status = -1; break; case ULHR_BAD: - status = error(_("unable to unpack %s header"), - oid_to_hex(oid)); - break; + ret = error(_("unable to unpack %s header"), + oid_to_hex(oid)); + goto corrupt; case ULHR_TOO_LONG: - status = error(_("header for %s too long, exceeds %d bytes"), - oid_to_hex(oid), MAX_HEADER_LEN); - break; + ret = error(_("header for %s too long, exceeds %d bytes"), + oid_to_hex(oid), MAX_HEADER_LEN); + goto corrupt; } - if (status && (flags & OBJECT_INFO_DIE_IF_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); -cleanup: - git_inflate_end(&stream); - munmap(map, mapsize); - if (oi->sizep == &size_scratch) - oi->sizep = NULL; - if (oi->typep == &type_scratch) - oi->typep = NULL; - oi->whence = OI_LOOSE; - return status; +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, @@ -678,32 +736,38 @@ struct transaction_packfile { uint32_t nr_written; }; -struct odb_transaction { - struct object_database *odb; +struct odb_transaction_files { + struct odb_transaction base; struct tmp_objdir *objdir; struct transaction_packfile packfile; }; -static void prepare_loose_object_transaction(struct odb_transaction *transaction) +static void prepare_loose_object_transaction(struct odb_transaction *base) { + struct odb_transaction_files *transaction = + container_of_or_null(base, struct odb_transaction_files, base); + /* * We lazily create the temporary object directory * the first time an object might be added, since * callers may not know whether any objects will be - * added at the time they call object_file_transaction_begin. + * added at the time they call odb_transaction_files_begin. */ if (!transaction || transaction->objdir) return; - transaction->objdir = tmp_objdir_create(transaction->odb->repo, "bulk-fsync"); + transaction->objdir = tmp_objdir_create(base->source->odb->repo, "bulk-fsync"); if (transaction->objdir) tmp_objdir_replace_primary_odb(transaction->objdir, 0); } -static void fsync_loose_object_transaction(struct odb_transaction *transaction, +static void fsync_loose_object_transaction(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 we have an active ODB transaction, we issue a call that * cleans the filesystem page cache but avoids a hardware flush @@ -722,7 +786,7 @@ static void fsync_loose_object_transaction(struct odb_transaction *transaction, /* * Cleanup after batch-mode fsync_object_files. */ -static void flush_loose_object_transaction(struct odb_transaction *transaction) +static void flush_loose_object_transaction(struct odb_transaction_files *transaction) { struct strbuf temp_path = STRBUF_INIT; struct tempfile *temp; @@ -740,7 +804,7 @@ static void flush_loose_object_transaction(struct odb_transaction *transaction) * the final name is visible. */ strbuf_addf(&temp_path, "%s/bulk_fsync_XXXXXX", - repo_get_object_directory(transaction->odb->repo)); + 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); @@ -986,35 +1050,15 @@ static int write_loose_object(struct odb_source *source, FOF_SKIP_COLLISION_CHECK); } -static int freshen_loose_object(struct object_database *odb, - const struct object_id *oid) +int odb_source_loose_freshen_object(struct odb_source *source, + const struct object_id *oid) { - odb_prepare_alternates(odb); - for (struct odb_source *source = odb->sources; source; source = source->next) - if (check_and_freshen_source(source, oid, 1)) - return 1; - return 0; + return !!check_and_freshen_source(source, oid, 1); } -static int freshen_packed_object(struct object_database *odb, - const struct object_id *oid) -{ - struct pack_entry e; - if (!find_pack_entry(odb->repo, oid, &e)) - return 0; - if (e.p->is_cruft) - return 0; - if (e.p->freshened) - return 1; - if (!freshen_file(e.p->pack_name)) - return 0; - e.p->freshened = 1; - return 1; -} - -int stream_loose_object(struct odb_source *source, - struct input_stream *in_stream, size_t len, - struct object_id *oid) +int odb_source_loose_write_stream(struct odb_source *source, + 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; struct object_id compat_oid; @@ -1091,12 +1135,10 @@ int stream_loose_object(struct odb_source *source, die(_("deflateEnd on stream object failed (%d)"), ret); close_loose_object(source, fd, tmp_file.buf); - if (freshen_packed_object(source->odb, oid) || - freshen_loose_object(source->odb, oid)) { + if (odb_freshen_object(source->odb, oid)) { unlink_or_warn(tmp_file.buf); goto cleanup; } - odb_loose_path(source, &filename, oid); /* We finally know the object path, and create the missing dir. */ @@ -1124,10 +1166,10 @@ int stream_loose_object(struct odb_source *source, return err; } -int write_object_file(struct odb_source *source, - const void *buf, unsigned long len, - enum object_type type, struct object_id *oid, - struct object_id *compat_oid_in, unsigned flags) +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, unsigned flags) { const struct git_hash_algo *algo = source->odb->repo->hash_algo; const struct git_hash_algo *compat = source->odb->repo->compat_hash_algo; @@ -1155,8 +1197,7 @@ int write_object_file(struct odb_source *source, * it out into .git/objects/??/?{38} file. */ write_object_file_prepare(algo, buf, len, type, oid, hdr, &hdrlen); - if (freshen_packed_object(source->odb, oid) || - freshen_loose_object(source->odb, oid)) + if (odb_freshen_object(source->odb, oid)) return 0; if (write_loose_object(source, oid, hdr, hdrlen, buf, len, 0, flags)) return -1; @@ -1179,7 +1220,7 @@ int force_object_loose(struct odb_source *source, int ret; for (struct odb_source *s = source->odb->sources; s; s = s->next) - if (has_loose_object(s, oid)) + if (odb_source_loose_has_object(s, oid)) return 0; oi.typep = &type; @@ -1331,11 +1372,11 @@ static int index_core(struct index_state *istate, return ret; } -static int already_written(struct odb_transaction *transaction, +static int already_written(struct odb_transaction_files *transaction, struct object_id *oid) { /* The object may already exist in the repository */ - if (odb_has_object(transaction->odb, oid, + if (odb_has_object(transaction->base.source->odb, oid, HAS_OBJECT_RECHECK_PACKED | HAS_OBJECT_FETCH_PROMISOR)) return 1; @@ -1349,14 +1390,14 @@ static int already_written(struct odb_transaction *transaction, } /* Lazily create backing packfile for the state */ -static void prepare_packfile_transaction(struct odb_transaction *transaction, +static void prepare_packfile_transaction(struct odb_transaction_files *transaction, unsigned flags) { struct transaction_packfile *state = &transaction->packfile; if (!(flags & INDEX_WRITE_OBJECT) || state->f) return; - state->f = create_tmp_packfile(transaction->odb->repo, + state->f = create_tmp_packfile(transaction->base.source->odb->repo, &state->pack_tmp_name); reset_pack_idx_option(&state->pack_idx_opts); @@ -1457,10 +1498,10 @@ static int stream_blob_to_pack(struct transaction_packfile *state, return 0; } -static void flush_packfile_transaction(struct odb_transaction *transaction) +static void flush_packfile_transaction(struct odb_transaction_files *transaction) { struct transaction_packfile *state = &transaction->packfile; - struct repository *repo = transaction->odb->repo; + struct repository *repo = transaction->base.source->odb->repo; unsigned char hash[GIT_MAX_RAWSZ]; struct strbuf packname = STRBUF_INIT; char *idx_tmp_name = NULL; @@ -1485,7 +1526,7 @@ static void flush_packfile_transaction(struct odb_transaction *transaction) } strbuf_addf(&packname, "%s/pack/pack-%s.", - repo_get_object_directory(transaction->odb->repo), + repo_get_object_directory(transaction->base.source->odb->repo), hash_to_hex_algop(hash, repo->hash_algo)); stage_tmp_packfiles(repo, &packname, state->pack_tmp_name, @@ -1525,7 +1566,7 @@ static void flush_packfile_transaction(struct odb_transaction *transaction) * binary blobs, they generally do not want to get any conversion, and * callers should avoid this code path when filters are requested. */ -static int index_blob_packfile_transaction(struct odb_transaction *transaction, +static int index_blob_packfile_transaction(struct odb_transaction_files *transaction, struct object_id *result_oid, int fd, size_t size, const char *path, unsigned flags) @@ -1544,7 +1585,7 @@ static int index_blob_packfile_transaction(struct odb_transaction *transaction, header_len = format_object_header((char *)obuf, sizeof(obuf), OBJ_BLOB, size); - transaction->odb->repo->hash_algo->init_fn(&ctx); + transaction->base.source->odb->repo->hash_algo->init_fn(&ctx); git_hash_update(&ctx, obuf, header_len); /* Note: idx is non-NULL when we are writing */ @@ -1649,11 +1690,15 @@ int index_fd(struct index_state *istate, struct object_id *oid, type, path, flags); } else { if (flags & INDEX_WRITE_OBJECT) { + struct object_database *odb = the_repository->objects; + struct odb_transaction_files *files_transaction; struct odb_transaction *transaction; - transaction = odb_transaction_begin(the_repository->objects); - ret = index_blob_packfile_transaction(the_repository->objects->transaction, - oid, fd, + transaction = odb_transaction_begin(odb); + files_transaction = container_of(odb->transaction, + struct odb_transaction_files, + base); + ret = index_blob_packfile_transaction(files_transaction, oid, fd, xsize_t(st->st_size), path, flags); odb_transaction_commit(transaction); @@ -1694,7 +1739,11 @@ int index_path(struct index_state *istate, struct object_id *oid, strbuf_release(&sb); break; case S_IFDIR: - return repo_resolve_gitlink_ref(istate->repo, path, "HEAD", oid); + if (repo_resolve_gitlink_ref(istate->repo, path, "HEAD", oid)) + return error(_("'%s' does not have a commit checked out"), path); + if (&hash_algos[oid->algo] != istate->repo->hash_algo) + return error(_("cannot add a submodule of a different hash algorithm")); + break; default: return error(_("%s: unsupported file type"), path); } @@ -1807,26 +1856,173 @@ int for_each_loose_file_in_source(struct odb_source *source, return r; } -int for_each_loose_object(struct object_database *odb, - each_loose_object_fn cb, void *data, - enum for_each_object_flags flags) -{ +struct for_each_object_wrapper_data { struct odb_source *source; + const struct object_info *request; + odb_for_each_object_cb cb; + void *cb_data; +}; - odb_prepare_alternates(odb); - for (source = odb->sources; source; source = source->next) { - int r = for_each_loose_file_in_source(source, cb, NULL, - NULL, data); - if (r) - return r; +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 (flags & FOR_EACH_OBJECT_LOCAL_ONLY) - break; + if (data->request) { + struct object_info oi = *data->request; + + if (read_object_info_from_path(data->source, path, oid, &oi, 0) < 0) + return -1; + + return data->cb(oid, &oi, data->cb_data); + } else { + return data->cb(oid, NULL, data->cb_data); + } +} + +static int for_each_prefixed_object_wrapper_cb(const struct object_id *oid, + void *cb_data) +{ + struct for_each_object_wrapper_data *data = cb_data; + if (data->request) { + struct object_info oi = *data->request; + + if (odb_source_loose_read_object_info(data->source, + oid, &oi, 0) < 0) + return -1; + + return data->cb(oid, &oi, data->cb_data); + } else { + return data->cb(oid, NULL, data->cb_data); + } +} + +int odb_source_loose_for_each_object(struct odb_source *source, + const struct object_info *request, + odb_for_each_object_cb cb, + void *cb_data, + const struct odb_for_each_object_options *opts) +{ + struct for_each_object_wrapper_data data = { + .source = source, + .request = request, + .cb = cb, + .cb_data = cb_data, + }; + + /* There are no loose promisor objects, so we can return immediately. */ + if ((opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY)) + return 0; + if ((opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !source->local) + return 0; + + if (opts->prefix) + return oidtree_each(odb_source_loose_cache(source, opts->prefix), + opts->prefix, opts->prefix_hex_len, + for_each_prefixed_object_wrapper_cb, &data); + + return for_each_loose_file_in_source(source, for_each_object_wrapper_cb, + NULL, NULL, &data); +} + +static int count_loose_object(const struct object_id *oid UNUSED, + struct object_info *oi UNUSED, + void *payload) +{ + unsigned long *count = payload; + (*count)++; + return 0; +} + +int odb_source_loose_count_objects(struct odb_source *source, + enum odb_count_objects_flags flags, + unsigned long *out) +{ + const unsigned hexsz = source->odb->repo->hash_algo->hexsz - 2; + char *path = NULL; + DIR *dir = NULL; + int ret; + + if (flags & ODB_COUNT_OBJECTS_APPROXIMATE) { + unsigned long count = 0; + struct dirent *ent; + + path = xstrfmt("%s/17", source->path); + + dir = opendir(path); + if (!dir) { + if (errno == ENOENT) { + *out = 0; + ret = 0; + goto out; + } + + ret = error_errno("cannot open object shard '%s'", path); + goto out; + } + + while ((ent = readdir(dir)) != NULL) { + if (strspn(ent->d_name, "0123456789abcdef") != hexsz || + ent->d_name[hexsz] != '\0') + continue; + count++; + } + + *out = count * 256; + ret = 0; + } else { + struct odb_for_each_object_options opts = { 0 }; + *out = 0; + ret = odb_source_loose_for_each_object(source, NULL, count_loose_object, + out, &opts); } +out: + if (dir) + closedir(dir); + free(path); + return ret; +} + +struct find_abbrev_len_data { + const struct object_id *oid; + unsigned len; +}; + +static int find_abbrev_len_cb(const struct object_id *oid, + struct object_info *oi UNUSED, + void *cb_data) +{ + struct find_abbrev_len_data *data = cb_data; + unsigned len = oid_common_prefix_hexlen(oid, data->oid); + if (len != hash_algos[oid->algo].hexsz && len >= data->len) + data->len = len + 1; return 0; } +int odb_source_loose_find_abbrev_len(struct odb_source *source, + const struct object_id *oid, + unsigned min_len, + unsigned *out) +{ + struct odb_for_each_object_options opts = { + .prefix = oid, + .prefix_hex_len = min_len, + }; + struct find_abbrev_len_data data = { + .oid = oid, + .len = min_len, + }; + int ret; + + ret = odb_source_loose_for_each_object(source, NULL, find_abbrev_len_cb, + &data, &opts); + *out = data.len; + + return ret; +} + static int append_loose_object(const struct object_id *oid, const char *path UNUSED, void *data) @@ -1835,44 +2031,51 @@ static int append_loose_object(const struct object_id *oid, return 0; } -struct oidtree *odb_loose_cache(struct odb_source *source, - const struct object_id *oid) +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(source->loose_objects_subdir_seen[0]); + 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(source->loose_objects_subdir_seen)) + (size_t) subdir_nr >= bitsizeof(files->loose->subdir_seen)) BUG("subdir_nr out of range"); - bitmap = &source->loose_objects_subdir_seen[word_index]; + bitmap = &files->loose->subdir_seen[word_index]; if (*bitmap & mask) - return source->loose_objects_cache; - if (!source->loose_objects_cache) { - ALLOC_ARRAY(source->loose_objects_cache, 1); - oidtree_init(source->loose_objects_cache); + 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, - source->loose_objects_cache); + files->loose->cache); *bitmap |= mask; strbuf_release(&buf); - return source->loose_objects_cache; + return files->loose->cache; } -void odb_clear_loose_cache(struct odb_source *source) +static void odb_source_loose_clear_cache(struct odb_source_loose *loose) { - oidtree_clear(source->loose_objects_cache); - FREE_AND_NULL(source->loose_objects_cache); - memset(&source->loose_objects_subdir_seen, 0, - sizeof(source->loose_objects_subdir_seen)); + 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, @@ -2000,31 +2203,170 @@ int read_loose_object(struct repository *repo, return ret; } -struct odb_transaction *object_file_transaction_begin(struct odb_source *source) +static void 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; - CALLOC_ARRAY(odb->transaction, 1); - odb->transaction->odb = odb; + transaction = xcalloc(1, sizeof(*transaction)); + transaction->base.source = source; + transaction->base.commit = odb_transaction_files_commit; - return odb->transaction; + return &transaction->base; } -void object_file_transaction_commit(struct odb_transaction *transaction) +struct odb_source_loose *odb_source_loose_new(struct odb_source *source) { - if (!transaction) + struct odb_source_loose *loose; + CALLOC_ARRAY(loose, 1); + loose->source = source; + return loose; +} + +void odb_source_loose_free(struct odb_source_loose *loose) +{ + if (!loose) return; + odb_source_loose_clear_cache(loose); + loose_object_map_clear(&loose->map); + free(loose); +} + +struct odb_loose_read_stream { + struct odb_read_stream base; + git_zstream z; + enum { + ODB_LOOSE_READ_STREAM_INUSE, + ODB_LOOSE_READ_STREAM_DONE, + ODB_LOOSE_READ_STREAM_ERROR, + } z_state; + void *mapped; + unsigned long mapsize; + char hdr[32]; + int hdr_avail; + int hdr_used; +}; + +static ssize_t read_istream_loose(struct odb_read_stream *_st, char *buf, size_t sz) +{ + struct odb_loose_read_stream *st = + container_of(_st, struct odb_loose_read_stream, base); + size_t total_read = 0; + + switch (st->z_state) { + case ODB_LOOSE_READ_STREAM_DONE: + return 0; + case ODB_LOOSE_READ_STREAM_ERROR: + return -1; + default: + break; + } + + if (st->hdr_used < st->hdr_avail) { + size_t to_copy = st->hdr_avail - st->hdr_used; + if (sz < to_copy) + to_copy = sz; + memcpy(buf, st->hdr + st->hdr_used, to_copy); + st->hdr_used += to_copy; + total_read += to_copy; + } + + while (total_read < sz) { + int status; + + st->z.next_out = (unsigned char *)buf + total_read; + st->z.avail_out = sz - total_read; + status = git_inflate(&st->z, Z_FINISH); + + total_read = st->z.next_out - (unsigned char *)buf; + + if (status == Z_STREAM_END) { + git_inflate_end(&st->z); + st->z_state = ODB_LOOSE_READ_STREAM_DONE; + break; + } + if (status != Z_OK && (status != Z_BUF_ERROR || total_read < sz)) { + git_inflate_end(&st->z); + st->z_state = ODB_LOOSE_READ_STREAM_ERROR; + return -1; + } + } + return total_read; +} + +static int close_istream_loose(struct odb_read_stream *_st) +{ + struct odb_loose_read_stream *st = + container_of(_st, struct odb_loose_read_stream, base); + + if (st->z_state == ODB_LOOSE_READ_STREAM_INUSE) + git_inflate_end(&st->z); + munmap(st->mapped, st->mapsize); + return 0; +} + +int odb_source_loose_read_object_stream(struct odb_read_stream **out, + struct odb_source *source, + const struct object_id *oid) +{ + struct object_info oi = OBJECT_INFO_INIT; + struct odb_loose_read_stream *st; + unsigned long mapsize; + void *mapped; + + mapped = odb_source_loose_map_object(source, oid, &mapsize); + if (!mapped) + return -1; /* - * Ensure the transaction ending matches the pending 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. */ - ASSERT(transaction == transaction->odb->transaction); + CALLOC_ARRAY(st, 1); - flush_loose_object_transaction(transaction); - flush_packfile_transaction(transaction); - transaction->odb->transaction = NULL; - free(transaction); + 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; }
diff --git a/object-file.h b/object-file.h index 3fd48dc..3686f18 100644 --- a/object-file.h +++ b/object-file.h
@@ -7,14 +7,6 @@ struct index_state; -/* - * Set this to 0 to prevent odb_read_object_info_extended() from fetching missing - * blobs. This has a difference only if extensions.partialClone is set. - * - * Its default value is 1. - */ -extern int fetch_if_missing; - enum { INDEX_WRITE_OBJECT = (1 << 0), INDEX_FORMAT_CHECK = (1 << 1), @@ -24,17 +16,63 @@ enum { int index_fd(struct index_state *istate, struct object_id *oid, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags); 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); + /* - * Populate and return the loose object cache array corresponding to the - * given object ID. + * Return true iff an object database source has a loose object + * with the specified name. This function does not respect replace + * references. */ -struct oidtree *odb_loose_cache(struct odb_source *source, +int odb_source_loose_has_object(struct odb_source *source, const struct object_id *oid); -/* Empty the loose object cache for the specified object directory. */ -void odb_clear_loose_cache(struct odb_source *source); +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, unsigned flags); + +int odb_source_loose_write_stream(struct odb_source *source, + struct odb_write_stream *stream, size_t len, + struct object_id *oid); /* * Put in `buf` the name of the file in the local object database that @@ -45,17 +83,6 @@ const char *odb_loose_path(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. - */ -int has_loose_object(struct odb_source *source, - const struct object_id *oid); - -void *map_loose_object(struct repository *r, const struct object_id *oid, - unsigned long *size); - -/* * Iterate over the files in the loose-object parts of the object * directory "path", triggering the following callbacks: * @@ -93,16 +120,43 @@ int for_each_loose_file_in_source(struct odb_source *source, void *data); /* - * Iterate over all accessible loose objects without respect to - * reachability. By default, this includes both local and alternate objects. - * The order in which objects are visited is unspecified. - * - * Any flags specific to packs are ignored. + * 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 for_each_loose_object(struct object_database *odb, - each_loose_object_fn, void *, - enum for_each_object_flags flags); +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); /** * format_object_header() is a thin wrapper around s xsnprintf() that @@ -112,55 +166,6 @@ int for_each_loose_object(struct object_database *odb, int format_object_header(char *str, size_t size, enum object_type type, size_t objsize); -/** - * 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 { - ULHR_OK, - ULHR_BAD, - ULHR_TOO_LONG, -}; -enum unpack_loose_header_result unpack_loose_header(git_zstream *stream, - unsigned char *map, - unsigned long mapsize, - void *buffer, - unsigned long bufsiz); - -/** - * parse_loose_header() parses the starting "<type> <len>\0" of an - * object. If it doesn't follow that format -1 is returned. To check - * the validity of the <type> populate the "typep" in the "struct - * object_info". It will be OBJ_BAD if the object type is unknown. The - * parsed <len> can be retrieved via "oi->sizep", and from there - * passed to unpack_loose_rest(). - */ -struct object_info; -int parse_loose_header(const char *hdr, struct object_info *oi); - -int write_object_file(struct odb_source *source, - const void *buf, unsigned long len, - enum object_type type, struct object_id *oid, - struct object_id *compat_oid_in, unsigned flags); - -struct input_stream { - const void *(*read)(struct input_stream *, unsigned long *len); - void *data; - int is_finished; -}; - -int stream_loose_object(struct odb_source *source, - struct input_stream *in_stream, size_t len, - struct object_id *oid); - int force_object_loose(struct odb_source *source, const struct object_id *oid, time_t mtime); @@ -180,11 +185,9 @@ int check_object_signature(struct repository *r, const struct object_id *oid, * Try reading the object named with "oid" using * the streaming interface and rehash it to do the same. */ -int stream_object_signature(struct repository *r, const struct object_id *oid); - -int loose_object_info(struct repository *r, - const struct object_id *oid, - struct object_info *oi, int flags); +int stream_object_signature(struct repository *r, + struct odb_read_stream *stream, + const struct object_id *oid); enum finalize_object_file_flags { FOF_SKIP_COLLISION_CHECK = 1, @@ -222,16 +225,10 @@ struct odb_transaction; /* * Tell the object database to optimize for adding - * multiple objects. object_file_transaction_commit must be called + * multiple objects. odb_transaction_files_commit must be called * to make new objects visible. If a transaction is already * pending, NULL is returned. */ -struct odb_transaction *object_file_transaction_begin(struct odb_source *source); - -/* - * Tell the object database to make any objects from the - * current transaction visible. - */ -void object_file_transaction_commit(struct odb_transaction *transaction); +struct odb_transaction *odb_transaction_files_begin(struct odb_source *source); #endif /* OBJECT_FILE_H */
diff --git a/object-name.c b/object-name.c index 766c757..21dcdc4 100644 --- a/object-name.c +++ b/object-name.c
@@ -15,11 +15,9 @@ #include "refs.h" #include "remote.h" #include "dir.h" +#include "odb.h" #include "oid-array.h" -#include "oidtree.h" -#include "packfile.h" #include "pretty.h" -#include "object-file.h" #include "read-cache-ll.h" #include "repo-settings.h" #include "repository.h" @@ -49,30 +47,29 @@ struct disambiguate_state { unsigned candidate_ok:1; unsigned disambiguate_fn_used:1; unsigned ambiguous:1; - unsigned always_call_fn:1; }; -static void update_candidates(struct disambiguate_state *ds, const struct object_id *current) +static int update_disambiguate_state(const struct object_id *current, + struct object_info *oi UNUSED, + void *cb_data) { + struct disambiguate_state *ds = cb_data; + /* The hash algorithm of current has already been filtered */ - if (ds->always_call_fn) { - ds->ambiguous = ds->fn(ds->repo, current, ds->cb_data) ? 1 : 0; - return; - } if (!ds->candidate_exists) { /* this is the first candidate */ oidcpy(&ds->candidate, current); ds->candidate_exists = 1; - return; + return 0; } else if (oideq(&ds->candidate, current)) { /* the same as what we already have seen */ - return; + return 0; } if (!ds->fn) { /* cannot disambiguate between ds->candidate and current */ ds->ambiguous = 1; - return; + return ds->ambiguous; } if (!ds->candidate_checked) { @@ -85,7 +82,7 @@ static void update_candidates(struct disambiguate_state *ds, const struct object /* discard the candidate; we know it does not satisfy fn */ oidcpy(&ds->candidate, current); ds->candidate_checked = 0; - return; + return 0; } /* if we reach this point, we know ds->candidate satisfies fn */ @@ -96,128 +93,12 @@ static void update_candidates(struct disambiguate_state *ds, const struct object */ ds->candidate_ok = 0; ds->ambiguous = 1; + return ds->ambiguous; } /* otherwise, current can be discarded and candidate is still good */ -} -static int match_hash(unsigned, const unsigned char *, const unsigned char *); - -static enum cb_next match_prefix(const struct object_id *oid, void *arg) -{ - struct disambiguate_state *ds = arg; - /* no need to call match_hash, oidtree_each did prefix match */ - update_candidates(ds, oid); - return ds->ambiguous ? CB_BREAK : CB_CONTINUE; -} - -static void find_short_object_filename(struct disambiguate_state *ds) -{ - struct odb_source *source; - - for (source = ds->repo->objects->sources; source && !ds->ambiguous; source = source->next) - oidtree_each(odb_loose_cache(source, &ds->bin_pfx), - &ds->bin_pfx, ds->len, match_prefix, ds); -} - -static int match_hash(unsigned len, const unsigned char *a, const unsigned char *b) -{ - do { - if (*a != *b) - return 0; - a++; - b++; - len -= 2; - } while (len > 1); - if (len) - if ((*a ^ *b) & 0xf0) - return 0; - return 1; -} - -static void unique_in_midx(struct multi_pack_index *m, - struct disambiguate_state *ds) -{ - for (; m; m = m->base_midx) { - uint32_t num, i, first = 0; - const struct object_id *current = NULL; - int len = ds->len > ds->repo->hash_algo->hexsz ? - ds->repo->hash_algo->hexsz : ds->len; - - if (!m->num_objects) - continue; - - num = m->num_objects + m->num_objects_in_base; - - bsearch_one_midx(&ds->bin_pfx, m, &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 && !ds->ambiguous; i++) { - struct object_id oid; - current = nth_midxed_object_oid(&oid, m, i); - if (!match_hash(len, ds->bin_pfx.hash, current->hash)) - break; - update_candidates(ds, current); - } - } -} - -static void unique_in_pack(struct packed_git *p, - struct disambiguate_state *ds) -{ - uint32_t num, i, first = 0; - int len = ds->len > ds->repo->hash_algo->hexsz ? - ds->repo->hash_algo->hexsz : ds->len; - - if (p->multi_pack_index) - return; - - if (open_pack_index(p) || !p->num_objects) - return; - - num = p->num_objects; - bsearch_pack(&ds->bin_pfx, 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 && !ds->ambiguous; i++) { - struct object_id oid; - nth_packed_object_id(&oid, p, i); - if (!match_hash(len, ds->bin_pfx.hash, oid.hash)) - break; - update_candidates(ds, &oid); - } -} - -static void find_short_packed_object(struct disambiguate_state *ds) -{ - struct odb_source *source; - struct packed_git *p; - - /* Skip, unless oids from the storage hash algorithm are wanted */ - if (ds->bin_pfx.algo && (&hash_algos[ds->bin_pfx.algo] != ds->repo->hash_algo)) - return; - - odb_prepare_alternates(ds->repo->objects); - for (source = ds->repo->objects->sources; source && !ds->ambiguous; source = source->next) { - struct multi_pack_index *m = get_multi_pack_index(source); - if (m) - unique_in_midx(m, ds); - } - - repo_for_each_pack(ds->repo, p) { - if (ds->ambiguous) - break; - unique_in_pack(p, ds); - } + return 0; } static int finish_object_disambiguation(struct disambiguate_state *ds, @@ -348,41 +229,57 @@ int set_disambiguate_hint_config(const char *var, const char *value) return error("unknown hint type for '%s': %s", var, value); } +static int parse_oid_prefix(const char *name, int len, + const struct git_hash_algo *algo, + char *hex_out, + struct object_id *oid_out) +{ + for (int i = 0; i < len; i++) { + unsigned char c = name[i]; + unsigned char val; + if (c >= '0' && c <= '9') { + val = c - '0'; + } else if (c >= 'a' && c <= 'f') { + val = c - 'a' + 10; + } else if (c >= 'A' && c <='F') { + val = c - 'A' + 10; + c -= 'A' - 'a'; + } else { + return -1; + } + + if (hex_out) + hex_out[i] = c; + if (oid_out) { + if (!(i & 1)) + val <<= 4; + oid_out->hash[i >> 1] |= val; + } + } + + if (hex_out) + hex_out[len] = '\0'; + if (oid_out) + oid_out->algo = algo ? hash_algo_by_ptr(algo) : GIT_HASH_UNKNOWN; + + return 0; +} + static int init_object_disambiguation(struct repository *r, const char *name, int len, const struct git_hash_algo *algo, struct disambiguate_state *ds) { - int i; - if (len < MINIMUM_ABBREV || len > GIT_MAX_HEXSZ) return -1; memset(ds, 0, sizeof(*ds)); - for (i = 0; i < len ;i++) { - unsigned char c = name[i]; - unsigned char val; - if (c >= '0' && c <= '9') - val = c - '0'; - else if (c >= 'a' && c <= 'f') - val = c - 'a' + 10; - else if (c >= 'A' && c <='F') { - val = c - 'A' + 10; - c -= 'A' - 'a'; - } - else - return -1; - ds->hex_pfx[i] = c; - if (!(i & 1)) - val <<= 4; - ds->bin_pfx.hash[i >> 1] |= val; - } + if (parse_oid_prefix(name, len, algo, ds->hex_pfx, &ds->bin_pfx) < 0) + return -1; ds->len = len; - ds->hex_pfx[len] = '\0'; ds->repo = r; - ds->bin_pfx.algo = algo ? hash_algo_by_ptr(algo) : GIT_HASH_UNKNOWN; odb_prepare_alternates(r->objects); return 0; } @@ -449,7 +346,7 @@ static int show_ambiguous_object(const struct object_id *oid, void *data) } else if (type == OBJ_TAG) { struct tag *tag = lookup_tag(ds->repo, oid); - if (!parse_tag(tag) && tag->tag) { + if (!parse_tag(ds->repo, tag) && tag->tag) { /* * TRANSLATORS: This is a line of ambiguous * tag object output. E.g.: @@ -510,8 +407,8 @@ static int collect_ambiguous(const struct object_id *oid, void *data) return 0; } -static int repo_collect_ambiguous(struct repository *r UNUSED, - const struct object_id *oid, +static int repo_collect_ambiguous(const struct object_id *oid, + struct object_info *oi UNUSED, void *data) { return collect_ambiguous(oid, data); @@ -561,6 +458,7 @@ static enum get_oid_result get_short_oid(struct repository *r, struct object_id *oid, unsigned flags) { + struct odb_for_each_object_options opts = { 0 }; int status; struct disambiguate_state ds; int quietly = !!(flags & GET_OID_QUIETLY); @@ -588,8 +486,11 @@ static enum get_oid_result get_short_oid(struct repository *r, else ds.fn = default_disambiguate_hint; - find_short_object_filename(&ds); - find_short_packed_object(&ds); + opts.prefix = &ds.bin_pfx; + opts.prefix_hex_len = ds.len; + + odb_for_each_object_ext(r->objects, NULL, update_disambiguate_state, + &ds, &opts); status = finish_object_disambiguation(&ds, oid); /* @@ -599,8 +500,8 @@ static enum get_oid_result get_short_oid(struct repository *r, */ if (status == MISSING_OBJECT) { odb_reprepare(r->objects); - find_short_object_filename(&ds); - find_short_packed_object(&ds); + odb_for_each_object_ext(r->objects, NULL, update_disambiguate_state, + &ds, &opts); status = finish_object_disambiguation(&ds, oid); } @@ -648,169 +549,25 @@ int repo_for_each_abbrev(struct repository *r, const char *prefix, const struct git_hash_algo *algo, each_abbrev_fn fn, void *cb_data) { + struct object_id prefix_oid = { 0 }; + struct odb_for_each_object_options opts = { + .prefix = &prefix_oid, + .prefix_hex_len = strlen(prefix), + }; struct oid_array collect = OID_ARRAY_INIT; - struct disambiguate_state ds; int ret; - if (init_object_disambiguation(r, prefix, strlen(prefix), algo, &ds) < 0) + if (parse_oid_prefix(prefix, opts.prefix_hex_len, algo, NULL, &prefix_oid) < 0) return -1; - ds.always_call_fn = 1; - ds.fn = repo_collect_ambiguous; - ds.cb_data = &collect; - find_short_object_filename(&ds); - find_short_packed_object(&ds); + if (odb_for_each_object_ext(r->objects, NULL, repo_collect_ambiguous, &collect, &opts) < 0) + return -1; ret = oid_array_for_each_unique(&collect, fn, cb_data); oid_array_clear(&collect); return ret; } -/* - * Return the slot of the most-significant bit set in "val". There are various - * ways to do this quickly with fls() or __builtin_clzl(), but speed is - * probably not a big deal here. - */ -static unsigned msb(unsigned long val) -{ - unsigned r = 0; - while (val >>= 1) - r++; - return r; -} - -struct min_abbrev_data { - unsigned int init_len; - unsigned int cur_len; - char *hex; - struct repository *repo; - const struct object_id *oid; -}; - -static inline char get_hex_char_from_oid(const struct object_id *oid, - unsigned int pos) -{ - static const char hex[] = "0123456789abcdef"; - - if ((pos & 1) == 0) - return hex[oid->hash[pos >> 1] >> 4]; - else - return hex[oid->hash[pos >> 1] & 0xf]; -} - -static int extend_abbrev_len(const struct object_id *oid, - struct min_abbrev_data *mad) -{ - unsigned int i = mad->init_len; - while (mad->hex[i] && mad->hex[i] == get_hex_char_from_oid(oid, i)) - i++; - - if (mad->hex[i] && i >= mad->cur_len) - mad->cur_len = i + 1; - - return 0; -} - -static int repo_extend_abbrev_len(struct repository *r UNUSED, - const struct object_id *oid, - void *cb_data) -{ - return extend_abbrev_len(oid, cb_data); -} - -static void find_abbrev_len_for_midx(struct multi_pack_index *m, - struct min_abbrev_data *mad) -{ - for (; m; m = m->base_midx) { - int match = 0; - uint32_t num, first = 0; - struct object_id oid; - const struct object_id *mad_oid; - - if (!m->num_objects) - continue; - - num = m->num_objects + m->num_objects_in_base; - mad_oid = mad->oid; - match = bsearch_one_midx(mad_oid, m, &first); - - /* - * first is now the position in the packfile where we - * would insert mad->hash 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. - */ - mad->init_len = 0; - if (!match) { - if (nth_midxed_object_oid(&oid, m, first)) - extend_abbrev_len(&oid, mad); - } else if (first < num - 1) { - if (nth_midxed_object_oid(&oid, m, first + 1)) - extend_abbrev_len(&oid, mad); - } - if (first > 0) { - if (nth_midxed_object_oid(&oid, m, first - 1)) - extend_abbrev_len(&oid, mad); - } - mad->init_len = mad->cur_len; - } -} - -static void find_abbrev_len_for_pack(struct packed_git *p, - struct min_abbrev_data *mad) -{ - int match = 0; - uint32_t num, first = 0; - struct object_id oid; - const struct object_id *mad_oid; - - if (p->multi_pack_index) - return; - - if (open_pack_index(p) || !p->num_objects) - return; - - num = p->num_objects; - mad_oid = mad->oid; - match = bsearch_pack(mad_oid, p, &first); - - /* - * first is now the position in the packfile where we would insert - * mad->hash 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. - */ - mad->init_len = 0; - if (!match) { - if (!nth_packed_object_id(&oid, p, first)) - extend_abbrev_len(&oid, mad); - } else if (first < num - 1) { - if (!nth_packed_object_id(&oid, p, first + 1)) - extend_abbrev_len(&oid, mad); - } - if (first > 0) { - if (!nth_packed_object_id(&oid, p, first - 1)) - extend_abbrev_len(&oid, mad); - } - mad->init_len = mad->cur_len; -} - -static void find_abbrev_len_packed(struct min_abbrev_data *mad) -{ - struct packed_git *p; - - odb_prepare_alternates(mad->repo->objects); - for (struct odb_source *source = mad->repo->objects->sources; source; source = source->next) { - struct multi_pack_index *m = get_multi_pack_index(source); - if (m) - find_abbrev_len_for_midx(m, mad); - } - - repo_for_each_pack(mad->repo, p) - find_abbrev_len_for_pack(p, mad); -} - void strbuf_repo_add_unique_abbrev(struct strbuf *sb, struct repository *repo, const struct object_id *oid, int abbrev_len) { @@ -827,61 +584,19 @@ void strbuf_add_unique_abbrev(struct strbuf *sb, const struct object_id *oid, } int repo_find_unique_abbrev_r(struct repository *r, char *hex, - const struct object_id *oid, int len) + const struct object_id *oid, int min_len) { const struct git_hash_algo *algo = oid->algo ? &hash_algos[oid->algo] : r->hash_algo; - struct disambiguate_state ds; - struct min_abbrev_data mad; - struct object_id oid_ret; - const unsigned hexsz = algo->hexsz; + unsigned len; - if (len < 0) { - unsigned long count = repo_approximate_object_count(r); - /* - * Add one because the MSB only tells us the highest bit set, - * not including the value of all the _other_ bits (so "15" - * is only one off of 2^4, but the MSB is the 3rd bit. - */ - len = msb(count) + 1; - /* - * We now know we have on the order of 2^len objects, which - * expects a collision at 2^(len/2). But we also care about hex - * chars, not bits, and there are 4 bits per hex. So all - * together we need to divide by 2 and round up. - */ - len = DIV_ROUND_UP(len, 2); - /* - * For very small repos, we stick with our regular fallback. - */ - if (len < FALLBACK_DEFAULT_ABBREV) - len = FALLBACK_DEFAULT_ABBREV; - } + if (odb_find_abbrev_len(r->objects, oid, min_len, &len) < 0) + len = algo->hexsz; oid_to_hex_r(hex, oid); - if (len >= hexsz || !len) - return hexsz; + hex[len] = 0; - mad.repo = r; - mad.init_len = len; - mad.cur_len = len; - mad.hex = hex; - mad.oid = oid; - - find_abbrev_len_packed(&mad); - - if (init_object_disambiguation(r, hex, mad.cur_len, algo, &ds) < 0) - return -1; - - ds.fn = repo_extend_abbrev_len; - ds.always_call_fn = 1; - ds.cb_data = (void *)&mad; - - find_short_object_filename(&ds); - (void)finish_object_disambiguation(&ds, &oid_ret); - - hex[mad.cur_len] = 0; - return mad.cur_len; + return len; } const char *repo_find_unique_abbrev(struct repository *r, @@ -1281,7 +996,7 @@ static int peel_onion(struct repository *r, const char *name, int len, commit_list_insert((struct commit *)o, &list); ret = get_oid_oneline(r, prefix, oid, list); - free_commit_list(list); + commit_list_free(list); free(prefix); return ret; } @@ -1446,18 +1161,16 @@ struct handle_one_ref_cb { struct commit_list **list; }; -static int handle_one_ref(const char *path, const char *referent UNUSED, const struct object_id *oid, - int flag UNUSED, - void *cb_data) +static int handle_one_ref(const struct reference *ref, void *cb_data) { struct handle_one_ref_cb *cb = cb_data; struct commit_list **list = cb->list; - struct object *object = parse_object(cb->repo, oid); + struct object *object = parse_object(cb->repo, ref->oid); if (!object) return 0; if (object->type == OBJ_TAG) { - object = deref_tag(cb->repo, object, path, - strlen(path)); + object = deref_tag(cb->repo, object, ref->name, + strlen(ref->name)); if (!object) return 0; } @@ -1625,7 +1338,7 @@ int repo_get_oid_mb(struct repository *r, if (!two) return -1; if (repo_get_merge_bases(r, one, two, &mbs) < 0) { - free_commit_list(mbs); + commit_list_free(mbs); return -1; } if (!mbs || mbs->next) @@ -1634,7 +1347,7 @@ int repo_get_oid_mb(struct repository *r, st = 0; oidcpy(oid, &mbs->item->object.oid); } - free_commit_list(mbs); + commit_list_free(mbs); return st; } @@ -1662,7 +1375,8 @@ static int interpret_empty_at(const char *name, int namelen, int len, struct str static int reinterpret(struct repository *r, const char *name, int namelen, int len, - struct strbuf *buf, unsigned allowed) + struct strbuf *buf, + enum interpret_branch_kind allowed) { /* we have extra data, which might need further processing */ struct strbuf tmp = STRBUF_INIT; @@ -1694,7 +1408,8 @@ static void set_shortened_ref(struct repository *r, struct strbuf *buf, const ch free(s); } -static int branch_interpret_allowed(const char *refname, unsigned allowed) +static int branch_interpret_allowed(const char *refname, + enum interpret_branch_kind allowed) { if (!allowed) return 1; @@ -1758,7 +1473,7 @@ int repo_interpret_branch_name(struct repository *r, struct strbuf *buf, const struct interpret_branch_name_options *options) { - char *at; + const char *at; const char *start; int len; @@ -2054,7 +1769,7 @@ static enum get_oid_result get_oid_with_context_1(struct repository *repo, refs_head_ref(get_main_ref_store(repo), handle_one_ref, &cb); ret = get_oid_oneline(repo, name + 2, oid, list); - free_commit_list(list); + commit_list_free(list); return ret; } if (namelen < 3 ||
diff --git a/object-name.h b/object-name.h index cda4934..167a915 100644 --- a/object-name.h +++ b/object-name.h
@@ -101,9 +101,12 @@ int set_disambiguate_hint_config(const char *var, const char *value); * If the input was ok but there are not N branch switches in the * reflog, it returns 0. */ -#define INTERPRET_BRANCH_LOCAL (1<<0) -#define INTERPRET_BRANCH_REMOTE (1<<1) -#define INTERPRET_BRANCH_HEAD (1<<2) +enum interpret_branch_kind { + INTERPRET_BRANCH_LOCAL = (1 << 0), + INTERPRET_BRANCH_REMOTE = (1 << 1), + INTERPRET_BRANCH_HEAD = (1 << 2), +}; + struct interpret_branch_name_options { /* * If "allowed" is non-zero, it is a treated as a bitfield of allowable @@ -111,7 +114,7 @@ struct interpret_branch_name_options { * ("refs/remotes/"), or "HEAD". If no "allowed" bits are set, any expansion is * allowed, even ones to refs outside of those namespaces. */ - unsigned allowed; + enum interpret_branch_kind allowed; /* * If ^{upstream} or ^{push} (or equivalent) is requested, and the
diff --git a/object.c b/object.c index 986114a..465902e 100644 --- a/object.c +++ b/object.c
@@ -6,6 +6,7 @@ #include "object.h" #include "replace-object.h" #include "object-file.h" +#include "odb/streaming.h" #include "blob.h" #include "statinfo.h" #include "tree.h" @@ -207,9 +208,11 @@ struct object *lookup_object_by_type(struct repository *r, } } -enum peel_status peel_object(struct repository *r, - const struct object_id *name, - struct object_id *oid) +enum peel_status peel_object_ext(struct repository *r, + const struct object_id *name, + struct object_id *oid, + unsigned flags, + enum object_type *typep) { struct object *o = lookup_unknown_object(r, name); @@ -219,17 +222,42 @@ enum peel_status peel_object(struct repository *r, return PEEL_INVALID; } - if (o->type != OBJ_TAG) + if (o->type != OBJ_TAG) { + *typep = o->type; return PEEL_NON_TAG; + } - o = deref_tag_noverify(r, o); + while (o && o->type == OBJ_TAG) { + o = parse_object(r, &o->oid); + if (o && o->type == OBJ_TAG && ((struct tag *)o)->tagged) { + o = ((struct tag *)o)->tagged; + + if (flags & PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE) { + int type = odb_read_object_info(r->objects, &o->oid, NULL); + if (type < 0 || !object_as_type(o, type, 0)) + return PEEL_INVALID; + } + } else { + o = NULL; + } + } if (!o) return PEEL_INVALID; oidcpy(oid, &o->oid); + *typep = o->type; return PEEL_PEELED; } +enum peel_status peel_object(struct repository *r, + const struct object_id *name, + struct object_id *oid, + unsigned flags) +{ + enum object_type dummy; + return peel_object_ext(r, name, oid, flags, &dummy); +} + struct object *parse_object_buffer(struct repository *r, const struct object_id *oid, enum object_type type, unsigned long size, void *buffer, int *eaten_p) { struct object *obj; @@ -314,11 +342,23 @@ struct object *parse_object_with_flags(struct repository *r, return &commit->object; } - if ((!obj || obj->type == OBJ_BLOB) && + if ((!obj || obj->type == OBJ_NONE || obj->type == OBJ_BLOB) && odb_read_object_info(r->objects, oid, NULL) == OBJ_BLOB) { - if (!skip_hash && stream_object_signature(r, repl) < 0) { - error(_("hash mismatch %s"), oid_to_hex(oid)); - return NULL; + if (!skip_hash) { + struct odb_read_stream *stream = odb_read_stream_open(r->objects, oid, NULL); + + if (!stream) { + error(_("unable to open object stream for %s"), oid_to_hex(oid)); + return NULL; + } + + if (stream_object_signature(r, stream, repl) < 0) { + error(_("hash mismatch %s"), oid_to_hex(oid)); + odb_read_stream_close(stream); + return NULL; + } + + odb_read_stream_close(stream); } parse_blob_buffer(lookup_blob(r, oid)); return lookup_object(r, oid); @@ -330,7 +370,7 @@ struct object *parse_object_with_flags(struct repository *r, * have the on-disk object with the correct type. */ if (skip_hash && discard_tree && - (!obj || obj->type == OBJ_TREE) && + (!obj || obj->type == OBJ_NONE || obj->type == OBJ_TREE) && odb_read_object_info(r->objects, oid, NULL) == OBJ_TREE) { return &lookup_tree(r, oid)->object; }
diff --git a/object.h b/object.h index fa504a0..d814647 100644 --- a/object.h +++ b/object.h
@@ -64,7 +64,7 @@ void object_array_init(struct object_array *array); /* * object flag allocation: - * revision.h: 0---------10 15 23------27 + * revision.h: 0---------10 15 23--------28 * fetch-pack.c: 01 67 * negotiator/default.c: 2--5 * walker.c: 0-2 @@ -80,14 +80,13 @@ void object_array_init(struct object_array *array); * list-objects-filter.c: 21 * bloom.c: 2122 * builtin/fsck.c: 0--3 - * builtin/gc.c: 0 * builtin/index-pack.c: 2021 * reflog.c: 10--12 * builtin/show-branch.c: 0-------------------------------------------26 * builtin/unpack-objects.c: 2021 * pack-bitmap.h: 2122 */ -#define FLAG_BITS 28 +#define FLAG_BITS 29 #define TYPE_BITS 3 @@ -288,6 +287,17 @@ enum peel_status { PEEL_BROKEN = -4 }; +enum peel_object_flags { + /* + * Always verify the object type of the tagged object, even in the case + * where the looked-up object already has an object type. This can be + * useful when the tagged object type may be invalid. One such case is + * when looking up objects via tags, where we blindly trust the object + * type declared by the tag. + */ + PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE = (1 << 0), +}; + /* * Peel the named object; i.e., if the object is a tag, resolve the * tag recursively until a non-tag is found. If successful, store the @@ -296,7 +306,14 @@ enum peel_status { * and leave oid unchanged. */ enum peel_status peel_object(struct repository *r, - const struct object_id *name, struct object_id *oid); + const struct object_id *name, + struct object_id *oid, + unsigned flags); +enum peel_status peel_object_ext(struct repository *r, + const struct object_id *name, + struct object_id *oid, + unsigned flags, + enum object_type *typep); struct object_list *object_list_insert(struct object *item, struct object_list **list_p);
diff --git a/odb.c b/odb.c index 00a6e71..3f94a53 100644 --- a/odb.c +++ b/odb.c
@@ -9,8 +9,10 @@ #include "khash.h" #include "lockfile.h" #include "loose.h" +#include "midx.h" #include "object-file-convert.h" #include "object-file.h" +#include "object-name.h" #include "odb.h" #include "packfile.h" #include "path.h" @@ -22,6 +24,7 @@ #include "strbuf.h" #include "strvec.h" #include "submodule.h" +#include "tmp-objdir.h" #include "trace2.h" #include "write-or-die.h" @@ -86,18 +89,20 @@ int odb_mkstemp(struct object_database *odb, /* * Return non-zero iff the path is usable as an alternate object database. */ -static int alt_odb_usable(struct object_database *o, - struct strbuf *path, - const char *normalized_objdir, khiter_t *pos) +static bool odb_is_source_usable(struct object_database *o, const char *path) { int r; + struct strbuf normalized_objdir = STRBUF_INIT; + bool usable = false; + + strbuf_realpath(&normalized_objdir, o->sources->path, 1); /* Detect cases where alternate disappeared */ - if (!is_directory(path->buf)) { + if (!is_directory(path)) { error(_("object directory %s does not exist; " "check .git/objects/info/alternates"), - path->buf); - return 0; + path); + goto out; } /* @@ -113,202 +118,133 @@ static int alt_odb_usable(struct object_database *o, assert(r == 1); /* never used */ kh_value(o->source_by_path, p) = o->sources; } - if (fspatheq(path->buf, normalized_objdir)) - return 0; - *pos = kh_put_odb_path_map(o->source_by_path, path->buf, &r); - /* r: 0 = exists, 1 = never used, 2 = deleted */ - return r == 0 ? 0 : 1; + + if (fspatheq(path, normalized_objdir.buf)) + goto out; + + if (kh_get_odb_path_map(o->source_by_path, path) < kh_end(o->source_by_path)) + goto out; + + usable = true; + +out: + strbuf_release(&normalized_objdir); + return usable; } -/* - * Prepare alternate object database registry. - * - * The variable alt_odb_list points at the list of struct - * odb_source. The elements on this list come from - * non-empty elements from colon separated ALTERNATE_DB_ENVIRONMENT - * environment variable, and $GIT_OBJECT_DIRECTORY/info/alternates, - * whose contents is similar to that environment variable but can be - * LF separated. Its base points at a statically allocated buffer that - * contains "/the/directory/corresponding/to/.git/objects/...", while - * its name points just after the slash at the end of ".git/objects/" - * in the example above, and has enough space to hold all hex characters - * of the object ID, an extra slash for the first level indirection, and - * the terminating NUL. - */ -static void read_info_alternates(struct object_database *odb, - const char *relative_base, - int depth); +void parse_alternates(const char *string, + int sep, + const char *relative_base, + struct strvec *out) +{ + struct strbuf pathbuf = STRBUF_INIT; + struct strbuf buf = STRBUF_INIT; -static struct odb_source *link_alt_odb_entry(struct object_database *odb, - const char *dir, - const char *relative_base, - int depth) + if (!string || !*string) + return; + + while (*string) { + const char *end; + + strbuf_reset(&buf); + strbuf_reset(&pathbuf); + + if (*string == '#') { + /* comment; consume up to next separator */ + end = strchrnul(string, sep); + } else if (*string == '"' && !unquote_c_style(&buf, string, &end)) { + /* + * quoted path; unquote_c_style has copied the + * data for us and set "end". Broken quoting (e.g., + * an entry that doesn't end with a quote) falls + * back to the unquoted case below. + */ + } else { + /* normal, unquoted path */ + end = strchrnul(string, sep); + strbuf_add(&buf, string, end - string); + } + + if (*end) + end++; + string = end; + + if (!buf.len) + continue; + + if (!is_absolute_path(buf.buf) && relative_base) { + strbuf_realpath(&pathbuf, relative_base, 1); + strbuf_addch(&pathbuf, '/'); + } + strbuf_addbuf(&pathbuf, &buf); + + strbuf_reset(&buf); + if (!strbuf_realpath(&buf, pathbuf.buf, 0)) { + error(_("unable to normalize alternate object path: %s"), + pathbuf.buf); + continue; + } + + /* + * The trailing slash after the directory name is given by + * this function at the end. Remove duplicates. + */ + while (buf.len && buf.buf[buf.len - 1] == '/') + strbuf_setlen(&buf, buf.len - 1); + + strvec_push(out, buf.buf); + } + + strbuf_release(&pathbuf); + strbuf_release(&buf); +} + +static struct odb_source *odb_add_alternate_recursively(struct object_database *odb, + const char *source, + int depth) { struct odb_source *alternate = NULL; - struct strbuf pathbuf = STRBUF_INIT; - struct strbuf tmp = STRBUF_INIT; + struct strvec sources = STRVEC_INIT; khiter_t pos; + int ret; - if (!is_absolute_path(dir) && relative_base) { - strbuf_realpath(&pathbuf, relative_base, 1); - strbuf_addch(&pathbuf, '/'); - } - strbuf_addstr(&pathbuf, dir); - - if (!strbuf_realpath(&tmp, pathbuf.buf, 0)) { - error(_("unable to normalize alternate object path: %s"), - pathbuf.buf); - goto error; - } - strbuf_swap(&pathbuf, &tmp); - - /* - * The trailing slash after the directory name is given by - * this function at the end. Remove duplicates. - */ - while (pathbuf.len && pathbuf.buf[pathbuf.len - 1] == '/') - strbuf_setlen(&pathbuf, pathbuf.len - 1); - - strbuf_reset(&tmp); - strbuf_realpath(&tmp, odb->sources->path, 1); - - if (!alt_odb_usable(odb, &pathbuf, tmp.buf, &pos)) + if (!odb_is_source_usable(odb, source)) goto error; - CALLOC_ARRAY(alternate, 1); - alternate->odb = odb; - alternate->local = false; - /* pathbuf.buf is already in r->objects->source_by_path */ - alternate->path = strbuf_detach(&pathbuf, NULL); + alternate = odb_source_new(odb, source, false); /* add the alternate entry */ *odb->sources_tail = alternate; odb->sources_tail = &(alternate->next); - alternate->next = NULL; - assert(odb->source_by_path); + + pos = kh_put_odb_path_map(odb->source_by_path, alternate->path, &ret); + if (!ret) + BUG("source must not yet exist"); kh_value(odb->source_by_path, pos) = alternate; /* recursively add alternates */ - read_info_alternates(odb, alternate->path, depth + 1); + odb_source_read_alternates(alternate, &sources); + if (sources.nr && depth + 1 > 5) { + error(_("%s: ignoring alternate object stores, nesting too deep"), + source); + } else { + for (size_t i = 0; i < sources.nr; i++) + odb_add_alternate_recursively(odb, sources.v[i], depth + 1); + } error: - strbuf_release(&tmp); - strbuf_release(&pathbuf); + strvec_clear(&sources); return alternate; } -static const char *parse_alt_odb_entry(const char *string, - int sep, - struct strbuf *out) -{ - const char *end; - - strbuf_reset(out); - - if (*string == '#') { - /* comment; consume up to next separator */ - end = strchrnul(string, sep); - } else if (*string == '"' && !unquote_c_style(out, string, &end)) { - /* - * quoted path; unquote_c_style has copied the - * data for us and set "end". Broken quoting (e.g., - * an entry that doesn't end with a quote) falls - * back to the unquoted case below. - */ - } else { - /* normal, unquoted path */ - end = strchrnul(string, sep); - strbuf_add(out, string, end - string); - } - - if (*end) - end++; - return end; -} - -static void link_alt_odb_entries(struct object_database *odb, const char *alt, - int sep, const char *relative_base, int depth) -{ - struct strbuf dir = STRBUF_INIT; - - if (!alt || !*alt) - return; - - if (depth > 5) { - error(_("%s: ignoring alternate object stores, nesting too deep"), - relative_base); - return; - } - - while (*alt) { - alt = parse_alt_odb_entry(alt, sep, &dir); - if (!dir.len) - continue; - link_alt_odb_entry(odb, dir.buf, relative_base, depth); - } - strbuf_release(&dir); -} - -static void read_info_alternates(struct object_database *odb, - const char *relative_base, - int depth) -{ - char *path; - struct strbuf buf = STRBUF_INIT; - - path = xstrfmt("%s/info/alternates", relative_base); - if (strbuf_read_file(&buf, path, 1024) < 0) { - warn_on_fopen_errors(path); - free(path); - return; - } - - link_alt_odb_entries(odb, buf.buf, '\n', relative_base, depth); - strbuf_release(&buf); - free(path); -} - void odb_add_to_alternates_file(struct object_database *odb, const char *dir) { - struct lock_file lock = LOCK_INIT; - char *alts = repo_git_path(odb->repo, "objects/info/alternates"); - FILE *in, *out; - int found = 0; - - hold_lock_file_for_update(&lock, alts, LOCK_DIE_ON_ERROR); - out = fdopen_lock_file(&lock, "w"); - if (!out) - die_errno(_("unable to fdopen alternates lockfile")); - - in = fopen(alts, "r"); - if (in) { - struct strbuf line = STRBUF_INIT; - - while (strbuf_getline(&line, in) != EOF) { - if (!strcmp(dir, line.buf)) { - found = 1; - break; - } - fprintf_or_die(out, "%s\n", line.buf); - } - - strbuf_release(&line); - fclose(in); - } - else if (errno != ENOENT) - die_errno(_("unable to read alternates file")); - - if (found) { - rollback_lock_file(&lock); - } else { - fprintf_or_die(out, "%s\n", dir); - if (commit_lock_file(&lock)) - die_errno(_("unable to move new alternates file into place")); - if (odb->loaded_alternates) - link_alt_odb_entries(odb, dir, '\n', NULL, 0); - } - free(alts); + int ret = odb_source_write_alternate(odb->sources, dir); + if (ret < 0) + die(NULL); + if (odb->loaded_alternates) + odb_add_alternate_recursively(odb, dir, 0); } struct odb_source *odb_add_to_alternates_memory(struct object_database *odb, @@ -319,7 +255,7 @@ struct odb_source *odb_add_to_alternates_memory(struct object_database *odb, * overwritten when they are. */ odb_prepare_alternates(odb); - return link_alt_odb_entry(odb, dir, NULL, 0); + return odb_add_alternate_recursively(odb, dir, 0); } struct odb_source *odb_set_temporary_primary_source(struct object_database *odb, @@ -337,29 +273,19 @@ struct odb_source *odb_set_temporary_primary_source(struct object_database *odb, * Make a new primary odb and link the old primary ODB in as an * alternate */ - source = xcalloc(1, sizeof(*source)); - source->odb = odb; - source->path = xstrdup(dir); + source = odb_source_new(odb, dir, false); /* * Disable ref updates while a temporary odb is active, since * the objects in the database may roll back. */ - source->disable_ref_updates = 1; + odb->repo->disable_ref_updates = true; source->will_destroy = will_destroy; source->next = odb->sources; odb->sources = source; return source->next; } -static void free_object_directory(struct odb_source *source) -{ - free(source->path); - odb_clear_loose_cache(source); - loose_object_map_clear(&source->loose_map); - free(source); -} - void odb_restore_primary_source(struct object_database *odb, struct odb_source *restore_source, const char *old_path) @@ -373,8 +299,9 @@ void odb_restore_primary_source(struct object_database *odb, if (cur_source->next != restore_source) BUG("we expect the old primary object store to be the first alternate"); + odb->repo->disable_ref_updates = false; odb->sources = restore_source; - free_object_directory(cur_source); + odb_source_free(cur_source); } char *compute_alternate_path(const char *path, struct strbuf *err) @@ -592,13 +519,19 @@ int odb_for_each_alternate(struct object_database *odb, void odb_prepare_alternates(struct object_database *odb) { + struct strvec sources = STRVEC_INIT; + if (odb->loaded_alternates) return; - link_alt_odb_entries(odb, odb->alternate_db, PATH_SEP, NULL, 0); + parse_alternates(odb->alternate_db, PATH_SEP, NULL, &sources); + odb_source_read_alternates(odb->sources, &sources); + for (size_t i = 0; i < sources.nr; i++) + odb_add_alternate_recursively(odb, sources.v[i], 0); - read_info_alternates(odb, odb->sources->path, 0); odb->loaded_alternates = 1; + + strvec_clear(&sources); } int odb_has_alternates(struct object_database *odb) @@ -651,52 +584,55 @@ static int do_oid_object_info_extended(struct object_database *odb, const struct object_id *oid, struct object_info *oi, unsigned flags) { - static struct object_info blank_oi = OBJECT_INFO_INIT; const struct cached_object *co; - struct pack_entry e; - int rtype; const struct object_id *real = oid; int already_retried = 0; - if (flags & OBJECT_INFO_LOOKUP_REPLACE) real = lookup_replace_object(odb->repo, oid); if (is_null_oid(real)) return -1; - if (!oi) - oi = &blank_oi; - co = find_cached_object(odb, real); if (co) { - if (oi->typep) - *(oi->typep) = co->type; - if (oi->sizep) - *(oi->sizep) = co->size; - if (oi->disk_sizep) - *(oi->disk_sizep) = 0; - if (oi->delta_base_oid) - oidclr(oi->delta_base_oid, odb->repo->hash_algo); - if (oi->contentp) - *oi->contentp = xmemdupz(co->buf, co->size); - oi->whence = OI_CACHED; + if (oi) { + if (oi->typep) + *(oi->typep) = co->type; + if (oi->sizep) + *(oi->sizep) = co->size; + if (oi->disk_sizep) + *(oi->disk_sizep) = 0; + if (oi->delta_base_oid) + oidclr(oi->delta_base_oid, odb->repo->hash_algo); + if (oi->contentp) + *oi->contentp = xmemdupz(co->buf, co->size); + if (oi->mtimep) + *oi->mtimep = 0; + oi->whence = OI_CACHED; + } return 0; } + odb_prepare_alternates(odb); + while (1) { - if (find_pack_entry(odb->repo, real, &e)) - break; + struct odb_source *source; - /* Most likely it's a loose object. */ - if (!loose_object_info(odb->repo, real, oi, flags)) - return 0; + for (source = odb->sources; source; source = source->next) + if (!odb_source_read_object_info(source, real, oi, flags)) + return 0; - /* Not a loose object; someone else may have just packed it. */ + /* + * When the object hasn't been found we try a second read and + * tell the sources so. This may cause them to invalidate + * caches or reload on-disk state. + */ if (!(flags & OBJECT_INFO_QUICK)) { - odb_reprepare(odb->repo->objects); - if (find_pack_entry(odb->repo, real, &e)) - break; + for (source = odb->sources; source; source = source->next) + if (!odb_source_read_object_info(source, real, oi, + flags | OBJECT_INFO_SECOND_READ)) + return 0; } /* @@ -729,25 +665,6 @@ static int do_oid_object_info_extended(struct object_database *odb, } return -1; } - - if (oi == &blank_oi) - /* - * We know that the caller doesn't actually need the - * information below, so return early. - */ - return 0; - rtype = packed_object_info(odb->repo, e.p, e.offset, oi); - if (rtype < 0) { - mark_bad_packed_object(e.p, real); - return do_oid_object_info_extended(odb, real, oi, 0); - } else if (oi->whence == OI_PACKED) { - oi->u.packed.offset = e.offset; - oi->u.packed.pack = e.p; - oi->u.packed.is_delta = (rtype == OBJ_REF_DELTA || - rtype == OBJ_OFS_DELTA); - } - - return 0; } static int oid_object_info_convert(struct repository *r, @@ -833,7 +750,7 @@ static int oid_object_info_convert(struct repository *r, int odb_read_object_info_extended(struct object_database *odb, const struct object_id *oid, struct object_info *oi, - unsigned flags) + enum object_info_flags flags) { int ret; @@ -955,7 +872,7 @@ void *odb_read_object_peeled(struct object_database *odb, } int odb_has_object(struct object_database *odb, const struct object_id *oid, - unsigned flags) + enum has_object_flags flags) { unsigned object_info_flags = 0; @@ -969,6 +886,157 @@ int odb_has_object(struct object_database *odb, const struct object_id *oid, return odb_read_object_info_extended(odb, oid, NULL, object_info_flags) >= 0; } +int odb_freshen_object(struct object_database *odb, + const struct object_id *oid) +{ + struct odb_source *source; + odb_prepare_alternates(odb); + for (source = odb->sources; source; source = source->next) + if (odb_source_freshen_object(source, oid)) + return 1; + return 0; +} + +int odb_for_each_object_ext(struct object_database *odb, + const struct object_info *request, + odb_for_each_object_cb cb, + void *cb_data, + const struct odb_for_each_object_options *opts) +{ + int ret; + + odb_prepare_alternates(odb); + for (struct odb_source *source = odb->sources; source; source = source->next) { + if (opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY && !source->local) + continue; + + ret = odb_source_for_each_object(source, request, cb, cb_data, opts); + if (ret) + return ret; + } + + return 0; +} + +int odb_for_each_object(struct object_database *odb, + const struct object_info *request, + odb_for_each_object_cb cb, + void *cb_data, + unsigned flags) +{ + struct odb_for_each_object_options opts = { + .flags = flags, + }; + return odb_for_each_object_ext(odb, request, cb, cb_data, &opts); +} + +int odb_count_objects(struct object_database *odb, + enum odb_count_objects_flags flags, + unsigned long *out) +{ + struct odb_source *source; + unsigned long count = 0; + int ret; + + if (odb->object_count_valid && odb->object_count_flags == flags) { + *out = odb->object_count; + return 0; + } + + odb_prepare_alternates(odb); + for (source = odb->sources; source; source = source->next) { + unsigned long c; + + ret = odb_source_count_objects(source, flags, &c); + if (ret < 0) + goto out; + + count += c; + } + + odb->object_count = count; + odb->object_count_valid = 1; + odb->object_count_flags = flags; + + *out = count; + ret = 0; + +out: + return ret; +} + +/* + * Return the slot of the most-significant bit set in "val". There are various + * ways to do this quickly with fls() or __builtin_clzl(), but speed is + * probably not a big deal here. + */ +static unsigned msb(unsigned long val) +{ + unsigned r = 0; + while (val >>= 1) + r++; + return r; +} + +int odb_find_abbrev_len(struct object_database *odb, + const struct object_id *oid, + int min_length, + unsigned *out) +{ + const struct git_hash_algo *algo = + oid->algo ? &hash_algos[oid->algo] : odb->repo->hash_algo; + const unsigned hexsz = algo->hexsz; + unsigned len; + int ret; + + if (min_length < 0) { + unsigned long count; + + if (odb_count_objects(odb, ODB_COUNT_OBJECTS_APPROXIMATE, &count) < 0) + count = 0; + + /* + * Add one because the MSB only tells us the highest bit set, + * not including the value of all the _other_ bits (so "15" + * is only one off of 2^4, but the MSB is the 3rd bit. + */ + len = msb(count) + 1; + /* + * We now know we have on the order of 2^len objects, which + * expects a collision at 2^(len/2). But we also care about hex + * chars, not bits, and there are 4 bits per hex. So all + * together we need to divide by 2 and round up. + */ + len = DIV_ROUND_UP(len, 2); + /* + * For very small repos, we stick with our regular fallback. + */ + if (len < FALLBACK_DEFAULT_ABBREV) + len = FALLBACK_DEFAULT_ABBREV; + } else { + len = min_length; + } + + if (len >= hexsz || !len) { + *out = hexsz; + ret = 0; + goto out; + } + + odb_prepare_alternates(odb); + for (struct odb_source *source = odb->sources; source; source = source->next) { + ret = odb_source_find_abbrev_len(source, oid, len, &len); + if (ret) + goto out; + } + + ret = 0; + *out = len; + +out: + return ret; +} + void odb_assert_oid_type(struct object_database *odb, const struct object_id *oid, enum object_type expect) { @@ -987,58 +1055,81 @@ int odb_write_object_ext(struct object_database *odb, struct object_id *compat_oid, unsigned flags) { - return write_object_file(odb->sources, buf, len, type, oid, compat_oid, flags); + return odb_source_write_object(odb->sources, buf, len, type, + oid, compat_oid, flags); } -struct object_database *odb_new(struct repository *repo) +int odb_write_object_stream(struct object_database *odb, + struct odb_write_stream *stream, size_t len, + struct object_id *oid) +{ + return odb_source_write_object_stream(odb->sources, stream, len, oid); +} + +struct object_database *odb_new(struct repository *repo, + const char *primary_source, + const char *secondary_sources) { struct object_database *o = xmalloc(sizeof(*o)); + char *to_free = NULL; memset(o, 0, sizeof(*o)); o->repo = repo; - o->packfiles = packfile_store_new(o); pthread_mutex_init(&o->replace_mutex, NULL); string_list_init_dup(&o->submodule_source_paths); + + if (!primary_source) + primary_source = to_free = xstrfmt("%s/objects", repo->commondir); + o->sources = odb_source_new(o, primary_source, true); + o->sources_tail = &o->sources->next; + o->alternate_db = xstrdup_or_null(secondary_sources); + + free(to_free); + return o; } -static void free_object_directories(struct object_database *o) +void odb_close(struct object_database *o) +{ + struct odb_source *source; + for (source = o->sources; source; source = source->next) + odb_source_close(source); + close_commit_graph(o); +} + +static void odb_free_sources(struct object_database *o) { while (o->sources) { struct odb_source *next; next = o->sources->next; - free_object_directory(o->sources); + odb_source_free(o->sources); o->sources = next; } kh_destroy_odb_path_map(o->source_by_path); o->source_by_path = NULL; } -void odb_clear(struct object_database *o) +void odb_free(struct object_database *o) { - FREE_AND_NULL(o->alternate_db); + if (!o) + return; + + free(o->alternate_db); oidmap_clear(&o->replace_map, 1); pthread_mutex_destroy(&o->replace_mutex); - free_commit_graph(o->commit_graph); - o->commit_graph = NULL; - o->commit_graph_attempted = 0; - - free_object_directories(o); - o->sources_tail = NULL; - o->loaded_alternates = 0; + odb_close(o); + odb_free_sources(o); for (size_t i = 0; i < o->cached_object_nr; i++) free((char *) o->cached_objects[i].value.buf); - FREE_AND_NULL(o->cached_objects); - - close_object_store(o); - packfile_store_free(o->packfiles); - o->packfiles = NULL; + free(o->cached_objects); string_list_clear(&o->submodule_source_paths, 0); + + free(o); } void odb_reprepare(struct object_database *o) @@ -1057,21 +1148,34 @@ void odb_reprepare(struct object_database *o) odb_prepare_alternates(o); for (source = o->sources; source; source = source->next) - odb_clear_loose_cache(source); + odb_source_reprepare(source); - o->approximate_object_count_valid = 0; - - packfile_store_reprepare(o->packfiles); + o->object_count_valid = 0; obj_read_unlock(); } struct odb_transaction *odb_transaction_begin(struct object_database *odb) { - return object_file_transaction_begin(odb->sources); + if (odb->transaction) + return NULL; + + odb->transaction = odb_transaction_files_begin(odb->sources); + + return odb->transaction; } void odb_transaction_commit(struct odb_transaction *transaction) { - object_file_transaction_commit(transaction); + if (!transaction) + return; + + /* + * Ensure the transaction ending matches the pending transaction. + */ + ASSERT(transaction == transaction->source->odb->transaction); + + transaction->commit(transaction); + transaction->source->odb->transaction = NULL; + free(transaction); }
diff --git a/odb.h b/odb.h index e6602dd..984bafc 100644 --- a/odb.h +++ b/odb.h
@@ -11,10 +11,19 @@ struct oidmap; struct oidtree; struct strbuf; +struct strvec; struct repository; struct multi_pack_index; /* + * Set this to 0 to prevent odb_read_object_info_extended() from fetching missing + * blobs. This has a difference only if extensions.partialClone is set. + * + * Its default value is 1. + */ +extern int fetch_if_missing; + +/* * Compute the exact path an alternate is at and returns it. In case of * error NULL is returned and the human readable error is added to `err` * `path` may be relative and should point to $GIT_DIR. @@ -22,77 +31,27 @@ struct multi_pack_index; */ char *compute_alternate_path(const char *path, struct strbuf *err); -/* - * The source is the part of the object database that stores the actual - * objects. It thus encapsulates the logic to read and write the specific - * on-disk format. An object database can have multiple sources: - * - * - The primary source, which is typically located in "$GIT_DIR/objects". - * This is where new objects are usually written to. - * - * - Alternate sources, which are configured via "objects/info/alternates" or - * via the GIT_ALTERNATE_OBJECT_DIRECTORIES environment variable. These - * alternate sources are only used to read objects. - */ -struct odb_source { - struct odb_source *next; - - /* Object database that owns this object source. */ - struct object_database *odb; - - /* - * 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 loose_objects_subdir_seen[8]; /* 256 bits */ - struct oidtree *loose_objects_cache; - - /* Map between object IDs for loose objects. */ - struct loose_object_map *loose_map; - - /* - * private data - * - * should only be accessed directly by packfile.c and midx.c - */ - struct multi_pack_index *midx; - - /* - * Figure out whether this is the local source of the owning - * repository, which would typically be its ".git/objects" directory. - * This local object directory is usually where objects would be - * written to. - */ - bool local; - - /* - * This is a temporary object store created by the tmp_objdir - * facility. Disable ref updates since the objects in the store - * might be discarded on rollback. - */ - int disable_ref_updates; - - /* - * This object store is ephemeral, so there is no need to fsync. - */ - int will_destroy; - - /* - * Path to the source. If this is a relative path, it is relative to - * the current working directory. - */ - char *path; -}; - struct packed_git; struct packfile_store; struct cached_object_entry; + +/* + * A transaction may be started for an object database prior to writing new + * objects via odb_transaction_begin(). These objects are not committed until + * odb_transaction_commit() is invoked. Only a single transaction may be pending + * at a time. + * + * Each ODB source is expected to implement its own transaction handling. + */ struct odb_transaction; +typedef void (*odb_transaction_commit_fn)(struct odb_transaction *transaction); +struct odb_transaction { + /* The ODB source the transaction is opened against. */ + struct odb_source *source; + + /* The ODB source specific callback invoked to commit a transaction. */ + odb_transaction_commit_fn commit; +}; /* * The object database encapsulates access to objects in a repository. It @@ -139,9 +98,6 @@ struct object_database { struct commit_graph *commit_graph; unsigned commit_graph_attempted : 1; /* if loading has been attempted */ - /* Should only be accessed directly by packfile.c and midx.c. */ - struct packfile_store *packfiles; - /* * 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 @@ -154,10 +110,11 @@ struct object_database { /* * A fast, rough count of the number of objects in the repository. * These two fields are not meant for direct access. Use - * repo_approximate_object_count() instead. + * odb_count_objects() instead. */ - unsigned long approximate_object_count; - unsigned approximate_object_count_valid : 1; + unsigned long object_count; + unsigned object_count_flags; + unsigned object_count_valid : 1; /* * Submodule source paths that will be added as additional sources to @@ -166,8 +123,30 @@ struct object_database { struct string_list submodule_source_paths; }; -struct object_database *odb_new(struct repository *repo); -void odb_clear(struct object_database *o); +/* + * Create a new object database for the given repository. + * + * If the primary source parameter is set it will override the usual primary + * object directory derived from the repository's common directory. The + * alternate sources are expected to be a PATH_SEP-separated list of secondary + * sources. Note that these alternate sources will be added in addition to, not + * instead of, the alternates identified by the primary source. + * + * Returns the newly created object database. + */ +struct object_database *odb_new(struct repository *repo, + const char *primary_source, + const char *alternate_sources); + +/* Free the object database and release all resources. */ +void odb_free(struct object_database *o); + +/* + * Close the object database and all of its sources so that any held resources + * will be released. The database can still be used after closing it, in which + * case these resources may be reallocated. + */ +void odb_close(struct object_database *o); /* * Clear caches, reload alternates and then reload object sources so that new @@ -314,12 +293,24 @@ struct object_info { struct object_id *delta_base_oid; 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; + /* Response */ enum { OI_CACHED, OI_LOOSE, OI_PACKED, - OI_DBCACHED } whence; union { /* @@ -333,7 +324,12 @@ struct object_info { struct { struct packed_git *pack; off_t offset; - unsigned int is_delta; + enum packed_object_type { + PACKED_OBJECT_TYPE_UNKNOWN, + PACKED_OBJECT_TYPE_FULL, + PACKED_OBJECT_TYPE_OFS_DELTA, + PACKED_OBJECT_TYPE_REF_DELTA, + } type; } packed; } u; }; @@ -344,23 +340,41 @@ struct object_info { */ #define OBJECT_INFO_INIT { 0 } -/* Invoke lookup_replace_object() on the given hash */ -#define OBJECT_INFO_LOOKUP_REPLACE 1 -/* Do not retry packed storage after checking packed and loose storage */ -#define OBJECT_INFO_QUICK 8 -/* - * Do not attempt to fetch the object if missing (even if fetch_is_missing is - * nonzero). - */ -#define OBJECT_INFO_SKIP_FETCH_OBJECT 16 -/* - * This is meant for bulk prefetching of missing blobs in a partial - * clone. Implies OBJECT_INFO_SKIP_FETCH_OBJECT and OBJECT_INFO_QUICK - */ -#define OBJECT_INFO_FOR_PREFETCH (OBJECT_INFO_SKIP_FETCH_OBJECT | OBJECT_INFO_QUICK) +/* Flags that can be passed to `odb_read_object_info_extended()`. */ +enum object_info_flags { + /* Invoke lookup_replace_object() on the given hash. */ + OBJECT_INFO_LOOKUP_REPLACE = (1 << 0), -/* Die if object corruption (not just an object being missing) was detected. */ -#define OBJECT_INFO_DIE_IF_CORRUPT 32 + /* Do not reprepare object sources when the first lookup has failed. */ + OBJECT_INFO_QUICK = (1 << 1), + + /* + * Do not attempt to fetch the object if missing (even if fetch_is_missing is + * nonzero). + */ + OBJECT_INFO_SKIP_FETCH_OBJECT = (1 << 2), + + /* Die if object corruption (not just an object being missing) was detected. */ + OBJECT_INFO_DIE_IF_CORRUPT = (1 << 3), + + /* + * We have already tried reading the object, but it couldn't be found + * via any of the attached sources, and are now doing a second read. + * This second read asks the individual sources to also evaluate + * whether any on-disk state may have changed that may have caused the + * object to appear. + * + * This flag is for internal use, only. The second read only occurs + * when `OBJECT_INFO_QUICK` was not passed. + */ + OBJECT_INFO_SECOND_READ = (1 << 4), + + /* + * This is meant for bulk prefetching of missing blobs in a partial + * clone. Implies OBJECT_INFO_SKIP_FETCH_OBJECT and OBJECT_INFO_QUICK. + */ + OBJECT_INFO_FOR_PREFETCH = (OBJECT_INFO_SKIP_FETCH_OBJECT | OBJECT_INFO_QUICK), +}; /* * Read object info from the object database and populate the `object_info` @@ -369,7 +383,7 @@ struct object_info { int odb_read_object_info_extended(struct object_database *odb, const struct object_id *oid, struct object_info *oi, - unsigned flags); + enum object_info_flags flags); /* * Read a subset of object info for the given object ID. Returns an `enum @@ -381,7 +395,7 @@ int odb_read_object_info(struct object_database *odb, const struct object_id *oid, unsigned long *sizep); -enum { +enum has_object_flags { /* Retry packed storage after checking packed and loose storage */ HAS_OBJECT_RECHECK_PACKED = (1 << 0), /* Allow fetching the object in case the repository has a promisor remote. */ @@ -394,7 +408,10 @@ enum { */ int odb_has_object(struct object_database *odb, const struct object_id *oid, - unsigned flags); + enum has_object_flags flags); + +int odb_freshen_object(struct object_database *odb, + const struct object_id *oid); void odb_assert_oid_type(struct object_database *odb, const struct object_id *oid, enum object_type expect); @@ -431,26 +448,119 @@ static inline void obj_read_unlock(void) if(obj_read_use_lock) pthread_mutex_unlock(&obj_read_mutex); } + /* Flags for for_each_*_object(). */ -enum for_each_object_flags { +enum odb_for_each_object_flags { /* Iterate only over local objects, not alternates. */ - FOR_EACH_OBJECT_LOCAL_ONLY = (1<<0), + ODB_FOR_EACH_OBJECT_LOCAL_ONLY = (1<<0), /* Only iterate over packs obtained from the promisor remote. */ - FOR_EACH_OBJECT_PROMISOR_ONLY = (1<<1), + ODB_FOR_EACH_OBJECT_PROMISOR_ONLY = (1<<1), /* * Visit objects within a pack in packfile order rather than .idx order */ - FOR_EACH_OBJECT_PACK_ORDER = (1<<2), + ODB_FOR_EACH_OBJECT_PACK_ORDER = (1<<2), /* Only iterate over packs that are not marked as kept in-core. */ - FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS = (1<<3), + ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS = (1<<3), /* Only iterate over packs that do not have .keep files. */ - FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4), + ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS = (1<<4), }; +/* + * A callback function that can be used to iterate through objects. If given, + * the optional `oi` parameter will be populated the same as if you would call + * `odb_read_object_info()`. + * + * Returning a non-zero error code will cause iteration to abort. The error + * code will be propagated. + */ +typedef int (*odb_for_each_object_cb)(const struct object_id *oid, + struct object_info *oi, + void *cb_data); + +/* + * Options that can be passed to `odb_for_each_object()` and its + * backend-specific implementations. + */ +struct odb_for_each_object_options { + /* A bitfield of `odb_for_each_object_flags`. */ + enum odb_for_each_object_flags flags; + + /* + * If set, only iterate through objects whose first `prefix_hex_len` + * hex characters matches the given prefix. + */ + const struct object_id *prefix; + size_t prefix_hex_len; +}; + +/* + * Iterate through all objects contained in the object database. Note that + * objects may be iterated over multiple times in case they are either stored + * in different backends or in case they are stored in multiple sources. + * If an object info request is given, then the object info will be read and + * passed to the callback as if `odb_read_object_info()` was called for the + * object. + * + * Returning a non-zero error code from the callback function will cause + * iteration to abort. The error code will be propagated. + * + * Returns 0 on success, a negative error code in case a failure occurred, or + * an arbitrary non-zero error code returned by the callback itself. + */ +int odb_for_each_object_ext(struct object_database *odb, + const struct object_info *request, + odb_for_each_object_cb cb, + void *cb_data, + const struct odb_for_each_object_options *opts); + +/* Same as `odb_for_each_object_ext()` with `opts.flags` set to the given flags. */ +int odb_for_each_object(struct object_database *odb, + const struct object_info *request, + odb_for_each_object_cb cb, + void *cb_data, + unsigned flags); + +enum odb_count_objects_flags { + /* + * Instead of providing an accurate count, allow the number of objects + * to be approximated. Details of how this approximation works are + * subject to the specific source's implementation. + */ + ODB_COUNT_OBJECTS_APPROXIMATE = (1 << 0), +}; + +/* + * Count the number of objects in the given object database. This object count + * may double-count objects that are stored in multiple backends, or which are + * stored multiple times in a single backend. + * + * Returns 0 on success, a negative error code otherwise. The number of objects + * will be assigned to the `out` pointer on success. + */ +int odb_count_objects(struct object_database *odb, + enum odb_count_objects_flags flags, + unsigned long *out); + +/* + * Given an object ID, find the minimum required length required to make the + * object ID unique across the whole object database. + * + * The `min_len` determines the minimum abbreviated length that'll be returned + * by this function. If `min_len < 0`, then the function will set a sensible + * default minimum abbreviation length. + * + * Returns 0 on success, a negative error code otherwise. The computed length + * will be assigned to `*out`. + */ +int odb_find_abbrev_len(struct object_database *odb, + const struct object_id *oid, + int min_len, + unsigned *out); + enum { /* * By default, `odb_write_object()` does not actually write anything @@ -489,4 +599,19 @@ static inline int odb_write_object(struct object_database *odb, return odb_write_object_ext(odb, buf, len, type, oid, NULL, 0); } +struct odb_write_stream { + const void *(*read)(struct odb_write_stream *, unsigned long *len); + void *data; + int is_finished; +}; + +int odb_write_object_stream(struct object_database *odb, + struct odb_write_stream *stream, size_t len, + struct object_id *oid); + +void parse_alternates(const char *string, + int sep, + const char *relative_base, + struct strvec *out); + #endif /* ODB_H */
diff --git a/odb/source-files.c b/odb/source-files.c new file mode 100644 index 0000000..7679756 --- /dev/null +++ b/odb/source-files.c
@@ -0,0 +1,294 @@ +#include "git-compat-util.h" +#include "abspath.h" +#include "chdir-notify.h" +#include "gettext.h" +#include "lockfile.h" +#include "object-file.h" +#include "odb.h" +#include "odb/source.h" +#include "odb/source-files.h" +#include "packfile.h" +#include "strbuf.h" +#include "write-or-die.h" + +static void odb_source_files_reparent(const char *name UNUSED, + const char *old_cwd, + const char *new_cwd, + void *cb_data) +{ + struct odb_source_files *files = cb_data; + char *path = reparent_relative_path(old_cwd, new_cwd, + files->base.path); + free(files->base.path); + files->base.path = path; +} + +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_release(&files->base); + free(files); +} + +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); +} + +static void odb_source_files_reprepare(struct odb_source *source) +{ + struct odb_source_files *files = odb_source_files_downcast(source); + odb_source_loose_reprepare(&files->base); + packfile_store_reprepare(files->packed); +} + +static int odb_source_files_read_object_info(struct odb_source *source, + const struct object_id *oid, + struct object_info *oi, + enum object_info_flags flags) +{ + 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)) + return 0; + + return -1; +} + +static int odb_source_files_read_object_stream(struct odb_read_stream **out, + struct odb_source *source, + 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)) + return 0; + return -1; +} + +static int odb_source_files_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_files *files = odb_source_files_downcast(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); + if (ret) + return ret; + } + + ret = packfile_store_for_each_object(files->packed, request, cb, cb_data, opts); + if (ret) + return ret; + + return 0; +} + +static int odb_source_files_count_objects(struct odb_source *source, + enum odb_count_objects_flags flags, + unsigned long *out) +{ + struct odb_source_files *files = odb_source_files_downcast(source); + unsigned long count; + int ret; + + ret = packfile_store_count_objects(files->packed, 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); + if (ret < 0) + goto out; + + count += loose_count; + } + + *out = count; + ret = 0; + +out: + return ret; +} + +static int odb_source_files_find_abbrev_len(struct odb_source *source, + const struct object_id *oid, + unsigned min_len, + unsigned *out) +{ + struct odb_source_files *files = odb_source_files_downcast(source); + unsigned len = min_len; + int ret; + + ret = packfile_store_find_abbrev_len(files->packed, oid, len, &len); + if (ret < 0) + goto out; + + ret = odb_source_loose_find_abbrev_len(source, oid, len, &len); + if (ret < 0) + goto out; + + *out = len; + ret = 0; + +out: + return ret; +} + +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)) + return 1; + return 0; +} + +static int odb_source_files_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, + unsigned flags) +{ + return odb_source_loose_write_object(source, buf, len, type, + oid, compat_oid, flags); +} + +static int odb_source_files_write_object_stream(struct odb_source *source, + struct odb_write_stream *stream, + size_t len, + struct object_id *oid) +{ + return odb_source_loose_write_stream(source, stream, len, oid); +} + +static int odb_source_files_begin_transaction(struct odb_source *source, + struct odb_transaction **out) +{ + struct odb_transaction *tx = odb_transaction_files_begin(source); + if (!tx) + return -1; + *out = tx; + return 0; +} + +static int odb_source_files_read_alternates(struct odb_source *source, + struct strvec *out) +{ + struct strbuf buf = STRBUF_INIT; + char *path; + + path = xstrfmt("%s/info/alternates", source->path); + if (strbuf_read_file(&buf, path, 1024) < 0) { + warn_on_fopen_errors(path); + free(path); + return 0; + } + parse_alternates(buf.buf, '\n', source->path, out); + + strbuf_release(&buf); + free(path); + return 0; +} + +static int odb_source_files_write_alternate(struct odb_source *source, + const char *alternate) +{ + struct lock_file lock = LOCK_INIT; + char *path = xstrfmt("%s/%s", source->path, "info/alternates"); + FILE *in, *out; + int found = 0; + int ret; + + hold_lock_file_for_update(&lock, path, LOCK_DIE_ON_ERROR); + out = fdopen_lock_file(&lock, "w"); + if (!out) { + ret = error_errno(_("unable to fdopen alternates lockfile")); + goto out; + } + + in = fopen(path, "r"); + if (in) { + struct strbuf line = STRBUF_INIT; + + while (strbuf_getline(&line, in) != EOF) { + if (!strcmp(alternate, line.buf)) { + found = 1; + break; + } + fprintf_or_die(out, "%s\n", line.buf); + } + + strbuf_release(&line); + fclose(in); + } else if (errno != ENOENT) { + ret = error_errno(_("unable to read alternates file")); + goto out; + } + + if (found) { + rollback_lock_file(&lock); + } else { + fprintf_or_die(out, "%s\n", alternate); + if (commit_lock_file(&lock)) { + ret = error_errno(_("unable to move new alternates file into place")); + goto out; + } + } + + ret = 0; + +out: + free(path); + return ret; +} + +struct odb_source_files *odb_source_files_new(struct object_database *odb, + const char *path, + bool local) +{ + struct odb_source_files *files; + + 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->base.free = odb_source_files_free; + files->base.close = odb_source_files_close; + files->base.reprepare = odb_source_files_reprepare; + 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; + files->base.count_objects = odb_source_files_count_objects; + files->base.find_abbrev_len = odb_source_files_find_abbrev_len; + files->base.freshen_object = odb_source_files_freshen_object; + files->base.write_object = odb_source_files_write_object; + files->base.write_object_stream = odb_source_files_write_object_stream; + files->base.begin_transaction = odb_source_files_begin_transaction; + files->base.read_alternates = odb_source_files_read_alternates; + files->base.write_alternate = odb_source_files_write_alternate; + + /* + * Ideally, we would only ever store absolute paths in the source. This + * is not (yet) possible though because we access and assume relative + * paths in the primary ODB source in some user-facing functionality. + */ + if (!is_absolute_path(path)) + chdir_notify_register(NULL, odb_source_files_reparent, files); + + return files; +}
diff --git a/odb/source-files.h b/odb/source-files.h new file mode 100644 index 0000000..23a3b4e --- /dev/null +++ b/odb/source-files.h
@@ -0,0 +1,35 @@ +#ifndef ODB_SOURCE_FILES_H +#define ODB_SOURCE_FILES_H + +#include "odb/source.h" + +struct odb_source_loose; +struct packfile_store; + +/* + * The files object database source uses a combination of loose objects and + * packfiles. It is the default backend used by Git to store objects. + */ +struct odb_source_files { + struct odb_source base; + struct odb_source_loose *loose; + struct packfile_store *packed; +}; + +/* Allocate and initialize a new object source. */ +struct odb_source_files *odb_source_files_new(struct object_database *odb, + const char *path, + bool local); + +/* + * Cast the given object database source to the files backend. This will cause + * a BUG in case the source doesn't use this backend. + */ +static inline struct odb_source_files *odb_source_files_downcast(struct odb_source *source) +{ + if (source->type != ODB_SOURCE_FILES) + BUG("trying to downcast source of type '%d' to files", source->type); + return container_of(source, struct odb_source_files, base); +} + +#endif
diff --git a/odb/source.c b/odb/source.c new file mode 100644 index 0000000..7993dcb --- /dev/null +++ b/odb/source.c
@@ -0,0 +1,38 @@ +#include "git-compat-util.h" +#include "object-file.h" +#include "odb/source-files.h" +#include "odb/source.h" +#include "packfile.h" + +struct odb_source *odb_source_new(struct object_database *odb, + const char *path, + bool local) +{ + return &odb_source_files_new(odb, path, local)->base; +} + +void odb_source_init(struct odb_source *source, + struct object_database *odb, + enum odb_source_type type, + const char *path, + bool local) +{ + source->odb = odb; + source->type = type; + source->local = local; + source->path = xstrdup(path); +} + +void odb_source_free(struct odb_source *source) +{ + if (!source) + return; + source->free(source); +} + +void odb_source_release(struct odb_source *source) +{ + if (!source) + return; + free(source->path); +}
diff --git a/odb/source.h b/odb/source.h new file mode 100644 index 0000000..a9d7d0b --- /dev/null +++ b/odb/source.h
@@ -0,0 +1,469 @@ +#ifndef ODB_SOURCE_H +#define ODB_SOURCE_H + +#include "object.h" +#include "odb.h" + +enum odb_source_type { + /* + * The "unknown" type, which should never be in use. This type mostly + * exists to catch cases where the type field remains zeroed out. + */ + ODB_SOURCE_UNKNOWN, + + /* The "files" backend that uses loose objects and packfiles. */ + ODB_SOURCE_FILES, +}; + +struct object_id; +struct odb_read_stream; +struct strvec; + +/* + * The source is the part of the object database that stores the actual + * objects. It thus encapsulates the logic to read and write the specific + * on-disk format. An object database can have multiple sources: + * + * - The primary source, which is typically located in "$GIT_DIR/objects". + * This is where new objects are usually written to. + * + * - Alternate sources, which are configured via "objects/info/alternates" or + * via the GIT_ALTERNATE_OBJECT_DIRECTORIES environment variable. These + * alternate sources are only used to read objects. + */ +struct odb_source { + struct odb_source *next; + + /* Object database that owns this object source. */ + struct object_database *odb; + + /* The type used by this source. */ + enum odb_source_type type; + + /* + * Figure out whether this is the local source of the owning + * repository, which would typically be its ".git/objects" directory. + * This local object directory is usually where objects would be + * written to. + */ + bool local; + + /* + * This object store is ephemeral, so there is no need to fsync. + */ + int will_destroy; + + /* + * Path to the source. If this is a relative path, it is relative to + * the current working directory. + */ + char *path; + + /* + * This callback is expected to free the underlying object database source and + * all associated resources. The function will never be called with a NULL pointer. + */ + void (*free)(struct odb_source *source); + + /* + * This callback is expected to close any open resources, like for + * example file descriptors or connections. The source is expected to + * still be usable after it has been closed. Closed resources may need + * to be reopened in that case. + */ + 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. + */ + void (*reprepare)(struct odb_source *source); + + /* + * This callback is expected to read object information from the object + * database source. The object info will be partially populated with + * pointers for each bit of information that was requested by the + * caller. + * + * The flags field is a combination of `OBJECT_INFO` flags. Only the + * following fields need to be handled by the backend: + * + * - `OBJECT_INFO_QUICK` indicates it is fine to use caches without + * re-verifying the data. + * + * - `OBJECT_INFO_SECOND_READ` indicates that the initial object + * lookup has failed and that the object sources should check + * whether any of its on-disk state has changed that may have + * caused the object to appear. Sources are free to ignore the + * second read in case they know that the first read would have + * already surfaced the object without reloading any on-disk state. + * + * The callback is expected to return a negative error code in case + * reading the object has failed, 0 otherwise. + */ + int (*read_object_info)(struct odb_source *source, + const struct object_id *oid, + struct object_info *oi, + enum object_info_flags flags); + + /* + * This callback is expected to create a new read stream that can be + * used to stream the object identified by the given ID. + * + * The callback is expected to return a negative error code in case + * creating the object stream has failed, 0 otherwise. + */ + int (*read_object_stream)(struct odb_read_stream **out, + struct odb_source *source, + const struct object_id *oid); + + /* + * This callback is expected to iterate over all objects stored in this + * source and invoke the callback function for each of them. It is + * valid to yield the same object multiple time. A non-zero exit code + * from the object callback shall abort iteration. + * + * The optional `request` structure should serve as a template for + * looking up object info for every individual iterated object. It + * should not be modified directly and should instead be copied into a + * separate `struct object_info` that gets passed to the callback. If + * the caller passes a `NULL` pointer then the object itself shall not + * be read. + * + * The callback is expected to return a negative error code in case the + * iteration has failed to read all objects, 0 otherwise. When the + * callback function returns a non-zero error code then that error code + * should be returned. + */ + int (*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); + + /* + * This callback is expected to count objects in the given object + * database source. The callback function does not have to guarantee + * that only unique objects are counted. The result shall be assigned + * to the `out` pointer. + * + * Accepts `enum odb_count_objects_flag` flags to alter the behaviour. + * + * The callback is expected to return 0 on success, or a negative error + * code otherwise. + */ + int (*count_objects)(struct odb_source *source, + enum odb_count_objects_flags flags, + unsigned long *out); + + /* + * This callback is expected to find the minimum required length to + * make the given object ID unique. + * + * The callback is expected to return a negative error code in case it + * failed, 0 otherwise. + */ + int (*find_abbrev_len)(struct odb_source *source, + const struct object_id *oid, + unsigned min_length, + unsigned *out); + + /* + * This callback is expected to freshen the given object so that its + * last access time is set to the current time. This is used to ensure + * that objects that are recent will not get garbage collected even if + * they were unreachable. + * + * Returns 0 in case the object does not exist, 1 in case the object + * has been freshened. + */ + int (*freshen_object)(struct odb_source *source, + const struct object_id *oid); + + /* + * This callback is expected to persist the given object into the + * object source. In case the object already exists it shall be + * freshened. + * + * The flags field is a combination of `WRITE_OBJECT` flags. + * + * The resulting object ID (and optionally the compatibility object ID) + * shall be written into the out pointers. The callback is expected to + * return 0 on success, a negative error code otherwise. + */ + int (*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, + unsigned flags); + + /* + * This callback is expected to persist the given object stream into + * the object source. + * + * The resulting object ID shall be written into the out pointer. The + * callback is expected to return 0 on success, a negative error code + * otherwise. + */ + int (*write_object_stream)(struct odb_source *source, + struct odb_write_stream *stream, size_t len, + struct object_id *oid); + + /* + * This callback is expected to create a new transaction that can be + * used to write objects to. The objects shall only be persisted into + * the object database when the transcation's commit function is + * called. Otherwise, the objects shall be discarded. + * + * Returns 0 on success, in which case the `*out` pointer will have + * been populated with the object database transaction. Returns a + * negative error code otherwise. + */ + int (*begin_transaction)(struct odb_source *source, + struct odb_transaction **out); + + /* + * This callback is expected to read the list of alternate object + * database sources connected to it and write them into the `strvec`. + * + * The result is expected to be paths to the alternates. All paths must + * be resolved to absolute paths. + * + * The callback is expected to return 0 on success, a negative error + * code otherwise. + */ + int (*read_alternates)(struct odb_source *source, + struct strvec *out); + + /* + * This callback is expected to persist the singular alternate passed + * to it into its list of alternates. Any pre-existing alternates are + * expected to remain active. Subsequent calls to `read_alternates` are + * thus expected to yield the pre-existing list of alternates plus the + * newly added alternate appended to its end. + * + * The callback is expected to return 0 on success, a negative error + * code otherwise. + */ + int (*write_alternate)(struct odb_source *source, + const char *alternate); +}; + +/* + * Allocate and initialize a new source for the given object database located + * at `path`. `local` indicates whether or not the source is the local and thus + * primary object source of the object database. + */ +struct odb_source *odb_source_new(struct object_database *odb, + const char *path, + bool local); + +/* + * Initialize the source for the given object database located at `path`. + * `local` indicates whether or not the source is the local and thus primary + * object source of the object database. + * + * This function is only supposed to be called by specific object source + * implementations. + */ +void odb_source_init(struct odb_source *source, + struct object_database *odb, + enum odb_source_type type, + const char *path, + bool local); + +/* + * Free the object database source, releasing all associated resources and + * freeing the structure itself. + */ +void odb_source_free(struct odb_source *source); + +/* + * Release the object database source, releasing all associated resources. + * + * This function is only supposed to be called by specific object source + * implementations. + */ +void odb_source_release(struct odb_source *source); + +/* + * Close the object database source without releasing he underlying data. The + * source can still be used going forward, but it first needs to be reopened. + * This can be useful to reduce resource usage. + */ +static inline void odb_source_close(struct odb_source *source) +{ + source->close(source); +} + +/* + * Reprepare 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) +{ + source->reprepare(source); +} + +/* + * Read an object from the object database source identified by its object ID. + * Returns 0 on success, a negative error code otherwise. + */ +static inline int odb_source_read_object_info(struct odb_source *source, + const struct object_id *oid, + struct object_info *oi, + enum object_info_flags flags) +{ + return source->read_object_info(source, oid, oi, flags); +} + +/* + * Create a new read stream for the given object ID. Returns 0 on success, a + * negative error code otherwise. + */ +static inline int odb_source_read_object_stream(struct odb_read_stream **out, + struct odb_source *source, + const struct object_id *oid) +{ + return source->read_object_stream(out, source, oid); +} + +/* + * Iterate through all objects contained in the given source and invoke the + * callback function for each of them. Returning a non-zero code from the + * callback function aborts iteration. There is no guarantee that objects + * 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 + * `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 + * `NULL` pointer. + * + * The flags is a bitfield of `ODB_FOR_EACH_OBJECT_*` flags. Not all flags may + * apply to a specific backend, so whether or not they are honored is defined + * by the implementation. + * + * Returns 0 when all objects have been iterated over, a negative error code in + * case iteration has failed, or a non-zero value returned from the callback. + */ +static inline int odb_source_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) +{ + return source->for_each_object(source, request, cb, cb_data, opts); +} + +/* + * Count the number of objects in the given object database source. + * + * Returns 0 on success, a negative error code otherwise. + */ +static inline int odb_source_count_objects(struct odb_source *source, + enum odb_count_objects_flags flags, + unsigned long *out) +{ + return source->count_objects(source, flags, out); +} + +/* + * Determine the minimum required length to make the given object ID unique in + * the given source. Returns 0 on success, a negative error code otherwise. + */ +static inline int odb_source_find_abbrev_len(struct odb_source *source, + const struct object_id *oid, + unsigned min_len, + unsigned *out) +{ + return source->find_abbrev_len(source, oid, min_len, out); +} + +/* + * Freshen an object in the object database by updating its timestamp. + * Returns 1 in case the object has been freshened, 0 in case the object does + * not exist. + */ +static inline int odb_source_freshen_object(struct odb_source *source, + const struct object_id *oid) +{ + return source->freshen_object(source, oid); +} + +/* + * Write an object into the object database source. Returns 0 on success, a + * negative error code otherwise. Populates the given out pointers for the + * object ID and the compatibility object ID, if non-NULL. + */ +static inline int odb_source_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, + unsigned flags) +{ + return source->write_object(source, buf, len, type, oid, + compat_oid, flags); +} + +/* + * Write an object into the object database source via a stream. The overall + * length of the object must be known in advance. + * + * Return 0 on success, a negative error code otherwise. Populates the given + * out pointer for the object ID. + */ +static inline int odb_source_write_object_stream(struct odb_source *source, + struct odb_write_stream *stream, + size_t len, + struct object_id *oid) +{ + return source->write_object_stream(source, stream, len, oid); +} + +/* + * Read the list of alternative object database sources from the given backend + * and populate the `strvec` with them. The listing is not recursive -- that + * is, if any of the yielded alternate sources has alternates itself, those + * will not be yielded as part of this function call. + * + * Return 0 on success, a negative error code otherwise. + */ +static inline int odb_source_read_alternates(struct odb_source *source, + struct strvec *out) +{ + return source->read_alternates(source, out); +} + +/* + * Write and persist a new alternate object database source for the given + * source. Any preexisting alternates are expected to stay valid, and the new + * alternate shall be appended to the end of the list. + * + * Returns 0 on success, a negative error code otherwise. + */ +static inline int odb_source_write_alternate(struct odb_source *source, + const char *alternate) +{ + return source->write_alternate(source, alternate); +} + +/* + * Create a new transaction that can be used to write objects into a temporary + * staging area. The objects will only be persisted when the transaction is + * committed. + * + * Returns 0 on success, a negative error code otherwise. + */ +static inline int odb_source_begin_transaction(struct odb_source *source, + struct odb_transaction **out) +{ + return source->begin_transaction(source, out); +} + +#endif
diff --git a/odb/streaming.c b/odb/streaming.c new file mode 100644 index 0000000..5927a12 --- /dev/null +++ b/odb/streaming.c
@@ -0,0 +1,289 @@ +/* + * Copyright (c) 2011, Google Inc. + */ + +#include "git-compat-util.h" +#include "convert.h" +#include "environment.h" +#include "repository.h" +#include "odb.h" +#include "odb/source.h" +#include "odb/streaming.h" +#include "replace-object.h" + +#define FILTER_BUFFER (1024*16) + +/***************************************************************** + * + * Filtered stream + * + *****************************************************************/ + +struct odb_filtered_read_stream { + struct odb_read_stream base; + struct odb_read_stream *upstream; + struct stream_filter *filter; + char ibuf[FILTER_BUFFER]; + char obuf[FILTER_BUFFER]; + int i_end, i_ptr; + int o_end, o_ptr; + int input_finished; +}; + +static int close_istream_filtered(struct odb_read_stream *_fs) +{ + struct odb_filtered_read_stream *fs = (struct odb_filtered_read_stream *)_fs; + free_stream_filter(fs->filter); + return odb_read_stream_close(fs->upstream); +} + +static ssize_t read_istream_filtered(struct odb_read_stream *_fs, char *buf, + size_t sz) +{ + struct odb_filtered_read_stream *fs = (struct odb_filtered_read_stream *)_fs; + size_t filled = 0; + + while (sz) { + /* do we already have filtered output? */ + if (fs->o_ptr < fs->o_end) { + size_t to_move = fs->o_end - fs->o_ptr; + if (sz < to_move) + to_move = sz; + memcpy(buf + filled, fs->obuf + fs->o_ptr, to_move); + fs->o_ptr += to_move; + sz -= to_move; + filled += to_move; + continue; + } + fs->o_end = fs->o_ptr = 0; + + /* do we have anything to feed the filter with? */ + if (fs->i_ptr < fs->i_end) { + size_t to_feed = fs->i_end - fs->i_ptr; + size_t to_receive = FILTER_BUFFER; + if (stream_filter(fs->filter, + fs->ibuf + fs->i_ptr, &to_feed, + fs->obuf, &to_receive)) + return -1; + fs->i_ptr = fs->i_end - to_feed; + fs->o_end = FILTER_BUFFER - to_receive; + continue; + } + + /* tell the filter to drain upon no more input */ + if (fs->input_finished) { + size_t to_receive = FILTER_BUFFER; + if (stream_filter(fs->filter, + NULL, NULL, + fs->obuf, &to_receive)) + return -1; + fs->o_end = FILTER_BUFFER - to_receive; + if (!fs->o_end) + break; + continue; + } + fs->i_end = fs->i_ptr = 0; + + /* refill the input from the upstream */ + if (!fs->input_finished) { + fs->i_end = odb_read_stream_read(fs->upstream, fs->ibuf, FILTER_BUFFER); + if (fs->i_end < 0) + return -1; + if (fs->i_end) + continue; + } + fs->input_finished = 1; + } + return filled; +} + +static struct odb_read_stream *attach_stream_filter(struct odb_read_stream *st, + struct stream_filter *filter) +{ + struct odb_filtered_read_stream *fs; + + CALLOC_ARRAY(fs, 1); + fs->base.close = close_istream_filtered; + fs->base.read = read_istream_filtered; + fs->upstream = st; + fs->filter = filter; + fs->base.size = -1; /* unknown */ + fs->base.type = st->type; + + return &fs->base; +} + +/***************************************************************** + * + * In-core stream + * + *****************************************************************/ + +struct odb_incore_read_stream { + struct odb_read_stream base; + char *buf; /* from odb_read_object_info_extended() */ + unsigned long read_ptr; +}; + +static int close_istream_incore(struct odb_read_stream *_st) +{ + struct odb_incore_read_stream *st = (struct odb_incore_read_stream *)_st; + free(st->buf); + return 0; +} + +static ssize_t read_istream_incore(struct odb_read_stream *_st, char *buf, size_t sz) +{ + struct odb_incore_read_stream *st = (struct odb_incore_read_stream *)_st; + size_t read_size = sz; + size_t remainder = st->base.size - st->read_ptr; + + if (remainder <= read_size) + read_size = remainder; + if (read_size) { + memcpy(buf, st->buf + st->read_ptr, read_size); + st->read_ptr += read_size; + } + return read_size; +} + +static int open_istream_incore(struct odb_read_stream **out, + struct object_database *odb, + const struct object_id *oid) +{ + struct object_info oi = OBJECT_INFO_INIT; + struct odb_incore_read_stream stream = { + .base.close = close_istream_incore, + .base.read = read_istream_incore, + }; + struct odb_incore_read_stream *st; + int ret; + + oi.typep = &stream.base.type; + 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; + + CALLOC_ARRAY(st, 1); + *st = stream; + *out = &st->base; + + return 0; +} + +/***************************************************************************** + * static helpers variables and functions for users of streaming interface + *****************************************************************************/ + +static int istream_source(struct odb_read_stream **out, + struct object_database *odb, + const struct object_id *oid) +{ + struct odb_source *source; + + odb_prepare_alternates(odb); + for (source = odb->sources; source; source = source->next) + if (!odb_source_read_object_stream(out, source, oid)) + return 0; + + return open_istream_incore(out, odb, oid); +} + +/**************************************************************** + * Users of streaming interface + ****************************************************************/ + +int odb_read_stream_close(struct odb_read_stream *st) +{ + int r = st->close(st); + free(st); + return r; +} + +ssize_t odb_read_stream_read(struct odb_read_stream *st, void *buf, size_t sz) +{ + return st->read(st, buf, sz); +} + +struct odb_read_stream *odb_read_stream_open(struct object_database *odb, + const struct object_id *oid, + struct stream_filter *filter) +{ + struct odb_read_stream *st; + const struct object_id *real = lookup_replace_object(odb->repo, oid); + int ret = istream_source(&st, odb, real); + + if (ret) + return NULL; + + if (filter) { + /* Add "&& !is_null_stream_filter(filter)" for performance */ + struct odb_read_stream *nst = attach_stream_filter(st, filter); + if (!nst) { + odb_read_stream_close(st); + return NULL; + } + st = nst; + } + + return st; +} + +int odb_stream_blob_to_fd(struct object_database *odb, + int fd, + const struct object_id *oid, + struct stream_filter *filter, + int can_seek) +{ + struct odb_read_stream *st; + ssize_t kept = 0; + int result = -1; + + st = odb_read_stream_open(odb, oid, filter); + if (!st) { + if (filter) + free_stream_filter(filter); + return result; + } + if (st->type != OBJ_BLOB) + goto close_and_exit; + for (;;) { + char buf[1024 * 16]; + ssize_t wrote, holeto; + ssize_t readlen = odb_read_stream_read(st, buf, sizeof(buf)); + + if (readlen < 0) + goto close_and_exit; + if (!readlen) + break; + if (can_seek && sizeof(buf) == readlen) { + for (holeto = 0; holeto < readlen; holeto++) + if (buf[holeto]) + break; + if (readlen == holeto) { + kept += holeto; + continue; + } + } + + if (kept && lseek(fd, kept, SEEK_CUR) == (off_t) -1) + goto close_and_exit; + else + kept = 0; + wrote = write_in_full(fd, buf, readlen); + + if (wrote < 0) + goto close_and_exit; + } + if (kept && (lseek(fd, kept - 1, SEEK_CUR) == (off_t) -1 || + xwrite(fd, "", 1) != 1)) + goto close_and_exit; + result = 0; + + close_and_exit: + odb_read_stream_close(st); + return result; +}
diff --git a/odb/streaming.h b/odb/streaming.h new file mode 100644 index 0000000..c7861f7 --- /dev/null +++ b/odb/streaming.h
@@ -0,0 +1,67 @@ +/* + * Copyright (c) 2011, Google Inc. + */ +#ifndef STREAMING_H +#define STREAMING_H 1 + +#include "object.h" + +struct object_database; +struct odb_read_stream; +struct stream_filter; + +typedef int (*odb_read_stream_close_fn)(struct odb_read_stream *); +typedef ssize_t (*odb_read_stream_read_fn)(struct odb_read_stream *, char *, size_t); + +/* + * A stream that can be used to read an object from the object database without + * loading all of it into memory. + */ +struct odb_read_stream { + odb_read_stream_close_fn close; + odb_read_stream_read_fn read; + enum object_type type; + unsigned long size; /* inflated size of full object */ +}; + +/* + * Create a new object stream for the given object database. An optional filter + * can be used to transform the object's content. + * + * Returns the stream on success, a `NULL` pointer otherwise. + */ +struct odb_read_stream *odb_read_stream_open(struct object_database *odb, + const struct object_id *oid, + struct stream_filter *filter); + +/* + * Close the given read stream and release all resources associated with it. + * Returns 0 on success, a negative error code otherwise. + */ +int odb_read_stream_close(struct odb_read_stream *stream); + +/* + * Read data from the stream into the buffer. Returns 0 on EOF and the number + * of bytes read on success. Returns a negative error code in case reading from + * the stream fails. + */ +ssize_t odb_read_stream_read(struct odb_read_stream *stream, void *buf, size_t len); + +/* + * Look up the object by its ID and write the full contents to the file + * descriptor. The object must be a blob, or the function will fail. When + * provided, the filter is used to transform the blob contents. + * + * `can_seek` should be set to 1 in case the given file descriptor can be + * seek(3p)'d on. This is used to support files with holes in case a + * significant portion of the blob contains NUL bytes. + * + * Returns a negative error code on failure, 0 on success. + */ +int odb_stream_blob_to_fd(struct object_database *odb, + int fd, + const struct object_id *oid, + struct stream_filter *filter, + int can_seek); + +#endif /* STREAMING_H */
diff --git a/oidmap.c b/oidmap.c index 508d6c7..a1ef535 100644 --- a/oidmap.c +++ b/oidmap.c
@@ -24,11 +24,28 @@ void oidmap_init(struct oidmap *map, size_t initial_size) void oidmap_clear(struct oidmap *map, int free_entries) { - if (!map) + oidmap_clear_with_free(map, + free_entries ? free : NULL); +} + +void oidmap_clear_with_free(struct oidmap *map, + oidmap_free_fn free_fn) +{ + struct hashmap_iter iter; + struct hashmap_entry *e; + + if (!map || !map->map.cmpfn) return; - /* TODO: make oidmap itself not depend on struct layouts */ - hashmap_clear_(&map->map, free_entries ? 0 : -1); + hashmap_iter_init(&map->map, &iter); + while ((e = hashmap_iter_next(&iter))) { + struct oidmap_entry *entry = + container_of(e, struct oidmap_entry, internal_entry); + if (free_fn) + free_fn(entry); + } + + hashmap_clear(&map->map); } void *oidmap_get(const struct oidmap *map, const struct object_id *key)
diff --git a/oidmap.h b/oidmap.h index 67fb322..acddcae 100644 --- a/oidmap.h +++ b/oidmap.h
@@ -36,6 +36,21 @@ struct oidmap { void oidmap_init(struct oidmap *map, size_t initial_size); /* + * Function type for functions that free oidmap entries. + */ +typedef void (*oidmap_free_fn)(void *); + +/* + * Clear an oidmap, freeing any allocated memory. The map is empty and + * can be reused without another explicit init. + * + * The `free_fn`, if not NULL, is called for each oidmap entry in the map + * to free any user data associated with the entry. + */ +void oidmap_clear_with_free(struct oidmap *map, + oidmap_free_fn free_fn); + +/* * Clear an oidmap, freeing any allocated memory. The map is empty and * can be reused without another explicit init. *
diff --git a/oidset.c b/oidset.c index 8d36aef8..c8ff0b3 100644 --- a/oidset.c +++ b/oidset.c
@@ -16,6 +16,22 @@ int oidset_contains(const struct oidset *set, const struct object_id *oid) return pos != kh_end(&set->set); } +bool oidset_equal(const struct oidset *a, const struct oidset *b) +{ + struct oidset_iter iter; + struct object_id *a_oid; + + if (oidset_size(a) != oidset_size(b)) + return false; + + oidset_iter_init(a, &iter); + while ((a_oid = oidset_iter_next(&iter))) + if (!oidset_contains(b, a_oid)) + return false; + + return true; +} + int oidset_insert(struct oidset *set, const struct object_id *oid) { int added;
diff --git a/oidset.h b/oidset.h index 0106b6f..e0f1a6f 100644 --- a/oidset.h +++ b/oidset.h
@@ -39,6 +39,11 @@ void oidset_init(struct oidset *set, size_t initial_size); int oidset_contains(const struct oidset *set, const struct object_id *oid); /** + * Returns true iff `a` and `b` contain the exact same OIDs. + */ +bool oidset_equal(const struct oidset *a, const struct oidset *b); + +/** * Insert the oid into the set; a copy is made, so "oid" does not need * to persist after this function is called. * @@ -94,11 +99,11 @@ void oidset_parse_file_carefully(struct oidset *set, const char *path, oidset_parse_tweak_fn fn, void *cbdata); struct oidset_iter { - kh_oid_set_t *set; + const kh_oid_set_t *set; khiter_t iter; }; -static inline void oidset_iter_init(struct oidset *set, +static inline void oidset_iter_init(const struct oidset *set, struct oidset_iter *iter) { iter->set = &set->set;
diff --git a/oidtree.c b/oidtree.c index 151568f..ab9fe7e 100644 --- a/oidtree.c +++ b/oidtree.c
@@ -6,14 +6,6 @@ #include "oidtree.h" #include "hash.h" -struct oidtree_iter_data { - oidtree_iter fn; - void *arg; - size_t *last_nibble_at; - int algo; - uint8_t last_byte; -}; - void oidtree_init(struct oidtree *ot) { cb_init(&ot->tree); @@ -54,8 +46,7 @@ void oidtree_insert(struct oidtree *ot, const struct object_id *oid) cb_insert(&ot->tree, on, sizeof(*oid)); } - -int oidtree_contains(struct oidtree *ot, const struct object_id *oid) +bool oidtree_contains(struct oidtree *ot, const struct object_id *oid) { struct object_id k; size_t klen = sizeof(k); @@ -69,41 +60,51 @@ int oidtree_contains(struct oidtree *ot, const struct object_id *oid) klen += BUILD_ASSERT_OR_ZERO(offsetof(struct object_id, hash) < offsetof(struct object_id, algo)); - return cb_lookup(&ot->tree, (const uint8_t *)&k, klen) ? 1 : 0; + return !!cb_lookup(&ot->tree, (const uint8_t *)&k, klen); } -static enum cb_next iter(struct cb_node *n, void *arg) +struct oidtree_each_data { + oidtree_each_cb cb; + void *cb_data; + size_t *last_nibble_at; + uint32_t algo; + uint8_t last_byte; +}; + +static int iter(struct cb_node *n, void *cb_data) { - struct oidtree_iter_data *x = arg; + struct oidtree_each_data *data = cb_data; struct object_id k; /* Copy to provide 4-byte alignment needed by struct object_id. */ memcpy(&k, n->k, sizeof(k)); - if (x->algo != GIT_HASH_UNKNOWN && x->algo != k.algo) - return CB_CONTINUE; + if (data->algo != GIT_HASH_UNKNOWN && data->algo != k.algo) + return 0; - if (x->last_nibble_at) { - if ((k.hash[*x->last_nibble_at] ^ x->last_byte) & 0xf0) - return CB_CONTINUE; + if (data->last_nibble_at) { + if ((k.hash[*data->last_nibble_at] ^ data->last_byte) & 0xf0) + return 0; } - return x->fn(&k, x->arg); + return data->cb(&k, data->cb_data); } -void oidtree_each(struct oidtree *ot, const struct object_id *oid, - size_t oidhexsz, oidtree_iter fn, void *arg) +int oidtree_each(struct oidtree *ot, const struct object_id *prefix, + size_t prefix_hex_len, oidtree_each_cb cb, void *cb_data) { - size_t klen = oidhexsz / 2; - struct oidtree_iter_data x = { 0 }; - assert(oidhexsz <= GIT_MAX_HEXSZ); + struct oidtree_each_data data = { + .cb = cb, + .cb_data = cb_data, + .algo = prefix->algo, + }; + size_t klen = prefix_hex_len / 2; + assert(prefix_hex_len <= GIT_MAX_HEXSZ); - x.fn = fn; - x.arg = arg; - x.algo = oid->algo; - if (oidhexsz & 1) { - x.last_byte = oid->hash[klen]; - x.last_nibble_at = &klen; + if (prefix_hex_len & 1) { + data.last_byte = prefix->hash[klen]; + data.last_nibble_at = &klen; } - cb_each(&ot->tree, (const uint8_t *)oid, klen, iter, &x); + + return cb_each(&ot->tree, prefix->hash, klen, iter, &data); }
diff --git a/oidtree.h b/oidtree.h index 77898f5..2b7bad2 100644 --- a/oidtree.h +++ b/oidtree.h
@@ -5,18 +5,52 @@ #include "hash.h" #include "mem-pool.h" +/* + * OID trees are an efficient storage for object IDs that use a critbit tree + * internally. Common prefixes are duplicated and object IDs are stored in a + * way that allow easy iteration over the objects in lexicographic order. As a + * consequence, operations that want to enumerate all object IDs that match a + * given prefix can be answered efficiently. + * + * Note that it is not (yet) possible to store data other than the object IDs + * themselves in this tree. + */ struct oidtree { struct cb_tree tree; struct mem_pool mem_pool; }; -void oidtree_init(struct oidtree *); -void oidtree_clear(struct oidtree *); -void oidtree_insert(struct oidtree *, const struct object_id *); -int oidtree_contains(struct oidtree *, const struct object_id *); +/* Initialize the oidtree so that it is ready for use. */ +void oidtree_init(struct oidtree *ot); -typedef enum cb_next (*oidtree_iter)(const struct object_id *, void *data); -void oidtree_each(struct oidtree *, const struct object_id *, - size_t oidhexsz, oidtree_iter, void *data); +/* + * Release all memory associated with the oidtree and reinitialize it for + * subsequent use. + */ +void oidtree_clear(struct oidtree *ot); + +/* Insert the object ID into the tree. */ +void oidtree_insert(struct oidtree *ot, const struct object_id *oid); + +/* Check whether the tree contains the given object ID. */ +bool oidtree_contains(struct oidtree *ot, const struct object_id *oid); + +/* + * Callback function used for `oidtree_each()`. Returning a non-zero exit code + * will cause iteration to stop. The exit code will be propagated to the caller + * of `oidtree_each()`. + */ +typedef int (*oidtree_each_cb)(const struct object_id *oid, + void *cb_data); + +/* + * Iterate through all object IDs in the tree whose prefix matches the given + * object ID prefix and invoke the callback function on each of them. + * + * Returns any non-zero exit code from the provided callback function. + */ +int oidtree_each(struct oidtree *ot, + const struct object_id *prefix, size_t prefix_hex_len, + oidtree_each_cb cb, void *cb_data); #endif /* OIDTREE_H */
diff --git a/oss-fuzz/fuzz-commit-graph.c b/oss-fuzz/fuzz-commit-graph.c index fb8b878..59bbb84 100644 --- a/oss-fuzz/fuzz-commit-graph.c +++ b/oss-fuzz/fuzz-commit-graph.c
@@ -10,6 +10,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { struct commit_graph *g; + memset(the_repository, 0, sizeof(*the_repository)); initialize_repository(the_repository); /*
diff --git a/pack-bitmap-write.c b/pack-bitmap-write.c index 4404921..8338d72 100644 --- a/pack-bitmap-write.c +++ b/pack-bitmap-write.c
@@ -306,7 +306,7 @@ struct bb_commit { static void clear_bb_commit(struct bb_commit *commit) { - free_commit_list(commit->reverse_edges); + commit_list_free(commit->reverse_edges); bitmap_free(commit->commit_mask); bitmap_free(commit->bitmap); } @@ -315,8 +315,7 @@ define_commit_slab(bb_data, struct bb_commit); struct bitmap_builder { struct bb_data data; - struct commit **commits; - size_t commits_nr, commits_alloc; + struct commit_stack commits; }; static void bitmap_builder_init(struct bitmap_builder *bb, @@ -329,8 +328,8 @@ static void bitmap_builder_init(struct bitmap_builder *bb, struct commit_list *r; unsigned int i, num_maximal = 0; - memset(bb, 0, sizeof(*bb)); init_bb_data(&bb->data); + commit_stack_init(&bb->commits); reset_revision_walk(); repo_init_revisions(writer->to_pack->repo, &revs, NULL); @@ -390,8 +389,7 @@ static void bitmap_builder_init(struct bitmap_builder *bb, if (c_ent->maximal) { num_maximal++; - ALLOC_GROW(bb->commits, bb->commits_nr + 1, bb->commits_alloc); - bb->commits[bb->commits_nr++] = commit; + commit_stack_push(&bb->commits, commit); } if (p) { @@ -416,7 +414,7 @@ static void bitmap_builder_init(struct bitmap_builder *bb, p_ent->maximal = 1; else { p_ent->maximal = 0; - free_commit_list(p_ent->reverse_edges); + commit_list_free(p_ent->reverse_edges); p_ent->reverse_edges = NULL; } @@ -438,8 +436,7 @@ static void bitmap_builder_init(struct bitmap_builder *bb, } for (r = reusable; r; r = r->next) { - ALLOC_GROW(bb->commits, bb->commits_nr + 1, bb->commits_alloc); - bb->commits[bb->commits_nr++] = r->item; + commit_stack_push(&bb->commits, r->item); } trace2_data_intmax("pack-bitmap-write", writer->repo, @@ -448,14 +445,13 @@ static void bitmap_builder_init(struct bitmap_builder *bb, "num_maximal_commits", num_maximal); release_revisions(&revs); - free_commit_list(reusable); + commit_list_free(reusable); } static void bitmap_builder_clear(struct bitmap_builder *bb) { deep_clear_bb_data(&bb->data, clear_bb_commit); - free(bb->commits); - bb->commits_nr = bb->commits_alloc = 0; + commit_stack_clear(&bb->commits); } static int fill_bitmap_tree(struct bitmap_writer *writer, @@ -478,7 +474,7 @@ static int fill_bitmap_tree(struct bitmap_writer *writer, return 0; bitmap_set(bitmap, pos); - if (parse_tree(tree) < 0) + if (repo_parse_tree(writer->repo, tree) < 0) die("unable to load tree object %s", oid_to_hex(&tree->object.oid)); init_tree_desc(&desc, &tree->object.oid, tree->buffer, tree->size); @@ -630,8 +626,8 @@ int bitmap_writer_build(struct bitmap_writer *writer) mapping = NULL; bitmap_builder_init(&bb, writer, old_bitmap); - for (i = bb.commits_nr; i > 0; i--) { - struct commit *commit = bb.commits[i-1]; + for (i = bb.commits.nr; i > 0; i--) { + struct commit *commit = bb.commits.items[i-1]; struct bb_commit *ent = bb_data_at(&bb.data, commit); struct commit *child; int reused = 0;
diff --git a/pack-bitmap.c b/pack-bitmap.c index 291e1a9..f6ec18d 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c
@@ -213,6 +213,28 @@ static uint32_t bitmap_num_objects(struct bitmap_index *index) return index->pack->num_objects; } +static uint32_t bitmap_name_hash(struct bitmap_index *index, uint32_t pos) +{ + if (bitmap_is_midx(index)) { + while (index && pos < index->midx->num_objects_in_base) { + ASSERT(bitmap_is_midx(index)); + index = index->base; + } + + if (!index) + BUG("NULL base bitmap for object position: %"PRIu32, pos); + + pos -= index->midx->num_objects_in_base; + if (pos >= index->midx->num_objects) + BUG("out-of-bounds midx bitmap object at %"PRIu32, pos); + } + + if (!index->hashes) + return 0; + + return get_be32(index->hashes + pos); +} + static struct repository *bitmap_repo(struct bitmap_index *bitmap_git) { if (bitmap_is_midx(bitmap_git)) @@ -419,11 +441,11 @@ char *midx_bitmap_filename(struct multi_pack_index *midx) struct strbuf buf = STRBUF_INIT; if (midx->has_chain) get_split_midx_filename_ext(midx->source, &buf, - get_midx_checksum(midx), + midx_get_checksum_hash(midx), MIDX_EXT_BITMAP); else get_midx_filename_ext(midx->source, &buf, - get_midx_checksum(midx), + midx_get_checksum_hash(midx), MIDX_EXT_BITMAP); return strbuf_detach(&buf, NULL); @@ -480,7 +502,7 @@ static int open_midx_bitmap_1(struct bitmap_index *bitmap_git, if (load_bitmap_header(bitmap_git) < 0) goto cleanup; - if (!hasheq(get_midx_checksum(bitmap_git->midx), bitmap_git->checksum, + if (!hasheq(midx_get_checksum_hash(bitmap_git->midx), bitmap_git->checksum, bitmap_repo(bitmap_git)->hash_algo)) { error(_("checksum doesn't match in MIDX and bitmap")); goto cleanup; @@ -1724,8 +1746,7 @@ static void show_objects_for_type( pack = bitmap_git->pack; } - if (bitmap_git->hashes) - hash = get_be32(bitmap_git->hashes + index_pos); + hash = bitmap_name_hash(bitmap_git, index_pos); show_reach(&oid, object_type, 0, hash, pack, ofs, payload); } @@ -1855,8 +1876,7 @@ static unsigned long get_size_by_pos(struct bitmap_index *bitmap_git, ofs = pack_pos_to_offset(pack, pos); } - if (packed_object_info(bitmap_repo(bitmap_git), pack, ofs, - &oi) < 0) { + if (packed_object_info(pack, ofs, &oi) < 0) { struct object_id oid; nth_bitmap_object_oid(bitmap_git, &oid, pack_pos_to_index(pack, pos)); @@ -2799,8 +2819,7 @@ void test_bitmap_walk(struct rev_info *revs) if (bitmap_is_midx(found)) fprintf_ln(stderr, "Located via MIDX '%s'.", - hash_to_hex_algop(get_midx_checksum(found->midx), - revs->repo->hash_algo)); + midx_get_checksum_hex(found->midx)); else fprintf_ln(stderr, "Located via pack '%s'.", hash_to_hex_algop(found->pack->hash, @@ -3124,8 +3143,8 @@ uint32_t *create_bitmap_mapping(struct bitmap_index *bitmap_git, if (oe) { reposition[i] = oe_in_pack_pos(mapping, oe) + 1; - if (bitmap_git->hashes && !oe->hash) - oe->hash = get_be32(bitmap_git->hashes + index_pos); + if (!oe->hash) + oe->hash = bitmap_name_hash(bitmap_git, index_pos); } } @@ -3294,7 +3313,7 @@ int bitmap_is_midx(struct bitmap_index *bitmap_git) return !!bitmap_git->midx; } -const struct string_list *bitmap_preferred_tips(struct repository *r) +static const struct string_list *bitmap_preferred_tips(struct repository *r) { const struct string_list *dest; @@ -3303,6 +3322,34 @@ const struct string_list *bitmap_preferred_tips(struct repository *r) return NULL; } +void for_each_preferred_bitmap_tip(struct repository *repo, + refs_for_each_cb cb, void *cb_data) +{ + struct refs_for_each_ref_options opts = { 0 }; + struct string_list_item *item; + const struct string_list *preferred_tips; + struct strbuf buf = STRBUF_INIT; + + preferred_tips = bitmap_preferred_tips(repo); + if (!preferred_tips) + return; + + for_each_string_list_item(item, preferred_tips) { + opts.prefix = item->string; + + if (!ends_with(opts.prefix, "/")) { + strbuf_reset(&buf); + strbuf_addf(&buf, "%s/", opts.prefix); + opts.prefix = buf.buf; + } + + refs_for_each_ref_ext(get_main_ref_store(repo), + cb, cb_data, &opts); + } + + strbuf_release(&buf); +} + int bitmap_is_preferred_refname(struct repository *r, const char *refname) { const struct string_list *preferred_tips = bitmap_preferred_tips(r);
diff --git a/pack-bitmap.h b/pack-bitmap.h index 1bd7a79..a95e1c2 100644 --- a/pack-bitmap.h +++ b/pack-bitmap.h
@@ -5,6 +5,7 @@ #include "khash.h" #include "pack.h" #include "pack-objects.h" +#include "refs.h" #include "string-list.h" struct commit; @@ -99,6 +100,13 @@ int for_each_bitmapped_object(struct bitmap_index *bitmap_git, show_reachable_fn show_reach, void *payload); +/* + * Iterate over all references that are configured as preferred bitmap tips via + * "pack.preferBitmapTips" and invoke the callback on each function. + */ +void for_each_preferred_bitmap_tip(struct repository *repo, + refs_for_each_cb cb, void *cb_data); + #define GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL \ "GIT_TEST_PACK_USE_BITMAP_BOUNDARY_TRAVERSAL" @@ -182,7 +190,6 @@ char *pack_bitmap_filename(struct packed_git *p); int bitmap_is_midx(struct bitmap_index *bitmap_git); -const struct string_list *bitmap_preferred_tips(struct repository *r); int bitmap_is_preferred_refname(struct repository *r, const char *refname); int verify_bitmap_files(struct repository *r);
diff --git a/pack-check.c b/pack-check.c index 67cb2cf..7378c80 100644 --- a/pack-check.c +++ b/pack-check.c
@@ -9,6 +9,7 @@ #include "packfile.h" #include "object-file.h" #include "odb.h" +#include "odb/streaming.h" struct idx_entry { off_t offset; @@ -104,6 +105,7 @@ static int verify_packfile(struct repository *r, QSORT(entries, nr_objects, compare_entries); for (i = 0; i < nr_objects; i++) { + struct odb_read_stream *stream = NULL; void *data; struct object_id oid; enum object_type type; @@ -152,7 +154,9 @@ static int verify_packfile(struct repository *r, type) < 0) err = error("packed %s from %s is corrupt", oid_to_hex(&oid), p->pack_name); - else if (!data && stream_object_signature(r, &oid) < 0) + else if (!data && + (packfile_read_object_stream(&stream, &oid, p, entries[i].offset) < 0 || + stream_object_signature(r, stream, &oid) < 0)) err = error("packed %s from %s is corrupt", oid_to_hex(&oid), p->pack_name); else if (fn) { @@ -163,12 +167,14 @@ static int verify_packfile(struct repository *r, } if (((base_count + i) & 1023) == 0) display_progress(progress, base_count + i); - free(data); + if (stream) + odb_read_stream_close(stream); + free(data); } + display_progress(progress, base_count + i); free(entries); - return err; }
diff --git a/pack-refs.c b/pack-refs.c index 1a5e07d..eb6b2ba 100644 --- a/pack-refs.c +++ b/pack-refs.c
@@ -14,10 +14,10 @@ int pack_refs_core(int argc, { struct ref_exclusions excludes = REF_EXCLUSIONS_INIT; struct string_list included_refs = STRING_LIST_INIT_NODUP; - struct pack_refs_opts pack_refs_opts = { + struct refs_optimize_opts optimize_opts = { .exclusions = &excludes, .includes = &included_refs, - .flags = PACK_REFS_PRUNE, + .flags = REFS_OPTIMIZE_PRUNE, }; struct string_list option_excluded_refs = STRING_LIST_INIT_NODUP; struct string_list_item *item; @@ -26,9 +26,9 @@ int pack_refs_core(int argc, struct option opts[] = { OPT_BOOL(0, "all", &pack_all, N_("pack everything")), - OPT_BIT(0, "prune", &pack_refs_opts.flags, N_("prune loose refs (default)"), PACK_REFS_PRUNE), - OPT_BIT(0, "auto", &pack_refs_opts.flags, N_("auto-pack refs as needed"), PACK_REFS_AUTO), - OPT_STRING_LIST(0, "include", pack_refs_opts.includes, N_("pattern"), + OPT_BIT(0, "prune", &optimize_opts.flags, N_("prune loose refs (default)"), REFS_OPTIMIZE_PRUNE), + OPT_BIT(0, "auto", &optimize_opts.flags, N_("auto-pack refs as needed"), REFS_OPTIMIZE_AUTO), + OPT_STRING_LIST(0, "include", optimize_opts.includes, N_("pattern"), N_("references to include")), OPT_STRING_LIST(0, "exclude", &option_excluded_refs, N_("pattern"), N_("references to exclude")), @@ -39,15 +39,15 @@ int pack_refs_core(int argc, usage_with_options(usage_opts, opts); for_each_string_list_item(item, &option_excluded_refs) - add_ref_exclusion(pack_refs_opts.exclusions, item->string); + add_ref_exclusion(optimize_opts.exclusions, item->string); if (pack_all) - string_list_append(pack_refs_opts.includes, "*"); + string_list_append(optimize_opts.includes, "*"); - if (!pack_refs_opts.includes->nr) - string_list_append(pack_refs_opts.includes, "refs/tags/*"); + if (!optimize_opts.includes->nr) + string_list_append(optimize_opts.includes, "refs/tags/*"); - ret = refs_optimize(get_main_ref_store(repo), &pack_refs_opts); + ret = refs_optimize(get_main_ref_store(repo), &optimize_opts); clear_ref_exclusions(&excludes); string_list_clear(&included_refs, 0);
diff --git a/pack-revindex.c b/pack-revindex.c index d0791cc..1b67863 100644 --- a/pack-revindex.c +++ b/pack-revindex.c
@@ -75,7 +75,7 @@ static void sort_revindex(struct revindex_entry *entries, unsigned n, off_t max) for (bits = 0; max >> bits; bits += DIGIT_SIZE) { unsigned i; - memset(pos, 0, BUCKETS * sizeof(*pos)); + MEMZERO_ARRAY(pos, BUCKETS); /* * We want pos[i] to store the index of the last element that @@ -277,6 +277,10 @@ int load_pack_revindex_from_disk(struct packed_git *p) { char *revindex_name; int ret; + + if (p->revindex_data) + return 0; + if (open_pack_index(p)) return -1; @@ -390,11 +394,11 @@ int load_midx_revindex(struct multi_pack_index *m) if (m->has_chain) get_split_midx_filename_ext(m->source, &revindex_name, - get_midx_checksum(m), + midx_get_checksum_hash(m), MIDX_EXT_REV); else get_midx_filename_ext(m->source, &revindex_name, - get_midx_checksum(m), + midx_get_checksum_hash(m), MIDX_EXT_REV); ret = load_revindex_from_disk(m->source->odb->repo->hash_algo, @@ -544,7 +548,7 @@ static int midx_key_to_pack_pos(struct multi_pack_index *m, struct midx_pack_key *key, uint32_t *pos) { - uint32_t *found; + const uint32_t *found; if (key->pack >= m->num_packs + m->num_packs_in_base) BUG("MIDX pack lookup out of bounds (%"PRIu32" >= %"PRIu32")",
diff --git a/pack-revindex.h b/pack-revindex.h index 422c248..0042892 100644 --- a/pack-revindex.h +++ b/pack-revindex.h
@@ -72,7 +72,8 @@ int verify_pack_revindex(struct packed_git *p); * multi-pack index by mmap-ing it and assigning pointers in the * multi_pack_index to point at it. * - * A negative number is returned on error. + * A negative number is returned on error. A positive number is returned in + * case the multi-pack-index does not have a reverse index. */ int load_midx_revindex(struct multi_pack_index *m);
diff --git a/packfile.c b/packfile.c index 1ae2b2f..48c8874 100644 --- a/packfile.c +++ b/packfile.c
@@ -20,6 +20,7 @@ #include "tree.h" #include "object-file.h" #include "odb.h" +#include "odb/streaming.h" #include "midx.h" #include "commit-graph.h" #include "pack-revindex.h" @@ -47,6 +48,89 @@ 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, @@ -271,15 +355,19 @@ static void scan_windows(struct packed_git *p, } } -static int unuse_one_window(struct packed_git *current) +static int unuse_one_window(struct object_database *odb) { - struct packed_git *p, *lru_p = NULL; + struct odb_source *source; + struct packfile_list_entry *e; + struct packed_git *lru_p = NULL; struct pack_window *lru_w = NULL, *lru_l = NULL; - if (current) - scan_windows(current, &lru_p, &lru_w, &lru_l); - for (p = current->repo->objects->packfiles->packs; p; p = p->next) - scan_windows(p, &lru_p, &lru_w, &lru_l); + for (source = odb->sources; source; source = source->next) { + struct odb_source_files *files = odb_source_files_downcast(source); + for (e = files->packed->packs.head; e; e = e->next) + scan_windows(e->pack, &lru_p, &lru_w, &lru_l); + } + if (lru_p) { munmap(lru_w->base, lru_w->len); pack_mapped -= lru_w->len; @@ -359,21 +447,6 @@ void close_pack(struct packed_git *p) oidset_clear(&p->bad_objects); } -void close_object_store(struct object_database *o) -{ - struct odb_source *source; - - packfile_store_close(o->packfiles); - - for (source = o->sources; source; source = source->next) { - if (source->midx) - close_midx(source->midx); - source->midx = NULL; - } - - close_commit_graph(o); -} - void unlink_pack_path(const char *pack_name, int force_delete) { static const char *exts[] = {".idx", ".pack", ".rev", ".keep", ".bitmap", ".promisor", ".mtimes"}; @@ -459,14 +532,19 @@ static void find_lru_pack(struct packed_git *p, struct packed_git **lru_p, struc static int close_one_pack(struct repository *r) { - struct packed_git *p, *lru_p = NULL; + struct odb_source *source; + struct packfile_list_entry *e; + struct packed_git *lru_p = NULL; struct pack_window *mru_w = NULL; int accept_windows_inuse = 1; - for (p = r->objects->packfiles->packs; p; p = p->next) { - if (p->pack_fd == -1) - continue; - find_lru_pack(p, &lru_p, &mru_w, &accept_windows_inuse); + for (source = r->objects->sources; source; source = source->next) { + struct odb_source_files *files = odb_source_files_downcast(source); + for (e = files->packed->packs.head; e; e = e->next) { + if (e->pack->pack_fd == -1) + continue; + find_lru_pack(e->pack, &lru_p, &mru_w, &accept_windows_inuse); + } } if (lru_p) @@ -669,8 +747,8 @@ unsigned char *use_pack(struct packed_git *p, win->len = (size_t)len; pack_mapped += win->len; - while (settings->packed_git_limit < pack_mapped - && unuse_one_window(p)) + while (settings->packed_git_limit < pack_mapped && + unuse_one_window(p->repo->objects)) ; /* nothing */ win->base = xmmap_gently(NULL, win->len, PROT_READ, MAP_PRIVATE, @@ -785,11 +863,8 @@ void packfile_store_add_pack(struct packfile_store *store, if (pack->pack_fd != -1) pack_open_fds++; - pack->next = store->packs; - store->packs = pack; - - hashmap_entry_init(&pack->packmap_ent, strhash(pack->pack_name)); - hashmap_add(&store->map, &pack->packmap_ent); + packfile_list_append(&store->packs, pack); + strmap_put(&store->packs_by_path, pack->pack_name, pack); } struct packed_git *packfile_store_load_pack(struct packfile_store *store, @@ -806,10 +881,9 @@ struct packed_git *packfile_store_load_pack(struct packfile_store *store, strbuf_strip_suffix(&key, ".idx"); strbuf_addstr(&key, ".pack"); - p = hashmap_get_entry_from_hash(&store->map, strhash(key.buf), key.buf, - struct packed_git, packmap_ent); + p = strmap_get(&store->packs_by_path, key.buf); if (!p) { - p = add_packed_git(store->odb->repo, idx_path, + p = add_packed_git(store->source->odb->repo, idx_path, strlen(idx_path), local); if (p) packfile_store_add_pack(store, p); @@ -908,23 +982,23 @@ void for_each_file_in_pack_dir(const char *objdir, } struct prepare_pack_data { - struct repository *r; + struct odb_source *source; struct string_list *garbage; - int local; - struct multi_pack_index *m; }; 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") && - !(data->m && midx_contains_pack(data->m, file_name))) { + !(files->packed->midx && + midx_contains_pack(files->packed->midx, file_name))) { char *trimmed_path = xstrndup(full_name, full_name_len); - packfile_store_load_pack(data->r->objects->packfiles, - trimmed_path, data->local); + packfile_store_load_pack(files->packed, + trimmed_path, data->source->local); free(trimmed_path); } @@ -953,10 +1027,8 @@ static void prepare_packed_git_one(struct odb_source *source) { struct string_list garbage = STRING_LIST_INIT_DUP; struct prepare_pack_data data = { - .m = source->midx, - .r = source->odb->repo, + .source = source, .garbage = &garbage, - .local = source->local, }; for_each_file_in_pack_dir(source->path, prepare_pack, &data); @@ -965,9 +1037,10 @@ static void prepare_packed_git_one(struct odb_source *source) string_list_clear(data.garbage, 0); } -DEFINE_LIST_SORT(static, sort_packs, struct packed_git, next); +DEFINE_LIST_SORT(static, sort_packs, struct packfile_list_entry, next); -static int sort_pack(const struct packed_git *a, const struct packed_git *b) +static int sort_pack(const struct packfile_list_entry *a, + const struct packfile_list_entry *b) { int st; @@ -977,7 +1050,7 @@ static int sort_pack(const struct packed_git *a, const struct packed_git *b) * remote ones could be on a network mounted filesystem. * Favor local ones for these reasons. */ - st = a->pack_local - b->pack_local; + st = a->pack->pack_local - b->pack->pack_local; if (st) return -st; @@ -986,38 +1059,26 @@ static int sort_pack(const struct packed_git *a, const struct packed_git *b) * and more recent objects tend to get accessed more * often. */ - if (a->mtime < b->mtime) + if (a->pack->mtime < b->pack->mtime) return 1; - else if (a->mtime == b->mtime) + else if (a->pack->mtime == b->pack->mtime) return 0; return -1; } -static void packfile_store_prepare_mru(struct packfile_store *store) -{ - struct packed_git *p; - - INIT_LIST_HEAD(&store->mru); - - for (p = store->packs; p; p = p->next) - list_add_tail(&p->mru, &store->mru); -} - void packfile_store_prepare(struct packfile_store *store) { - struct odb_source *source; - if (store->initialized) return; - odb_prepare_alternates(store->odb); - for (source = store->odb->sources; source; source = source->next) { - prepare_multi_pack_index_one(source); - prepare_packed_git_one(source); - } - sort_packs(&store->packs, sort_pack); + prepare_multi_pack_index_one(store->source); + prepare_packed_git_one(store->source); - packfile_store_prepare_mru(store); + 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; } @@ -1027,58 +1088,48 @@ void packfile_store_reprepare(struct packfile_store *store) packfile_store_prepare(store); } -struct packed_git *packfile_store_get_packs(struct packfile_store *store) +struct packfile_list_entry *packfile_store_get_packs(struct packfile_store *store) { packfile_store_prepare(store); - for (struct odb_source *source = store->odb->sources; source; source = source->next) { - struct multi_pack_index *m = source->midx; - if (!m) - continue; + if (store->midx) { + struct multi_pack_index *m = store->midx; for (uint32_t i = 0; i < m->num_packs + m->num_packs_in_base; i++) prepare_midx_pack(m, i); } - return store->packs; + return store->packs.head; } -struct list_head *packfile_store_get_packs_mru(struct packfile_store *store) +int packfile_store_count_objects(struct packfile_store *store, + enum odb_count_objects_flags flags UNUSED, + unsigned long *out) { - packfile_store_prepare(store); - return &store->mru; -} + struct packfile_list_entry *e; + struct multi_pack_index *m; + unsigned long count = 0; + int ret; -/* - * Give a fast, rough count of the number of objects in the repository. This - * ignores loose objects completely. If you have a lot of them, then either - * you should repack because your performance will be awful, or they are - * all unreachable objects about to be pruned, in which case they're not really - * interesting as a measure of repo size in the first place. - */ -unsigned long repo_approximate_object_count(struct repository *r) -{ - if (!r->objects->approximate_object_count_valid) { - struct odb_source *source; - unsigned long count = 0; - struct packed_git *p; + m = get_multi_pack_index(store->source); + if (m) + count += m->num_objects + m->num_objects_in_base; - packfile_store_prepare(r->objects->packfiles); - - for (source = r->objects->sources; source; source = source->next) { - struct multi_pack_index *m = get_multi_pack_index(source); - if (m) - count += m->num_objects; + 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; } - for (p = r->objects->packfiles->packs; p; p = p->next) { - if (open_pack_index(p)) - continue; - count += p->num_objects; - } - r->objects->approximate_object_count = count; - r->objects->approximate_object_count_valid = 1; + count += e->pack->num_objects; } - return r->objects->approximate_object_count; + + *out = count; + ret = 0; + +out: + return ret; } unsigned long unpack_object_header_buffer(const unsigned char *buf, @@ -1195,11 +1246,17 @@ void mark_bad_packed_object(struct packed_git *p, const struct object_id *oid) const struct packed_git *has_packed_and_bad(struct repository *r, const struct object_id *oid) { - struct packed_git *p; + struct odb_source *source; - for (p = r->objects->packfiles->packs; p; p = p->next) - if (oidset_contains(&p->bad_objects, oid)) - return p; + for (source = r->objects->sources; source; source = source->next) { + struct odb_source_files *files = odb_source_files_downcast(source); + struct packfile_list_entry *e; + + for (e = files->packed->packs.head; e; e = e->next) + if (oidset_contains(&e->pack->bad_objects, oid)) + return e->pack; + } + return NULL; } @@ -1525,24 +1582,26 @@ static void add_delta_base_cache(struct packed_git *p, off_t base_offset, hashmap_add(&delta_base_cache, &ent->ent); } -int packed_object_info(struct repository *r, struct packed_git *p, - off_t obj_offset, struct object_info *oi) +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) { struct pack_window *w_curs = NULL; unsigned long size; off_t curpos = obj_offset; - enum object_type type; + enum object_type type = OBJ_NONE; + uint32_t pack_pos; + int ret; /* * We always get the representation type, but only convert it to * a "real" type later if the caller is interested. */ if (oi->contentp) { - *oi->contentp = cache_or_unpack_entry(r, p, obj_offset, oi->sizep, + *oi->contentp = cache_or_unpack_entry(p->repo, p, obj_offset, oi->sizep, &type); if (!*oi->contentp) type = OBJ_BAD; - } else { + } else if (oi->sizep || oi->typep || oi->delta_base_oid) { type = unpack_object_header(p, &w_curs, &curpos, &size); } @@ -1552,12 +1611,12 @@ int packed_object_info(struct repository *r, struct packed_git *p, off_t base_offset = get_delta_base(p, &w_curs, &tmp_pos, type, obj_offset); if (!base_offset) { - type = OBJ_BAD; + ret = -1; goto out; } *oi->sizep = get_size_from_delta(p, &w_curs, tmp_pos); if (*oi->sizep == 0) { - type = OBJ_BAD; + ret = -1; goto out; } } else { @@ -1565,26 +1624,45 @@ int packed_object_info(struct repository *r, struct packed_git *p, } } - if (oi->disk_sizep) { - uint32_t pos; - if (offset_to_pack_pos(p, obj_offset, &pos) < 0) { + if (oi->disk_sizep || (oi->mtimep && p->is_cruft)) { + if (offset_to_pack_pos(p, obj_offset, &pack_pos) < 0) { error("could not find object at offset %"PRIuMAX" " "in pack %s", (uintmax_t)obj_offset, p->pack_name); - type = OBJ_BAD; + ret = -1; goto out; } + } - *oi->disk_sizep = pack_pos_to_offset(p, pos + 1) - obj_offset; + if (oi->disk_sizep) + *oi->disk_sizep = pack_pos_to_offset(p, pack_pos + 1) - obj_offset; + + if (oi->mtimep) { + if (p->is_cruft) { + uint32_t index_pos; + + if (load_pack_mtimes(p) < 0) + die(_("could not load .mtimes for cruft pack '%s'"), + pack_basename(p)); + + if (maybe_index_pos) + index_pos = *maybe_index_pos; + else + index_pos = pack_pos_to_index(p, pack_pos); + + *oi->mtimep = nth_packed_mtime(p, index_pos); + } else { + *oi->mtimep = p->mtime; + } } if (oi->typep) { enum object_type ptot; - ptot = packed_to_object_type(r, p, obj_offset, + ptot = packed_to_object_type(p->repo, p, obj_offset, type, &w_curs, curpos); if (oi->typep) *oi->typep = ptot; if (ptot < 0) { - type = OBJ_BAD; + ret = -1; goto out; } } @@ -1594,19 +1672,43 @@ int packed_object_info(struct repository *r, struct packed_git *p, if (get_delta_base_oid(p, &w_curs, curpos, oi->delta_base_oid, type, obj_offset) < 0) { - type = OBJ_BAD; + ret = -1; goto out; } } else oidclr(oi->delta_base_oid, p->repo->hash_algo); } - oi->whence = in_delta_base_cache(p, obj_offset) ? OI_DBCACHED : - OI_PACKED; + oi->whence = OI_PACKED; + oi->u.packed.offset = obj_offset; + oi->u.packed.pack = p; + + 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; + } + + ret = 0; out: unuse_pack(&w_curs); - return type; + return ret; +} + +int packed_object_info(struct packed_git *p, off_t obj_offset, + struct object_info *oi) +{ + return packed_object_info_with_index_pos(p, obj_offset, NULL, oi); } static void *unpack_compressed_entry(struct packed_git *p, @@ -2007,19 +2109,6 @@ int is_pack_valid(struct packed_git *p) return !open_packed_git(p); } -struct packed_git *find_oid_pack(const struct object_id *oid, - struct packed_git *packs) -{ - struct packed_git *p; - - for (p = packs; p; p = p->next) { - if (find_pack_entry_one(oid, p)) - return p; - } - return NULL; - -} - static int fill_pack_entry(const struct object_id *oid, struct pack_entry *e, struct packed_git *p) @@ -2048,48 +2137,100 @@ static int fill_pack_entry(const struct object_id *oid, return 1; } -int find_pack_entry(struct repository *r, const struct object_id *oid, struct pack_entry *e) +static int find_pack_entry(struct packfile_store *store, + const struct object_id *oid, + struct pack_entry *e) { - struct list_head *pos; + struct packfile_list_entry *l; - packfile_store_prepare(r->objects->packfiles); + packfile_store_prepare(store); + if (store->midx && fill_midx_entry(store->midx, oid, e)) + return 1; - for (struct odb_source *source = r->objects->sources; source; source = source->next) - if (source->midx && fill_midx_entry(source->midx, oid, e)) - return 1; + for (l = store->packs.head; l; l = l->next) { + struct packed_git *p = l->pack; - if (!r->objects->packfiles->packs) - return 0; - - list_for_each(pos, &r->objects->packfiles->mru) { - struct packed_git *p = list_entry(pos, struct packed_git, mru); if (!p->multi_pack_index && fill_pack_entry(oid, e, p)) { - list_move(&p->mru, &r->objects->packfiles->mru); + if (!store->skip_mru_updates) + packfile_list_prepend(&store->packs, p); return 1; } } + return 0; } -static void maybe_invalidate_kept_pack_cache(struct repository *r, - unsigned flags) +int packfile_store_freshen_object(struct packfile_store *store, + const struct object_id *oid) { - if (!r->objects->packfiles->kept_cache.packs) - return; - if (r->objects->packfiles->kept_cache.flags == flags) - return; - FREE_AND_NULL(r->objects->packfiles->kept_cache.packs); - r->objects->packfiles->kept_cache.flags = 0; + 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; } -struct packed_git **kept_pack_cache(struct repository *r, unsigned flags) +int packfile_store_read_object_info(struct packfile_store *store, + const struct object_id *oid, + struct object_info *oi, + enum object_info_flags flags) { - maybe_invalidate_kept_pack_cache(r, flags); + struct pack_entry e; + int ret; - if (!r->objects->packfiles->kept_cache.packs) { + /* + * 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, + unsigned flags) +{ + if (!store->kept_cache.packs) + return; + if (store->kept_cache.flags == flags) + return; + FREE_AND_NULL(store->kept_cache.packs); + store->kept_cache.flags = 0; +} + +struct packed_git **packfile_store_get_kept_pack_cache(struct packfile_store *store, + unsigned flags) +{ + maybe_invalidate_kept_pack_cache(store, flags); + + if (!store->kept_cache.packs) { struct packed_git **packs = NULL; + struct packfile_list_entry *e; size_t nr = 0, alloc = 0; - struct packed_git *p; /* * We want "all" packs here, because we need to cover ones that @@ -2099,9 +2240,12 @@ struct packed_git **kept_pack_cache(struct repository *r, unsigned flags) * covers, one kept and one not kept, but the midx returns only * the non-kept version. */ - repo_for_each_pack(r, p) { - if ((p->pack_keep && (flags & ON_DISK_KEEP_PACKS)) || - (p->pack_keep_in_core && (flags & IN_CORE_KEEP_PACKS))) { + for (e = packfile_store_get_packs(store); e; e = e->next) { + struct packed_git *p = e->pack; + + if ((p->pack_keep && (flags & KEPT_PACK_ON_DISK)) || + (p->pack_keep_in_core && (flags & KEPT_PACK_IN_CORE)) || + (p->pack_keep_in_core_open && (flags & KEPT_PACK_IN_CORE_OPEN))) { ALLOC_GROW(packs, nr + 1, alloc); packs[nr++] = p; } @@ -2109,50 +2253,59 @@ struct packed_git **kept_pack_cache(struct repository *r, unsigned flags) ALLOC_GROW(packs, nr + 1, alloc); packs[nr] = NULL; - r->objects->packfiles->kept_cache.packs = packs; - r->objects->packfiles->kept_cache.flags = flags; + store->kept_cache.packs = packs; + store->kept_cache.flags = flags; } - return r->objects->packfiles->kept_cache.packs; + return store->kept_cache.packs; } -int find_kept_pack_entry(struct repository *r, - const struct object_id *oid, - unsigned flags, - struct pack_entry *e) +int has_object_pack(struct repository *r, const struct object_id *oid) { - struct packed_git **cache; + struct odb_source *source; + struct pack_entry e; - for (cache = kept_pack_cache(r, flags); *cache; cache++) { - struct packed_git *p = *cache; - if (fill_pack_entry(oid, e, p)) - return 1; + 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; } return 0; } -int has_object_pack(struct repository *r, const struct object_id *oid) -{ - struct pack_entry e; - return find_pack_entry(r, oid, &e); -} - int has_object_kept_pack(struct repository *r, const struct object_id *oid, unsigned flags) { + struct odb_source *source; struct pack_entry e; - return find_kept_pack_entry(r, oid, flags, &e); + + for (source = r->objects->sources; source; source = source->next) { + struct odb_source_files *files = odb_source_files_downcast(source); + struct packed_git **cache; + + cache = packfile_store_get_kept_pack_cache(files->packed, flags); + + for (; *cache; cache++) { + struct packed_git *p = *cache; + if (fill_pack_entry(oid, &e, p)) + return 1; + } + } + + return 0; } int for_each_object_in_pack(struct packed_git *p, each_packed_object_fn cb, void *data, - enum for_each_object_flags flags) + unsigned flags) { uint32_t i; int r = 0; - if (flags & FOR_EACH_OBJECT_PACK_ORDER) { + if (flags & ODB_FOR_EACH_OBJECT_PACK_ORDER) { if (load_pack_revindex(p->repo, p)) return -1; } @@ -2173,7 +2326,7 @@ int for_each_object_in_pack(struct packed_git *p, * - in pack-order, it is pack position, which we must * convert to an index position in order to get the oid. */ - if (flags & FOR_EACH_OBJECT_PACK_ORDER) + if (flags & ODB_FOR_EACH_OBJECT_PACK_ORDER) index_pos = pack_pos_to_index(p, i); else index_pos = i; @@ -2189,57 +2342,399 @@ int for_each_object_in_pack(struct packed_git *p, return r; } -int for_each_packed_object(struct repository *repo, each_packed_object_fn cb, - void *data, enum for_each_object_flags flags) -{ - struct packed_git *p; - int r = 0; - int pack_errors = 0; +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; +}; - repo_for_each_pack(repo, p) { - if ((flags & FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local) +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; - if ((flags & FOR_EACH_OBJECT_PROMISOR_ONLY) && + + 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 ((flags & FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) && + if ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) && p->pack_keep_in_core) continue; - if ((flags & FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) && + 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; } - r = for_each_object_in_pack(p, cb, data, flags); - if (r) - break; + + ret = for_each_object_in_pack(p, packfile_store_for_each_object_wrapper, + &data, opts->flags); + if (ret) + goto out; } - return r ? r : pack_errors; + + ret = 0; + +out: + store->skip_mru_updates = false; + + if (!ret && pack_errors) + ret = -1; + return ret; } -static int add_promisor_object(const struct object_id *oid, - struct packed_git *pack, - uint32_t pos UNUSED, - void *set_) +static int extend_abbrev_len(const struct object_id *a, + const struct object_id *b, + unsigned *out) { - struct oidset *set = set_; + 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; +}; + +static int add_promisor_object(const struct object_id *oid, + struct object_info *oi UNUSED, + void *cb_data) +{ + struct add_promisor_object_data *data = cb_data; struct object *obj; int we_parsed_object; - obj = lookup_object(pack->repo, oid); + obj = lookup_object(data->repo, oid); if (obj && obj->parsed) { we_parsed_object = 0; } else { we_parsed_object = 1; - obj = parse_object(pack->repo, oid); + obj = parse_object_with_flags(data->repo, oid, + PARSE_OBJECT_SKIP_HASH_CHECK); } if (!obj) return 1; - oidset_insert(set, oid); + oidset_insert(data->set, oid); /* * If this is a tree, commit, or tag, the objects it refers @@ -2257,19 +2752,19 @@ static int add_promisor_object(const struct object_id *oid, */ return 0; while (tree_entry_gently(&desc, &entry)) - oidset_insert(set, &entry.oid); + oidset_insert(data->set, &entry.oid); if (we_parsed_object) free_tree_buffer(tree); } else if (obj->type == OBJ_COMMIT) { struct commit *commit = (struct commit *) obj; struct commit_list *parents = commit->parents; - oidset_insert(set, get_commit_tree_oid(commit)); + oidset_insert(data->set, get_commit_tree_oid(commit)); for (; parents; parents = parents->next) - oidset_insert(set, &parents->item->object.oid); + oidset_insert(data->set, &parents->item->object.oid); } else if (obj->type == OBJ_TAG) { struct tag *tag = (struct tag *) obj; - oidset_insert(set, get_tagged_oid(tag)); + oidset_insert(data->set, get_tagged_oid(tag)); } return 0; } @@ -2281,10 +2776,13 @@ int is_promisor_object(struct repository *r, const struct object_id *oid) if (!promisor_objects_prepared) { if (repo_has_promisor_remote(r)) { - for_each_packed_object(r, add_promisor_object, - &promisor_objects, - FOR_EACH_OBJECT_PROMISOR_ONLY | - FOR_EACH_OBJECT_PACK_ORDER); + struct add_promisor_object_data data = { + .repo = r, + .set = &promisor_objects, + }; + + odb_for_each_object(r->objects, NULL, add_promisor_object, &data, + ODB_FOR_EACH_OBJECT_PROMISOR_ONLY | ODB_FOR_EACH_OBJECT_PACK_ORDER); } promisor_objects_prepared = 1; } @@ -2311,45 +2809,169 @@ int parse_pack_header_option(const char *in, unsigned char *out, unsigned int *l return 0; } -static int pack_map_entry_cmp(const void *cmp_data UNUSED, - const struct hashmap_entry *entry, - const struct hashmap_entry *entry2, - const void *keydata) -{ - const char *key = keydata; - const struct packed_git *pg1, *pg2; - - pg1 = container_of(entry, const struct packed_git, packmap_ent); - pg2 = container_of(entry2, const struct packed_git, packmap_ent); - - return strcmp(pg1->pack_name, key ? key : pg2->pack_name); -} - -struct packfile_store *packfile_store_new(struct object_database *odb) +struct packfile_store *packfile_store_new(struct odb_source *source) { struct packfile_store *store; CALLOC_ARRAY(store, 1); - store->odb = odb; - INIT_LIST_HEAD(&store->mru); - hashmap_init(&store->map, pack_map_entry_cmp, NULL, 0); + store->source = source; + strmap_init(&store->packs_by_path); return store; } void packfile_store_free(struct packfile_store *store) { - for (struct packed_git *p = store->packs, *next; p; p = next) { - next = p->next; - free(p); - } - hashmap_clear(&store->map); + 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 packed_git *p = store->packs; p; p = p->next) { - if (p->do_not_close) + 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(p); + 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; + git_zstream z; + enum { + ODB_PACKED_READ_STREAM_UNINITIALIZED, + ODB_PACKED_READ_STREAM_INUSE, + ODB_PACKED_READ_STREAM_DONE, + ODB_PACKED_READ_STREAM_ERROR, + } z_state; + off_t pos; +}; + +static ssize_t read_istream_pack_non_delta(struct odb_read_stream *_st, char *buf, + size_t sz) +{ + struct odb_packed_read_stream *st = (struct odb_packed_read_stream *)_st; + size_t total_read = 0; + + switch (st->z_state) { + case ODB_PACKED_READ_STREAM_UNINITIALIZED: + memset(&st->z, 0, sizeof(st->z)); + git_inflate_init(&st->z); + st->z_state = ODB_PACKED_READ_STREAM_INUSE; + break; + case ODB_PACKED_READ_STREAM_DONE: + return 0; + case ODB_PACKED_READ_STREAM_ERROR: + return -1; + case ODB_PACKED_READ_STREAM_INUSE: + break; + } + + while (total_read < sz) { + int status; + struct pack_window *window = NULL; + unsigned char *mapped; + + mapped = use_pack(st->pack, &window, + st->pos, &st->z.avail_in); + + st->z.next_out = (unsigned char *)buf + total_read; + st->z.avail_out = sz - total_read; + st->z.next_in = mapped; + status = git_inflate(&st->z, Z_FINISH); + + st->pos += st->z.next_in - mapped; + total_read = st->z.next_out - (unsigned char *)buf; + unuse_pack(&window); + + if (status == Z_STREAM_END) { + git_inflate_end(&st->z); + st->z_state = ODB_PACKED_READ_STREAM_DONE; + break; + } + + /* + * Unlike the loose object case, we do not have to worry here + * about running out of input bytes and spinning infinitely. If + * we get Z_BUF_ERROR due to too few input bytes, then we'll + * replenish them in the next use_pack() call when we loop. If + * we truly hit the end of the pack (i.e., because it's corrupt + * or truncated), then use_pack() catches that and will die(). + */ + if (status != Z_OK && status != Z_BUF_ERROR) { + git_inflate_end(&st->z); + st->z_state = ODB_PACKED_READ_STREAM_ERROR; + return -1; + } + } + return total_read; +} + +static int close_istream_pack_non_delta(struct odb_read_stream *_st) +{ + struct odb_packed_read_stream *st = (struct odb_packed_read_stream *)_st; + if (st->z_state == ODB_PACKED_READ_STREAM_INUSE) + git_inflate_end(&st->z); + return 0; +} + +int packfile_read_object_stream(struct odb_read_stream **out, + const struct object_id *oid, + struct packed_git *pack, + off_t offset) +{ + struct odb_packed_read_stream *stream; + struct pack_window *window = NULL; + enum object_type in_pack_type; + unsigned long size; + + in_pack_type = unpack_object_header(pack, &window, &offset, &size); + unuse_pack(&window); + + if (repo_settings_get_big_file_threshold(pack->repo) >= size) + return -1; + + switch (in_pack_type) { + default: + return -1; /* we do not do deltas for now */ + case OBJ_BAD: + mark_bad_packed_object(pack, oid); + return -1; + case OBJ_COMMIT: + case OBJ_TREE: + case OBJ_BLOB: + case OBJ_TAG: + break; + } + + CALLOC_ARRAY(stream, 1); + stream->base.close = close_istream_pack_non_delta; + stream->base.read = read_istream_pack_non_delta; + stream->base.type = in_pack_type; + stream->base.size = size; + stream->z_state = ODB_PACKED_READ_STREAM_UNINITIALIZED; + stream->pack = pack; + stream->pos = offset; + + *out = &stream->base; + + 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 c9d0b93..6e8802e 100644 --- a/packfile.h +++ b/packfile.h
@@ -4,15 +4,16 @@ #include "list.h" #include "object.h" #include "odb.h" +#include "odb/source-files.h" #include "oidset.h" +#include "repository.h" +#include "strmap.h" /* in odb.h */ struct object_info; +struct odb_read_stream; struct packed_git { - struct hashmap_entry packmap_ent; - struct packed_git *next; - struct list_head mru; struct pack_window *windows; off_t pack_size; const void *index_data; @@ -27,6 +28,7 @@ struct packed_git { unsigned pack_local:1, pack_keep:1, pack_keep_in_core:1, + pack_keep_in_core_open:1, freshened:1, do_not_close:1, pack_promisor:1, @@ -52,53 +54,91 @@ 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 object_database *odb; + struct odb_source *source; /* - * The list of packfiles in the order in which they are being added to - * the store. + * The list of packfiles in the order in which they have been most + * recently used. */ - struct packed_git *packs; + 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 `kept_pack_cache()`. The - * list of packs gets invalidated when the stored flags and the flags - * passed to `kept_pack_cache()` mismatch. + * 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; - /* A most-recently-used ordered version of the packs list. */ - struct list_head mru; + /* 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 hashmap map; + 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. + * database source. */ -struct packfile_store *packfile_store_new(struct object_database *odb); +struct packfile_store *packfile_store_new(struct odb_source *source); /* * Free the packfile store and all its associated state. All packfiles @@ -137,23 +177,81 @@ void packfile_store_add_pack(struct packfile_store *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 repo_for_each_pack_data { + struct odb_source *source; + struct packfile_list_entry *entry; +}; + +static inline struct repo_for_each_pack_data repo_for_eack_pack_data_init(struct repository *repo) +{ + struct repo_for_each_pack_data data = { 0 }; + + odb_prepare_alternates(repo->objects); + + for (struct odb_source *source = repo->objects->sources; source; source = source->next) { + struct odb_source_files *files = odb_source_files_downcast(source); + struct packfile_list_entry *entry = packfile_store_get_packs(files->packed); + if (!entry) + continue; + data.source = source; + data.entry = entry; + break; + } + + return data; +} + +static inline void repo_for_each_pack_data_next(struct repo_for_each_pack_data *data) +{ + struct odb_source *source; + + data->entry = data->entry->next; + if (data->entry) + return; + + for (source = data->source->next; source; source = source->next) { + struct odb_source_files *files = odb_source_files_downcast(source); + struct packfile_list_entry *entry = packfile_store_get_packs(files->packed); + if (!entry) + continue; + data->source = source; + data->entry = entry; + return; + } + + data->source = NULL; + data->entry = NULL; +} + +/* * Load and iterate through all packs of the given repository. This helper * function will yield packfiles from all object sources connected to the * repository. */ #define repo_for_each_pack(repo, p) \ - for (p = packfile_store_get_packs(repo->objects->packfiles); p; p = p->next) + for (struct repo_for_each_pack_data eack_pack_data = repo_for_eack_pack_data_init(repo); \ + ((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); /* - * Get all packs managed by the given store, including packfiles that are - * referenced by multi-pack indices. + * 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. */ -struct packed_git *packfile_store_get_packs(struct packfile_store *store); - -/* - * Get all packs in most-recently-used order. - */ -struct list_head *packfile_store_get_packs_mru(struct packfile_store *store); +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 @@ -163,6 +261,33 @@ struct list_head *packfile_store_get_packs_mru(struct packfile_store *store); struct packed_git *packfile_store_load_pack(struct packfile_store *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, + unsigned flags); + struct pack_window { struct pack_window *next; unsigned char *base; @@ -229,9 +354,27 @@ typedef int each_packed_object_fn(const struct object_id *oid, void *data); int for_each_object_in_pack(struct packed_git *p, each_packed_object_fn, void *data, - enum for_each_object_flags flags); -int for_each_packed_object(struct repository *repo, each_packed_object_fn cb, - void *data, enum for_each_object_flags flags); + unsigned 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 @@ -239,20 +382,6 @@ int for_each_packed_object(struct repository *repo, each_packed_object_fn cb, #define PACKDIR_FILE_GARBAGE 4 extern void (*report_garbage)(unsigned seen_bits, const char *path); -/* - * Give a rough count of objects in the repository. This sacrifices accuracy - * for speed. - */ -unsigned long repo_approximate_object_count(struct repository *r); - -/* - * 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 *find_oid_pack(const struct object_id *oid, - struct packed_git *packs); - void pack_report(struct repository *repo); /* @@ -276,7 +405,6 @@ struct object_database; unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned long *); void close_pack_windows(struct packed_git *); void close_pack(struct packed_git *); -void close_object_store(struct object_database *o); void unuse_pack(struct pack_window **); void clear_delta_base_cache(void); struct packed_git *add_packed_git(struct repository *r, const char *path, @@ -335,34 +463,30 @@ off_t get_delta_base(struct packed_git *p, struct pack_window **w_curs, off_t *curpos, enum object_type type, off_t delta_obj_offset); +int packfile_read_object_stream(struct odb_read_stream **out, + const struct object_id *oid, + struct packed_git *pack, + off_t offset); + void release_pack_memory(size_t); /* global flag to enable extra checks when accessing packed objects */ extern int do_check_packed_object_crc; -int packed_object_info(struct repository *r, - struct packed_git *pack, +/* + * 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, off_t offset, struct object_info *); 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 *); -#define ON_DISK_KEEP_PACKS 1 -#define IN_CORE_KEEP_PACKS 2 - -/* - * Iff a pack file in the given repository contains the object named by sha1, - * return true and store its location to e. - */ -int find_pack_entry(struct repository *r, const struct object_id *oid, struct pack_entry *e); -int find_kept_pack_entry(struct repository *r, const struct object_id *oid, unsigned flags, struct pack_entry *e); - int has_object_pack(struct repository *r, const struct object_id *oid); int has_object_kept_pack(struct repository *r, const struct object_id *oid, unsigned flags); -struct packed_git **kept_pack_cache(struct repository *r, unsigned flags); - /* * Return 1 if an object in a promisor packfile is or refers to the given * object, 0 otherwise.
diff --git a/parallel-checkout.c b/parallel-checkout.c index fba6aa6..0bf4bd6 100644 --- a/parallel-checkout.c +++ b/parallel-checkout.c
@@ -13,7 +13,7 @@ #include "read-cache-ll.h" #include "run-command.h" #include "sigchain.h" -#include "streaming.h" +#include "odb/streaming.h" #include "symlinks.h" #include "thread-utils.h" #include "trace2.h" @@ -281,7 +281,8 @@ static int write_pc_item_to_fd(struct parallel_checkout_item *pc_item, int fd, filter = get_stream_filter_ca(&pc_item->ca, &pc_item->ce->oid); if (filter) { - if (stream_blob_to_fd(fd, &pc_item->ce->oid, filter, 1)) { + if (odb_stream_blob_to_fd(the_repository->objects, fd, + &pc_item->ce->oid, filter, 1)) { /* On error, reset fd to try writing without streaming */ if (reset_fd(fd, path)) return -1;
diff --git a/parse-options.c b/parse-options.c index c9cafc2..a676da8 100644 --- a/parse-options.c +++ b/parse-options.c
@@ -5,6 +5,7 @@ #include "gettext.h" #include "strbuf.h" #include "string-list.h" +#include "strmap.h" #include "utf8.h" static int disallow_abbreviated_options; @@ -641,6 +642,7 @@ static void check_typos(const char *arg, const struct option *options) static void parse_options_check(const struct option *opts) { char short_opts[128]; + bool saw_number_option = false; void *subcommand_value = NULL; memset(short_opts, '\0', sizeof(short_opts)); @@ -655,6 +657,11 @@ static void parse_options_check(const struct option *opts) else if (short_opts[opts->short_name]++) optbug(opts, "short name already used"); } + if (opts->type == OPTION_NUMBER) { + if (saw_number_option) + optbug(opts, "duplicate numerical option"); + saw_number_option = true; + } if (opts->flags & PARSE_OPT_NODASH && ((opts->flags & PARSE_OPT_OPTARG) || !(opts->flags & PARSE_OPT_NOARG) || @@ -714,6 +721,20 @@ static void parse_options_check(const struct option *opts) BUG_if_bug("invalid 'struct option'"); } +static void parse_options_check_harder(const struct option *opts) +{ + struct strset long_names = STRSET_INIT; + + for (; opts->type != OPTION_END; opts++) { + if (opts->long_name) { + if (!strset_add(&long_names, opts->long_name)) + optbug(opts, "long name already used"); + } + } + BUG_if_bug("invalid 'struct option'"); + strset_clear(&long_names); +} + static int has_subcommands(const struct option *options) { for (; options->type != OPTION_END; options++) @@ -1339,6 +1360,8 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t const char *prefix = usage_prefix; int saw_empty_line = 0; + parse_options_check_harder(opts); + if (!usagestr) return PARSE_OPT_HELP;
diff --git a/patch-ids.c b/patch-ids.c index a5683b4..1fbc88c 100644 --- a/patch-ids.c +++ b/patch-ids.c
@@ -41,7 +41,14 @@ static int patch_id_neq(const void *cmpfn_data, const struct hashmap_entry *entry_or_key, const void *keydata UNUSED) { - /* NEEDSWORK: const correctness? */ + /* + * We drop the 'const' modifier here intentionally. + * + * Even though eptr and entry_or_key are const, we want to + * lazily compute their .patch_id members; see b3dfeebb (rebase: + * avoid computing unnecessary patch IDs, 2016-07-29). So we cast + * the constness away with container_of(). + */ struct diff_options *opt = (void *)cmpfn_data; struct patch_id *a, *b;
diff --git a/path-walk.c b/path-walk.c index f1ceed9..2aa3e7d 100644 --- a/path-walk.c +++ b/path-walk.c
@@ -11,6 +11,7 @@ #include "list-objects.h" #include "object.h" #include "oid-array.h" +#include "path.h" #include "prio-queue.h" #include "repository.h" #include "revision.h" @@ -62,6 +63,8 @@ struct path_walk_context { */ struct prio_queue path_stack; struct strset path_stack_pushed; + + unsigned exact_pathspecs:1; }; static int compare_by_type(const void *one, const void *two, void *cb_data) @@ -137,7 +140,7 @@ static int add_tree_entries(struct path_walk_context *ctx, error(_("failed to walk children of tree %s: not found"), oid_to_hex(oid)); return -1; - } else if (parse_tree_gently(tree, 1)) { + } else if (repo_parse_tree_gently(ctx->repo, tree, 1)) { error("bad tree object %s", oid_to_hex(oid)); return -1; } @@ -206,6 +209,33 @@ static int add_tree_entries(struct path_walk_context *ctx, match != MATCHED) continue; } + if (ctx->revs->prune_data.nr && ctx->exact_pathspecs) { + struct pathspec *pd = &ctx->revs->prune_data; + bool found = false; + int did_strip_suffix = strbuf_strip_suffix(&path, "/"); + + + for (int i = 0; i < pd->nr; i++) { + struct pathspec_item *item = &pd->items[i]; + + /* + * Continue if either is a directory prefix + * of the other. + */ + if (dir_prefix(path.buf, item->match) || + dir_prefix(item->match, path.buf)) { + found = true; + break; + } + } + + if (did_strip_suffix) + strbuf_addch(&path, '/'); + + /* Skip paths that do not match the prefix. */ + if (!found) + continue; + } add_path_to_list(ctx, path.buf, type, &entry.oid, !(o->flags & UNINTERESTING)); @@ -274,6 +304,13 @@ static int walk_path(struct path_walk_context *ctx, return 0; } + if (list->type == OBJ_BLOB && + ctx->revs->prune_data.nr && + !match_pathspec(ctx->repo->index, &ctx->revs->prune_data, + path, strlen(path), 0, + NULL, 0)) + return 0; + /* Evaluate function pointer on this data, if requested. */ if ((list->type == OBJ_TREE && ctx->info->trees) || (list->type == OBJ_BLOB && ctx->info->blobs) || @@ -481,6 +518,12 @@ int walk_objects_by_path(struct path_walk_info *info) if (info->tags) info->revs->tag_objects = 1; + if (ctx.revs->prune_data.nr) { + if (!ctx.revs->prune_data.has_wildcard && + !ctx.revs->prune_data.magic) + ctx.exact_pathspecs = 1; + } + /* Insert a single list for the root tree into the paths. */ CALLOC_ARRAY(root_tree_list, 1); root_tree_list->type = OBJ_TREE;
diff --git a/path.c b/path.c index 7f56eaf..d7e17bf 100644 --- a/path.c +++ b/path.c
@@ -4,7 +4,6 @@ #include "git-compat-util.h" #include "abspath.h" -#include "environment.h" #include "gettext.h" #include "repository.h" #include "strbuf.h" @@ -57,9 +56,9 @@ static void strbuf_cleanup_path(struct strbuf *sb) strbuf_remove(sb, 0, path - sb->buf); } -static int dir_prefix(const char *buf, const char *dir) +int dir_prefix(const char *buf, const char *dir) { - int len = strlen(dir); + size_t len = strlen(dir); return !strncmp(buf, dir, len) && (is_dir_sep(buf[len]) || buf[len] == '\0'); } @@ -486,17 +485,16 @@ const char *mkpath(const char *fmt, ...) return cleanup_path(pathname->buf); } -const char *worktree_git_path(struct repository *r, - const struct worktree *wt, const char *fmt, ...) +const char *worktree_git_path(const struct worktree *wt, const char *fmt, ...) { struct strbuf *pathname = get_pathname(); va_list args; - if (wt && wt->repo != r) - BUG("worktree not connected to expected repository"); + if (!wt) + BUG("%s() called with NULL worktree", __func__); va_start(args, fmt); - repo_git_pathv(r, wt, pathname, fmt, args); + repo_git_pathv(wt->repo, wt, pathname, fmt, args); va_end(args); return pathname->buf; } @@ -738,122 +736,22 @@ char *interpolate_path(const char *path, int real_home) return NULL; } -/* - * First, one directory to try is determined by the following algorithm. - * - * (0) If "strict" is given, the path is used as given and no DWIM is - * done. Otherwise: - * (1) "~/path" to mean path under the running user's home directory; - * (2) "~user/path" to mean path under named user's home directory; - * (3) "relative/path" to mean cwd relative directory; or - * (4) "/absolute/path" to mean absolute directory. - * - * Unless "strict" is given, we check "%s/.git", "%s", "%s.git/.git", "%s.git" - * in this order. We select the first one that is a valid git repository, and - * chdir() to it. If none match, or we fail to chdir, we return NULL. - * - * If all goes well, we return the directory we used to chdir() (but - * before ~user is expanded), avoiding getcwd() resolving symbolic - * links. User relative paths are also returned as they are given, - * except DWIM suffixing. - */ -const char *enter_repo(const char *path, unsigned flags) -{ - static struct strbuf validated_path = STRBUF_INIT; - static struct strbuf used_path = STRBUF_INIT; - - if (!path) - return NULL; - - if (!(flags & ENTER_REPO_STRICT)) { - static const char *suffix[] = { - "/.git", "", ".git/.git", ".git", NULL, - }; - const char *gitfile; - int len = strlen(path); - int i; - while ((1 < len) && (path[len-1] == '/')) - len--; - - /* - * We can handle arbitrary-sized buffers, but this remains as a - * sanity check on untrusted input. - */ - if (PATH_MAX <= len) - return NULL; - - strbuf_reset(&used_path); - strbuf_reset(&validated_path); - strbuf_add(&used_path, path, len); - strbuf_add(&validated_path, path, len); - - if (used_path.buf[0] == '~') { - char *newpath = interpolate_path(used_path.buf, 0); - if (!newpath) - return NULL; - strbuf_attach(&used_path, newpath, strlen(newpath), - strlen(newpath)); - } - for (i = 0; suffix[i]; i++) { - struct stat st; - size_t baselen = used_path.len; - strbuf_addstr(&used_path, suffix[i]); - if (!stat(used_path.buf, &st) && - (S_ISREG(st.st_mode) || - (S_ISDIR(st.st_mode) && is_git_directory(used_path.buf)))) { - strbuf_addstr(&validated_path, suffix[i]); - break; - } - strbuf_setlen(&used_path, baselen); - } - if (!suffix[i]) - return NULL; - gitfile = read_gitfile(used_path.buf); - if (!(flags & ENTER_REPO_ANY_OWNER_OK)) - die_upon_dubious_ownership(gitfile, NULL, used_path.buf); - if (gitfile) { - strbuf_reset(&used_path); - strbuf_addstr(&used_path, gitfile); - } - if (chdir(used_path.buf)) - return NULL; - path = validated_path.buf; - } - else { - const char *gitfile = read_gitfile(path); - if (!(flags & ENTER_REPO_ANY_OWNER_OK)) - die_upon_dubious_ownership(gitfile, NULL, path); - if (gitfile) - path = gitfile; - if (chdir(path)) - return NULL; - } - - if (is_git_directory(".")) { - set_git_dir(".", 0); - check_repository_format(NULL); - return path; - } - - return NULL; -} - int calc_shared_perm(struct repository *repo, int mode) { int tweak; - - if (repo_settings_get_shared_repository(repo) < 0) - tweak = -repo_settings_get_shared_repository(repo); + int shared_repo = repo_settings_get_shared_repository(repo); + if (shared_repo < 0) + tweak = -shared_repo; else - tweak = repo_settings_get_shared_repository(repo); + tweak = shared_repo; if (!(mode & S_IWUSR)) tweak &= ~0222; if (mode & S_IXUSR) /* Copy read bits to execute bits */ tweak |= (tweak & 0444) >> 2; - if (repo_settings_get_shared_repository(repo) < 0) + if (shared_repo < 0) mode = (mode & ~0777) | tweak; else mode |= tweak; @@ -1212,6 +1110,14 @@ const char *remove_leading_path(const char *in, const char *prefix) * end with a '/', then the callers need to be fixed up accordingly. * */ + +static const char *skip_slashes(const char *p) +{ + while (is_dir_sep(*p)) + p++; + return p; +} + int normalize_path_copy_len(char *dst, const char *src, int *prefix_len) { char *dst0; @@ -1229,8 +1135,7 @@ int normalize_path_copy_len(char *dst, const char *src, int *prefix_len) } dst0 = dst; - while (is_dir_sep(*src)) - src++; + src = skip_slashes(src); for (;;) { char c = *src; @@ -1250,8 +1155,7 @@ int normalize_path_copy_len(char *dst, const char *src, int *prefix_len) } else if (is_dir_sep(src[1])) { /* (2) */ src += 2; - while (is_dir_sep(*src)) - src++; + src = skip_slashes(src); continue; } else if (src[1] == '.') { if (!src[2]) { @@ -1261,8 +1165,7 @@ int normalize_path_copy_len(char *dst, const char *src, int *prefix_len) } else if (is_dir_sep(src[2])) { /* (4) */ src += 3; - while (is_dir_sep(*src)) - src++; + src = skip_slashes(src); goto up_one; } } @@ -1282,6 +1185,8 @@ int normalize_path_copy_len(char *dst, const char *src, int *prefix_len) up_one: /* + * strip the last component + * * dst0..dst is prefix portion, and dst[-1] is '/'; * go up one level. */
diff --git a/path.h b/path.h index e67348f..0434ba5 100644 --- a/path.h +++ b/path.h
@@ -66,13 +66,11 @@ const char *repo_git_path_replace(struct repository *repo, /* * Similar to repo_git_path() but can produce paths for a specified - * worktree instead of current one. When no worktree is given, then the path is - * computed relative to main worktree of the given repository. + * worktree instead of current one. */ -const char *worktree_git_path(struct repository *r, - const struct worktree *wt, +const char *worktree_git_path(const struct worktree *wt, const char *fmt, ...) - __attribute__((format (printf, 3, 4))); + __attribute__((format (printf, 2, 3))); /* * The `repo_worktree_path` family of functions will construct a path into a @@ -114,6 +112,12 @@ const char *repo_submodule_path_replace(struct repository *repo, const char *fmt, ...) __attribute__((format (printf, 4, 5))); +/* + * Given a directory name 'dir' (not ending with a trailing '/'), + * determine if 'buf' is equal to 'dir' or has prefix 'dir'+'/'. + */ +int dir_prefix(const char *buf, const char *dir); + void report_linked_checkout_garbage(struct repository *r); /* @@ -146,21 +150,6 @@ int adjust_shared_perm(struct repository *repo, const char *path); char *interpolate_path(const char *path, int real_home); -/* The bits are as follows: - * - * - ENTER_REPO_STRICT: callers that require exact paths (as opposed - * to allowing known suffixes like ".git", ".git/.git" to be - * omitted) can set this bit. - * - * - ENTER_REPO_ANY_OWNER_OK: callers that are willing to run without - * ownership check can set this bit. - */ -enum { - ENTER_REPO_STRICT = (1<<0), - ENTER_REPO_ANY_OWNER_OK = (1<<1), -}; - -const char *enter_repo(const char *path, unsigned flags); const char *remove_leading_path(const char *in, const char *prefix); const char *relative_path(const char *in, const char *prefix, struct strbuf *sb); int normalize_path_copy_len(char *dst, const char *src, int *prefix_len);
diff --git a/pkt-line.c b/pkt-line.c index fc583fe..3fc3e9e 100644 --- a/pkt-line.c +++ b/pkt-line.c
@@ -384,10 +384,10 @@ int packet_length(const char lenbuf_hex[4], size_t size) hexval(lenbuf_hex[3]); } -static char *find_packfile_uri_path(const char *buffer) +static const char *find_packfile_uri_path(const char *buffer) { const char *URI_MARK = "://"; - char *path; + const char *path; int len; /* First char is sideband mark */ @@ -417,7 +417,7 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer, { int len; char linelen[4]; - char *uri_path_start; + const char *uri_path_start; if (get_packet_data(fd, src_buffer, src_len, linelen, 4, options) < 0) { *pktlen = -1;
diff --git a/po/bg.po b/po/bg.po index 047febf..aa96f8b 100644 --- a/po/bg.po +++ b/po/bg.po
@@ -1,7 +1,7 @@ # Bulgarian translation of git po-file. -# Copyright (C) 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025 Alexander Shopov <ash@kambanaria.org>. +# Copyright (C) 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026 Alexander Shopov <ash@kambanaria.org>. # This file is distributed under the same license as the git package. -# Alexander Shopov <ash@kambanaria.org>, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025. +# Alexander Shopov <ash@kambanaria.org>, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026. # ======================== # DICTIONARY TO MERGE IN GIT GUI # ------------------------ @@ -244,6 +244,15 @@ # initial branch първоначален клон # garbage повредени данни # keep file файл за запазване на директория +# commit transaction завършвам транзакция +# ref transaction транзакция за указатели +# refspec указател на версия +# stage добавям в индекса +# unstage изваждам от индекса +# upstream branch следен клон +# revert отменям +# +# # # ------------------------ # „$var“ - може да не сработва за shell има gettext и eval_gettext - проверка - намират се лесно по „$ @@ -270,10 +279,10 @@ # for i in `sort -u FILES`; do cnt=`grep $i FILES | wc -l`; echo $cnt $i ;done | sort -n msgid "" msgstr "" -"Project-Id-Version: git v2.52.0-rc1\n" +"Project-Id-Version: git v2.53.0-rc0\n" "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n" -"POT-Creation-Date: 2025-11-06 23:58+0000\n" -"PO-Revision-Date: 2025-11-09 18:23+0100\n" +"POT-Creation-Date: 2026-01-23 18:13+0100\n" +"PO-Revision-Date: 2026-01-24 11:18+0100\n" "Last-Translator: Alexander Shopov <ash@kambanaria.org>\n" "Language-Team: Bulgarian <dict@fsa-bg.org>\n" "Language: bg\\n\"\n" @@ -1053,7 +1062,7 @@ "modifications.\n" msgstr "" "Следните пътища са преместени извън дефинициите за частично\n" -"изтегляне, но вече не са частични заради локални промени.\n" +"изтегляне, но вече не са частични заради локални промѐни.\n" msgid "" "To correct the sparsity of these paths, do the following:\n" @@ -2051,11 +2060,11 @@ msgid "'%s' is not a valid branch name" msgstr "„%s“ не е позволено име за клон" -msgid "See `man git check-ref-format`" +msgid "See 'git help check-ref-format'" msgstr "" -"Вижте страницата в ръководството:\n" +"Вижте:\n" "\n" -" git check-ref-format" +" git help check-ref-format" #, c-format msgid "a branch named '%s' already exists" @@ -2988,6 +2997,18 @@ msgstr "трябва да завършва с цвят" #, c-format +msgid "unknown value for config '%s': %s" +msgstr "непозната стойност за настройката „%s“: „%s“" + +msgid "" +"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and " +"\"histogram\"" +msgstr "" +"опцията приема следните варианти за алгоритъм за разлики: „myers“ (по " +"Майерс), „minimal“ (минимизиране на разликите), „patience“ (пасианс) и " +"„histogram“ (хистограмен)" + +#, c-format msgid "cannot find revision %s to ignore" msgstr "версията за прескачане „%s“ липсва" @@ -3053,6 +3074,12 @@ msgid "ignore whitespace differences" msgstr "без разлики в знаците за интервали" +msgid "<algorithm>" +msgstr "АЛГОРИТЪМ" + +msgid "choose a diff algorithm" +msgstr "избор на АЛГОРИТЪМа за разлики" + msgid "rev" msgstr "ВЕРС" @@ -3069,7 +3096,7 @@ msgid "color lines by age" msgstr "оцветяване на редовете по възраст" -msgid "spend extra cycles to find better match" +msgid "spend extra cycles to find a better match" msgstr "допълнителни изчисления за по-добри резултати" msgid "use revisions from <file> instead of calling git-rev-list" @@ -3301,13 +3328,13 @@ msgstr "да не се ползва" msgid "upstream" -msgstr "клон-източник" +msgstr "КЛОН_ИЗТОЧНИК" msgid "change the upstream info" msgstr "смяна на клона-източник" msgid "unset the upstream info" -msgstr "изчистване на информацията за клон-източник" +msgstr "изчистване на информацията за КЛОНа_ИЗТОЧНИК" msgid "use colored output" msgstr "цветен изход" @@ -4287,8 +4314,8 @@ msgstr "липсва име на клон, използвайте опцията „-%c“" #, c-format -msgid "could not resolve %s" -msgstr "„%s“ не може да се проследи" +msgid "could not resolve '%s'" +msgstr "„%s“ липсва" msgid "invalid path specification" msgstr "указан е неправилен път" @@ -4696,7 +4723,7 @@ msgstr "задаване на настройките на новото хранилище" msgid "server-specific" -msgstr "специфични за сървъра" +msgstr "СПЕЦИФИЧНИ_ЗА_СЪРВЪРА" msgid "option to transmit" msgstr "опция за пренос" @@ -4944,7 +4971,7 @@ msgid "maximum ratio between two levels of a split commit-graph" msgstr "" -"максимално отношение на броя подавания в две последователни нива в раздробен " +"максимално отношение на броя подавания в две последователни нива̀ в раздробен " "граф" msgid "only expire files older than a given date-time" @@ -5578,7 +5605,8 @@ "<name>" msgstr "" "git config get [ОПЦИЯ_ЗА_ФАЙЛ] [ОПЦИЯ_ЗА_ИЗВЕЖДАНЕ] [--includes] [--all] [--" -"regexp=РЕГ_ИЗР][--value=ШАБЛОН] [--fixed-value] [--default=СТАНДАРТНО] ИМЕ" +"regexp=РЕГУЛЯРЕН_ИЗРАЗ] [--value=ШАБЛОН] [--fixed-value] [--" +"default=СТАНДАРТНО] ИМЕ" msgid "" "git config set [<file-option>] [--type=<type>] [--comment=<message>] [--all] " @@ -5814,21 +5842,31 @@ #, c-format msgid "" "cannot overwrite multiple values with a single value\n" -" Use a regexp, --add or --replace-all to change %s." +" Use --value=<pattern>, --append or --all to change %s." msgstr "" -"множество стойности не може да се замени с една.\n" -"За да променѝте „%s“, ползвайте регулярен израз или опциите „--add“ и „--" -"replace-all“." +"множество стойности не може да се заменят с една.\n" +" За да променѝте „%s“, ползвайте --value=РЕГУЛЯРЕН_ИЗРАЗ, „--append“ или " +"„--all“." + +msgid "unset all multi-valued config options" +msgstr "" +"изчистване на всички стойности за конфигурационните опции, които поддържат " +"много стойности" + +msgid "unset multi-valued config options with matching values" +msgstr "" +"изчистване на всички напасващи стойности за конфигурационните опции, които " +"поддържат много стойности" #, c-format msgid "no such section: %s" msgstr "такъв раззел няма: %s" msgid "editing stdin is not supported" -msgstr "не се поддържа редактиране на стандартния вход" +msgstr "редактирането на стандартния вход не се поддържа" msgid "editing blobs is not supported" -msgstr "не се поддържа редактиране на обекти-BLOB" +msgstr "редактирането на обекти-BLOB не се поддържа" #, c-format msgid "cannot create configuration file %s" @@ -5910,6 +5948,15 @@ "опцията „--comment“ е съвместима само с действията „add“ (добавяне)/„set“ " "(задаване)/„replace“ (замяна)" +#, c-format +msgid "" +"cannot overwrite multiple values with a single value\n" +" Use a regexp, --add or --replace-all to change %s." +msgstr "" +"множество стойности не може да се замени с една.\n" +"За да променѝте „%s“, ползвайте регулярен израз или опциите „--add“ и „--" +"replace-all“." + msgid "print sizes in human readable format" msgstr "извеждане на размерите на обектите във формат четим от хора" @@ -6119,16 +6166,16 @@ msgstr "опцията „-z“ е задължителна" msgid "pathspec arguments not supported" -msgstr "не се поддържат аргументи с шаблони за пътища" +msgstr "аргументи с шаблони за пътища не се поддържат" msgid "revision arguments not allowed" -msgstr "не се поддържат опции за версии" +msgstr "опциите за версии не се поддържат" msgid "invalid raw diff input" msgstr "неправилен необработен вход за разлики" msgid "tree objects not supported" -msgstr "не се поддържат обекти-дървета" +msgstr "обектите-дървета не се поддържат" msgid "got EOF while reading path" msgstr "край на файл (EOF) при изчитане на път" @@ -6325,12 +6372,6 @@ "reencode=[yes|no]“ за обработка" #, c-format -msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it" -msgstr "" -"получено е подписано подаване „%s“ — ползвайте „--signed-commits=РЕЖИМ“ за " -"обработка" - -#, c-format msgid "exporting %<PRIuMAX> signature(s) for commit %s" msgstr "изнасят се %<PRIuMAX> подпис(а) за подаването „%s“" @@ -6339,6 +6380,19 @@ msgstr "пропускане на подписите към подаването „%s“" #, c-format +msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it" +msgstr "" +"получено е подписано подаване „%s“ — ползвайте „--signed-commits=РЕЖИМ“ за " +"обработка" + +msgid "" +"'strip-if-invalid' is not a valid mode for git fast-export with --signed-" +"commits=<mode>" +msgstr "" +"„strip-if-invalid“ е грешен РЕЖИМ за „git fast-export“ с опцията „--signed-" +"commits=РЕЖИМ“" + +#, c-format msgid "" "omitting tag %s,\n" "since tags of trees (or tags of tags of trees, etc.) are not supported." @@ -6352,11 +6406,6 @@ msgstr "етикетът „%s“ не може да се прочете" #, c-format -msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it" -msgstr "" -"получен е подписан етикет „%s“ — ползвайте „--signed-tags=РЕЖИМ“ за обработка" - -#, c-format msgid "exporting signed tag %s" msgstr "изнасяне на подписан етикет „%s“" @@ -6365,6 +6414,18 @@ msgstr "пропускане на подписите към етикета „%s“" #, c-format +msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it" +msgstr "" +"получен е подписан етикет „%s“ — ползвайте „--signed-tags=РЕЖИМ“ за обработка" + +msgid "" +"'strip-if-invalid' is not a valid mode for git fast-export with --signed-" +"tags=<mode>" +msgstr "" +"„strip-if-invalid“ е грешен РЕЖИМ за „git fast-export“ с опцията „--signed-" +"tags=РЕЖИМ“" + +#, c-format msgid "" "tag %s tags unexported object; use --tag-of-filtered-object=<mode> to handle " "it" @@ -6756,6 +6817,33 @@ msgid "parse_one_signature() returned unknown hash algo" msgstr "„parse_one_signature()“ върна непознат алгоритъм за контролна сума" +msgid "unknown" +msgstr "непознат" + +#, c-format +msgid "" +"stripping invalid signature for commit '%.100s...'\n" +" allegedly by %s" +msgstr "" +"пропускане на грешния подпис към подаването „%.100s…“,\n" +" вероятно е на %s" + +#, c-format +msgid "" +"stripping invalid signature for commit '%.*s'\n" +" allegedly by %s" +msgstr "" +"пропускане на грешния подпис към подаването „%.*s“,\n" +" вероятно е на %s" + +#, c-format +msgid "" +"stripping invalid signature for commit\n" +" allegedly by %s" +msgstr "" +"пропускане на грешен подпис към подаване,\n" +" вероятно е на %s" + msgid "expected committer but didn't get one" msgstr "очакваше се подаващ, но такъв липсва" @@ -6770,10 +6858,6 @@ msgid "importing a commit signature verbatim" msgstr "дословно внасяне на подпис на подаване" -msgid "encountered signed tag; use --signed-tags=<mode> to handle it" -msgstr "" -"получен е подписан етикет — ползвайте „--signed-tags=РЕЖИМ“ за обработка" - #, c-format msgid "importing a tag signature verbatim for tag '%s'" msgstr "дословно внасяне на подпис на етикет за „%s“" @@ -6782,6 +6866,17 @@ msgid "stripping a tag signature for tag '%s'" msgstr "пропускане на подписа на етикета „%s“" +msgid "encountered signed tag; use --signed-tags=<mode> to handle it" +msgstr "" +"получен е подписан етикет — ползвайте „--signed-tags=РЕЖИМ“ за обработка" + +msgid "" +"'strip-if-invalid' is not a valid mode for git fast-import with --signed-" +"tags=<mode>" +msgstr "" +"„strip-if-invalid“ е грешен РЕЖИМ за „git fast-import“ с опцията „--signed-" +"tags=РЕЖИМ“" + #, c-format msgid "expected 'from' command, got '%s'" msgstr "очаква се команда „from“, а бе получена: „%s“" @@ -6941,7 +7036,7 @@ msgid "git fetch [<options>] <group>" msgstr "git fetch [ОПЦИЯ…] ГРУПА" -msgid "git fetch --multiple [<options>] [(<repository> | <group>)...]" +msgid "git fetch --multiple [<options>] [(<repository>|<group>)...]" msgstr "git fetch --multiple [ОПЦИЯ…] [(ХРАНИЛИЩЕ|ГРУПА)…]" msgid "git fetch --all [<options>]" @@ -7188,7 +7283,7 @@ msgstr "изискване на атомарни операции за обновяване на указателите" msgid "path to upload pack on remote end" -msgstr "отдалечен път, където да се качи пакетът" +msgstr "отдалечен ПЪТ, където да се качи пакетът" msgid "force overwrite of local reference" msgstr "принудително презаписване на локален указател" @@ -7219,7 +7314,7 @@ "хранилище и презаписване на променените" msgid "on-demand" -msgstr "ПРИ НУЖДА" +msgstr "ПРИ_НУЖДА" msgid "control recursive fetching of submodules" msgstr "управление на рекурсивното доставяне на подмодулите" @@ -7436,9 +7531,6 @@ msgid "got bad config --config=%s" msgstr "получена е неправилна настройка „--config=%s“" -msgid "unknown" -msgstr "непознат" - #. TRANSLATORS: e.g. error in tree 01bfda: <more explanation> #, c-format msgid "error in %s %s: %s" @@ -7533,8 +7625,12 @@ msgid "%s: not a commit" msgstr "%s: не е подаване!" +#, c-format +msgid "invalid parameter: expected sha1, got '%s'" +msgstr "неправилен параметър: очаква се SHA1, а бе получено: „%s“" + msgid "notice: No default references" -msgstr "внимание: няма указатели по подразбиране" +msgstr "ВНИМАНИЕ: няма указатели по подразбиране" #, c-format msgid "%s: hash-path mismatch, found at: %s" @@ -7559,26 +7655,6 @@ msgstr "Проверка на директориите с обекти" #, c-format -msgid "Checking %s link" -msgstr "Проверка на връзките на „%s“" - -#, c-format -msgid "invalid %s" -msgstr "неправилен указател „%s“" - -#, c-format -msgid "%s points to something strange (%s)" -msgstr "„%s“ сочи към нещо необичайно (%s)" - -#, c-format -msgid "%s: detached HEAD points at nothing" -msgstr "%s: отделеният връх „HEAD“ не сочи към нищо" - -#, c-format -msgid "notice: %s points to an unborn branch (%s)" -msgstr "предупреждение: „%s“ сочи към неродѐн клон (%s)" - -#, c-format msgid "Checking cache tree of %s" msgstr "Проверка на кеша на обектите-дървета на „%s“" @@ -7657,14 +7733,6 @@ msgid "Checking objects" msgstr "Проверка на обектите" -#, c-format -msgid "%s: object missing" -msgstr "„%s“: липсващ обект" - -#, c-format -msgid "invalid parameter: expected sha1, got '%s'" -msgstr "неправилен параметър: очаква се SHA1, а бе получено: „%s“" - msgid "git fsmonitor--daemon start [<options>]" msgstr "git fsmonitor--daemon start [ОПЦИЯ…]" @@ -8056,6 +8124,9 @@ msgid "failed to add repo to global config" msgstr "неуспешно добавяне на хранилище към файла с глобални настройки" +msgid "check a specific task" +msgstr "проверка на определена задача" + msgid "git maintenance <subcommand> [<options>]" msgstr "git maintenance ПОДКОМАНДА [ОПЦИЯ…]" @@ -8467,7 +8538,7 @@ #, c-format msgid "pack version %<PRIu32> unsupported" -msgstr "не се поддържа пакетиране вeрсия „%<PRIu32>“" +msgstr "пакетирането на вeрсия „%<PRIu32>“ не се поддържа" #, c-format msgid "pack has bad object at offset %<PRIuMAX>: %s" @@ -8520,6 +8591,10 @@ msgstr "грешка при проверката на пакетирани обекти" #, c-format +msgid "invalid %s" +msgstr "неправилен указател „%s“" + +#, c-format msgid "Not all child objects of %s are reachable" msgstr "Някои обекти, наследници на „%s“, не може да бъдат достигнати" @@ -8665,6 +8740,9 @@ msgid "--verify with no packfile name given" msgstr "опцията „--verify“ изисква име на пакетен файл" +msgid "cannot perform queued object checks outside of a repository" +msgstr "проверките на обекта в опашката не може да се изпълнят извън хранилище" + msgid "fsck error in pack objects" msgstr "грешка при проверка с „fsck“ на пакетните обекти" @@ -9442,14 +9520,6 @@ "git merge-file [ОПЦИЯ…] [-L ИМЕ_1 [-L ОРИГИНАЛ [-L ИМЕ_2]]] ФАЙЛ_1 ОРИГ_ФАЙЛ " "ФАЙЛ_2" -msgid "" -"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and " -"\"histogram\"" -msgstr "" -"опцията приема следните варианти за алгоритъм за разлики: „myers“ (по " -"Майерс), „minimal“ (минимизиране на разликите), „patience“ (пасианс) и " -"„histogram“ (хистограмен)" - msgid "send results to standard output" msgstr "извеждане на резултатите на стандартния изход" @@ -9462,12 +9532,6 @@ msgid "use a zealous diff3 based merge" msgstr "засилено сливане на базата на „diff3“" -msgid "<algorithm>" -msgstr "АЛГОРИТЪМ" - -msgid "choose a diff algorithm" -msgstr "избор на АЛГОРИТЪМа за разлики" - msgid "for conflicts, use this marker size" msgstr "при конфликти да се ползва маркер с такъв БРОЙ знаци" @@ -9860,7 +9924,7 @@ #, c-format msgid "When finished, apply stashed changes with `git stash pop`\n" -msgstr "При завършване скатаните промени да се приложат с „git stash pop“\n" +msgstr "При завършване скатаните промѐни да се приложат с „git stash pop“\n" #, c-format msgid "warning: tag input does not pass fsck: %s" @@ -10572,6 +10636,11 @@ msgstr "видът на обекта „%s“ в пакет „%s“ не може да се определи" #, c-format +msgid "packfile %s is a promisor but --exclude-promisor-objects was given" +msgstr "" +"пакетът „%s“ е гарантиращ, но е зададена опцията „--exclude-promisor-objects“" + +#, c-format msgid "could not find pack '%s'" msgstr "пакетът „%s“ не може да се открие" @@ -10850,11 +10919,11 @@ msgid "git patch-id [--stable | --unstable | --verbatim]" msgstr "git patch-id [--stable|--unstable|--verbatim]" -msgid "use the unstable patch-id algorithm" -msgstr "използване на нестабилния алгоритъм за идентифициране на кръпка" +msgid "use the unstable patch ID algorithm" +msgstr "използване на нестабилния алгоритъм за идентифициране на кръпки" -msgid "use the stable patch-id algorithm" -msgstr "използване на стабилния алгоритъм за идентифициране на кръпка" +msgid "use the stable patch ID algorithm" +msgstr "използване на стабилния алгоритъм за идентифициране на кръпки" msgid "don't strip whitespace from the patch" msgstr "без махане на празните знаци в кръпката" @@ -10877,41 +10946,6 @@ msgid "git pull [<options>] [<repository> [<refspec>...]]" msgstr "git push [ОПЦИЯ…] [ХРАНИЛИЩЕ [УКАЗАТЕЛ_НА_ВЕРСИЯ…]]" -msgid "control for recursive fetching of submodules" -msgstr "управление на рекурсивното доставяне на подмодулите" - -msgid "Options related to merging" -msgstr "Опции при сливане" - -msgid "incorporate changes by rebasing rather than merging" -msgstr "внасяне на промѐните чрез пребазиране, а не чрез сливане" - -msgid "allow fast-forward" -msgstr "позволяване на превъртания" - -msgid "control use of pre-merge-commit and commit-msg hooks" -msgstr "" -"дали куките преди подаване и сливане и при промяна на съобщението за " -"подаване (pre-merge-commit и commit-msg) да се изпълнят" - -msgid "automatically stash/stash pop before and after" -msgstr "автоматично скатаване/прилагане на скатаното преди и след пребазиране" - -msgid "Options related to fetching" -msgstr "Опции при доставяне" - -msgid "force overwrite of local branch" -msgstr "принудително презаписване на локалния клон" - -msgid "number of submodules pulled in parallel" -msgstr "брой подмодули издърпани паралелно" - -msgid "use IPv4 addresses only" -msgstr "само адреси IPv4" - -msgid "use IPv6 addresses only" -msgstr "само адреси IPv6" - msgid "" "There is no candidate for rebasing against among the refs that you just " "fetched." @@ -11016,6 +11050,41 @@ "използвайте опциите „--rebase“, „--no-rebase“, „--ff-only“. Те са с\n" "приоритет пред настройките.\n" +msgid "control for recursive fetching of submodules" +msgstr "управление на рекурсивното доставяне на подмодулите" + +msgid "Options related to merging" +msgstr "Опции при сливане" + +msgid "incorporate changes by rebasing rather than merging" +msgstr "внасяне на промѐните чрез пребазиране, а не чрез сливане" + +msgid "allow fast-forward" +msgstr "позволяване на превъртания" + +msgid "control use of pre-merge-commit and commit-msg hooks" +msgstr "" +"дали куките преди подаване и сливане и при промяна на съобщението за " +"подаване (pre-merge-commit и commit-msg) да се изпълнят" + +msgid "automatically stash/stash pop before and after" +msgstr "автоматично скатаване/прилагане на скатаното преди и след пребазиране" + +msgid "Options related to fetching" +msgstr "Опции при доставяне" + +msgid "force overwrite of local branch" +msgstr "принудително презаписване на локалния клон" + +msgid "number of submodules pulled in parallel" +msgstr "брой подмодули издърпани паралелно" + +msgid "use IPv4 addresses only" +msgstr "само адреси IPv4" + +msgid "use IPv6 addresses only" +msgstr "само адреси IPv6" + msgid "Updating an unborn branch with changes added to the index." msgstr "Обновяване на неродѐн клон с промѐните от индекса" @@ -11247,7 +11316,7 @@ "See the 'Note about fast-forwards' in 'git push --help' for details." msgstr "" "Обновяването е отхвърлено, защото върхът на отдалечения клон съдържа " -"промени\n" +"промѐни\n" "след последното изтегляне. Внесете отдалечените промѐни (напр. с командата\n" "„git pull…“), преди отново да изтласкате промѐните.\n" "За повече информация вижте раздела „Note about fast-forwards“ в страницата\n" @@ -11266,7 +11335,7 @@ "instead" msgstr "" "рекурсивно обхождане на подмодулите чрез „push.recurseSubmodules=only“. " -"Вместо това се ползва ПРИ НУЖДА" +"Вместо това се ползва ПРИ_НУЖДА" #, c-format msgid "invalid value for '%s'" @@ -12941,6 +13010,10 @@ msgid "only one pattern can be given with -l" msgstr "опцията „-l“ приема точно един шаблон" +#, c-format +msgid "'%s' is not a valid commit-ish for %s" +msgstr "„%s“ не прилича на подаване за %s" + msgid "need some commits to replay" msgstr "необходимо е да има подавания за прилагане отново" @@ -12957,27 +13030,16 @@ "цели с множество източници не може да се придвижат напред, защото подредбата " "не е добре дефинирана" -msgid "" -"cannot implicitly determine whether this is an --advance or --onto operation" -msgstr "" -"не може да се определи дали това действие е за „--advance“ или „--onto“" - -msgid "" -"cannot advance target with multiple source branches because ordering would " -"be ill-defined" -msgstr "" -"цели с множество клони-източници не може да се придвижат напред, защото " -"подредбата не е добре дефинирана" - -msgid "cannot implicitly determine correct base for --onto" -msgstr "правилната база за „--onto“ не може да се определи" +#, c-format +msgid "invalid %s value: '%s'" +msgstr "неправилна стойност за „%s“: „%s“" msgid "" "(EXPERIMENTAL!) git replay ([--contained] --onto <newbase> | --advance " -"<branch>) <revision-range>..." +"<branch>) [--ref-action[=<mode>]] <revision-range>" msgstr "" "(ЕКСПЕРИМЕНТАЛНО!) git replay ([--contained] --onto НОВА_БАЗА | --advance " -"КЛОН) ДИАПАЗОН_ПОДАВАНИЯ…" +"КЛОН) [--ref-action[=РЕЖИМ]] ДИАПАЗОН_ПОДАВАНИЯ…" msgid "make replay advance given branch" msgstr "прилагането наново придвижва дадения КЛОН напред" @@ -12985,8 +13047,14 @@ msgid "replay onto given commit" msgstr "прилагането наново върху даденото ПОДАВАНЕ" -msgid "advance all branches contained in revision-range" -msgstr "придвижване на всички КЛОНи в ДИАПАЗОНа_ПОДАВАНИЯ" +msgid "update all branches that point at commits in <revision-range>" +msgstr "" +"обновяване на всички клони, които сочат към подавания в ДИАПАЗОНа_ПОДАВАНИЯ" + +msgid "control ref update behavior (update|print)" +msgstr "" +"поведение при обновяването на указател: „update“ (обновяване)/„print“ " +"(извеждане)" msgid "option --onto or --advance is mandatory" msgstr "изисква се някоя от опциите „--onto“ или „--advance“" @@ -12999,14 +13067,26 @@ "някои опции за проследяване на указатели ще бъдат променени, защото битът " "„%s“ в структурата „struct rev_info“ има превес" +#, c-format +msgid "failed to begin ref transaction: %s" +msgstr "неуспешно начало на транзакция за указатели: %s" + msgid "error preparing revisions" msgstr "грешка при подготовката на версии" -msgid "replaying down to root commit is not supported yet!" -msgstr "не се поддържа прилагане наново и на началното подаване!" +msgid "replaying down from root commit is not supported yet!" +msgstr "прилагането наново от началното подаване не се поддържа" msgid "replaying merge commits is not supported yet!" -msgstr "не се поддържа прилагане наново и на подавания със сливане!" +msgstr "прилагането наново на подавания със сливане не се поддържа!" + +#, c-format +msgid "failed to update ref '%s': %s" +msgstr "указателят „%s“ не може да се обнови: %s" + +#, c-format +msgid "failed to commit ref transaction: %s" +msgstr "транзакцията за указатели не може да се завърши: %s" #, c-format msgid "key '%s' not found" @@ -13022,9 +13102,15 @@ msgid "synonym for --format=nul" msgstr "псевдоним на „--format=nul“" +msgid "print all keys/values" +msgstr "извеждане на всички ключове/стойности" + msgid "unsupported output format" msgstr "неподдържан изходен формат" +msgid "--all and <key> cannot be used together" +msgstr "опцията „--all“ и изричен КЛЮЧ са несъвместими" + msgid "References" msgstr "Указатели" @@ -13055,6 +13141,12 @@ msgid "Blobs" msgstr "Обекти-BLOB" +msgid "Inflated size" +msgstr "Декомпресиран размер" + +msgid "Disk size" +msgstr "Размер на диска" + msgid "Repository structure" msgstr "Структура на хранилището" @@ -14309,7 +14401,7 @@ #, c-format msgid "failed to update remote for submodule '%s'" -msgstr "отдалеченият адрес на подмодула „%s“ не може да се промени" +msgstr "отдалеченият адрес на подмодула „%s“ не може да се променѝ" msgid "suppress output of synchronizing submodule url" msgstr "без извеждане на информация при синхронизирането на подмодул" @@ -15604,6 +15696,10 @@ msgid "bundle list at '%s' has no mode" msgstr "липсва режим в списъка от пратки „%s“" +#, c-format +msgid "bundle list at '%s': bundle '%s' has no uri" +msgstr "списък от пратки при „%s“: липсва адрес за пратка „%s“" + msgid "failed to create temporary file" msgstr "не може да се създаде временен файл" @@ -15627,6 +15723,10 @@ "преминато е ограничениeто за рекурсивно обхождане на адреси на пратки (%d)" #, c-format +msgid "bundle '%s' has no uri" +msgstr "липсва адрес за пратка „%s“" + +#, c-format msgid "failed to download bundle from URI '%s'" msgstr "неуспешно изтегляне на пратка от адрес: „%s“" @@ -16041,7 +16141,7 @@ msgstr "Стандартната помощна програма за „git-merge-index“" msgid "Perform merge without touching index or working tree" -msgstr "Извършване на сливането без промени по индекса или работното време" +msgstr "Извършване на сливането без промѐни по индекса или работното време" msgid "Run merge conflict resolution tools to resolve merge conflicts" msgstr "Изпълнение на програмите за коригиране на конфликтите при сливане" @@ -16135,8 +16235,8 @@ msgid "Reuse recorded resolution of conflicted merges" msgstr "Преизползване на вече запазено коригиране на конфликт при сливане" -msgid "Reset current HEAD to the specified state" -msgstr "Привеждане на указателя „HEAD“ към зададеното състояние" +msgid "Set `HEAD` or the index to a known state" +msgstr "Задаване на указателя „HEAD“ или индекса към известно състояние" msgid "Restore working tree files" msgstr "Възстановяване на файловете в работното дърво" @@ -16488,7 +16588,7 @@ msgstr "Изчистване на отбелязванията на подаванията в гра̀фа с подаванията" msgid "Computing commit graph topological levels" -msgstr "Изчисляване на топологичните нива в гра̀фа с подаванията" +msgstr "Изчисляване на топологичните нива̀ в гра̀фа с подаванията" msgid "Computing commit graph generation numbers" msgstr "Изчисляване на номерата на поколенията в гра̀фа с подаванията" @@ -17717,10 +17817,6 @@ msgstr "Непозната стойност „%s“ за настройката „diff.submodule“" #, c-format -msgid "unknown value for config '%s': %s" -msgstr "непозната стойност за настройката „%s“: „%s“" - -#, c-format msgid "" "Found errors in 'diff.dirstat' config variable:\n" "%s" @@ -17797,7 +17893,7 @@ #, c-format msgid "invalid mode '%s' in --color-moved-ws" -msgstr "неправилен режим „%s“ за „ --color-moved-ws“" +msgstr "грешен режим „%s“ за „ --color-moved-ws“" #, c-format msgid "invalid argument to %s" @@ -18309,7 +18405,7 @@ #, c-format msgid "invalid mode for object creation: %s" -msgstr "неправилен режим за създаването на обекти: %s" +msgstr "грешен режим за създаването на обекти: %s" #, c-format msgid "malformed value for %s" @@ -19425,7 +19521,7 @@ msgstr "" " ⁃ преминаване към подмодула (%s), след които или де се слее подаването\n" " „%s“, или да се обнови към съществуващо подаване, в които\n" -" тези промени са слети\n" +" тези промѐни са слети\n" #, c-format msgid "" @@ -19923,6 +20019,9 @@ msgid "%s: failed to insert into database" msgstr "„%s“ не може да се вмъкне в базата от данни" +msgid "cannot add a submodule of a different hash algorithm" +msgstr "не може да добавите подмодул с различен алгоритъм за контролни суми" + #, c-format msgid "%s: unsupported file type" msgstr "неподдържан вид файл: „%s“" @@ -20772,7 +20871,7 @@ msgstr "неуспешно изчистване на буферите при запис на пакет" msgid "protocol error: impossibly long line" -msgstr "протоколна грешка: прекалено дълъг ред" +msgstr "ПРОТОКОЛНА ГРЕШКА: прекалено дълъг ред" msgid "packet write with format failed" msgstr "неуспешен запис на пакет с формат" @@ -20794,15 +20893,15 @@ #, c-format msgid "protocol error: bad line length character: %.4s" -msgstr "протоколна грешка: неправилeн знак за дължина на ред: %.4s" +msgstr "ПРОТОКОЛНА ГРЕШКА: неправилeн знак за дължина на ред: %.4s" #, c-format msgid "protocol error: bad line length %d" -msgstr "протоколна грешка: неправилна дължина на ред: %d" +msgstr "ПРОТОКОЛНА ГРЕШКА: неправилна дължина на ред: %d" #, c-format msgid "remote error: %s" -msgstr "отдалечена грешка: %s" +msgstr "ОТДАЛЕЧЕНА ГРЕШКА: %s" msgid "Refreshing index" msgstr "Обновяване на индекса" @@ -21436,6 +21535,10 @@ msgstr "" "опцията „--format=%.*s“ е несъвместима с „--python“, „--shell“, „--tcl“" +#, c-format +msgid "parse_object_buffer failed on %s for %s" +msgstr "неуспешно анализиране чрез „parse_object_buffer“ на „%s“ за „%s“" + msgid "failed to run 'describe'" msgstr "неуспешно изпълнение на „describe“" @@ -21467,10 +21570,6 @@ msgstr "обектът „%s“ липсва за „%s“" #, c-format -msgid "parse_object_buffer failed on %s for %s" -msgstr "неуспешно анализиране чрез „parse_object_buffer“ на „%s“ за „%s“" - -#, c-format msgid "malformed object at '%s'" msgstr "обект със сгрешен формат при „%s“" @@ -21777,6 +21876,18 @@ msgstr "името на указател „%s“ е символен указател, не може да се копира" #, c-format +msgid "reftable stack for worktree '%s' is broken" +msgstr "таблицата с указатели за работното дърво в „%s“ е повредена" + +#, c-format +msgid "could not create iterator for worktree '%s'" +msgstr "не може да се създаде итератор за работното дърво в „%s“" + +#, c-format +msgid "could not read record for worktree '%s'" +msgstr "не може да се създаде запис за работното дърво в „%s“" + +#, c-format msgid "pattern '%s' has no '*'" msgstr "шаблонът „%s“ не съдържа „*“" @@ -21878,14 +21989,14 @@ #, c-format msgid "protocol error: expected sha/ref, got '%s'" -msgstr "протоколна грешка: очаква се SHA1 или указател, а бе получено: „%s“" +msgstr "ПРОТОКОЛНА ГРЕШКА: очаква се SHA1 или указател, а бе получено: „%s“" #, c-format msgid "http transport does not support %s" msgstr "транспортът по http не поддържа „%s“" msgid "protocol error: expected '<url> <path>', missing space" -msgstr "протоколна грешка: очаква се „АДРЕС ПЪТ“, липсва интервал" +msgstr "ПРОТОКОЛНА ГРЕШКА: очаква се „АДРЕС ПЪТ“, липсва интервал" #, c-format msgid "failed to download file at URL '%s'" @@ -22212,6 +22323,11 @@ "командата „pack-objects“ не може да завърши за препакетирането на " "гарантиращите обекти" +msgid "could not start pack-objects to repack promisor packs" +msgstr "" +"командата „pack-objects“ не може да се стартира за препакетирането на " +"гарантиращите обекти" + #, c-format msgid "pack prefix %s does not begin with objdir %s" msgstr "името на пакетния файл „%s“ не започва с директорията за обекти с „%s“" @@ -22502,7 +22618,7 @@ msgstr "(enable=включване|disable=изключване|keep=запазване)" msgid "signal how to adjust background maintenance" -msgstr "как да се промени поддръжката на заден фон" +msgstr "как да се променѝ поддръжката на заден фон" msgid "" "scalar reconfigure [--maintenance=(enable|disable|keep)] [--all | " @@ -23044,7 +23160,7 @@ "'merge -c' on the commit" msgstr "" "„reword“ не приема подаване със сливане. Ако искате да приложите\n" -"сливането наново и да промените съобщението при подаване, изпълнете\n" +"сливането наново и да променѝте съобщението при подаване, изпълнете\n" "\n" " git merge -C ПОДАВАНЕ" @@ -23265,10 +23381,6 @@ msgid "illegal label name: '%.*s'" msgstr "неправилно име на етикет: „%.*s“" -#, c-format -msgid "could not resolve '%s'" -msgstr "„%s“ липсва" - msgid "writing fake root commit" msgstr "запазване на фалшиво начално подаване" @@ -23370,7 +23482,7 @@ msgstr "указателят за автоматично скатано e файл с указател" msgid "could not detach HEAD" -msgstr "указателят „HEAD“ не може да се отдели" +msgstr "указателят „HEAD“ не може да се отделѝ" #, c-format msgid "Stopped at HEAD\n" @@ -23762,49 +23874,68 @@ msgid "bad %s format: %%%.*s" msgstr "неправилен формат за „%s“: %%%.*s" -#. TRANSLATORS: IEC 80000-13:2008 gibibyte #, c-format -msgid "%u.%2.2u GiB" -msgstr "%u.%2.2u GiB" +msgid "%u.%2.2u" +msgstr "%u.%2.2u" -#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second -#, c-format -msgid "%u.%2.2u GiB/s" -msgstr "%u.%2.2u GiB/s" +#. TRANSLATORS: SI decimal prefix symbol for 10^9 +msgid "G" +msgstr "G" -#. TRANSLATORS: IEC 80000-13:2008 mebibyte -#, c-format -msgid "%u.%2.2u MiB" -msgstr "%u.%2.2u MiB" +#. TRANSLATORS: SI decimal prefix symbol for 10^6 +msgid "M" +msgstr "M" -#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second -#, c-format -msgid "%u.%2.2u MiB/s" -msgstr "%u.%2.2u MiB/s" +#. TRANSLATORS: SI decimal prefix symbol for 10^3 +msgid "k" +msgstr "k" -#. TRANSLATORS: IEC 80000-13:2008 kibibyte -#, c-format -msgid "%u.%2.2u KiB" -msgstr "%u.%2.2u KiB" +#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second and gibibyte +msgid "GiB/s" +msgstr "GiB/s" -#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second -#, c-format -msgid "%u.%2.2u KiB/s" -msgstr "%u.%2.2u KiB/s" +msgid "GiB" +msgstr "GiB" -#. TRANSLATORS: IEC 80000-13:2008 byte -#, c-format -msgid "%u byte" -msgid_plural "%u bytes" -msgstr[0] "%u байт" -msgstr[1] "%u байта" +#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second and mebibyte +msgid "MiB/s" +msgstr "MiB/s" + +msgid "MiB" +msgstr "MiB" + +#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second and kibibyte +msgid "KiB/s" +msgstr "KiB/s" + +msgid "KiB" +msgstr "KiB" + +#. TRANSLATORS: IEC 80000-13:2008 byte/second and byte +msgid "B/s" +msgstr "B/s" + +msgid "B" +msgstr "B" #. TRANSLATORS: IEC 80000-13:2008 byte/second +msgid "byte/s" +msgid_plural "bytes/s" +msgstr[0] "byte/s" +msgstr[1] "byte/s" + +#. TRANSLATORS: IEC 80000-13:2008 byte +msgid "byte" +msgid_plural "bytes" +msgstr[0] "байт" +msgstr[1] "байта" + +#. TRANSLATORS: The first argument is the number string. The second +#. argument is the unit string (i.e. "12.34 MiB/s"). +#. #, c-format -msgid "%u byte/s" -msgid_plural "%u bytes/s" -msgstr[0] "%u байт/сек." -msgstr[1] "%u байта/сек." +msgid "%s %s" +msgstr "%s %s" #, c-format msgid "ignoring suspicious submodule name: %s" @@ -23821,7 +23952,7 @@ #, c-format msgid "Could not update .gitmodules entry %s" -msgstr "Записът „%s“ във файла „.gitmodules“ не може да се промени" +msgstr "Записът „%s“ във файла „.gitmodules“ не може да се променѝ" msgid "Cannot change unmerged .gitmodules, resolve merge conflicts first" msgstr "" @@ -24102,9 +24233,6 @@ msgid "number of requests per thread" msgstr "брой заявки на нишка" -msgid "byte" -msgstr "байта" - msgid "ballast character" msgstr "знаци за пращане" @@ -24666,16 +24794,16 @@ msgstr "ГРЕШКА: съобщението не може да се форматира: %s\n" msgid "usage: " -msgstr "употреба: " +msgstr "УПОТРЕБА: " msgid "fatal: " -msgstr "фатална грешка: " +msgstr "ФАТАЛНА ГРЕШКА: " msgid "error: " msgstr "ГРЕШКА: " msgid "warning: " -msgstr "предупреждение: " +msgstr "ПРЕДУПРЕЖДЕНИЕ: " #, c-format msgid "'%s' is nominated for removal.\n" @@ -25611,7 +25739,7 @@ #, perl-format msgid "Outlook reassigned Message-ID to: %s\n" -msgstr "Outlook промени „Message-ID“ да е: %s\n" +msgstr "Outlook променѝ „Message-ID“ да е: %s\n" msgid "Warning: Could not retrieve Message-ID from server response.\n" msgstr "ПРЕДУПРЕЖДЕНИЕ: в отговора на сървъра липсва „Message-ID“.\n"
diff --git a/po/fr.po b/po/fr.po index 6364071..780bc7b 100644 --- a/po/fr.po +++ b/po/fr.po
@@ -87,8 +87,8 @@ msgstr "" "Project-Id-Version: git\n" "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n" -"POT-Creation-Date: 2025-11-07 21:01+0100\n" -"PO-Revision-Date: 2025-11-09 14:56+0100\n" +"POT-Creation-Date: 2026-01-22 23:57+0000\n" +"PO-Revision-Date: 2026-01-28 19:43+0100\n" "Last-Translator: Cédric Malard <c.malard-git@valdun.net>\n" "Language-Team: Jean-Noël Avila <jn.avila@free.fr>\n" "Language: fr\n" @@ -1851,8 +1851,8 @@ msgid "'%s' is not a valid branch name" msgstr "'%s' n'est pas un nom de branche valide" -msgid "See `man git check-ref-format`" -msgstr "Voir `man git check-ref-format`" +msgid "See 'git help check-ref-format'" +msgstr "Voir 'git help check-ref-format'" #, c-format msgid "a branch named '%s' already exists" @@ -2766,6 +2766,17 @@ msgstr "doit finir avec une couleur" #, c-format +msgid "unknown value for config '%s': %s" +msgstr "valeur inconnue pour la config '%s' : %s" + +msgid "" +"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and " +"\"histogram\"" +msgstr "" +"l'option diff-algorithm accept \"myers\", \"minimal\", \"patience\" et " +"\"histogram\"" + +#, c-format msgid "cannot find revision %s to ignore" msgstr "référence à ignorer %s introuvable" @@ -2822,6 +2833,12 @@ msgid "ignore whitespace differences" msgstr "ignorer les différences d'espace" +msgid "<algorithm>" +msgstr "<algorithme>" + +msgid "choose a diff algorithm" +msgstr "choisir un algorithme de différence" + msgid "rev" msgstr "rév" @@ -2838,7 +2855,7 @@ msgid "color lines by age" msgstr "colorier les lignes par âge" -msgid "spend extra cycles to find better match" +msgid "spend extra cycles to find a better match" msgstr "" "dépenser des cycles supplémentaires pour trouver une meilleure correspondance" @@ -4054,8 +4071,8 @@ msgstr "nom de branche manquant ; essayez -%c" #, c-format -msgid "could not resolve %s" -msgstr "impossible de résoudre %s" +msgid "could not resolve '%s'" +msgstr "impossible de résoudre '%s'" msgid "invalid path specification" msgstr "spécification de chemin invalide" @@ -5561,10 +5578,18 @@ #, c-format msgid "" "cannot overwrite multiple values with a single value\n" -" Use a regexp, --add or --replace-all to change %s." +" Use --value=<pattern>, --append or --all to change %s." msgstr "" "impossible de surcharger des valeurs multiples avec une seule valeur\n" -" Utilisez une regex, --add ou --replace-all pour modifier %s." +" Utilisez --value=<motif>, --append ou --all pour modifier %s." + +msgid "unset all multi-valued config options" +msgstr "réinitialiser toutes les options de configuration multi-valeurs" + +msgid "unset multi-valued config options with matching values" +msgstr "" +"réinitialiser toutes les options de configuration multi-valeurs ayant des " +"valeurs correspondantes" #, c-format msgid "no such section: %s" @@ -5651,6 +5676,14 @@ msgid "--comment is only applicable to add/set/replace operations" msgstr "--comment n'est applicable qu'avec les opérations add/set/replace" +#, c-format +msgid "" +"cannot overwrite multiple values with a single value\n" +" Use a regexp, --add or --replace-all to change %s." +msgstr "" +"impossible de surcharger des valeurs multiples avec une seule valeur\n" +" Utilisez une regex, --add ou --replace-all pour modifier %s." + msgid "print sizes in human readable format" msgstr "affiche les tailles dans un format humainement lisible" @@ -6058,11 +6091,6 @@ "--reencode=(yes|no) pour le gérer" #, c-format -msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it" -msgstr "" -"commit signé %s rencontré ; utilisez --signed-commits=<mode> pour le gérer" - -#, c-format msgid "exporting %<PRIuMAX> signature(s) for commit %s" msgstr "export de %<PRIuMAX> signature(s) pour le commit %s" @@ -6071,6 +6099,18 @@ msgstr "suppression de signature(s) du commit %s" #, c-format +msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it" +msgstr "" +"commit signé %s rencontré ; utilisez --signed-commits=<mode> pour le gérer" + +msgid "" +"'strip-if-invalid' is not a valid mode for git fast-export with --signed-" +"commits=<mode>" +msgstr "" +"'strip-if-invalid' n'est pas un mode valide pour git fast-export avec --" +"signed-commits=<mode>" + +#, c-format msgid "" "omitting tag %s,\n" "since tags of trees (or tags of tags of trees, etc.) are not supported." @@ -6084,11 +6124,6 @@ msgstr "impossible de lire l'étiquette %s" #, c-format -msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it" -msgstr "" -"étiquette signée %s rencontrée ; utilisez --signed-tags=<mode> pour la gérer" - -#, c-format msgid "exporting signed tag %s" msgstr "export de l'étiquette signée %s" @@ -6097,6 +6132,18 @@ msgstr "suppression de la signature dans l'étiquette %s" #, c-format +msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it" +msgstr "" +"étiquette signée %s rencontrée ; utilisez --signed-tags=<mode> pour la gérer" + +msgid "" +"'strip-if-invalid' is not a valid mode for git fast-export with --signed-" +"tags=<mode>" +msgstr "" +"'strip-if-invalid' n'est pas un mode valide pour git fast-export avec --" +"signed-tags=<mode>" + +#, c-format msgid "" "tag %s tags unexported object; use --tag-of-filtered-object=<mode> to handle " "it" @@ -6491,6 +6538,33 @@ msgid "parse_one_signature() returned unknown hash algo" msgstr "parse_one_signature() a renvoyé un algo d'empreinte inconnu" +msgid "unknown" +msgstr "inconnu" + +#, c-format +msgid "" +"stripping invalid signature for commit '%.100s...'\n" +" allegedly by %s" +msgstr "" +"suppression de la signature invalide pour le commit '%.100s...'\n" +" prétendument par %s" + +#, c-format +msgid "" +"stripping invalid signature for commit '%.*s'\n" +" allegedly by %s" +msgstr "" +"suppression de la signature invalide pour le commit '%.*s'\n" +" prétendument par %s" + +#, c-format +msgid "" +"stripping invalid signature for commit\n" +" allegedly by %s" +msgstr "" +"suppression de la signature invalide pour le commit\n" +" prétendument par %s" + msgid "expected committer but didn't get one" msgstr "validateur attendu mais aucune information fournie" @@ -6504,10 +6578,6 @@ msgid "importing a commit signature verbatim" msgstr "import d'une signature de commit verbatim" -msgid "encountered signed tag; use --signed-tags=<mode> to handle it" -msgstr "" -"étiquette signée rencontrée ; utilisez --signed-tags=<mode> pour la gérer" - #, c-format msgid "importing a tag signature verbatim for tag '%s'" msgstr "import d'une signature d'étiquette verbatim pour l'étiquette '%s'" @@ -6516,6 +6586,17 @@ msgid "stripping a tag signature for tag '%s'" msgstr "suppression d'une signature d'étiquette de l'étiquette '%s'" +msgid "encountered signed tag; use --signed-tags=<mode> to handle it" +msgstr "" +"étiquette signée rencontrée ; utilisez --signed-tags=<mode> pour la gérer" + +msgid "" +"'strip-if-invalid' is not a valid mode for git fast-import with --signed-" +"tags=<mode>" +msgstr "" +"'strip-if-invalid' n'est pas un mode valide pour git fast-import avec --" +"signed-tags=<mode>" + #, c-format msgid "expected 'from' command, got '%s'" msgstr "Commande 'from' attendue, '%s' trouvé" @@ -6675,8 +6756,8 @@ msgid "git fetch [<options>] <group>" msgstr "git fetch [<options>] <groupe>" -msgid "git fetch --multiple [<options>] [(<repository> | <group>)...]" -msgstr "git fetch --multiple [<options>] [(<dépôt> | <groupe>)...]" +msgid "git fetch --multiple [<options>] [(<repository>|<group>)...]" +msgstr "git fetch --multiple [<options>] [(<dépôt>|<groupe>)...]" msgid "git fetch --all [<options>]" msgstr "git fetch --all [<options>]" @@ -7159,9 +7240,6 @@ msgid "got bad config --config=%s" msgstr "config incorrecte --config=%s" -msgid "unknown" -msgstr "inconnu" - #. TRANSLATORS: e.g. error in tree 01bfda: <more explanation> #, c-format msgid "error in %s %s: %s" @@ -7256,6 +7334,10 @@ msgid "%s: not a commit" msgstr "l'objet %s n'est pas un commit" +#, c-format +msgid "invalid parameter: expected sha1, got '%s'" +msgstr "paramètre invalide : sha-1 attendu, '%s' trouvé" + msgid "notice: No default references" msgstr "note : pas de référence par défaut" @@ -7282,26 +7364,6 @@ msgstr "Vérification des répertoires d'objet" #, c-format -msgid "Checking %s link" -msgstr "Vérification du lien %s" - -#, c-format -msgid "invalid %s" -msgstr "%s invalide" - -#, c-format -msgid "%s points to something strange (%s)" -msgstr "%s pointe sur quelque chose bizarre (%s)" - -#, c-format -msgid "%s: detached HEAD points at nothing" -msgstr "%s : la HEAD détachée ne pointe sur rien" - -#, c-format -msgid "notice: %s points to an unborn branch (%s)" -msgstr "note : %s pointe sur une branche non-née (%s)" - -#, c-format msgid "Checking cache tree of %s" msgstr "Vérification de l'arbre cache de %s" @@ -7381,14 +7443,6 @@ msgid "Checking objects" msgstr "Vérification des objets" -#, c-format -msgid "%s: object missing" -msgstr "%s : objet manquant" - -#, c-format -msgid "invalid parameter: expected sha1, got '%s'" -msgstr "paramètre invalide : sha-1 attendu, '%s' trouvé" - msgid "git fsmonitor--daemon start [<options>]" msgstr "git fsmonitor--daemon start [<options>]" @@ -7777,6 +7831,9 @@ msgid "failed to add repo to global config" msgstr "échec de l'ajout du dépôt à la config globale" +msgid "check a specific task" +msgstr "vérifier une tâche spécifique" + msgid "git maintenance <subcommand> [<options>]" msgstr "git maintenance <subcommand> [<options>]" @@ -8238,6 +8295,10 @@ msgstr "erreur de fsck dans l'objet empaqueté" #, c-format +msgid "invalid %s" +msgstr "%s invalide" + +#, c-format msgid "Not all child objects of %s are reachable" msgstr "Tous les objets enfants de %s ne sont pas accessibles" @@ -8380,6 +8441,11 @@ msgid "--verify with no packfile name given" msgstr "--verify sans nom de fichier paquet donné" +msgid "cannot perform queued object checks outside of a repository" +msgstr "" +"impossible d'effectuer les vérifications des objets en attentes hors d'un " +"dépôt" + msgid "fsck error in pack objects" msgstr "erreur de fsck dans les objets paquets" @@ -9155,13 +9221,6 @@ "git merge-file [<options>] [-L <nom1> [-L <orig> [-L <nom2>]]] <fichier1> " "<fichier-orig> <fichier2>" -msgid "" -"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and " -"\"histogram\"" -msgstr "" -"l'option diff-algorithm accept \"myers\", \"minimal\", \"patience\" et " -"\"histogram\"" - msgid "send results to standard output" msgstr "envoyer les résultats sur la sortie standard" @@ -9174,12 +9233,6 @@ msgid "use a zealous diff3 based merge" msgstr "utiliser une fusion basée sur un diff3 zélée" -msgid "<algorithm>" -msgstr "<algorithme>" - -msgid "choose a diff algorithm" -msgstr "choisir un algorithme de différence" - msgid "for conflicts, use this marker size" msgstr "pour les conflits, utiliser cette taille de marqueur" @@ -10271,6 +10324,12 @@ msgstr "impossible d'obtenir le type de l'objet %s dans le paquet %s" #, c-format +msgid "packfile %s is a promisor but --exclude-promisor-objects was given" +msgstr "" +"le fichier paquet %s est un prometteur mais --exclude-promisor-objects a été " +"spécifé" + +#, c-format msgid "could not find pack '%s'" msgstr "impossible de trouver le paquet '%s'" @@ -10539,10 +10598,10 @@ msgid "git patch-id [--stable | --unstable | --verbatim]" msgstr "git patch-id [--stable | --unstable | --verbatim]" -msgid "use the unstable patch-id algorithm" -msgstr "utiliser l'algorithme instable patch-id" +msgid "use the unstable patch ID algorithm" +msgstr "utiliser l'algorithme patch-id instable" -msgid "use the stable patch-id algorithm" +msgid "use the stable patch ID algorithm" msgstr "utiliser l'algorithme stable patch-id" msgid "don't strip whitespace from the patch" @@ -10566,39 +10625,6 @@ msgid "git pull [<options>] [<repository> [<refspec>...]]" msgstr "git pull [<options>] [<dépôt> [<spécification-de-référence>...]]" -msgid "control for recursive fetching of submodules" -msgstr "contrôler la récupération récursive dans les sous-modules" - -msgid "Options related to merging" -msgstr "Options relatives à la fusion" - -msgid "incorporate changes by rebasing rather than merging" -msgstr "incorporer les modifications en rebasant plutôt qu'en fusionnant" - -msgid "allow fast-forward" -msgstr "autoriser l'avance rapide" - -msgid "control use of pre-merge-commit and commit-msg hooks" -msgstr "contrôler l'utilisation des crochets pre-merge-commit et commit-msg" - -msgid "automatically stash/stash pop before and after" -msgstr "remiser et réappliquer automatiquement avant et après" - -msgid "Options related to fetching" -msgstr "Options relatives au rapatriement" - -msgid "force overwrite of local branch" -msgstr "forcer l'écrasement de la branche locale" - -msgid "number of submodules pulled in parallel" -msgstr "nombre de sous-modules tirés en parallèle" - -msgid "use IPv4 addresses only" -msgstr "n'utiliser que des adresses IPv4" - -msgid "use IPv6 addresses only" -msgstr "n'utiliser que des adresses IPv6" - msgid "" "There is no candidate for rebasing against among the refs that you just " "fetched." @@ -10705,6 +10731,39 @@ "passer --rebase, --no-rebase ou --ff-only sur la ligne de commande pour\n" "remplacer à l'invocation la valeur par défaut configurée.\n" +msgid "control for recursive fetching of submodules" +msgstr "contrôler la récupération récursive dans les sous-modules" + +msgid "Options related to merging" +msgstr "Options relatives à la fusion" + +msgid "incorporate changes by rebasing rather than merging" +msgstr "incorporer les modifications en rebasant plutôt qu'en fusionnant" + +msgid "allow fast-forward" +msgstr "autoriser l'avance rapide" + +msgid "control use of pre-merge-commit and commit-msg hooks" +msgstr "contrôler l'utilisation des crochets pre-merge-commit et commit-msg" + +msgid "automatically stash/stash pop before and after" +msgstr "remiser et réappliquer automatiquement avant et après" + +msgid "Options related to fetching" +msgstr "Options relatives au rapatriement" + +msgid "force overwrite of local branch" +msgstr "forcer l'écrasement de la branche locale" + +msgid "number of submodules pulled in parallel" +msgstr "nombre de sous-modules tirés en parallèle" + +msgid "use IPv4 addresses only" +msgstr "n'utiliser que des adresses IPv4" + +msgid "use IPv6 addresses only" +msgstr "n'utiliser que des adresses IPv6" + msgid "Updating an unborn branch with changes added to the index." msgstr "" "Mise à jour d'une branche non encore créée avec les changements ajoutés dans " @@ -12600,6 +12659,10 @@ msgid "only one pattern can be given with -l" msgstr "-l n'accepte qu'un motifs" +#, c-format +msgid "'%s' is not a valid commit-ish for %s" +msgstr "'%s' n'est pas un commit-esque valide pour l'option %s" + msgid "need some commits to replay" msgstr "commits requis pour pouvoir rejouer" @@ -12616,28 +12679,16 @@ "impossible d'avancer la cible avec des sources multiples parce l'ordre ne " "serait pas total" -msgid "" -"cannot implicitly determine whether this is an --advance or --onto operation" -msgstr "" -"impossible de déterminer implicitement s'il y a une opération --advance ou --" -"onto" - -msgid "" -"cannot advance target with multiple source branches because ordering would " -"be ill-defined" -msgstr "" -"impossible d'avancer la cible sur des branches sources multiples parce que " -"l'ordre ne serait pas total" - -msgid "cannot implicitly determine correct base for --onto" -msgstr "impossible de déterminer implicitement une base correcte pour --onto" +#, c-format +msgid "invalid %s value: '%s'" +msgstr "valeur invalide de %s : '%s'" msgid "" "(EXPERIMENTAL!) git replay ([--contained] --onto <newbase> | --advance " -"<branch>) <revision-range>..." +"<branch>) [--ref-action[=<mode>]] <revision-range>" msgstr "" "(EXPERIMENTAL!) git replay ([--contained] --onto <nouvelle-base> | --advance " -"<branche>) <plage-de-révision>..." +"<branche>) [--ref-action[=<mode>]] <plage-de-révision>" msgid "make replay advance given branch" msgstr "faire rejouer en avançant la branche indiquée" @@ -12645,8 +12696,13 @@ msgid "replay onto given commit" msgstr "rejouer par-dessus le commit indiqué" -msgid "advance all branches contained in revision-range" -msgstr "avancer toutes les branches contenues dans la plage-de-révisions" +msgid "update all branches that point at commits in <revision-range>" +msgstr "" +"mettre à jour les branches qui pointent sur les commits dans <plage-de-" +"révisions>" + +msgid "control ref update behavior (update|print)" +msgstr "contrôler le comportement de mise à jour de ref (update|print)" msgid "option --onto or --advance is mandatory" msgstr "une option --onto ou --advance est obligatoire" @@ -12659,16 +12715,28 @@ "certaines options de parcours de révs seront surchargées car le bit '%s' " "dans 'struct rev_info' sera forcé" +#, c-format +msgid "failed to begin ref transaction: %s" +msgstr "impossible de commencer la transaction de réf ; %s" + msgid "error preparing revisions" msgstr "erreur lors de la préparation des révisions" -msgid "replaying down to root commit is not supported yet!" -msgstr "rejouer jusqu'au commit racine n'est pas encore géré !" +msgid "replaying down from root commit is not supported yet!" +msgstr "rejouer depuis le commit racine n'est pas encore géré !" msgid "replaying merge commits is not supported yet!" msgstr "rejouer des commits de fusion n'est pas encore géré !" #, c-format +msgid "failed to update ref '%s': %s" +msgstr "échec de la mise à jour de la réf '%s' : %s" + +#, c-format +msgid "failed to commit ref transaction: %s" +msgstr "impossible de valider la transaction de réf : %s" + +#, c-format msgid "key '%s' not found" msgstr "clé '%s' non trouvée" @@ -12682,9 +12750,15 @@ msgid "synonym for --format=nul" msgstr "synonymes de --format=nul" +msgid "print all keys/values" +msgstr "afficher toutes les clés/valeurs" + msgid "unsupported output format" msgstr "format de sortie non géré" +msgid "--all and <key> cannot be used together" +msgstr "--all et <clé> ne peuvent pas être utilisées ensemble" + msgid "References" msgstr "Références" @@ -12715,6 +12789,12 @@ msgid "Blobs" msgstr "Blobs" +msgid "Inflated size" +msgstr "taille décompressée" + +msgid "Disk size" +msgstr "Taille sur disque" + msgid "Repository structure" msgstr "Structure de dépôt" @@ -15271,6 +15351,10 @@ msgid "bundle list at '%s' has no mode" msgstr "la liste de colis n'a pas de mode à '%s'" +#, c-format +msgid "bundle list at '%s': bundle '%s' has no uri" +msgstr "la liste de colis à '%s' ; le colis '%s' n'a pas d'URI" + msgid "failed to create temporary file" msgstr "impossible de créer un fichier temporaire" @@ -15293,6 +15377,10 @@ msgstr "limite de récursion d'URI de colis dépassée (%d)" #, c-format +msgid "bundle '%s' has no uri" +msgstr "le colis '%s' n'a pas d'URI" + +#, c-format msgid "failed to download bundle from URI '%s'" msgstr "impossible de télécharger le colis depuis l'URI '%s'" @@ -15805,8 +15893,8 @@ msgid "Reuse recorded resolution of conflicted merges" msgstr "Réutiliser une résolution enregistrée de fusions conflictuelles" -msgid "Reset current HEAD to the specified state" -msgstr "Réinitialiser la HEAD courante à l'état spécifié" +msgid "Set `HEAD` or the index to a known state" +msgstr "Positionner `HEAD` ou l'index à un état connu" msgid "Restore working tree files" msgstr "Restaurer les fichiers l'arbre de travail" @@ -17376,10 +17464,6 @@ "Valeur inconnue pour la variable de configuration 'diff.submodule' : '%s'" #, c-format -msgid "unknown value for config '%s': %s" -msgstr "valeur inconnue pour la config '%s' : %s" - -#, c-format msgid "" "Found errors in 'diff.dirstat' config variable:\n" "%s" @@ -19555,6 +19639,10 @@ msgid "%s: failed to insert into database" msgstr "%s : échec de l'insertion dans la base de données" +msgid "cannot add a submodule of a different hash algorithm" +msgstr "" +"impossible d'ajouter un sous-module ayant un algorithme d'empreinte différent" + #, c-format msgid "%s: unsupported file type" msgstr "%s : type de fichier non supporté" @@ -21037,6 +21125,10 @@ msgid "--format=%.*s cannot be used with --python, --shell, --tcl" msgstr "--format=%.*s ne peut pas être utilisé avec --python, --shell, --tcl" +#, c-format +msgid "parse_object_buffer failed on %s for %s" +msgstr "échec de parse_object_buffer sur %s pour %s" + msgid "failed to run 'describe'" msgstr "échec pour lancer 'describe'" @@ -21068,10 +21160,6 @@ msgstr "objet manquant %s pour %s" #, c-format -msgid "parse_object_buffer failed on %s for %s" -msgstr "échec de parse_object_buffer sur %s pour %s" - -#, c-format msgid "malformed object at '%s'" msgstr "objet malformé à '%s'" @@ -21377,6 +21465,18 @@ "le nom de réf %s est une réf symbolique, la copie n'est pas prise en charge" #, c-format +msgid "reftable stack for worktree '%s' is broken" +msgstr "la pile de reftable pour l'arbre-de-travail '%s' est cassée" + +#, c-format +msgid "could not create iterator for worktree '%s'" +msgstr "impossible de créer l'itérateur l'arbre-de-travail '%s'" + +#, c-format +msgid "could not read record for worktree '%s'" +msgstr "impossible de lire l'enregistrement pour l'arbre-de-travail '%s'" + +#, c-format msgid "pattern '%s' has no '*'" msgstr "la valeur '%s' du motif n'a pas de '*'" @@ -21800,10 +21900,11 @@ msgid "could not start pack-objects to repack promisor objects" msgstr "" -"ne pas démarrer pack-objects pour ré-empaqueter les objects de prometteur" +"échec du démarrage de pack-objects pour ré-empaqueter les objets de " +"prometteur" msgid "failed to feed promisor objects to pack-objects" -msgstr "Échéc de la fourniture les objets du prometteur à pack-objects" +msgstr "échec de la fourniture les objets du prometteur à pack-objects" msgid "repack: Expecting full hex object ID lines only from pack-objects." msgstr "" @@ -21815,6 +21916,11 @@ "impossible de terminer pack-objects pour ré-empaqueter les objets de " "prometteur" +msgid "could not start pack-objects to repack promisor packs" +msgstr "" +"échec du démarrage de pack-objects pour ré-empaqueter les paquets de " +"prometteur" + #, c-format msgid "pack prefix %s does not begin with objdir %s" msgstr "le préfixe %s ne commence pas avec objdir %s" @@ -22820,10 +22926,6 @@ msgid "illegal label name: '%.*s'" msgstr "nom de label illégal '%.*s'" -#, c-format -msgid "could not resolve '%s'" -msgstr "impossible de résoudre '%s'" - msgid "writing fake root commit" msgstr "écriture d'un commit racine bidon" @@ -23309,49 +23411,68 @@ msgid "bad %s format: %%%.*s" msgstr "mauvais format %s : %%%.*s" -#. TRANSLATORS: IEC 80000-13:2008 gibibyte #, c-format -msgid "%u.%2.2u GiB" -msgstr "%u.%2.2u Gio" +msgid "%u.%2.2u" +msgstr "%u.%2.2u" -#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second -#, c-format -msgid "%u.%2.2u GiB/s" -msgstr "%u.%2.2u Gio/s" +#. TRANSLATORS: SI decimal prefix symbol for 10^9 +msgid "G" +msgstr "G" -#. TRANSLATORS: IEC 80000-13:2008 mebibyte -#, c-format -msgid "%u.%2.2u MiB" -msgstr "%u.%2.2u Mio" +#. TRANSLATORS: SI decimal prefix symbol for 10^6 +msgid "M" +msgstr "M" -#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second -#, c-format -msgid "%u.%2.2u MiB/s" -msgstr "%u.%2.2u Mio/s" +#. TRANSLATORS: SI decimal prefix symbol for 10^3 +msgid "k" +msgstr "k" -#. TRANSLATORS: IEC 80000-13:2008 kibibyte -#, c-format -msgid "%u.%2.2u KiB" -msgstr "%u.%2.2u Kio" +#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second and gibibyte +msgid "GiB/s" +msgstr "GiO/s" -#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second -#, c-format -msgid "%u.%2.2u KiB/s" -msgstr "%u.%2.2u Kio/s" +msgid "GiB" +msgstr "GiO" -#. TRANSLATORS: IEC 80000-13:2008 byte -#, c-format -msgid "%u byte" -msgid_plural "%u bytes" -msgstr[0] "%u octet" -msgstr[1] "%u octets" +#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second and mebibyte +msgid "MiB/s" +msgstr "MiO/s" + +msgid "MiB" +msgstr "MiO" + +#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second and kibibyte +msgid "KiB/s" +msgstr "KiO/s" + +msgid "KiB" +msgstr "KiO" + +#. TRANSLATORS: IEC 80000-13:2008 byte/second and byte +msgid "B/s" +msgstr "Octets/s" + +msgid "B" +msgstr "Octets" #. TRANSLATORS: IEC 80000-13:2008 byte/second +msgid "byte/s" +msgid_plural "bytes/s" +msgstr[0] "octet/s" +msgstr[1] "octets/s" + +#. TRANSLATORS: IEC 80000-13:2008 byte +msgid "byte" +msgid_plural "bytes" +msgstr[0] "octet" +msgstr[1] "octets" + +#. TRANSLATORS: The first argument is the number string. The second +#. argument is the unit string (i.e. "12.34 MiB/s"). +#. #, c-format -msgid "%u byte/s" -msgid_plural "%u bytes/s" -msgstr[0] "%u octet/s" -msgstr[1] "%u octets/s" +msgid "%s %s" +msgstr "%s %s" #, c-format msgid "ignoring suspicious submodule name: %s" @@ -23651,9 +23772,6 @@ msgid "number of requests per thread" msgstr "nombre de requêtes par fil d'exécution" -msgid "byte" -msgstr "octet" - msgid "ballast character" msgstr "caractère ballast" @@ -25268,63 +25386,3 @@ #, perl-format msgid "Do you really want to send %s? [y|N]: " msgstr "Souhaitez-vous réellement envoyer %s ?[y|N] : " - -#~ msgid "No previous hunk" -#~ msgstr "Pas de section précédente" - -#~ msgid "No next hunk" -#~ msgstr "Pas de section suivante" - -#~ msgid "git for-each-ref [<options>] [<pattern>]" -#~ msgstr "git for-each-ref [<options>] [<motif>]" - -#~ msgid "git for-each-ref [--merged [<commit>]] [--no-merged [<commit>]]" -#~ msgstr "git for-each-ref [--merged [<commit>]] [--no-merged [<commit>]]" - -#~ msgid "git for-each-ref [--contains [<commit>]] [--no-contains [<commit>]]" -#~ msgstr "git for-each-ref [--contains [<commit>]] [--no-contains [<commit>]]" - -#~ msgid "git for-each-ref [--start-after <marker>]" -#~ msgstr "git for-each-ref [--start-after <marqueur>]" - -#~ msgid "" -#~ "git pack-refs [--all] [--no-prune] [--auto] [--include <pattern>] [--" -#~ "exclude <pattern>]" -#~ msgstr "" -#~ "git pack-refs [--all] [--no-prune] [--auto] [--include <motif>] [--" -#~ "exclude <motif>]" - -#, c-format -#~ msgid "deleting '%s' failed" -#~ msgstr "échec de suppression de '%s'" - -#, c-format -#~ msgid "creating '%s' failed" -#~ msgstr "échec de création de '%s'" - -#, c-format -#~ msgid "could not open index for %s" -#~ msgstr "impossible d'ouvrir l'index pour %s" - -#~ msgid "cannot handle pushes this big" -#~ msgstr "impossible de gérer des poussées aussi grosses" - -#, c-format -#~ msgid "" -#~ "'%s' is nominated for removal.\n" -#~ "If you still use this command, please add an extra\n" -#~ "option, '--i-still-use-this', on the command line\n" -#~ "and let us know you still use it by sending an e-mail\n" -#~ "to <git@vger.kernel.org>. Thanks.\n" -#~ msgstr "" -#~ "La suppression de '%s' est prévue.\n" -#~ "Si vous utilisez cette commande, veuillez ajouter\n" -#~ "une option supplémentaire, '--i-still-use-this',\n" -#~ "sur la ligne de commande pour nous avertir par\n" -#~ "un courriel à <git@vger.kernel.org>. Merci.\n" - -#~ msgid "start-after" -#~ msgstr "start-after" - -#~ msgid "compact-summary" -#~ msgstr "résumé-compact"
diff --git a/po/ga.po b/po/ga.po index bed2ea2..4c05a25 100644 --- a/po/ga.po +++ b/po/ga.po
@@ -7,8 +7,8 @@ msgstr "" "Project-Id-Version: Git\n" "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n" -"POT-Creation-Date: 2025-11-06 23:58+0000\n" -"PO-Revision-Date: 2025-11-07 21:39+0000\n" +"POT-Creation-Date: 2026-01-22 23:57+0000\n" +"PO-Revision-Date: 2026-01-23 10:47+0000\n" "Last-Translator: Aindriú Mac Giolla Eoin <aindriu80@gmail.com>\n" "Language-Team: none\n" "Language: ga\n" @@ -16,7 +16,7 @@ "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n==2 ? 1 : 2;\n" -"X-Generator: Poedit 3.7\n" +"X-Generator: Poedit 3.8\n" #, c-format msgid "%s cannot be negative" @@ -382,8 +382,8 @@ #, c-format msgid "Discard this hunk from index and worktree [y,n,q,a,d%s,?]? " msgstr "" -"An bhfuil an píosa beag seo le fáil réidh ón innéacs agus ón gcrann oibre " -"[y,n,q,a,d%s,?]? " +"An bhfuil an píosa beag seo le fáil réidh ón innéacs agus ón gcrann oibre [y," +"n,q,a,d%s,?]? " msgid "" "y - discard this hunk from index and worktree\n" @@ -402,8 +402,8 @@ #, c-format msgid "Apply mode change to index and worktree [y,n,q,a,d%s,?]? " msgstr "" -"Cuir athrú mód i bhfeidhm ar an innéacs agus ar an gcrann oibre " -"[y,n,q,a,d%s,?]? " +"Cuir athrú mód i bhfeidhm ar an innéacs agus ar an gcrann oibre [y,n,q,a," +"d%s,?]? " #, c-format msgid "Apply deletion to index and worktree [y,n,q,a,d%s,?]? " @@ -413,14 +413,14 @@ #, c-format msgid "Apply addition to index and worktree [y,n,q,a,d%s,?]? " msgstr "" -"Cuir an breiseán i bhfeidhm ar an innéacs agus ar an gcrann oibre " -"[y,n,q,a,d%s,?]? " +"Cuir an breiseán i bhfeidhm ar an innéacs agus ar an gcrann oibre [y,n,q,a," +"d%s,?]? " #, c-format msgid "Apply this hunk to index and worktree [y,n,q,a,d%s,?]? " msgstr "" -"Cuir an píosa seo i bhfeidhm ar an innéacs agus ar an gcrann oibre " -"[y,n,q,a,d%s,?]? " +"Cuir an píosa seo i bhfeidhm ar an innéacs agus ar an gcrann oibre [y,n,q,a," +"d%s,?]? " msgid "" "y - apply this hunk to index and worktree\n" @@ -618,9 +618,9 @@ #, c-format msgid "Sorry, only %d hunk available." msgid_plural "Sorry, only %d hunks available." -msgstr[0] "Tá brón orm, níl ach %d píosa ar fáil." -msgstr[1] "Tá brón orm, níl ach %d hunks ar fáil." -msgstr[2] "Tá brón orm, níl ach %d hunks ar fáil." +msgstr[0] "Ár leithscéal, níl ach %d píosa ar fáil." +msgstr[1] "Ár leithscéal, níl ach %d hunks ar fáil." +msgstr[2] "Ár leithscéal, níl ach %d hunks ar fáil." msgid "No other hunks to search" msgstr "Níl aon phíosa eile le cuardach" @@ -636,14 +636,14 @@ msgstr "Níl aon hunk ag teacht leis an bpatrún tugtha" msgid "Sorry, cannot split this hunk" -msgstr "Tá brón orainn, ní féidir an hunk seo a roinnt" +msgstr "Ár leithscéal, ní féidir an hunk seo a roinnt" #, c-format msgid "Split into %d hunks." msgstr "Roinn ina %d hunks." msgid "Sorry, cannot edit this hunk" -msgstr "Tá brón orainn, ní féidir an hunk seo a chur in eagar" +msgstr "Ár leithscéal, ní féidir an hunk seo a chur in eagar" #, c-format msgid "Unknown command '%s' (use '?' for help)" @@ -1755,12 +1755,10 @@ msgid "not tracking: ambiguous information for ref '%s'" msgstr "gan rianú: faisnéis dhébhríoch le haghaidh tagairt '%s'" -#. #-#-#-#-# branch.c.po #-#-#-#-# #. TRANSLATORS: This is a line listing a remote with duplicate #. refspecs in the advice message below. For RTL languages you'll #. probably want to swap the "%s" and leading " " space around. #. -#. #-#-#-#-# object-name.c.po #-#-#-#-# #. TRANSLATORS: This is line item of ambiguous object output #. from describe_ambiguous_object() above. For RTL languages #. you'll probably want to swap the "%s" and leading " " space @@ -1798,8 +1796,8 @@ msgid "'%s' is not a valid branch name" msgstr "Ní ainm brainse bailí é '%s'" -msgid "See `man git check-ref-format`" -msgstr "Féach `man git check-ref-format`" +msgid "See 'git help check-ref-format'" +msgstr "Féach 'git help check-ref-format'" #, c-format msgid "a branch named '%s' already exists" @@ -2715,6 +2713,16 @@ msgstr "caithfidh deireadh a chur le dath" #, c-format +msgid "unknown value for config '%s': %s" +msgstr "luach anaithnid do chumraíocht '%s': %s" + +msgid "" +"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and " +"\"histogram\"" +msgstr "" +"glacann difr-algartam rogha le “myers”, “íosta”, “foighne” agus “histogram”" + +#, c-format msgid "cannot find revision %s to ignore" msgstr "ní féidir athbhreithniú %s a fháil le neamhair" @@ -2769,6 +2777,12 @@ msgid "ignore whitespace differences" msgstr "neamhaird a dhéanamh ar dhifríochtaí spás b" +msgid "<algorithm>" +msgstr "<algorithm>" + +msgid "choose a diff algorithm" +msgstr "roghnaigh algartam diff" + msgid "rev" msgstr "rev" @@ -2784,8 +2798,8 @@ msgid "color lines by age" msgstr "línte datha de réir aois" -msgid "spend extra cycles to find better match" -msgstr "timthriallta breise a chaitheamh chun meaitseáil níos fearr" +msgid "spend extra cycles to find a better match" +msgstr "caith timthriallta breise chun meaitseáil níos fearr a aimsiú" msgid "use revisions from <file> instead of calling git-rev-list" msgstr "athbhreithnithe a úsáid as in <file>ionad glaoch ar git-rev-list" @@ -3100,11 +3114,11 @@ msgstr "Ní fhaightear CEAD thíos na refs/heads!" msgid "" -"branch with --recurse-submodules can only be used if " -"submodule.propagateBranches is enabled" +"branch with --recurse-submodules can only be used if submodule." +"propagateBranches is enabled" msgstr "" -"ní féidir brainse le --recurse-submodules a úsáid ach amháin má tá " -"submodule.propagateBranches cumasaithe" +"ní féidir brainse le --recurse-submodules a úsáid ach amháin má tá submodule." +"propagateBranches cumasaithe" msgid "--recurse-submodules can only be used to create branches" msgstr "Ní féidir --recurse-submodules a úsáid ach chun brainsí a chruthú" @@ -3997,8 +4011,8 @@ msgstr "ainm brainse ar iarraidh; iarracht -%c" #, c-format -msgid "could not resolve %s" -msgstr "ní fhéadfaí %s a réiteach" +msgid "could not resolve '%s'" +msgstr "ní fhéadfaí '%s' a réiteach" msgid "invalid path specification" msgstr "sonraíocht chosáin neamhbhailí" @@ -5493,10 +5507,17 @@ #, c-format msgid "" "cannot overwrite multiple values with a single value\n" -" Use a regexp, --add or --replace-all to change %s." +" Use --value=<pattern>, --append or --all to change %s." msgstr "" -"ní féidir luachanna iolracha a fhorscríobh le luach amháin\n" -" Úsáid regexp, --add nó --replace-all chun %s a athrú." +"ní féidir luachanna iolracha a athscríobh le luach amháin. \n" +" Úsáid --value=<patrún>, --append nó --all chun %s a athrú." + +msgid "unset all multi-valued config options" +msgstr "dísocraigh gach rogha cumraíochta illuachmhar" + +msgid "unset multi-valued config options with matching values" +msgstr "" +"roghanna cumraíochta illuachacha a dhíshuiteáil le luachanna comhoiriúnacha" #, c-format msgid "no such section: %s" @@ -5583,6 +5604,14 @@ msgstr "" "Níl --comment infheidhme ach le hoibríochtaí a chur leis /socraí/athsholáthar" +#, c-format +msgid "" +"cannot overwrite multiple values with a single value\n" +" Use a regexp, --add or --replace-all to change %s." +msgstr "" +"ní féidir luachanna iolracha a fhorscríobh le luach amháin\n" +" Úsáid regexp, --add nó --replace-all chun %s a athrú." + msgid "print sizes in human readable format" msgstr "méideanna priontála i bhformáid inléite don duine" @@ -5988,12 +6017,6 @@ "as --reencode=[yes|no] chun é a láimhseáil" #, c-format -msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it" -msgstr "" -"bhuail an tiomantas sínithe %s; bain úsáid as --signed-commits=<mode> chun é " -"a láimhseáil" - -#, c-format msgid "exporting %<PRIuMAX> signature(s) for commit %s" msgstr "ag easpórtáil %<PRIuMAX> sínithe le haghaidh tiomnú %s" @@ -6002,6 +6025,19 @@ msgstr "ag baint sínithe ó thiomnadh %s" #, c-format +msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it" +msgstr "" +"bhuail an tiomantas sínithe %s; bain úsáid as --signed-commits=<mode> chun é " +"a láimhseáil" + +msgid "" +"'strip-if-invalid' is not a valid mode for git fast-export with --signed-" +"commits=<mode>" +msgstr "" +"'strip-if-invalid' ní mód bailí é seo le haghaidh easpórtáil le haghaidh git fast-export le --signed-" +"commits=<mód>" + +#, c-format msgid "" "omitting tag %s,\n" "since tags of trees (or tags of tags of trees, etc.) are not supported." @@ -6014,12 +6050,6 @@ msgstr "níorbh fhéidir an clib %s a léamh" #, c-format -msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it" -msgstr "" -"bhuail mé le clib shínithe %s; bain úsáid as --signed-tags=<mode> chun é a " -"láimhseáil" - -#, c-format msgid "exporting signed tag %s" msgstr "clib shínithe %s á onnmhairiú" @@ -6028,6 +6058,19 @@ msgstr "ag baint síniú ón gclib %s" #, c-format +msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it" +msgstr "" +"bhuail mé le clib shínithe %s; bain úsáid as --signed-tags=<mode> chun é a " +"láimhseáil" + +msgid "" +"'strip-if-invalid' is not a valid mode for git fast-export with --signed-" +"tags=<mode>" +msgstr "" +"'strip-if-invalid' ní mód bailí é seo le haghaidh git fast-export le --signed-" +"tags=<mode>" + +#, c-format msgid "" "tag %s tags unexported object; use --tag-of-filtered-object=<mode> to handle " "it" @@ -6419,6 +6462,33 @@ msgid "parse_one_signature() returned unknown hash algo" msgstr "d’fhill parse_one_signature() algartam haise anaithnid" +msgid "unknown" +msgstr "anaithnid" + +#, c-format +msgid "" +"stripping invalid signature for commit '%.100s...'\n" +" allegedly by %s" +msgstr "" +"ag baint síniú neamhbhailí don tiomantas '%.100s...' \n" +" de réir dealraimh le %s" + +#, c-format +msgid "" +"stripping invalid signature for commit '%.*s'\n" +" allegedly by %s" +msgstr "" +"ag baint síniú neamhbhailí don tiomantas '%.*s'\n" +" de réir dealraimh le %s" + +#, c-format +msgid "" +"stripping invalid signature for commit\n" +" allegedly by %s" +msgstr "" +"ag baint síniú neamhbhailí don tiomnú\n" +" de réir dealraimh ag %s" + msgid "expected committer but didn't get one" msgstr "bhí mé ag súil le gealltóir ach ní bhfuair mé ceann" @@ -6433,11 +6503,6 @@ msgid "importing a commit signature verbatim" msgstr "síniú tiomantais a allmhairiú focal ar fhocal" -msgid "encountered signed tag; use --signed-tags=<mode> to handle it" -msgstr "" -"bhuail mé le clib shínithe; bain úsáid as --signed-tags=<mode> chun é a " -"láimhseáil" - #, c-format msgid "importing a tag signature verbatim for tag '%s'" msgstr "ag allmhairiú síniú clibe focal ar fhocal don chlib '%s'" @@ -6446,6 +6511,18 @@ msgid "stripping a tag signature for tag '%s'" msgstr "ag baint síniú clibe don chlib '%s'" +msgid "encountered signed tag; use --signed-tags=<mode> to handle it" +msgstr "" +"bhuail mé le clib shínithe; bain úsáid as --signed-tags=<mode> chun é a " +"láimhseáil" + +msgid "" +"'strip-if-invalid' is not a valid mode for git fast-import with --signed-" +"tags=<mode>" +msgstr "" +"Ní mód bailí é 'strip-if-invalid' le haghaidh git fast-import le --signed-" +"tags=<mode>" + #, c-format msgid "expected 'from' command, got '%s'" msgstr "ag súil le hordú 'from', fuarthas '%s'" @@ -6603,8 +6680,8 @@ msgid "git fetch [<options>] <group>" msgstr "git fetch [<options>] <group>" -msgid "git fetch --multiple [<options>] [(<repository> | <group>)...]" -msgstr "git fetch --multiple [<options>] [(<repository> | <group>)...]" +msgid "git fetch --multiple [<options>] [(<repository>|<group>)...]" +msgstr "git fetch --multiple [<roghanna>] [(<stór>|<grúpa>)...]" msgid "git fetch --all [<options>]" msgstr "git fetch --all [<options>]" @@ -6971,8 +7048,8 @@ msgstr "ní thacaíonn an prótacal le --negotiate-only, ag scoir" msgid "" -"--filter can only be used with the remote configured in " -"extensions.partialclone" +"--filter can only be used with the remote configured in extensions." +"partialclone" msgstr "" "--filter Ní féidir ach an scagaire a úsáid ach leis an iargúlta cumraithe in " "extensions.partialclone" @@ -7085,9 +7162,6 @@ msgid "got bad config --config=%s" msgstr "fuair cumraíocht go dona --config=%s" -msgid "unknown" -msgstr "anaithnid" - #. TRANSLATORS: e.g. error in tree 01bfda: <more explanation> #, c-format msgid "error in %s %s: %s" @@ -7182,6 +7256,10 @@ msgid "%s: not a commit" msgstr "%s: ní gealltanas" +#, c-format +msgid "invalid parameter: expected sha1, got '%s'" +msgstr "paraiméadar neamhbhailí: súil le sha1, fuair '%s'" + msgid "notice: No default references" msgstr "fógra: Gan aon tagairtí réamhshocraithe" @@ -7208,26 +7286,6 @@ msgstr "Seiceáil eolairí réada" #, c-format -msgid "Checking %s link" -msgstr "Nasc %s a sheiceáil" - -#, c-format -msgid "invalid %s" -msgstr "%s neamhbhailí" - -#, c-format -msgid "%s points to something strange (%s)" -msgstr "Léiríonn %s rud éigin aisteach (%s)" - -#, c-format -msgid "%s: detached HEAD points at nothing" -msgstr "%s: pointí HEAD scoite ag aon rud" - -#, c-format -msgid "notice: %s points to an unborn branch (%s)" -msgstr "fógra: Díríonn %s chuig brainse neamhbhreithe (%s)" - -#, c-format msgid "Checking cache tree of %s" msgstr "Crann taisce de %s a sheiceáil" @@ -7306,14 +7364,6 @@ msgid "Checking objects" msgstr "Rud a sheiceáil" -#, c-format -msgid "%s: object missing" -msgstr "%s: réad ar iarraidh" - -#, c-format -msgid "invalid parameter: expected sha1, got '%s'" -msgstr "paraiméadar neamhbhailí: súil le sha1, fuair '%s'" - msgid "git fsmonitor--daemon start [<options>]" msgstr "git fsmonitor--daemon start [<options>]" @@ -7534,8 +7584,8 @@ msgid "" "skipping incremental-repack task because core.multiPackIndex is disabled" msgstr "" -"ag scipeáil an tasc athphacála incriminteach mar go bhfuil " -"core.multiPackIndex díchumasaithe" +"ag scipeáil an tasc athphacála incriminteach mar go bhfuil core." +"multiPackIndex díchumasaithe" msgid "failed to perform geometric repack" msgstr "theip ar athphacáil gheoiméadrach a dhéanamh" @@ -7698,6 +7748,9 @@ msgid "failed to add repo to global config" msgstr "theip ar repo a chur le cumraíocht domhanda" +msgid "check a specific task" +msgstr "seiceáil tasc ar leith" + msgid "git maintenance <subcommand> [<options>]" msgstr "git maintenance <subcommand> [<options>]" @@ -7712,7 +7765,6 @@ msgid "invalid number of threads specified (%d) for %s" msgstr "líon neamhbhailí na snáitheanna a shonraítear (%d) do %s" -#. #-#-#-#-# grep.c.po #-#-#-#-# #. TRANSLATORS: %s is the configuration #. variable for tweaking threads, currently #. grep.threads @@ -8156,6 +8208,10 @@ msgstr "earráid fsck i réad pacáilte" #, c-format +msgid "invalid %s" +msgstr "%s neamhbhailí" + +#, c-format msgid "Not all child objects of %s are reachable" msgstr "Ní féidir rochtain a fháil ar gach réad linbh de %s" @@ -8299,6 +8355,9 @@ msgid "--verify with no packfile name given" msgstr "--verify gan ainm comhaid pacáiste tugtha" +msgid "cannot perform queued object checks outside of a repository" +msgstr "ní féidir seiceálacha réada scuaine a dhéanamh lasmuigh de stórlann" + msgid "fsck error in pack objects" msgstr "fsck earráid i rudaí pacáiste" @@ -9055,12 +9114,6 @@ "git merge-file [<options>] [-L <name1> [-L <orig> [-L <name2>]]] <file1> " "<orig-file> <file2>" -msgid "" -"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and " -"\"histogram\"" -msgstr "" -"glacann difr-algartam rogha le “myers”, “íosta”, “foighne” agus “histogram”" - msgid "send results to standard output" msgstr "torthaí a sheoladh chuig aschur caigh" @@ -9073,12 +9126,6 @@ msgid "use a zealous diff3 based merge" msgstr "bain úsáid as cumaisc díograiseach bunaithe ar diff3" -msgid "<algorithm>" -msgstr "<algorithm>" - -msgid "choose a diff algorithm" -msgstr "roghnaigh algartam diff" - msgid "for conflicts, use this marker size" msgstr "le haghaidh coinbhleachtaí, bain úsáid as an méid marcóra" @@ -10050,8 +10097,8 @@ msgid "disabling bitmap writing, packs are split due to pack.packSizeLimit" msgstr "" -"scríobh bitmap a dhíchumasú, roinntear pacáistí mar gheall ar " -"pack.packSizeLimit" +"scríobh bitmap a dhíchumasú, roinntear pacáistí mar gheall ar pack." +"packSizeLimit" msgid "Writing objects" msgstr "Rudaí a scríobh" @@ -10158,6 +10205,10 @@ msgstr "ní fhéadfaí cineál réada %s a fháil i bpacáiste %s" #, c-format +msgid "packfile %s is a promisor but --exclude-promisor-objects was given" +msgstr "is gealltóir é an comhad paca %s ach tugadh --exclude-promisor-objects" + +#, c-format msgid "could not find pack '%s'" msgstr "ní raibh an pacáiste '%s' in ann a aimsiú" @@ -10417,11 +10468,11 @@ msgid "git patch-id [--stable | --unstable | --verbatim]" msgstr "git patch-id [--seasmhach | --éagobhsaí | --verbatim]" -msgid "use the unstable patch-id algorithm" -msgstr "bain úsáid as an algartam paith-id éagobhsaí" +msgid "use the unstable patch ID algorithm" +msgstr "bain úsáid as an algartam aitheantais paiste éagobhsaí" -msgid "use the stable patch-id algorithm" -msgstr "bain úsáid as an algartam paith-id cobhsaí" +msgid "use the stable patch ID algorithm" +msgstr "bain úsáid as an algartam ID paiste cobhsaí" msgid "don't strip whitespace from the patch" msgstr "ná tarraingt spás bán ón bpaiste" @@ -10444,39 +10495,6 @@ msgid "git pull [<options>] [<repository> [<refspec>...]]" msgstr "git pull [<options>] [<repository> [<refspec>...]]" -msgid "control for recursive fetching of submodules" -msgstr "rialú maidir le fo-mhodúil a fháil athshlánach" - -msgid "Options related to merging" -msgstr "Roghanna a bhaineann le cumasc" - -msgid "incorporate changes by rebasing rather than merging" -msgstr "athruithe a ionchorprú trí athbhunú seachas cumasc" - -msgid "allow fast-forward" -msgstr "ligean go tapa ar aghaidh" - -msgid "control use of pre-merge-commit and commit-msg hooks" -msgstr "úsáid crúcaí réamh-chumaisc agus comh-msg a rialú" - -msgid "automatically stash/stash pop before and after" -msgstr "pop a stash/stash go huathoibríoch roimh agus tar éis" - -msgid "Options related to fetching" -msgstr "Roghanna a bhaineann le tarraingt" - -msgid "force overwrite of local branch" -msgstr "forscríobh fórsa ar bhrainse áitiúil" - -msgid "number of submodules pulled in parallel" -msgstr "líon na bhfo-mhodúil tarraingthe go comhthreom" - -msgid "use IPv4 addresses only" -msgstr "bain úsáid as seoltaí IPv4 amháin" - -msgid "use IPv6 addresses only" -msgstr "bain úsáid as seoltaí IPv6 amháin" - msgid "" "There is no candidate for rebasing against among the refs that you just " "fetched." @@ -10585,6 +10603,39 @@ "shárú\n" "ionghairm.\n" +msgid "control for recursive fetching of submodules" +msgstr "rialú maidir le fo-mhodúil a fháil athshlánach" + +msgid "Options related to merging" +msgstr "Roghanna a bhaineann le cumasc" + +msgid "incorporate changes by rebasing rather than merging" +msgstr "athruithe a ionchorprú trí athbhunú seachas cumasc" + +msgid "allow fast-forward" +msgstr "ligean go tapa ar aghaidh" + +msgid "control use of pre-merge-commit and commit-msg hooks" +msgstr "úsáid crúcaí réamh-chumaisc agus comh-msg a rialú" + +msgid "automatically stash/stash pop before and after" +msgstr "pop a stash/stash go huathoibríoch roimh agus tar éis" + +msgid "Options related to fetching" +msgstr "Roghanna a bhaineann le tarraingt" + +msgid "force overwrite of local branch" +msgstr "forscríobh fórsa ar bhrainse áitiúil" + +msgid "number of submodules pulled in parallel" +msgstr "líon na bhfo-mhodúil tarraingthe go comhthreom" + +msgid "use IPv4 addresses only" +msgstr "bain úsáid as seoltaí IPv4 amháin" + +msgid "use IPv6 addresses only" +msgstr "bain úsáid as seoltaí IPv6 amháin" + msgid "Updating an unborn branch with changes added to the index." msgstr "Brainse breithe a nuashonrú le hathruithe curtha leis an innéacs." @@ -11853,8 +11904,8 @@ msgstr "" "Tá tagairtí contrártha sa\n" "tagarmharc sprice nua ag an gcianrialtán atá tú ag iarraidh a athainmniú. Is " -"dóichí gur mar gheall ar iarracht a dhéanamh cianrialtán a neadú ann féin, " -"e.g. trí 'tuismitheoir' a athainmniú go 'tuismitheoir/leanbh'\n" +"dóichí gur mar gheall ar iarracht a dhéanamh cianrialtán a neadú ann féin, e." +"g. trí 'tuismitheoir' a athainmniú go 'tuismitheoir/leanbh'\n" "nó trí chianrialtán a dhí-neadú, e.g. an bealach eile.\n" "\n" "Más amhlaidh atá, is féidir leat é seo a réiteach tríd an\n" @@ -12463,6 +12514,10 @@ msgid "only one pattern can be given with -l" msgstr "ní féidir ach patrún amháin a thabhairt le -l" +#, c-format +msgid "'%s' is not a valid commit-ish for %s" +msgstr "Ní comhartha bailí commit-ish é '%s' do %s" + msgid "need some commits to replay" msgstr "teastaíonn roinnt gealltanais chun athsheinm" @@ -12479,27 +12534,16 @@ "ní féidir leis an sprioc a chur chun cinn le foinsí iolracha toisc go mbeadh " "ordú" -msgid "" -"cannot implicitly determine whether this is an --advance or --onto operation" -msgstr "" -"ní féidir a chinneadh go hinneach an oibríocht --advance nó --onto é seo" - -msgid "" -"cannot advance target with multiple source branches because ordering would " -"be ill-defined" -msgstr "" -"ní féidir leis an sprioc a chur chun cinn le brainsí foinse iolracha mar go " -"mbeadh ordú" - -msgid "cannot implicitly determine correct base for --onto" -msgstr "ní féidir leis an mbonn ceart do --onto a chinneadh go hinneach" +#, c-format +msgid "invalid %s value: '%s'" +msgstr "luach neamhbhailí %s: '%s'" msgid "" "(EXPERIMENTAL!) git replay ([--contained] --onto <newbase> | --advance " -"<branch>) <revision-range>..." +"<branch>) [--ref-action[=<mode>]] <revision-range>" msgstr "" -"(TURGNAMHACH!) git replay ([--contained] --onto <newbase> | --advance " -"<branch>) <revision-range>..." +"(TURGNAMHACH!) athsheinm git ([--contained] --onto <newbase> | --advance " +"<brainse>) [--ref-action[=<mód>]] <raon-athbhreithnithe>" msgid "make replay advance given branch" msgstr "athsheoladh a dhéanamh roimh ré brainse ar leith" @@ -12507,8 +12551,12 @@ msgid "replay onto given commit" msgstr "athsheoladh ar thiomantas a thugtar" -msgid "advance all branches contained in revision-range" -msgstr "gach brainse atá sa raon athbhreithnithe a chur chun cinn" +msgid "update all branches that point at commits in <revision-range>" +msgstr "" +"nuashonraigh na brainsí uile a dhíríonn ar thiomnuithe i <revision-range>" + +msgid "control ref update behavior (update|print)" +msgstr "iompar nuashonraithe tag rialaithe (nuashonrú|priontáil)" msgid "option --onto or --advance is mandatory" msgstr "tá rogha --onto nó --advance éigeantach" @@ -12521,16 +12569,28 @@ "cuirfear roinnt roghanna siúil rev a athshealbhú mar go gcuirfear giotán " "'%s' i 'struct rev_info' iallach" +#, c-format +msgid "failed to begin ref transaction: %s" +msgstr "theip ar thosú an idirbhirt tagartha: %s" + msgid "error preparing revisions" msgstr "earráid ag ullmhú athbhreith" -msgid "replaying down to root commit is not supported yet!" -msgstr "ní thacaítear le athsheinm síos go dtí tiomantas fréimhe fós!" +msgid "replaying down from root commit is not supported yet!" +msgstr "ní thacaítear le hathsheinm anuas ó fréamh-thiomantas go fóill!" msgid "replaying merge commits is not supported yet!" msgstr "ní thacaítear le gealltanna cumaisc athsheinm fós!" #, c-format +msgid "failed to update ref '%s': %s" +msgstr "theip ar an tagairt '%s' a nuashonrú: %s" + +#, c-format +msgid "failed to commit ref transaction: %s" +msgstr "theip ar an idirbheart tagartha a dhéanamh: %s" + +#, c-format msgid "key '%s' not found" msgstr "níor aimsíodh an eochair '%s'" @@ -12544,9 +12604,15 @@ msgid "synonym for --format=nul" msgstr "comhchiallach le --format=nul" +msgid "print all keys/values" +msgstr "priontáil na heochracha/luachanna go léir" + msgid "unsupported output format" msgstr "formáid aschuir neamhthacaithe" +msgid "--all and <key> cannot be used together" +msgstr "Ní féidir --all agus <eochair> a úsáid le chéile" + msgid "References" msgstr "Tagairtí" @@ -12577,6 +12643,12 @@ msgid "Blobs" msgstr "Blobaí" +msgid "Inflated size" +msgstr "Méid insilte" + +msgid "Disk size" +msgstr "Méid diosca" + msgid "Repository structure" msgstr "Struchtúr an stórais" @@ -15087,6 +15159,10 @@ msgid "bundle list at '%s' has no mode" msgstr "níl aon mhodh ag liosta beartán ag '%s'" +#, c-format +msgid "bundle list at '%s': bundle '%s' has no uri" +msgstr "liosta beartán ag '%s': níl aon uri ag an mbeartán '%s'" + msgid "failed to create temporary file" msgstr "theip ar chomhad sealadach a chruthú" @@ -15109,6 +15185,10 @@ msgstr "sháraigh teorainn athshlánaithe URI beartán (%d)" #, c-format +msgid "bundle '%s' has no uri" +msgstr "níl aon URI ag an bpacáiste '%s'" + +#, c-format msgid "failed to download bundle from URI '%s'" msgstr "theip ar an mbeartán a íoslódáil ó URI '%s'" @@ -15615,8 +15695,8 @@ msgid "Reuse recorded resolution of conflicted merges" msgstr "Réiteach taifeadta ar chumaisc coinbhleachta a athúsáid" -msgid "Reset current HEAD to the specified state" -msgstr "Athshocraigh HEAD reatha go dtí an stát sonraithe" +msgid "Set `HEAD` or the index to a known state" +msgstr "Socraigh `HEAD` nó an t-innéacs go staid aitheanta" msgid "Restore working tree files" msgstr "Athchóirigh comhaid crann oibre" @@ -16034,11 +16114,11 @@ #, c-format msgid "" -"attempting to write a commit-graph, but 'commitGraph.changedPathsVersion' " -"(%d) is not supported" +"attempting to write a commit-graph, but 'commitGraph." +"changedPathsVersion' (%d) is not supported" msgstr "" -"ag iarraidh commit-graph a scríobh, ach tá 'commitGraph.changedPathsVersion' " -"(%d) ní thacaítear leis" +"ag iarraidh commit-graph a scríobh, ach tá 'commitGraph." +"changedPathsVersion' (%d) ní thacaítear leis" msgid "too many commits to write graph" msgstr "an iomarca gealltanais graf a scríobh" @@ -17131,10 +17211,6 @@ msgstr "Luach anaithnid d'athróg cumraithe 'diff.submodule': '%s'" #, c-format -msgid "unknown value for config '%s': %s" -msgstr "luach anaithnid do chumraíocht '%s': %s" - -#, c-format msgid "" "Found errors in 'diff.dirstat' config variable:\n" "%s" @@ -18190,8 +18266,8 @@ "given pattern contains NULL byte (via -f <file>). This is only supported " "with -P under PCRE v2" msgstr "" -"tá byte NULL (trí -f<file>) i bpatrún tugtha. Ní thacaítear leis seo ach le " -"-P faoi PCRE v2" +"tá byte NULL (trí -f<file>) i bpatrún tugtha. Ní thacaítear leis seo ach le -" +"P faoi PCRE v2" #, c-format msgid "'%s': unable to read %s" @@ -18357,8 +18433,8 @@ msgstr "" "Rinneadh neamhaird ar an gcroca '%s' toisc nach bhfuil sé socraithe mar " "infheidhmithe.\n" -"Is féidir leat an rabhadh seo a dhíchumasú le `git config set " -"advice.ignoredHook false `." +"Is féidir leat an rabhadh seo a dhíchumasú le `git config set advice." +"ignoredHook false `." msgid "not a git repository" msgstr "ní stór git é" @@ -19282,6 +19358,9 @@ msgid "%s: failed to insert into database" msgstr "%s: theip ort a chur isteach sa bhunachar sonraí" +msgid "cannot add a submodule of a different hash algorithm" +msgstr "ní féidir fomhodúl d'algartam haise difriúil a chur leis" + #, c-format msgid "%s: unsupported file type" msgstr "%s: cineál comhaid gan tacaíocht" @@ -20757,6 +20836,10 @@ msgid "--format=%.*s cannot be used with --python, --shell, --tcl" msgstr "--format=%.*s ní féidir é a úsáid le --python, --shell, --tcl" +#, c-format +msgid "parse_object_buffer failed on %s for %s" +msgstr "theip ar parse_object_buffer ar %s do %s" + msgid "failed to run 'describe'" msgstr "theip ar 'cur síos' a rith" @@ -20788,10 +20871,6 @@ msgstr "réad atá ar iarraidh %s do %s" #, c-format -msgid "parse_object_buffer failed on %s for %s" -msgstr "theip ar parse_object_buffer ar %s do %s" - -#, c-format msgid "malformed object at '%s'" msgstr "réad mífhoirmithe ag '%s'" @@ -21099,6 +21178,18 @@ msgstr "rs tagairt siombalach é refname %s, ní thacaítear leis a chóipeáil" #, c-format +msgid "reftable stack for worktree '%s' is broken" +msgstr "tá an stac ath-inúsáidte don chrann oibre '%s' briste" + +#, c-format +msgid "could not create iterator for worktree '%s'" +msgstr "níorbh fhéidir athrá a chruthú don chrann oibre '%s'" + +#, c-format +msgid "could not read record for worktree '%s'" +msgstr "níorbh fhéidir taifead a léamh don chrann oibre '%s'" + +#, c-format msgid "pattern '%s' has no '*'" msgstr "níl aon '*' ag patrún '%s'" @@ -21537,6 +21628,10 @@ msgstr "" "ní fhéadfadh sé rudaí pacáiste a chríochnú chun rudaí geallta a athphacáil" +msgid "could not start pack-objects to repack promisor packs" +msgstr "" +"níorbh fhéidir pacáistí-réada a thosú chun pacáistí gealltanais a athphacáil" + #, c-format msgid "pack prefix %s does not begin with objdir %s" msgstr "ní thosaíonn réimír pacáiste %s le objdir %s" @@ -22540,10 +22635,6 @@ msgid "illegal label name: '%.*s'" msgstr "ainm lipéid neamhdhleathach: '%.*s'" -#, c-format -msgid "could not resolve '%s'" -msgstr "ní fhéadfaí '%s' a réiteach" - msgid "writing fake root commit" msgstr "tiomantas fréamh bréige a scríobh" @@ -23029,51 +23120,70 @@ msgid "bad %s format: %%%.*s" msgstr "fhormáid %s olc: %%%.*s" -#. TRANSLATORS: IEC 80000-13:2008 gibibyte #, c-format -msgid "%u.%2.2u GiB" -msgstr "%u.%2.2u GiB" +msgid "%u.%2.2u" +msgstr "%u.%2.2u" -#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second -#, c-format -msgid "%u.%2.2u GiB/s" -msgstr "%u.%2.2u GiB/s" +#. TRANSLATORS: SI decimal prefix symbol for 10^9 +msgid "G" +msgstr "G" -#. TRANSLATORS: IEC 80000-13:2008 mebibyte -#, c-format -msgid "%u.%2.2u MiB" -msgstr "%u.%2.2u MiB" +#. TRANSLATORS: SI decimal prefix symbol for 10^6 +msgid "M" +msgstr "M" -#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second -#, c-format -msgid "%u.%2.2u MiB/s" -msgstr "%u.%2.2u MiB/s" +#. TRANSLATORS: SI decimal prefix symbol for 10^3 +msgid "k" +msgstr "k" -#. TRANSLATORS: IEC 80000-13:2008 kibibyte -#, c-format -msgid "%u.%2.2u KiB" -msgstr "%u.%2.2u KiB" +#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second and gibibyte +msgid "GiB/s" +msgstr "GiB/s" -#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second -#, c-format -msgid "%u.%2.2u KiB/s" -msgstr "%u.%2.2u KiB/s" +msgid "GiB" +msgstr "GiB" -#. TRANSLATORS: IEC 80000-13:2008 byte -#, c-format -msgid "%u byte" -msgid_plural "%u bytes" -msgstr[0] "%u beart" -msgstr[1] "%u beart" -msgstr[2] "%u beart" +#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second and mebibyte +msgid "MiB/s" +msgstr "MiB/s" + +msgid "MiB" +msgstr "MiB" + +#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second and kibibyte +msgid "KiB/s" +msgstr "KiB/s" + +msgid "KiB" +msgstr "KiB" + +#. TRANSLATORS: IEC 80000-13:2008 byte/second and byte +msgid "B/s" +msgstr "B/s" + +msgid "B" +msgstr "B" #. TRANSLATORS: IEC 80000-13:2008 byte/second +msgid "byte/s" +msgid_plural "bytes/s" +msgstr[0] "beart/s" +msgstr[1] "bearta/s" +msgstr[2] "bearta/s" + +#. TRANSLATORS: IEC 80000-13:2008 byte +msgid "byte" +msgid_plural "bytes" +msgstr[0] "beart" +msgstr[1] "bearta" +msgstr[2] "bearta" + +#. TRANSLATORS: The first argument is the number string. The second +#. argument is the unit string (i.e. "12.34 MiB/s"). +#. #, c-format -msgid "%u byte/s" -msgid_plural "%u bytes/s" -msgstr[0] "%u beart/s" -msgstr[1] "%u beart/s" -msgstr[2] "%u beart/s" +msgid "%s %s" +msgstr "%s %s" #, c-format msgid "ignoring suspicious submodule name: %s" @@ -23368,9 +23478,6 @@ msgid "number of requests per thread" msgstr "líon iarratais in aghaidh an snáithe" -msgid "byte" -msgstr "beart" - msgid "ballast character" msgstr "carachtar balasta" @@ -24989,6 +25096,86 @@ msgid "Do you really want to send %s? [y|N]: " msgstr "An bhfuil tú ag iarraidh %s a sheoladh i ndáiríre? [y|N]: " +#, c-format +#~ msgid "could not resolve %s" +#~ msgstr "ní fhéadfaí %s a réiteach" + +#, c-format +#~ msgid "Checking %s link" +#~ msgstr "Nasc %s a sheiceáil" + +#, c-format +#~ msgid "%s points to something strange (%s)" +#~ msgstr "Léiríonn %s rud éigin aisteach (%s)" + +#, c-format +#~ msgid "%s: detached HEAD points at nothing" +#~ msgstr "%s: pointí HEAD scoite ag aon rud" + +#, c-format +#~ msgid "notice: %s points to an unborn branch (%s)" +#~ msgstr "fógra: Díríonn %s chuig brainse neamhbhreithe (%s)" + +#, c-format +#~ msgid "%s: object missing" +#~ msgstr "%s: réad ar iarraidh" + +#~ msgid "" +#~ "cannot implicitly determine whether this is an --advance or --onto " +#~ "operation" +#~ msgstr "" +#~ "ní féidir a chinneadh go hinneach an oibríocht --advance nó --onto é seo" + +#~ msgid "" +#~ "cannot advance target with multiple source branches because ordering " +#~ "would be ill-defined" +#~ msgstr "" +#~ "ní féidir leis an sprioc a chur chun cinn le brainsí foinse iolracha mar " +#~ "go mbeadh ordú" + +#~ msgid "cannot implicitly determine correct base for --onto" +#~ msgstr "ní féidir leis an mbonn ceart do --onto a chinneadh go hinneach" + +#~ msgid "advance all branches contained in revision-range" +#~ msgstr "gach brainse atá sa raon athbhreithnithe a chur chun cinn" + +#~ msgid "Reset current HEAD to the specified state" +#~ msgstr "Athshocraigh HEAD reatha go dtí an stát sonraithe" + +#, c-format +#~ msgid "%u.%2.2u GiB/s" +#~ msgstr "%u.%2.2u GiB/s" + +#, c-format +#~ msgid "%u.%2.2u MiB" +#~ msgstr "%u.%2.2u MiB" + +#, c-format +#~ msgid "%u.%2.2u MiB/s" +#~ msgstr "%u.%2.2u MiB/s" + +#, c-format +#~ msgid "%u.%2.2u KiB" +#~ msgstr "%u.%2.2u KiB" + +#, c-format +#~ msgid "%u.%2.2u KiB/s" +#~ msgstr "%u.%2.2u KiB/s" + +#, c-format +#~ msgid "%u byte" +#~ msgid_plural "%u bytes" +#~ msgstr[0] "%u beart" +#~ msgstr[1] "%u beart" +#~ msgstr[2] "%u beart" + +#, c-format +#~ msgid "%u byte/s" +#~ msgid_plural "%u bytes/s" +#~ msgstr[0] "%u beart/s" +#~ msgstr[1] "%u beart/s" +#~ msgstr[2] "%u beart/s" + #~ msgid "No previous hunk" #~ msgstr "Níl aon hunk roimhe seo"
diff --git a/po/id.po b/po/id.po index b85b11c..8a171cc 100644 --- a/po/id.po +++ b/po/id.po
@@ -7,8 +7,8 @@ msgstr "" "Project-Id-Version: Git\n" "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n" -"POT-Creation-Date: 2025-11-06 23:58+0000\n" -"PO-Revision-Date: 2025-11-13 08:58+0700\n" +"POT-Creation-Date: 2026-01-22 23:57+0000\n" +"PO-Revision-Date: 2025-01-27 09:10+0700\n" "Last-Translator: Bagas Sanjaya <bagasdotme@gmail.com>\n" "Language-Team: Indonesian\n" "Language: id\n" @@ -957,9 +957,9 @@ #: builtin/fast-export.c builtin/fetch.c builtin/help.c builtin/index-pack.c #: builtin/init-db.c builtin/log.c builtin/ls-files.c builtin/merge-base.c #: builtin/merge-tree.c builtin/merge.c builtin/rebase.c builtin/repack.c -#: builtin/replay.c builtin/reset.c builtin/rev-parse.c builtin/show-branch.c -#: builtin/stash.c builtin/submodule--helper.c builtin/tag.c builtin/worktree.c -#: parse-options.c range-diff.c revision.c +#: builtin/reset.c builtin/rev-parse.c builtin/show-branch.c builtin/stash.c +#: builtin/submodule--helper.c builtin/tag.c builtin/worktree.c parse-options.c +#: range-diff.c revision.c #, c-format msgid "options '%s' and '%s' cannot be used together" msgstr "Opsi '%s' dan '%s' tidak dapat digunakan bersamaan" @@ -2134,8 +2134,8 @@ msgstr "'%s' bukan nama cabang valid" #: branch.c builtin/branch.c -msgid "See `man git check-ref-format`" -msgstr "Lihat `man git check-ref-format`" +msgid "See 'git help check-ref-format'" +msgstr "Lihat `git help check-ref-format`" #: branch.c #, c-format @@ -3276,6 +3276,19 @@ msgid "must end with a color" msgstr "harus berakhir dengan warna" +#: builtin/blame.c diff.c merge-ort.c transport.c +#, c-format +msgid "unknown value for config '%s': %s" +msgstr "nilai tidak dikenal untuk konfigurasi '%s': %s" + +#: builtin/blame.c builtin/merge-file.c diff.c +msgid "" +"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and " +"\"histogram\"" +msgstr "" +"opsi diff-algorithm terima \"myers\", \"minimal\", \"patience\" dan " +"\"histogram\"" + #: builtin/blame.c #, c-format msgid "cannot find revision %s to ignore" @@ -3347,6 +3360,14 @@ msgid "ignore whitespace differences" msgstr "abaikan perbedaan spasi putih" +#: builtin/blame.c builtin/merge-file.c diff.c +msgid "<algorithm>" +msgstr "<algoritma>" + +#: builtin/blame.c builtin/merge-file.c diff.c +msgid "choose a diff algorithm" +msgstr "pilih algoritma diff" + #: builtin/blame.c builtin/clone.c builtin/log.c msgid "rev" msgstr "revisi" @@ -3368,8 +3389,8 @@ msgstr "warnai baris oleh umur" #: builtin/blame.c -msgid "spend extra cycles to find better match" -msgstr "perlihatkan siklus ekstra untuk menemukan cocokan yang lebih baik" +msgid "spend extra cycles to find a better match" +msgstr "habiskan siklus tambahan untuk mencari pasangan yang lebih baik" #: builtin/blame.c msgid "use revisions from <file> instead of calling git-rev-list" @@ -3922,7 +3943,7 @@ "Anda dapat menghapus baris-baris yang Anda tidak ingin dibagi.\n" #: builtin/bugreport.c builtin/commit.c builtin/fast-export.c -#: builtin/pack-objects.c builtin/rebase.c parse-options.h +#: builtin/pack-objects.c builtin/rebase.c builtin/replay.c parse-options.h msgid "mode" msgstr "mode" @@ -4857,10 +4878,10 @@ msgid "missing branch name; try -%c" msgstr "kehilangan nama cabang; coba -%c" -#: builtin/checkout.c +#: builtin/checkout.c sequencer.c #, c-format -msgid "could not resolve %s" -msgstr "tidak dapat menyelesaikan %s" +msgid "could not resolve '%s'" +msgstr "tidak dapat menguraikan '%s'" #: builtin/checkout.c msgid "invalid path specification" @@ -6715,10 +6736,18 @@ #, c-format msgid "" "cannot overwrite multiple values with a single value\n" -" Use a regexp, --add or --replace-all to change %s." +" Use --value=<pattern>, --append or --all to change %s." msgstr "" "tidak dapat menimpa banyak nilai dengan nilai tunggal\n" -" Gunakan regexp, --add atau --replace-all untuk mengubah %s." +" Gunakan --value=<pola>, --append atau --all untuk mengubah %s." + +#: builtin/config.c +msgid "unset all multi-valued config options" +msgstr "batal setel semua opsi konfigurasi multi-nilai" + +#: builtin/config.c +msgid "unset multi-valued config options with matching values" +msgstr "batal setel opsi konfigurasi multi-nilai dengan nilai yang sepadan" #: builtin/config.c #, c-format @@ -6832,6 +6861,15 @@ "--comment hanya dapat diterapkan pada operasi penambahan/penyetelan/" "penggantian" +#: builtin/config.c +#, c-format +msgid "" +"cannot overwrite multiple values with a single value\n" +" Use a regexp, --add or --replace-all to change %s." +msgstr "" +"tidak dapat menimpa banyak nilai dengan nilai tunggal\n" +" Gunakan regexp, --add atau --replace-all untuk mengubah %s." + #: builtin/count-objects.c msgid "print sizes in human readable format" msgstr "cetak ukuran dalam format yang bisa dibaca manusia" @@ -7344,13 +7382,6 @@ #: builtin/fast-export.c #, c-format -msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it" -msgstr "" -"menemukan komit bertandatangan %s; gunakan --signed-commits=<mode> untuk " -"menanganinya" - -#: builtin/fast-export.c -#, c-format msgid "exporting %<PRIuMAX> signature(s) for commit %s" msgstr "mengekspor %<PRIuMAX> tandatangan untuk komit %s" @@ -7361,6 +7392,21 @@ #: builtin/fast-export.c #, c-format +msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it" +msgstr "" +"menemukan komit bertandatangan %s; gunakan --signed-commits=<mode> untuk " +"menanganinya" + +#: builtin/fast-export.c +msgid "" +"'strip-if-invalid' is not a valid mode for git fast-export with --signed-" +"commits=<mode>" +msgstr "" +"'strip-if-invalid' bukan mode valid untuk git fast-export dengan --signed-" +"commits=<mode>" + +#: builtin/fast-export.c +#, c-format msgid "" "omitting tag %s,\n" "since tags of trees (or tags of tags of trees, etc.) are not supported." @@ -7375,13 +7421,6 @@ #: builtin/fast-export.c #, c-format -msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it" -msgstr "" -"menemukan tag bertandatangan %s; gunakan --signed-tags=<mode> untuk " -"menanganinya" - -#: builtin/fast-export.c -#, c-format msgid "exporting signed tag %s" msgstr "mengekspor tag bertandatangan %s" @@ -7392,6 +7431,21 @@ #: builtin/fast-export.c #, c-format +msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it" +msgstr "" +"menemukan tag bertandatangan %s; gunakan --signed-tags=<mode> untuk " +"menanganinya" + +#: builtin/fast-export.c +msgid "" +"'strip-if-invalid' is not a valid mode for git fast-export with --signed-" +"tags=<mode>" +msgstr "" +"'strip-if-invalid' bukan mode valid untuk git fast-export dengan --signed-" +"tags=<mode>" + +#: builtin/fast-export.c +#, c-format msgid "" "tag %s tags unexported object; use --tag-of-filtered-object=<mode> to handle " "it" @@ -7883,6 +7937,37 @@ msgid "parse_one_signature() returned unknown hash algo" msgstr "parse_one_signature() mengembalikan algoritma hash tak dikenal" +#: builtin/fast-import.c builtin/fsck.c +msgid "unknown" +msgstr "tidak dikenal" + +#: builtin/fast-import.c +#, c-format +msgid "" +"stripping invalid signature for commit '%.100s...'\n" +" allegedly by %s" +msgstr "" +"mengupas tandatangan tidak valid untuk komit '%.100s...'\n" +" yang diduga dibuat oleh %s" + +#: builtin/fast-import.c +#, c-format +msgid "" +"stripping invalid signature for commit '%.*s'\n" +" allegedly by %s" +msgstr "" +"mengupas tandatangan tidak valid untuk komit '%.*s'\n" +" yang diduga dibuat oleh %s" + +#: builtin/fast-import.c +#, c-format +msgid "" +"stripping invalid signature for commit\n" +" allegedly by %s" +msgstr "" +"mengupas tandatangan tidak valid untuk komit\n" +" yang diduga dibuat oleh %s" + #: builtin/fast-import.c msgid "expected committer but didn't get one" msgstr "pengkomit diharapkan tapi tidak mendapatkannya" @@ -7902,11 +7987,6 @@ msgstr "mengimpor tandatangan komit secara verbatim" #: builtin/fast-import.c -msgid "encountered signed tag; use --signed-tags=<mode> to handle it" -msgstr "" -"menemukan tag bertandatangan; gunakan --signed-tags=<mode> untuk menanganinya" - -#: builtin/fast-import.c #, c-format msgid "importing a tag signature verbatim for tag '%s'" msgstr "mengimpor tag secara verbatim untuk tag '%s'" @@ -7917,6 +7997,19 @@ msgstr "mengupas tandatangan tag untuk tag '%s'" #: builtin/fast-import.c +msgid "encountered signed tag; use --signed-tags=<mode> to handle it" +msgstr "" +"menemukan tag bertandatangan; gunakan --signed-tags=<mode> untuk menanganinya" + +#: builtin/fast-import.c +msgid "" +"'strip-if-invalid' is not a valid mode for git fast-import with --signed-" +"tags=<mode>" +msgstr "" +"'strip-if-invalid' bukan mode valid untuk git fast-import dengan --signed-" +"tags=<mode>" + +#: builtin/fast-import.c #, c-format msgid "expected 'from' command, got '%s'" msgstr "perintah 'from' diharapkan, dapat '%s'" @@ -8114,8 +8207,8 @@ msgstr "git fetch [<opsi>] <grup>" #: builtin/fetch.c -msgid "git fetch --multiple [<options>] [(<repository> | <group>)...]" -msgstr "git fetch --multiple [<opsi>] [(<repositori> | <grup>)]" +msgid "git fetch --multiple [<options>] [(<repository>|<group>)...]" +msgstr "git fetch --multiple [<opsi>] [(<repositori> | <grup>)...]" #: builtin/fetch.c msgid "git fetch --all [<options>]" @@ -8708,10 +8801,6 @@ msgid "got bad config --config=%s" msgstr "dapat konfigurasi jelek --config=%s" -#: builtin/fsck.c -msgid "unknown" -msgstr "tidak dikenal" - #. TRANSLATORS: e.g. error in tree 01bfda: <more explanation> #: builtin/fsck.c #, c-format @@ -8831,6 +8920,11 @@ msgstr "%s: bukan sebuah komit" #: builtin/fsck.c +#, c-format +msgid "invalid parameter: expected sha1, got '%s'" +msgstr "parameter tidak valid: sha1 diharapkan, dapat '%s'" + +#: builtin/fsck.c msgid "notice: No default references" msgstr "catatan: Tidak ada referensi asali" @@ -8864,31 +8958,6 @@ #: builtin/fsck.c #, c-format -msgid "Checking %s link" -msgstr "Memeriksa tautan %s" - -#: builtin/fsck.c builtin/index-pack.c -#, c-format -msgid "invalid %s" -msgstr "%s tidak valid" - -#: builtin/fsck.c -#, c-format -msgid "%s points to something strange (%s)" -msgstr "%s menunjuk ke sesuatu yang aneh (%s)" - -#: builtin/fsck.c -#, c-format -msgid "%s: detached HEAD points at nothing" -msgstr "%s: HEAD terpisah tidak menunjuk ke apapun" - -#: builtin/fsck.c -#, c-format -msgid "notice: %s points to an unborn branch (%s)" -msgstr "catatan: %s menunjuk ke cabang yang belum lahir (%s)" - -#: builtin/fsck.c -#, c-format msgid "Checking cache tree of %s" msgstr "Memeriksa pohon tembolok %s" @@ -8988,16 +9057,6 @@ msgid "Checking objects" msgstr "Memeriksa objek" -#: builtin/fsck.c -#, c-format -msgid "%s: object missing" -msgstr "%s: objek hilang" - -#: builtin/fsck.c -#, c-format -msgid "invalid parameter: expected sha1, got '%s'" -msgstr "parameter tidak valid: sha1 diharapkan, dapat '%s'" - #: builtin/fsmonitor--daemon.c msgid "git fsmonitor--daemon start [<options>]" msgstr "git fsmonitor--daemon start [<opsi>]" @@ -9478,6 +9537,10 @@ msgstr "gagal menambahkan repositori ke konfigurasi global" #: builtin/gc.c +msgid "check a specific task" +msgstr "periksa tugas yang spesifik" + +#: builtin/gc.c msgid "git maintenance <subcommand> [<options>]" msgstr "git maintenance <subperintah> [<opsi>]" @@ -10060,6 +10123,11 @@ #: builtin/index-pack.c #, c-format +msgid "invalid %s" +msgstr "%s tidak valid" + +#: builtin/index-pack.c +#, c-format msgid "Not all child objects of %s are reachable" msgstr "Tidak semua objek anak dari %s bisa dicapai" @@ -10234,6 +10302,11 @@ msgid "--verify with no packfile name given" msgstr "--verify tanpa nama berkas paket diberikan" +#: builtin/index-pack.c +msgid "cannot perform queued object checks outside of a repository" +msgstr "" +"tidak dapat melakukan pemeriksaan objek yang mengantre di luar repositori" + #: builtin/index-pack.c builtin/unpack-objects.c msgid "fsck error in pack objects" msgstr "kesalahan fsck dalam objek paket" @@ -11208,14 +11281,6 @@ "git merge-file [<opsi>] [-L <nama 1> [-L <asli> [-L <nama 2>]]] <berkas 1> " "<berkas asli> <berkas 2>" -#: builtin/merge-file.c diff.c -msgid "" -"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and " -"\"histogram\"" -msgstr "" -"opsi diff-algorithm terima \"myers\", \"minimal\", \"patience\" dan " -"\"histogram\"" - #: builtin/merge-file.c msgid "send results to standard output" msgstr "kirim hasil ke keluaran standar" @@ -11232,14 +11297,6 @@ msgid "use a zealous diff3 based merge" msgstr "gunakan penggabungan berdasarkan diff3 yang bersemangat" -#: builtin/merge-file.c diff.c -msgid "<algorithm>" -msgstr "<algoritma>" - -#: builtin/merge-file.c diff.c -msgid "choose a diff algorithm" -msgstr "pilih algoritma diff" - #: builtin/merge-file.c msgid "for conflicts, use this marker size" msgstr "untuk konflik, gunakan ukuran penanda ini" @@ -12594,6 +12651,11 @@ #: builtin/pack-objects.c #, c-format +msgid "packfile %s is a promisor but --exclude-promisor-objects was given" +msgstr "berkas pak %s adalah pejanji tapi --exclude-promisor-objects diberikan" + +#: builtin/pack-objects.c +#, c-format msgid "could not find pack '%s'" msgstr "tidak dapat menemukan pak '%s'" @@ -12930,12 +12992,12 @@ msgstr "git patch-id [--stable | --unstable | --verbatim]" #: builtin/patch-id.c -msgid "use the unstable patch-id algorithm" -msgstr "gunakan algoritma id tambalan tidak stabil" +msgid "use the unstable patch ID algorithm" +msgstr "gunakan algoritma ID tambalan yang tidak stabil" #: builtin/patch-id.c -msgid "use the stable patch-id algorithm" -msgstr "gunakan algoritma id tambalan stabil" +msgid "use the stable patch ID algorithm" +msgstr "gunakan algoritma ID tambalan yang stabil" #: builtin/patch-id.c msgid "don't strip whitespace from the patch" @@ -12966,50 +13028,6 @@ msgstr "git pull [<opsi>] [<repositori> [<spek referensi>]]" #: builtin/pull.c -msgid "control for recursive fetching of submodules" -msgstr "kontrol pengambilan rekursif submodul" - -#: builtin/pull.c -msgid "Options related to merging" -msgstr "Opsi yang berkaitan dengan penggabungan" - -#: builtin/pull.c -msgid "incorporate changes by rebasing rather than merging" -msgstr "masukkan perubahan dengan pendasaran ulang daripada penggabungan" - -#: builtin/pull.c builtin/revert.c -msgid "allow fast-forward" -msgstr "perbolehkan maju cepat" - -#: builtin/pull.c -msgid "control use of pre-merge-commit and commit-msg hooks" -msgstr "kontrol penggunaan kail pre-merge-commit dan commit-msg" - -#: builtin/pull.c parse-options.h -msgid "automatically stash/stash pop before and after" -msgstr "stash/stash pop otomatis sebelum dan sesudah" - -#: builtin/pull.c -msgid "Options related to fetching" -msgstr "Opsi yang berkaitan dengan pengambilan" - -#: builtin/pull.c -msgid "force overwrite of local branch" -msgstr "paksa timpa cabang lokal" - -#: builtin/pull.c -msgid "number of submodules pulled in parallel" -msgstr "nomor submodul ditarik dalam paralel" - -#: builtin/pull.c parse-options.h -msgid "use IPv4 addresses only" -msgstr "gunakan hanya alamat IPv4" - -#: builtin/pull.c parse-options.h -msgid "use IPv6 addresses only" -msgstr "gunakan hanya alamat IPv6" - -#: builtin/pull.c msgid "" "There is no candidate for rebasing against among the refs that you just " "fetched." @@ -13133,6 +13151,50 @@ "asali terkonfigurasi untuk setiap invokasi.\n" #: builtin/pull.c +msgid "control for recursive fetching of submodules" +msgstr "kontrol pengambilan rekursif submodul" + +#: builtin/pull.c +msgid "Options related to merging" +msgstr "Opsi yang berkaitan dengan penggabungan" + +#: builtin/pull.c +msgid "incorporate changes by rebasing rather than merging" +msgstr "masukkan perubahan dengan pendasaran ulang daripada penggabungan" + +#: builtin/pull.c builtin/revert.c +msgid "allow fast-forward" +msgstr "perbolehkan maju cepat" + +#: builtin/pull.c +msgid "control use of pre-merge-commit and commit-msg hooks" +msgstr "kontrol penggunaan kail pre-merge-commit dan commit-msg" + +#: builtin/pull.c parse-options.h +msgid "automatically stash/stash pop before and after" +msgstr "stash/stash pop otomatis sebelum dan sesudah" + +#: builtin/pull.c +msgid "Options related to fetching" +msgstr "Opsi yang berkaitan dengan pengambilan" + +#: builtin/pull.c +msgid "force overwrite of local branch" +msgstr "paksa timpa cabang lokal" + +#: builtin/pull.c +msgid "number of submodules pulled in parallel" +msgstr "nomor submodul ditarik dalam paralel" + +#: builtin/pull.c parse-options.h +msgid "use IPv4 addresses only" +msgstr "gunakan hanya alamat IPv4" + +#: builtin/pull.c parse-options.h +msgid "use IPv6 addresses only" +msgstr "gunakan hanya alamat IPv6" + +#: builtin/pull.c msgid "Updating an unborn branch with changes added to the index." msgstr "" "Memperbarui cabang yang belum lahir dengan perubahan yang ditambahkan ke " @@ -15421,6 +15483,11 @@ msgstr "hanya satu pola yang dapat diberikan dengan -l" #: builtin/replay.c +#, c-format +msgid "'%s' is not a valid commit-ish for %s" +msgstr "'%s' bukan mirip komit yang valid untuk %s" + +#: builtin/replay.c msgid "need some commits to replay" msgstr "butuh beberapa komit untuk dimainkan ulang" @@ -15441,31 +15508,17 @@ "menjadi tidak jelas" #: builtin/replay.c -msgid "" -"cannot implicitly determine whether this is an --advance or --onto operation" -msgstr "" -"tidak dapat menentukan secara tidak langsung apakah ini operasi --advance " -"atau --onto" - -#: builtin/replay.c -msgid "" -"cannot advance target with multiple source branches because ordering would " -"be ill-defined" -msgstr "" -"tidak dapat memajukan target dengan banyak cabang sumber karena " -"pengurutannya akan menjadi tidak jelas" - -#: builtin/replay.c -msgid "cannot implicitly determine correct base for --onto" -msgstr "tidak dapat menentukan secara tidak langsung dasar untuk --onto" +#, c-format +msgid "invalid %s value: '%s'" +msgstr "nilai %s tidak valid: '%s'" #: builtin/replay.c msgid "" "(EXPERIMENTAL!) git replay ([--contained] --onto <newbase> | --advance " -"<branch>) <revision-range>..." +"<branch>) [--ref-action[=<mode>]] <revision-range>" msgstr "" "(EKSPERIMENTAL!) git replay ([--contained] --onto <dasar baru> | --advance " -"<cabang>) <rentang revisi>..." +"<cabang>) [--ref-action[=<mode>]] <rentang revisi>" #: builtin/replay.c msgid "make replay advance given branch" @@ -15476,8 +15529,13 @@ msgstr "mainkan ulang pada komit yang diberikan" #: builtin/replay.c -msgid "advance all branches contained in revision-range" -msgstr "majukan semua cabang yang berada pada rentang komit" +msgid "update all branches that point at commits in <revision-range>" +msgstr "" +"perbarui semua cabang yang menunjuk pada komit di dalam <rentang revisi>" + +#: builtin/replay.c +msgid "control ref update behavior (update|print)" +msgstr "atur perilaku pembaruan referensi (update|print)" #: builtin/replay.c msgid "option --onto or --advance is mandatory" @@ -15493,17 +15551,32 @@ "rev_info' akan dipaksakan" #: builtin/replay.c +#, c-format +msgid "failed to begin ref transaction: %s" +msgstr "gagal memulai transaksi referensi: %s" + +#: builtin/replay.c msgid "error preparing revisions" msgstr "kesalahan menyiapkan revisi" #: builtin/replay.c -msgid "replaying down to root commit is not supported yet!" -msgstr "memainkan ulang ke komit akar belum didukung!" +msgid "replaying down from root commit is not supported yet!" +msgstr "memainkan ulang dari komit akar belum didukung!" #: builtin/replay.c msgid "replaying merge commits is not supported yet!" msgstr "memainkan ulang komit penggabungan belum didukung!" +#: builtin/replay.c +#, c-format +msgid "failed to update ref '%s': %s" +msgstr "gagal memperbarui referensi '%s': %s" + +#: builtin/replay.c +#, c-format +msgid "failed to commit ref transaction: %s" +msgstr "gagal mengkomit transaksi referensi: %s" + #: builtin/repo.c #, c-format msgid "key '%s' not found" @@ -15523,10 +15596,18 @@ msgstr "sinonim untuk --format=nul" #: builtin/repo.c +msgid "print all keys/values" +msgstr "cetak semua kunci/nilai" + +#: builtin/repo.c msgid "unsupported output format" msgstr "format keluaran tidak didukung" #: builtin/repo.c +msgid "--all and <key> cannot be used together" +msgstr "--all dan <kunci> tidak dapat digunakan bersamaan" + +#: builtin/repo.c msgid "References" msgstr "Referensi" @@ -15567,6 +15648,14 @@ msgstr "Blob" #: builtin/repo.c +msgid "Inflated size" +msgstr "Ukuran yang berkembang" + +#: builtin/repo.c +msgid "Disk size" +msgstr "Ukuran disk" + +#: builtin/repo.c msgid "Repository structure" msgstr "Struktur repositori" @@ -17604,7 +17693,7 @@ msgid "'%s' already exists in the index and is not a submodule" msgstr "'%s' sudah ada di dalam indeks dan bukan submodul" -#: builtin/submodule--helper.c read-cache.c +#: builtin/submodule--helper.c object-file.c #, c-format msgid "'%s' does not have a commit checked out" msgstr "'%s' tidak punya sebuah komit tercheckout" @@ -18675,6 +18764,11 @@ msgstr "daftar bundel pada '%s' tidak punya mode" #: bundle-uri.c +#, c-format +msgid "bundle list at '%s': bundle '%s' has no uri" +msgstr "daftar bundel pada '%s': bundel '%s' tidak punya uri" + +#: bundle-uri.c msgid "failed to create temporary file" msgstr "tidak dapat membuat berkas sementara" @@ -18703,6 +18797,11 @@ #: bundle-uri.c #, c-format +msgid "bundle '%s' has no uri" +msgstr "bundel '%s' tidak punya uri" + +#: bundle-uri.c +#, c-format msgid "failed to download bundle from URI '%s'" msgstr "gagal mengunduh bundel dari URI '%s'" @@ -19357,8 +19456,8 @@ msgstr "Gunakan ulang resolusi konflik penggabungan terekam" #: command-list.h -msgid "Reset current HEAD to the specified state" -msgstr "Setel ulang HEAD saat ini ke keadaan yang disebutkan" +msgid "Set `HEAD` or the index to a known state" +msgstr "Setel `HEAD` atau indeks ke keadaan yang diketahui" #: command-list.h msgid "Restore working tree files" @@ -21243,11 +21342,6 @@ msgid "Unknown value for 'diff.submodule' config variable: '%s'" msgstr "Nilai tidak dikenal untuk variabel konfigurasi 'diff.submodule': '%s'" -#: diff.c merge-ort.c transport.c -#, c-format -msgid "unknown value for config '%s': %s" -msgstr "nilai tidak dikenal untuk konfigurasi '%s': %s" - #: diff.c #, c-format msgid "" @@ -23872,6 +23966,10 @@ msgstr "%s: gagal memasukkan ke dalam basis data" #: object-file.c +msgid "cannot add a submodule of a different hash algorithm" +msgstr "tidak dapat menambahkan submodul dari algoritma hash yang berbeda" + +#: object-file.c #, c-format msgid "%s: unsupported file type" msgstr "%s: tipe berkas tidak didukung" @@ -25642,6 +25740,11 @@ msgstr "--format=%.*s tidak dapat digunakan dengan --python, --shell, --tcl" #: ref-filter.c +#, c-format +msgid "parse_object_buffer failed on %s for %s" +msgstr "parse_object_buffer gagal pada %s untuk %s" + +#: ref-filter.c msgid "failed to run 'describe'" msgstr "gagal menjalankan 'describe'" @@ -25681,11 +25784,6 @@ #: ref-filter.c #, c-format -msgid "parse_object_buffer failed on %s for %s" -msgstr "parse_object_buffer gagal pada %s untuk %s" - -#: ref-filter.c -#, c-format msgid "malformed object at '%s'" msgstr "objek rusak pada '%s'" @@ -26046,6 +26144,21 @@ msgid "refname %s is a symbolic ref, copying it is not supported" msgstr "nama referensi %s simbolik, menyalinnya tidak didukung" +#: refs/reftable-backend.c +#, c-format +msgid "reftable stack for worktree '%s' is broken" +msgstr "tumpukan tabel referensi untuk pohon kerja '%s' rusak" + +#: refs/reftable-backend.c +#, c-format +msgid "could not create iterator for worktree '%s'" +msgstr "tidak dapat membuat iterator untuk pohon kerja '%s'" + +#: refs/reftable-backend.c +#, c-format +msgid "could not read record for worktree '%s'" +msgstr "tidak dapat membaca rekaman untuk pohon kerja '%s'" + #: refspec.c #, c-format msgid "pattern '%s' has no '*'" @@ -26559,6 +26672,10 @@ msgstr "" "tidak dapat menyelesaikan pack-objects untuk mengepak ulang objek pejanji" +#: repack-promisor.c +msgid "could not start pack-objects to repack promisor packs" +msgstr "tidak dapat memulai pack-objects untuk mengepak ulang pak pejanji" + #: repack.c #, c-format msgid "pack prefix %s does not begin with objdir %s" @@ -27785,11 +27902,6 @@ msgstr "nama label ilegal: '%.*s'" #: sequencer.c -#, c-format -msgid "could not resolve '%s'" -msgstr "tidak dapat menguraikan '%s'" - -#: sequencer.c msgid "writing fake root commit" msgstr "menulis komit akar palsu" @@ -28373,57 +28485,83 @@ msgid "bad %s format: %%%.*s" msgstr "format %s jelek: %%%.*s" -#. TRANSLATORS: IEC 80000-13:2008 gibibyte #: strbuf.c #, c-format -msgid "%u.%2.2u GiB" -msgstr "%u.%2.2u gibibita" +msgid "%u.%2.2u" +msgstr "%u.%2.2u" -#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second +#. TRANSLATORS: SI decimal prefix symbol for 10^9 #: strbuf.c -#, c-format -msgid "%u.%2.2u GiB/s" -msgstr "%u.%2.2u gibibita/detik" +msgid "G" +msgstr "G" -#. TRANSLATORS: IEC 80000-13:2008 mebibyte +#. TRANSLATORS: SI decimal prefix symbol for 10^6 #: strbuf.c -#, c-format -msgid "%u.%2.2u MiB" -msgstr "%u.%2.2u mebibita" +msgid "M" +msgstr "M" -#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second +#. TRANSLATORS: SI decimal prefix symbol for 10^3 #: strbuf.c -#, c-format -msgid "%u.%2.2u MiB/s" -msgstr "%u.%2.2u mebibita/detik" +msgid "k" +msgstr "k" -#. TRANSLATORS: IEC 80000-13:2008 kibibyte +#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second and gibibyte #: strbuf.c -#, c-format -msgid "%u.%2.2u KiB" -msgstr "%u.%2.2u kibibita" +msgid "GiB/s" +msgstr "GiB/detik" -#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second #: strbuf.c -#, c-format -msgid "%u.%2.2u KiB/s" -msgstr "%u.%2.2u kibibita/detik" +msgid "GiB" +msgstr "GiB" -#. TRANSLATORS: IEC 80000-13:2008 byte +#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second and mebibyte #: strbuf.c -#, c-format -msgid "%u byte" -msgid_plural "%u bytes" -msgstr[0] "%u bita" -msgstr[1] "%u bita" +msgid "MiB/s" +msgstr "MiB/detik" + +#: strbuf.c +msgid "MiB" +msgstr "MiB" + +#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second and kibibyte +#: strbuf.c +msgid "KiB/s" +msgstr "KiB/detik" + +#: strbuf.c +msgid "KiB" +msgstr "KiB" + +#. TRANSLATORS: IEC 80000-13:2008 byte/second and byte +#: strbuf.c +msgid "B/s" +msgstr "B/detik" + +#: strbuf.c +msgid "B" +msgstr "B" #. TRANSLATORS: IEC 80000-13:2008 byte/second #: strbuf.c +msgid "byte/s" +msgid_plural "bytes/s" +msgstr[0] "bita/detik" +msgstr[1] "bita/detik" + +#. TRANSLATORS: IEC 80000-13:2008 byte +#: strbuf.c +msgid "byte" +msgid_plural "bytes" +msgstr[0] "bita" +msgstr[1] "bita" + +#. TRANSLATORS: The first argument is the number string. The second +#. argument is the unit string (i.e. "12.34 MiB/s"). +#. +#: strbuf.c #, c-format -msgid "%u byte/s" -msgid_plural "%u bytes/s" -msgstr[0] "%u bita/detik" -msgstr[1] "%u bita/detik" +msgid "%s %s" +msgstr "%s %s" #: submodule-config.c #, c-format @@ -28790,10 +28928,6 @@ msgstr "jumlah permintaan tiap utas" #: t/helper/test-simple-ipc.c -msgid "byte" -msgstr "bita" - -#: t/helper/test-simple-ipc.c msgid "ballast character" msgstr "karakter pemberat"
diff --git a/po/sv.po b/po/sv.po index a7ae972..7e2e441 100644 --- a/po/sv.po +++ b/po/sv.po
@@ -1,14 +1,14 @@ # Swedish translations for Git. -# Copyright (C) 2010-2025 Peter Krefting <peter@softwolves.pp.se> +# Copyright (C) 2010-2026 Peter Krefting <peter@softwolves.pp.se> # This file is distributed under the same license as the Git package. -# Peter Krefting <peter@softwolves.pp.se>, 2010-2025. +# Peter Krefting <peter@softwolves.pp.se>, 2010-2026. # msgid "" msgstr "" -"Project-Id-Version: git 2.52.0\n" +"Project-Id-Version: git 2.53.0\n" "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n" -"POT-Creation-Date: 2025-11-06 23:58+0000\n" -"PO-Revision-Date: 2025-11-07 15:54+0100\n" +"POT-Creation-Date: 2026-01-22 23:57+0000\n" +"PO-Revision-Date: 2026-01-27 19:33+0100\n" "Last-Translator: Peter Krefting <peter@softwolves.pp.se>\n" "Language-Team: Svenska <tp-sv@listor.tp-sv.se>\n" "Language: sv\n" @@ -552,7 +552,7 @@ "j - gå till nästa obestämda stycke, börja om vid slutet\n" "J - gå till nästa stycke, börja om vid slutet\n" "k - gå till föregående obestämda stycke, börja om vid början\n" -"K - gå till föregående styckem, börja om vid början\n" +"K - gå till föregående stycken, börja om vid början\n" "g - välj ett stycke att gå till\n" "/ - sök efter stycke som motsvarar angivet reguljärt uttryck\n" "s - dela aktuellt stycke i mindre styckens\n" @@ -1733,8 +1733,8 @@ msgid "'%s' is not a valid branch name" msgstr "”%s” är inte ett giltigt grennamn" -msgid "See `man git check-ref-format`" -msgstr "Se ”man git check-ref-format”" +msgid "See 'git help check-ref-format'" +msgstr "Se ”git help check-ref-format”" #, c-format msgid "a branch named '%s' already exists" @@ -2628,6 +2628,16 @@ msgstr "måste sluta med en färg" #, c-format +msgid "unknown value for config '%s': %s" +msgstr "okänt värde för inställningen ”%s”: %s" + +msgid "" +"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and " +"\"histogram\"" +msgstr "" +"flaggan diff-algorithm godtar ”myers”, ”minimal”, ”patience” och ”histogram”" + +#, c-format msgid "cannot find revision %s to ignore" msgstr "kan inte hitta revision %s att ignorera" @@ -2679,6 +2689,12 @@ msgid "ignore whitespace differences" msgstr "ignorera ändringar i blanksteg" +msgid "<algorithm>" +msgstr "<algoritm>" + +msgid "choose a diff algorithm" +msgstr "välj en diff-algoritm" + msgid "rev" msgstr "incheckning" @@ -2694,8 +2710,8 @@ msgid "color lines by age" msgstr "färglägg rader efter ålder" -msgid "spend extra cycles to find better match" -msgstr "slösa extra cykler med att hitta bättre träff" +msgid "spend extra cycles to find a better match" +msgstr "slösa extra cykler med att hitta en bättre träff" msgid "use revisions from <file> instead of calling git-rev-list" msgstr "använd revisioner från <fil> istället för att anropa git-rev-list" @@ -3877,8 +3893,8 @@ msgstr "grennamn saknas; försök med -%c" #, c-format -msgid "could not resolve %s" -msgstr "kunde inte upplösa %s" +msgid "could not resolve '%s'" +msgstr "kunde inte upplösa ”%s”" msgid "invalid path specification" msgstr "felaktig sökvägsangivelse" @@ -5341,10 +5357,16 @@ #, c-format msgid "" "cannot overwrite multiple values with a single value\n" -" Use a regexp, --add or --replace-all to change %s." +" Use --value=<pattern>, --append or --all to change %s." msgstr "" "kan inte skriva över flera värden med ett ensamt värde\n" -" Använd en regexp, --add eller --replace-all för att ändra %s." +" Använd --value=<mönster>, --append eller --all för att ändra %s." + +msgid "unset all multi-valued config options" +msgstr "ta bort alla värden för konfigurationsflagga med flera värden" + +msgid "unset multi-valued config options with matching values" +msgstr "ta bort konfigurationsflagga med flera värden för träffade värden" #, c-format msgid "no such section: %s" @@ -5429,6 +5451,14 @@ msgid "--comment is only applicable to add/set/replace operations" msgstr "--comment gäller bara för ”get”/”set”/”replace”-operationerna" +#, c-format +msgid "" +"cannot overwrite multiple values with a single value\n" +" Use a regexp, --add or --replace-all to change %s." +msgstr "" +"kan inte skriva över flera värden med ett ensamt värde\n" +" Använd en regexp, --add eller --replace-all för att ändra %s." + msgid "print sizes in human readable format" msgstr "skriv storlekar i människoläsbart format" @@ -5788,7 +5818,7 @@ #, c-format msgid "unknown %s mode: %s" -msgstr "okänd läge för %s: %s" +msgstr "okänt läge för %s: %s" #, c-format msgid "unknown tag-of-filtered mode: %s" @@ -5830,14 +5860,8 @@ "encountered commit-specific encoding %.*s in commit %s; use --reencode=[yes|" "no] to handle it" msgstr "" -"påträffade incheckningsspecifik teckenkodning %.*s i incheckningen %s; " -"använd --reencode=[yes|no] för att hantera den" - -#, c-format -msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it" -msgstr "" -"påträffade en signerad incheckning %s; använd --signed-commits=<läge> för " -"att hantera den" +"påträffade den incheckningsspecifika teckenkodningen %.*s i incheckningen " +"%s; använd --reencode=[yes|no] för att hantera den" #, c-format msgid "exporting %<PRIuMAX> signature(s) for commit %s" @@ -5848,6 +5872,19 @@ msgstr "tar bort signatur(er) från incheckning %s" #, c-format +msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it" +msgstr "" +"påträffade den signerade incheckningen %s; använd --signed-commits=<läge> " +"för att hantera den" + +msgid "" +"'strip-if-invalid' is not a valid mode for git fast-export with --signed-" +"commits=<mode>" +msgstr "" +"”strip-if-invalid” är inte ett giltigt läge för git fast-export med --signed-" +"commits=<läge>" + +#, c-format msgid "" "omitting tag %s,\n" "since tags of trees (or tags of tags of trees, etc.) are not supported." @@ -5860,12 +5897,6 @@ msgstr "kunde inte läsa taggen %s" #, c-format -msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it" -msgstr "" -"påträffade signerade taggen %s; använd --signed-tags=<läge> för att hantera " -"den" - -#, c-format msgid "exporting signed tag %s" msgstr "exporterar signerad tagg %s" @@ -5874,6 +5905,19 @@ msgstr "tar bort signatur från taggen %s" #, c-format +msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it" +msgstr "" +"påträffade den signerade taggen %s; använd --signed-tags=<läge> för att " +"hantera den" + +msgid "" +"'strip-if-invalid' is not a valid mode for git fast-export with --signed-" +"tags=<mode>" +msgstr "" +"”strip-if-invalid” är inte ett giltigt läge för git fast-export med --signed-" +"tags=<läge>" + +#, c-format msgid "" "tag %s tags unexported object; use --tag-of-filtered-object=<mode> to handle " "it" @@ -6261,6 +6305,33 @@ msgid "parse_one_signature() returned unknown hash algo" msgstr "parse_one_signature() returnerade okänd hashningsalgoritm" +msgid "unknown" +msgstr "okänd" + +#, c-format +msgid "" +"stripping invalid signature for commit '%.100s...'\n" +" allegedly by %s" +msgstr "" +"tar bort ogiltig signatur från incheckning ”%.100s...”\n" +" påstås vara av %s" + +#, c-format +msgid "" +"stripping invalid signature for commit '%.*s'\n" +" allegedly by %s" +msgstr "" +"tar bort ogiltig signatur från incheckning ”%.*s”\n" +" påstås vara av %s" + +#, c-format +msgid "" +"stripping invalid signature for commit\n" +" allegedly by %s" +msgstr "" +"tar bort ogiltig signatur från incheckning\n" +" påstås vara av %s" + msgid "expected committer but didn't get one" msgstr "förväntade incheckare men fick ingen" @@ -6275,10 +6346,6 @@ msgid "importing a commit signature verbatim" msgstr "importerar en incheckningssignatur oförändrad" -msgid "encountered signed tag; use --signed-tags=<mode> to handle it" -msgstr "" -"upptäckte en signerad tagg; använd --signed-tags=<läge> för att hantera den" - #, c-format msgid "importing a tag signature verbatim for tag '%s'" msgstr "importerar en taggsignatur oförändrad för taggen ”%s”" @@ -6287,6 +6354,17 @@ msgid "stripping a tag signature for tag '%s'" msgstr "tar bort en taggsignatur för taggen ”%s”" +msgid "encountered signed tag; use --signed-tags=<mode> to handle it" +msgstr "" +"upptäckte en signerad tagg; använd --signed-tags=<läge> för att hantera den" + +msgid "" +"'strip-if-invalid' is not a valid mode for git fast-import with --signed-" +"tags=<mode>" +msgstr "" +"”strip-if-invalid” är inte ett giltigt läge för git fast-import med --signed-" +"tags=<läge>" + #, c-format msgid "expected 'from' command, got '%s'" msgstr "förväntade ”from”-kommando, fick %s" @@ -6443,8 +6521,8 @@ msgid "git fetch [<options>] <group>" msgstr "git fetch [<flaggor>] <grupp>" -msgid "git fetch --multiple [<options>] [(<repository> | <group>)...]" -msgstr "git fetch --multiple [<flaggor>] [(<arkiv> | <grupp>)...]" +msgid "git fetch --multiple [<options>] [(<repository>|<group>)...]" +msgstr "git fetch --multiple [<flaggor>] [(<arkiv>|<grupp>)...]" msgid "git fetch --all [<options>]" msgstr "git fetch --all [<flaggor>]" @@ -6912,9 +6990,6 @@ msgid "got bad config --config=%s" msgstr "fick felaktig konfiguration --config=%s" -msgid "unknown" -msgstr "okänd" - #. TRANSLATORS: e.g. error in tree 01bfda: <more explanation> #, c-format msgid "error in %s %s: %s" @@ -7013,6 +7088,10 @@ msgid "%s: not a commit" msgstr "%s: inte en incheckning!" +#, c-format +msgid "invalid parameter: expected sha1, got '%s'" +msgstr "ogiltig parameter: förväntade sha1, fick ”%s”" + msgid "notice: No default references" msgstr "obs: Inga förvalda referenser" @@ -7039,26 +7118,6 @@ msgstr "Kontrollerar objektkataloger" #, c-format -msgid "Checking %s link" -msgstr "Kontrollerar %s-länk" - -#, c-format -msgid "invalid %s" -msgstr "ogiltigt %s" - -#, c-format -msgid "%s points to something strange (%s)" -msgstr "%s pekar på något konstigt (%s)" - -#, c-format -msgid "%s: detached HEAD points at nothing" -msgstr "%s: frånkopplat HEAD pekar på ingenting" - -#, c-format -msgid "notice: %s points to an unborn branch (%s)" -msgstr "obs: %s pekar på en ofödd gren (%s)" - -#, c-format msgid "Checking cache tree of %s" msgstr "Kontrollerar cacheträd för %s" @@ -7139,14 +7198,6 @@ msgid "Checking objects" msgstr "Kontrollerar objekt" -#, c-format -msgid "%s: object missing" -msgstr "%s: objekt saknas" - -#, c-format -msgid "invalid parameter: expected sha1, got '%s'" -msgstr "ogiltig parameter: förväntade sha1, fick ”%s”" - msgid "git fsmonitor--daemon start [<options>]" msgstr "git fsmonitor--daemon start [<flaggor>]" @@ -7523,6 +7574,9 @@ msgid "failed to add repo to global config" msgstr "misslyckades lägga till arkiv till global konfiguration" +msgid "check a specific task" +msgstr "kontrollera en specifik uppgift" + msgid "git maintenance <subcommand> [<options>]" msgstr "git maintenance <underkommando> [<flaggor>]" @@ -7972,6 +8026,10 @@ msgstr "fsck-fel i packat objekt" #, c-format +msgid "invalid %s" +msgstr "ogiltigt %s" + +#, c-format msgid "Not all child objects of %s are reachable" msgstr "Inte alla barnobjekt för %s kan nås" @@ -8110,6 +8168,9 @@ msgid "--verify with no packfile name given" msgstr "--verify angavs utan paketfilnamn" +msgid "cannot perform queued object checks outside of a repository" +msgstr "kan inte utföra köade objektkontroller utanför ett arkiv" + msgid "fsck error in pack objects" msgstr "fsck-fel i packat objekt" @@ -8361,7 +8422,7 @@ msgstr "tokigt in-reply-to: %s" msgid "git format-patch [<options>] [<since> | <revision-range>]" -msgstr "git format-patch [<flaggor>] [<sedan> | <revisionsintervall>]" +msgstr "git format-patch [<flaggor>] [<sedan> | <revisions-intervall>]" msgid "two output directories?" msgstr "två utdatakataloger?" @@ -8852,12 +8913,6 @@ "git merge-file [<alternativ>] [-L <namn1> [-L <orig> [-L <namn2>]]] <fil1> " "<origfil> <fil2>" -msgid "" -"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and " -"\"histogram\"" -msgstr "" -"flaggan diff-algorithm godtar ”myers”, ”minimal”, ”patience” och ”histogram”" - msgid "send results to standard output" msgstr "sänd resultat till standard ut" @@ -8870,12 +8925,6 @@ msgid "use a zealous diff3 based merge" msgstr "använd nitisk diff3-baserad sammanslagning" -msgid "<algorithm>" -msgstr "<algoritm>" - -msgid "choose a diff algorithm" -msgstr "välj en diff-algoritm" - msgid "for conflicts, use this marker size" msgstr "för konflikter, använd denna markörstorlek" @@ -9943,6 +9992,10 @@ msgstr "kunde inte hämta typ för objektet %s i paketet %s" #, c-format +msgid "packfile %s is a promisor but --exclude-promisor-objects was given" +msgstr "packfilen %s är ett kontrakt men --exclude-promisor-objects angavs" + +#, c-format msgid "could not find pack '%s'" msgstr "kunde inte hitta paketet ”%s”" @@ -10199,11 +10252,11 @@ msgid "git patch-id [--stable | --unstable | --verbatim]" msgstr "git patch-id [--stable | --unstable | --verbatim]" -msgid "use the unstable patch-id algorithm" -msgstr "använd den instabila patch-id-algoritmen" +msgid "use the unstable patch ID algorithm" +msgstr "använd den instabila patch ID-algoritmen" -msgid "use the stable patch-id algorithm" -msgstr "använd den stabila patch-id-algoritmen" +msgid "use the stable patch ID algorithm" +msgstr "använd den stabila patch ID-algoritmen" msgid "don't strip whitespace from the patch" msgstr "ta inte bort blanksteg från patchen" @@ -10226,39 +10279,6 @@ msgid "git pull [<options>] [<repository> [<refspec>...]]" msgstr "git pull [<flaggor>] [<arkiv> [<refspec>...]]" -msgid "control for recursive fetching of submodules" -msgstr "styrning för rekursiv hämtning av undermoduler" - -msgid "Options related to merging" -msgstr "Alternativ gällande sammanslagning" - -msgid "incorporate changes by rebasing rather than merging" -msgstr "inlemma ändringar genom ombasering i stället för sammanslagning" - -msgid "allow fast-forward" -msgstr "tillåt snabbspolning" - -msgid "control use of pre-merge-commit and commit-msg hooks" -msgstr "styr användning av pre-merge-commit- och commit-msg-krokar" - -msgid "automatically stash/stash pop before and after" -msgstr "utför automatiskt stash/stash pop före och efter" - -msgid "Options related to fetching" -msgstr "Alternativ gällande hämtningar" - -msgid "force overwrite of local branch" -msgstr "tvinga överskrivning av lokal gren" - -msgid "number of submodules pulled in parallel" -msgstr "antal undermoduler som hämtas parallellt" - -msgid "use IPv4 addresses only" -msgstr "använd endast IPv4-adresser" - -msgid "use IPv6 addresses only" -msgstr "använd endast IPv6-adresser" - msgid "" "There is no candidate for rebasing against among the refs that you just " "fetched." @@ -10361,6 +10381,39 @@ "eller --ff-only på kommandoraden för att överstyra det konfigurerade\n" "förvalet vid körning.\n" +msgid "control for recursive fetching of submodules" +msgstr "styrning för rekursiv hämtning av undermoduler" + +msgid "Options related to merging" +msgstr "Alternativ gällande sammanslagning" + +msgid "incorporate changes by rebasing rather than merging" +msgstr "inlemma ändringar genom ombasering i stället för sammanslagning" + +msgid "allow fast-forward" +msgstr "tillåt snabbspolning" + +msgid "control use of pre-merge-commit and commit-msg hooks" +msgstr "styr användning av pre-merge-commit- och commit-msg-krokar" + +msgid "automatically stash/stash pop before and after" +msgstr "utför automatiskt stash/stash pop före och efter" + +msgid "Options related to fetching" +msgstr "Alternativ gällande hämtningar" + +msgid "force overwrite of local branch" +msgstr "tvinga överskrivning av lokal gren" + +msgid "number of submodules pulled in parallel" +msgstr "antal undermoduler som hämtas parallellt" + +msgid "use IPv4 addresses only" +msgstr "använd endast IPv4-adresser" + +msgid "use IPv6 addresses only" +msgstr "använd endast IPv6-adresser" + msgid "Updating an unborn branch with changes added to the index." msgstr "Uppdaterar en ofödd gren med ändringar som lagts till i indexet." @@ -11344,7 +11397,7 @@ #, c-format msgid "reflog could not be found: '%s'" -msgstr "refernsloggen hittades inte: ”%s”" +msgstr "referensloggen hittades inte: ”%s”" msgid "no reflog specified to delete" msgstr "ingen referenslogg att ta bort angavs" @@ -12206,6 +12259,10 @@ msgid "only one pattern can be given with -l" msgstr "endast ett mönster kan anges med -l" +#, c-format +msgid "'%s' is not a valid commit-ish for %s" +msgstr "”%s” är inte en giltig incheckning-igt för %s" + msgid "need some commits to replay" msgstr "behöver några incheckningar för omspelning" @@ -12222,27 +12279,16 @@ "kan inte flytta målet framåt när det finns flera källor eftersom ordningen " "inte kan fastställas" -msgid "" -"cannot implicitly determine whether this is an --advance or --onto operation" -msgstr "" -"kan inte avgöra om den underförstådda processen är --advance eller --onto" - -msgid "" -"cannot advance target with multiple source branches because ordering would " -"be ill-defined" -msgstr "" -"kan inte flytta målet framåt när det finns flera källgrenar eftersom " -"ordningen inte kan fastställas" - -msgid "cannot implicitly determine correct base for --onto" -msgstr "kan inte avgöra den underförstådda basen för --onto" +#, c-format +msgid "invalid %s value: '%s'" +msgstr "ogiltigt värde för %s: ”%s”" msgid "" "(EXPERIMENTAL!) git replay ([--contained] --onto <newbase> | --advance " -"<branch>) <revision-range>..." +"<branch>) [--ref-action[=<mode>]] <revision-range>" msgstr "" "(EXPERIMENTELLT!) git replay ([--contained] --onto <nybas> | --advance " -"<gren>) <revisions-intervall>..." +"<gren>) [--ref-action[=<läge>]] <revisions-intervall>" msgid "make replay advance given branch" msgstr "låt omspelningen flytta den givna grenen framåt" @@ -12250,8 +12296,12 @@ msgid "replay onto given commit" msgstr "spela om ovanpå en given incheckning" -msgid "advance all branches contained in revision-range" -msgstr "flytta alla grenar som finns i revisionsintervallet framåt" +msgid "update all branches that point at commits in <revision-range>" +msgstr "" +"uppdatera alla grenar som pekar på incheckningar i <revisions-intervall>" + +msgid "control ref update behavior (update|print)" +msgstr "styr beteende för referensuppdatering (update|print)" msgid "option --onto or --advance is mandatory" msgstr "flaggan --onto eller --advance måste anges" @@ -12264,16 +12314,28 @@ "några flaggor för revisionstraversering kommer överstyras eftersom ”%s”-" "biten i ”struct rev_info” kommer att tvingas" +#, c-format +msgid "failed to begin ref transaction: %s" +msgstr "misslyckades påbörja ref-transaktion: %s" + msgid "error preparing revisions" msgstr "fel när revisioner skulle förberedas" -msgid "replaying down to root commit is not supported yet!" -msgstr "kan ännu inte spela om hela vägen ned till rotincheckningen!" +msgid "replaying down from root commit is not supported yet!" +msgstr "kan ännu inte spela om hela vägen ned från rotincheckningen!" msgid "replaying merge commits is not supported yet!" msgstr "kan ännu inte spela om sammanslagningsincheckningar!" #, c-format +msgid "failed to update ref '%s': %s" +msgstr "misslyckades uppdatera referensen ”%s”: %s" + +#, c-format +msgid "failed to commit ref transaction: %s" +msgstr "misslyckades checka in referenstransaktionen: %s" + +#, c-format msgid "key '%s' not found" msgstr "nyckeln ”%s” hittades inte" @@ -12287,9 +12349,15 @@ msgid "synonym for --format=nul" msgstr "synonym till --format=nul" +msgid "print all keys/values" +msgstr "visa alla nycklar/värden" + msgid "unsupported output format" msgstr "utdataformatet stöds ej" +msgid "--all and <key> cannot be used together" +msgstr "--all och <nyckel> kan inte användas samtidigt" + msgid "References" msgstr "Referenser" @@ -12320,6 +12388,12 @@ msgid "Blobs" msgstr "Blob:ar" +msgid "Inflated size" +msgstr "Uppackad storlek" + +msgid "Disk size" +msgstr "Diskstorlek" + msgid "Repository structure" msgstr "Arkivstruktur" @@ -14786,6 +14860,10 @@ msgid "bundle list at '%s' has no mode" msgstr "buntlistan på ”%s” har inget läge" +#, c-format +msgid "bundle list at '%s': bundle '%s' has no uri" +msgstr "buntlistan på ”%s”: bunten ”%s” har ingen uri" + msgid "failed to create temporary file" msgstr "misslyckades skapa temporär fil" @@ -14808,6 +14886,10 @@ msgstr "överskred buntens URI-rekursionsgräns (%d)" #, c-format +msgid "bundle '%s' has no uri" +msgstr "buntlistan ”%s” har ingen uri" + +#, c-format msgid "failed to download bundle from URI '%s'" msgstr "misslyckades hämta bunt från URI:en ”%s”" @@ -15302,8 +15384,8 @@ msgid "Reuse recorded resolution of conflicted merges" msgstr "Återanvänd sparad lösning av sammanslagningskonflikter" -msgid "Reset current HEAD to the specified state" -msgstr "Återställ aktuell HEAD till angivet tillstånd" +msgid "Set `HEAD` or the index to a known state" +msgstr "Sätt ”HEAD” eller indexet till ett känt tillstånd" msgid "Restore working tree files" msgstr "Återställ filer i arbetskatalogen" @@ -16812,10 +16894,6 @@ msgstr "Okänt värde för konfigurationsvariabeln ”diff.submodule”: ”%s”" #, c-format -msgid "unknown value for config '%s': %s" -msgstr "okänt värde för inställningen ”%s”: %s" - -#, c-format msgid "" "Found errors in 'diff.dirstat' config variable:\n" "%s" @@ -18921,6 +18999,9 @@ msgid "%s: failed to insert into database" msgstr "%s: misslyckades lägga in i databasen" +msgid "cannot add a submodule of a different hash algorithm" +msgstr "kan inte lägga till submodul med annan hashningsalgoritm" + #, c-format msgid "%s: unsupported file type" msgstr "%s: filtypen stöds ej" @@ -20371,6 +20452,10 @@ msgid "--format=%.*s cannot be used with --python, --shell, --tcl" msgstr "--format=%.*s kan inte användas med --python, --shell, --tcl" +#, c-format +msgid "parse_object_buffer failed on %s for %s" +msgstr "parse_object_buffer misslyckades på %s för %s" + msgid "failed to run 'describe'" msgstr "misslyckades att köra ”describe”" @@ -20402,10 +20487,6 @@ msgstr "objektet %s saknas för %s" #, c-format -msgid "parse_object_buffer failed on %s for %s" -msgstr "parse_object_buffer misslyckades på %s för %s" - -#, c-format msgid "malformed object at '%s'" msgstr "felformat objekt vid ”%s”" @@ -20700,6 +20781,18 @@ msgstr "referensnamnet %s är en symbolisk referens, kopiering stöds inte" #, c-format +msgid "reftable stack for worktree '%s' is broken" +msgstr "referensstacken för arbetskatalogen ”%s” är trasig" + +#, c-format +msgid "could not create iterator for worktree '%s'" +msgstr "kunde inte skapa iterator för arbetskatalogen ”%s”" + +#, c-format +msgid "could not read record for worktree '%s'" +msgstr "kunde inte läsa posten för arbetskatalogen ”%s”" + +#, c-format msgid "pattern '%s' has no '*'" msgstr "mönstret ”%s” innehåller ingen ”*”" @@ -21116,6 +21209,9 @@ msgid "could not finish pack-objects to repack promisor objects" msgstr "kunde inte avsluta pack-objects för att packa om kontraktsobjekt" +msgid "could not start pack-objects to repack promisor packs" +msgstr "kunde inte starta pack-objects för att packa om kontraktspaket" + #, c-format msgid "pack prefix %s does not begin with objdir %s" msgstr "paketprefixet %s börjar inte med objkat %s" @@ -21904,7 +22000,7 @@ msgstr "" "”reword” tar inte en sammanslagningsincheckning. Om du\n" "ville spela upp sammanslagningen på nytt och ändra texten\n" -"i incheckningsmeddelandet använder du ”merge -C” på\n" +"i incheckningsmeddelandet använder du ”merge -c” på\n" "incheckningen" #. TRANSLATORS: 'edit', 'merge -C' and 'break' should @@ -22114,10 +22210,6 @@ msgid "illegal label name: '%.*s'" msgstr "ogiltigt etikettnamn: ”%.*s”" -#, c-format -msgid "could not resolve '%s'" -msgstr "kunde inte upplösa ”%s”" - msgid "writing fake root commit" msgstr "skriver fejkad rotincheckning" @@ -22592,49 +22684,68 @@ msgid "bad %s format: %%%.*s" msgstr "felaktigt %s-format: %%%.*s" -#. TRANSLATORS: IEC 80000-13:2008 gibibyte #, c-format -msgid "%u.%2.2u GiB" -msgstr "%u,%2.2u GiB" +msgid "%u.%2.2u" +msgstr "%u,%2.2u" -#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second -#, c-format -msgid "%u.%2.2u GiB/s" -msgstr "%u,%2.2u GiB/s" +#. TRANSLATORS: SI decimal prefix symbol for 10^9 +msgid "G" +msgstr "G" -#. TRANSLATORS: IEC 80000-13:2008 mebibyte -#, c-format -msgid "%u.%2.2u MiB" -msgstr "%u,%2.2u MiB" +#. TRANSLATORS: SI decimal prefix symbol for 10^6 +msgid "M" +msgstr "M" -#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second -#, c-format -msgid "%u.%2.2u MiB/s" -msgstr "%u,%2.2u MiB/s" +#. TRANSLATORS: SI decimal prefix symbol for 10^3 +msgid "k" +msgstr "k" -#. TRANSLATORS: IEC 80000-13:2008 kibibyte -#, c-format -msgid "%u.%2.2u KiB" -msgstr "%u,%2.2u KiB" +#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second and gibibyte +msgid "GiB/s" +msgstr "GiB/s" -#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second -#, c-format -msgid "%u.%2.2u KiB/s" -msgstr "%u,%2.2u KiB/s" +msgid "GiB" +msgstr "GiB" -#. TRANSLATORS: IEC 80000-13:2008 byte -#, c-format -msgid "%u byte" -msgid_plural "%u bytes" -msgstr[0] "%u byte" -msgstr[1] "%u bytes" +#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second and mebibyte +msgid "MiB/s" +msgstr "MiB/s" + +msgid "MiB" +msgstr "MiB" + +#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second and kibibyte +msgid "KiB/s" +msgstr "KiB/s" + +msgid "KiB" +msgstr "KiB" + +#. TRANSLATORS: IEC 80000-13:2008 byte/second and byte +msgid "B/s" +msgstr "B/s" + +msgid "B" +msgstr "B" #. TRANSLATORS: IEC 80000-13:2008 byte/second +msgid "byte/s" +msgid_plural "bytes/s" +msgstr[0] "byte/s" +msgstr[1] "byte/s" + +#. TRANSLATORS: IEC 80000-13:2008 byte +msgid "byte" +msgid_plural "bytes" +msgstr[0] "byte" +msgstr[1] "byte" + +#. TRANSLATORS: The first argument is the number string. The second +#. argument is the unit string (i.e. "12.34 MiB/s"). +#. #, c-format -msgid "%u byte/s" -msgid_plural "%u bytes/s" -msgstr[0] "%u byte/s" -msgstr[1] "%u bytes/s" +msgid "%s %s" +msgstr "%s %s" #, c-format msgid "ignoring suspicious submodule name: %s" @@ -22926,9 +23037,6 @@ msgid "number of requests per thread" msgstr "antal frågor per tråd" -msgid "byte" -msgstr "byte" - msgid "ballast character" msgstr "ballasttecken"
diff --git a/po/tr.po b/po/tr.po index 7af591b..c459b4a 100644 --- a/po/tr.po +++ b/po/tr.po
@@ -97,8 +97,8 @@ msgstr "" "Project-Id-Version: Git Turkish Localization Project\n" "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n" -"POT-Creation-Date: 2025-11-15 01:30+0300\n" -"PO-Revision-Date: 2025-11-15 02:00+0300\n" +"POT-Creation-Date: 2026-01-27 22:54+0300\n" +"PO-Revision-Date: 2026-01-27 22:00+0300\n" "Last-Translator: Emir SARI <emir_sari@icloud.com>\n" "Language-Team: Turkish <kde-i18n-doc@kde.org>\n" "Language: tr\n" @@ -642,16 +642,16 @@ "P - print the current hunk using the pager\n" "? - print help\n" msgstr "" -"j - bu parça için sonra karar ver, bir sonraki karar verilmemiş parçayı gör\n" -"J - bu parça için sonra karar ver, bir sonraki parçayı gör\n" -"k - bu parça için sonra karar ver, bir önceki karar verilmemiş parçayı gör\n" -"K - bu parça için sonra karar ver, bir önceki parçayı gör\n" +"j - sonraki karar verilmemiş parçaya git, bitince başa dön\n" +"J - sonraki parçaya git, bitince başa dön\n" +"k - önceki karar verilmemiş parçaya git, bitince sona dön\n" +"K - önceki parçaya git, bitince sona dön\n" "g - gidilecek bir parça seç\n" "/ - verilen düzenli ifade ile eşleşen bir parça ara\n" "s - geçerli parçayı daha ufak parçalara böl\n" "e - geçerli parçayı elle düzenle\n" "p - geçerli parçayı yazdır\n" -"P - geçerli sayfayı sayfalayıcı kullanarak yazdır\n" +"P - geçerli sayfayı sayfalayıcıyı kullanarak yazdır\n" "? - yardımı yazdır\n" #, c-format @@ -1821,26 +1821,26 @@ msgid "'%s' is not a valid branch name" msgstr "'%s' geçerli bir dal adı değil" -msgid "See `man git check-ref-format`" -msgstr "'man git check-ref-format' kılavuz sayfasına bakın" +msgid "See 'git help check-ref-format'" +msgstr "\"git help check-ref-format\" bölümüne bakın" #, c-format msgid "a branch named '%s' already exists" -msgstr "'%s' adında bir dal halihazırda var" +msgstr "\"%s\" adında bir dal halihazırda var" #, c-format msgid "cannot force update the branch '%s' used by worktree at '%s'" msgstr "" -"şuradaki çalışma ağacı tarafından kullanılan '%s' dalı zorla " -"güncellenemiyor: '%s'" +"\"%2$s\" konumundaki çalışma ağacı tarafından kullanılan \"%1$s\" dalı zorla " +"güncellenemiyor" #, c-format msgid "cannot set up tracking information; starting point '%s' is not a branch" -msgstr "izleme bilgisi ayarlanamıyor; başlangıç noktası '%s' bir dal değil" +msgstr "izleme bilgisi ayarlanamıyor; başlangıç noktası \"%s\" bir dal değil" #, c-format msgid "the requested upstream branch '%s' does not exist" -msgstr "istenen üstkaynak dalı '%s' yok" +msgstr "istenen üstkaynak dalı \"%s\" yok" msgid "" "\n" @@ -2720,6 +2720,17 @@ msgstr "bir renk ile bitmeli" #, c-format +msgid "unknown value for config '%s': %s" +msgstr "'%s' yapılandırması için bilinmeyen değer: %s" + +msgid "" +"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and " +"\"histogram\"" +msgstr "" +"diff-algorithm seçeneği şunları kabul eder: \"myers\", \"minimal\", " +"\"patience\" ve \"histogram\"" + +#, c-format msgid "cannot find revision %s to ignore" msgstr "yok saymak için %s revizyonu bulunamıyor" @@ -2771,6 +2782,12 @@ msgid "ignore whitespace differences" msgstr "boşluk ayrımlarını yok say" +msgid "<algorithm>" +msgstr "<algoritma>" + +msgid "choose a diff algorithm" +msgstr "bir diff algoritması seç" + msgid "rev" msgstr "revizyon" @@ -2786,7 +2803,7 @@ msgid "color lines by age" msgstr "satırları yaşına göre renklendir" -msgid "spend extra cycles to find better match" +msgid "spend extra cycles to find a better match" msgstr "daha iyi eşleşme bulmak için ek döngüler harca" msgid "use revisions from <file> instead of calling git-rev-list" @@ -3971,8 +3988,8 @@ msgstr "eksik dal adı; -%c deneyin" #, c-format -msgid "could not resolve %s" -msgstr "%s çözülemedi" +msgid "could not resolve '%s'" +msgstr "'%s' çözülemedi" msgid "invalid path specification" msgstr "geçersiz yol belirtimi" @@ -5452,11 +5469,16 @@ #, c-format msgid "" "cannot overwrite multiple values with a single value\n" -" Use a regexp, --add or --replace-all to change %s." +" Use --value=<pattern>, --append or --all to change %s." msgstr "" "Tek bir değer ile birden çok değerin üzerine yazılamıyor.\n" -" %s değerini değiştirmek için bir düzenli ifade, --add veya --replace-" -"all kullanın." +" %s değişikliği için --value=<dizgi>, --append veya --all kullanın." + +msgid "unset all multi-valued config options" +msgstr "birden çok değerli yapılandırma seçeneklerinin tüm değerlerini kaldır" + +msgid "unset multi-valued config options with matching values" +msgstr "birden çok değerli yapılandırma seçeneğini eşleşen değerlerle değiştir" #, c-format msgid "no such section: %s" @@ -5542,6 +5564,15 @@ msgid "--comment is only applicable to add/set/replace operations" msgstr "--comment yalnızca ekle/ayarla/değiştir işlemlerine uygulanabilir" +#, c-format +msgid "" +"cannot overwrite multiple values with a single value\n" +" Use a regexp, --add or --replace-all to change %s." +msgstr "" +"Tek bir değer ile birden çok değerin üzerine yazılamıyor.\n" +" %s değerini değiştirmek için bir düzenli ifade, --add veya --replace-" +"all kullanın." + msgid "print sizes in human readable format" msgstr "yazdırma boyutları kişi tarafından okunabilir biçimde" @@ -5945,12 +5976,6 @@ "reencode[yes|no] kullanın: %s" #, c-format -msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it" -msgstr "" -"imzalanmış %s işlemesine denk gelindi; işlemek için --signed-commits=<kip> " -"kullanın" - -#, c-format msgid "exporting %<PRIuMAX> signature(s) for commit %s" msgstr "şu işleme için %<PRIuMAX> imza dışa aktarılıyor: %s" @@ -5959,6 +5984,19 @@ msgstr "%s işlemesinden imzalar soyuluyor" #, c-format +msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it" +msgstr "" +"imzalanmış %s işlemesine denk gelindi; işlemek için --signed-commits=<kip> " +"kullanın" + +msgid "" +"'strip-if-invalid' is not a valid mode for git fast-export with --signed-" +"commits=<mode>" +msgstr "" +"\"strip-if-invalid\", git fast-export için --signed-commits=<kip> ile " +"geçerli bir kip değil" + +#, c-format msgid "" "omitting tag %s,\n" "since tags of trees (or tags of tags of trees, etc.) are not supported." @@ -5971,12 +6009,6 @@ msgstr "%s etiketi okunamadı" #, c-format -msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it" -msgstr "" -"imzalanmış %s etiketinee denk gelindi; işlemek için --signed-tags=<kip> " -"kullanın" - -#, c-format msgid "exporting signed tag %s" msgstr "imzalanmış %s etiketi dışa aktarılıyor" @@ -5985,6 +6017,19 @@ msgstr "%s etiketinden imza soyuluyor" #, c-format +msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it" +msgstr "" +"imzalanmış %s etiketinee denk gelindi; işlemek için --signed-tags=<kip> " +"kullanın" + +msgid "" +"'strip-if-invalid' is not a valid mode for git fast-export with --signed-" +"tags=<mode>" +msgstr "" +"\"strip-if-invalid\", git fast-export için --signed-tags=<kip> ile geçerli " +"bir kip değil" + +#, c-format msgid "" "tag %s tags unexported object; use --tag-of-filtered-object=<mode> to handle " "it" @@ -6374,6 +6419,33 @@ msgid "parse_one_signature() returned unknown hash algo" msgstr "parse_one_signature(), bilinmeyen bir sağlama algoritması döndürdü" +msgid "unknown" +msgstr "bilinmeyen" + +#, c-format +msgid "" +"stripping invalid signature for commit '%.100s...'\n" +" allegedly by %s" +msgstr "" +"\"%.100s...\" işlemesi için olan geçersiz imzalar soyuluyor;\n" +" görünüşe göre %s tarafından" + +#, c-format +msgid "" +"stripping invalid signature for commit '%.*s'\n" +" allegedly by %s" +msgstr "" +"\"%.*s...\" işlemesi için olan geçersiz imza soyuluyor;\n" +" görünüşe göre %s tarafından" + +#, c-format +msgid "" +"stripping invalid signature for commit\n" +" allegedly by %s" +msgstr "" +"işleme için olan geçersiz imza soyuluyor;\n" +" görünüşe göre %s tarafından" + msgid "expected committer but didn't get one" msgstr "işlemeci bekleniyordu; ancak bir tane bulunamadı" @@ -6387,10 +6459,6 @@ msgid "importing a commit signature verbatim" msgstr "bir işleme imzası olduğu gibi içe aktarılıyor" -msgid "encountered signed tag; use --signed-tags=<mode> to handle it" -msgstr "" -"imzalanmış etikete denk gelindi; işlemek için --signed-tags=<kip> kullanın" - #, c-format msgid "importing a tag signature verbatim for tag '%s'" msgstr "\"%s\" etiketi için bir etiket imzası olduğu gibi içe aktarılıyor" @@ -6399,6 +6467,17 @@ msgid "stripping a tag signature for tag '%s'" msgstr "\"%s\" etiketi için bir etiket imzası soyuluyor" +msgid "encountered signed tag; use --signed-tags=<mode> to handle it" +msgstr "" +"imzalanmış etikete denk gelindi; işlemek için --signed-tags=<kip> kullanın" + +msgid "" +"'strip-if-invalid' is not a valid mode for git fast-import with --signed-" +"tags=<mode>" +msgstr "" +"\"strip-if-invalid\", git fast-import için --signed-tags=<kip> ile geçerli " +"bir kip değil" + #, c-format msgid "expected 'from' command, got '%s'" msgstr "\"from\" komutu bekleniyordu, \"%s\" alındı" @@ -6555,8 +6634,8 @@ msgid "git fetch [<options>] <group>" msgstr "git fetch [<seçenekler>] <grup>" -msgid "git fetch --multiple [<options>] [(<repository> | <group>)...]" -msgstr "git fetch --multiple [<seçenekler>] [(<depo> | <grup>)...]" +msgid "git fetch --multiple [<options>] [(<repository>|<group>)...]" +msgstr "git fetch --multiple [<seçenekler>] [(<depo>|<grup>)...]" msgid "git fetch --all [<options>]" msgstr "git fetch --all [<seçenekler>]" @@ -7031,9 +7110,6 @@ msgid "got bad config --config=%s" msgstr "hatayı yapılandırma alındı, --config=%s" -msgid "unknown" -msgstr "bilinmeyen" - #. TRANSLATORS: e.g. error in tree 01bfda: <more explanation> #, c-format msgid "error in %s %s: %s" @@ -7128,6 +7204,10 @@ msgid "%s: not a commit" msgstr "%s: bir işleme değil" +#, c-format +msgid "invalid parameter: expected sha1, got '%s'" +msgstr "geçersiz parametre: sha1 bekleniyordu, '%s' alındı" + msgid "notice: No default references" msgstr "Uyarı: Öntanımlı başvurular yok" @@ -7154,26 +7234,6 @@ msgstr "Nesne dizinleri denetleniyor" #, c-format -msgid "Checking %s link" -msgstr "%s bağ denetleniyor" - -#, c-format -msgid "invalid %s" -msgstr "geçersiz %s" - -#, c-format -msgid "%s points to something strange (%s)" -msgstr "%s garip bir şeye işaret ediyor (%s)" - -#, c-format -msgid "%s: detached HEAD points at nothing" -msgstr "%s: ayrık HEAD bir şeye işaret etmiyor" - -#, c-format -msgid "notice: %s points to an unborn branch (%s)" -msgstr "Uyarı: %s henüz doğmamış bir dala işaret ediyor (%s)" - -#, c-format msgid "Checking cache tree of %s" msgstr "%s ögesinin önbellek ağacı denetleniyor" @@ -7252,14 +7312,6 @@ msgid "Checking objects" msgstr "Nesneler denetleniyor" -#, c-format -msgid "%s: object missing" -msgstr "%s: nesne kayıp" - -#, c-format -msgid "invalid parameter: expected sha1, got '%s'" -msgstr "geçersiz parametre: sha1 bekleniyordu, '%s' alındı" - msgid "git fsmonitor--daemon start [<options>]" msgstr "git fsmonitor--daemon start [<seçenekler>]" @@ -7637,6 +7689,9 @@ msgid "failed to add repo to global config" msgstr "depo, global yapılandırmaya eklenemedi" +msgid "check a specific task" +msgstr "belirli bir görevi denetle" + msgid "git maintenance <subcommand> [<options>]" msgstr "git maintenance <altkomut> [<seçenekler>]" @@ -8092,6 +8147,10 @@ msgstr "paketlenmiş nesne içinde fsck hatası" #, c-format +msgid "invalid %s" +msgstr "geçersiz %s" + +#, c-format msgid "Not all child objects of %s are reachable" msgstr "%s ögesinin tüm alt ögeleri ulaşılabilir değil" @@ -8231,6 +8290,9 @@ msgid "--verify with no packfile name given" msgstr "--verify ile bir paket dosyası adı verilmedi" +msgid "cannot perform queued object checks outside of a repository" +msgstr "depo dışında kuyruğa alınmış nesne denetimleri gerçekleştirilemez" + msgid "fsck error in pack objects" msgstr "paket nesnelerinde fsck hatası" @@ -8978,13 +9040,6 @@ "git merge-file [<seçenekler>] [-L <ad1> [-L <orij> [-L <ad2>]]] <dosya1> " "<orij-dosya> <dosya2>" -msgid "" -"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and " -"\"histogram\"" -msgstr "" -"diff-algorithm seçeneği şunları kabul eder: \"myers\", \"minimal\", " -"\"patience\" ve \"histogram\"" - msgid "send results to standard output" msgstr "sonuçları standart çıktıya gönder" @@ -8997,12 +9052,6 @@ msgid "use a zealous diff3 based merge" msgstr "gayretli bir diff3 tabanlı birleştirme kullan" -msgid "<algorithm>" -msgstr "<algoritma>" - -msgid "choose a diff algorithm" -msgstr "bir diff algoritması seç" - msgid "for conflicts, use this marker size" msgstr "çakışmalarda bu imleyici boyutunu kullan" @@ -10067,6 +10116,10 @@ msgstr "%s nesnesinin türü alınamıyor (%s paketinde)" #, c-format +msgid "packfile %s is a promisor but --exclude-promisor-objects was given" +msgstr "paket dosyası %s bir vaatçi; ancak --exclude-promisor-objects verildi" + +#, c-format msgid "could not find pack '%s'" msgstr "'%s' paketi bulunamadı" @@ -10323,10 +10376,10 @@ msgid "git patch-id [--stable | --unstable | --verbatim]" msgstr "git patch-id [--stable | --unstable | --verbatim]" -msgid "use the unstable patch-id algorithm" -msgstr "kararlı olmayan yama kimliği algoritmasını kullan" +msgid "use the unstable patch ID algorithm" +msgstr "kararsız yama kimliği algoritmasını kullan" -msgid "use the stable patch-id algorithm" +msgid "use the stable patch ID algorithm" msgstr "kararlı yama kimliği algoritmasını kullan" msgid "don't strip whitespace from the patch" @@ -10351,39 +10404,6 @@ msgid "git pull [<options>] [<repository> [<refspec>...]]" msgstr "git pull [<seçenekler>] [<depo> [<bşvr-blrtç>...]]" -msgid "control for recursive fetching of submodules" -msgstr "altmodüllerin özyineli getirilmesi için denetleme" - -msgid "Options related to merging" -msgstr "Birleştirme ile ilgili seçenekler" - -msgid "incorporate changes by rebasing rather than merging" -msgstr "değişiklikleri birleştirme yerine yeniden temellendirme ile kat" - -msgid "allow fast-forward" -msgstr "ileri sarıma izin ver" - -msgid "control use of pre-merge-commit and commit-msg hooks" -msgstr "pre-merge-commit ve commit-msg kancalarının kullanımını denetle" - -msgid "automatically stash/stash pop before and after" -msgstr "öncesinde ve sonrasında kendiliğinden zulala/zulaları patlat" - -msgid "Options related to fetching" -msgstr "Getirme ile ilgili seçenekler" - -msgid "force overwrite of local branch" -msgstr "zorla yerel dalın üzerine yaz" - -msgid "number of submodules pulled in parallel" -msgstr "paralelde çekilen altmodüllerin sayısı" - -msgid "use IPv4 addresses only" -msgstr "yalnızca IPv4 adresleri kullan" - -msgid "use IPv6 addresses only" -msgstr "yalnızca IPv6 adresleri kullan" - msgid "" "There is no candidate for rebasing against among the refs that you just " "fetched." @@ -10485,6 +10505,39 @@ "yapılandırmayı yürütme sırasında --rebase, --no-rebase veya\n" "--ff-only ile bir kerelik geçersiz kılabilirsiniz.\n" +msgid "control for recursive fetching of submodules" +msgstr "altmodüllerin özyineli getirilmesi için denetleme" + +msgid "Options related to merging" +msgstr "Birleştirme ile ilgili seçenekler" + +msgid "incorporate changes by rebasing rather than merging" +msgstr "değişiklikleri birleştirme yerine yeniden temellendirme ile kat" + +msgid "allow fast-forward" +msgstr "ileri sarıma izin ver" + +msgid "control use of pre-merge-commit and commit-msg hooks" +msgstr "pre-merge-commit ve commit-msg kancalarının kullanımını denetle" + +msgid "automatically stash/stash pop before and after" +msgstr "öncesinde ve sonrasında kendiliğinden zulala/zulaları patlat" + +msgid "Options related to fetching" +msgstr "Getirme ile ilgili seçenekler" + +msgid "force overwrite of local branch" +msgstr "zorla yerel dalın üzerine yaz" + +msgid "number of submodules pulled in parallel" +msgstr "paralelde çekilen altmodüllerin sayısı" + +msgid "use IPv4 addresses only" +msgstr "yalnızca IPv4 adresleri kullan" + +msgid "use IPv6 addresses only" +msgstr "yalnızca IPv6 adresleri kullan" + msgid "Updating an unborn branch with changes added to the index." msgstr "İndekse eklenen değişikliklerle henüz doğmamış bir dal güncelleniyor." @@ -12346,6 +12399,10 @@ msgid "only one pattern can be given with -l" msgstr "-l ile yalnızca bir dizgi verilebilir" +#, c-format +msgid "'%s' is not a valid commit-ish for %s" +msgstr "\"%s\", %s için geçerli bir işlememsi değil" + msgid "need some commits to replay" msgstr "yeniden oynatmak için birkaç işleme gerekli" @@ -12362,27 +12419,16 @@ "birden çok kaynaklı hedef ilerletilemiyor; çünkü sıralama hatalı tanımlanmış " "olurdu" -msgid "" -"cannot implicitly determine whether this is an --advance or --onto operation" -msgstr "" -"bunun --advance veya --onto işlemi olup olmadığı örtük olarak algılanamıyor" - -msgid "" -"cannot advance target with multiple source branches because ordering would " -"be ill-defined" -msgstr "" -"birden çok kaynak dallı hedef ilerletilemiyor; çünkü sıralama hatalı " -"tanımlanmış olurdu" - -msgid "cannot implicitly determine correct base for --onto" -msgstr "--onto için olan doğru temel örtük olarak algılanamıyor" +#, c-format +msgid "invalid %s value: '%s'" +msgstr "geçersiz %s değeri: \"%s\"" msgid "" "(EXPERIMENTAL!) git replay ([--contained] --onto <newbase> | --advance " -"<branch>) <revision-range>..." +"<branch>) [--ref-action[=<mode>]] <revision-range>" msgstr "" "(DENEYSEL!) git replay ([--contained] --onto <yeni-temel> | --advance <dal>) " -"<revizyon-erimi>..." +"[--ref-action[=<kip>]] <revizyon-erimi>..." msgid "make replay advance given branch" msgstr "verilen dalı önceden yeniden oynat" @@ -12390,8 +12436,11 @@ msgid "replay onto given commit" msgstr "verilen işlemeye yeniden oynat" -msgid "advance all branches contained in revision-range" -msgstr "revizyon eriminde içerilen tüm dalları ilerlet" +msgid "update all branches that point at commits in <revision-range>" +msgstr "<revizyon-erimi>'ndeki işlemelere işaret eden tüm dalları güncelle" + +msgid "control ref update behavior (update|print)" +msgstr "başvuru güncelleme davranışını denetle (update|print)" msgid "option --onto or --advance is mandatory" msgstr "--onto veya --advance seçeneğinin kullanımı zorunlu" @@ -12404,16 +12453,28 @@ "'struct rev_info' içindeki '%s' biti zorlanacağından kimi revizyon yürütme " "seçenekleri geçersiz kılınacak" +#, c-format +msgid "failed to begin ref transaction: %s" +msgstr "başvuru işlemine başlanılamadı: %s" + msgid "error preparing revisions" msgstr "revizyonlar hazırlanırken hata" -msgid "replaying down to root commit is not supported yet!" -msgstr "kök işlemeye kadar yeniden oynatma henüz desteklenmiyor!" +msgid "replaying down from root commit is not supported yet!" +msgstr "kök işlemeden aşağı yeniden oynatma henüz desteklenmiyor!" msgid "replaying merge commits is not supported yet!" msgstr "birleştirme işlemelerini yeniden oynatma henüz desteklenmiyor!" #, c-format +msgid "failed to update ref '%s': %s" +msgstr "\"%s\" başvurusu güncellenemedi: %s" + +#, c-format +msgid "failed to commit ref transaction: %s" +msgstr "başvuru işlemi işlenemedi: %s" + +#, c-format msgid "key '%s' not found" msgstr "'%s' anahtarı bulunamadı" @@ -12427,9 +12488,15 @@ msgid "synonym for --format=nul" msgstr "--format=nul eşanlamlısı" +msgid "print all keys/values" +msgstr "tüm anahtarları/değerleri yazdır" + msgid "unsupported output format" msgstr "desteklenmeyen çıktı biçimi" +msgid "--all and <key> cannot be used together" +msgstr "--all ve <anahtar> birlikte kullanılamaz" + msgid "References" msgstr "Başvurular" @@ -12460,6 +12527,12 @@ msgid "Blobs" msgstr "İkili geniş nesneler" +msgid "Inflated size" +msgstr "Şişirilmiş boyut" + +msgid "Disk size" +msgstr "Disk boyutu" + msgid "Repository structure" msgstr "Depo yapısı" @@ -14920,11 +14993,15 @@ #, c-format msgid "could not parse bundle list key %s with value '%s'" -msgstr "demet liste anahtarı %s ile '%s' değeri ayrıştırılamıyor" +msgstr "demet liste anahtarı %s ile \"%s\" değeri ayrıştırılamıyor" #, c-format msgid "bundle list at '%s' has no mode" -msgstr "'%s' konumundaki demet listesinin kipi yok" +msgstr "\"%s\" konumundaki demet listesinin kipi yok" + +#, c-format +msgid "bundle list at '%s': bundle '%s' has no uri" +msgstr "\"%s\" konumundaki demet: \"%s\" demetinde URI yok" msgid "failed to create temporary file" msgstr "geçici dosya oluşturulamadı" @@ -14948,16 +15025,20 @@ msgstr "demet URI özyineleme sınırı aşıldı (%d)" #, c-format +msgid "bundle '%s' has no uri" +msgstr "\"%s\" demetinde URI yok" + +#, c-format msgid "failed to download bundle from URI '%s'" -msgstr "'%s' URI'sinden demet indirilemedi" +msgstr "\"%s\" URI'sinden demet indirilemedi" #, c-format msgid "file at URI '%s' is not a bundle or bundle list" -msgstr "'%s' URI'sindeki dosya bir demet veya demet listesi değil" +msgstr "\"%s\" URI'sindeki dosya bir demet veya demet listesi değil" #, c-format msgid "bundle-uri: unexpected argument: '%s'" -msgstr "bundle-uri: beklenmedik argüman: '%s'" +msgstr "bundle-uri: beklenmedik argüman: \"%s\"" msgid "bundle-uri: expected flush after arguments" msgstr "bundle-uri: argümanlardan sonra floş bekleniyordu" @@ -15439,8 +15520,8 @@ msgid "Reuse recorded resolution of conflicted merges" msgstr "Çakışmaların kayıtlı çözümlerini yeniden kullan" -msgid "Reset current HEAD to the specified state" -msgstr "Geçerli HEAD'i belirtilen duruma sıfırla" +msgid "Set `HEAD` or the index to a known state" +msgstr "\"HEAD\"i veya indeksi bilinen bir duruma ayarla" msgid "Restore working tree files" msgstr "Çalışma ağacı dosyalarını eski durumuna getir" @@ -16951,10 +17032,6 @@ msgstr "'diff.submodule' yapılandırma değişkeni için bilinmeyen değer: '%s'" #, c-format -msgid "unknown value for config '%s': %s" -msgstr "'%s' yapılandırması için bilinmeyen değer: %s" - -#, c-format msgid "" "Found errors in 'diff.dirstat' config variable:\n" "%s" @@ -19065,6 +19142,9 @@ msgid "%s: failed to insert into database" msgstr "%s: veritabanına ekleme başarısız" +msgid "cannot add a submodule of a different hash algorithm" +msgstr "başka bir sağlama algoritmalı altmodül eklenemiyor" + #, c-format msgid "%s: unsupported file type" msgstr "%s: desteklenmeyen dosya türü" @@ -20510,6 +20590,10 @@ msgid "--format=%.*s cannot be used with --python, --shell, --tcl" msgstr "--format=%.*s, --python, --shell ve --tcl ile kullanılamaz" +#, c-format +msgid "parse_object_buffer failed on %s for %s" +msgstr "parse_object_buffer %s üzerinde başarısız oldu (%s için)" + msgid "failed to run 'describe'" msgstr "'describe' çalıştırılamadı" @@ -20541,10 +20625,6 @@ msgstr "eksik nesne %s (%s için)" #, c-format -msgid "parse_object_buffer failed on %s for %s" -msgstr "parse_object_buffer %s üzerinde başarısız oldu (%s için)" - -#, c-format msgid "malformed object at '%s'" msgstr "'%s' konumunda hatalı oluşturulmuş nesne" @@ -20845,16 +20925,28 @@ msgstr "başvuru adı %s bir sembolik bağ, onu kopyalamak desteklenmiyor" #, c-format +msgid "reftable stack for worktree '%s' is broken" +msgstr "\"%s\" çalışma ağacı için başvuru tablosu yığını bozuk" + +#, c-format +msgid "could not create iterator for worktree '%s'" +msgstr "\"%s\" çalışma ağacı için yineleyici oluşturulamadı" + +#, c-format +msgid "could not read record for worktree '%s'" +msgstr "\"%s\" çalışma ağacı için kayıt okunamadı" + +#, c-format msgid "pattern '%s' has no '*'" -msgstr "'%s' dizgisinde '*' yok" +msgstr "\"%s\" dizgisinde \"*\" yok" #, c-format msgid "replacement '%s' has no '*'" -msgstr "'%s' yedeğinde '*' yok" +msgstr "\"%s\" ikamesinde \"*\" yok" #, c-format msgid "invalid quoting in push-option value: '%s'" -msgstr "push-option değerinde geçersiz tırnak içine alım: '%s'" +msgstr "push-option değerinde geçersiz tırnak içine alım: \"%s\"" #, c-format msgid "unknown value for object-format: %s" @@ -21267,6 +21359,9 @@ msgid "could not finish pack-objects to repack promisor objects" msgstr "vaatçi nesneleri yeniden paketleme için pack-objects bitirilemedi" +msgid "could not start pack-objects to repack promisor packs" +msgstr "vaatçi paketleri yeniden paketlemek üzere pack-objects başlatılamadı" + #, c-format msgid "pack prefix %s does not begin with objdir %s" msgstr "paket öneki %s, nesne dizini %s ile başlamıyor" @@ -22261,10 +22356,6 @@ msgid "illegal label name: '%.*s'" msgstr "izin verilmeyen etiket adı: '%.*s'" -#, c-format -msgid "could not resolve '%s'" -msgstr "'%s' çözülemedi" - msgid "writing fake root commit" msgstr "sahte kök işlemesi yazılıyor" @@ -22739,49 +22830,68 @@ msgid "bad %s format: %%%.*s" msgstr "hatalı %s biçimi: %%%.*s" -#. TRANSLATORS: IEC 80000-13:2008 gibibyte #, c-format -msgid "%u.%2.2u GiB" -msgstr "%u.%2.2u GiB" +msgid "%u.%2.2u" +msgstr "%u.%2.2u" -#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second -#, c-format -msgid "%u.%2.2u GiB/s" -msgstr "%u.%2.2u GiB/sn" +#. TRANSLATORS: SI decimal prefix symbol for 10^9 +msgid "G" +msgstr "G" -#. TRANSLATORS: IEC 80000-13:2008 mebibyte -#, c-format -msgid "%u.%2.2u MiB" -msgstr "%u.%2.2u MiB" +#. TRANSLATORS: SI decimal prefix symbol for 10^6 +msgid "M" +msgstr "M" -#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second -#, c-format -msgid "%u.%2.2u MiB/s" -msgstr "%u.%2.2u MiB/sn" +#. TRANSLATORS: SI decimal prefix symbol for 10^3 +msgid "k" +msgstr "k" -#. TRANSLATORS: IEC 80000-13:2008 kibibyte -#, c-format -msgid "%u.%2.2u KiB" -msgstr "%u.%2.2u KiB" +#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second and gibibyte +msgid "GiB/s" +msgstr "GiB/sn" -#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second -#, c-format -msgid "%u.%2.2u KiB/s" -msgstr "%u.%2.2u KiB/sn" +msgid "GiB" +msgstr "GiB" -#. TRANSLATORS: IEC 80000-13:2008 byte -#, c-format -msgid "%u byte" -msgid_plural "%u bytes" -msgstr[0] "%u bayt" -msgstr[1] "%u bayt" +#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second and mebibyte +msgid "MiB/s" +msgstr "MiB/sn" + +msgid "MiB" +msgstr "MiB" + +#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second and kibibyte +msgid "KiB/s" +msgstr "KiB/sn" + +msgid "KiB" +msgstr "KiB" + +#. TRANSLATORS: IEC 80000-13:2008 byte/second and byte +msgid "B/s" +msgstr "B/sn" + +msgid "B" +msgstr "B" #. TRANSLATORS: IEC 80000-13:2008 byte/second +msgid "byte/s" +msgid_plural "bytes/s" +msgstr[0] "bayt/sn" +msgstr[1] "bayt/sn" + +#. TRANSLATORS: IEC 80000-13:2008 byte +msgid "byte" +msgid_plural "bytes" +msgstr[0] "bayt" +msgstr[1] "bayt" + +#. TRANSLATORS: The first argument is the number string. The second +#. argument is the unit string (i.e. "12.34 MiB/s"). +#. #, c-format -msgid "%u byte/s" -msgid_plural "%u bytes/s" -msgstr[0] "%u bayt/sn" -msgstr[1] "%u bayt/sn" +msgid "%s %s" +msgstr "%s %s" #, c-format msgid "ignoring suspicious submodule name: %s" @@ -23072,9 +23182,6 @@ msgid "number of requests per thread" msgstr "iş parçacığı başına düşen istek sayısı" -msgid "byte" -msgstr "bayt" - msgid "ballast character" msgstr "dengeleyici karakter"
diff --git a/po/zh_CN.po b/po/zh_CN.po index db7518c..2e249de 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po
@@ -1,6 +1,7 @@ # Chinese translations for Git package # Git 软件包的简体中文翻译. # Copyright (C) 2012,2013 Jiang Xin <worldhello.net AT gmail.com> +# Copyright (C) 2021-2023 Fangyi Zhou <me@fangyi.io> # This file is distributed under the same license as the Git package. # Contributors: # - Fangyi Zhou <me AT fangyi.io> @@ -16,15 +17,15 @@ # - Zhuang Ya <zhuangya AT me.com> # - Teng Long <dyroneteng AT gmail.com> # -# Git glossary for Chinese translators +# Git glossary for Chinese translators # # English | Chinese # ---------------------------------+-------------------------------------- -# 3-way merge | 三方合并 +# 3-way merge | 三路合并 # abbreviate | 简写(的 SHA-1 值) # alternate object database | 备用对象库 # attribute source | 属性来源 -# amend | 修补 +# amend | 修订 # ancestor | 祖先,祖先提交 # annotated tag | 附注标签 # bare repository | 纯仓库 @@ -33,9 +34,9 @@ # blob object | 数据对象 # bloom filter | 布隆过滤器 # branch | 分支 -# bundle | 归档包 +# bundle | 捆绑包 # bypass | 绕过 -# cache | 索引(的别称) +# cache | 缓存(索引的别称) # chain | (提交)链 # changeset | 变更集 # checkout | 检出 @@ -48,7 +49,7 @@ # commit object | 提交对象 # commit-graph | 提交图 # commit-ish (also committish) | 提交号 -# cone | 锥形(稀疏检出模型);锥(稀疏检出) +# cone | 锥形模式(稀疏检出);锥(稀疏检出) # conflict | 冲突 # core Git | 核心 Git 工具 # cover letter | 附函 @@ -59,28 +60,29 @@ # dirty | 脏(的工作区) # dumb HTTP protocol | 哑 HTTP 协议 # enlistment | 登记(在 scalar 中使用) -# evil merge | 坏合并(合并引入了父提交没有的修改) +# evil merge | 恶意合并(合并引入了额外变更,即父提交没有的修改) # fast-forward | 快进 # fetch | 获取 # file system | 文件系统 # fork | 派生 # Git archive | 仓库(对于 arch 用户) # gitfile | gitfile(仓库链接文件) -# grafts | (提交)移植 +# grafts | (提交)嫁接 # hash | 哈希值 # HEAD | HEAD(头指针,亦即当前分支) # head | 头、分支 # head ref | 分支 # header | 头信息 # hook | 钩子 -# hunk | 补丁片段 # index | 索引 # index entry | 索引条目 # loose object | 松散对象 # loose refs | 松散引用 -# magic | 神奇前缀(路径规格支持的一种前缀表达式) -# master | master(默认分支名) +# magic | 魔法前缀(路径规格支持的一种前缀表达式) +# master | master(传统默认分支名,将被 main 替代) # merge | 合并 +# merge-base | 合并基线 +# note | 注解 # object | 对象 # object database | 对象库 # object identifier | 对象标识符 @@ -95,11 +97,12 @@ # parent | 父提交 # partial clone | 部分克隆 # patch | 补丁 +# patch hunk | 补丁块 # pathspec | 路径规格 # pattern | 模式 # pickaxe | 挖掘 -# plumbing | 管件(Git 底层核心命令的别称) -# porcelain | 瓷件(Git 上层封装命令的别称) +# plumbing | 管件(Git 底层命令的别称) +# porcelain | 瓷件(Git 高层命令的别称) # precious-objects repo | 珍品仓库 # preferred pack | 首选包(多包索引中引入的首选包概念) # promisor | 承诺者 @@ -130,7 +133,7 @@ # squash | 挤压 # stage | n. 暂存区(即索引); v. 暂存 # stale | 过期的 -# stash | n. 贮藏区; v. 贮藏 +# stash | n. 储藏区; v. 储藏 # submodule | 子模组 # symref | 符号引用 # tag | n. 标签; v. 打标签 @@ -146,16 +149,28 @@ # unpack | 解包 # unreachable object | 不可达对象 # unstage | 取消暂存 -# upstream | 上游 -# upstream branch | 上游分支 +# upstream | 上游(分支/仓库) # working tree | 工作区 -# Fangyi Zhou <me@fangyi.io>, 2021-2023. +# +# Preferred Chinese Terms +# +# - 默认 (not 缺省) +# - 其他 (not 其它) +# - 作为 (not 做为) +# - 当作 (not 当做) +# - 登录 (not 登陆) +# - 账户 (not 帐户) +# - 密码 (not 口令) +# - 撤销 (not 撤消) +# - 吗? (not 么?) # 疑问助词 +# - 无法 (not 不能):客观。客观障碍、技术限制、物理规律,非主观 +# - 不能 (not 无法):主观。能力不足、规则限制、逻辑冲突、设计限制、意愿拒绝 # msgid "" msgstr "" "Project-Id-Version: Git\n" "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n" -"POT-Creation-Date: 2025-11-15 22:02+0800\n" +"POT-Creation-Date: 2026-01-29 19:17+0800\n" "PO-Revision-Date: 2025-11-16 01:03+0800\n" "Last-Translator: Teng Long <dyroneteng@gmail.com>\n" "Language-Team: GitHub <https://github.com/dyrone/git/>\n" @@ -178,7 +193,7 @@ #: add-interactive.c builtin/merge.c builtin/rebase.c reset.c sequencer.c msgid "could not read index" -msgstr "不能读取索引" +msgstr "无法读取索引" #: add-interactive.c msgid "binary" @@ -199,11 +214,11 @@ #: add-interactive.c #, c-format msgid "could not stage '%s'" -msgstr "不能暂存 '%s'" +msgstr "无法暂存 '%s'" #: add-interactive.c builtin/stash.c reset.c sequencer.c msgid "could not write index" -msgstr "不能写入索引" +msgstr "无法写入索引" #: add-interactive.c #, c-format @@ -215,7 +230,7 @@ #: add-interactive.c #, c-format msgid "note: %s is untracked now.\n" -msgstr "说明:%s 现已成为未跟踪的。\n" +msgstr "注意:%s 现在未被跟踪。\n" #: add-interactive.c apply.c builtin/checkout.c builtin/reset.c #, c-format @@ -228,7 +243,7 @@ #: add-interactive.c msgid "Could not parse HEAD^{tree}" -msgstr "不能解析 HEAD^{tree}" +msgstr "无法解析 HEAD^{tree}" #: add-interactive.c #, c-format @@ -274,7 +289,7 @@ #: add-interactive.c msgid "Review diff" -msgstr "检视 diff" +msgstr "查看差异" #: add-interactive.c msgid "show paths with changes" @@ -350,11 +365,11 @@ #: add-interactive.c msgid "staged" -msgstr "缓存" +msgstr "已暂存" #: add-interactive.c msgid "unstaged" -msgstr "未缓存" +msgstr "未暂存" #: add-interactive.c apply.c builtin/am.c builtin/bugreport.c builtin/clone.c #: builtin/diagnose.c builtin/fetch.c builtin/hook.c builtin/merge.c @@ -364,7 +379,7 @@ #: add-interactive.c msgid "could not refresh index" -msgstr "不能刷新索引" +msgstr "无法刷新索引" #: add-interactive.c builtin/clean.c #, c-format @@ -379,12 +394,12 @@ #: add-patch.c #, c-format msgid "Stage deletion [y,n,q,a,d%s,?]? " -msgstr "暂存删除动作 [y,n,q,a,d%s,?]? " +msgstr "暂存删除变更 [y,n,q,a,d%s,?]? " #: add-patch.c #, c-format msgid "Stage addition [y,n,q,a,d%s,?]? " -msgstr "暂存添加动作 [y,n,q,a,d%s,?]? " +msgstr "暂存新增变更 [y,n,q,a,d%s,?]? " #: add-patch.c #, c-format @@ -414,28 +429,28 @@ #: add-patch.c #, c-format msgid "Stash mode change [y,n,q,a,d%s,?]? " -msgstr "贮藏模式变更 [y,n,q,a,d%s,?]? " +msgstr "储藏模式变更 [y,n,q,a,d%s,?]? " #: add-patch.c #, c-format msgid "Stash deletion [y,n,q,a,d%s,?]? " -msgstr "贮藏删除动作 [y,n,q,a,d%s,?]? " +msgstr "储藏删除变更 [y,n,q,a,d%s,?]? " #: add-patch.c #, c-format msgid "Stash addition [y,n,q,a,d%s,?]? " -msgstr "贮藏添加动作 [y,n,q,a,d%s,?]? " +msgstr "储藏新增变更 [y,n,q,a,d%s,?]? " #: add-patch.c #, c-format msgid "Stash this hunk [y,n,q,a,d%s,?]? " -msgstr "贮藏该块 [y,n,q,a,d%s,?]? " +msgstr "储藏该块 [y,n,q,a,d%s,?]? " #: add-patch.c msgid "" "If the patch applies cleanly, the edited hunk will immediately be marked for " "stashing." -msgstr "如果补丁能正确地应用,编辑块将立即标记为贮藏。" +msgstr "如果补丁能正确地应用,编辑块将立即标记为储藏。" #: add-patch.c msgid "" @@ -445,11 +460,11 @@ "a - stash this hunk and all later hunks in the file\n" "d - do not stash this hunk or any of the later hunks in the file\n" msgstr "" -"y - 贮藏该块\n" -"n - 不要贮藏该块\n" -"q - 退出。不贮藏该块及后面的全部块\n" -"a - 贮藏该块和本文件中后面的全部块\n" -"d - 不贮藏该块和本文件中后面的全部块\n" +"y - 储藏该块\n" +"n - 不要储藏该块\n" +"q - 退出。不储藏该块及后面的全部块\n" +"a - 储藏该块和本文件中后面的全部块\n" +"d - 不储藏该块和本文件中后面的全部块\n" #: add-patch.c #, c-format @@ -459,12 +474,12 @@ #: add-patch.c #, c-format msgid "Unstage deletion [y,n,q,a,d%s,?]? " -msgstr "取消暂存删除动作 [y,n,q,a,d%s,?]? " +msgstr "取消暂存删除变更 [y,n,q,a,d%s,?]? " #: add-patch.c #, c-format msgid "Unstage addition [y,n,q,a,d%s,?]? " -msgstr "取消暂存添加动作 [y,n,q,a,d%s,?]? " +msgstr "取消暂存新增变更 [y,n,q,a,d%s,?]? " #: add-patch.c #, c-format @@ -475,7 +490,7 @@ msgid "" "If the patch applies cleanly, the edited hunk will immediately be marked for " "unstaging." -msgstr "如果补丁能正确地应用,编辑块将立即标记为未暂存。" +msgstr "如果补丁能正确地应用,编辑块将立即标记为取消暂存。" #: add-patch.c msgid "" @@ -539,12 +554,12 @@ #: add-patch.c #, c-format msgid "Discard deletion from worktree [y,n,q,a,d%s,?]? " -msgstr "从工作区中丢弃删除动作 [y,n,q,a,d%s,?]? " +msgstr "从工作区中丢弃删除变更 [y,n,q,a,d%s,?]? " #: add-patch.c #, c-format msgid "Discard addition from worktree [y,n,q,a,d%s,?]? " -msgstr "从工作区中丢弃添加动作 [y,n,q,a,d%s,?]? " +msgstr "从工作区中丢弃新增变更 [y,n,q,a,d%s,?]? " #: add-patch.c #, c-format @@ -579,12 +594,12 @@ #: add-patch.c #, c-format msgid "Discard deletion from index and worktree [y,n,q,a,d%s,?]? " -msgstr "从索引和工作区中丢弃删除动作 [y,n,q,a,d%s,?]? " +msgstr "从索引和工作区中丢弃删除变更 [y,n,q,a,d%s,?]? " #: add-patch.c #, c-format msgid "Discard addition from index and worktree [y,n,q,a,d%s,?]? " -msgstr "从索引和工作区中丢弃添加动作 [y,n,q,a,d%s,?]? " +msgstr "从索引和工作区中丢弃新增变更 [y,n,q,a,d%s,?]? " #: add-patch.c #, c-format @@ -680,11 +695,11 @@ #: add-patch.c msgid "could not parse diff" -msgstr "不能解析差异信息" +msgstr "无法解析差异信息" #: add-patch.c msgid "could not parse colored diff" -msgstr "不能解析彩色差异信息" +msgstr "无法解析彩色差异信息" #: add-patch.c #, c-format @@ -707,7 +722,7 @@ "expected context line #%d in\n" "%.*s" msgstr "" -"预期上下文行 #%d 于\n" +"预期上下文行 #%d 位于\n" "%.*s" #: add-patch.c @@ -720,7 +735,7 @@ msgstr "" "块不重叠:\n" "%.*s\n" -"\t不是结尾于:\n" +"\t未以以下内容结尾:\n" "%.*s" #: add-patch.c @@ -746,7 +761,7 @@ "edit again. If all lines of the hunk are removed, then the edit is\n" "aborted and the hunk is left unchanged.\n" msgstr "" -"如果不能干净地应用,您将有机会重新编辑。如果该块的全部内容删除,则\n" +"如果无法干净地应用,您将有机会重新编辑。如果该块的全部内容删除,则\n" "此次编辑被终止,该块不会被修改。\n" #: add-patch.c @@ -766,15 +781,15 @@ #: add-patch.c msgid "" "Your edited hunk does not apply. Edit again (saying \"no\" discards!) [y/n]? " -msgstr "您的编辑块不能被应用。重新编辑(选择 \"no\" 丢弃!) [y/n]? " +msgstr "您的编辑块无法被应用。重新编辑(选择 \"no\" 丢弃!) [y/n]? " #: add-patch.c msgid "The selected hunks do not apply to the index!" -msgstr "选中的块不能应用到索引!" +msgstr "选中的块无法应用到索引!" #: add-patch.c msgid "Apply them to the worktree anyway? " -msgstr "无论如何都要应用到工作区么?" +msgstr "仍要应用到工作区吗?" #: add-patch.c msgid "Nothing was applied.\n" @@ -813,15 +828,15 @@ #: add-patch.c msgid "No other hunk" -msgstr "没有其它块" +msgstr "没有其他块" #: add-patch.c msgid "No other undecided hunk" -msgstr "没有其它未决定的块" +msgstr "没有其他未决定的块" #: add-patch.c msgid "No other hunks to goto" -msgstr "没有其它可供跳转的块" +msgstr "没有其他可供跳转的块" #: add-patch.c msgid "go to which hunk (<ret> to see more)? " @@ -845,7 +860,7 @@ #: add-patch.c msgid "No other hunks to search" -msgstr "没有其它可供查找的块" +msgstr "没有其他可供查找的块" #: add-patch.c msgid "search for regex? " @@ -862,7 +877,7 @@ #: add-patch.c msgid "Sorry, cannot split this hunk" -msgstr "对不起,不能拆分这个块" +msgstr "对不起,无法拆分这个块" #: add-patch.c #, c-format @@ -871,7 +886,7 @@ #: add-patch.c msgid "Sorry, cannot edit this hunk" -msgstr "对不起,不能编辑这个块" +msgstr "对不起,无法编辑这个块" #: add-patch.c #, c-format @@ -922,7 +937,7 @@ #: advice.c msgid "Reverting is not possible because you have unmerged files." -msgstr "无法回退,因为您有未合并的文件。" +msgstr "无法还原,因为您有未合并的文件。" #: advice.c msgid "Rebasing is not possible because you have unmerged files." @@ -1049,7 +1064,7 @@ "* Use \"git add --sparse <paths>\" to update the index\n" "* Use \"git sparse-checkout reapply\" to apply the sparsity rules" msgstr "" -"为了修正这些的路径的稀疏性,请运行下列命令:\n" +"为了修正这些路径的稀疏性,请运行下列命令:\n" "* 使用 \"git add --sparse <路径>\" 来更新索引\n" "* 使用 \"git sparse-checkout reapply\" 来应用稀疏规则" @@ -1083,9 +1098,9 @@ #: builtin/fast-export.c builtin/fetch.c builtin/help.c builtin/index-pack.c #: builtin/init-db.c builtin/log.c builtin/ls-files.c builtin/merge-base.c #: builtin/merge-tree.c builtin/merge.c builtin/rebase.c builtin/repack.c -#: builtin/replay.c builtin/reset.c builtin/rev-parse.c builtin/show-branch.c -#: builtin/stash.c builtin/submodule--helper.c builtin/tag.c builtin/worktree.c -#: parse-options.c range-diff.c revision.c +#: builtin/reset.c builtin/rev-parse.c builtin/show-branch.c builtin/stash.c +#: builtin/submodule--helper.c builtin/tag.c builtin/worktree.c parse-options.c +#: range-diff.c revision.c #, c-format msgid "options '%s' and '%s' cannot be used together" msgstr "选项 '%s' 和 '%s' 不能同时使用" @@ -1116,7 +1131,7 @@ #: apply.c #, c-format msgid "unable to find filename in patch at line %d" -msgstr "不能在补丁的第 %d 行找到文件名" +msgstr "无法在补丁的第 %d 行找到文件名" #: apply.c #, c-format @@ -1225,7 +1240,7 @@ #: apply.c #, c-format msgid "unable to open or read %s" -msgstr "不能打开或读取 %s" +msgstr "无法打开或读取 %s" #: apply.c #, c-format @@ -1250,7 +1265,7 @@ "while searching for:\n" "%.*s" msgstr "" -"当查询:\n" +"在搜索:\n" "%.*s" #: apply.c @@ -1261,12 +1276,12 @@ #: apply.c #, c-format msgid "cannot reverse-apply a binary patch without the reverse hunk to '%s'" -msgstr "不能反向应用一个缺少到 '%s' 的反向数据块的二进制补丁" +msgstr "无法反向应用一个缺少到 '%s' 的反向数据块的二进制补丁" #: apply.c #, c-format msgid "cannot apply binary patch to '%s' without full index line" -msgstr "不能在 '%s' 上应用没有完整索引行的二进制补丁" +msgstr "无法在 '%s' 上应用没有完整索引行的二进制补丁" #: apply.c #, c-format @@ -1302,7 +1317,7 @@ #: apply.c builtin/mv.c #, c-format msgid "cannot checkout %s" -msgstr "不能检出 %s" +msgstr "无法检出 %s" #: apply.c midx.c pack-mtimes.c pack-revindex.c setup.c #, c-format @@ -1312,7 +1327,7 @@ #: apply.c #, c-format msgid "reading from '%s' beyond a symbolic link" -msgstr "读取位于符号链接中的 '%s'" +msgstr "从 '%s' 读取时越过符号链接" #: apply.c #, c-format @@ -1331,12 +1346,12 @@ #: apply.c msgid "repository lacks the necessary blob to perform 3-way merge." -msgstr "仓库缺乏执行三方合并所必需的数据对象。" +msgstr "仓库缺乏执行三路合并所必需的数据对象。" #: apply.c #, c-format msgid "Performing three-way merge...\n" -msgstr "执行三方合并...\n" +msgstr "执行三路合并...\n" #: apply.c #, c-format @@ -1346,12 +1361,12 @@ #: apply.c #, c-format msgid "Failed to perform three-way merge...\n" -msgstr "无法执行三方合并...\n" +msgstr "无法执行三路合并...\n" #: apply.c #, c-format msgid "Applied patch to '%s' with conflicts.\n" -msgstr "应用补丁到 '%s' 存在冲突。\n" +msgstr "应用补丁到 '%s' 时发生冲突。\n" #: apply.c #, c-format @@ -1361,7 +1376,7 @@ #: apply.c #, c-format msgid "Falling back to direct application...\n" -msgstr "回落到直接应用...\n" +msgstr "改为直接应用...\n" #: apply.c msgid "removal patch leaves file contents" @@ -1405,7 +1420,7 @@ #: apply.c #, c-format msgid "affected file '%s' is beyond a symbolic link" -msgstr "受影响的文件 '%s' 位于符号链接中" +msgstr "受影响的文件 '%s' 位于符号链接之外" #: apply.c #, c-format @@ -1435,32 +1450,32 @@ #: apply.c #, c-format msgid "could not add %s to temporary index" -msgstr "不能在临时索引中添加 %s" +msgstr "无法在临时索引中添加 %s" #: apply.c #, c-format msgid "could not write temporary index to %s" -msgstr "不能把临时索引写入到 %s" +msgstr "无法把临时索引写入到 %s" #: apply.c #, c-format msgid "unable to remove %s from index" -msgstr "不能从索引中移除 %s" +msgstr "无法从索引中移除 %s" #: apply.c #, c-format msgid "corrupt patch for submodule %s" -msgstr "子模组 %s 损坏的补丁" +msgstr "针对子模组 %s 的补丁已损坏" #: apply.c #, c-format msgid "unable to stat newly created file '%s'" -msgstr "不能对新建文件 '%s' 调用 stat" +msgstr "无法对新建文件 '%s' 调用 stat" #: apply.c #, c-format msgid "unable to create backing store for newly created file %s" -msgstr "不能为新建文件 %s 创建后端存储" +msgstr "无法为新建文件 %s 创建后端存储" #: apply.c #, c-format @@ -1480,7 +1495,7 @@ #: apply.c #, c-format msgid "unable to write file '%s' mode %o" -msgstr "不能写文件 '%s' 权限 %o" +msgstr "无法写文件 '%s' 权限 %o" #: apply.c #, c-format @@ -1495,37 +1510,37 @@ #, c-format msgid "Applying patch %%s with %d reject..." msgid_plural "Applying patch %%s with %d rejects..." -msgstr[0] "应用 %%s 个补丁,其中 %d 个被拒绝..." -msgstr[1] "应用 %%s 个补丁,其中 %d 个被拒绝..." +msgstr[0] "应用补丁 %%s,其中 %d 个被拒绝..." +msgstr[1] "应用补丁 %%s,其中 %d 个被拒绝..." #: apply.c #, c-format msgid "cannot open %s" -msgstr "不能打开 %s" +msgstr "无法打开 %s" #: apply.c rerere.c #, c-format msgid "cannot unlink '%s'" -msgstr "不能删除 '%s'" +msgstr "无法删除 '%s'" #: apply.c #, c-format msgid "Hunk #%d applied cleanly." -msgstr "第 #%d 个片段成功应用。" +msgstr "第 #%d 个块成功应用。" #: apply.c #, c-format msgid "Rejected hunk #%d." -msgstr "拒绝第 #%d 个片段。" +msgstr "拒绝第 #%d 个块。" #: apply.c #, c-format msgid "Skipped patch '%s'." -msgstr "略过补丁 '%s'。" +msgstr "跳过补丁 '%s'。" #: apply.c msgid "No valid patches in input (allow with \"--allow-empty\")" -msgstr "输入中没有合法的补丁 (使用 \"--allow-empty\" 来允许)" +msgstr "输入中没有合法补丁(可用 \"--allow-empty\" 允许)" #: apply.c t/helper/test-cache-tree.c msgid "unable to read index file" @@ -1534,14 +1549,14 @@ #: apply.c #, c-format msgid "can't open patch '%s': %s" -msgstr "不能打开补丁 '%s':%s" +msgstr "无法打开补丁 '%s':%s" #: apply.c #, c-format msgid "squelched %d whitespace error" msgid_plural "squelched %d whitespace errors" -msgstr[0] "抑制下仍有 %d 个空白字符误用" -msgstr[1] "抑制下仍有 %d 个空白字符误用" +msgstr[0] "已抑制 %d 个空白字符错误" +msgstr[1] "已抑制 %d 个空白字符错误" #: apply.c #, c-format @@ -1563,11 +1578,11 @@ #: apply.c msgid "don't apply changes matching the given path" -msgstr "不要应用与给出路径向匹配的变更" +msgstr "不要应用与给定路径相匹配的变更" #: apply.c msgid "apply changes matching the given path" -msgstr "应用与给出路径向匹配的变更" +msgstr "应用与给定路径相匹配的变更" #: apply.c builtin/am.c msgid "num" @@ -1579,7 +1594,7 @@ #: apply.c msgid "ignore additions made by the patch" -msgstr "忽略补丁中的添加的文件" +msgstr "忽略补丁新增的内容" #: apply.c msgid "instead of applying the patch, output diffstat for the input" @@ -1619,7 +1634,7 @@ #: apply.c msgid "attempt three-way merge, fall back on normal patch if that fails" -msgstr "尝试三路合并,如果失败则回落至正常补丁模式" +msgstr "尝试三路合并,如果失败则回落到普通补丁模式" #: apply.c builtin/merge-file.c msgid "for conflicts, use our version" @@ -1635,7 +1650,7 @@ #: apply.c msgid "build a temporary index based on embedded index information" -msgstr "创建一个临时索引基于嵌入的索引信息" +msgstr "基于嵌入的索引信息创建临时索引" #: apply.c builtin/checkout-index.c msgid "paths are separated with NUL character" @@ -1652,7 +1667,7 @@ #: apply.c msgid "detect new or modified lines that have whitespace errors" -msgstr "检查新增和修改的行中间的空白字符滥用" +msgstr "检测新增或修改的行是否存在空白错误" #: apply.c msgid "ignore changes in whitespace when finding context" @@ -1668,19 +1683,19 @@ #: apply.c msgid "leave the rejected hunks in corresponding *.rej files" -msgstr "将拒绝的补丁片段保存在对应的 *.rej 文件中" +msgstr "将被拒绝的块保存在对应的 *.rej 文件中" #: apply.c msgid "allow overlapping hunks" -msgstr "允许重叠的补丁片段" +msgstr "允许重叠的块" #: apply.c msgid "tolerate incorrectly detected missing new-line at the end of file" -msgstr "允许不正确的文件末尾换行符" +msgstr "容忍误判的文件末尾缺失换行符" #: apply.c msgid "do not trust the line counts in the hunk headers" -msgstr "不信任补丁片段的头信息中的行号" +msgstr "不信任块头信息中的行数" #: apply.c builtin/am.c msgid "root" @@ -1701,12 +1716,12 @@ #: archive-tar.c archive-zip.c #, c-format msgid "cannot stream blob %s" -msgstr "不能打开数据对象 %s" +msgstr "无法流式传输数据对象 %s" #: archive-tar.c archive-zip.c #, c-format msgid "unsupported file mode: 0%o (SHA1: %s)" -msgstr "不支持的文件模式:0%o (SHA1: %s)" +msgstr "不支持的文件模式:0%o (SHA1:%s)" #: archive-tar.c archive-zip.c builtin/pack-objects.c #, c-format @@ -1759,12 +1774,12 @@ #: archive.c builtin/fast-import.c builtin/gc.c builtin/notes.c builtin/tag.c #, c-format msgid "cannot read '%s'" -msgstr "不能读取 '%s'" +msgstr "无法读取 '%s'" #: archive.c #, c-format msgid "pathspec '%s' matches files outside the current directory" -msgstr "路径规格 '%s' 匹配了当前目录外的文件'" +msgstr "路径规格 '%s' 匹配了当前目录外的文件" #: archive.c builtin/add.c builtin/rm.c #, c-format @@ -1881,7 +1896,7 @@ #: archive.c builtin/archive.c msgid "retrieve the archive from remote repository <repo>" -msgstr "从远程仓库(<仓库>)提取归档文件" +msgstr "从远程仓库 <仓库> 提取归档文件" #: archive.c builtin/archive.c builtin/difftool.c builtin/notes.c msgid "command" @@ -1929,7 +1944,7 @@ #: attr.c msgid "unable to add additional attribute" -msgstr "不能添加额外属性" +msgstr "无法添加额外属性" #: attr.c #, c-format @@ -1946,7 +1961,7 @@ "Negative patterns are ignored in git attributes\n" "Use '\\!' for literal leading exclamation." msgstr "" -"负值模版在 git attributes 中被忽略\n" +"否定模式在 git attributes 中被忽略\n" "当字符串确实要以感叹号开始时,使用 '\\!'。" #: attr.c @@ -1981,7 +1996,7 @@ #: builtin/pack-objects.c combine-diff.c object-file.c rerere.c #, c-format msgid "unable to read %s" -msgstr "不能读 %s" +msgstr "无法读取 %s" #: bisect.c #, c-format @@ -2004,7 +2019,7 @@ "The merge base %s is bad.\n" "This means the bug has been fixed between %s and [%s].\n" msgstr "" -"合并基线 %s 是坏的。\n" +"合并基线 %s 是有问题的。\n" "这意味着介于 %s 和 [%s] 之间的 bug 已经被修复。\n" #: bisect.c @@ -2043,14 +2058,14 @@ "So we cannot be sure the first %s commit is between %s and %s.\n" "We continue anyway." msgstr "" -"介于 %s 和 [%s] 的合并基线一定被忽略了。\n" +"介于 %s 和 [%s] 的合并基线必须被跳过。\n" "所以我们无法确认第一个 %s 提交是否介于 %s 和 %s 之间。\n" -"我们仍旧继续。" +"我们仍然继续。" #: bisect.c #, c-format msgid "Bisecting: a merge base must be tested\n" -msgstr "二分查找中:合并基线必须是经过测试的\n" +msgstr "二分查找中:合并基线必须经过测试\n" #: bisect.c #, c-format @@ -2060,17 +2075,17 @@ #: bisect.c #, c-format msgid "could not create file '%s'" -msgstr "不能创建文件 '%s'" +msgstr "无法创建文件 '%s'" #: bisect.c builtin/notes.c #, c-format msgid "unable to start 'show' for object '%s'" -msgstr "不能为对象 '%s' 开始 'show'" +msgstr "无法对对象 '%s' 启动 'show'" #: bisect.c builtin/merge.c #, c-format msgid "could not read file '%s'" -msgstr "不能读取文件 '%s'" +msgstr "无法读取文件 '%s'" #: bisect.c msgid "reading bisect refs failed" @@ -2134,7 +2149,7 @@ #: blame.c #, c-format msgid "cannot read blob %s for path %s" -msgstr "不能为路径 %2$s 读取数据对象 %1$s" +msgstr "无法为路径 %2$s 读取数据对象 %1$s" #: branch.c msgid "" @@ -2173,7 +2188,7 @@ "the remote tracking information by invoking:" msgstr "" "\n" -"在修复错误后,您可以通过执行以下命令来尝试修改远程跟踪分支:" +"在修复错误后,您可以通过执行以下命令来尝试修复远程跟踪信息:" #: branch.c #, c-format @@ -2188,7 +2203,7 @@ #: branch.c #, c-format msgid "not tracking: ambiguous information for ref '%s'" -msgstr "不在跟踪中:引用 '%s' 有歧义的信息" +msgstr "未跟踪:引用 '%s' 的信息含糊不清" # 译者:为保证在输出中对齐,注意调整句中空格! #. #-#-#-#-# branch.c.po #-#-#-#-# @@ -2222,11 +2237,11 @@ "different remotes' fetch refspecs map into different\n" "tracking namespaces." msgstr "" -"有多个远程的获取引用规格映射到了追踪引用 '%s':\n" +"有多个远程的获取引用规格映射到了跟踪引用 '%s':\n" "%s\n" "这一般是个配置错误。\n" "\n" -"如果要支持设置追踪分支,请保证不同远程的获取引用规格映射至不同的追踪命名空" +"如果要支持设置跟踪分支,请保证不同远程的获取引用规格映射至不同的跟踪命名空" "间。" #: branch.c @@ -2235,8 +2250,8 @@ msgstr "'%s' 不是一个有效的分支名称" #: branch.c builtin/branch.c -msgid "See `man git check-ref-format`" -msgstr "查阅 `man git check-ref-format`" +msgid "See 'git help check-ref-format'" +msgstr "参阅 'git help check-ref-format'" #: branch.c #, c-format @@ -2302,13 +2317,13 @@ "You may try updating the submodules using 'git checkout --no-recurse-" "submodules %s && git submodule update --init'" msgstr "" -"你可以用 'git checkout --no-recurse-submodules %s && git submodule update --" +"您可以用 'git checkout --no-recurse-submodules %s && git submodule update --" "init' 来尝试更新子模组" #: branch.c #, c-format msgid "submodule '%s': cannot create branch '%s'" -msgstr "子模组 '%s':不能创建分支 '%s'" +msgstr "子模组 '%s':无法创建分支 '%s'" #: branch.c #, c-format @@ -2322,7 +2337,7 @@ #: builtin/add.c #, c-format msgid "cannot chmod %cx '%s'" -msgstr "不能 chmod %cx '%s'" +msgstr "无法 chmod %cx '%s'" #: builtin/add.c msgid "Unstaged changes after refreshing the index:" @@ -2330,7 +2345,7 @@ #: builtin/add.c msgid "could not read the index" -msgstr "不能读取索引" +msgstr "无法读取索引" #: builtin/add.c msgid "editing patch failed" @@ -2339,16 +2354,16 @@ #: builtin/add.c read-cache.c #, c-format msgid "could not stat '%s'" -msgstr "不能对 '%s' 调用 stat" +msgstr "无法对 '%s' 调用 stat" #: builtin/add.c msgid "empty patch. aborted" -msgstr "空补丁。异常终止" +msgstr "空补丁。已中止" #: builtin/add.c #, c-format msgid "could not apply '%s'" -msgstr "不能应用 '%s'" +msgstr "无法应用 '%s'" #: builtin/add.c msgid "The following paths are ignored by one of your .gitignore files:\n" @@ -2358,13 +2373,13 @@ #: builtin/prune-packed.c builtin/pull.c builtin/push.c builtin/remote.c #: builtin/rm.c builtin/send-pack.c builtin/sparse-checkout.c msgid "dry run" -msgstr "演习" +msgstr "试运行" #: builtin/add.c builtin/check-ignore.c builtin/commit.c #: builtin/count-objects.c builtin/fsck.c builtin/log.c builtin/mv.c #: builtin/read-tree.c builtin/refs.c msgid "be verbose" -msgstr "冗长输出" +msgstr "输出更详细" #: builtin/add.c msgid "interactive picking" @@ -2392,11 +2407,11 @@ #: builtin/add.c msgid "record only the fact that the path will be added later" -msgstr "只记录,该路径稍后再添加" +msgstr "只记录该路径稍后将被添加" #: builtin/add.c msgid "add changes from all tracked and untracked files" -msgstr "添加所有改变的已跟踪文件和未跟踪文件" +msgstr "添加所有已跟踪和未跟踪文件的变更" #: builtin/add.c msgid "ignore paths removed in the working tree (same as --no-all)" @@ -2408,11 +2423,11 @@ #: builtin/add.c msgid "just skip files which cannot be added because of errors" -msgstr "跳过因出错不能添加的文件" +msgstr "跳过因出错无法添加的文件" #: builtin/add.c msgid "check if - even missing - files are ignored in dry run" -msgstr "检查在演习模式下文件(即使不存在)是否被忽略" +msgstr "检查试运行时文件(即使不存在)是否被忽略" #: builtin/add.c builtin/mv.c builtin/rm.c msgid "allow updating entries outside of the sparse-checkout cone" @@ -2424,7 +2439,7 @@ #: builtin/add.c msgid "warn when adding an embedded repository" -msgstr "创建一个嵌入式仓库时给予警告" +msgstr "添加嵌入式仓库时给予警告" #: builtin/add.c #, c-format @@ -2443,7 +2458,7 @@ "\n" "See \"git help submodule\" for more information." msgstr "" -"您在当前仓库中添加了另一个Git仓库。克隆外层的仓库将不包含嵌入仓库的\n" +"您在当前仓库中添加了另一个 Git 仓库。外层仓库的克隆将不包含嵌入式仓库的\n" "内容,并且不知道该如何获取它。如果您要添加一个子模组,使用:\n" "\n" "\tgit submodule add <url> %s\n" @@ -2508,7 +2523,7 @@ #: builtin/am.c builtin/mailinfo.c mailinfo.c #, c-format msgid "bad action '%s' for '%s'" -msgstr "'%2$s' 的错误动作 '%1$s'" +msgstr "用于 '%2$s' 的动作 '%1$s' 无效" #: builtin/am.c builtin/blame.c builtin/fetch.c builtin/pack-objects.c #: builtin/pull.c builtin/revert.c diff-merges.c diff.c environment.c @@ -2520,16 +2535,16 @@ #: builtin/am.c builtin/commit.c builtin/merge.c sequencer.c #, c-format msgid "could not read '%s'" -msgstr "不能读取 '%s'" +msgstr "无法读取 '%s'" #: builtin/am.c msgid "could not parse author script" -msgstr "不能解析作者脚本" +msgstr "无法解析作者脚本" #: builtin/am.c builtin/replace.c commit.c sequencer.c #, c-format msgid "could not parse %s" -msgstr "不能解析 %s" +msgstr "无法解析 %s" #: builtin/am.c #, c-format @@ -2632,15 +2647,15 @@ #: builtin/am.c builtin/checkout.c builtin/clone.c commit-graph.c #, c-format msgid "unable to parse commit %s" -msgstr "不能解析提交 %s" +msgstr "无法解析提交 %s" #: builtin/am.c msgid "Repository lacks necessary blobs to fall back on 3-way merge." -msgstr "仓库缺乏必要的数据对象以进行三方合并。" +msgstr "仓库缺乏必要的数据对象,无法回落到三路合并。" #: builtin/am.c msgid "Using index info to reconstruct a base tree..." -msgstr "使用索引来重建一个(三方合并的)基础目录树..." +msgstr "使用索引来重建一个(三路合并的)基础目录树..." #: builtin/am.c msgid "" @@ -2652,7 +2667,7 @@ #: builtin/am.c msgid "Falling back to patching base and 3-way merge..." -msgstr "回落到基础版本上打补丁及进行三方合并..." +msgstr "回落到在基础版本上打补丁并进行三路合并..." #: builtin/am.c msgid "Failed to merge in the changes." @@ -2695,7 +2710,7 @@ #: builtin/am.c #, c-format msgid "Dirty index: cannot apply patches (dirty: %s)" -msgstr "脏索引:不能应用补丁(脏文件:%s)" +msgstr "脏索引:无法应用补丁(脏文件:%s)" #: builtin/am.c #, c-format @@ -2740,7 +2755,7 @@ "already introduced the same changes; you might want to skip this patch." msgstr "" "没有变更 —— 您是不是忘了执行 'git add'?\n" -"如果没有什么要添加到暂存区的,则很可能是其它提交已经引入了相同的变更。\n" +"如果没有什么要添加到暂存区的,则很可能是其他提交已经引入了相同的变更。\n" "您也许想要跳过这个补丁。" #: builtin/am.c @@ -2751,13 +2766,13 @@ "You might run `git rm` on a file to accept \"deleted by them\" for it." msgstr "" "在您的索引中仍存在未合并的路径。\n" -"您应该对已经冲突解决的每一个文件执行 'git add' 来标记已经完成。 \n" +"您应该对已经冲突解决的每一个文件执行 'git add' 来标记已经完成。\n" "您可以对 \"由他们删除\" 的文件执行 `git rm` 命令。" #: builtin/am.c builtin/reset.c #, c-format msgid "Could not parse object '%s'." -msgstr "不能解析对象 '%s'。" +msgstr "无法解析对象 '%s'。" #: builtin/am.c msgid "failed to clean index" @@ -2796,7 +2811,7 @@ #: builtin/am.c msgid "allow fall back on 3way merging if needed" -msgstr "如果必要,允许使用三方合并。" +msgstr "如有需要,允许回落到三路合并" #: builtin/am.c builtin/init-db.c builtin/prune-packed.c builtin/repack.c #: builtin/stash.c @@ -2859,7 +2874,7 @@ #: builtin/am.c msgid "override error message when patch failure occurs" -msgstr "打补丁失败时显示的错误信息" +msgstr "覆盖打补丁失败时的错误信息" #: builtin/am.c msgid "continue applying patches after resolving a conflict" @@ -2895,7 +2910,7 @@ #: builtin/am.c msgid "lie about committer date" -msgstr "将作者日期作为提交日期" +msgstr "伪造提交者日期" #: builtin/am.c msgid "use current timestamp for author date" @@ -2904,7 +2919,7 @@ #: builtin/am.c builtin/commit-tree.c builtin/commit.c builtin/merge.c #: builtin/pull.c builtin/rebase.c builtin/revert.c builtin/tag.c msgid "key-id" -msgstr "key-id" +msgstr "密钥 ID" #: builtin/am.c builtin/rebase.c msgid "GPG-sign commits" @@ -2941,7 +2956,7 @@ "Stray %s directory found.\n" "Use \"git am --abort\" to remove it." msgstr "" -"发现了错误的 %s 目录。\n" +"发现了残留的 %s 目录。\n" "使用 \"git am --abort\" 删除它。" #: builtin/am.c @@ -2958,7 +2973,7 @@ #: builtin/archive.c diagnose.c msgid "could not redirect output" -msgstr "不能重定向输出" +msgstr "无法重定向输出" #: builtin/archive.c msgid "git archive: expected ACK/NAK, got a flush packet" @@ -3030,17 +3045,17 @@ #: builtin/bisect.c #, c-format msgid "cannot open file '%s' in mode '%s'" -msgstr "不能以 '%2$s' 模式打开文件 '%1$s'" +msgstr "无法以 '%2$s' 模式打开文件 '%1$s'" #: builtin/bisect.c #, c-format msgid "could not write to file '%s'" -msgstr "不能写入文件 '%s'" +msgstr "无法写入文件 '%s'" #: builtin/bisect.c #, c-format msgid "cannot open file '%s' for reading" -msgstr "不能打开文件 '%s' 来读取" +msgstr "无法打开文件 '%s' 来读取" #: builtin/bisect.c #, c-format @@ -3050,12 +3065,12 @@ #: builtin/bisect.c #, c-format msgid "can't use the builtin command '%s' as a term" -msgstr "不能使用内置命令 '%s' 作为术语" +msgstr "无法使用内置命令 '%s' 作为术语" #: builtin/bisect.c #, c-format msgid "can't change the meaning of the term '%s'" -msgstr "不能修改术语 '%s' 的含义" +msgstr "无法修改术语 '%s' 的含义" #: builtin/bisect.c msgid "please use two different terms" @@ -3075,12 +3090,12 @@ #, c-format msgid "" "could not check out original HEAD '%s'. Try 'git bisect reset <commit>'." -msgstr "不能检出原始 HEAD '%s'。尝试 'git bisect reset <提交>'。" +msgstr "无法检出原始 HEAD '%s'。尝试 'git bisect reset <提交>'。" #: builtin/bisect.c #, c-format msgid "Bad bisect_write argument: %s" -msgstr "坏的 bisect_write 参数:%s" +msgstr "错误的 bisect_write 参数:%s" #: builtin/bisect.c #, c-format @@ -3128,7 +3143,7 @@ #. #: builtin/bisect.c msgid "Are you sure [Y/n]? " -msgstr "您确认么[Y/n]? " +msgstr "您确认吗 [Y/n]? " #: builtin/bisect.c msgid "status: waiting for both good and bad commits\n" @@ -3186,7 +3201,7 @@ #: builtin/bisect.c msgid "bad HEAD - I need a HEAD" -msgstr "坏的 HEAD - 我需要一个 HEAD" +msgstr "无效的 HEAD - 需要一个 HEAD" #: builtin/bisect.c #, c-format @@ -3195,7 +3210,7 @@ #: builtin/bisect.c msgid "bad HEAD - strange symbolic ref" -msgstr "坏的 HEAD - 奇怪的符号引用" +msgstr "无效的 HEAD - 奇怪的符号引用" #: builtin/bisect.c #, c-format @@ -3212,7 +3227,7 @@ #. #: builtin/bisect.c msgid "Do you want me to do it for you [Y/n]? " -msgstr "您想让我为您这样做么[Y/n]? " +msgstr "您想让我为您这样做吗[Y/n]? " #: builtin/bisect.c msgid "Please call `--bisect-state` with at least one argument" @@ -3226,12 +3241,12 @@ #: builtin/bisect.c #, c-format msgid "Bad rev input: %s" -msgstr "坏的版本输入:%s" +msgstr "错误的版本输入:%s" #: builtin/bisect.c #, c-format msgid "Bad rev input (not a commit): %s" -msgstr "坏的版本输入(不是提交):%s" +msgstr "错误的版本输入(不是提交):%s" #: builtin/bisect.c msgid "We are not bisecting." @@ -3240,12 +3255,12 @@ #: builtin/bisect.c #, c-format msgid "'%s'?? what are you talking about?" -msgstr "'%s'?? 您在说什么?" +msgstr "'%s'??您在说什么?" #: builtin/bisect.c #, c-format msgid "cannot read file '%s' for replaying" -msgstr "不能读取文件 '%s' 来重放" +msgstr "无法读取文件 '%s' 来重放" #: builtin/bisect.c #, c-format @@ -3278,7 +3293,7 @@ #: builtin/bisect.c msgid "bisect run cannot continue any more" -msgstr "二分查找不能继续运行" +msgstr "二分查找无法继续运行" #: builtin/bisect.c msgid "bisect run success" @@ -3341,16 +3356,29 @@ #: builtin/blame.c #, c-format msgid "expecting a color: %s" -msgstr "期望一个颜色:%s" +msgstr "期望是颜色:%s" #: builtin/blame.c msgid "must end with a color" msgstr "必须以一个颜色结尾" +#: builtin/blame.c diff.c merge-ort.c transport.c +#, c-format +msgid "unknown value for config '%s': %s" +msgstr "配置 '%s' 未知的取值:%s" + +#: builtin/blame.c builtin/merge-file.c diff.c +msgid "" +"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and " +"\"histogram\"" +msgstr "" +"选项 diff-algorithm 接受参数 \"myers\"、\"minimal\"、\"patience\" 和 " +"\"histogram\"" + #: builtin/blame.c #, c-format msgid "cannot find revision %s to ignore" -msgstr "不能找到要忽略的版本 %s" +msgstr "无法找到要忽略的版本 %s" #: builtin/blame.c msgid "show blame entries as we find them, incrementally" @@ -3392,7 +3420,7 @@ #: builtin/blame.c msgid "show porcelain format with per-line commit information" -msgstr "为每一行显示机器适用的提交信息" +msgstr "以 porcelain 格式显示每行的提交信息" #: builtin/blame.c msgid "use the same output mode as git-annotate (Default: off)" @@ -3418,6 +3446,14 @@ msgid "ignore whitespace differences" msgstr "忽略空白差异" +#: builtin/blame.c builtin/merge-file.c diff.c +msgid "<algorithm>" +msgstr "<算法>" + +#: builtin/blame.c builtin/merge-file.c diff.c +msgid "choose a diff algorithm" +msgstr "选择一个差异算法" + #: builtin/blame.c builtin/clone.c builtin/log.c msgid "rev" msgstr "版本" @@ -3432,15 +3468,15 @@ #: builtin/blame.c msgid "color redundant metadata from previous line differently" -msgstr "使用颜色间隔输出与前一行不同的重复元信息" +msgstr "用不同颜色标示与前一行重复的元信息" #: builtin/blame.c msgid "color lines by age" msgstr "依据时间着色" #: builtin/blame.c -msgid "spend extra cycles to find better match" -msgstr "花费额外的循环来找到更好的匹配" +msgid "spend extra cycles to find a better match" +msgstr "花费更多计算来找到更好的匹配" #: builtin/blame.c msgid "use revisions from <file> instead of calling git-rev-list" @@ -3448,7 +3484,7 @@ #: builtin/blame.c msgid "use <file>'s contents as the final image" -msgstr "使用 <文件> 的内容作为最终的镜像" +msgstr "使用 <文件> 的内容作为最终结果" #: builtin/blame.c msgid "score" @@ -3472,7 +3508,7 @@ #: builtin/blame.c msgid "--progress can't be used with --incremental or porcelain formats" -msgstr "--progress 不能和 --incremental 或机器内部格式一起使用" +msgstr "--progress 不能和 --incremental 或 porcelain 格式一起使用" #. TRANSLATORS: This string is used to tell us the #. maximum display width for a relative timestamp in @@ -3521,7 +3557,7 @@ #: builtin/branch.c msgid "git branch [<options>] (-c | -C) [<old-branch>] <new-branch>" -msgstr "git branch [<选项>] (-c | -C) [<老分支>] <新分支>" +msgstr "git branch [<选项>] (-c | -C) [<旧分支>] <新分支>" #: builtin/branch.c msgid "git branch [<options>] [-r | -a] [--points-at]" @@ -3577,7 +3613,7 @@ #: builtin/branch.c #, c-format msgid "cannot delete branch '%s' used by worktree at '%s'" -msgstr "无法强制更新被工作区 '%2$s' 所使用的分支 '%1$s'" +msgstr "无法删除被工作区 '%2$s' 使用的分支 '%1$s'" #: builtin/branch.c #, c-format @@ -3596,7 +3632,7 @@ #: builtin/branch.c #, c-format msgid "branch '%s' not found" -msgstr "分支 '%s' 未发现" +msgstr "分支 '%s' 未找到" #: builtin/branch.c #, c-format @@ -3610,11 +3646,11 @@ #: builtin/branch.c builtin/tag.c msgid "unable to parse format string" -msgstr "不能解析格式化字符串" +msgstr "无法解析格式化字符串" #: builtin/branch.c msgid "could not resolve HEAD" -msgstr "不能解析 HEAD 提交" +msgstr "无法解析 HEAD" #: builtin/branch.c #, c-format @@ -3629,12 +3665,12 @@ #: builtin/branch.c #, c-format msgid "branch %s is being bisected at %s" -msgstr "分支 %s 正被二分查找于 %s" +msgstr "分支 %s 正在 %s 处进行二分查找" #: builtin/branch.c #, c-format msgid "HEAD of working tree %s is not updated" -msgstr "工作区 %s 的 HEAD 指向没有被更新" +msgstr "工作区 %s 的 HEAD 未更新" #: builtin/branch.c #, c-format @@ -3691,7 +3727,7 @@ msgstr "" "请编辑分支的描述\n" " %s\n" -"以 '%s' 开头的行将被过滤。\n" +"以 '%s' 开头的行将被移除。\n" #: builtin/branch.c msgid "Generic options" @@ -3743,7 +3779,7 @@ #: builtin/branch.c msgid "Specific git-branch actions:" -msgstr "具体的 git-branch 动作:" +msgstr "具体的 git-branch 操作:" #: builtin/branch.c msgid "list both remote-tracking and local branches" @@ -3791,7 +3827,7 @@ #: builtin/branch.c msgid "edit the description for the branch" -msgstr "标记分支的描述" +msgstr "编辑分支的描述" #: builtin/branch.c msgid "force creation, move/rename, deletion" @@ -3819,7 +3855,7 @@ #: builtin/branch.c builtin/for-each-ref.c builtin/tag.c msgid "sorting and filtering are case insensitive" -msgstr "排序和过滤属于大小写不敏感" +msgstr "排序和过滤不区分大小写" #: builtin/branch.c builtin/ls-files.c msgid "recurse through submodules" @@ -3836,7 +3872,7 @@ #: builtin/branch.c builtin/clone.c msgid "HEAD not found below refs/heads!" -msgstr "HEAD 没有位于 /refs/heads 之下!" +msgstr "HEAD 没有位于 refs/heads 之下!" #: builtin/branch.c msgid "" @@ -3855,11 +3891,11 @@ #: builtin/branch.c msgid "cannot give description to detached HEAD" -msgstr "不能向分离头指针提供描述" +msgstr "无法向分离头指针提供描述" #: builtin/branch.c msgid "cannot edit description of more than one branch" -msgstr "不能为一个以上的分支编辑描述" +msgstr "无法为一个以上的分支编辑描述" #: builtin/branch.c msgid "cannot copy the current branch while not on any" @@ -3903,7 +3939,7 @@ #: builtin/branch.c msgid "could not unset upstream of HEAD when it does not point to any branch" -msgstr "无法取消 HEAD 的上游设置因为它没有指向一个分支" +msgstr "无法取消 HEAD 的上游设置,因为它没有指向任何分支" #: builtin/branch.c #, c-format @@ -3946,9 +3982,9 @@ " [(-s | --suffix) <format> | --no-suffix]\n" " [--diagnose[=<mode>]]" msgstr "" -"git bugreport [-o | --output-directory <文件>]\n" +"git bugreport [-o | --output-directory <路径>]\n" " [(-s | --suffix) <格式> | --no-suffix]\n" -" [--diagnose[=<模式>]" +" [--diagnose[=<模式>]]" #: builtin/bugreport.c msgid "" @@ -3979,13 +4015,13 @@ "\n" "您所期望的与实际发生的有什么不同?\n" "\n" -"您想要补充的其它内容:\n" +"您想要补充的其他内容:\n" "\n" "请检查下面错误报告中余下的内容。\n" "您可以删除任何您不想共享的内容。\n" #: builtin/bugreport.c builtin/commit.c builtin/fast-export.c -#: builtin/pack-objects.c builtin/rebase.c parse-options.h +#: builtin/pack-objects.c builtin/rebase.c builtin/replay.c parse-options.h msgid "mode" msgstr "模式" @@ -4010,12 +4046,12 @@ #: builtin/bugreport.c builtin/diagnose.c #, c-format msgid "could not create leading directories for '%s'" -msgstr "不能为 '%s' 创建先导目录" +msgstr "无法为 '%s' 创建上级目录" #: builtin/bugreport.c builtin/diagnose.c #, c-format msgid "unable to create diagnostics archive %s" -msgstr "不能创建诊断归档包 %s" +msgstr "无法创建诊断归档包 %s" #: builtin/bugreport.c msgid "System Info" @@ -4061,11 +4097,11 @@ #: builtin/bundle.c builtin/pack-objects.c msgid "do not show progress meter" -msgstr "不显示进度表" +msgstr "不显示进度条" #: builtin/bundle.c builtin/pack-objects.c msgid "show progress meter" -msgstr "显示进度表" +msgstr "显示进度条" #: builtin/bundle.c msgid "historical; same as --progress" @@ -4077,28 +4113,28 @@ #: builtin/bundle.c msgid "specify bundle format version" -msgstr "指定归档包的格式版本" +msgstr "指定捆绑包的格式版本" #: builtin/bundle.c msgid "Need a repository to create a bundle." -msgstr "需要一个仓库来创建归档包。" +msgstr "需要一个仓库来创建捆绑包。" #: builtin/bundle.c msgid "do not show bundle details" -msgstr "不显示归档包的细节" +msgstr "不显示捆绑包的细节" #: builtin/bundle.c bundle.c msgid "need a repository to verify a bundle" -msgstr "需要一个仓库来校验一个归档包" +msgstr "需要一个仓库来校验一个捆绑包" #: builtin/bundle.c #, c-format msgid "%s is okay\n" -msgstr "%s 可以\n" +msgstr "%s 没问题\n" #: builtin/bundle.c msgid "Need a repository to unbundle." -msgstr "需要一个仓库来解开归档包。" +msgstr "需要一个仓库来解开捆绑包。" #: builtin/bundle.c msgid "Unbundling objects" @@ -4107,7 +4143,7 @@ #: builtin/cat-file.c #, c-format msgid "cannot read object %s '%s'" -msgstr "不能读取对象 %s '%s'" +msgstr "无法读取对象 %s '%s'" #: builtin/cat-file.c msgid "flush is only for --buffer mode" @@ -4178,7 +4214,7 @@ #: builtin/cat-file.c msgid "Emit [broken] object attributes" -msgstr "输出 [坏的] 对象属性" +msgstr "输出 [损坏的] 对象属性" #: builtin/cat-file.c msgid "show object type (one of 'blob', 'tree', 'commit', 'tag', ...)" @@ -4206,11 +4242,11 @@ #: builtin/cat-file.c msgid "stdin is NUL-terminated" -msgstr "标准输入以 NUL 字符分隔" +msgstr "标准输入以 NUL 字符终止" #: builtin/cat-file.c msgid "stdin and stdout is NUL-terminated" -msgstr "标准输入和标准输出以 NUL 字符分隔" +msgstr "标准输入和标准输出以 NUL 字符终止" #: builtin/cat-file.c msgid "read commands from stdin" @@ -4256,7 +4292,7 @@ #: builtin/cat-file.c msgid "use a <path> for (--textconv | --filters); Not with 'batch'" -msgstr "(--textconv | --filters) 使用 <路径>;而不是 'batch'" +msgstr "为 (--textconv | --filters) 使用 <路径>;不能与 'batch' 同用" #: builtin/cat-file.c msgid "objects filter only supported in batch mode" @@ -4421,7 +4457,7 @@ #: builtin/checkout-index.c msgid "stage should be between 1 and 3 or all" -msgstr "索引值应该取值 1 到 3 或者 all" +msgstr "暂存区编号应为 1 到 3 或 all" #: builtin/checkout-index.c msgid "check out all files in the index" @@ -4437,7 +4473,7 @@ #: builtin/checkout-index.c msgid "no warning for existing files and files not in index" -msgstr "存在或不在索引中的文件都没有警告" +msgstr "对已存在文件和不在索引中的文件不发出警告" #: builtin/checkout-index.c msgid "don't checkout new files" @@ -4498,7 +4534,7 @@ #: builtin/checkout.c #, c-format msgid "path '%s': cannot merge" -msgstr "path '%s':无法合并" +msgstr "路径 '%s':无法合并" #: builtin/checkout.c #, c-format @@ -4534,7 +4570,7 @@ #: builtin/checkout.c #, c-format msgid "Cannot update paths and switch to branch '%s' at the same time." -msgstr "不能同时更新路径并切换到分支'%s'。" +msgstr "不能同时更新路径并切换到分支 '%s'。" #: builtin/checkout.c #, c-format @@ -4577,13 +4613,13 @@ "cannot continue with staged changes in the following files:\n" "%s" msgstr "" -"不能继续,下列文件有暂存的修改:\n" +"无法继续,下列文件有暂存的修改:\n" "%s" #: builtin/checkout.c #, c-format msgid "Can not do reflog for '%s': %s\n" -msgstr "不能对 '%s' 执行 reflog 操作:%s\n" +msgstr "无法对 '%s' 执行 reflog 操作:%s\n" #: builtin/checkout.c msgid "HEAD is now at" @@ -4591,7 +4627,7 @@ #: builtin/checkout.c builtin/clone.c msgid "unable to update HEAD" -msgstr "不能更新 HEAD" +msgstr "无法更新 HEAD" #: builtin/checkout.c #, c-format @@ -4622,7 +4658,7 @@ #: builtin/checkout.c #, c-format msgid " ... and %d more.\n" -msgstr " ... 及其它 %d 个。\n" +msgstr " ... 及其他 %d 个。\n" #: builtin/checkout.c #, c-format @@ -4709,8 +4745,8 @@ "\n" " git checkout --track origin/<名称>\n" "\n" -"如果您总是喜欢使用模糊的简短分支名 <名称>,而不喜欢如 'origin' 的远程\n" -"名称,可以在配置中设置 checkout.defaultRemote=origin。" +"如果您希望在检出模糊的简短分支名 <名称> 时总是偏好某个远程(如 'origin'),\n" +"可以在配置中设置 checkout.defaultRemote=origin。" #: builtin/checkout.c #, c-format @@ -4759,7 +4795,7 @@ #: builtin/checkout.c msgid "" "If you want to detach HEAD at the commit, try again with the --detach option." -msgstr "如果你在本提交分离头指针,使用 --detach 选项重试。" +msgstr "如果您想在该提交处分离 HEAD,请使用 --detach 选项重试。" #: builtin/checkout.c msgid "" @@ -4832,7 +4868,7 @@ #: builtin/checkout.c #, c-format msgid "Cannot switch branch to a non-commit '%s'" -msgstr "不能切换分支到一个非提交 '%s'" +msgstr "无法切换分支到一个非提交 '%s'" #: builtin/checkout.c msgid "missing branch or commit argument" @@ -4845,7 +4881,7 @@ #: builtin/checkout.c msgid "perform a 3-way merge with the new branch" -msgstr "和新的分支执行三方合并" +msgstr "和新的分支执行三路合并" #: builtin/checkout.c builtin/log.c builtin/range-diff.c parse-options.h msgid "style" @@ -4869,7 +4905,7 @@ #: builtin/checkout.c msgid "new unborn branch" -msgstr "新的未诞生的分支" +msgstr "新的尚未诞生分支" #: builtin/checkout.c builtin/merge.c msgid "update ignored files (default)" @@ -4889,12 +4925,12 @@ #: builtin/checkout.c msgid "do not limit pathspecs to sparse entries only" -msgstr "对路径不做稀疏检出的限制" +msgstr "对路径规格不只限于稀疏条目" #: builtin/checkout.c #, c-format msgid "options '-%c', '-%c', and '%s' cannot be used together" -msgstr "选项 '-%c'、'-%c' 和 ‘%s' 不能同时使用" +msgstr "选项 '-%c'、'-%c' 和 '%s' 不能同时使用" #: builtin/checkout.c msgid "--track needs a branch name" @@ -4905,10 +4941,10 @@ msgid "missing branch name; try -%c" msgstr "缺少分支名,尝试 -%c" -#: builtin/checkout.c +#: builtin/checkout.c sequencer.c #, c-format -msgid "could not resolve %s" -msgstr "无法解析 %s" +msgid "could not resolve '%s'" +msgstr "无法解析 '%s'" #: builtin/checkout.c msgid "invalid path specification" @@ -4917,12 +4953,12 @@ #: builtin/checkout.c #, c-format msgid "'%s' is not a commit and a branch '%s' cannot be created from it" -msgstr "'%s' 不是一个提交,不能基于它创建分支 '%s'" +msgstr "'%s' 不是一个提交,无法基于它创建分支 '%s'" #: builtin/checkout.c #, c-format msgid "git checkout: --detach does not take a path argument '%s'" -msgstr "git checkout:--detach 不能接收路径参数 '%s'" +msgstr "git checkout:--detach 不接收路径参数 '%s'" #: builtin/checkout.c msgid "" @@ -4978,7 +5014,7 @@ #: builtin/checkout.c msgid "which tree-ish to checkout from" -msgstr "要检出哪一个树" +msgstr "要从哪个树对象检出" #: builtin/checkout.c msgid "restore the index" @@ -5001,7 +5037,7 @@ "git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] " "[<pathspec>...]" msgstr "" -"git clean [-d] [-f] [-i] [-n] [-q] [-e <模式>] [-x | -X] [--] <路径规格>..." +"git clean [-d] [-f] [-i] [-n] [-q] [-e <模式>] [-x | -X] [--] [<路径规格>...]" #: builtin/clean.c builtin/sparse-checkout.c #, c-format @@ -5016,22 +5052,22 @@ #: builtin/clean.c #, c-format msgid "Skipping repository %s\n" -msgstr "忽略仓库 %s\n" +msgstr "跳过仓库 %s\n" #: builtin/clean.c #, c-format msgid "Would skip repository %s\n" -msgstr "将忽略仓库 %s\n" +msgstr "将跳过仓库 %s\n" #: builtin/clean.c midx.c #, c-format msgid "failed to remove %s" -msgstr "无法删除 '%s'" +msgstr "无法删除 %s" #: builtin/clean.c #, c-format msgid "could not lstat %s\n" -msgstr "不能对 %s 调用 lstat\n" +msgstr "无法对 %s 调用 lstat\n" #: builtin/clean.c msgid "Refusing to remove current working directory\n" @@ -5083,7 +5119,7 @@ #: builtin/clean.c #, c-format msgid "Input ignore patterns>> " -msgstr "输入模版以排除条目>> " +msgstr "输入忽略模式>> " #: builtin/clean.c #, c-format @@ -5111,7 +5147,7 @@ "? - help for prompt selection" msgstr "" "clean - 开始清理\n" -"filter by pattern - 通过模版排除要删除的条目\n" +"filter by pattern - 通过模式排除要删除的条目\n" "select by numbers - 通过数字选择要删除的条目\n" "ask each - 针对删除逐一询问(就像 \"rm -i\")\n" "quit - 停止删除并退出\n" @@ -5169,7 +5205,7 @@ #: builtin/clone.c #, c-format msgid "info: Could not add alternate for '%s': %s\n" -msgstr "info: 不能为 '%s' 添加一个备用:%s\n" +msgstr "info:无法为 '%s' 添加一个备用:%s\n" #: builtin/clone.c builtin/diff.c builtin/rm.c grep.c setup.c #, c-format @@ -5248,7 +5284,7 @@ #: builtin/clone.c #, c-format msgid "unable to update %s" -msgstr "不能更新 %s" +msgstr "无法更新 %s" #: builtin/clone.c msgid "failed to initialize sparse-checkout" @@ -5260,7 +5296,7 @@ #: builtin/clone.c msgid "unable to checkout working tree" -msgstr "不能检出工作区" +msgstr "无法检出工作区" #: builtin/clone.c msgid "unable to write parameters to config file" @@ -5320,7 +5356,7 @@ #: builtin/clone.c builtin/init-db.c msgid "directory from which templates will be used" -msgstr "模板目录将被使用" +msgstr "将从该目录使用模板" #: builtin/clone.c builtin/submodule--helper.c msgid "reference repository" @@ -5370,7 +5406,7 @@ #: builtin/clone.c builtin/fetch.c builtin/pull.c msgid "deepen history of shallow clone, excluding ref" -msgstr "深化浅克隆的历史,除了给定的引用" +msgstr "深化浅克隆的历史,不包含指定引用" #: builtin/clone.c builtin/submodule--helper.c msgid "clone only one branch, HEAD or --branch" @@ -5382,15 +5418,15 @@ #: builtin/clone.c msgid "any cloned submodules will be shallow" -msgstr "子模组将以浅下载模式克隆" +msgstr "所有被克隆的子模组都将是浅克隆" #: builtin/clone.c builtin/init-db.c msgid "gitdir" -msgstr "git目录" +msgstr "git 目录" #: builtin/clone.c builtin/init-db.c msgid "separate git dir from working tree" -msgstr "git目录和工作区分离" +msgstr "将 git 目录与工作区分离" #: builtin/clone.c builtin/init-db.c builtin/submodule--helper.c msgid "specify the reference format to use" @@ -5398,7 +5434,7 @@ #: builtin/clone.c msgid "key=value" -msgstr "key=value" +msgstr "键=值" #: builtin/clone.c msgid "set config inside the new repository" @@ -5407,7 +5443,7 @@ #: builtin/clone.c builtin/fetch.c builtin/ls-remote.c builtin/pull.c #: builtin/push.c builtin/send-pack.c msgid "server-specific" -msgstr "server-specific" +msgstr "服务端特定" #: builtin/clone.c builtin/fetch.c builtin/ls-remote.c builtin/pull.c #: builtin/push.c builtin/send-pack.c @@ -5424,15 +5460,15 @@ #: builtin/clone.c msgid "initialize sparse-checkout file to include only files at root" -msgstr "初始化稀疏检出文件,只包含根目录文件" +msgstr "初始化稀疏检出文件,只包含根目录中的文件" #: builtin/clone.c msgid "uri" -msgstr "uri" +msgstr "URI" #: builtin/clone.c msgid "a URI for downloading bundles before fetching from origin remote" -msgstr "用于在从 origin 远程获取之前下载归档包的 URI" +msgstr "用于在从 origin 远程获取之前下载捆绑包的 URI" #: builtin/clone.c msgid "git clone [<options>] [--] <repo> [<dir>]" @@ -5480,12 +5516,12 @@ #: builtin/clone.c builtin/difftool.c builtin/log.c builtin/worktree.c #, c-format msgid "could not create leading directories of '%s'" -msgstr "不能为 '%s' 创建先导目录" +msgstr "无法为 '%s' 创建上级目录" #: builtin/clone.c #, c-format msgid "could not create work tree dir '%s'" -msgstr "不能创建工作区目录 '%s'" +msgstr "无法创建工作区目录 '%s'" #: builtin/clone.c #, c-format @@ -5538,20 +5574,20 @@ #: builtin/clone.c msgid "cannot clone from filtered bundle" -msgstr "无法从经过过滤的归档包克隆" +msgstr "无法从经过过滤的捆绑包克隆" #: builtin/clone.c msgid "failed to initialize the repo, skipping bundle URI" -msgstr "无法初始化仓库,跳过归档包 URI" +msgstr "无法初始化仓库,跳过捆绑包 URI" #: builtin/clone.c #, c-format msgid "failed to fetch objects from bundle URI '%s'" -msgstr "无法从归档包 URI '%s' 获取对象" +msgstr "无法从捆绑包 URI '%s' 获取对象" #: builtin/clone.c msgid "failed to fetch advertised bundles" -msgstr "无法获取公布的归档包" +msgstr "无法获取公布的捆绑包" #: builtin/clone.c msgid "remote transport reported error" @@ -5560,7 +5596,7 @@ #: builtin/clone.c #, c-format msgid "Remote branch %s not found in upstream %s" -msgstr "远程分支 %s 在上游 %s 未发现" +msgstr "远程分支 %s 在上游 %s 未找到" #: builtin/clone.c #, c-format @@ -5625,7 +5661,7 @@ msgstr "" "git commit-graph write [--object-dir <目录>] [--append]\n" " [--split[=<策略>]] [--reachable | --stdin-packs | --" -"stdin-commits] \n" +"stdin-commits]\n" " [--changed-paths] [--[no-]max-new-filters <n>] [--" "[no-]progress]\n" " <切分选项>" @@ -5641,7 +5677,7 @@ #: builtin/commit-graph.c msgid "if the commit-graph is split, only verify the tip file" -msgstr "如果提交图被拆分,只验证头一个文件" +msgstr "如果提交图被拆分,只验证顶端文件" #: builtin/commit-graph.c #, c-format @@ -5687,7 +5723,7 @@ #: builtin/commit-graph.c msgid "include all commits already in the commit-graph file" -msgstr "包含 commit-graph 文件中已有所有提交" +msgstr "包含 commit-graph 文件中已有的所有提交" #: builtin/commit-graph.c msgid "enable computation for changed paths" @@ -5730,7 +5766,7 @@ "git commit-tree [(-p <parent>)...] [-S[<keyid>]] [(-m <message>)...]\n" " [(-F <file>)...] <tree>" msgstr "" -"git commit-tree [(-p <父提交>)...] [-S[<私钥 ID>]] [(-m <消息>)...]\n" +"git commit-tree [(-p <父提交>)...] [-S[<密钥 ID>]] [(-m <消息>)...]\n" " [(-F <文件>)...] <树>" #: builtin/commit-tree.c @@ -5777,7 +5813,7 @@ #: builtin/commit-tree.c builtin/commit.c builtin/merge.c builtin/pull.c #: builtin/revert.c msgid "GPG sign commit" -msgstr "GPG 提交签名" +msgstr "GPG 签名提交" #: builtin/commit-tree.c msgid "must give exactly one tree" @@ -5806,7 +5842,7 @@ " [--allow-empty-message] [--no-verify] [-e] [--author=<作者>]\n" " [--date=<日期>] [--cleanup=<模式>] [--[no-]status]\n" " [-i | -o] [--pathspec-from-file=<文件> [--pathspec-file-nul]]\n" -" [(--trailer <键>[(=|:)<值>])...] [-S[<私钥 ID>]]\n" +" [(--trailer <键>[(=|:)<值>])...] [-S[<密钥 ID>]]\n" " [--] [<路径规格>...]" #: builtin/commit.c @@ -5819,8 +5855,8 @@ "it empty. You can repeat your command with --allow-empty, or you can\n" "remove the commit entirely with \"git reset HEAD^\".\n" msgstr "" -"您要修补最近的提交,但这么做会让它成为空提交。您可以重复您的命令并带上\n" -"--allow-empty 选项,或者您可用命令 \"git reset HEAD^\" 整个删除该提交。\n" +"您要修订最近的提交,但这么做会让它成为空提交。您可以重复您的命令并带上\n" +"--allow-empty 选项,或者您可用命令 \"git reset HEAD^\" 彻底删除该提交。\n" #: builtin/commit.c msgid "" @@ -5875,11 +5911,11 @@ #: builtin/commit.c msgid "No paths with --include/--only does not make sense." -msgstr "参数 --include/--only 不跟路径没有意义。" +msgstr "在未指定路径时使用 --include/--only 没有意义。" #: builtin/commit.c msgid "unable to create temporary index" -msgstr "不能创建临时索引" +msgstr "无法创建临时索引" #: builtin/commit.c msgid "interactive add failed" @@ -5891,7 +5927,7 @@ #: builtin/commit.c msgid "Failed to update main cache tree" -msgstr "不能更新树的主缓存" +msgstr "无法更新主缓存树" #: builtin/commit.c msgid "cannot do a partial commit during a merge." @@ -5941,7 +5977,7 @@ #: builtin/commit.c #, c-format msgid "could not lookup commit '%s'" -msgstr "不能查询提交 '%s'" +msgstr "无法查询提交 '%s'" #: builtin/commit.c builtin/shortlog.c #, c-format @@ -5950,12 +5986,12 @@ #: builtin/commit.c msgid "could not read log from standard input" -msgstr "不能从标准输入中读取日志信息" +msgstr "无法从标准输入中读取日志信息" #: builtin/commit.c #, c-format msgid "could not read log file '%s'" -msgstr "不能读取日志文件 '%s'" +msgstr "无法读取日志文件 '%s'" #: builtin/commit.c #, c-format @@ -5964,11 +6000,11 @@ #: builtin/commit.c msgid "could not read SQUASH_MSG" -msgstr "不能读取 SQUASH_MSG" +msgstr "无法读取 SQUASH_MSG" #: builtin/commit.c msgid "could not read MERGE_MSG" -msgstr "不能读取 MERGE_MSG" +msgstr "无法读取 MERGE_MSG" #: builtin/commit.c bundle.c rerere.c sequencer.c #, c-format @@ -5977,7 +6013,7 @@ #: builtin/commit.c msgid "could not write commit template" -msgstr "不能写提交模版" +msgstr "无法写提交模板" #: builtin/commit.c #, c-format @@ -6092,11 +6128,11 @@ #: builtin/commit.c msgid "You are in the middle of a merge -- cannot reword." -msgstr "您正处于一个合并过程中 -- 无法改写说明。" +msgstr "您正处于一个合并过程中 -- 不能改写说明。" #: builtin/commit.c msgid "You are in the middle of a cherry-pick -- cannot reword." -msgstr "您正处于一个拣选过程中 -- 无法改写说明。" +msgstr "您正处于一个拣选过程中 -- 不能改写说明。" #: builtin/commit.c #, c-format @@ -6110,19 +6146,19 @@ #: builtin/commit.c msgid "You have nothing to amend." -msgstr "您没有可修补的提交。" +msgstr "您没有可修订的提交。" #: builtin/commit.c msgid "You are in the middle of a merge -- cannot amend." -msgstr "您正处于一个合并过程中 -- 无法修补提交。" +msgstr "您正处于一个合并过程中 -- 不能修订提交。" #: builtin/commit.c msgid "You are in the middle of a cherry-pick -- cannot amend." -msgstr "您正处于一个拣选过程中 -- 无法修补提交。" +msgstr "您正处于一个拣选过程中 -- 不能修订提交。" #: builtin/commit.c msgid "You are in the middle of a rebase -- cannot amend." -msgstr "您正处于一个变基过程中 -- 无法修补提交。" +msgstr "您正处于一个变基过程中 -- 不能修订提交。" #: builtin/commit.c msgid "--reset-author can be used only with -C, -c or --amend." @@ -6136,7 +6172,7 @@ #: builtin/commit.c #, c-format msgid "paths '%s ...' with -a does not make sense" -msgstr "路径 '%s ...' 和 -a 选项同时使用没有意义" +msgstr "路径 '%s ...' 和 -a 选项同时使用没有意义" #: builtin/commit.c msgid "show status concisely" @@ -6148,7 +6184,7 @@ #: builtin/commit.c msgid "show stash information" -msgstr "显示贮藏区信息" +msgstr "显示储藏信息" #: builtin/commit.c msgid "compute full ahead/behind values" @@ -6202,7 +6238,7 @@ #: builtin/commit.c msgid "detect renames, optionally set similarity index" -msgstr "检测重命名,可以设置索引相似度" +msgstr "检测重命名,可设置相似度索引" #: builtin/commit.c msgid "Unsupported combination of ignored and untracked-files arguments" @@ -6262,7 +6298,7 @@ #: builtin/commit.c msgid "" "use autosquash formatted message to fixup or amend/reword specified commit" -msgstr "使用自动挤压格式的提交说明对指定的提交进行修正、修补或改写说明" +msgstr "使用自动挤压格式的提交说明对指定的提交进行修正、修订或改写说明" #: builtin/commit.c msgid "use autosquash formatted message to squash specified commit" @@ -6339,7 +6375,7 @@ #: builtin/commit.c msgid "ok to record an empty change" -msgstr "允许一个空提交" +msgstr "允许记录空变更" #: builtin/commit.c msgid "ok to record a change with an empty message" @@ -6347,7 +6383,7 @@ #: builtin/commit.c sequencer.c msgid "could not parse HEAD commit" -msgstr "不能解析 HEAD 提交" +msgstr "无法解析 HEAD 提交" #: builtin/commit.c #, c-format @@ -6356,22 +6392,22 @@ #: builtin/commit.c msgid "could not read MERGE_MODE" -msgstr "不能读取 MERGE_MODE" +msgstr "无法读取 MERGE_MODE" #: builtin/commit.c #, c-format msgid "could not read commit message: %s" -msgstr "不能读取提交说明:%s" +msgstr "无法读取提交说明:%s" #: builtin/commit.c #, c-format msgid "Aborting commit due to empty commit message.\n" -msgstr "终止提交因为提交说明为空。\n" +msgstr "由于提交说明为空,终止提交。\n" #: builtin/commit.c #, c-format msgid "Aborting commit; you did not edit the message.\n" -msgstr "终止提交;您未更改来自模版的提交说明。\n" +msgstr "终止提交;您未更改来自模板的提交说明。\n" #: builtin/commit.c #, c-format @@ -6522,7 +6558,7 @@ #: builtin/config.c msgid "terminate values with NUL byte" -msgstr "终止值是 NUL 字节" +msgstr "以 NUL 字节终止值" #: builtin/config.c msgid "show variable names only" @@ -6547,7 +6583,7 @@ #: builtin/config.c msgid "only one type at a time" -msgstr "一次只能一个类型" +msgstr "一次只能指定一个类型" #: builtin/config.c #, c-format @@ -6557,7 +6593,7 @@ #: builtin/config.c #, c-format msgid "wrong number of arguments, should be from %d to %d" -msgstr "错误的参数个数,应该为从 %d 个到 %d 个" +msgstr "错误的参数个数,应为 %d 到 %d 个" #: builtin/config.c #, c-format @@ -6585,7 +6621,7 @@ #: builtin/config.c msgid "not in a git directory" -msgstr "不在 git 仓库中" +msgstr "不在 git 目录中" #: builtin/config.c msgid "writing to stdin is not supported" @@ -6604,9 +6640,9 @@ "#\tname = %s\n" "#\temail = %s\n" msgstr "" -"# This is Git's per-user configuration file.\n" +"# 这是 Git 的用户级配置文件。\n" "[user]\n" -"# Please adapt and uncomment the following lines:\n" +"# 请根据需要修改并取消注释以下行:\n" "#\tname = %s\n" "#\temail = %s\n" @@ -6641,7 +6677,7 @@ #: builtin/config.c msgid "Other" -msgstr "其它" +msgstr "其他" #: builtin/config.c msgid "respect include directives on lookup" @@ -6694,7 +6730,7 @@ #: builtin/config.c msgid "--fixed-value only applies with 'value-pattern'" -msgstr "--fixed-value 仅适用于有 '值模式'" +msgstr "--fixed-value 仅适用于 '值模式'" #: builtin/config.c msgid "--default= cannot be used with --all or --url=" @@ -6732,10 +6768,18 @@ #, c-format msgid "" "cannot overwrite multiple values with a single value\n" -" Use a regexp, --add or --replace-all to change %s." +" Use --value=<pattern>, --append or --all to change %s." msgstr "" -"无法用一个值覆盖多个值\n" -" 使用一个正则表达式、--add 或 --replace-all 来修改 %s。" +"无法用单个值覆盖多个值\n" +" 使用 --value=<模式>, --append 或 --all 来修改 %s。" + +#: builtin/config.c +msgid "unset all multi-valued config options" +msgstr "取消设置所有多值配置选项" + +#: builtin/config.c +msgid "unset multi-valued config options with matching values" +msgstr "取消设置匹配值的多值配置选项" #: builtin/config.c #, c-format @@ -6753,7 +6797,7 @@ #: builtin/config.c #, c-format msgid "cannot create configuration file %s" -msgstr "不能创建配置文件 %s" +msgstr "无法创建配置文件 %s" #: builtin/config.c msgid "Action" @@ -6845,9 +6889,18 @@ msgid "--comment is only applicable to add/set/replace operations" msgstr "--comment 仅适用于 add/set/replace 操作" +#: builtin/config.c +#, c-format +msgid "" +"cannot overwrite multiple values with a single value\n" +" Use a regexp, --add or --replace-all to change %s." +msgstr "" +"无法用一个值覆盖多个值\n" +" 使用一个正则表达式、--add 或 --replace-all 来修改 %s。" + #: builtin/count-objects.c msgid "print sizes in human readable format" -msgstr "以用户可读的格式显示大小" +msgstr "以人类可读的格式显示大小" #: builtin/credential-cache--daemon.c #, c-format @@ -6910,7 +6963,7 @@ #: builtin/describe.c #, c-format msgid "annotated tag %s not available" -msgstr "附注标签 %s 无效" +msgstr "附注标签 %s 不可用" #: builtin/describe.c #, c-format @@ -6930,7 +6983,7 @@ #: builtin/describe.c #, c-format msgid "finished search at %s\n" -msgstr "完成搜索 %s\n" +msgstr "在 %s 处完成搜索\n" #: builtin/describe.c #, c-format @@ -6999,7 +7052,7 @@ #: builtin/describe.c msgid "use any tag, even unannotated" -msgstr "使用任意标签,即使未附带注释" +msgstr "使用任意标签,即使未附注" #: builtin/describe.c msgid "always use long format" @@ -7073,12 +7126,12 @@ #: builtin/diff-pairs.c #, c-format msgid "unable to parse mode: %s" -msgstr "不能解析模式:%s" +msgstr "无法解析模式:%s" #: builtin/diff-pairs.c #, c-format msgid "unable to parse object id: %s" -msgstr "不能解析对象 ID:%s" +msgstr "无法解析对象 ID:%s" #: builtin/diff-pairs.c msgid "git diff-pairs -z [<diff-options>]" @@ -7177,7 +7230,7 @@ #: builtin/diff.c #, c-format msgid "%s...%s: multiple merge bases, using %s" -msgstr "%s...%s:多条合并基线,使用 %s" +msgstr "%s...%s:多个合并基线,使用 %s" #: builtin/difftool.c msgid "git difftool [<options>] [<commit> [<commit>]] [--] [<path>...]" @@ -7213,12 +7266,12 @@ #: builtin/difftool.c msgid "working tree file has been left." -msgstr "工作区文件被留了下来。" +msgstr "工作区文件已保留。" #: builtin/difftool.c sequencer.c #, c-format msgid "could not copy '%s' to '%s'" -msgstr "不能拷贝 '%s' 至 '%s'" +msgstr "无法拷贝 '%s' 至 '%s'" #: builtin/difftool.c #, c-format @@ -7346,13 +7399,7 @@ msgid "" "encountered commit-specific encoding %.*s in commit %s; use --reencode=[yes|" "no] to handle it" -msgstr "" -"在提交 %3$s 中遇到提交特定编码 %2$.*1$s;使用 --reencode=[yes|no] 来处理" - -#: builtin/fast-export.c -#, c-format -msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it" -msgstr "遇到已签名提交 %s;使用 --signed-commits=<模式> 来处理" +msgstr "遇到提交特定编码 %.*s 的提交为 %s;使用 --reencode=[yes|no] 来处理" #: builtin/fast-export.c #, c-format @@ -7366,6 +7413,19 @@ #: builtin/fast-export.c #, c-format +msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it" +msgstr "遇到已签名提交 %s;使用 --signed-commits=<模式> 来处理" + +#: builtin/fast-export.c +msgid "" +"'strip-if-invalid' is not a valid mode for git fast-export with --signed-" +"commits=<mode>" +msgstr "" +"在 git fast-export 的 --signed-commits=<模式> 中,'strip-if-invalid' 不是有效" +"模式" + +#: builtin/fast-export.c +#, c-format msgid "" "omitting tag %s,\n" "since tags of trees (or tags of tags of trees, etc.) are not supported." @@ -7380,11 +7440,6 @@ #: builtin/fast-export.c #, c-format -msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it" -msgstr "遇到已签名标签 %s;使用 --signed-tags=<模式> 来处理" - -#: builtin/fast-export.c -#, c-format msgid "exporting signed tag %s" msgstr "正在导出已签名标签 %s" @@ -7395,6 +7450,19 @@ #: builtin/fast-export.c #, c-format +msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it" +msgstr "遇到已签名标签 %s;使用 --signed-tags=<模式> 来处理" + +#: builtin/fast-export.c +msgid "" +"'strip-if-invalid' is not a valid mode for git fast-export with --signed-" +"tags=<mode>" +msgstr "" +"在 git fast-export 的 --signed-tags=<模式> 中,'strip-if-invalid' 不是有效模" +"式" + +#: builtin/fast-export.c +#, c-format msgid "" "tag %s tags unexported object; use --tag-of-filtered-object=<mode> to handle " "it" @@ -7488,7 +7556,7 @@ #: builtin/fast-export.c msgid "fake a tagger when tags lack one" -msgstr "当标签缺少标记人字段时,假装提供一个" +msgstr "当标签缺少打标签者时,伪造一个" #: builtin/fast-export.c msgid "output full tree for each commit" @@ -7516,7 +7584,7 @@ #: builtin/fast-export.c msgid "from:to" -msgstr "from:to" +msgstr "从:到" #: builtin/fast-export.c msgid "convert <from> to <to> in anonymized output" @@ -7524,7 +7592,7 @@ #: builtin/fast-export.c msgid "reference parents which are not in fast-export stream by object id" -msgstr "引用父对象 ID 不在 fast-export 流中" +msgstr "通过对象 ID 引用不在 fast-export 流中的父对象" #: builtin/fast-export.c msgid "show original object ids of blobs/commits" @@ -7532,7 +7600,7 @@ #: builtin/fast-export.c msgid "label tags with mark ids" -msgstr "对带有标记 ID 的标签做标记" +msgstr "用标记 ID 标注标签" #: builtin/fast-import.c #, c-format @@ -7649,7 +7717,7 @@ #: sequencer.c #, c-format msgid "unable to create leading directories of %s" -msgstr "不能为 %s 创建先导目录" +msgstr "无法为 %s 创建上级目录" #: builtin/fast-import.c #, c-format @@ -7798,7 +7866,7 @@ #: builtin/fast-import.c #, c-format msgid "%s not found: %s" -msgstr "未找到%s:%s" +msgstr "未找到 %s:%s" #: builtin/fast-import.c msgid "tree" @@ -7816,7 +7884,7 @@ #: builtin/fast-import.c msgid "can't add a note on empty branch." -msgstr "不能在空分支上添加注释。" +msgstr "无法在空分支上添加注解。" #: builtin/fast-import.c #, c-format @@ -7883,9 +7951,40 @@ msgid "parse_one_signature() returned unknown hash algo" msgstr "parse_one_signature() 返回了未知的哈希算法" +#: builtin/fast-import.c builtin/fsck.c +msgid "unknown" +msgstr "未知" + +#: builtin/fast-import.c +#, c-format +msgid "" +"stripping invalid signature for commit '%.100s...'\n" +" allegedly by %s" +msgstr "" +"正在剥离提交 '%.100s...' 的无效签名\n" +" 据称由 %s 提供" + +#: builtin/fast-import.c +#, c-format +msgid "" +"stripping invalid signature for commit '%.*s'\n" +" allegedly by %s" +msgstr "" +"正在剥离提交 '%.*s' 的无效签名\n" +" 据称由 %s 提供" + +#: builtin/fast-import.c +#, c-format +msgid "" +"stripping invalid signature for commit\n" +" allegedly by %s" +msgstr "" +"正在剥离提交的无效签名\n" +" 据称由 %s 提供" + #: builtin/fast-import.c msgid "expected committer but didn't get one" -msgstr "预期提交者但未获取" +msgstr "预期提交者但未获得" #: builtin/fast-import.c msgid "encountered signed commit; use --signed-commits=<mode> to handle it" @@ -7900,10 +7999,6 @@ msgstr "逐字导入提交签名" #: builtin/fast-import.c -msgid "encountered signed tag; use --signed-tags=<mode> to handle it" -msgstr "遇到已签名标签;使用 --signed-tags=<模式> 来处理它" - -#: builtin/fast-import.c #, c-format msgid "importing a tag signature verbatim for tag '%s'" msgstr "逐字导入标签 '%s' 的签名" @@ -7914,6 +8009,18 @@ msgstr "正在剥离标签 '%s' 的签名" #: builtin/fast-import.c +msgid "encountered signed tag; use --signed-tags=<mode> to handle it" +msgstr "遇到已签名标签;使用 --signed-tags=<模式> 来处理它" + +#: builtin/fast-import.c +msgid "" +"'strip-if-invalid' is not a valid mode for git fast-import with --signed-" +"tags=<mode>" +msgstr "" +"在 git fast-import 的 --signed-tags=<模式> 中,'strip-if-invalid' 不是有效模" +"式" + +#: builtin/fast-import.c #, c-format msgid "expected 'from' command, got '%s'" msgstr "预期 'from' 命令,实际:'%s'" @@ -8100,7 +8207,7 @@ #: builtin/fetch-pack.c #, c-format msgid "Lockfile created but not reported: %s" -msgstr "Lockfile 已创建但未报告:%s" +msgstr "锁文件已创建但未报告:%s" #: builtin/fetch.c msgid "git fetch [<options>] [<repository> [<refspec>...]]" @@ -8111,8 +8218,8 @@ msgstr "git fetch [<选项>] <组>" #: builtin/fetch.c -msgid "git fetch --multiple [<options>] [(<repository> | <group>)...]" -msgstr "git fetch --multiple [<选项>] [(<仓库> | <组>)...]" +msgid "git fetch --multiple [<options>] [(<repository>|<group>)...]" +msgstr "git fetch --multiple [<选项>] [(<仓库>|<组>)...]" #: builtin/fetch.c msgid "git fetch --all [<options>]" @@ -8134,7 +8241,7 @@ #: builtin/fetch.c #, c-format msgid "object %s not found" -msgstr "对象 %s 未发现" +msgstr "对象 %s 未找到" #: builtin/fetch.c msgid "[up to date]" @@ -8154,11 +8261,11 @@ #: builtin/fetch.c msgid "unable to update local ref" -msgstr "不能更新本地引用" +msgstr "无法更新本地引用" #: builtin/fetch.c msgid "would clobber existing tag" -msgstr "会破坏现有的标签" +msgstr "会覆盖现有的标签" #: builtin/fetch.c msgid "[new tag]" @@ -8183,7 +8290,7 @@ #: builtin/fetch.c builtin/grep.c sequencer.c #, c-format msgid "cannot open '%s'" -msgstr "不能打开 '%s'" +msgstr "无法打开 '%s'" #: builtin/fetch.c msgid "" @@ -8237,7 +8344,7 @@ #: builtin/fetch.c #, c-format msgid "option \"%s\" is ignored for %s" -msgstr "选项 \"%s\" 为 %s 所忽略" +msgstr "选项 \"%s\" 对 %s 将被忽略" #: builtin/fetch.c odb.c #, c-format @@ -8260,7 +8367,7 @@ msgstr "" "运行 'git remote set-head %s %s' 同步变更,或通过配置选项\n" "'remote.%s.followRemoteHEAD' 设置为其他值以屏蔽此提示。具体可通过\n" -"运行命令 'git config remote.%s.followRemoteHEAD warn-if-not-branch-%s'\n" +"运行命令 'git config set remote.%s.followRemoteHEAD warn-if-not-branch-%s'\n" "以禁用该警告,直到远程将 HEAD 更改为其他内容。" #: builtin/fetch.c @@ -8295,7 +8402,7 @@ "some local refs could not be updated; try running\n" " 'git remote prune %s' to remove any old, conflicting branches" msgstr "" -"一些本地引用不能被更新;尝试运行\n" +"一些本地引用无法被更新;尝试运行\n" " 'git remote prune %s' 来删除旧的、有冲突的分支" #: builtin/fetch.c @@ -8342,7 +8449,7 @@ #: builtin/fetch.c #, c-format msgid "could not fetch %s" -msgstr "不能获取 %s" +msgstr "无法获取 %s" #: builtin/fetch.c #, c-format @@ -8405,11 +8512,11 @@ #: builtin/fetch.c builtin/pull.c msgid "prune remote-tracking branches no longer on remote" -msgstr "清除远程已经不存在的分支的跟踪分支" +msgstr "清除远程已不存在的远程跟踪分支" #: builtin/fetch.c msgid "prune local tags no longer on remote and clobber changed tags" -msgstr "清除远程不存在的本地标签,并且替换变更标签" +msgstr "清除远程已不存在的本地标签,并覆盖已变更的标签" # 译者:可选值,不能翻译 #: builtin/fetch.c builtin/pull.c @@ -8456,7 +8563,7 @@ msgid "" "default for recursive fetching of submodules (lower priority than config " "files)" -msgstr "递归获取子模组的缺省值(比配置文件优先级低)" +msgstr "递归获取子模组的默认值(比配置文件优先级低)" #: builtin/fetch.c builtin/pull.c msgid "accept refs that update .git/shallow" @@ -8513,7 +8620,7 @@ #: builtin/fetch.c #, c-format msgid "failed to fetch bundles from '%s'" -msgstr "无法从 '%s' 获取归档包" +msgstr "无法从 '%s' 获取捆绑包" #: builtin/fetch.c msgid "fetch --all does not take a repository argument" @@ -8585,19 +8692,19 @@ #: builtin/for-each-ref.c msgid "quote placeholders suitably for shells" -msgstr "引用占位符适用于 shells" +msgstr "为 shell 适当引用占位符" #: builtin/for-each-ref.c msgid "quote placeholders suitably for perl" -msgstr "引用占位符适用于 perl" +msgstr "为 perl 适当引用占位符" #: builtin/for-each-ref.c msgid "quote placeholders suitably for python" -msgstr "引用占位符适用于 python" +msgstr "为 python 适当引用占位符" #: builtin/for-each-ref.c msgid "quote placeholders suitably for Tcl" -msgstr "引用占位符适用于 Tcl" +msgstr "为 Tcl 适当引用占位符" #: builtin/for-each-ref.c msgid "show only <n> matched refs" @@ -8684,10 +8791,6 @@ msgid "got bad config --config=%s" msgstr "发现错误的配置行 --config=%s" -#: builtin/fsck.c -msgid "unknown" -msgstr "未知" - #. TRANSLATORS: e.g. error in tree 01bfda: <more explanation> #: builtin/fsck.c #, c-format @@ -8739,18 +8842,18 @@ #: builtin/fsck.c msgid "could not create lost-found" -msgstr "不能创建 lost-found" +msgstr "无法创建 lost-found" #: builtin/fsck.c builtin/gc.c builtin/rebase.c rebase-interactive.c rerere.c #: sequencer.c #, c-format msgid "could not write '%s'" -msgstr "不能写入 '%s'" +msgstr "无法写入 '%s'" #: builtin/fsck.c #, c-format msgid "could not finish '%s'" -msgstr "不能完成 '%s'" +msgstr "无法完成 '%s'" #: builtin/fsck.c #, c-format @@ -8807,6 +8910,11 @@ msgstr "%s:不是一个提交" #: builtin/fsck.c +#, c-format +msgid "invalid parameter: expected sha1, got '%s'" +msgstr "无效的参数:期望 sha1,得到 '%s'" + +#: builtin/fsck.c msgid "notice: No default references" msgstr "注意:无默认引用" @@ -8823,7 +8931,7 @@ #: builtin/fsck.c #, c-format msgid "%s: object could not be parsed: %s" -msgstr "%s:不能解析对象:%s" +msgstr "%s:无法解析对象:%s" #: builtin/fsck.c #, c-format @@ -8840,31 +8948,6 @@ #: builtin/fsck.c #, c-format -msgid "Checking %s link" -msgstr "正在检查 %s 链接" - -#: builtin/fsck.c builtin/index-pack.c -#, c-format -msgid "invalid %s" -msgstr "无效的 %s" - -#: builtin/fsck.c -#, c-format -msgid "%s points to something strange (%s)" -msgstr "%s 指向奇怪的东西(%s)" - -#: builtin/fsck.c -#, c-format -msgid "%s: detached HEAD points at nothing" -msgstr "%s:分离头指针的指向不存在" - -#: builtin/fsck.c -#, c-format -msgid "notice: %s points to an unborn branch (%s)" -msgstr "注意:%s 指向一个尚未诞生的分支(%s)" - -#: builtin/fsck.c -#, c-format msgid "Checking cache tree of %s" msgstr "正在检查缓存树 %s" @@ -8964,16 +9047,6 @@ msgid "Checking objects" msgstr "正在检查对象" -#: builtin/fsck.c -#, c-format -msgid "%s: object missing" -msgstr "%s:对象缺失" - -#: builtin/fsck.c -#, c-format -msgid "invalid parameter: expected sha1, got '%s'" -msgstr "无效的参数:期望 sha1,得到 '%s'" - #: builtin/fsmonitor--daemon.c msgid "git fsmonitor--daemon start [<options>]" msgstr "git fsmonitor--daemon start [<选项>]" @@ -9010,7 +9083,7 @@ #: builtin/fsmonitor--daemon.c #, c-format msgid "fsmonitor: cookie_result '%d' != SEEN" -msgstr "fsmonitor: cookie_result '%d' != SEEN" +msgstr "fsmonitor:cookie_result '%d' != SEEN" #: builtin/fsmonitor--daemon.c #, c-format @@ -9036,7 +9109,7 @@ #: builtin/fsmonitor--daemon.c #, c-format msgid "could not cd home '%s'" -msgstr "不能切换至家目录 '%s'" +msgstr "无法切换至家目录 '%s'" #: builtin/fsmonitor--daemon.c #, c-format @@ -9108,7 +9181,7 @@ #: builtin/gc.c setup.c #, c-format msgid "cannot stat '%s'" -msgstr "不能对 '%s' 调用 stat" +msgstr "无法对 '%s' 调用 stat" #: builtin/gc.c #, c-format @@ -9154,7 +9227,7 @@ #: builtin/gc.c msgid "repack all other packs except the largest pack" -msgstr "除了最大的包之外,对所有其它包文件重新打包" +msgstr "除了最大的包之外,对所有其他包文件重新打包" #: builtin/gc.c builtin/repack.c msgid "pack prefix to store a pack containing pruned objects" @@ -9276,7 +9349,7 @@ #: builtin/gc.c #, c-format msgid "task '%s' cannot be selected multiple times" -msgstr "任务 '%s' 不能被多次选择" +msgstr "任务 '%s' 无法被多次选择" #: builtin/gc.c msgid "run tasks based on the state of the repository" @@ -9284,7 +9357,7 @@ #: builtin/gc.c msgid "perform maintenance in the background" -msgstr "在后台执行运维" +msgstr "在后台执行维护" #: builtin/gc.c msgid "frequency" @@ -9296,7 +9369,7 @@ #: builtin/gc.c msgid "do not report progress or other information over stderr" -msgstr "不通过标准错误报告进度或其它信息" +msgstr "不通过标准错误报告进度或其他信息" #: builtin/gc.c msgid "task" @@ -9445,6 +9518,10 @@ msgstr "无法将仓库添加到全局配置" #: builtin/gc.c +msgid "check a specific task" +msgstr "检查特定任务" + +#: builtin/gc.c msgid "git maintenance <subcommand> [<options>]" msgstr "git maintenance <子命令> [<选项>]" @@ -9521,7 +9598,7 @@ #: builtin/grep.c msgid "process binary files as text" -msgstr "把二进制文件当做文本处理" +msgstr "把二进制文件当作文本处理" #: builtin/grep.c msgid "don't match patterns in binary files" @@ -9629,7 +9706,7 @@ #: builtin/grep.c msgid "shortcut for -C NUM" -msgstr "快捷键 -C 数字" +msgstr "-C NUM 的快捷方式" #: builtin/grep.c msgid "show a line with the function name before matches" @@ -9661,7 +9738,7 @@ #: builtin/grep.c msgid "pager" -msgstr "分页" +msgstr "分页器" #: builtin/grep.c msgid "show matching files in the pager" @@ -9686,7 +9763,7 @@ #: builtin/grep.c #, c-format msgid "unable to resolve revision: %s" -msgstr "不能解析版本:%s" +msgstr "无法解析版本:%s" #: builtin/grep.c msgid "--untracked not supported with --recurse-submodules" @@ -9768,7 +9845,7 @@ #: builtin/help.c msgid "exclude guides" -msgstr "排除向导" +msgstr "排除指南" #: builtin/help.c msgid "show man page" @@ -9969,7 +10046,7 @@ #: builtin/index-pack.c msgid "offset value overflow for delta base object" -msgstr "偏移值覆盖了 delta 基准对象" +msgstr "delta 基准对象的偏移值溢出" #: builtin/index-pack.c msgid "delta base offset is out of bound" @@ -10003,12 +10080,12 @@ #: builtin/index-pack.c #, c-format msgid "cannot read existing object info %s" -msgstr "不能读取现存对象信息 %s" +msgstr "无法读取现存对象信息 %s" #: builtin/index-pack.c #, c-format msgid "cannot read existing object %s" -msgstr "不能读取现存对象 %s" +msgstr "无法读取现存对象 %s" #: builtin/index-pack.c #, c-format @@ -10021,6 +10098,11 @@ #: builtin/index-pack.c #, c-format +msgid "invalid %s" +msgstr "无效的 %s" + +#: builtin/index-pack.c +#, c-format msgid "Not all child objects of %s are reachable" msgstr "%s 的所有子对象并非都可达" @@ -10038,11 +10120,11 @@ #: builtin/index-pack.c msgid "pack is corrupted (SHA1 mismatch)" -msgstr "包冲突(SHA1 不匹配)" +msgstr "包已损坏(SHA1 不匹配)" #: builtin/index-pack.c msgid "cannot fstat packfile" -msgstr "不能对包文件调用 fstat" +msgstr "无法对包文件调用 fstat" #: builtin/index-pack.c msgid "pack has junk at the end" @@ -10059,7 +10141,7 @@ #: builtin/index-pack.c builtin/pack-objects.c #, c-format msgid "unable to create thread: %s" -msgstr "不能创建线程:%s" +msgstr "无法创建线程:%s" #: builtin/index-pack.c msgid "confusion beyond insanity" @@ -10087,7 +10169,7 @@ #: builtin/index-pack.c #, c-format msgid "unable to deflate appended object (%d)" -msgstr "不能压缩附加对象(%d)" +msgstr "无法压缩附加对象(%d)" #: builtin/index-pack.c #, c-format @@ -10112,7 +10194,7 @@ #: builtin/index-pack.c #, c-format msgid "unable to rename temporary '*.%s' file to '%s'" -msgstr "不能重命名临时文件 '*.%s' 为 '%s'" +msgstr "无法重命名临时文件 '*.%s' 为 '%s'" #: builtin/index-pack.c msgid "error while closing pack file" @@ -10144,8 +10226,8 @@ #, c-format msgid "chain length = %d: %lu object" msgid_plural "chain length = %d: %lu objects" -msgstr[0] "链长 = %d: %lu 对象" -msgstr[1] "链长 = %d: %lu 对象" +msgstr[0] "链长 = %d:%lu 对象" +msgstr[1] "链长 = %d:%lu 对象" #: builtin/index-pack.c msgid "could not start pack-objects to repack local links" @@ -10176,7 +10258,7 @@ #: builtin/index-pack.c #, c-format msgid "bad %s" -msgstr "错误选项 %s" +msgstr "错误的 %s" #: builtin/index-pack.c builtin/init-db.c setup.c #, c-format @@ -10185,7 +10267,7 @@ #: builtin/index-pack.c msgid "--promisor cannot be used with a pack name" -msgstr "--promisor 无法与包名称一起使用" +msgstr "--promisor 不能与包名称一起使用" #: builtin/index-pack.c msgid "--stdin requires a git repository" @@ -10195,6 +10277,10 @@ msgid "--verify with no packfile name given" msgstr "--verify 没有提供包文件名参数" +#: builtin/index-pack.c +msgid "cannot perform queued object checks outside of a repository" +msgstr "无法在仓库之外执行排队的对象检查" + #: builtin/index-pack.c builtin/unpack-objects.c msgid "fsck error in pack objects" msgstr "在打包对象中 fsck 检查出错" @@ -10227,7 +10313,7 @@ #: builtin/init-db.c builtin/verify-pack.c msgid "hash" -msgstr "hash" +msgstr "哈希" #: builtin/init-db.c builtin/show-index.c builtin/verify-pack.c msgid "specify the hash algorithm to use" @@ -10236,12 +10322,12 @@ #: builtin/init-db.c #, c-format msgid "cannot mkdir %s" -msgstr "不能创建目录 %s" +msgstr "无法创建目录 %s" #: builtin/init-db.c #, c-format msgid "cannot chdir to %s" -msgstr "不能切换目录到 %s" +msgstr "无法切换目录到 %s" #: builtin/init-db.c #, c-format @@ -10253,11 +10339,11 @@ #: builtin/init-db.c #, c-format msgid "Cannot access work tree '%s'" -msgstr "不能访问工作区 '%s'" +msgstr "无法访问工作区 '%s'" #: builtin/init-db.c msgid "--separate-git-dir incompatible with bare repository" -msgstr "--separate-git-dir 不能用于纯仓库" +msgstr "--separate-git-dir 和纯仓库不兼容" #: builtin/interpret-trailers.c msgid "" @@ -10272,7 +10358,7 @@ #: builtin/interpret-trailers.c wrapper.c #, c-format msgid "could not stat %s" -msgstr "不能对 %s 调用 stat" +msgstr "无法对 %s 调用 stat" #: builtin/interpret-trailers.c #, c-format @@ -10286,21 +10372,21 @@ #: builtin/interpret-trailers.c msgid "could not open temporary file" -msgstr "不能打开临时文件" +msgstr "无法打开临时文件" #: builtin/interpret-trailers.c #, c-format msgid "could not read input file '%s'" -msgstr "不能读取输入文件 '%s'" +msgstr "无法读取输入文件 '%s'" #: builtin/interpret-trailers.c builtin/mktag.c imap-send.c msgid "could not read from stdin" -msgstr "不能自标准输入读取" +msgstr "无法自标准输入读取" #: builtin/interpret-trailers.c #, c-format msgid "could not rename temporary file to %s" -msgstr "不能重命名临时文件为 %s" +msgstr "无法重命名临时文件为 %s" #: builtin/interpret-trailers.c msgid "edit files in place" @@ -10431,7 +10517,7 @@ #: builtin/log.c msgid "-L<range>:<file> cannot be used with pathspec" -msgstr "-L<范围>:<文件> 不能和路径表达式共用" +msgstr "-L<范围>:<文件> 不能和路径规格共用" #: builtin/log.c msgid "" @@ -10452,7 +10538,7 @@ #: builtin/log.c #, c-format msgid "git show %s: bad file" -msgstr "git show %s: 损坏的文件" +msgstr "git show %s:损坏的文件" #: builtin/log.c #, c-format @@ -10524,7 +10610,7 @@ #: builtin/log.c msgid "could not find exact merge base" -msgstr "不能找到准确的合并基线" +msgstr "无法找到准确的合并基线" #: builtin/log.c msgid "" @@ -10550,7 +10636,7 @@ #: builtin/log.c msgid "cannot get patch id" -msgstr "无法得到补丁 id" +msgstr "无法得到补丁 ID" #: builtin/log.c msgid "failed to infer range-diff origin of current series" @@ -10595,7 +10681,7 @@ #: builtin/log.c msgid "reroll-count" -msgstr "重制-计数" +msgstr "重制次数" #: builtin/log.c msgid "mark the series as Nth re-roll" @@ -10651,7 +10737,7 @@ #: builtin/log.c msgid "show patch format instead of default (patch + stat)" -msgstr "显示纯补丁格式而非默认的(补丁+状态)" +msgstr "显示纯补丁格式而非默认的(补丁+统计)" #: builtin/log.c msgid "Messaging" @@ -10659,7 +10745,7 @@ #: builtin/log.c msgid "header" -msgstr "header" +msgstr "头信息" #: builtin/log.c msgid "add email header" @@ -10683,7 +10769,7 @@ #: builtin/log.c msgid "set From address to <ident> (or committer ident if absent)" -msgstr "将 From 地址设置为 <标识>(如若不提供,则用提交者 ID 做为地址)" +msgstr "将 From 地址设置为 <标识>(如若不提供,则用提交者 ID 作为地址)" #: builtin/log.c msgid "message-id" @@ -10777,7 +10863,7 @@ #: builtin/log.c builtin/submodule--helper.c rerere.c submodule.c #, c-format msgid "could not create directory '%s'" -msgstr "不能创建目录 '%s'" +msgstr "无法创建目录 '%s'" #: builtin/log.c msgid "--interdiff requires --cover-letter or single patch" @@ -10826,7 +10912,7 @@ #, c-format msgid "" "Could not find a tracked remote branch, please specify <upstream> manually.\n" -msgstr "不能找到跟踪的远程分支,请手工指定 <上游>。\n" +msgstr "无法找到跟踪的远程分支,请手工指定 <上游>。\n" #: builtin/ls-files.c builtin/ls-tree.c #, c-format @@ -10867,7 +10953,7 @@ #: builtin/ls-files.c msgid "show other files in the output" -msgstr "显示其它文件" +msgstr "显示其他文件" #: builtin/ls-files.c msgid "show ignored files in the output" @@ -10969,7 +11055,7 @@ #: builtin/ls-remote.c builtin/rebase.c msgid "exec" -msgstr "exec" +msgstr "可执行命令" #: builtin/ls-remote.c msgid "path of git-upload-pack on the remote host" @@ -10997,7 +11083,7 @@ #: builtin/ls-remote.c msgid "exit with exit code 2 if no matching refs are found" -msgstr "若未找到匹配的引用则以退出码2退出" +msgstr "若未找到匹配的引用则以退出码 2 退出" #: builtin/ls-remote.c msgid "show underlying ref in addition to the object pointed by it" @@ -11131,11 +11217,11 @@ #: builtin/merge-base.c msgid "list revs not reachable from others" -msgstr "显示不能被其他访问到的版本" +msgstr "显示无法被其他访问到的版本" #: builtin/merge-base.c msgid "is the first one ancestor of the other?" -msgstr "第一个是其他的祖先提交么?" +msgstr "第一个是其他的祖先提交吗?" #: builtin/merge-base.c msgid "find where <commit> forked from reflog of <ref>" @@ -11149,14 +11235,6 @@ "git merge-file [<选项>] [-L <名字1> [-L <初始名字> [-L <名字2>]]] <文件1> <初" "始文件> <文件2>" -#: builtin/merge-file.c diff.c -msgid "" -"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and " -"\"histogram\"" -msgstr "" -"选项 diff-algorithm 接受参数 \"myers\"、\"minimal\"、\"patience\" 和 " -"\"histogram\"" - #: builtin/merge-file.c msgid "send results to standard output" msgstr "将结果发送到标准输出" @@ -11173,14 +11251,6 @@ msgid "use a zealous diff3 based merge" msgstr "使用基于狂热 diff3(zealous diff3)的合并" -#: builtin/merge-file.c diff.c -msgid "<algorithm>" -msgstr "<算法>" - -#: builtin/merge-file.c diff.c -msgid "choose a diff algorithm" -msgstr "选择一个差异算法" - #: builtin/merge-file.c msgid "for conflicts, use this marker size" msgstr "如果冲突,使用指定长度的标记" @@ -11200,12 +11270,12 @@ #: builtin/merge-file.c msgid "Could not write object file" -msgstr "不能写入对象文件" +msgstr "无法写入对象文件" #: builtin/merge-recursive.c #, c-format msgid "could not parse object '%s'" -msgstr "不能解析对象 '%s'" +msgstr "无法解析对象 '%s'" #: builtin/merge-recursive.c #, c-format @@ -11216,7 +11286,7 @@ #: builtin/merge-recursive.c msgid "not handling anything other than two heads merge." -msgstr "不能处理两个头合并之外的任何操作。" +msgstr "无法处理两个头合并之外的任何操作。" #: builtin/merge-recursive.c #, c-format @@ -11287,7 +11357,7 @@ #: builtin/merge-tree.c builtin/merge.c builtin/pull.c msgid "option=value" -msgstr "option=value" +msgstr "选项=值" #: builtin/merge-tree.c builtin/merge.c builtin/pull.c msgid "option for selected merge strategy" @@ -11323,7 +11393,7 @@ #: builtin/merge.c #, c-format msgid "Could not find merge strategy '%s'.\n" -msgstr "不能找到合并策略 '%s'。\n" +msgstr "无法找到合并策略 '%s'。\n" #: builtin/merge.c #, c-format @@ -11373,7 +11443,7 @@ #: builtin/merge.c builtin/pull.c msgid "abort if fast-forward is not possible" -msgstr "如果不能快进就放弃合并" +msgstr "如果无法快进就放弃合并" #: builtin/merge.c builtin/pull.c msgid "verify that the named commit has a valid GPG signature" @@ -11415,11 +11485,11 @@ #: builtin/merge.c msgid "could not run stash." -msgstr "不能运行贮藏。" +msgstr "无法运行储藏。" #: builtin/merge.c msgid "stash failed" -msgstr "贮藏失败" +msgstr "储藏失败" #: builtin/merge.c msgid "read-tree failed" @@ -11455,7 +11525,7 @@ #: builtin/merge.c merge-ort-wrappers.c msgid "Unable to write index." -msgstr "不能写入索引。" +msgstr "无法写入索引。" #: builtin/merge.c msgid "Not handling anything other than two heads merge." @@ -11464,12 +11534,12 @@ #: builtin/merge.c builtin/sparse-checkout.c #, c-format msgid "unable to write %s" -msgstr "不能写 %s" +msgstr "无法写 %s" #: builtin/merge.c #, c-format msgid "Could not read from '%s'" -msgstr "不能从 '%s' 读取" +msgstr "无法从 '%s' 读取" #: builtin/merge.c #, c-format @@ -11536,12 +11606,12 @@ #: builtin/merge.c editor.c read-cache.c wrapper.c #, c-format msgid "could not close '%s'" -msgstr "不能关闭 '%s'" +msgstr "无法关闭 '%s'" #: builtin/merge.c #, c-format msgid "not something we can merge in %s: %s" -msgstr "不能在 %s 中合并:%s" +msgstr "无法在 %s 中合并:%s" #: builtin/merge.c msgid "--abort expects no arguments" @@ -11598,7 +11668,7 @@ #: builtin/merge.c #, c-format msgid "%s - not something we can merge" -msgstr "%s - 不能被合并" +msgstr "%s - 无法被合并" #: builtin/merge.c msgid "Can merge only exactly one commit into empty head" @@ -11626,7 +11696,7 @@ #: builtin/merge.c #, c-format msgid "Nope.\n" -msgstr "无。\n" +msgstr "不行。\n" #: builtin/merge.c #, c-format @@ -11661,7 +11731,7 @@ #: builtin/merge.c #, c-format msgid "When finished, apply stashed changes with `git stash pop`\n" -msgstr "在完成后,使用 `git stash pop` 应用贮藏的变更\n" +msgstr "在完成后,使用 `git stash pop` 应用储藏的变更\n" #: builtin/mktag.c #, c-format @@ -11676,7 +11746,7 @@ #: builtin/mktag.c #, c-format msgid "could not read tagged object '%s'" -msgstr "不能读取被标记的对象 '%s'" +msgstr "无法读取被标记的对象 '%s'" #: builtin/mktag.c #, c-format @@ -11780,7 +11850,7 @@ #: builtin/mv.c msgid "Please stage your changes to .gitmodules or stash them to proceed" -msgstr "请将您的修改暂存到 .gitmodules 中或贮藏后再继续" +msgstr "请将您的修改暂存到 .gitmodules 中或储藏后再继续" #: builtin/mv.c #, c-format @@ -11789,7 +11859,7 @@ #: builtin/mv.c msgid "force move/rename even if target exists" -msgstr "强制移动/重命令,即使目标存在" +msgstr "强制移动/重命名,即使目标存在" #: builtin/mv.c msgid "skip move/rename errors" @@ -11807,7 +11877,7 @@ #: builtin/mv.c msgid "bad source" -msgstr "坏的源" +msgstr "错误的源" #: builtin/mv.c msgid "destination exists" @@ -11815,7 +11885,7 @@ #: builtin/mv.c msgid "can not move directory into itself" -msgstr "不能将目录移动到自身" +msgstr "无法将目录移动到自身" #: builtin/mv.c msgid "destination already exists" @@ -11840,7 +11910,7 @@ #: builtin/mv.c msgid "Cannot overwrite" -msgstr "不能覆盖" +msgstr "无法覆盖" #: builtin/mv.c msgid "multiple sources for the same target" @@ -11916,7 +11986,7 @@ #: builtin/name-rev.c msgid "allow to print `undefined` names (default)" -msgstr "允许打印 `未定义` 的名称(默认)" +msgstr "允许打印 `undefined` 的名称(默认)" #: builtin/name-rev.c msgid "dereference tags in the input (internal use)" @@ -11961,7 +12031,7 @@ #: builtin/notes.c msgid "" "git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>" -msgstr "git notes [--ref <注解引用>] merge [-v | -q] [-s <策略> ] <注解引用>" +msgstr "git notes [--ref <注解引用>] merge [-v | -q] [-s <策略>] <注解引用>" #: builtin/notes.c msgid "git notes [--ref <notes-ref>] remove [<object>...]" @@ -12029,7 +12099,7 @@ #: builtin/notes.c msgid "could not read 'show' output" -msgstr "不能读取 'show' 的输出" +msgstr "无法读取 'show' 的输出" #: builtin/notes.c #, c-format @@ -12042,7 +12112,7 @@ #: builtin/notes.c msgid "unable to write note object" -msgstr "不能写注解对象" +msgstr "无法写注解对象" #: builtin/notes.c #, c-format @@ -12052,7 +12122,7 @@ #: builtin/notes.c builtin/tag.c #, c-format msgid "could not open or read '%s'" -msgstr "不能打开或读取 '%s'" +msgstr "无法打开或读取 '%s'" #: builtin/notes.c #, c-format @@ -12067,7 +12137,7 @@ #: builtin/notes.c #, c-format msgid "cannot read note data from non-blob object '%s'." -msgstr "不能从非数据对象 '%s' 中读取注解数据。" +msgstr "无法从非数据对象 '%s' 中读取注解数据。" #: builtin/notes.c #, c-format @@ -12080,7 +12150,7 @@ #: builtin/notes.c #, c-format msgid "refusing to %s notes in %s (outside of refs/notes/)" -msgstr "拒绝向 %2$s(在 refs/notes/ 之外)%1$s注解" +msgstr "拒绝向 %2$s(在 refs/notes/ 之外)%1$s 注解" #: builtin/notes.c #, c-format @@ -12093,7 +12163,7 @@ #: builtin/notes.c msgid "note contents in a file" -msgstr "注解内容到一个文件中" +msgstr "注解内容在一个文件中" #: builtin/notes.c msgid "reuse and edit specified note object" @@ -12101,7 +12171,7 @@ #: builtin/notes.c msgid "edit note message in editor" -msgstr "在编辑器中编辑注释说明" +msgstr "在编辑器中编辑注解说明" #: builtin/notes.c msgid "reuse specified note object" @@ -12109,7 +12179,7 @@ #: builtin/notes.c msgid "allow storing empty note" -msgstr "允许保存空白注释" +msgstr "允许保存空注解" #: builtin/notes.c msgid "replace existing notes" @@ -12121,7 +12191,7 @@ #: builtin/notes.c msgid "insert <paragraph-break> between paragraphs" -msgstr "在段落之间插入<分段符>" +msgstr "在段落之间插入 <分段符>" #: builtin/notes.c msgid "remove unnecessary whitespace" @@ -12132,7 +12202,7 @@ msgid "" "Cannot add notes. Found existing notes for object %s. Use '-f' to overwrite " "existing notes" -msgstr "不能添加注解。发现对象 %s 已存在注解。使用 '-f' 覆盖现存注解" +msgstr "无法添加注解。发现对象 %s 已存在注解。使用 '-f' 覆盖现存注解" #: builtin/notes.c #, c-format @@ -12161,12 +12231,12 @@ msgid "" "Cannot copy notes. Found existing notes for object %s. Use '-f' to overwrite " "existing notes" -msgstr "不能拷贝注解。发现对象 %s 已存在注解。使用 '-f' 覆盖现存注解" +msgstr "无法拷贝注解。发现对象 %s 已存在注解。使用 '-f' 覆盖现存注解" #: builtin/notes.c #, c-format msgid "missing notes on source object %s. Cannot copy." -msgstr "源对象 %s 缺少注解。不能拷贝。" +msgstr "源对象 %s 缺少注解。无法拷贝。" #: builtin/notes.c #, c-format @@ -12238,7 +12308,7 @@ #: builtin/notes.c msgid "Aborting notes merge resolution" -msgstr "中止注解合并的方案" +msgstr "中止注解合并的解决过程" #: builtin/notes.c msgid "abort notes merge" @@ -12274,7 +12344,7 @@ "'git notes merge --commit', or abort the merge with 'git notes merge --" "abort'.\n" msgstr "" -"自动合并说明失败。修改 %s 中的冲突并且使用命令 'git notes merge --commit' 提" +"自动注解合并失败。修改 %s 中的冲突并且使用命令 'git notes merge --commit' 提" "交结果,或者使用命令 'git notes merge --abort' 终止合并。\n" #: builtin/notes.c builtin/tag.c @@ -12335,7 +12405,7 @@ " [--revs [--unpacked | --all]] [--keep-pack=<包名>]\n" " [--cruft] [--cruft-expiration=<时间>]\n" " [--stdout [--filter=<过滤规格>] | <基线名称>]\n" -" [--shallow] [--keep-true-parents] [--[no-]sparse]\"\n" +" [--shallow] [--keep-true-parents] [--[no-]sparse]\n" " [--name-hash-version=<n>] [--path-walk] < <对象列表>" #: builtin/pack-objects.c @@ -12414,7 +12484,7 @@ #: builtin/pack-objects.c #, c-format msgid "delta base offset overflow in pack for %s" -msgstr "%s 压缩中 delta 基准偏移越界" +msgstr "包 %s 中 delta 基准偏移溢出" #: builtin/pack-objects.c #, c-format @@ -12510,8 +12580,13 @@ #: builtin/pack-objects.c #, c-format +msgid "packfile %s is a promisor but --exclude-promisor-objects was given" +msgstr "包文件 %s 是承诺者,但已给出 --exclude-promisor-objects" + +#: builtin/pack-objects.c +#, c-format msgid "could not find pack '%s'" -msgstr "不能找到包 '%s'" +msgstr "无法找到包 '%s'" #: builtin/pack-objects.c #, c-format @@ -12550,7 +12625,7 @@ #: builtin/pack-objects.c reachable.c msgid "could not load cruft pack .mtimes" -msgstr "不能载入废弃包 .mtimes" +msgstr "无法载入废弃包 .mtimes" #: builtin/pack-objects.c msgid "cannot open pack index" @@ -12577,7 +12652,7 @@ #: builtin/pack-objects.c builtin/rev-parse.c #, c-format msgid "bad revision '%s'" -msgstr "坏的版本 '%s'" +msgstr "错误的版本 '%s'" #: builtin/pack-objects.c msgid "unable to add recent objects" @@ -12727,7 +12802,7 @@ #: builtin/pack-objects.c msgid "ignore this pack" -msgstr "忽略该 pack" +msgstr "忽略该包" #: builtin/pack-objects.c msgid "pack compression level" @@ -12735,7 +12810,7 @@ #: builtin/pack-objects.c msgid "do not hide commits by grafts" -msgstr "显示被移植隐藏的提交" +msgstr "显示被嫁接隐藏的提交" #: builtin/pack-objects.c msgid "use a bitmap index if available to speed up counting objects" @@ -12828,7 +12903,7 @@ "reused %<PRIu32> (from %<PRIuMAX>)" msgstr "" "总共 %<PRIu32>(差异 %<PRIu32>),复用 %<PRIu32>(差异 %<PRIu32>),包复用 " -"%<PRIu32>(来自 %<PRIuMAX> 个包)" +"%<PRIu32>(来自 %<PRIuMAX> 个包)" #: builtin/pack-refs.c msgid "git pack-refs " @@ -12839,12 +12914,12 @@ msgstr "git patch-id [--stable | --unstable | --verbatim]" #: builtin/patch-id.c -msgid "use the unstable patch-id algorithm" -msgstr "使用不稳定的 patch-id 算法" +msgid "use the unstable patch ID algorithm" +msgstr "使用不稳定的 patch ID 算法" #: builtin/patch-id.c -msgid "use the stable patch-id algorithm" -msgstr "使用稳定的 patch-id 算法" +msgid "use the stable patch ID algorithm" +msgstr "使用稳定的 patch ID 算法" #: builtin/patch-id.c msgid "don't strip whitespace from the patch" @@ -12875,50 +12950,6 @@ msgstr "git pull [<选项>] [<仓库> [<引用规格>...]]" #: builtin/pull.c -msgid "control for recursive fetching of submodules" -msgstr "控制子模组的递归获取" - -#: builtin/pull.c -msgid "Options related to merging" -msgstr "和合并相关的选项" - -#: builtin/pull.c -msgid "incorporate changes by rebasing rather than merging" -msgstr "使用变基操作取代合并操作以合入修改" - -#: builtin/pull.c builtin/revert.c -msgid "allow fast-forward" -msgstr "允许快进式" - -#: builtin/pull.c -msgid "control use of pre-merge-commit and commit-msg hooks" -msgstr "控制 pre-merge-commit 和 commit-msg 钩子的使用" - -#: builtin/pull.c parse-options.h -msgid "automatically stash/stash pop before and after" -msgstr "在操作前后执行自动贮藏和弹出贮藏" - -#: builtin/pull.c -msgid "Options related to fetching" -msgstr "和获取相关的参数" - -#: builtin/pull.c -msgid "force overwrite of local branch" -msgstr "强制覆盖本地分支" - -#: builtin/pull.c -msgid "number of submodules pulled in parallel" -msgstr "并发拉取的子模组的数量" - -#: builtin/pull.c parse-options.h -msgid "use IPv4 addresses only" -msgstr "只使用 IPv4 地址" - -#: builtin/pull.c parse-options.h -msgid "use IPv6 addresses only" -msgstr "只使用 IPv6 地址" - -#: builtin/pull.c msgid "" "There is no candidate for rebasing against among the refs that you just " "fetched." @@ -13025,8 +13056,52 @@ " git config pull.ff only # 仅快进\n" "\n" "您可以将 \"git config\" 替换为 \"git config --global\" 以便为所有仓库设置\n" -"缺省的配置项。您也可以在每次执行 pull 命令时添加 --rebase、--no-rebase,\n" -"或者 --ff-only 参数覆盖缺省设置。\n" +"默认的配置项。您也可以在每次执行 pull 命令时添加 --rebase、--no-rebase,\n" +"或者 --ff-only 参数覆盖默认设置。\n" + +#: builtin/pull.c +msgid "control for recursive fetching of submodules" +msgstr "控制子模组的递归获取" + +#: builtin/pull.c +msgid "Options related to merging" +msgstr "和合并相关的选项" + +#: builtin/pull.c +msgid "incorporate changes by rebasing rather than merging" +msgstr "使用变基操作取代合并操作以合入修改" + +#: builtin/pull.c builtin/revert.c +msgid "allow fast-forward" +msgstr "允许快进" + +#: builtin/pull.c +msgid "control use of pre-merge-commit and commit-msg hooks" +msgstr "控制 pre-merge-commit 和 commit-msg 钩子的使用" + +#: builtin/pull.c parse-options.h +msgid "automatically stash/stash pop before and after" +msgstr "在操作前后执行自动储藏和弹出储藏" + +#: builtin/pull.c +msgid "Options related to fetching" +msgstr "和获取相关的参数" + +#: builtin/pull.c +msgid "force overwrite of local branch" +msgstr "强制覆盖本地分支" + +#: builtin/pull.c +msgid "number of submodules pulled in parallel" +msgstr "并发拉取的子模组的数量" + +#: builtin/pull.c parse-options.h +msgid "use IPv4 addresses only" +msgstr "只使用 IPv4 地址" + +#: builtin/pull.c parse-options.h +msgid "use IPv6 addresses only" +msgstr "只使用 IPv6 地址" #: builtin/pull.c msgid "Updating an unborn branch with changes added to the index." @@ -13038,7 +13113,7 @@ #: builtin/pull.c builtin/rebase.c msgid "Please commit or stash them." -msgstr "请提交或贮藏修改。" +msgstr "请提交或储藏修改。" #: builtin/pull.c #, c-format @@ -13085,7 +13160,7 @@ #: builtin/pull.c msgid "cannot rebase with locally recorded submodule modifications" -msgstr "本地子模组中有修改,无法变基" +msgstr "本地子模组中有修改,不能变基" #: builtin/push.c msgid "git push [<options>] [<repository> [<refspec>...]]" @@ -13300,7 +13375,7 @@ #: builtin/push.c msgid "push tags (can't be used with --all or --branches or --mirror)" -msgstr "推送标签(不能使用 --all or --branches or --mirror)" +msgstr "推送标签(不能与 --all、--branches 或 --mirror 同时使用)" #: builtin/push.c builtin/send-pack.c msgid "force updates" @@ -13361,7 +13436,7 @@ #: builtin/push.c t/helper/test-bundle-uri.c #, c-format msgid "bad repository '%s'" -msgstr "坏的仓库 '%s'" +msgstr "错误的仓库 '%s'" #: builtin/push.c msgid "" @@ -13489,11 +13564,11 @@ #: builtin/read-tree.c msgid "3-way merge if no file level merging required" -msgstr "如果没有文件级合并需要,执行三方合并" +msgstr "如果没有文件级合并需要,执行三路合并" #: builtin/read-tree.c msgid "3-way merge in presence of adds and removes" -msgstr "存在添加和删除时,也执行三方合并" +msgstr "存在添加和删除时,也执行三路合并" #: builtin/read-tree.c msgid "same as -m, but discard unmerged entries" @@ -13517,7 +13592,7 @@ #: builtin/read-tree.c msgid "allow explicitly ignored files to be overwritten" -msgstr "允许忽略文件中设定的文件可以被覆盖" +msgstr "允许显式忽略的文件被覆盖" #: builtin/read-tree.c msgid "don't check the working tree after merging" @@ -13560,7 +13635,7 @@ #: builtin/rebase.c sequencer.c #, c-format msgid "could not read '%s'." -msgstr "不能读取 '%s'。" +msgstr "无法读取 '%s'。" #: builtin/rebase.c #, c-format @@ -13701,7 +13776,7 @@ #: builtin/rebase.c msgid "use the merge-base of upstream and branch as the current base" -msgstr "使用上游和分支的合并基线做为当前基线" +msgstr "使用上游和分支的合并基线作为当前基线" #: builtin/rebase.c msgid "allow pre-rebase hook to run" @@ -13877,7 +13952,7 @@ #: builtin/rebase.c msgid "Cannot read HEAD" -msgstr "不能读取 HEAD" +msgstr "无法读取 HEAD" #: builtin/rebase.c msgid "" @@ -13947,7 +14022,7 @@ #: builtin/rebase.c msgid "Could not create new root commit" -msgstr "不能创建新的根提交" +msgstr "无法创建新的根提交" #: builtin/rebase.c #, c-format @@ -13961,7 +14036,7 @@ #: builtin/rebase.c msgid "Could not resolve HEAD to a commit" -msgstr "不能解析 HEAD 至一个提交" +msgstr "无法解析 HEAD 至一个提交" #: builtin/rebase.c #, c-format @@ -14095,7 +14170,7 @@ #: builtin/reflog.c msgid "git reflog write <ref> <old-oid> <new-oid> <message>" -msgstr "git reflog write <引用> <旧oid> <新oid> <消息>" +msgstr "git reflog write <引用> <旧OID> <新OID> <消息>" #: builtin/reflog.c msgid "" @@ -14233,7 +14308,7 @@ #: builtin/reflog.c #, c-format msgid "cannot start transaction: %s" -msgstr "不能启动事务:%s" +msgstr "无法启动事务:%s" #: builtin/reflog.c #, c-format @@ -14255,7 +14330,7 @@ #: builtin/refs.c msgid "git refs exists <ref>" -msgstr "git reflog exists <引用>" +msgstr "git refs exists <引用>" #: builtin/refs.c msgid "git refs optimize " @@ -14292,7 +14367,7 @@ #: builtin/refs.c msgid "git refs list " -msgstr "git reflog list" +msgstr "git refs list " #: builtin/refs.c msgid "'git refs exists' requires a reference" @@ -14391,7 +14466,7 @@ #: builtin/remote.c #, c-format msgid "Could not fetch %s" -msgstr "不能获取 %s" +msgstr "无法获取 %s" #: builtin/remote.c msgid "" @@ -14482,12 +14557,12 @@ #: builtin/remote.c #, c-format msgid "could not set '%s'" -msgstr "不能设置 '%s'" +msgstr "无法设置 '%s'" #: builtin/remote.c config.c #, c-format msgid "could not unset '%s'" -msgstr "不能取消设置 '%s'" +msgstr "无法取消设置 '%s'" #: builtin/remote.c #, c-format @@ -14498,7 +14573,7 @@ msgstr "" "配置(%s)remote.pushDefault 位于:\n" "\t%s:%d\n" -"现在在为不存在的远程名 '%s' 命名" +"现在指向不存在的远程名 '%s'" #: builtin/remote.c msgid "" @@ -14524,7 +14599,7 @@ #: builtin/remote.c #, c-format msgid "Could not rename config section '%s' to '%s'" -msgstr "不能重命名配置小节 '%s' 到 '%s'" +msgstr "无法重命名配置小节 '%s' 到 '%s'" #: builtin/remote.c msgid "Renaming remote references" @@ -14564,7 +14639,7 @@ #: builtin/remote.c #, c-format msgid "Could not remove config section '%s'" -msgstr "不能移除配置小节 '%s'" +msgstr "无法移除配置小节 '%s'" #: builtin/remote.c #, c-format @@ -14590,7 +14665,7 @@ #: builtin/remote.c #, c-format msgid "invalid branch.%s.merge; cannot rebase onto > 1 branch" -msgstr "无效的 branch.%s.merge,不能变基到一个以上的分支" +msgstr "无效的 branch.%s.merge,无法变基到一个以上的分支" #: builtin/remote.c #, c-format @@ -14620,7 +14695,7 @@ #: builtin/remote.c #, c-format msgid "%-*s and with remote %s\n" -msgstr "%-*s 以及和远程 %s\n" +msgstr "%-*s 以及与远程 %s\n" #: builtin/remote.c msgid "create" @@ -14793,7 +14868,7 @@ #: builtin/remote.c #, c-format msgid "Could not set up %s" -msgstr "不能设置 %s" +msgstr "无法设置 %s" #: builtin/remote.c #, c-format @@ -14842,7 +14917,7 @@ #: builtin/remote.c msgid "manipulate push URLs" -msgstr "操作推送 URLS" +msgstr "管理推送 URL" #: builtin/remote.c msgid "add URL" @@ -14850,7 +14925,7 @@ #: builtin/remote.c msgid "delete URLs" -msgstr "删除 URLS" +msgstr "删除 URL" #: builtin/remote.c msgid "--add --delete doesn't make sense" @@ -14859,7 +14934,7 @@ #: builtin/remote.c #, c-format msgid "Invalid old URL pattern: %s" -msgstr "无效的旧 URL 匹配模版:%s" +msgstr "无效的旧 URL 匹配模板:%s" #: builtin/remote.c #, c-format @@ -14955,7 +15030,7 @@ #: builtin/repack.c msgid "with -A, do not loosen objects older than this" -msgstr "使用 -A,不要将早于给定时间的对象过期" +msgstr "使用 -A,不要将早于给定时间的对象变为松散对象" #: builtin/repack.c msgid "with -a, repack unreachable objects" @@ -14995,7 +15070,7 @@ #: builtin/repack.c msgid "find a geometric progression with factor <N>" -msgstr "使用因子 <n> 查找几何级数" +msgstr "使用因子 <N> 查找几何级数" #: builtin/repack.c msgid "write a multi-pack index of the resulting packs" @@ -15165,12 +15240,12 @@ #: builtin/replace.c #, c-format msgid "could not write replacement commit for: '%s'" -msgstr "不能为 '%s' 写替换提交" +msgstr "无法为 '%s' 写替换提交" #: builtin/replace.c #, c-format msgid "graft for '%s' unnecessary" -msgstr "对 '%s' 移植没有必要" +msgstr "对 '%s' 嫁接没有必要" #: builtin/replace.c #, c-format @@ -15183,7 +15258,7 @@ "could not convert the following graft(s):\n" "%s" msgstr "" -"不能转换下列移植:\n" +"无法转换下列嫁接:\n" "%s" #: builtin/replace.c @@ -15204,7 +15279,7 @@ #: builtin/replace.c msgid "convert existing graft file" -msgstr "转换现存的移植文件" +msgstr "转换现存的嫁接文件" #: builtin/replace.c msgid "replace the ref if it exists" @@ -15255,6 +15330,11 @@ msgstr "只能为 -l 提供一个模式" #: builtin/replay.c +#, c-format +msgid "'%s' is not a valid commit-ish for %s" +msgstr "'%s' 不是 %s 的有效提交号" + +#: builtin/replay.c msgid "need some commits to replay" msgstr "需要一些提交来重放" @@ -15270,30 +15350,20 @@ msgid "" "cannot advance target with multiple sources because ordering would be ill-" "defined" -msgstr "不能使用多个源推进目标,因为无法明确如何排序" +msgstr "无法使用多个源推进目标,因为无法明确如何排序" #: builtin/replay.c -msgid "" -"cannot implicitly determine whether this is an --advance or --onto operation" -msgstr "不能隐式地确定这是 --advance 还是 --onto 的操作" - -#: builtin/replay.c -msgid "" -"cannot advance target with multiple source branches because ordering would " -"be ill-defined" -msgstr "不能使用多个源分支推进目标,因为无法明确如何排序" - -#: builtin/replay.c -msgid "cannot implicitly determine correct base for --onto" -msgstr "不能隐式地确定 --onto 正确的基线" +#, c-format +msgid "invalid %s value: '%s'" +msgstr "无效的 %s 值:'%s'" #: builtin/replay.c msgid "" "(EXPERIMENTAL!) git replay ([--contained] --onto <newbase> | --advance " -"<branch>) <revision-range>..." +"<branch>) [--ref-action[=<mode>]] <revision-range>" msgstr "" -"(试验中!)git replay ([--contained] --onto <新基线> | --advance <分支>) <版" -"本范围>..." +"(试验中!)git replay ([--contained] --onto <新基线> | --advance <分支>) [--" +"ref-action[=<模式>]] <版本范围>" #: builtin/replay.c msgid "make replay advance given branch" @@ -15304,8 +15374,12 @@ msgstr "重放到给定提交" #: builtin/replay.c -msgid "advance all branches contained in revision-range" -msgstr "演进版本范围中包含的所有分支" +msgid "update all branches that point at commits in <revision-range>" +msgstr "更新指向 <版本范围> 中提交的所有分支" + +#: builtin/replay.c +msgid "control ref update behavior (update|print)" +msgstr "控制引用更新行为(update|print)" #: builtin/replay.c msgid "option --onto or --advance is mandatory" @@ -15320,16 +15394,31 @@ "一些版本遍历选项将被覆盖,如 'struct rev_info' 中的 '%s' 位将被强制设定" #: builtin/replay.c +#, c-format +msgid "failed to begin ref transaction: %s" +msgstr "无法开始引用事务:%s" + +#: builtin/replay.c msgid "error preparing revisions" msgstr "准备版本时错误" #: builtin/replay.c -msgid "replaying down to root commit is not supported yet!" -msgstr "目前还不支持重放到根提交!" +msgid "replaying down from root commit is not supported yet!" +msgstr "目前还不支持从根提交向下重放!" #: builtin/replay.c msgid "replaying merge commits is not supported yet!" -msgstr "目前还不支持重放到合并提交!" +msgstr "目前还不支持重放合并提交!" + +#: builtin/replay.c +#, c-format +msgid "failed to update ref '%s': %s" +msgstr "无法更新引用 '%s':%s" + +#: builtin/replay.c +#, c-format +msgid "failed to commit ref transaction: %s" +msgstr "无法提交引用事务:%s" #: builtin/repo.c #, c-format @@ -15350,10 +15439,18 @@ msgstr "和 --format=nul 同义" #: builtin/repo.c +msgid "print all keys/values" +msgstr "打印所有键/值" + +#: builtin/repo.c msgid "unsupported output format" msgstr "不支持的输出格式" #: builtin/repo.c +msgid "--all and <key> cannot be used together" +msgstr "--all 不能和 <key> 一起使用" + +#: builtin/repo.c msgid "References" msgstr "引用" @@ -15375,7 +15472,7 @@ #: builtin/repo.c msgid "Others" -msgstr "其它" +msgstr "其他" #: builtin/repo.c msgid "Reachable objects" @@ -15394,6 +15491,14 @@ msgstr "数据对象" #: builtin/repo.c +msgid "Inflated size" +msgstr "展开后大小" + +#: builtin/repo.c +msgid "Disk size" +msgstr "磁盘大小" + +#: builtin/repo.c msgid "Repository structure" msgstr "仓库结构" @@ -15431,7 +15536,7 @@ #: builtin/reset.c msgid "git reset [-q] [<tree-ish>] [--] <pathspec>..." -msgstr "git reset [-q] [<树对象>] [--] <路径表达式>..." +msgstr "git reset [-q] [<树对象>] [--] <路径规格>..." #: builtin/reset.c msgid "" @@ -15440,7 +15545,7 @@ #: builtin/reset.c msgid "git reset --patch [<tree-ish>] [--] [<pathspec>...]" -msgstr "git reset --patch [<树对象>] [--] [<路径表达式>...]" +msgstr "git reset --patch [<树对象>] [--] [<路径规格>...]" #: builtin/reset.c msgid "mixed" @@ -15532,13 +15637,13 @@ #: builtin/reset.c #, c-format msgid "Cannot do %s reset with paths." -msgstr "不能带路径进行%s重置。" +msgstr "无法带路径进行%s重置。" # 译者:汉字之间无空格,故删除%s前后空格 #: builtin/reset.c #, c-format msgid "%s reset is not allowed in a bare repository" -msgstr "不能对纯仓库进行%s重置" +msgstr "不允许对纯仓库进行%s重置" #: builtin/reset.c msgid "Unstaged changes after reset:" @@ -15554,11 +15659,11 @@ #: builtin/reset.c #, c-format msgid "Could not reset index file to revision '%s'." -msgstr "不能重置索引文件至版本 '%s'。" +msgstr "无法重置索引文件至版本 '%s'。" #: builtin/reset.c msgid "Could not write new index file." -msgstr "不能写入新的索引文件。" +msgstr "无法写入新的索引文件。" #: builtin/rev-list.c #, c-format @@ -15636,7 +15741,7 @@ #: builtin/rev-parse.c #, c-format msgid "not a gitdir '%s'" -msgstr "不是一个git目录 '%s'" +msgstr "不是一个 git 目录 '%s'" #: builtin/rev-parse.c msgid "--git-path requires an argument" @@ -15683,7 +15788,7 @@ #: builtin/rev-parse.c msgid "Could not read the index" -msgstr "不能读取索引" +msgstr "无法读取索引" #: builtin/rev-parse.c #, c-format @@ -15725,15 +15830,15 @@ #: builtin/revert.c msgid "end revert or cherry-pick sequence" -msgstr "终止反转或拣选操作" +msgstr "结束还原或拣选操作" #: builtin/revert.c msgid "resume revert or cherry-pick sequence" -msgstr "继续反转或拣选操作" +msgstr "继续还原或拣选操作" #: builtin/revert.c msgid "cancel revert or cherry-pick sequence" -msgstr "取消反转或拣选操作" +msgstr "取消还原或拣选操作" #: builtin/revert.c msgid "skip current commit and continue" @@ -15865,7 +15970,7 @@ #: builtin/rm.c msgid "please stage your changes to .gitmodules or stash them to proceed" -msgstr "请将您的修改暂存到 .gitmodules 中或贮藏后再继续" +msgstr "请将您的修改暂存到 .gitmodules 中或储藏后再继续" #: builtin/rm.c #, c-format @@ -15875,7 +15980,7 @@ #: builtin/rm.c #, c-format msgid "git rm: unable to remove %s" -msgstr "git rm:不能删除 %s" +msgstr "git rm:无法删除 %s" #: builtin/send-pack.c msgid "" @@ -16033,11 +16138,11 @@ #: builtin/show-branch.c msgid "show possible merge bases" -msgstr "显示可能合并的基线" +msgstr "显示可能的合并基线" #: builtin/show-branch.c msgid "show refs unreachable from any other ref" -msgstr "显示没有任何引用的的引用" +msgstr "显示任何其他引用都不可达的引用" #: builtin/show-branch.c msgid "show commits in topological order" @@ -16087,8 +16192,8 @@ #, 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 个以上的版本。" #: builtin/show-branch.c #, c-format @@ -16098,7 +16203,7 @@ #: builtin/show-branch.c #, c-format msgid "cannot find commit %s (%s)" -msgstr "不能找到提交 %s(%s)" +msgstr "无法找到提交 %s(%s)" #: builtin/show-index.c msgid "hash-algorithm" @@ -16158,7 +16263,7 @@ #: builtin/show-ref.c msgid "dereference tags into object IDs" -msgstr "转换标签到对象 ID" +msgstr "将标签解引用为对象 ID" #: builtin/show-ref.c msgid "only show SHA1 hash using <n> digits" @@ -16207,7 +16312,7 @@ #: builtin/sparse-checkout.c #, c-format msgid "unable to fdopen %s" -msgstr "不能 fdopen %s" +msgstr "无法 fdopen %s" #: builtin/sparse-checkout.c msgid "failed to initialize worktree config" @@ -16363,11 +16468,11 @@ #: builtin/sparse-checkout.c msgid "when used with --rules-file interpret patterns as cone mode patterns" -msgstr "通过 --rules-file 选项传递的模型将被作为锥形(稀疏检出模型)进行解析" +msgstr "通过 --rules-file 选项传递的模式将被作为锥形模式(稀疏检出)进行解析" #: builtin/sparse-checkout.c msgid "use patterns in <file> instead of the current ones." -msgstr "从 <文件> 参数中读取模式,而不是从标准输入" +msgstr "使用 <文件> 中的模式替代当前模式。" #: builtin/stash.c msgid "git stash list [<log-options>]" @@ -16379,23 +16484,23 @@ "options>] [<stash>]" msgstr "" "git stash show [-u | --include-untracked | --only-untracked] [<差异选项>] [<" -"贮存>]" +"储藏>]" #: builtin/stash.c msgid "git stash drop [-q | --quiet] [<stash>]" -msgstr "git stash drop [-q | --quiet] [<贮存>]" +msgstr "git stash drop [-q | --quiet] [<储藏>]" #: builtin/stash.c msgid "git stash pop [--index] [-q | --quiet] [<stash>]" -msgstr "git stash pop [--index] [-q | --quiet] [<贮存>]" +msgstr "git stash pop [--index] [-q | --quiet] [<储藏>]" #: builtin/stash.c msgid "git stash apply [--index] [-q | --quiet] [<stash>]" -msgstr "git stash apply [--index] [-q | --quiet] [<贮存>]" +msgstr "git stash apply [--index] [-q | --quiet] [<储藏>]" #: builtin/stash.c msgid "git stash branch <branchname> [<stash>]" -msgstr "git stash branch <分支名> [<stash>]" +msgstr "git stash branch <分支名> [<储藏>]" #: builtin/stash.c msgid "git stash store [(-m | --message) <message>] [-q | --quiet] <commit>" @@ -16412,7 +16517,7 @@ msgstr "" "git stash [push [-p | --patch] [-S | --staged] [-k | --[no-]keep-index] [-q " "| --quiet]\n" -" [-u | --include-untracked] [-a | --all] [(-m | --message <消息>]\n" +" [-u | --include-untracked] [-a | --all] [(-m | --message) <消息>]\n" " [--pathspec-from-file=<文件> [--pathspec-file-nul]]\n" " [--] [<路径规格>...]]" @@ -16432,7 +16537,7 @@ #: builtin/stash.c msgid "git stash export (--print | --to-ref <ref>) [<stash>...]" -msgstr "git stash export (--print | --to-ref <引用>) [<贮藏>...]" +msgstr "git stash export (--print | --to-ref <引用>) [<储藏>...]" #: builtin/stash.c msgid "git stash import <commit>" @@ -16441,11 +16546,11 @@ #: builtin/stash.c #, c-format msgid "'%s' is not a stash-like commit" -msgstr "'%s' 不像是一个贮藏提交" +msgstr "'%s' 不像是一个储藏提交" #: builtin/stash.c msgid "No stash entries found." -msgstr "未发现贮藏条目。" +msgstr "未发现储藏条目。" #: builtin/stash.c #, c-format @@ -16474,7 +16579,7 @@ #: builtin/stash.c msgid "cannot apply a stash in the middle of a merge" -msgstr "无法在合并过程中应用贮藏" +msgstr "无法在合并过程中应用储藏" #: builtin/stash.c #, c-format @@ -16487,7 +16592,7 @@ #: builtin/stash.c msgid "could not save index tree" -msgstr "不能保存索引树" +msgstr "无法保存索引树" #: builtin/stash.c #, c-format @@ -16496,11 +16601,11 @@ #: builtin/stash.c msgid "Index was not unstashed." -msgstr "索引未从贮藏中恢复。" +msgstr "索引未从储藏中恢复。" #: builtin/stash.c msgid "could not restore untracked files from stash" -msgstr "无法从贮藏条目中恢复未跟踪文件" +msgstr "无法从储藏条目中恢复未跟踪文件" #: builtin/stash.c msgid "attempt to recreate the index" @@ -16514,16 +16619,16 @@ #: builtin/stash.c #, c-format msgid "%s: Could not drop stash entry" -msgstr "%s:无法丢弃贮藏条目" +msgstr "%s:无法丢弃储藏条目" #: builtin/stash.c #, c-format msgid "'%s' is not a stash reference" -msgstr "'%s' 不是一个贮藏引用" +msgstr "'%s' 不是一个储藏引用" #: builtin/stash.c msgid "The stash entry is kept in case you need it again." -msgstr "贮藏条目被保留以备您再次需要。" +msgstr "储藏条目被保留以备您再次需要。" #: builtin/stash.c msgid "No branch name specified" @@ -16539,11 +16644,11 @@ #: builtin/stash.c msgid "include untracked files in the stash" -msgstr "在贮藏中包含未跟踪文件" +msgstr "在储藏中包含未跟踪文件" #: builtin/stash.c msgid "only show untracked files in the stash" -msgstr "仅显示贮藏中的未跟踪文件" +msgstr "仅显示储藏中的未跟踪文件" #: builtin/stash.c #, c-format @@ -16552,7 +16657,7 @@ #: builtin/stash.c msgid "stash message" -msgstr "贮藏说明" +msgstr "储藏说明" #: builtin/stash.c msgid "\"git stash store\" requires one <commit> argument" @@ -16588,7 +16693,7 @@ #: builtin/stash.c msgid "Cannot record working tree state" -msgstr "不能记录工作区状态" +msgstr "无法记录工作区状态" #: builtin/stash.c msgid "Can't use --patch and --include-untracked or --all at the same time" @@ -16608,7 +16713,7 @@ #: builtin/stash.c msgid "Cannot initialize stash" -msgstr "无法初始化贮藏" +msgstr "无法初始化储藏" #: builtin/stash.c msgid "Cannot save the current status" @@ -16629,11 +16734,11 @@ #: builtin/stash.c msgid "stash staged changes only" -msgstr "只贮藏暂存的变更" +msgstr "只储藏暂存的变更" #: builtin/stash.c msgid "stash in patch mode" -msgstr "以补丁模式贮藏" +msgstr "以补丁模式储藏" #: builtin/stash.c msgid "quiet mode" @@ -16641,7 +16746,7 @@ #: builtin/stash.c msgid "include untracked files in stash" -msgstr "贮藏中包含未跟踪文件" +msgstr "储藏中包含未跟踪文件" #: builtin/stash.c msgid "include ignore files" @@ -16650,7 +16755,7 @@ #: builtin/stash.c #, c-format msgid "cannot parse commit %s" -msgstr "不能解析提交 %s" +msgstr "无法解析提交 %s" #: builtin/stash.c #, c-format @@ -16674,7 +16779,7 @@ #: builtin/stash.c #, c-format msgid "%s is not a valid exported stash commit" -msgstr "%s 不是一个有效的且已导出的贮藏提交" +msgstr "%s 不是一个有效的且已导出的储藏提交" #: builtin/stash.c #, c-format @@ -16684,7 +16789,7 @@ #: builtin/stash.c #, c-format msgid "found stash commit %s without expected prefix" -msgstr "发现贮藏提交 %s 缺少预期前缀" +msgstr "发现储藏提交 %s 缺少预期前缀" #: builtin/stash.c #, c-format @@ -16694,7 +16799,7 @@ #: builtin/stash.c #, c-format msgid "%s does not look like a stash commit" -msgstr "%s 看起来不像是一个贮藏提交" +msgstr "%s 看起来不像是一个储藏提交" #: builtin/stash.c #, c-format @@ -16704,7 +16809,7 @@ #: builtin/stash.c #, c-format msgid "cannot save the stash for %s" -msgstr "无法保存 %s 的贮藏" +msgstr "无法保存 %s 的储藏" #: builtin/stash.c msgid "unable to write base commit" @@ -16713,7 +16818,7 @@ #: builtin/stash.c #, c-format msgid "unable to find stash entry %s" -msgstr "无法找到贮藏条目 %s" +msgstr "无法找到储藏条目 %s" #: builtin/stash.c msgid "print the object ID instead of writing it to a ref" @@ -16863,7 +16968,7 @@ #: builtin/submodule--helper.c #, c-format msgid "couldn't hash object from '%s'" -msgstr "不能从 '%s' 创建哈希对象" +msgstr "无法从 '%s' 创建哈希对象" #: builtin/submodule--helper.c #, c-format @@ -16892,7 +16997,7 @@ #: builtin/submodule--helper.c msgid "could not fetch a revision for HEAD" -msgstr "不能为 HEAD 获取一个版本" +msgstr "无法为 HEAD 获取一个版本" #: builtin/submodule--helper.c #, c-format @@ -16946,7 +17051,7 @@ #: builtin/submodule--helper.c #, c-format msgid "could not create empty submodule directory %s" -msgstr "不能创建空的子模组目录 %s" +msgstr "无法创建空的子模组目录 %s" #: builtin/submodule--helper.c #, c-format @@ -16991,17 +17096,17 @@ #: builtin/submodule--helper.c #, c-format msgid "submodule '%s' cannot add alternate: %s" -msgstr "子模组 '%s' 不能添加仓库备选:%s" +msgstr "子模组 '%s' 无法添加仓库备选:%s" #: builtin/submodule--helper.c #, c-format msgid "Value '%s' for submodule.alternateErrorStrategy is not recognized" -msgstr "不能识别 submodule.alternateErrorStrategy 的取值 '%s'" +msgstr "无法识别 submodule.alternateErrorStrategy 的取值 '%s'" #: builtin/submodule--helper.c #, c-format msgid "Value '%s' for submodule.alternateLocation is not recognized" -msgstr "不能识别 submodule.alternateLocation 的取值 '%s'" +msgstr "无法识别 submodule.alternateLocation 的取值 '%s'" #: builtin/submodule--helper.c submodule.c #, c-format @@ -17378,7 +17483,7 @@ msgid "'%s' already exists in the index and is not a submodule" msgstr "'%s' 已经存在于索引中且不是一个子模组" -#: builtin/submodule--helper.c read-cache.c +#: builtin/submodule--helper.c object-file.c #, c-format msgid "'%s' does not have a commit checked out" msgstr "'%s' 没有检出一个提交" @@ -17550,7 +17655,7 @@ #: builtin/tag.c msgid "bad object type." -msgstr "坏的对象类型。" +msgstr "错误的对象类型。" #: builtin/tag.c msgid "no tag message?" @@ -17913,7 +18018,7 @@ #: builtin/update-index.c msgid "Untracked cache disabled" -msgstr "缓存未跟踪文件被禁用" +msgstr "未跟踪文件缓存已禁用" #: builtin/update-index.c msgid "" @@ -17926,7 +18031,7 @@ #: builtin/update-index.c #, c-format msgid "Untracked cache enabled for '%s'" -msgstr "缓存未跟踪文件在 '%s' 启用" +msgstr "已为 '%s' 启用未跟踪文件缓存" #: builtin/update-index.c msgid "core.fsmonitor is unset; set it if you really want to enable fsmonitor" @@ -18110,7 +18215,7 @@ #: builtin/worktree.c #, c-format msgid "Removing %s/%s: %s" -msgstr "删除 %s/%s: %s" +msgstr "删除 %s/%s:%s" #: builtin/worktree.c msgid "report pruned working trees" @@ -18166,7 +18271,7 @@ #: builtin/worktree.c #, c-format msgid "could not create directory of '%s'" -msgstr "不能创建目录 '%s'" +msgstr "无法创建目录 '%s'" #: builtin/worktree.c msgid "initializing" @@ -18204,7 +18309,7 @@ "HEAD path: '%s'\n" "HEAD contents: '%s'" msgstr "" -"HEAD 指向了一个无用的(或孤立的)引用。\n" +"HEAD 指向了一个无效的(或孤立的)引用。\n" "HEAD 路径: '%s'\n" "HEAD 内容: '%s'" @@ -18218,7 +18323,7 @@ #: builtin/worktree.c msgid "checkout <branch> even if already checked out in other worktree" -msgstr "检出 <分支>,即使已经被检出到其它工作区" +msgstr "检出 <分支>,即使已经被检出到其他工作区" #: builtin/worktree.c msgid "create a new branch" @@ -18280,7 +18385,7 @@ #: builtin/worktree.c msgid "add 'prunable' annotation to worktrees older than <time>" -msgstr "向早于 <时间> 的工作区添添加“可修剪”注释" +msgstr "向早于 <时间> 的工作区添加“可修剪”注释" #: builtin/worktree.c msgid "terminate records with a NUL character" @@ -18312,7 +18417,7 @@ #: builtin/worktree.c msgid "working trees containing submodules cannot be moved or removed" -msgstr "不能移动或删除包含子模组的工作区" +msgstr "无法移动或删除包含子模组的工作区" #: builtin/worktree.c msgid "force move even if worktree is dirty or locked" @@ -18425,12 +18530,17 @@ #: bundle-uri.c #, c-format msgid "could not parse bundle list key %s with value '%s'" -msgstr "不能解析归档包列表键 %s 的值 '%s'" +msgstr "无法解析捆绑包列表键 %s 的值 '%s'" #: bundle-uri.c #, c-format msgid "bundle list at '%s' has no mode" -msgstr "在 '%s' 的归档包列表没有模式" +msgstr "在 '%s' 的捆绑包列表没有模式" + +#: bundle-uri.c +#, c-format +msgid "bundle list at '%s': bundle '%s' has no uri" +msgstr "在 '%s' 的捆绑包列表中,捆绑包 '%s' 没有 URI" #: bundle-uri.c msgid "failed to create temporary file" @@ -18438,12 +18548,12 @@ #: bundle-uri.c msgid "insufficient capabilities" -msgstr "缺乏能力" +msgstr "能力不足" #: bundle-uri.c #, c-format msgid "file downloaded from '%s' is not a bundle" -msgstr "下载自 '%s' 的文件不是归档包" +msgstr "下载自 '%s' 的文件不是捆绑包" #: bundle-uri.c msgid "failed to store maximum creation token" @@ -18452,7 +18562,7 @@ #: bundle-uri.c #, c-format msgid "unrecognized bundle mode from URI '%s'" -msgstr "不可辨认的归档包模式来自 URI '%s'" +msgstr "不可辨认的捆绑包模式来自 URI '%s'" #: bundle-uri.c #, c-format @@ -18461,39 +18571,44 @@ #: bundle-uri.c #, c-format +msgid "bundle '%s' has no uri" +msgstr "捆绑包 '%s' 没有 URI" + +#: bundle-uri.c +#, c-format msgid "failed to download bundle from URI '%s'" -msgstr "无法从 URI '%s' 下载归档包" +msgstr "无法从 URI '%s' 下载捆绑包" #: bundle-uri.c #, c-format msgid "file at URI '%s' is not a bundle or bundle list" -msgstr "位于 URI '%s' 的文件不是归档包或归档包列表" +msgstr "位于 URI '%s' 的文件不是捆绑包或捆绑包列表" #: bundle-uri.c #, c-format msgid "bundle-uri: unexpected argument: '%s'" -msgstr "bundle-uri: 意外的参数:'%s'" +msgstr "bundle-uri:意外的参数:'%s'" #: bundle-uri.c msgid "bundle-uri: expected flush after arguments" -msgstr "bundle-uri: 在参数之后应有一个 flush" +msgstr "bundle-uri:在参数之后应有一个 flush" #: bundle-uri.c msgid "bundle-uri: got an empty line" -msgstr "bundle-uri: 获得了空行" +msgstr "bundle-uri:获得了空行" #: bundle-uri.c msgid "bundle-uri: line is not of the form 'key=value'" -msgstr "bundle-uri: 行不是以 'key=value' 格式" +msgstr "bundle-uri:行不是 'key=value' 格式" #: bundle-uri.c msgid "bundle-uri: line has empty key or value" -msgstr "bundle-uri: 行有空的键或值" +msgstr "bundle-uri:行有空的键或值" #: bundle.c #, c-format msgid "unrecognized bundle hash algorithm: %s" -msgstr "未能识别的归档包哈希算法:%s" +msgstr "未能识别的捆绑包哈希算法:%s" #: bundle.c #, c-format @@ -18503,7 +18618,7 @@ #: bundle.c #, c-format msgid "'%s' does not look like a v2 or v3 bundle file" -msgstr "'%s' 不像是一个 v2 或 v3 版本的归档包文件" +msgstr "'%s' 不像是一个 v2 或 v3 版本的捆绑包文件" #: bundle.c #, c-format @@ -18524,37 +18639,37 @@ #, c-format msgid "The bundle contains this ref:" msgid_plural "The bundle contains these %<PRIuMAX> refs:" -msgstr[0] "这个归档包中含有这个引用:" -msgstr[1] "这个归档包中含有 %<PRIuMAX> 个引用:" +msgstr[0] "这个捆绑包中含有这个引用:" +msgstr[1] "这个捆绑包中含有 %<PRIuMAX> 个引用:" #: bundle.c msgid "The bundle records a complete history." -msgstr "这个归档包记录一个完整历史。" +msgstr "这个捆绑包记录一个完整历史。" #: bundle.c #, c-format msgid "The bundle requires this ref:" msgid_plural "The bundle requires these %<PRIuMAX> refs:" -msgstr[0] "这个归档包需要这个引用:" -msgstr[1] "这个归档包需要 %<PRIuMAX> 个引用:" +msgstr[0] "这个捆绑包需要这个引用:" +msgstr[1] "这个捆绑包需要 %<PRIuMAX> 个引用:" #: bundle.c #, c-format msgid "The bundle uses this hash algorithm: %s" -msgstr "该归档包使用的哈希算法:%s" +msgstr "该捆绑包使用的哈希算法:%s" #: bundle.c #, c-format msgid "The bundle uses this filter: %s" -msgstr "归档包使用了过滤器:%s" +msgstr "捆绑包使用了过滤器:%s" #: bundle.c msgid "unable to dup bundle descriptor" -msgstr "无法复制归档包描述符" +msgstr "无法复制捆绑包描述符" #: bundle.c msgid "Could not spawn pack-objects" -msgstr "不能生成 pack-objects 进程" +msgstr "无法生成 pack-objects 进程" #: bundle.c msgid "pack-objects died" @@ -18568,21 +18683,21 @@ #: bundle.c #, c-format msgid "unsupported bundle version %d" -msgstr "不支持的归档包版本 %d" +msgstr "不支持的捆绑包版本 %d" #: bundle.c #, c-format msgid "cannot write bundle version %d with algorithm %s" -msgstr "不能写入,归档包版本 %d 不支持算法 %s" +msgstr "无法写入,捆绑包版本 %d 不支持算法 %s" #: bundle.c msgid "Refusing to create empty bundle." -msgstr "不能创建空的归档包。" +msgstr "无法创建空的捆绑包。" #: bundle.c #, c-format msgid "cannot create '%s'" -msgstr "不能创建 '%s'" +msgstr "无法创建 '%s'" #: bundle.c msgid "index-pack died" @@ -18719,7 +18834,7 @@ #: command-list.h msgid "Copy files from the index to the working tree" -msgstr "从索引拷贝文件到工作区" +msgstr "从索引复制文件到工作区" #: command-list.h msgid "Find commits yet to be applied to upstream" @@ -18851,7 +18966,7 @@ #: command-list.h msgid "Output information on each ref" -msgstr "对每一个引用输出信息 " +msgstr "对每一个引用输出信息" #: command-list.h msgid "Run a Git command on a list of repositories" @@ -18903,11 +19018,11 @@ #: command-list.h msgid "Push objects over HTTP/DAV to another repository" -msgstr "通过 HTTP/DAV 推送对象另一个仓库" +msgstr "通过 HTTP/DAV 推送对象到另一个仓库" #: command-list.h msgid "Send a collection of patches from stdin to an IMAP folder" -msgstr "从标准输入将一组补丁发送到IMAP文件夹" +msgstr "从标准输入将一组补丁发送到 IMAP 文件夹" #: command-list.h msgid "Build pack index file for an existing packed archive" @@ -18975,7 +19090,7 @@ #: command-list.h msgid "The standard helper program to use with git-merge-index" -msgstr "与 git-merge-index 一起使用的标准向导程序" +msgstr "与 git-merge-index 一起使用的标准辅助程序" #: command-list.h msgid "Perform merge without touching index or working tree" @@ -19008,7 +19123,7 @@ #: command-list.h msgid "Add or inspect object notes" -msgstr "添加或检查对象注释" +msgstr "添加或检查对象注解" #: command-list.h msgid "Import from and submit to Perforce repositories" @@ -19024,7 +19139,7 @@ #: command-list.h msgid "Pack heads and tags for efficient repository access" -msgstr "打包头和标签以实现高效的仓库访问" +msgstr "打包分支和标签以实现高效的仓库访问" #: command-list.h msgid "Compute unique ID for a patch" @@ -19048,7 +19163,7 @@ #: command-list.h msgid "Applies a quilt patchset onto the current branch" -msgstr "将一个 quilt 补丁集应用到当前分支。" +msgstr "将一个 quilt 补丁集应用到当前分支" #: command-list.h msgid "Compare two commit ranges (e.g. two versions of a branch)" @@ -19076,7 +19191,7 @@ #: command-list.h msgid "Manage set of tracked repositories" -msgstr "管理已跟踪仓库" +msgstr "管理已跟踪的仓库集合" #: command-list.h msgid "Pack unpacked objects in a repository" @@ -19088,7 +19203,7 @@ #: command-list.h msgid "EXPERIMENTAL: Replay commits on a new base, works with bare repos too" -msgstr "试验中:基于一个新基线重放提交,同样适用于纯仓库" +msgstr "试验中:在新基线上重放提交,也适用于纯仓库" #: command-list.h msgid "Retrieve information about the repository" @@ -19096,15 +19211,15 @@ #: command-list.h msgid "Generates a summary of pending changes" -msgstr "生成待定更改的摘要" +msgstr "生成待处理变更的摘要" #: command-list.h msgid "Reuse recorded resolution of conflicted merges" -msgstr "重用冲突合并的解决方案记录" +msgstr "重用已记录的冲突合并解决方案" #: command-list.h -msgid "Reset current HEAD to the specified state" -msgstr "重置当前 HEAD 到指定状态" +msgid "Set `HEAD` or the index to a known state" +msgstr "将 `HEAD` 或索引设置为已知状态" #: command-list.h msgid "Restore working tree files" @@ -19112,7 +19227,7 @@ #: command-list.h msgid "Lists commit objects in reverse chronological order" -msgstr "按时间顺序列出提交对象" +msgstr "按时间逆序列出提交对象" #: command-list.h msgid "Pick out and massage parameters" @@ -19120,7 +19235,7 @@ #: command-list.h msgid "Revert some existing commits" -msgstr "回退一些现存提交" +msgstr "还原一些现有提交" #: command-list.h msgid "Remove files from the working tree and from the index" @@ -19144,11 +19259,11 @@ #: command-list.h msgid "Restricted login shell for Git-only SSH access" -msgstr "只允许 Git SSH 访问的受限登录shell" +msgstr "只允许 Git SSH 访问的受限登录 shell" #: command-list.h msgid "Summarize 'git log' output" -msgstr "'git log' 输出摘要" +msgstr "汇总 'git log' 输出" #: command-list.h msgid "Show various types of objects" @@ -19177,7 +19292,7 @@ #: command-list.h msgid "Stash the changes in a dirty working directory away" -msgstr "贮藏脏工作区中的修改" +msgstr "储藏脏工作区中的修改" #: command-list.h msgid "Show the working tree status" @@ -19193,7 +19308,7 @@ #: command-list.h msgid "Bidirectional operation between a Subversion repository and Git" -msgstr "Subersion 仓库和 Git 之间的双向操作" +msgstr "Subversion 仓库和 Git 之间的双向操作" #: command-list.h msgid "Switch branches" @@ -19225,7 +19340,7 @@ #: command-list.h msgid "Update auxiliary info file to help dumb servers" -msgstr "更新辅助信息文件以帮助哑协议服务" +msgstr "更新辅助信息文件以帮助哑协议服务器" #: command-list.h msgid "Send archive back to git-archive" @@ -19237,15 +19352,15 @@ #: command-list.h msgid "Show a Git logical variable" -msgstr "显示一个Git逻辑变量" +msgstr "显示一个 Git 逻辑变量" #: command-list.h msgid "Check the GPG signature of commits" -msgstr "检查 GPG 提交签名" +msgstr "检查提交的 GPG 签名" #: command-list.h msgid "Validate packed Git archive files" -msgstr "校验打包的Git存仓文件" +msgstr "校验打包的 Git 归档文件" #: command-list.h msgid "Check the GPG signature of tags" @@ -19281,7 +19396,7 @@ #: command-list.h msgid "Providing usernames and passwords to Git" -msgstr "为 Git 提供用户名和口令" +msgstr "为 Git 提供用户名和密码" #: command-list.h msgid "Git for CVS users" @@ -19293,7 +19408,7 @@ #: command-list.h msgid "A useful minimum set of commands for Everyday Git" -msgstr "每一天 Git 的一组有用的最小命令集合" +msgstr "日常 Git 的一组有用的最小命令集合" #: command-list.h msgid "Frequently asked questions about using Git" @@ -19301,7 +19416,7 @@ #: command-list.h msgid "The bundle file format" -msgstr "归档包文件格式" +msgstr "捆绑包文件格式" #: command-list.h msgid "Chunk-based file formats" @@ -19349,11 +19464,11 @@ #: command-list.h msgid "Git namespaces" -msgstr "Git 名字空间" +msgstr "Git 命名空间" #: command-list.h msgid "Protocol v0 and v1 capabilities" -msgstr "协议 v0 和 v1 能力" +msgstr "协议 v0 和 v1 功能" #: command-list.h msgid "Things common to various protocols" @@ -19365,15 +19480,15 @@ #: command-list.h msgid "How packs are transferred over-the-wire" -msgstr "包在线路中传输的方式" +msgstr "包在网络上传输的方式" #: command-list.h msgid "Git Wire Protocol, Version 2" -msgstr "Git 传输协议,第二版" +msgstr "Git 传输协议,第 2 版" #: command-list.h msgid "Helper programs to interact with remote repositories" -msgstr "与远程仓库交互的助手程序" +msgstr "与远程仓库交互的辅助程序" #: command-list.h msgid "Git Repository Layout" @@ -19381,11 +19496,11 @@ #: command-list.h msgid "Specifying revisions and ranges for Git" -msgstr "指定 Git 的版本和版本范围" +msgstr "指定 Git 的版本和范围" #: command-list.h msgid "Mounting one repository inside another" -msgstr "将一个仓库安装到另外一个仓库中" +msgstr "将一个仓库挂载到另一个仓库中" #: command-list.h msgid "A tutorial introduction to Git" @@ -19397,7 +19512,7 @@ #: command-list.h msgid "Git web interface (web frontend to Git repositories)" -msgstr "Git web 界面(Git 仓库的 web 前端)" +msgstr "Git Web 界面(Git 仓库的 Web 前端)" #: command-list.h msgid "An overview of recommended workflows with Git" @@ -19440,7 +19555,7 @@ msgid "" "ignoring too-small changed-path chunk (%<PRIuMAX> < %<PRIuMAX>) in commit-" "graph file" -msgstr "忽略提交图文件中过小的更改路径块(%<PRIuMAX> < %<PRIuMAX>)" +msgstr "忽略提交图文件中过小的变更路径块(%<PRIuMAX> < %<PRIuMAX>)" #: commit-graph.c #, c-format @@ -19554,7 +19669,7 @@ #: commit-graph.c msgid "Computing commit changed paths Bloom filters" -msgstr "计算提交变更路径的布隆过滤器" +msgstr "正在计算提交变更路径的布隆过滤器" #: commit-graph.c msgid "Collecting referenced commits" @@ -19579,7 +19694,7 @@ #: commit-graph.c msgid "Finding commits for commit graph among packed objects" -msgstr "正在打包对象中查找提交图的提交" +msgstr "正在已打包对象中查找提交图的提交" #: commit-graph.c msgid "Finding extra edges in commit graph" @@ -19602,8 +19717,8 @@ #, c-format msgid "Writing out commit graph in %d pass" msgid_plural "Writing out commit graph in %d passes" -msgstr[0] "正在用 %d 步写出提交图" -msgstr[1] "正在用 %d 步写出提交图" +msgstr[0] "正在用 %d 次写出提交图" +msgstr[1] "正在用 %d 次写出提交图" #: commit-graph.c msgid "unable to open commit-graph chain file" @@ -19625,7 +19740,7 @@ #: commit-graph.c #, c-format msgid "cannot merge graph %s, too many commits: %<PRIuMAX>" -msgstr "无法合并提交图 %s, 提交过多:%<PRIuMAX>" +msgstr "无法合并提交图 %s,提交过多:%<PRIuMAX>" #: commit-graph.c msgid "Scanning merged commits" @@ -19637,7 +19752,7 @@ #: commit-graph.c msgid "attempting to write a commit-graph, but 'core.commitGraph' is disabled" -msgstr "正尝试写提交图,但是 'core.commitGraph' 被禁用" +msgstr "正在尝试写入提交图,但 'core.commitGraph' 被禁用" #: commit-graph.c #, c-format @@ -19648,7 +19763,7 @@ #: commit-graph.c msgid "too many commits to write graph" -msgstr "提交太多不能画图" +msgstr "提交太多,无法写入提交图" #: commit-graph.c msgid "the commit-graph file has incorrect checksum and is likely corrupt" @@ -19718,7 +19833,7 @@ #: commit-reach.c sequencer.c #, c-format msgid "could not parse commit %s" -msgstr "不能解析提交 %s" +msgstr "无法解析提交 %s" #: commit.c #, c-format @@ -19737,10 +19852,10 @@ "\"git config set advice.graftFileDeprecated false\"" msgstr "" "对 <GIT_DIR>/info/grafts 的支持已过时,并将在\n" -"未来的Git版本中被移除。\n" +"未来的 Git 版本中被移除。\n" "\n" "请使用 \"git replace --convert-graft-file\" 将\n" -"(提交)移植转换为替换引用。\n" +"嫁接转换为替换引用。\n" "\n" "运行 \"git config set advice.graftFileDeprecated false\"\n" "可关闭本消息" @@ -19768,7 +19883,7 @@ #: commit.c #, c-format msgid "Commit %s has a good GPG signature by %s\n" -msgstr "提交 %s 有一个来自 %s 的好的 GPG 签名。\n" +msgstr "提交 %s 有一个来自 %s 的有效 GPG 签名。\n" #: commit.c msgid "" @@ -19777,7 +19892,7 @@ "variable i18n.commitEncoding to the encoding your project uses.\n" msgstr "" "警告:提交说明不符合 UTF-8 字符编码。\n" -"您可以通过修补提交来改正提交说明,或者将配置变量 i18n.commitEncoding\n" +"您可以通过修订提交来改正提交说明,或者将配置变量 i18n.commitEncoding\n" "设置为您项目所用的字符编码。\n" #: compat/compiler.h @@ -19791,7 +19906,7 @@ #: compat/disk.h #, c-format msgid "could not determine free disk size for '%s'" -msgstr "不能确定 '%s' 的空余磁盘空间" +msgstr "无法确定 '%s' 的空余磁盘空间" #: compat/disk.h #, c-format @@ -19801,7 +19916,7 @@ #: compat/fsmonitor/fsm-health-win32.c #, c-format msgid "[GLE %ld] health thread could not open '%ls'" -msgstr "[GLE %ld] 健康监测线程不能打开 '%ls'" +msgstr "[GLE %ld] 健康监测线程无法打开 '%ls'" #: compat/fsmonitor/fsm-health-win32.c #, c-format @@ -19811,7 +19926,7 @@ #: compat/fsmonitor/fsm-health-win32.c compat/fsmonitor/fsm-listen-win32.c #, c-format msgid "could not convert to wide characters: '%s'" -msgstr "不能转换至宽字符:'%s'" +msgstr "无法转换至宽字符:'%s'" #: compat/fsmonitor/fsm-health-win32.c #, c-format @@ -19831,7 +19946,7 @@ #: compat/fsmonitor/fsm-ipc-darwin.c #, c-format msgid "Invalid path: %s" -msgstr "无效路径: %s" +msgstr "无效路径:%s" #: compat/fsmonitor/fsm-listen-darwin.c msgid "Unable to create FSEventStream." @@ -19864,7 +19979,7 @@ #: compat/fsmonitor/fsm-listen-win32.c #, c-format msgid "GetOverlappedResult failed on '%s' [GLE %ld]" -msgstr "GetOverlappedResult 失败于 '%s' [GLE %ld]" +msgstr "GetOverlappedResult 在 '%s' 上失败 [GLE %ld]" #: compat/fsmonitor/fsm-listen-win32.c #, c-format @@ -19899,12 +20014,12 @@ #: compat/fsmonitor/fsm-path-utils-win32.c #, c-format msgid "[GLE %ld] unable to get protocol information for '%ls'" -msgstr "[GLE %ld] 无法获取 '%ls' 的协议信息 " +msgstr "[GLE %ld] 无法获取 '%ls' 的协议信息" #: compat/mingw.c #, c-format msgid "failed to copy SID (%ld)" -msgstr "无法拷贝 SID (%ld)" +msgstr "无法复制 SID (%ld)" #: compat/mingw.c #, c-format @@ -19941,7 +20056,7 @@ #: compat/regex/regcomp.c msgid "Invalid back reference" -msgstr "无效的反向索引" +msgstr "无效的反向引用" #: compat/regex/regcomp.c msgid "Unmatched [ or [^" @@ -20066,7 +20181,7 @@ "remote URLs cannot be configured in file directly or indirectly included by " "includeIf.hasconfig:remote.*.url" msgstr "" -"远程 URL 不能在文件中配置,不管直接地还是通过 " +"远程 URL 无法在文件中配置,不管直接地还是通过 " "includeIf.hasconfig:remote.*.url 间接地包含" #: config.c @@ -20082,7 +20197,7 @@ #: config.c #, c-format msgid "missing environment variable '%s' for configuration '%.*s'" -msgstr "缺少环境变量 '%s' 于配置 '%.*s' " +msgstr "配置 '%3$.*2$s' 缺少环境变量 '%1$s'" #: config.c #, c-format @@ -20111,7 +20226,7 @@ #: config.c #, c-format msgid "bogus config parameter: %s" -msgstr "伪配置参数:%s" +msgstr "无效的配置参数:%s" #: config.c #, c-format @@ -20121,7 +20236,7 @@ #: config.c #, c-format msgid "bogus count in %s" -msgstr "%s 中错误计数" +msgstr "%s 中计数错误" #: config.c #, c-format @@ -20214,7 +20329,7 @@ #: config.c #, c-format msgid "bad boolean config value '%s' for '%s'" -msgstr "'%2$s' 的错误的布尔取值 '%1$s'" +msgstr "配置变量 '%2$s' 的布尔值 '%1$s' 无效" #: config.c #, c-format @@ -20239,7 +20354,7 @@ #: config.c #, c-format msgid "unable to resolve config blob '%s'" -msgstr "不能解析配置对象 '%s'" +msgstr "无法解析配置对象 '%s'" #: config.c msgid "unable to parse command-line config" @@ -20334,7 +20449,7 @@ #: config.c #, c-format msgid "could not lock config file %s" -msgstr "不能锁定配置文件 %s" +msgstr "无法锁定配置文件 %s" #: config.c #, c-format @@ -20354,7 +20469,7 @@ #: config.c #, c-format msgid "unable to mmap '%s'%s" -msgstr "不能 mmap '%s'%s" +msgstr "无法 mmap '%s'%s" #: config.c #, c-format @@ -20364,12 +20479,12 @@ #: config.c #, c-format msgid "could not write config file %s" -msgstr "不能写入配置文件 %s" +msgstr "无法写入配置文件 %s" #: config.c #, c-format msgid "could not set '%s' to '%s'" -msgstr "不能设置 '%s' 为 '%s'" +msgstr "无法设置 '%s' 为 '%s'" #: config.c #, c-format @@ -20379,7 +20494,7 @@ #: config.c #, c-format msgid "refusing to work with overly long line in '%s' on line %<PRIuMAX>" -msgstr "拒绝支持内容过长的行,位于文件 '%s' 中的第 %<PRIuMAX> 行" +msgstr "拒绝处理内容过长的行,位于文件 '%s' 中的第 %<PRIuMAX> 行" #: config.c #, c-format @@ -20450,7 +20565,7 @@ #: connect.c #, c-format msgid "error on bundle-uri response line %d: %s" -msgstr "于 bundle-uri 响应行 %d 发生错误:%s" +msgstr "在 bundle-uri 响应行 %d 发生错误:%s" #: connect.c msgid "expected flush after bundle-uri listing" @@ -20535,11 +20650,11 @@ #: connect.c #, c-format msgid "cannot start proxy %s" -msgstr "不能启动代理 %s" +msgstr "无法启动代理 %s" #: connect.c msgid "no path specified; see 'git help pull' for valid url syntax" -msgstr "未指定路径,执行 'git help pull' 查看有效的 url 语法" +msgstr "未指定路径,执行 'git help pull' 查看有效的 URL 语法" #: connect.c msgid "newline is forbidden in git:// hosts and repo paths" @@ -20568,7 +20683,7 @@ #: connected.c msgid "Could not run 'git rev-list'" -msgstr "不能执行 'git rev-list'" +msgstr "无法执行 'git rev-list'" #: connected.c msgid "failed write to rev-list" @@ -20593,7 +20708,7 @@ msgid "" "in the working copy of '%s', CRLF will be replaced by LF the next time Git " "touches it" -msgstr "在 '%s' 的工作拷贝中,下次 Git 接触时 CRLF 将被 LF 替换" +msgstr "在 '%s' 的工作拷贝中,下次 Git 处理时 CRLF 将被 LF 替换" #: convert.c #, c-format @@ -20605,7 +20720,7 @@ msgid "" "in the working copy of '%s', LF will be replaced by CRLF the next time Git " "touches it" -msgstr "在 '%s' 的工作拷贝中,下次 Git 接触时 LF 将被 CRLF 替换" +msgstr "在 '%s' 的工作拷贝中,下次 Git 处理时 LF 将被 CRLF 替换" #: convert.c #, c-format @@ -20631,7 +20746,7 @@ "The file '%s' is missing a byte order mark (BOM). Please use UTF-%sBE or UTF-" "%sLE (depending on the byte order) as working-tree-encoding." msgstr "" -"文件 '%s' 缺失一个字节顺序标记(BOM)。请使用 UTF-%sBE or UTF-%sLE(取决于字" +"文件 '%s' 缺失一个字节顺序标记(BOM)。请使用 UTF-%sBE 或 UTF-%sLE(取决于字" "节序)作为工作区编码。" #: convert.c @@ -20642,22 +20757,22 @@ #: convert.c #, c-format msgid "encoding '%s' from %s to %s and back is not the same" -msgstr "将'%s' 的编码从 %s 到 %s 来回转换不一致" +msgstr "将 '%s' 的编码从 %s 到 %s 来回转换不一致" #: convert.c #, c-format msgid "cannot fork to run external filter '%s'" -msgstr "不能 fork 以执行外部过滤器 '%s'" +msgstr "无法 fork 以执行外部过滤器 '%s'" #: convert.c #, c-format msgid "cannot feed the input to external filter '%s'" -msgstr "不能将输入传递给外部过滤器 '%s'" +msgstr "无法将输入传递给外部过滤器 '%s'" #: convert.c #, c-format msgid "external filter '%s' failed %d" -msgstr "外部过滤器 '%s' 失败码 %d" +msgstr "外部过滤器 '%s' 失败,退出码 %d" #: convert.c #, c-format @@ -20719,12 +20834,12 @@ #: credential.c #, c-format msgid "url has no scheme: %s" -msgstr "URL 没有 scheme:%s" +msgstr "URL 缺少协议:%s" #: credential.c #, c-format msgid "credential url cannot be parsed: %s" -msgstr "不能解析凭据 URL:%s" +msgstr "无法解析凭据 URL:%s" #: daemon.c #, c-format @@ -20799,8 +20914,8 @@ #, c-format msgid "%s, %<PRIuMAX> month ago" msgid_plural "%s, %<PRIuMAX> months ago" -msgstr[0] "%s %<PRIuMAX> 个月前" -msgstr[1] "%s %<PRIuMAX> 个月前" +msgstr[0] "%s,%<PRIuMAX> 个月前" +msgstr[1] "%s,%<PRIuMAX> 个月前" #: date.c #, c-format @@ -20816,7 +20931,7 @@ #: delta-islands.c #, c-format msgid "bad tree object %s" -msgstr "坏的树对象 %s" +msgstr "无效的树对象 %s" #: delta-islands.c #, c-format @@ -20826,12 +20941,12 @@ #: delta-islands.c #, c-format msgid "island regex from config has too many capture groups (max=%d)" -msgstr "来自 config 的数据岛正则表达式有太多的捕获组(最多 %d 个)" +msgstr "来自配置的数据岛正则表达式有太多的捕获组(最多 %d 个)" #: delta-islands.c #, c-format msgid "Marked %d islands, done.\n" -msgstr "已标记 %d 个数据岛,结束。\n" +msgstr "已标记 %d 个数据岛,完成。\n" #: diagnose.c #, c-format @@ -20860,7 +20975,7 @@ #: diagnose.c #, c-format msgid "could not add directory '%s' to archiver" -msgstr "无法添加目录 '%s' 至归档器" +msgstr "无法添加目录 '%s' 到归档器" #: diagnose.c msgid "failed to write archive" @@ -20880,7 +20995,7 @@ #: diff-lib.c msgid "unable to get HEAD" -msgstr "不能解析 HEAD" +msgstr "无法解析 HEAD" #: diff-lib.c msgid "no merge base found" @@ -20912,7 +21027,7 @@ msgid "" "Limiting comparison with pathspecs is only supported if both paths are " "directories." -msgstr "仅当两个路径均为目录时,才支持使用路径表达式限制比较范围。" +msgstr "仅当两个路径均为目录时,才支持使用路径规格限制比较范围。" # 译者:注意保持前导空格 #: diff.c @@ -20947,18 +21062,13 @@ msgid "" "color-moved-ws: allow-indentation-change cannot be combined with other " "whitespace modes" -msgstr "color-moved-ws:allow-indentation-change 不能与其它空白字符模式共用" +msgstr "color-moved-ws:allow-indentation-change 不能与其他空白字符模式共用" #: diff.c #, c-format msgid "Unknown value for 'diff.submodule' config variable: '%s'" msgstr "配置变量 'diff.submodule' 未知的取值:'%s'" -#: diff.c merge-ort.c transport.c -#, c-format -msgid "unknown value for config '%s': %s" -msgstr "配置 '%s' 未知的取值:%s" - #: diff.c #, c-format msgid "" @@ -20975,12 +21085,12 @@ #: diff.c msgid "--follow requires exactly one pathspec" -msgstr "--follow 明确要求只跟一个路径规格" +msgstr "--follow 要求恰好一个路径规格" #: diff.c #, c-format msgid "pathspec magic not supported by --follow: %s" -msgstr "指定 --follow: %s 的同时,不支持在路径规格中使用神奇前缀" +msgstr "--follow 不支持在路径规格中使用魔法前缀:%s" #: diff.c parse-options.c #, c-format @@ -21030,7 +21140,7 @@ #: diff.c #, c-format msgid "unable to resolve '%s'" -msgstr "不能解析 '%s'" +msgstr "无法解析 '%s'" #: diff.c #, c-format @@ -21045,12 +21155,12 @@ #: diff.c #, c-format msgid "bad --color-moved argument: %s" -msgstr "坏的 --color-moved 参数:%s" +msgstr "无效的 --color-moved 参数:%s" #: diff.c #, c-format msgid "invalid mode '%s' in --color-moved-ws" -msgstr "--color-moved-ws 中的无效模式 '%s' " +msgstr "--color-moved-ws 中的无效模式 '%s'" #: diff.c #, c-format @@ -21078,11 +21188,11 @@ #: diff.c #, c-format msgid "bad --word-diff argument: %s" -msgstr "坏的 --word-diff 参数:%s" +msgstr "无效的 --word-diff 参数:%s" #: diff.c msgid "Diff output format options" -msgstr "差异输出格式化选项" +msgstr "差异输出格式选项" #: diff.c msgid "generate patch" @@ -21163,15 +21273,15 @@ #: diff.c msgid "generate diffstat with a given width" -msgstr "使用给定的长度生成差异统计" +msgstr "使用给定的宽度生成差异统计" #: diff.c msgid "generate diffstat with a given name width" -msgstr "使用给定的文件名长度生成差异统计" +msgstr "使用给定的文件名宽度生成差异统计" #: diff.c msgid "generate diffstat with a given graph width" -msgstr "使用给定的图形长度生成差异统计" +msgstr "使用给定的图形宽度生成差异统计" #: diff.c msgid "<count>" @@ -21212,8 +21322,7 @@ "do not munge pathnames and use NULs as output field terminators in --raw or " "--numstat" msgstr "" -"在 --raw 或者 --numstat 中,不对路径字符转码并使用 NUL 字符做为输出字段的分隔" -"符" +"在 --raw 或者 --numstat 中,不对路径名转码并使用 NUL 字符作为输出字段的分隔符" #: diff.c msgid "<prefix>" @@ -21221,11 +21330,11 @@ #: diff.c msgid "show the given source prefix instead of \"a/\"" -msgstr "显示给定的源前缀取代 \"a/\"" +msgstr "显示给定的源前缀替代 \"a/\"" #: diff.c msgid "show the given destination prefix instead of \"b/\"" -msgstr "显示给定的目标前缀取代 \"b/\"" +msgstr "显示给定的目标前缀替代 \"b/\"" #: diff.c msgid "prepend an additional prefix to every line of output" @@ -21269,7 +21378,7 @@ #: diff.c msgid "break complete rewrite changes into pairs of delete and create" -msgstr "将完全重写的变更打破为成对的删除和创建" +msgstr "将完全重写的变更拆分为成对的删除和创建" #: diff.c msgid "detect renames" @@ -21285,7 +21394,7 @@ #: diff.c msgid "use unmodified files as source to find copies" -msgstr "使用未修改的文件做为发现拷贝的源" +msgstr "使用未修改的文件作为发现拷贝的源" #: diff.c msgid "disable rename detection" @@ -21293,7 +21402,7 @@ #: diff.c msgid "use empty blobs as rename source" -msgstr "使用空的数据对象做为重命名的源" +msgstr "使用空的数据对象作为重命名的源" #: diff.c msgid "continue listing the history of a file beyond renames" @@ -21339,11 +21448,11 @@ #: diff.c msgid "ignore changes whose all lines match <regex>" -msgstr "忽略所有行都和正则表达式匹配的变更" +msgstr "忽略所有行都匹配 <正则> 的变更" #: diff.c msgid "heuristic to shift diff hunk boundaries for easy reading" -msgstr "启发式转换差异边界以便阅读" +msgstr "启发式调整差异块边界以便阅读" #: diff.c msgid "generate diff using the \"patience diff\" algorithm" @@ -21387,7 +21496,7 @@ #: diff.c msgid "Other diff options" -msgstr "其它差异选项" +msgstr "其他差异选项" #: diff.c msgid "when run from subdir, exclude changes outside and show relative paths" @@ -21395,7 +21504,7 @@ #: diff.c msgid "treat all files as text" -msgstr "把所有文件当做文本处理" +msgstr "把所有文件当作文本处理" #: diff.c msgid "swap two inputs, reverse the diff" @@ -21411,7 +21520,7 @@ #: diff.c msgid "allow an external diff helper to be executed" -msgstr "允许执行一个外置的差异助手" +msgstr "允许执行一个外部差异辅助程序" #: diff.c msgid "run external text conversion filters when comparing binary files" @@ -21439,7 +21548,7 @@ #: diff.c msgid "treat 'git add -N' entries as real in the index" -msgstr "将索引中 'git add -N' 条目当做真实的" +msgstr "将索引中 'git add -N' 条目当作真实的" #: diff.c msgid "<string>" @@ -21459,11 +21568,11 @@ #: diff.c msgid "show all changes in the changeset with -S or -G" -msgstr "显示使用 -S 或 -G 的变更集的所有变更" +msgstr "显示由 -S 或 -G 选中的变更集内的全部变更" #: diff.c msgid "treat <string> in -S as extended POSIX regular expression" -msgstr "将 -S 的 <string> 当做扩展的 POSIX 正则表达式" +msgstr "将 -S 的 <string> 当作扩展的 POSIX 正则表达式" #: diff.c msgid "control the order in which files appear in the output" @@ -21536,7 +21645,7 @@ #: diffcore-rename.c msgid "Performing inexact rename detection" -msgstr "正在进行非精确的重命名探测" +msgstr "正在进行非精确的重命名检测" #: diffcore-rotate.c #, c-format @@ -21546,7 +21655,7 @@ #: dir.c #, c-format msgid "pathspec '%s' did not match any file(s) known to git" -msgstr "路径规格 '%s' 未匹配任何 git 已知文件" +msgstr "路径规格 '%s' 未匹配任何 Git 已知文件" #: dir.c #, c-format @@ -21565,12 +21674,12 @@ #: dir.c msgid "disabling cone pattern matching" -msgstr "停用锥形模式匹配" +msgstr "停用锥形模式(稀疏检出)匹配" #: dir.c #, c-format msgid "cannot use %s as an exclude file" -msgstr "不能将 %s 用作排除文件" +msgstr "无法将 %s 用作排除文件" #: dir.c msgid "failed to get kernel name and information" @@ -21578,7 +21687,7 @@ #: dir.c msgid "untracked cache is disabled on this system or location" -msgstr "缓存未跟踪文件在本系统或位置中被禁用" +msgstr "本系统或位置已禁用未跟踪文件缓存" #: dir.c msgid "" @@ -21596,12 +21705,12 @@ #: dir.c #, c-format msgid "could not create directories for %s" -msgstr "不能为 %s 创建目录" +msgstr "无法为 %s 创建目录" #: dir.c #, c-format msgid "could not migrate git directory from '%s' to '%s'" -msgstr "不能从 '%s' 迁移 git 目录到 '%s'" +msgstr "无法从 '%s' 迁移 Git 目录到 '%s'" #: editor.c #, c-format @@ -21611,12 +21720,12 @@ #: editor.c sequencer.c wrapper.c #, c-format msgid "could not write to '%s'" -msgstr "不能写入 '%s'" +msgstr "无法写入 '%s'" #: editor.c #, c-format msgid "could not edit '%s'" -msgstr "不能编辑 '%s'" +msgstr "无法编辑 '%s'" #: entry.c msgid "Filtering content" @@ -21625,12 +21734,12 @@ #: entry.c #, c-format msgid "could not stat file '%s'" -msgstr "不能对文件 '%s' 调用 stat" +msgstr "无法对文件 '%s' 调用 stat" #: environment.c #, c-format msgid "bad git namespace path \"%s\"" -msgstr "错误的 git 名字空间路径 \"%s\"" +msgstr "错误的 git 命名空间路径 \"%s\"" #: environment.c #, c-format @@ -21669,7 +21778,7 @@ #: environment.c msgid "core.fsyncObjectFiles is deprecated; use core.fsync instead" -msgstr "core.fsyncObjectFiles 已经被弃用;取而代之使用 core.fsync" +msgstr "core.fsyncObjectFiles 已经被弃用;改用 core.fsync" #: environment.c #, c-format @@ -21706,7 +21815,7 @@ msgstr "" "您正在尝试获取 %s,它位于提交图文件中,但不在对象数据库中。\n" "这可能是由于仓库损坏造成的。\n" -"如果您尝试通过重新获取丢失的对象来修复此仓库损坏,请对丢失的对象使用 'git " +"如果您尝试通过重新获取丢失的对象来修复此仓库损坏,请对缺失的对象执行 'git " "fetch --refetch'。" #: fetch-pack.c @@ -21728,21 +21837,21 @@ #: fetch-pack.c msgid "unable to write to remote" -msgstr "无法写到远程" +msgstr "无法写入远程" #: fetch-pack.c msgid "Server supports filter" -msgstr "服务器支持 filter" +msgstr "服务器支持过滤器" #: fetch-pack.c #, c-format msgid "invalid shallow line: %s" -msgstr "无效的 shallow 信息:%s" +msgstr "无效的 shallow 行:%s" #: fetch-pack.c #, c-format msgid "invalid unshallow line: %s" -msgstr "无效的 unshallow 信息:%s" +msgstr "无效的 unshallow 行:%s" #: fetch-pack.c #, c-format @@ -21752,7 +21861,7 @@ #: fetch-pack.c #, c-format msgid "no shallow found: %s" -msgstr "未发现 shallow:%s" +msgstr "未找到 shallow:%s" #: fetch-pack.c #, c-format @@ -21794,11 +21903,11 @@ #: fetch-pack.c msgid "fetch-pack: unable to fork off sideband demultiplexer" -msgstr "fetch-pack:无法派生 sideband 多路输出" +msgstr "fetch-pack:无法派生 sideband 多路分解器" #: fetch-pack.c msgid "protocol error: bad pack header" -msgstr "协议错误:坏的包头" +msgstr "协议错误:无效的包头" #: fetch-pack.c #, c-format @@ -21816,12 +21925,12 @@ #: fetch-pack.c msgid "error in sideband demultiplexer" -msgstr "sideband 多路输出出错" +msgstr "sideband 多路分解器出错" #: fetch-pack.c #, c-format msgid "Server version is %.*s" -msgstr "服务器版本 %.*s" +msgstr "服务器版本为 %.*s" #: fetch-pack.c #, c-format @@ -21830,7 +21939,7 @@ #: fetch-pack.c msgid "Server does not support shallow clients" -msgstr "服务器不支持浅客户端" +msgstr "服务器不支持浅克隆客户端" #: fetch-pack.c msgid "Server does not support --shallow-since" @@ -21908,7 +22017,7 @@ #: fetch-pack.c #, c-format msgid "expected no other sections to be sent after no '%s'" -msgstr "在没有 '%s' 后不应该发送其它小节" +msgstr "在未发送 '%s' 后不应该再发送其他小节" #: fetch-pack.c #, c-format @@ -21936,11 +22045,11 @@ #: fetch-pack.c msgid "no matching remote head" -msgstr "没有匹配的远程分支" +msgstr "没有匹配的远程头引用" #: fetch-pack.c msgid "unexpected 'ready' from remote" -msgstr "来自远程的意外的 'ready'" +msgstr "来自远程的意外 'ready'" #: fetch-pack.c #, c-format @@ -21955,12 +22064,12 @@ #: fsmonitor-ipc.c #, c-format msgid "fsmonitor_ipc__send_query: invalid path '%s'" -msgstr "fsmonitor_ipc__send_query: 无效路径 '%s'" +msgstr "fsmonitor_ipc__send_query:无效路径 '%s'" #: fsmonitor-ipc.c #, c-format msgid "fsmonitor_ipc__send_query: unspecified error on '%s'" -msgstr "fsmonitor_ipc__send_query: 未知错误于 '%s'" +msgstr "fsmonitor_ipc__send_query:在 '%s' 上发生未知错误" #: fsmonitor-ipc.c msgid "fsmonitor--daemon is not running" @@ -21969,7 +22078,7 @@ #: fsmonitor-ipc.c #, c-format msgid "could not send '%s' command to fsmonitor--daemon" -msgstr "无法发送 '%s' 命令至 fsmonitor--daemon" +msgstr "无法发送 '%s' 命令到 fsmonitor--daemon" #: fsmonitor-settings.c #, c-format @@ -22011,8 +22120,8 @@ msgstr "" "git [-v | --version] [-h | --help] [-C <路径>] [-c <名称>=<取值>]\n" " [--exec-path[=<路径>]] [--html-path] [--man-path] [--info-path]\n" -" [-p | --paginate | -P | --no-pager] [--no-replace-objects] [\"--" -"no-lazy-fetch]\n" +" [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--no-" +"lazy-fetch]\n" " [--no-optional-locks] [--no-advice] [--bare] [--git-dir=<路径>]\n" " [--work-tree=<路径>] [--namespace=<名称>]\n" " [--config-env=<名称>=<环境变量>] <命令> [<参数>]" @@ -22052,7 +22161,7 @@ #: git.c #, c-format msgid "no config key given for --config-env\n" -msgstr "没有为 --config-env 提供配置名称\n" +msgstr "没有为 --config-env 提供配置键名\n" #: git.c #, c-format @@ -22075,8 +22184,8 @@ "alias '%s' changes environment variables.\n" "You can use '!git' in the alias to do this" msgstr "" -"别名 '%s' 修改环境变量。您可以使用在别名中\n" -"使用 '!git'" +"别名 '%s' 会修改环境变量。\n" +"您可以在别名中使用 '!git' 来做到这一点" #: git.c #, c-format @@ -22091,7 +22200,7 @@ #: git.c #, c-format msgid "alias loop detected: expansion of '%s' does not terminate:%s" -msgstr "检测到别名循环:'%s'的扩展未终止:%s" +msgstr "检测到别名循环:'%s' 的扩展未终止:%s" #: git.c msgid "write failure on standard output" @@ -22108,7 +22217,7 @@ #: git.c #, c-format msgid "cannot handle %s as a builtin" -msgstr "不能作为内置命令处理 %s" +msgstr "无法作为内置命令处理 %s" #: git.c #, c-format @@ -22122,7 +22231,7 @@ #: git.c #, c-format msgid "expansion of alias '%s' failed; '%s' is not a git command\n" -msgstr "展开别名命令 '%s' 失败,'%s' 不是一个 git 命令\n" +msgstr "展开别名命令 '%s' 失败,'%s' 不是一个 Git 命令\n" #: git.c #, c-format @@ -22131,7 +22240,7 @@ #: gpg-interface.c msgid "could not create temporary file" -msgstr "不能创建临时文件" +msgstr "无法创建临时文件" #: gpg-interface.c #, c-format @@ -22149,28 +22258,28 @@ "ssh-keygen -Y find-principals/verify is needed for ssh signature " "verification (available in openssh version 8.2p1+)" msgstr "" -"ssh 签名验证需要 ssh-keygen -Y find-principals/verify \n" -"(openssh 8.2p1+ 版本可用)" +"ssh 签名验证需要 ssh-keygen -Y find-principals/verify\n" +"(OpenSSH 8.2p1+ 版本可用)" #: gpg-interface.c #, c-format msgid "ssh signing revocation file configured but not found: %s" -msgstr "设置了 ssh 签名吊销文件但无法找到:%s" +msgstr "已设置 SSH 签名吊销文件但无法找到:%s" #: gpg-interface.c #, c-format msgid "bad/incompatible signature '%s'" -msgstr "坏的/不兼容的签名 '%s‘" +msgstr "无效/不兼容的签名 '%s'" #: gpg-interface.c #, c-format msgid "failed to get the ssh fingerprint for key '%s'" -msgstr "无法得到密钥 '%s' 的 ssh 指纹" +msgstr "无法得到密钥 '%s' 的 SSH 指纹" #: gpg-interface.c #, c-format msgid "failed to get the ssh fingerprint for key %s" -msgstr "无法得到密钥 '%s' 的 ssh 指纹" +msgstr "无法得到密钥 %s 的 SSH 指纹" #: gpg-interface.c msgid "" @@ -22219,12 +22328,12 @@ msgid "" "ssh-keygen -Y sign is needed for ssh signing (available in openssh version " "8.2p1+)" -msgstr "ssh 签名需要 ssh-keygen -Y sign (openssh 8.2p1+ 版本可用)" +msgstr "ssh 签名需要 ssh-keygen -Y sign(OpenSSH 8.2p1+ 版本可用)" #: gpg-interface.c #, c-format msgid "failed reading ssh signing data buffer from '%s'" -msgstr "无法从 '%s' 读入 ssh 签名数据" +msgstr "无法从 '%s' 读入 SSH 签名数据" #: graph.c #, c-format @@ -22275,23 +22384,23 @@ #: help.c msgid "Ancillary Commands / Manipulators" -msgstr "辅助命令/操作者" +msgstr "辅助命令/操作类" #: help.c msgid "Ancillary Commands / Interrogators" -msgstr "辅助命令/询问者" +msgstr "辅助命令/查询类" #: help.c msgid "Interacting with Others" -msgstr "与其它系统交互" +msgstr "与其他系统交互" #: help.c msgid "Low-level Commands / Manipulators" -msgstr "低级命令/操作者" +msgstr "低级命令/操作类" #: help.c msgid "Low-level Commands / Interrogators" -msgstr "低级命令/询问者" +msgstr "低级命令/查询类" #: help.c msgid "Low-level Commands / Syncing Repositories" @@ -22299,7 +22408,7 @@ #: help.c msgid "Low-level Commands / Internal Helpers" -msgstr "低级命令/内部助手" +msgstr "低级命令/内部辅助程序" #: help.c msgid "User-facing repository, command and file interfaces" @@ -22344,7 +22453,7 @@ #: help.c msgid "See 'git help <command>' to read about a specific subcommand" -msgstr "执行 'git help <command>' 来了解特定子命令" +msgstr "执行 'git help <command>' 来了解特定子命令" #: help.c #, c-format @@ -22362,7 +22471,7 @@ #: help.c msgid "Uh oh. Your system reports no Git commands at all." -msgstr "唉呀,您的系统中未发现 Git 命令。" +msgstr "哎呀,您的系统中未发现 Git 命令。" #: help.c #, c-format @@ -22416,10 +22525,10 @@ "Did you mean one of these?" msgstr[0] "" "\n" -"您指的是这个么?" +"您指的是这个吗?" msgstr[1] "" "\n" -"您指的是这其中的某一个么?" +"您指的是这其中的某一个吗?" #: hook.c #, c-format @@ -22432,7 +22541,7 @@ #: http-fetch.c msgid "not a git repository" -msgstr "不是 git 仓库" +msgstr "不是 Git 仓库" #: http-fetch.c #, c-format @@ -22465,7 +22574,7 @@ #: http.c #, c-format msgid "Could not set SSL backend to '%s': cURL was built without SSL backends" -msgstr "无法设置 SSL 后端为 '%s':cURL: cURL 没有使用 SSL 后端构建" +msgstr "无法设置 SSL 后端为 '%s':cURL 没有使用 SSL 后端构建" #: http.c #, c-format @@ -22474,7 +22583,7 @@ #: http.c msgid "refusing to read cookies from http.cookiefile '-'" -msgstr "拒绝从 http.cookiefile '-' 读取 cookies" +msgstr "拒绝从 http.cookiefile '-' 读取 Cookie" #: http.c msgid "ignoring http.savecookies for empty http.cookiefile" @@ -22487,7 +22596,7 @@ " asked for: %s\n" " redirect: %s" msgstr "" -"不能更新重定向的 url base:\n" +"无法更新重定向后的 URL 基址:\n" " 请求:%s\n" " 重定向:%s" @@ -22527,7 +22636,7 @@ " git config --global user.email \"you@example.com\"\n" " git config --global user.name \"Your Name\"\n" "\n" -"来设置您账号的缺省身份标识。\n" +"来设置您账号的默认身份标识。\n" "如果仅在本仓库设置身份标识,则省略 --global 参数。\n" "\n" @@ -22594,7 +22703,7 @@ #: list-objects-filter-options.c msgid "sparse:path filters support has been dropped" -msgstr "sparse:path 过滤器支持已被删除" +msgstr "sparse:path 过滤器支持已被移除" #: list-objects-filter-options.c #, c-format @@ -22634,7 +22743,7 @@ #: list-objects-filter.c #, c-format msgid "unable to access sparse blob in '%s'" -msgstr "不能访问 '%s' 中的稀疏数据对象" +msgstr "无法访问 '%s' 中的稀疏数据对象" #: list-objects-filter.c #, c-format @@ -22669,15 +22778,15 @@ msgstr "" "无法创建 '%s.lock':%s。\n" "\n" -"似乎另外一个 git 进程在这个仓库中运行,例如:'git commit' 命令打\n" +"似乎另一个 Git 进程在这个仓库中运行,例如:'git commit' 命令打\n" "开了一个编辑器。请确认所有进程都已经关闭然后重试。如果仍然报错,\n" -"可能之前有一个 git 进程在这个仓库中异常退出:\n" +"可能之前有一个 Git 进程在这个仓库中异常退出:\n" "手动删除这个文件再继续。" #: lockfile.c #, c-format msgid "Unable to create '%s.lock': %s" -msgstr "不能创建 '%s.lock':%s" +msgstr "无法创建 '%s.lock':%s" #: log-tree.c msgid "unable to create temporary object directory" @@ -22686,7 +22795,7 @@ #: loose.c #, c-format msgid "could not write loose object index %s" -msgstr "不能写入松散对象索引 %s" +msgstr "无法写入松散对象索引 %s" #: loose.c #, c-format @@ -22719,17 +22828,17 @@ #: merge-ort-wrappers.c #, c-format msgid "Could not parse object '%s'" -msgstr "不能解析对象 '%s'" +msgstr "无法解析对象 '%s'" #: merge-ort.c #, c-format msgid "Failed to merge submodule %s (not checked out)" -msgstr "无法合并子模组 %s (没有检出)" +msgstr "无法合并子模组 %s(没有检出)" #: merge-ort.c #, c-format msgid "Failed to merge submodule %s (no merge base)" -msgstr "无法合并子模组 %s (没有合并基线)" +msgstr "无法合并子模组 %s(没有合并基线)" #: merge-ort.c #, c-format @@ -22744,7 +22853,7 @@ #: merge-ort.c #, c-format msgid "Failed to merge submodule %s (commits don't follow merge-base)" -msgstr "无法合并子模组 %s (提交未跟随合并基线)" +msgstr "无法合并子模组 %s(提交未跟随合并基线)" #: merge-ort.c #, c-format @@ -22779,7 +22888,7 @@ #: merge-ort.c #, c-format msgid "error: unable to add %s to database" -msgstr "错误:不能添加 %s 至对象库" +msgstr "错误:无法将 %s 添加到对象库" #: merge-ort.c #, c-format @@ -22792,8 +22901,8 @@ "CONFLICT (implicit dir rename): Existing file/dir at %s in the way of " "implicit directory rename(s) putting the following path(s) there: %s." msgstr "" -"冲突(隐式目录重命名):处于隐式目录重命名的现存文件/目录 %s,将以下路径放" -"在:%s。" +"冲突(隐式目录重命名):隐式目录重命名要把以下路径放到 %s,但该处已有文件/目" +"录:%s。" #: merge-ort.c #, c-format @@ -22880,7 +22989,7 @@ #: merge-ort.c #, c-format msgid "error: cannot read object %s" -msgstr "错误:不能读取对象 %s" +msgstr "错误:无法读取对象 %s" #: merge-ort.c #, c-format @@ -22892,7 +23001,7 @@ msgid "" "CONFLICT (file/directory): directory in the way of %s from %s; moving it to " "%s instead." -msgstr "冲突(文件/目录):目录已存在于 %2$s 中的 %1$s,将其移动到 %3$s。" +msgstr "冲突(文件/目录):目录阻挡了来自 %2$s 的 %1$s,改为将其移动到 %3$s。" #: merge-ort.c #, c-format @@ -22909,8 +23018,8 @@ "CONFLICT (distinct types): %s had different types on each side; renamed one " "of them so each can be recorded somewhere." msgstr "" -"冲突(不同类型):%s 在两侧有不同的类型,将其中之一重命名以便它们能记录在不同" -"位置。" +"冲突(不同类型):%s 在两侧类型不同,已重命名其中之一,以便两者都能记录在某" +"处。" #: merge-ort.c msgid "content" @@ -23012,7 +23121,7 @@ #: midx-write.c #, c-format msgid "could not parse line: %s" -msgstr "不能解析行:%s" +msgstr "无法解析行:%s" #: midx-write.c #, c-format @@ -23021,7 +23130,7 @@ #: midx-write.c msgid "could not load pack" -msgstr "不能载入包" +msgstr "无法载入包" #: midx-write.c #, c-format @@ -23059,7 +23168,7 @@ #: midx-write.c #, c-format msgid "cannot select preferred pack %s with no objects" -msgstr "不能选择没有对象的首选包 %s" +msgstr "无法选择没有对象的首选包 %s" #: midx-write.c #, c-format @@ -23105,7 +23214,7 @@ #: midx-write.c msgid "cannot expire packs from an incremental multi-pack-index" -msgstr "增量多包索引中的包不能过期" +msgstr "增量多包索引中的包无法过期" #: midx-write.c msgid "Counting referenced objects" @@ -23121,11 +23230,11 @@ #: midx-write.c msgid "could not start pack-objects" -msgstr "不能开始 pack-objects" +msgstr "无法开始 pack-objects" #: midx-write.c msgid "could not finish pack-objects" -msgstr "不能结束 pack-objects" +msgstr "无法结束 pack-objects" #: midx.c msgid "multi-pack-index OID fanout is of the wrong size" @@ -23158,7 +23267,7 @@ #: midx.c #, c-format msgid "multi-pack-index version %d not recognized" -msgstr "multi-pack-index 版本 %d 不能被识别" +msgstr "multi-pack-index 版本 %d 未被识别" #: midx.c #, c-format @@ -23207,7 +23316,7 @@ #: midx.c #, c-format msgid "invalid multi-pack-index chain: line '%s' not a hash" -msgstr "无效的多包索引链:第 '%s' 行不是哈希值" +msgstr "无效的多包索引链:行 '%s' 不是哈希值" #: midx.c msgid "unable to find all multi-pack index files" @@ -23220,7 +23329,7 @@ #: midx.c #, c-format msgid "bad pack-int-id: %u (%u total packs)" -msgstr "错的 pack-int-id:%u(共有 %u 个包)" +msgstr "无效的 pack-int-id:%u(共有 %u 个包)" #: midx.c msgid "MIDX does not contain the BTMP chunk" @@ -23229,11 +23338,11 @@ #: midx.c #, c-format msgid "could not load bitmapped pack %<PRIu32>" -msgstr "不能打开已被位图索引的包 %<PRIu32>" +msgstr "无法加载已被位图索引的包 %<PRIu32>" #: midx.c msgid "multi-pack-index stores a 64-bit offset, but off_t is too small" -msgstr "多包索引存储一个64位偏移,但是 off_t 太小" +msgstr "多包索引存储一个 64 位偏移,但是 off_t 太小" #: midx.c msgid "multi-pack-index large offset out of bounds" @@ -23245,7 +23354,7 @@ #: midx.c msgid "incorrect checksum" -msgstr "不正确的校验码" +msgstr "校验码不正确" #: midx.c msgid "Looking for referenced packfiles" @@ -23253,7 +23362,7 @@ #: midx.c msgid "the midx contains no oid" -msgstr "midx 不包含 oid" +msgstr "MIDX 不包含 OID" #: midx.c msgid "Verifying OID order in multi-pack-index" @@ -23290,17 +23399,17 @@ #: name-hash.c #, c-format msgid "unable to create lazy_dir thread: %s" -msgstr "不能创建 lazy_dir 线程:%s" +msgstr "无法创建 lazy_dir 线程:%s" #: name-hash.c #, c-format msgid "unable to create lazy_name thread: %s" -msgstr "不能创建 lazy_name 线程:%s" +msgstr "无法创建 lazy_name 线程:%s" #: name-hash.c #, c-format msgid "unable to join lazy_name thread: %s" -msgstr "不能加入 lazy_name 线程:%s" +msgstr "无法加入 lazy_name 线程:%s" #: notes-merge.c #, c-format @@ -23309,23 +23418,23 @@ "Please, use 'git notes merge --commit' or 'git notes merge --abort' to " "commit/abort the previous merge before you start a new notes merge." msgstr "" -"您的前一次注释合并尚未结束(存在 %s)。\n" -"在开始一个新的注释合并之前,请使用 'git notes merge --commit' 或者 'git " +"您的前一次注解合并尚未结束(存在 %s)。\n" +"在开始一个新的注解合并之前,请使用 'git notes merge --commit' 或者 'git " "notes merge --abort' 来提交/终止前一次合并。" #: notes-merge.c #, c-format msgid "You have not concluded your notes merge (%s exists)." -msgstr "您尚未结束注释合并(存在 %s)。" +msgstr "您尚未结束注解合并(存在 %s)。" #: notes-utils.c msgid "Cannot commit uninitialized/unreferenced notes tree" -msgstr "不能提交未初始化/未引用的注解树" +msgstr "无法提交未初始化/未引用的注解树" #: notes-utils.c #, c-format msgid "Bad notes.rewriteMode value: '%s'" -msgstr "坏的 notes.rewriteMode 值:'%s'" +msgstr "无效的 notes.rewriteMode 值:'%s'" #: notes-utils.c #, c-format @@ -23339,16 +23448,16 @@ #: notes-utils.c #, c-format msgid "Bad %s value: '%s'" -msgstr "坏的 %s 值:'%s'" +msgstr "无效的 %s 值:'%s'" #: object-file-convert.c msgid "failed to decode tree entry" -msgstr "无法解码树对象" +msgstr "无法解码树条目" #: object-file-convert.c #, c-format msgid "failed to map tree entry for %s" -msgstr "无法为 %s 映射树对象" +msgstr "无法为 %s 映射树条目" #: object-file-convert.c #, c-format @@ -23412,7 +23521,7 @@ #: object-file.c #, c-format msgid "unable to open %s" -msgstr "不能打开 %s" +msgstr "无法打开 %s" #: object-file.c #, c-format @@ -23448,12 +23557,12 @@ #: object-file.c msgid "unable to write loose object file" -msgstr "不能写松散对象文件" +msgstr "无法写入松散对象文件" #: object-file.c #, c-format msgid "unable to deflate new object %s (%d)" -msgstr "不能压缩新对象 %s(%d)" +msgstr "无法压缩新对象 %s(%d)" #: object-file.c #, c-format @@ -23463,7 +23572,7 @@ #: object-file.c #, c-format msgid "confused by unstable object source data for %s" -msgstr "被 %s 的不稳定对象源数据搞糊涂了" +msgstr "由于 %s 的对象源数据不稳定而无法处理" #: object-file.c #, c-format @@ -23473,7 +23582,7 @@ #: object-file.c #, c-format msgid "unable to stream deflate new object (%d)" -msgstr "不能流式压缩新对象(%d)" +msgstr "无法流式压缩新对象(%d)" #: object-file.c #, c-format @@ -23488,7 +23597,7 @@ #: object-file.c #, c-format msgid "cannot read object for %s" -msgstr "不能读取对象 %s" +msgstr "无法读取对象 %s" #: object-file.c #, c-format @@ -23520,6 +23629,10 @@ msgstr "%s:无法插入数据库" #: object-file.c +msgid "cannot add a submodule of a different hash algorithm" +msgstr "无法添加使用不同哈希算法的子模组" + +#: object-file.c #, c-format msgid "%s: unsupported file type" msgstr "%s:不支持的文件类型" @@ -23532,7 +23645,7 @@ #: object-file.c #, c-format msgid "unable to mmap %s" -msgstr "不能 mmap %s" +msgstr "无法 mmap %s" #: object-file.c #, c-format @@ -23561,7 +23674,7 @@ #: object-name.c #, c-format msgid "%s [bad object]" -msgstr "%s [坏的对象]" +msgstr "%s [无效的对象]" #. TRANSLATORS: This is a line of ambiguous commit #. object output. E.g.: @@ -23598,7 +23711,7 @@ #: object-name.c #, c-format msgid "%s [bad tag, could not parse it]" -msgstr "%s [坏的标签,无法解析]" +msgstr "%s [无效的标签,无法解析]" #. TRANSLATORS: This is a line of ambiguous <type> #. object output. E.g. "deadbeef tree". @@ -23619,7 +23732,7 @@ #: object-name.c #, c-format msgid "short object ID %s is ambiguous" -msgstr "短对象ID %s 存在歧义" +msgstr "短对象 ID %s 存在歧义" #. TRANSLATORS: The argument is the list of ambiguous #. objects composed in show_ambiguous_object(). See @@ -23631,7 +23744,7 @@ "The candidates are:\n" "%s" msgstr "" -"候选者有:\n" +"候选项有:\n" "%s" #: object-name.c @@ -23646,14 +23759,14 @@ "examine these refs and maybe delete them. Turn this message off by\n" "running \"git config set advice.objectNameWarning false\"" msgstr "" -"Git 通常不会创建一个以40个十六进制字符结尾的引用,因为当您只提供40\n" +"Git 通常不会创建一个以 40 个十六进制字符结尾的引用,因为当您只提供 40\n" "个十六进制字符时将被忽略。这些引用可能被错误地创建。例如:\n" "\n" " git switch -c $br $(git rev-parse ...)\n" "\n" -"当 \"$br\" 某种原因空白时,一个40位十六进制的引用将被创建。请检查这些\n" +"当 \"$br\" 由于某种原因为空时,一个 40 位十六进制的引用将被创建。请检查这些\n" "引用,可能需要删除它们。运行 \"git config set advice.objectNameWarning\n" -"false\" 命令关闭本消息通知。" +"false\" 来关闭本消息通知。" #: object-name.c #, c-format @@ -23714,7 +23827,7 @@ #: object-name.c msgid "relative path syntax can't be used outside working tree" -msgstr "不能在工作区之外使用相对路径语法" +msgstr "无法在工作区之外使用相对路径语法" #: object-name.c #, c-format @@ -23744,7 +23857,7 @@ #: object.c #, c-format msgid "unable to parse object: %s" -msgstr "不能解析对象:%s" +msgstr "无法解析对象:%s" #: object.c #, c-format @@ -23801,7 +23914,7 @@ #: odb.c #, c-format msgid "reference repository '%s' is grafted" -msgstr "参考仓库 '%s' 已被移植" +msgstr "参考仓库 '%s' 已被嫁接" #: odb.c #, c-format @@ -23893,7 +24006,7 @@ #: pack-bitmap.c #, c-format msgid "corrupt ewah bitmap: truncated header for entry %d" -msgstr "损坏的 EWAH 位图:条目 %d 截断的文件头" +msgstr "损坏的 EWAH 位图:条目 %d 的文件头被截断" #: pack-bitmap.c #, c-format @@ -23910,7 +24023,7 @@ #: pack-bitmap.c msgid "cannot fstat bitmap file" -msgstr "不能对位图文件调用 fstat" +msgstr "无法对位图文件调用 fstat" #: pack-bitmap.c msgid "checksum doesn't match in MIDX and bitmap" @@ -23923,7 +24036,7 @@ #: pack-bitmap.c #, c-format msgid "could not open pack %s" -msgstr "不能打开包 %s" +msgstr "无法打开包 %s" #: pack-bitmap.c msgid "corrupt bitmap lookup table: triplet position out of index" @@ -23946,7 +24059,7 @@ #: pack-bitmap.c #, c-format msgid "unable to load pack: '%s', disabling pack-reuse" -msgstr "无法打开包:'%s',禁用包重用" +msgstr "无法加载包:'%s',禁用包重用" #: pack-bitmap.c msgid "unable to compute preferred pack, disabling pack-reuse" @@ -23955,7 +24068,7 @@ #: pack-bitmap.c #, c-format msgid "object '%s' not found in type bitmaps" -msgstr "对象 %s 为在类型位图中找到" +msgstr "对象 '%s' 未在类型位图中找到" #: pack-bitmap.c #, c-format @@ -23970,7 +24083,7 @@ #: pack-bitmap.c #, c-format msgid "object not in bitmap: '%s'" -msgstr "对象不在位图中:%s" +msgstr "对象不在位图中:'%s'" #: pack-bitmap.c msgid "failed to load bitmap indexes" @@ -24022,12 +24135,12 @@ #: pack-mtimes.c #, c-format msgid "mtimes file %s has unsupported version %<PRIu32>" -msgstr "mtimes 文件 %s 不支持的版本 %<PRIu32>" +msgstr "mtimes 文件 %s 版本 %<PRIu32> 不受支持" #: pack-mtimes.c #, c-format msgid "mtimes file %s has unsupported hash id %<PRIu32>" -msgstr "mtimes 文件 %s 有不支持的哈希 ID %<PRIu32>" +msgstr "mtimes 文件 %s 的哈希 ID %<PRIu32> 不受支持" #: pack-mtimes.c #, c-format @@ -24036,7 +24149,7 @@ #: pack-refs.c msgid "pack everything" -msgstr "打包一切" +msgstr "打包全部" #: pack-refs.c msgid "prune loose refs (default)" @@ -24067,26 +24180,26 @@ #: pack-revindex.c #, c-format msgid "reverse-index file %s has unknown signature" -msgstr "反向索引文件 %s 有错误的签名" +msgstr "反向索引文件 %s 有未知的签名" #: pack-revindex.c #, c-format msgid "reverse-index file %s has unsupported version %<PRIu32>" -msgstr "反向索引文件 %s 不支持的版本 %<PRIu32>" +msgstr "反向索引文件 %s 的版本 %<PRIu32> 不受支持" #: pack-revindex.c #, c-format msgid "reverse-index file %s has unsupported hash id %<PRIu32>" -msgstr "反向索引文件 %s 有不支持的哈希 ID %<PRIu32>" +msgstr "反向索引文件 %s 的哈希 ID %<PRIu32> 不受支持" #: pack-revindex.c msgid "invalid checksum" -msgstr "无效的校验码 %s" +msgstr "无效的校验码" #: pack-revindex.c #, c-format msgid "invalid rev-index position at %<PRIu64>: %<PRIu32> != %<PRIu32>" -msgstr "位于 %<PRIu64> 的无效的反向索引:%<PRIu32> != %<PRIu32>" +msgstr "在 %<PRIu64> 的反向索引位置无效:%<PRIu32> != %<PRIu32>" #: pack-revindex.c msgid "multi-pack-index reverse-index chunk is the wrong size" @@ -24098,12 +24211,12 @@ #: pack-write.c msgid "cannot both write and verify reverse index" -msgstr "无法同时写入和校验反向索引" +msgstr "不能同时写入和校验反向索引" #: pack-write.c #, c-format msgid "could not stat: %s" -msgstr "不能调用 stat:%s" +msgstr "无法调用 stat:%s" #: pack-write.c #, c-format @@ -24122,7 +24235,7 @@ #: packfile.c #, c-format msgid "packfile %s cannot be mapped%s" -msgstr "包文件 %s 不能被映射%s" +msgstr "包文件 %s 无法被映射%s" #: packfile.c #, c-format @@ -24187,7 +24300,7 @@ #: parse-options.c #, c-format msgid "%s expects a non-negative integer value with an optional k/m/g suffix" -msgstr "%s 期望一个非负整数和一个可选的 k/m/g 后缀" +msgstr "%s 期望一个非负整数以及可选的 k/m/g 后缀" #: parse-options.c #, c-format @@ -24221,7 +24334,7 @@ #: parse-options.c #, c-format msgid "unknown non-ascii option in string: `%s'" -msgstr "字符串中未知的非 ascii 字符选项:`%s'" +msgstr "字符串中未知的非 ASCII 字符选项:`%s'" #. TRANSLATORS: The "<%s>" part of this string #. stands for an optional value given to a command @@ -24354,21 +24467,21 @@ #: parse-options.h msgid "how to strip spaces and #comments from message" -msgstr "设置如何删除提交说明里的空格和#注释" +msgstr "设置如何删除提交说明里的空格和 #注释" #: parse-options.h msgid "read pathspec from file" -msgstr "从文件读取路径表达式" +msgstr "从文件读取路径规格" #: parse-options.h msgid "" "with --pathspec-from-file, pathspec elements are separated with NUL character" -msgstr "使用 --pathspec-from-file,路径表达式用空字符分隔" +msgstr "使用 --pathspec-from-file,路径规格元素用 NUL 字符分隔" #: parse.c #, c-format msgid "bad boolean environment value '%s' for '%s'" -msgstr "对于 '%2$s' 的错误的布尔环境取值 '%1$s'" +msgstr "环境变量 '%2$s' 的布尔值 '%1$s' 无效" #: path-walk.c #, c-format @@ -24392,7 +24505,7 @@ #: path.c #, c-format msgid "Could not make %s writable by group" -msgstr "不能设置 %s 为组可写" +msgstr "无法设置 %s 为组可写" #: pathspec.c msgid "Escape character '\\' not allowed as last character in attr value" @@ -24419,26 +24532,26 @@ msgid "" "global 'literal' pathspec setting is incompatible with all other global " "pathspec settings" -msgstr "全局的 'literal' 路径规格设置和其它的全局路径规格设置不兼容" +msgstr "全局的 'literal' 路径规格设置和其他的全局路径规格设置不兼容" #: pathspec.c msgid "invalid parameter for pathspec magic 'prefix'" -msgstr "路径规格包含无效的神奇前缀" +msgstr "路径规格魔法 'prefix' 的参数无效" #: pathspec.c #, c-format msgid "Invalid pathspec magic '%.*s' in '%s'" -msgstr "在路径规格 '%3$s' 中无效的神奇前缀 '%2$.*1$s'" +msgstr "路径规格魔法前缀 '%.*s' 在 '%s' 中无效" #: pathspec.c #, c-format msgid "Missing ')' at the end of pathspec magic in '%s'" -msgstr "路径规格 '%s' 的神奇前缀结尾少了一个 ')'" +msgstr "路径规格 '%s' 的魔法前缀结尾少了一个 ')'" #: pathspec.c #, c-format msgid "Unimplemented pathspec magic '%c' in '%s'" -msgstr "路径规格 '%2$s' 中包含未实现的神奇前缀 '%1$c'" +msgstr "路径规格 '%2$s' 中包含未实现的魔法前缀 '%1$c'" #: pathspec.c #, c-format @@ -24463,25 +24576,25 @@ #: pathspec.c #, c-format msgid "%s: pathspec magic not supported by this command: %s" -msgstr "%s:路径规格神奇前缀不被此命令支持:%s" +msgstr "%s:路径规格魔法前缀不被此命令支持:%s" #: pathspec.c #, c-format msgid "pathspec '%s' is beyond a symbolic link" -msgstr "路径规格 '%s' 位于符号链接中" +msgstr "路径规格 '%s' 位于符号链接之后" #: pathspec.c #, c-format msgid "line is badly quoted: %s" -msgstr "行被错误地引用:%s" +msgstr "行引用不正确:%s" #: pkt-line.c msgid "unable to write flush packet" -msgstr "无法写 flush 包" +msgstr "无法写入 flush 包" #: pkt-line.c msgid "unable to write delim packet" -msgstr "无法写 delim 包" +msgstr "无法写入 delim 包" #: pkt-line.c msgid "unable to write response end packet" @@ -24489,11 +24602,11 @@ #: pkt-line.c msgid "flush packet write failed" -msgstr "flush 包写错误" +msgstr "flush 包写入失败" #: pkt-line.c msgid "protocol error: impossibly long line" -msgstr "协议错误:不可能的长行" +msgstr "协议错误:行过长" #: pkt-line.c msgid "packet write with format failed" @@ -24519,7 +24632,7 @@ #: pkt-line.c #, c-format msgid "protocol error: bad line length character: %.4s" -msgstr "协议错误:错误的行长度字符串:%.4s" +msgstr "协议错误:错误的行长度字符:%.4s" #: pkt-line.c #, c-format @@ -24542,11 +24655,11 @@ #: pretty.c msgid "unable to parse --pretty format" -msgstr "不能解析 --pretty 格式" +msgstr "无法解析 --pretty 格式" #: promisor-remote.c msgid "lazy fetching disabled; some objects may not be available" -msgstr "禁用延迟获取,某些对象可能不可用" +msgstr "已禁用延迟获取,某些对象可能不可用" #: promisor-remote.c msgid "promisor-remote: unable to fork off fetch subprocess" @@ -24607,7 +24720,7 @@ #: protocol-caps.c msgid "object-info: expected flush after arguments" -msgstr "object-info:在参数之后应有一个 flush" +msgstr "object-info:在参数之后应有一个 flush 包" #: prune-packed.c msgid "Removing duplicate objects" @@ -24621,7 +24734,7 @@ #: pseudo-merge.c #, c-format msgid "%s must be non-negative, using default" -msgstr "%s 必须为非负整数,使用默认值" +msgstr "%s 必须为非负值,使用默认值" #: pseudo-merge.c #, c-format @@ -24647,7 +24760,7 @@ #, c-format msgid "" "pseudo-merge regex from config has too many capture groups (max=%<PRIuMAX>)" -msgstr "来自 config 的伪合并正则表达式有太多的捕获组(最多 %<PRIuMAX> 个)" +msgstr "来自配置的伪合并正则表达式有太多的捕获组(最多 %<PRIuMAX> 个)" #: pseudo-merge.c #, c-format @@ -24681,16 +24794,16 @@ #: range-diff.c msgid "could not start `log`" -msgstr "不能启动 `log`" +msgstr "无法启动 `log`" #: range-diff.c msgid "could not read `log` output" -msgstr "不能读取 `log` 的输出" +msgstr "无法读取 `log` 的输出" #: range-diff.c sequencer.c #, c-format msgid "could not parse commit '%s'" -msgstr "不能解析提交 '%s'" +msgstr "无法解析提交 '%s'" #: range-diff.c #, c-format @@ -24721,12 +24834,12 @@ #: range-diff.c #, c-format msgid "could not parse log for '%s'" -msgstr "不能解析 '%s' 的日志" +msgstr "无法解析 '%s' 的日志" #: reachable.c #, c-format msgid "invalid extra cruft tip: '%s'" -msgstr "无效的额外废弃提交版本:'%s'" +msgstr "无效的额外废弃对象 tip:'%s'" #: reachable.c msgid "unable to enumerate additional recent objects" @@ -24739,12 +24852,12 @@ #: read-cache.c msgid "cannot create an empty blob in the object database" -msgstr "不能在对象数据库中创建空的数据对象" +msgstr "无法在对象数据库中创建空的数据对象" #: read-cache.c #, c-format msgid "%s: can only add regular files, symbolic links or git-directories" -msgstr "%s:只能添加常规文件、符号链接或 git 目录" +msgstr "%s:只能添加常规文件、符号链接或 Git 目录" #: read-cache.c #, c-format @@ -24786,16 +24899,16 @@ #: read-cache.c #, c-format msgid "bad signature 0x%08x" -msgstr "坏的签名 0x%08x" +msgstr "无效的签名 0x%08x" #: read-cache.c #, c-format msgid "bad index version %d" -msgstr "坏的索引版本 %d" +msgstr "无效的索引版本 %d" #: read-cache.c msgid "bad index file sha1 signature" -msgstr "坏的索引文件 sha1 签名" +msgstr "无效的索引文件 SHA-1 签名" #: read-cache.c #, c-format @@ -24849,7 +24962,7 @@ #: read-cache.c #, c-format msgid "%s: cannot stat the open index" -msgstr "%s:不能对打开的索引执行 stat 操作" +msgstr "%s:无法对打开的索引执行 stat 操作" #: read-cache.c #, c-format @@ -24892,7 +25005,7 @@ #: read-cache.c #, c-format msgid "unable to open git dir: %s" -msgstr "不能打开 git 目录:%s" +msgstr "无法打开 Git 目录:%s" #: read-cache.c #, c-format @@ -24902,12 +25015,12 @@ #: read-cache.c #, c-format msgid "cannot fix permission bits on '%s'" -msgstr "不能修复 '%s' 的权限位" +msgstr "无法修复 '%s' 的权限位" #: read-cache.c #, c-format msgid "%s: cannot drop to stage #0" -msgstr "%s:不能落到暂存区 #0" +msgstr "%s:无法落到暂存区 #0" #: read-cache.c #, c-format @@ -24965,7 +25078,7 @@ "命令:\n" "p, pick <提交> = 使用提交\n" "r, reword <提交> = 使用提交,但编辑提交说明\n" -"e, edit <提交> = 使用提交,但停止以便修补提交\n" +"e, edit <提交> = 使用提交,但停止以便修订提交\n" "s, squash <提交> = 使用提交,但挤压到前一个提交\n" "f, fixup [-C | -c] <提交> = 类似于 \"squash\",但只保留前一个提交\n" " 的提交说明,除非使用了 -C 参数,此情况下则只\n" @@ -24977,9 +25090,9 @@ "l, label <label> = 为当前 HEAD 打上标记\n" "t, reset <label> = 重置 HEAD 到该标记\n" "m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]\n" -". 创建一个合并提交,并使用原始的合并提交说明(如果没有指定\n" -". 原始提交,使用注释部分的 oneline 作为提交说明)。使用\n" -". -c <提交> 可以编辑提交说明。\n" +" 创建一个合并提交,并使用原始的合并提交说明(如果没有指定\n" +" 原始提交,使用注释部分的 oneline 作为提交说明)。使用\n" +" -c <提交> 可以编辑提交说明。\n" "u, update-ref <引用> = 为引用 <ref> 设置一个占位符,以将该引用更新为此处的新" "提交。\n" " 此 <引用> 在变基结束后更新。\n" @@ -24990,8 +25103,8 @@ #, c-format msgid "Rebase %s onto %s (%d command)" msgid_plural "Rebase %s onto %s (%d commands)" -msgstr[0] "变基 %s 到 %s(%d 个提交)" -msgstr[1] "变基 %s 到 %s(%d 个提交)" +msgstr[0] "变基 %s 到 %s(%d 个命令)" +msgstr[1] "变基 %s 到 %s(%d 个命令)" #: rebase-interactive.c msgid "" @@ -25036,7 +25149,7 @@ #: rebase-interactive.c #, c-format msgid "could not write '%s'." -msgstr "不能写入 '%s'。" +msgstr "无法写入 '%s'。" #: rebase-interactive.c #, c-format @@ -25135,7 +25248,7 @@ #: ref-filter.c #, c-format msgid "argument expected for %s" -msgstr "预期参数 %s" +msgstr "缺少 %s 的参数" #: ref-filter.c #, c-format @@ -25145,7 +25258,7 @@ #: ref-filter.c #, c-format msgid "cannot fully parse %s=%s" -msgstr "不能完整解析 %s=%s" +msgstr "无法完整解析 %s=%s" #: ref-filter.c #, c-format @@ -25206,7 +25319,7 @@ #, c-format msgid "" "not a git repository, but the field '%.*s' requires access to object data" -msgstr "不是 git 仓库,但是字段 '%.*s' 需要访问对象数据" +msgstr "不是 Git 仓库,但是字段 '%.*s' 需要访问对象数据" #: ref-filter.c #, c-format @@ -25249,6 +25362,11 @@ msgstr "--format=%.*s 不能和 --python、--shell、--tcl 同时使用" #: ref-filter.c +#, c-format +msgid "parse_object_buffer failed on %s for %s" +msgstr "parse_object_buffer 失败于 %2$s 的 %1$s" + +#: ref-filter.c msgid "failed to run 'describe'" msgstr "无法运行 'describe'" @@ -25288,11 +25406,6 @@ #: ref-filter.c #, c-format -msgid "parse_object_buffer failed on %s for %s" -msgstr "parse_object_buffer 失败于 %2$s 的 %1$s" - -#: ref-filter.c -#, c-format msgid "malformed object at '%s'" msgstr "格式错误的对象 '%s'" @@ -25323,7 +25436,7 @@ #: ref-filter.h msgid "key" -msgstr "key" +msgstr "键" #: ref-filter.h msgid "field name to sort on" @@ -25426,7 +25539,7 @@ #: refs.c #, c-format msgid "log for ref %s unexpectedly ended on %s" -msgstr "引用 %s 的日志意外终止于 %s " +msgstr "引用 %s 的日志意外终止于 %s" #: refs.c #, c-format @@ -25483,7 +25596,7 @@ #: refs.c #, c-format msgid "cannot process '%s' and '%s' at the same time" -msgstr "无法同时处理 '%s' 和 '%s'" +msgstr "不能同时处理 '%s' 和 '%s'" #: refs.c #, c-format @@ -25618,17 +25731,17 @@ #: refs/reftable-backend.c #, c-format msgid "reftable: transaction prepare: %s" -msgstr "reftable: 事务准备:%s" +msgstr "reftable:事务准备:%s" #: refs/reftable-backend.c #, c-format msgid "reftable: transaction failure: %s" -msgstr "reftable: 事务失败:%s" +msgstr "reftable:事务失败:%s" #: refs/reftable-backend.c #, c-format msgid "unable to compact stack: %s" -msgstr "无法压缩栈:%s" +msgstr "无法压缩堆栈:%s" #: refs/reftable-backend.c #, c-format @@ -25640,6 +25753,21 @@ msgid "refname %s is a symbolic ref, copying it is not supported" msgstr "引用名 %s 是一个符号引用,不支持复制" +#: refs/reftable-backend.c +#, c-format +msgid "reftable stack for worktree '%s' is broken" +msgstr "工作区 '%s' 的 reftable 堆栈已损坏" + +#: refs/reftable-backend.c +#, c-format +msgid "could not create iterator for worktree '%s'" +msgstr "无法为工作区 '%s' 创建迭代器" + +#: refs/reftable-backend.c +#, c-format +msgid "could not read record for worktree '%s'" +msgstr "无法读取工作区 '%s' 的记录" + #: refspec.c #, c-format msgid "pattern '%s' has no '*'" @@ -25663,7 +25791,7 @@ #: remote-curl.c #, c-format msgid "%sinfo/refs not valid: is this a git repository?" -msgstr "%sinfo/refs 无效:这是 git 仓库么?" +msgstr "%sinfo/refs 无效:这是 Git 仓库吗?" #: remote-curl.c msgid "invalid server response; expected service, got flush packet" @@ -25714,7 +25842,7 @@ #: remote-curl.c #, c-format msgid "remote-curl: bad line length character: %.4s" -msgstr "remote-curl:错误的行宽字符:%.4s" +msgstr "remote-curl:错误的行长度字符:%.4s" #: remote-curl.c msgid "remote-curl: unexpected response end packet" @@ -25728,17 +25856,17 @@ #: remote-curl.c #, c-format msgid "cannot deflate request; zlib deflate error %d" -msgstr "不能压缩请求,zlib 压缩错误 %d" +msgstr "无法压缩请求,zlib 压缩错误 %d" #: remote-curl.c #, c-format msgid "cannot deflate request; zlib end error %d" -msgstr "不能压缩请求,zlib 结束错误 %d" +msgstr "无法压缩请求,zlib 结束错误 %d" #: remote-curl.c #, c-format msgid "%d bytes of length header were received" -msgstr "收到了 %d 字节长度的头信息" +msgstr "收到了 %d 字节的长度头部" #: remote-curl.c #, c-format @@ -25747,7 +25875,7 @@ #: remote-curl.c msgid "dumb http transport does not support shallow capabilities" -msgstr "哑 http 传输不支持浅克隆能力" +msgstr "哑 HTTP 传输不支持浅克隆能力" #: remote-curl.c msgid "fetch failed." @@ -25755,7 +25883,7 @@ #: remote-curl.c msgid "cannot fetch by sha1 over smart http" -msgstr "无法通过智能 HTTP 获取 sha1" +msgstr "无法通过智能 HTTP 获取 SHA-1" #: remote-curl.c #, c-format @@ -25765,7 +25893,7 @@ #: remote-curl.c #, c-format msgid "http transport does not support %s" -msgstr "http 传输协议不支持 %s" +msgstr "HTTP 传输协议不支持 %s" #: remote-curl.c msgid "protocol error: expected '<url> <path>', missing space" @@ -25782,7 +25910,7 @@ #: remote-curl.c msgid "remote-curl: usage: git remote-curl <remote> [<url>]" -msgstr "remote-curl:用法:git remote-curl <远程> [<url>]" +msgstr "remote-curl:用法:git remote-curl <远程> [<URL>]" #: remote-curl.c msgid "remote-curl: error reading command stream from git" @@ -25790,7 +25918,7 @@ #: remote-curl.c msgid "remote-curl: fetch attempted without a local repo" -msgstr "remote-curl:尝试没有本地仓库下获取" +msgstr "remote-curl:在没有本地仓库的情况下尝试获取" #: remote-curl.c #, c-format @@ -25810,13 +25938,13 @@ "If you cannot, please let us know why you still need to use it by\n" "sending an e-mail to <git@vger.kernel.org>." msgstr "" -"正在从 \"%s/%s\" 读取远程内容,该内容被提名删除。\n" +"正在从 \"%s/%s\" 读取远程内容,该内容被列入移除计划。\n" "\n" -"如果你仍然在使用 \"remotes/\" 目录,建议迁移为基于配置远程的方式:\n" +"如果您仍然在使用 \"remotes/\" 目录,建议迁移为基于配置远程的方式:\n" "\n" "\tgit remote rename %s %s\n" "\n" -"如果你无法迁移,请发送电子邮件至 <git@vger.kernel.org> 告知我们你\n" +"如果您无法迁移,请发送电子邮件至 <git@vger.kernel.org> 告知我们您\n" "仍然需要使用它的原因。" #: remote.c @@ -25850,7 +25978,7 @@ #: remote.c #, c-format msgid "Cannot fetch both %s and %s to %s" -msgstr "不能同时获取 %s 和 %s 至 %s" +msgstr "不能同时获取 %s 和 %s 到 %s" #: remote.c #, c-format @@ -25906,7 +26034,7 @@ "'%s:refs/heads/%s'?" msgstr "" "引用规格的 <src> 是一个提交对象。您是想创建一个新的分支而向\n" -"'%s:refs/heads/%s' 推送么?" +"'%s:refs/heads/%s' 推送吗?" #: remote.c #, c-format @@ -25916,7 +26044,7 @@ "'%s:refs/tags/%s'?" msgstr "" "引用规格的 <src> 是一个标签对象。您是想创建一个新的标签而向\n" -"'%s:refs/tags/%s' 推送么?" +"'%s:refs/tags/%s' 推送吗?" #: remote.c #, c-format @@ -25926,7 +26054,7 @@ "'%s:refs/tags/%s'?" msgstr "" "引用规格的 <src> 是一个树对象。您是想为这个树对象创建标签而向\n" -"'%s:refs/tags/%s' 推送么?" +"'%s:refs/tags/%s' 推送吗?" #: remote.c #, c-format @@ -25936,7 +26064,7 @@ "'%s:refs/tags/%s'?" msgstr "" "引用规格的 <src> 是一个数据对象。您是想为这个数据对象创建标签而向\n" -"'%s:refs/tags/%s' 推送么?" +"'%s:refs/tags/%s' 推送吗?" #: remote.c #, c-format @@ -26014,7 +26142,7 @@ #: remote.c #, c-format msgid "* Ignoring funny ref '%s' locally" -msgstr "* 在本地忽略可笑的引用 '%s'" +msgstr "* 在本地忽略异常的引用 '%s'" #: remote.c #, c-format @@ -26093,12 +26221,12 @@ #: remote.c #, c-format msgid "cannot strip one component off url '%s'" -msgstr "无法从 url '%s' 剥离一个组件" +msgstr "无法从 URL '%s' 剥离一个组件" #: repack-geometry.c #, c-format msgid "cannot open index for %s" -msgstr "不能打开 %s 的索引" +msgstr "无法打开 %s 的索引" #: repack-geometry.c #, c-format @@ -26108,7 +26236,7 @@ #: repack-geometry.c #, c-format msgid "pack %s too large to roll up" -msgstr "包 %s 太大导致数字溢出" +msgstr "包 %s 太大,无法汇总" #: repack-midx.c #, c-format @@ -26117,12 +26245,12 @@ #: repack-midx.c msgid "could not close refs snapshot tempfile" -msgstr "不能关闭引用快照临时文件" +msgstr "无法关闭引用快照临时文件" #: repack-midx.c #, c-format msgid "could not remove stale bitmap: %s" -msgstr "无法删除过期的位图: %s" +msgstr "无法删除过期的位图:%s" #: repack-promisor.c msgid "could not start pack-objects to repack promisor objects" @@ -26138,7 +26266,11 @@ #: repack-promisor.c msgid "could not finish pack-objects to repack promisor objects" -msgstr "无法完成 pack-objects 来重新打包 promisor 对象" +msgstr "无法完成 pack-objects 来重新打包承诺者对象" + +#: repack-promisor.c +msgid "could not start pack-objects to repack promisor packs" +msgstr "无法启动 pack-objects 来重新打包承诺者包" #: repack.c #, c-format @@ -26148,7 +26280,7 @@ #: repack.c #, c-format msgid "renaming pack to '%s' failed" -msgstr "重命名包至 '%s' 失败" +msgstr "重命名包为 '%s' 失败" #: repack.c #, c-format @@ -26158,12 +26290,12 @@ #: repack.c sequencer.c #, c-format msgid "could not unlink: %s" -msgstr "不能删除:%s" +msgstr "无法删除:%s" #: replace-object.c #, c-format msgid "bad replace ref name: %s" -msgstr "错误的替换引用名称:%s" +msgstr "无效的替换引用名称:%s" #: replace-object.c #, c-format @@ -26191,7 +26323,7 @@ #: rerere.c #, c-format msgid "could not parse conflict hunks in '%s'" -msgstr "不能解析 '%s' 中的冲突块" +msgstr "无法解析 '%s' 中的冲突块" #: rerere.c #, c-format @@ -26221,7 +26353,7 @@ #: rerere.c #, c-format msgid "cannot unlink stray '%s'" -msgstr "不能删除 stray '%s'" +msgstr "无法删除多余的 '%s'" #: rerere.c #, c-format @@ -26250,15 +26382,15 @@ #: rerere.c msgid "unable to open rr-cache directory" -msgstr "不能打开 rr-cache 目录" +msgstr "无法打开 rr-cache 目录" #: rerere.h msgid "update the index with reused conflict resolution if possible" -msgstr "如果可能,重用冲突解决更新索引" +msgstr "如果可能,使用复用的冲突解决结果更新索引" #: reset.c msgid "could not determine HEAD revision" -msgstr "不能确定 HEAD 版本" +msgstr "无法确定 HEAD 版本" #: reset.c sequencer.c #, c-format @@ -26268,7 +26400,7 @@ #: revision.c #, c-format msgid "unsupported section for hidden refs: %s" -msgstr "不支持的隐藏引用片段: %s" +msgstr "不支持的隐藏引用小节:%s" #: revision.c msgid "--exclude-hidden= passed more than once" @@ -26282,7 +26414,7 @@ #: revision.c #, c-format msgid "%s exists but is a symbolic ref" -msgstr "%s 存在但是一个符号引用" +msgstr "%s 存在但为符号引用" #: revision.c msgid "" @@ -26308,7 +26440,7 @@ #: revision.c msgid "your current branch appears to be broken" -msgstr "您的当前分支好像被损坏" +msgstr "您的当前分支似乎已损坏" #: revision.c #, c-format @@ -26326,7 +26458,7 @@ #: run-command.c #, c-format msgid "cannot create async thread: %s" -msgstr "不能创建 async 线程:%s" +msgstr "无法创建 async 线程:%s" #: scalar.c worktree.c #, c-format @@ -26414,7 +26546,7 @@ #: scalar.c msgid "only download metadata for the branch that will be checked out" -msgstr "只下载要检出的分支的元信息" +msgstr "只下载要检出的分支的元数据" #: scalar.c msgid "create repository within 'src' directory" @@ -26422,7 +26554,7 @@ #: scalar.c msgid "specify if tags should be fetched during clone" -msgstr "如若应在克隆期间获取标签则指定" +msgstr "指定是否在克隆期间获取标签" #: scalar.c msgid "specify if background maintenance should be enabled" @@ -26507,7 +26639,7 @@ #: scalar.c msgid "--all or <enlistment>, but not both" -msgstr "--all 或者 <登记>,而不是两个一起" +msgstr "--all 或 <登记>,但不能同时使用" #: scalar.c #, c-format @@ -26592,7 +26724,7 @@ #: scalar.c #, c-format msgid "could not change to '%s'" -msgstr "无法变更到 '%s'" +msgstr "无法切换到 '%s'" #: scalar.c msgid "-c requires a <key>=<value> argument" @@ -26615,7 +26747,7 @@ #: send-pack.c #, c-format msgid "unable to parse remote unpack status: %s" -msgstr "不能解析远程解包状态:%s" +msgstr "无法解析远程解包状态:%s" #: send-pack.c #, c-format @@ -26735,12 +26867,12 @@ #: sequencer.c #, c-format msgid "could not lock '%s'" -msgstr "不能锁定 '%s'" +msgstr "无法锁定 '%s'" #: sequencer.c #, c-format msgid "could not write eol to '%s'" -msgstr "不能将换行符写入 '%s'" +msgstr "无法将换行符写入 '%s'" #: sequencer.c #, c-format @@ -26750,11 +26882,11 @@ #: sequencer.c #, c-format msgid "your local changes would be overwritten by %s." -msgstr "您的本地修改将被%s覆盖。" +msgstr "您的本地修改将被 %s 覆盖。" #: sequencer.c msgid "commit your changes or stash them to proceed." -msgstr "提交您的修改或贮藏后再继续。" +msgstr "提交您的修改或储藏后再继续。" #. TRANSLATORS: %s will be "revert", "cherry-pick" or #. "rebase". @@ -26766,16 +26898,16 @@ #: sequencer.c msgid "unable to update cache tree" -msgstr "不能更新缓存树" +msgstr "无法更新缓存树" #: sequencer.c msgid "could not resolve HEAD commit" -msgstr "不能解析 HEAD 提交" +msgstr "无法解析 HEAD 提交" #: sequencer.c #, c-format msgid "no key present in '%.*s'" -msgstr "在 '%.*s' 中没有 key" +msgstr "在 '%.*s' 中没有键" #: sequencer.c #, c-format @@ -26897,11 +27029,11 @@ #: sequencer.c msgid "could not parse newly created commit" -msgstr "不能解析新创建的提交" +msgstr "无法解析新创建的提交" #: sequencer.c msgid "unable to resolve HEAD after creating commit" -msgstr "创建提交后,不能解析 HEAD" +msgstr "创建提交后,无法解析 HEAD" #: sequencer.c msgid "detached HEAD" @@ -26914,7 +27046,7 @@ #: sequencer.c msgid "could not parse HEAD" -msgstr "不能解析 HEAD" +msgstr "无法解析 HEAD" #: sequencer.c #, c-format @@ -26923,12 +27055,12 @@ #: sequencer.c msgid "unable to parse commit author" -msgstr "不能解析提交作者" +msgstr "无法解析提交作者" #: sequencer.c #, c-format msgid "unable to read commit message from '%s'" -msgstr "不能从 '%s' 读取提交说明" +msgstr "无法从 '%s' 读取提交说明" #: sequencer.c #, c-format @@ -26942,12 +27074,12 @@ #: sequencer.c #, c-format msgid "could not update %s" -msgstr "不能更新 %s" +msgstr "无法更新 %s" #: sequencer.c #, c-format msgid "could not parse parent commit %s" -msgstr "不能解析父提交 %s" +msgstr "无法解析父提交 %s" #: sequencer.c #, c-format @@ -26980,7 +27112,7 @@ #: sequencer.c #, c-format msgid "cannot write '%s'" -msgstr "不能写 '%s'" +msgstr "无法写入 '%s'" #: sequencer.c msgid "need a HEAD to fixup" @@ -26988,16 +27120,16 @@ #: sequencer.c msgid "could not read HEAD" -msgstr "不能读取 HEAD" +msgstr "无法读取 HEAD" #: sequencer.c msgid "could not read HEAD's commit message" -msgstr "不能读取 HEAD 的提交说明" +msgstr "无法读取 HEAD 的提交说明" #: sequencer.c #, c-format msgid "could not read commit message of %s" -msgstr "不能读取 %s 的提交说明" +msgstr "无法读取 %s 的提交说明" #: sequencer.c msgid "your index file is unmerged." @@ -27005,7 +27137,7 @@ #: sequencer.c msgid "cannot fixup root commit" -msgstr "不能修复根提交" +msgstr "无法修复根提交" #: sequencer.c #, c-format @@ -27020,24 +27152,24 @@ #: sequencer.c #, c-format msgid "cannot get commit message for %s" -msgstr "不能得到 %s 的提交说明" +msgstr "无法获取 %s 的提交说明" #. TRANSLATORS: The first %s will be a "todo" command like #. "revert" or "pick", the second %s a SHA1. #: sequencer.c #, c-format msgid "%s: cannot parse parent commit %s" -msgstr "%s:不能解析父提交 %s" +msgstr "%s:无法解析父提交 %s" #: sequencer.c #, c-format msgid "could not revert %s... %s" -msgstr "不能还原 %s... %s" +msgstr "无法还原 %s... %s" #: sequencer.c #, c-format msgid "could not apply %s... %s" -msgstr "不能应用 %s... %s" +msgstr "无法应用 %s... %s" #: sequencer.c #, c-format @@ -27107,7 +27239,7 @@ "'break' to give the control back to you so that you can\n" "do 'git commit --amend && git rebase --continue'." msgstr "" -"'edit”' 不接受合并提交。如果您想要重放合并,\n" +"'edit' 不接受合并提交。如果您想要重放合并,\n" "请在提交上使用 'merge -C',然后使用 'break'\n" "将控制权交还给您,以便您可以执行\n" "'git commit --amend && git rebase --continue'。" @@ -27164,11 +27296,11 @@ #: sequencer.c msgid "cannot cherry-pick during a revert." -msgstr "不能在回退中执行拣选。" +msgstr "无法在回退中执行拣选。" #: sequencer.c msgid "cannot revert during a cherry-pick." -msgstr "不能在拣选中执行回退。" +msgstr "无法在拣选中执行回退。" #: sequencer.c msgid "unusable squash-onto" @@ -27204,7 +27336,7 @@ #: sequencer.c #, c-format msgid "could not create sequencer directory '%s'" -msgstr "不能创建序列目录 '%s'" +msgstr "无法创建序列目录 '%s'" #: sequencer.c msgid "no cherry-pick or revert in progress" @@ -27212,16 +27344,16 @@ #: sequencer.c msgid "cannot resolve HEAD" -msgstr "不能解析 HEAD" +msgstr "无法解析 HEAD" #: sequencer.c msgid "cannot abort from a branch yet to be born" -msgstr "不能从尚未建立的分支终止" +msgstr "无法从尚未建立的分支终止" #: sequencer.c #, c-format msgid "cannot read '%s': %s" -msgstr "不能读取 '%s':%s" +msgstr "无法读取 '%s':%s" #: sequencer.c msgid "unexpected end of file" @@ -27258,12 +27390,12 @@ "have you committed already?\n" "try \"git %s --continue\"" msgstr "" -"您已经提交了么?\n" +"您已经提交了吗?\n" "试试 \"git %s --continue\"" #: sequencer.c msgid "cannot read HEAD" -msgstr "不能读取 HEAD" +msgstr "无法读取 HEAD" #: sequencer.c msgid "could not write commit message file" @@ -27280,7 +27412,7 @@ "\n" " git rebase --continue\n" msgstr "" -"您现在可以修补这个提交,使用\n" +"您现在可以修订这个提交,使用\n" "\n" " git commit --amend %s\n" "\n" @@ -27291,12 +27423,12 @@ #: sequencer.c #, c-format msgid "Could not apply %s... %.*s" -msgstr "不能应用 %s... %.*s" +msgstr "无法应用 %s... %.*s" #: sequencer.c #, c-format msgid "Could not merge %.*s" -msgstr "不能合并 %.*s" +msgstr "无法合并 %.*s" #: sequencer.c #, c-format @@ -27320,7 +27452,7 @@ #: sequencer.c msgid "and made changes to the index and/or the working tree.\n" -msgstr "并且修改索引和/或工作区。\n" +msgstr "并已修改索引和/或工作区。\n" #: sequencer.c #, c-format @@ -27334,7 +27466,7 @@ msgstr "" "执行成功:%s\n" "但是在索引和/或工作区中存在变更。\n" -"提交或贮藏修改,然后运行\n" +"提交或储藏修改,然后运行\n" "\n" " git rebase --continue\n" "\n" @@ -27345,13 +27477,8 @@ msgstr "非法的标签名称:'%.*s'" #: sequencer.c -#, c-format -msgid "could not resolve '%s'" -msgstr "无法解析 '%s'" - -#: sequencer.c msgid "writing fake root commit" -msgstr "写伪根提交" +msgstr "正在写入伪根提交" #: sequencer.c msgid "writing squash-onto" @@ -27359,7 +27486,7 @@ #: sequencer.c msgid "cannot merge without a current revision" -msgstr "没有当前版本不能合并" +msgstr "没有当前版本,无法合并" #: sequencer.c #, c-format @@ -27369,21 +27496,21 @@ #: sequencer.c #, c-format msgid "nothing to merge: '%.*s'" -msgstr "无可用合并:'%.*s'" +msgstr "无可合并内容:'%.*s'" #: sequencer.c msgid "octopus merge cannot be executed on top of a [new root]" -msgstr "章鱼合并不能在一个新的根提交上执行" +msgstr "章鱼合并无法在一个新的根提交上执行" #: sequencer.c #, c-format msgid "could not get commit message of '%s'" -msgstr "不能获取 '%s' 的提交说明" +msgstr "无法获取 '%s' 的提交说明" #: sequencer.c #, c-format msgid "could not even attempt to merge '%.*s'" -msgstr "甚至不能尝试合并 '%.*s'" +msgstr "甚至无法尝试合并 '%.*s'" #: sequencer.c msgid "merge: Unable to write new index file" @@ -27415,22 +27542,22 @@ #: sequencer.c msgid "Cannot autostash" -msgstr "无法自动贮藏" +msgstr "无法自动储藏" #: sequencer.c #, c-format msgid "Unexpected stash response: '%s'" -msgstr "意外的贮藏响应:'%s'" +msgstr "意外的储藏响应:'%s'" #: sequencer.c #, c-format msgid "Could not create directory for '%s'" -msgstr "不能为 '%s' 创建目录" +msgstr "无法为 '%s' 创建目录" #: sequencer.c #, c-format msgid "Created autostash: %s\n" -msgstr "创建了自动贮藏:%s\n" +msgstr "创建了自动储藏:%s\n" #: sequencer.c msgid "could not reset --hard" @@ -27439,12 +27566,12 @@ #: sequencer.c #, c-format msgid "Applied autostash.\n" -msgstr "已应用自动贮藏。\n" +msgstr "已应用自动储藏。\n" #: sequencer.c #, c-format msgid "cannot store %s" -msgstr "不能存储 %s" +msgstr "无法存储 %s" #: sequencer.c #, c-format @@ -27454,24 +27581,24 @@ "You can run \"git stash pop\" or \"git stash drop\" at any time.\n" msgstr "" "%s\n" -"您的修改在贮藏区中很安全。\n" +"您的修改在储藏区中很安全。\n" "您可以在任何时候运行 \"git stash pop\" 或 \"git stash drop\"。\n" #: sequencer.c msgid "Applying autostash resulted in conflicts." -msgstr "应用自动贮藏导致冲突。" +msgstr "应用自动储藏导致冲突。" #: sequencer.c msgid "Autostash exists; creating a new stash entry." -msgstr "自动贮藏已经存在;正在创建一个新的贮藏条目。" +msgstr "自动储藏已经存在;正在创建一个新的储藏条目。" #: sequencer.c msgid "autostash reference is a symref" -msgstr "自动贮藏的引用是一个符号引用" +msgstr "自动储藏的引用是一个符号引用" #: sequencer.c msgid "could not detach HEAD" -msgstr "不能分离头指针" +msgstr "无法分离头指针" #: sequencer.c #, c-format @@ -27520,16 +27647,16 @@ #: sequencer.c msgid "could not read orig-head" -msgstr "不能读取 orig-head" +msgstr "无法读取 orig-head" #: sequencer.c msgid "could not read 'onto'" -msgstr "不能读取 'onto'" +msgstr "无法读取 'onto'" #: sequencer.c #, c-format msgid "could not update HEAD to %s" -msgstr "不能更新 HEAD 为 %s" +msgstr "无法更新 HEAD 为 %s" #: sequencer.c #, c-format @@ -27542,7 +27669,7 @@ #: sequencer.c msgid "cannot amend non-existing commit" -msgstr "不能修补不存在的提交" +msgstr "无法修订不存在的提交" #: sequencer.c #, c-format @@ -27566,25 +27693,25 @@ #: sequencer.c #, c-format msgid "could not write file: '%s'" -msgstr "不能写入文件:'%s'" +msgstr "无法写入文件:'%s'" #: sequencer.c msgid "could not remove CHERRY_PICK_HEAD" -msgstr "不能删除 CHERRY_PICK_HEAD" +msgstr "无法删除 CHERRY_PICK_HEAD" #: sequencer.c msgid "could not commit staged changes." -msgstr "不能提交暂存的修改。" +msgstr "无法提交暂存的修改。" #: sequencer.c #, c-format msgid "%s: can't cherry-pick a %s" -msgstr "%s:不能拣选一个%s" +msgstr "%s:不能拣选一个 %s" #: sequencer.c #, c-format msgid "%s: bad revision" -msgstr "%s:错误的版本" +msgstr "%s:无效的版本" #: sequencer.c msgid "can't revert as initial commit" @@ -27719,7 +27846,7 @@ #: setup.c #, c-format msgid "not a git repository: %s" -msgstr "不是 git 仓库:%s" +msgstr "不是 Git 仓库:%s" #: setup.c #, c-format @@ -27729,12 +27856,12 @@ #: setup.c #, c-format msgid "not a git repository: '%s'" -msgstr "不是 git 仓库:'%s'" +msgstr "不是 Git 仓库:'%s'" #: setup.c #, c-format msgid "cannot chdir to '%s'" -msgstr "不能切换目录到 '%s'" +msgstr "无法切换目录到 '%s'" #: setup.c #, c-format @@ -27761,17 +27888,17 @@ #: setup.c msgid "Unable to read current working directory" -msgstr "不能读取当前工作目录" +msgstr "无法读取当前工作目录" #: setup.c #, c-format msgid "cannot change to '%s'" -msgstr "不能切换到 '%s'" +msgstr "无法切换到 '%s'" #: setup.c #, c-format msgid "not a git repository (or any of the parent directories): %s" -msgstr "不是 git 仓库(或者任何父目录):%s" +msgstr "不是 Git 仓库(或者任何父目录):%s" #: setup.c #, c-format @@ -27779,7 +27906,7 @@ "not a git repository (or any parent up to mount point %s)\n" "Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set)." msgstr "" -"不是 git 仓库(或者直至挂载点 %s 的任何父目录)\n" +"不是 Git 仓库(或者直至挂载点 %s 的任何父目录)\n" "停止在文件系统边界(未设置 GIT_DISCOVERY_ACROSS_FILESYSTEM)。" #: setup.c @@ -27807,42 +27934,42 @@ #: setup.c #, c-format msgid "cannot stat template '%s'" -msgstr "不能对模版 '%s' 调用 stat" +msgstr "无法对模板 '%s' 调用 stat" #: setup.c #, c-format msgid "cannot opendir '%s'" -msgstr "不能打开目录 '%s'" +msgstr "无法打开目录 '%s'" #: setup.c #, c-format msgid "cannot readlink '%s'" -msgstr "不能读取链接 '%s'" +msgstr "无法读取链接 '%s'" #: setup.c #, c-format msgid "cannot symlink '%s' '%s'" -msgstr "不能自 '%s' 到 '%s' 创建符号链接" +msgstr "无法从 '%s' 到 '%s' 创建符号链接" #: setup.c #, c-format msgid "cannot copy '%s' to '%s'" -msgstr "不能拷贝 '%s' 至 '%s'" +msgstr "无法拷贝 '%s' 至 '%s'" #: setup.c #, c-format msgid "ignoring template %s" -msgstr "忽略模版 %s" +msgstr "忽略模板 %s" #: setup.c #, c-format msgid "templates not found in %s" -msgstr "没有在 %s 中找到模版" +msgstr "没有在 %s 中找到模板" #: setup.c #, c-format msgid "not copying templates from '%s': %s" -msgstr "没有从 '%s' 复制模版:%s" +msgstr "没有从 '%s' 复制模板:%s" #: setup.c #, c-format @@ -27857,12 +27984,12 @@ #: setup.c #, c-format msgid "unable to handle file type %d" -msgstr "不能处理 %d 类型的文件" +msgstr "无法处理 %d 类型的文件" #: setup.c #, c-format msgid "unable to move %s to %s" -msgstr "不能移动 %s 至 %s" +msgstr "无法移动 %s 至 %s" #: setup.c msgid "attempt to reinitialize repository with different hash" @@ -27881,22 +28008,22 @@ #: setup.c #, c-format msgid "Reinitialized existing shared Git repository in %s%s\n" -msgstr "重新初始化已存在的共享 Git 仓库于 %s%s\n" +msgstr "在 %s%s 重新初始化已存在的共享 Git 仓库\n" #: setup.c #, c-format msgid "Reinitialized existing Git repository in %s%s\n" -msgstr "已重新初始化已存在的 Git 仓库于 %s%s\n" +msgstr "在 %s%s 重新初始化已存在的 Git 仓库\n" #: setup.c #, c-format msgid "Initialized empty shared Git repository in %s%s\n" -msgstr "已初始化空的共享 Git 仓库于 %s%s\n" +msgstr "在 %s%s 初始化空的共享 Git 仓库\n" #: setup.c #, c-format msgid "Initialized empty Git repository in %s%s\n" -msgstr "已初始化空的 Git 仓库于 %s%s\n" +msgstr "在 %s%s 初始化空的 Git 仓库\n" #: sparse-index.c #, c-format @@ -27911,71 +28038,97 @@ #: strbuf.c #, c-format msgid "bad %s format: element '%s' does not start with '('" -msgstr "坏的 %s 格式:元素 '%s' 没有以 '(' 开头" +msgstr "无效的 %s 格式:元素 '%s' 没有以 '(' 开头" #. TRANSLATORS: The first %s is a command like "ls-tree". #: strbuf.c #, c-format msgid "bad %s format: element '%s' does not end in ')'" -msgstr "坏的 %s 格式:元素 '%s' 没有以 ')' 结尾" +msgstr "无效的 %s 格式:元素 '%s' 没有以 ')' 结尾" #. TRANSLATORS: %s is a command like "ls-tree". #: strbuf.c #, c-format msgid "bad %s format: %%%.*s" -msgstr "坏的 %s 格式: %%%.*s" +msgstr "无效的 %s 格式:%%%.*s" -#. TRANSLATORS: IEC 80000-13:2008 gibibyte #: strbuf.c #, c-format -msgid "%u.%2.2u GiB" -msgstr "%u.%2.2u GiB" +msgid "%u.%2.2u" +msgstr "%u.%2.2u" -#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second +#. TRANSLATORS: SI decimal prefix symbol for 10^9 #: strbuf.c -#, c-format -msgid "%u.%2.2u GiB/s" -msgstr "%u.%2.2u GiB/s" +msgid "G" +msgstr "G" -#. TRANSLATORS: IEC 80000-13:2008 mebibyte +#. TRANSLATORS: SI decimal prefix symbol for 10^6 #: strbuf.c -#, c-format -msgid "%u.%2.2u MiB" -msgstr "%u.%2.2u MiB" +msgid "M" +msgstr "M" -#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second +#. TRANSLATORS: SI decimal prefix symbol for 10^3 #: strbuf.c -#, c-format -msgid "%u.%2.2u MiB/s" -msgstr "%u.%2.2u MiB/s" +msgid "k" +msgstr "k" -#. TRANSLATORS: IEC 80000-13:2008 kibibyte +#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second and gibibyte #: strbuf.c -#, c-format -msgid "%u.%2.2u KiB" -msgstr "%u.%2.2u KiB" +msgid "GiB/s" +msgstr "GiB/s" -#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second #: strbuf.c -#, c-format -msgid "%u.%2.2u KiB/s" -msgstr "%u.%2.2u KiB/s" +msgid "GiB" +msgstr "GiB" -#. TRANSLATORS: IEC 80000-13:2008 byte +#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second and mebibyte #: strbuf.c -#, c-format -msgid "%u byte" -msgid_plural "%u bytes" -msgstr[0] "%u 字节" -msgstr[1] "%u 字节" +msgid "MiB/s" +msgstr "MiB/s" + +#: strbuf.c +msgid "MiB" +msgstr "MiB" + +#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second and kibibyte +#: strbuf.c +msgid "KiB/s" +msgstr "KiB/s" + +#: strbuf.c +msgid "KiB" +msgstr "KiB" + +#. TRANSLATORS: IEC 80000-13:2008 byte/second and byte +#: strbuf.c +msgid "B/s" +msgstr "B/s" + +#: strbuf.c +msgid "B" +msgstr "B" #. TRANSLATORS: IEC 80000-13:2008 byte/second #: strbuf.c +msgid "byte/s" +msgid_plural "bytes/s" +msgstr[0] "字节/秒" +msgstr[1] "字节/秒" + +#. TRANSLATORS: IEC 80000-13:2008 byte +#: strbuf.c +msgid "byte" +msgid_plural "bytes" +msgstr[0] "字节" +msgstr[1] "字节" + +#. TRANSLATORS: The first argument is the number string. The second +#. argument is the unit string (i.e. "12.34 MiB/s"). +#. +#: strbuf.c #, c-format -msgid "%u byte/s" -msgid_plural "%u bytes/s" -msgstr[0] "%u 字节/秒" -msgstr[1] "%u 字节/秒" +msgid "%s %s" +msgstr "%s %s" #: submodule-config.c #, c-format @@ -27994,7 +28147,7 @@ #: submodule-config.c #, c-format msgid "Could not update .gitmodules entry %s" -msgstr "不能更新 .gitmodules 条目 %s" +msgstr "无法更新 .gitmodules 条目 %s" #: submodule.c msgid "Cannot change unmerged .gitmodules, resolve merge conflicts first" @@ -28027,14 +28180,14 @@ #: submodule.c #, c-format msgid "bad --ignore-submodules argument: %s" -msgstr "坏的 --ignore-submodules 参数:%s" +msgstr "无效的 --ignore-submodules 参数:%s" #: submodule.c #, c-format msgid "" "Submodule in commit %s at path: '%s' collides with a submodule named the " "same. Skipping it." -msgstr "提交 %s 中位于路径 '%s' 的子模组和同名的子模组冲突。 跳过它。" +msgstr "提交 %s 中位于路径 '%s' 的子模组和同名的子模组冲突。跳过它。" #: submodule.c #, c-format @@ -28076,12 +28229,12 @@ #: submodule.c #, c-format msgid "Could not access submodule '%s' at commit %s\n" -msgstr "无法访问子模组 '%s' 提交 %s\n" +msgstr "无法访问子模组 '%s' 的提交 %s\n" #: submodule.c #, c-format msgid "Fetching submodule %s%s at commit %s\n" -msgstr "正在获取子模组 %s%s 提交 %s\n" +msgstr "正在获取子模组 %s%s 的提交 %s\n" #: submodule.c #, c-format @@ -28095,7 +28248,7 @@ #: submodule.c #, c-format msgid "'%s' not recognized as a git repository" -msgstr "无法将 '%s' 识别为 git 仓库" +msgstr "无法将 '%s' 识别为 Git 仓库" #: submodule.c #, c-format @@ -28125,7 +28278,7 @@ #: submodule.c #, c-format msgid "could not recurse into submodule '%s'" -msgstr "无法递归进子模组路径 '%s'" +msgstr "无法递归进入子模组 '%s'" #: submodule.c msgid "could not reset submodule index" @@ -28144,7 +28297,7 @@ #: submodule.c #, c-format msgid "submodule git dir '%s' is inside git dir '%.*s'" -msgstr "子模组 git 目录 '%s' 位于 git 目录 '%.*s' 中" +msgstr "子模组 Git 目录 '%s' 位于 Git 目录 '%.*s' 中" #: submodule.c #, c-format @@ -28165,12 +28318,12 @@ #: submodule.c #, c-format msgid "could not lookup name for submodule '%s'" -msgstr "不能查询子模组 '%s' 的名称" +msgstr "无法查询子模组 '%s' 的名称" #: submodule.c #, c-format msgid "refusing to move '%s' into an existing git dir" -msgstr "禁止移动 '%s' 到现存 git 目录中" +msgstr "拒绝移动 '%s' 到现存 Git 目录中" #: submodule.c #, c-format @@ -28179,7 +28332,7 @@ "'%s' to\n" "'%s'\n" msgstr "" -"将 '%s%s' 的 git 目录从\n" +"将 '%s%s' 的 Git 目录从\n" "'%s' 迁移至\n" "'%s'\n" @@ -28190,7 +28343,7 @@ #: submodule.c #, c-format msgid "ls-tree returned unexpected return code %d" -msgstr "ls-tree 返回未知返回值 %d" +msgstr "ls-tree 返回意外返回码 %d" #: symlinks.c #, c-format @@ -28199,7 +28352,7 @@ #: t/helper/test-bundle-uri.c msgid "no remote configured to get bundle URIs from" -msgstr "没有远程被设置为可以获取归档包 URI" +msgstr "没有配置可用于获取 bundle URI 的远程" #: t/helper/test-bundle-uri.c msgid "could not get the bundle-uri list" @@ -28264,7 +28417,7 @@ #: t/helper/test-read-midx.c msgid "could not determine MIDX preferred pack" -msgstr "不能确定多包索引的首选包" +msgstr "无法确定多包索引的首选包" #: t/helper/test-serve-v2.c msgid "test-tool serve-v2 [<options>]" @@ -28276,43 +28429,43 @@ #: t/helper/test-simple-ipc.c msgid "test-helper simple-ipc is-active [<name>] [<options>]" -msgstr "test-helper simple-ipc is-active [<名字>] [<选项>]" +msgstr "test-helper simple-ipc is-active [<名称>] [<选项>]" #: t/helper/test-simple-ipc.c msgid "test-helper simple-ipc run-daemon [<name>] [<threads>]" -msgstr "test-helper simple-ipc run-daemon [<名字>] [<线程>]" +msgstr "test-helper simple-ipc run-daemon [<名称>] [<线程>]" #: t/helper/test-simple-ipc.c msgid "test-helper simple-ipc start-daemon [<name>] [<threads>] [<max-wait>]" -msgstr "test-helper simple-ipc start-daemon [<名字>] [<线程>] [<最大等待>]" +msgstr "test-helper simple-ipc start-daemon [<名称>] [<线程>] [<最大等待>]" #: t/helper/test-simple-ipc.c msgid "test-helper simple-ipc stop-daemon [<name>] [<max-wait>]" -msgstr "test-helper simple-ipc stop-daemon [<名字>] [<最大等待>]" +msgstr "test-helper simple-ipc stop-daemon [<名称>] [<最大等待>]" #: t/helper/test-simple-ipc.c msgid "test-helper simple-ipc send [<name>] [<token>]" -msgstr "test-helper simple-ipc send [<名字>] [<令牌>]" +msgstr "test-helper simple-ipc send [<名称>] [<令牌>]" #: t/helper/test-simple-ipc.c msgid "test-helper simple-ipc sendbytes [<name>] [<bytecount>] [<byte>]" -msgstr "test-helper simple-ipc sendbytes [<名字>] [<字节数>] [<字节>]" +msgstr "test-helper simple-ipc sendbytes [<名称>] [<字节数>] [<字节>]" #: t/helper/test-simple-ipc.c msgid "" "test-helper simple-ipc multiple [<name>] [<threads>] [<bytecount>] " "[<batchsize>]" msgstr "" -"test-helper simple-ipc multiple [<名字>] [<线程>] [<字节计数>] [<批处理大" +"test-helper simple-ipc multiple [<名称>] [<线程>] [<字节计数>] [<批处理大" "小>]" #: t/helper/test-simple-ipc.c msgid "name or pathname of unix domain socket" -msgstr "unix 域套接字的名称或路径名" +msgstr "Unix 域套接字的名称或路径名" #: t/helper/test-simple-ipc.c msgid "named-pipe name" -msgstr "命名管道的名字" +msgstr "命名管道的名称" #: t/helper/test-simple-ipc.c msgid "number of threads in server thread pool" @@ -28331,12 +28484,8 @@ msgstr "每个线程的请求数" #: t/helper/test-simple-ipc.c -msgid "byte" -msgstr "字节" - -#: t/helper/test-simple-ipc.c msgid "ballast character" -msgstr "ballast character" +msgstr "压仓字符" #: t/helper/test-simple-ipc.c msgid "token" @@ -28403,7 +28552,7 @@ msgid "" "unknown mandatory capability %s; this remote helper probably needs newer " "version of Git" -msgstr "未知的强制能力 %s,该远程助手可能需要新版本的Git" +msgstr "未知的强制能力 %s,该远程助手可能需要新版本的 Git" #: transport-helper.c msgid "this remote helper should implement refspec capability" @@ -28421,7 +28570,7 @@ #: transport-helper.c msgid "couldn't run fast-import" -msgstr "不能执行 fast-import" +msgstr "无法执行 fast-import" #: transport-helper.c msgid "error while running fast-import" @@ -28448,7 +28597,7 @@ #: transport-helper.c #, c-format msgid "can't connect to subservice %s" -msgstr "不能连接到子服务 %s" +msgstr "无法连接到子服务 %s" #: transport-helper.c transport.c msgid "--negotiate-only requires protocol v2" @@ -28500,7 +28649,7 @@ #: transport-helper.c msgid "remote-helper doesn't support push; refspec needed" -msgstr "remote-heper 不支持推送,需要引用规格" +msgstr "remote-helper 不支持推送,需要引用规格" #: transport-helper.c #, c-format @@ -28557,7 +28706,7 @@ #: transport-helper.c #, c-format msgid "can't start thread for copying data: %s" -msgstr "不能启动线程来拷贝数据:%s" +msgstr "无法启动线程来拷贝数据:%s" #: transport-helper.c #, c-format @@ -28571,7 +28720,7 @@ #: transport-helper.c msgid "can't start thread for copying data" -msgstr "不能启动线程来拷贝数据" +msgstr "无法启动线程来拷贝数据" #: transport.c #, c-format @@ -28581,7 +28730,7 @@ #: transport.c #, c-format msgid "could not read bundle '%s'" -msgstr "无法读取归档包 '%s'" +msgstr "无法读取捆绑包 '%s'" #: transport.c #, c-format @@ -28602,7 +28751,7 @@ #: transport.c msgid "could not parse transport.color.* config" -msgstr "不能解析 transport.color.* 配置" +msgstr "无法解析 transport.color.* 配置" #: transport.c msgid "support for protocol v2 not implemented yet" @@ -28657,7 +28806,7 @@ #: transport.c msgid "failed to push all needed submodules" -msgstr "不能推送全部需要的子模组" +msgstr "无法推送全部需要的子模组" #: transport.c msgid "bundle-uri operation not supported by protocol" @@ -28673,7 +28822,7 @@ #: tree-walk.c msgid "too-short tree object" -msgstr "太短的树对象" +msgstr "树对象太短" #: tree-walk.c msgid "malformed mode in tree entry" @@ -28685,7 +28834,7 @@ #: tree-walk.c msgid "too-short tree file" -msgstr "太短的树文件" +msgstr "树文件太短" #: unpack-trees.c #, c-format @@ -28694,7 +28843,7 @@ "%%sPlease commit your changes or stash them before you switch branches." msgstr "" "您对下列文件的本地修改将被检出操作覆盖:\n" -"%%s请在切换分支前提交或贮藏您的修改。" +"%%s请在切换分支前提交或储藏您的修改。" #: unpack-trees.c #, c-format @@ -28712,7 +28861,7 @@ "%%sPlease commit your changes or stash them before you merge." msgstr "" "您对下列文件的本地修改将被合并操作覆盖:\n" -"%%s请在合并前提交或贮藏您的修改。" +"%%s请在合并前提交或储藏您的修改。" #: unpack-trees.c #, c-format @@ -28730,7 +28879,7 @@ "%%sPlease commit your changes or stash them before you %s." msgstr "" "您对下列文件的本地修改将被 %s 覆盖:\n" -"%%s请在 %s 之前提交或贮藏您的修改。" +"%%s请在 %s 之前提交或储藏您的修改。" #: unpack-trees.c #, c-format @@ -28872,7 +29021,7 @@ #: unpack-trees.c #, c-format msgid "Entry '%s' overlaps with '%s'. Cannot bind." -msgstr "条目 '%s' 和 '%s' 重叠。无法合并。" +msgstr "条目 '%s' 和 '%s' 重叠。无法绑定。" #: unpack-trees.c #, c-format @@ -28890,7 +29039,7 @@ "patterns:\n" "%s" msgstr "" -"尽管存在稀疏检出模板,以下路径不是最新,因而保留:\n" +"尽管存在稀疏检出模式,以下路径不是最新,因而保留:\n" "%s" #: unpack-trees.c @@ -28899,7 +29048,7 @@ "The following paths are unmerged and were left despite sparse patterns:\n" "%s" msgstr "" -"尽管存在稀疏检出模板,以下路径处于未合并状态,因而保留:\n" +"尽管存在稀疏检出模式,以下路径处于未合并状态,因而保留:\n" "%s" #: unpack-trees.c @@ -28909,7 +29058,7 @@ "patterns:\n" "%s" msgstr "" -"尽管存在稀疏检出模板,以下路径已经存在,因而未更新:\n" +"尽管存在稀疏检出模式,以下路径已经存在,因而未更新:\n" "%s" #: unpack-trees.c @@ -29003,7 +29152,7 @@ #: usage.c #, c-format msgid "'%s' is nominated for removal.\n" -msgstr "'%s' 被提名以移除。\n" +msgstr "'%s' 已被列入移除计划。\n" #: usage.c #, c-format @@ -29156,7 +29305,7 @@ #: worktree.c msgid "unable to set extensions.relativeWorktrees setting" -msgstr "无法设定 extensions.relativeWorktrees 的设置" +msgstr "无法设定 extensions.relativeWorktrees 的配置" #: wrapper.c #, c-format @@ -29176,11 +29325,11 @@ #: wrapper.c #, c-format msgid "unable to access '%s'" -msgstr "不能访问 '%s'" +msgstr "无法访问 '%s'" #: wrapper.c msgid "unable to get current working directory" -msgstr "不能获取当前工作目录" +msgstr "无法获取当前工作目录" #: wrapper.c msgid "unable to get random bytes" @@ -29345,8 +29494,8 @@ #, c-format msgid "Your stash currently has %d entry" msgid_plural "Your stash currently has %d entries" -msgstr[0] "您的贮藏区当前有 %d 条记录" -msgstr[1] "您的贮藏区当前有 %d 条记录" +msgstr[0] "您的储藏区当前有 %d 条记录" +msgstr[1] "您的储藏区当前有 %d 条记录" #: wt-status.c msgid "Submodules changed but not updated:" @@ -29372,8 +29521,8 @@ "You can use '--no-ahead-behind' to avoid this.\n" msgstr "" "\n" -"花了 %.2f 秒才计算出分支的领先/落后范围。\n" -"为避免,您可以使用 '--no-ahead-behind'。\n" +"花了 %.2f 秒才计算出分支的领先/落后值。\n" +"为避免这种情况,您可以使用 '--no-ahead-behind'。\n" #: wt-status.c msgid "You have unmerged paths." @@ -29445,11 +29594,11 @@ #: wt-status.c #, c-format msgid " (see more in file %s)" -msgstr " (更多参见文件 %s)" +msgstr " (更多内容参见文件 %s)" #: wt-status.c msgid "No commands remaining." -msgstr "未剩下任何命令。" +msgstr "没有剩余命令。" #: wt-status.c #, c-format @@ -29505,7 +29654,7 @@ # 译者:注意保持前导空格 #: wt-status.c msgid " (Once your working directory is clean, run \"git rebase --continue\")" -msgstr " (一旦您工作目录提交干净后,运行 \"git rebase --continue\")" +msgstr " (一旦您的工作目录干净后,运行 \"git rebase --continue\")" #: wt-status.c #, c-format @@ -29519,7 +29668,7 @@ # 译者:注意保持前导空格 #: wt-status.c msgid " (use \"git commit --amend\" to amend the current commit)" -msgstr " (使用 \"git commit --amend\" 修补当前提交)" +msgstr " (使用 \"git commit --amend\" 修订当前提交)" # 译者:注意保持前导空格 #: wt-status.c @@ -29563,12 +29712,12 @@ #: wt-status.c msgid "Revert currently in progress." -msgstr "还原操作正在行中。" +msgstr "还原操作正在进行中。" #: wt-status.c #, c-format msgid "You are currently reverting commit %s." -msgstr "您在执行反转提交 %s 的操作。" +msgstr "您在执行还原提交 %s 的操作。" # 译者:注意保持前导空格 #: wt-status.c @@ -29593,7 +29742,7 @@ # 译者:注意保持前导空格 #: wt-status.c msgid " (use \"git revert --abort\" to cancel the revert operation)" -msgstr " (使用 \"git revert --abort\" 以取消反转提交操作)" +msgstr " (使用 \"git revert --abort\" 以取消还原操作)" #: wt-status.c #, c-format @@ -29611,12 +29760,12 @@ #: wt-status.c msgid "You are in a sparse checkout." -msgstr "您处于一个稀疏检出中。" +msgstr "您处于稀疏检出状态。" #: wt-status.c #, c-format msgid "You are in a sparse checkout with %d%% of tracked files present." -msgstr "您处于稀疏检出状态,包含 %d%% 的跟踪文件" +msgstr "您处于稀疏检出状态,已包含 %d%% 的跟踪文件。" #: wt-status.c msgid "On branch " @@ -29664,22 +29813,22 @@ "It took %.2f seconds to enumerate untracked files,\n" "but the results were cached, and subsequent runs may be faster." msgstr "" -"枚举未追踪的文件花了 %.2f 秒,\n" +"枚举未跟踪的文件花了 %.2f 秒,\n" "结果已被缓存,后续的执行会更快。" #: wt-status.c #, c-format msgid "It took %.2f seconds to enumerate untracked files." -msgstr "枚举未追踪的文件花了 %.2f 秒。" +msgstr "枚举未跟踪的文件花了 %.2f 秒。" #: wt-status.c msgid "See 'git help status' for information on how to improve this." -msgstr "参见 'git help status' 来获取如何改善的信息。" +msgstr "参见 'git help status' 了解如何改善。" #: wt-status.c #, c-format msgid "Untracked files not listed%s" -msgstr "未跟踪的文件没有列出%s" +msgstr "未跟踪的文件未列出%s" # 译者:中文字符串拼接,可删除前导空格 #: wt-status.c @@ -29730,11 +29879,11 @@ #: wt-status.c #, c-format msgid "nothing to commit, working tree clean\n" -msgstr "无文件要提交,干净的工作区\n" +msgstr "无文件要提交,工作区干净\n" #: wt-status.c msgid "No commits yet on " -msgstr "尚无提交在 " +msgstr "尚无提交于 " #: wt-status.c msgid "HEAD (no branch)" @@ -29757,7 +29906,7 @@ #: wt-status.c #, c-format msgid "cannot %s: You have unstaged changes." -msgstr "不能%s:您有未暂存的变更。" +msgstr "无法 %s:您有未暂存的变更。" #: wt-status.c msgid "additionally, your index contains uncommitted changes." @@ -29766,7 +29915,7 @@ #: wt-status.c #, c-format msgid "cannot %s: Your index contains uncommitted changes." -msgstr "不能%s:您的索引中包含未提交的变更。" +msgstr "不能 %s:您的索引中包含未提交的变更。" #: xdiff-interface.c #, c-format @@ -29781,7 +29930,7 @@ #: git-merge-octopus.sh msgid "Automated merge did not work." -msgstr "自动合并未生效。" +msgstr "自动合并未成功。" #: git-merge-octopus.sh msgid "Should not be doing an octopus." @@ -29819,26 +29968,26 @@ #: git-sh-setup.sh #, sh-format msgid "Cannot chdir to $cdup, the toplevel of the working tree" -msgstr "不能切换目录到 $cdup,工作区的顶级目录" +msgstr "无法切换到工作区的顶级目录 $cdup" #: git-sh-setup.sh #, sh-format msgid "fatal: $program_name cannot be used without a working tree." -msgstr "致命错误:$program_name 不能在没有工作区的情况下使用" +msgstr "致命错误:$program_name 无法在没有工作区的情况下使用" #: git-sh-setup.sh msgid "Cannot rewrite branches: You have unstaged changes." -msgstr "不能重写分支:您有未暂存的变更。" +msgstr "无法重写分支:您有未暂存的变更。" #: git-sh-setup.sh #, sh-format msgid "Cannot $action: You have unstaged changes." -msgstr "不能 $action:您有未暂存的变更。" +msgstr "无法 $action:您有未暂存的变更。" #: git-sh-setup.sh #, sh-format msgid "Cannot $action: Your index contains uncommitted changes." -msgstr "不能 $action:您的索引中包含未提交的变更。" +msgstr "无法 $action:您的索引中包含未提交的变更。" #: git-sh-setup.sh msgid "Additionally, your index contains uncommitted changes." @@ -29850,11 +29999,11 @@ #: git-sh-setup.sh msgid "Unable to determine absolute path of git directory" -msgstr "不能确定 git 目录的绝对路径" +msgstr "无法确定 Git 目录的绝对路径" #: git-send-email.perl msgid "local zone differs from GMT by a non-minute interval\n" -msgstr "本地时间和 GMT 有不到一分钟间隔\n" +msgstr "本地时间与 GMT 的偏移不是整数分钟\n" #: git-send-email.perl msgid "local time offset greater than or equal to 24 hours\n" @@ -29878,11 +30027,11 @@ #: git-send-email.perl #, perl-format msgid "'%s.final' contains the composed email.\n" -msgstr "'%s.final' 包含编辑的邮件。\n" +msgstr "'%s.final' 包含已撰写的邮件。\n" #: git-send-email.perl msgid "--dump-aliases incompatible with other options\n" -msgstr "--dump-aliases 和其它选项不兼容\n" +msgstr "--dump-aliases 和其他选项不兼容\n" #: git-send-email.perl msgid "--dump-aliases and --translate-aliases are mutually exclusive\n" @@ -29900,7 +30049,7 @@ #: git-send-email.perl msgid "Cannot run git format-patch from outside a repository\n" -msgstr "不能在仓库之外运行 git format-patch\n" +msgstr "无法在仓库之外运行 git format-patch\n" #: git-send-email.perl msgid "" @@ -29936,7 +30085,7 @@ #: git-send-email.perl #, perl-format msgid "warning: sendmail line is not recognized: %s\n" -msgstr "警告:不能识别的 sendmail 行:%s\n" +msgstr "警告:无法识别的 sendmail 行:%s\n" #: git-send-email.perl #, perl-format @@ -29956,7 +30105,7 @@ #: git-send-email.perl #, perl-format msgid "Failed to opendir %s: %s" -msgstr "无法打开目录 %s: %s" +msgstr "无法打开目录 %s:%s" #: git-send-email.perl msgid "" @@ -29976,7 +30125,7 @@ #: git-send-email.perl #, perl-format msgid "Failed to open for writing %s: %s" -msgstr "为写入打开 %s 失败: %s" +msgstr "为写入打开 %s 失败:%s" #: git-send-email.perl msgid "" @@ -29994,12 +30143,12 @@ #: git-send-email.perl #, perl-format msgid "Failed to open %s.final: %s" -msgstr "无法打开 %s.final: %s" +msgstr "无法打开 %s.final:%s" #: git-send-email.perl #, perl-format msgid "Failed to open %s: %s" -msgstr "无法打开 %s: %s" +msgstr "无法打开 %s:%s" #: git-send-email.perl msgid "Summary email is empty, skipping it\n" @@ -30019,7 +30168,7 @@ #: git-send-email.perl msgid "Which 8bit encoding should I declare [UTF-8]? " -msgstr "要声明 8bit 为什么样的编码格式 [UTF-8]?" +msgstr "要声明哪种 8bit 编码格式 [UTF-8]?" #: git-send-email.perl #, perl-format @@ -30031,11 +30180,11 @@ msgstr "" "拒绝发送,因为补丁\n" "\t%s\n" -"包含模版标题 '*** SUBJECT HERE ***'。如果确实想要发送,使用参数 --force。\n" +"包含模板标题 '*** SUBJECT HERE ***'。如果确实想要发送,使用参数 --force。\n" #: git-send-email.perl msgid "To whom should the emails be sent (if anyone)?" -msgstr "邮件将要发送给谁?" +msgstr "邮件将要发送给谁(如果有的话)?" #: git-send-email.perl #, perl-format @@ -30044,12 +30193,12 @@ #: git-send-email.perl msgid "Message-ID to be used as In-Reply-To for the first email (if any)? " -msgstr "Message-ID 被用作第一封邮件的 In-Reply-To ?" +msgstr "如有的话,Message-ID 要用作第一封邮件的 In-Reply-To 吗?" #: git-send-email.perl #, perl-format msgid "error: unable to extract a valid address from: %s\n" -msgstr "错误:不能从 %s 中提取一个有效的邮件地址\n" +msgstr "错误:无法从 %s 中提取一个有效的邮件地址\n" #. TRANSLATORS: Make sure to include [q] [d] [e] in your #. translation. The program will only accept English input @@ -30077,8 +30226,8 @@ "\n" msgstr "" " 以上的抄送列表(Cc)已经用补丁提交信息中发现的地址进行\n" -" 了扩展。缺省 send-email 会给出提示。这个行为可以通过\n" -" sendemail.confirm 配置设置。\n" +" 了扩展。默认情况下 send-email 会给出提示。这个行为由\n" +" sendemail.confirm 配置控制。\n" "\n" " 更多信息,执行 'git send-email --help'。\n" " 要保持当前行为,但不显示此信息,运行 'git config --global\n" @@ -30090,7 +30239,7 @@ #. at this point. #: git-send-email.perl msgid "Send this email? ([y]es|[n]o|[e]dit|[q]uit|[a]ll): " -msgstr "发送这封邮件?([y]es|[n]o|[e]dit|[q]uit|[a]ll): " +msgstr "发送这封邮件?([y]es|[n]o|[e]dit|[q]uit|[a]ll):" #: git-send-email.perl msgid "Send this email reply required" @@ -30135,7 +30284,7 @@ #: git-send-email.perl #, perl-format msgid "Dry-Sent %s" -msgstr "演习发送 %s" +msgstr "模拟发送 %s" #: git-send-email.perl #, perl-format @@ -30144,7 +30293,7 @@ #: git-send-email.perl msgid "Dry-OK. Log says:" -msgstr "演习成功。日志说:" +msgstr "模拟成功。日志说:" #: git-send-email.perl msgid "OK. Log says:" @@ -30166,22 +30315,22 @@ #: git-send-email.perl #, perl-format msgid "(mbox) Adding cc: %s from line '%s'\n" -msgstr "(mbox) 添加 cc:%s 自行 '%s'\n" +msgstr "(mbox) 添加 cc:%s 来自行 '%s'\n" #: git-send-email.perl #, perl-format msgid "(mbox) Adding to: %s from line '%s'\n" -msgstr "(mbox) 添加 to:%s 自行 '%s'\n" +msgstr "(mbox) 添加 to:%s 来自行 '%s'\n" #: git-send-email.perl #, perl-format msgid "(non-mbox) Adding cc: %s from line '%s'\n" -msgstr "(non-mbox) 添加 cc:%s 自行 '%s'\n" +msgstr "(non-mbox) 添加 cc:%s 来自行 '%s'\n" #: git-send-email.perl #, perl-format msgid "(body) Adding cc: %s from line '%s'\n" -msgstr "(body) 添加 cc: %s 自行 '%s'\n" +msgstr "(body) 添加 cc:%s 来自行 '%s'\n" #: git-send-email.perl #, perl-format @@ -30191,12 +30340,12 @@ #: git-send-email.perl #, perl-format msgid "(%s) Could not execute '%s'" -msgstr "(%s) 不能执行 '%s'" +msgstr "(%s) 无法执行 '%s'" #: git-send-email.perl #, perl-format msgid "(%s) Malformed output from '%s'" -msgstr "(%s) 非法的输出信息,来自于: '%s'" +msgstr "(%s) 来自 '%s' 的输出信息格式错误" #: git-send-email.perl #, perl-format @@ -30206,11 +30355,11 @@ #: git-send-email.perl #, perl-format msgid "(%s) Adding %s: %s from: '%s'\n" -msgstr "(%s) 添加 %s: %s 自:'%s'\n" +msgstr "(%s) 添加 %s:%s 自:'%s'\n" #: git-send-email.perl msgid "cannot send message as 7bit" -msgstr "不能以 7bit 形式发送信息" +msgstr "无法以 7bit 形式发送信息" #: git-send-email.perl msgid "invalid transfer encoding" @@ -30230,7 +30379,7 @@ #: git-send-email.perl #, perl-format msgid "unable to open %s: %s\n" -msgstr "不能打开 %s:%s\n" +msgstr "无法打开 %s:%s\n" #: git-send-email.perl #, perl-format
diff --git a/po/zh_TW.po b/po/zh_TW.po index 5301aff..7172d6d 100644 --- a/po/zh_TW.po +++ b/po/zh_TW.po
@@ -19,9 +19,9 @@ # - Yichao Yu <yyc1992 AT gmail.com> # - Zhuang Ya <zhuangya AT me.com> # -# Yi-Jyun Pan <pan93412@gmail.com>, 2021, 2022, 2023, 2024, 2025. +# Yi-Jyun Pan <pan93412@gmail.com>, 2021, 2022, 2023, 2024, 2025, 2026. # Kaiyang Wu <self@origincode.me>, 2022. -# Lumynous <lumynou5.tw@gmail.com>, 2023, 2024, 2025. +# Lumynous <lumynou5.tw@gmail.com>, 2023, 2024, 2025, 2026. # Kisaragi Hiu <mail@kisaragi-hiu.com>, 2024. # Ngoo Ka-iu <willy04wu69@gmail.com>, 2024. # Nightfeather Chen <slat@nightfeather.me>, 2024. @@ -30,9 +30,9 @@ msgstr "" "Project-Id-Version: Git\n" "Report-Msgid-Bugs-To: Git Mailing List <git@vger.kernel.org>\n" -"POT-Creation-Date: 2025-11-12 20:14+0800\n" -"PO-Revision-Date: 2025-11-15 10:02+0000\n" -"Last-Translator: Lumynous <lumynou5.tw@gmail.com>\n" +"POT-Creation-Date: 2026-01-28 22:23+0800\n" +"PO-Revision-Date: 2026-01-28 22:42+0800\n" +"Last-Translator: Yi-Jyun Pan <pan93412@gmail.com>\n" "Language-Team: Chinese (Traditional Han script) <https://weblate.slat.org/" "projects/git-po/git-cli/zh_Hant/>\n" "Language: zh_TW\n" @@ -40,9 +40,9 @@ "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 5.13.2\n" -"X-ZhConverter: 繁化姬 dict-f4bc617e-r910 @ 2019/11/16 20:23:12 | " -"https://zhconvert.org\n" +"X-Generator: Poedit 3.8\n" +"X-ZhConverter: 繁化姬 dict-f4bc617e-r910 @ 2019/11/16 20:23:12 | https://" +"zhconvert.org\n" #: add-interactive.c #, c-format @@ -962,9 +962,9 @@ #: builtin/fast-export.c builtin/fetch.c builtin/help.c builtin/index-pack.c #: builtin/init-db.c builtin/log.c builtin/ls-files.c builtin/merge-base.c #: builtin/merge-tree.c builtin/merge.c builtin/rebase.c builtin/repack.c -#: builtin/replay.c builtin/reset.c builtin/rev-parse.c builtin/show-branch.c -#: builtin/stash.c builtin/submodule--helper.c builtin/tag.c builtin/worktree.c -#: parse-options.c range-diff.c revision.c +#: builtin/reset.c builtin/rev-parse.c builtin/show-branch.c builtin/stash.c +#: builtin/submodule--helper.c builtin/tag.c builtin/worktree.c parse-options.c +#: range-diff.c revision.c #, c-format msgid "options '%s' and '%s' cannot be used together" msgstr "無法同時使用「%s」和「%s」選項" @@ -2106,8 +2106,8 @@ msgstr "「%s」不是有效的分支名稱" #: branch.c builtin/branch.c -msgid "See `man git check-ref-format`" -msgstr "請參閱「man git check-ref-format」" +msgid "See 'git help check-ref-format'" +msgstr "請參閱「git help check-ref-format」" #: branch.c #, c-format @@ -3228,6 +3228,18 @@ msgid "must end with a color" msgstr "結尾必須是一個顏色" +#: builtin/blame.c diff.c merge-ort.c transport.c +#, c-format +msgid "unknown value for config '%s': %s" +msgstr "設定 '%s' 的取值未知:%s" + +#: builtin/blame.c builtin/merge-file.c diff.c +msgid "" +"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and " +"\"histogram\"" +msgstr "" +"diff-algorithm 選項有 \"myers\"、\"minimal\"、\"patience\" 和 \"histogram\"" + #: builtin/blame.c #, c-format msgid "cannot find revision %s to ignore" @@ -3299,6 +3311,14 @@ msgid "ignore whitespace differences" msgstr "忽略空白差異" +#: builtin/blame.c builtin/merge-file.c diff.c +msgid "<algorithm>" +msgstr "<演算法>" + +#: builtin/blame.c builtin/merge-file.c diff.c +msgid "choose a diff algorithm" +msgstr "選擇一個差異演算法" + #: builtin/blame.c builtin/clone.c builtin/log.c msgid "rev" msgstr "rev" @@ -3320,8 +3340,8 @@ msgstr "根據時間著色" #: builtin/blame.c -msgid "spend extra cycles to find better match" -msgstr "循環更多次來找到更佳符合項目" +msgid "spend extra cycles to find a better match" +msgstr "多跑幾輪來找到更符合的項目" #: builtin/blame.c msgid "use revisions from <file> instead of calling git-rev-list" @@ -3868,7 +3888,7 @@ "您可刪除任何您不想分享的地方。\n" #: builtin/bugreport.c builtin/commit.c builtin/fast-export.c -#: builtin/pack-objects.c builtin/rebase.c parse-options.h +#: builtin/pack-objects.c builtin/rebase.c builtin/replay.c parse-options.h msgid "mode" msgstr "mode" @@ -4779,10 +4799,10 @@ msgid "missing branch name; try -%c" msgstr "缺少分支名稱;請嘗試 -%c" -#: builtin/checkout.c +#: builtin/checkout.c sequencer.c #, c-format -msgid "could not resolve %s" -msgstr "無法解析 %s" +msgid "could not resolve '%s'" +msgstr "無法解析「%s」" #: builtin/checkout.c msgid "invalid path specification" @@ -6617,10 +6637,18 @@ #, c-format msgid "" "cannot overwrite multiple values with a single value\n" -" Use a regexp, --add or --replace-all to change %s." +" Use --value=<pattern>, --append or --all to change %s." msgstr "" "無法用一個值覆蓋多個值\n" -" 使用一個常規表示式、--add 或 --replace-all 來修改 %s。" +" 使用 --value=<pattern>、--append 或 --all 來變更 %s。" + +#: builtin/config.c +msgid "unset all multi-valued config options" +msgstr "取消設定所有多值組態選項" + +#: builtin/config.c +msgid "unset multi-valued config options with matching values" +msgstr "取消設定其值相符的多值組態選項" #: builtin/config.c #, c-format @@ -6730,6 +6758,15 @@ msgid "--comment is only applicable to add/set/replace operations" msgstr "--comment 只能用於加入、設定和取代動作" +#: builtin/config.c +#, c-format +msgid "" +"cannot overwrite multiple values with a single value\n" +" Use a regexp, --add or --replace-all to change %s." +msgstr "" +"無法用一個值覆蓋多個值\n" +" 使用一個常規表示式、--add 或 --replace-all 來修改 %s。" + #: builtin/count-objects.c msgid "print sizes in human readable format" msgstr "以使用者可讀的格式顯示大小" @@ -7236,11 +7273,6 @@ #: builtin/fast-export.c #, c-format -msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it" -msgstr "發現已簽署的提交 %s;使用 --signed-commits=<mode> 來處理" - -#: builtin/fast-export.c -#, c-format msgid "exporting %<PRIuMAX> signature(s) for commit %s" msgstr "匯出提交 %2$s 的 %1$<PRIuMAX> 個簽章" @@ -7251,6 +7283,19 @@ #: builtin/fast-export.c #, c-format +msgid "encountered signed commit %s; use --signed-commits=<mode> to handle it" +msgstr "發現已簽署的提交 %s;使用 --signed-commits=<mode> 來處理" + +#: builtin/fast-export.c +msgid "" +"'strip-if-invalid' is not a valid mode for git fast-export with --signed-" +"commits=<mode>" +msgstr "" +"「strip-if-invalid」不是搭配 --signed-commits=<mode> 使用 git fast-export 的" +"有效模式" + +#: builtin/fast-export.c +#, c-format msgid "" "omitting tag %s,\n" "since tags of trees (or tags of tags of trees, etc.) are not supported." @@ -7265,11 +7310,6 @@ #: builtin/fast-export.c #, c-format -msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it" -msgstr "發現已簽署的標籤 %s;使用 --signed-tags=<mode> 來處理" - -#: builtin/fast-export.c -#, c-format msgid "exporting signed tag %s" msgstr "匯出已簽署的標籤 %s" @@ -7280,6 +7320,19 @@ #: builtin/fast-export.c #, c-format +msgid "encountered signed tag %s; use --signed-tags=<mode> to handle it" +msgstr "發現已簽署的標籤 %s;使用 --signed-tags=<mode> 來處理" + +#: builtin/fast-export.c +msgid "" +"'strip-if-invalid' is not a valid mode for git fast-export with --signed-" +"tags=<mode>" +msgstr "" +"「strip-if-invalid」不是搭配 --signed-tags=<mode> 使用 git fast-export 的有效" +"模式" + +#: builtin/fast-export.c +#, c-format msgid "" "tag %s tags unexported object; use --tag-of-filtered-object=<mode> to handle " "it" @@ -7769,6 +7822,37 @@ msgid "parse_one_signature() returned unknown hash algo" msgstr "parse_one_signature() 回傳了未知的雜湊演算法" +#: builtin/fast-import.c builtin/fsck.c +msgid "unknown" +msgstr "未知" + +#: builtin/fast-import.c +#, c-format +msgid "" +"stripping invalid signature for commit '%.100s...'\n" +" allegedly by %s" +msgstr "" +"正在去除提交「%.100s...」的無效簽章\n" +" 據稱由 %s 所簽" + +#: builtin/fast-import.c +#, c-format +msgid "" +"stripping invalid signature for commit '%.*s'\n" +" allegedly by %s" +msgstr "" +"正在去除去除「%.*s..」的無效簽章\n" +" 據稱由 %s 所簽" + +#: builtin/fast-import.c +#, c-format +msgid "" +"stripping invalid signature for commit\n" +" allegedly by %s" +msgstr "" +"正在去除提交的無效簽章\n" +" 據稱由 %s 所簽" + #: builtin/fast-import.c msgid "expected committer but didn't get one" msgstr "預期有提交者,卻沒收到" @@ -7786,10 +7870,6 @@ msgstr "按原樣匯入提交簽章" #: builtin/fast-import.c -msgid "encountered signed tag; use --signed-tags=<mode> to handle it" -msgstr "發現已簽署的標籤;使用 --signed-tags=<mode> 來處理" - -#: builtin/fast-import.c #, c-format msgid "importing a tag signature verbatim for tag '%s'" msgstr "按原樣匯入標籤「%s」的簽章" @@ -7800,6 +7880,18 @@ msgstr "去除標籤「%s」的簽章" #: builtin/fast-import.c +msgid "encountered signed tag; use --signed-tags=<mode> to handle it" +msgstr "發現已簽署的標籤;使用 --signed-tags=<mode> 來處理" + +#: builtin/fast-import.c +msgid "" +"'strip-if-invalid' is not a valid mode for git fast-import with --signed-" +"tags=<mode>" +msgstr "" +"「strip-if-invalid」不是搭配 --signed-tags=<mode> 使用 git fast-import 的有效" +"模式" + +#: builtin/fast-import.c #, c-format msgid "expected 'from' command, got '%s'" msgstr "預期「from」命令,卻收到「%s」" @@ -7997,8 +8089,8 @@ msgstr "git fetch [<選項>] <組>" #: builtin/fetch.c -msgid "git fetch --multiple [<options>] [(<repository> | <group>)...]" -msgstr "git fetch --multiple [<選項>] [(<版本庫> | <組>)...]" +msgid "git fetch --multiple [<options>] [(<repository>|<group>)...]" +msgstr "git fetch --multiple [<options>] [(<repository>|<group>)...]" #: builtin/fetch.c msgid "git fetch --all [<options>]" @@ -8574,10 +8666,6 @@ msgid "got bad config --config=%s" msgstr "收到無效的組態 --config=%s" -#: builtin/fsck.c -msgid "unknown" -msgstr "未知" - #. TRANSLATORS: e.g. error in tree 01bfda: <more explanation> #: builtin/fsck.c #, c-format @@ -8697,6 +8785,11 @@ msgstr "%s:不是一個提交" #: builtin/fsck.c +#, c-format +msgid "invalid parameter: expected sha1, got '%s'" +msgstr "無效的參數:期望 sha1,得到 '%s'" + +#: builtin/fsck.c msgid "notice: No default references" msgstr "注意:無預設引用" @@ -8730,31 +8823,6 @@ #: builtin/fsck.c #, c-format -msgid "Checking %s link" -msgstr "正在檢查 %s 連結" - -#: builtin/fsck.c builtin/index-pack.c -#, c-format -msgid "invalid %s" -msgstr "無效的 %s" - -#: builtin/fsck.c -#, c-format -msgid "%s points to something strange (%s)" -msgstr "%s 指向奇怪的東西(%s)" - -#: builtin/fsck.c -#, c-format -msgid "%s: detached HEAD points at nothing" -msgstr "%s:分離開頭指標的指向不存在" - -#: builtin/fsck.c -#, c-format -msgid "notice: %s points to an unborn branch (%s)" -msgstr "注意:%s 指向一個尚未誕生的分支(%s)" - -#: builtin/fsck.c -#, c-format msgid "Checking cache tree of %s" msgstr "正在檢查 %s 的快取樹" @@ -8854,16 +8922,6 @@ msgid "Checking objects" msgstr "正在檢查物件" -#: builtin/fsck.c -#, c-format -msgid "%s: object missing" -msgstr "%s:物件缺少" - -#: builtin/fsck.c -#, c-format -msgid "invalid parameter: expected sha1, got '%s'" -msgstr "無效的參數:期望 sha1,得到 '%s'" - #: builtin/fsmonitor--daemon.c msgid "git fsmonitor--daemon start [<options>]" msgstr "git fsmonitor--daemon start [<options>]" @@ -9336,6 +9394,10 @@ msgstr "無法將版本庫加至全域設定" #: builtin/gc.c +msgid "check a specific task" +msgstr "檢查指定作業" + +#: builtin/gc.c msgid "git maintenance <subcommand> [<options>]" msgstr "git maintenance <子命令> [<選項>]" @@ -9910,6 +9972,11 @@ #: builtin/index-pack.c #, c-format +msgid "invalid %s" +msgstr "無效的 %s" + +#: builtin/index-pack.c +#, c-format msgid "Not all child objects of %s are reachable" msgstr "%s 的所有子物件並非都可以取得" @@ -10079,6 +10146,10 @@ msgid "--verify with no packfile name given" msgstr "--verify 沒有提供 packfile 名稱參數" +#: builtin/index-pack.c +msgid "cannot perform queued object checks outside of a repository" +msgstr "無法在版本庫外執行排入佇列的物件檢查" + #: builtin/index-pack.c builtin/unpack-objects.c msgid "fsck error in pack objects" msgstr "在打包物件中 fsck 檢查發生錯誤" @@ -11033,13 +11104,6 @@ "git merge-file [<選項>] [-L <檔案1> [-L <初始> [-L <名字2>]]] <檔案1> <初始文" "件> <檔案2>" -#: builtin/merge-file.c diff.c -msgid "" -"option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and " -"\"histogram\"" -msgstr "" -"diff-algorithm 選項有 \"myers\"、\"minimal\"、\"patience\" 和 \"histogram\"" - #: builtin/merge-file.c msgid "send results to standard output" msgstr "將結果傳送到標準輸出" @@ -11056,14 +11120,6 @@ msgid "use a zealous diff3 based merge" msgstr "使用基於 zealous diff3 的合併" -#: builtin/merge-file.c diff.c -msgid "<algorithm>" -msgstr "<演算法>" - -#: builtin/merge-file.c diff.c -msgid "choose a diff algorithm" -msgstr "選擇一個差異演算法" - #: builtin/merge-file.c msgid "for conflicts, use this marker size" msgstr "如果衝突,使用指定長度的標記" @@ -12392,6 +12448,11 @@ #: builtin/pack-objects.c #, c-format +msgid "packfile %s is a promisor but --exclude-promisor-objects was given" +msgstr "封包檔案 %s 是承諾者,但是指定了 --exclude-promisor-objects" + +#: builtin/pack-objects.c +#, c-format msgid "could not find pack '%s'" msgstr "找不到「%s」包" @@ -12721,12 +12782,12 @@ msgstr "git patch-id [--stable | --unstable | --verbatim]" #: builtin/patch-id.c -msgid "use the unstable patch-id algorithm" -msgstr "使用不穩定的 patch-id 演算法" +msgid "use the unstable patch ID algorithm" +msgstr "使用不穩定的修補檔 ID 演算法" #: builtin/patch-id.c -msgid "use the stable patch-id algorithm" -msgstr "使用穩定的 patch-id 演算法" +msgid "use the stable patch ID algorithm" +msgstr "使用穩定的修補檔 ID 演算法" #: builtin/patch-id.c msgid "don't strip whitespace from the patch" @@ -12757,50 +12818,6 @@ msgstr "git pull [<選項>] [<版本庫> [<引用規格>...]]" #: builtin/pull.c -msgid "control for recursive fetching of submodules" -msgstr "控制子模組的遞迴取得" - -#: builtin/pull.c -msgid "Options related to merging" -msgstr "和合併相關的選項" - -#: builtin/pull.c -msgid "incorporate changes by rebasing rather than merging" -msgstr "使用重定基底動作取代合併動作以套用修改" - -#: builtin/pull.c builtin/revert.c -msgid "allow fast-forward" -msgstr "允許快轉式" - -#: builtin/pull.c -msgid "control use of pre-merge-commit and commit-msg hooks" -msgstr "控制 pre-merge-commit 和 commit-msg 掛鉤的使用" - -#: builtin/pull.c parse-options.h -msgid "automatically stash/stash pop before and after" -msgstr "在動作前後執行自動貯存和彈出貯存" - -#: builtin/pull.c -msgid "Options related to fetching" -msgstr "和取得相關的參數" - -#: builtin/pull.c -msgid "force overwrite of local branch" -msgstr "強制覆蓋本機分支" - -#: builtin/pull.c -msgid "number of submodules pulled in parallel" -msgstr "並行拉取的子模組數量" - -#: builtin/pull.c parse-options.h -msgid "use IPv4 addresses only" -msgstr "只使用 IPv4 位址" - -#: builtin/pull.c parse-options.h -msgid "use IPv6 addresses only" -msgstr "只使用 IPv6 位址" - -#: builtin/pull.c msgid "" "There is no candidate for rebasing against among the refs that you just " "fetched." @@ -12913,6 +12930,50 @@ "設定的預設值。\n" #: builtin/pull.c +msgid "control for recursive fetching of submodules" +msgstr "控制子模組的遞迴取得" + +#: builtin/pull.c +msgid "Options related to merging" +msgstr "和合併相關的選項" + +#: builtin/pull.c +msgid "incorporate changes by rebasing rather than merging" +msgstr "使用重定基底動作取代合併動作以套用修改" + +#: builtin/pull.c builtin/revert.c +msgid "allow fast-forward" +msgstr "允許快轉式" + +#: builtin/pull.c +msgid "control use of pre-merge-commit and commit-msg hooks" +msgstr "控制 pre-merge-commit 和 commit-msg 掛鉤的使用" + +#: builtin/pull.c parse-options.h +msgid "automatically stash/stash pop before and after" +msgstr "在動作前後執行自動貯存和彈出貯存" + +#: builtin/pull.c +msgid "Options related to fetching" +msgstr "和取得相關的參數" + +#: builtin/pull.c +msgid "force overwrite of local branch" +msgstr "強制覆蓋本機分支" + +#: builtin/pull.c +msgid "number of submodules pulled in parallel" +msgstr "並行拉取的子模組數量" + +#: builtin/pull.c parse-options.h +msgid "use IPv4 addresses only" +msgstr "只使用 IPv4 位址" + +#: builtin/pull.c parse-options.h +msgid "use IPv6 addresses only" +msgstr "只使用 IPv6 位址" + +#: builtin/pull.c msgid "Updating an unborn branch with changes added to the index." msgstr "更新尚未誕生的分支,變更新增至索引。" @@ -15139,6 +15200,11 @@ msgstr "只能為 -l 提供一個模式" #: builtin/replay.c +#, c-format +msgid "'%s' is not a valid commit-ish for %s" +msgstr "「%s」不是 %s 有效的提交指示元" + +#: builtin/replay.c msgid "need some commits to replay" msgstr "需要一些提交才能重放" @@ -15157,27 +15223,17 @@ msgstr "無法用多個來源演進目的地,以免無法確定排序" #: builtin/replay.c -msgid "" -"cannot implicitly determine whether this is an --advance or --onto operation" -msgstr "無法假設這是 --advance 還是 --onto 動作" - -#: builtin/replay.c -msgid "" -"cannot advance target with multiple source branches because ordering would " -"be ill-defined" -msgstr "無法由多個來源分支演進目的地,以免無法確定排序" - -#: builtin/replay.c -msgid "cannot implicitly determine correct base for --onto" -msgstr "無法假設 --onto 的正確基底" +#, c-format +msgid "invalid %s value: '%s'" +msgstr "無效的 %s 值:「%s」" #: builtin/replay.c msgid "" "(EXPERIMENTAL!) git replay ([--contained] --onto <newbase> | --advance " -"<branch>) <revision-range>..." +"<branch>) [--ref-action[=<mode>]] <revision-range>" msgstr "" -"(實驗性功能!)git replay ([--contained] --onto <newbase> | --advance " -"<branch>) <revision-range>..." +"(實驗性功能!) git replay ([--contained] --onto <newbase> | --advance " +"<branch>) [--ref-action[=<mode>]] <revision-range>" #: builtin/replay.c msgid "make replay advance given branch" @@ -15188,8 +15244,12 @@ msgstr "重放到指定提交" #: builtin/replay.c -msgid "advance all branches contained in revision-range" -msgstr "演進所有包含在 revision-range 中的分支" +msgid "update all branches that point at commits in <revision-range>" +msgstr "更新所有指向 <revision-range> 範圍中提交的分支" + +#: builtin/replay.c +msgid "control ref update behavior (update|print)" +msgstr "控制引用的更新行為(update|print)" #: builtin/replay.c msgid "option --onto or --advance is mandatory" @@ -15203,17 +15263,32 @@ msgstr "將覆寫部分修訂版遍歷選項,強制使用「struct rev_info」的「%s」位元" #: builtin/replay.c +#, c-format +msgid "failed to begin ref transaction: %s" +msgstr "無法開始引用事務:%s" + +#: builtin/replay.c msgid "error preparing revisions" msgstr "無法準備修訂集" #: builtin/replay.c -msgid "replaying down to root commit is not supported yet!" -msgstr "尚不支援重放到根提交!" +msgid "replaying down from root commit is not supported yet!" +msgstr "尚不支援從根提交重放!" #: builtin/replay.c msgid "replaying merge commits is not supported yet!" msgstr "尚不支援重放合併提交!" +#: builtin/replay.c +#, c-format +msgid "failed to update ref '%s': %s" +msgstr "無法更新「%s」引用:%s" + +#: builtin/replay.c +#, c-format +msgid "failed to commit ref transaction: %s" +msgstr "無法提交引用事務:%s" + #: builtin/repo.c #, c-format msgid "key '%s' not found" @@ -15233,10 +15308,18 @@ msgstr "和 --format=nul 同義" #: builtin/repo.c +msgid "print all keys/values" +msgstr "輸出所有鍵值對" + +#: builtin/repo.c msgid "unsupported output format" msgstr "不支援的輸出格式" #: builtin/repo.c +msgid "--all and <key> cannot be used together" +msgstr "--all 和 <key> 不能同時使用" + +#: builtin/repo.c msgid "References" msgstr "引用" @@ -15277,6 +15360,14 @@ msgstr "資料物件" #: builtin/repo.c +msgid "Inflated size" +msgstr "未壓縮大小" + +#: builtin/repo.c +msgid "Disk size" +msgstr "磁碟大小" + +#: builtin/repo.c msgid "Repository structure" msgstr "版本庫結構" @@ -17261,7 +17352,7 @@ msgid "'%s' already exists in the index and is not a submodule" msgstr "「%s」已在索引中,且不是子模組" -#: builtin/submodule--helper.c read-cache.c +#: builtin/submodule--helper.c object-file.c #, c-format msgid "'%s' does not have a commit checked out" msgstr "'%s' 沒有簽出一個提交" @@ -18314,6 +18405,11 @@ msgstr "位於「%s」的套件包清單沒有模式" #: bundle-uri.c +#, c-format +msgid "bundle list at '%s': bundle '%s' has no uri" +msgstr "套件包清單,位於「%s」:「%s」套件包沒有 URI" + +#: bundle-uri.c msgid "failed to create temporary file" msgstr "無法建立暫存檔" @@ -18342,6 +18438,11 @@ #: bundle-uri.c #, c-format +msgid "bundle '%s' has no uri" +msgstr "「%s」套件包沒有 URI" + +#: bundle-uri.c +#, c-format msgid "failed to download bundle from URI '%s'" msgstr "無法從「%s」URI 下載套件包" @@ -18982,8 +19083,8 @@ msgstr "重用衝突合併的解決方案記錄" #: command-list.h -msgid "Reset current HEAD to the specified state" -msgstr "重設目前 HEAD 到指定狀態" +msgid "Set `HEAD` or the index to a known state" +msgstr "將 `HEAD` 或索引設為已知狀態" #: command-list.h msgid "Restore working tree files" @@ -20821,11 +20922,6 @@ msgid "Unknown value for 'diff.submodule' config variable: '%s'" msgstr "設定變數 'diff.submodule' 未知的取值:'%s'" -#: diff.c merge-ort.c transport.c -#, c-format -msgid "unknown value for config '%s': %s" -msgstr "設定 '%s' 的取值未知:%s" - #: diff.c #, c-format msgid "" @@ -23388,6 +23484,10 @@ msgstr "%s:插入資料庫失敗" #: object-file.c +msgid "cannot add a submodule of a different hash algorithm" +msgstr "無法加入使用不同雜湊演算法的子模組" + +#: object-file.c #, c-format msgid "%s: unsupported file type" msgstr "%s:不支援的檔案類型" @@ -25117,6 +25217,11 @@ msgstr "--format=%.*s 無法和 --python、--shell、--tcl 一起使用" #: ref-filter.c +#, c-format +msgid "parse_object_buffer failed on %s for %s" +msgstr "parse_object_buffer 失敗於 %2$s 的 %1$s" + +#: ref-filter.c msgid "failed to run 'describe'" msgstr "無法執行「describe」" @@ -25156,11 +25261,6 @@ #: ref-filter.c #, c-format -msgid "parse_object_buffer failed on %s for %s" -msgstr "parse_object_buffer 失敗於 %2$s 的 %1$s" - -#: ref-filter.c -#, c-format msgid "malformed object at '%s'" msgstr "格式錯誤的物件 '%s'" @@ -25510,6 +25610,21 @@ msgid "refname %s is a symbolic ref, copying it is not supported" msgstr "引用名稱 %s 是符號引用,不支援複製" +#: refs/reftable-backend.c +#, c-format +msgid "reftable stack for worktree '%s' is broken" +msgstr "「%s」工作區的 reftable 堆疊已經損壞" + +#: refs/reftable-backend.c +#, c-format +msgid "could not create iterator for worktree '%s'" +msgstr "無法建立「%s」工作區的迭代器" + +#: refs/reftable-backend.c +#, c-format +msgid "could not read record for worktree '%s'" +msgstr "無法讀取「%s」工作區的記錄" + #: refspec.c #, c-format msgid "pattern '%s' has no '*'" @@ -26009,6 +26124,10 @@ msgid "could not finish pack-objects to repack promisor objects" msgstr "無法結束 pack-objects 來重新打包 promisor 物件" +#: repack-promisor.c +msgid "could not start pack-objects to repack promisor packs" +msgstr "無法開始 pack-objects 來重新打包 promisor 封包" + #: repack.c #, c-format msgid "pack prefix %s does not begin with objdir %s" @@ -27221,11 +27340,6 @@ msgstr "非法的標籤名稱:'%.*s'" #: sequencer.c -#, c-format -msgid "could not resolve '%s'" -msgstr "無法解析「%s」" - -#: sequencer.c msgid "writing fake root commit" msgstr "寫偽根提交" @@ -27799,55 +27913,81 @@ msgid "bad %s format: %%%.*s" msgstr "無效的 %s 格式:%%%.*s" -#. TRANSLATORS: IEC 80000-13:2008 gibibyte #: strbuf.c #, c-format -msgid "%u.%2.2u GiB" -msgstr "%u.%2.2u GiB" +msgid "%u.%2.2u" +msgstr "%u.%2.2u" -#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second +#. TRANSLATORS: SI decimal prefix symbol for 10^9 #: strbuf.c -#, c-format -msgid "%u.%2.2u GiB/s" -msgstr "%u.%2.2u GiB/s" +msgid "G" +msgstr "G" -#. TRANSLATORS: IEC 80000-13:2008 mebibyte +#. TRANSLATORS: SI decimal prefix symbol for 10^6 #: strbuf.c -#, c-format -msgid "%u.%2.2u MiB" -msgstr "%u.%2.2u MiB" +msgid "M" +msgstr "M" -#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second +#. TRANSLATORS: SI decimal prefix symbol for 10^3 #: strbuf.c -#, c-format -msgid "%u.%2.2u MiB/s" -msgstr "%u.%2.2u MiB/s" +msgid "k" +msgstr "k" -#. TRANSLATORS: IEC 80000-13:2008 kibibyte +#. TRANSLATORS: IEC 80000-13:2008 gibibyte/second and gibibyte #: strbuf.c -#, c-format -msgid "%u.%2.2u KiB" -msgstr "%u.%2.2u KiB" +msgid "GiB/s" +msgstr "GiB/秒" -#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second #: strbuf.c -#, c-format -msgid "%u.%2.2u KiB/s" -msgstr "%u.%2.2u KiB/s" +msgid "GiB" +msgstr "GiB" -#. TRANSLATORS: IEC 80000-13:2008 byte +#. TRANSLATORS: IEC 80000-13:2008 mebibyte/second and mebibyte #: strbuf.c -#, c-format -msgid "%u byte" -msgid_plural "%u bytes" -msgstr[0] "%u 位元組" +msgid "MiB/s" +msgstr "MiB/秒" + +#: strbuf.c +msgid "MiB" +msgstr "MiB" + +#. TRANSLATORS: IEC 80000-13:2008 kibibyte/second and kibibyte +#: strbuf.c +msgid "KiB/s" +msgstr "KiB/秒" + +#: strbuf.c +msgid "KiB" +msgstr "KiB" + +#. TRANSLATORS: IEC 80000-13:2008 byte/second and byte +#: strbuf.c +msgid "B/s" +msgstr "B/秒" + +#: strbuf.c +msgid "B" +msgstr "B" #. TRANSLATORS: IEC 80000-13:2008 byte/second #: strbuf.c +msgid "byte/s" +msgid_plural "bytes/s" +msgstr[0] "位元組/秒" + +#. TRANSLATORS: IEC 80000-13:2008 byte +#: strbuf.c +msgid "byte" +msgid_plural "bytes" +msgstr[0] "位元組" + +#. TRANSLATORS: The first argument is the number string. The second +#. argument is the unit string (i.e. "12.34 MiB/s"). +#. +#: strbuf.c #, c-format -msgid "%u byte/s" -msgid_plural "%u bytes/s" -msgstr[0] "%u 位元組/秒" +msgid "%s %s" +msgstr "%s %s" #: submodule-config.c #, c-format @@ -28203,10 +28343,6 @@ msgstr "每個執行緒的請求數" #: t/helper/test-simple-ipc.c -msgid "byte" -msgstr "位元組" - -#: t/helper/test-simple-ipc.c msgid "ballast character" msgstr "穩定 (ballast) 字元" @@ -30123,511 +30259,3 @@ #, perl-format msgid "Do you really want to send %s? [y|N]: " msgstr "您真的要傳送 %s?[y|N]: " - -#~ msgid "No previous hunk" -#~ msgstr "沒有上一個區塊" - -#~ msgid "No next hunk" -#~ msgstr "沒有下一個區塊" - -#~ msgid "git for-each-ref [<options>] [<pattern>]" -#~ msgstr "git for-each-ref [<選項>] [<模式>]" - -#~ msgid "git for-each-ref [--merged [<commit>]] [--no-merged [<commit>]]" -#~ msgstr "git for-each-ref [--merged [<提交>]] [--no-merged [<提交>]]" - -#~ msgid "git for-each-ref [--contains [<commit>]] [--no-contains [<commit>]]" -#~ msgstr "git for-each-ref [--contains [<提交>]] [--no-contains [<提交>]]" - -#~ msgid "git for-each-ref [--start-after <marker>]" -#~ msgstr "git for-each-ref [--start-after <marker>]" - -#~ msgid "" -#~ "git pack-refs [--all] [--no-prune] [--auto] [--include <pattern>] [--" -#~ "exclude <pattern>]" -#~ msgstr "" -#~ "git pack-refs [--all] [--no-prune] [--auto] [--include <pattern>] [--" -#~ "exclude <pattern>]" - -#, c-format -#~ msgid "deleting '%s' failed" -#~ msgstr "刪除 '%s' 失敗" - -#, c-format -#~ msgid "creating '%s' failed" -#~ msgstr "建立 '%s' 失敗" - -#, c-format -#~ msgid "could not open index for %s" -#~ msgstr "無法開啟 %s 的索引" - -#~ msgid "cannot handle pushes this big" -#~ msgstr "不能處理這麼大的推送" - -#, c-format -#~ msgid "" -#~ "'%s' is nominated for removal.\n" -#~ "If you still use this command, please add an extra\n" -#~ "option, '--i-still-use-this', on the command line\n" -#~ "and let us know you still use it by sending an e-mail\n" -#~ "to <git@vger.kernel.org>. Thanks.\n" -#~ msgstr "" -#~ "「%s」命令已被提名移除。\n" -#~ "如果您仍在使用該命令,請多加上「--i-still-use-this」\n" -#~ "選項,然後寄封電子郵件到 <git@vger.kernel.org>,\n" -#~ "讓我們知道您還在使用,謝謝。\n" - -#~ msgid "start-after" -#~ msgstr "start-after" - -#~ msgid "compact-summary" -#~ msgstr "精要摘要 (compact-summary)" - -# 譯者:請維持前導空格 -#, c-format -#~ msgid " (%s will become dangling)" -#~ msgstr " (%s 將成為懸空狀態)" - -# 譯者:請維持前導空格 -#, c-format -#~ msgid " (%s has become dangling)" -#~ msgstr " (%s 已成為懸空狀態)" - -#~ msgid "use at most one of --auto and --schedule=<frequency>" -#~ msgstr "--auto 和 --schedule=<頻率> 請任選一" - -#, c-format -#~ msgid "Final output: %d %s\n" -#~ msgstr "最終輸出:%d %s\n" - -#, c-format -#~ msgid "%d (FSCK_IGNORE?) should never trigger this callback" -#~ msgstr "%d (FSCK_IGNORE?) 不應觸發這個回呼函式" - -#~ msgid "" -#~ "git pack-objects --stdout [<options>] [< <ref-list> | < <object-list>]" -#~ msgstr "git pack-objects --stdout [<選項>] [< <引用列表> | < <物件列表>]" - -#~ msgid "" -#~ "git pack-objects [<options>] <base-name> [< <ref-list> | < <object-list>]" -#~ msgstr "git pack-objects [<選項>] <前綴名稱> [< <引用列表> | < <物件列表>]" - -#~ msgid "cannot use --stdin-packs with --cruft" -#~ msgstr "無法將 --stdin-packs 與 --cruft 組合使用" - -#, c-format -#~ msgid "unreachable: invalid reference: %s" -#~ msgstr "不可達:無效引用:%s" - -#~ msgid "trying to write commit not in index" -#~ msgstr "嘗試寫入不在索引的提交" - -#~ msgid "git cat-file (-t | -s) [--allow-unknown-type] <object>" -#~ msgstr "git cat-file (-t | -s) [--allow-unknown-type] <object>" - -#~ msgid "allow -s and -t to work with broken/corrupt objects" -#~ msgstr "允許 -s 和 -t 對損壞的物件生效" - -#, c-format -#~ msgid "%s: object is of unknown type '%s': %s" -#~ msgstr "%s:物件屬於「%s」未知類型:%s" - -#~ msgid "(bad commit)\n" -#~ msgstr "(壞提交)\n" - -#, c-format -#~ msgid "add_cacheinfo failed for path '%s'; merge aborting." -#~ msgstr "add_cacheinfo 對路徑 '%s' 執行失敗,合併終止。" - -#, c-format -#~ msgid "add_cacheinfo failed to refresh for path '%s'; merge aborting." -#~ msgstr "add_cacheinfo 無法重新整理路徑 '%s',合併終止。" - -#, c-format -#~ msgid "failed to create path '%s'%s" -#~ msgstr "建立路徑 '%s'%s 失敗" - -#, c-format -#~ msgid "Removing %s to make room for subdirectory\n" -#~ msgstr "刪除 %s 以便為子目錄留出空間\n" - -#~ msgid ": perhaps a D/F conflict?" -#~ msgstr ":可能是一個目錄/檔案衝突?" - -#, c-format -#~ msgid "refusing to lose untracked file at '%s'" -#~ msgstr "拒絕捨棄 '%s' 中的未追蹤檔案" - -#, c-format -#~ msgid "blob expected for %s '%s'" -#~ msgstr "%s '%s' 應為資料物件" - -#, c-format -#~ msgid "failed to open '%s': %s" -#~ msgstr "開啟 '%s' 失敗:%s" - -#, c-format -#~ msgid "failed to symlink '%s': %s" -#~ msgstr "建立符號連結 '%s' 失敗:%s" - -#, c-format -#~ msgid "do not know what to do with %06o %s '%s'" -#~ msgstr "不知道如何處理 %06o %s '%s'" - -#, c-format -#~ msgid "Failed to merge submodule %s (repository corrupt)" -#~ msgstr "無法合併子模組 %s (版本庫損壞)" - -#, c-format -#~ msgid "Fast-forwarding submodule %s to the following commit:" -#~ msgstr "子模組 %s 快轉到如下提交:" - -#, c-format -#~ msgid "Fast-forwarding submodule %s" -#~ msgstr "快轉子模組 %s" - -#, c-format -#~ msgid "Failed to merge submodule %s (merge following commits not found)" -#~ msgstr "無法合併子模組 %s (找不到合併跟隨的提交)" - -#, c-format -#~ msgid "Failed to merge submodule %s (not fast-forward)" -#~ msgstr "無法合併子模組 %s(非快轉)" - -#~ msgid "Found a possible merge resolution for the submodule:\n" -#~ msgstr "找到子模組的一個可能的合併方案:\n" - -#, c-format -#~ msgid "" -#~ "If this is correct simply add it to the index for example\n" -#~ "by using:\n" -#~ "\n" -#~ " git update-index --cacheinfo 160000 %s \"%s\"\n" -#~ "\n" -#~ "which will accept this suggestion.\n" -#~ msgstr "" -#~ "正確的話,就能直接加進索引,例如執行下述命令:\n" -#~ "\n" -#~ " git update-index --cacheinfo 160000 %s \"%s\"\n" -#~ "\n" -#~ "接受本建議。\n" - -#, c-format -#~ msgid "Failed to merge submodule %s (multiple merges found)" -#~ msgstr "無法合併子模組 %s (發現多個合併)" - -#~ msgid "failed to execute internal merge" -#~ msgstr "無法執行內部合併" - -#, c-format -#~ msgid "unable to add %s to database" -#~ msgstr "無法將 %s 加進資料庫" - -#, c-format -#~ msgid "Error: Refusing to lose untracked file at %s; writing to %s instead." -#~ msgstr "錯誤:拒絕遺失未追蹤檔案 '%s',而是寫入 %s。" - -#, c-format -#~ msgid "" -#~ "CONFLICT (%s/delete): %s deleted in %s and %s in %s. Version %s of %s " -#~ "left in tree." -#~ msgstr "" -#~ "衝突(%1$s/刪除):%2$s 在 %3$s 中被刪除,在 %5$s 中被 %4$s。%7$s 的 %6$s " -#~ "版本被保留。" - -#, c-format -#~ msgid "" -#~ "CONFLICT (%s/delete): %s deleted in %s and %s to %s in %s. Version %s of " -#~ "%s left in tree." -#~ msgstr "" -#~ "衝突(%1$s/刪除):%2$s 在 %3$s 中被刪除,在 %6$s 中的 %5$s 被 %4$s。%8$s " -#~ "的 %7$s 版本被保留。" - -#, c-format -#~ msgid "" -#~ "CONFLICT (%s/delete): %s deleted in %s and %s in %s. Version %s of %s " -#~ "left in tree at %s." -#~ msgstr "" -#~ "衝突(%1$s/刪除):%2$s 在 %3$s 中被刪除,在 %5$s 中被 %4$s。%7$s 的 %6$s " -#~ "版本保留在 %8$s 中。" - -#, c-format -#~ msgid "" -#~ "CONFLICT (%s/delete): %s deleted in %s and %s to %s in %s. Version %s of " -#~ "%s left in tree at %s." -#~ msgstr "" -#~ "衝突(%1$s/刪除):%2$s 在 %3$s 中被刪除,在 %6$s 中的 %5$s 被 %4$s。%8$s " -#~ "的 %7$s 版本保留在 %9$s 中。" - -#~ msgid "rename" -#~ msgstr "重新命名" - -#~ msgid "renamed" -#~ msgstr "已重新命名" - -#, c-format -#~ msgid "Refusing to lose dirty file at %s" -#~ msgstr "拒絕遺失髒檔案 '%s'" - -#, c-format -#~ msgid "Refusing to lose untracked file at %s, even though it's in the way." -#~ msgstr "拒絕在 '%s' 處失去未追蹤檔案,即使它存在於重新命名中。" - -#, c-format -#~ msgid "CONFLICT (rename/add): Rename %s->%s in %s. Added %s in %s" -#~ msgstr "" -#~ "衝突(重新命名/新增):在 %3$s 中重新命名 %1$s->%2$s。在 %5$s 中新增 %4$s" - -#, c-format -#~ msgid "%s is a directory in %s adding as %s instead" -#~ msgstr "%s 是 %s 中的一個目錄而已 %s 為名被新增" - -#, c-format -#~ msgid "Refusing to lose untracked file at %s; adding as %s instead" -#~ msgstr "拒絕遺失未追蹤檔案 '%s',而是新增為 %s" - -#, c-format -#~ msgid "" -#~ "CONFLICT (rename/rename): Rename \"%s\"->\"%s\" in branch \"%s\" rename " -#~ "\"%s\"->\"%s\" in \"%s\"%s" -#~ msgstr "" -#~ "衝突(重新命名/重新命名):在分支 \"%3$s\" 中重新命名 \"%1$s\"->\"%2$s\"," -#~ "在分支 \"%6$s\" 中重新命名 \"%4$s\"->\"%5$s\"%7$s" - -#~ msgid " (left unresolved)" -#~ msgstr " (留下未解決)" - -#, c-format -#~ msgid "CONFLICT (rename/rename): Rename %s->%s in %s. Rename %s->%s in %s" -#~ msgstr "" -#~ "衝突(重新命名/重新命名):在 %3$s 中重新命名 %1$s->%2$s,在 %6$s 中重新命" -#~ "名 %4$s->%5$s" - -#, c-format -#~ msgid "" -#~ "CONFLICT (directory rename split): Unclear where to place %s because " -#~ "directory %s was renamed to multiple other directories, with no " -#~ "destination getting a majority of the files." -#~ msgstr "" -#~ "衝突(分割的目錄重新命名):不清楚 %s 應該放在哪裡,因為目錄 %s 被重新命名" -#~ "到多個其它目錄,沒有目錄包含大部分檔案。" - -#, c-format -#~ msgid "" -#~ "CONFLICT (rename/rename): Rename directory %s->%s in %s. Rename directory " -#~ "%s->%s in %s" -#~ msgstr "" -#~ "衝突(重新命名/重新命名):在 %3$s 中重新命名目錄 %1$s->%2$s,在 %6$s 中重" -#~ "新命名目錄 %4$s->%5$s" - -#, c-format -#~ msgid "object %s is not a blob" -#~ msgstr "物件 %s 不是一個資料物件" - -#~ msgid "modify" -#~ msgstr "修改" - -#~ msgid "modified" -#~ msgstr "修改" - -#, c-format -#~ msgid "Skipped %s (merged same as existing)" -#~ msgstr "略過 %s(已經做過相同合併)" - -#, c-format -#~ msgid "Adding as %s instead" -#~ msgstr "而是以 %s 為名新增" - -#, c-format -#~ msgid "Removing %s" -#~ msgstr "刪除 %s" - -#~ msgid "file/directory" -#~ msgstr "檔案/目錄" - -#~ msgid "directory/file" -#~ msgstr "目錄/檔案" - -#, c-format -#~ msgid "" -#~ "CONFLICT (%s): There is a directory with name %s in %s. Adding %s as %s" -#~ msgstr "" -#~ "衝突(%1$s):在 %3$s 中有一個名為 %2$s 的目錄。以 %5$s 為名新增 %4$s" - -#, c-format -#~ msgid "Adding %s" -#~ msgstr "新增 %s" - -#, c-format -#~ msgid "CONFLICT (add/add): Merge conflict in %s" -#~ msgstr "衝突(add/add):合併衝突於 %s" - -#, c-format -#~ msgid "merging of trees %s and %s failed" -#~ msgstr "無法合併樹 %s 和 %s" - -#~ msgid "Merging:" -#~ msgstr "合併:" - -#, c-format -#~ msgid "found %u common ancestor:" -#~ msgid_plural "found %u common ancestors:" -#~ msgstr[0] "發現 %u 個共同祖先:" - -#~ msgid "merge returned no commit" -#~ msgstr "合併未返回提交" - -#~ msgid "cannot write incremental MIDX with bitmap" -#~ msgstr "無法寫入有位圖的增量 MIDX" - -#, c-format -#~ msgid "Could not find remote branch %s to clone." -#~ msgstr "找不到要複製的遠端分支 %s。" - -#, c-format -#~ msgid "merging cannot continue; got unclean result of %d" -#~ msgstr "無法繼續合併:從 %d 收到的結果不乾淨" - -#~ msgid "--onto and --advance are incompatible" -#~ msgstr "--onto 和 --advance 不相容" - -#, c-format -#~ msgid "key '%s' of pattern had no '*'" -#~ msgstr "模式的鍵 '%s' 沒有 '*'" - -#, c-format -#~ msgid "preferred pack (%s) is invalid" -#~ msgstr "偏好的封包 (%s) 無效" - -#, c-format -#~ msgid "" -#~ "more than %i tags found; listed %i most recent\n" -#~ "gave up search at %s\n" -#~ msgstr "" -#~ "發現多於 %i 個標籤,列出最近的 %i 個\n" -#~ "在 %s 放棄搜尋\n" - -#~ msgid "Public key pinning not supported with cURL < 7.39.0" -#~ msgstr "不支援公鑰檔案鎖定,因為 cURL < 7.39.0" - -#~ msgid "CURLSSLOPT_NO_REVOKE not supported with cURL < 7.44.0" -#~ msgstr "不支援 CURLSSLOPT_NO_REVOKE,因為 cURL < 7.44.0" - -#~ msgid "revision walk setup failed\n" -#~ msgstr "修訂版遍歷設定失敗\n" - -#, c-format -#~ msgid "unable to parse contact: %s" -#~ msgstr "無法解析聯絡地址:%s" - -#~ msgid "" -#~ "the add.interactive.useBuiltin setting has been removed!\n" -#~ "See its entry in 'git help config' for details." -#~ msgstr "" -#~ "add.interactive.useBuiltin 設定已被移除!\n" -#~ "深入了解請參閱 “git help config” 中的對應條目。" - -#~ msgid "git archive: Remote with no URL" -#~ msgstr "git archive: 未提供遠端 URL" - -#~ msgid "only one action at a time" -#~ msgstr "一次只能有一個動作" - -#~ msgid "use [RFC PATCH] instead of [PATCH]" -#~ msgstr "使用 [RFC PATCH] 代替 [PATCH]" - -#, c-format -#~ msgid "remote '%s' has no configured URL" -#~ msgstr "“%s” 遠端未設定 URL" - -#, c-format -#~ msgid "truncating .rej filename to %.*s.rej" -#~ msgstr "正在將 .rej 檔案名稱截短為 %.*s.rej" - -#~ msgid "" -#~ "Use -f if you really want to add them.\n" -#~ "Turn this message off by running\n" -#~ "\"git config advice.addIgnoredFile false\"" -#~ msgstr "" -#~ "若您真的想加入,請傳入 -f。\n" -#~ "如要關閉此訊息,請執行\n" -#~ "“git config advice.addIgnoredFile false”" - -#~ msgid "" -#~ "Maybe you wanted to say 'git add .'?\n" -#~ "Turn this message off by running\n" -#~ "\"git config advice.addEmptyPathspec false\"" -#~ msgstr "" -#~ "可能您想做 “git add .”?\n" -#~ "如要關閉此訊息,請執行\n" -#~ "“git config advice.addEmptyPathspec false”" - -#~ msgid "" -#~ "clean.requireForce defaults to true and neither -i, -n, nor -f given; " -#~ "refusing to clean" -#~ msgstr "" -#~ "clean.requireForce 預設為 true 且未提供 -i、-n 或 -f 選項,拒絕執行清理動" -#~ "作" - -#, c-format -#~ msgid "bad ls-files format: element '%s' does not start with '('" -#~ msgstr "ls-files 格式錯誤:“%s” 元素不以 “(” 開頭" - -#, c-format -#~ msgid "bad ls-files format: element '%s' does not end in ')'" -#~ msgstr "ls-files 格式錯誤:“%s” 元素不以 “)” 結尾" - -#, c-format -#~ msgid "bad ls-files format: %%%.*s" -#~ msgstr "ls-files 格式錯誤:%%%.*s" - -#~ msgid "keep redundant, empty commits" -#~ msgstr "保持多餘的、空的提交" - -#~ msgid "core.commentChar should only be one ASCII character" -#~ msgstr "core.commentChar 應該是一個 ASCII 字元" - -#~ msgid "" -#~ "--bundle-uri is incompatible with --depth, --shallow-since, and --shallow-" -#~ "exclude" -#~ msgstr "" -#~ "--bundle-uri 與 --depth、--shallow-since 和 --shallow-exclude 不相容" - -#~ msgid "--merge-base is incompatible with --stdin" -#~ msgstr "--merge-base 與 --stdin 不相容" - -#~ msgid "" -#~ "apply options are incompatible with rebase.autoSquash. Consider adding --" -#~ "no-autosquash" -#~ msgstr "apply 選項與 rebase.autoSquash 不相容。請考慮加上 --no-autosquash" - -#~ msgid "--exclude-hidden cannot be used together with --branches" -#~ msgstr "--exclude-hidden 無法與 --branches 同時使用" - -#~ msgid "--exclude-hidden cannot be used together with --tags" -#~ msgstr "--exclude-hidden 無法與 --tags 同時使用" - -#~ msgid "--exclude-hidden cannot be used together with --remotes" -#~ msgstr "--exclude-hidden 無法與 --remotes 同時使用" - -#, c-format -#~ msgid "only one of '%s', '%s' or '%s' can be given" -#~ msgstr "只能傳入 “%s”、“%s” 或 “%s”" - -#, c-format -#~ msgid "'%s' and '%s' cannot be used together" -#~ msgstr "無法同時使用 “%s” 和 “%s”" - -#, c-format -#~ msgid "options '%s', and '%s' cannot be used together" -#~ msgstr "無法同時使用 “%s” 和 “%s” 選項" - -#~ msgid "<commit-ish>" -#~ msgstr "<提交指示元>" - -#, c-format -#~ msgid "%s is incompatible with %s" -#~ msgstr "%s 與 %s 不相容" - -#~ msgid "unhandled options" -#~ msgstr "未處理選項"
diff --git a/pretty.c b/pretty.c index e0646bb..8148039 100644 --- a/pretty.c +++ b/pretty.c
@@ -781,7 +781,7 @@ static int mailmap_name(const char **email, size_t *email_len, static struct string_list *mail_map; if (!mail_map) { CALLOC_ARRAY(mail_map, 1); - read_mailmap(mail_map); + read_mailmap(the_repository, mail_map); } return mail_map->nr && map_user(mail_map, email, email_len, name, name_len); } @@ -1549,6 +1549,21 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ if (!commit->object.parsed) parse_object(the_repository, &commit->object.oid); + if (starts_with(placeholder, "(count)")) { + if (!c->pretty_ctx->rev) + die(_("%s is not supported by this command"), "%(count)"); + strbuf_addf(sb, "%0*d", decimal_width(c->pretty_ctx->rev->total), + c->pretty_ctx->rev->nr); + return 7; + } + + if (starts_with(placeholder, "(total)")) { + if (!c->pretty_ctx->rev) + die(_("%s is not supported by this command"), "%(total)"); + strbuf_addf(sb, "%d", c->pretty_ctx->rev->total); + return 7; + } + switch (placeholder[0]) { case 'H': /* commit hash */ strbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));
diff --git a/promisor-remote.c b/promisor-remote.c index 77ebf53..96fa215 100644 --- a/promisor-remote.c +++ b/promisor-remote.c
@@ -193,6 +193,7 @@ void promisor_remote_clear(struct promisor_remote_config *config) while (config->promisors) { struct promisor_remote *r = config->promisors; free(r->partial_clone_filter); + free(r->advertised_filter); config->promisors = config->promisors->next; free(r); } @@ -375,18 +376,24 @@ static char *fields_from_config(struct string_list *fields_list, const char *con return fields; } +static struct string_list *initialize_fields_list(struct string_list *fields_list, int *initialized, + const char *config_key) +{ + if (!*initialized) { + fields_list->cmp = strcasecmp; + fields_from_config(fields_list, config_key); + *initialized = 1; + } + + return fields_list; +} + static struct string_list *fields_sent(void) { static struct string_list fields_list = STRING_LIST_INIT_NODUP; static int initialized; - if (!initialized) { - fields_list.cmp = strcasecmp; - fields_from_config(&fields_list, "promisor.sendFields"); - initialized = 1; - } - - return &fields_list; + return initialize_fields_list(&fields_list, &initialized, "promisor.sendFields"); } static struct string_list *fields_checked(void) @@ -394,13 +401,15 @@ static struct string_list *fields_checked(void) static struct string_list fields_list = STRING_LIST_INIT_NODUP; static int initialized; - if (!initialized) { - fields_list.cmp = strcasecmp; - fields_from_config(&fields_list, "promisor.checkFields"); - initialized = 1; - } + return initialize_fields_list(&fields_list, &initialized, "promisor.checkFields"); +} - return &fields_list; +static struct string_list *fields_stored(void) +{ + static struct string_list fields_list = STRING_LIST_INIT_NODUP; + static int initialized; + + return initialize_fields_list(&fields_list, &initialized, "promisor.storeFields"); } /* @@ -692,6 +701,132 @@ static struct promisor_info *parse_one_advertised_remote(const char *remote_info return info; } +static bool store_one_field(struct repository *repo, const char *remote_name, + const char *field_name, const char *field_key, + const char *advertised, const char *current) +{ + if (advertised && (!current || strcmp(current, advertised))) { + char *key = xstrfmt("remote.%s.%s", remote_name, field_key); + + fprintf(stderr, _("Storing new %s from server for remote '%s'.\n" + " '%s' -> '%s'\n"), + field_name, remote_name, + current ? current : "", + advertised); + + repo_config_set_gently(repo, key, advertised); + free(key); + + return true; + } + + return false; +} + +/* Check that a filter is valid by parsing it */ +static bool valid_filter(const char *filter, const char *remote_name) +{ + struct list_objects_filter_options filter_opts = LIST_OBJECTS_FILTER_INIT; + struct strbuf err = STRBUF_INIT; + int res = gently_parse_list_objects_filter(&filter_opts, filter, &err); + + if (res) + warning(_("invalid filter '%s' for remote '%s' " + "will not be stored: %s"), + filter, remote_name, err.buf); + + list_objects_filter_release(&filter_opts); + strbuf_release(&err); + + return !res; +} + +/* Check that a token doesn't contain any control character */ +static bool valid_token(const char *token, const char *remote_name) +{ + const char *c = token; + + for (; *c; c++) + if (iscntrl(*c)) { + warning(_("invalid token '%s' for remote '%s' " + "will not be stored"), + token, remote_name); + return false; + } + + return true; +} + +struct store_info { + struct repository *repo; + struct string_list config_info; + bool store_filter; + bool store_token; +}; + +static struct store_info *store_info_new(struct repository *repo) +{ + struct string_list *fields_to_store = fields_stored(); + struct store_info *s = xmalloc(sizeof(*s)); + + s->repo = repo; + + string_list_init_nodup(&s->config_info); + promisor_config_info_list(repo, &s->config_info, fields_to_store); + string_list_sort(&s->config_info); + + s->store_filter = !!string_list_lookup(fields_to_store, promisor_field_filter); + s->store_token = !!string_list_lookup(fields_to_store, promisor_field_token); + + return s; +} + +static void store_info_free(struct store_info *s) +{ + if (s) { + promisor_info_list_clear(&s->config_info); + free(s); + } +} + +static bool promisor_store_advertised_fields(struct promisor_info *advertised, + struct store_info *store_info) +{ + struct promisor_info *p; + struct string_list_item *item; + const char *remote_name = advertised->name; + bool reload_config = false; + + if (!(store_info->store_filter || store_info->store_token)) + return false; + + /* + * Get existing config info for the advertised promisor + * remote. This ensures the remote is already configured on + * the client side. + */ + item = string_list_lookup(&store_info->config_info, remote_name); + + if (!item) + return false; + + p = item->util; + + if (store_info->store_filter && advertised->filter && + valid_filter(advertised->filter, remote_name)) + reload_config |= store_one_field(store_info->repo, remote_name, + "filter", promisor_field_filter, + advertised->filter, p->filter); + + if (store_info->store_token && advertised->token && + valid_token(advertised->token, remote_name)) + reload_config |= store_one_field(store_info->repo, remote_name, + "token", promisor_field_token, + advertised->token, p->token); + + return reload_config; +} + static void filter_promisor_remote(struct repository *repo, struct strvec *accepted, const char *info) @@ -700,7 +835,10 @@ static void filter_promisor_remote(struct repository *repo, enum accept_promisor accept = ACCEPT_NONE; struct string_list config_info = STRING_LIST_INIT_NODUP; struct string_list remote_info = STRING_LIST_INIT_DUP; + struct store_info *store_info = NULL; struct string_list_item *item; + bool reload_config = false; + struct string_list accepted_filters = STRING_LIST_INIT_DUP; if (!repo_config_get_string_tmp(the_repository, "promisor.acceptfromserver", &accept_str)) { if (!*accept_str || !strcasecmp("None", accept_str)) @@ -736,35 +874,73 @@ static void filter_promisor_remote(struct repository *repo, string_list_sort(&config_info); } - if (should_accept_remote(accept, advertised, &config_info)) + if (should_accept_remote(accept, advertised, &config_info)) { + if (!store_info) + store_info = store_info_new(repo); + if (promisor_store_advertised_fields(advertised, store_info)) + reload_config = true; + strvec_push(accepted, advertised->name); + /* Capture advertised filters for accepted remotes */ + if (advertised->filter) { + struct string_list_item *i; + i = string_list_append(&accepted_filters, advertised->name); + i->util = xstrdup(advertised->filter); + } + } + promisor_info_free(advertised); } promisor_info_list_clear(&config_info); string_list_clear(&remote_info, 0); + store_info_free(store_info); + + if (reload_config) + repo_promisor_remote_reinit(repo); + + /* Apply accepted remote filters to the stable repo state */ + for_each_string_list_item(item, &accepted_filters) { + struct promisor_remote *r = repo_promisor_remote_find(repo, item->string); + if (r) { + free(r->advertised_filter); + r->advertised_filter = item->util; + item->util = NULL; + } + } + + string_list_clear(&accepted_filters, 1); + + /* Mark the remotes as accepted in the repository state */ + for (size_t i = 0; i < accepted->nr; i++) { + struct promisor_remote *r = repo_promisor_remote_find(repo, accepted->v[i]); + if (r) + r->accepted = 1; + } } -char *promisor_remote_reply(const char *info) +void promisor_remote_reply(const char *info, char **accepted_out) { struct strvec accepted = STRVEC_INIT; - struct strbuf reply = STRBUF_INIT; filter_promisor_remote(the_repository, &accepted, info); - if (!accepted.nr) - return NULL; - - for (size_t i = 0; i < accepted.nr; i++) { - if (i) - strbuf_addch(&reply, ';'); - strbuf_addstr_urlencode(&reply, accepted.v[i], allow_unsanitized); + if (accepted_out) { + if (accepted.nr) { + struct strbuf reply = STRBUF_INIT; + for (size_t i = 0; i < accepted.nr; i++) { + if (i) + strbuf_addch(&reply, ';'); + strbuf_addstr_urlencode(&reply, accepted.v[i], allow_unsanitized); + } + *accepted_out = strbuf_detach(&reply, NULL); + } else { + *accepted_out = NULL; + } } strvec_clear(&accepted); - - return strbuf_detach(&reply, NULL); } void mark_promisor_remotes_as_accepted(struct repository *r, const char *remotes) @@ -789,3 +965,33 @@ void mark_promisor_remotes_as_accepted(struct repository *r, const char *remotes string_list_clear(&accepted_remotes, 0); } + +char *promisor_remote_construct_filter(struct repository *repo) +{ + struct promisor_remote *r; + struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT; + struct strbuf err = STRBUF_INIT; + char *result = NULL; + + promisor_remote_init(repo); + + for (r = repo->promisor_remote_config->promisors; r; r = r->next) { + if (r->accepted && r->advertised_filter) + if (gently_parse_list_objects_filter(&filter_options, + r->advertised_filter, + &err)) { + warning(_("promisor remote '%s' advertised invalid filter '%s': %s"), + r->name, r->advertised_filter, err.buf); + strbuf_reset(&err); + continue; + } + } + + if (filter_options.choice) + result = xstrdup(expand_list_objects_filter_spec(&filter_options)); + + list_objects_filter_release(&filter_options); + strbuf_release(&err); + + return result; +}
diff --git a/promisor-remote.h b/promisor-remote.h index 263d331..3d4d2de 100644 --- a/promisor-remote.h +++ b/promisor-remote.h
@@ -15,6 +15,7 @@ struct object_id; struct promisor_remote { struct promisor_remote *next; char *partial_clone_filter; + char *advertised_filter; unsigned int accepted : 1; const char name[FLEX_ARRAY]; }; @@ -48,12 +49,12 @@ char *promisor_remote_info(struct repository *repo); /* * Prepare a reply to a "promisor-remote" advertisement from a server. * Check the value of "promisor.acceptfromserver" and maybe the - * configured promisor remotes, if any, to prepare the reply. - * Return value is NULL if no promisor remote from the server - * is accepted. Otherwise it contains the names of the accepted promisor - * remotes separated by ';'. See gitprotocol-v2(5). + * configured promisor remotes, if any, to prepare the reply. If the + * `accepted_out` argument is not NULL, it is set to either NULL or to + * the names of the accepted promisor remotes separated by ';' if + * any. See gitprotocol-v2(5). */ -char *promisor_remote_reply(const char *info); +void promisor_remote_reply(const char *info, char **accepted_out); /* * Set the 'accepted' flag for some promisor remotes. Useful on the @@ -67,4 +68,10 @@ void mark_promisor_remotes_as_accepted(struct repository *repo, const char *remo */ int repo_has_accepted_promisor_remote(struct repository *r); +/* + * Use the filters from the accepted remotes to create a combined + * filter (useful in `--filter=auto` mode). + */ +char *promisor_remote_construct_filter(struct repository *repo); + #endif /* PROMISOR_REMOTE_H */
diff --git a/protocol-caps.c b/protocol-caps.c index ecdd0dc..35072ed 100644 --- a/protocol-caps.c +++ b/protocol-caps.c
@@ -4,7 +4,6 @@ #include "hex.h" #include "pkt-line.h" #include "hash.h" -#include "hex.h" #include "object.h" #include "odb.h" #include "repository.h"
diff --git a/pseudo-merge.c b/pseudo-merge.c index 893b763..a2d5bd8 100644 --- a/pseudo-merge.c +++ b/pseudo-merge.c
@@ -221,28 +221,25 @@ void load_pseudo_merges_from_config(struct repository *r, } } -static int find_pseudo_merge_group_for_ref(const char *refname, - const char *referent UNUSED, - const struct object_id *oid, - int flags UNUSED, - void *_data) +static int find_pseudo_merge_group_for_ref(const struct reference *ref, void *_data) { struct bitmap_writer *writer = _data; + const struct object_id *maybe_peeled = ref->oid; struct object_id peeled; struct commit *c; uint32_t i; int has_bitmap; - if (!peel_iterated_oid(the_repository, oid, &peeled)) - oid = &peeled; + if (!reference_get_peeled_oid(the_repository, ref, &peeled)) + maybe_peeled = &peeled; - c = lookup_commit(the_repository, oid); + c = lookup_commit(the_repository, maybe_peeled); if (!c) return 0; - if (!packlist_find(writer->to_pack, oid)) + if (!packlist_find(writer->to_pack, maybe_peeled)) return 0; - has_bitmap = bitmap_writer_has_bitmapped_object_id(writer, oid); + has_bitmap = bitmap_writer_has_bitmapped_object_id(writer, maybe_peeled); for (i = 0; i < writer->pseudo_merge_groups.nr; i++) { struct pseudo_merge_group *group; @@ -252,7 +249,7 @@ static int find_pseudo_merge_group_for_ref(const char *refname, size_t j; group = writer->pseudo_merge_groups.items[i].util; - if (regexec(group->pattern, refname, ARRAY_SIZE(captures), + if (regexec(group->pattern, ref->name, ARRAY_SIZE(captures), captures, 0)) continue; @@ -269,7 +266,7 @@ static int find_pseudo_merge_group_for_ref(const char *refname, if (group_name.len) strbuf_addch(&group_name, '-'); - strbuf_add(&group_name, refname + match->rm_so, + strbuf_add(&group_name, ref->name + match->rm_so, match->rm_eo - match->rm_so); }
diff --git a/range-diff.c b/range-diff.c index 57edff4..2712a9a 100644 --- a/range-diff.c +++ b/range-diff.c
@@ -140,7 +140,7 @@ static int read_patches(const char *range, struct string_list *list, if (eol) *eol = '\n'; orig_len = len; - len = parse_git_diff_header(&root, &linenr, 0, line, + len = parse_git_diff_header(&root, NULL, &linenr, 0, line, len, size, &patch); if (len < 0) { error(_("could not parse git header '%.*s'"),
diff --git a/reachable.c b/reachable.c index 22266db..101cfc2 100644 --- a/reachable.c +++ b/reachable.c
@@ -83,18 +83,17 @@ static void add_rebase_files(struct rev_info *revs) free_worktrees(worktrees); } -static int add_one_ref(const char *path, const char *referent UNUSED, const struct object_id *oid, - int flag, void *cb_data) +static int add_one_ref(const struct reference *ref, void *cb_data) { struct rev_info *revs = (struct rev_info *)cb_data; struct object *object; - if ((flag & REF_ISSYMREF) && (flag & REF_ISBROKEN)) { - warning("symbolic ref is dangling: %s", path); + if ((ref->flags & REF_ISSYMREF) && (ref->flags & REF_ISBROKEN)) { + warning("symbolic ref is dangling: %s", ref->name); return 0; } - object = parse_object_or_die(the_repository, oid, path); + object = parse_object_or_die(the_repository, ref->oid, ref->name); add_pending_object(revs, object, ""); return 0; @@ -192,30 +191,27 @@ static int obj_is_recent(const struct object_id *oid, timestamp_t mtime, return oidset_contains(&data->extra_recent_oids, oid); } -static void add_recent_object(const struct object_id *oid, - struct packed_git *pack, - off_t offset, - timestamp_t mtime, - struct recent_data *data) +static int want_recent_object(struct recent_data *data, + const struct object_id *oid) { + if (data->ignore_in_core_kept_packs && + has_object_kept_pack(data->revs->repo, oid, KEPT_PACK_IN_CORE)) + return 0; + return 1; +} + +static int add_recent_object(const struct object_id *oid, + struct object_info *oi, + void *cb_data) +{ + struct recent_data *data = cb_data; struct object *obj; - enum object_type type; - if (!obj_is_recent(oid, mtime, data)) - return; + if (!want_recent_object(data, oid) || + !obj_is_recent(oid, *oi->mtimep, data)) + return 0; - /* - * We do not want to call parse_object here, because - * inflating blobs and trees could be very expensive. - * However, we do need to know the correct type for - * later processing, and the revision machinery expects - * commits and tags to have been parsed. - */ - type = odb_read_object_info(the_repository->objects, oid, NULL); - if (type < 0) - die("unable to get object info for %s", oid_to_hex(oid)); - - switch (type) { + switch (*oi->typep) { case OBJ_TAG: case OBJ_COMMIT: obj = parse_object_or_die(the_repository, oid, NULL); @@ -228,77 +224,22 @@ static void add_recent_object(const struct object_id *oid, break; default: die("unknown object type for %s: %s", - oid_to_hex(oid), type_name(type)); + oid_to_hex(oid), type_name(*oi->typep)); } if (!obj) die("unable to lookup %s", oid_to_hex(oid)); + if (obj->flags & SEEN) + return 0; add_pending_object(data->revs, obj, ""); - if (data->cb) - data->cb(obj, pack, offset, mtime); -} - -static int want_recent_object(struct recent_data *data, - const struct object_id *oid) -{ - if (data->ignore_in_core_kept_packs && - has_object_kept_pack(data->revs->repo, oid, IN_CORE_KEEP_PACKS)) - return 0; - return 1; -} - -static int add_recent_loose(const struct object_id *oid, - const char *path, void *data) -{ - struct stat st; - struct object *obj; - - if (!want_recent_object(data, oid)) - return 0; - - obj = lookup_object(the_repository, oid); - - if (obj && obj->flags & SEEN) - return 0; - - if (stat(path, &st) < 0) { - /* - * It's OK if an object went away during our iteration; this - * could be due to a simultaneous repack. But anything else - * we should abort, since we might then fail to mark objects - * which should not be pruned. - */ - if (errno == ENOENT) - return 0; - return error_errno("unable to stat %s", oid_to_hex(oid)); + if (data->cb) { + if (oi->whence == OI_PACKED) + data->cb(obj, oi->u.packed.pack, oi->u.packed.offset, *oi->mtimep); + else + data->cb(obj, NULL, 0, *oi->mtimep); } - add_recent_object(oid, NULL, 0, st.st_mtime, data); - return 0; -} - -static int add_recent_packed(const struct object_id *oid, - struct packed_git *p, - uint32_t pos, - void *data) -{ - struct object *obj; - timestamp_t mtime = p->mtime; - - if (!want_recent_object(data, oid)) - return 0; - - obj = lookup_object(the_repository, oid); - - if (obj && obj->flags & SEEN) - return 0; - if (p->is_cruft) { - if (load_pack_mtimes(p) < 0) - die(_("could not load cruft pack .mtimes")); - mtime = nth_packed_mtime(p, pos); - } - add_recent_object(oid, p, nth_packed_object_offset(p, pos), mtime, data); return 0; } @@ -308,7 +249,13 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs, int ignore_in_core_kept_packs) { struct recent_data data; - enum for_each_object_flags flags; + unsigned flags; + enum object_type type; + time_t mtime; + struct object_info oi = { + .mtimep = &mtime, + .typep = &type, + }; int r; data.revs = revs; @@ -319,17 +266,14 @@ int add_unseen_recent_objects_to_traversal(struct rev_info *revs, oidset_init(&data.extra_recent_oids, 0); data.extra_recent_oids_loaded = 0; - r = for_each_loose_object(the_repository->objects, add_recent_loose, &data, - FOR_EACH_OBJECT_LOCAL_ONLY); + flags = ODB_FOR_EACH_OBJECT_LOCAL_ONLY | ODB_FOR_EACH_OBJECT_PACK_ORDER; + if (ignore_in_core_kept_packs) + flags |= ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS; + + r = odb_for_each_object(revs->repo->objects, &oi, add_recent_object, &data, flags); if (r) goto done; - flags = FOR_EACH_OBJECT_LOCAL_ONLY | FOR_EACH_OBJECT_PACK_ORDER; - if (ignore_in_core_kept_packs) - flags |= FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS; - - r = for_each_packed_object(revs->repo, add_recent_packed, &data, flags); - done: oidset_clear(&data.extra_recent_oids);
diff --git a/read-cache-ll.h b/read-cache-ll.h index 71b49d9..2c8b4b2 100644 --- a/read-cache-ll.h +++ b/read-cache-ll.h
@@ -481,7 +481,7 @@ int cmp_cache_name_compare(const void *a_, const void *b_); int add_files_to_cache(struct repository *repo, const char *prefix, const struct pathspec *pathspec, char *ps_matched, - int include_sparse, int flags); + int include_sparse, int flags, int ignored_too ); void overlay_tree_on_index(struct index_state *istate, const char *tree_name, const char *prefix);
diff --git a/read-cache.c b/read-cache.c index 032480d..5049f9b 100644 --- a/read-cache.c +++ b/read-cache.c
@@ -47,6 +47,9 @@ #include "csum-file.h" #include "promisor-remote.h" #include "hook.h" +#include "submodule.h" +#include "submodule-config.h" +#include "advice.h" /* Mask for the name length in ce_flags in the on-disk index */ @@ -470,6 +473,17 @@ int ie_modified(struct index_state *istate, * then we know it is. */ if ((changed & DATA_CHANGED) && +#ifdef GIT_WINDOWS_NATIVE + /* + * Work around Git for Windows v2.27.0 fixing a bug where symlinks' + * target path lengths were not read at all, and instead recorded + * as 4096: now, all symlinks would appear as modified. + * + * So let's just special-case symlinks with a target path length + * (i.e. `sd_size`) of 4096 and force them to be re-checked. + */ + (!S_ISLNK(st->st_mode) || ce->ce_stat_data.sd_size != MAX_PATH) && +#endif (S_ISGITLINK(ce->ce_mode) || ce->ce_stat_data.sd_size != 0)) return changed; @@ -706,7 +720,6 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, int add_option = (ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE| (intent_only ? ADD_CACHE_NEW_ONLY : 0)); unsigned hash_flags = pretend ? 0 : INDEX_WRITE_OBJECT; - struct object_id oid; if (flags & ADD_CACHE_RENORMALIZE) hash_flags |= INDEX_RENORMALIZE; @@ -716,8 +729,6 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, namelen = strlen(path); if (S_ISDIR(st_mode)) { - if (repo_resolve_gitlink_ref(the_repository, path, "HEAD", &oid) < 0) - return error(_("'%s' does not have a commit checked out"), path); while (namelen && path[namelen-1] == '/') namelen--; } @@ -3810,7 +3821,7 @@ void overlay_tree_on_index(struct index_state *istate, if (repo_get_oid(the_repository, tree_name, &oid)) die("tree-ish %s not found.", tree_name); - tree = parse_tree_indirect(&oid); + tree = repo_parse_tree_indirect(the_repository, &oid); if (!tree) die("bad tree-ish %s", tree_name); @@ -3881,9 +3892,12 @@ void overlay_tree_on_index(struct index_state *istate, struct update_callback_data { struct index_state *index; + struct repository *repo; + struct pathspec *pathspec; int include_sparse; int flags; int add_errors; + int ignored_too; }; static int fix_unmerged_status(struct diff_filepair *p, @@ -3907,8 +3921,68 @@ static int fix_unmerged_status(struct diff_filepair *p, return DIFF_STATUS_MODIFIED; } +static int skip_submodule(const char *path, + struct repository *repo, + struct pathspec *pathspec, + int ignored_too) +{ + struct stat st; + const struct submodule *sub; + int pathspec_matches = 0; + int ps_i; + char *norm_pathspec = NULL; + + /* Only consider if path is a directory */ + if (lstat(path, &st) || !S_ISDIR(st.st_mode)) + return 0; + + /* Check if it's a submodule with ignore=all */ + sub = submodule_from_path(repo, null_oid(the_hash_algo), path); + if (!sub || !sub->name || !sub->ignore || strcmp(sub->ignore, "all")) + return 0; + + trace_printf("ignore=all: %s\n", path); + trace_printf("pathspec %s\n", (pathspec && pathspec->nr) + ? "has pathspec" + : "no pathspec"); + + /* Check if submodule path is explicitly mentioned in pathspec */ + if (pathspec) { + for (ps_i = 0; ps_i < pathspec->nr; ps_i++) { + const char *m = pathspec->items[ps_i].match; + if (!m) + continue; + norm_pathspec = xstrdup(m); + strip_dir_trailing_slashes(norm_pathspec); + if (!strcmp(path, norm_pathspec)) { + pathspec_matches = 1; + FREE_AND_NULL(norm_pathspec); + break; + } + FREE_AND_NULL(norm_pathspec); + } + } + + /* If explicitly matched and forced, allow adding */ + if (pathspec_matches) { + if (ignored_too && ignored_too > 0) { + trace_printf("Add submodule due to --force: %s\n", path); + return 0; + } else { + advise_if_enabled(ADVICE_ADD_IGNORED_FILE, + _("Skipping submodule due to ignore=all: %s\n" + "Use --force if you really want to add the submodule."), path); + return 1; + } + } + + /* No explicit pathspec match -> skip silently */ + trace_printf("Pathspec to submodule does not match explicitly: %s\n", path); + return 1; +} + static void update_callback(struct diff_queue_struct *q, - struct diff_options *opt UNUSED, void *cbdata) + struct diff_options *opt UNUSED, void *cbdata) { int i; struct update_callback_data *data = cbdata; @@ -3918,7 +3992,7 @@ static void update_callback(struct diff_queue_struct *q, const char *path = p->one->path; if (!data->include_sparse && - !path_in_sparse_checkout(path, data->index)) + !path_in_sparse_checkout(path, data->index)) continue; switch (fix_unmerged_status(p, data)) { @@ -3926,6 +4000,11 @@ static void update_callback(struct diff_queue_struct *q, die(_("unexpected diff status %c"), p->status); case DIFF_STATUS_MODIFIED: case DIFF_STATUS_TYPE_CHANGED: + if (skip_submodule(path, data->repo, + data->pathspec, + data->ignored_too)) + continue; + if (add_file_to_index(data->index, path, data->flags)) { if (!(data->flags & ADD_CACHE_IGNORE_ERRORS)) die(_("updating files failed")); @@ -3946,7 +4025,7 @@ static void update_callback(struct diff_queue_struct *q, int add_files_to_cache(struct repository *repo, const char *prefix, const struct pathspec *pathspec, char *ps_matched, - int include_sparse, int flags) + int include_sparse, int flags, int ignored_too ) { struct odb_transaction *transaction; struct update_callback_data data; @@ -3956,6 +4035,9 @@ int add_files_to_cache(struct repository *repo, const char *prefix, data.index = repo->index; data.include_sparse = include_sparse; data.flags = flags; + data.repo = repo; + data.ignored_too = ignored_too; + data.pathspec = (struct pathspec *)pathspec; repo_init_revisions(repo, &rev, prefix); setup_revisions(0, NULL, &rev, NULL);
diff --git a/ref-filter.c b/ref-filter.c index 30cc488..1da4c0e 100644 --- a/ref-filter.c +++ b/ref-filter.c
@@ -91,6 +91,7 @@ static struct expand_data { struct object_id delta_base_oid; void *content; + struct object *maybe_object; struct object_info info; } oi, oi_deref; @@ -1475,11 +1476,29 @@ static void grab_common_values(struct atom_value *val, int deref, struct expand_ } } -/* See grab_values */ -static void grab_tag_values(struct atom_value *val, int deref, struct object *obj) +static struct object *get_or_parse_object(struct expand_data *data, const char *refname, + struct strbuf *err, int *eaten) { + if (!data->maybe_object) { + data->maybe_object = parse_object_buffer(the_repository, &data->oid, data->type, + data->size, data->content, eaten); + if (!data->maybe_object) { + strbuf_addf(err, _("parse_object_buffer failed on %s for %s"), + oid_to_hex(&data->oid), refname); + return NULL; + } + } + + return data->maybe_object; +} + +/* See grab_values */ +static int grab_tag_values(struct atom_value *val, int deref, + struct expand_data *data, const char *refname, + struct strbuf *err, int *eaten) +{ + struct tag *tag = NULL; int i; - struct tag *tag = (struct tag *) obj; for (i = 0; i < used_atom_cnt; i++) { const char *name = used_atom[i].name; @@ -1487,6 +1506,14 @@ static void grab_tag_values(struct atom_value *val, int deref, struct object *ob struct atom_value *v = &val[i]; if (!!deref != (*name == '*')) continue; + + if (!tag) { + tag = (struct tag *) get_or_parse_object(data, refname, + err, eaten); + if (!tag) + return -1; + } + if (deref) name++; if (atom_type == ATOM_TAG) @@ -1496,22 +1523,35 @@ static void grab_tag_values(struct atom_value *val, int deref, struct object *ob else if (atom_type == ATOM_OBJECT && tag->tagged) v->s = xstrdup(oid_to_hex(&tag->tagged->oid)); } + + return 0; } /* See grab_values */ -static void grab_commit_values(struct atom_value *val, int deref, struct object *obj) +static int grab_commit_values(struct atom_value *val, int deref, + struct expand_data *data, const char *refname, + struct strbuf *err, int *eaten) { int i; - struct commit *commit = (struct commit *) obj; + struct commit *commit = NULL; for (i = 0; i < used_atom_cnt; i++) { const char *name = used_atom[i].name; enum atom_type atom_type = used_atom[i].atom_type; struct atom_value *v = &val[i]; + if (!!deref != (*name == '*')) continue; if (deref) name++; + + if (!commit) { + commit = (struct commit *) get_or_parse_object(data, refname, + err, eaten); + if (!commit) + return -1; + } + if (atom_type == ATOM_TREE && grab_oid(name, "tree", get_commit_tree_oid(commit), v, &used_atom[i])) continue; @@ -1531,6 +1571,8 @@ static void grab_commit_values(struct atom_value *val, int deref, struct object v->s = strbuf_detach(&s, NULL); } } + + return 0; } static const char *find_wholine(const char *who, int wholen, const char *buf) @@ -1711,7 +1753,7 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void (starts_with(name + wholen, "email") && (atom->u.email_option.option & EO_MAILMAP))) { if (!mailmap.items) - read_mailmap(&mailmap); + read_mailmap(the_repository, &mailmap); strbuf_addstr(&mailmap_buf, buf); apply_mailmap_to_header(&mailmap_buf, headers, &mailmap); wholine = find_wholine(who, wholen, mailmap_buf.buf); @@ -1759,10 +1801,12 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void } } -static void grab_signature(struct atom_value *val, int deref, struct object *obj) +static int grab_signature(struct atom_value *val, int deref, + struct expand_data *data, const char *refname, + struct strbuf *err, int *eaten) { int i; - struct commit *commit = (struct commit *) obj; + struct commit *commit = NULL; struct signature_check sigc = { 0 }; int signature_checked = 0; @@ -1790,6 +1834,13 @@ static void grab_signature(struct atom_value *val, int deref, struct object *obj continue; if (!signature_checked) { + if (!commit) { + commit = (struct commit *) get_or_parse_object(data, refname, + err, eaten); + if (!commit) + return -1; + } + check_commit_signature(commit, &sigc); signature_checked = 1; } @@ -1843,6 +1894,8 @@ static void grab_signature(struct atom_value *val, int deref, struct object *obj if (signature_checked) signature_check_clear(&sigc); + + return 0; } static void find_subpos(const char *buf, @@ -1920,9 +1973,8 @@ static void append_lines(struct strbuf *out, const char *buf, unsigned long size } static void grab_describe_values(struct atom_value *val, int deref, - struct object *obj) + struct expand_data *data) { - struct commit *commit = (struct commit *)obj; int i; for (i = 0; i < used_atom_cnt; i++) { @@ -1944,7 +1996,7 @@ static void grab_describe_values(struct atom_value *val, int deref, cmd.git_cmd = 1; strvec_push(&cmd.args, "describe"); strvec_pushv(&cmd.args, atom->u.describe_args.v); - strvec_push(&cmd.args, oid_to_hex(&commit->object.oid)); + strvec_push(&cmd.args, oid_to_hex(&data->oid)); if (pipe_command(&cmd, NULL, 0, &out, 0, &err, 0) < 0) { error(_("failed to run 'describe'")); v->s = xstrdup(""); @@ -2066,24 +2118,36 @@ static void fill_missing_values(struct atom_value *val) * pointed at by the ref itself; otherwise it is the object the * ref (which is a tag) refers to. */ -static void grab_values(struct atom_value *val, int deref, struct object *obj, struct expand_data *data) +static int grab_values(struct atom_value *val, int deref, struct expand_data *data, + const char *refname, struct strbuf *err, int *eaten) { void *buf = data->content; + int ret; - switch (obj->type) { + switch (data->type) { case OBJ_TAG: - grab_tag_values(val, deref, obj); + ret = grab_tag_values(val, deref, data, refname, err, eaten); + if (ret < 0) + goto out; + grab_sub_body_contents(val, deref, data); grab_person("tagger", val, deref, buf); - grab_describe_values(val, deref, obj); + grab_describe_values(val, deref, data); break; case OBJ_COMMIT: - grab_commit_values(val, deref, obj); + ret = grab_commit_values(val, deref, data, refname, err, eaten); + if (ret < 0) + goto out; + grab_sub_body_contents(val, deref, data); grab_person("author", val, deref, buf); grab_person("committer", val, deref, buf); - grab_signature(val, deref, obj); - grab_describe_values(val, deref, obj); + + ret = grab_signature(val, deref, data, refname, err, eaten); + if (ret < 0) + goto out; + + grab_describe_values(val, deref, data); break; case OBJ_TREE: /* grab_tree_values(val, deref, obj, buf, sz); */ @@ -2094,8 +2158,12 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, s grab_sub_body_contents(val, deref, data); break; default: - die("Eh? Object of type %d?", obj->type); + die("Eh? Object of type %d?", data->type); } + + ret = 0; +out: + return ret; } static inline char *copy_advance(char *dst, const char *src) @@ -2105,32 +2173,34 @@ static inline char *copy_advance(char *dst, const char *src) return dst; } -static const char *lstrip_ref_components(const char *refname, int len) +static int normalize_component_count(const char *refname, int len) { - long remaining = len; - const char *start = xstrdup(refname); - const char *to_free = start; - if (len < 0) { - int i; - const char *p = refname; + int slashes = 0; - /* Find total no of '/' separated path-components */ - for (i = 0; p[i]; p[i] == '/' ? i++ : *p++) - ; + for (const char *p = refname; *p; p++) { + if (*p == '/') + slashes++; + } + /* * The number of components we need to strip is now * the total minus the components to be left (Plus one * because we count the number of '/', but the number * of components is one more than the no of '/'). */ - remaining = i + len + 1; + len = slashes + len + 1; } + return len; +} + +static const char *lstrip_ref_components(const char *refname, int len) +{ + int remaining = normalize_component_count(refname, len); while (remaining > 0) { - switch (*start++) { + switch (*refname++) { case '\0': - free((char *)to_free); return xstrdup(""); case '/': remaining--; @@ -2138,42 +2208,21 @@ static const char *lstrip_ref_components(const char *refname, int len) } } - start = xstrdup(start); - free((char *)to_free); - return start; + return xstrdup(refname); } static const char *rstrip_ref_components(const char *refname, int len) { - long remaining = len; - const char *start = xstrdup(refname); - const char *to_free = start; + int remaining = normalize_component_count(refname, len); + const char *end = refname + strlen(refname); - if (len < 0) { - int i; - const char *p = refname; - - /* Find total no of '/' separated path-components */ - for (i = 0; p[i]; p[i] == '/' ? i++ : *p++) - ; - /* - * The number of components we need to strip is now - * the total minus the components to be left (Plus one - * because we count the number of '/', but the number - * of components is one more than the no of '/'). - */ - remaining = i + len + 1; - } - - while (remaining-- > 0) { - char *p = strrchr(start, '/'); - if (!p) { - free((char *)to_free); + while (remaining > 0) { + if (end == refname) return xstrdup(""); - } else - p[0] = '\0'; + if (*--end == '/') + remaining--; } - return start; + return xmemdupz(refname, end - refname); } static const char *show_ref(struct refname_atom *atom, const char *refname) @@ -2292,38 +2341,43 @@ static const char *get_refname(struct used_atom *atom, struct ref_array_item *re return show_ref(&atom->u.refname, ref->refname); } -static int get_object(struct ref_array_item *ref, int deref, struct object **obj, +static int get_object(struct ref_array_item *ref, int deref, struct expand_data *oi, struct strbuf *err) { - /* parse_object_buffer() will set eaten to 0 if free() will be needed */ - int eaten = 1; + /* parse_object_buffer() will set eaten to 1 if free() will be needed */ + int eaten = 0; + int ret; + + oi->maybe_object = NULL; + if (oi->info.contentp) { /* We need to know that to use parse_object_buffer properly */ oi->info.sizep = &oi->size; oi->info.typep = &oi->type; } + if (odb_read_object_info_extended(the_repository->objects, &oi->oid, &oi->info, - OBJECT_INFO_LOOKUP_REPLACE)) - return strbuf_addf_ret(err, -1, _("missing object %s for %s"), - oid_to_hex(&oi->oid), ref->refname); + OBJECT_INFO_LOOKUP_REPLACE)) { + ret = strbuf_addf_ret(err, -1, _("missing object %s for %s"), + oid_to_hex(&oi->oid), ref->refname); + goto out; + } if (oi->info.disk_sizep && oi->disk_size < 0) BUG("Object size is less than zero."); if (oi->info.contentp) { - *obj = parse_object_buffer(the_repository, &oi->oid, oi->type, oi->size, oi->content, &eaten); - if (!*obj) { - if (!eaten) - free(oi->content); - return strbuf_addf_ret(err, -1, _("parse_object_buffer failed on %s for %s"), - oid_to_hex(&oi->oid), ref->refname); - } - grab_values(ref->value, deref, *obj, oi); + ret = grab_values(ref->value, deref, oi, ref->refname, err, &eaten); + if (ret < 0) + goto out; } grab_common_values(ref->value, deref, oi); + ret = 0; + +out: if (!eaten) free(oi->content); - return 0; + return ret; } static void populate_worktree_map(struct hashmap *map, struct worktree **worktrees) @@ -2376,7 +2430,6 @@ static char *get_worktree_path(const struct ref_array_item *ref) */ static int populate_value(struct ref_array_item *ref, struct strbuf *err) { - struct object *obj; int i; struct object_info empty = OBJECT_INFO_INIT; int ahead_behind_atoms = 0; @@ -2564,24 +2617,32 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err) oi.oid = ref->objectname; - if (get_object(ref, 0, &obj, &oi, err)) + if (get_object(ref, 0, &oi, err)) return -1; /* * If there is no atom that wants to know about tagged * object, we are done. */ - if (!need_tagged || (obj->type != OBJ_TAG)) + if (!need_tagged || (oi.type != OBJ_TAG)) return 0; /* * If it is a tag object, see if we use the peeled value. If we do, * grab the peeled OID. */ - if (need_tagged && peel_iterated_oid(the_repository, &obj->oid, &oi_deref.oid)) - die("bad tag"); + if (need_tagged) { + if (!is_null_oid(&ref->peeled_oid)) { + oidcpy(&oi_deref.oid, &ref->peeled_oid); + } else if (!peel_object(the_repository, &oi.oid, &oi_deref.oid, + PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE)) { + /* We managed to peel the object ourselves. */ + } else { + die("bad tag"); + } + } - return get_object(ref, 1, &obj, &oi_deref, err); + return get_object(ref, 1, &oi_deref, err); } /* @@ -2701,7 +2762,7 @@ static int start_ref_iterator_after(struct ref_iterator *iter, const char *marke return ret; } -static int for_each_fullref_with_seek(struct ref_filter *filter, each_ref_fn cb, +static int for_each_fullref_with_seek(struct ref_filter *filter, refs_for_each_cb cb, void *cb_data, unsigned int flags) { struct ref_iterator *iter; @@ -2724,13 +2785,17 @@ static int for_each_fullref_with_seek(struct ref_filter *filter, each_ref_fn cb, * pattern match, so the callback still has to match each ref individually. */ static int for_each_fullref_in_pattern(struct ref_filter *filter, - each_ref_fn cb, + refs_for_each_cb cb, void *cb_data) { + struct refs_for_each_ref_options opts = { + .exclude_patterns = filter->exclude.v, + }; + if (filter->kind & FILTER_REFS_ROOT_REFS) { /* In this case, we want to print all refs including root refs. */ return for_each_fullref_with_seek(filter, cb, cb_data, - DO_FOR_EACH_INCLUDE_ROOT_REFS); + REFS_FOR_EACH_INCLUDE_ROOT_REFS); } if (!filter->match_as_path) { @@ -2756,10 +2821,9 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter, return for_each_fullref_with_seek(filter, cb, cb_data, 0); } - return refs_for_each_fullref_in_prefixes(get_main_ref_store(the_repository), - NULL, filter->name_patterns, - filter->exclude.v, - cb, cb_data); + return refs_for_each_ref_in_prefixes(get_main_ref_store(the_repository), + filter->name_patterns, &opts, + cb, cb_data); } /* @@ -2786,7 +2850,7 @@ static int match_points_at(struct oid_array *points_at, while (obj && obj->type == OBJ_TAG) { struct tag *tag = (struct tag *)obj; - if (parse_tag(tag) < 0) { + if (parse_tag(the_repository, tag) < 0) { obj = NULL; break; } @@ -2807,12 +2871,15 @@ static int match_points_at(struct oid_array *points_at, * Callers can then fill in other struct members at their leisure. */ static struct ref_array_item *new_ref_array_item(const char *refname, - const struct object_id *oid) + const struct object_id *oid, + const struct object_id *peeled_oid) { struct ref_array_item *ref; FLEX_ALLOC_STR(ref, refname, refname); oidcpy(&ref->objectname, oid); + if (peeled_oid) + oidcpy(&ref->peeled_oid, peeled_oid); ref->rest = NULL; return ref; @@ -2826,9 +2893,10 @@ static void ref_array_append(struct ref_array *array, struct ref_array_item *ref struct ref_array_item *ref_array_push(struct ref_array *array, const char *refname, - const struct object_id *oid) + const struct object_id *oid, + const struct object_id *peeled_oid) { - struct ref_array_item *ref = new_ref_array_item(refname, oid); + struct ref_array_item *ref = new_ref_array_item(refname, oid, peeled_oid); ref_array_append(array, ref); return ref; } @@ -2871,25 +2939,25 @@ static int filter_ref_kind(struct ref_filter *filter, const char *refname) return ref_kind_from_refname(refname); } -static struct ref_array_item *apply_ref_filter(const char *refname, const char *referent, const struct object_id *oid, - int flag, struct ref_filter *filter) +static struct ref_array_item *apply_ref_filter(const struct reference *ref, + struct ref_filter *filter) { - struct ref_array_item *ref; + struct ref_array_item *item; struct commit *commit = NULL; unsigned int kind; - if (flag & REF_BAD_NAME) { - warning(_("ignoring ref with broken name %s"), refname); + if (ref->flags & REF_BAD_NAME) { + warning(_("ignoring ref with broken name %s"), ref->name); return NULL; } - if (flag & REF_ISBROKEN) { - warning(_("ignoring broken ref %s"), refname); + if (ref->flags & REF_ISBROKEN) { + warning(_("ignoring broken ref %s"), ref->name); return NULL; } /* Obtain the current ref kind from filter_ref_kind() and ignore unwanted refs. */ - kind = filter_ref_kind(filter, refname); + kind = filter_ref_kind(filter, ref->name); /* * Generally HEAD refs are printed with special description denoting a rebase, @@ -2902,13 +2970,13 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const char * else if (!(kind & filter->kind)) return NULL; - if (!filter_pattern_match(filter, refname)) + if (!filter_pattern_match(filter, ref->name)) return NULL; - if (filter_exclude_match(filter, refname)) + if (filter_exclude_match(filter, ref->name)) return NULL; - if (filter->points_at.nr && !match_points_at(&filter->points_at, oid, refname)) + if (filter->points_at.nr && !match_points_at(&filter->points_at, ref->oid, ref->name)) return NULL; /* @@ -2918,7 +2986,7 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const char * */ if (filter->reachable_from || filter->unreachable_from || filter->with_commit || filter->no_commit || filter->verbose) { - commit = lookup_commit_reference_gently(the_repository, oid, 1); + commit = lookup_commit_reference_gently(the_repository, ref->oid, 1); if (!commit) return NULL; /* We perform the filtering for the '--contains' option... */ @@ -2936,13 +3004,13 @@ static struct ref_array_item *apply_ref_filter(const char *refname, const char * * to do its job and the resulting list may yet to be pruned * by maxcount logic. */ - ref = new_ref_array_item(refname, oid); - ref->commit = commit; - ref->flag = flag; - ref->kind = kind; - ref->symref = xstrdup_or_null(referent); + item = new_ref_array_item(ref->name, ref->oid, ref->peeled_oid); + item->commit = commit; + item->flag = ref->flags; + item->kind = kind; + item->symref = xstrdup_or_null(ref->target); - return ref; + return item; } struct ref_filter_cbdata { @@ -2954,14 +3022,14 @@ struct ref_filter_cbdata { * A call-back given to for_each_ref(). Filter refs and keep them for * later object processing. */ -static int filter_one(const char *refname, const char *referent, const struct object_id *oid, int flag, void *cb_data) +static int filter_one(const struct reference *ref, void *cb_data) { struct ref_filter_cbdata *ref_cbdata = cb_data; - struct ref_array_item *ref; + struct ref_array_item *item; - ref = apply_ref_filter(refname, referent, oid, flag, ref_cbdata->filter); - if (ref) - ref_array_append(ref_cbdata->array, ref); + item = apply_ref_filter(ref, ref_cbdata->filter); + if (item) + ref_array_append(ref_cbdata->array, item); return 0; } @@ -2990,17 +3058,17 @@ struct ref_filter_and_format_cbdata { } internal; }; -static int filter_and_format_one(const char *refname, const char *referent, const struct object_id *oid, int flag, void *cb_data) +static int filter_and_format_one(const struct reference *ref, void *cb_data) { struct ref_filter_and_format_cbdata *ref_cbdata = cb_data; - struct ref_array_item *ref; + struct ref_array_item *item; struct strbuf output = STRBUF_INIT, err = STRBUF_INIT; - ref = apply_ref_filter(refname, referent, oid, flag, ref_cbdata->filter); - if (!ref) + item = apply_ref_filter(ref, ref_cbdata->filter); + if (!item) return 0; - if (format_ref_array_item(ref, ref_cbdata->format, &output, &err)) + if (format_ref_array_item(item, ref_cbdata->format, &output, &err)) die("%s", err.buf); if (output.len || !ref_cbdata->format->array_opts.omit_empty) { @@ -3010,7 +3078,7 @@ static int filter_and_format_one(const char *refname, const char *referent, cons strbuf_release(&output); strbuf_release(&err); - free_array_item(ref); + free_array_item(item); /* * Increment the running count of refs that match the filter. If @@ -3219,7 +3287,7 @@ void filter_is_base(struct repository *r, free(bases); } -static int do_filter_refs(struct ref_filter *filter, unsigned int type, each_ref_fn fn, void *cb_data) +static int do_filter_refs(struct ref_filter *filter, unsigned int type, refs_for_each_cb fn, void *cb_data) { const char *prefix = NULL; int ret = 0; @@ -3583,13 +3651,14 @@ void print_formatted_ref_array(struct ref_array *array, struct ref_format *forma } void pretty_print_ref(const char *name, const struct object_id *oid, + const struct object_id *peeled_oid, struct ref_format *format) { struct ref_array_item *ref_item; struct strbuf output = STRBUF_INIT; struct strbuf err = STRBUF_INIT; - ref_item = new_ref_array_item(name, oid); + ref_item = new_ref_array_item(name, oid, peeled_oid); ref_item->kind = ref_kind_from_refname(name); if (format_ref_array_item(ref_item, format, &output, &err)) die("%s", err.buf); @@ -3697,9 +3766,9 @@ void ref_filter_clear(struct ref_filter *filter) { strvec_clear(&filter->exclude); oid_array_clear(&filter->points_at); - free_commit_list(filter->with_commit); - free_commit_list(filter->no_commit); - free_commit_list(filter->reachable_from); - free_commit_list(filter->unreachable_from); + commit_list_free(filter->with_commit); + commit_list_free(filter->no_commit); + commit_list_free(filter->reachable_from); + commit_list_free(filter->unreachable_from); ref_filter_init(filter); }
diff --git a/ref-filter.h b/ref-filter.h index 235c60f..120221b 100644 --- a/ref-filter.h +++ b/ref-filter.h
@@ -41,6 +41,7 @@ enum ref_sorting_order { struct ref_array_item { struct object_id objectname; + struct object_id peeled_oid; const char *rest; int flag; unsigned int kind; @@ -187,6 +188,7 @@ void print_formatted_ref_array(struct ref_array *array, struct ref_format *forma * name must be a fully qualified refname. */ void pretty_print_ref(const char *name, const struct object_id *oid, + const struct object_id *peeled_oid, struct ref_format *format); /* @@ -195,7 +197,8 @@ void pretty_print_ref(const char *name, const struct object_id *oid, */ struct ref_array_item *ref_array_push(struct ref_array *array, const char *refname, - const struct object_id *oid); + const struct object_id *oid, + const struct object_id *peeled_oid); /* * If the provided format includes ahead-behind atoms, then compute the
diff --git a/reflog-walk.c b/reflog-walk.c index 4f1ce04..4dbeaa9 100644 --- a/reflog-walk.c +++ b/reflog-walk.c
@@ -157,7 +157,8 @@ int add_reflog_for_walk(struct reflog_walk_info *info, int recno = -1; struct string_list_item *item; struct complete_reflogs *reflogs; - char *branch, *at = strchr(name, '@'); + char *branch; + const char *at = strchr(name, '@'); struct commit_reflog *commit_reflog; enum selector_type selector = SELECTOR_NONE;
diff --git a/reflog.c b/reflog.c index 65ef259..1460ae9 100644 --- a/reflog.c +++ b/reflog.c
@@ -423,16 +423,13 @@ int should_expire_reflog_ent_verbose(struct object_id *ooid, return expire; } -static int push_tip_to_list(const char *refname UNUSED, - const char *referent UNUSED, - const struct object_id *oid, - int flags, void *cb_data) +static int push_tip_to_list(const struct reference *ref, void *cb_data) { struct commit_list **list = cb_data; struct commit *tip_commit; - if (flags & REF_ISSYMREF) + if (ref->flags & REF_ISSYMREF) return 0; - tip_commit = lookup_commit_reference_gently(the_repository, oid, 1); + tip_commit = lookup_commit_reference_gently(the_repository, ref->oid, 1); if (!tip_commit) return 0; commit_list_insert(tip_commit, list); @@ -496,7 +493,7 @@ void reflog_expiry_cleanup(void *cb_data) case UE_HEAD: for (elem = cb->tips; elem; elem = elem->next) clear_commit_marks(elem->item, REACHABLE); - free_commit_list(cb->tips); + commit_list_free(cb->tips); break; case UE_NORMAL: clear_commit_marks(cb->tip_commit, REACHABLE); @@ -504,7 +501,7 @@ void reflog_expiry_cleanup(void *cb_data) } for (elem = cb->mark_list; elem; elem = elem->next) clear_commit_marks(elem->item, REACHABLE); - free_commit_list(cb->mark_list); + commit_list_free(cb->mark_list); } int count_reflog_ent(const char *refname UNUSED,
diff --git a/refs.c b/refs.c index 9653813..5d1d285 100644 --- a/refs.c +++ b/refs.c
@@ -5,6 +5,7 @@ #define USE_THE_REPOSITORY_VARIABLE #include "git-compat-util.h" +#include "abspath.h" #include "advice.h" #include "config.h" #include "environment.h" @@ -15,7 +16,6 @@ #include "iterator.h" #include "refs.h" #include "refs/refs-internal.h" -#include "run-command.h" #include "hook.h" #include "object-name.h" #include "odb.h" @@ -26,7 +26,6 @@ #include "strvec.h" #include "repo-settings.h" #include "setup.h" -#include "sigchain.h" #include "date.h" #include "commit.h" #include "wildmatch.h" @@ -65,6 +64,9 @@ const char *ref_storage_format_to_name(enum ref_storage_format ref_storage_forma return be->name; } +static const char *abort_by_ref_transaction_hook = + N_("in '%s' phase, update aborted by the reference-transaction hook"); + /* * How to handle various characters in refnames: * 0: An acceptable character for refs @@ -320,6 +322,49 @@ int check_refname_format(const char *refname, int flags) return check_or_sanitize_refname(refname, flags, NULL); } +int refs_fsck_ref(struct ref_store *refs UNUSED, struct fsck_options *o, + struct fsck_ref_report *report, + const char *refname UNUSED, const struct object_id *oid) +{ + if (is_null_oid(oid)) + return fsck_report_ref(o, report, FSCK_MSG_BAD_REF_OID, + "points to invalid object ID '%s'", + oid_to_hex(oid)); + + return 0; +} + +int refs_fsck_symref(struct ref_store *refs UNUSED, struct fsck_options *o, + struct fsck_ref_report *report, + const char *refname, const char *target) +{ + const char *stripped_refname; + + parse_worktree_ref(refname, NULL, NULL, &stripped_refname); + + if (!strcmp(stripped_refname, "HEAD") && + !starts_with(target, "refs/heads/") && + fsck_report_ref(o, report, FSCK_MSG_BAD_HEAD_TARGET, + "HEAD points to non-branch '%s'", target)) + return -1; + + if (is_root_ref(target)) + return 0; + + if (check_refname_format(target, 0) && + fsck_report_ref(o, report, FSCK_MSG_BAD_REFERENT_NAME, + "points to invalid refname '%s'", target)) + return -1; + + if (!starts_with(target, "refs/") && + !starts_with(target, "worktrees/") && + fsck_report_ref(o, report, FSCK_MSG_SYMREF_TARGET_IS_NOT_A_REF, + "points to non-ref target '%s'", target)) + return -1; + + return 0; +} + int refs_fsck(struct ref_store *refs, struct fsck_options *o, struct worktree *wt) { @@ -401,8 +446,8 @@ char *refs_resolve_refdup(struct ref_store *refs, /* The argument to for_each_filter_refs */ struct for_each_ref_filter { const char *pattern; - const char *prefix; - each_ref_fn *fn; + size_t trim_prefix; + refs_for_each_cb *fn; void *cb_data; }; @@ -426,17 +471,21 @@ int refs_ref_exists(struct ref_store *refs, const char *refname) NULL, NULL); } -static int for_each_filter_refs(const char *refname, const char *referent, - const struct object_id *oid, - int flags, void *data) +static int for_each_filter_refs(const struct reference *ref, void *data) { struct for_each_ref_filter *filter = data; - if (wildmatch(filter->pattern, refname, 0)) + if (wildmatch(filter->pattern, ref->name, 0)) return 0; - if (filter->prefix) - skip_prefix(refname, filter->prefix, &refname); - return filter->fn(refname, referent, oid, flags, filter->cb_data); + if (filter->trim_prefix) { + struct reference skipped = *ref; + if (strlen(skipped.name) <= filter->trim_prefix) + BUG("attempt to trim too many characters"); + skipped.name += filter->trim_prefix; + return filter->fn(&skipped, filter->cb_data); + } else { + return filter->fn(ref, filter->cb_data); + } } struct warn_if_dangling_data { @@ -447,17 +496,15 @@ struct warn_if_dangling_data { int dry_run; }; -static int warn_if_dangling_symref(const char *refname, const char *referent UNUSED, - const struct object_id *oid UNUSED, - int flags, void *cb_data) +static int warn_if_dangling_symref(const struct reference *ref, void *cb_data) { struct warn_if_dangling_data *d = cb_data; const char *resolves_to, *msg; - if (!(flags & REF_ISSYMREF)) + if (!(ref->flags & REF_ISSYMREF)) return 0; - resolves_to = refs_resolve_ref_unsafe(d->refs, refname, 0, NULL, NULL); + resolves_to = refs_resolve_ref_unsafe(d->refs, ref->name, 0, NULL, NULL); if (!resolves_to || !string_list_has_string(d->refnames, resolves_to)) { return 0; @@ -466,7 +513,7 @@ static int warn_if_dangling_symref(const char *refname, const char *referent UNU msg = d->dry_run ? _("%s%s will become dangling after %s is deleted\n") : _("%s%s has become dangling after %s was deleted\n"); - fprintf(d->fp, msg, d->indent, refname, resolves_to); + fprintf(d->fp, msg, d->indent, ref->name, resolves_to); return 0; } @@ -481,25 +528,40 @@ void refs_warn_dangling_symrefs(struct ref_store *refs, FILE *fp, .indent = indent, .dry_run = dry_run, }; - refs_for_each_rawref(refs, warn_if_dangling_symref, &data); + struct refs_for_each_ref_options opts = { + .flags = REFS_FOR_EACH_INCLUDE_BROKEN, + }; + refs_for_each_ref_ext(refs, warn_if_dangling_symref, &data, &opts); } -int refs_for_each_tag_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data) +int refs_for_each_tag_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data) { - return refs_for_each_ref_in(refs, "refs/tags/", fn, cb_data); + struct refs_for_each_ref_options opts = { + .prefix = "refs/tags/", + .trim_prefix = strlen("refs/tags/"), + }; + return refs_for_each_ref_ext(refs, cb, cb_data, &opts); } -int refs_for_each_branch_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data) +int refs_for_each_branch_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data) { - return refs_for_each_ref_in(refs, "refs/heads/", fn, cb_data); + struct refs_for_each_ref_options opts = { + .prefix = "refs/heads/", + .trim_prefix = strlen("refs/heads/"), + }; + return refs_for_each_ref_ext(refs, cb, cb_data, &opts); } -int refs_for_each_remote_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data) +int refs_for_each_remote_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data) { - return refs_for_each_ref_in(refs, "refs/remotes/", fn, cb_data); + struct refs_for_each_ref_options opts = { + .prefix = "refs/remotes/", + .trim_prefix = strlen("refs/remotes/"), + }; + return refs_for_each_ref_ext(refs, cb, cb_data, &opts); } -int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_data) +int refs_head_ref_namespaced(struct ref_store *refs, refs_for_each_cb fn, void *cb_data) { struct strbuf buf = STRBUF_INIT; int ret = 0; @@ -507,8 +569,15 @@ int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_da int flag; strbuf_addf(&buf, "%sHEAD", get_git_namespace()); - if (!refs_read_ref_full(refs, buf.buf, RESOLVE_REF_READING, &oid, &flag)) - ret = fn(buf.buf, NULL, &oid, flag, cb_data); + if (!refs_read_ref_full(refs, buf.buf, RESOLVE_REF_READING, &oid, &flag)) { + struct reference ref = { + .name = buf.buf, + .oid = &oid, + .flags = flag, + }; + + ret = fn(&ref, cb_data); + } strbuf_release(&buf); return ret; @@ -540,42 +609,6 @@ void normalize_glob_ref(struct string_list_item *item, const char *prefix, strbuf_release(&normalized_pattern); } -int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn, - const char *pattern, const char *prefix, void *cb_data) -{ - struct strbuf real_pattern = STRBUF_INIT; - struct for_each_ref_filter filter; - int ret; - - if (!prefix && !starts_with(pattern, "refs/")) - strbuf_addstr(&real_pattern, "refs/"); - else if (prefix) - strbuf_addstr(&real_pattern, prefix); - strbuf_addstr(&real_pattern, pattern); - - if (!has_glob_specials(pattern)) { - /* Append implied '/' '*' if not present. */ - strbuf_complete(&real_pattern, '/'); - /* No need to check for '*', there is none. */ - strbuf_addch(&real_pattern, '*'); - } - - filter.pattern = real_pattern.buf; - filter.prefix = prefix; - filter.fn = fn; - filter.cb_data = cb_data; - ret = refs_for_each_ref(refs, for_each_filter_refs, &filter); - - strbuf_release(&real_pattern); - return ret; -} - -int refs_for_each_glob_ref(struct ref_store *refs, each_ref_fn fn, - const char *pattern, void *cb_data) -{ - return refs_for_each_glob_ref_in(refs, fn, pattern, NULL, cb_data); -} - const char *prettify_refname(const char *name) { if (skip_prefix(name, "refs/heads/", &name) || @@ -710,7 +743,8 @@ static char *substitute_branch_name(struct repository *r, return NULL; } -void copy_branchname(struct strbuf *sb, const char *name, unsigned allowed) +void copy_branchname(struct strbuf *sb, const char *name, + enum interpret_branch_kind allowed) { int len = strlen(name); struct interpret_branch_name_options options = { @@ -1217,6 +1251,7 @@ void ref_transaction_free(struct ref_transaction *transaction) free(transaction->updates[i]->committer_info); free((char *)transaction->updates[i]->new_target); free((char *)transaction->updates[i]->old_target); + free((char *)transaction->updates[i]->rejection_details); free(transaction->updates[i]); } @@ -1231,7 +1266,8 @@ void ref_transaction_free(struct ref_transaction *transaction) int ref_transaction_maybe_set_rejected(struct ref_transaction *transaction, size_t update_idx, - enum ref_transaction_error err) + enum ref_transaction_error err, + struct strbuf *details) { if (update_idx >= transaction->nr) BUG("trying to set rejection on invalid update index"); @@ -1257,6 +1293,7 @@ int ref_transaction_maybe_set_rejected(struct ref_transaction *transaction, transaction->updates[update_idx]->refname, 0); transaction->updates[update_idx]->rejection_err = err; + transaction->updates[update_idx]->rejection_details = strbuf_detach(details, NULL); ALLOC_GROW(transaction->rejections->update_indices, transaction->rejections->nr + 1, transaction->rejections->alloc); @@ -1735,14 +1772,21 @@ const char *find_descendant_ref(const char *dirname, return NULL; } -int refs_head_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data) +int refs_head_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data) { struct object_id oid; int flag; if (refs_resolve_ref_unsafe(refs, "HEAD", RESOLVE_REF_READING, - &oid, &flag)) - return fn("HEAD", NULL, &oid, flag, cb_data); + &oid, &flag)) { + struct reference ref = { + .name = "HEAD", + .oid = &oid, + .flags = flag, + }; + + return fn(&ref, cb_data); + } return 0; } @@ -1752,7 +1796,7 @@ struct ref_iterator *refs_ref_iterator_begin( const char *prefix, const char **exclude_patterns, int trim, - enum do_for_each_ref_flags flags) + enum refs_for_each_flag flags) { struct ref_iterator *iter; struct strvec normalized_exclude_patterns = STRVEC_INIT; @@ -1774,14 +1818,14 @@ struct ref_iterator *refs_ref_iterator_begin( exclude_patterns = normalized_exclude_patterns.v; } - if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) { + if (!(flags & REFS_FOR_EACH_INCLUDE_BROKEN)) { static int ref_paranoia = -1; if (ref_paranoia < 0) ref_paranoia = git_env_bool("GIT_REF_PARANOIA", 1); if (ref_paranoia) { - flags |= DO_FOR_EACH_INCLUDE_BROKEN; - flags |= DO_FOR_EACH_OMIT_DANGLING_SYMREFS; + flags |= REFS_FOR_EACH_INCLUDE_BROKEN; + flags |= REFS_FOR_EACH_OMIT_DANGLING_SYMREFS; } } @@ -1798,85 +1842,105 @@ struct ref_iterator *refs_ref_iterator_begin( return iter; } -static int do_for_each_ref(struct ref_store *refs, const char *prefix, - const char **exclude_patterns, - each_ref_fn fn, int trim, - enum do_for_each_ref_flags flags, void *cb_data) -{ - struct ref_iterator *iter; - - if (!refs) - return 0; - - iter = refs_ref_iterator_begin(refs, prefix, exclude_patterns, trim, - flags); - - return do_for_each_ref_iterator(iter, fn, cb_data); -} - -int refs_for_each_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data) -{ - return do_for_each_ref(refs, "", NULL, fn, 0, 0, cb_data); -} - -int refs_for_each_ref_in(struct ref_store *refs, const char *prefix, - each_ref_fn fn, void *cb_data) -{ - return do_for_each_ref(refs, prefix, NULL, fn, strlen(prefix), 0, cb_data); -} - -int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix, - const char **exclude_patterns, - each_ref_fn fn, void *cb_data) -{ - return do_for_each_ref(refs, prefix, exclude_patterns, fn, 0, 0, cb_data); -} - -int refs_for_each_replace_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data) -{ - const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref; - return do_for_each_ref(refs, git_replace_ref_base, NULL, fn, - strlen(git_replace_ref_base), - DO_FOR_EACH_INCLUDE_BROKEN, cb_data); -} - -int refs_for_each_namespaced_ref(struct ref_store *refs, - const char **exclude_patterns, - each_ref_fn fn, void *cb_data) +int refs_for_each_ref_ext(struct ref_store *refs, + refs_for_each_cb cb, void *cb_data, + const struct refs_for_each_ref_options *opts) { struct strvec namespaced_exclude_patterns = STRVEC_INIT; - struct strbuf prefix = STRBUF_INIT; + struct strbuf namespaced_prefix = STRBUF_INIT; + struct strbuf real_pattern = STRBUF_INIT; + struct for_each_ref_filter filter; + struct ref_iterator *iter; + size_t trim_prefix = opts->trim_prefix; + const char **exclude_patterns; + const char *prefix; int ret; - exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns, - get_git_namespace(), - &namespaced_exclude_patterns); + if (!refs) + BUG("no ref store passed"); - strbuf_addf(&prefix, "%srefs/", get_git_namespace()); - ret = do_for_each_ref(refs, prefix.buf, exclude_patterns, fn, 0, 0, cb_data); + if (opts->trim_prefix) { + size_t prefix_len; + + if (!opts->prefix) + BUG("trimming only allowed with a prefix"); + + prefix_len = strlen(opts->prefix); + if (prefix_len == opts->trim_prefix && opts->prefix[prefix_len - 1] != '/') + BUG("ref pattern must end in a trailing slash when trimming"); + } + + if (opts->pattern) { + if (!opts->prefix && !starts_with(opts->pattern, "refs/")) + strbuf_addstr(&real_pattern, "refs/"); + else if (opts->prefix) + strbuf_addstr(&real_pattern, opts->prefix); + strbuf_addstr(&real_pattern, opts->pattern); + + if (!has_glob_specials(opts->pattern)) { + /* Append implied '/' '*' if not present. */ + strbuf_complete(&real_pattern, '/'); + /* No need to check for '*', there is none. */ + strbuf_addch(&real_pattern, '*'); + } + + filter.pattern = real_pattern.buf; + filter.trim_prefix = opts->trim_prefix; + filter.fn = cb; + filter.cb_data = cb_data; + + /* + * We need to trim the prefix in the callback function as the + * pattern is expected to match on the full refname. + */ + trim_prefix = 0; + + cb = for_each_filter_refs; + cb_data = &filter; + } + + if (opts->namespace) { + strbuf_addstr(&namespaced_prefix, opts->namespace); + if (opts->prefix) + strbuf_addstr(&namespaced_prefix, opts->prefix); + else + strbuf_addstr(&namespaced_prefix, "refs/"); + + prefix = namespaced_prefix.buf; + exclude_patterns = get_namespaced_exclude_patterns(opts->exclude_patterns, + opts->namespace, + &namespaced_exclude_patterns); + } else { + prefix = opts->prefix ? opts->prefix : ""; + exclude_patterns = opts->exclude_patterns; + } + + iter = refs_ref_iterator_begin(refs, prefix, exclude_patterns, + trim_prefix, opts->flags); + + ret = do_for_each_ref_iterator(iter, cb, cb_data); strvec_clear(&namespaced_exclude_patterns); - strbuf_release(&prefix); + strbuf_release(&namespaced_prefix); + strbuf_release(&real_pattern); return ret; } -int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data) +int refs_for_each_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data) { - return refs_for_each_rawref_in(refs, "", fn, cb_data); + struct refs_for_each_ref_options opts = { 0 }; + return refs_for_each_ref_ext(refs, cb, cb_data, &opts); } -int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix, - each_ref_fn fn, void *cb_data) +int refs_for_each_replace_ref(struct ref_store *refs, refs_for_each_cb cb, void *cb_data) { - return do_for_each_ref(refs, prefix, NULL, fn, 0, - DO_FOR_EACH_INCLUDE_BROKEN, cb_data); -} - -int refs_for_each_include_root_refs(struct ref_store *refs, each_ref_fn fn, - void *cb_data) -{ - return do_for_each_ref(refs, "", NULL, fn, 0, - DO_FOR_EACH_INCLUDE_ROOT_REFS, cb_data); + const char *git_replace_ref_base = ref_namespace[NAMESPACE_REPLACE].ref; + struct refs_for_each_ref_options opts = { + .prefix = git_replace_ref_base, + .trim_prefix = strlen(git_replace_ref_base), + .flags = REFS_FOR_EACH_INCLUDE_BROKEN, + }; + return refs_for_each_ref_ext(refs, cb, cb_data, &opts); } static int qsort_strcmp(const void *va, const void *vb) @@ -1937,40 +2001,31 @@ static void find_longest_prefixes(struct string_list *out, strbuf_release(&prefix); } -int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store, - const char *namespace, - const char **patterns, - const char **exclude_patterns, - each_ref_fn fn, void *cb_data) +int refs_for_each_ref_in_prefixes(struct ref_store *ref_store, + const char **prefixes, + const struct refs_for_each_ref_options *opts, + refs_for_each_cb cb, void *cb_data) { - struct strvec namespaced_exclude_patterns = STRVEC_INIT; - struct string_list prefixes = STRING_LIST_INIT_DUP; + struct string_list longest_prefixes = STRING_LIST_INIT_DUP; struct string_list_item *prefix; - struct strbuf buf = STRBUF_INIT; - int ret = 0, namespace_len; + int ret = 0; - find_longest_prefixes(&prefixes, patterns); + if (opts->prefix) + BUG("refs_for_each_ref_in_prefixes called with specific prefix"); - if (namespace) - strbuf_addstr(&buf, namespace); - namespace_len = buf.len; + find_longest_prefixes(&longest_prefixes, prefixes); - exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns, - namespace, - &namespaced_exclude_patterns); + for_each_string_list_item(prefix, &longest_prefixes) { + struct refs_for_each_ref_options prefix_opts = *opts; + prefix_opts.prefix = prefix->string; - for_each_string_list_item(prefix, &prefixes) { - strbuf_addstr(&buf, prefix->string); - ret = refs_for_each_fullref_in(ref_store, buf.buf, - exclude_patterns, fn, cb_data); + ret = refs_for_each_ref_ext(ref_store, cb, cb_data, + &prefix_opts); if (ret) break; - strbuf_setlen(&buf, namespace_len); } - strvec_clear(&namespaced_exclude_patterns); - string_list_clear(&prefixes, 0); - strbuf_release(&buf); + string_list_clear(&longest_prefixes, 0); return ret; } @@ -2106,15 +2161,93 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs, return NULL; } +void refs_create_refdir_stubs(struct repository *repo, const char *refdir, + const char *refs_heads_content) +{ + struct strbuf path = STRBUF_INIT; + + strbuf_addf(&path, "%s/HEAD", refdir); + write_file(path.buf, "ref: refs/heads/.invalid"); + adjust_shared_perm(repo, path.buf); + + strbuf_reset(&path); + strbuf_addf(&path, "%s/refs", refdir); + safe_create_dir(repo, path.buf, 1); + + if (refs_heads_content) { + strbuf_reset(&path); + strbuf_addf(&path, "%s/refs/heads", refdir); + write_file(path.buf, "%s", refs_heads_content); + adjust_shared_perm(repo, path.buf); + } + + strbuf_release(&path); +} + /* backend functions */ int ref_store_create_on_disk(struct ref_store *refs, int flags, struct strbuf *err) { - return refs->be->create_on_disk(refs, flags, err); + int ret = refs->be->create_on_disk(refs, flags, err); + + if (!ret) { + /* Creation of stubs for linked worktrees are handled in the worktree code. */ + if (!(flags & REF_STORE_CREATE_ON_DISK_IS_WORKTREE) && refs->repo->ref_storage_payload) { + refs_create_refdir_stubs(refs->repo, refs->repo->gitdir, + "repository uses alternate refs storage"); + } else if (ref_storage_format_by_name(refs->be->name) != REF_STORAGE_FORMAT_FILES) { + struct strbuf msg = STRBUF_INIT; + strbuf_addf(&msg, "this repository uses the %s format", refs->be->name); + refs_create_refdir_stubs(refs->repo, refs->gitdir, msg.buf); + strbuf_release(&msg); + } + } + + return ret; } int ref_store_remove_on_disk(struct ref_store *refs, struct strbuf *err) { - return refs->be->remove_on_disk(refs, err); + int ret = refs->be->remove_on_disk(refs, err); + + if (!ret) { + enum ref_storage_format format = ref_storage_format_by_name(refs->be->name); + struct strbuf sb = STRBUF_INIT; + + /* Backends apart from the files backend create stubs. */ + if (format == REF_STORAGE_FORMAT_FILES) + return ret; + + /* Alternate refs backend require stubs in the gitdir. */ + if (refs->repo->ref_storage_payload) + return ret; + + strbuf_addf(&sb, "%s/HEAD", refs->gitdir); + if (unlink(sb.buf) < 0) { + strbuf_addf(err, "could not delete stub HEAD: %s", + strerror(errno)); + ret = -1; + } + strbuf_reset(&sb); + + strbuf_addf(&sb, "%s/refs/heads", refs->gitdir); + if (unlink(sb.buf) < 0) { + strbuf_addf(err, "could not delete stub heads: %s", + strerror(errno)); + ret = -1; + } + strbuf_reset(&sb); + + strbuf_addf(&sb, "%s/refs", refs->gitdir); + if (rmdir(sb.buf) < 0) { + strbuf_addf(err, "could not delete refs directory: %s", + strerror(errno)); + ret = -1; + } + + strbuf_release(&sb); + } + + return ret; } int repo_resolve_gitlink_ref(struct repository *r, @@ -2167,7 +2300,11 @@ static struct ref_store *ref_store_init(struct repository *repo, if (!be) BUG("reference backend is unknown"); - refs = be->init(repo, gitdir, flags); + /* + * TODO Send in a 'struct worktree' instead of a 'gitdir', and + * allow the backend to handle how it wants to deal with worktrees. + */ + refs = be->init(repo, repo->ref_storage_payload, gitdir, flags); return refs; } @@ -2299,25 +2436,28 @@ void base_ref_store_init(struct ref_store *refs, struct repository *repo, refs->gitdir = xstrdup(path); } -/* backend functions */ -int refs_pack_refs(struct ref_store *refs, struct pack_refs_opts *opts) -{ - return refs->be->pack_refs(refs, opts); -} - -int refs_optimize(struct ref_store *refs, struct pack_refs_opts *opts) +int refs_optimize(struct ref_store *refs, struct refs_optimize_opts *opts) { return refs->be->optimize(refs, opts); } -int peel_iterated_oid(struct repository *r, const struct object_id *base, struct object_id *peeled) +int refs_optimize_required(struct ref_store *refs, + struct refs_optimize_opts *opts, + bool *required) { - if (current_ref_iter && - (current_ref_iter->oid == base || - oideq(current_ref_iter->oid, base))) - return ref_iterator_peel(current_ref_iter, peeled); + return refs->be->optimize_required(refs, opts, required); +} - return peel_object(r, base, peeled) ? -1 : 0; +int reference_get_peeled_oid(struct repository *repo, + const struct reference *ref, + struct object_id *peeled_oid) +{ + if (ref->peeled_oid) { + oidcpy(peeled_oid, ref->peeled_oid); + return 0; + } + + return peel_object(repo, ref->oid, peeled_oid, 0) ? -1 : 0; } int refs_update_symref(struct ref_store *refs, const char *ref, @@ -2405,68 +2545,87 @@ static int ref_update_reject_duplicates(struct string_list *refnames, return 0; } +struct transaction_feed_cb_data { + size_t index; + struct strbuf buf; +}; + +static int transaction_hook_feed_stdin(int hook_stdin_fd, void *pp_cb, void *pp_task_cb) +{ + struct hook_cb_data *hook_cb = pp_cb; + struct ref_transaction *transaction = hook_cb->options->feed_pipe_ctx; + struct transaction_feed_cb_data *feed_cb_data = pp_task_cb; + struct strbuf *buf = &feed_cb_data->buf; + struct ref_update *update; + size_t i = feed_cb_data->index++; + int ret; + + if (i >= transaction->nr) + return 1; /* No more refs to process */ + + update = transaction->updates[i]; + + if (update->flags & REF_LOG_ONLY) + return 0; + + strbuf_reset(buf); + + if (!(update->flags & REF_HAVE_OLD)) + strbuf_addf(buf, "%s ", oid_to_hex(null_oid(the_hash_algo))); + else if (update->old_target) + strbuf_addf(buf, "ref:%s ", update->old_target); + else + strbuf_addf(buf, "%s ", oid_to_hex(&update->old_oid)); + + if (!(update->flags & REF_HAVE_NEW)) + strbuf_addf(buf, "%s ", oid_to_hex(null_oid(the_hash_algo))); + else if (update->new_target) + strbuf_addf(buf, "ref:%s ", update->new_target); + else + strbuf_addf(buf, "%s ", oid_to_hex(&update->new_oid)); + + strbuf_addf(buf, "%s\n", update->refname); + + ret = write_in_full(hook_stdin_fd, buf->buf, buf->len); + if (ret < 0 && errno != EPIPE) + return ret; + + return 0; /* no more input to feed */ +} + +static void *transaction_feed_cb_data_alloc(void *feed_pipe_ctx UNUSED) +{ + struct transaction_feed_cb_data *data; + CALLOC_ARRAY(data, 1); + strbuf_init(&data->buf, 0); + data->index = 0; + return data; +} + +static void transaction_feed_cb_data_free(void *data) +{ + struct transaction_feed_cb_data *d = data; + if (!d) + return; + strbuf_release(&d->buf); + free(d); +} + static int run_transaction_hook(struct ref_transaction *transaction, const char *state) { - struct child_process proc = CHILD_PROCESS_INIT; - struct strbuf buf = STRBUF_INIT; - const char *hook; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; int ret = 0; - hook = find_hook(transaction->ref_store->repo, "reference-transaction"); - if (!hook) - return ret; + strvec_push(&opt.args, state); - strvec_pushl(&proc.args, hook, state, NULL); - proc.in = -1; - proc.stdout_to_stderr = 1; - proc.trace2_hook_name = "reference-transaction"; + opt.feed_pipe = transaction_hook_feed_stdin; + opt.feed_pipe_ctx = transaction; + opt.feed_pipe_cb_data_alloc = transaction_feed_cb_data_alloc; + opt.feed_pipe_cb_data_free = transaction_feed_cb_data_free; - ret = start_command(&proc); - if (ret) - return ret; + ret = run_hooks_opt(transaction->ref_store->repo, "reference-transaction", &opt); - sigchain_push(SIGPIPE, SIG_IGN); - - for (size_t i = 0; i < transaction->nr; i++) { - struct ref_update *update = transaction->updates[i]; - - if (update->flags & REF_LOG_ONLY) - continue; - - strbuf_reset(&buf); - - if (!(update->flags & REF_HAVE_OLD)) - strbuf_addf(&buf, "%s ", oid_to_hex(null_oid(the_hash_algo))); - else if (update->old_target) - strbuf_addf(&buf, "ref:%s ", update->old_target); - else - strbuf_addf(&buf, "%s ", oid_to_hex(&update->old_oid)); - - if (!(update->flags & REF_HAVE_NEW)) - strbuf_addf(&buf, "%s ", oid_to_hex(null_oid(the_hash_algo))); - else if (update->new_target) - strbuf_addf(&buf, "ref:%s ", update->new_target); - else - strbuf_addf(&buf, "%s ", oid_to_hex(&update->new_oid)); - - strbuf_addf(&buf, "%s\n", update->refname); - - if (write_in_full(proc.in, buf.buf, buf.len) < 0) { - if (errno != EPIPE) { - /* Don't leak errno outside this API */ - errno = 0; - ret = -1; - } - break; - } - } - - close(proc.in); - sigchain_pop(SIGPIPE); - strbuf_release(&buf); - - ret |= finish_command(&proc); return ret; } @@ -2491,7 +2650,7 @@ int ref_transaction_prepare(struct ref_transaction *transaction, break; } - if (refs->repo->objects->sources->disable_ref_updates) { + if (refs->repo->disable_ref_updates) { strbuf_addstr(err, _("ref updates forbidden inside quarantine environment")); return -1; @@ -2501,6 +2660,13 @@ int ref_transaction_prepare(struct ref_transaction *transaction, if (ref_update_reject_duplicates(&transaction->refnames, err)) return REF_TRANSACTION_ERROR_GENERIC; + /* Preparing checks before locking references */ + ret = run_transaction_hook(transaction, "preparing"); + if (ret) { + ref_transaction_abort(transaction, err); + die(_(abort_by_ref_transaction_hook), "preparing"); + } + ret = refs->be->transaction_prepare(refs, transaction, err); if (ret) return ret; @@ -2508,7 +2674,7 @@ int ref_transaction_prepare(struct ref_transaction *transaction, ret = run_transaction_hook(transaction, "prepared"); if (ret) { ref_transaction_abort(transaction, err); - die(_("ref updates aborted by hook")); + die(_(abort_by_ref_transaction_hook), "prepared"); } return 0; @@ -2638,30 +2804,33 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs if (!initial_transaction && (strset_contains(&conflicting_dirnames, dirname.buf) || !refs_read_raw_ref(refs, dirname.buf, &oid, &referent, - &type, &ignore_errno))) { - if (transaction && ref_transaction_maybe_set_rejected( - transaction, *update_idx, - REF_TRANSACTION_ERROR_NAME_CONFLICT)) { - strset_remove(&dirnames, dirname.buf); - strset_add(&conflicting_dirnames, dirname.buf); - continue; - } + &type, &ignore_errno))) { strbuf_addf(err, _("'%s' exists; cannot create '%s'"), dirname.buf, refname); + + if (transaction && ref_transaction_maybe_set_rejected( + transaction, *update_idx, + REF_TRANSACTION_ERROR_NAME_CONFLICT, err)) { + strset_remove(&dirnames, dirname.buf); + strset_add(&conflicting_dirnames, dirname.buf); + goto next_ref; + } + goto cleanup; } if (extras && string_list_has_string(extras, dirname.buf)) { - if (transaction && ref_transaction_maybe_set_rejected( - transaction, *update_idx, - REF_TRANSACTION_ERROR_NAME_CONFLICT)) { - strset_remove(&dirnames, dirname.buf); - continue; - } - strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"), refname, dirname.buf); + + if (transaction && ref_transaction_maybe_set_rejected( + transaction, *update_idx, + REF_TRANSACTION_ERROR_NAME_CONFLICT, err)) { + strset_remove(&dirnames, dirname.buf); + goto next_ref; + } + goto cleanup; } } @@ -2682,23 +2851,23 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs if (!iter) iter = refs_ref_iterator_begin(refs, dirname.buf, NULL, 0, - DO_FOR_EACH_INCLUDE_BROKEN); + REFS_FOR_EACH_INCLUDE_BROKEN); else if (ref_iterator_seek(iter, dirname.buf, REF_ITERATOR_SEEK_SET_PREFIX) < 0) goto cleanup; while ((ok = ref_iterator_advance(iter)) == ITER_OK) { if (skip && - string_list_has_string(skip, iter->refname)) + string_list_has_string(skip, iter->ref.name)) continue; + strbuf_addf(err, _("'%s' exists; cannot create '%s'"), + iter->ref.name, refname); if (transaction && ref_transaction_maybe_set_rejected( transaction, *update_idx, - REF_TRANSACTION_ERROR_NAME_CONFLICT)) - continue; + REF_TRANSACTION_ERROR_NAME_CONFLICT, err)) + goto next_ref; - strbuf_addf(err, _("'%s' exists; cannot create '%s'"), - iter->refname, refname); goto cleanup; } @@ -2708,15 +2877,17 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs extra_refname = find_descendant_ref(dirname.buf, extras, skip); if (extra_refname) { - if (transaction && ref_transaction_maybe_set_rejected( - transaction, *update_idx, - REF_TRANSACTION_ERROR_NAME_CONFLICT)) - continue; - strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"), refname, extra_refname); + + if (transaction && ref_transaction_maybe_set_rejected( + transaction, *update_idx, + REF_TRANSACTION_ERROR_NAME_CONFLICT, err)) + goto next_ref; + goto cleanup; } +next_ref:; } ret = 0; @@ -2753,14 +2924,10 @@ struct do_for_each_reflog_help { void *cb_data; }; -static int do_for_each_reflog_helper(const char *refname, - const char *referent UNUSED, - const struct object_id *oid UNUSED, - int flags UNUSED, - void *cb_data) +static int do_for_each_reflog_helper(const struct reference *ref, void *cb_data) { struct do_for_each_reflog_help *hp = cb_data; - return hp->fn(refname, hp->cb_data); + return hp->fn(ref->name, hp->cb_data); } int refs_for_each_reflog(struct ref_store *refs, each_reflog_fn fn, void *cb_data) @@ -2849,7 +3016,7 @@ void ref_transaction_for_each_rejected_update(struct ref_transaction *transactio (update->flags & REF_HAVE_OLD) ? &update->old_oid : NULL, (update->flags & REF_HAVE_NEW) ? &update->new_oid : NULL, update->old_target, update->new_target, - update->rejection_err, cb_data); + update->rejection_err, update->rejection_details, cb_data); } } @@ -2976,25 +3143,24 @@ struct migration_data { uint64_t index; }; -static int migrate_one_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid, - int flags, void *cb_data) +static int migrate_one_ref(const struct reference *ref, void *cb_data) { struct migration_data *data = cb_data; struct strbuf symref_target = STRBUF_INIT; int ret; - if (flags & REF_ISSYMREF) { - ret = refs_read_symbolic_ref(data->old_refs, refname, &symref_target); + if (ref->flags & REF_ISSYMREF) { + ret = refs_read_symbolic_ref(data->old_refs, ref->name, &symref_target); if (ret < 0) goto done; - ret = ref_transaction_update(data->transaction, refname, NULL, null_oid(the_hash_algo), + ret = ref_transaction_update(data->transaction, ref->name, NULL, null_oid(the_hash_algo), symref_target.buf, NULL, REF_SKIP_CREATE_REFLOG | REF_NO_DEREF, NULL, data->errbuf); if (ret < 0) goto done; } else { - ret = ref_transaction_create(data->transaction, refname, oid, NULL, + ret = ref_transaction_create(data->transaction, ref->name, ref->oid, NULL, REF_SKIP_CREATE_REFLOG | REF_SKIP_OID_VERIFICATION, NULL, data->errbuf); if (ret < 0) @@ -3131,6 +3297,9 @@ int repo_migrate_ref_storage_format(struct repository *repo, struct strbuf *errbuf) { struct ref_store *old_refs = NULL, *new_refs = NULL; + struct refs_for_each_ref_options for_each_ref_opts = { + .flags = REFS_FOR_EACH_INCLUDE_ROOT_REFS | REFS_FOR_EACH_INCLUDE_BROKEN, + }; struct ref_transaction *transaction = NULL; struct strbuf new_gitdir = STRBUF_INIT; struct migration_data data = { @@ -3214,7 +3383,7 @@ int repo_migrate_ref_storage_format(struct repository *repo, data.errbuf = errbuf; /* - * We need to use the internal `do_for_each_ref()` here so that we can + * We need to use `refs_for_each_ref_ext()` here so that we can * also include broken refs and symrefs. These would otherwise be * skipped silently. * @@ -3224,9 +3393,7 @@ int repo_migrate_ref_storage_format(struct repository *repo, * allow for a central lock due to its design. It's thus on the user to * ensure that there are no concurrent writes. */ - ret = do_for_each_ref(old_refs, "", NULL, migrate_one_ref, 0, - DO_FOR_EACH_INCLUDE_ROOT_REFS | DO_FOR_EACH_INCLUDE_BROKEN, - &data); + ret = refs_for_each_ref_ext(old_refs, migrate_one_ref, &data, &for_each_ref_opts); if (ret < 0) goto done; @@ -3347,3 +3514,40 @@ const char *ref_transaction_error_msg(enum ref_transaction_error err) return "unknown failure"; } } + +void refs_compute_filesystem_location(const char *gitdir, const char *payload, + bool *is_worktree, struct strbuf *refdir, + struct strbuf *ref_common_dir) +{ + struct strbuf sb = STRBUF_INIT; + + *is_worktree = get_common_dir_noenv(ref_common_dir, gitdir); + + if (!payload) { + /* + * We can use the 'gitdir' as the 'refdir' without appending the + * worktree path, as the 'gitdir' here is already the worktree + * path and is different from 'commondir' denoted by 'ref_common_dir'. + */ + strbuf_addstr(refdir, gitdir); + return; + } + + if (!is_absolute_path(payload)) { + strbuf_addf(&sb, "%s/%s", ref_common_dir->buf, payload); + strbuf_realpath(ref_common_dir, sb.buf, 1); + } else { + strbuf_realpath(ref_common_dir, payload, 1); + } + + strbuf_addbuf(refdir, ref_common_dir); + + if (*is_worktree) { + const char *wt_id = strrchr(gitdir, '/'); + if (!wt_id) + BUG("worktree path does not contain slash"); + strbuf_addf(refdir, "/worktrees/%s", wt_id + 1); + } + + strbuf_release(&sb); +}
diff --git a/refs.h b/refs.h index 4e6bd63..d65de6a 100644 --- a/refs.h +++ b/refs.h
@@ -1,6 +1,7 @@ #ifndef REFS_H #define REFS_H +#include "object-name.h" #include "commit.h" #include "repository.h" #include "repo-settings.h" @@ -170,7 +171,7 @@ int ref_store_remove_on_disk(struct ref_store *refs, struct strbuf *err); * * peel_object(r, oid, &peeled); * - * with the "oid" value given to the each_ref_fn callback, except + * with the "oid" value given to the refs_for_each_cb callback, except * that some ref storage may be able to answer the query without * actually loading the object in memory. */ @@ -225,7 +226,7 @@ char *repo_default_branch_name(struct repository *r, int quiet); * repo_interpret_branch_name() for details. */ void copy_branchname(struct strbuf *sb, const char *name, - unsigned allowed); + enum interpret_branch_kind allowed); /* * Like copy_branchname() above, but confirm that the result is @@ -329,40 +330,115 @@ int check_tag_ref(struct strbuf *sb, const char *name); struct ref_transaction; /* - * Bit values set in the flags argument passed to each_ref_fn() and + * Bit values set in the flags argument passed to refs_for_each_cb() and * stored in ref_iterator::flags. Other bits are for internal use * only: */ +enum reference_status { + /* Reference is a symbolic reference. */ + REF_ISSYMREF = (1 << 0), -/* Reference is a symbolic reference. */ -#define REF_ISSYMREF 0x01 + /* Reference is a packed reference. */ + REF_ISPACKED = (1 << 1), -/* Reference is a packed reference. */ -#define REF_ISPACKED 0x02 + /* + * Reference cannot be resolved to an object name: dangling symbolic + * reference (directly or indirectly), corrupt reference file, + * reference exists but name is bad, or symbolic reference refers to + * ill-formatted reference name. + */ + REF_ISBROKEN = (1 << 2), + + /* + * Reference name is not well formed. + * + * See git-check-ref-format(1) for the definition of well formed ref names. + */ + REF_BAD_NAME = (1 << 3), +}; + +/* A reference passed to `for_each_ref()`-style callbacks. */ +struct reference { + /* The fully-qualified name of the reference. */ + const char *name; + + /* The target of a symbolic ref. `NULL` for direct references. */ + const char *target; + + /* + * The object ID of a reference. Either the direct object ID or the + * resolved object ID in the case of a symbolic ref. May be the zero + * object ID in case the symbolic ref cannot be resolved. + */ + const struct object_id *oid; + + /* + * An optional peeled object ID. This field _may_ be set for tags in + * case the peeled value is present in the backend. Please refer to + * `reference_get_peeled_oid()`. + */ + const struct object_id *peeled_oid; + + /* A bitfield of `enum reference_status` flags. */ + unsigned flags; +}; /* - * Reference cannot be resolved to an object name: dangling symbolic - * reference (directly or indirectly), corrupt reference file, - * reference exists but name is bad, or symbolic reference refers to - * ill-formatted reference name. - */ -#define REF_ISBROKEN 0x04 - -/* - * Reference name is not well formed. + * Peel the tag to a non-tag commit. If present, this uses the peeled object ID + * exposed by the reference backend. Otherwise, the object is peeled via the + * object database, which is less efficient. * - * See git-check-ref-format(1) for the definition of well formed ref names. + * Return `0` if the reference could be peeled, a negative error code + * otherwise. */ -#define REF_BAD_NAME 0x08 +int reference_get_peeled_oid(struct repository *repo, + const struct reference *ref, + struct object_id *peeled_oid); /* * The signature for the callback function for the for_each_*() - * functions below. The memory pointed to by the refname and oid - * arguments is only guaranteed to be valid for the duration of a + * functions below. The memory pointed to by the `struct reference` + * argument is only guaranteed to be valid for the duration of a * single callback invocation. */ -typedef int each_ref_fn(const char *refname, const char *referent, - const struct object_id *oid, int flags, void *cb_data); +typedef int refs_for_each_cb(const struct reference *ref, void *cb_data); + +/* + * These flags are passed to refs_ref_iterator_begin() (and do_for_each_ref(), + * which feeds it). + */ +enum refs_for_each_flag { + /* + * Include broken references in a do_for_each_ref*() iteration, which + * would normally be omitted. This includes both refs that point to + * missing objects (a true repository corruption), ones with illegal + * names (which we prefer not to expose to callers), as well as + * dangling symbolic refs (i.e., those that point to a non-existent + * ref; this is not a corruption, but as they have no valid oid, we + * omit them from normal iteration results). + */ + REFS_FOR_EACH_INCLUDE_BROKEN = (1 << 0), + + /* + * Only include per-worktree refs in a do_for_each_ref*() iteration. + * Normally this will be used with a files ref_store, since that's + * where all reference backends will presumably store their + * per-worktree refs. + */ + REFS_FOR_EACH_PER_WORKTREE_ONLY = (1 << 1), + + /* + * Omit dangling symrefs from output; this only has an effect with + * INCLUDE_BROKEN, since they are otherwise not included at all. + */ + REFS_FOR_EACH_OMIT_DANGLING_SYMREFS = (1 << 2), + + /* + * Include root refs i.e. HEAD and pseudorefs along with the regular + * refs. + */ + REFS_FOR_EACH_INCLUDE_ROOT_REFS = (1 << 3), +}; /* * The following functions invoke the specified callback function for @@ -374,70 +450,75 @@ typedef int each_ref_fn(const char *refname, const char *referent, * stop the iteration. Returned references are sorted. */ int refs_head_ref(struct ref_store *refs, - each_ref_fn fn, void *cb_data); -int refs_for_each_ref(struct ref_store *refs, - each_ref_fn fn, void *cb_data); -int refs_for_each_ref_in(struct ref_store *refs, const char *prefix, - each_ref_fn fn, void *cb_data); -int refs_for_each_tag_ref(struct ref_store *refs, - each_ref_fn fn, void *cb_data); -int refs_for_each_branch_ref(struct ref_store *refs, - each_ref_fn fn, void *cb_data); -int refs_for_each_remote_ref(struct ref_store *refs, - each_ref_fn fn, void *cb_data); -int refs_for_each_replace_ref(struct ref_store *refs, - each_ref_fn fn, void *cb_data); + refs_for_each_cb fn, void *cb_data); +int refs_head_ref_namespaced(struct ref_store *refs, + refs_for_each_cb fn, void *cb_data); -/* - * references matching any pattern in "exclude_patterns" are omitted from the - * result set on a best-effort basis. - */ -int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix, - const char **exclude_patterns, - each_ref_fn fn, void *cb_data); + +struct refs_for_each_ref_options { + /* Only iterate over references that have this given prefix. */ + const char *prefix; + + /* + * A globbing pattern that can be used to only yield refs that match. + * If given, refs will be matched against the pattern with + * `wildmatch()`. + * + * If the pattern doesn't contain any globbing characters then it is + * treated as if it was ending with "/" and "*". + */ + const char *pattern; + + /* + * If set, only yield refs part of the configured namespace. Exclude + * patterns will be rewritten to apply to the namespace, and the prefix + * will be considered relative to the namespace. + */ + const char *namespace; + + /* + * Exclude any references that match any of these patterns on a + * best-effort basis. The caller needs to be prepared for the exclude + * patterns to be ignored. + * + * The array must be terminated with a NULL sentinel value. + */ + const char **exclude_patterns; + + /* + * The number of bytes to trim from the refname. Note that the trimmed + * bytes must not cause the reference to become empty. As such, this + * field should typically only be set when one uses a `prefix` ending + * in a slash. + */ + size_t trim_prefix; + + /* Flags that change which refs will be included. */ + enum refs_for_each_flag flags; +}; + +int refs_for_each_ref(struct ref_store *refs, + refs_for_each_cb fn, void *cb_data); +int refs_for_each_ref_ext(struct ref_store *refs, + refs_for_each_cb cb, void *cb_data, + const struct refs_for_each_ref_options *opts); +int refs_for_each_tag_ref(struct ref_store *refs, + refs_for_each_cb fn, void *cb_data); +int refs_for_each_branch_ref(struct ref_store *refs, + refs_for_each_cb fn, void *cb_data); +int refs_for_each_remote_ref(struct ref_store *refs, + refs_for_each_cb fn, void *cb_data); +int refs_for_each_replace_ref(struct ref_store *refs, + refs_for_each_cb fn, void *cb_data); /** - * iterate all refs in "patterns" by partitioning patterns into disjoint sets + * Iterate all refs in "prefixes" by partitioning prefixes into disjoint sets * and iterating the longest-common prefix of each set. - * - * references matching any pattern in "exclude_patterns" are omitted from the - * result set on a best-effort basis. - * - * callers should be prepared to ignore references that they did not ask for. */ -int refs_for_each_fullref_in_prefixes(struct ref_store *refs, - const char *namespace, - const char **patterns, - const char **exclude_patterns, - each_ref_fn fn, void *cb_data); - -/* iterates all refs that match the specified glob pattern. */ -int refs_for_each_glob_ref(struct ref_store *refs, each_ref_fn fn, - const char *pattern, void *cb_data); - -int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn, - const char *pattern, const char *prefix, void *cb_data); - -int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_data); - -/* - * references matching any pattern in "exclude_patterns" are omitted from the - * result set on a best-effort basis. - */ -int refs_for_each_namespaced_ref(struct ref_store *refs, - const char **exclude_patterns, - each_ref_fn fn, void *cb_data); - -/* can be used to learn about broken ref and symref */ -int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data); -int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix, - each_ref_fn fn, void *cb_data); - -/* - * Iterates over all refs including root refs, i.e. pseudorefs and HEAD. - */ -int refs_for_each_include_root_refs(struct ref_store *refs, each_ref_fn fn, - void *cb_data); +int refs_for_each_ref_in_prefixes(struct ref_store *refs, + const char **prefixes, + const struct refs_for_each_ref_options *opts, + refs_for_each_cb cb, void *cb_data); /* * Normalizes partial refs to their fully qualified form. @@ -461,32 +542,33 @@ void refs_warn_dangling_symrefs(struct ref_store *refs, FILE *fp, const struct string_list *refnames); /* - * Flags for controlling behaviour of pack_refs() - * PACK_REFS_PRUNE: Prune loose refs after packing - * PACK_REFS_AUTO: Pack refs on a best effort basis. The heuristics and end - * result are decided by the ref backend. Backends may ignore - * this flag and fall back to a normal repack. + * Flags for controlling behaviour of refs_optimize() + * REFS_OPTIMIZE_PRUNE: Prune loose refs after packing + * REFS_OPTIMIZE_AUTO: Pack refs on a best effort basis. The heuristics and end + * result are decided by the ref backend. Backends may ignore + * this flag and fall back to a normal repack. */ -#define PACK_REFS_PRUNE (1 << 0) -#define PACK_REFS_AUTO (1 << 1) +#define REFS_OPTIMIZE_PRUNE (1 << 0) +#define REFS_OPTIMIZE_AUTO (1 << 1) -struct pack_refs_opts { +struct refs_optimize_opts { unsigned int flags; struct ref_exclusions *exclusions; struct string_list *includes; }; /* - * Write a packed-refs file for the current repository. - * flags: Combination of the above PACK_REFS_* flags. - */ -int refs_pack_refs(struct ref_store *refs, struct pack_refs_opts *opts); - -/* * Optimize the ref store. The exact behavior is up to the backend. * For the files backend, this is equivalent to packing refs. */ -int refs_optimize(struct ref_store *refs, struct pack_refs_opts *opts); +int refs_optimize(struct ref_store *refs, struct refs_optimize_opts *opts); + +/* + * Check if refs backend can be optimized by calling 'refs_optimize'. + */ +int refs_optimize_required(struct ref_store *ref_store, + struct refs_optimize_opts *opts, + bool *required); /* * Setup reflog before using. Fill in err and return -1 on failure. @@ -614,6 +696,24 @@ int refs_for_each_reflog(struct ref_store *refs, each_reflog_fn fn, void *cb_dat */ int check_refname_format(const char *refname, int flags); +struct fsck_ref_report; + +/* + * Perform generic checks for a specific direct ref. This function is + * expected to be called by the ref backends for every symbolic ref. + */ +int refs_fsck_ref(struct ref_store *refs, struct fsck_options *o, + struct fsck_ref_report *report, + const char *refname, const struct object_id *oid); + +/* + * Perform generic checks for a specific symref target. This function is + * expected to be called by the ref backends for every symbolic ref. + */ +int refs_fsck_symref(struct ref_store *refs, struct fsck_options *o, + struct fsck_ref_report *report, + const char *refname, const char *target); + /* * Check the reference database for consistency. Return 0 if refs and * reflogs are consistent, and non-zero otherwise. The errors will be @@ -936,6 +1036,7 @@ typedef void ref_transaction_for_each_rejected_update_fn(const char *refname, const char *old_target, const char *new_target, enum ref_transaction_error err, + const char *details, void *cb_data); void ref_transaction_for_each_rejected_update(struct ref_transaction *transaction, ref_transaction_for_each_rejected_update_fn cb, @@ -1251,10 +1352,6 @@ int repo_migrate_ref_storage_format(struct repository *repo, * to the next entry, ref_iterator_advance() aborts the iteration, * frees the ref_iterator, and returns ITER_ERROR. * - * The reference currently being looked at can be peeled by calling - * ref_iterator_peel(). This function is often faster than peel_ref(), - * so it should be preferred when iterating over references. - * * Putting it all together, a typical iteration looks like this: * * int ok; @@ -1269,9 +1366,6 @@ int repo_migrate_ref_storage_format(struct repository *repo, * // Access information about the current reference: * if (!(iter->flags & REF_ISSYMREF)) * printf("%s is %s\n", iter->refname, oid_to_hex(iter->oid)); - * - * // If you need to peel the reference: - * ref_iterator_peel(iter, &oid); * } * * if (ok != ITER_DONE) @@ -1281,43 +1375,6 @@ int repo_migrate_ref_storage_format(struct repository *repo, struct ref_iterator; /* - * These flags are passed to refs_ref_iterator_begin() (and do_for_each_ref(), - * which feeds it). - */ -enum do_for_each_ref_flags { - /* - * Include broken references in a do_for_each_ref*() iteration, which - * would normally be omitted. This includes both refs that point to - * missing objects (a true repository corruption), ones with illegal - * names (which we prefer not to expose to callers), as well as - * dangling symbolic refs (i.e., those that point to a non-existent - * ref; this is not a corruption, but as they have no valid oid, we - * omit them from normal iteration results). - */ - DO_FOR_EACH_INCLUDE_BROKEN = (1 << 0), - - /* - * Only include per-worktree refs in a do_for_each_ref*() iteration. - * Normally this will be used with a files ref_store, since that's - * where all reference backends will presumably store their - * per-worktree refs. - */ - DO_FOR_EACH_PER_WORKTREE_ONLY = (1 << 1), - - /* - * Omit dangling symrefs from output; this only has an effect with - * INCLUDE_BROKEN, since they are otherwise not included at all. - */ - DO_FOR_EACH_OMIT_DANGLING_SYMREFS = (1 << 2), - - /* - * Include root refs i.e. HEAD and pseudorefs along with the regular - * refs. - */ - DO_FOR_EACH_INCLUDE_ROOT_REFS = (1 << 3), -}; - -/* * Return an iterator that goes over each reference in `refs` for * which the refname begins with prefix. If trim is non-zero, then * trim that many characters off the beginning of each refname. @@ -1326,7 +1383,7 @@ enum do_for_each_ref_flags { struct ref_iterator *refs_ref_iterator_begin( struct ref_store *refs, const char *prefix, const char **exclude_patterns, - int trim, enum do_for_each_ref_flags flags); + int trim, enum refs_for_each_flag flags); /* * Advance the iterator to the first or next item and return ITER_OK. @@ -1362,13 +1419,6 @@ enum ref_iterator_seek_flag { int ref_iterator_seek(struct ref_iterator *ref_iterator, const char *refname, unsigned int flags); -/* - * If possible, peel the reference currently being viewed by the - * iterator. Return 0 on success. - */ -int ref_iterator_peel(struct ref_iterator *ref_iterator, - struct object_id *peeled); - /* Free the reference iterator and any associated resources. */ void ref_iterator_free(struct ref_iterator *ref_iterator); @@ -1382,6 +1432,19 @@ void ref_iterator_free(struct ref_iterator *ref_iterator); * iterator style. */ int do_for_each_ref_iterator(struct ref_iterator *iter, - each_ref_fn fn, void *cb_data); + refs_for_each_cb fn, void *cb_data); + +/* + * Git only recognizes a directory as a repository if it contains: + * - HEAD file + * - refs/ folder + * While it is necessary within the files backend, newer backends may not + * follow the same structure. To go around this, we create stubs as necessary. + * + * If provided with a 'refs_heads_content', we create the 'refs/heads/head' file + * with the provided message. + */ +void refs_create_refdir_stubs(struct repository *repo, const char *refdir, + const char *refs_heads_content); #endif /* REFS_H */
diff --git a/refs/debug.c b/refs/debug.c index c59c172..639db0f 100644 --- a/refs/debug.c +++ b/refs/debug.c
@@ -124,11 +124,22 @@ static int debug_transaction_abort(struct ref_store *refs, return res; } -static int debug_pack_refs(struct ref_store *ref_store, struct pack_refs_opts *opts) +static int debug_optimize(struct ref_store *ref_store, struct refs_optimize_opts *opts) { struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; - int res = drefs->refs->be->pack_refs(drefs->refs, opts); - trace_printf_key(&trace_refs, "pack_refs: %d\n", res); + int res = drefs->refs->be->optimize(drefs->refs, opts); + trace_printf_key(&trace_refs, "optimize: %d\n", res); + return res; +} + +static int debug_optimize_required(struct ref_store *ref_store, + struct refs_optimize_opts *opts, + bool *required) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + int res = drefs->refs->be->optimize_required(drefs->refs, opts, required); + trace_printf_key(&trace_refs, "optimize_required: %s, res: %d\n", + *required ? "yes" : "no", res); return res; } @@ -168,11 +179,9 @@ static int debug_ref_iterator_advance(struct ref_iterator *ref_iterator) trace_printf_key(&trace_refs, "iterator_advance: (%d)\n", res); else trace_printf_key(&trace_refs, "iterator_advance: %s (0)\n", - diter->iter->refname); + diter->iter->ref.name); - diter->base.refname = diter->iter->refname; - diter->base.oid = diter->iter->oid; - diter->base.flags = diter->iter->flags; + diter->base.ref = diter->iter->ref; return res; } @@ -187,16 +196,6 @@ static int debug_ref_iterator_seek(struct ref_iterator *ref_iterator, return res; } -static int debug_ref_iterator_peel(struct ref_iterator *ref_iterator, - struct object_id *peeled) -{ - struct debug_ref_iterator *diter = - (struct debug_ref_iterator *)ref_iterator; - int res = diter->iter->vtable->peel(diter->iter, peeled); - trace_printf_key(&trace_refs, "iterator_peel: %s: %d\n", diter->iter->refname, res); - return res; -} - static void debug_ref_iterator_release(struct ref_iterator *ref_iterator) { struct debug_ref_iterator *diter = @@ -208,7 +207,6 @@ static void debug_ref_iterator_release(struct ref_iterator *ref_iterator) static struct ref_iterator_vtable debug_ref_iterator_vtable = { .advance = debug_ref_iterator_advance, .seek = debug_ref_iterator_seek, - .peel = debug_ref_iterator_peel, .release = debug_ref_iterator_release, }; @@ -452,7 +450,9 @@ struct ref_storage_be refs_be_debug = { .transaction_finish = debug_transaction_finish, .transaction_abort = debug_transaction_abort, - .pack_refs = debug_pack_refs, + .optimize = debug_optimize, + .optimize_required = debug_optimize_required, + .rename_ref = debug_rename_ref, .copy_ref = debug_copy_ref,
diff --git a/refs/files-backend.c b/refs/files-backend.c index 1adc4b5..0537a72 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c
@@ -106,19 +106,24 @@ static void clear_loose_ref_cache(struct files_ref_store *refs) * set of caches. */ static struct ref_store *files_ref_store_init(struct repository *repo, + const char *payload, const char *gitdir, unsigned int flags) { struct files_ref_store *refs = xcalloc(1, sizeof(*refs)); struct ref_store *ref_store = (struct ref_store *)refs; - struct strbuf sb = STRBUF_INIT; + struct strbuf ref_common_dir = STRBUF_INIT; + struct strbuf refdir = STRBUF_INIT; + bool is_worktree; - base_ref_store_init(ref_store, repo, gitdir, &refs_be_files); + refs_compute_filesystem_location(gitdir, payload, &is_worktree, &refdir, + &ref_common_dir); + + base_ref_store_init(ref_store, repo, refdir.buf, &refs_be_files); refs->store_flags = flags; - get_common_dir_noenv(&sb, gitdir); - refs->gitcommondir = strbuf_detach(&sb, NULL); + refs->gitcommondir = strbuf_detach(&ref_common_dir, NULL); refs->packed_ref_store = - packed_ref_store_init(repo, refs->gitcommondir, flags); + packed_ref_store_init(repo, NULL, refs->gitcommondir, flags); refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo); repo_config_get_bool(repo, "core.prefersymlinkrefs", &refs->prefer_symlink_refs); @@ -126,6 +131,8 @@ static struct ref_store *files_ref_store_init(struct repository *repo, chdir_notify_reparent("files-backend $GIT_COMMONDIR", &refs->gitcommondir); + strbuf_release(&refdir); + return ref_store; } @@ -354,13 +361,11 @@ static int for_each_root_ref(struct files_ref_store *refs, void *cb_data) { struct strbuf path = STRBUF_INIT, refname = STRBUF_INIT; - const char *dirname = refs->loose->root->name; struct dirent *de; - size_t dirnamelen; int ret; DIR *d; - files_ref_path(refs, &path, dirname); + files_ref_path(refs, &path, ""); d = opendir(path.buf); if (!d) { @@ -368,9 +373,6 @@ static int for_each_root_ref(struct files_ref_store *refs, return -1; } - strbuf_addstr(&refname, dirname); - dirnamelen = refname.len; - while ((de = readdir(d)) != NULL) { unsigned char dtype; @@ -378,6 +380,8 @@ static int for_each_root_ref(struct files_ref_store *refs, continue; if (ends_with(de->d_name, ".lock")) continue; + + strbuf_reset(&refname); strbuf_addstr(&refname, de->d_name); dtype = get_dtype(de, &path, 1); @@ -386,8 +390,6 @@ static int for_each_root_ref(struct files_ref_store *refs, if (ret) goto done; } - - strbuf_setlen(&refname, dirnamelen); } ret = 0; @@ -444,7 +446,7 @@ static struct ref_cache *get_loose_ref_cache(struct files_ref_store *refs, dir = get_ref_dir(refs->loose->root); - if (flags & DO_FOR_EACH_INCLUDE_ROOT_REFS) + if (flags & REFS_FOR_EACH_INCLUDE_ROOT_REFS) add_root_refs(refs, dir); /* @@ -960,27 +962,24 @@ static int files_ref_iterator_advance(struct ref_iterator *ref_iterator) int ok; while ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) { - if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY && - parse_worktree_ref(iter->iter0->refname, NULL, NULL, + if (iter->flags & REFS_FOR_EACH_PER_WORKTREE_ONLY && + parse_worktree_ref(iter->iter0->ref.name, NULL, NULL, NULL) != REF_WORKTREE_CURRENT) continue; - if ((iter->flags & DO_FOR_EACH_OMIT_DANGLING_SYMREFS) && - (iter->iter0->flags & REF_ISSYMREF) && - (iter->iter0->flags & REF_ISBROKEN)) + if ((iter->flags & REFS_FOR_EACH_OMIT_DANGLING_SYMREFS) && + (iter->iter0->ref.flags & REF_ISSYMREF) && + (iter->iter0->ref.flags & REF_ISBROKEN)) continue; - if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) && - !ref_resolves_to_object(iter->iter0->refname, + if (!(iter->flags & REFS_FOR_EACH_INCLUDE_BROKEN) && + !ref_resolves_to_object(iter->iter0->ref.name, iter->repo, - iter->iter0->oid, - iter->iter0->flags)) + iter->iter0->ref.oid, + iter->iter0->ref.flags)) continue; - iter->base.refname = iter->iter0->refname; - iter->base.oid = iter->iter0->oid; - iter->base.flags = iter->iter0->flags; - iter->base.referent = iter->iter0->referent; + iter->base.ref = iter->iter0->ref; return ITER_OK; } @@ -996,15 +995,6 @@ static int files_ref_iterator_seek(struct ref_iterator *ref_iterator, return ref_iterator_seek(iter->iter0, refname, flags); } -static int files_ref_iterator_peel(struct ref_iterator *ref_iterator, - struct object_id *peeled) -{ - struct files_ref_iterator *iter = - (struct files_ref_iterator *)ref_iterator; - - return ref_iterator_peel(iter->iter0, peeled); -} - static void files_ref_iterator_release(struct ref_iterator *ref_iterator) { struct files_ref_iterator *iter = @@ -1015,7 +1005,6 @@ static void files_ref_iterator_release(struct ref_iterator *ref_iterator) static struct ref_iterator_vtable files_ref_iterator_vtable = { .advance = files_ref_iterator_advance, .seek = files_ref_iterator_seek, - .peel = files_ref_iterator_peel, .release = files_ref_iterator_release, }; @@ -1030,7 +1019,7 @@ static struct ref_iterator *files_ref_iterator_begin( struct ref_iterator *ref_iterator; unsigned int required_flags = REF_STORE_READ; - if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) + if (!(flags & REFS_FOR_EACH_INCLUDE_BROKEN)) required_flags |= REF_STORE_ODB; refs = files_downcast(ref_store, required_flags, "ref_iterator_begin"); @@ -1068,7 +1057,7 @@ static struct ref_iterator *files_ref_iterator_begin( */ packed_iter = refs_ref_iterator_begin( refs->packed_ref_store, prefix, exclude_patterns, 0, - DO_FOR_EACH_INCLUDE_BROKEN); + REFS_FOR_EACH_INCLUDE_BROKEN); overlay_iter = overlay_ref_iterator_begin(loose_iter, packed_iter); @@ -1367,37 +1356,36 @@ static void prune_refs(struct files_ref_store *refs, struct ref_to_prune **refs_ * Return true if the specified reference should be packed. */ static int should_pack_ref(struct files_ref_store *refs, - const char *refname, - const struct object_id *oid, unsigned int ref_flags, - struct pack_refs_opts *opts) + const struct reference *ref, + struct refs_optimize_opts *opts) { struct string_list_item *item; /* Do not pack per-worktree refs: */ - if (parse_worktree_ref(refname, NULL, NULL, NULL) != + if (parse_worktree_ref(ref->name, NULL, NULL, NULL) != REF_WORKTREE_SHARED) return 0; /* Do not pack symbolic refs: */ - if (ref_flags & REF_ISSYMREF) + if (ref->flags & REF_ISSYMREF) return 0; /* Do not pack broken refs: */ - if (!ref_resolves_to_object(refname, refs->base.repo, oid, ref_flags)) + if (!ref_resolves_to_object(ref->name, refs->base.repo, ref->oid, ref->flags)) return 0; - if (ref_excluded(opts->exclusions, refname)) + if (ref_excluded(opts->exclusions, ref->name)) return 0; for_each_string_list_item(item, opts->includes) - if (!wildmatch(item->string, refname, 0)) + if (!wildmatch(item->string, ref->name, 0)) return 1; return 0; } static int should_pack_refs(struct files_ref_store *refs, - struct pack_refs_opts *opts) + struct refs_optimize_opts *opts) { struct ref_iterator *iter; size_t packed_size; @@ -1405,7 +1393,7 @@ static int should_pack_refs(struct files_ref_store *refs, size_t limit; int ret; - if (!(opts->flags & PACK_REFS_AUTO)) + if (!(opts->flags & REFS_OPTIMIZE_AUTO)) return 1; ret = packed_refs_size(refs->packed_ref_store, &packed_size); @@ -1443,8 +1431,7 @@ static int should_pack_refs(struct files_ref_store *refs, iter = cache_ref_iterator_begin(get_loose_ref_cache(refs, 0), NULL, refs->base.repo, 0); while ((ret = ref_iterator_advance(iter)) == ITER_OK) { - if (should_pack_ref(refs, iter->refname, iter->oid, - iter->flags, opts)) + if (should_pack_ref(refs, &iter->ref, opts)) refcount++; if (refcount >= limit) { ref_iterator_free(iter); @@ -1459,8 +1446,8 @@ static int should_pack_refs(struct files_ref_store *refs, return 0; } -static int files_pack_refs(struct ref_store *ref_store, - struct pack_refs_opts *opts) +static int files_optimize(struct ref_store *ref_store, + struct refs_optimize_opts *opts) { struct files_ref_store *refs = files_downcast(ref_store, REF_STORE_WRITE | REF_STORE_ODB, @@ -1489,24 +1476,24 @@ static int files_pack_refs(struct ref_store *ref_store, * in the packed ref cache. If the reference should be * pruned, also add it to refs_to_prune. */ - if (!should_pack_ref(refs, iter->refname, iter->oid, iter->flags, opts)) + if (!should_pack_ref(refs, &iter->ref, opts)) continue; /* * Add a reference creation for this reference to the * packed-refs transaction: */ - if (ref_transaction_update(transaction, iter->refname, - iter->oid, NULL, NULL, NULL, + if (ref_transaction_update(transaction, iter->ref.name, + iter->ref.oid, NULL, NULL, NULL, REF_NO_DEREF, NULL, &err)) die("failure preparing to create packed reference %s: %s", - iter->refname, err.buf); + iter->ref.name, err.buf); /* Schedule the loose reference for pruning if requested. */ - if ((opts->flags & PACK_REFS_PRUNE)) { + if ((opts->flags & REFS_OPTIMIZE_PRUNE)) { struct ref_to_prune *n; - FLEX_ALLOC_STR(n, name, iter->refname); - oidcpy(&n->oid, iter->oid); + FLEX_ALLOC_STR(n, name, iter->ref.name); + oidcpy(&n->oid, iter->ref.oid); n->next = refs_to_prune; refs_to_prune = n; } @@ -1527,13 +1514,14 @@ static int files_pack_refs(struct ref_store *ref_store, return 0; } -static int files_optimize(struct ref_store *ref_store, struct pack_refs_opts *opts) +static int files_optimize_required(struct ref_store *ref_store, + struct refs_optimize_opts *opts, + bool *required) { - /* - * For the "files" backend, "optimizing" is the same as "packing". - * So, we just call the existing worker function for packing. - */ - return files_pack_refs(ref_store, opts); + struct files_ref_store *refs = files_downcast(ref_store, REF_STORE_READ, + "optimize_required"); + *required = should_pack_refs(refs, opts); + return 0; } /* @@ -1825,7 +1813,7 @@ static int commit_ref(struct ref_lock *lock) size_t len = strlen(path); struct strbuf sb_path = STRBUF_INIT; - strbuf_attach(&sb_path, path, len, len); + strbuf_attach(&sb_path, path, len, len + 1); /* * If this fails, commit_lock_file() will also fail @@ -2394,7 +2382,7 @@ static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator) REFNAME_ALLOW_ONELEVEL)) continue; - iter->base.refname = diter->relative_path; + iter->base.ref.name = diter->relative_path; return ITER_OK; } @@ -2408,12 +2396,6 @@ static int files_reflog_iterator_seek(struct ref_iterator *ref_iterator UNUSED, BUG("ref_iterator_seek() called for reflog_iterator"); } -static int files_reflog_iterator_peel(struct ref_iterator *ref_iterator UNUSED, - struct object_id *peeled UNUSED) -{ - BUG("ref_iterator_peel() called for reflog_iterator"); -} - static void files_reflog_iterator_release(struct ref_iterator *ref_iterator) { struct files_reflog_iterator *iter = @@ -2424,7 +2406,6 @@ static void files_reflog_iterator_release(struct ref_iterator *ref_iterator) static struct ref_iterator_vtable files_reflog_iterator_vtable = { .advance = files_reflog_iterator_advance, .seek = files_reflog_iterator_seek, - .peel = files_reflog_iterator_peel, .release = files_reflog_iterator_release, }; @@ -3004,10 +2985,9 @@ static int files_transaction_prepare(struct ref_store *ref_store, head_ref, &refnames_to_check, err); if (ret) { - if (ref_transaction_maybe_set_rejected(transaction, i, ret)) { - strbuf_reset(err); + if (ref_transaction_maybe_set_rejected(transaction, i, + ret, err)) { ret = 0; - continue; } goto cleanup; @@ -3165,20 +3145,20 @@ static int parse_and_write_reflog(struct files_ref_store *refs, return 0; } -static int ref_present(const char *refname, const char *referent UNUSED, - const struct object_id *oid UNUSED, - int flags UNUSED, - void *cb_data) +static int ref_present(const struct reference *ref, void *cb_data) { struct string_list *affected_refnames = cb_data; - return string_list_has_string(affected_refnames, refname); + return string_list_has_string(affected_refnames, ref->name); } static int files_transaction_finish_initial(struct files_ref_store *refs, struct ref_transaction *transaction, struct strbuf *err) { + struct refs_for_each_ref_options opts = { + .flags = REFS_FOR_EACH_INCLUDE_BROKEN, + }; size_t i; int ret = 0; struct string_list affected_refnames = STRING_LIST_INIT_NODUP; @@ -3203,8 +3183,8 @@ static int files_transaction_finish_initial(struct files_ref_store *refs, * so here we really only check that none of the references * that we are creating already exists. */ - if (refs_for_each_rawref(&refs->base, ref_present, - &transaction->refnames)) + if (refs_for_each_ref_ext(&refs->base, ref_present, + &transaction->refnames, &opts)) BUG("initial ref transaction called with existing refs"); packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, @@ -3729,7 +3709,11 @@ static int files_ref_store_remove_on_disk(struct ref_store *ref_store, if (for_each_root_ref(refs, remove_one_root_ref, &data) < 0) ret = -1; - if (ref_store_remove_on_disk(refs->packed_ref_store, err) < 0) + /* + * Directly access the cleanup functions for packed-refs as the generic function + * would try to clear stubs which isn't required for the files backend. + */ + if (refs->packed_ref_store->be->remove_on_disk(refs->packed_ref_store, err) < 0) ret = -1; strbuf_release(&sb); @@ -3744,64 +3728,50 @@ static int files_ref_store_remove_on_disk(struct ref_store *ref_store, typedef int (*files_fsck_refs_fn)(struct ref_store *ref_store, struct fsck_options *o, const char *refname, - struct dir_iterator *iter); + const char *path, + int mode); -static int files_fsck_symref_target(struct fsck_options *o, +static int files_fsck_symref_target(struct ref_store *ref_store, + struct fsck_options *o, struct fsck_ref_report *report, + const char *refname, struct strbuf *referent, unsigned int symbolic_link) { - int is_referent_root; char orig_last_byte; size_t orig_len; int ret = 0; orig_len = referent->len; orig_last_byte = referent->buf[orig_len - 1]; - if (!symbolic_link) + + if (!symbolic_link) { strbuf_rtrim(referent); - is_referent_root = is_root_ref(referent->buf); - if (!is_referent_root && - !starts_with(referent->buf, "refs/") && - !starts_with(referent->buf, "worktrees/")) { - ret = fsck_report_ref(o, report, - FSCK_MSG_SYMREF_TARGET_IS_NOT_A_REF, - "points to non-ref target '%s'", referent->buf); + if (referent->len == orig_len || + (referent->len < orig_len && orig_last_byte != '\n')) { + ret |= fsck_report_ref(o, report, + FSCK_MSG_REF_MISSING_NEWLINE, + "misses LF at the end"); + } + if (referent->len != orig_len && referent->len != orig_len - 1) { + ret |= fsck_report_ref(o, report, + FSCK_MSG_TRAILING_REF_CONTENT, + "has trailing whitespaces or newlines"); + } } - if (!is_referent_root && check_refname_format(referent->buf, 0)) { - ret = fsck_report_ref(o, report, - FSCK_MSG_BAD_REFERENT_NAME, - "points to invalid refname '%s'", referent->buf); - goto out; - } + ret |= refs_fsck_symref(ref_store, o, report, refname, referent->buf); - if (symbolic_link) - goto out; - - if (referent->len == orig_len || - (referent->len < orig_len && orig_last_byte != '\n')) { - ret = fsck_report_ref(o, report, - FSCK_MSG_REF_MISSING_NEWLINE, - "misses LF at the end"); - } - - if (referent->len != orig_len && referent->len != orig_len - 1) { - ret = fsck_report_ref(o, report, - FSCK_MSG_TRAILING_REF_CONTENT, - "has trailing whitespaces or newlines"); - } - -out: - return ret; + return ret ? -1 : 0; } static int files_fsck_refs_content(struct ref_store *ref_store, struct fsck_options *o, const char *target_name, - struct dir_iterator *iter) + const char *path, + int mode) { struct strbuf ref_content = STRBUF_INIT; struct strbuf abs_gitdir = STRBUF_INIT; @@ -3815,7 +3785,7 @@ static int files_fsck_refs_content(struct ref_store *ref_store, report.path = target_name; - if (S_ISLNK(iter->st.st_mode)) { + if (S_ISLNK(mode)) { const char *relative_referent_path = NULL; ret = fsck_report_ref(o, &report, @@ -3827,7 +3797,7 @@ static int files_fsck_refs_content(struct ref_store *ref_store, if (!is_dir_sep(abs_gitdir.buf[abs_gitdir.len - 1])) strbuf_addch(&abs_gitdir, '/'); - strbuf_add_real_path(&ref_content, iter->path.buf); + strbuf_add_real_path(&ref_content, path); skip_prefix(ref_content.buf, abs_gitdir.buf, &relative_referent_path); @@ -3836,11 +3806,12 @@ static int files_fsck_refs_content(struct ref_store *ref_store, else strbuf_addbuf(&referent, &ref_content); - ret |= files_fsck_symref_target(o, &report, &referent, 1); + ret |= files_fsck_symref_target(ref_store, o, &report, + target_name, &referent, 1); goto cleanup; } - if (strbuf_read_file(&ref_content, iter->path.buf, 0) < 0) { + if (strbuf_read_file(&ref_content, path, 0) < 0) { /* * Ref file could be removed by another concurrent process. We should * ignore this error and continue to the next ref. @@ -3848,7 +3819,7 @@ static int files_fsck_refs_content(struct ref_store *ref_store, if (errno == ENOENT) goto cleanup; - ret = error_errno(_("cannot read ref file '%s'"), iter->path.buf); + ret = error_errno(_("cannot read ref file '%s'"), path); goto cleanup; } @@ -3875,8 +3846,11 @@ static int files_fsck_refs_content(struct ref_store *ref_store, "has trailing garbage: '%s'", trailing); goto cleanup; } + + ret = refs_fsck_ref(ref_store, o, &report, target_name, &oid); } else { - ret = files_fsck_symref_target(o, &report, &referent, 0); + ret = files_fsck_symref_target(ref_store, o, &report, + target_name, &referent, 0); goto cleanup; } @@ -3890,21 +3864,25 @@ static int files_fsck_refs_content(struct ref_store *ref_store, static int files_fsck_refs_name(struct ref_store *ref_store UNUSED, struct fsck_options *o, const char *refname, - struct dir_iterator *iter) + const char *path, + int mode UNUSED) { struct strbuf sb = STRBUF_INIT; + const char *filename; int ret = 0; + filename = basename((char *) path); + /* * Ignore the files ending with ".lock" as they may be lock files * However, do not allow bare ".lock" files. */ - if (iter->basename[0] != '.' && ends_with(iter->basename, ".lock")) + if (filename[0] != '.' && ends_with(filename, ".lock")) goto cleanup; - /* - * This works right now because we never check the root refs. - */ + if (is_root_ref(refname)) + goto cleanup; + if (check_refname_format(refname, 0)) { struct fsck_ref_report report = { 0 }; @@ -3919,11 +3897,44 @@ static int files_fsck_refs_name(struct ref_store *ref_store UNUSED, return ret; } +static const files_fsck_refs_fn fsck_refs_fn[]= { + files_fsck_refs_name, + files_fsck_refs_content, + NULL, +}; + +static int files_fsck_ref(struct ref_store *ref_store, + struct fsck_options *o, + const char *refname, + const char *path, + int mode) +{ + int ret = 0; + + if (o->verbose) + fprintf_ln(stderr, "Checking %s", refname); + + if (!S_ISREG(mode) && !S_ISLNK(mode)) { + struct fsck_ref_report report = { .path = refname }; + + if (fsck_report_ref(o, &report, + FSCK_MSG_BAD_REF_FILETYPE, + "unexpected file type")) + ret = -1; + goto out; + } + + for (size_t i = 0; fsck_refs_fn[i]; i++) + if (fsck_refs_fn[i](ref_store, o, refname, path, mode)) + ret = -1; + +out: + return ret; +} + static int files_fsck_refs_dir(struct ref_store *ref_store, struct fsck_options *o, - const char *refs_check_dir, - struct worktree *wt, - files_fsck_refs_fn *fsck_refs_fn) + struct worktree *wt) { struct strbuf refname = STRBUF_INIT; struct strbuf sb = STRBUF_INIT; @@ -3931,7 +3942,7 @@ static int files_fsck_refs_dir(struct ref_store *ref_store, int iter_status; int ret = 0; - strbuf_addf(&sb, "%s/%s", ref_store->gitdir, refs_check_dir); + strbuf_addf(&sb, "%s/refs", ref_store->gitdir); iter = dir_iterator_begin(sb.buf, 0); if (!iter) { @@ -3943,31 +3954,17 @@ static int files_fsck_refs_dir(struct ref_store *ref_store, } while ((iter_status = dir_iterator_advance(iter)) == ITER_OK) { - if (S_ISDIR(iter->st.st_mode)) { + if (S_ISDIR(iter->st.st_mode)) continue; - } else if (S_ISREG(iter->st.st_mode) || - S_ISLNK(iter->st.st_mode)) { - strbuf_reset(&refname); - if (!is_main_worktree(wt)) - strbuf_addf(&refname, "worktrees/%s/", wt->id); - strbuf_addf(&refname, "%s/%s", refs_check_dir, - iter->relative_path); + strbuf_reset(&refname); + if (!is_main_worktree(wt)) + strbuf_addf(&refname, "worktrees/%s/", wt->id); + strbuf_addf(&refname, "refs/%s", iter->relative_path); - if (o->verbose) - fprintf_ln(stderr, "Checking %s", refname.buf); - - for (size_t i = 0; fsck_refs_fn[i]; i++) { - if (fsck_refs_fn[i](ref_store, o, refname.buf, iter)) - ret = -1; - } - } else { - struct fsck_ref_report report = { .path = iter->basename }; - if (fsck_report_ref(o, &report, - FSCK_MSG_BAD_REF_FILETYPE, - "unexpected file type")) - ret = -1; - } + if (files_fsck_ref(ref_store, o, refname.buf, + iter->path.buf, iter->st.st_mode) < 0) + ret = -1; } if (iter_status != ITER_DONE) @@ -3980,17 +3977,35 @@ static int files_fsck_refs_dir(struct ref_store *ref_store, return ret; } -static int files_fsck_refs(struct ref_store *ref_store, - struct fsck_options *o, - struct worktree *wt) -{ - files_fsck_refs_fn fsck_refs_fn[]= { - files_fsck_refs_name, - files_fsck_refs_content, - NULL, - }; +struct files_fsck_root_ref_data { + struct files_ref_store *refs; + struct fsck_options *o; + struct worktree *wt; + struct strbuf refname; + struct strbuf path; +}; - return files_fsck_refs_dir(ref_store, o, "refs", wt, fsck_refs_fn); +static int files_fsck_root_ref(const char *refname, void *cb_data) +{ + struct files_fsck_root_ref_data *data = cb_data; + struct stat st; + + strbuf_reset(&data->refname); + if (!is_main_worktree(data->wt)) + strbuf_addf(&data->refname, "worktrees/%s/", data->wt->id); + strbuf_addstr(&data->refname, refname); + + strbuf_reset(&data->path); + strbuf_addf(&data->path, "%s/%s", data->refs->gitcommondir, data->refname.buf); + + if (stat(data->path.buf, &st)) { + if (errno == ENOENT) + return 0; + return error_errno("failed to read ref: '%s'", data->path.buf); + } + + return files_fsck_ref(&data->refs->base, data->o, data->refname.buf, + data->path.buf, st.st_mode); } static int files_fsck(struct ref_store *ref_store, @@ -3999,9 +4014,27 @@ static int files_fsck(struct ref_store *ref_store, { struct files_ref_store *refs = files_downcast(ref_store, REF_STORE_READ, "fsck"); + struct files_fsck_root_ref_data data = { + .refs = refs, + .o = o, + .wt = wt, + .refname = STRBUF_INIT, + .path = STRBUF_INIT, + }; + int ret = 0; - return files_fsck_refs(ref_store, o, wt) | - refs->packed_ref_store->be->fsck(refs->packed_ref_store, o, wt); + if (files_fsck_refs_dir(ref_store, o, wt) < 0) + ret = -1; + + if (for_each_root_ref(refs, files_fsck_root_ref, &data) < 0) + ret = -1; + + if (refs->packed_ref_store->be->fsck(refs->packed_ref_store, o, wt) < 0) + ret = -1; + + strbuf_release(&data.refname); + strbuf_release(&data.path); + return ret; } struct ref_storage_be refs_be_files = { @@ -4015,8 +4048,8 @@ struct ref_storage_be refs_be_files = { .transaction_finish = files_transaction_finish, .transaction_abort = files_transaction_abort, - .pack_refs = files_pack_refs, .optimize = files_optimize, + .optimize_required = files_optimize_required, .rename_ref = files_rename_ref, .copy_ref = files_copy_ref,
diff --git a/refs/iterator.c b/refs/iterator.c index 17ef841..d5cacde 100644 --- a/refs/iterator.c +++ b/refs/iterator.c
@@ -21,12 +21,6 @@ int ref_iterator_seek(struct ref_iterator *ref_iterator, const char *refname, return ref_iterator->vtable->seek(ref_iterator, refname, flags); } -int ref_iterator_peel(struct ref_iterator *ref_iterator, - struct object_id *peeled) -{ - return ref_iterator->vtable->peel(ref_iterator, peeled); -} - void ref_iterator_free(struct ref_iterator *ref_iterator) { if (ref_iterator) { @@ -41,10 +35,7 @@ void base_ref_iterator_init(struct ref_iterator *iter, struct ref_iterator_vtable *vtable) { iter->vtable = vtable; - iter->refname = NULL; - iter->referent = NULL; - iter->oid = NULL; - iter->flags = 0; + memset(&iter->ref, 0, sizeof(iter->ref)); } struct empty_ref_iterator { @@ -63,12 +54,6 @@ static int empty_ref_iterator_seek(struct ref_iterator *ref_iterator UNUSED, return 0; } -static int empty_ref_iterator_peel(struct ref_iterator *ref_iterator UNUSED, - struct object_id *peeled UNUSED) -{ - BUG("peel called for empty iterator"); -} - static void empty_ref_iterator_release(struct ref_iterator *ref_iterator UNUSED) { } @@ -76,7 +61,6 @@ static void empty_ref_iterator_release(struct ref_iterator *ref_iterator UNUSED) static struct ref_iterator_vtable empty_ref_iterator_vtable = { .advance = empty_ref_iterator_advance, .seek = empty_ref_iterator_seek, - .peel = empty_ref_iterator_peel, .release = empty_ref_iterator_release, }; @@ -127,8 +111,8 @@ enum iterator_selection ref_iterator_select(struct ref_iterator *iter_worktree, * latter. */ if (iter_worktree) { - int cmp = strcmp(iter_worktree->refname, - iter_common->refname); + int cmp = strcmp(iter_worktree->ref.name, + iter_common->ref.name); if (cmp < 0) return ITER_SELECT_0; else if (!cmp) @@ -139,7 +123,7 @@ enum iterator_selection ref_iterator_select(struct ref_iterator *iter_worktree, * We now know that the lexicographically-next ref is a common * ref. When the common ref is a shared one we return it. */ - if (parse_worktree_ref(iter_common->refname, NULL, NULL, + if (parse_worktree_ref(iter_common->ref.name, NULL, NULL, NULL) == REF_WORKTREE_SHARED) return ITER_SELECT_1; @@ -212,10 +196,7 @@ static int merge_ref_iterator_advance(struct ref_iterator *ref_iterator) } if (selection & ITER_YIELD_CURRENT) { - iter->base.referent = (*iter->current)->referent; - iter->base.refname = (*iter->current)->refname; - iter->base.oid = (*iter->current)->oid; - iter->base.flags = (*iter->current)->flags; + iter->base.ref = (*iter->current)->ref; return ITER_OK; } } @@ -246,18 +227,6 @@ static int merge_ref_iterator_seek(struct ref_iterator *ref_iterator, return 0; } -static int merge_ref_iterator_peel(struct ref_iterator *ref_iterator, - struct object_id *peeled) -{ - struct merge_ref_iterator *iter = - (struct merge_ref_iterator *)ref_iterator; - - if (!iter->current) { - BUG("peel called before advance for merge iterator"); - } - return ref_iterator_peel(*iter->current, peeled); -} - static void merge_ref_iterator_release(struct ref_iterator *ref_iterator) { struct merge_ref_iterator *iter = @@ -269,7 +238,6 @@ static void merge_ref_iterator_release(struct ref_iterator *ref_iterator) static struct ref_iterator_vtable merge_ref_iterator_vtable = { .advance = merge_ref_iterator_advance, .seek = merge_ref_iterator_seek, - .peel = merge_ref_iterator_peel, .release = merge_ref_iterator_release, }; @@ -313,7 +281,7 @@ static enum iterator_selection overlay_iterator_select( else if (!front) return ITER_SELECT_1; - cmp = strcmp(front->refname, back->refname); + cmp = strcmp(front->ref.name, back->ref.name); if (cmp < 0) return ITER_SELECT_0; @@ -371,7 +339,7 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator) int ok; while ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) { - int cmp = compare_prefix(iter->iter0->refname, iter->prefix); + int cmp = compare_prefix(iter->iter0->ref.name, iter->prefix); if (cmp < 0) continue; /* @@ -382,6 +350,8 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator) if (cmp > 0) return ITER_DONE; + iter->base.ref = iter->iter0->ref; + if (iter->trim) { /* * It is nonsense to trim off characters that @@ -392,15 +362,11 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator) * one character left in the refname after * trimming, report it as a bug: */ - if (strlen(iter->iter0->refname) <= iter->trim) + if (strlen(iter->base.ref.name) <= iter->trim) BUG("attempt to trim too many characters"); - iter->base.refname = iter->iter0->refname + iter->trim; - } else { - iter->base.refname = iter->iter0->refname; + iter->base.ref.name += iter->trim; } - iter->base.oid = iter->iter0->oid; - iter->base.flags = iter->iter0->flags; return ITER_OK; } @@ -420,15 +386,6 @@ static int prefix_ref_iterator_seek(struct ref_iterator *ref_iterator, return ref_iterator_seek(iter->iter0, refname, flags); } -static int prefix_ref_iterator_peel(struct ref_iterator *ref_iterator, - struct object_id *peeled) -{ - struct prefix_ref_iterator *iter = - (struct prefix_ref_iterator *)ref_iterator; - - return ref_iterator_peel(iter->iter0, peeled); -} - static void prefix_ref_iterator_release(struct ref_iterator *ref_iterator) { struct prefix_ref_iterator *iter = @@ -440,7 +397,6 @@ static void prefix_ref_iterator_release(struct ref_iterator *ref_iterator) static struct ref_iterator_vtable prefix_ref_iterator_vtable = { .advance = prefix_ref_iterator_advance, .seek = prefix_ref_iterator_seek, - .peel = prefix_ref_iterator_peel, .release = prefix_ref_iterator_release, }; @@ -466,23 +422,18 @@ struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0, return ref_iterator; } -struct ref_iterator *current_ref_iter = NULL; - int do_for_each_ref_iterator(struct ref_iterator *iter, - each_ref_fn fn, void *cb_data) + refs_for_each_cb fn, void *cb_data) { int retval = 0, ok; - struct ref_iterator *old_ref_iter = current_ref_iter; - current_ref_iter = iter; while ((ok = ref_iterator_advance(iter)) == ITER_OK) { - retval = fn(iter->refname, iter->referent, iter->oid, iter->flags, cb_data); + retval = fn(&iter->ref, cb_data); if (retval) goto out; } out: - current_ref_iter = old_ref_iter; if (ok == ITER_ERROR) retval = -1; ref_iterator_free(iter);
diff --git a/refs/packed-backend.c b/refs/packed-backend.c index a8c22a0..23ed629 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c
@@ -211,7 +211,12 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot) return snapshot->refs->base.repo->hash_algo->hexsz; } +/* + * Since packed-refs is only stored in the common dir, don't parse the + * payload and rely on the files-backend to set 'gitdir' correctly. + */ struct ref_store *packed_ref_store_init(struct repository *repo, + const char *payload UNUSED, const char *gitdir, unsigned int store_flags) { @@ -882,6 +887,7 @@ static int next_record(struct packed_ref_iterator *iter) { const char *p, *eol; + memset(&iter->base.ref, 0, sizeof(iter->base.ref)); strbuf_reset(&iter->refname_buf); /* @@ -908,7 +914,7 @@ static int next_record(struct packed_ref_iterator *iter) if (iter->pos == iter->eof) return ITER_DONE; - iter->base.flags = REF_ISPACKED; + iter->base.ref.flags = REF_ISPACKED; p = iter->pos; if (iter->eof - p < snapshot_hexsz(iter->snapshot) + 2 || @@ -916,6 +922,7 @@ static int next_record(struct packed_ref_iterator *iter) !isspace(*p++)) die_invalid_line(iter->snapshot->refs->path, iter->pos, iter->eof - iter->pos); + iter->base.ref.oid = &iter->oid; eol = memchr(p, '\n', iter->eof - p); if (!eol) @@ -923,22 +930,22 @@ static int next_record(struct packed_ref_iterator *iter) iter->pos, iter->eof - iter->pos); strbuf_add(&iter->refname_buf, p, eol - p); - iter->base.refname = iter->refname_buf.buf; + iter->base.ref.name = iter->refname_buf.buf; if (refname_contains_nul(&iter->refname_buf)) - die("packed refname contains embedded NULL: %s", iter->base.refname); + die("packed refname contains embedded NULL: %s", iter->base.ref.name); - if (check_refname_format(iter->base.refname, REFNAME_ALLOW_ONELEVEL)) { - if (!refname_is_safe(iter->base.refname)) + if (check_refname_format(iter->base.ref.name, REFNAME_ALLOW_ONELEVEL)) { + if (!refname_is_safe(iter->base.ref.name)) die("packed refname is dangerous: %s", - iter->base.refname); + iter->base.ref.name); oidclr(&iter->oid, iter->repo->hash_algo); - iter->base.flags |= REF_BAD_NAME | REF_ISBROKEN; + iter->base.ref.flags |= REF_BAD_NAME | REF_ISBROKEN; } if (iter->snapshot->peeled == PEELED_FULLY || (iter->snapshot->peeled == PEELED_TAGS && - starts_with(iter->base.refname, "refs/tags/"))) - iter->base.flags |= REF_KNOWS_PEELED; + starts_with(iter->base.ref.name, "refs/tags/"))) + iter->base.ref.flags |= REF_KNOWS_PEELED; iter->pos = eol + 1; @@ -956,11 +963,12 @@ static int next_record(struct packed_ref_iterator *iter) * definitely know the value of *this* reference. But * we suppress it if the reference is broken: */ - if ((iter->base.flags & REF_ISBROKEN)) { + if ((iter->base.ref.flags & REF_ISBROKEN)) { oidclr(&iter->peeled, iter->repo->hash_algo); - iter->base.flags &= ~REF_KNOWS_PEELED; + iter->base.ref.flags &= ~REF_KNOWS_PEELED; } else { - iter->base.flags |= REF_KNOWS_PEELED; + iter->base.ref.flags |= REF_KNOWS_PEELED; + iter->base.ref.peeled_oid = &iter->peeled; } } else { oidclr(&iter->peeled, iter->repo->hash_algo); @@ -976,15 +984,15 @@ static int packed_ref_iterator_advance(struct ref_iterator *ref_iterator) int ok; while ((ok = next_record(iter)) == ITER_OK) { - const char *refname = iter->base.refname; + const char *refname = iter->base.ref.name; const char *prefix = iter->prefix; - if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY && - !is_per_worktree_ref(iter->base.refname)) + if (iter->flags & REFS_FOR_EACH_PER_WORKTREE_ONLY && + !is_per_worktree_ref(iter->base.ref.name)) continue; - if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) && - !ref_resolves_to_object(iter->base.refname, iter->repo, + if (!(iter->flags & REFS_FOR_EACH_INCLUDE_BROKEN) && + !ref_resolves_to_object(iter->base.ref.name, iter->repo, &iter->oid, iter->flags)) continue; @@ -1027,22 +1035,6 @@ static int packed_ref_iterator_seek(struct ref_iterator *ref_iterator, return 0; } -static int packed_ref_iterator_peel(struct ref_iterator *ref_iterator, - struct object_id *peeled) -{ - struct packed_ref_iterator *iter = - (struct packed_ref_iterator *)ref_iterator; - - if ((iter->base.flags & REF_KNOWS_PEELED)) { - oidcpy(peeled, &iter->peeled); - return is_null_oid(&iter->peeled) ? -1 : 0; - } else if ((iter->base.flags & (REF_ISBROKEN | REF_ISSYMREF))) { - return -1; - } else { - return peel_object(iter->repo, &iter->oid, peeled) ? -1 : 0; - } -} - static void packed_ref_iterator_release(struct ref_iterator *ref_iterator) { struct packed_ref_iterator *iter = @@ -1056,7 +1048,6 @@ static void packed_ref_iterator_release(struct ref_iterator *ref_iterator) static struct ref_iterator_vtable packed_ref_iterator_vtable = { .advance = packed_ref_iterator_advance, .seek = packed_ref_iterator_seek, - .peel = packed_ref_iterator_peel, .release = packed_ref_iterator_release, }; @@ -1173,7 +1164,7 @@ static struct ref_iterator *packed_ref_iterator_begin( struct ref_iterator *ref_iterator; unsigned int required_flags = REF_STORE_READ; - if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) + if (!(flags & REFS_FOR_EACH_INCLUDE_BROKEN)) required_flags |= REF_STORE_ODB; refs = packed_downcast(ref_store, required_flags, "ref_iterator_begin"); @@ -1194,7 +1185,6 @@ static struct ref_iterator *packed_ref_iterator_begin( iter->snapshot = snapshot; acquire_snapshot(snapshot); strbuf_init(&iter->refname_buf, 0); - iter->base.oid = &iter->oid; iter->repo = ref_store->repo; iter->flags = flags; @@ -1416,7 +1406,7 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re * of updates is exhausted, leave i set to updates->nr. */ iter = packed_ref_iterator_begin(&refs->base, "", NULL, - DO_FOR_EACH_INCLUDE_BROKEN); + REFS_FOR_EACH_INCLUDE_BROKEN); if ((ok = ref_iterator_advance(iter)) != ITER_OK) { ref_iterator_free(iter); iter = NULL; @@ -1436,7 +1426,7 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re if (!iter) cmp = +1; else - cmp = strcmp(iter->refname, update->refname); + cmp = strcmp(iter->ref.name, update->refname); } if (!cmp) { @@ -1452,23 +1442,23 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re update->refname); ret = REF_TRANSACTION_ERROR_CREATE_EXISTS; - if (ref_transaction_maybe_set_rejected(transaction, i, ret)) { - strbuf_reset(err); + if (ref_transaction_maybe_set_rejected(transaction, i, + ret, err)) { ret = 0; continue; } goto error; - } else if (!oideq(&update->old_oid, iter->oid)) { + } else if (!oideq(&update->old_oid, iter->ref.oid)) { strbuf_addf(err, "cannot update ref '%s': " "is at %s but expected %s", update->refname, - oid_to_hex(iter->oid), + oid_to_hex(iter->ref.oid), oid_to_hex(&update->old_oid)); ret = REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE; - if (ref_transaction_maybe_set_rejected(transaction, i, ret)) { - strbuf_reset(err); + if (ref_transaction_maybe_set_rejected(transaction, i, + ret, err)) { ret = 0; continue; } @@ -1511,8 +1501,8 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re oid_to_hex(&update->old_oid)); ret = REF_TRANSACTION_ERROR_NONEXISTENT_REF; - if (ref_transaction_maybe_set_rejected(transaction, i, ret)) { - strbuf_reset(err); + if (ref_transaction_maybe_set_rejected(transaction, i, + ret, err)) { ret = 0; continue; } @@ -1523,13 +1513,8 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re if (cmp < 0) { /* Pass the old reference through. */ - - struct object_id peeled; - int peel_error = ref_iterator_peel(iter, &peeled); - - if (write_packed_entry(out, iter->refname, - iter->oid, - peel_error ? NULL : &peeled)) + if (write_packed_entry(out, iter->ref.name, + iter->ref.oid, iter->ref.peeled_oid)) goto write_error; if ((ok = ref_iterator_advance(iter)) != ITER_OK) { @@ -1547,9 +1532,8 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re i++; } else { struct object_id peeled; - int peel_error = peel_object(refs->base.repo, - &update->new_oid, - &peeled); + int peel_error = peel_object(refs->base.repo, &update->new_oid, + &peeled, PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE); if (write_packed_entry(out, update->refname, &update->new_oid, @@ -1794,8 +1778,8 @@ static int packed_transaction_finish(struct ref_store *ref_store, return ret; } -static int packed_pack_refs(struct ref_store *ref_store UNUSED, - struct pack_refs_opts *pack_opts UNUSED) +static int packed_optimize(struct ref_store *ref_store UNUSED, + struct refs_optimize_opts *opts UNUSED) { /* * Packed refs are already packed. It might be that loose refs @@ -1805,6 +1789,17 @@ static int packed_pack_refs(struct ref_store *ref_store UNUSED, return 0; } +static int packed_optimize_required(struct ref_store *ref_store UNUSED, + struct refs_optimize_opts *opts UNUSED, + bool *required) +{ + /* + * Packed refs are already optimized. + */ + *required = false; + return 0; +} + static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_store UNUSED) { return empty_ref_iterator_begin(); @@ -2150,7 +2145,9 @@ struct ref_storage_be refs_be_packed = { .transaction_finish = packed_transaction_finish, .transaction_abort = packed_transaction_abort, - .pack_refs = packed_pack_refs, + .optimize = packed_optimize, + .optimize_required = packed_optimize_required, + .rename_ref = NULL, .copy_ref = NULL,
diff --git a/refs/packed-backend.h b/refs/packed-backend.h index 9481d5e..2c2377a 100644 --- a/refs/packed-backend.h +++ b/refs/packed-backend.h
@@ -14,6 +14,7 @@ struct ref_transaction; */ struct ref_store *packed_ref_store_init(struct repository *repo, + const char *payload, const char *gitdir, unsigned int store_flags);
diff --git a/refs/ref-cache.c b/refs/ref-cache.c index e5e5df1..ffef01a 100644 --- a/refs/ref-cache.c +++ b/refs/ref-cache.c
@@ -425,10 +425,11 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator) level->prefix_state = entry_prefix_state; level->index = -1; } else { - iter->base.refname = entry->name; - iter->base.referent = entry->u.value.referent; - iter->base.oid = &entry->u.value.oid; - iter->base.flags = entry->flag; + memset(&iter->base.ref, 0, sizeof(iter->base.ref)); + iter->base.ref.name = entry->name; + iter->base.ref.target = entry->u.value.referent; + iter->base.ref.oid = &entry->u.value.oid; + iter->base.ref.flags = entry->flag; return ITER_OK; } } @@ -545,14 +546,6 @@ static int cache_ref_iterator_seek(struct ref_iterator *ref_iterator, return 0; } -static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator, - struct object_id *peeled) -{ - struct cache_ref_iterator *iter = - (struct cache_ref_iterator *)ref_iterator; - return peel_object(iter->repo, ref_iterator->oid, peeled) ? -1 : 0; -} - static void cache_ref_iterator_release(struct ref_iterator *ref_iterator) { struct cache_ref_iterator *iter = @@ -564,7 +557,6 @@ static void cache_ref_iterator_release(struct ref_iterator *ref_iterator) static struct ref_iterator_vtable cache_ref_iterator_vtable = { .advance = cache_ref_iterator_advance, .seek = cache_ref_iterator_seek, - .peel = cache_ref_iterator_peel, .release = cache_ref_iterator_release, };
diff --git a/refs/refs-internal.h b/refs/refs-internal.h index 4ef3bd7..d79e35f 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h
@@ -128,6 +128,7 @@ struct ref_update { * was rejected. */ enum ref_transaction_error rejection_err; + const char *rejection_details; /* * If this ref_update was split off of a symref update via @@ -153,7 +154,8 @@ int refs_read_raw_ref(struct ref_store *ref_store, const char *refname, */ int ref_transaction_maybe_set_rejected(struct ref_transaction *transaction, size_t update_idx, - enum ref_transaction_error err); + enum ref_transaction_error err, + struct strbuf *details); /* * Add a ref_update with the specified properties to transaction, and @@ -249,10 +251,7 @@ const char *find_descendant_ref(const char *dirname, */ struct ref_iterator { struct ref_iterator_vtable *vtable; - const char *refname; - const char *referent; - const struct object_id *oid; - unsigned int flags; + struct reference ref; }; /* @@ -361,12 +360,6 @@ typedef int ref_iterator_seek_fn(struct ref_iterator *ref_iterator, const char *refname, unsigned int flags); /* - * Peels the current ref, returning 0 for success or -1 for failure. - */ -typedef int ref_iterator_peel_fn(struct ref_iterator *ref_iterator, - struct object_id *peeled); - -/* * Implementations of this function should free any resources specific * to the derived class. */ @@ -375,23 +368,9 @@ typedef void ref_iterator_release_fn(struct ref_iterator *ref_iterator); struct ref_iterator_vtable { ref_iterator_advance_fn *advance; ref_iterator_seek_fn *seek; - ref_iterator_peel_fn *peel; ref_iterator_release_fn *release; }; -/* - * current_ref_iter is a performance hack: when iterating over - * references using the for_each_ref*() functions, current_ref_iter is - * set to the reference iterator before calling the callback function. - * If the callback function calls peel_ref(), then peel_ref() first - * checks whether the reference to be peeled is the one referred to by - * the iterator (it usually is) and if so, asks the iterator for the - * peeled version of the reference if it is available. This avoids a - * refname lookup in a common case. current_ref_iter is set to NULL - * when the iteration is over. - */ -extern struct ref_iterator *current_ref_iter; - struct ref_store; /* refs backends */ @@ -412,6 +391,7 @@ struct ref_store; * the ref_store and to record the ref_store for later lookup. */ typedef struct ref_store *ref_store_init_fn(struct repository *repo, + const char *payload, const char *gitdir, unsigned int flags); /* @@ -445,10 +425,13 @@ typedef int ref_transaction_commit_fn(struct ref_store *refs, struct ref_transaction *transaction, struct strbuf *err); -typedef int pack_refs_fn(struct ref_store *ref_store, - struct pack_refs_opts *opts); typedef int optimize_fn(struct ref_store *ref_store, - struct pack_refs_opts *opts); + struct refs_optimize_opts *opts); + +typedef int optimize_required_fn(struct ref_store *ref_store, + struct refs_optimize_opts *opts, + bool *required); + typedef int rename_ref_fn(struct ref_store *ref_store, const char *oldref, const char *newref, const char *logmsg); @@ -573,8 +556,8 @@ struct ref_storage_be { ref_transaction_finish_fn *transaction_finish; ref_transaction_abort_fn *transaction_abort; - pack_refs_fn *pack_refs; optimize_fn *optimize; + optimize_required_fn *optimize_required; rename_ref_fn *rename_ref; copy_ref_fn *copy_ref; @@ -686,4 +669,17 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs unsigned int initial_transaction, struct strbuf *err); +/* + * Given a gitdir and the reference storage payload provided, retrieve the + * 'refdir' and 'ref_common_dir'. The former is where references should be + * stored for the current worktree, the latter is the common reference + * directory if working with a linked worktree. If working with the main + * worktree, both values will be the same. + * + * This is used by backends that store references in the repository directly. + */ +void refs_compute_filesystem_location(const char *gitdir, const char *payload, + bool *is_worktree, struct strbuf *refdir, + struct strbuf *ref_common_dir); + #endif /* REFS_REFS_INTERNAL_H */
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index eeec647..b124404 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c
@@ -10,9 +10,10 @@ #include "../gettext.h" #include "../hash.h" #include "../hex.h" -#include "../iterator.h" #include "../ident.h" +#include "../iterator.h" #include "../object.h" +#include "../parse.h" #include "../path.h" #include "../refs.h" #include "../reftable/reftable-basics.h" @@ -25,8 +26,8 @@ #include "../setup.h" #include "../strmap.h" #include "../trace2.h" +#include "../worktree.h" #include "../write-or-die.h" -#include "parse.h" #include "refs-internal.h" /* @@ -172,6 +173,37 @@ static struct reftable_ref_store *reftable_be_downcast(struct ref_store *ref_sto return refs; } +static int backend_for_worktree(struct reftable_backend **out, + struct reftable_ref_store *store, + const char *worktree_name) +{ + struct strbuf worktree_dir = STRBUF_INIT; + int ret; + + *out = strmap_get(&store->worktree_backends, worktree_name); + if (*out) { + ret = 0; + goto out; + } + + strbuf_addf(&worktree_dir, "%s/worktrees/%s/reftable", + store->base.repo->commondir, worktree_name); + + CALLOC_ARRAY(*out, 1); + store->err = ret = reftable_backend_init(*out, worktree_dir.buf, + &store->write_options); + if (ret < 0) { + free(*out); + goto out; + } + + strmap_put(&store->worktree_backends, worktree_name, *out); + +out: + strbuf_release(&worktree_dir); + return ret; +} + /* * Some refs are global to the repository (refs/heads/{*}), while others are * local to the worktree (eg. HEAD, refs/bisect/{*}). We solve this by having @@ -191,19 +223,19 @@ static int backend_for(struct reftable_backend **out, const char **rewritten_ref, int reload) { - struct reftable_backend *be; const char *wtname; int wtname_len; + int ret; if (!refname) { - be = &store->main_backend; + *out = &store->main_backend; + ret = 0; goto out; } switch (parse_worktree_ref(refname, &wtname, &wtname_len, rewritten_ref)) { case REF_WORKTREE_OTHER: { static struct strbuf wtname_buf = STRBUF_INIT; - struct strbuf wt_dir = STRBUF_INIT; /* * We're using a static buffer here so that we don't need to @@ -223,20 +255,8 @@ static int backend_for(struct reftable_backend **out, * already and error out when trying to write a reference via * both stacks. */ - be = strmap_get(&store->worktree_backends, wtname_buf.buf); - if (!be) { - strbuf_addf(&wt_dir, "%s/worktrees/%s/reftable", - store->base.repo->commondir, wtname_buf.buf); + ret = backend_for_worktree(out, store, wtname_buf.buf); - CALLOC_ARRAY(be, 1); - store->err = reftable_backend_init(be, wt_dir.buf, - &store->write_options); - assert(store->err != REFTABLE_API_ERROR); - - strmap_put(&store->worktree_backends, wtname_buf.buf, be); - } - - strbuf_release(&wt_dir); goto out; } case REF_WORKTREE_CURRENT: @@ -245,27 +265,24 @@ static int backend_for(struct reftable_backend **out, * main worktree. We thus return the main stack in that case. */ if (!store->worktree_backend.stack) - be = &store->main_backend; + *out = &store->main_backend; else - be = &store->worktree_backend; + *out = &store->worktree_backend; + ret = 0; goto out; case REF_WORKTREE_MAIN: case REF_WORKTREE_SHARED: - be = &store->main_backend; + *out = &store->main_backend; + ret = 0; goto out; default: BUG("unhandled worktree reference type"); } out: - if (reload) { - int ret = reftable_stack_reload(be->stack); - if (ret) - return ret; - } - *out = be; - - return 0; + if (reload && !ret) + ret = reftable_stack_reload((*out)->stack); + return ret; } static int should_write_log(struct reftable_ref_store *refs, const char *refname) @@ -355,18 +372,24 @@ static int reftable_be_fsync(int fd) } static struct ref_store *reftable_be_init(struct repository *repo, + const char *payload, const char *gitdir, unsigned int store_flags) { struct reftable_ref_store *refs = xcalloc(1, sizeof(*refs)); + struct strbuf ref_common_dir = STRBUF_INIT; + struct strbuf refdir = STRBUF_INIT; struct strbuf path = STRBUF_INIT; - int is_worktree; + bool is_worktree; mode_t mask; mask = umask(0); umask(mask); - base_ref_store_init(&refs->base, repo, gitdir, &refs_be_reftable); + refs_compute_filesystem_location(gitdir, payload, &is_worktree, &refdir, + &ref_common_dir); + + base_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable); strmap_init(&refs->worktree_backends); refs->store_flags = store_flags; refs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo); @@ -402,14 +425,11 @@ static struct ref_store *reftable_be_init(struct repository *repo, /* * Set up the main reftable stack that is hosted in GIT_COMMON_DIR. * This stack contains both the shared and the main worktree refs. - * - * Note that we don't try to resolve the path in case we have a - * worktree because `get_common_dir_noenv()` already does it for us. */ - is_worktree = get_common_dir_noenv(&path, gitdir); + strbuf_addbuf(&path, &ref_common_dir); if (!is_worktree) { strbuf_reset(&path); - strbuf_realpath(&path, gitdir, 0); + strbuf_realpath(&path, ref_common_dir.buf, 0); } strbuf_addstr(&path, "/reftable"); refs->err = reftable_backend_init(&refs->main_backend, path.buf, @@ -426,10 +446,9 @@ static struct ref_store *reftable_be_init(struct repository *repo, * do it efficiently. */ if (is_worktree) { - strbuf_reset(&path); - strbuf_addf(&path, "%s/reftable", gitdir); + strbuf_addstr(&refdir, "/reftable"); - refs->err = reftable_backend_init(&refs->worktree_backend, path.buf, + refs->err = reftable_backend_init(&refs->worktree_backend, refdir.buf, &refs->write_options); if (refs->err) goto done; @@ -439,6 +458,8 @@ static struct ref_store *reftable_be_init(struct repository *repo, done: assert(refs->err != REFTABLE_API_ERROR); + strbuf_release(&ref_common_dir); + strbuf_release(&refdir); strbuf_release(&path); return &refs->base; } @@ -474,19 +495,6 @@ static int reftable_be_create_on_disk(struct ref_store *ref_store, safe_create_dir(the_repository, sb.buf, 1); strbuf_reset(&sb); - strbuf_addf(&sb, "%s/HEAD", refs->base.gitdir); - write_file(sb.buf, "ref: refs/heads/.invalid"); - adjust_shared_perm(the_repository, sb.buf); - strbuf_reset(&sb); - - strbuf_addf(&sb, "%s/refs", refs->base.gitdir); - safe_create_dir(the_repository, sb.buf, 1); - strbuf_reset(&sb); - - strbuf_addf(&sb, "%s/refs/heads", refs->base.gitdir); - write_file(sb.buf, "this repository uses the reftable format"); - adjust_shared_perm(the_repository, sb.buf); - strbuf_release(&sb); return 0; } @@ -512,30 +520,6 @@ static int reftable_be_remove_on_disk(struct ref_store *ref_store, strerror(errno)); ret = -1; } - strbuf_reset(&sb); - - strbuf_addf(&sb, "%s/HEAD", refs->base.gitdir); - if (unlink(sb.buf) < 0) { - strbuf_addf(err, "could not delete stub HEAD: %s", - strerror(errno)); - ret = -1; - } - strbuf_reset(&sb); - - strbuf_addf(&sb, "%s/refs/heads", refs->base.gitdir); - if (unlink(sb.buf) < 0) { - strbuf_addf(err, "could not delete stub heads: %s", - strerror(errno)); - ret = -1; - } - strbuf_reset(&sb); - - strbuf_addf(&sb, "%s/refs", refs->base.gitdir); - if (rmdir(sb.buf) < 0) { - strbuf_addf(err, "could not delete refs directory: %s", - strerror(errno)); - ret = -1; - } strbuf_release(&sb); return ret; @@ -547,6 +531,7 @@ struct reftable_ref_iterator { struct reftable_iterator iter; struct reftable_ref_record ref; struct object_id oid; + struct object_id peeled_oid; char *prefix; size_t prefix_len; @@ -644,7 +629,7 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator) * the root refs are to be included. We emulate the same behaviour here. */ if (!starts_with(iter->ref.refname, "refs/") && - !(iter->flags & DO_FOR_EACH_INCLUDE_ROOT_REFS && + !(iter->flags & REFS_FOR_EACH_INCLUDE_ROOT_REFS && is_root_ref(iter->ref.refname))) { continue; } @@ -658,7 +643,7 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator) if (iter->exclude_patterns && should_exclude_current_ref(iter)) continue; - if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY && + if (iter->flags & REFS_FOR_EACH_PER_WORKTREE_ONLY && parse_worktree_ref(iter->ref.refname, NULL, NULL, NULL) != REF_WORKTREE_CURRENT) continue; @@ -671,6 +656,8 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator) case REFTABLE_REF_VAL2: oidread(&iter->oid, iter->ref.value.val2.value, refs->base.repo->hash_algo); + oidread(&iter->peeled_oid, iter->ref.value.val2.target_value, + refs->base.repo->hash_algo); break; case REFTABLE_REF_SYMREF: referent = refs_resolve_ref_unsafe(&iter->refs->base, @@ -694,20 +681,23 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator) flags |= REF_BAD_NAME | REF_ISBROKEN; } - if (iter->flags & DO_FOR_EACH_OMIT_DANGLING_SYMREFS && + if (iter->flags & REFS_FOR_EACH_OMIT_DANGLING_SYMREFS && flags & REF_ISSYMREF && flags & REF_ISBROKEN) continue; - if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) && + if (!(iter->flags & REFS_FOR_EACH_INCLUDE_BROKEN) && !ref_resolves_to_object(iter->ref.refname, refs->base.repo, &iter->oid, flags)) continue; - iter->base.refname = iter->ref.refname; - iter->base.referent = referent; - iter->base.oid = &iter->oid; - iter->base.flags = flags; + memset(&iter->base.ref, 0, sizeof(iter->base.ref)); + iter->base.ref.name = iter->ref.refname; + iter->base.ref.target = referent; + iter->base.ref.oid = &iter->oid; + if (iter->ref.value_type == REFTABLE_REF_VAL2) + iter->base.ref.peeled_oid = &iter->peeled_oid; + iter->base.ref.flags = flags; break; } @@ -738,21 +728,6 @@ static int reftable_ref_iterator_seek(struct ref_iterator *ref_iterator, return iter->err; } -static int reftable_ref_iterator_peel(struct ref_iterator *ref_iterator, - struct object_id *peeled) -{ - struct reftable_ref_iterator *iter = - (struct reftable_ref_iterator *)ref_iterator; - - if (iter->ref.value_type == REFTABLE_REF_VAL2) { - oidread(peeled, iter->ref.value.val2.target_value, - iter->refs->base.repo->hash_algo); - return 0; - } - - return -1; -} - static void reftable_ref_iterator_release(struct ref_iterator *ref_iterator) { struct reftable_ref_iterator *iter = @@ -770,7 +745,6 @@ static void reftable_ref_iterator_release(struct ref_iterator *ref_iterator) static struct ref_iterator_vtable reftable_ref_iterator_vtable = { .advance = reftable_ref_iterator_advance, .seek = reftable_ref_iterator_seek, - .peel = reftable_ref_iterator_peel, .release = reftable_ref_iterator_release, }; @@ -828,7 +802,7 @@ static struct reftable_ref_iterator *ref_iterator_for_stack(struct reftable_ref_ iter = xcalloc(1, sizeof(*iter)); base_ref_iterator_init(&iter->base, &reftable_ref_iterator_vtable); - iter->base.oid = &iter->oid; + iter->base.ref.oid = &iter->oid; iter->flags = flags; iter->refs = refs; iter->exclude_patterns = filter_exclude_patterns(exclude_patterns); @@ -864,7 +838,7 @@ static struct ref_iterator *reftable_be_iterator_begin(struct ref_store *ref_sto struct reftable_ref_store *refs; unsigned int required_flags = REF_STORE_READ; - if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) + if (!(flags & REFS_FOR_EACH_INCLUDE_BROKEN)) required_flags |= REF_STORE_ODB; refs = reftable_be_downcast(ref_store, required_flags, "ref_iterator_begin"); @@ -1411,10 +1385,9 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store, &refnames_to_check, head_type, &head_referent, &referent, err); if (ret) { - if (ref_transaction_maybe_set_rejected(transaction, i, ret)) { - strbuf_reset(err); + if (ref_transaction_maybe_set_rejected(transaction, i, + ret, err)) { ret = 0; - continue; } goto done; @@ -1642,7 +1615,8 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data ref.refname = (char *)u->refname; ref.update_index = ts; - peel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled); + peel_error = peel_object(arg->refs->base.repo, &u->new_oid, &peeled, + PEEL_OBJECT_VERIFY_TAGGED_OBJECT_TYPE); if (!peel_error) { ref.value_type = REFTABLE_REF_VAL2; memcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ); @@ -1709,11 +1683,11 @@ static int reftable_be_transaction_finish(struct ref_store *ref_store UNUSED, return ret; } -static int reftable_be_pack_refs(struct ref_store *ref_store, - struct pack_refs_opts *opts) +static int reftable_be_optimize(struct ref_store *ref_store, + struct refs_optimize_opts *opts) { struct reftable_ref_store *refs = - reftable_be_downcast(ref_store, REF_STORE_WRITE | REF_STORE_ODB, "pack_refs"); + reftable_be_downcast(ref_store, REF_STORE_WRITE | REF_STORE_ODB, "optimize_refs"); struct reftable_stack *stack; int ret; @@ -1724,7 +1698,7 @@ static int reftable_be_pack_refs(struct ref_store *ref_store, if (!stack) stack = refs->main_backend.stack; - if (opts->flags & PACK_REFS_AUTO) + if (opts->flags & REFS_OPTIMIZE_AUTO) ret = reftable_stack_auto_compact(stack); else ret = reftable_stack_compact_all(stack, NULL); @@ -1742,10 +1716,27 @@ static int reftable_be_pack_refs(struct ref_store *ref_store, return ret; } -static int reftable_be_optimize(struct ref_store *ref_store, - struct pack_refs_opts *opts) +static int reftable_be_optimize_required(struct ref_store *ref_store, + struct refs_optimize_opts *opts, + bool *required) { - return reftable_be_pack_refs(ref_store, opts); + struct reftable_ref_store *refs = reftable_be_downcast(ref_store, REF_STORE_READ, + "optimize_refs_required"); + struct reftable_stack *stack; + bool use_heuristics = false; + + if (refs->err) + return refs->err; + + stack = refs->worktree_backend.stack; + if (!stack) + stack = refs->main_backend.stack; + + if (opts->flags & REFS_OPTIMIZE_AUTO) + use_heuristics = true; + + return reftable_stack_compaction_required(stack, use_heuristics, + required); } struct write_create_symref_arg { @@ -2072,7 +2063,7 @@ static int reftable_reflog_iterator_advance(struct ref_iterator *ref_iterator) strbuf_reset(&iter->last_name); strbuf_addstr(&iter->last_name, iter->log.refname); - iter->base.refname = iter->log.refname; + iter->base.ref.name = iter->log.refname; break; } @@ -2092,13 +2083,6 @@ static int reftable_reflog_iterator_seek(struct ref_iterator *ref_iterator UNUSE return -1; } -static int reftable_reflog_iterator_peel(struct ref_iterator *ref_iterator UNUSED, - struct object_id *peeled UNUSED) -{ - BUG("reftable reflog iterator cannot be peeled"); - return -1; -} - static void reftable_reflog_iterator_release(struct ref_iterator *ref_iterator) { struct reftable_reflog_iterator *iter = @@ -2111,7 +2095,6 @@ static void reftable_reflog_iterator_release(struct ref_iterator *ref_iterator) static struct ref_iterator_vtable reftable_reflog_iterator_vtable = { .advance = reftable_reflog_iterator_advance, .seek = reftable_reflog_iterator_seek, - .peel = reftable_reflog_iterator_peel, .release = reftable_reflog_iterator_release, }; @@ -2515,7 +2498,7 @@ static int write_reflog_expiry_table(struct reftable_writer *writer, void *cb_da ref.refname = (char *)arg->refname; ref.update_index = ts; - if (!peel_object(arg->refs->base.repo, &arg->update_oid, &peeled)) { + if (!peel_object(arg->refs->base.repo, &arg->update_oid, &peeled, 0)) { ref.value_type = REFTABLE_REF_VAL2; memcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ); memcpy(ref.value.val2.value, arg->update_oid.hash, GIT_MAX_RAWSZ); @@ -2746,24 +2729,92 @@ static int reftable_fsck_error_handler(struct reftable_fsck_info *info, } static int reftable_be_fsck(struct ref_store *ref_store, struct fsck_options *o, - struct worktree *wt UNUSED) + struct worktree *wt) { - struct reftable_ref_store *refs; - struct strmap_entry *entry; - struct hashmap_iter iter; - int ret = 0; + struct reftable_ref_store *refs = + reftable_be_downcast(ref_store, REF_STORE_READ, "fsck"); + struct reftable_ref_iterator *iter = NULL; + struct reftable_ref_record ref = { 0 }; + struct fsck_ref_report report = { 0 }; + struct strbuf refname = STRBUF_INIT; + struct reftable_backend *backend; + int ret, errors = 0; - refs = reftable_be_downcast(ref_store, REF_STORE_READ, "fsck"); - - ret |= reftable_fsck_check(refs->main_backend.stack, reftable_fsck_error_handler, - reftable_fsck_verbose_handler, o); - - strmap_for_each_entry(&refs->worktree_backends, &iter, entry) { - struct reftable_backend *b = (struct reftable_backend *)entry->value; - ret |= reftable_fsck_check(b->stack, reftable_fsck_error_handler, - reftable_fsck_verbose_handler, o); + if (is_main_worktree(wt)) { + backend = &refs->main_backend; + } else { + ret = backend_for_worktree(&backend, refs, wt->id); + if (ret < 0) { + ret = error(_("reftable stack for worktree '%s' is broken"), + wt->id); + goto out; + } } + errors |= reftable_fsck_check(backend->stack, reftable_fsck_error_handler, + reftable_fsck_verbose_handler, o); + + iter = ref_iterator_for_stack(refs, backend->stack, "", NULL, 0); + if (!iter) { + ret = error(_("could not create iterator for worktree '%s'"), wt->id); + goto out; + } + + while (1) { + ret = reftable_iterator_next_ref(&iter->iter, &ref); + if (ret > 0) + break; + if (ret < 0) { + ret = error(_("could not read record for worktree '%s'"), wt->id); + goto out; + } + + strbuf_reset(&refname); + if (!is_main_worktree(wt)) + strbuf_addf(&refname, "worktrees/%s/", wt->id); + strbuf_addstr(&refname, ref.refname); + report.path = refname.buf; + + switch (ref.value_type) { + case REFTABLE_REF_VAL1: + case REFTABLE_REF_VAL2: { + struct object_id oid; + unsigned hash_id; + + switch (reftable_stack_hash_id(backend->stack)) { + case REFTABLE_HASH_SHA1: + hash_id = GIT_HASH_SHA1; + break; + case REFTABLE_HASH_SHA256: + hash_id = GIT_HASH_SHA256; + break; + default: + BUG("unhandled hash ID %d", + reftable_stack_hash_id(backend->stack)); + } + + oidread(&oid, reftable_ref_record_val1(&ref), + &hash_algos[hash_id]); + + errors |= refs_fsck_ref(ref_store, o, &report, ref.refname, &oid); + break; + } + case REFTABLE_REF_SYMREF: + errors |= refs_fsck_symref(ref_store, o, &report, ref.refname, + ref.value.symref); + break; + default: + BUG("unhandled reference value type %d", ref.value_type); + } + } + + ret = errors ? -1 : 0; + +out: + if (iter) + ref_iterator_free(&iter->base); + reftable_ref_record_release(&ref); + strbuf_release(&refname); return ret; } @@ -2778,8 +2829,9 @@ struct ref_storage_be refs_be_reftable = { .transaction_finish = reftable_be_transaction_finish, .transaction_abort = reftable_be_transaction_abort, - .pack_refs = reftable_be_pack_refs, .optimize = reftable_be_optimize, + .optimize_required = reftable_be_optimize_required, + .rename_ref = reftable_be_rename_ref, .copy_ref = reftable_be_copy_ref,
diff --git a/refspec.c b/refspec.c index 0775358..fb89bce 100644 --- a/refspec.c +++ b/refspec.c
@@ -85,7 +85,7 @@ static int parse_refspec(struct refspec_item *item, const char *refspec, int fet if (!*item->src) return 0; /* negative refspecs must not be empty */ else if (llen == the_hash_algo->hexsz && !get_oid_hex(item->src, &unused)) - return 0; /* negative refpsecs cannot be exact sha1 */ + return 0; /* negative refspecs cannot be exact sha1 */ else if (!check_refname_format(item->src, flags)) ; /* valid looking ref is ok */ else
diff --git a/reftable/reftable-stack.h b/reftable/reftable-stack.h index d70fcb7..c2415cb 100644 --- a/reftable/reftable-stack.h +++ b/reftable/reftable-stack.h
@@ -123,6 +123,17 @@ struct reftable_log_expiry_config { int reftable_stack_compact_all(struct reftable_stack *st, struct reftable_log_expiry_config *config); +/* + * Check if compaction is required. + * + * When `use_heuristics` is false, check if all tables can be compacted to a + * single table. If true, use heuristics to determine if the tables need to be + * compacted to maintain geometric progression. + */ +int reftable_stack_compaction_required(struct reftable_stack *st, + bool use_heuristics, + bool *required); + /* heuristically compact unbalanced table stack. */ int reftable_stack_auto_compact(struct reftable_stack *st);
diff --git a/reftable/stack.c b/reftable/stack.c index 65d8982..1c9f21d 100644 --- a/reftable/stack.c +++ b/reftable/stack.c
@@ -1626,7 +1626,8 @@ struct segment suggest_compaction_segment(uint64_t *sizes, size_t n, return seg; } -static uint64_t *stack_table_sizes_for_compaction(struct reftable_stack *st) +static int stack_segments_for_compaction(struct reftable_stack *st, + struct segment *seg) { int version = (st->opts.hash_id == REFTABLE_HASH_SHA1) ? 1 : 2; int overhead = header_size(version) - 1; @@ -1634,31 +1635,63 @@ static uint64_t *stack_table_sizes_for_compaction(struct reftable_stack *st) REFTABLE_CALLOC_ARRAY(sizes, st->merged->tables_len); if (!sizes) - return NULL; + return REFTABLE_OUT_OF_MEMORY_ERROR; for (size_t i = 0; i < st->merged->tables_len; i++) sizes[i] = st->tables[i]->size - overhead; - return sizes; + *seg = suggest_compaction_segment(sizes, st->merged->tables_len, + st->opts.auto_compaction_factor); + reftable_free(sizes); + + return 0; +} + +static int update_segment_if_compaction_required(struct reftable_stack *st, + struct segment *seg, + bool use_geometric, + bool *required) +{ + int err; + + if (st->merged->tables_len < 2) { + *required = false; + return 0; + } + + if (!use_geometric) { + *required = true; + return 0; + } + + err = stack_segments_for_compaction(st, seg); + if (err) + return err; + + *required = segment_size(seg) > 0; + return 0; +} + +int reftable_stack_compaction_required(struct reftable_stack *st, + bool use_heuristics, + bool *required) +{ + struct segment seg; + return update_segment_if_compaction_required(st, &seg, use_heuristics, + required); } int reftable_stack_auto_compact(struct reftable_stack *st) { struct segment seg; - uint64_t *sizes; + bool required; + int err; - if (st->merged->tables_len < 2) - return 0; + err = update_segment_if_compaction_required(st, &seg, true, &required); + if (err) + return err; - sizes = stack_table_sizes_for_compaction(st); - if (!sizes) - return REFTABLE_OUT_OF_MEMORY_ERROR; - - seg = suggest_compaction_segment(sizes, st->merged->tables_len, - st->opts.auto_compaction_factor); - reftable_free(sizes); - - if (segment_size(&seg) > 0) + if (required) return stack_compact_range(st, seg.start, seg.end - 1, NULL, STACK_COMPACT_RANGE_BEST_EFFORT);
diff --git a/remote-curl.c b/remote-curl.c index 69f9194..aba60d5 100644 --- a/remote-curl.c +++ b/remote-curl.c
@@ -529,6 +529,17 @@ static struct discovery *discover_refs(const char *service, int for_push) show_http_message(&type, &charset, &buffer); die(_("unable to access '%s' with http.pinnedPubkey configuration: %s"), transport_anonymize_url(url.buf), curl_errorstr); + case HTTP_RATE_LIMITED: + if (http_options.retry_after > 0) { + show_http_message(&type, &charset, &buffer); + die(_("rate limited by '%s', please try again in %ld seconds"), + transport_anonymize_url(url.buf), + http_options.retry_after); + } else { + show_http_message(&type, &charset, &buffer); + die(_("rate limited by '%s', please try again later"), + transport_anonymize_url(url.buf)); + } default: show_http_message(&type, &charset, &buffer); die(_("unable to access '%s': %s"), @@ -876,6 +887,7 @@ static int probe_rpc(struct rpc_state *rpc, struct slot_results *results) headers = curl_slist_append(headers, rpc->hdr_content_type); headers = curl_slist_append(headers, rpc->hdr_accept); + headers = http_append_auth_header(&http_auth, headers); curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0L); curl_easy_setopt(slot->curl, CURLOPT_POST, 1L); @@ -1551,6 +1563,13 @@ int cmd_main(int argc, const char **argv) goto cleanup; } + /* + * yuck, see 9e89dcb66a (builtin/ls-remote: fall back to SHA1 outside + * of a repo, 2024-08-02) + */ + if (nongit) + repo_set_hash_algo(the_repository, GIT_HASH_DEFAULT); + options.verbosity = 1; options.progress = !!isatty(2); options.thin = 1;
diff --git a/remote.c b/remote.c index df9675c..7ca2a65 100644 --- a/remote.c +++ b/remote.c
@@ -29,6 +29,12 @@ enum map_direction { FROM_SRC, FROM_DST }; +enum { + ENABLE_ADVICE_PULL = (1 << 0), + ENABLE_ADVICE_PUSH = (1 << 1), + ENABLE_ADVICE_DIVERGENCE = (1 << 2), +}; + struct counted_string { size_t len; const char *s; @@ -272,6 +278,7 @@ static void branch_release(struct branch *branch) free((char *)branch->refname); free(branch->remote_name); free(branch->pushremote_name); + free(branch->push_tracking_ref); merge_clear(branch); } @@ -1381,12 +1388,7 @@ static struct ref **tail_ref(struct ref **head) return tail; } -struct tips { - struct commit **tip; - size_t nr, alloc; -}; - -static void add_to_tips(struct tips *tips, const struct object_id *oid) +static void add_to_tips(struct commit_stack *tips, const struct object_id *oid) { struct commit *commit; @@ -1396,8 +1398,7 @@ static void add_to_tips(struct tips *tips, const struct object_id *oid) if (!commit || (commit->object.flags & TMP_MARK)) return; commit->object.flags |= TMP_MARK; - ALLOC_GROW(tips->tip, tips->nr + 1, tips->alloc); - tips->tip[tips->nr++] = commit; + commit_stack_push(tips, commit); } static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***dst_tail) @@ -1406,13 +1407,12 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds struct string_list src_tag = STRING_LIST_INIT_NODUP; struct string_list_item *item; struct ref *ref; - struct tips sent_tips; + struct commit_stack sent_tips = COMMIT_STACK_INIT; /* * Collect everything we know they would have at the end of * this push, and collect all tags they have. */ - memset(&sent_tips, 0, sizeof(sent_tips)); for (ref = *dst; ref; ref = ref->next) { if (ref->peer_ref && !is_null_oid(&ref->peer_ref->new_oid)) @@ -1422,7 +1422,7 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds if (starts_with(ref->name, "refs/tags/")) string_list_append(&dst_tag, ref->name); } - clear_commit_marks_many(sent_tips.nr, sent_tips.tip, TMP_MARK); + clear_commit_marks_many(sent_tips.nr, sent_tips.items, TMP_MARK); string_list_sort(&dst_tag); @@ -1450,9 +1450,7 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds if (sent_tips.nr) { const int reachable_flag = 1; struct commit_list *found_commits; - struct commit **src_commits; - size_t nr_src_commits = 0, alloc_src_commits = 16; - ALLOC_ARRAY(src_commits, alloc_src_commits); + struct commit_stack src_commits = COMMIT_STACK_INIT; for_each_string_list_item(item, &src_tag) { struct ref *ref = item->util; @@ -1467,12 +1465,13 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds /* not pushing a commit, which is not an error */ continue; - ALLOC_GROW(src_commits, nr_src_commits + 1, alloc_src_commits); - src_commits[nr_src_commits++] = commit; + commit_stack_push(&src_commits, commit); } - found_commits = get_reachable_subset(sent_tips.tip, sent_tips.nr, - src_commits, nr_src_commits, + found_commits = get_reachable_subset(sent_tips.items, + sent_tips.nr, + src_commits.items, + src_commits.nr, reachable_flag); for_each_string_list_item(item, &src_tag) { @@ -1502,13 +1501,14 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds dst_ref->peer_ref = copy_ref(ref); } - clear_commit_marks_many(nr_src_commits, src_commits, reachable_flag); - free(src_commits); - free_commit_list(found_commits); + clear_commit_marks_many(src_commits.nr, src_commits.items, + reachable_flag); + commit_stack_clear(&src_commits); + commit_list_free(found_commits); } string_list_clear(&src_tag, 0); - free(sent_tips.tip); + commit_stack_clear(&sent_tips); } struct ref *find_ref_by_name(const struct ref *list, const char *name) @@ -1838,7 +1838,7 @@ int branch_merge_matches(struct branch *branch, } __attribute__((format (printf,2,3))) -static const char *error_buf(struct strbuf *err, const char *fmt, ...) +static char *error_buf(struct strbuf *err, const char *fmt, ...) { if (err) { va_list ap; @@ -1876,9 +1876,9 @@ const char *branch_get_upstream(struct branch *branch, struct strbuf *err) return branch->merge[0]->dst; } -static const char *tracking_for_push_dest(struct remote *remote, - const char *refname, - struct strbuf *err) +static char *tracking_for_push_dest(struct remote *remote, + const char *refname, + struct strbuf *err) { char *ret; @@ -1890,8 +1890,8 @@ static const char *tracking_for_push_dest(struct remote *remote, return ret; } -static const char *branch_get_push_1(struct repository *repo, - struct branch *branch, struct strbuf *err) +static char *branch_get_push_1(struct repository *repo, + struct branch *branch, struct strbuf *err) { struct remote_state *remote_state = repo->remote_state; struct remote *remote; @@ -1906,7 +1906,7 @@ static const char *branch_get_push_1(struct repository *repo, if (remote->push.nr) { char *dst; - const char *ret; + char *ret; dst = apply_refspecs(&remote->push, branch->refname); if (!dst) @@ -1931,12 +1931,13 @@ static const char *branch_get_push_1(struct repository *repo, return tracking_for_push_dest(remote, branch->refname, err); case PUSH_DEFAULT_UPSTREAM: - return branch_get_upstream(branch, err); + return xstrdup_or_null(branch_get_upstream(branch, err)); case PUSH_DEFAULT_UNSPECIFIED: case PUSH_DEFAULT_SIMPLE: { - const char *up, *cur; + const char *up; + char *cur; up = branch_get_upstream(branch, err); if (!up) @@ -1944,9 +1945,11 @@ static const char *branch_get_push_1(struct repository *repo, cur = tracking_for_push_dest(remote, branch->refname, err); if (!cur) return NULL; - if (strcmp(cur, up)) + if (strcmp(cur, up)) { + free(cur); return error_buf(err, _("cannot resolve 'simple' push to a single destination")); + } return cur; } } @@ -2237,43 +2240,49 @@ int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs, return stat_branch_pair(branch->refname, base, num_ours, num_theirs, abf); } -/* - * Return true when there is anything to report, otherwise false. - */ -int format_tracking_info(struct branch *branch, struct strbuf *sb, - enum ahead_behind_flags abf, - int show_divergence_advice) +static char *resolve_compare_branch(struct branch *branch, const char *name) { - int ours, theirs, sti; - const char *full_base; - char *base; - int upstream_is_gone = 0; + const char *resolved = NULL; - sti = stat_tracking_info(branch, &ours, &theirs, &full_base, 0, abf); - if (sti < 0) { - if (!full_base) - return 0; - upstream_is_gone = 1; + if (!branch || !name) + return NULL; + + if (!strcasecmp(name, "@{upstream}")) { + resolved = branch_get_upstream(branch, NULL); + } else if (!strcasecmp(name, "@{push}")) { + resolved = branch_get_push(branch, NULL); + } else { + warning(_("ignoring value '%s' for status.compareBranches, " + "only @{upstream} and @{push} are supported"), + name); + return NULL; } - base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository), - full_base, 0); - if (upstream_is_gone) { - strbuf_addf(sb, - _("Your branch is based on '%s', but the upstream is gone.\n"), - base); - if (advice_enabled(ADVICE_STATUS_HINTS)) - strbuf_addstr(sb, - _(" (use \"git branch --unset-upstream\" to fixup)\n")); - } else if (!sti) { + if (resolved) + return xstrdup(resolved); + return NULL; +} + +static void format_branch_comparison(struct strbuf *sb, + bool up_to_date, + int ours, int theirs, + const char *branch_name, + enum ahead_behind_flags abf, + unsigned flags) +{ + bool use_push_advice = (flags & ENABLE_ADVICE_PUSH); + bool use_pull_advice = (flags & ENABLE_ADVICE_PULL); + bool use_divergence_advice = (flags & ENABLE_ADVICE_DIVERGENCE); + + if (up_to_date) { strbuf_addf(sb, _("Your branch is up to date with '%s'.\n"), - base); + branch_name); } else if (abf == AHEAD_BEHIND_QUICK) { strbuf_addf(sb, _("Your branch and '%s' refer to different commits.\n"), - base); - if (advice_enabled(ADVICE_STATUS_HINTS)) + branch_name); + if (use_push_advice && advice_enabled(ADVICE_STATUS_HINTS)) strbuf_addf(sb, _(" (use \"%s\" for details)\n"), "git status --ahead-behind"); } else if (!theirs) { @@ -2281,8 +2290,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb, Q_("Your branch is ahead of '%s' by %d commit.\n", "Your branch is ahead of '%s' by %d commits.\n", ours), - base, ours); - if (advice_enabled(ADVICE_STATUS_HINTS)) + branch_name, ours); + if (use_push_advice && advice_enabled(ADVICE_STATUS_HINTS)) strbuf_addstr(sb, _(" (use \"git push\" to publish your local commits)\n")); } else if (!ours) { @@ -2292,8 +2301,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb, "Your branch is behind '%s' by %d commits, " "and can be fast-forwarded.\n", theirs), - base, theirs); - if (advice_enabled(ADVICE_STATUS_HINTS)) + branch_name, theirs); + if (use_pull_advice && advice_enabled(ADVICE_STATUS_HINTS)) strbuf_addstr(sb, _(" (use \"git pull\" to update your local branch)\n")); } else { @@ -2305,31 +2314,121 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb, "and have %d and %d different commits each, " "respectively.\n", ours + theirs), - base, ours, theirs); - if (show_divergence_advice && - advice_enabled(ADVICE_STATUS_HINTS)) + branch_name, ours, theirs); + if (use_divergence_advice && advice_enabled(ADVICE_STATUS_HINTS)) strbuf_addstr(sb, _(" (use \"git pull\" if you want to integrate the remote branch with yours)\n")); } - free(base); - return 1; } -static int one_local_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid, - int flag UNUSED, - void *cb_data) +/* + * Return true when there is anything to report, otherwise false. + */ +int format_tracking_info(struct branch *branch, struct strbuf *sb, + enum ahead_behind_flags abf, + int show_divergence_advice) +{ + char *compare_branches = NULL; + struct string_list branches = STRING_LIST_INIT_DUP; + struct strset processed_refs = STRSET_INIT; + int reported = 0; + size_t i; + const char *upstream_ref; + const char *push_ref; + + repo_config_get_string(the_repository, "status.comparebranches", + &compare_branches); + + if (compare_branches) { + string_list_split(&branches, compare_branches, " ", -1); + string_list_remove_empty_items(&branches, 0); + } else { + string_list_append(&branches, "@{upstream}"); + } + + upstream_ref = branch_get_upstream(branch, NULL); + push_ref = branch_get_push(branch, NULL); + + for (i = 0; i < branches.nr; i++) { + char *full_ref; + char *short_ref; + int ours, theirs, cmp; + int is_upstream, is_push; + unsigned flags = 0; + + full_ref = resolve_compare_branch(branch, + branches.items[i].string); + if (!full_ref) + continue; + + if (!strset_add(&processed_refs, full_ref)) { + free(full_ref); + continue; + } + + short_ref = refs_shorten_unambiguous_ref( + get_main_ref_store(the_repository), full_ref, 0); + + is_upstream = upstream_ref && !strcmp(full_ref, upstream_ref); + is_push = push_ref && !strcmp(full_ref, push_ref); + + if (is_upstream && (!push_ref || !strcmp(upstream_ref, push_ref))) + is_push = 1; + + cmp = stat_branch_pair(branch->refname, full_ref, + &ours, &theirs, abf); + + if (cmp < 0) { + if (is_upstream) { + strbuf_addf(sb, + _("Your branch is based on '%s', but the upstream is gone.\n"), + short_ref); + if (advice_enabled(ADVICE_STATUS_HINTS)) + strbuf_addstr(sb, + _(" (use \"git branch --unset-upstream\" to fixup)\n")); + reported = 1; + } + free(full_ref); + free(short_ref); + continue; + } + + if (reported) + strbuf_addstr(sb, "\n"); + + if (is_upstream) + flags |= ENABLE_ADVICE_PULL; + if (is_push) + flags |= ENABLE_ADVICE_PUSH; + if (show_divergence_advice && is_upstream) + flags |= ENABLE_ADVICE_DIVERGENCE; + format_branch_comparison(sb, !cmp, ours, theirs, short_ref, + abf, flags); + reported = 1; + + free(full_ref); + free(short_ref); + } + + string_list_clear(&branches, 0); + strset_clear(&processed_refs); + free(compare_branches); + return reported; +} + +static int one_local_ref(const struct reference *ref, void *cb_data) { struct ref ***local_tail = cb_data; - struct ref *ref; + struct ref *local_ref; /* we already know it starts with refs/ to get here */ - if (check_refname_format(refname + 5, 0)) + if (check_refname_format(ref->name + 5, 0)) return 0; - ref = alloc_ref(refname); - oidcpy(&ref->new_oid, oid); - **local_tail = ref; - *local_tail = &ref->next; + local_ref = alloc_ref(ref->name); + oidcpy(&local_ref->new_oid, ref->oid); + **local_tail = local_ref; + *local_tail = &local_ref->next; return 0; } @@ -2402,15 +2501,14 @@ struct stale_heads_info { struct refspec *rs; }; -static int get_stale_heads_cb(const char *refname, const char *referent UNUSED, const struct object_id *oid, - int flags, void *cb_data) +static int get_stale_heads_cb(const struct reference *ref, void *cb_data) { struct stale_heads_info *info = cb_data; struct string_list matches = STRING_LIST_INIT_DUP; struct refspec_item query; int i, stale = 1; memset(&query, 0, sizeof(struct refspec_item)); - query.dst = (char *)refname; + query.dst = (char *)ref->name; refspec_find_all_matches(info->rs, &query, &matches); if (matches.nr == 0) @@ -2423,7 +2521,7 @@ static int get_stale_heads_cb(const char *refname, const char *referent UNUSED, * overlapping refspecs, we need to go over all of the * matching refs. */ - if (flags & REF_ISSYMREF) + if (ref->flags & REF_ISSYMREF) goto clean_exit; for (i = 0; stale && i < matches.nr; i++) @@ -2431,8 +2529,8 @@ static int get_stale_heads_cb(const char *refname, const char *referent UNUSED, stale = 0; if (stale) { - struct ref *ref = make_linked_ref(refname, &info->stale_refs_tail); - oidcpy(&ref->new_oid, oid); + struct ref *linked_ref = make_linked_ref(ref->name, &info->stale_refs_tail); + oidcpy(&linked_ref->new_oid, ref->oid); } clean_exit: @@ -2547,36 +2645,9 @@ static int remote_tracking(struct remote *remote, const char *refname, return 0; } -/* - * The struct "reflog_commit_array" and related helper functions - * are used for collecting commits into an array during reflog - * traversals in "check_and_collect_until()". - */ -struct reflog_commit_array { - struct commit **item; - size_t nr, alloc; -}; - -#define REFLOG_COMMIT_ARRAY_INIT { 0 } - -/* Append a commit to the array. */ -static void append_commit(struct reflog_commit_array *arr, - struct commit *commit) -{ - ALLOC_GROW(arr->item, arr->nr + 1, arr->alloc); - arr->item[arr->nr++] = commit; -} - -/* Free and reset the array. */ -static void free_commit_array(struct reflog_commit_array *arr) -{ - FREE_AND_NULL(arr->item); - arr->nr = arr->alloc = 0; -} - struct check_and_collect_until_cb_data { struct commit *remote_commit; - struct reflog_commit_array *local_commits; + struct commit_stack *local_commits; timestamp_t remote_reflog_timestamp; }; @@ -2608,7 +2679,7 @@ static int check_and_collect_until(const char *refname UNUSED, return 1; if ((commit = lookup_commit_reference(the_repository, n_oid))) - append_commit(cb->local_commits, commit); + commit_stack_push(cb->local_commits, commit); /* * If the reflog entry timestamp is older than the remote ref's @@ -2636,7 +2707,7 @@ static int is_reachable_in_reflog(const char *local, const struct ref *remote) struct commit *commit; struct commit **chunk; struct check_and_collect_until_cb_data cb; - struct reflog_commit_array arr = REFLOG_COMMIT_ARRAY_INIT; + struct commit_stack arr = COMMIT_STACK_INIT; size_t size = 0; int ret = 0; @@ -2667,8 +2738,8 @@ static int is_reachable_in_reflog(const char *local, const struct ref *remote) * Check if the remote commit is reachable from any * of the commits in the collected array, in batches. */ - for (chunk = arr.item; chunk < arr.item + arr.nr; chunk += size) { - size = arr.item + arr.nr - chunk; + for (chunk = arr.items; chunk < arr.items + arr.nr; chunk += size) { + size = arr.items + arr.nr - chunk; if (MERGE_BASES_BATCH_SIZE < size) size = MERGE_BASES_BATCH_SIZE; @@ -2677,7 +2748,7 @@ static int is_reachable_in_reflog(const char *local, const struct ref *remote) } cleanup_return: - free_commit_array(&arr); + commit_stack_clear(&arr); return ret; }
diff --git a/remote.h b/remote.h index 0ca399e..fc05294 100644 --- a/remote.h +++ b/remote.h
@@ -331,7 +331,7 @@ struct branch { int merge_alloc; - const char *push_tracking_ref; + char *push_tracking_ref; }; struct branch *branch_get(const char *name);
diff --git a/repack-geometry.c b/repack-geometry.c index b3e32cd..7cebd0c 100644 --- a/repack-geometry.c +++ b/repack-geometry.c
@@ -66,45 +66,54 @@ void pack_geometry_init(struct pack_geometry *geometry, if (p->is_cruft) continue; - ALLOC_GROW(geometry->pack, - geometry->pack_nr + 1, - geometry->pack_alloc); + if (p->pack_promisor) { + ALLOC_GROW(geometry->promisor_pack, + geometry->promisor_pack_nr + 1, + geometry->promisor_pack_alloc); - geometry->pack[geometry->pack_nr] = p; - geometry->pack_nr++; + geometry->promisor_pack[geometry->promisor_pack_nr] = p; + geometry->promisor_pack_nr++; + } else { + ALLOC_GROW(geometry->pack, + geometry->pack_nr + 1, + geometry->pack_alloc); + + geometry->pack[geometry->pack_nr] = p; + geometry->pack_nr++; + } } QSORT(geometry->pack, geometry->pack_nr, pack_geometry_cmp); + QSORT(geometry->promisor_pack, geometry->promisor_pack_nr, pack_geometry_cmp); strbuf_release(&buf); } -void pack_geometry_split(struct pack_geometry *geometry) +static uint32_t compute_pack_geometry_split(struct packed_git **pack, size_t pack_nr, + int split_factor) { uint32_t i; uint32_t split; off_t total_size = 0; - if (!geometry->pack_nr) { - geometry->split = geometry->pack_nr; - return; - } + if (!pack_nr) + return 0; /* * First, count the number of packs (in descending order of size) which * already form a geometric progression. */ - for (i = geometry->pack_nr - 1; i > 0; i--) { - struct packed_git *ours = geometry->pack[i]; - struct packed_git *prev = geometry->pack[i - 1]; + for (i = pack_nr - 1; i > 0; i--) { + struct packed_git *ours = pack[i]; + struct packed_git *prev = pack[i - 1]; - if (unsigned_mult_overflows(geometry->split_factor, + if (unsigned_mult_overflows(split_factor, pack_geometry_weight(prev))) die(_("pack %s too large to consider in geometric " "progression"), prev->pack_name); if (pack_geometry_weight(ours) < - geometry->split_factor * pack_geometry_weight(prev)) + split_factor * pack_geometry_weight(prev)) break; } @@ -130,21 +139,19 @@ void pack_geometry_split(struct pack_geometry *geometry) * the geometric progression. */ for (i = 0; i < split; i++) { - struct packed_git *p = geometry->pack[i]; + struct packed_git *p = pack[i]; if (unsigned_add_overflows(total_size, pack_geometry_weight(p))) die(_("pack %s too large to roll up"), p->pack_name); total_size += pack_geometry_weight(p); } - for (i = split; i < geometry->pack_nr; i++) { - struct packed_git *ours = geometry->pack[i]; + for (i = split; i < pack_nr; i++) { + struct packed_git *ours = pack[i]; - if (unsigned_mult_overflows(geometry->split_factor, - total_size)) + if (unsigned_mult_overflows(split_factor, total_size)) die(_("pack %s too large to roll up"), ours->pack_name); - if (pack_geometry_weight(ours) < - geometry->split_factor * total_size) { + if (pack_geometry_weight(ours) < split_factor * total_size) { if (unsigned_add_overflows(total_size, pack_geometry_weight(ours))) die(_("pack %s too large to roll up"), @@ -156,7 +163,16 @@ void pack_geometry_split(struct pack_geometry *geometry) break; } - geometry->split = split; + return split; +} + +void pack_geometry_split(struct pack_geometry *geometry) +{ + geometry->split = compute_pack_geometry_split(geometry->pack, geometry->pack_nr, + geometry->split_factor); + geometry->promisor_split = compute_pack_geometry_split(geometry->promisor_pack, + geometry->promisor_pack_nr, + geometry->split_factor); } struct packed_git *pack_geometry_preferred_pack(struct pack_geometry *geometry) @@ -194,17 +210,18 @@ struct packed_git *pack_geometry_preferred_pack(struct pack_geometry *geometry) return NULL; } -void pack_geometry_remove_redundant(struct pack_geometry *geometry, - struct string_list *names, - struct existing_packs *existing, - const char *packdir) +static void remove_redundant_packs(struct packed_git **pack, + uint32_t pack_nr, + struct string_list *names, + struct existing_packs *existing, + const char *packdir) { const struct git_hash_algo *algop = existing->repo->hash_algo; struct strbuf buf = STRBUF_INIT; uint32_t i; - for (i = 0; i < geometry->split; i++) { - struct packed_git *p = geometry->pack[i]; + for (i = 0; i < pack_nr; i++) { + struct packed_git *p = pack[i]; if (string_list_has_string(names, hash_to_hex_algop(p->hash, algop))) continue; @@ -223,10 +240,22 @@ void pack_geometry_remove_redundant(struct pack_geometry *geometry, strbuf_release(&buf); } +void pack_geometry_remove_redundant(struct pack_geometry *geometry, + struct string_list *names, + struct existing_packs *existing, + const char *packdir) +{ + remove_redundant_packs(geometry->pack, geometry->split, + names, existing, packdir); + remove_redundant_packs(geometry->promisor_pack, geometry->promisor_split, + names, existing, packdir); +} + void pack_geometry_release(struct pack_geometry *geometry) { if (!geometry) return; free(geometry->pack); + free(geometry->promisor_pack); }
diff --git a/repack-midx.c b/repack-midx.c index 6f6202c..0682b80 100644 --- a/repack-midx.c +++ b/repack-midx.c
@@ -16,25 +16,23 @@ struct midx_snapshot_ref_data { int preferred; }; -static int midx_snapshot_ref_one(const char *refname UNUSED, - const char *referent UNUSED, - const struct object_id *oid, - int flag UNUSED, void *_data) +static int midx_snapshot_ref_one(const struct reference *ref, void *_data) { struct midx_snapshot_ref_data *data = _data; + const struct object_id *maybe_peeled = ref->oid; struct object_id peeled; - if (!peel_iterated_oid(data->repo, oid, &peeled)) - oid = &peeled; + if (!reference_get_peeled_oid(data->repo, ref, &peeled)) + maybe_peeled = &peeled; - if (oidset_insert(&data->seen, oid)) + if (oidset_insert(&data->seen, maybe_peeled)) return 0; /* already seen */ - if (odb_read_object_info(data->repo->objects, oid, NULL) != OBJ_COMMIT) + if (odb_read_object_info(data->repo->objects, maybe_peeled, NULL) != OBJ_COMMIT) return 0; fprintf(data->f->fp, "%s%s\n", data->preferred ? "+" : "", - oid_to_hex(oid)); + oid_to_hex(maybe_peeled)); return 0; } @@ -42,7 +40,6 @@ static int midx_snapshot_ref_one(const char *refname UNUSED, void midx_snapshot_refs(struct repository *repo, struct tempfile *f) { struct midx_snapshot_ref_data data; - const struct string_list *preferred = bitmap_preferred_tips(repo); data.repo = repo; data.f = f; @@ -53,16 +50,9 @@ void midx_snapshot_refs(struct repository *repo, struct tempfile *f) die(_("could not open tempfile %s for writing"), get_tempfile_path(f)); - if (preferred) { - struct string_list_item *item; - - data.preferred = 1; - for_each_string_list_item(item, preferred) - refs_for_each_ref_in(get_main_ref_store(repo), - item->string, - midx_snapshot_ref_one, &data); - data.preferred = 0; - } + data.preferred = 1; + for_each_preferred_bitmap_tip(repo, midx_snapshot_ref_one, &data); + data.preferred = 0; refs_for_each_ref(get_main_ref_store(repo), midx_snapshot_ref_one, &data);
diff --git a/repack-promisor.c b/repack-promisor.c index ee6e066..90318ce 100644 --- a/repack-promisor.c +++ b/repack-promisor.c
@@ -17,8 +17,8 @@ struct write_oid_context { * necessary. */ static int write_oid(const struct object_id *oid, - struct packed_git *pack UNUSED, - uint32_t pos UNUSED, void *data) + struct object_info *oi UNUSED, + void *data) { struct write_oid_context *ctx = data; struct child_process *cmd = ctx->cmd; @@ -34,39 +34,17 @@ static int write_oid(const struct object_id *oid, return 0; } -void repack_promisor_objects(struct repository *repo, - const struct pack_objects_args *args, - struct string_list *names, const char *packtmp) +static void finish_repacking_promisor_objects(struct repository *repo, + struct child_process *cmd, + struct string_list *names, + const char *packtmp) { - struct write_oid_context ctx; - struct child_process cmd = CHILD_PROCESS_INIT; - FILE *out; struct strbuf line = STRBUF_INIT; + FILE *out; - prepare_pack_objects(&cmd, args, packtmp); - cmd.in = -1; + close(cmd->in); - /* - * NEEDSWORK: Giving pack-objects only the OIDs without any ordering - * hints may result in suboptimal deltas in the resulting pack. See if - * the OIDs can be sent with fake paths such that pack-objects can use a - * {type -> existing pack order} ordering when computing deltas instead - * of a {type -> size} ordering, which may produce better deltas. - */ - ctx.cmd = &cmd; - ctx.algop = repo->hash_algo; - for_each_packed_object(repo, write_oid, &ctx, - FOR_EACH_OBJECT_PROMISOR_ONLY); - - if (cmd.in == -1) { - /* No packed objects; cmd was never started */ - child_process_clear(&cmd); - return; - } - - close(cmd.in); - - out = xfdopen(cmd.out, "r"); + out = xfdopen(cmd->out, "r"); while (strbuf_getline_lf(&line, out) != EOF) { struct string_list_item *item; char *promisor_name; @@ -96,7 +74,66 @@ void repack_promisor_objects(struct repository *repo, } fclose(out); - if (finish_command(&cmd)) + if (finish_command(cmd)) die(_("could not finish pack-objects to repack promisor objects")); strbuf_release(&line); } + +void repack_promisor_objects(struct repository *repo, + const struct pack_objects_args *args, + struct string_list *names, const char *packtmp) +{ + struct write_oid_context ctx; + struct child_process cmd = CHILD_PROCESS_INIT; + + prepare_pack_objects(&cmd, args, packtmp); + cmd.in = -1; + + /* + * NEEDSWORK: Giving pack-objects only the OIDs without any ordering + * hints may result in suboptimal deltas in the resulting pack. See if + * the OIDs can be sent with fake paths such that pack-objects can use a + * {type -> existing pack order} ordering when computing deltas instead + * of a {type -> size} ordering, which may produce better deltas. + */ + ctx.cmd = &cmd; + ctx.algop = repo->hash_algo; + odb_for_each_object(repo->objects, NULL, write_oid, &ctx, + ODB_FOR_EACH_OBJECT_PROMISOR_ONLY); + + if (cmd.in == -1) { + /* No packed objects; cmd was never started */ + child_process_clear(&cmd); + return; + } + + finish_repacking_promisor_objects(repo, &cmd, names, packtmp); +} + +void pack_geometry_repack_promisors(struct repository *repo, + const struct pack_objects_args *args, + const struct pack_geometry *geometry, + struct string_list *names, + const char *packtmp) +{ + struct child_process cmd = CHILD_PROCESS_INIT; + FILE *in; + + if (!geometry->promisor_split) + return; + + prepare_pack_objects(&cmd, args, packtmp); + strvec_push(&cmd.args, "--stdin-packs"); + cmd.in = -1; + if (start_command(&cmd)) + die(_("could not start pack-objects to repack promisor packs")); + + in = xfdopen(cmd.in, "w"); + for (size_t i = 0; i < geometry->promisor_split; i++) + fprintf(in, "%s\n", pack_basename(geometry->promisor_pack[i])); + for (size_t i = geometry->promisor_split; i < geometry->promisor_pack_nr; i++) + fprintf(in, "^%s\n", pack_basename(geometry->promisor_pack[i])); + fclose(in); + + finish_repacking_promisor_objects(repo, &cmd, names, packtmp); +}
diff --git a/repack.h b/repack.h index 3a688a1..bc9f2e1 100644 --- a/repack.h +++ b/repack.h
@@ -103,9 +103,19 @@ struct pack_geometry { uint32_t pack_nr, pack_alloc; uint32_t split; + struct packed_git **promisor_pack; + uint32_t promisor_pack_nr, promisor_pack_alloc; + uint32_t promisor_split; + int split_factor; }; +void pack_geometry_repack_promisors(struct repository *repo, + const struct pack_objects_args *args, + const struct pack_geometry *geometry, + struct string_list *names, + const char *packtmp); + void pack_geometry_init(struct pack_geometry *geometry, struct existing_packs *existing, const struct pack_objects_args *args);
diff --git a/replace-object.c b/replace-object.c index 3eae051..03d0f1f 100644 --- a/replace-object.c +++ b/replace-object.c
@@ -8,31 +8,27 @@ #include "repository.h" #include "commit.h" -static int register_replace_ref(const char *refname, - const char *referent UNUSED, - const struct object_id *oid, - int flag UNUSED, - void *cb_data) +static int register_replace_ref(const struct reference *ref, void *cb_data) { struct repository *r = cb_data; /* Get sha1 from refname */ - const char *slash = strrchr(refname, '/'); - const char *hash = slash ? slash + 1 : refname; + const char *slash = strrchr(ref->name, '/'); + const char *hash = slash ? slash + 1 : ref->name; struct replace_object *repl_obj = xmalloc(sizeof(*repl_obj)); if (get_oid_hex_algop(hash, &repl_obj->original.oid, r->hash_algo)) { free(repl_obj); - warning(_("bad replace ref name: %s"), refname); + warning(_("bad replace ref name: %s"), ref->name); return 0; } /* Copy sha1 from the read ref */ - oidcpy(&repl_obj->replacement, oid); + oidcpy(&repl_obj->replacement, ref->oid); /* Register new object */ if (oidmap_put(&r->objects->replace_map, repl_obj)) - die(_("duplicate replace ref: %s"), refname); + die(_("duplicate replace ref: %s"), ref->name); return 0; }
diff --git a/replay.c b/replay.c new file mode 100644 index 0000000..cf1f0bc --- /dev/null +++ b/replay.c
@@ -0,0 +1,463 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "git-compat-util.h" +#include "environment.h" +#include "hex.h" +#include "merge-ort.h" +#include "object-name.h" +#include "refs.h" +#include "replay.h" +#include "revision.h" +#include "sequencer.h" +#include "strmap.h" +#include "tree.h" + +/* + * We technically need USE_THE_REPOSITORY_VARIABLE for DEFAULT_ABBREV, but + * do not want to use the_repository. + */ +#define the_repository DO_NOT_USE_THE_REPOSITORY + +enum replay_mode { + REPLAY_MODE_PICK, + REPLAY_MODE_REVERT, +}; + +static const char *short_commit_name(struct repository *repo, + struct commit *commit) +{ + return repo_find_unique_abbrev(repo, &commit->object.oid, + DEFAULT_ABBREV); +} + +static struct commit *peel_committish(struct repository *repo, + const char *name, + const char *mode) +{ + struct object *obj; + struct object_id oid; + + if (repo_get_oid(repo, name, &oid)) + die(_("'%s' is not a valid commit-ish for %s"), name, mode); + obj = parse_object_or_die(repo, &oid, name); + return (struct commit *)repo_peel_to_type(repo, name, 0, obj, + OBJ_COMMIT); +} + +static char *get_author(const char *message) +{ + size_t len; + const char *a; + + a = find_commit_header(message, "author", &len); + if (a) + return xmemdupz(a, len); + + return NULL; +} + +static void generate_revert_message(struct strbuf *msg, + struct commit *commit, + struct repository *repo) +{ + const char *out_enc = get_commit_output_encoding(); + const char *message = repo_logmsg_reencode(repo, commit, NULL, out_enc); + const char *subject_start; + int subject_len; + char *subject; + + subject_len = find_commit_subject(message, &subject_start); + subject = xmemdupz(subject_start, subject_len); + + sequencer_format_revert_message(repo, subject, commit, + commit->parents ? commit->parents->item : NULL, + false, msg); + + free(subject); + repo_unuse_commit_buffer(repo, commit, message); +} + +static struct commit *create_commit(struct repository *repo, + struct tree *tree, + struct commit *based_on, + struct commit *parent, + enum replay_mode mode) +{ + struct object_id ret; + struct object *obj = NULL; + struct commit_list *parents = NULL; + char *author = NULL; + char *sign_commit = NULL; /* FIXME: cli users might want to sign again */ + struct commit_extra_header *extra = NULL; + struct strbuf msg = STRBUF_INIT; + const char *out_enc = get_commit_output_encoding(); + const char *message = repo_logmsg_reencode(repo, based_on, + NULL, out_enc); + const char *orig_message = NULL; + const char *exclude_gpgsig[] = { "gpgsig", "gpgsig-sha256", NULL }; + + commit_list_insert(parent, &parents); + extra = read_commit_extra_headers(based_on, exclude_gpgsig); + if (mode == REPLAY_MODE_REVERT) { + generate_revert_message(&msg, based_on, repo); + /* For revert, use current user as author (NULL = use default) */ + } else if (mode == REPLAY_MODE_PICK) { + find_commit_subject(message, &orig_message); + strbuf_addstr(&msg, orig_message); + author = get_author(message); + } else { + BUG("unexpected replay mode %d", mode); + } + reset_ident_date(); + if (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents, + &ret, author, NULL, sign_commit, extra)) { + error(_("failed to write commit object")); + goto out; + } + + obj = parse_object(repo, &ret); + +out: + repo_unuse_commit_buffer(repo, based_on, message); + free_commit_extra_headers(extra); + free_commit_list(parents); + strbuf_release(&msg); + free(author); + return (struct commit *)obj; +} + +struct ref_info { + struct commit *onto; + struct strset positive_refs; + struct strset negative_refs; + size_t positive_refexprs; + size_t negative_refexprs; +}; + +static void get_ref_information(struct repository *repo, + struct rev_cmdline_info *cmd_info, + struct ref_info *ref_info) +{ + ref_info->onto = NULL; + strset_init(&ref_info->positive_refs); + strset_init(&ref_info->negative_refs); + ref_info->positive_refexprs = 0; + ref_info->negative_refexprs = 0; + + /* + * When the user specifies e.g. + * git replay origin/main..mybranch + * git replay ^origin/next mybranch1 mybranch2 + * we want to be able to determine where to replay the commits. In + * these examples, the branches are probably based on an old version + * of either origin/main or origin/next, so we want to replay on the + * newest version of that branch. In contrast we would want to error + * out if they ran + * git replay ^origin/master ^origin/next mybranch + * git replay mybranch~2..mybranch + * the first of those because there's no unique base to choose, and + * the second because they'd likely just be replaying commits on top + * of the same commit and not making any difference. + */ + for (size_t i = 0; i < cmd_info->nr; i++) { + struct rev_cmdline_entry *e = cmd_info->rev + i; + struct object_id oid; + const char *refexpr = e->name; + char *fullname = NULL; + int can_uniquely_dwim = 1; + + if (*refexpr == '^') + refexpr++; + if (repo_dwim_ref(repo, refexpr, strlen(refexpr), &oid, &fullname, 0) != 1) + can_uniquely_dwim = 0; + + if (e->flags & BOTTOM) { + if (can_uniquely_dwim) + strset_add(&ref_info->negative_refs, fullname); + if (!ref_info->negative_refexprs) + ref_info->onto = lookup_commit_reference_gently(repo, + &e->item->oid, 1); + ref_info->negative_refexprs++; + } else { + if (can_uniquely_dwim) + strset_add(&ref_info->positive_refs, fullname); + ref_info->positive_refexprs++; + } + + free(fullname); + } +} + +static void set_up_branch_mode(struct repository *repo, + char **branch_name, + const char *option_name, + struct ref_info *rinfo, + struct commit **onto) +{ + struct object_id oid; + char *fullname = NULL; + + if (repo_dwim_ref(repo, *branch_name, strlen(*branch_name), + &oid, &fullname, 0) == 1) { + free(*branch_name); + *branch_name = fullname; + } else { + die(_("argument to %s must be a reference"), option_name); + } + *onto = peel_committish(repo, *branch_name, option_name); + if (rinfo->positive_refexprs > 1) + die(_("'%s' cannot be used with multiple revision ranges " + "because the ordering would be ill-defined"), + option_name); +} + +static void set_up_replay_mode(struct repository *repo, + struct rev_cmdline_info *cmd_info, + const char *onto_name, + bool *detached_head, + char **advance_name, + char **revert_name, + struct commit **onto, + struct strset **update_refs) +{ + struct ref_info rinfo; + int head_flags = 0; + + refs_read_ref_full(get_main_ref_store(repo), "HEAD", + RESOLVE_REF_NO_RECURSE, NULL, &head_flags); + *detached_head = !(head_flags & REF_ISSYMREF); + + get_ref_information(repo, cmd_info, &rinfo); + if (!rinfo.positive_refexprs) + die(_("need some commits to replay")); + + if (onto_name) { + *onto = peel_committish(repo, onto_name, "--onto"); + if (rinfo.positive_refexprs < + strset_get_size(&rinfo.positive_refs)) + die(_("all positive revisions given must be references")); + *update_refs = xcalloc(1, sizeof(**update_refs)); + **update_refs = rinfo.positive_refs; + memset(&rinfo.positive_refs, 0, sizeof(**update_refs)); + } else if (*advance_name) { + set_up_branch_mode(repo, advance_name, "--advance", &rinfo, onto); + } else if (*revert_name) { + set_up_branch_mode(repo, revert_name, "--revert", &rinfo, onto); + } else { + BUG("expected one of onto_name, *advance_name, or *revert_name"); + } + strset_clear(&rinfo.negative_refs); + strset_clear(&rinfo.positive_refs); +} + +static struct commit *mapped_commit(kh_oid_map_t *replayed_commits, + struct commit *commit, + struct commit *fallback) +{ + khint_t pos; + if (!commit) + return fallback; + pos = kh_get_oid_map(replayed_commits, commit->object.oid); + if (pos == kh_end(replayed_commits)) + return fallback; + return kh_value(replayed_commits, pos); +} + +static struct commit *pick_regular_commit(struct repository *repo, + struct commit *pickme, + kh_oid_map_t *replayed_commits, + struct commit *onto, + struct merge_options *merge_opt, + struct merge_result *result, + enum replay_mode mode) +{ + struct commit *base, *replayed_base; + struct tree *pickme_tree, *base_tree, *replayed_base_tree; + + if (pickme->parents) { + base = pickme->parents->item; + base_tree = repo_get_commit_tree(repo, base); + } else { + base = NULL; + base_tree = lookup_tree(repo, repo->hash_algo->empty_tree); + } + + replayed_base = mapped_commit(replayed_commits, base, onto); + replayed_base_tree = repo_get_commit_tree(repo, replayed_base); + pickme_tree = repo_get_commit_tree(repo, pickme); + + if (mode == REPLAY_MODE_PICK) { + /* Cherry-pick: normal order */ + merge_opt->branch1 = short_commit_name(repo, replayed_base); + merge_opt->branch2 = short_commit_name(repo, pickme); + if (pickme->parents) + merge_opt->ancestor = xstrfmt("parent of %s", merge_opt->branch2); + else + merge_opt->ancestor = xstrdup("empty tree"); + + merge_incore_nonrecursive(merge_opt, + base_tree, + replayed_base_tree, + pickme_tree, + result); + + free((char *)merge_opt->ancestor); + } else if (mode == REPLAY_MODE_REVERT) { + /* Revert: swap base and pickme to reverse the diff */ + const char *pickme_name = short_commit_name(repo, pickme); + merge_opt->branch1 = short_commit_name(repo, replayed_base); + merge_opt->branch2 = xstrfmt("parent of %s", pickme_name); + merge_opt->ancestor = pickme_name; + + merge_incore_nonrecursive(merge_opt, + pickme_tree, + replayed_base_tree, + base_tree, + result); + + free((char *)merge_opt->branch2); + } else { + BUG("unexpected replay mode %d", mode); + } + merge_opt->ancestor = NULL; + merge_opt->branch2 = NULL; + if (!result->clean) + return NULL; + /* Drop commits that become empty */ + if (oideq(&replayed_base_tree->object.oid, &result->tree->object.oid) && + !oideq(&pickme_tree->object.oid, &base_tree->object.oid)) + return replayed_base; + return create_commit(repo, result->tree, pickme, replayed_base, mode); +} + +void replay_result_release(struct replay_result *result) +{ + for (size_t i = 0; i < result->updates_nr; i++) + free(result->updates[i].refname); + free(result->updates); +} + +static void replay_result_queue_update(struct replay_result *result, + const char *refname, + const struct object_id *old_oid, + const struct object_id *new_oid) +{ + ALLOC_GROW(result->updates, result->updates_nr + 1, result->updates_alloc); + result->updates[result->updates_nr].refname = xstrdup(refname); + result->updates[result->updates_nr].old_oid = *old_oid; + result->updates[result->updates_nr].new_oid = *new_oid; + result->updates_nr++; +} + +int replay_revisions(struct rev_info *revs, + struct replay_revisions_options *opts, + struct replay_result *out) +{ + kh_oid_map_t *replayed_commits = NULL; + struct strset *update_refs = NULL; + struct commit *last_commit = NULL; + struct commit *commit; + struct commit *onto = NULL; + struct merge_options merge_opt; + struct merge_result result = { + .clean = 1, + }; + bool detached_head; + char *advance; + char *revert; + enum replay_mode mode = REPLAY_MODE_PICK; + int ret; + + advance = xstrdup_or_null(opts->advance); + revert = xstrdup_or_null(opts->revert); + if (revert) + mode = REPLAY_MODE_REVERT; + set_up_replay_mode(revs->repo, &revs->cmdline, opts->onto, + &detached_head, &advance, &revert, &onto, &update_refs); + + if (prepare_revision_walk(revs) < 0) { + ret = error(_("error preparing revisions")); + goto out; + } + + init_basic_merge_options(&merge_opt, revs->repo); + merge_opt.show_rename_progress = 0; + last_commit = onto; + replayed_commits = kh_init_oid_map(); + while ((commit = get_revision(revs))) { + const struct name_decoration *decoration; + khint_t pos; + int hr; + + if (commit->parents && commit->parents->next) + die(_("replaying merge commits is not supported yet!")); + + last_commit = pick_regular_commit(revs->repo, commit, replayed_commits, + mode == REPLAY_MODE_REVERT ? last_commit : onto, + &merge_opt, &result, mode); + if (!last_commit) + break; + + /* Record commit -> last_commit mapping */ + pos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr); + if (hr == 0) + BUG("Duplicate rewritten commit: %s\n", + oid_to_hex(&commit->object.oid)); + kh_value(replayed_commits, pos) = last_commit; + + /* Update any necessary branches */ + if (advance || revert) + continue; + + for (decoration = get_name_decoration(&commit->object); + decoration; + decoration = decoration->next) + { + if (decoration->type != DECORATION_REF_LOCAL && + decoration->type != DECORATION_REF_HEAD) + continue; + + /* + * We only need to update HEAD separately in case it's + * detached. If it's not we'd already update the branch + * it is pointing to. + */ + if (decoration->type == DECORATION_REF_HEAD && !detached_head) + continue; + + if (!opts->contained && + !strset_contains(update_refs, decoration->name)) + continue; + + replay_result_queue_update(out, decoration->name, + &commit->object.oid, + &last_commit->object.oid); + } + } + + if (!result.clean) { + ret = 1; + goto out; + } + + /* In --advance or --revert mode, update the target ref */ + if (advance || revert) { + const char *ref = advance ? advance : revert; + replay_result_queue_update(out, ref, + &onto->object.oid, + &last_commit->object.oid); + } + + ret = 0; + +out: + if (update_refs) { + strset_clear(update_refs); + free(update_refs); + } + kh_destroy_oid_map(replayed_commits); + merge_finalize(&merge_opt, &result); + free(advance); + free(revert); + return ret; +}
diff --git a/replay.h b/replay.h new file mode 100644 index 0000000..e916a5f --- /dev/null +++ b/replay.h
@@ -0,0 +1,68 @@ +#ifndef REPLAY_H +#define REPLAY_H + +#include "hash.h" + +struct repository; +struct rev_info; + +/* + * A set of options that can be passed to `replay_revisions()`. + */ +struct replay_revisions_options { + /* + * Starting point at which to create the new commits; must be a branch + * name. The branch will be updated to point to the rewritten commits. + * This option is mutually exclusive with `onto` and `revert`. + */ + const char *advance; + + /* + * Starting point at which to create the new commits; must be a + * committish. References pointing at decendants of `onto` will be + * updated to point to the new commits. + */ + const char *onto; + + /* + * Starting point at which to create revert commits; must be a branch + * name. The branch will be updated to point to the revert commits. + * This option is mutually exclusive with `onto` and `advance`. + */ + const char *revert; + + /* + * Update branches that point at commits in the given revision range. + * Requires `onto` to be set. + */ + int contained; +}; + +/* This struct is used as an out-parameter by `replay_revisions()`. */ +struct replay_result { + /* + * The set of reference updates that are caused by replaying the + * commits. + */ + struct replay_ref_update { + char *refname; + struct object_id old_oid; + struct object_id new_oid; + } *updates; + size_t updates_nr, updates_alloc; +}; + +void replay_result_release(struct replay_result *result); + +/* + * Replay a set of commits onto a new location. Leaves both the working tree, + * index and references untouched. Reference updates caused by the replay will + * be recorded in the `updates` out pointer. + * + * Returns 0 on success, 1 on conflict and a negative error code otherwise. + */ +int replay_revisions(struct rev_info *revs, + struct replay_revisions_options *opts, + struct replay_result *out); + +#endif
diff --git a/repo-settings.c b/repo-settings.c index 195c24e..208e09f 100644 --- a/repo-settings.c +++ b/repo-settings.c
@@ -100,6 +100,9 @@ void prepare_repo_settings(struct repository *r) */ if (!repo_config_get_int(r, "index.version", &value)) r->settings.index_version = value; + repo_cfg_int(r, "core.maxtreedepth", + &r->settings.max_allowed_tree_depth, + DEFAULT_MAX_ALLOWED_TREE_DEPTH); if (!repo_config_get_string_tmp(r, "core.untrackedcache", &strval)) { int v = git_parse_maybe_bool(strval);
diff --git a/repo-settings.h b/repo-settings.h index d477885..cad9c3f 100644 --- a/repo-settings.h +++ b/repo-settings.h
@@ -67,6 +67,8 @@ struct repo_settings { size_t packed_git_limit; unsigned long big_file_threshold; + int max_allowed_tree_depth; + char *hooks_path; }; #define REPO_SETTINGS_INIT { \ @@ -78,6 +80,7 @@ struct repo_settings { .delta_base_cache_limit = DEFAULT_DELTA_BASE_CACHE_LIMIT, \ .packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE, \ .packed_git_limit = DEFAULT_PACKED_GIT_LIMIT, \ + .max_allowed_tree_depth = DEFAULT_MAX_ALLOWED_TREE_DEPTH, \ } void prepare_repo_settings(struct repository *r);
diff --git a/repository.c b/repository.c index 6faf5c7..9e5537f 100644 --- a/repository.c +++ b/repository.c
@@ -1,8 +1,11 @@ #include "git-compat-util.h" #include "abspath.h" #include "repository.h" +#include "hook.h" #include "odb.h" +#include "odb/source.h" #include "config.h" +#include "gettext.h" #include "object.h" #include "lockfile.h" #include "path.h" @@ -38,7 +41,7 @@ struct repository *the_repository = &the_repo; static void set_default_hash_algo(struct repository *repo) { const char *hash_name; - int algo; + uint32_t algo; hash_name = getenv("GIT_TEST_DEFAULT_HASH_ALGO"); if (!hash_name) @@ -50,14 +53,27 @@ static void set_default_hash_algo(struct repository *repo) repo_set_hash_algo(repo, algo); } +struct repo_config_values *repo_config_values(struct repository *repo) +{ + if (repo != the_repository) + BUG("trying to read config from wrong repository instance"); + if (!repo->initialized) + BUG("config values from uninitialized repository"); + return &repo->config_values_private_; +} + void initialize_repository(struct repository *repo) { - repo->objects = odb_new(repo); + if (repo->initialized) + BUG("repository initialized already"); + repo->initialized = true; + repo->remote_state = remote_state_new(); repo->parsed_objects = parsed_object_pool_new(repo); ALLOC_ARRAY(repo->index, 1); index_state_init(repo->index, repo); repo->check_deprecated_config = true; + repo_config_values_init(&repo->config_values_private_); /* * When a command runs inside a repository, it learns what @@ -166,43 +182,45 @@ void repo_set_gitdir(struct repository *repo, repo_set_commondir(repo, o->commondir); - if (!repo->objects->sources) { - CALLOC_ARRAY(repo->objects->sources, 1); - repo->objects->sources->odb = repo->objects; - repo->objects->sources->local = true; - repo->objects->sources_tail = &repo->objects->sources->next; - } - expand_base_dir(&repo->objects->sources->path, o->object_dir, - repo->commondir, "objects"); + if (!repo->objects) + repo->objects = odb_new(repo, o->object_dir, o->alternate_db); + else if (!o->skip_initializing_odb) + BUG("cannot reinitialize an already-initialized object directory"); - repo->objects->sources->disable_ref_updates = o->disable_ref_updates; + repo->disable_ref_updates = o->disable_ref_updates; - free(repo->objects->alternate_db); - repo->objects->alternate_db = xstrdup_or_null(o->alternate_db); expand_base_dir(&repo->graft_file, o->graft_file, repo->commondir, "info/grafts"); expand_base_dir(&repo->index_file, o->index_file, repo->gitdir, "index"); } -void repo_set_hash_algo(struct repository *repo, int hash_algo) +void repo_set_hash_algo(struct repository *repo, uint32_t hash_algo) { repo->hash_algo = &hash_algos[hash_algo]; } -void repo_set_compat_hash_algo(struct repository *repo, int algo) +void repo_set_compat_hash_algo(struct repository *repo MAYBE_UNUSED, uint32_t algo) { +#ifdef WITH_RUST if (hash_algo_by_ptr(repo->hash_algo) == algo) BUG("hash_algo and compat_hash_algo match"); repo->compat_hash_algo = algo ? &hash_algos[algo] : NULL; if (repo->compat_hash_algo) repo_read_loose_object_map(repo); +#else + if (algo) + die(_("compatibility hash algorithm support requires Rust")); +#endif } void repo_set_ref_storage_format(struct repository *repo, - enum ref_storage_format format) + enum ref_storage_format format, + const char *payload) { repo->ref_storage_format = format; + free(repo->ref_storage_payload); + repo->ref_storage_payload = xstrdup_or_null(payload); } /* @@ -284,10 +302,12 @@ int repo_init(struct repository *repo, repo_set_hash_algo(repo, format.hash_algo); repo_set_compat_hash_algo(repo, format.compat_hash_algo); - repo_set_ref_storage_format(repo, format.ref_storage_format); + repo_set_ref_storage_format(repo, format.ref_storage_format, + format.ref_storage_payload); repo->repository_format_worktree_config = format.worktree_config; repo->repository_format_relative_worktrees = format.relative_worktrees; repo->repository_format_precious_objects = format.precious_objects; + repo->repository_format_submodule_path_cfg = format.submodule_path_cfg; /* take ownership of format.partial_clone */ repo->repository_format_partial_clone = format.partial_clone; @@ -357,7 +377,6 @@ int repo_submodule_init(struct repository *subrepo, static void repo_clear_path_cache(struct repo_path_cache *cache) { FREE_AND_NULL(cache->squash_msg); - FREE_AND_NULL(cache->squash_msg); FREE_AND_NULL(cache->merge_msg); FREE_AND_NULL(cache->merge_rr); FREE_AND_NULL(cache->merge_mode); @@ -377,9 +396,10 @@ void repo_clear(struct repository *repo) FREE_AND_NULL(repo->index_file); FREE_AND_NULL(repo->worktree); FREE_AND_NULL(repo->submodule_prefix); + FREE_AND_NULL(repo->ref_storage_payload); - odb_clear(repo->objects); - FREE_AND_NULL(repo->objects); + odb_free(repo->objects); + repo->objects = NULL; parsed_object_pool_clear(repo->parsed_objects); FREE_AND_NULL(repo->parsed_objects); @@ -401,6 +421,11 @@ void repo_clear(struct repository *repo) FREE_AND_NULL(repo->index); } + if (repo->hook_config_cache) { + hook_cache_clear(repo->hook_config_cache); + FREE_AND_NULL(repo->hook_config_cache); + } + if (repo->promisor_remote_config) { promisor_remote_clear(repo->promisor_remote_config); FREE_AND_NULL(repo->promisor_remote_config);
diff --git a/repository.h b/repository.h index 5808a5d..078059a 100644 --- a/repository.h +++ b/repository.h
@@ -3,6 +3,7 @@ #include "strmap.h" #include "repo-settings.h" +#include "environment.h" struct config_set; struct git_hash_algo; @@ -72,6 +73,13 @@ struct repository { struct ref_store *refs_private; /* + * Disable ref updates. This is especially used in contexts where + * transactions may still be rolled back so that we don't start to + * reference objects that may vanish. + */ + bool disable_ref_updates; + + /* * A strmap of ref_stores, stored by submodule name, accessible via * `repo_get_submodule_ref_store()`. */ @@ -141,8 +149,16 @@ struct repository { /* Repository's compatibility hash algorithm. */ const struct git_hash_algo *compat_hash_algo; + /* Repository's config values parsed by git_default_config() */ + struct repo_config_values config_values_private_; + /* Repository's reference storage format, as serialized on disk. */ enum ref_storage_format ref_storage_format; + /* + * Reference storage information as needed for the backend. This contains + * only the payload from the reference URI without the schema. + */ + char *ref_storage_payload; /* A unique-id for tracing purposes. */ int trace2_repo_id; @@ -150,6 +166,12 @@ struct repository { /* True if commit-graph has been disabled within this process. */ int commit_graph_disabled; + /* + * Lazily-populated cache mapping hook event names to configured hooks. + * NULL until first hook use. + */ + struct strmap *hook_config_cache; + /* Configurations related to promisor remotes. */ char *repository_format_partial_clone; struct promisor_remote_config *promisor_remote_config; @@ -158,12 +180,16 @@ struct repository { int repository_format_worktree_config; int repository_format_relative_worktrees; int repository_format_precious_objects; + int repository_format_submodule_path_cfg; /* Indicate if a repository has a different 'commondir' from 'gitdir' */ unsigned different_commondir:1; /* Should repo_config() check for deprecated settings */ bool check_deprecated_config; + + /* Has this repository instance been initialized? */ + bool initialized; }; #ifdef USE_THE_REPOSITORY_VARIABLE @@ -187,16 +213,18 @@ struct set_gitdir_args { const char *graft_file; const char *index_file; const char *alternate_db; - int disable_ref_updates; + bool disable_ref_updates; + bool skip_initializing_odb; }; void repo_set_gitdir(struct repository *repo, const char *root, const struct set_gitdir_args *extra_args); void repo_set_worktree(struct repository *repo, const char *path); -void repo_set_hash_algo(struct repository *repo, int algo); -void repo_set_compat_hash_algo(struct repository *repo, int compat_algo); +void repo_set_hash_algo(struct repository *repo, uint32_t algo); +void repo_set_compat_hash_algo(struct repository *repo, uint32_t compat_algo); void repo_set_ref_storage_format(struct repository *repo, - enum ref_storage_format format); + enum ref_storage_format format, + const char *payload); void initialize_repository(struct repository *repo); RESULT_MUST_BE_USED int repo_init(struct repository *r, const char *gitdir, const char *worktree);
diff --git a/rerere.c b/rerere.c index 6ec5596..0296700 100644 --- a/rerere.c +++ b/rerere.c
@@ -403,12 +403,8 @@ static int handle_conflict(struct strbuf *out, struct rerere_io *io, strbuf_addbuf(out, &two); rerere_strbuf_putconflict(out, '>', marker_size); if (ctx) { - git_hash_update(ctx, one.buf ? - one.buf : "", - one.len + 1); - git_hash_update(ctx, two.buf ? - two.buf : "", - two.len + 1); + git_hash_update(ctx, one.buf, one.len + 1); + git_hash_update(ctx, two.buf, two.len + 1); } break; } else if (hunk == RR_SIDE_1)
diff --git a/reset.c b/reset.c index bb59027..46e30e6 100644 --- a/reset.c +++ b/reset.c
@@ -163,7 +163,7 @@ int reset_head(struct repository *r, const struct reset_head_opts *opts) goto leave_reset_head; } - tree = parse_tree_indirect(oid); + tree = repo_parse_tree_indirect(the_repository, oid); if (!tree) { ret = error(_("unable to read tree (%s)"), oid_to_hex(oid)); goto leave_reset_head;
diff --git a/revision.c b/revision.c index cf5e6c1..fda405b 100644 --- a/revision.c +++ b/revision.c
@@ -72,7 +72,7 @@ static void mark_tree_contents_uninteresting(struct repository *r, struct tree_desc desc; struct name_entry entry; - if (parse_tree_gently(tree, 1) < 0) + if (repo_parse_tree_gently(the_repository, tree, 1) < 0) return; init_tree_desc(&desc, &tree->object.oid, tree->buffer, tree->size); @@ -179,7 +179,7 @@ static void add_children_by_path(struct repository *r, if (!tree) return; - if (parse_tree_gently(tree, 1) < 0) + if (repo_parse_tree_gently(the_repository, tree, 1) < 0) return; init_tree_desc(&desc, &tree->object.oid, tree->buffer, tree->size); @@ -250,29 +250,6 @@ void mark_trees_uninteresting_sparse(struct repository *r, paths_and_oids_clear(&map); } -struct commit_stack { - struct commit **items; - size_t nr, alloc; -}; -#define COMMIT_STACK_INIT { 0 } - -static void commit_stack_push(struct commit_stack *stack, struct commit *commit) -{ - ALLOC_GROW(stack->items, stack->nr + 1, stack->alloc); - stack->items[stack->nr++] = commit; -} - -static struct commit *commit_stack_pop(struct commit_stack *stack) -{ - return stack->nr ? stack->items[--stack->nr] : NULL; -} - -static void commit_stack_clear(struct commit_stack *stack) -{ - FREE_AND_NULL(stack->items); - stack->nr = stack->alloc = 0; -} - static void mark_one_parent_uninteresting(struct rev_info *revs, struct commit *commit, struct commit_stack *pending) { @@ -1071,7 +1048,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit) continue; } - free_commit_list(parent->next); + commit_list_free(parent->next); parent->next = NULL; while (commit->parents != parent) pop_commit(&commit->parents); @@ -1106,7 +1083,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit) die("cannot simplify commit %s (invalid %s)", oid_to_hex(&commit->object.oid), oid_to_hex(&p->object.oid)); - free_commit_list(p->parents); + commit_list_free(p->parents); p->parents = NULL; } /* fallthrough */ @@ -1173,7 +1150,8 @@ static int process_parents(struct rev_info *revs, struct commit *commit, struct commit *p = parent->item; parent = parent->next; if (p) - p->object.flags |= UNINTERESTING; + p->object.flags |= UNINTERESTING | + CHILD_VISITED; if (repo_parse_commit_gently(revs->repo, p, 1) < 0) continue; if (p->parents) @@ -1227,7 +1205,7 @@ static int process_parents(struct rev_info *revs, struct commit *commit, if (!*slot) *slot = *revision_sources_at(revs->sources, commit); } - p->object.flags |= pass_flags; + p->object.flags |= pass_flags | CHILD_VISITED; if (!(p->object.flags & SEEN)) { p->object.flags |= (SEEN | NOT_USER_GIVEN); if (list) @@ -1428,7 +1406,7 @@ static void limit_to_ancestry(struct commit_list *bottoms, struct commit_list *l p->item->object.flags &= ~(TMP_MARK | ANCESTRY_PATH); for (p = bottoms; p; p = p->next) p->item->object.flags &= ~(TMP_MARK | ANCESTRY_PATH); - free_commit_list(rlist); + commit_list_free(rlist); } /* @@ -1531,7 +1509,7 @@ static int limit_list(struct rev_info *revs) } } - free_commit_list(original_list); + commit_list_free(original_list); revs->commits = newlist; return 0; } @@ -1644,19 +1622,17 @@ struct all_refs_cb { struct worktree *wt; }; -static int handle_one_ref(const char *path, const char *referent UNUSED, const struct object_id *oid, - int flag UNUSED, - void *cb_data) +static int handle_one_ref(const struct reference *ref, void *cb_data) { struct all_refs_cb *cb = cb_data; struct object *object; - if (ref_excluded(&cb->all_revs->ref_excludes, path)) + if (ref_excluded(&cb->all_revs->ref_excludes, ref->name)) return 0; - object = get_reference(cb->all_revs, path, oid, cb->all_flags); - add_rev_cmdline(cb->all_revs, object, path, REV_CMD_REF, cb->all_flags); - add_pending_object(cb->all_revs, object, path); + object = get_reference(cb->all_revs, ref->name, ref->oid, cb->all_flags); + add_rev_cmdline(cb->all_revs, object, ref->name, REV_CMD_REF, cb->all_flags); + add_pending_object(cb->all_revs, object, ref->name); return 0; } @@ -1671,7 +1647,7 @@ static void init_all_refs_cb(struct all_refs_cb *cb, struct rev_info *revs, static void handle_refs(struct ref_store *refs, struct rev_info *revs, unsigned flags, - int (*for_each)(struct ref_store *, each_ref_fn, void *)) + int (*for_each)(struct ref_store *, refs_for_each_cb, void *)) { struct all_refs_cb cb; @@ -1872,7 +1848,7 @@ void add_index_objects_to_pending(struct rev_info *revs, unsigned int flags) wt_gitdir = get_worktree_git_dir(wt); if (read_index_from(&istate, - worktree_git_path(the_repository, wt, "index"), + worktree_git_path(wt, "index"), wt_gitdir) > 0) do_add_index_objects_to_pending(revs, &istate, flags); @@ -2036,7 +2012,7 @@ static void prepare_show_merge(struct rev_info *revs) exit(128); add_rev_cmdline_list(revs, bases, REV_CMD_MERGE_BASE, UNINTERESTING | BOTTOM); add_pending_commit_list(revs, bases, UNINTERESTING | BOTTOM); - free_commit_list(bases); + commit_list_free(bases); head->object.flags |= SYMMETRIC_LEFT; if (!istate->cache_nr) @@ -2062,41 +2038,32 @@ static void prepare_show_merge(struct rev_info *revs) free(prune); } -static int dotdot_missing(const char *arg, char *dotdot, +static int dotdot_missing(const char *full_name, struct rev_info *revs, int symmetric) { if (revs->ignore_missing) return 0; - /* de-munge so we report the full argument */ - *dotdot = '.'; die(symmetric ? "Invalid symmetric difference expression %s" - : "Invalid revision range %s", arg); + : "Invalid revision range %s", full_name); } -static int handle_dotdot_1(const char *arg, char *dotdot, +static int handle_dotdot_1(const char *a_name, const char *b_name, + const char *full_name, int symmetric, struct rev_info *revs, int flags, int cant_be_filename, struct object_context *a_oc, struct object_context *b_oc) { - const char *a_name, *b_name; struct object_id a_oid, b_oid; struct object *a_obj, *b_obj; unsigned int a_flags, b_flags; - int symmetric = 0; unsigned int flags_exclude = flags ^ (UNINTERESTING | BOTTOM); unsigned int oc_flags = GET_OID_COMMITTISH | GET_OID_RECORD_PATH; - a_name = arg; if (!*a_name) a_name = "HEAD"; - b_name = dotdot + 2; - if (*b_name == '.') { - symmetric = 1; - b_name++; - } if (!*b_name) b_name = "HEAD"; @@ -2105,15 +2072,13 @@ static int handle_dotdot_1(const char *arg, char *dotdot, return -1; if (!cant_be_filename) { - *dotdot = '.'; - verify_non_filename(revs->prefix, arg); - *dotdot = '\0'; + verify_non_filename(revs->prefix, full_name); } a_obj = parse_object(revs->repo, &a_oid); b_obj = parse_object(revs->repo, &b_oid); if (!a_obj || !b_obj) - return dotdot_missing(arg, dotdot, revs, symmetric); + return dotdot_missing(full_name, revs, symmetric); if (!symmetric) { /* just A..B */ @@ -2127,16 +2092,16 @@ static int handle_dotdot_1(const char *arg, char *dotdot, a = lookup_commit_reference(revs->repo, &a_obj->oid); b = lookup_commit_reference(revs->repo, &b_obj->oid); if (!a || !b) - return dotdot_missing(arg, dotdot, revs, symmetric); + return dotdot_missing(full_name, revs, symmetric); if (repo_get_merge_bases(the_repository, a, b, &exclude) < 0) { - free_commit_list(exclude); + commit_list_free(exclude); return -1; } add_rev_cmdline_list(revs, exclude, REV_CMD_MERGE_BASE, flags_exclude); add_pending_commit_list(revs, exclude, flags_exclude); - free_commit_list(exclude); + commit_list_free(exclude); b_flags = flags; a_flags = flags | SYMMETRIC_LEFT; @@ -2156,16 +2121,23 @@ static int handle_dotdot(const char *arg, int cant_be_filename) { struct object_context a_oc = {0}, b_oc = {0}; - char *dotdot = strstr(arg, ".."); + const char *dotdot = strstr(arg, ".."); + char *tmp; + int symmetric = 0; int ret; if (!dotdot) return -1; - *dotdot = '\0'; - ret = handle_dotdot_1(arg, dotdot, revs, flags, cant_be_filename, - &a_oc, &b_oc); - *dotdot = '.'; + tmp = xmemdupz(arg, dotdot - arg); + dotdot += 2; + if (*dotdot == '.') { + symmetric = 1; + dotdot++; + } + ret = handle_dotdot_1(tmp, dotdot, arg, symmetric, revs, flags, + cant_be_filename, &a_oc, &b_oc); + free(tmp); object_context_release(&a_oc); object_context_release(&b_oc); @@ -2175,7 +2147,10 @@ static int handle_dotdot(const char *arg, static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int flags, unsigned revarg_opt) { struct object_context oc = {0}; - char *mark; + const char *mark; + char *arg_minus_at = NULL; + char *arg_minus_excl = NULL; + char *arg_minus_dash = NULL; struct object *object; struct object_id oid; int local_flags; @@ -2202,18 +2177,17 @@ static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int fl mark = strstr(arg, "^@"); if (mark && !mark[2]) { - *mark = 0; - if (add_parents_only(revs, arg, flags, 0)) { + arg_minus_at = xmemdupz(arg, mark - arg); + if (add_parents_only(revs, arg_minus_at, flags, 0)) { ret = 0; goto out; } - *mark = '^'; } mark = strstr(arg, "^!"); if (mark && !mark[2]) { - *mark = 0; - if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM), 0)) - *mark = '^'; + arg_minus_excl = xmemdupz(arg, mark - arg); + if (add_parents_only(revs, arg_minus_excl, flags ^ (UNINTERESTING | BOTTOM), 0)) + arg = arg_minus_excl; } mark = strstr(arg, "^-"); if (mark) { @@ -2227,9 +2201,9 @@ static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int fl } } - *mark = 0; - if (!add_parents_only(revs, arg, flags ^ (UNINTERESTING | BOTTOM), exclude_parent)) - *mark = '^'; + arg_minus_dash = xmemdupz(arg, mark - arg); + if (add_parents_only(revs, arg_minus_dash, flags ^ (UNINTERESTING | BOTTOM), exclude_parent)) + arg = arg_minus_dash; } local_flags = 0; @@ -2264,6 +2238,9 @@ static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int fl out: object_context_release(&oc); + free(arg_minus_at); + free(arg_minus_excl); + free(arg_minus_dash); return ret; } @@ -2402,6 +2379,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg } else if ((argcount = parse_long_opt("until", argv, &optarg))) { revs->min_age = approxidate(optarg); return argcount; + } else if (!strcmp(arg, "--maximal-only")) { + revs->maximal_only = 1; } else if (!strcmp(arg, "--first-parent")) { revs->first_parent_only = 1; } else if (!strcmp(arg, "--exclude-first-parent-only")) { @@ -2543,14 +2522,14 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg die(_("--unpacked=<packfile> no longer supported")); } else if (!strcmp(arg, "--no-kept-objects")) { revs->no_kept_objects = 1; - revs->keep_pack_cache_flags |= IN_CORE_KEEP_PACKS; - revs->keep_pack_cache_flags |= ON_DISK_KEEP_PACKS; + revs->keep_pack_cache_flags |= KEPT_PACK_IN_CORE; + revs->keep_pack_cache_flags |= KEPT_PACK_ON_DISK; } else if (skip_prefix(arg, "--no-kept-objects=", &optarg)) { revs->no_kept_objects = 1; if (!strcmp(optarg, "in-core")) - revs->keep_pack_cache_flags |= IN_CORE_KEEP_PACKS; + revs->keep_pack_cache_flags |= KEPT_PACK_IN_CORE; if (!strcmp(optarg, "on-disk")) - revs->keep_pack_cache_flags |= ON_DISK_KEEP_PACKS; + revs->keep_pack_cache_flags |= KEPT_PACK_ON_DISK; } else if (!strcmp(arg, "-r")) { revs->diff = 1; revs->diffopt.flags.recursive = 1; @@ -2753,23 +2732,25 @@ void revision_opts_finish(struct rev_info *revs) } } -static int for_each_bisect_ref(struct ref_store *refs, each_ref_fn fn, +static int for_each_bisect_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data, const char *term) { + struct refs_for_each_ref_options opts = { 0 }; struct strbuf bisect_refs = STRBUF_INIT; int status; strbuf_addf(&bisect_refs, "refs/bisect/%s", term); - status = refs_for_each_fullref_in(refs, bisect_refs.buf, NULL, fn, cb_data); + opts.prefix = bisect_refs.buf; + status = refs_for_each_ref_ext(refs, fn, cb_data, &opts); strbuf_release(&bisect_refs); return status; } -static int for_each_bad_bisect_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data) +static int for_each_bad_bisect_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data) { return for_each_bisect_ref(refs, fn, cb_data, term_bad); } -static int for_each_good_bisect_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data) +static int for_each_good_bisect_ref(struct ref_store *refs, refs_for_each_cb fn, void *cb_data) { return for_each_bisect_ref(refs, fn, cb_data, term_good); } @@ -2839,10 +2820,13 @@ static int handle_revision_pseudo_opt(struct rev_info *revs, handle_refs(refs, revs, *flags, refs_for_each_remote_ref); clear_ref_exclusions(&revs->ref_excludes); } else if ((argcount = parse_long_opt("glob", argv, &optarg))) { + struct refs_for_each_ref_options opts = { + .pattern = optarg, + }; struct all_refs_cb cb; init_all_refs_cb(&cb, revs, *flags); - refs_for_each_glob_ref(get_main_ref_store(the_repository), - handle_one_ref, optarg, &cb); + refs_for_each_ref_ext(get_main_ref_store(the_repository), + handle_one_ref, &cb, &opts); clear_ref_exclusions(&revs->ref_excludes); return argcount; } else if ((argcount = parse_long_opt("exclude", argv, &optarg))) { @@ -2852,34 +2836,46 @@ static int handle_revision_pseudo_opt(struct rev_info *revs, exclude_hidden_refs(&revs->ref_excludes, optarg); return argcount; } else if (skip_prefix(arg, "--branches=", &optarg)) { + struct refs_for_each_ref_options opts = { + .prefix = "refs/heads/", + .trim_prefix = strlen("refs/heads/"), + .pattern = optarg, + }; struct all_refs_cb cb; if (revs->ref_excludes.hidden_refs_configured) return error(_("options '%s' and '%s' cannot be used together"), "--exclude-hidden", "--branches"); init_all_refs_cb(&cb, revs, *flags); - refs_for_each_glob_ref_in(get_main_ref_store(the_repository), - handle_one_ref, optarg, - "refs/heads/", &cb); + refs_for_each_ref_ext(get_main_ref_store(the_repository), + handle_one_ref, &cb, &opts); clear_ref_exclusions(&revs->ref_excludes); } else if (skip_prefix(arg, "--tags=", &optarg)) { + struct refs_for_each_ref_options opts = { + .prefix = "refs/tags/", + .trim_prefix = strlen("refs/tags/"), + .pattern = optarg, + }; struct all_refs_cb cb; if (revs->ref_excludes.hidden_refs_configured) return error(_("options '%s' and '%s' cannot be used together"), "--exclude-hidden", "--tags"); init_all_refs_cb(&cb, revs, *flags); - refs_for_each_glob_ref_in(get_main_ref_store(the_repository), - handle_one_ref, optarg, - "refs/tags/", &cb); + refs_for_each_ref_ext(get_main_ref_store(the_repository), + handle_one_ref, &cb, &opts); clear_ref_exclusions(&revs->ref_excludes); } else if (skip_prefix(arg, "--remotes=", &optarg)) { + struct refs_for_each_ref_options opts = { + .prefix = "refs/remotes/", + .trim_prefix = strlen("refs/remotes/"), + .pattern = optarg, + }; struct all_refs_cb cb; if (revs->ref_excludes.hidden_refs_configured) return error(_("options '%s' and '%s' cannot be used together"), "--exclude-hidden", "--remotes"); init_all_refs_cb(&cb, revs, *flags); - refs_for_each_glob_ref_in(get_main_ref_store(the_repository), - handle_one_ref, optarg, - "refs/remotes/", &cb); + refs_for_each_ref_ext(get_main_ref_store(the_repository), + handle_one_ref, &cb, &opts); clear_ref_exclusions(&revs->ref_excludes); } else if (!strcmp(arg, "--reflog")) { add_reflogs_to_pending(revs, *flags); @@ -3172,6 +3168,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s !!revs->reverse, "--reverse", !!revs->reflog_info, "--walk-reflogs"); + die_for_incompatible_opt2(!!revs->boundary, "--boundary", + !!revs->maximal_only, "--maximal-only"); + if (revs->no_walk && revs->graph) die(_("options '%s' and '%s' cannot be used together"), "--no-walk", "--graph"); if (!revs->reflog_info && revs->grep_filter.use_reflog_filter) @@ -3246,13 +3245,13 @@ static void release_revisions_bloom_keyvecs(struct rev_info *revs) static void free_void_commit_list(void *list) { - free_commit_list(list); + commit_list_free(list); } void release_revisions(struct rev_info *revs) { - free_commit_list(revs->commits); - free_commit_list(revs->ancestry_path_bottoms); + commit_list_free(revs->commits); + commit_list_free(revs->ancestry_path_bottoms); release_display_notes(&revs->notes_opt); object_array_clear(&revs->pending); object_array_clear(&revs->boundary_commits); @@ -3360,7 +3359,7 @@ static int mark_redundant_parents(struct commit *commit) if (i != cnt || cnt+marked != orig_cnt) die("mark_redundant_parents %d %d %d %d", orig_cnt, cnt, i, marked); - free_commit_list(h); + commit_list_free(h); return marked; } @@ -3651,8 +3650,7 @@ void reset_revision_walk(void) } static int mark_uninteresting(const struct object_id *oid, - struct packed_git *pack UNUSED, - uint32_t pos UNUSED, + struct object_info *oi UNUSED, void *cb) { struct rev_info *revs = cb; @@ -3961,10 +3959,9 @@ int prepare_revision_walk(struct rev_info *revs) (revs->limited && limiting_can_increase_treesame(revs))) revs->treesame.name = "treesame"; - if (revs->exclude_promisor_objects) { - for_each_packed_object(revs->repo, mark_uninteresting, revs, - FOR_EACH_OBJECT_PROMISOR_ONLY); - } + if (revs->exclude_promisor_objects) + odb_for_each_object(revs->repo->objects, NULL, mark_uninteresting, + revs, ODB_FOR_EACH_OBJECT_PROMISOR_ONLY); if (!revs->reflog_info) prepare_to_use_bloom_filter(revs); @@ -4150,6 +4147,8 @@ enum commit_action get_commit_action(struct rev_info *revs, struct commit *commi { if (commit->object.flags & SHOWN) return commit_ignore; + if (revs->maximal_only && (commit->object.flags & CHILD_VISITED)) + return commit_ignore; if (revs->unpacked && has_object_pack(revs->repo, &commit->object.oid)) return commit_ignore; if (revs->no_kept_objects) { @@ -4249,7 +4248,7 @@ static void save_parents(struct rev_info *revs, struct commit *commit) if (*pp) return; if (commit->parents) - *pp = copy_commit_list(commit->parents); + *pp = commit_list_copy(commit->parents); else *pp = EMPTY_PARENT_LIST; } @@ -4257,7 +4256,7 @@ static void save_parents(struct rev_info *revs, struct commit *commit) static void free_saved_parent(struct commit_list **parents) { if (*parents != EMPTY_PARENT_LIST) - free_commit_list(*parents); + commit_list_free(*parents); } static void free_saved_parents(struct rev_info *revs) @@ -4318,8 +4317,8 @@ static void track_linear(struct rev_info *revs, struct commit *commit) if (revs->linear) commit->object.flags |= TRACK_LINEAR; } - free_commit_list(revs->previous_parents); - revs->previous_parents = copy_commit_list(commit->parents); + commit_list_free(revs->previous_parents); + revs->previous_parents = commit_list_copy(commit->parents); } static struct commit *get_revision_1(struct rev_info *revs) @@ -4407,7 +4406,7 @@ static void create_boundary_commit_list(struct rev_info *revs) * boundary commits anyway. (This is what the code has always * done.) */ - free_commit_list(revs->commits); + commit_list_free(revs->commits); revs->commits = NULL; /* @@ -4529,7 +4528,7 @@ struct commit *get_revision(struct rev_info *revs) reversed = NULL; while ((c = get_revision_internal(revs))) commit_list_insert(c, &reversed); - free_commit_list(revs->commits); + commit_list_free(revs->commits); revs->commits = reversed; revs->reverse = 0; revs->reverse_output_stage = 1; @@ -4547,7 +4546,7 @@ struct commit *get_revision(struct rev_info *revs) graph_update(revs->graph, c); if (!c) { free_saved_parents(revs); - free_commit_list(revs->previous_parents); + commit_list_free(revs->previous_parents); revs->previous_parents = NULL; } return c;
diff --git a/revision.h b/revision.h index b36acfc..584f133 100644 --- a/revision.h +++ b/revision.h
@@ -4,6 +4,7 @@ #include "commit.h" #include "grep.h" #include "notes.h" +#include "object-name.h" #include "oidset.h" #include "pretty.h" #include "diff.h" @@ -52,7 +53,9 @@ #define NOT_USER_GIVEN (1u<<25) #define TRACK_LINEAR (1u<<26) #define ANCESTRY_PATH (1u<<27) -#define ALL_REV_FLAGS (((1u<<11)-1) | NOT_USER_GIVEN | TRACK_LINEAR | PULL_MERGE) +#define CHILD_VISITED (1u<<28) +#define ALL_REV_FLAGS (((1u<<11)-1) | NOT_USER_GIVEN | TRACK_LINEAR \ + | PULL_MERGE | CHILD_VISITED) #define DECORATE_SHORT_REFS 1 #define DECORATE_FULL_REFS 2 @@ -189,6 +192,7 @@ struct rev_info { left_right:1, left_only:1, right_only:1, + maximal_only:1, rewrite_parents:1, print_parents:1, show_decorations:1,
diff --git a/run-command.c b/run-command.c index ed9575b..32c290e 100644 --- a/run-command.c +++ b/run-command.c
@@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" @@ -742,8 +741,8 @@ int start_command(struct child_process *cmd) fflush(NULL); - if (cmd->close_object_store) - close_object_store(the_repository->objects); + if (cmd->odb_to_close) + odb_close(cmd->odb_to_close); #ifndef GIT_WINDOWS_NATIVE { @@ -1478,15 +1477,40 @@ enum child_state { GIT_CP_WAIT_CLEANUP, }; +struct parallel_child { + enum child_state state; + struct child_process process; + struct strbuf err; + void *data; +}; + +static int child_is_working(const struct parallel_child *pp_child) +{ + return pp_child->state == GIT_CP_WORKING; +} + +static int child_is_ready_for_cleanup(const struct parallel_child *pp_child) +{ + return child_is_working(pp_child) && !pp_child->process.in; +} + +static int child_is_receiving_input(const struct parallel_child *pp_child) +{ + return child_is_working(pp_child) && pp_child->process.in > 0; +} +static int child_is_sending_output(const struct parallel_child *pp_child) +{ + /* + * all pp children which buffer output through run_command via ungroup=0 + * redirect stdout to stderr, so we just need to check process.err. + */ + return child_is_working(pp_child) && pp_child->process.err > 0; +} + struct parallel_processes { size_t nr_processes; - struct { - enum child_state state; - struct child_process process; - struct strbuf err; - void *data; - } *children; + struct parallel_child *children; /* * The struct pollfd is logically part of *children, * but the system call expects it as its own array. @@ -1509,7 +1533,7 @@ static void kill_children(const struct parallel_processes *pp, int signo) { for (size_t i = 0; i < opts->processes; i++) - if (pp->children[i].state == GIT_CP_WORKING) + if (child_is_working(&pp->children[i])) kill(pp->children[i].process.pid, signo); } @@ -1545,7 +1569,7 @@ static void pp_init(struct parallel_processes *pp, CALLOC_ARRAY(pp->children, n); if (!opts->ungroup) - CALLOC_ARRAY(pp->pfd, n); + CALLOC_ARRAY(pp->pfd, n * 2); for (size_t i = 0; i < n; i++) { strbuf_init(&pp->children[i].err, 0); @@ -1652,21 +1676,101 @@ static int pp_start_one(struct parallel_processes *pp, return 0; } -static void pp_buffer_stderr(struct parallel_processes *pp, - const struct run_process_parallel_opts *opts, - int output_timeout) +static void pp_buffer_stdin(struct parallel_processes *pp, + const struct run_process_parallel_opts *opts) { - while (poll(pp->pfd, opts->processes, output_timeout) < 0) { + /* Buffer stdin for each pipe. */ + for (size_t i = 0; i < opts->processes; i++) { + struct child_process *proc = &pp->children[i].process; + int ret; + + if (!child_is_receiving_input(&pp->children[i])) + continue; + + /* + * child input is provided via path_to_stdin when the feed_pipe cb is + * missing, so we just signal an EOF. + */ + if (!opts->feed_pipe) { + close(proc->in); + proc->in = 0; + continue; + } + + /** + * Feed the pipe: + * ret < 0 means error + * ret == 0 means there is more data to be fed + * ret > 0 means feeding finished + */ + ret = opts->feed_pipe(proc->in, opts->data, pp->children[i].data); + if (ret < 0) + die_errno("feed_pipe"); + + if (ret) { + close(proc->in); + proc->in = 0; + } + } +} + +static void pp_buffer_io(struct parallel_processes *pp, + const struct run_process_parallel_opts *opts, + int timeout) +{ + /* for each potential child slot, prepare two pollfd entries */ + for (size_t i = 0; i < opts->processes; i++) { + if (child_is_sending_output(&pp->children[i])) { + pp->pfd[2*i].fd = pp->children[i].process.err; + pp->pfd[2*i].events = POLLIN | POLLHUP; + } else { + pp->pfd[2*i].fd = -1; + } + + if (child_is_receiving_input(&pp->children[i])) { + pp->pfd[2*i+1].fd = pp->children[i].process.in; + pp->pfd[2*i+1].events = POLLOUT; + } else { + pp->pfd[2*i+1].fd = -1; + } + } + + while (poll(pp->pfd, opts->processes * 2, timeout) < 0) { if (errno == EINTR) continue; pp_cleanup(pp, opts); die_errno("poll"); } - /* Buffer output from all pipes. */ for (size_t i = 0; i < opts->processes; i++) { - if (pp->children[i].state == GIT_CP_WORKING && - pp->pfd[i].revents & (POLLIN | POLLHUP)) { + /* Handle input feeding (stdin) */ + if (pp->pfd[2*i+1].revents & (POLLOUT | POLLHUP | POLLERR)) { + if (opts->feed_pipe) { + int ret = opts->feed_pipe(pp->children[i].process.in, + opts->data, + pp->children[i].data); + if (ret < 0) + die_errno("feed_pipe"); + if (ret) { + /* done feeding */ + close(pp->children[i].process.in); + pp->children[i].process.in = 0; + } + } else { + /* + * No feed_pipe means there is nothing to do, so + * close the fd. Child input can be fed by other + * methods, such as opts->path_to_stdin which + * slurps a file via dup2, so clean up here. + */ + close(pp->children[i].process.in); + pp->children[i].process.in = 0; + } + } + + /* Handle output reading (stderr) */ + if (child_is_working(&pp->children[i]) && + pp->pfd[2*i].revents & (POLLIN | POLLHUP)) { int n = strbuf_read_once(&pp->children[i].err, pp->children[i].process.err, 0); if (n == 0) { @@ -1683,7 +1787,7 @@ static void pp_output(const struct parallel_processes *pp) { size_t i = pp->output_owner; - if (pp->children[i].state == GIT_CP_WORKING && + if (child_is_working(&pp->children[i]) && pp->children[i].err.len) { strbuf_write(&pp->children[i].err, stderr); strbuf_reset(&pp->children[i].err); @@ -1722,6 +1826,7 @@ static int pp_collect_finished(struct parallel_processes *pp, pp->children[i].state = GIT_CP_FREE; if (pp->pfd) pp->pfd[i].fd = -1; + pp->children[i].process.in = 0; child_process_init(&pp->children[i].process); if (opts->ungroup) { @@ -1748,7 +1853,7 @@ static int pp_collect_finished(struct parallel_processes *pp, * running process time. */ for (i = 0; i < n; i++) - if (pp->children[(pp->output_owner + i) % n].state == GIT_CP_WORKING) + if (child_is_working(&pp->children[(pp->output_owner + i) % n])) break; pp->output_owner = (pp->output_owner + i) % n; } @@ -1756,10 +1861,25 @@ static int pp_collect_finished(struct parallel_processes *pp, return result; } +static void pp_handle_child_IO(struct parallel_processes *pp, + const struct run_process_parallel_opts *opts, + int timeout) +{ + if (opts->ungroup) { + pp_buffer_stdin(pp, opts); + for (size_t i = 0; i < opts->processes; i++) + if (child_is_ready_for_cleanup(&pp->children[i])) + pp->children[i].state = GIT_CP_WAIT_CLEANUP; + } else { + pp_buffer_io(pp, opts, timeout); + pp_output(pp); + } +} + void run_processes_parallel(const struct run_process_parallel_opts *opts) { int i, code; - int output_timeout = 100; + int timeout = 100; int spawn_cap = 4; struct parallel_processes_for_signal pp_sig; struct parallel_processes pp = { @@ -1775,6 +1895,13 @@ void run_processes_parallel(const struct run_process_parallel_opts *opts) "max:%"PRIuMAX, (uintmax_t)opts->processes); + /* + * Child tasks might receive input via stdin, terminating early (or not), so + * ignore the default SIGPIPE which gets handled by each feed_pipe_fn which + * actually writes the data to children stdin fds. + */ + sigchain_push(SIGPIPE, SIG_IGN); + pp_init(&pp, opts, &pp_sig); while (1) { for (i = 0; @@ -1792,13 +1919,7 @@ void run_processes_parallel(const struct run_process_parallel_opts *opts) } if (!pp.nr_processes) break; - if (opts->ungroup) { - for (size_t i = 0; i < opts->processes; i++) - pp.children[i].state = GIT_CP_WAIT_CLEANUP; - } else { - pp_buffer_stderr(&pp, opts, output_timeout); - pp_output(&pp); - } + pp_handle_child_IO(&pp, opts, timeout); code = pp_collect_finished(&pp, opts); if (code) { pp.shutdown = 1; @@ -1809,15 +1930,18 @@ void run_processes_parallel(const struct run_process_parallel_opts *opts) pp_cleanup(&pp, opts); + sigchain_pop(SIGPIPE); + if (do_trace2) trace2_region_leave(tr2_category, tr2_label, NULL); } -int prepare_auto_maintenance(int quiet, struct child_process *maint) +int prepare_auto_maintenance(struct repository *r, int quiet, + struct child_process *maint) { int enabled, auto_detach; - if (!repo_config_get_bool(the_repository, "maintenance.auto", &enabled) && + if (!repo_config_get_bool(r, "maintenance.auto", &enabled) && !enabled) return 0; @@ -1826,12 +1950,12 @@ int prepare_auto_maintenance(int quiet, struct child_process *maint) * honoring `gc.autoDetach`. This is somewhat weird, but required to * retain behaviour from when we used to run git-gc(1) here. */ - if (repo_config_get_bool(the_repository, "maintenance.autodetach", &auto_detach) && - repo_config_get_bool(the_repository, "gc.autodetach", &auto_detach)) - auto_detach = 1; + if (repo_config_get_bool(r, "maintenance.autodetach", &auto_detach) && + repo_config_get_bool(r, "gc.autodetach", &auto_detach)) + auto_detach = git_env_bool("GIT_TEST_MAINT_AUTO_DETACH", true); maint->git_cmd = 1; - maint->close_object_store = 1; + maint->odb_to_close = r->objects; strvec_pushl(&maint->args, "maintenance", "run", "--auto", NULL); strvec_push(&maint->args, quiet ? "--quiet" : "--no-quiet"); strvec_push(&maint->args, auto_detach ? "--detach" : "--no-detach"); @@ -1839,15 +1963,15 @@ int prepare_auto_maintenance(int quiet, struct child_process *maint) return 1; } -int run_auto_maintenance(int quiet) +int run_auto_maintenance(struct repository *r, int quiet) { struct child_process maint = CHILD_PROCESS_INIT; - if (!prepare_auto_maintenance(quiet, &maint)) + if (!prepare_auto_maintenance(r, quiet, &maint)) return 0; return run_command(&maint); } -void prepare_other_repo_env(struct strvec *env, const char *new_git_dir) +void sanitize_repo_env(struct strvec *env) { const char * const *var; @@ -1856,6 +1980,11 @@ void prepare_other_repo_env(struct strvec *env, const char *new_git_dir) strcmp(*var, CONFIG_COUNT_ENVIRONMENT)) strvec_push(env, *var); } +} + +void prepare_other_repo_env(struct strvec *env, const char *new_git_dir) +{ + sanitize_repo_env(env); strvec_pushf(env, "%s=%s", GIT_DIR_ENVIRONMENT, new_git_dir); }
diff --git a/run-command.h b/run-command.h index 0df25e4..8ca496d 100644 --- a/run-command.h +++ b/run-command.h
@@ -5,6 +5,8 @@ #include "strvec.h" +struct repository; + /** * The run-command API offers a versatile tool to run sub-processes with * redirected input and output as well as with a modified environment @@ -136,7 +138,7 @@ struct child_process { * want to repack because that would delete `.pack` files (and on * Windows, you cannot delete files that are still in use). */ - unsigned close_object_store:1; + struct object_database *odb_to_close; unsigned stdout_to_stderr:1; unsigned clean_on_exit:1; @@ -227,12 +229,13 @@ int run_command(struct child_process *); * process has been prepared and is ready to run, or 0 in case auto-maintenance * should be skipped. */ -int prepare_auto_maintenance(int quiet, struct child_process *maint); +int prepare_auto_maintenance(struct repository *r, int quiet, + struct child_process *maint); /* * Trigger an auto-gc */ -int run_auto_maintenance(int quiet); +int run_auto_maintenance(struct repository *r, int quiet); /** * Execute the given command, sending "in" to its stdin, and capturing its @@ -421,6 +424,21 @@ typedef int (*start_failure_fn)(struct strbuf *out, void *pp_task_cb); /** + * This callback is repeatedly called on every child process who requests + * start_command() to create a pipe by setting child_process.in < 0. + * + * pp_cb is the callback cookie as passed into run_processes_parallel, and + * pp_task_cb is the callback cookie as passed into get_next_task_fn. + * + * Returns < 0 for error + * Returns == 0 when there is more data to be fed (will be called again) + * Returns > 0 when finished (child closed fd or no more data to be fed) + */ +typedef int (*feed_pipe_fn)(int child_in, + void *pp_cb, + void *pp_task_cb); + +/** * This callback is called on every child process that finished processing. * * See run_processes_parallel() below for a discussion of the "struct @@ -473,6 +491,12 @@ struct run_process_parallel_opts */ start_failure_fn start_failure; + /* + * feed_pipe: see feed_pipe_fn() above. This can be NULL to omit any + * special handling. + */ + feed_pipe_fn feed_pipe; + /** * task_finished: See task_finished_fn() above. This can be * NULL to omit any special handling. @@ -510,12 +534,17 @@ struct run_process_parallel_opts void run_processes_parallel(const struct run_process_parallel_opts *opts); /** + * Unset all local-repo GIT_* variables in env; see local_repo_env in + * environment.h. GIT_CONFIG_PARAMETERS and GIT_CONFIG_COUNT are preserved + * to pass -c and --config-env options from the parent process. + */ +void sanitize_repo_env(struct strvec *env); + +/** * Convenience function which prepares env for a command to be run in a - * new repo. This adds all GIT_* environment variables to env with the - * exception of GIT_CONFIG_PARAMETERS and GIT_CONFIG_COUNT (which cause the - * corresponding environment variables to be unset in the subprocess) and adds - * an environment variable pointing to new_git_dir. See local_repo_env in - * environment.h for more information. + * new repo. This removes variables pointing to the local repository (using + * sanitize_repo_env() above), and adds an environment variable pointing to + * new_git_dir. */ void prepare_other_repo_env(struct strvec *env, const char *new_git_dir);
diff --git a/scalar.c b/scalar.c index f754311..4efb6ac 100644 --- a/scalar.c +++ b/scalar.c
@@ -19,6 +19,7 @@ #include "help.h" #include "setup.h" #include "trace2.h" +#include "path.h" static void setup_enlistment_directory(int argc, const char **argv, const char * const *usagestr, @@ -95,7 +96,17 @@ struct scalar_config { int overwrite_on_reconfigure; }; -static int set_scalar_config(const struct scalar_config *config, int reconfigure) +static int set_scalar_config(const char *key, const char *value) +{ + char *file = repo_git_path(the_repository, "config"); + int res = repo_config_set_multivar_in_file_gently(the_repository, file, + key, value, NULL, + " # set by scalar", 0); + free(file); + return res; +} + +static int set_config_if_missing(const struct scalar_config *config, int reconfigure) { char *value = NULL; int res; @@ -103,7 +114,7 @@ static int set_scalar_config(const struct scalar_config *config, int reconfigure if ((reconfigure && config->overwrite_on_reconfigure) || repo_config_get_string(the_repository, config->key, &value)) { trace2_data_string("scalar", the_repository, config->key, "created"); - res = repo_config_set_gently(the_repository, config->key, config->value); + res = set_scalar_config(config->key, config->value); } else { trace2_data_string("scalar", the_repository, config->key, "exists"); res = 0; @@ -121,14 +132,38 @@ static int have_fsmonitor_support(void) static int set_recommended_config(int reconfigure) { + /* + * Be sure to update Documentation/scalar.adoc if you add, update, + * or remove any of these recommended settings. + */ struct scalar_config config[] = { - /* Required */ - { "am.keepCR", "true", 1 }, - { "core.FSCache", "true", 1 }, - { "core.multiPackIndex", "true", 1 }, - { "core.preloadIndex", "true", 1 }, + { "am.keepCR", "true" }, + { "commitGraph.changedPaths", "true" }, + { "commitGraph.generationVersion", "1" }, + { "core.autoCRLF", "false" }, + { "core.logAllRefUpdates", "true" }, + { "core.safeCRLF", "false" }, + { "credential.https://dev.azure.com.useHttpPath", "true" }, + { "feature.experimental", "false" }, + { "feature.manyFiles", "false" }, + { "fetch.showForcedUpdates", "false" }, + { "fetch.unpackLimit", "1" }, + { "fetch.writeCommitGraph", "false" }, + { "gc.auto", "0" }, + { "gui.GCWarning", "false" }, + { "index.skipHash", "true", 1 /* Fix previous setting. */ }, + { "index.threads", "true"}, + { "index.version", "4" }, + { "merge.renames", "true" }, + { "merge.stat", "false" }, + { "pack.useBitmaps", "false" }, + { "pack.usePathWalk", "true" }, + { "receive.autoGC", "false" }, + { "status.aheadBehind", "false" }, + + /* platform-specific */ #ifndef WIN32 - { "core.untrackedCache", "true", 1 }, + { "core.untrackedCache", "true" }, #else /* * Unfortunately, Scalar's Functional Tests demonstrated @@ -142,50 +177,25 @@ static int set_recommended_config(int reconfigure) * Therefore, with a sad heart, we disable this very useful * feature on Windows. */ - { "core.untrackedCache", "false", 1 }, + { "core.untrackedCache", "false" }, + + /* Other Windows-specific required settings: */ + { "http.sslBackend", "schannel" }, #endif - { "core.logAllRefUpdates", "true", 1 }, - { "credential.https://dev.azure.com.useHttpPath", "true", 1 }, - { "credential.validate", "false", 1 }, /* GCM4W-only */ - { "gc.auto", "0", 1 }, - { "gui.GCWarning", "false", 1 }, - { "index.skipHash", "false", 1 }, - { "index.threads", "true", 1 }, - { "index.version", "4", 1 }, - { "merge.stat", "false", 1 }, - { "merge.renames", "true", 1 }, - { "pack.useBitmaps", "false", 1 }, - { "pack.useSparse", "true", 1 }, - { "receive.autoGC", "false", 1 }, - { "feature.manyFiles", "false", 1 }, - { "feature.experimental", "false", 1 }, - { "fetch.unpackLimit", "1", 1 }, - { "fetch.writeCommitGraph", "false", 1 }, -#ifdef WIN32 - { "http.sslBackend", "schannel", 1 }, -#endif - /* Optional */ - { "status.aheadBehind", "false" }, - { "commitGraph.changedPaths", "true" }, - { "commitGraph.generationVersion", "1" }, - { "core.autoCRLF", "false" }, - { "core.safeCRLF", "false" }, - { "fetch.showForcedUpdates", "false" }, - { "pack.usePathWalk", "true" }, { NULL, NULL }, }; int i; char *value; for (i = 0; config[i].key; i++) { - if (set_scalar_config(config + i, reconfigure)) + if (set_config_if_missing(config + i, reconfigure)) return error(_("could not configure %s=%s"), config[i].key, config[i].value); } if (have_fsmonitor_support()) { struct scalar_config fsmonitor = { "core.fsmonitor", "true" }; - if (set_scalar_config(&fsmonitor, reconfigure)) + if (set_config_if_missing(&fsmonitor, reconfigure)) return error(_("could not configure %s=%s"), fsmonitor.key, fsmonitor.value); } @@ -197,9 +207,8 @@ static int set_recommended_config(int reconfigure) if (repo_config_get_string(the_repository, "log.excludeDecoration", &value)) { trace2_data_string("scalar", the_repository, "log.excludeDecoration", "created"); - if (repo_config_set_multivar_gently(the_repository, "log.excludeDecoration", - "refs/prefetch/*", - CONFIG_REGEX_NONE, 0)) + if (set_scalar_config("log.excludeDecoration", + "refs/prefetch/*")) return error(_("could not configure " "log.excludeDecoration")); } else { @@ -384,7 +393,7 @@ static int delete_enlistment(struct strbuf *enlistment) { struct strbuf parent = STRBUF_INIT; size_t offset; - char *path_sep; + const char *path_sep; if (unregister_dir()) return error(_("failed to unregister repository")); @@ -931,7 +940,7 @@ static int cmd_delete(int argc, const char **argv) if (dir_inside_of(cwd, enlistment.buf) >= 0) res = error(_("refusing to delete current working directory")); else { - close_object_store(the_repository->objects); + odb_close(the_repository->objects); res = delete_enlistment(&enlistment); } strbuf_release(&enlistment);
diff --git a/send-pack.c b/send-pack.c index 67d6987..07ecfae 100644 --- a/send-pack.c +++ b/send-pack.c
@@ -391,7 +391,7 @@ static int generate_push_cert(struct strbuf *req_buf, if (!update_seen) goto free_return; - if (sign_buffer(&cert, &cert, signing_key)) + if (sign_buffer(&cert, &cert, signing_key, 0)) die(_("failed to sign the push certificate")); packet_buf_write(req_buf, "push-cert%c%s", 0, cap_string);
diff --git a/sequencer.c b/sequencer.c index 5476d39..b7d8dca 100644 --- a/sequencer.c +++ b/sequencer.c
@@ -209,6 +209,7 @@ static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, "rebase-merge/reschedul static GIT_PATH_FUNC(rebase_path_no_reschedule_failed_exec, "rebase-merge/no-reschedule-failed-exec") static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, "rebase-merge/drop_redundant_commits") static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, "rebase-merge/keep_redundant_commits") +static GIT_PATH_FUNC(rebase_path_trailer, "rebase-merge/trailer") /* * A 'struct replay_ctx' represents the private state of the sequencer. @@ -420,6 +421,7 @@ void replay_opts_release(struct replay_opts *opts) if (opts->revs) release_revisions(opts->revs); free(opts->revs); + strvec_clear(&opts->trailer_args); replay_ctx_release(ctx); free(opts->ctx); } @@ -767,7 +769,7 @@ static int do_recursive_merge(struct repository *r, o.buffer_output = 2; o.show_rename_progress = 1; - head_tree = parse_tree_indirect(head); + head_tree = repo_parse_tree_indirect(the_repository, head); if (!head_tree) return error(_("unable to read tree (%s)"), oid_to_hex(head)); next_tree = next ? repo_get_commit_tree(r, next) : empty_tree(r); @@ -1292,32 +1294,40 @@ int update_head_with_reflog(const struct commit *old_head, return ret; } +static int pipe_from_strbuf(int hook_stdin_fd, void *pp_cb, void *pp_task_cb UNUSED) +{ + struct hook_cb_data *hook_cb = pp_cb; + struct strbuf *to_pipe = hook_cb->options->feed_pipe_ctx; + int ret; + + if (!to_pipe) + BUG("pipe_from_strbuf called without feed_pipe_ctx"); + + ret = write_in_full(hook_stdin_fd, to_pipe->buf, to_pipe->len); + if (ret < 0 && errno != EPIPE) + return ret; + + return 1; /* done writing */ +} + static int run_rewrite_hook(const struct object_id *oldoid, const struct object_id *newoid) { - struct child_process proc = CHILD_PROCESS_INIT; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; int code; struct strbuf sb = STRBUF_INIT; - const char *hook_path = find_hook(the_repository, "post-rewrite"); - if (!hook_path) - return 0; - - strvec_pushl(&proc.args, hook_path, "amend", NULL); - proc.in = -1; - proc.stdout_to_stderr = 1; - proc.trace2_hook_name = "post-rewrite"; - - code = start_command(&proc); - if (code) - return code; strbuf_addf(&sb, "%s %s\n", oid_to_hex(oldoid), oid_to_hex(newoid)); - sigchain_push(SIGPIPE, SIG_IGN); - write_in_full(proc.in, sb.buf, sb.len); - close(proc.in); + + opt.feed_pipe_ctx = &sb; + opt.feed_pipe = pipe_from_strbuf; + + strvec_push(&opt.args, "amend"); + + code = run_hooks_opt(the_repository, "post-rewrite", &opt); + strbuf_release(&sb); - sigchain_pop(SIGPIPE); - return finish_command(&proc); + return code; } void commit_post_rewrite(struct repository *r, @@ -1558,7 +1568,7 @@ static int try_to_commit(struct repository *r, res = error(_("unable to parse commit author")); goto out; } - parents = copy_commit_list(current_head->parents); + parents = commit_list_copy(current_head->parents); extra = read_commit_extra_headers(current_head, exclude_gpgsig); } else if (current_head && (!(flags & CREATE_ROOT_COMMIT) || (flags & AMEND_MSG))) { @@ -1690,7 +1700,7 @@ static int try_to_commit(struct repository *r, out: free_commit_extra_headers(extra); - free_commit_list(parents); + commit_list_free(parents); strbuf_release(&err); strbuf_release(&commit_msg); free(amend_author); @@ -2019,12 +2029,15 @@ static int append_squash_message(struct strbuf *buf, const char *body, if (is_fixup_flag(command, flag) && !seen_squash(ctx)) { /* * We're replacing the commit message so we need to - * append the Signed-off-by: trailer if the user - * requested '--signoff'. + * append any trailers if the user requested + * '--signoff' or '--trailer'. */ if (opts->signoff) append_signoff(buf, 0, 0); + if (opts->trailer_args.nr) + amend_strbuf_with_trailers(buf, &opts->trailer_args); + if ((command == TODO_FIXUP) && (flag & TODO_REPLACE_FIXUP_MSG) && (file_exists(rebase_path_fixup_msg()) || @@ -2198,15 +2211,16 @@ static int should_edit(struct replay_opts *opts) { return opts->edit; } -static void refer_to_commit(struct replay_opts *opts, - struct strbuf *msgbuf, struct commit *commit) +static void refer_to_commit(struct repository *r, struct strbuf *msgbuf, + const struct commit *commit, + bool use_commit_reference) { - if (opts->commit_use_reference) { + if (use_commit_reference) { struct pretty_print_context ctx = { .abbrev = DEFAULT_ABBREV, .date_mode.type = DATE_SHORT, }; - repo_format_commit_message(the_repository, commit, + repo_format_commit_message(r, commit, "%h (%s, %ad)", msgbuf, &ctx); } else { strbuf_addstr(msgbuf, oid_to_hex(&commit->object.oid)); @@ -2356,38 +2370,14 @@ static int do_pick_commit(struct repository *r, */ if (command == TODO_REVERT) { - const char *orig_subject; - base = commit; base_label = msg.label; next = parent; next_label = msg.parent_label; - if (opts->commit_use_reference) { - strbuf_commented_addf(&ctx->message, comment_line_str, - "*** SAY WHY WE ARE REVERTING ON THE TITLE LINE ***"); - } else if (skip_prefix(msg.subject, "Revert \"", &orig_subject) && - /* - * We don't touch pre-existing repeated reverts, because - * theoretically these can be nested arbitrarily deeply, - * thus requiring excessive complexity to deal with. - */ - !starts_with(orig_subject, "Revert \"")) { - strbuf_addstr(&ctx->message, "Reapply \""); - strbuf_addstr(&ctx->message, orig_subject); - strbuf_addstr(&ctx->message, "\n"); - } else { - strbuf_addstr(&ctx->message, "Revert \""); - strbuf_addstr(&ctx->message, msg.subject); - strbuf_addstr(&ctx->message, "\"\n"); - } - strbuf_addstr(&ctx->message, "\nThis reverts commit "); - refer_to_commit(opts, &ctx->message, commit); - - if (commit->parents && commit->parents->next) { - strbuf_addstr(&ctx->message, ", reversing\nchanges made to "); - refer_to_commit(opts, &ctx->message, parent); - } - strbuf_addstr(&ctx->message, ".\n"); + sequencer_format_revert_message(r, msg.subject, commit, + parent, + opts->commit_use_reference, + &ctx->message); } else { const char *p; @@ -2443,6 +2433,9 @@ static int do_pick_commit(struct repository *r, if (opts->signoff && !is_fixup(command)) append_signoff(&ctx->message, 0, 0); + if (opts->trailer_args.nr && !is_fixup(command)) + amend_strbuf_with_trailers(&ctx->message, &opts->trailer_args); + if (is_rebase_i(opts) && write_author_script(msg.message) < 0) res = -1; else if (!opts->strategy || @@ -2468,8 +2461,8 @@ static int do_pick_commit(struct repository *r, res |= try_merge_command(r, opts->strategy, opts->xopts.nr, opts->xopts.v, common, oid_to_hex(&head), remotes); - free_commit_list(common); - free_commit_list(remotes); + commit_list_free(common); + commit_list_free(remotes); } /* @@ -3172,6 +3165,33 @@ static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf) parse_strategy_opts(opts, buf->buf); } +static int read_trailers(struct replay_opts *opts, struct strbuf *buf) +{ + ssize_t len; + + strbuf_reset(buf); + len = strbuf_read_file(buf, rebase_path_trailer(), 0); + if (len > 0) { + char *p = buf->buf, *nl; + + trailer_config_init(); + + while ((nl = strchr(p, '\n'))) { + *nl = '\0'; + if (!*p) + return error(_("trailers file contains empty line")); + strvec_push(&opts->trailer_args, p); + p = nl + 1; + } + } else if (!len) { + return error(_("trailers file is empty")); + } else if (errno != ENOENT) { + return error(_("cannot read trailers files")); + } + + return 0; +} + static int read_populate_opts(struct replay_opts *opts) { struct replay_ctx *ctx = opts->ctx; @@ -3233,6 +3253,11 @@ static int read_populate_opts(struct replay_opts *opts) opts->keep_redundant_commits = 1; read_strategy_opts(opts, &buf); + + if (read_trailers(opts, &buf)) { + ret = -1; + goto done_rebase_i; + } strbuf_reset(&buf); if (read_oneliner(&ctx->current_fixups, @@ -3328,6 +3353,14 @@ int write_basic_state(struct replay_opts *opts, const char *head_name, write_file(rebase_path_reschedule_failed_exec(), "%s", ""); else write_file(rebase_path_no_reschedule_failed_exec(), "%s", ""); + if (opts->trailer_args.nr) { + struct strbuf buf = STRBUF_INIT; + + for (size_t i = 0; i < opts->trailer_args.nr; i++) + strbuf_addf(&buf, "%s\n", opts->trailer_args.v[i]); + write_file(rebase_path_trailer(), "%s", buf.buf); + strbuf_release(&buf); + } return 0; } @@ -4044,7 +4077,7 @@ static int do_reset(struct repository *r, goto cleanup; } - tree = parse_tree_indirect(&oid); + tree = repo_parse_tree_indirect(the_repository, &oid); if (!tree) return error(_("unable to read tree (%s)"), oid_to_hex(&oid)); prime_cache_tree(r, r->index, tree); @@ -4309,7 +4342,7 @@ static int do_merge(struct repository *r, git_path_merge_head(r), 0); write_message("no-ff", 5, git_path_merge_mode(r), 0); - bases = reverse_commit_list(bases); + bases = commit_list_reverse(bases); repo_read_index(r); init_ui_merge_options(&o, r); @@ -4373,8 +4406,8 @@ static int do_merge(struct repository *r, leave_merge: strbuf_release(&ref_name); rollback_lock_file(&lock); - free_commit_list(to_merge); - free_commit_list(bases); + commit_list_free(to_merge); + commit_list_free(bases); return ret; } @@ -5572,6 +5605,43 @@ int sequencer_pick_revisions(struct repository *r, return res; } +void sequencer_format_revert_message(struct repository *r, + const char *subject, + const struct commit *commit, + const struct commit *parent, + bool use_commit_reference, + struct strbuf *message) +{ + const char *orig_subject; + + if (use_commit_reference) { + strbuf_commented_addf(message, comment_line_str, + "*** SAY WHY WE ARE REVERTING ON THE TITLE LINE ***"); + } else if (skip_prefix(subject, "Revert \"", &orig_subject) && + /* + * We don't touch pre-existing repeated reverts, because + * theoretically these can be nested arbitrarily deeply, + * thus requiring excessive complexity to deal with. + */ + !starts_with(orig_subject, "Revert \"")) { + strbuf_addstr(message, "Reapply \""); + strbuf_addstr(message, orig_subject); + strbuf_addstr(message, "\n"); + } else { + strbuf_addstr(message, "Revert \""); + strbuf_addstr(message, subject); + strbuf_addstr(message, "\"\n"); + } + strbuf_addstr(message, "\nThis reverts commit "); + refer_to_commit(r, message, commit, use_commit_reference); + + if (commit->parents && commit->parents->next) { + strbuf_addstr(message, ", reversing\nchanges made to "); + refer_to_commit(r, message, parent, use_commit_reference); + } + strbuf_addstr(message, ".\n"); +} + void append_signoff(struct strbuf *msgbuf, size_t ignore_footer, unsigned flag) { unsigned no_dup_sob = flag & APPEND_SIGNOFF_DEDUP; @@ -6031,11 +6101,11 @@ static int make_script_with_merges(struct pretty_print_context *pp, oidset_insert(&shown, oid); } - free_commit_list(list); + commit_list_free(list); } - free_commit_list(commits); - free_commit_list(tips); + commit_list_free(commits); + commit_list_free(tips); strbuf_release(&label_from_message); strbuf_release(&oneline);
diff --git a/sequencer.h b/sequencer.h index 719684c..a6fa670 100644 --- a/sequencer.h +++ b/sequencer.h
@@ -57,6 +57,8 @@ struct replay_opts { int ignore_date; int commit_use_reference; + struct strvec trailer_args; + int mainline; char *gpg_sign; @@ -84,6 +86,7 @@ struct replay_opts { #define REPLAY_OPTS_INIT { \ .edit = -1, \ .action = -1, \ + .trailer_args = STRVEC_INIT, \ .xopts = STRVEC_INIT, \ .ctx = replay_ctx_new(), \ } @@ -271,4 +274,17 @@ int sequencer_determine_whence(struct repository *r, enum commit_whence *whence) */ int sequencer_get_update_refs_state(const char *wt_dir, struct string_list *refs); +/* + * Format a revert commit message with appropriate 'Revert "<subject>"' or + * 'Reapply "<subject>"' prefix and 'This reverts commit <ref>.' body. + * When use_commit_reference is set, <ref> is an abbreviated hash with + * subject and date; otherwise the full hex hash is used. + */ +void sequencer_format_revert_message(struct repository *r, + const char *subject, + const struct commit *commit, + const struct commit *parent, + bool use_commit_reference, + struct strbuf *message); + #endif /* SEQUENCER_H */
diff --git a/serve.c b/serve.c index 53ecab3..49a6e39 100644 --- a/serve.c +++ b/serve.c
@@ -14,7 +14,7 @@ static int advertise_sid = -1; static int advertise_object_info = -1; -static int client_hash_algo = GIT_HASH_SHA1_LEGACY; +static uint32_t client_hash_algo = GIT_HASH_SHA1_LEGACY; static int always_advertise(struct repository *r UNUSED, struct strbuf *value UNUSED)
diff --git a/server-info.c b/server-info.c index b9a7105..4243e24 100644 --- a/server-info.c +++ b/server-info.c
@@ -148,23 +148,21 @@ static int update_info_file(struct repository *r, char *path, return ret; } -static int add_info_ref(const char *path, const char *referent UNUSED, const struct object_id *oid, - int flag UNUSED, - void *cb_data) +static int add_info_ref(const struct reference *ref, void *cb_data) { struct update_info_ctx *uic = cb_data; - struct object *o = parse_object(uic->repo, oid); + struct object *o = parse_object(uic->repo, ref->oid); if (!o) return -1; - if (uic_printf(uic, "%s %s\n", oid_to_hex(oid), path) < 0) + if (uic_printf(uic, "%s %s\n", oid_to_hex(ref->oid), ref->name) < 0) return -1; if (o->type == OBJ_TAG) { - o = deref_tag(uic->repo, o, path, 0); + o = deref_tag(uic->repo, o, ref->name, 0); if (o) if (uic_printf(uic, "%s %s^{}\n", - oid_to_hex(&o->oid), path) < 0) + oid_to_hex(&o->oid), ref->name) < 0) return -1; } return 0;
diff --git a/setup.c b/setup.c index 7086741..7ec4427 100644 --- a/setup.c +++ b/setup.c
@@ -22,11 +22,9 @@ #include "chdir-notify.h" #include "path.h" #include "quote.h" -#include "tmp-objdir.h" #include "trace.h" #include "trace2.h" #include "worktree.h" -#include "exec-cmd.h" static int inside_git_dir = -1; static int inside_work_tree = -1; @@ -633,6 +631,21 @@ static enum extension_result handle_extension_v0(const char *var, return EXTENSION_UNKNOWN; } +static void parse_reference_uri(const char *value, char **format, + char **payload) +{ + const char *schema_end; + + schema_end = strstr(value, "://"); + if (!schema_end) { + *format = xstrdup(value); + *payload = NULL; + } else { + *format = xstrndup(value, schema_end - value); + *payload = xstrdup_or_null(schema_end + 3); + } +} + /* * Record any new extensions in this function. */ @@ -675,10 +688,17 @@ static enum extension_result handle_extension(const char *var, return EXTENSION_OK; } else if (!strcmp(ext, "refstorage")) { unsigned int format; + char *format_str; if (!value) return config_error_nonbool(var); - format = ref_storage_format_by_name(value); + + parse_reference_uri(value, &format_str, + &data->ref_storage_payload); + + format = ref_storage_format_by_name(format_str); + free(format_str); + if (format == REF_STORAGE_FORMAT_UNKNOWN) return error(_("invalid value for '%s': '%s'"), "extensions.refstorage", value); @@ -687,6 +707,9 @@ static enum extension_result handle_extension(const char *var, } else if (!strcmp(ext, "relativeworktrees")) { data->relative_worktrees = git_config_bool(var, value); return EXTENSION_OK; + } else if (!strcmp(ext, "submodulepathconfig")) { + data->submodule_path_cfg = git_config_bool(var, value); + return EXTENSION_OK; } return EXTENSION_UNKNOWN; } @@ -851,6 +874,7 @@ void clear_repository_format(struct repository_format *format) string_list_clear(&format->v1_only_extensions, 0); free(format->work_tree); free(format->partial_clone); + free(format->ref_storage_payload); init_repository_format(format); } @@ -896,8 +920,10 @@ int verify_repository_format(const struct repository_format *format, void read_gitfile_error_die(int error_code, const char *path, const char *dir) { switch (error_code) { - case READ_GITFILE_ERR_STAT_FAILED: case READ_GITFILE_ERR_NOT_A_FILE: + case READ_GITFILE_ERR_STAT_FAILED: + case READ_GITFILE_ERR_MISSING: + case READ_GITFILE_ERR_IS_A_DIR: /* non-fatal; follow return path */ break; case READ_GITFILE_ERR_OPEN_FAILED: @@ -940,8 +966,14 @@ const char *read_gitfile_gently(const char *path, int *return_error_code) static struct strbuf realpath = STRBUF_INIT; if (stat(path, &st)) { - /* NEEDSWORK: discern between ENOENT vs other errors */ - error_code = READ_GITFILE_ERR_STAT_FAILED; + if (errno == ENOENT || errno == ENOTDIR) + error_code = READ_GITFILE_ERR_MISSING; + else + error_code = READ_GITFILE_ERR_STAT_FAILED; + goto cleanup_return; + } + if (S_ISDIR(st.st_mode)) { + error_code = READ_GITFILE_ERR_IS_A_DIR; goto cleanup_return; } if (!S_ISREG(st.st_mode)) { @@ -1002,6 +1034,83 @@ const char *read_gitfile_gently(const char *path, int *return_error_code) return error_code ? NULL : path; } +static void setup_git_env_internal(const char *git_dir, + bool skip_initializing_odb) +{ + char *git_replace_ref_base; + const char *shallow_file; + const char *replace_ref_base; + struct set_gitdir_args args = { NULL }; + struct strvec to_free = STRVEC_INIT; + + args.commondir = getenv_safe(&to_free, GIT_COMMON_DIR_ENVIRONMENT); + args.object_dir = getenv_safe(&to_free, DB_ENVIRONMENT); + args.graft_file = getenv_safe(&to_free, GRAFT_ENVIRONMENT); + args.index_file = getenv_safe(&to_free, INDEX_ENVIRONMENT); + args.alternate_db = getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT); + if (getenv(GIT_QUARANTINE_ENVIRONMENT)) + args.disable_ref_updates = true; + args.skip_initializing_odb = skip_initializing_odb; + + repo_set_gitdir(the_repository, git_dir, &args); + strvec_clear(&to_free); + + 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); + + shallow_file = getenv(GIT_SHALLOW_FILE_ENVIRONMENT); + if (shallow_file) + set_alternate_shallow_file(the_repository, shallow_file, 0); + + if (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0)) + fetch_if_missing = 0; +} + +void setup_git_env(const char *git_dir) +{ + setup_git_env_internal(git_dir, false); +} + +static void set_git_dir_1(const char *path, bool skip_initializing_odb) +{ + xsetenv(GIT_DIR_ENVIRONMENT, path, 1); + setup_git_env_internal(path, skip_initializing_odb); +} + +static void update_relative_gitdir(const char *name UNUSED, + const char *old_cwd, + const char *new_cwd, + void *data UNUSED) +{ + char *path = reparent_relative_path(old_cwd, new_cwd, + repo_get_git_dir(the_repository)); + trace_printf_key(&trace_setup_key, + "setup: move $GIT_DIR to '%s'", + path); + set_git_dir_1(path, true); + free(path); +} + +static void set_git_dir(const char *path, int make_realpath) +{ + struct strbuf realpath = STRBUF_INIT; + + if (make_realpath) { + strbuf_realpath(&realpath, path, 1); + path = realpath.buf; + } + + set_git_dir_1(path, false); + if (!is_absolute_path(path)) + chdir_notify_register(NULL, update_relative_gitdir, NULL); + + strbuf_release(&realpath); +} + static const char *setup_explicit_git_dir(const char *gitdirenv, struct strbuf *cwd, struct repository_format *repo_fmt, @@ -1248,7 +1357,7 @@ static int safe_directory_cb(const char *key, const char *value, } else { char *allowed = NULL; - if (!git_config_pathname(&allowed, key, value)) { + if (!git_config_pathname(&allowed, key, value) && allowed) { char *normalized = NULL; /* @@ -1500,20 +1609,37 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir, if (offset > min_offset) strbuf_addch(dir, '/'); strbuf_addstr(dir, DEFAULT_GIT_DIR_ENVIRONMENT); - gitdirenv = read_gitfile_gently(dir->buf, die_on_error ? - NULL : &error_code); + gitdirenv = read_gitfile_gently(dir->buf, &error_code); if (!gitdirenv) { - if (die_on_error || - error_code == READ_GITFILE_ERR_NOT_A_FILE) { - /* NEEDSWORK: fail if .git is not file nor dir */ + switch (error_code) { + case READ_GITFILE_ERR_MISSING: + /* no .git in this directory, move on */ + break; + case READ_GITFILE_ERR_IS_A_DIR: if (is_git_directory(dir->buf)) { gitdirenv = DEFAULT_GIT_DIR_ENVIRONMENT; gitdir_path = xstrdup(dir->buf); } - } else if (error_code != READ_GITFILE_ERR_STAT_FAILED) - return GIT_DIR_INVALID_GITFILE; - } else + break; + case READ_GITFILE_ERR_STAT_FAILED: + if (die_on_error) + die(_("error reading '%s'"), dir->buf); + else + return GIT_DIR_INVALID_GITFILE; + case READ_GITFILE_ERR_NOT_A_FILE: + if (die_on_error) + die(_("not a regular file: '%s'"), dir->buf); + else + return GIT_DIR_INVALID_GITFILE; + default: + if (die_on_error) + read_gitfile_error_die(error_code, dir->buf, NULL); + else + return GIT_DIR_INVALID_GITFILE; + } + } else { gitfile = xstrdup(dir->buf); + } /* * Earlier, we tentatively added DEFAULT_GIT_DIR_ENVIRONMENT * to check that directory for a repository. @@ -1628,79 +1754,85 @@ enum discovery_result discover_git_directory_reason(struct strbuf *commondir, return result; } -void setup_git_env(const char *git_dir) +const char *enter_repo(const char *path, unsigned flags) { - char *git_replace_ref_base; - const char *shallow_file; - const char *replace_ref_base; - struct set_gitdir_args args = { NULL }; - struct strvec to_free = STRVEC_INIT; + static struct strbuf validated_path = STRBUF_INIT; + static struct strbuf used_path = STRBUF_INIT; - args.commondir = getenv_safe(&to_free, GIT_COMMON_DIR_ENVIRONMENT); - args.object_dir = getenv_safe(&to_free, DB_ENVIRONMENT); - args.graft_file = getenv_safe(&to_free, GRAFT_ENVIRONMENT); - args.index_file = getenv_safe(&to_free, INDEX_ENVIRONMENT); - args.alternate_db = getenv_safe(&to_free, ALTERNATE_DB_ENVIRONMENT); - if (getenv(GIT_QUARANTINE_ENVIRONMENT)) { - args.disable_ref_updates = 1; + if (!path) + return NULL; + + if (!(flags & ENTER_REPO_STRICT)) { + static const char *suffix[] = { + "/.git", "", ".git/.git", ".git", NULL, + }; + const char *gitfile; + int len = strlen(path); + int i; + while ((1 < len) && (path[len-1] == '/')) + len--; + + /* + * We can handle arbitrary-sized buffers, but this remains as a + * sanity check on untrusted input. + */ + if (PATH_MAX <= len) + return NULL; + + strbuf_reset(&used_path); + strbuf_reset(&validated_path); + strbuf_add(&used_path, path, len); + strbuf_add(&validated_path, path, len); + + if (used_path.buf[0] == '~') { + char *newpath = interpolate_path(used_path.buf, 0); + if (!newpath) + return NULL; + strbuf_attach(&used_path, newpath, strlen(newpath), + strlen(newpath)); + } + for (i = 0; suffix[i]; i++) { + struct stat st; + size_t baselen = used_path.len; + strbuf_addstr(&used_path, suffix[i]); + if (!stat(used_path.buf, &st) && + (S_ISREG(st.st_mode) || + (S_ISDIR(st.st_mode) && is_git_directory(used_path.buf)))) { + strbuf_addstr(&validated_path, suffix[i]); + break; + } + strbuf_setlen(&used_path, baselen); + } + if (!suffix[i]) + return NULL; + gitfile = read_gitfile(used_path.buf); + if (!(flags & ENTER_REPO_ANY_OWNER_OK)) + die_upon_dubious_ownership(gitfile, NULL, used_path.buf); + if (gitfile) { + strbuf_reset(&used_path); + strbuf_addstr(&used_path, gitfile); + } + if (chdir(used_path.buf)) + return NULL; + path = validated_path.buf; + } + else { + const char *gitfile = read_gitfile(path); + if (!(flags & ENTER_REPO_ANY_OWNER_OK)) + die_upon_dubious_ownership(gitfile, NULL, path); + if (gitfile) + path = gitfile; + if (chdir(path)) + return NULL; } - repo_set_gitdir(the_repository, git_dir, &args); - strvec_clear(&to_free); - - 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); - - shallow_file = getenv(GIT_SHALLOW_FILE_ENVIRONMENT); - if (shallow_file) - set_alternate_shallow_file(the_repository, shallow_file, 0); - - if (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0)) - fetch_if_missing = 0; -} - -static void set_git_dir_1(const char *path) -{ - xsetenv(GIT_DIR_ENVIRONMENT, path, 1); - setup_git_env(path); -} - -static void update_relative_gitdir(const char *name UNUSED, - const char *old_cwd, - const char *new_cwd, - void *data UNUSED) -{ - char *path = reparent_relative_path(old_cwd, new_cwd, - repo_get_git_dir(the_repository)); - struct tmp_objdir *tmp_objdir = tmp_objdir_unapply_primary_odb(); - - trace_printf_key(&trace_setup_key, - "setup: move $GIT_DIR to '%s'", - path); - set_git_dir_1(path); - if (tmp_objdir) - tmp_objdir_reapply_primary_odb(tmp_objdir, old_cwd, new_cwd); - free(path); -} - -void set_git_dir(const char *path, int make_realpath) -{ - struct strbuf realpath = STRBUF_INIT; - - if (make_realpath) { - strbuf_realpath(&realpath, path, 1); - path = realpath.buf; + if (is_git_directory(".")) { + set_git_dir(".", 0); + check_repository_format(NULL); + return path; } - set_git_dir_1(path); - if (!is_absolute_path(path)) - chdir_notify_register(NULL, update_relative_gitdir, NULL); - - strbuf_release(&realpath); + return NULL; } static int git_work_tree_initialized; @@ -1733,6 +1865,7 @@ const char *setup_git_directory_gently(int *nongit_ok) static struct strbuf cwd = STRBUF_INIT; struct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT, report = STRBUF_INIT; const char *prefix = NULL; + const char *ref_backend_uri; struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; /* @@ -1860,11 +1993,14 @@ const char *setup_git_directory_gently(int *nongit_ok) repo_set_compat_hash_algo(the_repository, repo_fmt.compat_hash_algo); repo_set_ref_storage_format(the_repository, - repo_fmt.ref_storage_format); + repo_fmt.ref_storage_format, + repo_fmt.ref_storage_payload); the_repository->repository_format_worktree_config = repo_fmt.worktree_config; the_repository->repository_format_relative_worktrees = repo_fmt.relative_worktrees; + the_repository->repository_format_submodule_path_cfg = + repo_fmt.submodule_path_cfg; /* take ownership of repo_fmt.partial_clone */ the_repository->repository_format_partial_clone = repo_fmt.partial_clone; @@ -1889,6 +2025,25 @@ const char *setup_git_directory_gently(int *nongit_ok) setenv(GIT_PREFIX_ENVIRONMENT, "", 1); } + /* + * The env variable should override the repository config + * for 'extensions.refStorage'. + */ + ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT); + if (ref_backend_uri) { + char *backend, *payload; + enum ref_storage_format format; + + parse_reference_uri(ref_backend_uri, &backend, &payload); + format = ref_storage_format_by_name(backend); + if (format == REF_STORAGE_FORMAT_UNKNOWN) + die(_("unknown ref storage format: '%s'"), backend); + repo_set_ref_storage_format(the_repository, format, payload); + + free(backend); + free(payload); + } + setup_original_cwd(); strbuf_release(&dir); @@ -1960,9 +2115,12 @@ void check_repository_format(struct repository_format *fmt) repo_set_hash_algo(the_repository, fmt->hash_algo); repo_set_compat_hash_algo(the_repository, fmt->compat_hash_algo); repo_set_ref_storage_format(the_repository, - fmt->ref_storage_format); + fmt->ref_storage_format, + fmt->ref_storage_payload); the_repository->repository_format_worktree_config = fmt->worktree_config; + the_repository->repository_format_submodule_path_cfg = + fmt->submodule_path_cfg; the_repository->repository_format_relative_worktrees = fmt->relative_worktrees; the_repository->repository_format_partial_clone = @@ -2221,6 +2379,7 @@ void initialize_repository_version(int hash_algo, { struct strbuf repo_version = STRBUF_INIT; int target_version = GIT_REPO_VERSION; + int default_submodule_path_config = 0; /* * Note that we initialize the repository version to 1 when the ref @@ -2230,7 +2389,8 @@ void initialize_repository_version(int hash_algo, * the remote repository's format. */ if (hash_algo != GIT_HASH_SHA1_LEGACY || - ref_storage_format != REF_STORAGE_FORMAT_FILES) + ref_storage_format != REF_STORAGE_FORMAT_FILES || + the_repository->ref_storage_payload) target_version = GIT_REPO_VERSION_READ; if (hash_algo != GIT_HASH_SHA1_LEGACY && hash_algo != GIT_HASH_UNKNOWN) @@ -2239,11 +2399,20 @@ void initialize_repository_version(int hash_algo, else if (reinit) repo_config_set_gently(the_repository, "extensions.objectformat", NULL); - if (ref_storage_format != REF_STORAGE_FORMAT_FILES) + if (the_repository->ref_storage_payload) { + struct strbuf ref_uri = STRBUF_INIT; + + strbuf_addf(&ref_uri, "%s://%s", + ref_storage_format_to_name(ref_storage_format), + the_repository->ref_storage_payload); + repo_config_set(the_repository, "extensions.refstorage", ref_uri.buf); + strbuf_release(&ref_uri); + } else if (ref_storage_format != REF_STORAGE_FORMAT_FILES) { repo_config_set(the_repository, "extensions.refstorage", ref_storage_format_to_name(ref_storage_format)); - else if (reinit) + } else if (reinit) { repo_config_set_gently(the_repository, "extensions.refstorage", NULL); + } if (reinit) { struct strbuf config = STRBUF_INIT; @@ -2259,6 +2428,15 @@ void initialize_repository_version(int hash_algo, clear_repository_format(&repo_fmt); } + repo_config_get_bool(the_repository, "init.defaultSubmodulePathConfig", + &default_submodule_path_config); + if (default_submodule_path_config) { + /* extensions.submodulepathconfig requires at least version 1 */ + if (target_version == 0) + target_version = 1; + repo_config_set(the_repository, "extensions.submodulepathconfig", "true"); + } + strbuf_addf(&repo_version, "%d", target_version); repo_config_set(the_repository, "core.repositoryformatversion", repo_version.buf); @@ -2277,14 +2455,12 @@ static int is_reinit(void) return ret; } -void create_reference_database(enum ref_storage_format ref_storage_format, - const char *initial_branch, int quiet) +void create_reference_database(const char *initial_branch, int quiet) { struct strbuf err = STRBUF_INIT; char *to_free = NULL; int reinit = is_reinit(); - repo_set_ref_storage_format(the_repository, ref_storage_format); if (ref_store_create_on_disk(get_main_ref_store(the_repository), 0, &err)) die("failed to set up refs db: %s", err.buf); @@ -2518,6 +2694,7 @@ static void repository_format_configure(struct repository_format *repo_fmt, .ignore_repo = 1, .ignore_worktree = 1, }; + const char *ref_backend_uri; const char *env; config_with_options(read_default_format_config, &cfg, NULL, NULL, &opts); @@ -2563,7 +2740,26 @@ static void repository_format_configure(struct repository_format *repo_fmt, } else { repo_fmt->ref_storage_format = REF_STORAGE_FORMAT_DEFAULT; } - repo_set_ref_storage_format(the_repository, repo_fmt->ref_storage_format); + + + ref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT); + if (ref_backend_uri) { + char *backend, *payload; + enum ref_storage_format format; + + parse_reference_uri(ref_backend_uri, &backend, &payload); + format = ref_storage_format_by_name(backend); + if (format == REF_STORAGE_FORMAT_UNKNOWN) + die(_("unknown ref storage format: '%s'"), backend); + + repo_fmt->ref_storage_format = format; + repo_fmt->ref_storage_payload = payload; + + free(backend); + } + + repo_set_ref_storage_format(the_repository, repo_fmt->ref_storage_format, + repo_fmt->ref_storage_payload); } int init_db(const char *git_dir, const char *real_git_dir, @@ -2611,7 +2807,7 @@ int init_db(const char *git_dir, const char *real_git_dir, * have set up the repository format such that we can evaluate * includeIf conditions correctly in the case of re-initialization. */ - repo_config(the_repository, platform_core_config, NULL); + repo_config(the_repository, git_default_core_config, NULL); safe_create_dir(the_repository, git_dir, 0); @@ -2619,8 +2815,7 @@ int init_db(const char *git_dir, const char *real_git_dir, &repo_fmt, init_shared_repository); if (!(flags & INIT_DB_SKIP_REFDB)) - create_reference_database(repo_fmt.ref_storage_format, - initial_branch, flags & INIT_DB_QUIET); + create_reference_database(initial_branch, flags & INIT_DB_QUIET); create_object_directory(); if (repo_settings_get_shared_repository(the_repository)) {
diff --git a/setup.h b/setup.h index 8522fa8..80bc6e5 100644 --- a/setup.h +++ b/setup.h
@@ -36,6 +36,8 @@ int is_nonbare_repository_dir(struct strbuf *path); #define READ_GITFILE_ERR_NO_PATH 6 #define READ_GITFILE_ERR_NOT_A_REPO 7 #define READ_GITFILE_ERR_TOO_LARGE 8 +#define READ_GITFILE_ERR_MISSING 9 +#define READ_GITFILE_ERR_IS_A_DIR 10 void read_gitfile_error_die(int error_code, const char *path, const char *dir); const char *read_gitfile_gently(const char *path, int *return_error_code); #define read_gitfile(path) read_gitfile_gently((path), NULL) @@ -94,9 +96,46 @@ static inline int discover_git_directory(struct strbuf *commondir, return 0; } -void set_git_dir(const char *path, int make_realpath); void set_git_work_tree(const char *tree); +/* Flags that can be passed to `enter_repo()`. */ +enum { + /* + * Callers that require exact paths (as opposed to allowing known + * suffixes like ".git", ".git/.git" to be omitted) can set this bit. + */ + ENTER_REPO_STRICT = (1<<0), + + /* + * Callers that are willing to run without ownership check can set this + * bit. + */ + ENTER_REPO_ANY_OWNER_OK = (1<<1), +}; + +/* + * Discover and enter a repository. + * + * First, one directory to try is determined by the following algorithm. + * + * (0) If "strict" is given, the path is used as given and no DWIM is + * done. Otherwise: + * (1) "~/path" to mean path under the running user's home directory; + * (2) "~user/path" to mean path under named user's home directory; + * (3) "relative/path" to mean cwd relative directory; or + * (4) "/absolute/path" to mean absolute directory. + * + * Unless "strict" is given, we check "%s/.git", "%s", "%s.git/.git", "%s.git" + * in this order. We select the first one that is a valid git repository, and + * chdir() to it. If none match, or we fail to chdir, we return NULL. + * + * If all goes well, we return the directory we used to chdir() (but + * before ~user is expanded), avoiding getcwd() resolving symbolic + * links. User relative paths are also returned as they are given, + * except DWIM suffixing. + */ +const char *enter_repo(const char *path, unsigned flags); + const char *setup_git_directory_gently(int *); const char *setup_git_directory(void); char *prefix_path(const char *prefix, int len, const char *path); @@ -130,10 +169,12 @@ struct repository_format { char *partial_clone; /* value of extensions.partialclone */ int worktree_config; int relative_worktrees; + int submodule_path_cfg; int is_bare; int hash_algo; int compat_hash_algo; enum ref_storage_format ref_storage_format; + char *ref_storage_payload; int sparse_index; char *work_tree; struct string_list unknown_extensions; @@ -203,8 +244,7 @@ int init_db(const char *git_dir, const char *real_git_dir, void initialize_repository_version(int hash_algo, enum ref_storage_format ref_storage_format, int reinit); -void create_reference_database(enum ref_storage_format ref_storage_format, - const char *initial_branch, int quiet); +void create_reference_database(const char *initial_branch, int quiet); /* * NOTE NOTE NOTE!!
diff --git a/shallow.c b/shallow.c index d9cd4e2..7a3dd56 100644 --- a/shallow.c +++ b/shallow.c
@@ -40,7 +40,7 @@ int register_shallow(struct repository *r, const struct object_id *oid) oidcpy(&graft->oid, oid); graft->nr_parent = -1; if (commit && commit->object.parsed) { - free_commit_list(commit->parents); + commit_list_free(commit->parents); commit->parents = NULL; } return register_commit_graft(r, graft, 0); @@ -130,11 +130,24 @@ static void free_depth_in_slab(int **ptr) { FREE_AND_NULL(*ptr); } -struct commit_list *get_shallow_commits(struct object_array *heads, int depth, - int shallow_flag, int not_shallow_flag) +/* + * This is a common internal function that can either return a list of + * shallow commits or calculate the current maximum depth of a shallow + * repository, depending on the input parameters. + * + * Depth calculation is triggered by passing the `shallows` parameter. + * In this case, the computed depth is stored in `max_cur_depth` (if it is + * provided), and the function returns NULL. + * + * Otherwise, `max_cur_depth` remains unchanged and the function returns + * a list of shallow commits. + */ +static struct commit_list *get_shallows_or_depth(struct object_array *heads, + struct object_array *shallows, int *max_cur_depth, + int depth, int shallow_flag, int not_shallow_flag) { size_t i = 0; - int cur_depth = 0; + int cur_depth = 0, cur_depth_shallow = 0; struct commit_list *result = NULL; struct object_array stack = OBJECT_ARRAY_INIT; struct commit *commit = NULL; @@ -168,16 +181,30 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth, } parse_commit_or_die(commit); cur_depth++; - if ((depth != INFINITE_DEPTH && cur_depth >= depth) || - (is_repository_shallow(the_repository) && !commit->parents && - (graft = lookup_commit_graft(the_repository, &commit->object.oid)) != NULL && - graft->nr_parent < 0)) { - commit_list_insert(commit, &result); - commit->object.flags |= shallow_flag; - commit = NULL; - continue; + if (shallows) { + for (size_t j = 0; j < shallows->nr; j++) + if (oideq(&commit->object.oid, &shallows->objects[j].item->oid)) + if (!cur_depth_shallow || cur_depth < cur_depth_shallow) + cur_depth_shallow = cur_depth; + + if ((is_repository_shallow(the_repository) && !commit->parents && + (graft = lookup_commit_graft(the_repository, &commit->object.oid)) != NULL && + graft->nr_parent < 0)) { + commit = NULL; + continue; + } + } else { + if ((depth != INFINITE_DEPTH && cur_depth >= depth) || + (is_repository_shallow(the_repository) && !commit->parents && + (graft = lookup_commit_graft(the_repository, &commit->object.oid)) != NULL && + graft->nr_parent < 0)) { + commit_list_insert(commit, &result); + commit->object.flags |= shallow_flag; + commit = NULL; + continue; + } + commit->object.flags |= not_shallow_flag; } - commit->object.flags |= not_shallow_flag; for (p = commit->parents, commit = NULL; p; p = p->next) { int **depth_slot = commit_depth_at(&depths, p->item); if (!*depth_slot) { @@ -198,10 +225,32 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth, } } deep_clear_commit_depth(&depths, free_depth_in_slab); + object_array_clear(&stack); + if (shallows && max_cur_depth) + *max_cur_depth = cur_depth_shallow; return result; } +int get_shallows_depth(struct object_array *heads, struct object_array *shallows) +{ + int max_cur_depth = 0; + get_shallows_or_depth(heads, shallows, &max_cur_depth, 0, 0, 0); + return max_cur_depth; + +} + +struct commit_list *get_shallow_commits(struct object_array *heads, + struct object_array *shallows, int deepen_relative, + int depth, int shallow_flag, int not_shallow_flag) +{ + if (shallows && deepen_relative) { + depth += get_shallows_depth(heads, shallows); + } + return get_shallows_or_depth(heads, NULL, NULL, + depth, shallow_flag, not_shallow_flag); +} + static void show_commit(struct commit *commit, void *data) { commit_list_insert(commit, data); @@ -267,7 +316,7 @@ struct commit_list *get_shallow_commits_by_rev_list(struct strvec *argv, break; } } - free_commit_list(not_shallow_list); + commit_list_free(not_shallow_list); /* * Now we can clean up NOT_SHALLOW on border commits. Having @@ -471,6 +520,7 @@ void prepare_shallow_info(struct shallow_info *info, struct oid_array *sa) { trace_printf_key(&trace_shallow, "shallow: prepare_shallow_info\n"); memset(info, 0, sizeof(*info)); + commit_stack_init(&info->commits); info->shallow = sa; if (!sa) return; @@ -503,6 +553,7 @@ void clear_shallow_info(struct shallow_info *info) free(info->shallow_ref); free(info->ours); free(info->theirs); + commit_stack_clear(&info->commits); } /* Step 4, remove non-existent ones in "theirs" after getting the pack */ @@ -626,14 +677,10 @@ static void paint_down(struct paint_info *info, const struct object_id *oid, free(tmp); } -static int mark_uninteresting(const char *refname UNUSED, - const char *referent UNUSED, - const struct object_id *oid, - int flags UNUSED, - void *cb_data UNUSED) +static int mark_uninteresting(const struct reference *ref, void *cb_data UNUSED) { struct commit *commit = lookup_commit_reference_gently(the_repository, - oid, 1); + ref->oid, 1); if (!commit) return 0; commit->object.flags |= UNINTERESTING; @@ -713,7 +760,7 @@ void assign_shallow_commits_to_refs(struct shallow_info *info, if (used) { int bitmap_size = DIV_ROUND_UP(pi.nr_bits, 32) * sizeof(uint32_t); - memset(used, 0, sizeof(*used) * info->shallow->nr); + MEMZERO_ARRAY(used, info->shallow->nr); for (i = 0; i < nr_shallow; i++) { const struct commit *c = lookup_commit(the_repository, &oid[shallow[i]]); @@ -737,23 +784,13 @@ void assign_shallow_commits_to_refs(struct shallow_info *info, free(shallow); } -struct commit_array { - struct commit **commits; - size_t nr, alloc; -}; - -static int add_ref(const char *refname UNUSED, - const char *referent UNUSED, - const struct object_id *oid, - int flags UNUSED, - void *cb_data) +static int add_ref(const struct reference *ref, void *cb_data) { - struct commit_array *ca = cb_data; - ALLOC_GROW(ca->commits, ca->nr + 1, ca->alloc); - ca->commits[ca->nr] = lookup_commit_reference_gently(the_repository, - oid, 1); - if (ca->commits[ca->nr]) - ca->nr++; + struct commit_stack *cs = cb_data; + struct commit *commit = lookup_commit_reference_gently(the_repository, + ref->oid, 1); + if (commit) + commit_stack_push(cs, commit); return 0; } @@ -778,11 +815,11 @@ static void post_assign_shallow(struct shallow_info *info, uint32_t **bitmap; size_t dst, i, j; size_t bitmap_nr = DIV_ROUND_UP(info->ref->nr, 32); - struct commit_array ca; + struct commit_stack cs = COMMIT_STACK_INIT; trace_printf_key(&trace_shallow, "shallow: post_assign_shallow\n"); if (ref_status) - memset(ref_status, 0, sizeof(*ref_status) * info->ref->nr); + MEMZERO_ARRAY(ref_status, info->ref->nr); /* Remove unreachable shallow commits from "theirs" */ for (i = dst = 0; i < info->nr_theirs; i++) { @@ -801,9 +838,8 @@ static void post_assign_shallow(struct shallow_info *info, } info->nr_theirs = dst; - memset(&ca, 0, sizeof(ca)); - refs_head_ref(get_main_ref_store(the_repository), add_ref, &ca); - refs_for_each_ref(get_main_ref_store(the_repository), add_ref, &ca); + refs_head_ref(get_main_ref_store(the_repository), add_ref, &cs); + refs_for_each_ref(get_main_ref_store(the_repository), add_ref, &cs); /* Remove unreachable shallow commits from "ours" */ for (i = dst = 0; i < info->nr_ours; i++) { @@ -816,7 +852,7 @@ static void post_assign_shallow(struct shallow_info *info, for (j = 0; j < bitmap_nr; j++) if (bitmap[0][j]) { /* Step 7, reachability test at commit level */ - int ret = repo_in_merge_bases_many(the_repository, c, ca.nr, ca.commits, 1); + int ret = repo_in_merge_bases_many(the_repository, c, cs.nr, cs.items, 1); if (ret < 0) exit(128); if (!ret) { @@ -828,7 +864,7 @@ static void post_assign_shallow(struct shallow_info *info, } info->nr_ours = dst; - free(ca.commits); + commit_stack_clear(&cs); } /* (Delayed) step 7, reachability test at commit level */ @@ -838,22 +874,17 @@ int delayed_reachability_test(struct shallow_info *si, int c) struct commit *commit = lookup_commit(the_repository, &si->shallow->oid[c]); - if (!si->commits) { - struct commit_array ca; - - memset(&ca, 0, sizeof(ca)); + if (!si->commits.nr) { refs_head_ref(get_main_ref_store(the_repository), - add_ref, &ca); + add_ref, &si->commits); refs_for_each_ref(get_main_ref_store(the_repository), - add_ref, &ca); - si->commits = ca.commits; - si->nr_commits = ca.nr; + add_ref, &si->commits); } si->reachable[c] = repo_in_merge_bases_many(the_repository, commit, - si->nr_commits, - si->commits, + si->commits.nr, + si->commits.items, 1); if (si->reachable[c] < 0) exit(128);
diff --git a/shallow.h b/shallow.h index ad591bd..e20ca4c 100644 --- a/shallow.h +++ b/shallow.h
@@ -1,6 +1,7 @@ #ifndef SHALLOW_H #define SHALLOW_H +#include "commit.h" #include "lockfile.h" #include "object.h" #include "repository.h" @@ -35,7 +36,9 @@ int commit_shallow_file(struct repository *r, struct shallow_lock *lk); /* rollback $GIT_DIR/shallow and reset stat-validity checks */ void rollback_shallow_file(struct repository *r, struct shallow_lock *lk); +int get_shallows_depth(struct object_array *heads, struct object_array *shallows); struct commit_list *get_shallow_commits(struct object_array *heads, + struct object_array *shallows, int deepen_relative, int depth, int shallow_flag, int not_shallow_flag); struct commit_list *get_shallow_commits_by_rev_list(struct strvec *argv, int shallow_flag, int not_shallow_flag); @@ -69,8 +72,7 @@ struct shallow_info { int *need_reachability_test; int *reachable; int *shallow_ref; - struct commit **commits; - size_t nr_commits; + struct commit_stack commits; }; void prepare_shallow_info(struct shallow_info *, struct oid_array *);
diff --git a/sideband.c b/sideband.c index ea7c252..1ed6614 100644 --- a/sideband.c +++ b/sideband.c
@@ -264,6 +264,7 @@ void send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_ma const char *p = data; while (sz) { + struct iovec iov[2]; unsigned n; char hdr[5]; @@ -273,12 +274,19 @@ void send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_ma if (0 <= band) { xsnprintf(hdr, sizeof(hdr), "%04x", n + 5); hdr[4] = band; - write_or_die(fd, hdr, 5); + iov[0].iov_base = hdr; + iov[0].iov_len = 5; } else { xsnprintf(hdr, sizeof(hdr), "%04x", n + 4); - write_or_die(fd, hdr, 4); + iov[0].iov_base = hdr; + iov[0].iov_len = 4; } - write_or_die(fd, p, n); + + iov[1].iov_base = (void *) p; + iov[1].iov_len = n; + + writev_or_die(fd, iov, ARRAY_SIZE(iov)); + p += n; sz -= n; }
diff --git a/sparse-index.c b/sparse-index.c index 76f90da..13629c0 100644 --- a/sparse-index.c +++ b/sparse-index.c
@@ -152,7 +152,9 @@ static int index_has_unmerged_entries(struct index_state *istate) int is_sparse_index_allowed(struct index_state *istate, int flags) { - if (!core_apply_sparse_checkout || !core_sparse_checkout_cone) + struct repo_config_values *cfg = repo_config_values(the_repository); + + if (!cfg->apply_sparse_checkout || !core_sparse_checkout_cone) return 0; if (!(flags & SPARSE_INDEX_MEMORY_ONLY)) { @@ -670,7 +672,9 @@ static void clear_skip_worktree_from_present_files_full(struct index_state *ista void clear_skip_worktree_from_present_files(struct index_state *istate) { - if (!core_apply_sparse_checkout || + struct repo_config_values *cfg = repo_config_values(the_repository); + + if (!cfg->apply_sparse_checkout || sparse_expect_files_outside_of_patterns) return;
diff --git a/split-index.c b/split-index.c index 4c74c4a..6ba2107 100644 --- a/split-index.c +++ b/split-index.c
@@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" @@ -6,6 +5,7 @@ #include "hash.h" #include "mem-pool.h" #include "read-cache-ll.h" +#include "repository.h" #include "split-index.h" #include "strbuf.h" #include "ewah/ewok.h" @@ -25,16 +25,17 @@ struct split_index *init_split_index(struct index_state *istate) int read_link_extension(struct index_state *istate, const void *data_, unsigned long sz) { + const struct git_hash_algo *algo = istate->repo->hash_algo; const unsigned char *data = data_; struct split_index *si; int ret; - if (sz < the_hash_algo->rawsz) + if (sz < algo->rawsz) return error("corrupt link extension (too short)"); si = init_split_index(istate); - oidread(&si->base_oid, data, the_repository->hash_algo); - data += the_hash_algo->rawsz; - sz -= the_hash_algo->rawsz; + oidread(&si->base_oid, data, algo); + data += algo->rawsz; + sz -= algo->rawsz; if (!sz) return 0; si->delete_bitmap = ewah_new(); @@ -56,7 +57,7 @@ int write_link_extension(struct strbuf *sb, struct index_state *istate) { struct split_index *si = istate->split_index; - strbuf_add(sb, si->base_oid.hash, the_hash_algo->rawsz); + strbuf_add(sb, si->base_oid.hash, istate->repo->hash_algo->rawsz); if (!si->delete_bitmap && !si->replace_bitmap) return 0; ewah_serialize_strbuf(si->delete_bitmap, sb);
diff --git a/src/cargo-meson.sh b/src/cargo-meson.sh index 3998db0..75f3cd1 100755 --- a/src/cargo-meson.sh +++ b/src/cargo-meson.sh
@@ -19,6 +19,18 @@ esac done +case "$(cargo -vV | sed -n 's/^host: \(.*\)$/\1/p')" in + *-windows-msvc) + LIBNAME=gitcore.lib + PATH="$(echo "$PATH" | tr ':' '\n' | grep -Ev "^(/mingw64/bin|/usr/bin)$" | paste -sd: -):/mingw64/bin:/usr/bin" + export PATH + ;; + *-windows-*) + LIBNAME=gitcore.lib;; + *) + LIBNAME=libgitcore.a;; +esac + cargo build --lib --quiet --manifest-path="$SOURCE_DIR/Cargo.toml" --target-dir="$BUILD_DIR" "$@" RET=$? if test $RET -ne 0 @@ -26,13 +38,6 @@ exit $RET fi -case "$(cargo -vV | sed -s 's/^host: \(.*\)$/\1/')" in - *-windows-*) - LIBNAME=gitcore.lib;; - *) - LIBNAME=libgitcore.a;; -esac - if ! cmp "$BUILD_DIR/$BUILD_TYPE/$LIBNAME" "$BUILD_DIR/libgitcore.a" >/dev/null 2>&1 then cp "$BUILD_DIR/$BUILD_TYPE/$LIBNAME" "$BUILD_DIR/libgitcore.a"
diff --git a/src/csum_file.rs b/src/csum_file.rs new file mode 100644 index 0000000..7f2c6c4 --- /dev/null +++ b/src/csum_file.rs
@@ -0,0 +1,81 @@ +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation: version 2 of the License, dated June 1991. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see <https://www.gnu.org/licenses/>. + +use crate::hash::{HashAlgorithm, GIT_MAX_RAWSZ}; +use std::ffi::CStr; +use std::io::{self, Write}; +use std::os::raw::c_void; + +/// A writer that can write files identified by their hash or containing a trailing hash. +pub struct HashFile { + ptr: *mut c_void, + algo: HashAlgorithm, +} + +impl HashFile { + /// Create a new HashFile. + /// + /// The hash used will be `algo`, its name should be in `name`, and an open file descriptor + /// pointing to that file should be in `fd`. + pub fn new(algo: HashAlgorithm, fd: i32, name: &CStr) -> HashFile { + HashFile { + ptr: unsafe { c::hashfd(algo.hash_algo_ptr(), fd, name.as_ptr()) }, + algo, + } + } + + /// Finalize this HashFile instance. + /// + /// Returns the hash computed over the data. + pub fn finalize(self, component: u32, flags: u32) -> Vec<u8> { + let mut result = vec![0u8; GIT_MAX_RAWSZ]; + unsafe { c::finalize_hashfile(self.ptr, result.as_mut_ptr(), component, flags) }; + result.truncate(self.algo.raw_len()); + result + } +} + +impl Write for HashFile { + fn write(&mut self, data: &[u8]) -> io::Result<usize> { + for chunk in data.chunks(u32::MAX as usize) { + unsafe { + c::hashwrite( + self.ptr, + chunk.as_ptr() as *const c_void, + chunk.len() as u32, + ) + }; + } + Ok(data.len()) + } + + fn flush(&mut self) -> io::Result<()> { + unsafe { c::hashflush(self.ptr) }; + Ok(()) + } +} + +pub mod c { + use std::os::raw::{c_char, c_int, c_void}; + + extern "C" { + pub fn hashfd(algop: *const c_void, fd: i32, name: *const c_char) -> *mut c_void; + pub fn hashwrite(f: *mut c_void, data: *const c_void, len: u32); + pub fn hashflush(f: *mut c_void); + pub fn finalize_hashfile( + f: *mut c_void, + data: *mut u8, + component: u32, + flags: u32, + ) -> c_int; + } +}
diff --git a/src/hash.rs b/src/hash.rs new file mode 100644 index 0000000..dea2998 --- /dev/null +++ b/src/hash.rs
@@ -0,0 +1,466 @@ +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation: version 2 of the License, dated June 1991. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see <https://www.gnu.org/licenses/>. + +use std::error::Error; +use std::fmt::{self, Debug, Display}; +use std::io::{self, Write}; +use std::os::raw::c_void; + +pub const GIT_MAX_RAWSZ: usize = 32; + +/// An error indicating an invalid hash algorithm. +/// +/// The contained `u32` is the same as the `algo` field in `ObjectID`. +#[derive(Debug, Copy, Clone)] +pub struct InvalidHashAlgorithm(pub u32); + +impl Display for InvalidHashAlgorithm { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "invalid hash algorithm {}", self.0) + } +} + +impl Error for InvalidHashAlgorithm {} + +/// A binary object ID. +#[repr(C)] +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)] +pub struct ObjectID { + pub hash: [u8; GIT_MAX_RAWSZ], + pub algo: u32, +} + +#[allow(dead_code)] +impl ObjectID { + /// Return a new object ID with the given algorithm and hash. + /// + /// `hash` must be exactly the proper length for `algo` and this function panics if it is not. + /// The extra internal storage of `hash`, if any, is zero filled. + pub fn new(algo: HashAlgorithm, hash: &[u8]) -> Self { + let mut data = [0u8; GIT_MAX_RAWSZ]; + // This verifies that the length of `hash` is correct. + data[0..algo.raw_len()].copy_from_slice(hash); + Self { + hash: data, + algo: algo as u32, + } + } + + /// Return the algorithm for this object ID. + /// + /// If the algorithm set internally is not valid, this function panics. + pub fn algo(&self) -> Result<HashAlgorithm, InvalidHashAlgorithm> { + HashAlgorithm::from_u32(self.algo).ok_or(InvalidHashAlgorithm(self.algo)) + } + + pub fn as_slice(&self) -> Result<&[u8], InvalidHashAlgorithm> { + match HashAlgorithm::from_u32(self.algo) { + Some(algo) => Ok(&self.hash[0..algo.raw_len()]), + None => Err(InvalidHashAlgorithm(self.algo)), + } + } + + pub fn as_mut_slice(&mut self) -> Result<&mut [u8], InvalidHashAlgorithm> { + match HashAlgorithm::from_u32(self.algo) { + Some(algo) => Ok(&mut self.hash[0..algo.raw_len()]), + None => Err(InvalidHashAlgorithm(self.algo)), + } + } +} + +impl Display for ObjectID { + /// Format this object ID as a hex object ID. + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let hash = self.as_slice().unwrap(); + for x in hash { + write!(f, "{:02x}", x)?; + } + Ok(()) + } +} + +impl Debug for ObjectID { + /// Format this object ID as a hex object ID with a colon and name appended to it. + /// + /// ``` + /// assert_eq!( + /// format!("{:?}", HashAlgorithm::SHA256.null_oid()), + /// "0000000000000000000000000000000000000000000000000000000000000000:sha256" + /// ); + /// ``` + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let hash = match self.as_slice() { + Ok(hash) => hash, + Err(_) => &self.hash, + }; + for x in hash { + write!(f, "{:02x}", x)?; + } + match self.algo() { + Ok(algo) => write!(f, ":{}", algo.name()), + Err(e) => write!(f, ":invalid-hash-algo-{}", e.0), + } + } +} + +/// A trait to implement hashing with a cryptographic algorithm. +pub trait CryptoDigest { + /// Return true if this digest is safe for use with untrusted data, false otherwise. + fn is_safe(&self) -> bool; + + /// Update the digest with the specified data. + fn update(&mut self, data: &[u8]); + + /// Return an object ID, consuming the hasher. + fn into_oid(self) -> ObjectID; + + /// Return a hash as a `Vec`, consuming the hasher. + fn into_vec(self) -> Vec<u8>; +} + +/// A structure to hash data with a cryptographic hash algorithm. +/// +/// Instances of this class are safe for use with untrusted data, provided Git has been compiled +/// with a collision-detecting implementation of SHA-1. +pub struct CryptoHasher { + algo: HashAlgorithm, + ctx: *mut c_void, +} + +impl CryptoHasher { + /// Create a new hasher with the algorithm specified with `algo`. + /// + /// This hasher is safe to use on untrusted data. If SHA-1 is selected and Git was compiled + /// with a collision-detecting implementation of SHA-1, then this function will use that + /// implementation and detect any attempts at a collision. + pub fn new(algo: HashAlgorithm) -> Self { + let ctx = unsafe { c::git_hash_alloc() }; + unsafe { c::git_hash_init(ctx, algo.hash_algo_ptr()) }; + Self { algo, ctx } + } +} + +impl CryptoDigest for CryptoHasher { + /// Return true if this digest is safe for use with untrusted data, false otherwise. + fn is_safe(&self) -> bool { + true + } + + /// Update the hasher with the specified data. + fn update(&mut self, data: &[u8]) { + unsafe { c::git_hash_update(self.ctx, data.as_ptr() as *const c_void, data.len()) }; + } + + /// Return an object ID, consuming the hasher. + fn into_oid(self) -> ObjectID { + let mut oid = ObjectID { + hash: [0u8; 32], + algo: self.algo as u32, + }; + unsafe { c::git_hash_final_oid(&mut oid as *mut ObjectID as *mut c_void, self.ctx) }; + oid + } + + /// Return a hash as a `Vec`, consuming the hasher. + fn into_vec(self) -> Vec<u8> { + let mut v = vec![0u8; self.algo.raw_len()]; + unsafe { c::git_hash_final(v.as_mut_ptr(), self.ctx) }; + v + } +} + +impl Clone for CryptoHasher { + fn clone(&self) -> Self { + let ctx = unsafe { c::git_hash_alloc() }; + unsafe { c::git_hash_clone(ctx, self.ctx) }; + Self { + algo: self.algo, + ctx, + } + } +} + +impl Drop for CryptoHasher { + fn drop(&mut self) { + unsafe { c::git_hash_free(self.ctx) }; + } +} + +impl Write for CryptoHasher { + fn write(&mut self, data: &[u8]) -> io::Result<usize> { + self.update(data); + Ok(data.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +/// A hash algorithm, +#[repr(C)] +#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] +pub enum HashAlgorithm { + SHA1 = 1, + SHA256 = 2, +} + +#[allow(dead_code)] +impl HashAlgorithm { + const SHA1_NULL_OID: ObjectID = ObjectID { + hash: [0u8; 32], + algo: Self::SHA1 as u32, + }; + const SHA256_NULL_OID: ObjectID = ObjectID { + hash: [0u8; 32], + algo: Self::SHA256 as u32, + }; + + const SHA1_EMPTY_TREE: ObjectID = ObjectID { + hash: *b"\x4b\x82\x5d\xc6\x42\xcb\x6e\xb9\xa0\x60\xe5\x4b\xf8\xd6\x92\x88\xfb\xee\x49\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + algo: Self::SHA1 as u32, + }; + const SHA256_EMPTY_TREE: ObjectID = ObjectID { + hash: *b"\x6e\xf1\x9b\x41\x22\x5c\x53\x69\xf1\xc1\x04\xd4\x5d\x8d\x85\xef\xa9\xb0\x57\xb5\x3b\x14\xb4\xb9\xb9\x39\xdd\x74\xde\xcc\x53\x21", + algo: Self::SHA256 as u32, + }; + + const SHA1_EMPTY_BLOB: ObjectID = ObjectID { + hash: *b"\xe6\x9d\xe2\x9b\xb2\xd1\xd6\x43\x4b\x8b\x29\xae\x77\x5a\xd8\xc2\xe4\x8c\x53\x91\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + algo: Self::SHA1 as u32, + }; + const SHA256_EMPTY_BLOB: ObjectID = ObjectID { + hash: *b"\x47\x3a\x0f\x4c\x3b\xe8\xa9\x36\x81\xa2\x67\xe3\xb1\xe9\xa7\xdc\xda\x11\x85\x43\x6f\xe1\x41\xf7\x74\x91\x20\xa3\x03\x72\x18\x13", + algo: Self::SHA256 as u32, + }; + + /// Return a hash algorithm based on the internal integer ID used by Git. + /// + /// Returns `None` if the algorithm doesn't indicate a valid algorithm. + pub const fn from_u32(algo: u32) -> Option<HashAlgorithm> { + match algo { + 1 => Some(HashAlgorithm::SHA1), + 2 => Some(HashAlgorithm::SHA256), + _ => None, + } + } + + /// Return a hash algorithm based on the internal integer ID used by Git. + /// + /// Returns `None` if the algorithm doesn't indicate a valid algorithm. + pub const fn from_format_id(algo: u32) -> Option<HashAlgorithm> { + match algo { + 0x73686131 => Some(HashAlgorithm::SHA1), + 0x73323536 => Some(HashAlgorithm::SHA256), + _ => None, + } + } + + /// The name of this hash algorithm as a string suitable for the configuration file. + pub const fn name(self) -> &'static str { + match self { + HashAlgorithm::SHA1 => "sha1", + HashAlgorithm::SHA256 => "sha256", + } + } + + /// The format ID of this algorithm for binary formats. + /// + /// Note that when writing this to a data format, it should be written in big-endian format + /// explicitly. + pub const fn format_id(self) -> u32 { + match self { + HashAlgorithm::SHA1 => 0x73686131, + HashAlgorithm::SHA256 => 0x73323536, + } + } + + /// The length of binary object IDs in this algorithm in bytes. + pub const fn raw_len(self) -> usize { + match self { + HashAlgorithm::SHA1 => 20, + HashAlgorithm::SHA256 => 32, + } + } + + /// The length of object IDs in this algorithm in hexadecimal characters. + pub const fn hex_len(self) -> usize { + self.raw_len() * 2 + } + + /// The number of bytes which is processed by one iteration of this algorithm's compression + /// function. + pub const fn block_size(self) -> usize { + match self { + HashAlgorithm::SHA1 => 64, + HashAlgorithm::SHA256 => 64, + } + } + + /// The object ID representing the empty blob. + pub const fn empty_blob(self) -> &'static ObjectID { + match self { + HashAlgorithm::SHA1 => &Self::SHA1_EMPTY_BLOB, + HashAlgorithm::SHA256 => &Self::SHA256_EMPTY_BLOB, + } + } + + /// The object ID representing the empty tree. + pub const fn empty_tree(self) -> &'static ObjectID { + match self { + HashAlgorithm::SHA1 => &Self::SHA1_EMPTY_TREE, + HashAlgorithm::SHA256 => &Self::SHA256_EMPTY_TREE, + } + } + + /// The object ID which is all zeros. + pub const fn null_oid(self) -> &'static ObjectID { + match self { + HashAlgorithm::SHA1 => &Self::SHA1_NULL_OID, + HashAlgorithm::SHA256 => &Self::SHA256_NULL_OID, + } + } + + /// A pointer to the C `struct git_hash_algo` for interoperability with C. + pub fn hash_algo_ptr(self) -> *const c_void { + unsafe { c::hash_algo_ptr_by_number(self as u32) } + } + + /// Create a hasher for this algorithm. + pub fn hasher(self) -> CryptoHasher { + CryptoHasher::new(self) + } +} + +pub mod c { + use std::os::raw::c_void; + + extern "C" { + pub fn hash_algo_ptr_by_number(n: u32) -> *const c_void; + pub fn unsafe_hash_algo(algop: *const c_void) -> *const c_void; + pub fn git_hash_alloc() -> *mut c_void; + pub fn git_hash_free(ctx: *mut c_void); + pub fn git_hash_init(dst: *mut c_void, algop: *const c_void); + pub fn git_hash_clone(dst: *mut c_void, src: *const c_void); + pub fn git_hash_update(ctx: *mut c_void, inp: *const c_void, len: usize); + pub fn git_hash_final(hash: *mut u8, ctx: *mut c_void); + pub fn git_hash_final_oid(hash: *mut c_void, ctx: *mut c_void); + } +} + +#[cfg(test)] +mod tests { + use super::{CryptoDigest, HashAlgorithm, ObjectID}; + use std::io::Write; + + fn all_algos() -> &'static [HashAlgorithm] { + &[HashAlgorithm::SHA1, HashAlgorithm::SHA256] + } + + #[test] + fn format_id_round_trips() { + for algo in all_algos() { + assert_eq!( + *algo, + HashAlgorithm::from_format_id(algo.format_id()).unwrap() + ); + } + } + + #[test] + fn offset_round_trips() { + for algo in all_algos() { + assert_eq!(*algo, HashAlgorithm::from_u32(*algo as u32).unwrap()); + } + } + + #[test] + fn slices_have_correct_length() { + for algo in all_algos() { + for oid in [algo.null_oid(), algo.empty_blob(), algo.empty_tree()] { + assert_eq!(oid.as_slice().unwrap().len(), algo.raw_len()); + } + } + } + + #[test] + fn object_ids_format_correctly() { + let entries = &[ + ( + HashAlgorithm::SHA1.null_oid(), + "0000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000:sha1", + ), + ( + HashAlgorithm::SHA1.empty_blob(), + "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", + "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391:sha1", + ), + ( + HashAlgorithm::SHA1.empty_tree(), + "4b825dc642cb6eb9a060e54bf8d69288fbee4904", + "4b825dc642cb6eb9a060e54bf8d69288fbee4904:sha1", + ), + ( + HashAlgorithm::SHA256.null_oid(), + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000:sha256", + ), + ( + HashAlgorithm::SHA256.empty_blob(), + "473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813", + "473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a303721813:sha256", + ), + ( + HashAlgorithm::SHA256.empty_tree(), + "6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321", + "6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321:sha256", + ), + ]; + for (oid, display, debug) in entries { + assert_eq!(format!("{}", oid), *display); + assert_eq!(format!("{:?}", oid), *debug); + } + } + + #[test] + fn hasher_works_correctly() { + for algo in all_algos() { + let tests: &[(&[u8], &ObjectID)] = &[ + (b"blob 0\0", algo.empty_blob()), + (b"tree 0\0", algo.empty_tree()), + ]; + for (data, oid) in tests { + let mut h = algo.hasher(); + assert!(h.is_safe()); + // Test that this works incrementally. + h.update(&data[0..2]); + h.update(&data[2..]); + + let h2 = h.clone(); + + let actual_oid = h.into_oid(); + assert_eq!(**oid, actual_oid); + + let v = h2.into_vec(); + assert_eq!((*oid).as_slice().unwrap(), &v); + + let mut h = algo.hasher(); + h.write_all(&data[0..2]).unwrap(); + h.write_all(&data[2..]).unwrap(); + + let actual_oid = h.into_oid(); + assert_eq!(**oid, actual_oid); + } + } + } +}
diff --git a/src/lib.rs b/src/lib.rs index 9da70d8..0c59829 100644 --- a/src/lib.rs +++ b/src/lib.rs
@@ -1 +1,4 @@ +pub mod csum_file; +pub mod hash; +pub mod loose; pub mod varint;
diff --git a/src/loose.rs b/src/loose.rs new file mode 100644 index 0000000..24accf9 --- /dev/null +++ b/src/loose.rs
@@ -0,0 +1,913 @@ +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation: version 2 of the License, dated June 1991. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, see <https://www.gnu.org/licenses/>. + +use crate::hash::{HashAlgorithm, ObjectID, GIT_MAX_RAWSZ}; +use std::collections::BTreeMap; +use std::convert::TryInto; +use std::io::{self, Write}; + +/// The type of object stored in the map. +/// +/// If this value is `Reserved`, then it is never written to disk and is used primarily to store +/// certain hard-coded objects, like the empty tree, empty blob, or null object ID. +/// +/// If this value is `LooseObject`, then this represents a loose object. `Shallow` represents a +/// shallow commit, its parent, or its tree. `Submodule` represents a submodule commit. +#[repr(C)] +#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq)] +pub enum MapType { + Reserved = 0, + LooseObject = 1, + Shallow = 2, + Submodule = 3, +} + +impl MapType { + pub fn from_u32(n: u32) -> Option<MapType> { + match n { + 0 => Some(Self::Reserved), + 1 => Some(Self::LooseObject), + 2 => Some(Self::Shallow), + 3 => Some(Self::Submodule), + _ => None, + } + } +} + +/// The value of an object stored in a `ObjectMemoryMap`. +/// +/// This keeps the object ID to which the key is mapped and its kind together. +struct MappedObject { + oid: ObjectID, + kind: MapType, +} + +/// Memory storage for a loose object. +struct ObjectMemoryMap { + to_compat: BTreeMap<ObjectID, MappedObject>, + to_storage: BTreeMap<ObjectID, MappedObject>, + compat: HashAlgorithm, + storage: HashAlgorithm, +} + +impl ObjectMemoryMap { + /// Create a new `ObjectMemoryMap`. + /// + /// The storage and compatibility `HashAlgorithm` instances are used to store the object IDs in + /// the correct map. + fn new(storage: HashAlgorithm, compat: HashAlgorithm) -> Self { + Self { + to_compat: BTreeMap::new(), + to_storage: BTreeMap::new(), + compat, + storage, + } + } + + fn len(&self) -> usize { + self.to_compat.len() + } + + /// Write this map to an interface implementing `std::io::Write`. + fn write<W: Write>(&self, wrtr: W) -> io::Result<()> { + const VERSION_NUMBER: u32 = 1; + const NUM_OBJECT_FORMATS: u32 = 2; + const PADDING: [u8; 4] = [0u8; 4]; + + let mut wrtr = wrtr; + let header_size: u32 = (4 * 5) + (4 + 4 + 8) * NUM_OBJECT_FORMATS + 8; + + wrtr.write_all(b"LMAP")?; + wrtr.write_all(&VERSION_NUMBER.to_be_bytes())?; + wrtr.write_all(&header_size.to_be_bytes())?; + wrtr.write_all(&(self.to_compat.len() as u32).to_be_bytes())?; + wrtr.write_all(&NUM_OBJECT_FORMATS.to_be_bytes())?; + + let storage_short_len = self.find_short_name_len(&self.to_compat, self.storage); + let compat_short_len = self.find_short_name_len(&self.to_storage, self.compat); + + let storage_npadding = Self::required_nul_padding(self.to_compat.len(), storage_short_len); + let compat_npadding = Self::required_nul_padding(self.to_compat.len(), compat_short_len); + + let mut offset: u64 = header_size as u64; + + for (algo, len, npadding) in &[ + (self.storage, storage_short_len, storage_npadding), + (self.compat, compat_short_len, compat_npadding), + ] { + wrtr.write_all(&algo.format_id().to_be_bytes())?; + wrtr.write_all(&(*len as u32).to_be_bytes())?; + + offset += *npadding; + wrtr.write_all(&offset.to_be_bytes())?; + + offset += self.to_compat.len() as u64 * (*len as u64 + algo.raw_len() as u64 + 4); + } + + wrtr.write_all(&offset.to_be_bytes())?; + + let order_map: BTreeMap<&ObjectID, usize> = self + .to_compat + .keys() + .enumerate() + .map(|(i, oid)| (oid, i)) + .collect(); + + wrtr.write_all(&PADDING[0..storage_npadding as usize])?; + for oid in self.to_compat.keys() { + wrtr.write_all(&oid.as_slice().unwrap()[0..storage_short_len])?; + } + for oid in self.to_compat.keys() { + wrtr.write_all(oid.as_slice().unwrap())?; + } + for meta in self.to_compat.values() { + wrtr.write_all(&(meta.kind as u32).to_be_bytes())?; + } + + wrtr.write_all(&PADDING[0..compat_npadding as usize])?; + for oid in self.to_storage.keys() { + wrtr.write_all(&oid.as_slice().unwrap()[0..compat_short_len])?; + } + for meta in self.to_compat.values() { + wrtr.write_all(meta.oid.as_slice().unwrap())?; + } + for meta in self.to_storage.values() { + wrtr.write_all(&(order_map[&meta.oid] as u32).to_be_bytes())?; + } + + Ok(()) + } + + fn required_nul_padding(nitems: usize, short_len: usize) -> u64 { + let shortened_table_len = nitems as u64 * short_len as u64; + let misalignment = shortened_table_len & 3; + // If the value is 0, return 0; otherwise, return the difference from 4. + (4 - misalignment) & 3 + } + + fn last_matching_offset(a: &ObjectID, b: &ObjectID, algop: HashAlgorithm) -> usize { + for i in 0..=algop.raw_len() { + if a.hash[i] != b.hash[i] { + return i; + } + } + algop.raw_len() + } + + fn find_short_name_len( + &self, + map: &BTreeMap<ObjectID, MappedObject>, + algop: HashAlgorithm, + ) -> usize { + if map.len() <= 1 { + return 1; + } + let mut len = 1; + let mut iter = map.keys(); + let mut cur = match iter.next() { + Some(cur) => cur, + None => return len, + }; + for item in iter { + let offset = Self::last_matching_offset(cur, item, algop); + if offset >= len { + len = offset + 1; + } + cur = item; + } + if len > algop.raw_len() { + algop.raw_len() + } else { + len + } + } +} + +struct ObjectFormatData { + data_off: usize, + shortened_len: usize, + full_off: usize, + mapping_off: Option<usize>, +} + +pub struct MmapedObjectMapIter<'a> { + offset: usize, + algos: Vec<HashAlgorithm>, + source: &'a MmapedObjectMap<'a>, +} + +impl<'a> Iterator for MmapedObjectMapIter<'a> { + type Item = Vec<ObjectID>; + + fn next(&mut self) -> Option<Self::Item> { + if self.offset >= self.source.nitems { + return None; + } + let offset = self.offset; + self.offset += 1; + let v: Vec<ObjectID> = self + .algos + .iter() + .cloned() + .filter_map(|algo| self.source.oid_from_offset(offset, algo)) + .collect(); + if v.len() != self.algos.len() { + return None; + } + Some(v) + } +} + +#[allow(dead_code)] +pub struct MmapedObjectMap<'a> { + memory: &'a [u8], + nitems: usize, + meta_off: usize, + obj_formats: BTreeMap<HashAlgorithm, ObjectFormatData>, + main_algo: HashAlgorithm, +} + +#[derive(Debug)] +#[allow(dead_code)] +enum MmapedParseError { + HeaderTooSmall, + InvalidSignature, + InvalidVersion, + UnknownAlgorithm, + OffsetTooLarge, + TooFewObjectFormats, + UnalignedData, + InvalidTrailerOffset, +} + +#[allow(dead_code)] +impl<'a> MmapedObjectMap<'a> { + fn new( + slice: &'a [u8], + hash_algo: HashAlgorithm, + ) -> Result<MmapedObjectMap<'a>, MmapedParseError> { + let object_format_header_size = 4 + 4 + 8; + let trailer_offset_size = 8; + let header_size: usize = + 4 + 4 + 4 + 4 + 4 + object_format_header_size * 2 + trailer_offset_size; + if slice.len() < header_size { + return Err(MmapedParseError::HeaderTooSmall); + } + if slice[0..4] != *b"LMAP" { + return Err(MmapedParseError::InvalidSignature); + } + if Self::u32_at_offset(slice, 4) != 1 { + return Err(MmapedParseError::InvalidVersion); + } + let _ = Self::u32_at_offset(slice, 8) as usize; + let nitems = Self::u32_at_offset(slice, 12) as usize; + let nobj_formats = Self::u32_at_offset(slice, 16) as usize; + if nobj_formats < 2 { + return Err(MmapedParseError::TooFewObjectFormats); + } + let mut offset = 20; + let mut meta_off = None; + let mut data = BTreeMap::new(); + for i in 0..nobj_formats { + if offset + object_format_header_size + trailer_offset_size > slice.len() { + return Err(MmapedParseError::HeaderTooSmall); + } + let format_id = Self::u32_at_offset(slice, offset); + let shortened_len = Self::u32_at_offset(slice, offset + 4) as usize; + let data_off = Self::u64_at_offset(slice, offset + 8); + + let algo = HashAlgorithm::from_format_id(format_id) + .ok_or(MmapedParseError::UnknownAlgorithm)?; + let data_off: usize = data_off + .try_into() + .map_err(|_| MmapedParseError::OffsetTooLarge)?; + + // Every object format must have these entries. + let shortened_table_len = shortened_len + .checked_mul(nitems) + .ok_or(MmapedParseError::OffsetTooLarge)?; + let full_off = data_off + .checked_add(shortened_table_len) + .ok_or(MmapedParseError::OffsetTooLarge)?; + Self::verify_aligned(full_off)?; + Self::verify_valid(slice, full_off as u64)?; + + let full_length = algo + .raw_len() + .checked_mul(nitems) + .ok_or(MmapedParseError::OffsetTooLarge)?; + let off = full_length + .checked_add(full_off) + .ok_or(MmapedParseError::OffsetTooLarge)?; + Self::verify_aligned(off)?; + Self::verify_valid(slice, off as u64)?; + + // This is for the metadata for the first object format and for the order mapping for + // other object formats. + let meta_size = nitems + .checked_mul(4) + .ok_or(MmapedParseError::OffsetTooLarge)?; + let meta_end = off + .checked_add(meta_size) + .ok_or(MmapedParseError::OffsetTooLarge)?; + Self::verify_valid(slice, meta_end as u64)?; + + let mut mapping_off = None; + if i == 0 { + meta_off = Some(off); + } else { + mapping_off = Some(off); + } + + data.insert( + algo, + ObjectFormatData { + data_off, + shortened_len, + full_off, + mapping_off, + }, + ); + offset += object_format_header_size; + } + let trailer = Self::u64_at_offset(slice, offset); + Self::verify_aligned(trailer as usize)?; + Self::verify_valid(slice, trailer)?; + let end = trailer + .checked_add(hash_algo.raw_len() as u64) + .ok_or(MmapedParseError::OffsetTooLarge)?; + if end != slice.len() as u64 { + return Err(MmapedParseError::InvalidTrailerOffset); + } + match meta_off { + Some(meta_off) => Ok(MmapedObjectMap { + memory: slice, + nitems, + meta_off, + obj_formats: data, + main_algo: hash_algo, + }), + None => Err(MmapedParseError::TooFewObjectFormats), + } + } + + fn iter(&self) -> MmapedObjectMapIter<'_> { + let mut algos = Vec::with_capacity(self.obj_formats.len()); + algos.push(self.main_algo); + for algo in self.obj_formats.keys().cloned() { + if algo != self.main_algo { + algos.push(algo); + } + } + MmapedObjectMapIter { + offset: 0, + algos, + source: self, + } + } + + /// Treats `sl` as if it were a set of slices of `wanted.len()` bytes, and searches for + /// `wanted` within it. + /// + /// If found, returns the offset of the subslice in `sl`. + /// + /// ``` + /// let sl = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + /// + /// assert_eq!(MmapedObjectMap::binary_search_slice(sl, &[2, 3]), Some(1)); + /// assert_eq!(MmapedObjectMap::binary_search_slice(sl, &[6, 7]), Some(4)); + /// assert_eq!(MmapedObjectMap::binary_search_slice(sl, &[1, 2]), None); + /// assert_eq!(MmapedObjectMap::binary_search_slice(sl, &[10, 20]), None); + /// ``` + fn binary_search_slice(sl: &[u8], wanted: &[u8]) -> Option<usize> { + let len = wanted.len(); + let res = sl.binary_search_by(|item| { + // We would like element_offset, but that is currently nightly only. Instead, do a + // pointer subtraction to find the index. + let index = unsafe { (item as *const u8).offset_from(sl.as_ptr()) } as usize; + // Now we have the index of this object. Round it down to the nearest full-sized + // chunk to find the actual offset where this starts. + let index = index - (index % len); + // Compute the comparison of that value instead, which will provide the expected + // result. + sl[index..index + wanted.len()].cmp(wanted) + }); + res.ok().map(|offset| offset / len) + } + + /// Look up `oid` in the map in order to convert it to `algo`. + /// + /// If this object is in the map, return the offset in the table for the main algorithm. + fn look_up_object(&self, oid: &ObjectID) -> Option<usize> { + let oid_algo = HashAlgorithm::from_u32(oid.algo)?; + let params = self.obj_formats.get(&oid_algo)?; + let short_table = + &self.memory[params.data_off..params.data_off + (params.shortened_len * self.nitems)]; + let index = Self::binary_search_slice( + short_table, + &oid.as_slice().unwrap()[0..params.shortened_len], + )?; + match params.mapping_off { + Some(from_off) => { + // oid is in a compatibility algorithm. Find the mapping index. + let mapped = Self::u32_at_offset(self.memory, from_off + index * 4) as usize; + if mapped >= self.nitems { + return None; + } + let oid_offset = params.full_off + mapped * oid_algo.raw_len(); + if self.memory[oid_offset..oid_offset + oid_algo.raw_len()] + != *oid.as_slice().unwrap() + { + return None; + } + Some(mapped) + } + None => { + // oid is in the main algorithm. Find the object ID in the main map to confirm + // it's correct. + let oid_offset = params.full_off + index * oid_algo.raw_len(); + if self.memory[oid_offset..oid_offset + oid_algo.raw_len()] + != *oid.as_slice().unwrap() + { + return None; + } + Some(index) + } + } + } + + #[allow(dead_code)] + fn map_object(&self, oid: &ObjectID, algo: HashAlgorithm) -> Option<MappedObject> { + let main = self.look_up_object(oid)?; + let meta = MapType::from_u32(Self::u32_at_offset(self.memory, self.meta_off + (main * 4)))?; + Some(MappedObject { + oid: self.oid_from_offset(main, algo)?, + kind: meta, + }) + } + + fn map_oid(&self, oid: &ObjectID, algo: HashAlgorithm) -> Option<ObjectID> { + if algo as u32 == oid.algo { + return Some(oid.clone()); + } + + let main = self.look_up_object(oid)?; + self.oid_from_offset(main, algo) + } + + fn oid_from_offset(&self, offset: usize, algo: HashAlgorithm) -> Option<ObjectID> { + let aparams = self.obj_formats.get(&algo)?; + + let mut hash = [0u8; GIT_MAX_RAWSZ]; + let len = algo.raw_len(); + let oid_off = aparams.full_off + (offset * len); + hash[0..len].copy_from_slice(&self.memory[oid_off..oid_off + len]); + Some(ObjectID { + hash, + algo: algo as u32, + }) + } + + fn u32_at_offset(slice: &[u8], offset: usize) -> u32 { + u32::from_be_bytes(slice[offset..offset + 4].try_into().unwrap()) + } + + fn u64_at_offset(slice: &[u8], offset: usize) -> u64 { + u64::from_be_bytes(slice[offset..offset + 8].try_into().unwrap()) + } + + fn verify_aligned(offset: usize) -> Result<(), MmapedParseError> { + if (offset & 3) != 0 { + return Err(MmapedParseError::UnalignedData); + } + Ok(()) + } + + fn verify_valid(slice: &[u8], offset: u64) -> Result<(), MmapedParseError> { + if offset >= slice.len() as u64 { + return Err(MmapedParseError::OffsetTooLarge); + } + Ok(()) + } +} + +/// A map for loose and other non-packed object IDs that maps between a storage and compatibility +/// mapping. +/// +/// In addition to the in-memory option, there is an optional batched storage, which can be used to +/// write objects to disk in an efficient way. +pub struct ObjectMap { + mem: ObjectMemoryMap, + batch: Option<ObjectMemoryMap>, +} + +impl ObjectMap { + /// Create a new `ObjectMap` with the given hash algorithms. + /// + /// This initializes the memory map to automatically map the empty tree, empty blob, and null + /// object ID. + pub fn new(storage: HashAlgorithm, compat: HashAlgorithm) -> Self { + let mut map = ObjectMemoryMap::new(storage, compat); + for (main, compat) in &[ + (storage.empty_tree(), compat.empty_tree()), + (storage.empty_blob(), compat.empty_blob()), + (storage.null_oid(), compat.null_oid()), + ] { + map.to_storage.insert( + (*compat).clone(), + MappedObject { + oid: (*main).clone(), + kind: MapType::Reserved, + }, + ); + map.to_compat.insert( + (*main).clone(), + MappedObject { + oid: (*compat).clone(), + kind: MapType::Reserved, + }, + ); + } + Self { + mem: map, + batch: None, + } + } + + pub fn hash_algo(&self) -> HashAlgorithm { + self.mem.storage + } + + /// Start a batch for efficient writing. + /// + /// If there is already a batch started, this does nothing and the existing batch is retained. + pub fn start_batch(&mut self) { + if self.batch.is_none() { + self.batch = Some(ObjectMemoryMap::new(self.mem.storage, self.mem.compat)); + } + } + + pub fn batch_len(&self) -> Option<usize> { + self.batch.as_ref().map(|b| b.len()) + } + + /// If a batch exists, write it to the writer. + pub fn finish_batch<W: Write>(&mut self, w: W) -> io::Result<()> { + if let Some(txn) = self.batch.take() { + txn.write(w)?; + } + Ok(()) + } + + /// If a batch exists, write it to the writer. + pub fn abort_batch(&mut self) { + self.batch = None; + } + + /// Return whether there is a batch already started. + /// + /// If you just want a batch to exist and don't care whether one has already been started, you + /// may simply call `start_batch` unconditionally. + pub fn has_batch(&self) -> bool { + self.batch.is_some() + } + + /// Insert an object into the map. + /// + /// If `write` is true and there is a batch started, write the object into the batch as well as + /// into the memory map. + pub fn insert(&mut self, oid1: &ObjectID, oid2: &ObjectID, kind: MapType, write: bool) { + let (compat_oid, storage_oid) = + if HashAlgorithm::from_u32(oid1.algo) == Some(self.mem.compat) { + (oid1, oid2) + } else { + (oid2, oid1) + }; + Self::insert_into(&mut self.mem, storage_oid, compat_oid, kind); + if write { + if let Some(ref mut batch) = self.batch { + Self::insert_into(batch, storage_oid, compat_oid, kind); + } + } + } + + fn insert_into( + map: &mut ObjectMemoryMap, + storage: &ObjectID, + compat: &ObjectID, + kind: MapType, + ) { + map.to_compat.insert( + storage.clone(), + MappedObject { + oid: compat.clone(), + kind, + }, + ); + map.to_storage.insert( + compat.clone(), + MappedObject { + oid: storage.clone(), + kind, + }, + ); + } + + #[allow(dead_code)] + fn map_object(&self, oid: &ObjectID, algo: HashAlgorithm) -> Option<&MappedObject> { + let map = if algo == self.mem.storage { + &self.mem.to_storage + } else { + &self.mem.to_compat + }; + map.get(oid) + } + + #[allow(dead_code)] + fn map_oid<'a, 'b: 'a>( + &'b self, + oid: &'a ObjectID, + algo: HashAlgorithm, + ) -> Option<&'a ObjectID> { + if algo as u32 == oid.algo { + return Some(oid); + } + let entry = self.map_object(oid, algo); + entry.map(|obj| &obj.oid) + } +} + +#[cfg(test)] +mod tests { + use super::{MapType, MmapedObjectMap, ObjectMap, ObjectMemoryMap}; + use crate::hash::{CryptoDigest, CryptoHasher, HashAlgorithm, ObjectID}; + use std::convert::TryInto; + use std::io::{self, Cursor, Write}; + + struct TrailingWriter { + curs: Cursor<Vec<u8>>, + hasher: CryptoHasher, + } + + impl TrailingWriter { + fn new() -> Self { + Self { + curs: Cursor::new(Vec::new()), + hasher: CryptoHasher::new(HashAlgorithm::SHA256), + } + } + + fn finalize(mut self) -> Vec<u8> { + let _ = self.hasher.flush(); + let mut v = self.curs.into_inner(); + v.extend(self.hasher.into_vec()); + v + } + } + + impl Write for TrailingWriter { + fn write(&mut self, data: &[u8]) -> io::Result<usize> { + self.hasher.write_all(data)?; + self.curs.write_all(data)?; + Ok(data.len()) + } + + fn flush(&mut self) -> io::Result<()> { + self.hasher.flush()?; + self.curs.flush()?; + Ok(()) + } + } + + fn sha1_oid(b: &[u8]) -> ObjectID { + assert_eq!(b.len(), 20); + let mut data = [0u8; 32]; + data[0..20].copy_from_slice(b); + ObjectID { + hash: data, + algo: HashAlgorithm::SHA1 as u32, + } + } + + fn sha256_oid(b: &[u8]) -> ObjectID { + assert_eq!(b.len(), 32); + ObjectID { + hash: b.try_into().unwrap(), + algo: HashAlgorithm::SHA256 as u32, + } + } + + #[allow(clippy::type_complexity)] + fn test_entries() -> &'static [(&'static str, &'static [u8], &'static [u8], MapType, bool)] { + // These are all example blobs containing the content in the first argument. + &[ + ("abc", b"\xf2\xba\x8f\x84\xab\x5c\x1b\xce\x84\xa7\xb4\x41\xcb\x19\x59\xcf\xc7\x09\x3b\x7f", b"\xc1\xcf\x6e\x46\x50\x77\x93\x0e\x88\xdc\x51\x36\x64\x1d\x40\x2f\x72\xa2\x29\xdd\xd9\x96\xf6\x27\xd6\x0e\x96\x39\xea\xba\x35\xa6", MapType::LooseObject, false), + ("def", b"\x0c\x00\x38\x32\xe7\xbf\xa9\xca\x8b\x5c\x20\x35\xc9\xbd\x68\x4a\x5f\x26\x23\xbc", b"\x8a\x90\x17\x26\x48\x4d\xb0\xf2\x27\x9f\x30\x8d\x58\x96\xd9\x6b\xf6\x3a\xd6\xde\x95\x7c\xa3\x8a\xdc\x33\x61\x68\x03\x6e\xf6\x63", MapType::Shallow, true), + ("ghi", b"\x45\xa8\x2e\x29\x5c\x52\x47\x31\x14\xc5\x7c\x18\xf4\xf5\x23\x68\xdf\x2a\x3c\xfd", b"\x6e\x47\x4c\x74\xf5\xd7\x78\x14\xc7\xf7\xf0\x7c\x37\x80\x07\x90\x53\x42\xaf\x42\x81\xe6\x86\x8d\x33\x46\x45\x4b\xb8\x63\xab\xc3", MapType::Submodule, false), + ("jkl", b"\x45\x32\x8c\x36\xff\x2e\x9b\x9b\x4e\x59\x2c\x84\x7d\x3f\x9a\x7f\xd9\xb3\xe7\x16", b"\xc3\xee\xf7\x54\xa2\x1e\xc6\x9d\x43\x75\xbe\x6f\x18\x47\x89\xa8\x11\x6f\xd9\x66\xfc\x67\xdc\x31\xd2\x11\x15\x42\xc8\xd5\xa0\xaf", MapType::LooseObject, true), + ] + } + + fn test_map(write_all: bool) -> Box<ObjectMap> { + let mut map = Box::new(ObjectMap::new(HashAlgorithm::SHA256, HashAlgorithm::SHA1)); + + map.start_batch(); + + for (_blob_content, sha1, sha256, kind, swap) in test_entries() { + let s256 = sha256_oid(sha256); + let s1 = sha1_oid(sha1); + let write = write_all || (*kind as u32 & 2) == 0; + if *swap { + // Insert the item into the batch arbitrarily based on the type. This tests that + // we can specify either order and we'll do the right thing. + map.insert(&s256, &s1, *kind, write); + } else { + map.insert(&s1, &s256, *kind, write); + } + } + + map + } + + #[test] + fn can_read_and_write_format() { + for full in &[true, false] { + let mut map = test_map(*full); + let mut wrtr = TrailingWriter::new(); + map.finish_batch(&mut wrtr).unwrap(); + + assert!(!map.has_batch()); + + let data = wrtr.finalize(); + MmapedObjectMap::new(&data, HashAlgorithm::SHA256).unwrap(); + } + } + + #[test] + fn looks_up_from_mmaped() { + let mut map = test_map(true); + let mut wrtr = TrailingWriter::new(); + map.finish_batch(&mut wrtr).unwrap(); + + assert!(!map.has_batch()); + + let data = wrtr.finalize(); + let entries = test_entries(); + let map = MmapedObjectMap::new(&data, HashAlgorithm::SHA256).unwrap(); + + for (_, sha1, sha256, kind, _) in entries { + let s256 = sha256_oid(sha256); + let s1 = sha1_oid(sha1); + + let res = map.map_object(&s256, HashAlgorithm::SHA1).unwrap(); + assert_eq!(res.oid, s1); + assert_eq!(res.kind, *kind); + let res = map.map_oid(&s256, HashAlgorithm::SHA1).unwrap(); + assert_eq!(res, s1); + + let res = map.map_object(&s256, HashAlgorithm::SHA256).unwrap(); + assert_eq!(res.oid, s256); + assert_eq!(res.kind, *kind); + let res = map.map_oid(&s256, HashAlgorithm::SHA256).unwrap(); + assert_eq!(res, s256); + + let res = map.map_object(&s1, HashAlgorithm::SHA256).unwrap(); + assert_eq!(res.oid, s256); + assert_eq!(res.kind, *kind); + let res = map.map_oid(&s1, HashAlgorithm::SHA256).unwrap(); + assert_eq!(res, s256); + + let res = map.map_object(&s1, HashAlgorithm::SHA1).unwrap(); + assert_eq!(res.oid, s1); + assert_eq!(res.kind, *kind); + let res = map.map_oid(&s1, HashAlgorithm::SHA1).unwrap(); + assert_eq!(res, s1); + } + + for octet in &[0x00u8, 0x6d, 0x6e, 0x8a, 0xff] { + let missing_oid = ObjectID { + hash: [*octet; 32], + algo: HashAlgorithm::SHA256 as u32, + }; + + assert!(map.map_object(&missing_oid, HashAlgorithm::SHA1).is_none()); + assert!(map.map_oid(&missing_oid, HashAlgorithm::SHA1).is_none()); + + assert_eq!( + map.map_oid(&missing_oid, HashAlgorithm::SHA256).unwrap(), + missing_oid + ); + } + } + + #[test] + fn binary_searches_slices_correctly() { + let sl = &[ + 0, 1, 2, 15, 14, 13, 18, 10, 2, 20, 20, 20, 21, 21, 0, 21, 21, 1, 21, 21, 21, 21, 21, + 22, 22, 23, 24, + ]; + + let expected: &[(&[u8], Option<usize>)] = &[ + (&[0, 1, 2], Some(0)), + (&[15, 14, 13], Some(1)), + (&[18, 10, 2], Some(2)), + (&[20, 20, 20], Some(3)), + (&[21, 21, 0], Some(4)), + (&[21, 21, 1], Some(5)), + (&[21, 21, 21], Some(6)), + (&[21, 21, 22], Some(7)), + (&[22, 23, 24], Some(8)), + (&[2, 15, 14], None), + (&[0, 21, 21], None), + (&[21, 21, 23], None), + (&[22, 22, 23], None), + (&[0xff, 0xff, 0xff], None), + (&[0, 0, 0], None), + ]; + + for (wanted, value) in expected { + assert_eq!(MmapedObjectMap::binary_search_slice(sl, wanted), *value); + } + } + + #[test] + fn looks_up_oid_correctly() { + let map = test_map(false); + let entries = test_entries(); + + let s256 = sha256_oid(entries[0].2); + let s1 = sha1_oid(entries[0].1); + + let missing_oid = ObjectID { + hash: [0xffu8; 32], + algo: HashAlgorithm::SHA256 as u32, + }; + + let res = map.map_object(&s256, HashAlgorithm::SHA1).unwrap(); + assert_eq!(res.oid, s1); + assert_eq!(res.kind, MapType::LooseObject); + let res = map.map_oid(&s256, HashAlgorithm::SHA1).unwrap(); + assert_eq!(*res, s1); + + let res = map.map_object(&s1, HashAlgorithm::SHA256).unwrap(); + assert_eq!(res.oid, s256); + assert_eq!(res.kind, MapType::LooseObject); + let res = map.map_oid(&s1, HashAlgorithm::SHA256).unwrap(); + assert_eq!(*res, s256); + + assert!(map.map_object(&missing_oid, HashAlgorithm::SHA1).is_none()); + assert!(map.map_oid(&missing_oid, HashAlgorithm::SHA1).is_none()); + + assert_eq!( + *map.map_oid(&missing_oid, HashAlgorithm::SHA256).unwrap(), + missing_oid + ); + } + + #[test] + fn looks_up_known_oids_correctly() { + let map = test_map(false); + + let funcs: &[&dyn Fn(HashAlgorithm) -> &'static ObjectID] = &[ + &|h: HashAlgorithm| h.empty_tree(), + &|h: HashAlgorithm| h.empty_blob(), + &|h: HashAlgorithm| h.null_oid(), + ]; + + for f in funcs { + let s256 = f(HashAlgorithm::SHA256); + let s1 = f(HashAlgorithm::SHA1); + + let res = map.map_object(s256, HashAlgorithm::SHA1).unwrap(); + assert_eq!(res.oid, *s1); + assert_eq!(res.kind, MapType::Reserved); + let res = map.map_oid(s256, HashAlgorithm::SHA1).unwrap(); + assert_eq!(*res, *s1); + + let res = map.map_object(s1, HashAlgorithm::SHA256).unwrap(); + assert_eq!(res.oid, *s256); + assert_eq!(res.kind, MapType::Reserved); + let res = map.map_oid(s1, HashAlgorithm::SHA256).unwrap(); + assert_eq!(*res, *s256); + } + } + + #[test] + fn nul_padding() { + assert_eq!(ObjectMemoryMap::required_nul_padding(1, 1), 3); + assert_eq!(ObjectMemoryMap::required_nul_padding(2, 1), 2); + assert_eq!(ObjectMemoryMap::required_nul_padding(3, 1), 1); + assert_eq!(ObjectMemoryMap::required_nul_padding(2, 2), 0); + + assert_eq!(ObjectMemoryMap::required_nul_padding(39, 3), 3); + } +}
diff --git a/src/meson.build b/src/meson.build index 25b9ad5..4573995 100644 --- a/src/meson.build +++ b/src/meson.build
@@ -1,5 +1,8 @@ libgit_rs_sources = [ + 'csum_file.rs', + 'hash.rs', 'lib.rs', + 'loose.rs', 'varint.rs', ]
diff --git a/strbuf.c b/strbuf.c index 6c3851a..3e04add 100644 --- a/strbuf.c +++ b/strbuf.c
@@ -168,7 +168,7 @@ int strbuf_reencode(struct strbuf *sb, const char *from, const char *to) if (!out) return -1; - strbuf_attach(sb, out, len, len); + strbuf_attach(sb, out, len, len + 1); return 0; } @@ -566,7 +566,7 @@ ssize_t strbuf_write(struct strbuf *sb, FILE *f) return sb->len ? fwrite(sb->buf, 1, sb->len, f) : 0; } -#define STRBUF_MAXLINK (2*PATH_MAX) +#define STRBUF_MAXLINK (32767) int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint) { @@ -578,12 +578,12 @@ int strbuf_readlink(struct strbuf *sb, const char *path, size_t hint) while (hint < STRBUF_MAXLINK) { ssize_t len; - strbuf_grow(sb, hint); - len = readlink(path, sb->buf, hint); + strbuf_grow(sb, hint + 1); + len = readlink(path, sb->buf, hint + 1); if (len < 0) { if (errno != ERANGE) break; - } else if (len < hint) { + } else if (len <= hint) { strbuf_setlen(sb, len); return 0; } @@ -836,47 +836,83 @@ void strbuf_addstr_urlencode(struct strbuf *sb, const char *s, strbuf_add_urlencode(sb, s, strlen(s), allow_unencoded_fn); } -static void strbuf_humanise(struct strbuf *buf, off_t bytes, - int humanise_rate) +void humanise_count(size_t count, char **value, const char **unit) { - if (bytes > 1 << 30) { - strbuf_addf(buf, - humanise_rate == 0 ? - /* TRANSLATORS: IEC 80000-13:2008 gibibyte */ - _("%u.%2.2u GiB") : - /* TRANSLATORS: IEC 80000-13:2008 gibibyte/second */ - _("%u.%2.2u GiB/s"), - (unsigned)(bytes >> 30), - (unsigned)(bytes & ((1 << 30) - 1)) / 10737419); - } else if (bytes > 1 << 20) { - unsigned x = bytes + 5243; /* for rounding */ - strbuf_addf(buf, - humanise_rate == 0 ? - /* TRANSLATORS: IEC 80000-13:2008 mebibyte */ - _("%u.%2.2u MiB") : - /* TRANSLATORS: IEC 80000-13:2008 mebibyte/second */ - _("%u.%2.2u MiB/s"), - x >> 20, ((x & ((1 << 20) - 1)) * 100) >> 20); - } else if (bytes > 1 << 10) { - unsigned x = bytes + 5; /* for rounding */ - strbuf_addf(buf, - humanise_rate == 0 ? - /* TRANSLATORS: IEC 80000-13:2008 kibibyte */ - _("%u.%2.2u KiB") : - /* TRANSLATORS: IEC 80000-13:2008 kibibyte/second */ - _("%u.%2.2u KiB/s"), - x >> 10, ((x & ((1 << 10) - 1)) * 100) >> 10); + if (count >= 1000000000) { + size_t x = count + 5000000; /* for rounding */ + *value = xstrfmt(_("%u.%2.2u"), (unsigned)(x / 1000000000), + (unsigned)(x % 1000000000 / 10000000)); + /* TRANSLATORS: SI decimal prefix symbol for 10^9 */ + *unit = _("G"); + } else if (count >= 1000000) { + size_t x = count + 5000; /* for rounding */ + *value = xstrfmt(_("%u.%2.2u"), (unsigned)(x / 1000000), + (unsigned)(x % 1000000 / 10000)); + /* TRANSLATORS: SI decimal prefix symbol for 10^6 */ + *unit = _("M"); + } else if (count >= 1000) { + size_t x = count + 5; /* for rounding */ + *value = xstrfmt(_("%u.%2.2u"), (unsigned)(x / 1000), + (unsigned)(x % 1000 / 10)); + /* TRANSLATORS: SI decimal prefix symbol for 10^3 */ + *unit = _("k"); } else { - strbuf_addf(buf, - humanise_rate == 0 ? - /* TRANSLATORS: IEC 80000-13:2008 byte */ - Q_("%u byte", "%u bytes", bytes) : - /* TRANSLATORS: IEC 80000-13:2008 byte/second */ - Q_("%u byte/s", "%u bytes/s", bytes), - (unsigned)bytes); + *value = xstrfmt("%u", (unsigned)count); + *unit = NULL; } } +void humanise_bytes(off_t bytes, char **value, const char **unit, + unsigned flags) +{ + int humanise_rate = flags & HUMANISE_RATE; + + if (bytes > 1 << 30) { + *value = xstrfmt(_("%u.%2.2u"), (unsigned)(bytes >> 30), + (unsigned)(bytes & ((1 << 30) - 1)) / 10737419); + /* TRANSLATORS: IEC 80000-13:2008 gibibyte/second and gibibyte */ + *unit = humanise_rate ? _("GiB/s") : _("GiB"); + } else if (bytes > 1 << 20) { + unsigned x = bytes + 5243; /* for rounding */ + *value = xstrfmt(_("%u.%2.2u"), x >> 20, + ((x & ((1 << 20) - 1)) * 100) >> 20); + /* TRANSLATORS: IEC 80000-13:2008 mebibyte/second and mebibyte */ + *unit = humanise_rate ? _("MiB/s") : _("MiB"); + } else if (bytes > 1 << 10) { + unsigned x = bytes + 5; /* for rounding */ + *value = xstrfmt(_("%u.%2.2u"), x >> 10, + ((x & ((1 << 10) - 1)) * 100) >> 10); + /* TRANSLATORS: IEC 80000-13:2008 kibibyte/second and kibibyte */ + *unit = humanise_rate ? _("KiB/s") : _("KiB"); + } else { + *value = xstrfmt("%u", (unsigned)bytes); + if (flags & HUMANISE_COMPACT) + /* TRANSLATORS: IEC 80000-13:2008 byte/second and byte */ + *unit = humanise_rate ? _("B/s") : _("B"); + else + *unit = humanise_rate ? + /* TRANSLATORS: IEC 80000-13:2008 byte/second */ + Q_("byte/s", "bytes/s", bytes) : + /* TRANSLATORS: IEC 80000-13:2008 byte */ + Q_("byte", "bytes", bytes); + } +} + +static void strbuf_humanise(struct strbuf *buf, off_t bytes, unsigned flags) +{ + char *value; + const char *unit; + + humanise_bytes(bytes, &value, &unit, flags); + + /* + * TRANSLATORS: The first argument is the number string. The second + * argument is the unit string (i.e. "12.34 MiB/s"). + */ + strbuf_addf(buf, _("%s %s"), value, unit); + free(value); +} + void strbuf_humanise_bytes(struct strbuf *buf, off_t bytes) { strbuf_humanise(buf, bytes, 0); @@ -884,7 +920,7 @@ void strbuf_humanise_bytes(struct strbuf *buf, off_t bytes) void strbuf_humanise_rate(struct strbuf *buf, off_t bytes) { - strbuf_humanise(buf, bytes, 1); + strbuf_humanise(buf, bytes, HUMANISE_RATE); } int printf_ln(const char *fmt, ...) @@ -1083,6 +1119,6 @@ void strbuf_stripspace(struct strbuf *sb, const char *comment_prefix) void strbuf_strip_file_from_path(struct strbuf *sb) { - char *path_sep = find_last_dir_sep(sb->buf); + const char *path_sep = find_last_dir_sep(sb->buf); strbuf_setlen(sb, path_sep ? path_sep - sb->buf + 1 : 0); }
diff --git a/strbuf.h b/strbuf.h index a580ac6..06e284f 100644 --- a/strbuf.h +++ b/strbuf.h
@@ -367,6 +367,31 @@ void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src); */ void strbuf_add_percentencode(struct strbuf *dst, const char *src, int flags); +enum humanise_flags { + /* + * Use rate based units for humanised values. + */ + HUMANISE_RATE = (1 << 0), + /* + * Use compact "B" unit symbol instead of "byte/bytes" for humanised + * values. + */ + HUMANISE_COMPACT = (1 << 1), +}; + +/** + * Converts the given byte size into a downscaled human-readable value and + * corresponding unit as two separate strings. + */ +void humanise_bytes(off_t bytes, char **value, const char **unit, + unsigned flags); + +/** + * Converts the given count into a downscaled human-readable value and + * corresponding unit as two separate strings. + */ +void humanise_count(size_t count, char **value, const char **unit); + /** * Append the given byte size as a human-readable string (i.e. 12.23 KiB, * 3.50 MiB).
diff --git a/streaming.c b/streaming.c deleted file mode 100644 index 4b13827..0000000 --- a/streaming.c +++ /dev/null
@@ -1,552 +0,0 @@ -/* - * Copyright (c) 2011, Google Inc. - */ - -#define USE_THE_REPOSITORY_VARIABLE - -#include "git-compat-util.h" -#include "convert.h" -#include "environment.h" -#include "streaming.h" -#include "repository.h" -#include "object-file.h" -#include "odb.h" -#include "replace-object.h" -#include "packfile.h" - -typedef int (*open_istream_fn)(struct git_istream *, - struct repository *, - const struct object_id *, - enum object_type *); -typedef int (*close_istream_fn)(struct git_istream *); -typedef ssize_t (*read_istream_fn)(struct git_istream *, char *, size_t); - -#define FILTER_BUFFER (1024*16) - -struct filtered_istream { - struct git_istream *upstream; - struct stream_filter *filter; - char ibuf[FILTER_BUFFER]; - char obuf[FILTER_BUFFER]; - int i_end, i_ptr; - int o_end, o_ptr; - int input_finished; -}; - -struct git_istream { - open_istream_fn open; - close_istream_fn close; - read_istream_fn read; - - unsigned long size; /* inflated size of full object */ - git_zstream z; - enum { z_unused, z_used, z_done, z_error } z_state; - - union { - struct { - char *buf; /* from odb_read_object_info_extended() */ - unsigned long read_ptr; - } incore; - - struct { - void *mapped; - unsigned long mapsize; - char hdr[32]; - int hdr_avail; - int hdr_used; - } loose; - - struct { - struct packed_git *pack; - off_t pos; - } in_pack; - - struct filtered_istream filtered; - } u; -}; - -/***************************************************************** - * - * Common helpers - * - *****************************************************************/ - -static void close_deflated_stream(struct git_istream *st) -{ - if (st->z_state == z_used) - git_inflate_end(&st->z); -} - - -/***************************************************************** - * - * Filtered stream - * - *****************************************************************/ - -static int close_istream_filtered(struct git_istream *st) -{ - free_stream_filter(st->u.filtered.filter); - return close_istream(st->u.filtered.upstream); -} - -static ssize_t read_istream_filtered(struct git_istream *st, char *buf, - size_t sz) -{ - struct filtered_istream *fs = &(st->u.filtered); - size_t filled = 0; - - while (sz) { - /* do we already have filtered output? */ - if (fs->o_ptr < fs->o_end) { - size_t to_move = fs->o_end - fs->o_ptr; - if (sz < to_move) - to_move = sz; - memcpy(buf + filled, fs->obuf + fs->o_ptr, to_move); - fs->o_ptr += to_move; - sz -= to_move; - filled += to_move; - continue; - } - fs->o_end = fs->o_ptr = 0; - - /* do we have anything to feed the filter with? */ - if (fs->i_ptr < fs->i_end) { - size_t to_feed = fs->i_end - fs->i_ptr; - size_t to_receive = FILTER_BUFFER; - if (stream_filter(fs->filter, - fs->ibuf + fs->i_ptr, &to_feed, - fs->obuf, &to_receive)) - return -1; - fs->i_ptr = fs->i_end - to_feed; - fs->o_end = FILTER_BUFFER - to_receive; - continue; - } - - /* tell the filter to drain upon no more input */ - if (fs->input_finished) { - size_t to_receive = FILTER_BUFFER; - if (stream_filter(fs->filter, - NULL, NULL, - fs->obuf, &to_receive)) - return -1; - fs->o_end = FILTER_BUFFER - to_receive; - if (!fs->o_end) - break; - continue; - } - fs->i_end = fs->i_ptr = 0; - - /* refill the input from the upstream */ - if (!fs->input_finished) { - fs->i_end = read_istream(fs->upstream, fs->ibuf, FILTER_BUFFER); - if (fs->i_end < 0) - return -1; - if (fs->i_end) - continue; - } - fs->input_finished = 1; - } - return filled; -} - -static struct git_istream *attach_stream_filter(struct git_istream *st, - struct stream_filter *filter) -{ - struct git_istream *ifs = xmalloc(sizeof(*ifs)); - struct filtered_istream *fs = &(ifs->u.filtered); - - ifs->close = close_istream_filtered; - ifs->read = read_istream_filtered; - fs->upstream = st; - fs->filter = filter; - fs->i_end = fs->i_ptr = 0; - fs->o_end = fs->o_ptr = 0; - fs->input_finished = 0; - ifs->size = -1; /* unknown */ - return ifs; -} - -/***************************************************************** - * - * Loose object stream - * - *****************************************************************/ - -static ssize_t read_istream_loose(struct git_istream *st, char *buf, size_t sz) -{ - size_t total_read = 0; - - switch (st->z_state) { - case z_done: - return 0; - case z_error: - return -1; - default: - break; - } - - if (st->u.loose.hdr_used < st->u.loose.hdr_avail) { - size_t to_copy = st->u.loose.hdr_avail - st->u.loose.hdr_used; - if (sz < to_copy) - to_copy = sz; - memcpy(buf, st->u.loose.hdr + st->u.loose.hdr_used, to_copy); - st->u.loose.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 = z_done; - break; - } - if (status != Z_OK && (status != Z_BUF_ERROR || total_read < sz)) { - git_inflate_end(&st->z); - st->z_state = z_error; - return -1; - } - } - return total_read; -} - -static int close_istream_loose(struct git_istream *st) -{ - close_deflated_stream(st); - munmap(st->u.loose.mapped, st->u.loose.mapsize); - return 0; -} - -static int open_istream_loose(struct git_istream *st, struct repository *r, - const struct object_id *oid, - enum object_type *type) -{ - struct object_info oi = OBJECT_INFO_INIT; - oi.sizep = &st->size; - oi.typep = type; - - st->u.loose.mapped = map_loose_object(r, oid, &st->u.loose.mapsize); - if (!st->u.loose.mapped) - return -1; - switch (unpack_loose_header(&st->z, st->u.loose.mapped, - st->u.loose.mapsize, st->u.loose.hdr, - sizeof(st->u.loose.hdr))) { - case ULHR_OK: - break; - case ULHR_BAD: - case ULHR_TOO_LONG: - goto error; - } - if (parse_loose_header(st->u.loose.hdr, &oi) < 0 || *type < 0) - goto error; - - st->u.loose.hdr_used = strlen(st->u.loose.hdr) + 1; - st->u.loose.hdr_avail = st->z.total_out; - st->z_state = z_used; - st->close = close_istream_loose; - st->read = read_istream_loose; - - return 0; -error: - git_inflate_end(&st->z); - munmap(st->u.loose.mapped, st->u.loose.mapsize); - return -1; -} - - -/***************************************************************** - * - * Non-delta packed object stream - * - *****************************************************************/ - -static ssize_t read_istream_pack_non_delta(struct git_istream *st, char *buf, - size_t sz) -{ - size_t total_read = 0; - - switch (st->z_state) { - case z_unused: - memset(&st->z, 0, sizeof(st->z)); - git_inflate_init(&st->z); - st->z_state = z_used; - break; - case z_done: - return 0; - case z_error: - return -1; - case z_used: - break; - } - - while (total_read < sz) { - int status; - struct pack_window *window = NULL; - unsigned char *mapped; - - mapped = use_pack(st->u.in_pack.pack, &window, - st->u.in_pack.pos, &st->z.avail_in); - - st->z.next_out = (unsigned char *)buf + total_read; - st->z.avail_out = sz - total_read; - st->z.next_in = mapped; - status = git_inflate(&st->z, Z_FINISH); - - st->u.in_pack.pos += st->z.next_in - mapped; - total_read = st->z.next_out - (unsigned char *)buf; - unuse_pack(&window); - - if (status == Z_STREAM_END) { - git_inflate_end(&st->z); - st->z_state = z_done; - break; - } - - /* - * Unlike the loose object case, we do not have to worry here - * about running out of input bytes and spinning infinitely. If - * we get Z_BUF_ERROR due to too few input bytes, then we'll - * replenish them in the next use_pack() call when we loop. If - * we truly hit the end of the pack (i.e., because it's corrupt - * or truncated), then use_pack() catches that and will die(). - */ - if (status != Z_OK && status != Z_BUF_ERROR) { - git_inflate_end(&st->z); - st->z_state = z_error; - return -1; - } - } - return total_read; -} - -static int close_istream_pack_non_delta(struct git_istream *st) -{ - close_deflated_stream(st); - return 0; -} - -static int open_istream_pack_non_delta(struct git_istream *st, - struct repository *r UNUSED, - const struct object_id *oid UNUSED, - enum object_type *type UNUSED) -{ - struct pack_window *window; - enum object_type in_pack_type; - - window = NULL; - - in_pack_type = unpack_object_header(st->u.in_pack.pack, - &window, - &st->u.in_pack.pos, - &st->size); - unuse_pack(&window); - switch (in_pack_type) { - default: - return -1; /* we do not do deltas for now */ - case OBJ_COMMIT: - case OBJ_TREE: - case OBJ_BLOB: - case OBJ_TAG: - break; - } - st->z_state = z_unused; - st->close = close_istream_pack_non_delta; - st->read = read_istream_pack_non_delta; - - return 0; -} - - -/***************************************************************** - * - * In-core stream - * - *****************************************************************/ - -static int close_istream_incore(struct git_istream *st) -{ - free(st->u.incore.buf); - return 0; -} - -static ssize_t read_istream_incore(struct git_istream *st, char *buf, size_t sz) -{ - size_t read_size = sz; - size_t remainder = st->size - st->u.incore.read_ptr; - - if (remainder <= read_size) - read_size = remainder; - if (read_size) { - memcpy(buf, st->u.incore.buf + st->u.incore.read_ptr, read_size); - st->u.incore.read_ptr += read_size; - } - return read_size; -} - -static int open_istream_incore(struct git_istream *st, struct repository *r, - const struct object_id *oid, enum object_type *type) -{ - struct object_info oi = OBJECT_INFO_INIT; - - st->u.incore.read_ptr = 0; - st->close = close_istream_incore; - st->read = read_istream_incore; - - oi.typep = type; - oi.sizep = &st->size; - oi.contentp = (void **)&st->u.incore.buf; - return odb_read_object_info_extended(r->objects, oid, &oi, - OBJECT_INFO_DIE_IF_CORRUPT); -} - -/***************************************************************************** - * static helpers variables and functions for users of streaming interface - *****************************************************************************/ - -static int istream_source(struct git_istream *st, - struct repository *r, - const struct object_id *oid, - enum object_type *type) -{ - unsigned long size; - int status; - struct object_info oi = OBJECT_INFO_INIT; - - oi.typep = type; - oi.sizep = &size; - status = odb_read_object_info_extended(r->objects, oid, &oi, 0); - if (status < 0) - return status; - - switch (oi.whence) { - case OI_LOOSE: - st->open = open_istream_loose; - return 0; - case OI_PACKED: - if (!oi.u.packed.is_delta && - repo_settings_get_big_file_threshold(the_repository) < size) { - st->u.in_pack.pack = oi.u.packed.pack; - st->u.in_pack.pos = oi.u.packed.offset; - st->open = open_istream_pack_non_delta; - return 0; - } - /* fallthru */ - default: - st->open = open_istream_incore; - return 0; - } -} - -/**************************************************************** - * Users of streaming interface - ****************************************************************/ - -int close_istream(struct git_istream *st) -{ - int r = st->close(st); - free(st); - return r; -} - -ssize_t read_istream(struct git_istream *st, void *buf, size_t sz) -{ - return st->read(st, buf, sz); -} - -struct git_istream *open_istream(struct repository *r, - const struct object_id *oid, - enum object_type *type, - unsigned long *size, - struct stream_filter *filter) -{ - struct git_istream *st = xmalloc(sizeof(*st)); - const struct object_id *real = lookup_replace_object(r, oid); - int ret = istream_source(st, r, real, type); - - if (ret) { - free(st); - return NULL; - } - - if (st->open(st, r, real, type)) { - if (open_istream_incore(st, r, real, type)) { - free(st); - return NULL; - } - } - if (filter) { - /* Add "&& !is_null_stream_filter(filter)" for performance */ - struct git_istream *nst = attach_stream_filter(st, filter); - if (!nst) { - close_istream(st); - return NULL; - } - st = nst; - } - - *size = st->size; - return st; -} - -int stream_blob_to_fd(int fd, const struct object_id *oid, struct stream_filter *filter, - int can_seek) -{ - struct git_istream *st; - enum object_type type; - unsigned long sz; - ssize_t kept = 0; - int result = -1; - - st = open_istream(the_repository, oid, &type, &sz, filter); - if (!st) { - if (filter) - free_stream_filter(filter); - return result; - } - if (type != OBJ_BLOB) - goto close_and_exit; - for (;;) { - char buf[1024 * 16]; - ssize_t wrote, holeto; - ssize_t readlen = read_istream(st, buf, sizeof(buf)); - - if (readlen < 0) - goto close_and_exit; - if (!readlen) - break; - if (can_seek && sizeof(buf) == readlen) { - for (holeto = 0; holeto < readlen; holeto++) - if (buf[holeto]) - break; - if (readlen == holeto) { - kept += holeto; - continue; - } - } - - if (kept && lseek(fd, kept, SEEK_CUR) == (off_t) -1) - goto close_and_exit; - else - kept = 0; - wrote = write_in_full(fd, buf, readlen); - - if (wrote < 0) - goto close_and_exit; - } - if (kept && (lseek(fd, kept - 1, SEEK_CUR) == (off_t) -1 || - xwrite(fd, "", 1) != 1)) - goto close_and_exit; - result = 0; - - close_and_exit: - close_istream(st); - return result; -}
diff --git a/streaming.h b/streaming.h deleted file mode 100644 index bd27f59..0000000 --- a/streaming.h +++ /dev/null
@@ -1,21 +0,0 @@ -/* - * Copyright (c) 2011, Google Inc. - */ -#ifndef STREAMING_H -#define STREAMING_H 1 - -#include "object.h" - -/* opaque */ -struct git_istream; -struct stream_filter; - -struct git_istream *open_istream(struct repository *, const struct object_id *, - enum object_type *, unsigned long *, - struct stream_filter *); -int close_istream(struct git_istream *); -ssize_t read_istream(struct git_istream *, void *, size_t); - -int stream_blob_to_fd(int fd, const struct object_id *, struct stream_filter *, int can_seek); - -#endif /* STREAMING_H */
diff --git a/string-list.c b/string-list.c index 08dc009..d260b87 100644 --- a/string-list.c +++ b/string-list.c
@@ -247,6 +247,12 @@ void string_list_sort(struct string_list *list) QSORT_S(list->items, list->nr, cmp_items, &sort_ctx); } +void string_list_sort_u(struct string_list *list, int free_util) +{ + string_list_sort(list); + string_list_remove_duplicates(list, free_util); +} + struct string_list_item *unsorted_string_list_lookup(struct string_list *list, const char *string) { @@ -275,6 +281,15 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_ list->nr--; } +void unsorted_string_list_remove(struct string_list *list, const char *str, + int free_util) +{ + struct string_list_item *item = unsorted_string_list_lookup(list, str); + if (item) + unsorted_string_list_delete_item(list, item - list->items, + free_util); +} + /* * append a substring [p..end] to list; return number of things it * appended to the list. @@ -327,7 +342,7 @@ static int split_string(struct string_list *list, const char *string, const char BUG("string_list_split() called without strdup_strings"); for (;;) { - char *end; + const char *end; if (flags & STRING_LIST_SPLIT_TRIM) { /* ltrim */
diff --git a/string-list.h b/string-list.h index fa6ba07..b86ee7c 100644 --- a/string-list.h +++ b/string-list.h
@@ -240,6 +240,12 @@ struct string_list_item *string_list_append_nodup(struct string_list *list, char void string_list_sort(struct string_list *list); /** + * Sort the list and then remove duplicate entries. If free_util is true, + * call free() on the util members of any items that have to be deleted. + */ +void string_list_sort_u(struct string_list *list, int free_util); + +/** * Like `string_list_has_string()` but for unsorted lists. Linear in * size of the list. */ @@ -260,6 +266,14 @@ struct string_list_item *unsorted_string_list_lookup(struct string_list *list, void unsorted_string_list_delete_item(struct string_list *list, int i, int free_util); /** + * Remove the first item matching `str` from an unsorted string_list. + * No-op if `str` is not found. If `free_util` is non-zero, the `util` + * pointer of the removed item is freed before deletion. + */ +void unsorted_string_list_remove(struct string_list *list, const char *str, + int free_util); + +/** * Split string into substrings on characters in `delim` and append the * substrings to `list`. The input string is not modified. * list->strdup_strings must be set, as new memory needs to be
diff --git a/submodule-config.c b/submodule-config.c index 1f19fe2..72a46b7 100644 --- a/submodule-config.c +++ b/submodule-config.c
@@ -14,6 +14,7 @@ #include "strbuf.h" #include "object-name.h" #include "odb.h" +#include "odb/source.h" #include "parse-options.h" #include "thread-utils.h" #include "tree-walk.h"
diff --git a/submodule.c b/submodule.c index 35c5515..b1a0363 100644 --- a/submodule.c +++ b/submodule.c
@@ -31,6 +31,8 @@ #include "commit-reach.h" #include "read-cache-ll.h" #include "setup.h" +#include "advice.h" +#include "url.h" static int config_update_recurse_submodules = RECURSE_SUBMODULES_OFF; static int initialized_fetch_ref_tips; @@ -99,7 +101,7 @@ int is_staging_gitmodules_ok(struct index_state *istate) } static int for_each_remote_ref_submodule(const char *submodule, - each_ref_fn fn, void *cb_data) + refs_for_each_cb fn, void *cb_data) { return refs_for_each_remote_ref(repo_get_submodule_ref_store(the_repository, submodule), @@ -639,7 +641,7 @@ void show_submodule_diff_summary(struct diff_options *o, const char *path, print_submodule_diff_summary(sub, &rev, o); out: - free_commit_list(merge_bases); + commit_list_free(merge_bases); release_revisions(&rev); clear_commit_marks(left, ~0); clear_commit_marks(right, ~0); @@ -729,7 +731,7 @@ void show_submodule_inline_diff(struct diff_options *o, const char *path, done: strbuf_release(&sb); - free_commit_list(merge_bases); + commit_list_free(merge_bases); if (left) clear_commit_marks(left, ~0); if (right) @@ -934,10 +936,7 @@ static void free_submodules_data(struct string_list *submodules) string_list_clear(submodules, 1); } -static int has_remote(const char *refname UNUSED, - const char *referent UNUSED, - const struct object_id *oid UNUSED, - int flags UNUSED, void *cb_data UNUSED) +static int has_remote(const struct reference *ref UNUSED, void *cb_data UNUSED) { return 1; } @@ -1255,13 +1254,10 @@ int push_unpushed_submodules(struct repository *r, return ret; } -static int append_oid_to_array(const char *ref UNUSED, - const char *referent UNUSED, - const struct object_id *oid, - int flags UNUSED, void *data) +static int append_oid_to_array(const struct reference *ref, void *data) { struct oid_array *array = data; - oid_array_append(array, oid); + oid_array_append(array, ref->oid); return 0; } @@ -1712,6 +1708,8 @@ static int get_next_submodule(struct child_process *cp, struct strbuf *err, if (spf->oid_fetch_tasks_nr) { struct fetch_task *task = spf->oid_fetch_tasks[spf->oid_fetch_tasks_nr - 1]; + struct child_process cp_remote = CHILD_PROCESS_INIT; + struct strbuf remote_name = STRBUF_INIT; spf->oid_fetch_tasks_nr--; child_process_init(cp); @@ -1725,8 +1723,19 @@ static int get_next_submodule(struct child_process *cp, struct strbuf *err, strvec_pushf(&cp->args, "--submodule-prefix=%s%s/", spf->prefix, task->sub->path); - /* NEEDSWORK: have get_default_remote from submodule--helper */ - strvec_push(&cp->args, "origin"); + cp_remote.git_cmd = 1; + strvec_pushl(&cp_remote.args, "submodule--helper", + "get-default-remote", task->sub->path, NULL); + + if (!capture_command(&cp_remote, &remote_name, 0)) { + strbuf_trim_trailing_newline(&remote_name); + strvec_push(&cp->args, remote_name.buf); + } else { + /* Fallback to "origin" if the helper fails */ + strvec_push(&cp->args, "origin"); + } + strbuf_release(&remote_name); + oid_array_for_each_unique(task->commits, append_oid_to_argv, &cp->args); @@ -1819,7 +1828,6 @@ int fetch_submodules(struct repository *r, int default_option, int quiet, int max_parallel_jobs) { - int i; struct submodule_parallel_fetch spf = SPF_INIT; const struct run_process_parallel_opts opts = { .tr2_category = "submodule", @@ -1846,8 +1854,7 @@ int fetch_submodules(struct repository *r, die(_("index file corrupt")); strvec_push(&spf.args, "fetch"); - for (i = 0; i < options->nr; i++) - strvec_push(&spf.args, options->v[i]); + strvec_pushv(&spf.args, options->v); strvec_push(&spf.args, "--recurse-submodules-default"); /* default value, "--submodule-prefix" and its value are added later */ @@ -2164,19 +2171,15 @@ int submodule_move_head(const char *path, const char *super_prefix, if (validate_submodule_git_dir(git_dir, sub->name) < 0) die(_("refusing to create/use '%s' in " - "another submodule's git dir"), - git_dir); + "another submodule's git dir. " + "Enabling extensions.submodulePathConfig " + "should fix this."), git_dir); free(git_dir); } } else { struct strbuf gitdir = STRBUF_INIT; submodule_name_to_gitdir(&gitdir, the_repository, sub->name); - if (validate_submodule_git_dir(gitdir.buf, - sub->name) < 0) - die(_("refusing to create/use '%s' in another " - "submodule's git dir"), - gitdir.buf); connect_work_tree_and_git_dir(path, gitdir.buf, 0); strbuf_release(&gitdir); @@ -2256,12 +2259,155 @@ int submodule_move_head(const char *path, const char *super_prefix, return ret; } -int validate_submodule_git_dir(char *git_dir, const char *submodule_name) +static int check_casefolding_conflict(const char *git_dir, + const char *submodule_name, + const bool suffixes_match) +{ + char *p, *modules_dir = xstrdup(git_dir); + struct dirent *de; + DIR *dir = NULL; + int ret = 0; + + if ((p = find_last_dir_sep(modules_dir))) + *p = '\0'; + + /* No conflict is possible if modules_dir doesn't exist (first clone) */ + if (!is_directory(modules_dir)) + goto cleanup; + + dir = opendir(modules_dir); + if (!dir) { + ret = -1; + goto cleanup; + } + + /* Check for another directory under .git/modules that differs only in case. */ + while ((de = readdir(dir))) { + if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) + continue; + + if ((suffixes_match || is_git_directory(git_dir)) && + !strcasecmp(de->d_name, submodule_name) && + strcmp(de->d_name, submodule_name)) { + ret = -1; /* collision found */ + break; + } + } + +cleanup: + if (dir) + closedir(dir); + free(modules_dir); + return ret; +} + +struct submodule_from_gitdir_cb { + const char *gitdir; + const char *submodule_name; + bool conflict_found; +}; + +static int find_conflict_by_gitdir_cb(const char *var, const char *value, + const struct config_context *ctx UNUSED, void *data) +{ + struct submodule_from_gitdir_cb *cb = data; + const char *submodule_name_start; + size_t submodule_name_len; + const char *suffix = ".gitdir"; + size_t suffix_len = strlen(suffix); + + if (!skip_prefix(var, "submodule.", &submodule_name_start)) + return 0; + + /* Check if submodule_name_start ends with ".gitdir" */ + submodule_name_len = strlen(submodule_name_start); + if (submodule_name_len < suffix_len || + strcmp(submodule_name_start + submodule_name_len - suffix_len, suffix) != 0) + return 0; /* Does not end with ".gitdir" */ + + submodule_name_len -= suffix_len; + + /* + * A conflict happens if: + * 1. The submodule names are different and + * 2. The gitdir paths resolve to the same absolute path + */ + if (value && strncmp(cb->submodule_name, submodule_name_start, submodule_name_len)) { + char *abs_path_cb = absolute_pathdup(cb->gitdir); + char *abs_path_value = absolute_pathdup(value); + + cb->conflict_found = !strcmp(abs_path_cb, abs_path_value); + + free(abs_path_cb); + free(abs_path_value); + } + + return cb->conflict_found; +} + +static bool submodule_conflicts_with_existing(const char *gitdir, const char *submodule_name) +{ + struct submodule_from_gitdir_cb cb = { 0 }; + cb.submodule_name = submodule_name; + cb.gitdir = gitdir; + + /* Find conflicts with existing repo gitdir configs */ + repo_config(the_repository, find_conflict_by_gitdir_cb, &cb); + + return cb.conflict_found; +} + +/* + * Encoded gitdir validation, only used when extensions.submodulePathConfig is enabled. + * This does not print errors like the non-encoded version, because encoding is supposed + * to mitigate / fix all these. + */ +static int validate_submodule_encoded_git_dir(char *git_dir, const char *submodule_name) +{ + const char *modules_marker = "/modules/"; + char *p = git_dir, *last_submodule_name = NULL; + int config_ignorecase = 0; + + if (!the_repository->repository_format_submodule_path_cfg) + BUG("validate_submodule_encoded_git_dir() must be called with " + "extensions.submodulePathConfig enabled."); + + /* Find the last submodule name in the gitdir path (modules can be nested). */ + while ((p = strstr(p, modules_marker))) { + last_submodule_name = p + strlen(modules_marker); + p++; + } + + /* Prevent the use of '/' in encoded names */ + if (!last_submodule_name || strchr(last_submodule_name, '/')) + return -1; + + /* Prevent conflicts with existing submodule gitdirs */ + if (is_git_directory(git_dir) && + submodule_conflicts_with_existing(git_dir, submodule_name)) + return -1; + + /* Prevent conflicts on case-folding filesystems */ + repo_config_get_bool(the_repository, "core.ignorecase", &config_ignorecase); + if (ignore_case || config_ignorecase) { + bool suffixes_match = !strcmp(last_submodule_name, submodule_name); + return check_casefolding_conflict(git_dir, submodule_name, + suffixes_match); + } + + return 0; +} + +static int validate_submodule_legacy_git_dir(char *git_dir, const char *submodule_name) { size_t len = strlen(git_dir), suffix_len = strlen(submodule_name); char *p; int ret = 0; + if (the_repository->repository_format_submodule_path_cfg) + BUG("validate_submodule_git_dir() must be called with " + "extensions.submodulePathConfig disabled."); + if (len <= suffix_len || (p = git_dir + len - suffix_len)[-1] != '/' || strcmp(p, submodule_name)) BUG("submodule name '%s' not a suffix of git dir '%s'", @@ -2297,6 +2443,14 @@ int validate_submodule_git_dir(char *git_dir, const char *submodule_name) return 0; } +int validate_submodule_git_dir(char *git_dir, const char *submodule_name) +{ + if (!the_repository->repository_format_submodule_path_cfg) + return validate_submodule_legacy_git_dir(git_dir, submodule_name); + + return validate_submodule_encoded_git_dir(git_dir, submodule_name); +} + int validate_submodule_path(const char *path) { char *p = xstrdup(path); @@ -2355,9 +2509,6 @@ static void relocate_single_git_dir_into_superproject(const char *path, die(_("could not lookup name for submodule '%s'"), path); submodule_name_to_gitdir(&new_gitdir, the_repository, sub->name); - if (validate_submodule_git_dir(new_gitdir.buf, sub->name) < 0) - die(_("refusing to move '%s' into an existing git dir"), - real_old_git_dir); if (safe_create_leading_directories_const(the_repository, new_gitdir.buf) < 0) die(_("could not create directory '%s'"), new_gitdir.buf); real_new_git_dir = real_pathdup(new_gitdir.buf, 1); @@ -2419,7 +2570,7 @@ void absorb_git_dir_into_superproject(const char *path, const struct submodule *sub; struct strbuf sub_gitdir = STRBUF_INIT; - if (err_code == READ_GITFILE_ERR_STAT_FAILED) { + if (err_code == READ_GITFILE_ERR_MISSING) { /* unpopulated as expected */ strbuf_release(&gitdir); return; @@ -2584,26 +2735,37 @@ int submodule_to_gitdir(struct repository *repo, void submodule_name_to_gitdir(struct strbuf *buf, struct repository *r, const char *submodule_name) { - /* - * NEEDSWORK: The current way of mapping a submodule's name to - * its location in .git/modules/ has problems with some naming - * schemes. For example, if a submodule is named "foo" and - * another is named "foo/bar" (whether present in the same - * superproject commit or not - the problem will arise if both - * superproject commits have been checked out at any point in - * time), or if two submodule names only have different cases in - * a case-insensitive filesystem. - * - * There are several solutions, including encoding the path in - * some way, introducing a submodule.<name>.gitdir config in - * .git/config (not .gitmodules) that allows overriding what the - * gitdir of a submodule would be (and teach Git, upon noticing - * a clash, to automatically determine a non-clashing name and - * to write such a config), or introducing a - * submodule.<name>.gitdir config in .gitmodules that repo - * administrators can explicitly set. Nothing has been decided, - * so for now, just append the name at the end of the path. - */ - repo_git_path_append(r, buf, "modules/"); - strbuf_addstr(buf, submodule_name); + if (!r->repository_format_submodule_path_cfg) { + /* + * If extensions.submodulePathConfig is disabled, + * continue to use the plain path. + */ + repo_git_path_append(r, buf, "modules/%s", submodule_name); + } else { + const char *gitdir; + char *key; + int ret; + + /* Otherwise the extension is enabled, so use the gitdir config. */ + key = xstrfmt("submodule.%s.gitdir", submodule_name); + ret = repo_config_get_string_tmp(r, key, &gitdir); + FREE_AND_NULL(key); + + if (ret) + die(_("the 'submodule.%s.gitdir' config does not exist for module '%s'. " + "Please ensure it is set, for example by running something like: " + "'git config submodule.%s.gitdir .git/modules/%s'. For details " + "see the extensions.submodulePathConfig documentation."), + submodule_name, submodule_name, submodule_name, submodule_name); + + strbuf_addstr(buf, gitdir); + } + + /* validate because users might have modified the config */ + if (validate_submodule_git_dir(buf->buf, submodule_name)) { + advise(_("enabling extensions.submodulePathConfig might fix the " + "following error, if it's not already enabled.")); + die(_("refusing to create/use '%s' in another submodule's " + " git dir."), buf->buf); + } }
diff --git a/subprojects/.gitignore b/subprojects/.gitignore index 63ea916..2bb68c8 100644 --- a/subprojects/.gitignore +++ b/subprojects/.gitignore
@@ -1 +1,2 @@ /*/ +.wraplock
diff --git a/subprojects/git-gui b/subprojects/git-gui new file mode 120000 index 0000000..c6d9170 --- /dev/null +++ b/subprojects/git-gui
@@ -0,0 +1 @@ +../git-gui \ No newline at end of file
diff --git a/subprojects/gitk b/subprojects/gitk new file mode 120000 index 0000000..b66ad18 --- /dev/null +++ b/subprojects/gitk
@@ -0,0 +1 @@ +../gitk-git \ No newline at end of file
diff --git a/symlinks.c b/symlinks.c index 9cc090d..9e01ab3 100644 --- a/symlinks.c +++ b/symlinks.c
@@ -74,11 +74,12 @@ static inline void reset_lstat_cache(struct cache_def *cache) */ static int lstat_cache_matchlen(struct cache_def *cache, const char *name, int len, - int *ret_flags, int track_flags, + unsigned int *ret_flags, unsigned int track_flags, int prefix_len_stat_func) { int match_len, last_slash, last_slash_dir, previous_slash; - int save_flags, ret, saved_errno = 0; + unsigned int save_flags; + int ret, saved_errno = 0; struct stat st; if (cache->track_flags != track_flags || @@ -192,10 +193,10 @@ static int lstat_cache_matchlen(struct cache_def *cache, return match_len; } -static int lstat_cache(struct cache_def *cache, const char *name, int len, - int track_flags, int prefix_len_stat_func) +static unsigned int lstat_cache(struct cache_def *cache, const char *name, int len, + unsigned int track_flags, int prefix_len_stat_func) { - int flags; + unsigned int flags; (void)lstat_cache_matchlen(cache, name, len, &flags, track_flags, prefix_len_stat_func); return flags; @@ -234,7 +235,7 @@ int check_leading_path(const char *name, int len, int warn_on_lstat_err) static int threaded_check_leading_path(struct cache_def *cache, const char *name, int len, int warn_on_lstat_err) { - int flags; + unsigned int flags; int match_len = lstat_cache_matchlen(cache, name, len, &flags, FL_SYMLINK|FL_NOENT|FL_DIR, USE_ONLY_LSTAT); int saved_errno = errno;
diff --git a/symlinks.h b/symlinks.h index 7ae3d5b..25bf04f 100644 --- a/symlinks.h +++ b/symlinks.h
@@ -5,8 +5,8 @@ struct cache_def { struct strbuf path; - int flags; - int track_flags; + unsigned int flags; + unsigned int track_flags; int prefix_len_stat_func; }; #define CACHE_DEF_INIT { \
diff --git a/t/check-non-portable-shell.pl b/t/check-non-portable-shell.pl index 6ee7700..18d944b 100755 --- a/t/check-non-portable-shell.pl +++ b/t/check-non-portable-shell.pl
@@ -36,7 +36,7 @@ $_ = $line; /\bcp\s+-a/ and err 'cp -a is not portable'; - /\bsed\s+-[^efn]\s+/ and err 'sed option not portable (use only -n, -e, -f)'; + /\bsed\s+-[^Eefn]\s+/ and err 'sed option not portable (use only -E, -n, -e, -f)'; /\becho\s+-[neE]/ and err 'echo with option is not portable (use printf)'; /^\s*declare\s+/ and err 'arrays/declare not portable'; /^\s*[^#]\s*which\s/ and err 'which is not portable (use type)';
diff --git a/t/for-each-ref-tests.sh b/t/for-each-ref-tests.sh index e3ad192..bd2d45c 100644 --- a/t/for-each-ref-tests.sh +++ b/t/for-each-ref-tests.sh
@@ -1744,6 +1744,15 @@ ) ' +test_expect_success '%(push) with an invalid push-simple config' ' + echo "refs/heads/main " >expect && + git -c push.default=simple \ + -c remote.pushdefault=myfork \ + for-each-ref \ + --format="%(refname) %(push)" refs/heads/main >actual && + test_cmp expect actual +' + test_expect_success "${git_for_each_ref} --ignore-case ignores case" ' ${git_for_each_ref} --format="%(refname)" refs/heads/MAIN >actual && test_must_be_empty actual && @@ -1809,7 +1818,9 @@ bad=$(git hash-object -w -t tag bad) && git update-ref refs/tags/broken-tag-bad $bad && test_must_fail ${git_for_each_ref} --format="%(*objectname)" \ - refs/tags/broken-tag-* + refs/tags/broken-tag-* && + test_must_fail ${git_for_each_ref} --format="%(*objectname)" \ + refs/tags/broken-tag-bad ' test_expect_success 'set up tag with signature and no blank lines' '
diff --git a/t/helper/test-cache-tree.c b/t/helper/test-cache-tree.c index 3ae45ce..ff61d0c 100644 --- a/t/helper/test-cache-tree.c +++ b/t/helper/test-cache-tree.c
@@ -41,7 +41,7 @@ int cmd__cache_tree(int argc, const char **argv) die(_("unable to read index file")); oidcpy(&oid, &the_repository->index->cache_tree->oid); - tree = parse_tree_indirect(&oid); + tree = repo_parse_tree_indirect(the_repository, &oid); if (!tree) die(_("not a tree object: %s"), oid_to_hex(&oid));
diff --git a/t/helper/test-example-tap.c b/t/helper/test-example-tap.c index 229d495..998a1f0 100644 --- a/t/helper/test-example-tap.c +++ b/t/helper/test-example-tap.c
@@ -63,6 +63,8 @@ static void t_messages(void) check_str("NULL", NULL); check_char('a', ==, '\n'); check_char('\\', ==, '\''); + check_char('\a', ==, '\v'); + check_char('\x00', ==, '\x01'); } static void t_empty(void) @@ -123,6 +125,8 @@ int cmd__example_tap(int argc UNUSED, const char **argv UNUSED) check_str("NULL", NULL); check_char('a', ==, '\n'); check_char('\\', ==, '\''); + check_char('\a', ==, '\v'); + check_char('\x00', ==, '\x01'); } if_test ("if_test test with no checks") ; /* nothing */
diff --git a/t/helper/test-genrandom.c b/t/helper/test-genrandom.c index 51b67f2..d681961 100644 --- a/t/helper/test-genrandom.c +++ b/t/helper/test-genrandom.c
@@ -6,6 +6,7 @@ #include "test-tool.h" #include "git-compat-util.h" +#include "parse.h" int cmd__genrandom(int argc, const char **argv) { @@ -22,7 +23,9 @@ int cmd__genrandom(int argc, const char **argv) next = next * 11 + *c; } while (*c++); - count = (argc == 3) ? strtoul(argv[2], NULL, 0) : ULONG_MAX; + count = ULONG_MAX; + if (argc == 3 && !git_parse_ulong(argv[2], &count)) + return error_errno("cannot parse argument '%s'", argv[2]); while (count--) { next = next * 1103515245 + 12345;
diff --git a/t/helper/test-match-trees.c b/t/helper/test-match-trees.c index e0e2048..2ed064b 100644 --- a/t/helper/test-match-trees.c +++ b/t/helper/test-match-trees.c
@@ -19,10 +19,10 @@ int cmd__match_trees(int ac UNUSED, const char **av) die("cannot parse %s as an object name", av[1]); if (repo_get_oid(the_repository, av[2], &hash2)) die("cannot parse %s as an object name", av[2]); - one = parse_tree_indirect(&hash1); + one = repo_parse_tree_indirect(the_repository, &hash1); if (!one) die("not a tree-ish %s", av[1]); - two = parse_tree_indirect(&hash2); + two = repo_parse_tree_indirect(the_repository, &hash2); if (!two) die("not a tree-ish %s", av[2]);
diff --git a/t/helper/test-mktemp.c b/t/helper/test-mktemp.c index 2290688..da19564 100644 --- a/t/helper/test-mktemp.c +++ b/t/helper/test-mktemp.c
@@ -6,10 +6,16 @@ int cmd__mktemp(int argc, const char **argv) { + char *template; + int fd; + if (argc != 2) usage("Expected 1 parameter defining the temporary file template"); + template = xstrdup(argv[1]); - xmkstemp(xstrdup(argv[1])); + fd = xmkstemp(template); + close(fd); + free(template); return 0; }
diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c index f5f3375..874542e 100644 --- a/t/helper/test-path-utils.c +++ b/t/helper/test-path-utils.c
@@ -477,14 +477,20 @@ int cmd__path_utils(int argc, const char **argv) if (argc > 5 && !strcmp(argv[1], "slice-tests")) { int res = 0; - long offset, stride, i; + long slice, slices_total, i; struct string_list list = STRING_LIST_INIT_NODUP; struct stat st; - offset = strtol(argv[2], NULL, 10); - stride = strtol(argv[3], NULL, 10); - if (stride < 1) - stride = 1; + slices_total = strtol(argv[3], NULL, 10); + if (slices_total < 1) + die("there must be at least one slice, got '%s'", + argv[3]); + + slice = strtol(argv[2], NULL, 10); + if (1 > slice || slice > slices_total) + die("slice must be in the range 1 <= slice <= %ld, got '%s'", + slices_total, argv[2]); + for (i = 4; i < argc; i++) if (stat(argv[i], &st)) res = error_errno("Cannot stat '%s'", argv[i]); @@ -492,7 +498,7 @@ int cmd__path_utils(int argc, const char **argv) string_list_append(&list, argv[i])->util = (void *)(intptr_t)st.st_size; QSORT(list.items, list.nr, cmp_by_st_size); - for (i = offset; i < list.nr; i+= stride) + for (i = slice - 1; i < list.nr; i+= slices_total) printf("%s\n", list.items[i].string); return !!res;
diff --git a/t/helper/test-reach.c b/t/helper/test-reach.c index 028ec00..3131b54 100644 --- a/t/helper/test-reach.c +++ b/t/helper/test-reach.c
@@ -34,8 +34,8 @@ int cmd__reach(int ac, const char **av) struct commit *A, *B; struct commit_list *X, *Y; struct object_array X_obj = OBJECT_ARRAY_INIT; - struct commit **X_array, **Y_array; - size_t X_nr, X_alloc, Y_nr, Y_alloc; + struct commit_stack X_stack = COMMIT_STACK_INIT; + struct commit_stack Y_stack = COMMIT_STACK_INIT; struct strbuf buf = STRBUF_INIT; struct repository *r = the_repository; @@ -46,10 +46,6 @@ int cmd__reach(int ac, const char **av) A = B = NULL; X = Y = NULL; - X_nr = Y_nr = 0; - X_alloc = Y_alloc = 16; - ALLOC_ARRAY(X_array, X_alloc); - ALLOC_ARRAY(Y_array, Y_alloc); while (strbuf_getline(&buf, stdin) != EOF) { struct object_id oid; @@ -63,7 +59,7 @@ int cmd__reach(int ac, const char **av) die("failed to resolve %s", buf.buf + 2); orig = parse_object(r, &oid); - peeled = deref_tag_noverify(the_repository, orig); + peeled = deref_tag(the_repository, orig, NULL, 0); if (!peeled) die("failed to load commit for input %s resulting in oid %s", @@ -88,15 +84,13 @@ int cmd__reach(int ac, const char **av) case 'X': commit_list_insert(c, &X); - ALLOC_GROW(X_array, X_nr + 1, X_alloc); - X_array[X_nr++] = c; + commit_stack_push(&X_stack, c); add_object_array(orig, NULL, &X_obj); break; case 'Y': commit_list_insert(c, &Y); - ALLOC_GROW(Y_array, Y_nr + 1, Y_alloc); - Y_array[Y_nr++] = c; + commit_stack_push(&Y_stack, c); break; default: @@ -112,26 +106,26 @@ int cmd__reach(int ac, const char **av) repo_in_merge_bases(the_repository, A, B)); else if (!strcmp(av[1], "in_merge_bases_many")) printf("%s(A,X):%d\n", av[1], - repo_in_merge_bases_many(the_repository, A, X_nr, X_array, 0)); + repo_in_merge_bases_many(the_repository, A, X_stack.nr, X_stack.items, 0)); else if (!strcmp(av[1], "is_descendant_of")) printf("%s(A,X):%d\n", av[1], repo_is_descendant_of(r, A, X)); else if (!strcmp(av[1], "get_branch_base_for_tip")) - printf("%s(A,X):%d\n", av[1], get_branch_base_for_tip(r, A, X_array, X_nr)); + printf("%s(A,X):%d\n", av[1], get_branch_base_for_tip(r, A, X_stack.items, X_stack.nr)); else if (!strcmp(av[1], "get_merge_bases_many")) { struct commit_list *list = NULL; if (repo_get_merge_bases_many(the_repository, - A, X_nr, - X_array, + A, X_stack.nr, + X_stack.items, &list) < 0) exit(128); printf("%s(A,X):\n", av[1]); print_sorted_commit_ids(list); - free_commit_list(list); + commit_list_free(list); } else if (!strcmp(av[1], "reduce_heads")) { struct commit_list *list = reduce_heads(X); printf("%s(X):\n", av[1]); print_sorted_commit_ids(list); - free_commit_list(list); + commit_list_free(list); } else if (!strcmp(av[1], "can_all_from_reach")) { printf("%s(X,Y):%d\n", av[1], can_all_from_reach(X, Y, 1)); } else if (!strcmp(av[1], "can_all_from_reach_with_flag")) { @@ -159,8 +153,8 @@ int cmd__reach(int ac, const char **av) const int reachable_flag = 1; int count = 0; struct commit_list *current; - struct commit_list *list = get_reachable_subset(X_array, X_nr, - Y_array, Y_nr, + struct commit_list *list = get_reachable_subset(X_stack.items, X_stack.nr, + Y_stack.items, Y_stack.nr, reachable_flag); printf("get_reachable_subset(X,Y)\n"); for (current = list; current; current = current->next) { @@ -169,8 +163,8 @@ int cmd__reach(int ac, const char **av) oid_to_hex(&list->item->object.oid)); count++; } - for (size_t i = 0; i < Y_nr; i++) { - if (Y_array[i]->object.flags & reachable_flag) + for (size_t i = 0; i < Y_stack.nr; i++) { + if (Y_stack.items[i]->object.flags & reachable_flag) count--; } @@ -178,14 +172,14 @@ int cmd__reach(int ac, const char **av) die(_("too many commits marked reachable")); print_sorted_commit_ids(list); - free_commit_list(list); + commit_list_free(list); } object_array_clear(&X_obj); strbuf_release(&buf); - free_commit_list(X); - free_commit_list(Y); - free(X_array); - free(Y_array); + commit_list_free(X); + commit_list_free(Y); + commit_stack_clear(&X_stack); + commit_stack_clear(&Y_stack); return 0; }
diff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c index 6de5d16..388d29e 100644 --- a/t/helper/test-read-midx.c +++ b/t/helper/test-read-midx.c
@@ -26,18 +26,22 @@ static int read_midx_file(const char *object_dir, const char *checksum, int show_objects) { uint32_t i; - struct multi_pack_index *m; + struct multi_pack_index *m, *tip; + int ret = 0; - m = setup_midx(object_dir); + m = tip = setup_midx(object_dir); if (!m) return 1; if (checksum) { - while (m && strcmp(hash_to_hex(get_midx_checksum(m)), checksum)) + while (m && strcmp(midx_get_checksum_hex(m), checksum)) m = m->base_midx; - if (!m) - return 1; + if (!m) { + ret = error(_("could not find MIDX with checksum %s"), + checksum); + goto out; + } } printf("header: %08x %d %d %d %d\n", @@ -82,9 +86,10 @@ static int read_midx_file(const char *object_dir, const char *checksum, } } - close_midx(m); +out: + close_midx(tip); - return 0; + return ret; } static int read_midx_checksum(const char *object_dir) @@ -94,7 +99,7 @@ static int read_midx_checksum(const char *object_dir) m = setup_midx(object_dir); if (!m) return 1; - printf("%s\n", hash_to_hex(get_midx_checksum(m))); + printf("%s\n", midx_get_checksum_hex(m)); close_midx(m); return 0;
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c index 83b06d3..74edf20 100644 --- a/t/helper/test-ref-store.c +++ b/t/helper/test-ref-store.c
@@ -154,27 +154,31 @@ static int cmd_rename_ref(struct ref_store *refs, const char **argv) return refs_rename_ref(refs, oldref, newref, logmsg); } -static int each_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid, - int flags, void *cb_data UNUSED) +static int each_ref(const struct reference *ref, void *cb_data UNUSED) { - printf("%s %s 0x%x\n", oid_to_hex(oid), refname, flags); + printf("%s %s 0x%x\n", oid_to_hex(ref->oid), ref->name, ref->flags); return 0; } static int cmd_for_each_ref(struct ref_store *refs, const char **argv) { const char *prefix = notnull(*argv++, "prefix"); - - return refs_for_each_ref_in(refs, prefix, each_ref, NULL); + struct refs_for_each_ref_options opts = { + .prefix = prefix, + .trim_prefix = strlen(prefix), + }; + return refs_for_each_ref_ext(refs, each_ref, NULL, &opts); } static int cmd_for_each_ref__exclude(struct ref_store *refs, const char **argv) { const char *prefix = notnull(*argv++, "prefix"); - const char **exclude_patterns = argv; + struct refs_for_each_ref_options opts = { + .prefix = prefix, + .exclude_patterns = argv, + }; - return refs_for_each_fullref_in(refs, prefix, exclude_patterns, each_ref, - NULL); + return refs_for_each_ref_ext(refs, each_ref, NULL, &opts); } static int cmd_resolve_ref(struct ref_store *refs, const char **argv)
diff --git a/t/helper/test-repository.c b/t/helper/test-repository.c index 63c37de..9ba94cd 100644 --- a/t/helper/test-repository.c +++ b/t/helper/test-repository.c
@@ -17,10 +17,6 @@ static void test_parse_commit_in_graph(const char *gitdir, const char *worktree, struct commit *c; struct commit_list *parent; - setup_git_env(gitdir); - - repo_clear(the_repository); - if (repo_init(&r, gitdir, worktree)) die("Couldn't init repo"); @@ -47,10 +43,6 @@ static void test_get_commit_tree_in_graph(const char *gitdir, struct commit *c; struct tree *tree; - setup_git_env(gitdir); - - repo_clear(the_repository); - if (repo_init(&r, gitdir, worktree)) die("Couldn't init repo"); @@ -75,24 +67,20 @@ static void test_get_commit_tree_in_graph(const char *gitdir, int cmd__repository(int argc, const char **argv) { - int nongit_ok = 0; - - setup_git_directory_gently(&nongit_ok); - if (argc < 2) die("must have at least 2 arguments"); if (!strcmp(argv[1], "parse_commit_in_graph")) { struct object_id oid; if (argc < 5) die("not enough arguments"); - if (parse_oid_hex(argv[4], &oid, &argv[4])) + if (parse_oid_hex_any(argv[4], &oid, &argv[4]) == GIT_HASH_UNKNOWN) die("cannot parse oid '%s'", argv[4]); test_parse_commit_in_graph(argv[2], argv[3], &oid); } else if (!strcmp(argv[1], "get_commit_tree_in_graph")) { struct object_id oid; if (argc < 5) die("not enough arguments"); - if (parse_oid_hex(argv[4], &oid, &argv[4])) + if (parse_oid_hex_any(argv[4], &oid, &argv[4]) == GIT_HASH_UNKNOWN) die("cannot parse oid '%s'", argv[4]); test_get_commit_tree_in_graph(argv[2], argv[3], &oid); } else {
diff --git a/t/helper/test-run-command.c b/t/helper/test-run-command.c index 3719f23..4a56456 100644 --- a/t/helper/test-run-command.c +++ b/t/helper/test-run-command.c
@@ -23,19 +23,26 @@ static int number_callbacks; static int parallel_next(struct child_process *cp, struct strbuf *err, void *cb, - void **task_cb UNUSED) + void **task_cb) { struct child_process *d = cb; if (number_callbacks >= 4) return 0; strvec_pushv(&cp->args, d->args.v); + cp->in = d->in; + cp->no_stdin = d->no_stdin; if (err) strbuf_addstr(err, "preloaded output of a child\n"); else fprintf(stderr, "preloaded output of a child\n"); number_callbacks++; + + /* test_stdin callback will use this to count remaining lines */ + *task_cb = xmalloc(sizeof(int)); + *(int*)(*task_cb) = 2; + return 1; } @@ -54,15 +61,48 @@ static int no_job(struct child_process *cp UNUSED, static int task_finished(int result UNUSED, struct strbuf *err, void *pp_cb UNUSED, - void *pp_task_cb UNUSED) + void *pp_task_cb) { if (err) strbuf_addstr(err, "asking for a quick stop\n"); else fprintf(stderr, "asking for a quick stop\n"); + + FREE_AND_NULL(pp_task_cb); + return 1; } +static int task_finished_quiet(int result UNUSED, + struct strbuf *err UNUSED, + void *pp_cb UNUSED, + void *pp_task_cb) +{ + FREE_AND_NULL(pp_task_cb); + return 0; +} + +static int test_stdin_pipe_feed(int hook_stdin_fd, void *cb UNUSED, void *task_cb) +{ + int *lines_remaining = task_cb; + + if (*lines_remaining) { + struct strbuf buf = STRBUF_INIT; + strbuf_addf(&buf, "sample stdin %d\n", --(*lines_remaining)); + if (write_in_full(hook_stdin_fd, buf.buf, buf.len) < 0) { + if (errno == EPIPE) { + /* child closed stdin, nothing more to do */ + strbuf_release(&buf); + return 1; + } + die_errno("write"); + } + strbuf_release(&buf); + } + + return !(*lines_remaining); +} + struct testsuite { struct string_list tests, failed; int next; @@ -157,6 +197,7 @@ static int testsuite(int argc, const char **argv) struct run_process_parallel_opts opts = { .get_next_task = next_test, .start_failure = test_failed, + .feed_pipe = test_stdin_pipe_feed, .task_finished = test_finished, .data = &suite, }; @@ -460,12 +501,19 @@ int cmd__run_command(int argc, const char **argv) if (!strcmp(argv[1], "run-command-parallel")) { opts.get_next_task = parallel_next; + opts.task_finished = task_finished_quiet; } else if (!strcmp(argv[1], "run-command-abort")) { opts.get_next_task = parallel_next; opts.task_finished = task_finished; } else if (!strcmp(argv[1], "run-command-no-jobs")) { opts.get_next_task = no_job; opts.task_finished = task_finished; + } else if (!strcmp(argv[1], "run-command-stdin")) { + proc.in = -1; + proc.no_stdin = 0; + opts.get_next_task = parallel_next; + opts.task_finished = task_finished_quiet; + opts.feed_pipe = test_stdin_pipe_feed; } else { ret = 1; fprintf(stderr, "check usage\n");
diff --git a/t/helper/test-simple-ipc.c b/t/helper/test-simple-ipc.c index 03cc5ee..442ad6b 100644 --- a/t/helper/test-simple-ipc.c +++ b/t/helper/test-simple-ipc.c
@@ -603,7 +603,12 @@ int cmd__simple_ipc(int argc, const char **argv) OPT_INTEGER(0, "bytecount", &cl_args.bytecount, N_("number of bytes")), OPT_INTEGER(0, "batchsize", &cl_args.batchsize, N_("number of requests per thread")), - OPT_STRING(0, "byte", &bytevalue, N_("byte"), N_("ballast character")), + /* + * The "byte" string here is not marked for translation and + * instead relies on translation in strbuf.c:humanise_bytes() to + * avoid conflict with the plural form. + */ + OPT_STRING(0, "byte", &bytevalue, "byte", N_("ballast character")), OPT_STRING(0, "token", &cl_args.token, N_("token"), N_("command token to send to the server")), OPT_END()
diff --git a/t/helper/test-trace2.c b/t/helper/test-trace2.c index 415df07..3b12f41 100644 --- a/t/helper/test-trace2.c +++ b/t/helper/test-trace2.c
@@ -467,6 +467,63 @@ static int ut_303redact_def_param(int argc, const char **argv) } /* + * Run a child process with specific trace2 environment settings so that + * we can capture its trace2 output (including cmd_ancestry) in isolation. + * + * test-tool trace2 400ancestry <target> <output_file> [<child_command_line>] + * + * <target> is one of: normal, perf, event + * + * For example: + * test-tool trace2 400ancestry normal out.normal test-tool trace2 001return 0 + * + * The child process inherits a controlled trace2 environment where only + * the specified target is directed to <output_file>. The parent's trace2 + * environment variables are cleared in the child so that only the child's + * events are captured. + * + * This is used by t0213-trace2-ancestry.sh to test cmd_ancestry events. + * The child process will see "test-tool" as its immediate parent in the + * process ancestry, giving us a predictable value to verify. + */ +static int ut_400ancestry(int argc, const char **argv) +{ + struct child_process cmd = CHILD_PROCESS_INIT; + const char *target; + const char *outfile; + int result; + + if (argc < 3) + die("expect <target> <output_file> <child_command_line>"); + + target = argv[0]; + outfile = argv[1]; + argv += 2; + argc -= 2; + + /* Clear all trace2 environment variables in the child. */ + strvec_push(&cmd.env, "GIT_TRACE2="); + strvec_push(&cmd.env, "GIT_TRACE2_PERF="); + strvec_push(&cmd.env, "GIT_TRACE2_EVENT="); + strvec_push(&cmd.env, "GIT_TRACE2_BRIEF=1"); + + /* Set only the requested target. */ + if (!strcmp(target, "normal")) + strvec_pushf(&cmd.env, "GIT_TRACE2=%s", outfile); + else if (!strcmp(target, "perf")) + strvec_pushf(&cmd.env, "GIT_TRACE2_PERF=%s", outfile); + else if (!strcmp(target, "event")) + strvec_pushf(&cmd.env, "GIT_TRACE2_EVENT=%s", outfile); + else + die("invalid target '%s', expected: normal, perf, event", + target); + + strvec_pushv(&cmd.args, argv); + result = run_command(&cmd); + exit(result); +} + +/* * Usage: * test-tool trace2 <ut_name_1> <ut_usage_1> * test-tool trace2 <ut_name_2> <ut_usage_2> @@ -497,6 +554,8 @@ static struct unit_test ut_table[] = { { ut_301redact_child_start, "301redact_child_start", "<argv...>" }, { ut_302redact_exec, "302redact_exec", "<exe> <argv...>" }, { ut_303redact_def_param, "303redact_def_param", "<key> <value>" }, + + { ut_400ancestry, "400ancestry", "<target> <output_file> [<child_command_line>]" }, }; /* clang-format on */
diff --git a/t/lib-cvs.sh b/t/lib-cvs.sh index 57b9b2d..c8b4404 100644 --- a/t/lib-cvs.sh +++ b/t/lib-cvs.sh
@@ -2,6 +2,12 @@ . ./test-lib.sh +if test -n "$NO_CVS_TESTS" +then + skip_all='skipping git cvs tests, NO_CVS_TESTS defined' + test_done +fi + unset CVS_SERVER if ! type cvs >/dev/null 2>&1
diff --git a/t/lib-git-p4.sh b/t/lib-git-p4.sh index 2a5b873..d22e9c6 100644 --- a/t/lib-git-p4.sh +++ b/t/lib-git-p4.sh
@@ -16,6 +16,11 @@ . ./test-lib.sh +if test -n "$NO_P4_TESTS" +then + skip_all='skipping git p4 tests, NO_P4_TESTS defined' + test_done +fi if ! test_have_prereq PYTHON then skip_all='skipping git p4 tests; python not available'
diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh index 5091db9..4c76e81 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh
@@ -167,6 +167,7 @@ install_script error.sh install_script apply-one-time-script.sh install_script nph-custom-auth.sh + install_script http-429.sh ln -s "$LIB_HTTPD_MODULE_PATH" "$HTTPD_ROOT_PATH/modules" @@ -319,13 +320,22 @@ ' } -set_askpass() { +set_askpass () { >"$TRASH_DIRECTORY/askpass-query" && echo "$1" >"$TRASH_DIRECTORY/askpass-user" && echo "$2" >"$TRASH_DIRECTORY/askpass-pass" } -expect_askpass() { +set_netrc () { + # $HOME=$TRASH_DIRECTORY + echo "machine $1 login $2 password $3" >"$TRASH_DIRECTORY/.netrc" +} + +clear_netrc () { + rm -f "$TRASH_DIRECTORY/.netrc" +} + +expect_askpass () { dest=$HTTPD_DEST${3+/$3} {
diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index e631ab0..40a690b 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf
@@ -139,6 +139,10 @@ SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH} SetEnv GIT_HTTP_EXPORT_ALL </LocationMatch> +<LocationMatch /http_429/> + SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH} + SetEnv GIT_HTTP_EXPORT_ALL +</LocationMatch> <LocationMatch /smart_v0/> SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH} SetEnv GIT_HTTP_EXPORT_ALL @@ -160,6 +164,7 @@ ScriptAlias /error_smart/ error-smart-http.sh/ ScriptAlias /error/ error.sh/ ScriptAliasMatch /one_time_script/(.*) apply-one-time-script.sh/$1 +ScriptAliasMatch /http_429/(.*) http-429.sh/$1 ScriptAliasMatch /custom_auth/(.*) nph-custom-auth.sh/$1 <Directory ${GIT_EXEC_PATH}> Options FollowSymlinks @@ -185,6 +190,9 @@ <Files apply-one-time-script.sh> Options ExecCGI </Files> +<Files http-429.sh> + Options ExecCGI +</Files> <Files ${GIT_EXEC_PATH}/git-http-backend> Options ExecCGI </Files> @@ -238,6 +246,10 @@ AuthName "git-auth" AuthUserFile passwd Require valid-user + + # return 403 for authenticated user: forbidden-user@host + RewriteCond "%{REMOTE_USER}" "^forbidden-user@host" + RewriteRule ^ - [F] </Location> <LocationMatch "^/auth-push/.*/git-receive-pack$">
diff --git a/t/lib-httpd/http-429.sh b/t/lib-httpd/http-429.sh new file mode 100644 index 0000000..c97b161 --- /dev/null +++ b/t/lib-httpd/http-429.sh
@@ -0,0 +1,98 @@ +#!/bin/sh + +# Script to return HTTP 429 Too Many Requests responses for testing retry logic. +# Usage: /http_429/<test-context>/<retry-after-value>/<repo-path> +# +# The test-context is a unique identifier for each test to isolate state files. +# The retry-after-value can be: +# - A number (e.g., "1", "2", "100") - sets Retry-After header to that many seconds +# - "none" - no Retry-After header +# - "invalid" - invalid Retry-After format +# - "permanent" - always return 429 (never succeed) +# - An HTTP-date string (RFC 2822 format) - sets Retry-After to that date +# +# On first call, returns 429. On subsequent calls (after retry), forwards to git-http-backend +# unless retry-after-value is "permanent". + +# Extract test context, retry-after value and repo path from PATH_INFO +# PATH_INFO format: /<test-context>/<retry-after-value>/<repo-path> +path_info="${PATH_INFO#/}" # Remove leading slash +test_context="${path_info%%/*}" # Get first component (test context) +remaining="${path_info#*/}" # Get rest +retry_after="${remaining%%/*}" # Get second component (retry-after value) +repo_path="${remaining#*/}" # Get rest (repo path) + +# Extract repository name from repo_path (e.g., "repo.git" from "repo.git/info/refs") +# The repo name is the first component before any "/" +repo_name="${repo_path%%/*}" + +# Use current directory (HTTPD_ROOT_PATH) for state file +# Create a safe filename from test_context, retry_after and repo_name +# This ensures all requests for the same test context share the same state file +safe_name=$(echo "${test_context}-${retry_after}-${repo_name}" | tr '/' '_' | tr -cd 'a-zA-Z0-9_-') +state_file="http-429-state-${safe_name}" + +# Check if this is the first call (no state file exists) +if test -f "$state_file" +then + # Already returned 429 once, forward to git-http-backend + # Set PATH_INFO to just the repo path (without retry-after value) + # Set GIT_PROJECT_ROOT so git-http-backend can find the repository + # Use exec to replace this process so git-http-backend gets the updated environment + PATH_INFO="/$repo_path" + export PATH_INFO + # GIT_PROJECT_ROOT points to the document root where repositories are stored + # The script runs from HTTPD_ROOT_PATH, and www/ is the document root + if test -z "$GIT_PROJECT_ROOT" + then + # Construct path: current directory (HTTPD_ROOT_PATH) + /www + GIT_PROJECT_ROOT="$(pwd)/www" + export GIT_PROJECT_ROOT + fi + exec "$GIT_EXEC_PATH/git-http-backend" +fi + +# Mark that we've returned 429 +touch "$state_file" + +# Output HTTP 429 response +printf "Status: 429 Too Many Requests\r\n" + +# Set Retry-After header based on retry_after value +case "$retry_after" in + none) + # No Retry-After header + ;; + invalid) + printf "Retry-After: invalid-format-123abc\r\n" + ;; + permanent) + # Always return 429, don't set state file for success + rm -f "$state_file" + printf "Retry-After: 1\r\n" + printf "Content-Type: text/plain\r\n" + printf "\r\n" + printf "Permanently rate limited\n" + exit 0 + ;; + *) + # Check if it's a number + case "$retry_after" in + [0-9]*) + # Numeric value + printf "Retry-After: %s\r\n" "$retry_after" + ;; + *) + # Assume it's an HTTP-date format (passed as-is, URL decoded) + # Apache may URL-encode the path, so decode common URL-encoded characters + # %20 = space, %2C = comma, %3A = colon + retry_value=$(echo "$retry_after" | sed -e 's/%20/ /g' -e 's/%2C/,/g' -e 's/%3A/:/g') + printf "Retry-After: %s\r\n" "$retry_value" + ;; + esac + ;; +esac + +printf "Content-Type: text/plain\r\n" +printf "\r\n" +printf "Rate limited\n"
diff --git a/t/lib-httpd/passwd b/t/lib-httpd/passwd index d9c122f..3bab7b6 100644 --- a/t/lib-httpd/passwd +++ b/t/lib-httpd/passwd
@@ -1 +1,2 @@ user@host:$apr1$LGPmCZWj$9vxEwj5Z5GzQLBMxp3mCx1 +forbidden-user@host:$apr1$LGPmCZWj$9vxEwj5Z5GzQLBMxp3mCx1
diff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh index 36f767c..f591de6 100644 --- a/t/lib-submodule-update.sh +++ b/t/lib-submodule-update.sh
@@ -95,14 +95,14 @@ git commit -m "modified file2 and added file3" && git push origin modifications ) && - git add sub1 && + git add --force sub1 && git commit -m "Modify sub1" && git checkout -b add_nested_sub modify_sub1 && git -C sub1 checkout -b "add_nested_sub" && git -C sub1 submodule add --branch no_submodule ../submodule_update_sub2 sub2 && git -C sub1 commit -a -m "add a nested submodule" && - git add sub1 && + git add --force sub1 && git commit -a -m "update submodule, that updates a nested submodule" && git checkout -b modify_sub1_recursively && git -C sub1 checkout -b modify_sub1_recursively && @@ -112,7 +112,7 @@ git -C sub1/sub2 commit -m "make a change in nested sub" && git -C sub1 add sub2 && git -C sub1 commit -m "update nested sub" && - git add sub1 && + git add --force sub1 && git commit -m "update sub1, that updates nested sub" && git -C sub1 push origin modify_sub1_recursively && git -C sub1/sub2 push origin modify_sub1_recursively &&
diff --git a/t/lib-unicode-nfc-nfd.sh b/t/lib-unicode-nfc-nfd.sh index aed0a4d..201ab9b 100755 --- a/t/lib-unicode-nfc-nfd.sh +++ b/t/lib-unicode-nfc-nfd.sh
@@ -75,7 +75,7 @@ # # Note that I've used the canonical ordering of the # combining characters. It is also possible to -# swap them. My testing shows that that non-standard +# swap them. My testing shows that non-standard # ordering also causes a collision in mkdir. However, # the resulting names don't draw correctly on the # terminal (implying that the on-disk format also has
diff --git a/t/lib-verify-submodule-gitdir-path.sh b/t/lib-verify-submodule-gitdir-path.sh new file mode 100644 index 0000000..4e0cfdc --- /dev/null +++ b/t/lib-verify-submodule-gitdir-path.sh
@@ -0,0 +1,24 @@ +# Helper to verify if repo $1 contains a submodule named $2 with gitdir path $3 + +# This does not check filesystem existence. That is done in submodule.c via the +# submodule_name_to_gitdir() API which this helper ends up calling. The gitdirs +# might or might not exist (e.g. when adding a new submodule), so this only +# checks the expected configuration path, which might be overridden by the user. + +verify_submodule_gitdir_path () { + repo="$1" && + name="$2" && + path="$3" && + ( + cd "$repo" && + # Compute expected absolute path + expected="$(git rev-parse --git-common-dir)/$path" && + expected="$(test-tool path-utils real_path "$expected")" && + # Compute actual absolute path + actual="$(git submodule--helper gitdir "$name")" && + actual="$(test-tool path-utils real_path "$actual")" && + echo "$expected" >expect && + echo "$actual" >actual && + test_cmp expect actual + ) +}
diff --git a/t/meson.build b/t/meson.build index a5531df..7528e5c 100644 --- a/t/meson.build +++ b/t/meson.build
@@ -4,6 +4,7 @@ 'unit-tests/u-example-decorate.c', 'unit-tests/u-hash.c', 'unit-tests/u-hashmap.c', + 'unit-tests/u-list-objects-filter-options.c', 'unit-tests/u-mem-pool.c', 'unit-tests/u-oid-array.c', 'unit-tests/u-oidmap.c', @@ -24,6 +25,7 @@ 'unit-tests/u-strvec.c', 'unit-tests/u-trailer.c', 'unit-tests/u-urlmatch-normalization.c', + 'unit-tests/u-utf8-width.c', ] clar_sources = [ @@ -79,6 +81,7 @@ 't0006-date.sh', 't0007-git-var.sh', 't0008-ignores.sh', + 't0009-git-dir-validation.sh', 't0010-racy-git.sh', 't0012-help.sh', 't0013-sha1dc.sh', @@ -97,6 +100,7 @@ 't0028-working-tree-encoding.sh', 't0029-core-unsetenvvars.sh', 't0030-stripspace.sh', + 't0031-lockfile-pid.sh', 't0033-safe-directory.sh', 't0034-root-safe-directory.sh', 't0035-safe-bare-repository.sh', @@ -131,6 +135,7 @@ 't0210-trace2-normal.sh', 't0211-trace2-perf.sh', 't0212-trace2-event.sh', + 't0213-trace2-ancestry.sh', 't0300-credentials.sh', 't0301-credential-cache.sh', 't0302-credential-store.sh', @@ -184,6 +189,7 @@ 't1308-config-set.sh', 't1309-early-config.sh', 't1310-config-default.sh', + 't1311-config-optional.sh', 't1350-config-hooks-path.sh', 't1400-update-ref.sh', 't1401-symbolic-ref.sh', @@ -208,6 +214,7 @@ 't1420-lost-found.sh', 't1421-reflog-write.sh', 't1422-show-ref-exists.sh', + 't1423-ref-backend.sh', 't1430-bad-ref-name.sh', 't1450-fsck.sh', 't1451-fsck-buffer.sh', @@ -237,7 +244,7 @@ 't1700-split-index.sh', 't1701-racy-split-index.sh', 't1800-hook.sh', - 't1900-repo.sh', + 't1900-repo-info.sh', 't1901-repo-structure.sh', 't2000-conflict-when-checking-files-out.sh', 't2002-checkout-cache-u.sh', @@ -290,6 +297,7 @@ 't2203-add-intent.sh', 't2204-add-ignored.sh', 't2205-add-worktree-config.sh', + 't2206-add-submodule-ignored.sh', 't2300-cd-to-toplevel.sh', 't2400-worktree-add.sh', 't2401-worktree-prune.sh', @@ -385,6 +393,10 @@ 't3436-rebase-more-options.sh', 't3437-rebase-fixup-options.sh', 't3438-rebase-broken-files.sh', + 't3440-rebase-trailer.sh', + 't3450-history.sh', + 't3451-history-reword.sh', + 't3452-history-split.sh', 't3500-cherry.sh', 't3501-revert-cherry-pick.sh', 't3502-cherry-pick-merge.sh', @@ -496,6 +508,8 @@ 't4070-diff-pairs.sh', 't4071-diff-minimal.sh', 't4072-diff-max-depth.sh', + 't4073-diff-stat-name-width.sh', + 't4074-diff-shifted-matched-group.sh', 't4100-apply-stat.sh', 't4101-apply-nonl.sh', 't4102-apply-rename.sh', @@ -612,6 +626,7 @@ 't5332-multi-pack-reuse.sh', 't5333-pseudo-merge-bitmaps.sh', 't5334-incremental-multi-pack-index.sh', + 't5335-compact-multi-pack-index.sh', 't5351-unpack-large-objects.sh', 't5400-send-pack.sh', 't5401-update-hooks.sh', @@ -688,6 +703,7 @@ 't5562-http-backend-content-length.sh', 't5563-simple-http-auth.sh', 't5564-http-proxy.sh', + 't5565-push-multiple.sh', 't5570-git-daemon.sh', 't5571-pre-push-hook.sh', 't5572-pull-submodule.sh', @@ -697,6 +713,7 @@ 't5581-http-curl-verbose.sh', 't5582-fetch-negative-refspec.sh', 't5583-push-branches.sh', + 't5584-http-429-retry.sh', 't5600-clone-fail-cleanup.sh', 't5601-clone.sh', 't5602-clone-remote-exec.sh', @@ -884,6 +901,8 @@ 't7422-submodule-output.sh', 't7423-submodule-symlinks.sh', 't7424-submodule-mixed-ref-formats.sh', + 't7425-submodule-gitdir-path-extension.sh', + 't7426-submodule-get-default-remote.sh', 't7450-bad-git-dotfiles.sh', 't7500-commit-template-squash-signoff.sh', 't7501-commit-basic-functionality.sh', @@ -956,6 +975,7 @@ 't8012-blame-colors.sh', 't8013-blame-ignore-revs.sh', 't8014-blame-ignore-fuzzy.sh', + 't8015-blame-diff-algorithm.sh', 't8020-last-modified.sh', 't9001-send-email.sh', 't9002-column.sh', @@ -1202,6 +1222,7 @@ test_environment = script_environment test_environment.set('GIT_BUILD_DIR', git_build_dir) +test_environment.set('MERGE_TOOLS_DIR', meson.project_source_root() / 'mergetools') foreach integration_test : integration_tests test(fs.stem(integration_test), shell,
diff --git a/t/pack-refs-tests.sh b/t/pack-refs-tests.sh index 3dbcc01..d76b087 100644 --- a/t/pack-refs-tests.sh +++ b/t/pack-refs-tests.sh
@@ -61,13 +61,13 @@ test_expect_success 'see if git ${pack_refs} --prune remove ref files' ' git branch f && git ${pack_refs} --all --prune && - ! test -f .git/refs/heads/f + test_path_is_missing .git/refs/heads/f ' test_expect_success 'see if git ${pack_refs} --prune removes empty dirs' ' git branch r/s/t && git ${pack_refs} --all --prune && - ! test -e .git/refs/heads/r + test_path_is_missing .git/refs/heads/r ' test_expect_success 'git branch g should work when git branch g/h has been deleted' ' @@ -111,43 +111,43 @@ git branch dont_pack2 && git branch pack_this && git ${pack_refs} --all --exclude "refs/heads/dont_pack*" && - test -f .git/refs/heads/dont_pack1 && - test -f .git/refs/heads/dont_pack2 && - ! test -f .git/refs/heads/pack_this' + test_path_is_file .git/refs/heads/dont_pack1 && + test_path_is_file .git/refs/heads/dont_pack2 && + test_path_is_missing .git/refs/heads/pack_this' test_expect_success 'test --no-exclude refs clears excluded refs' ' git branch dont_pack3 && git branch dont_pack4 && git ${pack_refs} --all --exclude "refs/heads/dont_pack*" --no-exclude && - ! test -f .git/refs/heads/dont_pack3 && - ! test -f .git/refs/heads/dont_pack4' + test_path_is_missing .git/refs/heads/dont_pack3 && + test_path_is_missing .git/refs/heads/dont_pack4' test_expect_success 'test only included refs are packed' ' git branch pack_this1 && git branch pack_this2 && git tag dont_pack5 && git ${pack_refs} --include "refs/heads/pack_this*" && - test -f .git/refs/tags/dont_pack5 && - ! test -f .git/refs/heads/pack_this1 && - ! test -f .git/refs/heads/pack_this2' + test_path_is_file .git/refs/tags/dont_pack5 && + test_path_is_missing .git/refs/heads/pack_this1 && + test_path_is_missing .git/refs/heads/pack_this2' test_expect_success 'test --no-include refs clears included refs' ' git branch pack1 && git branch pack2 && git ${pack_refs} --include "refs/heads/pack*" --no-include && - test -f .git/refs/heads/pack1 && - test -f .git/refs/heads/pack2' + test_path_is_file .git/refs/heads/pack1 && + test_path_is_file .git/refs/heads/pack2' test_expect_success 'test --exclude takes precedence over --include' ' git branch dont_pack5 && git ${pack_refs} --include "refs/heads/pack*" --exclude "refs/heads/pack*" && - test -f .git/refs/heads/dont_pack5' + test_path_is_file .git/refs/heads/dont_pack5' test_expect_success 'see if up-to-date packed refs are preserved' ' git branch q && git ${pack_refs} --all --prune && git update-ref refs/heads/q refs/heads/q && - ! test -f .git/refs/heads/q + test_path_is_missing .git/refs/heads/q ' test_expect_success 'pack, prune and repack' ' @@ -354,8 +354,8 @@ # Create 14 additional references, which brings us to # 15 together with the default branch. - printf "create refs/heads/loose-%d HEAD\n" $(test_seq 14) >stdin && - git update-ref --stdin <stdin && + test_seq -f "create refs/heads/loose-%d HEAD" 14 | + git update-ref --stdin && test_path_is_missing .git/packed-refs && git ${pack_refs} --auto --all && test_path_is_missing .git/packed-refs && @@ -379,8 +379,8 @@ test_line_count = 2 .git/packed-refs && # Create 15 loose references. - printf "create refs/heads/loose-%d HEAD\n" $(test_seq 15) >stdin && - git update-ref --stdin <stdin && + test_seq -f "create refs/heads/loose-%d HEAD" 15 | + git update-ref --stdin && git ${pack_refs} --auto --all && test_line_count = 2 .git/packed-refs && @@ -401,18 +401,14 @@ # Create 99 packed refs. This should cause the heuristic # to require more than the minimum amount of loose refs. - test_seq 99 | - while read i - do - printf "create refs/heads/packed-%d HEAD\n" $i || return 1 - done >stdin && - git update-ref --stdin <stdin && + test_seq -f "create refs/heads/packed-%d HEAD" 99 | + git update-ref --stdin && git ${pack_refs} --all && test_line_count = 101 .git/packed-refs && # Create 24 loose refs, which should not yet cause us to repack. - printf "create refs/heads/loose-%d HEAD\n" $(test_seq 24) >stdin && - git update-ref --stdin <stdin && + test_seq -f "create refs/heads/loose-%d HEAD" 24 | + git update-ref --stdin && git ${pack_refs} --auto --all && test_line_count = 101 .git/packed-refs && @@ -420,12 +416,42 @@ # Note that we explicitly do not check for strict # boundaries here, as this also depends on the size of # the object hash. - printf "create refs/heads/addn-%d HEAD\n" $(test_seq 10) >stdin && - git update-ref --stdin <stdin && + test_seq -f "create refs/heads/addn-%d HEAD" 10 | + git update-ref --stdin && git ${pack_refs} --auto --all && test_line_count = 135 .git/packed-refs ) ' done -test_done +test_expect_success 'pack-refs does not store invalid peeled tag value' ' + test_when_finished rm -rf repo && + git init repo && + ( + cd repo && + git commit --allow-empty --message initial && + + echo garbage >blob-content && + blob_id=$(git hash-object -w -t blob blob-content) && + + # Write an invalid tag into the object database. The tag itself + # is well-formed, but the tagged object is a blob while we + # claim that it is a commit. + cat >tag-content <<-EOF && + object $blob_id + type commit + tag bad-tag + tagger C O Mitter <committer@example.com> 1112354055 +0200 + + annotated + EOF + tag_id=$(git hash-object -w -t tag tag-content) && + git update-ref refs/tags/bad-tag "$tag_id" && + + # The packed-refs file should not contain the peeled object ID. + # If it did this would cause commands that use the peeled value + # to not notice this corrupted tag. + git pack-refs --all && + test_grep ! "^\^" .git/packed-refs + ) +'
diff --git a/t/perf/p1006-cat-file.sh b/t/perf/p1006-cat-file.sh index dcd8015..da34ece 100755 --- a/t/perf/p1006-cat-file.sh +++ b/t/perf/p1006-cat-file.sh
@@ -9,4 +9,18 @@ git cat-file --batch-all-objects --batch-check ' +test_perf 'list all objects (sorted)' ' + git cat-file --batch-all-objects --batch-check="%(objectname)" +' + +test_perf 'list all objects (unsorted)' ' + git cat-file --batch-all-objects --batch-check="%(objectname)" \ + --unordered +' + +test_perf 'list blobs' ' + git cat-file --batch-all-objects --batch-check="%(objectname)" \ + --unordered --filter=object:type=blob +' + test_done
diff --git a/t/perf/p3400-rebase.sh b/t/perf/p3400-rebase.sh index e6b0277..56dc3e1 100755 --- a/t/perf/p3400-rebase.sh +++ b/t/perf/p3400-rebase.sh
@@ -9,21 +9,44 @@ git checkout -f -B base && git checkout -B to-rebase && git checkout -B upstream && - for i in $(test_seq 100) - do - # simulate huge diffs - echo change$i >unrelated-file$i && - test_seq 1000 >>unrelated-file$i && - git add unrelated-file$i && - test_tick && - git commit -m commit$i unrelated-file$i && - echo change$i >unrelated-file$i && - test_seq 1000 | sort -nr >>unrelated-file$i && - git add unrelated-file$i && - test_tick && - git commit -m commit$i-reverse unrelated-file$i || - return 1 - done && + test_seq 1000 >content_fwd && + sort -nr content_fwd >content_rev && + ( + for i in $(test_seq 100) + do + test_tick && + echo "commit refs/heads/upstream" && + echo "committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE" && + echo "data <<EOF" && + echo "commit$i" && + echo "EOF" && + + if test "$i" = 1; then + echo "from refs/heads/upstream^0" + fi && + + echo "M 100644 inline unrelated-file$i" && + echo "data <<EOF" && + echo "change$i" && + cat content_fwd && + echo "EOF" && + + echo "commit refs/heads/upstream" && + echo "committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE" && + echo "data <<EOF" && + echo "commit$i-reverse" && + echo "EOF" && + echo "M 100644 inline unrelated-file$i" && + echo "data <<EOF" && + echo "change$i" && + cat content_rev && + echo "EOF" || exit 1 + done + ) >fast_import_stream && + + git fast-import <fast_import_stream && + git repack -a -d && + git checkout -f upstream && git checkout to-rebase && test_commit our-patch interesting-file '
diff --git a/t/perf/p6010-merge-base.sh b/t/perf/p6010-merge-base.sh index 54f52fa..08212dd 100755 --- a/t/perf/p6010-merge-base.sh +++ b/t/perf/p6010-merge-base.sh
@@ -83,9 +83,9 @@ test_expect_success 'setup' ' max_level=15 && build_history $max_level | git fast-import --export-marks=marks && - git tag one && + git branch one && build_history2 $max_level | git fast-import --import-marks=marks --force && - git tag two && + git branch two && git gc && git log --format=%H --no-merges >expect ' @@ -98,4 +98,8 @@ test_cmp expect actual ' +test_perf 'git show-branch' ' + git show-branch one two +' + test_done
diff --git a/t/perf/perf-lib.sh b/t/perf/perf-lib.sh index b15c74d..2ac0078 100644 --- a/t/perf/perf-lib.sh +++ b/t/perf/perf-lib.sh
@@ -20,7 +20,7 @@ # These variables must be set before the inclusion of test-lib.sh below, # because it will change our working directory. TEST_DIRECTORY=$(pwd)/.. -TEST_OUTPUT_DIRECTORY=$(pwd) +perf_dir=$(pwd) TEST_NO_CREATE_REPO=t TEST_NO_MALLOC_CHECK=t @@ -58,6 +58,7 @@ fi . "$GIT_BUILD_DIR"/GIT-BUILD-OPTIONS +: ${TEST_OUTPUT_DIRECTORY:=$perf_dir} . "$GIT_SOURCE_DIR"/t/test-lib.sh # Then restore GIT_PERF_* settings.
diff --git a/t/perf/run b/t/perf/run index 073bcb2..13913db 100755 --- a/t/perf/run +++ b/t/perf/run
@@ -204,8 +204,18 @@ get_var_from_env_or_config "GIT_PERF_CODESPEED_OUTPUT" "perf" "codespeedOutput" "--bool" get_var_from_env_or_config "GIT_PERF_SEND_TO_CODESPEED" "perf" "sendToCodespeed" +# Preserve GIT_PERF settings from the environment when loading +# GIT-BUILD-OPTIONS; see the similar hack in perf-lib.sh. +git_perf_settings="$(env | + sed -n "/^GIT_PERF_/{ + # escape all single-quotes in the value + s/'/'\\\\''/g + # turn this into an eval-able assignment + s/^\\([^=]*=\\)\\(.*\\)/\\1'\\2'/p + }")" cd "$(dirname $0)" . ../../GIT-BUILD-OPTIONS +eval "$git_perf_settings" if test -n "$TEST_OUTPUT_DIRECTORY" then
diff --git a/t/t0001-init.sh b/t/t0001-init.sh index 618da08..e4d32bb 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh
@@ -425,7 +425,11 @@ git init --separate-git-dir ../realgitdir ) && echo "gitdir: $(pwd)/realgitdir" >expected && - test_cmp expected newdir/.git && + case "$GIT_TEST_CMP" in + # `git diff --no-index` does not resolve symlinks + *--no-index*) cmp expected newdir/.git;; + *) test_cmp expected newdir/.git;; + esac && test_cmp expected newdir/here && test_path_is_dir realgitdir/refs '
diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh index 3c98b62..582e207 100755 --- a/t/t0003-attributes.sh +++ b/t/t0003-attributes.sh
@@ -664,4 +664,24 @@ test_cmp expect err ' +test_expect_success ULIMIT_STACK_SIZE 'deep macro recursion' ' + n=3000 && + { + i=0 && + while test $i -lt $n; do + echo "[attr]a$i a$((i+1))" && + i=$((i+1)) || + return 1 + done && + echo "[attr]a$n -text" && + echo "file a0" + } >.gitattributes && + { + echo "file: text: unset" && + test_seq -f "file: a%d: set" 0 $n + } >expect && + run_with_limited_stack git check-attr -a file >actual && + test_cmp expect actual +' + test_done
diff --git a/t/t0008-ignores.sh b/t/t0008-ignores.sh index db8bde2..e716b5c 100755 --- a/t/t0008-ignores.sh +++ b/t/t0008-ignores.sh
@@ -946,7 +946,7 @@ ' test_expect_success SYMLINKS 'symlinks not respected in-tree' ' - test_when_finished "rm .gitignore" && + test_when_finished "rm -rf subdir .gitignore err actual" && ln -s ignore .gitignore && mkdir subdir && ln -s ignore subdir/.gitignore && @@ -957,6 +957,7 @@ test_expect_success EXPENSIVE 'large exclude file ignored in tree' ' test_when_finished "rm .gitignore" && + find . -name .gitignore -exec rm "{}" ";" && dd if=/dev/zero of=.gitignore bs=101M count=1 && git ls-files -o --exclude-standard 2>err && echo "warning: ignoring excessively large pattern file: .gitignore" >expect &&
diff --git a/t/t0009-git-dir-validation.sh b/t/t0009-git-dir-validation.sh new file mode 100755 index 0000000..33d21ed --- /dev/null +++ b/t/t0009-git-dir-validation.sh
@@ -0,0 +1,77 @@ +#!/bin/sh + +test_description='setup: validation of .git file/directory types + +Verify that setup_git_directory() correctly handles: +1. Valid .git directories (including symlinks to them). +2. Invalid .git files (FIFOs, sockets) by erroring out. +3. Invalid .git files (garbage) by erroring out. +' + +. ./test-lib.sh + +test_expect_success 'setup: create parent git repository' ' + git init parent && + test_commit -C parent "root-commit" +' + +test_expect_success SYMLINKS 'setup: .git as a symlink to a directory is valid' ' + test_when_finished "rm -rf parent/link-to-dir" && + mkdir -p parent/link-to-dir && + ( + cd parent/link-to-dir && + git init real-repo && + ln -s real-repo/.git .git && + git rev-parse --git-dir >actual && + echo .git >expect && + test_cmp expect actual + ) +' + +test_expect_success PIPE 'setup: .git as a FIFO (named pipe) is rejected' ' + test_when_finished "rm -rf parent/fifo-trap" && + mkdir -p parent/fifo-trap && + ( + cd parent/fifo-trap && + mkfifo .git && + test_must_fail git rev-parse --git-dir 2>stderr && + grep "not a regular file" stderr + ) +' + +test_expect_success SYMLINKS,PIPE 'setup: .git as a symlink to a FIFO is rejected' ' + test_when_finished "rm -rf parent/symlink-fifo-trap" && + mkdir -p parent/symlink-fifo-trap && + ( + cd parent/symlink-fifo-trap && + mkfifo target-fifo && + ln -s target-fifo .git && + test_must_fail git rev-parse --git-dir 2>stderr && + grep "not a regular file" stderr + ) +' + +test_expect_success 'setup: .git with garbage content is rejected' ' + test_when_finished "rm -rf parent/garbage-trap" && + mkdir -p parent/garbage-trap && + ( + cd parent/garbage-trap && + echo "garbage" >.git && + test_must_fail git rev-parse --git-dir 2>stderr && + grep "invalid gitfile format" stderr + ) +' + +test_expect_success 'setup: .git as an empty directory is ignored' ' + test_when_finished "rm -rf parent/empty-dir" && + mkdir -p parent/empty-dir && + ( + cd parent/empty-dir && + git rev-parse --git-dir >expect && + mkdir .git && + git rev-parse --git-dir >actual && + test_cmp expect actual + ) +' + +test_done
diff --git a/t/t0012-help.sh b/t/t0012-help.sh index d3a0967..c33501b 100755 --- a/t/t0012-help.sh +++ b/t/t0012-help.sh
@@ -141,20 +141,23 @@ '\''git help config'\'' for more information EOF - grep -v -E \ - -e "^[^.]+\.[^.]+$" \ - -e "^[^.]+\.[^.]+\.[^.]+$" \ - help.output >actual && + sed -E -e " + /^[^.]+\.[^.]+$/d + /^[^.]+\.[^.]+\.[^.]+$/d + " help.output >actual && test_cmp expect actual ' test_expect_success 'git help --config-for-completion' ' git help -c >human && - grep -E \ - -e "^[^.]+\.[^.]+$" \ - -e "^[^.]+\.[^.]+\.[^.]+$" human | - sed -e "s/\*.*//" -e "s/<.*//" | - sort -u >human.munged && + sed -E -e " + /^[^.]+\.[^.]+$/b out + /^[^.]+\.[^.]+\.[^.]+$/b out + d + : out + s/\*.*// + s/<.*// + " human | sort -u >human.munged && git help --config-for-completion >vars && test_cmp human.munged vars @@ -162,14 +165,16 @@ test_expect_success 'git help --config-sections-for-completion' ' git help -c >human && - grep -E \ - -e "^[^.]+\.[^.]+$" \ - -e "^[^.]+\.[^.]+\.[^.]+$" human | - sed -e "s/\..*//" | - sort -u >human.munged && + sed -E -e " + /^[^.]+\.[^.]+$/b out + /^[^.]+\.[^.]+\.[^.]+$/b out + d + : out + s/\..*// + " human | sort -u >expect && - git help --config-sections-for-completion >sections && - test_cmp human.munged sections + git help --config-sections-for-completion >actual && + test_cmp expect actual ' test_section_spacing () {
diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh index 07a53e7..68b4903 100755 --- a/t/t0014-alias.sh +++ b/t/t0014-alias.sh
@@ -112,4 +112,89 @@ done ' +test_expect_success 'alias without value reports error' ' + test_when_finished "git config --unset alias.noval" && + cat >>.git/config <<-\EOF && + [alias] + noval + EOF + test_must_fail git noval 2>error && + test_grep "alias.noval" error +' + +test_expect_success 'subsection syntax works' ' + test_config alias.testnew.command "!echo ran-subsection" && + git testnew >output && + test_grep "ran-subsection" output +' + +test_expect_success 'subsection syntax only accepts command key' ' + test_config alias.invalid.notcommand value && + test_must_fail git invalid 2>error && + test_grep -i "not a git command" error +' + +test_expect_success 'subsection syntax requires value for command' ' + test_when_finished "git config --remove-section alias.noval" && + cat >>.git/config <<-\EOF && + [alias "noval"] + command + EOF + test_must_fail git noval 2>error && + test_grep "alias.noval.command" error +' + +test_expect_success 'simple syntax is case-insensitive' ' + test_config alias.LegacyCase "!echo ran-legacy" && + git legacycase >output && + test_grep "ran-legacy" output +' + +test_expect_success 'subsection syntax is case-sensitive' ' + test_config alias.SubCase.command "!echo ran-upper" && + test_config alias.subcase.command "!echo ran-lower" && + git SubCase >upper.out && + git subcase >lower.out && + test_grep "ran-upper" upper.out && + test_grep "ran-lower" lower.out +' + +test_expect_success 'UTF-8 alias with Swedish characters' ' + test_config alias."förgrena".command "!echo ran-swedish" && + git förgrena >output && + test_grep "ran-swedish" output +' + +test_expect_success 'UTF-8 alias with CJK characters' ' + test_config alias."分支".command "!echo ran-cjk" && + git 分支 >output && + test_grep "ran-cjk" output +' + +test_expect_success 'alias with spaces in name' ' + test_config alias."test name".command "!echo ran-spaces" && + git "test name" >output && + test_grep "ran-spaces" output +' + +test_expect_success 'subsection aliases listed in help -a' ' + test_config alias."förgrena".command "!echo test" && + git help -a >output && + test_grep "förgrena" output +' + +test_expect_success 'empty subsection treated as no subsection' ' + test_config "alias..something" "!echo foobar" && + git something >actual && + echo foobar >expect && + test_cmp expect actual +' + +test_expect_success 'alias with leading dot via subsection syntax' ' + test_config alias.".something".command "!echo foobar" && + git .something >actual && + echo foobar >expect && + test_cmp expect actual +' + test_done
diff --git a/t/t0031-lockfile-pid.sh b/t/t0031-lockfile-pid.sh new file mode 100755 index 0000000..8ef87ad --- /dev/null +++ b/t/t0031-lockfile-pid.sh
@@ -0,0 +1,105 @@ +#!/bin/sh + +test_description='lock file PID info tests + +Tests for PID info file alongside lock files. +The feature is opt-in via core.lockfilePid config setting (boolean). +' + +. ./test-lib.sh + +test_expect_success 'stale lock detected when PID is not running' ' + git init repo && + ( + cd repo && + touch .git/index.lock && + printf "pid 99999" >.git/index~pid.lock && + test_must_fail git -c core.lockfilePid=true add . 2>err && + test_grep "process 99999, which is no longer running" err && + test_grep "appears to be stale" err + ) +' + +test_expect_success 'PID info not shown by default' ' + git init repo2 && + ( + cd repo2 && + touch .git/index.lock && + printf "pid 99999" >.git/index~pid.lock && + test_must_fail git add . 2>err && + # Should not crash, just show normal error without PID + test_grep "Unable to create" err && + ! test_grep "is held by process" err + ) +' + +test_expect_success 'running process detected when PID is alive' ' + git init repo3 && + ( + cd repo3 && + echo content >file && + # Get the correct PID for this platform + shell_pid=$$ && + if test_have_prereq MINGW && test -f /proc/$shell_pid/winpid + then + # In Git for Windows, Bash uses MSYS2 PIDs but git.exe + # uses Windows PIDs. Use the Windows PID. + shell_pid=$(cat /proc/$shell_pid/winpid) + fi && + # Create a lock and PID file with current shell PID (which is running) + touch .git/index.lock && + printf "pid %d" "$shell_pid" >.git/index~pid.lock && + # Verify our PID is shown in the error message + test_must_fail git -c core.lockfilePid=true add file 2>err && + test_grep "held by process $shell_pid" err + ) +' + +test_expect_success 'PID info file cleaned up on successful operation when enabled' ' + git init repo4 && + ( + cd repo4 && + echo content >file && + git -c core.lockfilePid=true add file && + # After successful add, no lock or PID files should exist + test_path_is_missing .git/index.lock && + test_path_is_missing .git/index~pid.lock + ) +' + +test_expect_success 'no PID file created by default' ' + git init repo5 && + ( + cd repo5 && + echo content >file && + git add file && + # PID file should not be created when feature is disabled + test_path_is_missing .git/index~pid.lock + ) +' + +test_expect_success 'core.lockfilePid=false does not create PID file' ' + git init repo6 && + ( + cd repo6 && + echo content >file && + git -c core.lockfilePid=false add file && + # PID file should not be created when feature is disabled + test_path_is_missing .git/index~pid.lock + ) +' + +test_expect_success 'existing PID files are read even when feature disabled' ' + git init repo7 && + ( + cd repo7 && + touch .git/index.lock && + printf "pid 99999" >.git/index~pid.lock && + # Even with lockfilePid disabled, existing PID files are read + # to help diagnose stale locks + test_must_fail git add . 2>err && + test_grep "process 99999" err + ) +' + +test_done
diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh index 76d4936..60cfe65 100755 --- a/t/t0061-run-command.sh +++ b/t/t0061-run-command.sh
@@ -164,6 +164,37 @@ test_line_count = 4 err ' +test_expect_success 'run_command listens to stdin' ' + cat >expect <<-\EOF && + preloaded output of a child + listening for stdin: + sample stdin 1 + sample stdin 0 + preloaded output of a child + listening for stdin: + sample stdin 1 + sample stdin 0 + preloaded output of a child + listening for stdin: + sample stdin 1 + sample stdin 0 + preloaded output of a child + listening for stdin: + sample stdin 1 + sample stdin 0 + EOF + + write_script stdin-script <<-\EOF && + echo "listening for stdin:" + while read line + do + echo "$line" + done + EOF + test-tool run-command run-command-stdin 2 ./stdin-script 2>actual && + test_cmp expect actual +' + cat >expect <<-EOF preloaded output of a child asking for a quick stop @@ -256,16 +287,8 @@ rm -f out && echo "echo %* >>out" >"$bat" && - # Ask git to invoke .bat; clone will fail due to fake SSH helper - test_must_fail env GIT_SSH="$bat" git clone myhost:src ssh-clone && - - # Spawning .bat can fail if there are two quoted cmd.exe arguments. - # .bat itself is first (due to spaces in name), so just one more is - # needed to verify. GIT_SSH will invoke .bat multiple times: - # 1) -G myhost - # 2) myhost "git-upload-pack src" - # First invocation will always succeed. Test the second one. - grep "git-upload-pack" out + test-tool run-command run-command "$bat" "arg with spaces" && + test_grep "arg with spaces" out ' test_done
diff --git a/t/t0068-for-each-repo.sh b/t/t0068-for-each-repo.sh index f2f3e50..80b163e 100755 --- a/t/t0068-for-each-repo.sh +++ b/t/t0068-for-each-repo.sh
@@ -2,17 +2,23 @@ test_description='git for-each-repo builtin' +# We need to test running 'git for-each-repo' outside of a repo context. +TEST_NO_CREATE_REPO=1 + . ./test-lib.sh test_expect_success 'run based on configured value' ' - git init one && - git init two && - git init three && - git init ~/four && + git init --initial-branch=one one && + git init --initial-branch=two two && + git -C two worktree add --orphan ../three && + git -C three checkout -b three && + git init --initial-branch=four ~/four && + git -C two commit --allow-empty -m "DID NOT RUN" && - git config run.key "$TRASH_DIRECTORY/one" && - git config --add run.key "$TRASH_DIRECTORY/three" && - git config --add run.key "~/four" && + git config --global run.key "$TRASH_DIRECTORY/one" && + git config --global --add run.key "$TRASH_DIRECTORY/three" && + git config --global --add run.key "~/four" && + git for-each-repo --config=run.key commit --allow-empty -m "ran" && git -C one log -1 --pretty=format:%s >message && grep ran message && @@ -22,6 +28,7 @@ grep ran message && git -C ~/four log -1 --pretty=format:%s >message && grep ran message && + git for-each-repo --config=run.key -- commit --allow-empty -m "ran again" && git -C one log -1 --pretty=format:%s >message && grep again message && @@ -30,7 +37,43 @@ git -C three log -1 --pretty=format:%s >message && grep again message && git -C ~/four log -1 --pretty=format:%s >message && - grep again message + grep again message && + + git -C three for-each-repo --config=run.key -- \ + commit --allow-empty -m "ran from worktree" && + git -C one log -1 --pretty=format:%s >message && + test_grep "ran from worktree" message && + git -C two log -1 --pretty=format:%s >message && + test_grep ! "ran from worktree" message && + git -C three log -1 --pretty=format:%s >message && + test_grep "ran from worktree" message && + git -C ~/four log -1 --pretty=format:%s >message && + test_grep "ran from worktree" message && + + # Test running with config values set by environment + cat >expect <<-EOF && + ran from worktree (HEAD -> refs/heads/one) + ran from worktree (HEAD -> refs/heads/three) + ran from worktree (HEAD -> refs/heads/four) + EOF + + GIT_CONFIG_PARAMETERS="${SQ}log.decorate=full${SQ}" \ + git -C three for-each-repo --config=run.key -- log --format="%s%d" -1 >out && + test_cmp expect out && + + cat >test-config <<-EOF && + [run] + key = $(pwd)/one + key = $(pwd)/three + key = $(pwd)/four + + [log] + decorate = full + EOF + + GIT_CONFIG_GLOBAL="$(pwd)/test-config" \ + git -C three for-each-repo --config=run.key -- log --format="%s%d" -1 >out && + test_cmp expect out ' test_expect_success 'do nothing on empty config' ' @@ -46,7 +89,7 @@ ' test_expect_success 'error on NULL value for config keys' ' - cat >>.git/config <<-\EOF && + cat >>.gitconfig <<-\EOF && [empty] key EOF @@ -59,8 +102,8 @@ ' test_expect_success '--keep-going' ' - git config keep.going non-existing && - git config --add keep.going . && + git config --global keep.going non-existing && + git config --global --add keep.going one && test_must_fail git for-each-repo --config=keep.going \ -- branch >out 2>err && @@ -70,7 +113,7 @@ test_must_fail git for-each-repo --config=keep.going --keep-going \ -- branch >out 2>err && test_grep "cannot change to .*non-existing" err && - git branch >expect && + git -C one branch >expect && test_cmp expect out '
diff --git a/t/t0080-unit-test-output.sh b/t/t0080-unit-test-output.sh index 3db10f0..66838a0 100755 --- a/t/t0080-unit-test-output.sh +++ b/t/t0080-unit-test-output.sh
@@ -6,10 +6,10 @@ test_expect_success 'TAP output from unit tests' - <<\EOT cat >expect <<-EOF && - # BUG: check outside of test at t/helper/test-example-tap.c:75 + # BUG: check outside of test at t/helper/test-example-tap.c:77 ok 1 - passing test ok 2 - passing test and assertion return 1 - # check "1 == 2" failed at t/helper/test-example-tap.c:79 + # check "1 == 2" failed at t/helper/test-example-tap.c:81 # left: 1 # right: 2 not ok 3 - failing test @@ -34,53 +34,65 @@ not ok 15 - failing check after TEST_TODO() ok 16 - failing check after TEST_TODO() returns 0 # check "!strcmp("\thello\\\\", "there\"\n")" failed at t/helper/test-example-tap.c:62 - # left: "\011hello\\\\" - # right: "there\"\012" + # left: "\thello\\\\" + # right: "there\"\n" # check "!strcmp("NULL", NULL)" failed at t/helper/test-example-tap.c:63 # left: "NULL" # right: NULL # check "'a' == '\n'" failed at t/helper/test-example-tap.c:64 # left: 'a' - # right: '\012' + # right: '\n' # check "'\\\\' == '\\''" failed at t/helper/test-example-tap.c:65 # left: '\\\\' # right: '\\'' + # check "'\a' == '\v'" failed at t/helper/test-example-tap.c:66 + # left: '\a' + # right: '\v' + # check "'\x00' == '\x01'" failed at t/helper/test-example-tap.c:67 + # left: '\000' + # right: '\001' not ok 17 - messages from failing string and char comparison - # BUG: test has no checks at t/helper/test-example-tap.c:94 + # BUG: test has no checks at t/helper/test-example-tap.c:96 not ok 18 - test with no checks ok 19 - test with no checks returns 0 ok 20 - if_test passing test - # check "1 == 2" failed at t/helper/test-example-tap.c:100 + # check "1 == 2" failed at t/helper/test-example-tap.c:102 # left: 1 # right: 2 not ok 21 - if_test failing test not ok 22 - if_test passing TEST_TODO() # TODO - # todo check 'check(1)' succeeded at t/helper/test-example-tap.c:104 + # todo check 'check(1)' succeeded at t/helper/test-example-tap.c:106 not ok 23 - if_test failing TEST_TODO() - # check "0" failed at t/helper/test-example-tap.c:106 + # check "0" failed at t/helper/test-example-tap.c:108 # skipping test - missing prerequisite - # skipping check '1' at t/helper/test-example-tap.c:108 + # skipping check '1' at t/helper/test-example-tap.c:110 ok 24 - if_test test_skip() # SKIP # skipping test - missing prerequisite ok 25 - if_test test_skip() inside TEST_TODO() # SKIP - # check "0" failed at t/helper/test-example-tap.c:113 + # check "0" failed at t/helper/test-example-tap.c:115 not ok 26 - if_test TEST_TODO() after failing check - # check "0" failed at t/helper/test-example-tap.c:119 + # check "0" failed at t/helper/test-example-tap.c:121 not ok 27 - if_test failing check after TEST_TODO() - # check "!strcmp("\thello\\\\", "there\"\n")" failed at t/helper/test-example-tap.c:122 - # left: "\011hello\\\\" - # right: "there\"\012" - # check "!strcmp("NULL", NULL)" failed at t/helper/test-example-tap.c:123 + # check "!strcmp("\thello\\\\", "there\"\n")" failed at t/helper/test-example-tap.c:124 + # left: "\thello\\\\" + # right: "there\"\n" + # check "!strcmp("NULL", NULL)" failed at t/helper/test-example-tap.c:125 # left: "NULL" # right: NULL - # check "'a' == '\n'" failed at t/helper/test-example-tap.c:124 + # check "'a' == '\n'" failed at t/helper/test-example-tap.c:126 # left: 'a' - # right: '\012' - # check "'\\\\' == '\\''" failed at t/helper/test-example-tap.c:125 + # right: '\n' + # check "'\\\\' == '\\''" failed at t/helper/test-example-tap.c:127 # left: '\\\\' # right: '\\'' + # check "'\a' == '\v'" failed at t/helper/test-example-tap.c:128 + # left: '\a' + # right: '\v' + # check "'\x00' == '\x01'" failed at t/helper/test-example-tap.c:129 + # left: '\000' + # right: '\001' not ok 28 - if_test messages from failing string and char comparison - # BUG: test has no checks at t/helper/test-example-tap.c:127 + # BUG: test has no checks at t/helper/test-example-tap.c:131 not ok 29 - if_test test with no checks 1..29 EOF
diff --git a/t/t0081-find-pack.sh b/t/t0081-find-pack.sh index 5a628bf..26f0174 100755 --- a/t/t0081-find-pack.sh +++ b/t/t0081-find-pack.sh
@@ -68,6 +68,7 @@ ' test_expect_success 'add more commits (as loose objects)' ' + test_config maintenance.auto false && test_commit six && test_commit seven &&
diff --git a/t/t0210-trace2-normal.sh b/t/t0210-trace2-normal.sh index 96c68f6..7e1e7af 100755 --- a/t/t0210-trace2-normal.sh +++ b/t/t0210-trace2-normal.sh
@@ -74,8 +74,9 @@ # This line is only emitted when RUNTIME_PREFIX is defined, # so just omit it for testing purposes. # - # 4. 'cmd_ancestry' is not implemented everywhere, so for portability's - # sake, skip it when parsing normal. + # 4. 'cmd_ancestry' output depends on how the test is run and + # is not relevant to the features we are testing here. + # Ancestry tests are covered in t0213-trace2-ancestry.sh instead. sed \ -e 's/elapsed:[0-9]*\.[0-9][0-9]*\([eE][-+]\{0,1\}[0-9][0-9]*\)\{0,1\}/elapsed:_TIME_/g' \ -e "s/^start '[^']*' \(.*\)/start _EXE_ \1/" \
diff --git a/t/t0213-trace2-ancestry.sh b/t/t0213-trace2-ancestry.sh new file mode 100755 index 0000000..a2b9536 --- /dev/null +++ b/t/t0213-trace2-ancestry.sh
@@ -0,0 +1,180 @@ +#!/bin/sh + +test_description='test trace2 cmd_ancestry event' + +. ./test-lib.sh + +# Turn off any inherited trace2 settings for this test. +sane_unset GIT_TRACE2 GIT_TRACE2_PERF GIT_TRACE2_EVENT +sane_unset GIT_TRACE2_BRIEF +sane_unset GIT_TRACE2_CONFIG_PARAMS + +# Add t/helper directory to PATH so that we can use a relative +# path to run nested instances of test-tool.exe (see 004child). +# This helps with HEREDOC comparisons later. +TTDIR="$GIT_BUILD_DIR/t/helper/" && export TTDIR +PATH="$TTDIR:$PATH" && export PATH + +# The 400ancestry helper spawns a child process so that the child +# sees "test-tool" in its process ancestry. We capture only the +# child's trace2 output to a file. +# +# The tests use git commands that spawn child git processes (e.g., +# alias resolution) to create a controlled multi-level process tree. +# Because cmd_ancestry walks the real process tree, processes will +# also report ancestors above "test-tool" that depend on the test +# runner environment (e.g., bash, make, tmux). The filter functions +# below truncate the ancestry at "test-tool", discarding anything +# above it, so only the controlled portion is verified. +# +# On platforms without a real procinfo implementation (the stub), +# no cmd_ancestry event is emitted. We detect this at runtime and +# skip the format-specific tests accordingly. + +# Determine if cmd_ancestry is supported on this platform. +test_expect_success 'detect cmd_ancestry support' ' + test_when_finished "rm -f trace.detect" && + GIT_TRACE2_BRIEF=1 GIT_TRACE2="$(pwd)/trace.detect" \ + test-tool trace2 001return 0 && + if grep -q "^cmd_ancestry" trace.detect + then + test_set_prereq TRACE2_ANCESTRY + fi +' + +# Filter functions for each trace2 target format. +# +# Each extracts cmd_ancestry events, strips format-specific syntax, +# and truncates the ancestor list at the outermost "test-tool" +# (or "test-tool.exe" on Windows), discarding any higher-level +# (uncontrolled) ancestors. +# +# Output is a space-separated list of ancestor names, one line per +# cmd_ancestry event, with the immediate parent listed first: +# +# test-tool (or: test-tool.exe) +# git test-tool (or: git.exe test-tool.exe) +# git test-tool test-tool (or: git.exe test-tool.exe test-tool.exe) + +if test_have_prereq MINGW +then + TT=test-tool$X +else + TT=test-tool +fi + +filter_ancestry_normal () { + sed -n '/^cmd_ancestry/{ + s/^cmd_ancestry // + s/ <- / /g + s/\(.*'"$TT"'\) .*/\1/ + p + }' +} + +filter_ancestry_perf () { + sed -n '/cmd_ancestry/{ + s/.*ancestry:\[// + s/\]// + s/\(.*'"$TT"'\) .*/\1/ + p + }' +} + +filter_ancestry_event () { + sed -n '/"cmd_ancestry"/{ + s/.*"ancestry":\[// + s/\].*// + s/"//g + s/,/ /g + s/\(.*'"$TT"'\) .*/\1/ + p + }' +} + +# On Windows (MINGW) when running with the bin-wrappers, we also see "sh.exe" in +# the ancestry. We must therefore account for this expected ancestry element in +# the expected output of the tests. +if test_have_prereq MINGW && test -z "$no_bin_wrappers"; then + SH_TT="sh$X $TT" +else + SH_TT="$TT" +fi + +# Git alias resolution spawns the target command as a child process. +# Using "git -c alias.xyz=version xyz" creates a two-level chain: +# +# test-tool (400ancestry) +# -> git (resolves alias xyz -> version) +# -> git (version) +# +# Both git processes are instrumented and emit cmd_ancestry. After +# filtering out ancestors above test-tool, we get: +# +# test-tool (from git alias resolver) +# git test-tool (from git version) + +test_expect_success TRACE2_ANCESTRY 'normal: git alias chain, 2 levels' ' + test_when_finished "rm -f trace.normal actual expect" && + test-tool trace2 400ancestry normal "$(pwd)/trace.normal" \ + git -c alias.xyz=version xyz && + filter_ancestry_normal <trace.normal >actual && + cat >expect <<-EOF && + $SH_TT + git$X $SH_TT + EOF + test_cmp expect actual +' + +test_expect_success TRACE2_ANCESTRY 'perf: git alias chain, 2 levels' ' + test_when_finished "rm -f trace.perf actual expect" && + test-tool trace2 400ancestry perf "$(pwd)/trace.perf" \ + git -c alias.xyz=version xyz && + filter_ancestry_perf <trace.perf >actual && + cat >expect <<-EOF && + $SH_TT + git$X $SH_TT + EOF + test_cmp expect actual +' + +test_expect_success TRACE2_ANCESTRY 'event: git alias chain, 2 levels' ' + test_when_finished "rm -f trace.event actual expect" && + test-tool trace2 400ancestry event "$(pwd)/trace.event" \ + git -c alias.xyz=version xyz && + filter_ancestry_event <trace.event >actual && + cat >expect <<-EOF && + $SH_TT + git$X $SH_TT + EOF + test_cmp expect actual +' + +# Use 004child to add a test-tool layer, creating a three-level chain: +# +# test-tool (400ancestry) +# -> test-tool (004child) +# -> git (resolves alias xyz -> version) +# -> git (version) +# +# Three instrumented processes emit cmd_ancestry. After filtering: +# +# test-tool (from test-tool 004child) +# test-tool test-tool (from git alias resolver) +# git test-tool test-tool (from git version) + +test_expect_success TRACE2_ANCESTRY 'normal: deeper chain, 3 levels' ' + test_when_finished "rm -f trace.normal actual expect" && + test-tool trace2 400ancestry normal "$(pwd)/trace.normal" \ + test-tool trace2 004child \ + git -c alias.xyz=version xyz && + filter_ancestry_normal <trace.normal >actual && + cat >expect <<-EOF && + $TT + $SH_TT $TT + git$X $SH_TT $TT + EOF + test_cmp expect actual +' + +test_done
diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh index 07aa834..64ead15 100755 --- a/t/t0300-credentials.sh +++ b/t/t0300-credentials.sh
@@ -675,7 +675,9 @@ test_expect_success 'match percent-encoded UTF-8 values in path' ' test_config credential.https://example.com.useHttpPath true && test_config credential.https://example.com/perú.git.helper "$HELPER" && - check fill <<-\EOF + # NOTE: do not quote this heredoc, Dash 0.5.13 has a bug with heredocs + # that contain multibyte chars. + check fill <<-EOF url=https://example.com/per%C3%BA.git -- protocol=https
diff --git a/t/t0301-credential-cache.sh b/t/t0301-credential-cache.sh index dc30289..6f7cfd9 100755 --- a/t/t0301-credential-cache.sh +++ b/t/t0301-credential-cache.sh
@@ -123,7 +123,8 @@ rmdir \"\$HOME/dir/\" && rm \"\$HOME/.git-credential-cache\" " && - mkdir -p -m 700 "$HOME/dir/" && + mkdir -p "$HOME/dir/" && + chmod 700 "$HOME/dir/" && ln -s "$HOME/dir" "$HOME/.git-credential-cache" && check approve cache <<-\EOF && protocol=https
diff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh index 2a5bdbe..52e1972 100755 --- a/t/t0410-partial-clone.sh +++ b/t/t0410-partial-clone.sh
@@ -11,7 +11,10 @@ GIT_TEST_COMMIT_GRAPH=0 delete_object () { - rm $1/.git/objects/$(echo $2 | sed -e 's|^..|&/|') + local repo="$1" + local obj="$2" + local path="$repo/.git/objects/$(test_oid_to_path "$obj")" && + rm "$path" } pack_as_from_promisor () {
diff --git a/t/t0450-txt-doc-vs-help.sh b/t/t0450-txt-doc-vs-help.sh index e12e18f..822b0d5 100755 --- a/t/t0450-txt-doc-vs-help.sh +++ b/t/t0450-txt-doc-vs-help.sh
@@ -56,7 +56,7 @@ b2t="$(builtin_to_adoc "$builtin")" && sed -n \ -E '/^\[(verse|synopsis)\]$/,/^$/ { - /^$/d; + /^$/q; /^\[(verse|synopsis)\]$/d; s/\{litdd\}/--/g; s/'\''(git[ a-z-]*)'\''/\1/g;
diff --git a/t/t0450/adoc-help-mismatches b/t/t0450/adoc-help-mismatches index 8ee2d3f..e8d6c13 100644 --- a/t/t0450/adoc-help-mismatches +++ b/t/t0450/adoc-help-mismatches
@@ -33,7 +33,6 @@ merge-file merge-index merge-one-file -multi-pack-index name-rev notes push
diff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh index b11126e..74bfa2e 100755 --- a/t/t0600-reffiles-backend.sh +++ b/t/t0600-reffiles-backend.sh
@@ -467,7 +467,7 @@ esac ' -test_expect_success SYMLINKS 'symref transaction supports symlinks' ' +test_expect_success SYMLINKS,!MINGW 'symref transaction supports symlinks' ' test_when_finished "git symbolic-ref -d TEST_SYMREF_HEAD" && git update-ref refs/heads/new @ && test_config core.prefersymlinkrefs true &&
diff --git a/t/t0601-reffiles-pack-refs.sh b/t/t0601-reffiles-pack-refs.sh index 12cf5d1..3c70697 100755 --- a/t/t0601-reffiles-pack-refs.sh +++ b/t/t0601-reffiles-pack-refs.sh
@@ -18,3 +18,5 @@ . ./test-lib.sh . "$TEST_DIRECTORY"/pack-refs-tests.sh + +test_done
diff --git a/t/t0602-reffiles-fsck.sh b/t/t0602-reffiles-fsck.sh index 0ef4836..3c1f553 100755 --- a/t/t0602-reffiles-fsck.sh +++ b/t/t0602-reffiles-fsck.sh
@@ -905,4 +905,34 @@ ) ' +test_expect_success 'complains about broken root ref' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + echo "ref: refs/heads/../HEAD" >.git/HEAD && + test_must_fail git refs verify 2>err && + cat >expect <<-EOF && + error: HEAD: badReferentName: points to invalid refname ${SQ}refs/heads/../HEAD${SQ} + EOF + test_cmp expect err + ) +' + +test_expect_success 'complains about broken root ref in worktree' ' + test_when_finished "rm -rf repo worktree" && + git init repo && + ( + cd repo && + test_commit initial && + git worktree add ../worktree && + echo "ref: refs/heads/../HEAD" >.git/worktrees/worktree/HEAD && + test_must_fail git refs verify 2>err && + cat >expect <<-EOF && + error: worktrees/worktree/HEAD: badReferentName: points to invalid refname ${SQ}refs/heads/../HEAD${SQ} + EOF + test_cmp expect err + ) +' + test_done
diff --git a/t/t0610-reftable-basics.sh b/t/t0610-reftable-basics.sh index 3ea5d51..e19e036 100755 --- a/t/t0610-reftable-basics.sh +++ b/t/t0610-reftable-basics.sh
@@ -207,7 +207,7 @@ test_commit file1 && for f in .git/reftable/*.ref do - : >"$f" || return 1 + test-tool truncate "$f" 0 || return 1 done && test_must_fail git update-ref refs/heads/main HEAD ) @@ -1135,4 +1135,32 @@ test_cmp expect actual ' +test_expect_success 'writes do not persist peeled value for invalid tags' ' + test_when_finished rm -rf repo && + git init repo && + ( + cd repo && + git commit --allow-empty --message initial && + + # We cannot easily verify that the peeled value is not stored + # in the tables. Instead, we test this indirectly: we create + # two tags that both point to the same object, but they claim + # different object types. If we parse both tags we notice that + # the parsed tagged object has a mismatch between the two tags + # and bail out. + # + # If we instead use the persisted peeled value we would not + # even parse the tags. As such, we would not notice the + # discrepancy either and thus listing these tags would succeed. + git tag tag-1 -m "tag 1" && + git cat-file tag tag-1 >raw-tag && + sed "s/^type commit$/type blob/" <raw-tag >broken-tag && + broken_tag_id=$(git hash-object -w -t tag broken-tag) && + git update-ref refs/tags/tag-2 $broken_tag_id && + + test_must_fail git for-each-ref --format="%(*objectname)" refs/tags/ 2>err && + test_grep "bad tag pointer" err + ) +' + test_done
diff --git a/t/t0613-reftable-write-options.sh b/t/t0613-reftable-write-options.sh index e334751..26b716c 100755 --- a/t/t0613-reftable-write-options.sh +++ b/t/t0613-reftable-write-options.sh
@@ -68,8 +68,8 @@ ( cd repo && test_commit initial && - test_seq -f "update refs/heads/branch-%d HEAD" 200 >input && - git update-ref --stdin <input && + test_seq -f "update refs/heads/branch-%d HEAD" 200 | + git update-ref --stdin && git pack-refs && cat >expect <<-EOF && @@ -178,8 +178,8 @@ ( cd repo && test_commit initial && - test_seq -f "update refs/heads/branch-%d HEAD" 10 >input && - git update-ref --stdin <input && + test_seq -f "update refs/heads/branch-%d HEAD" 10 | + git update-ref --stdin && git -c reftable.restartInterval=1 pack-refs && cat >expect <<-EOF && @@ -218,8 +218,8 @@ ( cd repo && test_commit initial && - test_seq -f "update refs/heads/branch-%d HEAD" 5 >input && - git update-ref --stdin <input && + test_seq -f "update refs/heads/branch-%d HEAD" 5 | + git update-ref --stdin && git -c reftable.blockSize=100 pack-refs && cat >expect <<-EOF && @@ -253,8 +253,8 @@ ( cd repo && test_commit initial && - test_seq -f "update refs/heads/branch-%d HEAD" 5 >input && - git update-ref --stdin <input && + test_seq -f "update refs/heads/branch-%d HEAD" 5 | + git update-ref --stdin && git -c reftable.blockSize=100 -c reftable.indexObjects=false pack-refs && cat >expect <<-EOF &&
diff --git a/t/t0614-reftable-fsck.sh b/t/t0614-reftable-fsck.sh index 85cc47d..d24b87f 100755 --- a/t/t0614-reftable-fsck.sh +++ b/t/t0614-reftable-fsck.sh
@@ -20,7 +20,7 @@ done && # The repository should end up with multiple tables. - test_line_count ">" 1 .git/reftable/tables.list && + test_line_count -gt 1 .git/reftable/tables.list && git refs verify 2>err && test_must_be_empty err @@ -55,4 +55,48 @@ ' done +test_expect_success 'worktree stacks can be verified' ' + test_when_finished "rm -rf repo worktree" && + git init repo && + test_commit -C repo initial && + git -C repo worktree add ../worktree && + + git -C worktree refs verify 2>err && + test_must_be_empty err && + + REFTABLE_DIR=$(git -C worktree rev-parse --git-dir)/reftable && + EXISTING_TABLE=$(head -n1 "$REFTABLE_DIR/tables.list") && + mv "$REFTABLE_DIR/$EXISTING_TABLE" "$REFTABLE_DIR/broken.ref" && + + for d in repo worktree + do + echo "broken.ref" >"$REFTABLE_DIR/tables.list" && + git -C "$d" refs verify 2>err && + cat >expect <<-EOF && + warning: broken.ref: badReftableTableName: invalid reftable table name + EOF + test_cmp expect err && + + echo garbage >"$REFTABLE_DIR/tables.list" && + test_must_fail git -C "$d" refs verify 2>err && + cat >expect <<-EOF && + error: reftable stack for worktree ${SQ}worktree${SQ} is broken + EOF + test_cmp expect err || return 1 + + done +' + +test_expect_success 'invalid symref gets reported' ' + test_when_finished "rm -rf repo" && + git init repo && + test_commit -C repo initial && + git -C repo symbolic-ref refs/heads/symref garbage && + test_must_fail git -C repo refs verify 2>err && + cat >expect <<-EOF && + error: refs/heads/symref: badReferentName: points to invalid refname ${SQ}garbage${SQ} + EOF + test_cmp expect err +' + test_done
diff --git a/t/t1005-read-tree-reset.sh b/t/t1005-read-tree-reset.sh index 6b5033d..1bf384d 100755 --- a/t/t1005-read-tree-reset.sh +++ b/t/t1005-read-tree-reset.sh
@@ -40,7 +40,7 @@ git ls-files -s && read_tree_u_must_succeed --reset -u HEAD && git ls-files -s >actual && - ! test -f old && + test_path_is_missing old && test_cmp expect actual ' @@ -56,7 +56,7 @@ git ls-files -s && read_tree_u_must_succeed --reset -u HEAD HEAD && git ls-files -s >actual && - ! test -f old && + test_path_is_missing old && test_cmp expect actual ' @@ -72,7 +72,7 @@ git ls-files -s && git reset --hard && git ls-files -s >actual && - ! test -f old && + test_path_is_missing old && test_cmp expect actual ' @@ -88,7 +88,7 @@ git ls-files -s && git checkout -f && git ls-files -s >actual && - ! test -f old && + test_path_is_missing old && test_cmp expect actual ' @@ -104,7 +104,7 @@ git ls-files -s && git checkout -f HEAD && git ls-files -s >actual && - ! test -f old && + test_path_is_missing old && test_cmp expect actual '
diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index 1f61b66..8e2c526 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh
@@ -241,10 +241,16 @@ hello_size=$(strlen "$hello_content") hello_oid=$(echo_without_newline "$hello_content" | git hash-object --stdin) -test_expect_success "setup" ' +test_expect_success "setup part 1" ' git config core.repositoryformatversion 1 && - git config extensions.objectformat $test_hash_algo && - git config extensions.compatobjectformat $test_compat_hash_algo && + git config extensions.objectformat $test_hash_algo +' + +test_expect_success RUST 'compat setup' ' + git config extensions.compatobjectformat $test_compat_hash_algo +' + +test_expect_success 'setup part 2' ' echo_without_newline "$hello_content" > hello && git update-index --add hello && echo_without_newline "$hello_content" > "path with spaces" && @@ -273,9 +279,13 @@ ' } -hello_compat_oid=$(git rev-parse --output-object-format=$test_compat_hash_algo $hello_oid) run_blob_tests $hello_oid -run_blob_tests $hello_compat_oid + +if test_have_prereq RUST +then + hello_compat_oid=$(git rev-parse --output-object-format=$test_compat_hash_algo $hello_oid) + run_blob_tests $hello_compat_oid +fi test_expect_success '--batch-check without %(rest) considers whole line' ' echo "$hello_oid blob $hello_size" >expect && @@ -286,62 +296,76 @@ ' tree_oid=$(git write-tree) -tree_compat_oid=$(git rev-parse --output-object-format=$test_compat_hash_algo $tree_oid) tree_size=$((2 * $(test_oid rawsz) + 13 + 24)) -tree_compat_size=$((2 * $(test_oid --hash=compat rawsz) + 13 + 24)) tree_pretty_content="100644 blob $hello_oid hello${LF}100755 blob $hello_oid path with spaces${LF}" -tree_compat_pretty_content="100644 blob $hello_compat_oid hello${LF}100755 blob $hello_compat_oid path with spaces${LF}" run_tests 'tree' $tree_oid "" $tree_size "" "$tree_pretty_content" -run_tests 'tree' $tree_compat_oid "" $tree_compat_size "" "$tree_compat_pretty_content" run_tests 'blob' "$tree_oid:hello" "100644" $hello_size "" "$hello_content" $hello_oid -run_tests 'blob' "$tree_compat_oid:hello" "100644" $hello_size "" "$hello_content" $hello_compat_oid run_tests 'blob' "$tree_oid:path with spaces" "100755" $hello_size "" "$hello_content" $hello_oid -run_tests 'blob' "$tree_compat_oid:path with spaces" "100755" $hello_size "" "$hello_content" $hello_compat_oid + +if test_have_prereq RUST +then + tree_compat_oid=$(git rev-parse --output-object-format=$test_compat_hash_algo $tree_oid) + tree_compat_size=$((2 * $(test_oid --hash=compat rawsz) + 13 + 24)) + tree_compat_pretty_content="100644 blob $hello_compat_oid hello${LF}100755 blob $hello_compat_oid path with spaces${LF}" + + run_tests 'tree' $tree_compat_oid "" $tree_compat_size "" "$tree_compat_pretty_content" + run_tests 'blob' "$tree_compat_oid:hello" "100644" $hello_size "" "$hello_content" $hello_compat_oid + run_tests 'blob' "$tree_compat_oid:path with spaces" "100755" $hello_size "" "$hello_content" $hello_compat_oid +fi commit_message="Initial commit" commit_oid=$(echo_without_newline "$commit_message" | git commit-tree $tree_oid) -commit_compat_oid=$(git rev-parse --output-object-format=$test_compat_hash_algo $commit_oid) commit_size=$(($(test_oid hexsz) + 137)) -commit_compat_size=$(($(test_oid --hash=compat hexsz) + 137)) commit_content="tree $tree_oid author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE $commit_message" -commit_compat_content="tree $tree_compat_oid +run_tests 'commit' $commit_oid "" $commit_size "$commit_content" "$commit_content" + +if test_have_prereq RUST +then + commit_compat_oid=$(git rev-parse --output-object-format=$test_compat_hash_algo $commit_oid) + commit_compat_size=$(($(test_oid --hash=compat hexsz) + 137)) + commit_compat_content="tree $tree_compat_oid author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE $commit_message" -run_tests 'commit' $commit_oid "" $commit_size "$commit_content" "$commit_content" -run_tests 'commit' $commit_compat_oid "" $commit_compat_size "$commit_compat_content" "$commit_compat_content" + run_tests 'commit' $commit_compat_oid "" $commit_compat_size "$commit_compat_content" "$commit_compat_content" +fi tag_header_without_oid="type blob tag hellotag tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" tag_header_without_timestamp="object $hello_oid $tag_header_without_oid" -tag_compat_header_without_timestamp="object $hello_compat_oid -$tag_header_without_oid" tag_description="This is a tag" tag_content="$tag_header_without_timestamp 0 +0000 $tag_description" -tag_compat_content="$tag_compat_header_without_timestamp 0 +0000 - -$tag_description" tag_oid=$(echo_without_newline "$tag_content" | git hash-object -t tag --stdin -w) tag_size=$(strlen "$tag_content") -tag_compat_oid=$(git rev-parse --output-object-format=$test_compat_hash_algo $tag_oid) -tag_compat_size=$(strlen "$tag_compat_content") - run_tests 'tag' $tag_oid "" $tag_size "$tag_content" "$tag_content" -run_tests 'tag' $tag_compat_oid "" $tag_compat_size "$tag_compat_content" "$tag_compat_content" + +if test_have_prereq RUST +then + tag_compat_header_without_timestamp="object $hello_compat_oid +$tag_header_without_oid" + tag_compat_content="$tag_compat_header_without_timestamp 0 +0000 + +$tag_description" + + tag_compat_oid=$(git rev-parse --output-object-format=$test_compat_hash_algo $tag_oid) + tag_compat_size=$(strlen "$tag_compat_content") + + run_tests 'tag' $tag_compat_oid "" $tag_compat_size "$tag_compat_content" "$tag_compat_content" +fi test_expect_success "Reach a blob from a tag pointing to it" ' echo_without_newline "$hello_content" >expect && @@ -590,7 +614,8 @@ } batch_tests $hello_oid $tree_oid $tree_size $commit_oid $commit_size "$commit_content" $tag_oid $tag_size "$tag_content" -batch_tests $hello_compat_oid $tree_compat_oid $tree_compat_size $commit_compat_oid $commit_compat_size "$commit_compat_content" $tag_compat_oid $tag_compat_size "$tag_compat_content" + +test_have_prereq RUST && batch_tests $hello_compat_oid $tree_compat_oid $tree_compat_size $commit_compat_oid $commit_compat_size "$commit_compat_content" $tag_compat_oid $tag_compat_size "$tag_compat_content" test_expect_success FUNNYNAMES 'setup with newline in input' ' @@ -643,7 +668,7 @@ ' test_expect_success 'setup blobs which are likely to delta' ' - test-tool genrandom foo 10240 >foo && + test-tool genrandom foo 10k >foo && { cat foo && echo plus; } >foo-plus && git add foo foo-plus && git commit -m foo && @@ -1048,18 +1073,28 @@ echo .. >>expect && echo HEAD:dir/subdir/out-of-repo-link-dir | git cat-file --batch-check --follow-symlinks >actual && test_cmp expect actual && - echo symlink 3 >expect && - echo ../ >>expect && + if test_have_prereq MINGW,SYMLINKS + then + test_write_lines "symlink 2" .. + else + test_write_lines "symlink 3" ../ + fi >expect && echo HEAD:dir/subdir/out-of-repo-link-dir-trailing | git cat-file --batch-check --follow-symlinks >actual && test_cmp expect actual ' test_expect_success 'git cat-file --batch-check --follow-symlinks works for symlinks with internal ..' ' - echo HEAD: | git cat-file --batch-check >expect && - echo HEAD:up-down | git cat-file --batch-check --follow-symlinks >actual && - test_cmp expect actual && - echo HEAD:up-down-trailing | git cat-file --batch-check --follow-symlinks >actual && - test_cmp expect actual && + if test_have_prereq !MINGW + then + # The `up-down` and `up-down-trailing` symlinks are normalized + # in MSYS in `winsymlinks` mode and are therefore in a + # different shape than Git expects them. + echo HEAD: | git cat-file --batch-check >expect && + echo HEAD:up-down | git cat-file --batch-check --follow-symlinks >actual && + test_cmp expect actual && + echo HEAD:up-down-trailing | git cat-file --batch-check --follow-symlinks >actual && + test_cmp expect actual + fi && echo HEAD:up-down-file | git cat-file --batch-check --follow-symlinks >actual && test_cmp found actual && echo symlink 7 >expect && @@ -1226,7 +1261,10 @@ test_unconfig extensions.compatobjectformat && printf "160000 commit $(test_oid deadbeef)\tsub\n" >tree-with-sub && tree=$(git mktree <tree-with-sub) && - test_config extensions.compatobjectformat $test_compat_hash_algo && + if test_have_prereq RUST + then + test_config extensions.compatobjectformat $test_compat_hash_algo + fi && git cat-file --batch-check >actual <<-EOF && $tree:sub
diff --git a/t/t1016-compatObjectFormat.sh b/t/t1016-compatObjectFormat.sh index 0efce53..92d48b9 100755 --- a/t/t1016-compatObjectFormat.sh +++ b/t/t1016-compatObjectFormat.sh
@@ -8,6 +8,12 @@ . ./test-lib.sh . "$TEST_DIRECTORY"/lib-gpg.sh +if ! test_have_prereq RUST +then + skip_all='interoperability requires a Git built with Rust' + test_done +fi + # All of the follow variables must be defined in the environment: # GIT_AUTHOR_NAME # GIT_AUTHOR_EMAIL
diff --git a/t/t1050-large.sh b/t/t1050-large.sh index 5be2736..7d40d08 100755 --- a/t/t1050-large.sh +++ b/t/t1050-large.sh
@@ -104,9 +104,9 @@ # mid1 and mid2 will fit within 256k limit but # appending mid3 will bust the limit and will # result in a separate packfile. - test-tool genrandom "a" $(( 66 * 1024 )) >mid1 && - test-tool genrandom "b" $(( 80 * 1024 )) >mid2 && - test-tool genrandom "c" $(( 128 * 1024 )) >mid3 && + test-tool genrandom "a" 66k >mid1 && + test-tool genrandom "b" 80k >mid2 && + test-tool genrandom "c" 128k >mid3 && git add mid1 mid2 mid3 && count=0 &&
diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh index b2da4fe..cd0aed9 100755 --- a/t/t1091-sparse-checkout-builtin.sh +++ b/t/t1091-sparse-checkout-builtin.sh
@@ -817,6 +817,54 @@ test_cmp expect out ' +test_expect_success 'sparse-checkout deduplicates repeated cone patterns' ' + rm -f repo/.git/info/sparse-checkout && + git -C repo sparse-checkout init --cone && + git -C repo sparse-checkout add --stdin <<-\EOF && + foo/bar/baz + a/b/c + foo/bar/baz + a/b + EOF + cat >expect <<-\EOF && + /* + !/*/ + /a/ + !/a/*/ + /foo/ + !/foo/*/ + /foo/bar/ + !/foo/bar/*/ + /a/b/ + /foo/bar/baz/ + EOF + test_cmp expect repo/.git/info/sparse-checkout +' + +test_expect_success 'sparse-checkout list deduplicates repeated cone patterns' ' + rm -f repo/.git/info/sparse-checkout && + git -C repo sparse-checkout init --cone && + cat <<-\EOF >repo/.git/info/sparse-checkout && + /* + !/*/ + /a/ + !/a/*/ + /foo/ + !/foo/*/ + /foo/bar/ + !/foo/bar/*/ + /a/b/ + /foo/bar/baz/ + /foo/bar/baz/ + EOF + git -C repo sparse-checkout list >actual && + cat <<-\EOF >expect && + a/b + foo/bar/baz + EOF + test_cmp expect actual +' + test_expect_success 'malformed cone-mode patterns' ' git -C repo sparse-checkout init --cone && mkdir -p repo/foo/bar &&
diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh index b0f691c..d98cb4a 100755 --- a/t/t1092-sparse-checkout-compatibility.sh +++ b/t/t1092-sparse-checkout-compatibility.sh
@@ -2559,4 +2559,18 @@ ensure_expanded cat-file --batch <in ' +test_expect_success 'merge -s ours' ' + init_repos && + + test_all_match git rev-parse HEAD^{tree} && + test_all_match git merge -s ours merge-right && + test_all_match git rev-parse HEAD^{tree} && + test_all_match git rev-parse HEAD^2 +' + +test_expect_success 'sparse-index is not expanded: merge-ours' ' + init_repos && + ensure_not_expanded merge -s ours merge-right +' + test_done
diff --git a/t/t1300-config.sh b/t/t1300-config.sh index 358d636..128971e 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh
@@ -1232,12 +1232,12 @@ test_when_finished "rm myconfig" && ln -s notyet myconfig && git config --file=myconfig test.frotz nitfol && - test -h myconfig && - test -f notyet && + test_path_is_symlink myconfig && + test_path_is_file notyet && test "z$(git config --file=notyet test.frotz)" = znitfol && git config --file=myconfig test.xyzzy rezrov && - test -h myconfig && - test -f notyet && + test_path_is_symlink myconfig && + test_path_is_file notyet && cat >expect <<-\EOF && nitfol rezrov @@ -2459,9 +2459,15 @@ cat >.git/config <<-\EOF && [section] -foo = true +foo = True number = 10 big = 1M +path = ~/dir +red = red +blue = Blue +date = Fri Jun 4 15:46:55 2010 +missing=:(optional)no-such-path +exists=:(optional)expect EOF test_expect_success 'identical modern --type specifiers are allowed' ' @@ -2503,6 +2509,82 @@ test_cmp_config 1048576 --type=bool --no-type --type=int section.big ' +test_expect_success 'list --type=int shows only canonicalizable int values' ' + cat >expect <<-EOF && + section.number=10 + section.big=1048576 + EOF + + git config ${mode_prefix}list --type=int >actual 2>err && + test_cmp expect actual && + test_must_be_empty err +' + +test_expect_success 'list --type=bool shows only canonicalizable bool values' ' + cat >expect <<-EOF && + section.foo=true + section.number=true + section.big=true + EOF + + git config ${mode_prefix}list --type=bool >actual 2>err && + test_cmp expect actual && + test_must_be_empty err +' + +test_expect_success 'list --type=bool-or-int shows only canonicalizable values' ' + cat >expect <<-EOF && + section.foo=true + section.number=10 + section.big=1048576 + EOF + + git config ${mode_prefix}list --type=bool-or-int >actual 2>err && + test_cmp expect actual && + test_must_be_empty err +' + +test_expect_success 'list --type=path shows only canonicalizable path values' ' + cat >expect <<-EOF && + section.foo=True + section.number=10 + section.big=1M + section.path=$HOME/dir + section.red=red + section.blue=Blue + section.date=Fri Jun 4 15:46:55 2010 + section.exists=expect + EOF + + git config ${mode_prefix}list --type=path >actual 2>err && + test_cmp expect actual && + test_must_be_empty err +' + +test_expect_success 'list --type=expiry-date shows only canonicalizable dates' ' + git config ${mode_prefix}list --type=expiry-date >actual 2>err && + + # section.number and section.big parse as relative dates that could + # have clock skew in their results. + test_grep section.big actual && + test_grep section.number actual && + test_grep "section.date=$(git config --type=expiry-date section.$key)" actual && + test_must_be_empty err +' + +test_expect_success 'list --type=color shows only canonicalizable color values' ' + cat >expect <<-EOF && + section.number=<> + section.red=<RED> + section.blue=<BLUE> + EOF + + git config ${mode_prefix}list --type=color >actual.raw 2>err && + test_decode_color <actual.raw >actual && + test_cmp expect actual && + test_must_be_empty err +' + test_expect_success '--type rejects unknown specifiers' ' test_must_fail git config --type=nonsense section.foo 2>error && test_grep "unrecognized --type argument" error
diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh index 8ff2b0c..6e51f89 100755 --- a/t/t1305-config-include.sh +++ b/t/t1305-config-include.sh
@@ -286,7 +286,7 @@ ) ' -test_expect_success SYMLINKS 'conditional include, gitdir matching symlink' ' +test_expect_success SYMLINKS,!MINGW 'conditional include, gitdir matching symlink' ' ln -s foo bar && ( cd bar && @@ -298,7 +298,7 @@ ) ' -test_expect_success SYMLINKS 'conditional include, gitdir matching symlink, icase' ' +test_expect_success SYMLINKS,!MINGW 'conditional include, gitdir matching symlink, icase' ' ( cd bar && echo "[includeIf \"gitdir/i:BAR/\"]path=bar8" >>.git/config &&
diff --git a/t/t1311-config-optional.sh b/t/t1311-config-optional.sh new file mode 100755 index 0000000..fbbacfc --- /dev/null +++ b/t/t1311-config-optional.sh
@@ -0,0 +1,38 @@ +#!/bin/sh +# +# Copyright (c) 2025 Google LLC +# + +test_description=':(optional) paths' + +. ./test-lib.sh + +test_expect_success 'var=:(optional)path-exists' ' + test_config a.path ":(optional)path-exists" && + >path-exists && + echo path-exists >expect && + + git config get --path a.path >actual && + test_cmp expect actual +' + +test_expect_success 'missing optional value is ignored' ' + test_config a.path ":(optional)no-such-path" && + # Using --show-scope ensures we skip writing not only the value + # but also any meta-information about the ignored key. + test_must_fail git config get --show-scope --path a.path >actual && + test_line_count = 0 actual +' + +test_expect_success 'missing optional value is ignored in multi-value config' ' + test_when_finished "git config unset --all a.path" && + git config set --append a.path ":(optional)path-exists" && + git config set --append a.path ":(optional)no-such-path" && + >path-exists && + echo path-exists >expect && + + git config --get --path a.path >actual && + test_cmp expect actual +' + +test_done
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index db7f544..b2858a9 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh
@@ -1380,16 +1380,16 @@ test_expect_success ULIMIT_FILE_DESCRIPTORS 'large transaction creating branches does not burst open file limit' ' ( - test_seq -f "create refs/heads/%d HEAD" 33 >large_input && - run_with_limited_open_files git update-ref --stdin <large_input && + test_seq -f "create refs/heads/%d HEAD" 33 | + run_with_limited_open_files git update-ref --stdin && git rev-parse --verify -q refs/heads/33 ) ' test_expect_success ULIMIT_FILE_DESCRIPTORS 'large transaction deleting branches does not burst open file limit' ' ( - test_seq -f "delete refs/heads/%d HEAD" 33 >large_input && - run_with_limited_open_files git update-ref --stdin <large_input && + test_seq -f "delete refs/heads/%d HEAD" 33 | + run_with_limited_open_files git update-ref --stdin && test_must_fail git rev-parse --verify -q refs/heads/33 ) ' @@ -2093,14 +2093,15 @@ format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin && format_command $type "update refs/heads/ref2" "$(test_oid 001)" "$head" >>stdin && - git update-ref $type --stdin --batch-updates <stdin >stdout && + git update-ref $type --stdin --batch-updates <stdin >stdout 2>err && echo $old_head >expect && git rev-parse refs/heads/ref1 >actual && test_cmp expect actual && echo $head >expect && git rev-parse refs/heads/ref2 >actual && test_cmp expect actual && - test_grep -q "invalid new value provided" stdout + test_grep "rejected refs/heads/ref2 $(test_oid 001) $head invalid new value provided" stdout && + test_grep "trying to write ref ${SQ}refs/heads/ref2${SQ} with nonexistent object" err ) ' @@ -2119,14 +2120,15 @@ format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin && format_command $type "update refs/heads/ref2" "$head_tree" "$head" >>stdin && - git update-ref $type --stdin --batch-updates <stdin >stdout && + git update-ref $type --stdin --batch-updates <stdin >stdout 2>err && echo $old_head >expect && git rev-parse refs/heads/ref1 >actual && test_cmp expect actual && echo $head >expect && git rev-parse refs/heads/ref2 >actual && test_cmp expect actual && - test_grep -q "invalid new value provided" stdout + test_grep "rejected refs/heads/ref2 $head_tree $head invalid new value provided" stdout && + test_grep "trying to write non-commit object $head_tree to branch ${SQ}refs/heads/ref2${SQ}" err ) ' @@ -2143,12 +2145,13 @@ format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin && format_command $type "update refs/heads/ref2" "$old_head" "$head" >>stdin && - git update-ref $type --stdin --batch-updates <stdin >stdout && + git update-ref $type --stdin --batch-updates <stdin >stdout 2>err && echo $old_head >expect && git rev-parse refs/heads/ref1 >actual && test_cmp expect actual && test_must_fail git rev-parse refs/heads/ref2 && - test_grep -q "reference does not exist" stdout + test_grep "rejected refs/heads/ref2 $old_head $head reference does not exist" stdout && + test_grep "cannot lock ref ${SQ}refs/heads/ref2${SQ}: unable to resolve reference ${SQ}refs/heads/ref2${SQ}" err ) ' @@ -2166,13 +2169,14 @@ format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin && format_command $type "update refs/heads/ref2" "$old_head" "$head" >>stdin && - git update-ref $type --no-deref --stdin --batch-updates <stdin >stdout && + git update-ref $type --no-deref --stdin --batch-updates <stdin >stdout 2>err && echo $old_head >expect && git rev-parse refs/heads/ref1 >actual && test_cmp expect actual && echo $head >expect && test_must_fail git rev-parse refs/heads/ref2 && - test_grep -q "reference does not exist" stdout + test_grep "rejected refs/heads/ref2 $old_head $head reference does not exist" stdout && + test_grep "cannot lock ref ${SQ}refs/heads/ref2${SQ}: reference is missing but expected $head" err ) ' @@ -2190,7 +2194,7 @@ format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin && format_command $type "symref-update refs/heads/ref2" "$old_head" "ref" "refs/heads/nonexistent" >>stdin && - git update-ref $type --no-deref --stdin --batch-updates <stdin >stdout && + git update-ref $type --no-deref --stdin --batch-updates <stdin >stdout 2>err && echo $old_head >expect && git rev-parse refs/heads/ref1 >actual && test_cmp expect actual && @@ -2198,7 +2202,8 @@ echo $head >expect && git rev-parse refs/heads/ref2 >actual && test_cmp expect actual && - test_grep -q "expected symref but found regular ref" stdout + test_grep "rejected refs/heads/ref2 $ZERO_OID $ZERO_OID expected symref but found regular ref" stdout && + test_grep "cannot lock ref ${SQ}refs/heads/ref2${SQ}: expected symref with target ${SQ}refs/heads/nonexistent${SQ}: but is a regular ref" err ) ' @@ -2216,14 +2221,15 @@ format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin && format_command $type "update refs/heads/ref2" "$old_head" "$Z" >>stdin && - git update-ref $type --stdin --batch-updates <stdin >stdout && + git update-ref $type --stdin --batch-updates <stdin >stdout 2>err && echo $old_head >expect && git rev-parse refs/heads/ref1 >actual && test_cmp expect actual && echo $head >expect && git rev-parse refs/heads/ref2 >actual && test_cmp expect actual && - test_grep -q "reference already exists" stdout + test_grep "rejected refs/heads/ref2 $old_head $ZERO_OID reference already exists" stdout && + test_grep "cannot lock ref ${SQ}refs/heads/ref2${SQ}: reference already exists" err ) ' @@ -2241,14 +2247,15 @@ format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin && format_command $type "update refs/heads/ref2" "$head" "$old_head" >>stdin && - git update-ref $type --stdin --batch-updates <stdin >stdout && + git update-ref $type --stdin --batch-updates <stdin >stdout 2>err && echo $old_head >expect && git rev-parse refs/heads/ref1 >actual && test_cmp expect actual && echo $head >expect && git rev-parse refs/heads/ref2 >actual && test_cmp expect actual && - test_grep -q "incorrect old value provided" stdout + test_grep "rejected refs/heads/ref2 $head $old_head incorrect old value provided" stdout && + test_grep "cannot lock ref ${SQ}refs/heads/ref2${SQ}: is at $head but expected $old_head" err ) ' @@ -2264,12 +2271,13 @@ git update-ref refs/heads/ref/foo $head && format_command $type "update refs/heads/ref/foo" "$old_head" "$head" >stdin && - format_command $type "update refs/heads/ref" "$old_head" "" >>stdin && - git update-ref $type --stdin --batch-updates <stdin >stdout && + format_command $type "update refs/heads/ref" "$old_head" "$ZERO_OID" >>stdin && + git update-ref $type --stdin --batch-updates <stdin >stdout 2>err && echo $old_head >expect && git rev-parse refs/heads/ref/foo >actual && test_cmp expect actual && - test_grep -q "refname conflict" stdout + test_grep "rejected refs/heads/ref $old_head $ZERO_OID refname conflict" stdout && + test_grep "${SQ}refs/heads/ref/foo${SQ} exists; cannot create ${SQ}refs/heads/ref${SQ}" err ) ' @@ -2284,13 +2292,14 @@ head=$(git rev-parse HEAD) && git update-ref refs/heads/ref/foo $head && - format_command $type "update refs/heads/foo" "$old_head" "" >stdin && - format_command $type "update refs/heads/ref" "$old_head" "" >>stdin && - git update-ref $type --stdin --batch-updates <stdin >stdout && + format_command $type "update refs/heads/foo" "$old_head" "$ZERO_OID" >stdin && + format_command $type "update refs/heads/ref" "$old_head" "$ZERO_OID" >>stdin && + git update-ref $type --stdin --batch-updates <stdin >stdout 2>err && echo $old_head >expect && git rev-parse refs/heads/foo >actual && test_cmp expect actual && - test_grep -q "refname conflict" stdout + test_grep "rejected refs/heads/ref $old_head $ZERO_OID refname conflict" stdout && + test_grep "${SQ}refs/heads/ref/foo${SQ} exists; cannot create ${SQ}refs/heads/ref${SQ}" err ) ' @@ -2309,14 +2318,15 @@ format_command $type "create refs/heads/ref" "$old_head" && format_command $type "create refs/heads/Foo" "$old_head" } >stdin && - git update-ref $type --stdin --batch-updates <stdin >stdout && + git update-ref $type --stdin --batch-updates <stdin >stdout 2>err && echo $head >expect && git rev-parse refs/heads/foo >actual && echo $old_head >expect && git rev-parse refs/heads/ref >actual && test_cmp expect actual && - test_grep -q "reference conflict due to case-insensitive filesystem" stdout + test_grep "rejected refs/heads/Foo $old_head $ZERO_OID reference conflict due to case-insensitive filesystem" stdout && + test_grep -e "cannot lock ref ${SQ}refs/heads/Foo${SQ}: Unable to create" -e "Foo.lock" err ) ' @@ -2357,8 +2367,9 @@ git symbolic-ref refs/heads/symbolic refs/heads/non-existent && format_command $type "delete refs/heads/symbolic" "$head" >stdin && - git update-ref $type --stdin --batch-updates <stdin >stdout && - test_grep "reference does not exist" stdout + git update-ref $type --stdin --batch-updates <stdin >stdout 2>err && + test_grep "rejected refs/heads/non-existent $ZERO_OID $head reference does not exist" stdout && + test_grep "cannot lock ref ${SQ}refs/heads/symbolic${SQ}: unable to resolve reference ${SQ}refs/heads/non-existent${SQ}" err ) ' @@ -2373,8 +2384,9 @@ head=$(git rev-parse HEAD) && format_command $type "delete refs/heads/new-branch" "$head" >stdin && - git update-ref $type --stdin --batch-updates <stdin >stdout && - test_grep "incorrect old value provided" stdout + git update-ref $type --stdin --batch-updates <stdin >stdout 2>err && + test_grep "rejected refs/heads/new-branch $ZERO_OID $head incorrect old value provided" stdout && + test_grep "cannot lock ref ${SQ}refs/heads/new-branch${SQ}: is at $(git rev-parse new-branch) but expected $head" err ) ' @@ -2387,8 +2399,9 @@ head=$(git rev-parse HEAD) && format_command $type "delete refs/heads/non-existent" "$head" >stdin && - git update-ref $type --stdin --batch-updates <stdin >stdout && - test_grep "reference does not exist" stdout + git update-ref $type --stdin --batch-updates <stdin >stdout 2>err && + test_grep "rejected refs/heads/non-existent $ZERO_OID $head reference does not exist" stdout && + test_grep "cannot lock ref ${SQ}refs/heads/non-existent${SQ}: unable to resolve reference ${SQ}refs/heads/non-existent${SQ}" err ) ' done
diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh index e30f87a..ce71f9a 100755 --- a/t/t1410-reflog.sh +++ b/t/t1410-reflog.sh
@@ -130,10 +130,10 @@ test_expect_success rewind ' test_tick && git reset --hard HEAD~2 && - test -f C && - test -f A/B/E && - ! test -f F && - ! test -f A/G && + test_path_is_file C && + test_path_is_file A/B/E && + test_path_is_missing F && + test_path_is_missing A/G && check_have A B C D E F G H I J K L &&
diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh index d91dd3a..4fe9d9b 100755 --- a/t/t1416-ref-transaction-hooks.sh +++ b/t/t1416-ref-transaction-hooks.sh
@@ -20,6 +20,7 @@ echo "$*" >>actual EOF cat >expect <<-EOF && + preparing prepared committed EOF @@ -27,6 +28,18 @@ test_cmp expect actual ' +test_expect_success 'hook aborts updating ref in preparing state' ' + git reset --hard PRE && + test_hook reference-transaction <<-\EOF && + if test "$1" = preparing + then + exit 1 + fi + EOF + test_must_fail git update-ref HEAD POST 2>err && + test_grep "in '\''preparing'\'' phase, update aborted by the reference-transaction hook" err +' + test_expect_success 'hook aborts updating ref in prepared state' ' git reset --hard PRE && test_hook reference-transaction <<-\EOF && @@ -36,7 +49,7 @@ fi EOF test_must_fail git update-ref HEAD POST 2>err && - test_grep "ref updates aborted by hook" err + test_grep "in '\''prepared'\'' phase, update aborted by the reference-transaction hook" err ' test_expect_success 'hook gets all queued updates in prepared state' ' @@ -121,6 +134,7 @@ cat >expect <<-EOF && hooks/update refs/tags/PRE $ZERO_OID $PRE_OID hooks/update refs/tags/POST $ZERO_OID $POST_OID + hooks/reference-transaction preparing hooks/reference-transaction prepared hooks/reference-transaction committed EOF @@ -143,6 +157,8 @@ git symbolic-ref refs/heads/symref refs/heads/main && cat >expect <<-EOF && + preparing + $ZERO_OID ref:refs/heads/main refs/heads/symref prepared $ZERO_OID ref:refs/heads/main refs/heads/symref committed @@ -171,14 +187,20 @@ # In the files backend, "delete" also triggers an additional transaction # update on the packed-refs backend, which constitutes additional reflog # entries. + cat >expect <<-EOF && + preparing + ref:refs/heads/main $ZERO_OID refs/heads/symref + ref:refs/heads/main $ZERO_OID refs/heads/symrefd + $ZERO_OID ref:refs/heads/main refs/heads/symrefc + ref:refs/heads/main ref:refs/heads/branch refs/heads/symrefu + EOF + if test_have_prereq REFFILES then - cat >expect <<-EOF + cat >>expect <<-EOF aborted $ZERO_OID $ZERO_OID refs/heads/symrefd EOF - else - >expect fi && cat >>expect <<-EOF &&
diff --git a/t/t1420-lost-found.sh b/t/t1420-lost-found.sh index 2fb2f44..926c6d6 100755 --- a/t/t1420-lost-found.sh +++ b/t/t1420-lost-found.sh
@@ -28,9 +28,12 @@ test_tick && git reset --hard HEAD^ && git fsck --lost-found && - test 2 = $(ls .git/lost-found/*/* | wc -l) && - test -f .git/lost-found/commit/$(cat lost-commit) && - test -f .git/lost-found/other/$(cat lost-other) + ls .git/lost-found/*/* >actual && + cat >expect <<-EOF && + .git/lost-found/commit/$(cat lost-commit) + .git/lost-found/other/$(cat lost-other) + EOF + test_cmp expect actual ' test_done
diff --git a/t/t1423-ref-backend.sh b/t/t1423-ref-backend.sh new file mode 100755 index 0000000..fd47d77 --- /dev/null +++ b/t/t1423-ref-backend.sh
@@ -0,0 +1,280 @@ +#!/bin/sh + +test_description='Test reference backend URIs' + +. ./test-lib.sh + +# Run a git command with the provided reference storage. Reset the backend +# post running the command. +# Usage: run_with_uri <repo> <backend> <uri> <cmd> +# <repo> is the relative path to the repo to run the command in. +# <backend> is the original ref storage of the repo. +# <uri> is the new URI to be set for the ref storage. +# <cmd> is the git subcommand to be run in the repository. +# <via> if 'config', set the backend via the 'extensions.refStorage' config. +# if 'env', set the backend via the 'GIT_REFERENCE_BACKEND' env. +run_with_uri () { + repo=$1 && + backend=$2 && + uri=$3 && + cmd=$4 && + via=$5 && + + git -C "$repo" config set core.repositoryformatversion 1 && + if test "$via" = "env" + then + test_env GIT_REFERENCE_BACKEND="$uri" git -C "$repo" $cmd + elif test "$via" = "config" + then + git -C "$repo" config set extensions.refStorage "$uri" && + git -C "$repo" $cmd && + git -C "$repo" config set extensions.refStorage "$backend" + fi +} + +# Test a repository with a given reference storage by running and comparing +# 'git refs list' before and after setting the new reference backend. If +# err_msg is set, expect the command to fail and grep for the provided err_msg. +# Usage: run_with_uri <repo> <backend> <uri> <cmd> +# <repo> is the relative path to the repo to run the command in. +# <backend> is the original ref storage of the repo. +# <uri> is the new URI to be set for the ref storage. +# <via> if 'config', set the backend via the 'extensions.refStorage' config. +# if 'env', set the backend via the 'GIT_REFERENCE_BACKEND' env. +# <err_msg> (optional) if set, check if 'git-refs(1)' failed with the provided msg. +test_refs_backend () { + repo=$1 && + backend=$2 && + uri=$3 && + via=$4 && + err_msg=$5 && + + + if test -n "$err_msg"; + then + if test "$via" = "env" + then + test_env GIT_REFERENCE_BACKEND="$uri" test_must_fail git -C "$repo" refs list 2>err + elif test "$via" = "config" + then + git -C "$repo" config set extensions.refStorage "$uri" && + test_must_fail git -C "$repo" refs list 2>err && + test_grep "$err_msg" err + fi + else + git -C "$repo" refs list >expect && + run_with_uri "$repo" "$backend" "$uri" "refs list" "$via">actual && + test_cmp expect actual + fi +} + +# Verify that the expected files are present in the gitdir and the refsdir. +# Usage: verify_files_exist <gitdir> <refdir> +# <gitdir> is the path for the gitdir. +# <refdir> is the path for the refdir. +verify_files_exist () { + gitdir=$1 && + refdir=$2 && + + # verify that the stubs were added to the $GITDIR. + echo "repository uses alternate refs storage" >expect && + test_cmp expect $gitdir/refs/heads && + echo "ref: refs/heads/.invalid" >expect && + test_cmp expect $gitdir/HEAD + + # verify that backend specific files exist. + case "$GIT_DEFAULT_REF_FORMAT" in + files) + test_path_is_dir $refdir/refs/heads && + test_path_is_file $refdir/HEAD;; + reftable) + test_path_is_dir $refdir/reftable && + test_path_is_file $refdir/reftable/tables.list;; + *) + BUG "unhandled ref format $GIT_DEFAULT_REF_FORMAT";; + esac +} + +methods="config env" +for method in $methods +do + +test_expect_success "$method: URI is invalid" ' + test_when_finished "rm -rf repo" && + git init repo && + test_refs_backend repo files "reftable@/home/reftable" "$method" \ + "invalid value for ${SQ}extensions.refstorage${SQ}" +' + +test_expect_success "$method: URI ends with colon" ' + test_when_finished "rm -rf repo" && + git init repo && + test_refs_backend repo files "reftable:" "$method" \ + "invalid value for ${SQ}extensions.refstorage${SQ}" +' + +test_expect_success "$method: unknown reference backend" ' + test_when_finished "rm -rf repo" && + git init repo && + test_refs_backend repo files "db://.git" "$method" \ + "invalid value for ${SQ}extensions.refstorage${SQ}" +' + +ref_formats="files reftable" +for from_format in $ref_formats +do + +for to_format in $ref_formats +do + if test "$from_format" = "$to_format" + then + continue + fi + + + for dir in "$(pwd)/repo/.git" "." + do + + test_expect_success "$method: read from $to_format backend, $dir dir" ' + test_when_finished "rm -rf repo" && + git init --ref-format=$from_format repo && + ( + cd repo && + test_commit 1 && + test_commit 2 && + test_commit 3 && + + git refs migrate --dry-run --ref-format=$to_format >out && + BACKEND_PATH="$dir/$(sed "s/.* ${SQ}.git\/\(.*\)${SQ}/\1/" out)" && + test_refs_backend . $from_format "$to_format://$BACKEND_PATH" "$method" + ) + ' + + test_expect_success "$method: write to $to_format backend, $dir dir" ' + test_when_finished "rm -rf repo" && + git init --ref-format=$from_format repo && + ( + cd repo && + test_commit 1 && + test_commit 2 && + test_commit 3 && + + git refs migrate --dry-run --ref-format=$to_format >out && + BACKEND_PATH="$dir/$(sed "s/.* ${SQ}.git\/\(.*\)${SQ}/\1/" out)" && + + test_refs_backend . $from_format "$to_format://$BACKEND_PATH" "$method" && + + git refs list >expect && + run_with_uri . "$from_format" "$to_format://$BACKEND_PATH" \ + "tag -d 1" "$method" && + git refs list >actual && + test_cmp expect actual && + + git refs list | grep -v "refs/tags/1" >expect && + run_with_uri . "$from_format" "$to_format://$BACKEND_PATH" \ + "refs list" "$method" >actual && + test_cmp expect actual + ) + ' + + test_expect_success "$method: with worktree and $to_format backend, $dir dir" ' + test_when_finished "rm -rf repo wt" && + git init --ref-format=$from_format repo && + ( + cd repo && + test_commit 1 && + test_commit 2 && + test_commit 3 && + + git refs migrate --dry-run --ref-format=$to_format >out && + BACKEND_PATH="$dir/$(sed "s/.* ${SQ}.git\/\(.*\)${SQ}/\1/" out)" && + + run_with_uri . "$from_format" "$to_format://$BACKEND_PATH" \ + "worktree add ../wt 2" "$method" && + + run_with_uri . "$from_format" "$to_format://$BACKEND_PATH" \ + "for-each-ref --include-root-refs" "$method" >actual && + run_with_uri ../wt "$from_format" "$to_format://$BACKEND_PATH" \ + "for-each-ref --include-root-refs" "$method" >expect && + ! test_cmp expect actual && + + run_with_uri . "$from_format" "$to_format://$BACKEND_PATH" \ + "rev-parse 2" "$method" >actual && + run_with_uri ../wt "$from_format" "$to_format://$BACKEND_PATH" \ + "rev-parse HEAD" "$method" >expect && + test_cmp expect actual + ) + ' + done # closes dir + + test_expect_success "migrating repository to $to_format with alternate refs directory" ' + test_when_finished "rm -rf repo refdir" && + mkdir refdir && + GIT_REFERENCE_BACKEND="${from_format}://$(pwd)/refdir" git init repo && + ( + cd repo && + + test_commit 1 && + test_commit 2 && + test_commit 3 && + + git refs migrate --ref-format=$to_format && + git refs list >out && + test_grep "refs/tags/1" out && + test_grep "refs/tags/2" out && + test_grep "refs/tags/3" out + ) + ' + +done # closes to_format +done # closes from_format + +done # closes method + +test_expect_success 'initializing repository with alt ref directory' ' + test_when_finished "rm -rf repo refdir" && + mkdir refdir && + BACKEND="$(test_detect_ref_format)://$(pwd)/refdir" && + GIT_REFERENCE_BACKEND=$BACKEND git init repo && + verify_files_exist repo/.git refdir && + ( + cd repo && + + git config get extensions.refstorage >actual && + echo $BACKEND >expect && + test_cmp expect actual && + + test_commit 1 && + test_commit 2 && + test_commit 3 && + git refs list >out && + test_grep "refs/tags/1" out && + test_grep "refs/tags/2" out && + test_grep "refs/tags/3" out + ) +' + +test_expect_success 'cloning repository with alt ref directory' ' + test_when_finished "rm -rf source repo refdir" && + mkdir refdir && + + git init source && + test_commit -C source 1 && + test_commit -C source 2 && + test_commit -C source 3 && + + BACKEND="$(test_detect_ref_format)://$(pwd)/refdir" && + GIT_REFERENCE_BACKEND=$BACKEND git clone source repo && + + git -C repo config get extensions.refstorage >actual && + echo $BACKEND >expect && + test_cmp expect actual && + + verify_files_exist repo/.git refdir && + + git -C source for-each-ref refs/tags/ >expect && + git -C repo for-each-ref refs/tags/ >actual && + test_cmp expect actual +' + +test_done
diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index c4b651c..54e81c2 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh
@@ -105,7 +105,7 @@ echo $ZERO_OID >.git/HEAD && # avoid corrupt/broken HEAD from interfering with repo discovery test_must_fail env GIT_DIR=.git git fsck 2>out && - test_grep "detached HEAD points" out + test_grep "HEAD: badRefOid: points to invalid object ID ${SQ}$ZERO_OID${SQ}" out ' test_expect_success 'HEAD link pointing at a funny place' ' @@ -113,7 +113,7 @@ test-tool ref-store main create-symref HEAD refs/funny/place && # avoid corrupt/broken HEAD from interfering with repo discovery test_must_fail env GIT_DIR=.git git fsck 2>out && - test_grep "HEAD points to something strange" out + test_grep "HEAD: badHeadTarget: HEAD points to non-branch ${SQ}refs/funny/place${SQ}" out ' test_expect_success REFFILES 'HEAD link pointing at a funny object (from different wt)' ' @@ -123,7 +123,7 @@ echo $ZERO_OID >.git/HEAD && # avoid corrupt/broken HEAD from interfering with repo discovery test_must_fail git -C wt fsck 2>out && - test_grep "main-worktree/HEAD: detached HEAD points" out + test_grep "HEAD: badRefOid: points to invalid object ID ${SQ}$ZERO_OID${SQ}" out ' test_expect_success REFFILES 'other worktree HEAD link pointing at a funny object' ' @@ -131,7 +131,7 @@ git worktree add other && echo $ZERO_OID >.git/worktrees/other/HEAD && test_must_fail git fsck 2>out && - test_grep "worktrees/other/HEAD: detached HEAD points" out + test_grep "worktrees/other/HEAD: badRefOid: points to invalid object ID ${SQ}$ZERO_OID${SQ}" out ' test_expect_success 'other worktree HEAD link pointing at missing object' ' @@ -148,7 +148,7 @@ git worktree add other && git -C other symbolic-ref HEAD refs/funny/place && test_must_fail git fsck 2>out && - test_grep "worktrees/other/HEAD points to something strange" out + test_grep "worktrees/other/HEAD: badHeadTarget: HEAD points to non-branch ${SQ}refs/funny/place${SQ}" out ' test_expect_success 'commit with multiple signatures is okay' ' @@ -852,6 +852,44 @@ ! grep corrupt out ' +test_expect_success 'fsck handles multiple packfiles with big blobs' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + + # We construct two packfiles with two objects in common and one + # object not in common. The objects in common can then be + # corrupted in one of the packfiles, respectively. The other + # objects that are unique to the packs are merely used to not + # have both packs contain the same data. + blob_one=$(test-tool genrandom one 200k | git hash-object -t blob -w --stdin) && + blob_two=$(test-tool genrandom two 200k | git hash-object -t blob -w --stdin) && + blob_three=$(test-tool genrandom three 200k | git hash-object -t blob -w --stdin) && + blob_four=$(test-tool genrandom four 200k | git hash-object -t blob -w --stdin) && + pack_one=$(printf "%s\n" "$blob_one" "$blob_two" "$blob_three" | git pack-objects .git/objects/pack/pack) && + pack_two=$(printf "%s\n" "$blob_two" "$blob_three" "$blob_four" | git pack-objects .git/objects/pack/pack) && + chmod a+w .git/objects/pack/pack-*.pack && + + # Corrupt blob two in the first pack. + git verify-pack -v .git/objects/pack/pack-$pack_one >objects && + offset_one=$(sed <objects -n "s/^$blob_two .* \(.*\)$/\1/p") && + printf "\0" | dd of=.git/objects/pack/pack-$pack_one.pack bs=1 conv=notrunc seek=$offset_one && + + # Corrupt blob three in the second pack. + git verify-pack -v .git/objects/pack/pack-$pack_two >objects && + offset_two=$(sed <objects -n "s/^$blob_three .* \(.*\)$/\1/p") && + printf "\0" | dd of=.git/objects/pack/pack-$pack_two.pack bs=1 conv=notrunc seek=$offset_two && + + # We now expect to see two failures for the corrupted objects, + # even though they exist in a non-corrupted form in the + # respective other pack. + test_must_fail git -c core.bigFileThreshold=100k fsck 2>err && + test_grep "unknown object type 0 at offset $offset_one in .git/objects/pack/pack-$pack_one.pack" err && + test_grep "unknown object type 0 at offset $offset_two in .git/objects/pack/pack-$pack_two.pack" err + ) +' + test_expect_success 'fsck fails on corrupt packfile' ' hsh=$(git commit-tree -m mycommit HEAD^{tree}) && pack=$(echo $hsh | git pack-objects .git/objects/pack/pack) && @@ -918,7 +956,7 @@ test_expect_success 'fsck detects truncated loose object' ' # make it big enough that we know we will truncate in the data # portion, not the header - test-tool genrandom truncate 4096 >file && + test-tool genrandom truncate 4k >file && blob=$(git hash-object -w file) && file=$(sha1_file $blob) && test_when_finished "remove_object $blob" &&
diff --git a/t/t1460-refs-migrate.sh b/t/t1460-refs-migrate.sh index 0e1116a..5246468 100755 --- a/t/t1460-refs-migrate.sh +++ b/t/t1460-refs-migrate.sh
@@ -276,11 +276,11 @@ test_when_finished "rm -rf repo" && git init --ref-format=files repo && test_commit -C repo first && - printf "create refs/heads/ref-%d HEAD\n" $(test_seq 5000) >stdin && - git -C repo update-ref --stdin <stdin && + test_seq -f "create refs/heads/ref-%d HEAD" 5000 | + git -C repo update-ref --stdin && test_commit -C repo second && - printf "update refs/heads/ref-%d HEAD\n" $(test_seq 3000) >stdin && - git -C repo update-ref --stdin <stdin && + test_seq -f "update refs/heads/ref-%d HEAD" 3000 | + git -C repo update-ref --stdin && test_migration repo reftable true '
diff --git a/t/t1463-refs-optimize.sh b/t/t1463-refs-optimize.sh index c11c905..9afe3c1 100755 --- a/t/t1463-refs-optimize.sh +++ b/t/t1463-refs-optimize.sh
@@ -15,3 +15,5 @@ pack_refs='refs optimize' . "$TEST_DIRECTORY"/pack-refs-tests.sh + +test_done
diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh index 7739ab6..98c5a77 100755 --- a/t/t1500-rev-parse.sh +++ b/t/t1500-rev-parse.sh
@@ -208,7 +208,7 @@ ' -test_expect_success 'rev-parse --show-object-format in repo with compat mode' ' +test_expect_success RUST 'rev-parse --show-object-format in repo with compat mode' ' mkdir repo && ( sane_unset GIT_DEFAULT_HASH &&
diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh index 4feaf0d..96749fc 100755 --- a/t/t1800-hook.sh +++ b/t/t1800-hook.sh
@@ -1,28 +1,118 @@ #!/bin/sh -test_description='git-hook command' +test_description='git-hook command and config-managed multihooks' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-terminal.sh +setup_hooks () { + test_config hook.ghi.command "/path/ghi" + test_config hook.ghi.event pre-commit --add + test_config hook.ghi.event test-hook --add + test_config_global hook.def.command "/path/def" + test_config_global hook.def.event pre-commit --add +} + +setup_hookdir () { + mkdir .git/hooks + write_script .git/hooks/pre-commit <<-EOF + echo \"Legacy Hook\" + EOF + test_when_finished rm -rf .git/hooks +} + test_expect_success 'git hook usage' ' test_expect_code 129 git hook && test_expect_code 129 git hook run && test_expect_code 129 git hook run -h && test_expect_code 129 git hook run --unknown 2>err && + test_expect_code 129 git hook list && + test_expect_code 129 git hook list -h && grep "unknown option" err ' +test_expect_success 'git hook list: unknown hook name is rejected' ' + test_must_fail git hook list prereceive 2>err && + test_grep "unknown hook event" err +' + +test_expect_success 'git hook run: unknown hook name is rejected' ' + test_must_fail git hook run prereceive 2>err && + test_grep "unknown hook event" err +' + +test_expect_success 'git hook list: known hook name is accepted' ' + test_must_fail git hook list pre-receive 2>err && + test_grep ! "unknown hook event" err +' + +test_expect_success 'git hook run: known hook name is accepted' ' + git hook run --ignore-missing pre-receive 2>err && + test_grep ! "unknown hook event" err +' + +test_expect_success 'git hook run: --allow-unknown-hook-name overrides rejection' ' + git hook run --allow-unknown-hook-name --ignore-missing custom-hook 2>err && + test_grep ! "unknown hook event" err +' + +test_expect_success 'git hook list: --allow-unknown-hook-name overrides rejection' ' + test_must_fail git hook list --allow-unknown-hook-name custom-hook 2>err && + test_grep ! "unknown hook event" err +' + +test_expect_success 'git hook list: nonexistent hook' ' + cat >stderr.expect <<-\EOF && + warning: no hooks found for event '\''test-hook'\'' + EOF + test_expect_code 1 git hook list --allow-unknown-hook-name test-hook 2>stderr.actual && + test_cmp stderr.expect stderr.actual +' + +test_expect_success 'git hook list: traditional hook from hookdir' ' + test_hook test-hook <<-EOF && + echo Test hook + EOF + + cat >expect <<-\EOF && + hook from hookdir + EOF + git hook list --allow-unknown-hook-name test-hook >actual && + test_cmp expect actual +' + +test_expect_success 'git hook list: configured hook' ' + test_config hook.myhook.command "echo Hello" && + test_config hook.myhook.event test-hook --add && + + echo "myhook" >expect && + git hook list --allow-unknown-hook-name test-hook >actual && + test_cmp expect actual +' + +test_expect_success 'git hook list: -z shows NUL-terminated output' ' + test_hook test-hook <<-EOF && + echo Test hook + EOF + test_config hook.myhook.command "echo Hello" && + test_config hook.myhook.event test-hook --add && + + printf "myhookQhook from hookdirQ" >expect && + git hook list --allow-unknown-hook-name -z test-hook >actual.raw && + nul_to_q <actual.raw >actual && + test_cmp expect actual +' + test_expect_success 'git hook run: nonexistent hook' ' cat >stderr.expect <<-\EOF && error: cannot find a hook named test-hook EOF - test_expect_code 1 git hook run test-hook 2>stderr.actual && + test_expect_code 1 git hook run --allow-unknown-hook-name test-hook 2>stderr.actual && test_cmp stderr.expect stderr.actual ' test_expect_success 'git hook run: nonexistent hook with --ignore-missing' ' - git hook run --ignore-missing does-not-exist 2>stderr.actual && + git hook run --allow-unknown-hook-name --ignore-missing does-not-exist 2>stderr.actual && test_must_be_empty stderr.actual ' @@ -34,7 +124,7 @@ cat >expect <<-\EOF && Test hook EOF - git hook run test-hook 2>actual && + git hook run --allow-unknown-hook-name test-hook 2>actual && test_cmp expect actual ' @@ -48,7 +138,7 @@ Will end up on stderr Will end up on stderr EOF - git hook run test-hook >stdout.actual 2>stderr.actual && + git hook run --allow-unknown-hook-name test-hook >stdout.actual 2>stderr.actual && test_cmp stderr.expect stderr.actual && test_must_be_empty stdout.actual ' @@ -60,12 +150,12 @@ exit $code EOF - test_expect_code $code git hook run test-hook + test_expect_code $code git hook run --allow-unknown-hook-name test-hook ' done test_expect_success 'git hook run arg u ments without -- is not allowed' ' - test_expect_code 129 git hook run test-hook arg u ments + test_expect_code 129 git hook run --allow-unknown-hook-name test-hook arg u ments ' test_expect_success 'git hook run -- pass arguments' ' @@ -79,16 +169,22 @@ u ments EOF - git hook run test-hook -- arg "u ments" 2>actual && + git hook run --allow-unknown-hook-name test-hook -- arg "u ments" 2>actual && test_cmp expect actual ' -test_expect_success 'git hook run -- out-of-repo runs excluded' ' - test_hook test-hook <<-EOF && - echo Test hook - EOF +test_expect_success 'git hook run: out-of-repo runs execute global hooks' ' + test_config_global hook.global-hook.event test-hook --add && + test_config_global hook.global-hook.command "echo no repo no problems" --add && - nongit test_must_fail git hook run test-hook + echo "global-hook" >expect && + nongit git hook list --allow-unknown-hook-name test-hook >actual && + test_cmp expect actual && + + echo "no repo no problems" >expect && + + nongit git hook run --allow-unknown-hook-name test-hook 2>actual && + test_cmp expect actual ' test_expect_success 'git -c core.hooksPath=<PATH> hook run' ' @@ -112,11 +208,11 @@ # Test various ways of specifying the path. See also # t1350-config-hooks-path.sh >actual && - git hook run test-hook -- ignored 2>>actual && - git -c core.hooksPath=my-hooks hook run test-hook -- one 2>>actual && - git -c core.hooksPath=my-hooks/ hook run test-hook -- two 2>>actual && - git -c core.hooksPath="$PWD/my-hooks" hook run test-hook -- three 2>>actual && - git -c core.hooksPath="$PWD/my-hooks/" hook run test-hook -- four 2>>actual && + git hook run --allow-unknown-hook-name test-hook -- ignored 2>>actual && + git -c core.hooksPath=my-hooks hook run --allow-unknown-hook-name test-hook -- one 2>>actual && + git -c core.hooksPath=my-hooks/ hook run --allow-unknown-hook-name test-hook -- two 2>>actual && + git -c core.hooksPath="$PWD/my-hooks" hook run --allow-unknown-hook-name test-hook -- three 2>>actual && + git -c core.hooksPath="$PWD/my-hooks/" hook run --allow-unknown-hook-name test-hook -- four 2>>actual && test_cmp expect actual ' @@ -150,6 +246,246 @@ test_hook_tty commit -m"B.new" ' +test_expect_success 'git hook list orders by config order' ' + setup_hooks && + + cat >expected <<-\EOF && + def + ghi + EOF + + git hook list pre-commit >actual && + test_cmp expected actual +' + +test_expect_success 'git hook list reorders on duplicate event declarations' ' + setup_hooks && + + # 'def' is usually configured globally; move it to the end by + # configuring it locally. + test_config hook.def.event "pre-commit" --add && + + cat >expected <<-\EOF && + ghi + def + EOF + + git hook list pre-commit >actual && + test_cmp expected actual +' + +test_expect_success 'git hook list: empty event value resets events' ' + setup_hooks && + + # ghi is configured for pre-commit; reset it with an empty value + test_config hook.ghi.event "" --add && + + # only def should remain for pre-commit + echo "def" >expected && + git hook list pre-commit >actual && + test_cmp expected actual +' + +test_expect_success 'hook can be configured for multiple events' ' + setup_hooks && + + # 'ghi' should be included in both 'pre-commit' and 'test-hook' + git hook list pre-commit >actual && + grep "ghi" actual && + git hook list --allow-unknown-hook-name test-hook >actual && + grep "ghi" actual +' + +test_expect_success 'git hook list shows hooks from the hookdir' ' + setup_hookdir && + + cat >expected <<-\EOF && + hook from hookdir + EOF + + git hook list pre-commit >actual && + test_cmp expected actual +' + +test_expect_success 'inline hook definitions execute oneliners' ' + test_config hook.oneliner.event "pre-commit" && + test_config hook.oneliner.command "echo \"Hello World\"" && + + echo "Hello World" >expected && + + # hooks are run with stdout_to_stderr = 1 + git hook run pre-commit 2>actual && + test_cmp expected actual +' + +test_expect_success 'inline hook definitions resolve paths' ' + write_script sample-hook.sh <<-\EOF && + echo \"Sample Hook\" + EOF + + test_when_finished "rm sample-hook.sh" && + + test_config hook.sample-hook.event pre-commit && + test_config hook.sample-hook.command "\"$(pwd)/sample-hook.sh\"" && + + echo \"Sample Hook\" >expected && + + # hooks are run with stdout_to_stderr = 1 + git hook run pre-commit 2>actual && + test_cmp expected actual +' + +test_expect_success 'hookdir hook included in git hook run' ' + setup_hookdir && + + echo \"Legacy Hook\" >expected && + + # hooks are run with stdout_to_stderr = 1 + git hook run pre-commit 2>actual && + test_cmp expected actual +' + +test_expect_success 'stdin to multiple hooks' ' + test_config hook.stdin-a.event "test-hook" && + test_config hook.stdin-a.command "xargs -P1 -I% echo a%" && + test_config hook.stdin-b.event "test-hook" && + test_config hook.stdin-b.command "xargs -P1 -I% echo b%" && + + cat >input <<-\EOF && + 1 + 2 + 3 + EOF + + cat >expected <<-\EOF && + a1 + a2 + a3 + b1 + b2 + b3 + EOF + + git hook run --allow-unknown-hook-name --to-stdin=input test-hook 2>actual && + test_cmp expected actual +' + +test_expect_success 'rejects hooks with no commands configured' ' + test_config hook.broken.event "test-hook" && + test_must_fail git hook list --allow-unknown-hook-name test-hook 2>actual && + test_grep "hook.broken.command" actual && + test_must_fail git hook run --allow-unknown-hook-name test-hook 2>actual && + test_grep "hook.broken.command" actual +' + +test_expect_success 'disabled hook is not run' ' + test_config hook.skipped.event "test-hook" && + test_config hook.skipped.command "echo \"Should not run\"" && + test_config hook.skipped.enabled false && + + git hook run --allow-unknown-hook-name --ignore-missing test-hook 2>actual && + test_must_be_empty actual +' + +test_expect_success 'disabled hook with no command warns' ' + test_config hook.nocommand.event "pre-commit" && + test_config hook.nocommand.enabled false && + + git hook list pre-commit 2>actual && + test_grep "disabled hook.*nocommand.*no command configured" actual +' + +test_expect_success 'disabled hook appears as disabled in git hook list' ' + test_config hook.active.event "pre-commit" && + test_config hook.active.command "echo active" && + test_config hook.inactive.event "pre-commit" && + test_config hook.inactive.command "echo inactive" && + test_config hook.inactive.enabled false && + + git hook list pre-commit >actual && + test_grep "^active$" actual && + test_grep "^disabled inactive$" actual +' + +test_expect_success 'disabled hook shows scope with --show-scope' ' + test_config hook.myhook.event "pre-commit" && + test_config hook.myhook.command "echo hi" && + test_config hook.myhook.enabled false && + + git hook list --show-scope pre-commit >actual && + test_grep "^local disabled myhook$" actual +' + +test_expect_success 'disabled configured hook is not reported as existing by hook_exists' ' + test_when_finished "rm -f git-bugreport-hook-exists-test.txt" && + test_config hook.linter.event "pre-commit" && + test_config hook.linter.command "echo lint" && + test_config hook.linter.enabled false && + + git bugreport -s hook-exists-test && + test_grep ! "pre-commit" git-bugreport-hook-exists-test.txt +' + +test_expect_success 'globally disabled hook can be re-enabled locally' ' + test_config_global hook.global-hook.event "test-hook" && + test_config_global hook.global-hook.command "echo \"global-hook ran\"" && + test_config_global hook.global-hook.enabled false && + test_config hook.global-hook.enabled true && + + echo "global-hook ran" >expected && + git hook run --allow-unknown-hook-name test-hook 2>actual && + test_cmp expected actual +' + +test_expect_success 'configured hooks run before hookdir hook' ' + setup_hookdir && + test_config hook.first.event "pre-commit" && + test_config hook.first.command "echo first" && + test_config hook.second.event "pre-commit" && + test_config hook.second.command "echo second" && + + cat >expected <<-\EOF && + first + second + hook from hookdir + EOF + + git hook list pre-commit >actual && + test_cmp expected actual && + + # "Legacy Hook" is the output of the hookdir pre-commit script + # written by setup_hookdir() above. + cat >expected <<-\EOF && + first + second + "Legacy Hook" + EOF + + git hook run pre-commit 2>actual && + test_cmp expected actual +' + +test_expect_success 'git hook list --show-scope shows config scope' ' + setup_hookdir && + test_config_global hook.global-hook.command "echo global" && + test_config_global hook.global-hook.event pre-commit --add && + test_config hook.local-hook.command "echo local" && + test_config hook.local-hook.event pre-commit --add && + + cat >expected <<-\EOF && + global global-hook + local local-hook + hook from hookdir + EOF + git hook list --show-scope pre-commit >actual && + test_cmp expected actual && + + # without --show-scope the scope must not appear + git hook list pre-commit >actual && + test_grep ! "^global " actual && + test_grep ! "^local " actual +' + test_expect_success 'git hook run a hook with a bad shebang' ' test_when_finished "rm -rf bad-hooks" && mkdir bad-hooks && @@ -157,7 +493,7 @@ test_expect_code 1 git \ -c core.hooksPath=bad-hooks \ - hook run test-hook >out 2>err && + hook run --allow-unknown-hook-name test-hook >out 2>err && test_must_be_empty out && # TODO: We should emit the same (or at least a more similar) @@ -167,6 +503,7 @@ ' test_expect_success 'stdin to hooks' ' + mkdir -p .git/hooks && write_script .git/hooks/test-hook <<-\EOF && echo BEGIN stdin cat @@ -180,8 +517,145 @@ EOF echo hello >input && - git hook run --to-stdin=input test-hook 2>actual && + git hook run --allow-unknown-hook-name --to-stdin=input test-hook 2>actual && test_cmp expect actual ' +check_stdout_separate_from_stderr () { + for hook in "$@" + do + # Ensure hook's stdout is only in stdout, not stderr + test_grep "Hook $hook stdout" stdout.actual || return 1 + test_grep ! "Hook $hook stdout" stderr.actual || return 1 + + # Ensure hook's stderr is only in stderr, not stdout + test_grep "Hook $hook stderr" stderr.actual || return 1 + test_grep ! "Hook $hook stderr" stdout.actual || return 1 + done +} + +check_stdout_merged_to_stderr () { + for hook in "$@" + do + # Ensure hook's stdout is only in stderr, not stdout + test_grep "Hook $hook stdout" stderr.actual || return 1 + test_grep ! "Hook $hook stdout" stdout.actual || return 1 + + # Ensure hook's stderr is only in stderr, not stdout + test_grep "Hook $hook stderr" stderr.actual || return 1 + test_grep ! "Hook $hook stderr" stdout.actual || return 1 + done +} + +setup_hooks () { + for hook in "$@" + do + test_hook $hook <<-EOF + echo >&1 Hook $hook stdout + echo >&2 Hook $hook stderr + EOF + done +} + +test_expect_success 'client hooks: pre-push expects separate stdout and stderr' ' + test_when_finished "rm -f stdout.actual stderr.actual" && + git init --bare remote && + git remote add origin remote && + test_commit A && + setup_hooks pre-push && + git push origin HEAD:main >stdout.actual 2>stderr.actual && + check_stdout_separate_from_stderr pre-push +' + +test_expect_success 'client hooks: commit hooks expect stdout redirected to stderr' ' + hooks="pre-commit prepare-commit-msg \ + commit-msg post-commit \ + reference-transaction" && + setup_hooks $hooks && + test_when_finished "rm -f stdout.actual stderr.actual" && + git checkout -B main && + git checkout -b branch-a && + test_commit commit-on-branch-a && + git commit --allow-empty -m "Test" >stdout.actual 2>stderr.actual && + check_stdout_merged_to_stderr $hooks +' + +test_expect_success 'client hooks: checkout hooks expect stdout redirected to stderr' ' + setup_hooks post-checkout reference-transaction && + test_when_finished "rm -f stdout.actual stderr.actual" && + git checkout -b new-branch main >stdout.actual 2>stderr.actual && + check_stdout_merged_to_stderr post-checkout reference-transaction +' + +test_expect_success 'client hooks: merge hooks expect stdout redirected to stderr' ' + setup_hooks pre-merge-commit post-merge reference-transaction && + test_when_finished "rm -f stdout.actual stderr.actual" && + test_commit new-branch-commit && + git merge --no-ff branch-a >stdout.actual 2>stderr.actual && + check_stdout_merged_to_stderr pre-merge-commit post-merge reference-transaction +' + +test_expect_success 'client hooks: post-rewrite hooks expect stdout redirected to stderr' ' + setup_hooks post-rewrite reference-transaction && + test_when_finished "rm -f stdout.actual stderr.actual" && + git commit --amend --allow-empty --no-edit >stdout.actual 2>stderr.actual && + check_stdout_merged_to_stderr post-rewrite reference-transaction +' + +test_expect_success 'client hooks: applypatch hooks expect stdout redirected to stderr' ' + setup_hooks applypatch-msg pre-applypatch post-applypatch && + test_when_finished "rm -f stdout.actual stderr.actual" && + git checkout -b branch-b main && + test_commit branch-b && + git format-patch -1 --stdout >patch && + git checkout -b branch-c main && + git am patch >stdout.actual 2>stderr.actual && + check_stdout_merged_to_stderr applypatch-msg pre-applypatch post-applypatch +' + +test_expect_success 'client hooks: rebase hooks expect stdout redirected to stderr' ' + setup_hooks pre-rebase && + test_when_finished "rm -f stdout.actual stderr.actual" && + git checkout -b branch-d main && + test_commit branch-d && + git checkout main && + test_commit diverge-main && + git checkout branch-d && + git rebase main >stdout.actual 2>stderr.actual && + check_stdout_merged_to_stderr pre-rebase +' + +test_expect_success 'client hooks: post-index-change expects stdout redirected to stderr' ' + setup_hooks post-index-change && + test_when_finished "rm -f stdout.actual stderr.actual" && + oid=$(git hash-object -w --stdin </dev/null) && + git update-index --add --cacheinfo 100644 $oid new-file \ + >stdout.actual 2>stderr.actual && + check_stdout_merged_to_stderr post-index-change +' + +test_expect_success 'server hooks expect stdout redirected to stderr' ' + test_when_finished "rm -f stdout.actual stderr.actual" && + git init --bare remote-server && + git remote add origin-server remote-server && + cd remote-server && + setup_hooks pre-receive update post-receive post-update && + cd .. && + git push origin-server HEAD:new-branch >stdout.actual 2>stderr.actual && + check_stdout_merged_to_stderr pre-receive update post-receive post-update +' + +test_expect_success 'server push-to-checkout hook expects stdout redirected to stderr' ' + test_when_finished "rm -f stdout.actual stderr.actual" && + git init server && + git -C server checkout -b main && + test_config -C server receive.denyCurrentBranch updateInstead && + git remote add origin-server-2 server && + cd server && + setup_hooks push-to-checkout && + cd .. && + git push origin-server-2 HEAD:main >stdout.actual 2>stderr.actual && + check_stdout_merged_to_stderr push-to-checkout +' + test_done
diff --git a/t/t1900-repo.sh b/t/t1900-repo-info.sh similarity index 66% rename from t/t1900-repo.sh rename to t/t1900-repo-info.sh index 2beba67..39bb77d 100755 --- a/t/t1900-repo.sh +++ b/t/t1900-repo-info.sh
@@ -25,7 +25,7 @@ eval "$init_command $repo_name" ' - test_expect_success "keyvalue: $label" ' + test_expect_success "lines: $label" ' echo "$key=$expected_value" > expect && git -C "$repo_name" repo info "$key" >actual && test_cmp expect actual @@ -106,8 +106,53 @@ test_expect_success 'git repo info uses the last requested format' ' echo "layout.bare=false" >expected && - git repo info --format=nul -z --format=keyvalue layout.bare >actual && + git repo info --format=nul -z --format=lines layout.bare >actual && test_cmp expected actual ' +test_expect_success 'git repo info --all and git repo info $(git repo info --keys) output the same data' ' + git repo info $(git repo info --keys) >expect && + git repo info --all >actual && + test_cmp expect actual +' + +test_expect_success 'git repo info --all <key> aborts' ' + echo "fatal: --all and <key> cannot be used together" >expect && + test_must_fail git repo info --all object.format 2>actual && + test_cmp expect actual +' + +test_expect_success 'git repo info --keys --format=nul uses nul-terminated output' ' + git repo info --keys --format=lines >lines && + lf_to_nul <lines >expect && + git repo info --keys --format=nul >actual && + test_cmp expect actual +' + +test_expect_success 'git repo info --keys aborts when using --format other than lines or nul' ' + echo "fatal: --keys can only be used with --format=lines or --format=nul" >expect && + test_must_fail git repo info --keys --format=table 2>actual && + test_cmp expect actual +' + +test_expect_success 'git repo info --keys aborts when requesting keys' ' + echo "fatal: --keys cannot be used with a <key> or --all" >expect && + test_must_fail git repo info --keys --all 2>actual_all && + test_must_fail git repo info --keys some.key 2>actual_key && + test_cmp expect actual_all && + test_cmp expect actual_key +' + +test_expect_success 'git repo info --keys uses lines as its default output format' ' + git repo info --keys --format=lines >expect && + git repo info --keys >actual && + test_cmp expect actual +' + +test_expect_success 'git repo info -h shows only repo info usage' ' + test_must_fail git repo info -h >actual && + test_grep "git repo info" actual && + test_grep ! "git repo structure" actual +' + test_done
diff --git a/t/t1901-repo-structure.sh b/t/t1901-repo-structure.sh index 36a71a1..10050ab 100755 --- a/t/t1901-repo-structure.sh +++ b/t/t1901-repo-structure.sh
@@ -4,27 +4,66 @@ . ./test-lib.sh +object_type_disk_usage() { + disk_usage_opt="--disk-usage" + + if test "$2" = "true" + then + disk_usage_opt="--disk-usage=human" + fi + + if test "$1" = "all" + then + git rev-list --all --objects $disk_usage_opt + else + git rev-list --all --objects $disk_usage_opt \ + --filter=object:type=$1 --filter-provided-objects + fi +} + test_expect_success 'empty repository' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && cat >expect <<-\EOF && - | Repository structure | Value | - | -------------------- | ----- | - | * References | | - | * Count | 0 | - | * Branches | 0 | - | * Tags | 0 | - | * Remotes | 0 | - | * Others | 0 | - | | | - | * Reachable objects | | - | * Count | 0 | - | * Commits | 0 | - | * Trees | 0 | - | * Blobs | 0 | - | * Tags | 0 | + | Repository structure | Value | + | ------------------------- | ------ | + | * References | | + | * Count | 0 | + | * Branches | 0 | + | * Tags | 0 | + | * Remotes | 0 | + | * Others | 0 | + | | | + | * Reachable objects | | + | * Count | 0 | + | * Commits | 0 | + | * Trees | 0 | + | * Blobs | 0 | + | * Tags | 0 | + | * Inflated size | 0 B | + | * Commits | 0 B | + | * Trees | 0 B | + | * Blobs | 0 B | + | * Tags | 0 B | + | * Disk size | 0 B | + | * Commits | 0 B | + | * Trees | 0 B | + | * Blobs | 0 B | + | * Tags | 0 B | + | | | + | * Largest objects | | + | * Commits | | + | * Maximum size | 0 B | + | * Maximum parents | 0 | + | * Trees | | + | * Maximum size | 0 B | + | * Maximum entries | 0 | + | * Blobs | | + | * Maximum size | 0 B | + | * Tags | | + | * Maximum size | 0 B | EOF git repo structure >out 2>err && @@ -34,12 +73,12 @@ ) ' -test_expect_success 'repository with references and objects' ' +test_expect_success SHA1 'repository with references and objects' ' test_when_finished "rm -rf repo" && git init repo && ( cd repo && - test_commit_bulk 42 && + test_commit_bulk 1005 && git tag -a foo -m bar && oid="$(git rev-parse HEAD)" && @@ -48,22 +87,54 @@ # Also creates a commit, tree, and blob. git notes add -m foo && - cat >expect <<-\EOF && - | Repository structure | Value | - | -------------------- | ----- | - | * References | | - | * Count | 4 | - | * Branches | 1 | - | * Tags | 1 | - | * Remotes | 1 | - | * Others | 1 | - | | | - | * Reachable objects | | - | * Count | 130 | - | * Commits | 43 | - | * Trees | 43 | - | * Blobs | 43 | - | * Tags | 1 | + # The tags disk size is handled specially due to the + # git-rev-list(1) --disk-usage=human option printing the full + # "byte/bytes" unit string instead of just "B". + cat >expect <<-EOF && + | Repository structure | Value | + | ------------------------- | ---------- | + | * References | | + | * Count | 4 | + | * Branches | 1 | + | * Tags | 1 | + | * Remotes | 1 | + | * Others | 1 | + | | | + | * Reachable objects | | + | * Count | 3.02 k | + | * Commits | 1.01 k | + | * Trees | 1.01 k | + | * Blobs | 1.01 k | + | * Tags | 1 | + | * Inflated size | 16.03 MiB | + | * Commits | 217.92 KiB | + | * Trees | 15.81 MiB | + | * Blobs | 11.68 KiB | + | * Tags | 132 B | + | * Disk size | $(object_type_disk_usage all true) | + | * Commits | $(object_type_disk_usage commit true) | + | * Trees | $(object_type_disk_usage tree true) | + | * Blobs | $(object_type_disk_usage blob true) | + | * Tags | $(object_type_disk_usage tag) B | + | | | + | * Largest objects | | + | * Commits | | + | * Maximum size [1] | 223 B | + | * Maximum parents [2] | 1 | + | * Trees | | + | * Maximum size [3] | 32.29 KiB | + | * Maximum entries [4] | 1.01 k | + | * Blobs | | + | * Maximum size [5] | 13 B | + | * Tags | | + | * Maximum size [6] | 132 B | + + [1] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a + [2] 0dc91eb18580102a3a216c8bfecedeba2b9f9b9a + [3] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c + [4] 60665251ab71dbd8c18d9bf2174f4ee0d58aa06c + [5] 97d808e45116bf02103490294d3d46dad7a2ac62 + [6] 4dae4f5954f5e6feb3577cfb1b181daa3fd3afd2 EOF git repo structure >out 2>err && @@ -73,7 +144,7 @@ ) ' -test_expect_success 'keyvalue and nul format' ' +test_expect_success SHA1 'lines and nul format' ' test_when_finished "rm -rf repo" && git init repo && ( @@ -81,7 +152,7 @@ test_commit_bulk 42 && git tag -a foo -m bar && - cat >expect <<-\EOF && + cat >expect <<-EOF && references.branches.count=1 references.tags.count=1 references.remotes.count=0 @@ -90,18 +161,45 @@ objects.trees.count=42 objects.blobs.count=42 objects.tags.count=1 + objects.commits.inflated_size=9225 + objects.trees.inflated_size=28554 + objects.blobs.inflated_size=453 + objects.tags.inflated_size=132 + objects.commits.disk_size=$(object_type_disk_usage commit) + objects.trees.disk_size=$(object_type_disk_usage tree) + objects.blobs.disk_size=$(object_type_disk_usage blob) + objects.tags.disk_size=$(object_type_disk_usage tag) + objects.commits.max_size=221 + objects.commits.max_size_oid=de3508174b5c2ace6993da67cae9be9069e2df39 + objects.trees.max_size=1335 + objects.trees.max_size_oid=09931deea9d81ec21300d3e13c74412f32eacec5 + objects.blobs.max_size=11 + objects.blobs.max_size_oid=eaeeedced46482bd4281fda5a5f05ce24854151f + objects.tags.max_size=132 + objects.tags.max_size_oid=1ee0f2b16ea37d895dbe9dbd76cd2ac70446176c + objects.commits.max_parents=1 + objects.commits.max_parents_oid=de3508174b5c2ace6993da67cae9be9069e2df39 + objects.trees.max_entries=42 + objects.trees.max_entries_oid=09931deea9d81ec21300d3e13c74412f32eacec5 EOF - git repo structure --format=keyvalue >out 2>err && + git repo structure --format=lines >out 2>err && test_cmp expect out && test_line_count = 0 err && - # Replace key and value delimiters for nul format. - tr "\n=" "\0\n" <expect >expect_nul && git repo structure --format=nul >out 2>err && + tr "\012\000" "=\012" <out >actual && - test_cmp expect_nul out && + test_cmp expect actual && + test_line_count = 0 err && + + # "-z", as a synonym to "--format=nul", participates in the + # usual "last one wins" rule. + git repo structure --format=table -z >out 2>err && + tr "\012\000" "=\012" <out >actual && + + test_cmp expect actual && test_line_count = 0 err ) ' @@ -126,4 +224,10 @@ ) ' +test_expect_success 'git repo structure -h shows only repo structure usage' ' + test_must_fail git repo structure -h >actual && + test_grep "git repo structure" actual && + test_grep ! "git repo info" actual +' + test_done
diff --git a/t/t2000-conflict-when-checking-files-out.sh b/t/t2000-conflict-when-checking-files-out.sh index f18616a..af199d8 100755 --- a/t/t2000-conflict-when-checking-files-out.sh +++ b/t/t2000-conflict-when-checking-files-out.sh
@@ -35,30 +35,30 @@ sed -e 's/^\([0-9]*\) [^ ]* [0-9a-f]* /tr: \1 /' } -date >path0 -mkdir path1 -date >path1/file1 +test_expect_success 'prepare files path0 and path1/file1' ' + date >path0 && + mkdir path1 && + date >path1/file1 && + git update-index --add path0 path1/file1 +' -test_expect_success \ - 'git update-index --add various paths.' \ - 'git update-index --add path0 path1/file1' +test_expect_success 'prepare working tree files with D/F conflicts' ' + rm -fr path0 path1 && + mkdir path0 && + date >path0/file0 && + date >path1 +' -rm -fr path0 path1 -mkdir path0 -date >path0/file0 -date >path1 +test_expect_success 'git checkout-index without -f should fail on conflicting work tree.' ' + test_must_fail git checkout-index -a +' -test_expect_success \ - 'git checkout-index without -f should fail on conflicting work tree.' \ - 'test_must_fail git checkout-index -a' - -test_expect_success \ - 'git checkout-index with -f should succeed.' \ - 'git checkout-index -f -a' - -test_expect_success \ - 'git checkout-index conflicting paths.' \ - 'test -f path0 && test -d path1 && test -f path1/file1' +test_expect_success 'git checkout-index with -f should succeed.' ' + git checkout-index -f -a && + test_path_is_file path0 && + test_path_is_dir path1 && + test_path_is_file path1/file1 +' test_expect_success SYMLINKS 'checkout-index -f twice with --prefix' ' mkdir -p tar/get && @@ -83,53 +83,63 @@ # path path3 is occupied by a non-directory. With "-f" it should remove # the symlink path3 and create directory path3 and file path3/file1. -mkdir path2 -date >path2/file0 -test_expect_success \ - 'git update-index --add path2/file0' \ - 'git update-index --add path2/file0' -test_expect_success \ - 'writing tree out with git write-tree' \ - 'tree1=$(git write-tree)' +test_expect_success 'prepare path2/file0 and index' ' + mkdir path2 && + date >path2/file0 && + git update-index --add path2/file0 +' + +test_expect_success 'write tree with path2/file0' ' + tree1=$(git write-tree) +' + test_debug 'show_files $tree1' -mkdir path3 -date >path3/file1 -test_expect_success \ - 'git update-index --add path3/file1' \ - 'git update-index --add path3/file1' -test_expect_success \ - 'writing tree out with git write-tree' \ - 'tree2=$(git write-tree)' +test_expect_success 'prepare path3/file1 and index' ' + mkdir path3 && + date >path3/file1 && + git update-index --add path3/file1 +' + +test_expect_success 'write tree with path3/file1' ' + tree2=$(git write-tree) +' + test_debug 'show_files $tree2' -rm -fr path3 -test_expect_success \ - 'read previously written tree and checkout.' \ - 'git read-tree -m $tree1 && git checkout-index -f -a' +test_expect_success 'read previously written tree and checkout.' ' + rm -fr path3 && + git read-tree -m $tree1 && + git checkout-index -f -a +' + test_debug 'show_files $tree1' -test_expect_success \ - 'add a symlink' \ - 'test_ln_s_add path2 path3' -test_expect_success \ - 'writing tree out with git write-tree' \ - 'tree3=$(git write-tree)' +test_expect_success 'add a symlink' ' + test_ln_s_add path2 path3 +' + +test_expect_success 'write tree with symlink path3' ' + tree3=$(git write-tree) +' + test_debug 'show_files $tree3' # Morten says "Got that?" here. # Test begins. -test_expect_success \ - 'read previously written tree and checkout.' \ - 'git read-tree $tree2 && git checkout-index -f -a' +test_expect_success 'read previously written tree and checkout.' ' + git read-tree $tree2 && + git checkout-index -f -a +' + test_debug 'show_files $tree2' -test_expect_success \ - 'checking out conflicting path with -f' \ - 'test ! -h path2 && test -d path2 && - test ! -h path3 && test -d path3 && - test ! -h path2/file0 && test -f path2/file0 && - test ! -h path3/file1 && test -f path3/file1' +test_expect_success 'checking out conflicting path with -f' ' + test_path_is_dir_not_symlink path2 && + test_path_is_dir_not_symlink path3 && + test_path_is_file_not_symlink path2/file0 && + test_path_is_file_not_symlink path3/file1 +' test_done
diff --git a/t/t2003-checkout-cache-mkdir.sh b/t/t2003-checkout-cache-mkdir.sh index ff163cf..19166ad 100755 --- a/t/t2003-checkout-cache-mkdir.sh +++ b/t/t2003-checkout-cache-mkdir.sh
@@ -24,27 +24,28 @@ mkdir path2 && ln -s path2 path1 && git checkout-index -f -a && - test ! -h path1 && test -d path1 && - test -f path1/file1 && test ! -f path2/file1 + test_path_is_dir_not_symlink path1 && + test_path_is_file path1/file1 && + test_path_is_missing path2/file1 ' test_expect_success 'use --prefix=path2/' ' rm -fr path0 path1 path2 && mkdir path2 && git checkout-index --prefix=path2/ -f -a && - test -f path2/path0 && - test -f path2/path1/file1 && - test ! -f path0 && - test ! -f path1/file1 + test_path_is_file path2/path0 && + test_path_is_file path2/path1/file1 && + test_path_is_missing path0 && + test_path_is_missing path1/file1 ' test_expect_success 'use --prefix=tmp-' ' rm -fr path0 path1 path2 tmp* && git checkout-index --prefix=tmp- -f -a && - test -f tmp-path0 && - test -f tmp-path1/file1 && - test ! -f path0 && - test ! -f path1/file1 + test_path_is_file tmp-path0 && + test_path_is_file tmp-path1/file1 && + test_path_is_missing path0 && + test_path_is_missing path1/file1 ' test_expect_success 'use --prefix=tmp- but with a conflicting file and dir' ' @@ -52,10 +53,10 @@ echo nitfol >tmp-path1 && mkdir tmp-path0 && git checkout-index --prefix=tmp- -f -a && - test -f tmp-path0 && - test -f tmp-path1/file1 && - test ! -f path0 && - test ! -f path1/file1 + test_path_is_file tmp-path0 && + test_path_is_file tmp-path1/file1 && + test_path_is_missing path0 && + test_path_is_missing path1/file1 ' test_expect_success SYMLINKS 'use --prefix=tmp/orary/ where tmp is a symlink' ' @@ -63,10 +64,10 @@ mkdir tmp1 tmp1/orary && ln -s tmp1 tmp && git checkout-index --prefix=tmp/orary/ -f -a && - test -d tmp1/orary && - test -f tmp1/orary/path0 && - test -f tmp1/orary/path1/file1 && - test -h tmp + test_path_is_dir tmp1/orary && + test_path_is_file tmp1/orary/path0 && + test_path_is_file tmp1/orary/path1/file1 && + test_path_is_symlink tmp ' test_expect_success SYMLINKS 'use --prefix=tmp/orary- where tmp is a symlink' ' @@ -74,9 +75,9 @@ mkdir tmp1 && ln -s tmp1 tmp && git checkout-index --prefix=tmp/orary- -f -a && - test -f tmp1/orary-path0 && - test -f tmp1/orary-path1/file1 && - test -h tmp + test_path_is_file tmp1/orary-path0 && + test_path_is_file tmp1/orary-path1/file1 && + test_path_is_symlink tmp ' test_expect_success SYMLINKS 'use --prefix=tmp- where tmp-path1 is a symlink' ' @@ -84,10 +85,9 @@ mkdir tmp1 && ln -s tmp1 tmp-path1 && git checkout-index --prefix=tmp- -f -a && - test -f tmp-path0 && - test ! -h tmp-path1 && - test -d tmp-path1 && - test -f tmp-path1/file1 + test_path_is_file tmp-path0 && + test_path_is_dir_not_symlink tmp-path1 && + test_path_is_file tmp-path1/file1 ' test_expect_success 'apply filter from working tree .gitattributes with --prefix' '
diff --git a/t/t2004-checkout-cache-temp.sh b/t/t2004-checkout-cache-temp.sh index b92d96f..0afe0ff 100755 --- a/t/t2004-checkout-cache-temp.sh +++ b/t/t2004-checkout-cache-temp.sh
@@ -42,7 +42,7 @@ test_line_count = 1 actual && test $(cut "-d " -f2 actual) = path1 && p=$(cut "-d " -f1 actual) && - test -f $p && + test_path_is_file $p && test $(cat $p) = tree1path1 ' @@ -55,7 +55,7 @@ do test $(grep $f actual | cut "-d " -f2) = $f && p=$(grep $f actual | cut "-d " -f1) && - test -f $p && + test_path_is_file $p && test $(cat $p) = tree1$f || return 1 done ' @@ -71,7 +71,7 @@ test_line_count = 1 actual && test $(cut "-d " -f2 actual) = path1 && p=$(cut "-d " -f1 actual) && - test -f $p && + test_path_is_file $p && test $(cat $p) = tree2path1 ' @@ -83,7 +83,7 @@ do test $(grep $f actual | cut "-d " -f2) = $f && p=$(grep $f actual | cut "-d " -f1) && - test -f $p && + test_path_is_file $p && test $(cat $p) = tree2$f || return 1 done ' @@ -108,9 +108,9 @@ test_line_count = 1 actual && test $(cut "-d " -f2 actual) = path1 && cut "-d " -f1 actual | (read s1 s2 s3 && - test -f $s1 && - test -f $s2 && - test -f $s3 && + test_path_is_file $s1 && + test_path_is_file $s2 && + test_path_is_file $s3 && test $(cat $s1) = tree1path1 && test $(cat $s2) = tree2path1 && test $(cat $s3) = tree3path1) @@ -143,8 +143,8 @@ test $(cut "-d " -f2 actual) = path2 && cut "-d " -f1 actual | (read s1 s2 s3 && test $s1 = . && - test -f $s2 && - test -f $s3 && + test_path_is_file $s2 && + test_path_is_file $s3 && test $(cat $s2) = tree2path2 && test $(cat $s3) = tree3path2) ' @@ -162,9 +162,9 @@ test_expect_success '-- path1: all 3 stages' ' test $(grep path1 actual | cut "-d " -f2) = path1 && grep path1 actual | cut "-d " -f1 | (read s1 s2 s3 && - test -f $s1 && - test -f $s2 && - test -f $s3 && + test_path_is_file $s1 && + test_path_is_file $s2 && + test_path_is_file $s3 && test $(cat $s1) = tree1path1 && test $(cat $s2) = tree2path1 && test $(cat $s3) = tree3path1) @@ -174,8 +174,8 @@ test $(grep path2 actual | cut "-d " -f2) = path2 && grep path2 actual | cut "-d " -f1 | (read s1 s2 s3 && test $s1 = . && - test -f $s2 && - test -f $s3 && + test_path_is_file $s2 && + test_path_is_file $s3 && test $(cat $s2) = tree2path2 && test $(cat $s3) = tree3path2) ' @@ -183,9 +183,9 @@ test_expect_success '-- path3: no stage 2, have stage 1 and 3' ' test $(grep path3 actual | cut "-d " -f2) = path3 && grep path3 actual | cut "-d " -f1 | (read s1 s2 s3 && - test -f $s1 && + test_path_is_file $s1 && test $s2 = . && - test -f $s3 && + test_path_is_file $s3 && test $(cat $s1) = tree1path3 && test $(cat $s3) = tree3path3) ' @@ -193,8 +193,8 @@ test_expect_success '-- path4: no stage 3, have stage 1 and 3' ' test $(grep path4 actual | cut "-d " -f2) = path4 && grep path4 actual | cut "-d " -f1 | (read s1 s2 s3 && - test -f $s1 && - test -f $s2 && + test_path_is_file $s1 && + test_path_is_file $s2 && test $s3 = . && test $(cat $s1) = tree1path4 && test $(cat $s2) = tree2path4) @@ -203,7 +203,7 @@ test_expect_success '-- asubdir/path5: no stage 2 and 3 have stage 1' ' test $(grep asubdir/path5 actual | cut "-d " -f2) = asubdir/path5 && grep asubdir/path5 actual | cut "-d " -f1 | (read s1 s2 s3 && - test -f $s1 && + test_path_is_file $s1 && test $s2 = . && test $s3 = . && test $(cat $s1) = tree1asubdir/path5) @@ -216,7 +216,7 @@ test_line_count = 1 actual && test $(grep path5 actual | cut "-d " -f2) = path5 && grep path5 actual | cut "-d " -f1 | (read s1 s2 s3 && - test -f ../$s1 && + test_path_is_file ../$s1 && test $s2 = . && test $s3 = . && test $(cat ../$s1) = tree1asubdir/path5) @@ -230,7 +230,7 @@ test_line_count = 1 actual && test $(cut "-d " -f2 actual) = path6 && p=$(cut "-d " -f1 actual) && - test -f $p && + test_path_is_file $p && test $(cat $p) = path7 '
diff --git a/t/t2021-checkout-overwrite.sh b/t/t2021-checkout-overwrite.sh index a5c03d5..38c41ae 100755 --- a/t/t2021-checkout-overwrite.sh +++ b/t/t2021-checkout-overwrite.sh
@@ -27,7 +27,7 @@ git rm --cached a/b && git commit -m "un-track the file" && test_must_fail git checkout start && - test -f a/b + test_path_is_file a/b ' test_expect_success 'create a commit where dir a/b changed to symlink' ' @@ -49,7 +49,7 @@ test_expect_success SYMLINKS 'the symlink remained' ' - test -h a/b + test_path_is_symlink a/b ' test_expect_success 'cleanup after previous symlink tests' '
diff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh index a397790..c01f1cd 100755 --- a/t/t2027-checkout-track.sh +++ b/t/t2027-checkout-track.sh
@@ -47,4 +47,22 @@ test_cmp_config refs/heads/main branch.b4.merge ' +test_expect_success 'ambiguous tracking info' ' + # Set up a few remote repositories + git init --bare --initial-branch=trunk src1 && + git init --bare --initial-branch=trunk src2 && + git push src1 one:refs/heads/trunk && + git push src2 two:refs/heads/trunk && + + git remote add -f src1 "file://$PWD/src1" && + git remote add -f src2 "file://$PWD/src2" && + + # DWIM + test_must_fail git checkout trunk 2>hint.checkout && + test_grep "hint: *git checkout --track" hint.checkout && + + test_must_fail git switch trunk 2>hint.switch && + test_grep "hint: *git switch --track" hint.switch +' + test_done
diff --git a/t/t2107-update-index-basic.sh b/t/t2107-update-index-basic.sh index cc72ead..3bffe5d 100755 --- a/t/t2107-update-index-basic.sh +++ b/t/t2107-update-index-basic.sh
@@ -86,7 +86,7 @@ # the_index.cache_changed is zero, rollback_lock_file fails git update-index --refresh --verbose >out && test_must_be_empty out && - ! test -f .git/index.lock + test_path_is_missing .git/index.lock ) '
diff --git a/t/t2203-add-intent.sh b/t/t2203-add-intent.sh index 192ad14..44c1936 100755 --- a/t/t2203-add-intent.sh +++ b/t/t2203-add-intent.sh
@@ -16,7 +16,8 @@ ' test_expect_success 'git status' ' - git status --porcelain | grep -v actual >actual && + git status --porcelain >actual.raw && + grep -v actual actual.raw >actual && cat >expect <<-\EOF && DA 1.t A elif @@ -26,7 +27,8 @@ ' test_expect_success 'git status with porcelain v2' ' - git status --porcelain=v2 | grep -v "^?" >actual && + git status --porcelain=v2 >actual.raw && + grep -v "^?" actual.raw >actual && nam1=$(echo 1 | git hash-object --stdin) && nam2=$(git hash-object elif) && cat >expect <<-EOF && @@ -171,17 +173,20 @@ mv first third && git add -N third && - git status | grep -v "^?" >actual.1 && + git status >actual.raw.1 && + grep -v "^?" actual.raw.1 >actual.1 && test_grep "renamed: *first -> third" actual.1 && - git status --porcelain | grep -v "^?" >actual.2 && + git status --porcelain >actual.raw.2 && + grep -v "^?" actual.raw.2 >actual.2 && cat >expected.2 <<-\EOF && R first -> third EOF test_cmp expected.2 actual.2 && hash=$(git hash-object third) && - git status --porcelain=v2 | grep -v "^?" >actual.3 && + git status --porcelain=v2 >actual.raw.3 && + grep -v "^?" actual.raw.3 >actual.3 && cat >expected.3 <<-EOF && 2 .R N... 100644 100644 100644 $hash $hash R100 third first EOF @@ -211,11 +216,13 @@ mv second third && git add -N third && - git status | grep -v "^?" >actual.1 && + git status >actual.raw.1 && + grep -v "^?" actual.raw.1 >actual.1 && test_grep "renamed: *first -> second" actual.1 && test_grep "renamed: *second -> third" actual.1 && - git status --porcelain | grep -v "^?" >actual.2 && + git status --porcelain >actual.raw.2 && + grep -v "^?" actual.raw.2 >actual.2 && cat >expected.2 <<-\EOF && R first -> second R second -> third @@ -223,7 +230,8 @@ test_cmp expected.2 actual.2 && hash=$(git hash-object third) && - git status --porcelain=v2 | grep -v "^?" >actual.3 && + git status --porcelain=v2 >actual.raw.3 && + grep -v "^?" actual.raw.3 >actual.3 && cat >expected.3 <<-EOF && 2 R. N... 100644 100644 100644 $hash $hash R100 second first 2 .R N... 100644 100644 100644 $hash $hash R100 third second
diff --git a/t/t2206-add-submodule-ignored.sh b/t/t2206-add-submodule-ignored.sh new file mode 100755 index 0000000..e581e87 --- /dev/null +++ b/t/t2206-add-submodule-ignored.sh
@@ -0,0 +1,134 @@ +#!/bin/sh +# shellcheck disable=SC2016 + +# shellcheck disable=SC2034 +test_description='git add respects submodule ignore=all and explicit pathspec' + +# This test covers the behavior of "git add", "git status" and "git log" when +# dealing with submodules that have the ignore=all setting in +# .gitmodules. It ensures that changes in such submodules are +# ignored by default, but can be staged with "git add --force". + +# shellcheck disable=SC1091 +. ./test-lib.sh + +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + +base_path=$(pwd -P) + +#1 +test_expect_success 'setup: create origin repos' ' + cd "${base_path}" && + git config --global protocol.file.allow always && + git init sub && + pwd && + cd sub && + test_commit sub_file1 && + git tag v1.0 && + test_commit sub_file2 && + git tag v2.0 && + test_commit sub_file3 && + git tag v3.0 && + cd "${base_path}" && + git init main && + cd main && + test_commit first && + cd "${base_path}" +' +#2 +# add submodule with default config (ignore=none) and +# check log that is contains a path entry for the submodule 'sub' +# change the commit in the submodule and check that 'git status' shows it as modified +test_expect_success 'main: add submodule with default config' ' + cd "${base_path}" && + cd main && + git submodule add ../sub && + git commit -m "add submodule" && + git log --oneline --name-only | grep "^sub$" && + git -C sub reset --hard v2.0 && + git status --porcelain | grep "^ M sub$" && + echo +' +#3 +# change the submodule config to ignore=all and check that status and log do not show changes +test_expect_success 'main: submodule config ignore=all' ' + cd "${base_path}" && + cd main && + git config -f .gitmodules submodule.sub.ignore all && + GIT_TRACE=1 git add . && + git commit -m "update submodule config sub.ignore all" && + ! git status --porcelain | grep "^.*$" && + ! git log --oneline --name-only | grep "^sub$" && + echo +' +#4 +# change the commit in the submodule and check that 'git status' does not show it as modified +# but 'git status --ignore-submodules=none' does show it as modified +test_expect_success 'sub: change to different sha1 and check status in main' ' + cd "${base_path}" && + cd main && + git -C sub reset --hard v1.0 && + ! git status --porcelain | grep "^ M sub$" && + git status --ignore-submodules=none --porcelain | grep "^ M sub$" && + echo +' + +#5 +# check that normal 'git add' does not stage the change in the submodule +test_expect_success 'main: check normal add and status' ' + cd "${base_path}" && + cd main && + GIT_TRACE=1 git add . && + ! git status --porcelain | grep "^ M sub$" && + echo +' + +#6 +# check that 'git add --force .' does not stage the change in the submodule +# and that 'git status' does not show it as modified +test_expect_success 'main: check --force add . and status' ' + cd "${base_path}" && + cd main && + GIT_TRACE=1 git add --force . && + ! git status --porcelain | grep "^M sub$" && + echo +' + +#7 +# check that 'git add .' does not stage the change in the submodule +# and that 'git status' does not show it as modified +test_expect_success 'main: check _add sub_ and status' ' + cd "${base_path}" && + cd main && + GIT_TRACE=1 git add sub 2>&1 | grep "Skipping submodule due to ignore=all: sub" && + ! git status --porcelain | grep "^M sub$" && + echo +' + +#8 +# check that 'git add --force sub' does stage the change in the submodule +# check that 'git add --force ./sub/' does stage the change in the submodule +# and that 'git status --porcelain' does show it as modified +# commit it.. +# check that 'git log --ignore-submodules=none' shows the submodule change +# in the log +test_expect_success 'main: check force add sub and ./sub/ and status' ' + cd "${base_path}" && + cd main && + echo "Adding with --force should work: git add --force sub" && + GIT_TRACE=1 git add --force sub && + git status --porcelain | grep "^M sub$" && + git restore --staged sub && + ! git status --porcelain | grep "^M sub$" && + echo "Adding with --force should work: git add --force ./sub/" && + GIT_TRACE=1 git add --force ./sub/ && + git status --porcelain | grep "^M sub$" && + git commit -m "update submodule pointer" && + ! git status --porcelain | grep "^ M sub$" && + git log --ignore-submodules=none --name-only --oneline | grep "^sub$" && + echo +' + +test_done +exit 0
diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index 023e130..58b4445 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh
@@ -987,7 +987,7 @@ then test_must_be_empty actual else - grep "$info_text" actual + test_grep "$info_text" actual fi elif [ "$outcome" = "no_infer" ] then @@ -996,39 +996,35 @@ then test_must_be_empty actual else - ! grep "$info_text" actual + test_grep ! "$info_text" actual fi elif [ "$outcome" = "fetch_error" ] then test_must_fail git $dashc_args worktree add $args 2>actual && - grep "$fetch_error_text" actual + test_grep "$fetch_error_text" actual elif [ "$outcome" = "fatal_orphan_bad_combo" ] then test_must_fail git $dashc_args worktree add $args 2>actual && if [ $use_quiet -eq 1 ] then - ! grep "$info_text" actual + test_grep ! "$info_text" actual else - grep "$info_text" actual + test_grep "$info_text" actual fi && - grep "$bad_combo_regex" actual + test_grep "$bad_combo_regex" actual elif [ "$outcome" = "warn_bad_head" ] then test_must_fail git $dashc_args worktree add $args 2>actual && if [ $use_quiet -eq 1 ] then - grep "$invalid_ref_regex" actual && - ! grep "$orphan_hint" actual + test_grep "$invalid_ref_regex" actual && + test_grep ! "$orphan_hint" actual else - headpath=$(git $dashc_args rev-parse --path-format=absolute --git-path HEAD) && - headcontents=$(cat "$headpath") && - grep "HEAD points to an invalid (or orphaned) reference" actual && - grep "HEAD path: .$headpath." actual && - grep "HEAD contents: .$headcontents." actual && - grep "$orphan_hint" actual && - ! grep "$info_text" actual + test_grep "HEAD points to an invalid (or orphaned) reference" actual && + test_grep "$orphan_hint" actual && + test_grep ! "$info_text" actual fi && - grep "$invalid_ref_regex" actual + test_grep "$invalid_ref_regex" actual else # Unreachable false
diff --git a/t/t2402-worktree-list.sh b/t/t2402-worktree-list.sh index 8ef1cad..e0c6abd 100755 --- a/t/t2402-worktree-list.sh +++ b/t/t2402-worktree-list.sh
@@ -29,23 +29,34 @@ test_cmp expect actual ' -test_expect_success '"list" all worktrees from main' ' - echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect && - test_when_finished "rm -rf here out actual expect && git worktree prune" && - git worktree add --detach here main && - echo "$(git -C here rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect && - git worktree list >out && - sed "s/ */ /g" <out >actual && +test_expect_success '"list" all worktrees from main core.quotepath=false' ' + test_config core.quotepath false && + echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect && + test_when_finished "rm -rf áááá out actual expect && git worktree prune" && + git worktree add --detach áááá main && + echo "$(git -C áááá rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect && + git worktree list >actual && + test_cmp expect actual +' + +test_expect_success '"list" all worktrees from main core.quotepath=true' ' + test_config core.quotepath true && + echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect && + test_when_finished "rm -rf á out actual expect && git worktree prune" && + git worktree add --detach á main && + echo "\"$(git -C á rev-parse --show-toplevel)\" $(git rev-parse --short HEAD) (detached HEAD)" | + sed s/á/\\\\303\\\\241/g >>expect && + git worktree list >actual && test_cmp expect actual ' test_expect_success '"list" all worktrees from linked' ' - echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect && - test_when_finished "rm -rf here out actual expect && git worktree prune" && - git worktree add --detach here main && - echo "$(git -C here rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect && - git -C here worktree list >out && - sed "s/ */ /g" <out >actual && + test_config core.quotepath false && + echo "$(git rev-parse --show-toplevel) $(git rev-parse --short HEAD) [$(git symbolic-ref --short HEAD)]" >expect && + test_when_finished "rm -rf áááá out actual expect && git worktree prune" && + git worktree add --detach áááá main && + echo "$(git -C áááá rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >>expect && + git -C áááá worktree list >actual && test_cmp expect actual '
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index f3e720d..e7829c2 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh
@@ -1494,7 +1494,8 @@ ' test_expect_success '--merged catches invalid object names' ' - test_must_fail git branch --merged 0000000000000000000000000000000000000000 + test_must_fail git branch --merged $ZERO_OID 2>err && + test_grep "must point to a commit" err ' test_expect_success '--list during rebase' ' @@ -1707,9 +1708,9 @@ ' test_expect_success 'errors if given a bad branch name' ' - cat <<-\EOF >expect && - fatal: '\''foo..bar'\'' is not a valid branch name - hint: See `man git check-ref-format` + cat <<-EOF >expect && + fatal: ${SQ}foo..bar${SQ} is not a valid branch name + hint: See ${SQ}git help check-ref-format${SQ} hint: Disable this message with "git config set advice.refSyntax false" EOF test_must_fail git branch foo..bar >actual 2>&1 &&
diff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh index 597df5e..0bb366f 100755 --- a/t/t3310-notes-merge-manual-resolve.sh +++ b/t/t3310-notes-merge-manual-resolve.sh
@@ -227,7 +227,8 @@ # Verify that current notes tree (pre-merge) has not changed (m == y) verify_notes y && verify_notes m && - test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" + git rev-parse refs/notes/m >actual && + test_cmp pre_merge_y actual ' cat <<EOF | sort >expect_notes_z @@ -260,7 +261,7 @@ ' test_expect_success 'cannot do merge w/conflicts when previous merge is unfinished' ' - test -d .git/NOTES_MERGE_WORKTREE && + test_path_is_dir .git/NOTES_MERGE_WORKTREE && test_must_fail git notes merge z >output 2>&1 && # Output should indicate what is wrong test_grep -q "\\.git/NOTES_MERGE_\\* exists" output @@ -320,7 +321,7 @@ EOF test_expect_success 'can do merge without conflicts even if previous merge is unfinished (x => w)' ' - test -d .git/NOTES_MERGE_WORKTREE && + test_path_is_dir .git/NOTES_MERGE_WORKTREE && git notes merge x && verify_notes w && # Verify that other notes refs has not changed (x and y) @@ -375,8 +376,10 @@ git notes merge --commit && notes_merge_files_gone && # Merge commit has pre-merge y and pre-merge z as parents - test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" && - test "$(git rev-parse refs/notes/m^2)" = "$(cat pre_merge_z)" && + git rev-parse refs/notes/m^1 >actual && + test_cmp pre_merge_y actual && + git rev-parse refs/notes/m^2 >actual && + test_cmp pre_merge_z actual && # Merge commit mentions the notes refs merged git log -1 --format=%B refs/notes/m > merge_commit_msg && grep -q refs/notes/m merge_commit_msg && @@ -428,14 +431,16 @@ # Verify that current notes tree (pre-merge) has not changed (m == y) verify_notes y && verify_notes m && - test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" + git rev-parse refs/notes/m >actual && + test_cmp pre_merge_y actual ' test_expect_success 'abort notes merge' ' git notes merge --abort && notes_merge_files_gone && # m has not moved (still == y) - test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" && + git rev-parse refs/notes/m >actual && + test_cmp pre_merge_y actual && # Verify that other notes refs has not changed (w, x, y and z) verify_notes w && verify_notes x && @@ -460,7 +465,8 @@ # Verify that current notes tree (pre-merge) has not changed (m == y) verify_notes y && verify_notes m && - test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" + git rev-parse refs/notes/m >actual && + test_cmp pre_merge_y actual ' cat <<EOF | sort >expect_notes_m @@ -500,8 +506,10 @@ git notes merge --commit && notes_merge_files_gone && # Merge commit has pre-merge y and pre-merge z as parents - test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" && - test "$(git rev-parse refs/notes/m^2)" = "$(cat pre_merge_z)" && + git rev-parse refs/notes/m^1 >actual && + test_cmp pre_merge_y actual && + git rev-parse refs/notes/m^2 >actual && + test_cmp pre_merge_z actual && # Merge commit mentions the notes refs merged git log -1 --format=%B refs/notes/m > merge_commit_msg && grep -q refs/notes/m merge_commit_msg && @@ -539,7 +547,8 @@ # Verify that current notes tree (pre-merge) has not changed (m == y) verify_notes y && verify_notes m && - test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" + git rev-parse refs/notes/m >actual && + test_cmp pre_merge_y actual ' cp expect_notes_w expect_notes_m @@ -548,7 +557,7 @@ test_expect_success 'reset notes ref m to somewhere else (w)' ' git update-ref refs/notes/m refs/notes/w && verify_notes m && - test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" + test_cmp_rev refs/notes/m refs/notes/w ' test_expect_success 'fail to finalize conflicting merge if underlying ref has moved in the meantime (m != NOTES_MERGE_PARTIAL^1)' ' @@ -564,18 +573,20 @@ # NOTES_MERGE_* refs and .git/NOTES_MERGE_* state files must remain git rev-parse --verify NOTES_MERGE_PARTIAL && git rev-parse --verify NOTES_MERGE_REF && - test -f .git/NOTES_MERGE_WORKTREE/$commit_sha1 && - test -f .git/NOTES_MERGE_WORKTREE/$commit_sha2 && - test -f .git/NOTES_MERGE_WORKTREE/$commit_sha3 && - test -f .git/NOTES_MERGE_WORKTREE/$commit_sha4 && + test_path_is_file .git/NOTES_MERGE_WORKTREE/$commit_sha1 && + test_path_is_file .git/NOTES_MERGE_WORKTREE/$commit_sha2 && + test_path_is_file .git/NOTES_MERGE_WORKTREE/$commit_sha3 && + test_path_is_file .git/NOTES_MERGE_WORKTREE/$commit_sha4 && # Refs are unchanged - test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" && - test "$(git rev-parse refs/notes/y)" = "$(git rev-parse NOTES_MERGE_PARTIAL^1)" && - test "$(git rev-parse refs/notes/m)" != "$(git rev-parse NOTES_MERGE_PARTIAL^1)" && + test_cmp_rev refs/notes/m refs/notes/w && + test_cmp_rev refs/notes/y NOTES_MERGE_PARTIAL^1 && + test_cmp_rev ! refs/notes/m NOTES_MERGE_PARTIAL^1 && # Mention refs/notes/m, and its current and expected value in output test_grep -q "refs/notes/m" output && - test_grep -q "$(git rev-parse refs/notes/m)" output && - test_grep -q "$(git rev-parse NOTES_MERGE_PARTIAL^1)" output && + oid=$(git rev-parse refs/notes/m) && + test_grep -q "$oid" output && + oid=$(git rev-parse NOTES_MERGE_PARTIAL^1) && + test_grep -q "$oid" output && # Verify that other notes refs has not changed (w, x, y and z) verify_notes w && verify_notes x && @@ -587,7 +598,7 @@ git notes merge --abort && notes_merge_files_gone && # m has not moved (still == w) - test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" && + test_cmp_rev refs/notes/m refs/notes/w && # Verify that other notes refs has not changed (w, x, y and z) verify_notes w && verify_notes x && @@ -606,8 +617,8 @@ test_must_fail git notes merge refs/notes/other && ( cd .git/NOTES_MERGE_WORKTREE && - echo "foo" > $(git rev-parse HEAD) && - echo "bar" >> $(git rev-parse HEAD) && + oid=$(git rev-parse HEAD) && + test_write_lines foo bar >"$oid" && git notes merge --commit ) && git notes show HEAD > actual_notes &&
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index e778dd8..3e44562 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh
@@ -31,6 +31,12 @@ . "$TEST_DIRECTORY"/lib-rebase.sh test_expect_success 'setup' ' + # Commit dates are hardcoded to 2005, and the reflog entries will have + # a matching timestamp. Maintenance may thus immediately expire + # reflogs if it was running. + git config set gc.reflogExpire never && + git config set gc.reflogExpireUnreachable never && + git switch -C primary && test_commit A file1 && test_commit B file1 &&
diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh index a1d7fa7..bc51a9d 100755 --- a/t/t3406-rebase-message.sh +++ b/t/t3406-rebase-message.sh
@@ -8,6 +8,12 @@ . ./test-lib.sh test_expect_success 'setup' ' + # Commit dates are hardcoded to 2005, and the reflog entries will have + # a matching timestamp. Maintenance may thus immediately expire + # reflogs if it was running. + git config set gc.reflogExpire never && + git config set gc.reflogExpireUnreachable never && + test_commit O fileO && test_commit X fileX && git branch fast-forward &&
diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh index cc627e3..84b2d0e 100755 --- a/t/t3430-rebase-merges.sh +++ b/t/t3430-rebase-merges.sh
@@ -507,9 +507,11 @@ git rebase -i --force-rebase -r HEAD^^ && test "Hank" = "$(git show -s --format=%an HEAD)" && test "$before" != $(git rev-parse HEAD) && - test_cmp_graph HEAD^^.. <<-\EOF + # NOTE: do not quote this heredoc, Dash 0.5.13 has a bug with heredocs + # that contain multibyte chars. + test_cmp_graph HEAD^^.. <<-EOF *-. Tüntenfüsch - |\ \ + |\\ \\ | | * three | * | two | |/
diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh index be09fc7..4336f41 100755 --- a/t/t3431-rebase-fork-point.sh +++ b/t/t3431-rebase-fork-point.sh
@@ -17,6 +17,12 @@ # C was formerly part of main but main was rewound to remove C # test_expect_success setup ' + # Commit dates are hardcoded to 2005, and the reflog entries will have + # a matching timestamp. Maintenance may thus immediately expire + # reflogs if it was running. + git config set gc.reflogExpire never && + git config set gc.reflogExpireUnreachable never && + test_commit A && test_commit B && test_commit C &&
diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index 5086e14..181d19d 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh
@@ -11,6 +11,12 @@ . ./test-lib.sh test_expect_success setup ' + # Commit dates are hardcoded to 2005, and the reflog entries will have + # a matching timestamp. Maintenance may thus immediately expire + # reflogs if it was running. + git config set gc.reflogExpire never && + git config set gc.reflogExpireUnreachable never && + test_commit A && test_commit B && test_commit C &&
diff --git a/t/t3440-rebase-trailer.sh b/t/t3440-rebase-trailer.sh new file mode 100755 index 0000000..8b47579 --- /dev/null +++ b/t/t3440-rebase-trailer.sh
@@ -0,0 +1,147 @@ +#!/bin/sh +# + +test_description='git rebase --trailer integration tests +We verify that --trailer works with the merge backend, +and that it is rejected early when the apply backend is requested.' + +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-rebase.sh # test_commit_message, helpers + +REVIEWED_BY_TRAILER="Reviewed-by: Dev <dev@example.com>" +SP=" " + +test_expect_success 'setup repo with a small history' ' + git commit --allow-empty -m "Initial empty commit" && + test_commit first file a && + test_commit second file && + git checkout -b conflict-branch first && + test_commit file-2 file-2 && + test_commit conflict file && + test_commit third file && + git checkout main +' + +test_expect_success 'apply backend is rejected with --trailer' ' + git checkout -B apply-backend third && + test_expect_code 128 \ + git rebase --apply --trailer "$REVIEWED_BY_TRAILER" HEAD^ 2>err && + test_grep "fatal: --trailer requires the merge backend" err +' + +test_expect_success 'reject empty --trailer argument' ' + git checkout -B empty-trailer third && + test_expect_code 128 git rebase --trailer "" HEAD^ 2>err && + test_grep "empty --trailer" err +' + +test_expect_success 'reject trailer with missing key before separator' ' + git checkout -B missing-key third && + test_expect_code 128 git rebase --trailer ": no-key" HEAD^ 2>err && + test_grep "missing key before separator" err +' + +test_expect_success 'allow trailer with missing value after separator' ' + git checkout -B missing-value third && + git rebase --trailer "Acked-by:" HEAD^ && + test_commit_message HEAD <<-EOF + third + + Acked-by:${SP} + EOF +' + +test_expect_success 'CLI trailer duplicates allowed; replace policy keeps last' ' + git checkout -B replace-policy third && + git -c trailer.Bug.ifexists=replace -c trailer.Bug.ifmissing=add \ + rebase --trailer "Bug: 123" --trailer "Bug: 456" HEAD^ && + test_commit_message HEAD <<-EOF + third + + Bug: 456 + EOF +' + +test_expect_success 'multiple Signed-off-by trailers all preserved' ' + git checkout -B multiple-signoff third && + git rebase --trailer "Signed-off-by: Dev A <a@example.com>" \ + --trailer "Signed-off-by: Dev B <b@example.com>" HEAD^ && + test_commit_message HEAD <<-EOF + third + + Signed-off-by: Dev A <a@example.com> + Signed-off-by: Dev B <b@example.com> + EOF +' + +test_expect_success 'rebase --trailer adds trailer after conflicts' ' + git checkout -B trailer-conflict third && + test_commit fourth file && + test_must_fail git rebase --trailer "$REVIEWED_BY_TRAILER" second && + git checkout --theirs file && + git add file && + git rebase --continue && + test_commit_message HEAD <<-EOF && + fourth + + $REVIEWED_BY_TRAILER + EOF + test_commit_message HEAD^ <<-EOF + third + + $REVIEWED_BY_TRAILER + EOF +' + +test_expect_success '--trailer handles fixup commands in todo list' ' + git checkout -B fixup-trailer third && + test_commit fixup-base base && + test_commit fixup-second second && + cat >todo <<-\EOF && + pick fixup-base fixup-base + fixup fixup-second fixup-second + EOF + ( + set_replace_editor todo && + git rebase -i --trailer "$REVIEWED_BY_TRAILER" HEAD~2 + ) && + test_commit_message HEAD <<-EOF && + fixup-base + + $REVIEWED_BY_TRAILER + EOF + git reset --hard fixup-second && + cat >todo <<-\EOF && + pick fixup-base fixup-base + fixup -C fixup-second fixup-second + EOF + ( + set_replace_editor todo && + git rebase -i --trailer "$REVIEWED_BY_TRAILER" HEAD~2 + ) && + test_commit_message HEAD <<-EOF + fixup-second + + $REVIEWED_BY_TRAILER + EOF +' + +test_expect_success 'rebase --root honors trailer.<name>.key' ' + git checkout -B root-trailer first && + git -c trailer.review.key=Reviewed-by rebase --root \ + --trailer=review="Dev <dev@example.com>" && + test_commit_message HEAD <<-EOF && + first + + Reviewed-by: Dev <dev@example.com> + EOF + test_commit_message HEAD^ <<-EOF + Initial empty commit + + Reviewed-by: Dev <dev@example.com> + EOF +' +test_done
diff --git a/t/t3450-history.sh b/t/t3450-history.sh new file mode 100755 index 0000000..f513463 --- /dev/null +++ b/t/t3450-history.sh
@@ -0,0 +1,17 @@ +#!/bin/sh + +test_description='tests for git-history command' + +. ./test-lib.sh + +test_expect_success 'does nothing without any arguments' ' + test_must_fail git history 2>err && + test_grep "need a subcommand" err +' + +test_expect_success 'raises an error with unknown argument' ' + test_must_fail git history garbage 2>err && + test_grep "unknown subcommand: .garbage." err +' + +test_done
diff --git a/t/t3451-history-reword.sh b/t/t3451-history-reword.sh new file mode 100755 index 0000000..de7b357 --- /dev/null +++ b/t/t3451-history-reword.sh
@@ -0,0 +1,399 @@ +#!/bin/sh + +test_description='tests for git-history reword subcommand' + +. ./test-lib.sh +. "$TEST_DIRECTORY/lib-log-graph.sh" + +reword_with_message () { + cat >message && + write_script fake-editor.sh <<-\EOF && + cp message "$1" + EOF + test_set_editor "$(pwd)"/fake-editor.sh && + git history reword "$@" && + rm fake-editor.sh message +} + +expect_graph () { + cat >expect && + lib_test_cmp_graph --graph --format=%s "$@" +} + +expect_log () { + git log --format="%s" "$@" >actual && + cat >expect && + test_cmp expect actual +} + +test_expect_success 'can reword tip of a branch' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit first && + test_commit second && + test_commit third && + + git symbolic-ref HEAD >expect && + reword_with_message HEAD <<-EOF && + third reworded + EOF + git symbolic-ref HEAD >actual && + test_cmp expect actual && + + expect_log <<-\EOF && + third reworded + second + first + EOF + + git reflog >reflog && + test_grep "reword: updating HEAD" reflog + ) +' + +test_expect_success 'can reword commit in the middle' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit first && + test_commit second && + test_commit third && + + git symbolic-ref HEAD >expect && + reword_with_message HEAD~ <<-EOF && + second reworded + EOF + git symbolic-ref HEAD >actual && + test_cmp expect actual && + + expect_log <<-\EOF + third + second reworded + first + EOF + ) +' + +test_expect_success 'can reword commit in the middle even on detached head' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit first && + test_commit second && + test_commit third_on_main && + git checkout --detach HEAD^ && + test_commit third_on_head && + + reword_with_message HEAD~ <<-EOF && + second reworded + EOF + + expect_graph HEAD --branches <<-\EOF + * third_on_head + | * third_on_main + |/ + * second reworded + * first + EOF + ) +' + +test_expect_success 'can reword the detached head' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit first && + test_commit second && + git checkout --detach HEAD && + test_commit third && + + reword_with_message HEAD <<-EOF && + third reworded + EOF + + expect_log <<-\EOF + third reworded + second + first + EOF + ) +' + +test_expect_success 'can reword root commit' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit first && + test_commit second && + test_commit third && + reword_with_message HEAD~2 <<-EOF && + first reworded + EOF + + expect_log <<-\EOF + third + second + first reworded + EOF + ) +' + +test_expect_success 'can reword in a bare repo' ' + test_when_finished "rm -rf repo repo.git" && + git init repo && + test_commit -C repo first && + git clone --bare repo repo.git && + ( + cd repo.git && + reword_with_message HEAD <<-EOF && + reworded + EOF + + expect_log <<-\EOF + reworded + EOF + ) +' + +test_expect_success 'can reword a commit on a different branch' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit base && + git branch theirs && + test_commit ours && + git switch theirs && + test_commit theirs && + + git rev-parse ours >ours-before && + reword_with_message theirs <<-EOF && + Reworded theirs + EOF + git rev-parse ours >ours-after && + test_cmp ours-before ours-after && + + expect_graph --branches <<-\EOF + * Reworded theirs + | * ours + |/ + * base + EOF + ) +' + +test_expect_success 'can reword a merge commit' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit base && + git branch branch && + test_commit ours && + git switch branch && + test_commit theirs && + git switch - && + git merge theirs && + + # It is not possible to replay merge commits embedded in the + # history (yet). + test_must_fail git -c core.editor=false history reword HEAD~ 2>err && + test_grep "replaying merge commits is not supported yet" err && + + # But it is possible to reword a merge commit directly. + reword_with_message HEAD <<-EOF && + Reworded merge commit + EOF + expect_graph <<-\EOF + * Reworded merge commit + |\ + | * theirs + * | ours + |/ + * base + EOF + ) +' + +test_expect_success '--dry-run prints ref updates without modifying repo' ' + test_when_finished "rm -rf repo" && + git init repo --initial-branch=main && + ( + cd repo && + test_commit base && + git branch branch && + test_commit ours && + git switch branch && + test_commit theirs && + + git refs list >refs-expect && + reword_with_message --dry-run --update-refs=head base >updates <<-\EOF && + reworded commit + EOF + git refs list >refs-actual && + test_cmp refs-expect refs-actual && + test_grep "update refs/heads/branch" updates && + test_grep ! "update refs/heads/main" updates && + + reword_with_message --dry-run base >updates <<-\EOF && + reworded commit + EOF + git refs list >refs-actual && + test_cmp refs-expect refs-actual && + + test_grep "update refs/heads/branch" updates && + test_grep "update refs/heads/main" updates && + git update-ref --stdin <updates && + expect_log --branches <<-\EOF + theirs + ours + reworded commit + EOF + ) +' + +test_expect_success '--update-refs=head updates only HEAD' ' + test_when_finished "rm -rf repo" && + git init repo --initial-branch=main && + ( + cd repo && + test_commit base && + git branch branch && + test_commit theirs && + git switch branch && + test_commit ours && + + # When told to update HEAD, only, the command will refuse to + # rewrite commits that are not an ancestor of HEAD. + test_must_fail git -c core.editor=false history reword --update-refs=head theirs 2>err && + test_grep "rewritten commit must be an ancestor of HEAD" err && + + reword_with_message --update-refs=head base >updates <<-\EOF && + reworded base + EOF + expect_log HEAD <<-\EOF && + ours + reworded base + EOF + expect_log main <<-\EOF + theirs + base + EOF + ) +' + +test_expect_success 'editor shows proper status' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit first && + + write_script fake-editor.sh <<-\EOF && + cp "$1" . && + printf "\namend a comment\n" >>"$1" + EOF + test_set_editor "$(pwd)"/fake-editor.sh && + git history reword HEAD && + + cat >expect <<-EOF && + first + + # Please enter the commit message for the reworded changes. Lines starting + # with ${SQ}#${SQ} will be ignored, and an empty message aborts the commit. + # Changes to be committed: + # new file: first.t + # + EOF + test_cmp expect COMMIT_EDITMSG && + + test_commit_message HEAD <<-\EOF + first + + amend a comment + EOF + ) +' + +# For now, git-history(1) does not yet execute any hooks. This is subject to +# change in the future, and if it does this test here is expected to start +# failing. In other words, this test is not an endorsement of the current +# status quo. +test_expect_success 'hooks are not executed for rewritten commits' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit first && + test_commit second && + test_commit third && + + ORIG_PATH="$(pwd)" && + export ORIG_PATH && + for hook in prepare-commit-msg pre-commit post-commit post-rewrite commit-msg + do + write_script .git/hooks/$hook <<-\EOF || exit 1 + touch "$ORIG_PATH/hooks.log + EOF + done && + + reword_with_message HEAD~ <<-EOF && + second reworded + EOF + + cat >expect <<-EOF && + third + second reworded + first + EOF + git log --format=%s >actual && + test_cmp expect actual && + + test_path_is_missing hooks.log + ) +' + +test_expect_success 'aborts with empty commit message' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit first && + + ! reword_with_message HEAD 2>err </dev/null && + test_grep "Aborting commit due to empty commit message." err + ) +' + +test_expect_success 'retains changes in the worktree and index' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + touch a b && + git add . && + git commit -m "initial commit" && + echo foo >a && + echo bar >b && + git add b && + reword_with_message HEAD <<-EOF && + message + EOF + cat >expect <<-\EOF && + M a + M b + ?? actual + ?? expect + EOF + git status --porcelain >actual && + test_cmp expect actual + ) +' + +test_done
diff --git a/t/t3452-history-split.sh b/t/t3452-history-split.sh new file mode 100755 index 0000000..8ed0ceb --- /dev/null +++ b/t/t3452-history-split.sh
@@ -0,0 +1,757 @@ +#!/bin/sh + +test_description='tests for git-history split subcommand' + +. ./test-lib.sh +. "$TEST_DIRECTORY/lib-log-graph.sh" + +# The fake editor takes multiple arguments, each of which represents a commit +# message. Subsequent invocations of the editor will then yield those messages +# in order. +# +set_fake_editor () { + printf "%s\n" "$@" >fake-input && + write_script fake-editor.sh <<-\EOF && + head -n1 fake-input >"$1" + sed 1d fake-input >fake-input.trimmed && + mv fake-input.trimmed fake-input + EOF + test_set_editor "$(pwd)"/fake-editor.sh +} + +expect_graph () { + cat >expect && + lib_test_cmp_graph --graph --format=%s "$@" +} + +expect_log () { + git log --format="%s" >actual && + cat >expect && + test_cmp expect actual +} + +expect_tree_entries () { + git ls-tree --name-only "$1" >actual && + cat >expect && + test_cmp expect actual +} + +test_expect_success 'refuses to work with merge commits' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit base && + git branch branch && + test_commit ours && + git switch branch && + test_commit theirs && + git switch - && + git merge theirs && + test_must_fail git history split HEAD 2>err && + test_grep "cannot split up merge commit" err && + test_must_fail git history split HEAD~ 2>err && + test_grep "replaying merge commits is not supported yet" err + ) +' + +test_expect_success 'errors on missing commit argument' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + test_must_fail git history split 2>err && + test_grep "command expects a committish" err + ) +' + +test_expect_success 'errors on unknown revision' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + test_must_fail git history split does-not-exist 2>err && + test_grep "commit cannot be found" err + ) +' + +test_expect_success '--dry-run does not modify any refs' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit base && + touch bar foo && + git add . && + git commit -m split-me && + + git refs list --include-root-refs >before && + + set_fake_editor "first" "second" && + git history split --dry-run HEAD <<-EOF && + y + n + EOF + + git refs list --include-root-refs >after && + test_cmp before after + ) +' + +test_expect_success 'can split up tip commit' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + touch bar foo && + git add . && + git commit -m split-me && + + git symbolic-ref HEAD >expect && + set_fake_editor "first" "second" && + git history split HEAD <<-EOF && + y + n + EOF + git symbolic-ref HEAD >actual && + test_cmp expect actual && + + expect_log <<-EOF && + second + first + initial + EOF + + expect_tree_entries HEAD~ <<-EOF && + bar + initial.t + EOF + + expect_tree_entries HEAD <<-EOF && + bar + foo + initial.t + EOF + + git reflog >reflog && + test_grep "split: updating HEAD" reflog + ) +' + +test_expect_success 'can split up root commit' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + touch bar foo && + git add . && + git commit -m root && + test_commit tip && + + set_fake_editor "first" "second" && + git history split HEAD~ <<-EOF && + y + n + EOF + + expect_log <<-EOF && + tip + second + first + EOF + + expect_tree_entries HEAD~2 <<-EOF && + bar + EOF + + expect_tree_entries HEAD~ <<-EOF && + bar + foo + EOF + + expect_tree_entries HEAD <<-EOF + bar + foo + tip.t + EOF + ) +' + +test_expect_success 'can split up in-between commit' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + touch bar foo && + git add . && + git commit -m split-me && + test_commit tip && + + set_fake_editor "first" "second" && + git history split HEAD~ <<-EOF && + y + n + EOF + + expect_log <<-EOF && + tip + second + first + initial + EOF + + expect_tree_entries HEAD~2 <<-EOF && + bar + initial.t + EOF + + expect_tree_entries HEAD~ <<-EOF && + bar + foo + initial.t + EOF + + expect_tree_entries HEAD <<-EOF + bar + foo + initial.t + tip.t + EOF + ) +' + +test_expect_success 'can split HEAD only' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit base && + touch a b && + git add . && + git commit -m split-me && + git branch unrelated && + + set_fake_editor "ours-a" "ours-b" && + git history split --update-refs=head HEAD <<-EOF && + y + n + EOF + expect_graph --branches <<-EOF + * ours-b + * ours-a + | * split-me + |/ + * base + EOF + ) +' + +test_expect_success 'can split detached HEAD' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + touch bar foo && + git add . && + git commit -m split-me && + git checkout --detach HEAD && + + set_fake_editor "first" "second" && + git history split --update-refs=head HEAD <<-EOF && + y + n + EOF + + # HEAD should be detached and updated. + test_must_fail git symbolic-ref HEAD && + + expect_log <<-EOF + second + first + initial + EOF + ) +' + +test_expect_success 'can split commit in unrelated branch' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit base && + git branch ours && + git switch --create theirs && + touch theirs-a theirs-b && + git add . && + git commit -m theirs && + git switch ours && + test_commit ours && + + # With --update-refs=head it is not possible to split up a + # commit that is unrelated to HEAD. + test_must_fail git history split --update-refs=head theirs 2>err && + test_grep "rewritten commit must be an ancestor of HEAD" err && + + set_fake_editor "theirs-rewritten-a" "theirs-rewritten-b" && + git history split theirs <<-EOF && + y + n + EOF + expect_graph --branches <<-EOF && + * ours + | * theirs-rewritten-b + | * theirs-rewritten-a + |/ + * base + EOF + + expect_tree_entries theirs~ <<-EOF && + base.t + theirs-a + EOF + + expect_tree_entries theirs <<-EOF + base.t + theirs-a + theirs-b + EOF + ) +' + +test_expect_success 'updates multiple descendant branches' ' + test_when_finished "rm -rf repo" && + git init repo --initial-branch=main && + ( + cd repo && + test_commit base && + touch file-a file-b && + git add . && + git commit -m split-me && + git branch branch && + test_commit on-main && + git switch branch && + test_commit on-branch && + git switch main && + + set_fake_editor "split-a" "split-b" && + git history split HEAD~ <<-EOF && + y + n + EOF + + # Both branches should now descend from the split commits. + expect_graph --branches <<-EOF + * on-branch + | * on-main + |/ + * split-b + * split-a + * base + EOF + ) +' + +test_expect_success 'can pick multiple hunks' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + touch bar baz foo qux && + git add . && + git commit -m split-me && + + set_fake_editor "first" "second" && + git history split HEAD <<-EOF && + y + n + y + n + EOF + + expect_tree_entries HEAD~ <<-EOF && + bar + foo + EOF + + expect_tree_entries HEAD <<-EOF + bar + baz + foo + qux + EOF + ) +' + +test_expect_success 'can use only last hunk' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + touch bar foo && + git add . && + git commit -m split-me && + + set_fake_editor "first" "second" && + git history split HEAD <<-EOF && + n + y + EOF + + expect_log <<-EOF && + second + first + EOF + + expect_tree_entries HEAD~ <<-EOF && + foo + EOF + + expect_tree_entries HEAD <<-EOF + bar + foo + EOF + ) +' + +test_expect_success 'can split commit with file deletions' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + echo a >a && + echo b >b && + echo c >c && + git add . && + git commit -m base && + git rm a b && + git commit -m delete-both && + + set_fake_editor "delete-a" "delete-b" && + git history split HEAD <<-EOF && + y + n + EOF + + expect_log <<-EOF && + delete-b + delete-a + base + EOF + + expect_tree_entries HEAD~ <<-EOF && + b + c + EOF + + expect_tree_entries HEAD <<-EOF + c + EOF + ) +' + +test_expect_success 'preserves original authorship' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + touch bar foo && + git add . && + GIT_AUTHOR_NAME="Other Author" \ + GIT_AUTHOR_EMAIL="other@example.com" \ + git commit -m split-me && + + set_fake_editor "first" "second" && + git history split HEAD <<-EOF && + y + n + EOF + + git log -1 --format="%an <%ae>" HEAD~ >actual && + echo "Other Author <other@example.com>" >expect && + test_cmp expect actual && + + git log -1 --format="%an <%ae>" HEAD >actual && + test_cmp expect actual + ) +' + +test_expect_success 'aborts with empty commit message' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + touch bar foo && + git add . && + git commit -m split-me && + + set_fake_editor "" && + test_must_fail git history split HEAD <<-EOF 2>err && + y + n + EOF + test_grep "Aborting commit due to empty commit message." err + ) +' + +test_expect_success 'commit message editor sees split-out changes' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + touch bar foo && + git add . && + git commit -m split-me && + + write_script fake-editor.sh <<-\EOF && + cat "$1" >>MESSAGES && + echo "some commit message" >"$1" + EOF + test_set_editor "$(pwd)"/fake-editor.sh && + + git history split HEAD <<-EOF && + y + n + EOF + + # Note that we expect to see the messages twice, once for each + # of the commits. The committed files are different though. + cat >expect <<-EOF && + split-me + + # Please enter the commit message for the split-out changes. Lines starting + # with ${SQ}#${SQ} will be ignored, and an empty message aborts the commit. + # Changes to be committed: + # new file: bar + # + split-me + + # Please enter the commit message for the split-out changes. Lines starting + # with ${SQ}#${SQ} will be ignored, and an empty message aborts the commit. + # Changes to be committed: + # new file: foo + # + EOF + test_cmp expect MESSAGES && + + expect_log <<-EOF + some commit message + some commit message + EOF + ) +' + +test_expect_success 'can use pathspec to limit what gets split' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + touch bar foo && + git add . && + git commit -m split-me && + + set_fake_editor "first" "second" && + git history split HEAD -- foo <<-EOF && + y + EOF + + expect_tree_entries HEAD~ <<-EOF && + foo + EOF + + expect_tree_entries HEAD <<-EOF + bar + foo + EOF + ) +' + +test_expect_success 'pathspec matching no files produces empty split error' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + touch bar foo && + git add . && + git commit -m split-me && + + set_fake_editor "first" "second" && + test_must_fail git history split HEAD -- nonexistent 2>err && + test_grep "split commit is empty" err + ) +' + +test_expect_success 'split with multiple pathspecs' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + touch a b c d && + git add . && + git commit -m split-me && + + # Only a and c should be offered for splitting. + set_fake_editor "split-ac" "remainder" && + git history split HEAD -- a c <<-EOF && + y + y + EOF + + expect_tree_entries HEAD~ <<-EOF && + a + c + initial.t + EOF + + expect_tree_entries HEAD <<-EOF + a + b + c + d + initial.t + EOF + ) +' + +test_expect_success 'split with file mode change' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + echo content >script && + git add . && + git commit -m base && + test_chmod +x script && + echo change >script && + git commit -a -m "mode and content change" && + + set_fake_editor "mode-change" "content-change" && + git history split HEAD <<-EOF && + y + n + EOF + + expect_log <<-EOF + content-change + mode-change + base + EOF + ) +' + +test_expect_success 'refuses to create empty split-out commit' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit base && + touch bar foo && + git add . && + git commit -m split-me && + + test_must_fail git history split HEAD 2>err <<-EOF && + n + n + EOF + test_grep "split commit is empty" err + ) +' + +test_expect_success 'hooks are not executed for rewritten commits' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + touch bar foo && + git add . && + git commit -m split-me && + old_head=$(git rev-parse HEAD) && + + ORIG_PATH="$(pwd)" && + export ORIG_PATH && + for hook in prepare-commit-msg pre-commit post-commit post-rewrite commit-msg + do + write_script .git/hooks/$hook <<-\EOF || exit 1 + touch "$ORIG_PATH"/hooks.log + EOF + done && + + set_fake_editor "first" "second" && + git history split HEAD <<-EOF && + y + n + EOF + + expect_log <<-EOF && + second + first + EOF + + test_path_is_missing hooks.log + ) +' + +test_expect_success 'refuses to create empty original commit' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + touch bar foo && + git add . && + git commit -m split-me && + + test_must_fail git history split HEAD 2>err <<-EOF && + y + y + EOF + test_grep "split commit tree matches original commit" err + ) +' + +test_expect_success 'retains changes in the worktree and index' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + echo a >a && + echo b >b && + git add . && + git commit -m "initial commit" && + echo a-modified >a && + echo b-modified >b && + git add b && + set_fake_editor "a-only" "remainder" && + git history split HEAD <<-EOF && + y + n + EOF + + expect_tree_entries HEAD~ <<-EOF && + a + EOF + expect_tree_entries HEAD <<-EOF && + a + b + EOF + + cat >expect <<-\EOF && + M a + M b + ?? actual + ?? expect + ?? fake-editor.sh + ?? fake-input + EOF + git status --porcelain >actual && + test_cmp expect actual + ) +' + +test_done
diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh index 58b3759..89819ad 100755 --- a/t/t3650-replay-basics.sh +++ b/t/t3650-replay-basics.sh
@@ -25,6 +25,8 @@ git switch -c topic3 && test_commit G && test_commit H && + git switch -c empty && + git commit --allow-empty -m empty && git switch -c topic4 main && test_commit I && test_commit J && @@ -43,6 +45,13 @@ test_commit L && test_commit M && + git switch --detach topic4 && + test_commit N && + test_commit O && + git switch -c topic-with-merge topic4 && + test_merge P O --no-ff && + git switch main && + git switch -c conflict B && test_commit C.conflict C.t conflict ' @@ -51,8 +60,57 @@ git clone --bare . bare ' +test_expect_success 'argument to --advance must be a reference' ' + echo "fatal: argument to --advance must be a reference" >expect && + oid=$(git rev-parse main) && + test_must_fail git replay --advance=$oid topic1..topic2 2>actual && + test_cmp expect actual +' + +test_expect_success '--onto with invalid commit-ish' ' + printf "fatal: ${SQ}refs/not-valid${SQ} is not " >expect && + printf "a valid commit-ish for --onto\n" >>expect && + test_must_fail git replay --onto=refs/not-valid topic1..topic2 2>actual && + test_cmp expect actual +' + +test_expect_success 'exactly one of --onto, --advance, or --revert is required' ' + echo "error: exactly one of --onto, --advance, or --revert is required" >expect && + test_might_fail git replay -h >>expect && + test_must_fail git replay topic1..topic2 2>actual && + test_cmp expect actual +' + +test_expect_success 'replay down to root onto another branch' ' + git replay --ref-action=print --onto main topic2 >result && + + test_line_count = 1 result && + + git log --format=%s $(cut -f 3 -d " " result) >actual && + test_write_lines E D C M L B A >expect && + test_cmp expect actual +' + +test_expect_success '--advance and --contained cannot be used together' ' + test_must_fail git replay --advance=main --contained \ + topic1..topic2 2>actual && + test_grep "cannot be used together" actual +' + +test_expect_success 'cannot advance target ... ordering would be ill-defined' ' + echo "fatal: ${SQ}--advance${SQ} cannot be used with multiple revision ranges because the ordering would be ill-defined" >expect && + test_must_fail git replay --advance=main main topic1 topic2 2>actual && + test_cmp expect actual +' + +test_expect_success 'replaying merge commits is not supported yet' ' + echo "fatal: replaying merge commits is not supported yet!" >expect && + test_must_fail git replay --advance=main main..topic-with-merge 2>actual && + test_cmp expect actual +' + test_expect_success 'using replay to rebase two branches, one on top of other' ' - git replay --onto main topic1..topic2 >result && + git replay --ref-action=print --onto main topic1..topic2 >result && test_line_count = 1 result && @@ -68,7 +126,7 @@ ' test_expect_success 'using replay on bare repo to rebase two branches, one on top of other' ' - git -C bare replay --onto main topic1..topic2 >result-bare && + git -C bare replay --ref-action=print --onto main topic1..topic2 >result-bare && test_cmp expect result-bare ' @@ -86,7 +144,7 @@ # 2nd field of result is refs/heads/main vs. refs/heads/topic2 # 4th field of result is hash for main instead of hash for topic2 - git replay --advance main topic1..topic2 >result && + git replay --ref-action=print --advance main topic1..topic2 >result && test_line_count = 1 result && @@ -102,10 +160,29 @@ ' test_expect_success 'using replay on bare repo to perform basic cherry-pick' ' - git -C bare replay --advance main topic1..topic2 >result-bare && + git -C bare replay --ref-action=print --advance main topic1..topic2 >result-bare && test_cmp expect result-bare ' +test_expect_success 'commits that become empty are dropped' ' + # Save original branches + git for-each-ref --format="update %(refname) %(objectname)" \ + refs/heads/ >original-branches && + test_when_finished "git update-ref --stdin <original-branches && + rm original-branches" && + # Cherry-pick tip of topic1 ("F"), from the middle of A..empty, to main + git replay --advance main topic1^! && + + # Replay all of A..empty onto main (which includes topic1 & thus F + # in the middle) + git replay --onto main --branches --ancestry-path=empty ^A \ + >result && + git log --format="%s%d" L..empty >actual && + test_write_lines >expect \ + "empty (empty)" "H (topic3)" G "C (topic1)" "F (main)" "M (tag: M)" && + test_cmp expect actual +' + test_expect_success 'replay on bare repo fails with both --advance and --onto' ' test_must_fail git -C bare replay --advance main --onto main topic1..topic2 >result-bare ' @@ -115,7 +192,7 @@ ' test_expect_success 'using replay to also rebase a contained branch' ' - git replay --contained --onto main main..topic3 >result && + git replay --ref-action=print --contained --onto main main..topic3 >result && test_line_count = 2 result && cut -f 3 -d " " result >new-branch-tips && @@ -139,12 +216,12 @@ ' test_expect_success 'using replay on bare repo to also rebase a contained branch' ' - git -C bare replay --contained --onto main main..topic3 >result-bare && + git -C bare replay --ref-action=print --contained --onto main main..topic3 >result-bare && test_cmp expect result-bare ' test_expect_success 'using replay to rebase multiple divergent branches' ' - git replay --onto main ^topic1 topic2 topic4 >result && + git replay --ref-action=print --onto main ^topic1 topic2 topic4 >result && test_line_count = 2 result && cut -f 3 -d " " result >new-branch-tips && @@ -168,7 +245,7 @@ ' test_expect_success 'using replay on bare repo to rebase multiple divergent branches, including contained ones' ' - git -C bare replay --contained --onto main ^main topic2 topic3 topic4 >result && + git -C bare replay --ref-action=print --contained --onto main ^main topic2 topic3 topic4 >result && test_line_count = 4 result && cut -f 3 -d " " result >new-branch-tips && @@ -195,6 +272,15 @@ done ' +test_expect_success 'using replay to update detached HEAD' ' + current_head=$(git branch --show-current) && + test_when_finished git switch "$current_head" && + git switch --detach && + test_commit something && + git replay --ref-action=print --onto HEAD~2 --ref-action=print HEAD~..HEAD >updates && + test_grep "update HEAD " updates +' + test_expect_success 'merge.directoryRenames=false' ' # create a test case that stress-tests the rename caching git switch -c rename-onto && @@ -217,4 +303,200 @@ --onto rename-onto rename-onto..rename-from ' +test_expect_success 'default atomic behavior updates refs directly' ' + # Use a separate branch to avoid contaminating topic2 for later tests + git branch test-atomic topic2 && + test_when_finished "git branch -D test-atomic" && + + # Test default atomic behavior (no output, refs updated) + git replay --onto main topic1..test-atomic >output && + test_must_be_empty output && + + # Verify ref was updated + git log --format=%s test-atomic >actual && + test_write_lines E D M L B A >expect && + test_cmp expect actual && + + # Verify reflog message includes SHA of onto commit + git reflog test-atomic -1 --format=%gs >reflog-msg && + ONTO_SHA=$(git rev-parse main) && + echo "replay --onto $ONTO_SHA" >expect-reflog && + test_cmp expect-reflog reflog-msg +' + +test_expect_success 'atomic behavior in bare repository' ' + # Store original state for cleanup + START=$(git -C bare rev-parse topic2) && + test_when_finished "git -C bare update-ref refs/heads/topic2 $START" && + + # Test atomic updates work in bare repo + git -C bare replay --onto main topic1..topic2 >output && + test_must_be_empty output && + + # Verify ref was updated in bare repo + git -C bare log --format=%s topic2 >actual && + test_write_lines E D M L B A >expect && + test_cmp expect actual +' + +test_expect_success 'reflog message for --advance mode' ' + # Store original state + START=$(git rev-parse main) && + test_when_finished "git update-ref refs/heads/main $START" && + + # Test --advance mode reflog message + git replay --advance main topic1..topic2 >output && + test_must_be_empty output && + + # Verify reflog message includes --advance and branch name + git reflog main -1 --format=%gs >reflog-msg && + echo "replay --advance main" >expect-reflog && + test_cmp expect-reflog reflog-msg +' + +test_expect_success 'replay.refAction=print config option' ' + # Store original state + START=$(git rev-parse topic2) && + test_when_finished "git branch -f topic2 $START" && + + # Test with config set to print + test_config replay.refAction print && + git replay --onto main topic1..topic2 >output && + test_line_count = 1 output && + test_grep "^update refs/heads/topic2 " output +' + +test_expect_success 'replay.refAction=update config option' ' + # Store original state + START=$(git rev-parse topic2) && + test_when_finished "git branch -f topic2 $START" && + + # Test with config set to update + test_config replay.refAction update && + git replay --onto main topic1..topic2 >output && + test_must_be_empty output && + + # Verify ref was updated + git log --format=%s topic2 >actual && + test_write_lines E D M L B A >expect && + test_cmp expect actual +' + +test_expect_success 'command-line --ref-action overrides config' ' + # Store original state + START=$(git rev-parse topic2) && + test_when_finished "git branch -f topic2 $START" && + + # Set config to update but use --ref-action=print + test_config replay.refAction update && + git replay --ref-action=print --onto main topic1..topic2 >output && + test_line_count = 1 output && + test_grep "^update refs/heads/topic2 " output +' + +test_expect_success 'invalid replay.refAction value' ' + test_config replay.refAction invalid && + test_must_fail git replay --onto main topic1..topic2 2>error && + test_grep "invalid.*replay.refAction.*value" error +' + +test_expect_success 'argument to --revert must be a reference' ' + echo "fatal: argument to --revert must be a reference" >expect && + oid=$(git rev-parse main) && + test_must_fail git replay --revert=$oid topic1..topic2 2>actual && + test_cmp expect actual +' + +test_expect_success 'cannot revert with multiple sources' ' + echo "fatal: ${SQ}--revert${SQ} cannot be used with multiple revision ranges because the ordering would be ill-defined" >expect && + test_must_fail git replay --revert main main topic1 topic2 2>actual && + test_cmp expect actual +' + +test_expect_success 'using replay --revert to revert commits' ' + # Reuse existing topic4 branch (has commits I and J on top of main) + START=$(git rev-parse topic4) && + test_when_finished "git branch -f topic4 $START" && + + # Revert commits I and J + git replay --revert topic4 topic4~2..topic4 && + + # Verify the revert commits were created (newest-first ordering + # means J is reverted first, then I on top) + git log --format=%s -4 topic4 >actual && + cat >expect <<-\EOF && + Revert "I" + Revert "J" + J + I + EOF + test_cmp expect actual && + + # Verify commit message format includes hash (tip is Revert "I") + test_commit_message topic4 <<-EOF && + Revert "I" + + This reverts commit $(git rev-parse I). + EOF + + # Verify reflog message + git reflog topic4 -1 --format=%gs >reflog-msg && + echo "replay --revert topic4" >expect-reflog && + test_cmp expect-reflog reflog-msg +' + +test_expect_success 'using replay --revert in bare repo' ' + # Reuse existing topic4 in bare repo + START=$(git -C bare rev-parse topic4) && + test_when_finished "git -C bare update-ref refs/heads/topic4 $START" && + + # Revert commit J in bare repo + git -C bare replay --revert topic4 topic4~1..topic4 && + + # Verify revert was created + git -C bare log -1 --format=%s topic4 >actual && + echo "Revert \"J\"" >expect && + test_cmp expect actual +' + +test_expect_success 'revert of revert uses Reapply' ' + # Use topic4 and first revert J, then revert the revert + START=$(git rev-parse topic4) && + test_when_finished "git branch -f topic4 $START" && + + # First revert J + git replay --revert topic4 topic4~1..topic4 && + REVERT_J=$(git rev-parse topic4) && + + # Now revert the revert - should become Reapply + git replay --revert topic4 topic4~1..topic4 && + + # Verify Reapply prefix and message format + test_commit_message topic4 <<-EOF + Reapply "J" + + This reverts commit $REVERT_J. + EOF +' + +test_expect_success 'git replay --revert with conflict' ' + # conflict branch has C.conflict which conflicts with topic1s C + test_expect_code 1 git replay --revert conflict B..topic1 +' + +test_expect_success 'git replay --revert incompatible with --contained' ' + test_must_fail git replay --revert topic4 --contained topic4~1..topic4 2>error && + test_grep "cannot be used together" error +' + +test_expect_success 'git replay --revert incompatible with --onto' ' + test_must_fail git replay --revert topic4 --onto main topic4~1..topic4 2>error && + test_grep "cannot be used together" error +' + +test_expect_success 'git replay --revert incompatible with --advance' ' + test_must_fail git replay --revert topic4 --advance main topic4~1..topic4 2>error && + test_grep "cannot be used together" error +' + test_done
diff --git a/t/t3700-add.sh b/t/t3700-add.sh index df580a5..2947bf9 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh
@@ -38,7 +38,8 @@ ' test_expect_success 'Post-check that foo is in the index' ' - git ls-files foo | grep foo + git ls-files foo >actual && + test_grep foo actual ' test_expect_success 'Test that "git add -- -q" works' ' @@ -140,7 +141,7 @@ git ls-files >files && sed -n "/\\.ig/p" <files >actual && test_must_be_empty actual && - grep a.if files + test_grep a.if files ' test_expect_success 'add ignored ones with -f' ' @@ -195,8 +196,9 @@ echo new > file && echo new > symlink && git add file symlink && - git ls-files --stage | grep "^100755 .* 0 file$" && - git ls-files --stage | grep "^120000 .* 0 symlink$" + git ls-files --stage >actual && + test_grep "^100755 .* 0 file$" actual && + test_grep "^120000 .* 0 symlink$" actual ' test_expect_success 'git add with filemode=0, symlinks=0 prefers stage 2 over stage 1' ' @@ -212,8 +214,9 @@ echo new > file && echo new > symlink && git add file symlink && - git ls-files --stage | grep "^100755 .* 0 file$" && - git ls-files --stage | grep "^120000 .* 0 symlink$" + git ls-files --stage >actual && + test_grep "^100755 .* 0 file$" actual && + test_grep "^120000 .* 0 symlink$" actual ' test_expect_success 'git add --refresh' ' @@ -238,8 +241,8 @@ test_must_be_empty actual && git diff-files --name-only >actual && - ! grep bar actual && - grep baz actual + test_grep ! bar actual && + test_grep baz actual ' test_expect_success 'git add --refresh correctly reports no match error' " @@ -254,7 +257,8 @@ date >foo2 && chmod 0 foo2 && test_must_fail git add --verbose . && - ! ( git ls-files foo1 | grep foo1 ) + git ls-files foo1 >actual && + test_grep ! foo1 actual ' rm -f foo2 @@ -265,7 +269,8 @@ date >foo2 && chmod 0 foo2 && test_must_fail git add --verbose --ignore-errors . && - git ls-files foo1 | grep foo1 + git ls-files foo1 >actual && + test_grep foo1 actual ' rm -f foo2 @@ -277,7 +282,8 @@ date >foo2 && chmod 0 foo2 && test_must_fail git add --verbose . && - git ls-files foo1 | grep foo1 + git ls-files foo1 >actual && + test_grep foo1 actual ' rm -f foo2 @@ -288,7 +294,8 @@ date >foo2 && chmod 0 foo2 && test_must_fail git add --verbose . && - ! ( git ls-files foo1 | grep foo1 ) + git ls-files foo1 >actual && + test_grep ! foo1 actual ' rm -f foo2 @@ -299,7 +306,8 @@ date >foo2 && chmod 0 foo2 && test_must_fail git add --verbose --no-ignore-errors . && - ! ( git ls-files foo1 | grep foo1 ) && + git ls-files foo1 >actual && + test_grep ! foo1 actual && git config add.ignore-errors 0 ' rm -f foo2 @@ -308,8 +316,10 @@ git reset --hard && touch fo\[ou\]bar foobar && git add '\''fo\[ou\]bar'\'' && - git ls-files fo\[ou\]bar | grep -F fo\[ou\]bar && - ! ( git ls-files foobar | grep foobar ) + git ls-files fo\[ou\]bar >actual && + test_grep -F fo\[ou\]bar actual && + git ls-files foobar >actual && + test_grep ! foobar actual ' test_expect_success 'git add to resolve conflicts on otherwise ignored path' ' @@ -326,7 +336,8 @@ test_expect_success '"add non-existent" should fail' ' test_must_fail git add non-existent && - ! (git ls-files | grep "non-existent") + git ls-files >actual && + test_grep ! "non-existent" actual ' test_expect_success 'git add -A on empty repo does not error out' ' @@ -388,6 +399,7 @@ test_must_fail git add empty >actual 2>&1 && cat >expect <<-EOF && error: '"'empty/'"' does not have a commit checked out + error: unable to index file '"'empty/'"' fatal: adding files failed EOF test_cmp expect actual @@ -535,9 +547,36 @@ touch x y z sub/a sub/dir/b && git add -A && git add --chmod=+x . && - test $(git ls-files --stage | grep ^100644 | wc -l) -eq 0 && + git ls-files --stage >actual && + test_grep ! ^100644 actual && git add --chmod=-x . && - test $(git ls-files --stage | grep ^100755 | wc -l) -eq 0 + git ls-files --stage >actual && + test_grep ! ^100755 actual + ) +' + +test_expect_success 'cannot add a submodule of a different algorithm' ' + git init --object-format=sha256 sha256 && + ( + cd sha256 && + test_commit abc && + git init --object-format=sha1 submodule && + test_commit -C submodule def && + test_must_fail git add submodule 2>err && + test_grep "cannot add a submodule of a different hash algorithm" err && + git ls-files --stage >entries && + test_grep ! ^160000 entries + ) && + git init --object-format=sha1 sha1 && + ( + cd sha1 && + test_commit abc && + git init --object-format=sha256 submodule && + test_commit -C submodule def && + test_must_fail git add submodule 2>err && + test_grep "cannot add a submodule of a different hash algorithm" err && + git ls-files --stage >entries && + test_grep ! ^160000 entries ) '
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 4285314..6e120a4 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh
@@ -527,7 +527,7 @@ _10 +15 _20 - (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_ + (1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_ EOF test_write_lines s y g 1 | git add -p >actual && tail -n 7 <actual >actual.trimmed && @@ -540,7 +540,7 @@ _10 +15 _20 - (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_ + (1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_ EOF test_write_lines s y g1 | git add -p >actual && tail -n 4 <actual >actual.trimmed && @@ -554,7 +554,7 @@ _10 +15 _20 - (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_ + (1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_ EOF test_write_lines s y /1,2 | git add -p >actual && tail -n 5 <actual >actual.trimmed && @@ -567,7 +567,7 @@ _10 +15 _20 - (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_ + (1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_ EOF test_write_lines s y / 1,2 | git add -p >actual && tail -n 4 <actual >actual.trimmed && @@ -579,11 +579,11 @@ tr _ " " >expect <<-EOF && +15 20 - (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? @@ -1,2 +1,3 @@ + (1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? @@ -1,2 +1,3 @@ 10 +15 20 - (1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_ + (1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]?_ EOF test_write_lines s y g 1 p | git add -p >actual && tail -n 7 <actual >actual.trimmed && @@ -595,11 +595,11 @@ cat >expect <<-EOF && <GREEN>+<RESET><GREEN>15<RESET> 20<RESET> - <BOLD;BLUE>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET>PAGER <CYAN>@@ -1,2 +1,3 @@<RESET> + <BOLD;BLUE>(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET>PAGER <CYAN>@@ -1,2 +1,3 @@<RESET> PAGER 10<RESET> PAGER <GREEN>+<RESET><GREEN>15<RESET> PAGER 20<RESET> - <BOLD;BLUE>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET> + <BOLD;BLUE>(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET> EOF test_write_lines s y g 1 P | ( @@ -810,7 +810,7 @@ <BOLD>-old<RESET> <BLUE>+new<RESET> <CYAN> more-context<RESET> - <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET> + <YELLOW>(1/2) Stage this hunk (was: y) [y,n,q,a,d,k,K,j,J,g,/,e,p,P,?]? <RESET> EOF test_cmp expect actual ' @@ -1441,5 +1441,105 @@ test_grep file out && test_grep ! file2 out ' +for cmd in add checkout reset "stash save" "stash push" +do + test_expect_success "$cmd rejects invalid --no-auto-advance options" ' + test_must_fail git $cmd --no-auto-advance 2>actual && + test_grep -E "requires .*--(interactive|patch)" actual + ' +done + +test_expect_success 'manual advance (">") moves to next file with --no-auto-advance' ' + git reset --hard && + echo line1 >first-file && + echo line2 >second-file && + git add -A && + git commit -m initial >/dev/null 2>&1 && + echo change_first >>first-file && + echo change_second >>second-file && + + printf ">\nq\n" | git add -p --no-auto-advance >output.test 2>&1 && + test_grep -E "(a|b)/second-file" output.test +' + +test_expect_success 'select n on a hunk, go to another file, come back and change to y stages' ' + git reset --hard && + echo one >f1 && + echo one >f2 && + git add -A && + git commit -m initial >/dev/null 2>&1 && + echo change1 >>f1 && + echo change2 >>f2 && + + printf "n\n>\n<\ny\nq\n" | git add -p --no-auto-advance >output.staged 2>&1 && + git diff --cached --name-only >staged && + test_grep -E "(a/f1)" output.staged +' + +test_expect_success 'select y on a hunk, go to another file, come back and change to n does not stage' ' + git reset --hard && + echo one >f1 && + echo one >f2 && + git add -A && + git commit -m initial >/dev/null 2>&1 && + echo change1 >>f1 && + echo change2 >>f2 && + + printf "y\n>\n<\nn\nq\n" | git add -p --no-auto-advance >output.unstaged 2>&1 && + git diff --cached --name-only >staged && + test_must_be_empty staged +' + +test_expect_success 'deciding all hunks in a file does not auto advance' ' + git reset --hard && + echo line >stay && + echo line >other && + git add -A && + git commit -m initial >/dev/null 2>&1 && + echo change >>stay && + echo change >>other && + test_write_lines y | git add -p --no-auto-advance >raw-output 2>&1 && + test_grep "(1/1) Stage this hunk (was: y)" raw-output && + test_grep ! "diff --git a/stay b/stay" raw-output +' +test_expect_success 'HUNKS SUMMARY does not show in help text when there are undecided hunks' ' + git reset --hard && + test_write_lines 1 2 3 4 5 6 7 8 9 >f && + git add f && + git commit -m initial >/dev/null 2>&1 && + test_write_lines 1 X 3 4 Y 6 7 Z 9 >f && + test_write_lines s y n | git add -p --no-auto-advance >raw-nostat 2>&1 && + test_grep ! "HUNKS SUMMARY - Hunks: " raw-nostat +' + +test_expect_success 'help text shows HUNK SUMMARY when all hunks have been decided' ' + git reset --hard && + test_write_lines 1 2 3 4 5 6 7 8 9 >f2 && + git add f2 && + git commit -m initial >/dev/null 2>&1 && + test_write_lines 1 X 3 4 Y 6 7 Z 9 >f2 && + printf "s\ny\nn\ny\n?\n" | git add -p --no-auto-advance >raw-stat 2>&1 && + test_grep "HUNKS SUMMARY - Hunks: 3, USE: 2, SKIP: 1" raw-stat +' + +test_expect_success 'selective staging across multiple files with --no-advance' ' + git reset --hard && + test_write_lines 1 2 3 4 5 6 7 8 9 >a.file && + test_write_lines 1 2 3 4 5 6 7 8 9 >b.file && + test_write_lines 1 2 3 4 5 6 7 8 9 >c.file && + git add -A && + git commit -m initial >/dev/null 2>&1 && + test_write_lines 1 A2 3 4 A5 6 7 8 9 >a.file && + test_write_lines 1 2 B3 4 5 6 7 B8 9 >b.file && + test_write_lines C1 2 3 4 5 C6 7 8 9 >c.file && + printf "s\ny\nn\n>\ns\nn\ny\n>\ns\ny\ny\nq\n" | git add -p --no-auto-advance >output.index 2>&1 && + git diff --cached >staged.diff && + test_grep "+A2" staged.diff && + test_grep ! "+A5" staged.diff && + test_grep "+B8" staged.diff && + test_grep ! "+B3" staged.diff && + test_grep "+C1" staged.diff && + test_grep "+C6" staged.diff +' test_done
diff --git a/t/t3902-quoted.sh b/t/t3902-quoted.sh index f528008..8660ec5 100755 --- a/t/t3902-quoted.sh +++ b/t/t3902-quoted.sh
@@ -60,16 +60,18 @@ "\346\277\261\351\207\216\347\264\224" EOF -cat >expect.raw <<\EOF +# NOTE: do not quote this heredoc, Dash 0.5.13 has a bug with heredocs +# that contain multibyte chars. +cat >expect.raw <<EOF Name -"Name and a\nLF" -"Name and an\tHT" -"Name\"" +"Name and a\\nLF" +"Name and an\\tHT" +"Name\\"" With SP in it -"濱野\t純" -"濱野\n純" +"濱野\\t純" +"濱野\\n純" 濱野 純 -"濱野\"純" +"濱野\\"純" 濱野/file 濱野純 EOF
diff --git a/t/t4007-rename-3.sh b/t/t4007-rename-3.sh index e8faf0d..1012a37 100755 --- a/t/t4007-rename-3.sh +++ b/t/t4007-rename-3.sh
@@ -41,6 +41,16 @@ compare_diff_raw current expected ' +test_expect_success 'exit code of quiet copy detection' ' + test_expect_code 1 \ + git diff --quiet --cached --find-copies-harder $tree +' + +test_expect_success 'exit code of quiet copy detection with --no-ext-diff' ' + test_expect_code 1 \ + git diff --quiet --cached --find-copies-harder --no-ext-diff $tree +' + # In the tree, there is only path0/COPYING. In the cache, path0 and # path1 both have COPYING and the latter is a copy of path0/COPYING. # However when we say we care only about path1, we should just see @@ -57,7 +67,28 @@ ' test_expect_success 'tweak work tree' ' - rm -f path0/COPYING && + rm -f path0/COPYING +' + +cat >expected <<EOF +:100644 100644 $blob $blob C100 path1/COPYING path0/COPYING +EOF + +# The cache has path0/COPYING and path1/COPYING, the working tree only +# path1/COPYING. This is a deletion -- we don't treat deduplication +# specially. In reverse it should be detected as a copy, though. +test_expect_success 'copy detection, files to index' ' + git diff-files -C --find-copies-harder -R >current && + compare_diff_raw current expected +' + +test_expect_success 'copy detection, files to preloaded index' ' + GIT_TEST_PRELOAD_INDEX=1 \ + git diff-files -C --find-copies-harder -R >current && + compare_diff_raw current expected +' + +test_expect_success 'tweak index' ' git update-index --remove path0/COPYING ' # In the tree, there is only path0/COPYING. In the cache, path0 does
diff --git a/t/t4012-diff-binary.sh b/t/t4012-diff-binary.sh index d1d30ac..97b5ac0 100755 --- a/t/t4012-diff-binary.sh +++ b/t/t4012-diff-binary.sh
@@ -68,7 +68,7 @@ sed -e "s/-CIT/xCIT/" <output >broken && test_must_fail git apply --stat --summary broken 2>detected && detected=$(cat detected) && - detected=$(expr "$detected" : "error.*at line \\([0-9]*\\)\$") && + detected=$(expr "$detected" : "error.*broken:\\([0-9]*\\)\$") && detected=$(sed -ne "${detected}p" broken) && test "$detected" = xCIT ' @@ -77,7 +77,7 @@ git diff --binary | sed -e "s/-CIT/xCIT/" >broken && test_must_fail git apply --stat --summary broken 2>detected && detected=$(cat detected) && - detected=$(expr "$detected" : "error.*at line \\([0-9]*\\)\$") && + detected=$(expr "$detected" : "error.*broken:\\([0-9]*\\)\$") && detected=$(sed -ne "${detected}p" broken) && test "$detected" = xCIT '
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 2782b1f..0b89d12 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh
@@ -380,6 +380,131 @@ done ' +test_expect_success 'cover letter with subject, author and count' ' + rm -rf patches && + test_when_finished "git reset --hard HEAD~1" && + test_when_finished "rm -rf patches test_file" && + touch test_file && + git add test_file && + git commit -m "This is a subject" && + git format-patch --commit-list-format="log:[%(count)/%(total)] %s (%an)" \ + -o patches HEAD~1 && + test_grep "^\[1/1\] This is a subject (A U Thor)$" patches/0000-cover-letter.patch +' + +test_expect_success 'cover letter with custom format no prefix' ' + rm -rf patches && + test_when_finished "git reset --hard HEAD~1" && + test_when_finished "rm -rf patches test_file" && + touch test_file && + git add test_file && + git commit -m "This is a subject" && + git format-patch --commit-list-format="[%(count)/%(total)] %s (%an)" \ + -o patches HEAD~1 && + test_grep "^\[1/1\] This is a subject (A U Thor)$" patches/0000-cover-letter.patch +' + +test_expect_success 'cover letter fail when no prefix and no placeholder' ' + rm -rf patches && + test_when_finished "git reset --hard HEAD~1" && + test_when_finished "rm -rf patches test_file err" && + touch test_file && + git add test_file && + git commit -m "This is a subject" && + test_must_fail git format-patch --commit-list-format="this should fail" \ + -o patches HEAD~1 2>err && + test_grep "is not a valid format string" err +' + +test_expect_success 'cover letter modern format' ' + test_when_finished "git reset --hard HEAD~1" && + test_when_finished "rm -rf patches test_file" && + touch test_file && + git add test_file && + git commit -m "This is a subject" && + git format-patch --commit-list-format="modern" -o patches HEAD~1 && + test_grep "^\[1/1\] This is a subject$" patches/0000-cover-letter.patch +' + +test_expect_success 'cover letter shortlog format' ' + test_when_finished "git reset --hard HEAD~1" && + test_when_finished "rm -rf expect patches result test_file" && + cat >expect <<-"EOF" && + A U Thor (1): + This is a subject + EOF + touch test_file && + git add test_file && + git commit -m "This is a subject" && + git format-patch --commit-list-format=shortlog -o patches HEAD~1 && + grep -E -A 1 "^A U Thor \([[:digit:]]+\):$" patches/0000-cover-letter.patch >result && + cat result && + test_cmp expect result +' + +test_expect_success 'no cover letter but with format specified' ' + test_when_finished "git reset --hard HEAD~1" && + test_when_finished "rm -rf patches result test_file" && + touch test_file && + git add test_file && + git commit -m "This is a subject" && + git format-patch --no-cover-letter --commit-list-format="[%(count)] %s" -o patches HEAD~1 && + test_path_is_missing patches/0000-cover-letter.patch +' + +test_expect_success 'cover letter config with count, subject and author' ' + test_when_finished "rm -rf patches result" && + test_when_finished "git config unset format.coverletter" && + test_when_finished "git config unset format.commitlistformat" && + git config set format.coverletter true && + git config set format.commitlistformat "log:[%(count)/%(total)] %s (%an)" && + git format-patch -o patches HEAD~2 && + grep -E "^[[[:digit:]]+/[[:digit:]]+] .* \(A U Thor\)" patches/0000-cover-letter.patch >result && + test_line_count = 2 result +' + +test_expect_success 'cover letter config with count and author' ' + test_when_finished "rm -rf patches result" && + test_when_finished "git config unset format.coverletter" && + test_when_finished "git config unset format.commitlistformat" && + git config set format.coverletter true && + git config set format.commitlistformat "log:[%(count)/%(total)] (%an)" && + git format-patch -o patches HEAD~2 && + grep -E "^[[[:digit:]]+/[[:digit:]]+] \(A U Thor\)" patches/0000-cover-letter.patch >result && + test_line_count = 2 result +' + +test_expect_success 'cover letter config commitlistformat set to modern' ' + test_when_finished "rm -rf patches result" && + test_when_finished "git config unset format.coverletter" && + test_when_finished "git config unset format.commitlistformat" && + git config set format.coverletter true && + git config set format.commitlistformat modern && + git format-patch -o patches HEAD~2 && + grep -E "^[[[:digit:]]+/[[:digit:]]+] .*$" patches/0000-cover-letter.patch >result && + test_line_count = 2 result +' + +test_expect_success 'cover letter config commitlistformat set to shortlog' ' + test_when_finished "rm -rf patches result" && + test_when_finished "git config unset format.coverletter" && + test_when_finished "git config unset format.commitlistformat" && + git config set format.coverletter true && + git config set format.commitlistformat shortlog && + git format-patch -o patches HEAD~2 && + grep -E "^A U Thor \([[:digit:]]+\)" patches/0000-cover-letter.patch >result && + test_line_count = 1 result +' + +test_expect_success 'cover letter config commitlistformat not set' ' + test_when_finished "rm -rf patches result" && + test_when_finished "git config unset format.coverletter" && + git config set format.coverletter true && + git format-patch -o patches HEAD~2 && + grep -E "^A U Thor \([[:digit:]]+\)" patches/0000-cover-letter.patch >result && + test_line_count = 1 result +' + test_expect_success 'reroll count' ' rm -fr patches && git format-patch -o patches --cover-letter --reroll-count 4 main..side >list && @@ -980,7 +1105,7 @@ test_expect_success 'get git version' ' git_version=$(git --version) && - git_version=${git_version##* } + git_version=${git_version#git version } ' signature() { @@ -1285,7 +1410,9 @@ check_author "Foö Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar" ' -cat >expect <<'EOF' +# NOTE: do not quote this heredoc, Dash 0.5.13 has a bug with heredocs +# that contain multibyte chars. +cat >expect <<EOF From: Foö Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar Foo Bar <author@example.com> @@ -1300,7 +1427,9 @@ test_cmp expect actual ' -cat >expect <<'EOF' +# NOTE: do not quote this heredoc, Dash 0.5.13 has a bug with heredocs +# that contain multibyte chars. +cat >expect <<EOF Subject: [PATCH] Foö EOF test_expect_success 'subject lines are unencoded with --no-encode-email-headers' ' @@ -1312,7 +1441,9 @@ test_cmp expect actual ' -cat >expect <<'EOF' +# NOTE: do not quote this heredoc, Dash 0.5.13 has a bug with heredocs +# that contain multibyte chars. +cat >expect <<EOF Subject: [PATCH] Foö EOF test_expect_success 'subject lines are unencoded with format.encodeEmailHeaders=false' ' @@ -1472,6 +1603,14 @@ test_cmp expect patch.head ' +test_expect_success '--from applies to cover letter' ' + test_when_finished "rm -rf patches" && + git format-patch -1 --cover-letter --from="Foo Bar <author@example.com>" -o patches && + echo "From: Foo Bar <author@example.com>" >expect && + grep "^From:" patches/0000-cover-letter.patch >patch.head && + test_cmp expect patch.head +' + test_expect_success '--from omits redundant in-body header' ' git format-patch -1 --stdout --from="A U Thor <author@example.com>" >patch && cat >expect <<-\EOF && @@ -1523,7 +1662,9 @@ test_env GIT_AUTHOR_NAME="éxötìc" test_commit exotic && test_when_finished "git reset --hard HEAD^" && git format-patch -1 --stdout --from >patch && - cat >expect <<-\EOF && + # NOTE: do not quote this heredoc, Dash 0.5.13 has a bug with heredocs + # that contain multibyte chars. + cat >expect <<-EOF && From: C O Mitter <committer@example.com> Content-Type: text/plain; charset=UTF-8 @@ -2541,10 +2682,26 @@ grep "^--- blorp" actual ' +test_expect_success 'format.noprefix=false' ' + git -c format.noprefix=false format-patch -1 --stdout >actual && + grep "^--- a/blorp" actual +' + test_expect_success 'format-patch --default-prefix overrides format.noprefix' ' git -c format.noprefix \ format-patch -1 --default-prefix --stdout >actual && grep "^--- a/blorp" actual ' +test_expect_success 'errors on format.noprefix which is not boolean' ' + cat >expect <<-EOF && + fatal: bad boolean config value ${SQ}not-a-bool${SQ} for ${SQ}format.noprefix${SQ} + hint: ${SQ}format.noprefix${SQ} used to accept any value and treat that as ${SQ}true${SQ}. + hint: Now it only accepts boolean values, like what ${SQ}diff.noprefix${SQ} does. + EOF + test_must_fail git -c format.noprefix=not-a-bool \ + format-patch -1 --stdout 2>actual && + test_cmp expect actual +' + test_done
diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 9de7f73..b691d29 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh
@@ -43,6 +43,79 @@ ' done +test_expect_success "incomplete line in both pre- and post-image context" ' + (echo foo && echo baz | tr -d "\012") >x && + git add x && + (echo bar && echo baz | tr -d "\012") >x && + git diff x && + git -c core.whitespace=incomplete diff --check x && + git diff -R x && + git -c core.whitespace=incomplete diff -R --check x +' + +test_expect_success "incomplete lines on both pre- and post-image" ' + # The interpretation taken here is "since you are touching + # the line anyway, you would better fix the incomplete line + # while you are at it." but this is debatable. + echo foo | tr -d "\012" >x && + git add x && + echo bar | tr -d "\012" >x && + git diff x && + test_must_fail git -c core.whitespace=incomplete diff --check x >error && + test_grep "no newline at the end of file" error && + git diff -R x && + test_must_fail git -c core.whitespace=incomplete diff -R --check x >error && + test_grep "no newline at the end of file" error +' + +test_expect_success "fix incomplete line in pre-image" ' + echo foo | tr -d "\012" >x && + git add x && + echo bar >x && + git diff x && + git -c core.whitespace=incomplete diff --check x && + git diff -R x && + test_must_fail git -c core.whitespace=incomplete diff -R --check x >error && + test_grep "no newline at the end of file" error +' + +test_expect_success "new incomplete line in post-image" ' + echo foo >x && + git add x && + echo bar | tr -d "\012" >x && + git diff x && + test_must_fail git -c core.whitespace=incomplete diff --check x >error && + test_grep "no newline at the end of file" error && + git diff -R x && + git -c core.whitespace=incomplete diff -R --check x +' + +test_expect_success SYMLINKS "incomplete-line error is disabled for symlinks" ' + test_when_finished "git reset --hard" && + test_when_finished "rm -f mylink" && + + # a regular file with an incomplete line + printf "%s" one >mylink && + git add mylink && + + # a symbolic link + rm mylink && + ln -s two mylink && + + git -c diff.color=always -c core.whitespace=incomplete \ + diff mylink >forward.raw && + test_decode_color >forward <forward.raw && + test_grep ! "<BRED>\\\\ No newline at end of file<RESET>" forward && + + git -c diff.color=always -c core.whitespace=incomplete \ + diff -R mylink >reverse.raw && + test_decode_color >reverse <reverse.raw && + test_grep "<BRED>\\\\ No newline at end of file<RESET>" reverse && + + git -c core.whitespace=incomplete diff --check mylink && + test_must_fail git -c core.whitespace=incomplete diff --check -R mylink +' + test_expect_success "Ray Lehtiniemi's example" ' cat <<-\EOF >x && do { @@ -1040,7 +1113,8 @@ { echo "0. blank-at-eol " && echo "1. still-blank-at-eol " && - echo "2. and a new line " + echo "2. and a new line " && + printf "3. and more" } >x && new_hash_x=$(git hash-object x) && after=$(git rev-parse --short "$new_hash_x") && @@ -1050,11 +1124,13 @@ <BOLD>index $before..$after 100644<RESET> <BOLD>--- a/x<RESET> <BOLD>+++ b/x<RESET> - <CYAN>@@ -1,2 +1,3 @@<RESET> + <CYAN>@@ -1,2 +1,4 @@<RESET> 0. blank-at-eol <RESET> <RED>-<RESET><RED>1. blank-at-eol<RESET><BLUE> <RESET> <GREEN>+<RESET><GREEN>1. still-blank-at-eol<RESET><BLUE> <RESET> <GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET> + <GREEN>+<RESET><GREEN>3. and more<RESET> + <BLUE>\ No newline at end of file<RESET> EOF cat >expect.all <<-EOF && @@ -1062,11 +1138,13 @@ <BOLD>index $before..$after 100644<RESET> <BOLD>--- a/x<RESET> <BOLD>+++ b/x<RESET> - <CYAN>@@ -1,2 +1,3 @@<RESET> + <CYAN>@@ -1,2 +1,4 @@<RESET> <RESET>0. blank-at-eol<RESET><BLUE> <RESET> <RED>-<RESET><RED>1. blank-at-eol<RESET><BLUE> <RESET> <GREEN>+<RESET><GREEN>1. still-blank-at-eol<RESET><BLUE> <RESET> <GREEN>+<RESET><GREEN>2. and a new line<RESET><BLUE> <RESET> + <GREEN>+<RESET><GREEN>3. and more<RESET> + <BLUE>\ No newline at end of file<RESET> EOF cat >expect.none <<-EOF @@ -1074,16 +1152,19 @@ <BOLD>index $before..$after 100644<RESET> <BOLD>--- a/x<RESET> <BOLD>+++ b/x<RESET> - <CYAN>@@ -1,2 +1,3 @@<RESET> + <CYAN>@@ -1,2 +1,4 @@<RESET> 0. blank-at-eol <RESET> <RED>-1. blank-at-eol <RESET> <GREEN>+1. still-blank-at-eol <RESET> <GREEN>+2. and a new line <RESET> + <GREEN>+3. and more<RESET> + \ No newline at end of file<RESET> EOF ' test_expect_success 'test --ws-error-highlight option' ' + git config core.whitespace blank-at-eol,incomplete-line && git diff --color --ws-error-highlight=default,old >current.raw && test_decode_color <current.raw >current && @@ -1100,6 +1181,7 @@ ' test_expect_success 'test diff.wsErrorHighlight config' ' + git config core.whitespace blank-at-eol,incomplete-line && git -c diff.wsErrorHighlight=default,old diff --color >current.raw && test_decode_color <current.raw >current && @@ -1116,6 +1198,7 @@ ' test_expect_success 'option overrides diff.wsErrorHighlight' ' + git config core.whitespace blank-at-eol,incomplete-line && git -c diff.wsErrorHighlight=none \ diff --color --ws-error-highlight=default,old >current.raw && @@ -1135,6 +1218,8 @@ ' test_expect_success 'detect moved code, complete file' ' + git config core.whitespace blank-at-eol && + git reset --hard && cat <<-\EOF >test.c && #include<stdio.h>
diff --git a/t/t4041-diff-submodule-option.sh b/t/t4041-diff-submodule-option.sh index 4d4aa16..4dd4954 100755 --- a/t/t4041-diff-submodule-option.sh +++ b/t/t4041-diff-submodule-option.sh
@@ -37,8 +37,12 @@ test_tick && # "git commit -m" would break MinGW, as Windows refuse to pass # $test_encoding encoded parameter to git. - echo "Add $name ($added $name)" | iconv -f utf-8 -t $test_encoding | - git -c "i18n.commitEncoding=$test_encoding" commit -F - + message="Add $name ($added $name)" && + if test_have_prereq ICONV + then + message=$(echo "$message" | iconv -f utf-8 -t $test_encoding) + fi && + echo "$message" | git -c "i18n.commitEncoding=$test_encoding" commit -F - done >/dev/null && git rev-parse --short --verify HEAD )
diff --git a/t/t4052-stat-output.sh b/t/t4052-stat-output.sh index 740bb97..7c74906 100755 --- a/t/t4052-stat-output.sh +++ b/t/t4052-stat-output.sh
@@ -413,4 +413,36 @@ test_cmp expect actual ' +# We want git-log to print only 1 commit containing a single branch graph and a +# diffstat (the diffstat display width, when not manually set through the +# option "--stat-width", will be automatically calculated). +# The diffstat will be only one file, with a placeholder FILENAME, that, with +# enough terminal display width, will contain the following line: +# "<RED>|<RESET> ${FILENAME} | 0" +# where "<RED>" and "<RESET>" are ANSI escape codes to color the text. +# To calculate the minimium terminal display width MIN_TERM_WIDTH so that the +# FILENAME in the diffstat will not be shortened, we take the FILENAME length +# and add 9 to it. +# To check if the diffstat width, when the line_prefix (the "<RED>|<RESET>" of +# the graph) contains ANSI escape codes (the ANSI escape codes to color the +# text), is calculated correctly, we: +# 1. check if it contains the line defined before when using MIN_TERM_WIDTH +# 2. check if it contains the line defined before, but with the FILENAME +# shortened by only one character, when using MIN_TERM_WIDTH - 1 + +test_expect_success 'diffstat where line_prefix contains ANSI escape codes is correct width' ' + FILENAME="placeholder-text-placeholder-text" && + FILENAME_TRIMMED="...eholder-text-placeholder-text" && + MIN_TERM_WIDTH=$((${#FILENAME} + 9)) && + test_config color.diff always && + git commit --allow-empty --allow-empty-message && + >${FILENAME} && + git add ${FILENAME} && + git commit --allow-empty-message && + COLUMNS=$((MIN_TERM_WIDTH)) git log --graph --stat -n1 | test_decode_color >out && + test_grep "<RED>|<RESET> ${FILENAME} | 0" out && + COLUMNS=$((MIN_TERM_WIDTH - 1)) git log --graph --stat -n1 | test_decode_color >out && + test_grep "<RED>|<RESET> ${FILENAME_TRIMMED} | 0" out +' + test_done
diff --git a/t/t4053-diff-no-index.sh b/t/t4053-diff-no-index.sh index 6959927..15076df 100755 --- a/t/t4053-diff-no-index.sh +++ b/t/t4053-diff-no-index.sh
@@ -76,6 +76,16 @@ ) ' +test_expect_success 'git diff --find-object outside repo fails gracefully' ' + ( + GIT_CEILING_DIRECTORIES=$TRASH_DIRECTORY/non && + export GIT_CEILING_DIRECTORIES && + cd non/git && + test_must_fail git diff --find-object=abc123 2>err && + test_grep "find-object requires a git repository" err + ) +' + test_expect_success 'diff D F and diff F D' ' ( cd repo &&
diff --git a/t/t4059-diff-submodule-not-initialized.sh b/t/t4059-diff-submodule-not-initialized.sh index 0fe8105..bb902ce 100755 --- a/t/t4059-diff-submodule-not-initialized.sh +++ b/t/t4059-diff-submodule-not-initialized.sh
@@ -35,8 +35,12 @@ test_tick && # "git commit -m" would break MinGW, as Windows refuse to pass # $test_encoding encoded parameter to git. - echo "Add $name ($added $name)" | iconv -f utf-8 -t $test_encoding | - git -c "i18n.commitEncoding=$test_encoding" commit -F - + message="Add $name ($added $name)" && + if test_have_prereq ICONV + then + message=$(echo "$message" | iconv -f utf-8 -t $test_encoding) + fi && + echo "$message" | git -c "i18n.commitEncoding=$test_encoding" commit -F - done >/dev/null && git rev-parse --short --verify HEAD )
diff --git a/t/t4060-diff-submodule-option-diff-format.sh b/t/t4060-diff-submodule-option-diff-format.sh index dbfeb74..d8f9213 100755 --- a/t/t4060-diff-submodule-option-diff-format.sh +++ b/t/t4060-diff-submodule-option-diff-format.sh
@@ -35,8 +35,12 @@ test_tick && # "git commit -m" would break MinGW, as Windows refuse to pass # $test_encoding encoded parameter to git. - echo "Add $name ($added $name)" | iconv -f utf-8 -t $test_encoding | - git -c "i18n.commitEncoding=$test_encoding" commit -F - + message="Add $name ($added $name)" && + if test_have_prereq ICONV + then + message=$(echo "$message" | iconv -f utf-8 -t $test_encoding) + fi && + echo "$message" | git -c "i18n.commitEncoding=$test_encoding" commit -F - done >/dev/null && git rev-parse --short --verify HEAD )
diff --git a/t/t4067-diff-partial-clone.sh b/t/t4067-diff-partial-clone.sh index 581250d..3081310 100755 --- a/t/t4067-diff-partial-clone.sh +++ b/t/t4067-diff-partial-clone.sh
@@ -132,6 +132,72 @@ test_line_count = 1 done_lines ' +test_expect_success 'diff succeeds even if prefetch triggered by break-rewrites' ' + test_when_finished "rm -rf server client trace" && + + test_create_repo server && + echo xyz >server/foo && + mkdir server/bar && + test_seq -f "line %d" 1 100 >server/bar/baz && + git -C server add -A && + git -C server commit -m x && + + echo xyzz >server/foo && + test_seq -f "line %d" 90 190 >server/bar/baz && + git -C server add -A && + git -C server commit -m x && + + test_config -C server uploadpack.allowfilter 1 && + test_config -C server uploadpack.allowanysha1inwant 1 && + git clone --filter=blob:limit=0 "file://$(pwd)/server" client && + + # Fetch bar/baz without fetching foo. + # Foo will be lazily fetched during break rewrites detection. + git -C client checkout HEAD~1 bar && + + # Ensure baz in the working tree is different from baz in HEAD~1. + # We need baz to trigger break-rewrites detection. + git -C client reset --hard HEAD && + + # break-rewrites detction in reset. + git -C client reset HEAD~1 +' + +test_expect_success 'diff succeeds even if entries are removed from queue' ' + test_when_finished "rm -rf server client trace" && + + test_create_repo server && + for l in a c e g i p + do + echo $l >server/$l && + git -C server add $l || return 1 + done && + git -C server commit -m x && + + for l in a e i + do + git -C server rm $l || return 1 + done && + + for l in b d f i + do + echo $l$l >server/$l && + git -C server add $l || return 1 + done && + git -C server commit -a -m x && + + test_config -C server uploadpack.allowfilter 1 && + test_config -C server uploadpack.allowanysha1inwant 1 && + git clone --filter=blob:limit=0 "file://$(pwd)/server" client && + + for file in $(ls client) + do + cat client/$file >$file && + mv $file client/$file || return 1 + done && + git -C client diff --name-only --relative HEAD^ +' + test_expect_success 'diff does not fetch anything if inexact rename detection is not needed' ' test_when_finished "rm -rf server client trace" &&
diff --git a/t/t4073-diff-stat-name-width.sh b/t/t4073-diff-stat-name-width.sh new file mode 100755 index 0000000..ec5d3c3 --- /dev/null +++ b/t/t4073-diff-stat-name-width.sh
@@ -0,0 +1,61 @@ +#!/bin/sh + +test_description='git-diff check diffstat filepaths length when containing UTF-8 chars' + +. ./test-lib.sh + + +create_files () { + mkdir -p "d你好" && + touch "d你好/f再见" +} + +test_expect_success 'setup' ' + git init && + git config core.quotepath off && + git commit -m "Initial commit" --allow-empty && + create_files && + git add . && + git commit -m "Added files" +' + +test_expect_success 'test name-width long enough for filepath' ' + git diff HEAD~1 HEAD --stat --stat-name-width=12 >out && + grep "d你好/f再见 |" out && + git diff HEAD~1 HEAD --stat --stat-name-width=11 >out && + grep "d你好/f再见 |" out +' + +test_expect_success 'test name-width not long enough for dir name' ' + git diff HEAD~1 HEAD --stat --stat-name-width=10 >out && + grep ".../f再见 |" out && + git diff HEAD~1 HEAD --stat --stat-name-width=9 >out && + grep ".../f再见 |" out +' + +test_expect_success 'test name-width not long enough for slash' ' + git diff HEAD~1 HEAD --stat --stat-name-width=8 >out && + grep "...f再见 |" out +' + +test_expect_success 'test name-width not long enough for file name' ' + git diff HEAD~1 HEAD --stat --stat-name-width=7 >out && + grep "...再见 |" out && + git diff HEAD~1 HEAD --stat --stat-name-width=6 >out && + grep "...见 |" out && + git diff HEAD~1 HEAD --stat --stat-name-width=5 >out && + grep "...见 |" out && + git diff HEAD~1 HEAD --stat --stat-name-width=4 >out && + grep "... |" out +' + +test_expect_success 'test name-width minimum length' ' + git diff HEAD~1 HEAD --stat --stat-name-width=3 >out && + grep "... |" out && + git diff HEAD~1 HEAD --stat --stat-name-width=2 >out && + grep "... |" out && + git diff HEAD~1 HEAD --stat --stat-name-width=1 >out && + grep "... |" out +' + +test_done
diff --git a/t/t4074-diff-shifted-matched-group.sh b/t/t4074-diff-shifted-matched-group.sh new file mode 100755 index 0000000..d77fa3b --- /dev/null +++ b/t/t4074-diff-shifted-matched-group.sh
@@ -0,0 +1,164 @@ +#!/bin/sh + +test_description='shifted diff groups re-diffing during histogram diff' + +. ./test-lib.sh + +test_expect_success 'shifted/merged diff group should re-diff to minimize patch' ' + test_write_lines A x A A A x A A A >file1 && + test_write_lines A x A Z A x A A A >file2 && + + file1_h=$(git rev-parse --short $(git hash-object file1)) && + file2_h=$(git rev-parse --short $(git hash-object file2)) && + + cat >expect <<-EOF && + diff --git a/file1 b/file2 + index $file1_h..$file2_h 100644 + --- a/file1 + +++ b/file2 + @@ -1,7 +1,7 @@ + A + x + A + -A + +Z + A + x + A + EOF + + test_expect_code 1 git diff --no-index --histogram file1 file2 >output && + test_cmp expect output +' + +test_expect_success 'merged diff group with no shift' ' + test_write_lines A Z B x >file1 && + test_write_lines C D x Z E x >file2 && + + file1_h=$(git rev-parse --short $(git hash-object file1)) && + file2_h=$(git rev-parse --short $(git hash-object file2)) && + + cat >expect <<-EOF && + diff --git a/file1 b/file2 + index $file1_h..$file2_h 100644 + --- a/file1 + +++ b/file2 + @@ -1,4 +1,6 @@ + -A + +C + +D + +x + Z + -B + +E + x + EOF + + test_expect_code 1 git diff --no-index --histogram file1 file2 >output && + test_cmp expect output +' + +test_expect_success 're-diff should preserve diff flags' ' + test_write_lines a b c a b c >file1 && + test_write_lines x " b" z a b c >file2 && + + file1_h=$(git rev-parse --short $(git hash-object file1)) && + file2_h=$(git rev-parse --short $(git hash-object file2)) && + + cat >expect <<-EOF && + diff --git a/file1 b/file2 + index $file1_h..$file2_h 100644 + --- a/file1 + +++ b/file2 + @@ -1,6 +1,6 @@ + -a + -b + -c + +x + + b + +z + a + b + c + EOF + + test_expect_code 1 git diff --no-index --histogram file1 file2 >output && + test_cmp expect output && + + cat >expect_iwhite <<-EOF && + diff --git a/file1 b/file2 + index $file1_h..$file2_h 100644 + --- a/file1 + +++ b/file2 + @@ -1,6 +1,6 @@ + -a + +x + b + -c + +z + a + b + c + EOF + + test_expect_code 1 git diff --no-index --histogram --ignore-all-space file1 file2 >output_iwhite && + test_cmp expect_iwhite output_iwhite +' + +test_expect_success 'shifting on either side should trigger re-diff properly' ' + test_write_lines a b c a b c a b c >file1 && + test_write_lines a b c a1 a2 a3 b c1 a b c >file2 && + + file1_h=$(git rev-parse --short $(git hash-object file1)) && + file2_h=$(git rev-parse --short $(git hash-object file2)) && + + cat >expect1 <<-EOF && + diff --git a/file1 b/file2 + index $file1_h..$file2_h 100644 + --- a/file1 + +++ b/file2 + @@ -1,9 +1,11 @@ + a + b + c + -a + +a1 + +a2 + +a3 + b + -c + +c1 + a + b + c + EOF + + test_expect_code 1 git diff --no-index --histogram file1 file2 >output1 && + test_cmp expect1 output1 && + + cat >expect2 <<-EOF && + diff --git a/file2 b/file1 + index $file2_h..$file1_h 100644 + --- a/file2 + +++ b/file1 + @@ -1,11 +1,9 @@ + a + b + c + -a1 + -a2 + -a3 + +a + b + -c1 + +c + a + b + c + EOF + + test_expect_code 1 git diff --no-index --histogram file2 file1 >output2 && + test_cmp expect2 output2 +' + +test_done
diff --git a/t/t4100-apply-stat.sh b/t/t4100-apply-stat.sh index a5664f3..8393076 100755 --- a/t/t4100-apply-stat.sh +++ b/t/t4100-apply-stat.sh
@@ -48,7 +48,93 @@ +b EOF test_must_fail git apply patch 2>err && - echo "error: corrupt patch at line 4" >expect && + echo "error: corrupt patch at patch:4" >expect && + test_cmp expect err +' + +test_expect_success 'applying a hunk header which overflows from stdin fails' ' + cat >patch <<-\EOF && + diff -u a/file b/file + --- a/file + +++ b/file + @@ -98765432109876543210 +98765432109876543210 @@ + -a + +b + EOF + test_must_fail git apply <patch 2>err && + echo "error: corrupt patch at <stdin>:4" >expect && + test_cmp expect err +' + +test_expect_success 'applying multiple patches reports the corrupted input' ' + cat >good.patch <<-\EOF && + diff -u a/file b/file + --- a/file + +++ b/file + @@ -1 +1 @@ + -a + +b + EOF + cat >bad.patch <<-\EOF && + diff -u a/file b/file + --- a/file + +++ b/file + @@ -98765432109876543210 +98765432109876543210 @@ + -a + +b + EOF + test_must_fail git apply --stat --summary good.patch bad.patch 2>err && + echo "error: corrupt patch at bad.patch:4" >expect && + test_cmp expect err +' + +test_expect_success 'applying a patch without a header reports the input' ' + cat >fragment.patch <<-\EOF && + @@ -1 +1 @@ + -a + +b + EOF + test_must_fail git apply fragment.patch 2>err && + echo "error: patch fragment without header at fragment.patch:1: @@ -1 +1 @@" >expect && + test_cmp expect err +' + +test_expect_success 'applying a patch with a missing filename reports the input' ' + cat >missing.patch <<-\EOF && + diff --git a/f b/f + index 7898192..6178079 100644 + --- a/f + @@ -1 +1 @@ + -a + +b + EOF + test_must_fail git apply missing.patch 2>err && + echo "error: git diff header lacks filename information at missing.patch:4" >expect && + test_cmp expect err +' + +test_expect_success 'applying a patch with an invalid mode reports the input' ' + cat >mode.patch <<-\EOF && + diff --git a/f b/f + old mode 10x644 + EOF + test_must_fail git apply mode.patch 2>err && + cat >expect <<-\EOF && + error: invalid mode at mode.patch:2: 10x644 + + EOF + test_cmp expect err +' + +test_expect_success 'applying a patch with only garbage reports the input' ' + cat >garbage.patch <<-\EOF && + diff --git a/f b/f + --- a/f + +++ b/f + this is garbage + EOF + test_must_fail git apply garbage.patch 2>err && + echo "error: patch with only garbage at garbage.patch:4" >expect && test_cmp expect err ' test_done
diff --git a/t/t4103-apply-binary.sh b/t/t4103-apply-binary.sh index 8e302a5..f2d41e0 100755 --- a/t/t4103-apply-binary.sh +++ b/t/t4103-apply-binary.sh
@@ -179,6 +179,24 @@ " <patch >patch.trunc && do_reset && - test_must_fail git apply patch.trunc + test_must_fail git apply patch.trunc 2>err && + line=$(awk "END { print NR + 1 }" patch.trunc) && + grep "error: corrupt binary patch at patch.trunc:$line: " err +' + +test_expect_success 'reject unrecognized binary diff' ' + cat >patch.bad <<-\EOF && + diff --git a/f b/f + new file mode 100644 + index 0000000..7898192 + GIT binary patch + bogus + EOF + test_must_fail git apply patch.bad 2>err && + cat >expect <<-\EOF && + error: unrecognized binary patch at patch.bad:4 + error: No valid patches in input (allow with "--allow-empty") + EOF + test_cmp expect err ' test_done
diff --git a/t/t4120-apply-popt.sh b/t/t4120-apply-popt.sh index 697e86c..c960fdf 100755 --- a/t/t4120-apply-popt.sh +++ b/t/t4120-apply-popt.sh
@@ -23,6 +23,47 @@ rmdir süb ' +test_expect_success 'git apply -p 1 patch' ' + cat >patch <<-\EOF && + From 90ad11d5b2d437e82d4d992f72fb44c2227798b5 Mon Sep 17 00:00:00 2001 + From: Mroik <mroik@delayed.space> + Date: Mon, 9 Mar 2026 23:25:00 +0100 + Subject: [PATCH] Test + + --- + t/test/test | 0 + 1 file changed, 0 insertions(+), 0 deletions(-) + create mode 100644 t/test/test + + diff --git a/t/test/test b/t/test/test + new file mode 100644 + index 0000000000..e69de29bb2 + -- + 2.53.0.851.ga537e3e6e9 + EOF + test_when_finished "rm -rf t" && + git apply -p 1 patch && + test_path_is_dir t +' + +test_expect_success 'apply fails due to non-num -p' ' + test_when_finished "rm -rf t test err" && + test_must_fail git apply -p malformed patch 2>err && + test_grep "option -p expects a non-negative integer" err +' + +test_expect_success 'apply fails due to trailing non-digit in -p' ' + test_when_finished "rm -rf t test err" && + test_must_fail git apply -p 2q patch 2>err && + test_grep "option -p expects a non-negative integer" err +' + +test_expect_success 'apply fails due to negative number in -p' ' + test_when_finished "rm -rf t test err patch" && + test_must_fail git apply -p -1 patch 2> err && + test_grep "option -p expects a non-negative integer" err +' + test_expect_success 'apply git diff with -p2' ' cp file1.saved file1 && git apply -p2 patch.file
diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index 485c7d2..29ea7d4 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh
@@ -556,4 +556,277 @@ git apply --include=used --stat --whitespace=error <patch ' +test_expect_success 'check incomplete lines (setup)' ' + rm -f .gitattributes && + git config core.whitespace incomplete-line +' + +test_expect_success 'incomplete context line (not an error)' ' + (test_write_lines 1 2 3 4 5 && printf 6) >sample-i && + (test_write_lines 1 2 3 0 5 && printf 6) >sample2-i && + cat sample-i >target && + git add target && + cat sample2-i >target && + git diff-files -p target >patch && + + cat sample-i >target && + git apply --whitespace=error <patch && + test_cmp sample2-i target && + + cat sample-i >target && + git apply --whitespace=error --check <patch 2>error && + test_cmp sample-i target && + test_must_be_empty error && + + cat sample2-i >target && + git apply --whitespace=error -R <patch && + test_cmp sample-i target && + + cat sample2-i >target && + git apply -R --whitespace=error --check <patch 2>error && + test_cmp sample2-i target && + test_must_be_empty error +' + +test_expect_success 'last line made incomplete (error)' ' + test_write_lines 1 2 3 4 5 6 >sample && + (test_write_lines 1 2 3 4 5 && printf 6) >sample-i && + cat sample >target && + git add target && + cat sample-i >target && + git diff-files -p target >patch && + + cat sample >target && + test_must_fail git apply --whitespace=error <patch 2>error && + test_grep "no newline" error && + + cat sample >target && + test_must_fail git apply --whitespace=error --check <patch 2>actual && + test_cmp sample target && + cat >expect <<-\EOF && + <stdin>:10: no newline at the end of file. + 6 + error: 1 line adds whitespace errors. + EOF + test_cmp expect actual && + + cat sample-i >target && + git apply --whitespace=error -R <patch && + test_cmp sample target && + + cat sample-i >target && + git apply --whitespace=error --check -R <patch 2>error && + test_cmp sample-i target && + test_must_be_empty error && + + cat sample >target && + git apply --whitespace=fix <patch && + test_cmp sample target +' + +test_expect_success 'incomplete line removed at the end (not an error)' ' + (test_write_lines 1 2 3 4 5 && printf 6) >sample-i && + test_write_lines 1 2 3 4 5 6 >sample && + cat sample-i >target && + git add target && + cat sample >target && + git diff-files -p target >patch && + + cat sample-i >target && + git apply --whitespace=error <patch && + test_cmp sample target && + + cat sample-i >target && + git apply --whitespace=error --check <patch 2>error && + test_cmp sample-i target && + test_must_be_empty error && + + cat sample >target && + test_must_fail git apply --whitespace=error -R <patch 2>error && + test_grep "no newline" error && + + cat sample >target && + test_must_fail git apply --whitespace=error --check -R <patch 2>actual && + test_cmp sample target && + cat >expect <<-\EOF && + <stdin>:9: no newline at the end of file. + 6 + error: 1 line adds whitespace errors. + EOF + test_cmp expect actual && + + cat sample >target && + git apply --whitespace=fix -R <patch && + test_cmp sample target +' + +test_expect_success 'incomplete line corrected at the end (not an error)' ' + (test_write_lines 1 2 3 4 5 && printf 6) >sample-i && + test_write_lines 1 2 3 4 5 7 >sample3 && + cat sample-i >target && + git add target && + cat sample3 >target && + git diff-files -p target >patch && + + cat sample-i >target && + git apply --whitespace=error <patch && + test_cmp sample3 target && + + cat sample-i >target && + git apply --whitespace=error --check <patch 2>error && + test_cmp sample-i target && + test_must_be_empty error && + + cat sample3 >target && + test_must_fail git apply --whitespace=error -R <patch 2>error && + test_grep "no newline" error && + + cat sample3 >target && + test_must_fail git apply --whitespace=error -R --check <patch 2>actual && + test_cmp sample3 target && + cat >expect <<-\EOF && + <stdin>:9: no newline at the end of file. + 6 + error: 1 line adds whitespace errors. + EOF + test_cmp expect actual && + + cat sample3 >target && + git apply --whitespace=fix -R <patch && + test_cmp sample target +' + +test_expect_success 'incomplete line modified at the end (error)' ' + (test_write_lines 1 2 3 4 5 && printf 6) >sample-i && + (test_write_lines 1 2 3 4 5 && printf 7) >sample3-i && + test_write_lines 1 2 3 4 5 6 >sample && + test_write_lines 1 2 3 4 5 7 >sample3 && + cat sample-i >target && + git add target && + cat sample3-i >target && + git diff-files -p target >patch && + + cat sample-i >target && + test_must_fail git apply --whitespace=error <patch 2>error && + test_grep "no newline" error && + + cat sample-i >target && + test_must_fail git apply --whitespace=error --check <patch 2>actual && + test_cmp sample-i target && + cat >expect <<-\EOF && + <stdin>:11: no newline at the end of file. + 7 + error: 1 line adds whitespace errors. + EOF + test_cmp expect actual && + + cat sample3-i >target && + test_must_fail git apply --whitespace=error -R <patch 2>error && + test_grep "no newline" error && + + cat sample3-i >target && + test_must_fail git apply --whitespace=error --check -R <patch 2>actual && + test_cmp sample3-i target && + cat >expect <<-\EOF && + <stdin>:9: no newline at the end of file. + 6 + error: 1 line adds whitespace errors. + EOF + test_cmp expect actual && + + cat sample-i >target && + git apply --whitespace=fix <patch && + test_cmp sample3 target && + + cat sample3-i >target && + git apply --whitespace=fix -R <patch && + test_cmp sample target +' + +test_expect_success "incomplete-line error is disabled for symlinks" ' + test_when_finished "git reset" && + test_when_finished "rm -f patch.txt" && + oneblob=$(printf "one" | git hash-object --stdin -w -t blob) && + twoblob=$(printf "two" | git hash-object --stdin -w -t blob) && + + oneshort=$(git rev-parse --short $oneblob) && + twoshort=$(git rev-parse --short $twoblob) && + + cat >patch0.txt <<-EOF && + diff --git a/mylink b/mylink + index $oneshort..$twoshort 120000 + --- a/mylink + +++ b/mylink + @@ -1 +1 @@ + -one + \ No newline at end of file + +two + \ No newline at end of file + EOF + + # the index has the preimage symlink + git update-index --add --cacheinfo "120000,$oneblob,mylink" && + + # check the patch going forward and reverse + git -c core.whitespace=incomplete apply --cached --check \ + --whitespace=error patch0.txt && + + git update-index --add --cacheinfo "120000,$twoblob,mylink" && + git -c core.whitespace=incomplete apply --cached --check \ + --whitespace=error -R patch0.txt && + + # the patch turns it into the postimage symlink + git update-index --add --cacheinfo "120000,$oneblob,mylink" && + git -c core.whitespace=incomplete apply --cached --whitespace=error \ + patch0.txt && + + # and then back. + git -c core.whitespace=incomplete apply --cached -R --whitespace=error \ + patch0.txt && + + # a text file turns into a symlink + cat >patch1.txt <<-EOF && + diff --git a/mylink b/mylink + deleted file mode 100644 + index $oneshort..0000000 + --- a/mylink + +++ /dev/null + @@ -1 +0,0 @@ + -one + \ No newline at end of file + diff --git a/mylink b/mylink + new file mode 120000 + index 0000000..$twoshort + --- /dev/null + +++ b/mylink + @@ -0,0 +1 @@ + +two + \ No newline at end of file + EOF + + # the index has the preimage text + git update-index --cacheinfo "100644,$oneblob,mylink" && + + # check + git -c core.whitespace=incomplete apply --cached \ + --check --whitespace=error patch1.txt && + + # reverse, leaving an incomplete text file, should error + git update-index --cacheinfo "120000,$twoblob,mylink" && + test_must_fail git -c core.whitespace=incomplete \ + apply --cached --check --whitespace=error -R patch1.txt && + + # apply to create a symbolic link + git update-index --cacheinfo "100644,$oneblob,mylink" && + git -c core.whitespace=incomplete apply --cached --whitespace=error \ + patch1.txt && + + # turning it back into an incomplete text file is an error + test_must_fail git -c core.whitespace=incomplete \ + apply --cached --whitespace=error -R patch1.txt + + + +' + test_done
diff --git a/t/t4128-apply-root.sh b/t/t4128-apply-root.sh index f6db5a7..5eba15f 100755 --- a/t/t4128-apply-root.sh +++ b/t/t4128-apply-root.sh
@@ -43,6 +43,47 @@ ' +test_expect_success 'apply --directory (./ prefix)' ' + git reset --hard initial && + git apply --directory=./some/sub -p3 --index patch && + echo Bello >expect && + git show :some/sub/dir/file >actual && + test_cmp expect actual && + test_cmp expect some/sub/dir/file +' + +test_expect_success 'apply --directory (double slash)' ' + git reset --hard initial && + git apply --directory=some//sub -p3 --index patch && + echo Bello >expect && + git show :some/sub/dir/file >actual && + test_cmp expect actual && + test_cmp expect some/sub/dir/file +' + +test_expect_success 'apply --directory (./ in the middle)' ' + git reset --hard initial && + git apply --directory=some/./sub -p3 --index patch && + echo Bello >expect && + git show :some/sub/dir/file >actual && + test_cmp expect actual && + test_cmp expect some/sub/dir/file +' + +test_expect_success 'apply --directory (../ in the middle)' ' + git reset --hard initial && + git apply --directory=some/../some/sub -p3 --index patch && + echo Bello >expect && + git show :some/sub/dir/file >actual && + test_cmp expect actual && + test_cmp expect some/sub/dir/file +' + +test_expect_success 'apply --directory rejects leading ../' ' + test_must_fail git apply --directory=../foo -p3 patch 2>err && + test_grep "unable to normalize directory" err +' + cat > patch << EOF diff --git a/newfile b/newfile new file mode 100644
diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh index 204325f..1717f40 100755 --- a/t/t4200-rerere.sh +++ b/t/t4200-rerere.sh
@@ -72,7 +72,7 @@ rm -rf .git/rr-cache && git config rerere.enabled false && test_must_fail git merge first && - ! test -d .git/rr-cache + test_path_is_missing .git/rr-cache ' test_expect_success 'activate rerere, old style (conflicting merge)' ' @@ -84,8 +84,8 @@ sha1=$(sed "s/ .*//" .git/MERGE_RR) && rr=.git/rr-cache/$sha1 && grep "^=======\$" $rr/preimage && - ! test -f $rr/postimage && - ! test -f $rr/thisimage + test_path_is_missing $rr/postimage && + test_path_is_missing $rr/thisimage ' test_expect_success 'rerere.enabled works, too' ' @@ -110,8 +110,8 @@ test_expect_success 'rr-cache looks sane' ' # no postimage or thisimage yet - ! test -f $rr/postimage && - ! test -f $rr/thisimage && + test_path_is_missing $rr/postimage && + test_path_is_missing $rr/thisimage && # preimage has right number of lines cnt=$(sed -ne "/^<<<<<<</,/^>>>>>>>/p" $rr/preimage | wc -l) && @@ -167,7 +167,7 @@ git show first:a1 | sed "s/To die: t/To die! T/" >expect && git commit -q -a -m "prefer first over second" && - test -f $rr/postimage && + test_path_is_file $rr/postimage && oldmtimepost=$(test-tool chmtime --get -60 $rr/postimage) && @@ -190,14 +190,14 @@ mv $rr/postimage .git/post-saved && echo "$sha1 a1" | tr "\012" "\000" >.git/MERGE_RR && git rerere clear && - ! test -d $rr + test_path_is_missing $rr ' test_expect_success 'leftover directory' ' git reset --hard && mkdir -p $rr && test_must_fail git merge first && - test -f $rr/preimage + test_path_is_file $rr/preimage ' test_expect_success 'missing preimage' ' @@ -205,7 +205,7 @@ mkdir -p $rr && cp .git/post-saved $rr/postimage && test_must_fail git merge first && - test -f $rr/preimage + test_path_is_file $rr/preimage ' test_expect_success 'set up for garbage collection tests' ' @@ -230,16 +230,16 @@ test_expect_success 'gc preserves young or recently used records' ' git rerere gc && - test -f $rr/preimage && - test -f $rr2/preimage + test_path_is_file $rr/preimage && + test_path_is_file $rr2/preimage ' test_expect_success 'old records rest in peace' ' test-tool chmtime =$just_over_60_days_ago $rr/postimage && test-tool chmtime =$just_over_15_days_ago $rr2/preimage && git rerere gc && - ! test -f $rr/preimage && - ! test -f $rr2/preimage + test_path_is_missing $rr/preimage && + test_path_is_missing $rr2/preimage ' rerere_gc_custom_expiry_test () {
diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh index 5f23fc1..9f41d56 100755 --- a/t/t4201-shortlog.sh +++ b/t/t4201-shortlog.sh
@@ -105,7 +105,9 @@ ' test_expect_success !MINGW,ICONV 'shortlog wrapping' ' - cat >expect <<\EOF && + # NOTE: do not quote this heredoc, Dash 0.5.13 has a bug with heredocs + # that contain multibyte chars. + cat >expect <<EOF && A U Thor (5): Test This is a very, very long first line for the commit message to see if
diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh index 8f2ba98..3865f6a 100755 --- a/t/t4205-log-pretty-formats.sh +++ b/t/t4205-log-pretty-formats.sh
@@ -9,7 +9,12 @@ . ./test-lib.sh # Tested non-UTF-8 encoding -test_encoding="ISO8859-1" +if test_have_prereq ICONV +then + test_encoding="ISO8859-1" +else + test_encoding="UTF-8" +fi sample_utf8_part=$(printf "f\303\244ng") @@ -18,7 +23,7 @@ # (translated with Google Translate), # encoded in UTF-8, used as a commit log message below. msg="initial. an${sample_utf8_part}lich\n" - if test -n "$1" + if test -n "$1" && test "$1" != "UTF-8" then printf "$msg" | iconv -f utf-8 -t "$1" else @@ -113,19 +118,19 @@ test_must_fail git log --pretty=test-foo ' -test_expect_success ICONV 'NUL separation' ' +test_expect_success 'NUL separation' ' printf "add bar\0$(commit_msg)" >expected && git log -z --pretty="format:%s" >actual && test_cmp expected actual ' -test_expect_success ICONV 'NUL termination' ' +test_expect_success 'NUL termination' ' printf "add bar\0$(commit_msg)\0" >expected && git log -z --pretty="tformat:%s" >actual && test_cmp expected actual ' -test_expect_success ICONV 'NUL separation with --stat' ' +test_expect_success 'NUL separation with --stat' ' stat0_part=$(git diff --stat HEAD^ HEAD) && stat1_part=$(git diff-tree --no-commit-id --stat --root HEAD^) && printf "add bar\n$stat0_part\n\0$(commit_msg)\n$stat1_part\n" >expected && @@ -180,7 +185,7 @@ head4=$(git rev-parse --verify --short HEAD~3) ' -test_expect_success ICONV 'left alignment formatting' ' +test_expect_success 'left alignment formatting' ' git log --pretty="tformat:%<(40)%s" >actual && qz_to_tab_space <<-EOF >expected && message two Z @@ -202,7 +207,7 @@ test_cmp expected actual ' -test_expect_success ICONV 'left alignment formatting at the nth column' ' +test_expect_success 'left alignment formatting at the nth column' ' git log --pretty="tformat:%h %<|(40)%s" >actual && qz_to_tab_space <<-EOF >expected && $head1 message two Z @@ -213,7 +218,7 @@ test_cmp expected actual ' -test_expect_success ICONV 'left alignment formatting at the nth column' ' +test_expect_success 'left alignment formatting at the nth column' ' COLUMNS=50 git log --pretty="tformat:%h %<|(-10)%s" >actual && qz_to_tab_space <<-EOF >expected && $head1 message two Z @@ -235,7 +240,7 @@ test_cmp expected actual ' -test_expect_success ICONV 'left alignment formatting with no padding' ' +test_expect_success 'left alignment formatting with no padding' ' git log --pretty="tformat:%<(1)%s" >actual && cat <<-EOF >expected && message two @@ -246,7 +251,7 @@ test_cmp expected actual ' -test_expect_success 'left alignment formatting with no padding. i18n.logOutputEncoding' ' +test_expect_success ICONV 'left alignment formatting with no padding. i18n.logOutputEncoding' ' git -c i18n.logOutputEncoding=$test_encoding log --pretty="tformat:%<(1)%s" >actual && cat <<-EOF | iconv -f utf-8 -t $test_encoding >expected && message two @@ -257,7 +262,7 @@ test_cmp expected actual ' -test_expect_success ICONV 'left alignment formatting with trunc' ' +test_expect_success 'left alignment formatting with trunc' ' git log --pretty="tformat:%<(10,trunc)%s" >actual && qz_to_tab_space <<-\EOF >expected && message .. @@ -279,7 +284,7 @@ test_cmp expected actual ' -test_expect_success ICONV 'left alignment formatting with ltrunc' ' +test_expect_success 'left alignment formatting with ltrunc' ' git log --pretty="tformat:%<(10,ltrunc)%s" >actual && qz_to_tab_space <<-EOF >expected && ..sage two @@ -301,7 +306,7 @@ test_cmp expected actual ' -test_expect_success ICONV 'left alignment formatting with mtrunc' ' +test_expect_success 'left alignment formatting with mtrunc' ' git log --pretty="tformat:%<(10,mtrunc)%s" >actual && qz_to_tab_space <<-\EOF >expected && mess.. two @@ -323,7 +328,7 @@ test_cmp expected actual ' -test_expect_success ICONV 'right alignment formatting' ' +test_expect_success 'right alignment formatting' ' git log --pretty="tformat:%>(40)%s" >actual && qz_to_tab_space <<-EOF >expected && Z message two @@ -345,7 +350,7 @@ test_cmp expected actual ' -test_expect_success ICONV 'right alignment formatting at the nth column' ' +test_expect_success 'right alignment formatting at the nth column' ' git log --pretty="tformat:%h %>|(40)%s" >actual && qz_to_tab_space <<-EOF >expected && $head1 message two @@ -356,7 +361,7 @@ test_cmp expected actual ' -test_expect_success ICONV 'right alignment formatting at the nth column' ' +test_expect_success 'right alignment formatting at the nth column' ' COLUMNS=50 git log --pretty="tformat:%h %>|(-10)%s" >actual && qz_to_tab_space <<-EOF >expected && $head1 message two @@ -391,7 +396,7 @@ test_cmp expected actual ' -test_expect_success ICONV 'right alignment formatting with no padding' ' +test_expect_success 'right alignment formatting with no padding' ' git log --pretty="tformat:%>(1)%s" >actual && cat <<-EOF >expected && message two @@ -402,7 +407,7 @@ test_cmp expected actual ' -test_expect_success ICONV 'right alignment formatting with no padding and with --graph' ' +test_expect_success 'right alignment formatting with no padding and with --graph' ' git log --graph --pretty="tformat:%>(1)%s" >actual && cat <<-EOF >expected && * message two @@ -424,7 +429,7 @@ test_cmp expected actual ' -test_expect_success ICONV 'center alignment formatting' ' +test_expect_success 'center alignment formatting' ' git log --pretty="tformat:%><(40)%s" >actual && qz_to_tab_space <<-EOF >expected && Z message two Z @@ -445,7 +450,8 @@ EOF test_cmp expected actual ' -test_expect_success ICONV 'center alignment formatting at the nth column' ' + +test_expect_success 'center alignment formatting at the nth column' ' git log --pretty="tformat:%h %><|(40)%s" >actual && qz_to_tab_space <<-EOF >expected && $head1 message two Z @@ -456,7 +462,7 @@ test_cmp expected actual ' -test_expect_success ICONV 'center alignment formatting at the nth column' ' +test_expect_success 'center alignment formatting at the nth column' ' COLUMNS=70 git log --pretty="tformat:%h %><|(-30)%s" >actual && qz_to_tab_space <<-EOF >expected && $head1 message two Z @@ -478,7 +484,7 @@ test_cmp expected actual ' -test_expect_success ICONV 'center alignment formatting with no padding' ' +test_expect_success 'center alignment formatting with no padding' ' git log --pretty="tformat:%><(1)%s" >actual && cat <<-EOF >expected && message two
diff --git a/t/t4254-am-corrupt.sh b/t/t4254-am-corrupt.sh index ae0a56c..96ddf3c 100755 --- a/t/t4254-am-corrupt.sh +++ b/t/t4254-am-corrupt.sh
@@ -65,9 +65,8 @@ test_expect_success 'try to apply corrupted patch' ' test_when_finished "git am --abort" && test_must_fail git -c advice.amWorkDir=false -c advice.mergeConflict=false am bad-patch.diff 2>actual && - echo "error: git diff header lacks filename information (line 4)" >expected && test_path_is_file f && - test_cmp expected actual + test_grep "error: git diff header lacks filename information at .*rebase-apply/patch:4" actual ' test_expect_success "NUL in commit message's body" '
diff --git a/t/t5003-archive-zip.sh b/t/t5003-archive-zip.sh index 961c6aa..c8c1c5c 100755 --- a/t/t5003-archive-zip.sh +++ b/t/t5003-archive-zip.sh
@@ -239,6 +239,40 @@ check_added with_untracked2 untracked one/untracked check_added with_untracked2 untracked two/untracked +test_expect_success 'git-archive --format=zip with bigFile delta chains' ' + test_when_finished rm -rf repo && + git init repo && + ( + cd repo && + test-tool genrandom foo 100000 >base && + { + cat base && + echo "trailing data" + } >delta-1 && + { + cat delta-1 && + echo "trailing data" + } >delta-2 && + git add . && + git commit -m "blobs" && + git repack -Ad && + git verify-pack -v .git/objects/pack/pack-*.idx >stats && + test_grep "chain length = 1: 1 object" stats && + test_grep "chain length = 2: 1 object" stats && + + git -c core.bigFileThreshold=1k archive --format=zip HEAD >archive.zip && + if test_have_prereq UNZIP + then + mkdir unpack && + cd unpack && + "$GIT_UNZIP" ../archive.zip && + test_cmp base ../base && + test_cmp delta-1 ../delta-1 && + test_cmp delta-2 ../delta-2 + fi + ) +' + # Test remote archive over HTTP protocol. # # Note: this should be the last part of this test suite, because
diff --git a/t/t5004-archive-corner-cases.sh b/t/t5004-archive-corner-cases.sh index 027dedd..df513a4 100755 --- a/t/t5004-archive-corner-cases.sh +++ b/t/t5004-archive-corner-cases.sh
@@ -176,8 +176,7 @@ blob=$(echo $s | git hash-object -w --stdin) && # create tree containing 65500 entries of that blob - test_seq -f "100644 blob $blob\t%d" 1 65500 >tree && - tree=$(git mktree <tree) && + tree=$(test_seq -f "100644 blob $blob\t%d" 1 65500 | git mktree) && # zip it, creating an archive a bit bigger than 4GB git archive -0 -o many-big.zip $tree &&
diff --git a/t/t5301-sliding-window.sh b/t/t5301-sliding-window.sh index ff6b515..3c3666b 100755 --- a/t/t5301-sliding-window.sh +++ b/t/t5301-sliding-window.sh
@@ -12,7 +12,7 @@ for i in a b c do echo $i >$i && - test-tool genrandom "$i" 32768 >>$i && + test-tool genrandom "$i" 32k >>$i && git update-index --add $i || return 1 done && echo d >d && cat c >>d && git update-index --add d &&
diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh index 413c992..9697448 100755 --- a/t/t5302-pack-index.sh +++ b/t/t5302-pack-index.sh
@@ -293,4 +293,20 @@ grep "maximum allowed size (20 bytes)" err ' +# git-index-pack(1) uses the default hash algorithm outside of the repository, +# and it has no way to tell it otherwise. So we can only run this test with the +# default hash algorithm, as it would otherwise fail to parse the tree. +test_expect_success DEFAULT_HASH_ALGORITHM 'index-pack --fsck-objects outside of a repo' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + printf "100644 blob $(test_oid 001)\t.gitattributes\n" >tree && + git mktree --missing <tree >tree-oid && + git pack-objects <tree-oid pack && + test_must_fail nongit git index-pack --fsck-objects "$(pwd)"/pack-*.pack 2>err && + test_grep "cannot perform queued object checks outside of a repository" err + ) +' + test_done
diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh index 6718fb9..f693cb5 100755 --- a/t/t5310-pack-bitmaps.sh +++ b/t/t5310-pack-bitmaps.sh
@@ -242,7 +242,7 @@ ' test_expect_success 'splitting packs does not generate bogus bitmaps' ' - test-tool genrandom foo $((1024 * 1024)) >rand && + test-tool genrandom foo 1m >rand && git add rand && git commit -m "commit with big file" && git -c pack.packSizeLimit=500k repack -adb && @@ -466,6 +466,47 @@ ) ' + test_expect_success 'pack.preferBitmapTips interprets patterns as hierarchy' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + # Create enough commits that not all will receive bitmap + # coverage even if they are all at the tip of some reference. + test_commit_bulk --message="%s" 103 && + git log --format="create refs/tags/%s/tag %H" HEAD >refs && + git update-ref --stdin <refs && + + # Create the bitmap. + git repack -adb && + test-tool bitmap list-commits | sort >commits-with-bitmap && + + # Verify that we have at least one commit that did not + # receive a bitmap. + git rev-list HEAD >commits.raw && + sort <commits.raw >commits && + comm -13 commits-with-bitmap commits >commits-wo-bitmap && + test_file_not_empty commits-wo-bitmap && + commit_id=$(head commits-wo-bitmap) && + ref_without_bitmap=$(git for-each-ref --points-at="$commit_id" --format="%(refname)") && + + # When passing the full refname we do not expect a + # bitmap to be generated, as it should be interpreted + # as if a slash was appended to the pattern. + git -c pack.preferBitmapTips="$ref_without_bitmap" repack -adb && + test-tool bitmap list-commits >after && + test_grep ! "$commit_id" after && + + # But if we pass the parent directory of the ref we + # should see a bitmap. + ref_namespace=$(dirname "$ref_without_bitmap") && + git -c pack.preferBitmapTips="$ref_namespace" repack -adb && + test-tool bitmap list-commits >after && + test_grep "$commit_id" after + ) + ' + test_expect_success 'complains about multiple pack bitmaps' ' rm -fr repo && git init repo &&
diff --git a/t/t5315-pack-objects-compression.sh b/t/t5315-pack-objects-compression.sh index 8bacd96..d0feab1 100755 --- a/t/t5315-pack-objects-compression.sh +++ b/t/t5315-pack-objects-compression.sh
@@ -10,7 +10,7 @@ # make sure it resulted in a loose object ob=$(sed -e "s/\(..\).*/\1/" object-name) && ject=$(sed -e "s/..\(.*\)/\1/" object-name) && - test -f .git/objects/$ob/$ject + test_path_is_file .git/objects/$ob/$ject ' while read expect config
diff --git a/t/t5316-pack-delta-depth.sh b/t/t5316-pack-delta-depth.sh index 03dfb7a..8a067a4 100755 --- a/t/t5316-pack-delta-depth.sh +++ b/t/t5316-pack-delta-depth.sh
@@ -48,6 +48,7 @@ # repeatedly-modified file to generate the delta chain). test_expect_success 'create series of packs' ' + test_config maintenance.auto false && test-tool genrandom foo 4096 >content && prev= && for i in $(test_seq 1 10)
diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index 93f319a..58e0b68 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh
@@ -21,7 +21,7 @@ EXTRA_CHUNKS="$5" { cat <<-EOF && - header: 4d494458 1 $HASH_LEN $NUM_CHUNKS $NUM_PACKS + header: 4d494458 2 $HASH_LEN $NUM_CHUNKS $NUM_PACKS chunks: pack-names oid-fanout oid-lookup object-offsets$EXTRA_CHUNKS num_objects: $NUM_OBJECTS packs: @@ -350,7 +350,69 @@ # the new MIDX git multi-pack-index write --preferred-pack=pack-$pack.pack ) +' +test_expect_success 'preferred pack cannot be determined without bitmap' ' + test_when_finished "rm -fr preferred-can-be-queried" && + git init preferred-can-be-queried && + ( + cd preferred-can-be-queried && + test_commit initial && + git repack -Adl --write-midx --no-write-bitmap-index && + test_must_fail test-tool read-midx --preferred-pack .git/objects 2>err && + test_grep "could not determine MIDX preferred pack" err && + git repack -Adl --write-midx --write-bitmap-index && + test-tool read-midx --preferred-pack .git/objects + ) +' + +test_midx_is_retained () { + test-tool chmtime =0 .git/objects/pack/multi-pack-index && + ls -l .git/objects/pack/multi-pack-index >expect && + git multi-pack-index write "$@" && + ls -l .git/objects/pack/multi-pack-index >actual && + test_cmp expect actual +} + +test_midx_is_rewritten () { + test-tool chmtime =0 .git/objects/pack/multi-pack-index && + ls -l .git/objects/pack/multi-pack-index >expect && + git multi-pack-index write "$@" && + ls -l .git/objects/pack/multi-pack-index >actual && + ! test_cmp expect actual +} + +test_expect_success 'up-to-date multi-pack-index is retained' ' + test_when_finished "rm -fr midx-up-to-date" && + git init midx-up-to-date && + ( + cd midx-up-to-date && + + # Write the initial pack that contains the most objects. + test_commit first && + test_commit second && + git repack -Ad --write-midx && + test_midx_is_retained && + + # Writing a new bitmap index should cause us to regenerate the MIDX. + test_midx_is_rewritten --bitmap && + test_midx_is_retained --bitmap && + + # Ensure that writing a new packfile causes us to rewrite the index. + test_commit incremental && + git repack -d && + test_midx_is_rewritten && + test_midx_is_retained && + + for pack in .git/objects/pack/*.idx + do + basename "$pack" || exit 1 + done >stdin && + test_line_count = 2 stdin && + test_midx_is_retained --stdin-packs <stdin && + head -n1 stdin >stdin.trimmed && + test_midx_is_rewritten --stdin-packs <stdin.trimmed + ) ' test_expect_success 'verify multi-pack-index success' ' @@ -450,12 +512,7 @@ "improper chunk offset(s)" ' -test_expect_success 'verify packnames out of order' ' - corrupt_midx_and_verify $MIDX_BYTE_PACKNAME_ORDER "z" $objdir \ - "pack names out of order" -' - -test_expect_success 'verify packnames out of order' ' +test_expect_success 'verify missing pack' ' corrupt_midx_and_verify $MIDX_BYTE_PACKNAME_ORDER "a" $objdir \ "failed to load pack" ' @@ -516,6 +573,15 @@ $objdir "incorrect checksum" ' +test_expect_success 'setup for v1-specific fsck tests' ' + git -c midx.version=1 multi-pack-index write +' + +test_expect_success 'verify packnames out of order (v1)' ' + corrupt_midx_and_verify $MIDX_BYTE_PACKNAME_ORDER "z" $objdir \ + "pack names out of order" +' + test_expect_success 'repack progress off for redirected stderr' ' GIT_PROGRESS_DELAY=0 git multi-pack-index --object-dir=$objdir repack 2>err && test_line_count = 0 err @@ -1253,6 +1319,7 @@ git init repo && ( cd repo && + git config set maintenance.auto false && for i in 1 2 3 4 5 do @@ -1283,4 +1350,47 @@ ) ' +test_expect_success 'pack.preferBitmapTips interprets patterns as hierarchy' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + # Create enough commits that not all will receive bitmap + # coverage even if they are all at the tip of some reference. + test_commit_bulk --message="%s" 103 && + git log --format="create refs/tags/%s %H" HEAD >refs && + git update-ref --stdin <refs && + + # Create the bitmap via the MIDX. + git repack -adb --write-midx && + test-tool bitmap list-commits | sort >commits-with-bitmap && + + # Verify that we have at least one commit that did not + # receive a bitmap. + git rev-list HEAD >commits.raw && + sort <commits.raw >commits && + comm -13 commits-with-bitmap commits >commits-wo-bitmap && + test_file_not_empty commits-wo-bitmap && + commit_id=$(head commits-wo-bitmap) && + ref_without_bitmap=$(git for-each-ref --points-at="$commit_id" --format="%(refname)") && + + # When passing the full refname we do not expect a bitmap to be + # generated, as it should be interpreted as if a slash was + # appended to the pattern. + rm .git/objects/pack/multi-pack-index* && + git -c pack.preferBitmapTips="$ref_without_bitmap" repack -adb --write-midx && + test-tool bitmap list-commits >after && + test_grep ! "$commit_id" after && + + # But if we pass the parent directory of the ref we should see + # a bitmap. + ref_namespace=$(dirname "$ref_without_bitmap") && + rm .git/objects/pack/multi-pack-index* && + git -c pack.preferBitmapTips="$ref_namespace" repack -adb --write-midx && + test-tool bitmap list-commits >after && + test_grep "$commit_id" after + ) +' + test_done
diff --git a/t/t5326-multi-pack-bitmaps.sh b/t/t5326-multi-pack-bitmaps.sh index 892aeb0..62bd973 100755 --- a/t/t5326-multi-pack-bitmaps.sh +++ b/t/t5326-multi-pack-bitmaps.sh
@@ -93,7 +93,8 @@ test_expect_success 'setup test_repository' ' rm -rf * .git && git init && - git config pack.writeBitmapLookupTable '"$writeLookupTable"' + git config pack.writeBitmapLookupTable '"$writeLookupTable"' && + git config maintenance.auto false ' midx_bitmap_core
diff --git a/t/t5327-multi-pack-bitmaps-rev.sh b/t/t5327-multi-pack-bitmaps-rev.sh index 9cac03a..cfa12de 100755 --- a/t/t5327-multi-pack-bitmaps-rev.sh +++ b/t/t5327-multi-pack-bitmaps-rev.sh
@@ -30,7 +30,8 @@ test_expect_success 'setup bitmap config' ' rm -rf * .git && git init && - git config pack.writeBitmapLookupTable '"$writeLookupTable"' + git config pack.writeBitmapLookupTable '"$writeLookupTable"' && + git config maintenance.auto false ' midx_bitmap_core rev
diff --git a/t/t5331-pack-objects-stdin.sh b/t/t5331-pack-objects-stdin.sh index 4a8df5a..c74b586 100755 --- a/t/t5331-pack-objects-stdin.sh +++ b/t/t5331-pack-objects-stdin.sh
@@ -14,6 +14,7 @@ test_expect_success 'setup for --stdin-packs tests' ' git init stdin-packs && + git -C stdin-packs config set maintenance.auto false && ( cd stdin-packs && @@ -255,6 +256,7 @@ git init repo && ( cd repo && + git config set maintenance.auto false && for c in A B C D do @@ -319,6 +321,63 @@ ) ' +test_expect_success '--stdin-packs with promisors' ' + test_when_finished "rm -fr repo" && + git init repo && + ( + cd repo && + git config set maintenance.auto false && + git remote add promisor garbage && + git config set remote.promisor.promisor true && + + for c in A B C D + do + echo "$c" >file && + git add file && + git commit --message "$c" && + git tag "$c" || return 1 + done && + + A="$(echo A | git pack-objects --revs $packdir/pack)" && + B="$(echo A..B | git pack-objects --revs $packdir/pack --filter=blob:none)" && + C="$(echo B..C | git pack-objects --revs $packdir/pack)" && + D="$(echo C..D | git pack-objects --revs $packdir/pack)" && + touch $packdir/pack-$B.promisor && + + test_must_fail git pack-objects --stdin-packs --exclude-promisor-objects pack- 2>err <<-EOF && + pack-$B.pack + EOF + test_grep "is a promisor but --exclude-promisor-objects was given" err && + + PACK=$(git pack-objects --stdin-packs=follow --exclude-promisor-objects $packdir/pack <<-EOF + pack-$D.pack + EOF + ) && + objects_in_packs $C $D >expect && + objects_in_packs $PACK >actual && + test_cmp expect actual && + rm -f $packdir/pack-$PACK.* + ) +' + +test_expect_success '--stdin-packs does not perform backfill fetch' ' + test_when_finished "rm -rf remote client" && + + git init remote && + test_commit_bulk -C remote 10 && + git -C remote config set --local uploadpack.allowfilter 1 && + git -C remote config set --local uploadpack.allowanysha1inwant 1 && + + git clone --filter=tree:0 "file://$(pwd)/remote" client && + ( + cd client && + ls .git/objects/pack/*.promisor | sed "s|.*/||; s/\.promisor$/.pack/" >packs && + test_line_count -gt 1 packs && + GIT_TRACE2_EVENT="$(pwd)/event.log" git pack-objects --stdin-packs pack <packs && + test_grep ! "\"event\":\"child_start\"" event.log + ) +' + stdin_packs__follow_with_only () { rm -fr stdin_packs__follow_with_only && git init stdin_packs__follow_with_only && @@ -356,4 +415,109 @@ stdin_packs__follow_with_only HEAD HEAD^{tree} ' +test_expect_success '--stdin-packs=follow with open-excluded packs' ' + test_when_finished "rm -fr repo" && + + git init repo && + ( + cd repo && + git config set maintenance.auto false && + + git branch -M main && + + # Create the following commit structure: + # + # A <-- B <-- D (main) + # ^ + # \ + # C (other) + test_commit A && + test_commit B && + git checkout -B other && + test_commit C && + git checkout main && + test_commit D && + + A="$(echo A | git pack-objects --revs $packdir/pack)" && + B="$(echo A..B | git pack-objects --revs $packdir/pack)" && + C="$(echo B..C | git pack-objects --revs $packdir/pack)" && + D="$(echo B..D | git pack-objects --revs $packdir/pack)" && + + C_ONLY="$(git rev-parse other | git pack-objects $packdir/pack)" && + + git prune-packed && + + # Create a pack using --stdin-packs=follow where: + # + # - pack D is included, + # - pack C_ONLY is excluded, but open, + # - pack B is excluded, but closed, and + # - packs A and C are unknown + # + # The resulting pack should therefore contain: + # + # - objects from the included pack D, + # - A.t (rescued via D^{tree}), and + # - C^{tree} and C.t (rescued via pack C_ONLY) + # + # , but should omit: + # + # - C (excluded via C_ONLY), + # - objects from pack B (trivially excluded-closed) + # - A and A^{tree} (ancestors of B) + P=$(git pack-objects --stdin-packs=follow $packdir/pack <<-EOF + pack-$D.pack + !pack-$C_ONLY.pack + ^pack-$B.pack + EOF + ) && + + { + objects_in_packs $D && + git rev-parse A:A.t "C^{tree}" C:C.t + } >expect.raw && + sort expect.raw >expect && + + objects_in_packs $P >actual && + test_cmp expect actual + ) +' + +test_expect_success '--stdin-packs with !-delimited pack without follow' ' + test_when_finished "rm -fr repo" && + + git init repo && + ( + test_commit A && + test_commit B && + test_commit C && + + A="$(echo A | git pack-objects --revs $packdir/pack)" && + B="$(echo A..B | git pack-objects --revs $packdir/pack)" && + C="$(echo B..C | git pack-objects --revs $packdir/pack)" && + + cat >in <<-EOF && + !pack-$A.pack + pack-$B.pack + pack-$C.pack + EOF + + # Without --stdin-packs=follow, we treat the first + # line of input as a literal packfile name, and thus + # expect pack-objects to complain of a missing pack + test_must_fail git pack-objects --stdin-packs --stdout \ + >/dev/null <in 2>err && + test_grep "could not find pack .!pack-$A.pack." err && + + # With --stdin-packs=follow, we treat the second line + # of input as indicating pack-$A.pack is an excluded + # open pack, and thus expect pack-objects to succeed + P=$(git pack-objects --stdin-packs=follow $packdir/pack <in) && + + objects_in_packs $B $C >expect && + objects_in_packs $P >actual && + test_cmp expect actual + ) +' + test_done
diff --git a/t/t5332-multi-pack-reuse.sh b/t/t5332-multi-pack-reuse.sh index 395d094..881ce66 100755 --- a/t/t5332-multi-pack-reuse.sh +++ b/t/t5332-multi-pack-reuse.sh
@@ -59,6 +59,7 @@ test_expect_success 'preferred pack is reused for single-pack reuse' ' test_config pack.allowPackReuse single && + git config set maintenance.auto false && for i in A B do
diff --git a/t/t5334-incremental-multi-pack-index.sh b/t/t5334-incremental-multi-pack-index.sh index d30d725..99c7d44 100755 --- a/t/t5334-incremental-multi-pack-index.sh +++ b/t/t5334-incremental-multi-pack-index.sh
@@ -15,6 +15,7 @@ test_expect_success 'convert non-incremental MIDX to incremental' ' test_commit base && + git config set maintenance.auto false && git repack -ad && git multi-pack-index write &&
diff --git a/t/t5335-compact-multi-pack-index.sh b/t/t5335-compact-multi-pack-index.sh new file mode 100755 index 0000000..40f3844 --- /dev/null +++ b/t/t5335-compact-multi-pack-index.sh
@@ -0,0 +1,293 @@ +#!/bin/sh + +test_description='multi-pack-index compaction' + +. ./test-lib.sh + +GIT_TEST_MULTI_PACK_INDEX=0 +GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 +GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL=0 + +objdir=.git/objects +packdir=$objdir/pack +midxdir=$packdir/multi-pack-index.d +midx_chain=$midxdir/multi-pack-index-chain + +nth_line() { + local n="$1" + shift + awk "NR==$n" "$@" +} + +write_packs () { + for c in "$@" + do + test_commit "$c" && + + git pack-objects --all --unpacked $packdir/pack-$c && + git prune-packed && + + git multi-pack-index write --incremental --bitmap || return 1 + done +} + +test_midx_layer_packs () { + local checksum="$1" && + shift && + + test-tool read-midx $objdir "$checksum" >out && + + printf "%s\n" "$@" >expect && + # NOTE: do *not* pipe through sort here, we want to ensure the + # order of packs is preserved during compaction. + grep "^pack-" out | cut -d"-" -f2 >actual && + + test_cmp expect actual +} + +test_midx_layer_object_uniqueness () { + : >objs.all + while read layer + do + test-tool read-midx --show-objects $objdir "$layer" >out && + grep "\.pack$" out | cut -d" " -f1 | sort >objs.layer && + test_stdout_line_count = 0 comm -12 objs.all objs.layer && + cat objs.all objs.layer | sort >objs.tmp && + mv objs.tmp objs.all || return 1 + done <$midx_chain +} + +test_expect_success 'MIDX compaction with lex-ordered pack names' ' + git init midx-compact-lex-order && + ( + cd midx-compact-lex-order && + + git config maintenance.auto false && + + write_packs A B C D E && + test_line_count = 5 $midx_chain && + + git multi-pack-index compact --incremental --bitmap \ + "$(nth_line 2 "$midx_chain")" \ + "$(nth_line 4 "$midx_chain")" && + test_line_count = 3 $midx_chain && + + test_midx_layer_packs "$(nth_line 1 "$midx_chain")" A && + test_midx_layer_packs "$(nth_line 2 "$midx_chain")" B C D && + test_midx_layer_packs "$(nth_line 3 "$midx_chain")" E && + + test_midx_layer_object_uniqueness + ) +' + +test_expect_success 'MIDX compaction with non-lex-ordered pack names' ' + git init midx-compact-non-lex-order && + ( + cd midx-compact-non-lex-order && + + git config maintenance.auto false && + + write_packs D C A B E && + test_line_count = 5 $midx_chain && + + git multi-pack-index compact --incremental --bitmap \ + "$(nth_line 2 "$midx_chain")" \ + "$(nth_line 4 "$midx_chain")" && + test_line_count = 3 $midx_chain && + + test_midx_layer_packs "$(nth_line 1 "$midx_chain")" D && + test_midx_layer_packs "$(nth_line 2 "$midx_chain")" C A B && + test_midx_layer_packs "$(nth_line 3 "$midx_chain")" E && + + test_midx_layer_object_uniqueness + ) +' + +test_expect_success 'setup for bogus MIDX compaction scenarios' ' + git init midx-compact-bogus && + ( + cd midx-compact-bogus && + + git config maintenance.auto false && + + write_packs A B C + ) +' + +test_expect_success 'MIDX compaction with missing endpoints' ' + ( + cd midx-compact-bogus && + + test_must_fail git multi-pack-index compact --incremental \ + "<missing>" "<missing>" 2>err && + test_grep "could not find MIDX: <missing>" err && + + test_must_fail git multi-pack-index compact --incremental \ + "<missing>" "$(nth_line 2 "$midx_chain")" 2>err && + test_grep "could not find MIDX: <missing>" err && + + test_must_fail git multi-pack-index compact --incremental \ + "$(nth_line 2 "$midx_chain")" "<missing>" 2>err && + test_grep "could not find MIDX: <missing>" err + ) +' + +test_expect_success 'MIDX compaction with reversed endpoints' ' + ( + cd midx-compact-bogus && + + from="$(nth_line 3 "$midx_chain")" && + to="$(nth_line 1 "$midx_chain")" && + + test_must_fail git multi-pack-index compact --incremental \ + "$from" "$to" 2>err && + + test_grep "MIDX $from must be an ancestor of $to" err + ) +' + +test_expect_success 'MIDX compaction with identical endpoints' ' + ( + cd midx-compact-bogus && + + from="$(nth_line 3 "$midx_chain")" && + to="$(nth_line 3 "$midx_chain")" && + + test_must_fail git multi-pack-index compact --incremental \ + "$from" "$to" 2>err && + + test_grep "MIDX compaction endpoints must be unique" err + ) +' + +test_expect_success 'MIDX compaction with midx.version=1' ' + ( + cd midx-compact-bogus && + + test_must_fail git -c midx.version=1 multi-pack-index compact \ + "$(nth_line 1 "$midx_chain")" \ + "$(nth_line 2 "$midx_chain")" 2>err && + + test_grep "fatal: cannot perform MIDX compaction with v1 format" err + ) +' + +midx_objs_by_pack () { + awk '/\.pack$/ { split($3, a, "-"); print a[2], $1 }' | sort +} + +tag_objs_from_pack () { + objs="$(git rev-list --objects --no-object-names "$2")" && + printf "$1 %s\n" $objs | sort +} + +test_expect_success 'MIDX compaction preserves pack object selection' ' + git init midx-compact-preserve-selection && + ( + cd midx-compact-preserve-selection && + + git config maintenance.auto false && + + test_commit A && + test_commit B && + + # Create two packs, one containing just the objects from + # A, and another containing all objects from the + # repository. + p1="$(echo A | git pack-objects --revs --delta-base-offset \ + $packdir/pack-1)" && + p0="$(echo B | git pack-objects --revs --delta-base-offset \ + $packdir/pack-0)" && + + echo "pack-1-$p1.idx" | git multi-pack-index write \ + --incremental --bitmap --stdin-packs && + echo "pack-0-$p0.idx" | git multi-pack-index write \ + --incremental --bitmap --stdin-packs && + + write_packs C && + + git multi-pack-index compact --incremental --bitmap \ + "$(nth_line 1 "$midx_chain")" \ + "$(nth_line 2 "$midx_chain")" && + + + test-tool read-midx --show-objects $objdir \ + "$(nth_line 1 "$midx_chain")" >AB.info && + test-tool read-midx --show-objects $objdir \ + "$(nth_line 2 "$midx_chain")" >C.info && + + midx_objs_by_pack <AB.info >AB.actual && + midx_objs_by_pack <C.info >C.actual && + + { + tag_objs_from_pack 1 A && + tag_objs_from_pack 0 A..B + } | sort >AB.expect && + tag_objs_from_pack C B..C >C.expect && + + test_cmp AB.expect AB.actual && + test_cmp C.expect C.actual + ) +' + +test_expect_success 'MIDX compaction with bitmaps' ' + git init midx-compact-with-bitmaps && + ( + cd midx-compact-with-bitmaps && + + git config maintenance.auto false && + + write_packs foo bar baz quux woot && + + test-tool read-midx --bitmap $objdir >bitmap.expect && + git multi-pack-index compact --incremental --bitmap \ + "$(nth_line 2 "$midx_chain")" \ + "$(nth_line 4 "$midx_chain")" && + test-tool read-midx --bitmap $objdir >bitmap.actual && + + test_cmp bitmap.expect bitmap.actual && + + true + ) +' + +test_expect_success 'MIDX compaction with bitmaps (non-trivial)' ' + git init midx-compact-with-bitmaps-non-trivial && + ( + cd midx-compact-with-bitmaps-non-trivial && + + git config maintenance.auto false && + + git branch -m main && + + # D(4) + # / + # A(1) --- B(2) --- C(3) --- G(7) + # \ + # E(5) --- F(6) + write_packs A B C && + git checkout -b side && + write_packs D && + git checkout -b other B && + write_packs E F && + git checkout main && + write_packs G && + + # Compact layers 2-4, leaving us with: + # + # [A, [B, C, D], E, F, G] + git multi-pack-index compact --incremental --bitmap \ + "$(nth_line 2 "$midx_chain")" \ + "$(nth_line 4 "$midx_chain")" && + + # Then compact the top two layers, condensing the above + # such that the new 4th layer contains F and G. + # + # [A, [B, C, D], E, [F, G]] + git multi-pack-index compact --incremental --bitmap \ + "$(nth_line 4 "$midx_chain")" \ + "$(nth_line 5 "$midx_chain")" + ) +' + +test_done
diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index 83b42ff..b32a0a6 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh
@@ -187,6 +187,7 @@ cd child && git config gc.autopacklimit 1 && git config gc.autodetach false && + git config maintenance.strategy gc && git branch test_auto_gc && # And create a file that follows the temporary object naming # convention for the auto-gc to remove
diff --git a/t/t5401-update-hooks.sh b/t/t5401-update-hooks.sh index 17a46fd..44ec875 100755 --- a/t/t5401-update-hooks.sh +++ b/t/t5401-update-hooks.sh
@@ -134,8 +134,8 @@ EOF rm -f victim.git/hooks/update victim.git/hooks/post-update && - printf "create refs/heads/branch_%d main\n" $(test_seq 100 999) >input && - git update-ref --stdin <input && + test_seq -f "create refs/heads/branch_%d main" 100 999 | + git update-ref --stdin && git push ./victim.git "+refs/heads/*:refs/heads/*" '
diff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh index 978f240..cb0300b 100755 --- a/t/t5403-post-checkout-hook.sh +++ b/t/t5403-post-checkout-hook.sh
@@ -9,9 +9,20 @@ . ./test-lib.sh +# Usage: check_post_checkout <file> <old-ref> <new-ref> <flag> +# +# Verify that the post-checkout hook arguments in <file> match the expected +# values: <old-ref> for the previous HEAD, <new-ref> for the new HEAD, and +# <flag> indicating whether this was a branch checkout (1) or file checkout (0). +check_post_checkout () { + test "$#" = 4 || BUG "check_post_checkout takes 4 args" + echo "old=$2 new=$3 flag=$4" >expect && + test_cmp expect "$1" +} + test_expect_success setup ' test_hook --setup post-checkout <<-\EOF && - echo "$@" >.git/post-checkout.args + echo "old=$1 new=$2 flag=$3" >.git/post-checkout.args EOF test_commit one && test_commit two && @@ -23,29 +34,30 @@ test_expect_success 'post-checkout receives the right arguments with HEAD unchanged ' ' test_when_finished "rm -f .git/post-checkout.args" && git checkout main && - read old new flag <.git/post-checkout.args && - test $old = $new && test $flag = 1 + check_post_checkout .git/post-checkout.args \ + "$(git rev-parse HEAD)" "$(git rev-parse HEAD)" 1 ' test_expect_success 'post-checkout args are correct with git checkout -b ' ' test_when_finished "rm -f .git/post-checkout.args" && git checkout -b new1 && - read old new flag <.git/post-checkout.args && - test $old = $new && test $flag = 1 + check_post_checkout .git/post-checkout.args \ + "$(git rev-parse HEAD)" "$(git rev-parse HEAD)" 1 ' test_expect_success 'post-checkout receives the right args with HEAD changed ' ' test_when_finished "rm -f .git/post-checkout.args" && + old=$(git rev-parse HEAD) && git checkout two && - read old new flag <.git/post-checkout.args && - test $old != $new && test $flag = 1 + check_post_checkout .git/post-checkout.args \ + "$old" "$(git rev-parse HEAD)" 1 ' test_expect_success 'post-checkout receives the right args when not switching branches ' ' test_when_finished "rm -f .git/post-checkout.args" && git checkout main -- three.t && - read old new flag <.git/post-checkout.args && - test $old = $new && test $flag = 0 + check_post_checkout .git/post-checkout.args \ + "$(git rev-parse HEAD)" "$(git rev-parse HEAD)" 0 ' test_rebase () { @@ -55,10 +67,8 @@ git checkout -B rebase-test main && rm -f .git/post-checkout.args && git rebase $args rebase-on-me && - read old new flag <.git/post-checkout.args && - test_cmp_rev main $old && - test_cmp_rev rebase-on-me $new && - test $flag = 1 + check_post_checkout .git/post-checkout.args \ + "$(git rev-parse main)" "$(git rev-parse rebase-on-me)" 1 ' test_expect_success "post-checkout is triggered on rebase $args with fast-forward" ' @@ -66,10 +76,8 @@ git checkout -B ff-rebase-test rebase-on-me^ && rm -f .git/post-checkout.args && git rebase $args rebase-on-me && - read old new flag <.git/post-checkout.args && - test_cmp_rev rebase-on-me^ $old && - test_cmp_rev rebase-on-me $new && - test $flag = 1 + check_post_checkout .git/post-checkout.args \ + "$(git rev-parse rebase-on-me^)" "$(git rev-parse rebase-on-me)" 1 ' test_expect_success "rebase $args fast-forward branch checkout runs post-checkout hook" ' @@ -79,10 +87,8 @@ git checkout two && rm -f .git/post-checkout.args && git rebase $args HEAD rebase-fast-forward && - read old new flag <.git/post-checkout.args && - test_cmp_rev two $old && - test_cmp_rev three $new && - test $flag = 1 + check_post_checkout .git/post-checkout.args \ + "$(git rev-parse two)" "$(git rev-parse three)" 1 ' test_expect_success "rebase $args checkout does not remove untracked files" ' @@ -106,10 +112,11 @@ test_expect_success 'post-checkout hook is triggered by clone' ' mkdir -p templates/hooks && write_script templates/hooks/post-checkout <<-\EOF && - echo "$@" >"$GIT_DIR/post-checkout.args" + echo "old=$1 new=$2 flag=$3" >"$GIT_DIR/post-checkout.args" EOF git clone --template=templates . clone3 && - test -f clone3/.git/post-checkout.args + check_post_checkout clone3/.git/post-checkout.args \ + "$(test_oid zero)" "$(git -C clone3 rev-parse HEAD)" 1 ' test_done
diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index 2677cd5..649a615 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh
@@ -154,7 +154,8 @@ ' test_expect_success 'clone shallow' ' - git clone --no-single-branch --depth 2 "file://$(pwd)/." shallow + git clone --no-single-branch --depth 2 "file://$(pwd)/." shallow && + git -C shallow config set maintenance.auto false ' test_expect_success 'clone shallow depth count' ' @@ -892,15 +893,20 @@ test_commit other && git commit-graph write --reachable && git config core.commitGraph true && + oid_algo=$(test_oid algo) && + oid_other=$(git rev-parse other) && + oid_main=$(git rev-parse main) && - GIT_PROTOCOL=version=2 git upload-pack . <<-EOF >/dev/null - 0012command=fetch - $(echo "object-format=$(test_oid algo)" | packetize) - 00010013deepen-since 1 - $(echo "want $(git rev-parse other)" | packetize) - $(echo "have $(git rev-parse main)" | packetize) + test-tool pkt-line pack >input <<-EOF && + command=fetch + object-format=$oid_algo + 0001 + deepen-since 1 + want $oid_other + have $oid_main 0000 EOF + GIT_PROTOCOL=version=2 git upload-pack . <input >/dev/null ) ' @@ -955,6 +961,29 @@ ) ' +test_expect_success 'fetching deepen beyond merged branch' ' + test_create_repo shallow-deepen-merged && + ( + cd shallow-deepen-merged && + git commit --allow-empty -m one && + git commit --allow-empty -m two && + git commit --allow-empty -m three && + git switch -c branch && + git commit --allow-empty -m four && + git commit --allow-empty -m five && + git switch main && + git merge --no-ff branch && + cd - && + git clone --bare --depth 3 "file://$(pwd)/shallow-deepen-merged" deepen.git && + git -C deepen.git fetch origin --deepen=1 && + git -C deepen.git rev-list --all >actual && + for commit in $(sed "/^$/d" deepen.git/shallow) + do + test_grep "$commit" actual || exit 1 + done + ) +' + test_negotiation_algorithm_default () { test_when_finished rm -rf clientv0 clientv2 && rm -rf server client &&
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index b7059cc..6fe21e2 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh
@@ -469,12 +469,17 @@ head_oid=$(git rev-parse HEAD) && cat >expected <<-EOF && + preparing + $ZERO_OID $head_oid refs/remotes/origin/atomic-hooks-1 + $ZERO_OID $head_oid refs/remotes/origin/atomic-hooks-2 prepared $ZERO_OID $head_oid refs/remotes/origin/atomic-hooks-1 $ZERO_OID $head_oid refs/remotes/origin/atomic-hooks-2 committed $ZERO_OID $head_oid refs/remotes/origin/atomic-hooks-1 $ZERO_OID $head_oid refs/remotes/origin/atomic-hooks-2 + preparing + $ZERO_OID ref:refs/remotes/origin/main refs/remotes/origin/HEAD EOF rm -f atomic/actual && @@ -497,7 +502,7 @@ head_oid=$(git rev-parse HEAD) && cat >expected <<-EOF && - prepared + preparing $ZERO_OID $head_oid refs/remotes/origin/atomic-hooks-abort-1 $ZERO_OID $head_oid refs/remotes/origin/atomic-hooks-abort-2 $ZERO_OID $head_oid refs/remotes/origin/atomic-hooks-abort-3 @@ -1321,6 +1326,7 @@ git config fetch.unpackLimit 1 && git config gc.autoPackLimit 1 && git config gc.autoDetach false && + git config maintenance.strategy gc && GIT_ASK_YESNO="$TRASH_DIRECTORY/askyesno" git fetch --verbose >fetch.out 2>&1 && test_grep "Auto packing the repository" fetch.out && ! grep "Should I try again" fetch.out @@ -1516,7 +1522,7 @@ git remote add origin ../base && touch refs/heads/foo.lock && test_must_fail git fetch -f origin "refs/heads/*:refs/heads/*" 2>err && - test_grep "error: fetching ref refs/heads/foo failed: reference already exists" err && + test_grep -e "error: cannot lock ref ${SQ}refs/heads/foo${SQ}: Unable to create" -e "refs/heads/foo.lock${SQ}: File exists." err && git rev-parse refs/heads/main >expect && git rev-parse refs/heads/branch >actual && test_cmp expect actual @@ -1530,7 +1536,7 @@ cd case_insensitive && git remote add origin -- ../case_sensitive_fd && test_must_fail git fetch -f origin "refs/heads/*:refs/heads/*" 2>err && - test_grep "failed: refname conflict" err && + test_grep "cannot process ${SQ}refs/remotes/origin/foo${SQ} and ${SQ}refs/remotes/origin/foo/bar${SQ} at the same time" err && git rev-parse refs/heads/main >expect && git rev-parse refs/heads/foo/bar >actual && test_cmp expect actual @@ -1544,7 +1550,7 @@ cd case_insensitive && git remote add origin -- ../case_sensitive_df && test_must_fail git fetch -f origin "refs/heads/*:refs/heads/*" 2>err && - test_grep "failed: refname conflict" err && + test_grep "cannot lock ref ${SQ}refs/remotes/origin/foo${SQ}: there is a non-empty directory ${SQ}./refs/remotes/origin/foo${SQ} blocking reference ${SQ}refs/remotes/origin/foo${SQ}" err && git rev-parse refs/heads/main >expect && git rev-parse refs/heads/Foo/bar >actual && test_cmp expect actual @@ -1552,6 +1558,7 @@ ' test_expect_success REFFILES 'D/F conflict on case sensitive filesystem with lock' ' + test_when_finished rm -rf base repo && ( git init --ref-format=reftable base && cd base && @@ -1577,6 +1584,155 @@ ) ' +test_expect_success 'fetch --tags fetches existing tags' ' + test_when_finished rm -rf base repo && + + git init base && + git -C base commit --allow-empty -m "empty-commit" && + + git clone --bare base repo && + + git -C base tag tag-1 && + git -C repo for-each-ref >out && + test_grep ! "tag-1" out && + git -C repo fetch --tags && + git -C repo for-each-ref >out && + test_grep "tag-1" out +' + +test_expect_success 'fetch --tags fetches non-conflicting tags' ' + test_when_finished rm -rf base repo && + + git init base && + git -C base commit --allow-empty -m "empty-commit" && + git -C base tag tag-1 && + + git clone --bare base repo && + + git -C base tag tag-2 && + git -C repo for-each-ref >out && + test_grep ! "tag-2" out && + + git -C base commit --allow-empty -m "second empty-commit" && + git -C base tag -f tag-1 && + + test_must_fail git -C repo fetch --tags 2>out && + test_grep "tag-1 (would clobber existing tag)" out && + git -C repo for-each-ref >out && + test_grep "tag-2" out +' + +test_expect_success "backfill tags when providing a refspec" ' + test_when_finished rm -rf source target && + + git init source && + git -C source commit --allow-empty --message common && + git clone file://"$(pwd)"/source target && + ( + cd source && + test_commit history && + test_commit fetch-me + ) && + + # The "history" tag is backfilled even though we requested + # to only fetch HEAD + git -C target fetch origin HEAD:branch && + git -C target tag -l >actual && + cat >expect <<-\EOF && + fetch-me + history + EOF + test_cmp expect actual +' + +test_expect_success REFFILES "FETCH_HEAD is updated even if ref updates fail" ' + test_when_finished rm -rf base repo && + + git init base && + ( + cd base && + test_commit "updated" && + + git update-ref refs/heads/foo @ && + git update-ref refs/heads/branch @ + ) && + + git init --bare repo && + ( + cd repo && + rm -f FETCH_HEAD && + git remote add origin ../base && + >refs/heads/foo.lock && + test_must_fail git fetch -f origin "refs/heads/*:refs/heads/*" 2>err && + test_grep -e "error: cannot lock ref ${SQ}refs/heads/foo${SQ}: Unable to create" -e "refs/heads/foo.lock${SQ}: File exists." err && + test_grep "branch ${SQ}branch${SQ} of ../base" FETCH_HEAD && + test_grep "branch ${SQ}foo${SQ} of ../base" FETCH_HEAD + ) +' + +test_expect_success "upstream tracking info is added with --set-upstream" ' + test_when_finished rm -rf base repo && + + git init --initial-branch=main base && + test_commit -C base "updated" && + + git init --bare --initial-branch=main repo && + ( + cd repo && + git remote add origin ../base && + git fetch origin --set-upstream main && + git config get branch.main.remote >actual && + echo "origin" >expect && + test_cmp expect actual + ) +' + +test_expect_success REFFILES "upstream tracking info is added even with conflicts" ' + test_when_finished rm -rf base repo && + + git init --initial-branch=main base && + test_commit -C base "updated" && + + git init --bare --initial-branch=main repo && + ( + cd repo && + git remote add origin ../base && + test_must_fail git config get branch.main.remote && + + mkdir -p refs/remotes/origin && + >refs/remotes/origin/main.lock && + test_must_fail git fetch origin --set-upstream main && + git config get branch.main.remote >actual && + echo "origin" >expect && + test_cmp expect actual + ) +' + +test_expect_success REFFILES "HEAD is updated even with conflicts" ' + test_when_finished rm -rf base repo && + + git init base && + ( + cd base && + test_commit "updated" && + + git update-ref refs/heads/foo @ && + git update-ref refs/heads/branch @ + ) && + + git init --bare repo && + ( + cd repo && + git remote add origin ../base && + + test_path_is_missing refs/remotes/origin/HEAD && + mkdir -p refs/remotes/origin && + >refs/remotes/origin/branch.lock && + test_must_fail git fetch origin && + test -f refs/remotes/origin/HEAD + ) +' + . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 46926e7..29e2f17 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh
@@ -1882,4 +1882,20 @@ git push testrepo :refs/heads/branch/conflict refs/heads/branch ' +test_expect_success 'pushing non-commit objects should report error' ' + test_when_finished "rm -rf dest repo" && + git init dest && + git init repo && + + ( + cd repo && + test_commit --annotate test && + + tagsha=$(git rev-parse test^{tag}) && + test_must_fail git push ../dest "$tagsha:refs/heads/branch" 2>err && + test_grep "! \[remote rejected\] $tagsha -> branch (invalid new value provided)" err && + test_grep "trying to write non-commit object $tagsha to branch ${SQ}refs/heads/branch${SQ}" err + ) +' + test_done
diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh index 5e56620..1242ee9 100755 --- a/t/t5526-fetch-submodules.sh +++ b/t/t5526-fetch-submodules.sh
@@ -834,11 +834,18 @@ git commit -m "updated submodules outside of refs/heads" && E=$(git rev-parse HEAD) && git update-ref refs/changes/3 $E && + FETCH_TRACE="$(pwd)/trace.out" && + test_when_finished "rm -f \"$FETCH_TRACE\"" && ( cd downstream && - git fetch --recurse-submodules origin refs/changes/3:refs/heads/my_branch && + GIT_TRACE="$FETCH_TRACE" git fetch --recurse-submodules origin \ + refs/changes/3:refs/heads/my_branch && git -C submodule cat-file -t $C && git -C sub1 cat-file -t $D && + test_grep "trace: built-in: git submodule--helper get-default-remote sub1" \ + "$FETCH_TRACE" && + test_grep "trace: built-in: git fetch .* --submodule-prefix=sub1/ origin" \ + "$FETCH_TRACE" && git checkout --recurse-submodules FETCH_HEAD ) ' @@ -929,6 +936,68 @@ ) ' +test_expect_success 'fetch new submodule commits on-demand outside standard refspec with custom remote name' ' + # depends on the previous test for setup + + # Rename the remote in sub1 from "origin" to "custom_remote" + git -C downstream/sub1 remote rename origin custom_remote && + + # Create new commits in the original submodules + C=$(git -C submodule commit-tree \ + -m "change outside refs/heads for custom remote" HEAD^{tree}) && + git -C submodule update-ref refs/changes/custom1 $C && + git update-index --cacheinfo 160000 $C submodule && + test_tick && + + D=$(git -C sub1 commit-tree \ + -m "change outside refs/heads for custom remote" HEAD^{tree}) && + git -C sub1 update-ref refs/changes/custom2 $D && + git update-index --cacheinfo 160000 $D sub1 && + + git commit \ + -m "updated submodules outside of refs/heads for custom remote" && + E=$(git rev-parse HEAD) && + git update-ref refs/changes/custom3 $E && + FETCH_TRACE="$(pwd)/trace.out" && + test_when_finished "rm -f \"$FETCH_TRACE\"" && + ( + cd downstream && + GIT_TRACE="$FETCH_TRACE" git fetch --recurse-submodules origin \ + refs/changes/custom3:refs/heads/my_other_branch && + git -C submodule cat-file -t $C && + git -C sub1 cat-file -t $D && + test_grep "trace: built-in: git submodule--helper get-default-remote sub1" \ + "$FETCH_TRACE" && + test_grep "trace: built-in: git fetch .* --submodule-prefix=sub1/ custom_remote $D" \ + "$FETCH_TRACE" && + git checkout --recurse-submodules FETCH_HEAD + ) +' + +test_expect_success 'fetch new submodule commit on-demand in FETCH_HEAD from custom remote' ' + # depends on the previous test for setup + + C=$(git -C submodule commit-tree -m "another change outside refs/heads for custom remote" HEAD^{tree}) && + git -C submodule update-ref refs/changes/custom4 $C && + git update-index --cacheinfo 160000 $C submodule && + test_tick && + + D=$(git -C sub1 commit-tree -m "another change outside refs/heads for custom remote" HEAD^{tree}) && + git -C sub1 update-ref refs/changes/custom5 $D && + git update-index --cacheinfo 160000 $D sub1 && + + git commit -m "updated submodules outside of refs/heads" && + E=$(git rev-parse HEAD) && + git update-ref refs/changes/custom6 $E && + ( + cd downstream && + git fetch --recurse-submodules origin refs/changes/custom6 && + git -C submodule cat-file -t $C && + git -C sub1 cat-file -t $D && + git checkout --recurse-submodules FETCH_HEAD + ) +' + add_commit_push () { dir="$1" && msg="$2" &&
diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh index ed0ad66..9d0a7f5 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh
@@ -102,6 +102,31 @@ expect_askpass both wrong ' +test_expect_success 'using credentials from netrc to clone successfully' ' + test_when_finished clear_netrc && + set_askpass wrong && + set_netrc 127.0.0.1 user@host pass@host && + git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-netrc && + expect_askpass none +' + +test_expect_success 'netrc unauthorized credentials (prompt after 401)' ' + test_when_finished clear_netrc && + set_askpass wrong && + set_netrc 127.0.0.1 user@host pass@wrong && + test_must_fail git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-netrc-401 && + expect_askpass both wrong +' + +test_expect_success 'netrc authorized but forbidden credentials (fail on 403)' ' + test_when_finished clear_netrc && + set_askpass wrong && + set_netrc 127.0.0.1 forbidden-user@host pass@host && + test_must_fail git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-netrc-403 2>err && + expect_askpass none && + grep "The requested URL returned error: 403" err +' + test_expect_success 'http auth can use user/pass in URL' ' set_askpass wrong && git clone "$HTTPD_URL_USER_PASS/auth/dumb/repo.git" clone-auth-none && @@ -339,32 +364,32 @@ ' test_expect_success 'did not use upload-pack service' ' - ! grep "/git-upload-pack" "$HTTPD_ROOT_PATH/access.log" + test_grep ! "/git-upload-pack" "$HTTPD_ROOT_PATH/access.log" ' -test_expect_success 'git client shows text/plain errors' ' +test_expect_success ICONV 'git client shows text/plain errors' ' test_must_fail git clone "$HTTPD_URL/error/text" 2>stderr && - grep "this is the error message" stderr + test_grep "this is the error message" stderr ' -test_expect_success 'git client does not show html errors' ' +test_expect_success ICONV 'git client does not show html errors' ' test_must_fail git clone "$HTTPD_URL/error/html" 2>stderr && - ! grep "this is the error message" stderr + test_grep ! "this is the error message" stderr ' -test_expect_success 'git client shows text/plain with a charset' ' +test_expect_success ICONV 'git client shows text/plain with a charset' ' test_must_fail git clone "$HTTPD_URL/error/charset" 2>stderr && - grep "this is the error message" stderr + test_grep "this is the error message" stderr ' test_expect_success ICONV 'http error messages are reencoded' ' test_must_fail git clone "$HTTPD_URL/error/utf16" 2>stderr && - grep "this is the error message" stderr + test_grep "this is the error message" stderr ' test_expect_success ICONV 'reencoding is robust to whitespace oddities' ' test_must_fail git clone "$HTTPD_URL/error/odd-spacing" 2>stderr && - grep "this is the error message" stderr + test_grep "this is the error message" stderr ' check_language () { @@ -406,7 +431,7 @@ test_expect_success 'git client send an empty Accept-Language' ' GIT_TRACE_CURL=true LANGUAGE= git ls-remote "$HTTPD_URL/dumb/repo.git" 2>stderr && - ! grep "^=> Send header: Accept-Language:" stderr + test_grep ! "^=> Send header: Accept-Language:" stderr ' test_expect_success 'remote-http complains cleanly about malformed urls' '
diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index b0d4ea7..a26b6c2 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh
@@ -333,12 +333,12 @@ test_expect_success 'cookies stored in http.cookiefile when http.savecookies set' ' cat >cookies.txt <<-\EOF && - 127.0.0.1 FALSE /smart_cookies/ FALSE 0 othername othervalue + 127.0.0.1 FALSE /smart_cookies FALSE 0 othername othervalue EOF sort >expect_cookies.txt <<-\EOF && - 127.0.0.1 FALSE /smart_cookies/ FALSE 0 othername othervalue - 127.0.0.1 FALSE /smart_cookies/repo.git/ FALSE 0 name value - 127.0.0.1 FALSE /smart_cookies/repo.git/info/ FALSE 0 name value + 127.0.0.1 FALSE /smart_cookies FALSE 0 othername othervalue + 127.0.0.1 FALSE /smart_cookies/repo.git FALSE 0 name value + 127.0.0.1 FALSE /smart_cookies/repo.git/info FALSE 0 name value EOF git config http.cookiefile cookies.txt && git config http.savecookies true && @@ -351,8 +351,11 @@ tag -m "foo" cookie-tag && git fetch $HTTPD_URL/smart_cookies/repo.git cookie-tag && - grep "^[^#]" cookies.txt | sort >cookies_stripped.txt && - test_cmp expect_cookies.txt cookies_stripped.txt + # Strip trailing slashes from cookie paths to handle output from both + # old curl ("/smart_cookies/") and new ("/smart_cookies"). + HT=" " && + grep "^[^#]" cookies.txt | sed "s,/$HT,$HT," | sort >cookies_clean.txt && + test_cmp expect_cookies.txt cookies_clean.txt ' test_expect_success 'transfer.hiderefs works over smart-http' ' @@ -779,4 +782,11 @@ test_cmp expect actual ' +test_expect_success 'ls-remote outside repo does not segfault with fetch refspec' ' + nongit git \ + -c remote.origin.url="$HTTPD_URL/smart/repo.git" \ + -c remote.origin.fetch=anything \ + ls-remote origin +' + test_done
diff --git a/t/t5563-simple-http-auth.sh b/t/t5563-simple-http-auth.sh index 317f33a..0063581 100755 --- a/t/t5563-simple-http-auth.sh +++ b/t/t5563-simple-http-auth.sh
@@ -469,7 +469,7 @@ EOF ' -test_expect_success 'access using basic auth with wwwauth header mixed line-endings' ' +test_expect_success 'access using basic auth with wwwauth header mixed continuations' ' test_when_finished "per_test_cleanup" && set_credential_reply get <<-EOF && @@ -490,7 +490,7 @@ printf "id=default response=WWW-Authenticate: FooBar param1=\"value1\"\r\n" >>"$CHALLENGE" && printf "id=default response= \r\n" >>"$CHALLENGE" && printf "id=default response=\tparam2=\"value2\"\r\n" >>"$CHALLENGE" && - printf "id=default response=WWW-Authenticate: Basic realm=\"example.com\"" >>"$CHALLENGE" && + printf "id=default response=WWW-Authenticate: Basic realm=\"example.com\"\r\n" >>"$CHALLENGE" && test_config_global credential.helper test-helper && git ls-remote "$HTTPD_URL/custom_auth/repo.git" && @@ -605,6 +605,51 @@ EOF ' +test_expect_success 'clone with bearer auth and probe_rpc' ' + test_when_finished "per_test_cleanup" && + test_when_finished "rm -rf large.git" && + + # Set up a repository large enough to trigger probe_rpc + git init large.git && + ( + cd large.git && + git config set maintenance.auto false && + git commit --allow-empty --message "initial" && + # Create many refs to trigger probe_rpc, which is called when + # the request body is larger than http.postBuffer. + # + # In the test later, http.postBuffer is set to 70000. Each + # "want" line is ~45 bytes, so we need at least 70000/45 = ~1600 + # refs + test_seq -f "create refs/heads/branch-%d @" 2000 | + git update-ref --stdin + ) && + git clone --bare large.git "$HTTPD_DOCUMENT_ROOT_PATH/large.git" && + + # Clone it through HTTP with a Bearer token + set_credential_reply get <<-EOF && + capability[]=authtype + authtype=Bearer + credential=YS1naXQtdG9rZW4= + EOF + + # Bearer token + cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && + id=1 creds=Bearer YS1naXQtdG9rZW4= + EOF + + cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && + id=1 status=200 + id=default response=WWW-Authenticate: Bearer authorize_uri="id.example.com" + EOF + + # Set a small buffer to force probe_rpc to be called + # Must be > LARGE_PACKET_MAX (65520) + test_config_global http.postBuffer 70000 && + test_config_global credential.helper test-helper && + git clone "$HTTPD_URL/custom_auth/large.git" partial-auth-clone 2>clone-error +' + test_expect_success 'access using three-legged auth' ' test_when_finished "per_test_cleanup" &&
diff --git a/t/t5564-http-proxy.sh b/t/t5564-http-proxy.sh index c3903fa..3bcbdef 100755 --- a/t/t5564-http-proxy.sh +++ b/t/t5564-http-proxy.sh
@@ -40,10 +40,10 @@ start_socks() { mkfifo socks_output && - { + ( "$PERL_PATH" "$TEST_DIRECTORY/socks4-proxy.pl" "$1" >socks_output & echo $! > "$TRASH_DIRECTORY/socks.pid" - } && + ) && read line <socks_output && test "$line" = ready }
diff --git a/t/t5565-push-multiple.sh b/t/t5565-push-multiple.sh new file mode 100755 index 0000000..7e93668 --- /dev/null +++ b/t/t5565-push-multiple.sh
@@ -0,0 +1,39 @@ +#!/bin/sh + +test_description='push to group' + +. ./test-lib.sh + +test_expect_success setup ' + for i in 1 2 3 + do + git init dest-$i && + git -C dest-$i symbolic-ref HEAD refs/heads/not-a-branch || + return 1 + done && + test_tick && + git commit --allow-empty -m "initial" && + git config set --append remote.them.pushurl "file://$(pwd)/dest-1" && + git config set --append remote.them.pushurl "file://$(pwd)/dest-2" && + git config set --append remote.them.pushurl "file://$(pwd)/dest-3" && + git config set --append remote.them.push "+refs/heads/*:refs/heads/*" +' + +test_expect_success 'push to group' ' + git push them && + j= && + for i in 1 2 3 + do + git -C dest-$i for-each-ref >actual-$i && + if test -n "$j" + then + test_cmp actual-$j actual-$i + else + cat actual-$i + fi && + j=$i || + return 1 + done +' + +test_done
diff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh index 45f384d..42d1432 100755 --- a/t/t5572-pull-submodule.sh +++ b/t/t5572-pull-submodule.sh
@@ -257,7 +257,26 @@ git -C a-submodule reset --hard HEAD^^ && git -C child pull --no-recurse-submodules && - git -C child submodule update + git -C child submodule update && + test_path_is_file child/a-submodule/moreecho.t +' + +test_expect_success 'fetch non-origin submodule remote named different from superproject' ' + git -C child/a-submodule remote rename origin o2 && + + # Create commit that is unreachable from current master branch + # newmain is already reset in the previous test + test_commit -C a-submodule echo_o2 && + test_commit -C a-submodule moreecho_o2 && + subc=$(git -C a-submodule rev-parse --short HEAD) && + + git -C parent/a-submodule fetch && + git -C parent/a-submodule checkout "$subc" && + git -C parent commit -m "update submodule o2" a-submodule && + git -C a-submodule reset --hard HEAD^^ && + + git -C child pull --recurse-submodules && + test_path_is_file child/a-submodule/moreecho_o2.t ' test_done
diff --git a/t/t5584-http-429-retry.sh b/t/t5584-http-429-retry.sh new file mode 100755 index 0000000..a22007b --- /dev/null +++ b/t/t5584-http-429-retry.sh
@@ -0,0 +1,266 @@ +#!/bin/sh + +test_description='test HTTP 429 Too Many Requests retry logic' + +. ./test-lib.sh + +. "$TEST_DIRECTORY"/lib-httpd.sh + +start_httpd + +test_expect_success 'setup test repository' ' + test_commit initial && + git clone --bare . "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" config http.receivepack true +' + +# This test suite uses a special HTTP 429 endpoint at /http_429/ that simulates +# rate limiting. The endpoint format is: +# /http_429/<test-context>/<retry-after-value>/<repo-path> +# The http-429.sh script (in t/lib-httpd) returns a 429 response with the +# specified Retry-After header on the first request for each test context, +# then forwards subsequent requests to git-http-backend. Each test context +# is isolated, allowing multiple tests to run independently. + +test_expect_success 'HTTP 429 with retries disabled (maxRetries=0) fails immediately' ' + # Set maxRetries to 0 (disabled) + test_config http.maxRetries 0 && + test_config http.retryAfter 1 && + + # Should fail immediately without any retry attempt + test_must_fail git ls-remote "$HTTPD_URL/http_429/retries-disabled/1/repo.git" 2>err && + + # Verify no retry happened (no "waiting" message in stderr) + test_grep ! -i "waiting.*retry" err +' + +test_expect_success 'HTTP 429 permanent should fail after max retries' ' + # Enable retries with a limit + test_config http.maxRetries 2 && + + # Git should retry but eventually fail when 429 persists + test_must_fail git ls-remote "$HTTPD_URL/http_429/permanent-fail/permanent/repo.git" 2>err +' + +test_expect_success 'HTTP 429 with Retry-After is retried and succeeds' ' + # Enable retries + test_config http.maxRetries 3 && + + # Git should retry after receiving 429 and eventually succeed + git ls-remote "$HTTPD_URL/http_429/retry-succeeds/1/repo.git" >output 2>err && + test_grep "refs/heads/" output +' + +test_expect_success 'HTTP 429 without Retry-After uses configured default' ' + # Enable retries and configure default delay + test_config http.maxRetries 3 && + test_config http.retryAfter 1 && + + # Git should retry using configured default and succeed + git ls-remote "$HTTPD_URL/http_429/no-retry-after-header/none/repo.git" >output 2>err && + test_grep "refs/heads/" output +' + +test_expect_success 'HTTP 429 retry delays are respected' ' + # Enable retries + test_config http.maxRetries 3 && + + # Time the operation - it should take at least 2 seconds due to retry delay + start=$(test-tool date getnanos) && + git ls-remote "$HTTPD_URL/http_429/retry-delays-respected/2/repo.git" >output 2>err && + duration=$(test-tool date getnanos $start) && + + # Verify it took at least 2 seconds (allowing some tolerance) + duration_int=${duration%.*} && + test "$duration_int" -ge 1 && + test_grep "refs/heads/" output +' + +test_expect_success 'HTTP 429 fails immediately if Retry-After exceeds http.maxRetryTime' ' + # Configure max retry time to 3 seconds (much less than requested 100) + test_config http.maxRetries 3 && + test_config http.maxRetryTime 3 && + + # Should fail immediately without waiting + start=$(test-tool date getnanos) && + test_must_fail git ls-remote "$HTTPD_URL/http_429/retry-after-exceeds-max-time/100/repo.git" 2>err && + duration=$(test-tool date getnanos $start) && + + # Should fail quickly (no 100 second wait) + duration_int=${duration%.*} && + test "$duration_int" -lt 99 && + test_grep "greater than http.maxRetryTime" err +' + +test_expect_success 'HTTP 429 fails if configured http.retryAfter exceeds http.maxRetryTime' ' + # Test misconfiguration: retryAfter > maxRetryTime + # Configure retryAfter larger than maxRetryTime + test_config http.maxRetries 3 && + test_config http.retryAfter 100 && + test_config http.maxRetryTime 5 && + + # Should fail immediately with configuration error + start=$(test-tool date getnanos) && + test_must_fail git ls-remote "$HTTPD_URL/http_429/config-retry-after-exceeds-max-time/none/repo.git" 2>err && + duration=$(test-tool date getnanos $start) && + + # Should fail quickly (no 100 second wait) + duration_int=${duration%.*} && + test "$duration_int" -lt 99 && + test_grep "configured http.retryAfter.*exceeds.*http.maxRetryTime" err +' + +test_expect_success 'HTTP 429 with Retry-After HTTP-date format' ' + # Test HTTP-date format (RFC 2822) in Retry-After header + raw=$(test-tool date timestamp now) && + now="${raw#* -> }" && + future_time=$((now + 2)) && + raw=$(test-tool date show:rfc2822 $future_time) && + future_date="${raw#* -> }" && + future_date_encoded=$(echo "$future_date" | sed "s/ /%20/g") && + + # Enable retries + test_config http.maxRetries 3 && + + # Git should parse the HTTP-date and retry after the delay + start=$(test-tool date getnanos) && + git ls-remote "$HTTPD_URL/http_429/http-date-format/$future_date_encoded/repo.git" >output 2>err && + duration=$(test-tool date getnanos $start) && + + # Should take at least 1 second (allowing tolerance for processing time) + duration_int=${duration%.*} && + test "$duration_int" -ge 1 && + test_grep "refs/heads/" output +' + +test_expect_success 'HTTP 429 with HTTP-date exceeding maxRetryTime fails immediately' ' + raw=$(test-tool date timestamp now) && + now="${raw#* -> }" && + future_time=$((now + 200)) && + raw=$(test-tool date show:rfc2822 $future_time) && + future_date="${raw#* -> }" && + future_date_encoded=$(echo "$future_date" | sed "s/ /%20/g") && + + # Configure max retry time much less than the 200 second delay + test_config http.maxRetries 3 && + test_config http.maxRetryTime 10 && + + # Should fail immediately without waiting 200 seconds + start=$(test-tool date getnanos) && + test_must_fail git ls-remote "$HTTPD_URL/http_429/http-date-exceeds-max-time/$future_date_encoded/repo.git" 2>err && + duration=$(test-tool date getnanos $start) && + + # Should fail quickly (not wait 200 seconds) + duration_int=${duration%.*} && + test "$duration_int" -lt 199 && + test_grep "http.maxRetryTime" err +' + +test_expect_success 'HTTP 429 with past HTTP-date should not wait' ' + raw=$(test-tool date timestamp now) && + now="${raw#* -> }" && + past_time=$((now - 10)) && + raw=$(test-tool date show:rfc2822 $past_time) && + past_date="${raw#* -> }" && + past_date_encoded=$(echo "$past_date" | sed "s/ /%20/g") && + + # Enable retries + test_config http.maxRetries 3 && + + # Git should retry immediately without waiting + start=$(test-tool date getnanos) && + git ls-remote "$HTTPD_URL/http_429/past-http-date/$past_date_encoded/repo.git" >output 2>err && + duration=$(test-tool date getnanos $start) && + + # Should complete quickly (no wait for a past-date Retry-After) + duration_int=${duration%.*} && + test "$duration_int" -lt 5 && + test_grep "refs/heads/" output +' + +test_expect_success 'HTTP 429 with invalid Retry-After format uses configured default' ' + # Configure default retry-after + test_config http.maxRetries 3 && + test_config http.retryAfter 1 && + + # Should use configured default (1 second) since header is invalid + start=$(test-tool date getnanos) && + git ls-remote "$HTTPD_URL/http_429/invalid-retry-after-format/invalid/repo.git" >output 2>err && + duration=$(test-tool date getnanos $start) && + + # Should take at least 1 second (the configured default) + duration_int=${duration%.*} && + test "$duration_int" -ge 1 && + test_grep "refs/heads/" output && + test_grep "waiting.*retry" err +' + +test_expect_success 'HTTP 429 will not be retried without config' ' + # Default config means http.maxRetries=0 (retries disabled) + # When 429 is received, it should fail immediately without retry + # Do NOT configure anything - use defaults (http.maxRetries defaults to 0) + + # Should fail immediately without retry + test_must_fail git ls-remote "$HTTPD_URL/http_429/no-retry-without-config/1/repo.git" 2>err && + + # Verify no retry happened (no "waiting" message) + test_grep ! -i "waiting.*retry" err && + + # Should get 429 error + test_grep "429" err +' + +test_expect_success 'GIT_HTTP_RETRY_AFTER overrides http.retryAfter config' ' + # Configure retryAfter to 10 seconds + test_config http.maxRetries 3 && + test_config http.retryAfter 10 && + + # Override with environment variable to 1 second + start=$(test-tool date getnanos) && + GIT_HTTP_RETRY_AFTER=1 git ls-remote "$HTTPD_URL/http_429/env-retry-after-override/none/repo.git" >output 2>err && + duration=$(test-tool date getnanos $start) && + + # Should use env var (1 second), not config (10 seconds) + duration_int=${duration%.*} && + test "$duration_int" -ge 1 && + test "$duration_int" -lt 5 && + test_grep "refs/heads/" output && + test_grep "waiting.*retry" err +' + +test_expect_success 'GIT_HTTP_MAX_RETRIES overrides http.maxRetries config' ' + # Configure maxRetries to 0 (disabled) + test_config http.maxRetries 0 && + test_config http.retryAfter 1 && + + # Override with environment variable to enable retries + GIT_HTTP_MAX_RETRIES=3 git ls-remote "$HTTPD_URL/http_429/env-max-retries-override/1/repo.git" >output 2>err && + + # Should retry (env var enables it despite config saying disabled) + test_grep "refs/heads/" output && + test_grep "waiting.*retry" err +' + +test_expect_success 'GIT_HTTP_MAX_RETRY_TIME overrides http.maxRetryTime config' ' + # Configure maxRetryTime to 100 seconds (would accept 50 second delay) + test_config http.maxRetries 3 && + test_config http.maxRetryTime 100 && + + # Override with environment variable to 10 seconds (should reject 50 second delay) + start=$(test-tool date getnanos) && + test_must_fail env GIT_HTTP_MAX_RETRY_TIME=10 \ + git ls-remote "$HTTPD_URL/http_429/env-max-retry-time-override/50/repo.git" 2>err && + duration=$(test-tool date getnanos $start) && + + # Should fail quickly (not wait 50 seconds) because env var limits to 10 + duration_int=${duration%.*} && + test "$duration_int" -lt 49 && + test_grep "greater than http.maxRetryTime" err +' + +test_expect_success 'verify normal repository access still works' ' + git ls-remote "$HTTPD_URL/smart/repo.git" >output && + test_grep "refs/heads/" output +' + +test_done
diff --git a/t/t5616-partial-clone.sh b/t/t5616-partial-clone.sh index 1e354e0..1c2805a 100755 --- a/t/t5616-partial-clone.sh +++ b/t/t5616-partial-clone.sh
@@ -229,7 +229,7 @@ GIT_TRACE2_EVENT="$PWD/trace1.event" \ git -C pc1 fetch --refetch origin && - test_subcommand git maintenance run --auto --no-quiet --detach <trace1.event && + test_subcommand git maintenance run --auto --no-quiet --no-detach <trace1.event && grep \"param\":\"gc.autopacklimit\",\"value\":\"1\" trace1.event && grep \"param\":\"maintenance.incremental-repack.auto\",\"value\":\"-1\" trace1.event && @@ -238,7 +238,7 @@ -c gc.autoPackLimit=0 \ -c maintenance.incremental-repack.auto=1234 \ -C pc1 fetch --refetch origin && - test_subcommand git maintenance run --auto --no-quiet --detach <trace2.event && + test_subcommand git maintenance run --auto --no-quiet --no-detach <trace2.event && grep \"param\":\"gc.autopacklimit\",\"value\":\"0\" trace2.event && grep \"param\":\"maintenance.incremental-repack.auto\",\"value\":\"-1\" trace2.event && @@ -247,7 +247,7 @@ -c gc.autoPackLimit=1234 \ -c maintenance.incremental-repack.auto=0 \ -C pc1 fetch --refetch origin && - test_subcommand git maintenance run --auto --no-quiet --detach <trace3.event && + test_subcommand git maintenance run --auto --no-quiet --no-detach <trace3.event && grep \"param\":\"gc.autopacklimit\",\"value\":\"1\" trace3.event && grep \"param\":\"maintenance.incremental-repack.auto\",\"value\":\"0\" trace3.event ' @@ -585,6 +585,7 @@ git clone --filter=blob:none "file://$(pwd)/srv.bare" pack-test && ls pack-test/.git/objects/pack/*pack >pack-list && test_line_count = 2 pack-list && + test_config -C pack-test maintenance.auto false && for i in A B C do test_commit -C src $i &&
diff --git a/t/t5620-backfill.sh b/t/t5620-backfill.sh index 58c8155..2c347a9 100755 --- a/t/t5620-backfill.sh +++ b/t/t5620-backfill.sh
@@ -7,6 +7,14 @@ . ./test-lib.sh +test_expect_success 'backfill rejects unexpected arguments' ' + test_must_fail git backfill unexpected-arg 2>err && + test_grep "ambiguous argument .*unexpected-arg" err && + + test_must_fail git backfill --all --unexpected-arg --first-parent 2>err && + test_grep "unrecognized argument: --unexpected-arg" err +' + # We create objects in the 'src' repo. test_expect_success 'setup repo for object creation' ' echo "{print \$1}" >print_1.awk && @@ -15,7 +23,7 @@ git init src && mkdir -p src/a/b/c && - mkdir -p src/d/e && + mkdir -p src/d/f && for i in 1 2 do @@ -26,8 +34,9 @@ echo "Version $i of file a/b/$n" > src/a/b/file.$n.txt && echo "Version $i of file a/b/c/$n" > src/a/b/c/file.$n.txt && echo "Version $i of file d/$n" > src/d/file.$n.txt && - echo "Version $i of file d/e/$n" > src/d/e/file.$n.txt && + echo "Version $i of file d/f/$n" > src/d/f/file.$n.txt && git -C src add . && + test_tick && git -C src commit -m "Iteration $n" || return 1 done done @@ -41,6 +50,53 @@ git -C srv.bare config --local uploadpack.allowanysha1inwant 1 ' +# Create a version of the repo with branches for testing revision +# arguments like --all, --first-parent, and --since. +# +# main: 8 commits (linear) + merge of side branch +# 48 original blobs + 4 side blobs = 52 blobs from main HEAD +# side: 2 commits adding s/file.{1,2}.txt (v1, v2), merged into main +# other: 1 commit adding o/file.{1,2}.txt (not merged) +# 54 total blobs reachable from --all +test_expect_success 'setup branched repo for revision tests' ' + git clone src src-revs && + + # Side branch from tip of main with unique files + git -C src-revs checkout -b side HEAD && + mkdir -p src-revs/s && + echo "Side version 1 of file 1" >src-revs/s/file.1.txt && + echo "Side version 1 of file 2" >src-revs/s/file.2.txt && + test_tick && + git -C src-revs add . && + git -C src-revs commit -m "Side commit 1" && + + echo "Side version 2 of file 1" >src-revs/s/file.1.txt && + echo "Side version 2 of file 2" >src-revs/s/file.2.txt && + test_tick && + git -C src-revs add . && + git -C src-revs commit -m "Side commit 2" && + + # Merge side into main + git -C src-revs checkout main && + test_tick && + git -C src-revs merge side --no-ff -m "Merge side branch" && + + # Other branch (not merged) for --all testing + git -C src-revs checkout -b other main~1 && + mkdir -p src-revs/o && + echo "Other content 1" >src-revs/o/file.1.txt && + echo "Other content 2" >src-revs/o/file.2.txt && + test_tick && + git -C src-revs add . && + git -C src-revs commit -m "Other commit" && + + git -C src-revs checkout main && + + git clone --bare "file://$(pwd)/src-revs" srv-revs.bare && + git -C srv-revs.bare config --local uploadpack.allowfilter 1 && + git -C srv-revs.bare config --local uploadpack.allowanysha1inwant 1 +' + # do basic partial clone from "srv.bare" test_expect_success 'do partial clone 1, backfill gets all objects' ' git clone --no-checkout --filter=blob:none \ @@ -176,6 +232,157 @@ test_line_count = 12 missing ' +test_expect_success 'backfill with revision range' ' + test_when_finished rm -rf backfill-revs && + git clone --no-checkout --filter=blob:none \ + --single-branch --branch=main \ + "file://$(pwd)/srv.bare" backfill-revs && + + # No blobs yet + git -C backfill-revs rev-list --quiet --objects --missing=print HEAD >missing && + test_line_count = 48 missing && + + git -C backfill-revs backfill HEAD~2..HEAD && + + # 30 objects downloaded. + git -C backfill-revs rev-list --quiet --objects --missing=print HEAD >missing && + test_line_count = 18 missing +' + +test_expect_success 'backfill with revisions over stdin' ' + test_when_finished rm -rf backfill-revs && + git clone --no-checkout --filter=blob:none \ + --single-branch --branch=main \ + "file://$(pwd)/srv.bare" backfill-revs && + + # No blobs yet + git -C backfill-revs rev-list --quiet --objects --missing=print HEAD >missing && + test_line_count = 48 missing && + + cat >in <<-EOF && + HEAD + ^HEAD~2 + EOF + + git -C backfill-revs backfill --stdin <in && + + # 30 objects downloaded. + git -C backfill-revs rev-list --quiet --objects --missing=print HEAD >missing && + test_line_count = 18 missing +' + +test_expect_success 'backfill with prefix pathspec' ' + test_when_finished rm -rf backfill-path && + git clone --bare --filter=blob:none \ + --single-branch --branch=main \ + "file://$(pwd)/srv.bare" backfill-path && + + # No blobs yet + git -C backfill-path rev-list --quiet --objects --missing=print HEAD >missing && + test_line_count = 48 missing && + + git -C backfill-path backfill HEAD -- d/f 2>err && + test_must_be_empty err && + + git -C backfill-path rev-list --quiet --objects --missing=print HEAD >missing && + test_line_count = 40 missing +' + +test_expect_success 'backfill with multiple pathspecs' ' + test_when_finished rm -rf backfill-path && + git clone --bare --filter=blob:none \ + --single-branch --branch=main \ + "file://$(pwd)/srv.bare" backfill-path && + + # No blobs yet + git -C backfill-path rev-list --quiet --objects --missing=print HEAD >missing && + test_line_count = 48 missing && + + git -C backfill-path backfill HEAD -- d/f a 2>err && + test_must_be_empty err && + + git -C backfill-path rev-list --quiet --objects --missing=print HEAD >missing && + test_line_count = 16 missing +' + +test_expect_success 'backfill with wildcard pathspec' ' + test_when_finished rm -rf backfill-path && + git clone --bare --filter=blob:none \ + --single-branch --branch=main \ + "file://$(pwd)/srv.bare" backfill-path && + + # No blobs yet + git -C backfill-path rev-list --quiet --objects --missing=print HEAD >missing && + test_line_count = 48 missing && + + git -C backfill-path backfill HEAD -- "d/file.*.txt" 2>err && + test_must_be_empty err && + + git -C backfill-path rev-list --quiet --objects --missing=print HEAD >missing && + test_line_count = 40 missing +' + +test_expect_success 'backfill with --all' ' + test_when_finished rm -rf backfill-all && + git clone --no-checkout --filter=blob:none \ + "file://$(pwd)/srv-revs.bare" backfill-all && + + # All blobs from all refs are missing + git -C backfill-all rev-list --quiet --objects --all --missing=print >missing && + test_line_count = 54 missing && + + # Backfill from HEAD gets main blobs only + git -C backfill-all backfill HEAD && + + # Other branch blobs still missing + git -C backfill-all rev-list --quiet --objects --all --missing=print >missing && + test_line_count = 2 missing && + + # Backfill with --all gets everything + git -C backfill-all backfill --all && + + git -C backfill-all rev-list --quiet --objects --all --missing=print >missing && + test_line_count = 0 missing +' + +test_expect_success 'backfill with --first-parent' ' + test_when_finished rm -rf backfill-fp && + git clone --no-checkout --filter=blob:none \ + --single-branch --branch=main \ + "file://$(pwd)/srv-revs.bare" backfill-fp && + + git -C backfill-fp rev-list --quiet --objects --missing=print HEAD >missing && + test_line_count = 52 missing && + + # --first-parent skips the side branch commits, so + # s/file.{1,2}.txt v1 blobs (only in side commit 1) are missed. + git -C backfill-fp backfill --first-parent HEAD && + + git -C backfill-fp rev-list --quiet --objects --missing=print HEAD >missing && + test_line_count = 2 missing +' + +test_expect_success 'backfill with --since' ' + test_when_finished rm -rf backfill-since && + git clone --no-checkout --filter=blob:none \ + --single-branch --branch=main \ + "file://$(pwd)/srv-revs.bare" backfill-since && + + git -C backfill-since rev-list --quiet --objects --missing=print HEAD >missing && + test_line_count = 52 missing && + + # Use a cutoff between commits 4 and 5 (between v1 and v2 + # iterations). Commits 5-8 still carry v1 of files 2-4 in + # their trees, but v1 of file.1.txt is only in commits 1-4. + SINCE=$(git -C backfill-since log --first-parent --reverse \ + --format=%ct HEAD~1 | sed -n 5p) && + git -C backfill-since backfill --since="@$((SINCE - 1))" HEAD && + + # 6 missing: v1 of file.1.txt in all 6 directories + git -C backfill-since rev-list --quiet --objects --missing=print HEAD >missing && + test_line_count = 6 missing +' + . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd
diff --git a/t/t5710-promisor-remote-capability.sh b/t/t5710-promisor-remote-capability.sh index 023735d..357822c 100755 --- a/t/t5710-promisor-remote-capability.sh +++ b/t/t5710-promisor-remote-capability.sh
@@ -20,7 +20,7 @@ test_commit -C template 1 && test_commit -C template 2 && test_commit -C template 3 && - test-tool genrandom foo 10240 >template/foo && + test-tool genrandom foo 10k >template/foo && git -C template add foo && git -C template commit -m foo ' @@ -360,6 +360,129 @@ check_missing_objects server 1 "$oid" ' +test_expect_success "clone with promisor.storeFields=partialCloneFilter" ' + git -C server config promisor.advertise true && + test_when_finished "rm -rf client" && + + git -C server remote add otherLop "https://invalid.invalid" && + git -C server config remote.otherLop.token "fooBar" && + git -C server config remote.otherLop.stuff "baz" && + git -C server config remote.otherLop.partialCloneFilter "blob:limit=10k" && + test_when_finished "git -C server remote remove otherLop" && + + git -C server config remote.lop.token "fooXXX" && + git -C server config remote.lop.partialCloneFilter "blob:limit=8k" && + + test_config -C server promisor.sendFields "partialCloneFilter, token" && + test_when_finished "rm trace" && + + # Clone from server to create a client + GIT_TRACE_PACKET="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \ + -c remote.lop.promisor=true \ + -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \ + -c remote.lop.url="file://$(pwd)/lop" \ + -c remote.lop.token="fooYYY" \ + -c remote.lop.partialCloneFilter="blob:none" \ + -c promisor.acceptfromserver=All \ + -c promisor.storeFields=partialcloneFilter \ + --no-local --filter="blob:limit=5k" server client 2>err && + + # Check that the filter from the server is stored + echo "blob:limit=8k" >expected && + git -C client config remote.lop.partialCloneFilter >actual && + test_cmp expected actual && + + # Check that user is notified when the filter is stored + test_grep "Storing new filter from server for remote '\''lop'\''" err && + test_grep "'\''blob:none'\'' -> '\''blob:limit=8k'\''" err && + + # Check that the token from the server is NOT stored + echo "fooYYY" >expected && + git -C client config remote.lop.token >actual && + test_cmp expected actual && + test_grep ! "Storing new token from server" err && + + # Check that the filter for an unknown remote is NOT stored + test_must_fail git -C client config remote.otherLop.partialCloneFilter >actual && + + # Check that the largest object is still missing on the server + check_missing_objects server 1 "$oid" && + + # Change the configuration on the server and fetch from the client + git -C server config remote.lop.partialCloneFilter "blob:limit=7k" && + GIT_NO_LAZY_FETCH=0 git -C client fetch \ + --filter="blob:limit=5k" ../server 2>err && + + # Check that the fetch updated the configuration on the client + echo "blob:limit=7k" >expected && + git -C client config remote.lop.partialCloneFilter >actual && + test_cmp expected actual && + + # Check that user is notified when the new filter is stored + test_grep "Storing new filter from server for remote '\''lop'\''" err && + test_grep "'\''blob:limit=8k'\'' -> '\''blob:limit=7k'\''" err +' + +test_expect_success "clone and fetch with --filter=auto" ' + git -C server config promisor.advertise true && + test_when_finished "rm -rf client trace" && + + git -C server config remote.lop.partialCloneFilter "blob:limit=9500" && + test_config -C server promisor.sendFields "partialCloneFilter" && + + GIT_TRACE_PACKET="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \ + -c remote.lop.promisor=true \ + -c remote.lop.url="file://$(pwd)/lop" \ + -c promisor.acceptfromserver=All \ + --no-local --filter=auto server client 2>err && + + test_grep "filter blob:limit=9500" trace && + test_grep ! "filter auto" trace && + + # Verify "auto" is persisted in config + echo auto >expected && + git -C client config remote.origin.partialCloneFilter >actual && + test_cmp expected actual && + + # Check that the largest object is still missing on the server + check_missing_objects server 1 "$oid" && + + # Now change the filter on the server + git -C server config remote.lop.partialCloneFilter "blob:limit=5678" && + + # Get a new commit on the server to ensure "git fetch" actually runs fetch-pack + test_commit -C template new-commit && + git -C template push --all "$(pwd)/server" && + + # Perform a fetch WITH --filter=auto + rm -rf trace && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch --filter=auto && + + # Verify that the new filter was used + test_grep "filter blob:limit=5678" trace && + + # Check that the largest object is still missing on the server + check_missing_objects server 1 "$oid" && + + # Change the filter on the server again + git -C server config remote.lop.partialCloneFilter "blob:limit=5432" && + + # Get yet a new commit on the server to ensure fetch-pack runs + test_commit -C template yet-a-new-commit && + git -C template push --all "$(pwd)/server" && + + # Perform a fetch WITHOUT --filter=auto + # Relies on "auto" being persisted in the client config + rm -rf trace && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch && + + # Verify that the new filter was used + test_grep "filter blob:limit=5432" trace && + + # Check that the largest object is still missing on the server + check_missing_objects server 1 "$oid" +' + test_expect_success "clone with promisor.advertise set to 'true' but don't delete the client" ' git -C server config promisor.advertise true && @@ -376,7 +499,7 @@ test_expect_success "setup for subsequent fetches" ' # Generate new commit with large blob - test-tool genrandom bar 10240 >template/bar && + test-tool genrandom bar 10k >template/bar && git -C template add bar && git -C template commit -m bar &&
diff --git a/t/t5750-bundle-uri-parse.sh b/t/t5750-bundle-uri-parse.sh index 80a3f83..294f9d9 100755 --- a/t/t5750-bundle-uri-parse.sh +++ b/t/t5750-bundle-uri-parse.sh
@@ -286,4 +286,30 @@ grep "could not parse bundle list key creationToken with value '\''bogus'\''" err ' +test_expect_success 'parse config format: bundle with missing uri' ' + cat >input <<-\EOF && + [bundle] + version = 1 + mode = all + [bundle "missing-uri"] + creationToken = 1 + EOF + + test_must_fail test-tool bundle-uri parse-config input 2>err && + grep "bundle '\''missing-uri'\'' has no uri" err +' + +test_expect_success 'parse config format: bundle with url instead of uri' ' + cat >input <<-\EOF && + [bundle] + version = 1 + mode = all + [bundle "typo"] + url = https://example.com/bundle.bdl + EOF + + test_must_fail test-tool bundle-uri parse-config input 2>err && + grep "bundle '\''typo'\'' has no uri" err +' + test_done
diff --git a/t/t6000-rev-list-misc.sh b/t/t6000-rev-list-misc.sh index fec1644..d0a2a86 100755 --- a/t/t6000-rev-list-misc.sh +++ b/t/t6000-rev-list-misc.sh
@@ -248,4 +248,19 @@ test_cmp expect actual ' +test_expect_success 'rev-list --boundary incompatible with --maximal-only' ' + test_when_finished rm -rf repo && + + git init repo && + test_commit -C repo 1 && + test_commit -C repo 2 && + + oid1=$(git -C repo rev-parse HEAD~) && + oid2=$(git -C repo rev-parse HEAD) && + + test_must_fail git -C repo rev-list --boundary --maximal-only \ + HEAD~1..HEAD 2>err && + test_grep "cannot be used together" err +' + test_done
diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index eb93d68..5819844 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh
@@ -378,15 +378,23 @@ test_cmp expect actual ' -iconv -f utf-8 -t $test_encoding > commit-msg <<EOF -Test printing of complex bodies - -This commit message is much longer than the others, -and it will be encoded in $test_encoding. We should therefore -include an ISO8859 character: ¡bueno! -EOF - test_expect_success 'setup complex body' ' + message=$(cat <<-EOF + Test printing of complex bodies + + This commit message is much longer than the others, + and it will be encoded in $test_encoding. We should therefore + include an ISO8859 character: ¡bueno! + EOF + ) && + + if test_have_prereq ICONV + then + echo "$message" | iconv -f utf-8 -t $test_encoding >commit-msg + else + echo "$message" >commit-msg + fi && + git config i18n.commitencoding $test_encoding && echo change2 >foo && git commit -a -F commit-msg && head3=$(git rev-parse --verify HEAD) && @@ -448,7 +456,12 @@ commit $head2 commit $head1 EOF - iconv -f utf-8 -t $test_encoding expected.utf-8 >expected.ISO8859-1 + if test_have_prereq ICONV + then + iconv -f utf-8 -t $test_encoding expected.utf-8 >expected.ISO8859-1 + else + cp expected.utf-8 expected.ISO8859-1 + fi ' test_format complex-body %b <expected.ISO8859-1
diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index cdc0270..1ba9ca2 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh
@@ -402,7 +402,7 @@ git bisect good $HASH1 && git bisect bad $HASH4 && ! git bisect run ./fail.sh 2>err && - sed -En 's/.*(bisect.*code) (-?[0-9]+) (from.*)/\1 -1 \3/p' err >actual && + sed -E -n 's/.*(bisect.*code) (-?[0-9]+) (from.*)/\1 -1 \3/p' err >actual && test_cmp expect actual "
diff --git a/t/t6040-tracking-info.sh b/t/t6040-tracking-info.sh index 0b719bb..0242b5b 100755 --- a/t/t6040-tracking-info.sh +++ b/t/t6040-tracking-info.sh
@@ -292,4 +292,358 @@ test_cmp expect actual ' +test_expect_success 'status tracking origin/main shows only main' ' + git -C test checkout b4 && + git -C test status >actual && + cat >expect <<-EOF && + On branch b4 + Your branch is ahead of ${SQ}origin/main${SQ} by 2 commits. + (use "git push" to publish your local commits) + + nothing to commit, working tree clean + EOF + test_cmp expect actual +' + +test_expect_success 'status --no-ahead-behind tracking origin/main shows only main' ' + git -C test checkout b4 && + git -C test status --no-ahead-behind >actual && + cat >expect <<-EOF && + On branch b4 + Your branch and ${SQ}origin/main${SQ} refer to different commits. + (use "git status --ahead-behind" for details) + + nothing to commit, working tree clean + EOF + test_cmp expect actual +' + +test_expect_success 'status.compareBranches deduplicates when upstream and push are the same' ' + test_config -C test status.compareBranches "@{upstream} @{push}" && + git -C test checkout main && + git -C test status >actual && + cat >expect <<-EOF && + On branch main + Your branch is up to date with ${SQ}origin/main${SQ}. + + nothing to commit, working tree clean + EOF + test_cmp expect actual +' + +test_expect_success 'status.compareBranches with only upstream shows only upstream' ' + test_config -C test status.compareBranches "@{upstream}" && + git -C test checkout main && + git -C test status >actual && + cat >expect <<-EOF && + On branch main + Your branch is up to date with ${SQ}origin/main${SQ}. + + nothing to commit, working tree clean + EOF + test_cmp expect actual +' + +test_expect_success 'status.compareBranches with only push shows only push' ' + test_config -C test push.default current && + test_config -C test status.compareBranches "@{push}" && + git -C test checkout -b feature2 origin/main && + git -C test push origin HEAD && + (cd test && advance work) && + git -C test status >actual && + cat >expect <<-EOF && + On branch feature2 + Your branch is ahead of ${SQ}origin/feature2${SQ} by 1 commit. + (use "git push" to publish your local commits) + + nothing to commit, working tree clean + EOF + test_cmp expect actual +' + +test_expect_success 'status.compareBranches shows ahead of both upstream and push branch' ' + test_config -C test push.default current && + test_config -C test status.compareBranches "@{upstream} @{push}" && + git -C test checkout -b feature3 origin/main && + git -C test push origin HEAD && + (cd test && advance work) && + git -C test status >actual && + cat >expect <<-EOF && + On branch feature3 + Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit. + + Your branch is ahead of ${SQ}origin/feature3${SQ} by 1 commit. + (use "git push" to publish your local commits) + + nothing to commit, working tree clean + EOF + test_cmp expect actual +' + +test_expect_success 'checkout with status.compareBranches shows both branches' ' + test_config -C test push.default current && + test_config -C test status.compareBranches "@{upstream} @{push}" && + git -C test checkout feature3 >actual && + cat >expect <<-EOF && + Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit. + + Your branch is ahead of ${SQ}origin/feature3${SQ} by 1 commit. + (use "git push" to publish your local commits) + EOF + test_cmp expect actual +' + +test_expect_success 'setup for ahead of tracked but diverged from main' ' + ( + cd test && + git checkout -b feature4 origin/main && + advance work1 && + git checkout origin/main && + advance work2 && + git push origin HEAD:main && + git checkout feature4 && + advance work3 + ) +' + +test_expect_success 'status.compareBranches shows diverged and ahead' ' + test_config -C test push.default current && + test_config -C test status.compareBranches "@{upstream} @{push}" && + git -C test checkout feature4 && + git -C test branch --set-upstream-to origin/main && + git -C test push origin HEAD && + (cd test && advance work) && + git -C test status >actual && + cat >expect <<-EOF && + On branch feature4 + Your branch and ${SQ}origin/main${SQ} have diverged, + and have 3 and 1 different commits each, respectively. + (use "git pull" if you want to integrate the remote branch with yours) + + Your branch is ahead of ${SQ}origin/feature4${SQ} by 1 commit. + (use "git push" to publish your local commits) + + nothing to commit, working tree clean + EOF + test_cmp expect actual +' + +test_expect_success 'status --no-ahead-behind with status.compareBranches' ' + test_config -C test push.default current && + test_config -C test status.compareBranches "@{upstream} @{push}" && + git -C test checkout feature4 && + git -C test status --no-ahead-behind >actual && + cat >expect <<-EOF && + On branch feature4 + Your branch and ${SQ}origin/main${SQ} refer to different commits. + + Your branch and ${SQ}origin/feature4${SQ} refer to different commits. + (use "git status --ahead-behind" for details) + + nothing to commit, working tree clean + EOF + test_cmp expect actual +' + +test_expect_success 'setup upstream remote' ' + ( + cd test && + git remote add upstream ../. && + git fetch upstream + ) +' + +test_expect_success 'status.compareBranches with upstream and origin remotes' ' + test_config -C test push.default current && + test_config -C test remote.pushDefault origin && + test_config -C test status.compareBranches "@{upstream} @{push}" && + git -C test checkout -b feature5 upstream/main && + git -C test push origin && + (cd test && advance work) && + git -C test status >actual && + cat >expect <<-EOF && + On branch feature5 + Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit. + + Your branch is ahead of ${SQ}origin/feature5${SQ} by 1 commit. + (use "git push" to publish your local commits) + + nothing to commit, working tree clean + EOF + test_cmp expect actual +' + +test_expect_success 'status.compareBranches supports ordered upstream/push entries' ' + test_config -C test push.default current && + test_config -C test remote.pushDefault origin && + test_config -C test status.compareBranches "@{push} @{upstream}" && + git -C test checkout -b feature6 upstream/main && + git -C test push origin && + (cd test && advance work) && + git -C test status >actual && + cat >expect <<-EOF && + On branch feature6 + Your branch is ahead of ${SQ}origin/feature6${SQ} by 1 commit. + (use "git push" to publish your local commits) + + Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit. + + nothing to commit, working tree clean + EOF + test_cmp expect actual +' + +test_expect_success 'status.compareBranches deduplicates repeated specifiers' ' + test_config -C test push.default current && + test_config -C test remote.pushDefault origin && + test_config -C test status.compareBranches "@{push} @{upstream} @{push}" && + git -C test checkout -b feature7 upstream/main && + git -C test push origin && + (cd test && advance work) && + git -C test status >actual && + cat >expect <<-EOF && + On branch feature7 + Your branch is ahead of ${SQ}origin/feature7${SQ} by 1 commit. + (use "git push" to publish your local commits) + + Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit. + + nothing to commit, working tree clean + EOF + test_cmp expect actual +' + +test_expect_success 'status.compareBranches with diverged push branch' ' + test_config -C test push.default current && + test_config -C test remote.pushDefault origin && + test_config -C test status.compareBranches "@{upstream} @{push}" && + git -C test checkout -b feature8 upstream/main && + (cd test && advance work81) && + git -C test push origin && + git -C test reset --hard upstream/main && + (cd test && advance work82) && + git -C test status >actual && + cat >expect <<-EOF && + On branch feature8 + Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit. + + Your branch and ${SQ}origin/feature8${SQ} have diverged, + and have 1 and 1 different commits each, respectively. + + nothing to commit, working tree clean + EOF + test_cmp expect actual +' + +test_expect_success 'status.compareBranches shows up to date branches' ' + test_config -C test push.default current && + test_config -C test remote.pushDefault origin && + test_config -C test status.compareBranches "@{upstream} @{push}" && + git -C test checkout -b feature9 upstream/main && + git -C test push origin && + git -C test status >actual && + cat >expect <<-EOF && + On branch feature9 + Your branch is up to date with ${SQ}upstream/main${SQ}. + + Your branch is up to date with ${SQ}origin/feature9${SQ}. + + nothing to commit, working tree clean + EOF + test_cmp expect actual +' + +test_expect_success 'status --no-ahead-behind with status.compareBranches up to date' ' + test_config -C test push.default current && + test_config -C test remote.pushDefault origin && + test_config -C test status.compareBranches "@{upstream} @{push}" && + git -C test checkout feature9 >actual && + git -C test push origin && + git -C test status --no-ahead-behind >actual && + cat >expect <<-EOF && + On branch feature9 + Your branch is up to date with ${SQ}upstream/main${SQ}. + + Your branch is up to date with ${SQ}origin/feature9${SQ}. + + nothing to commit, working tree clean + EOF + test_cmp expect actual +' + +test_expect_success 'checkout with status.compareBranches shows up to date' ' + test_config -C test push.default current && + test_config -C test remote.pushDefault origin && + test_config -C test status.compareBranches "@{upstream} @{push}" && + git -C test checkout feature9 >actual && + cat >expect <<-EOF && + Your branch is up to date with ${SQ}upstream/main${SQ}. + + Your branch is up to date with ${SQ}origin/feature9${SQ}. + EOF + test_cmp expect actual +' + +test_expect_success 'status.compareBranches with upstream behind and push up to date' ' + test_config -C test push.default current && + test_config -C test remote.pushDefault origin && + test_config -C test status.compareBranches "@{upstream} @{push}" && + git -C test checkout -b ahead upstream/main && + (cd test && advance work) && + git -C test push upstream HEAD && + git -C test checkout -b feature10 upstream/main && + git -C test push origin && + git -C test branch --set-upstream-to upstream/ahead && + git -C test status >actual && + cat >expect <<-EOF && + On branch feature10 + Your branch is behind ${SQ}upstream/ahead${SQ} by 1 commit, and can be fast-forwarded. + (use "git pull" to update your local branch) + + Your branch is up to date with ${SQ}origin/feature10${SQ}. + + nothing to commit, working tree clean + EOF + test_cmp expect actual +' + +test_expect_success 'status.compareBranches with remapped push refspec' ' + test_config -C test remote.origin.push refs/heads/feature11:refs/heads/remapped && + test_config -C test status.compareBranches "@{upstream} @{push}" && + git -C test checkout -b feature11 origin/main && + git -C test push && + (cd test && advance work) && + git -C test status >actual && + cat >expect <<-EOF && + On branch feature11 + Your branch is ahead of ${SQ}origin/main${SQ} by 1 commit. + + Your branch is ahead of ${SQ}origin/remapped${SQ} by 1 commit. + (use "git push" to publish your local commits) + + nothing to commit, working tree clean + EOF + test_cmp expect actual +' + +test_expect_success 'status.compareBranches with remapped push and upstream remote' ' + test_config -C test remote.pushDefault origin && + test_config -C test remote.origin.push refs/heads/feature12:refs/heads/remapped && + test_config -C test status.compareBranches "@{upstream} @{push}" && + git -C test checkout -b feature12 upstream/main && + git -C test push origin && + (cd test && advance work) && + git -C test status >actual && + cat >expect <<-EOF && + On branch feature12 + Your branch is ahead of ${SQ}upstream/main${SQ} by 1 commit. + + Your branch is ahead of ${SQ}origin/remapped${SQ} by 1 commit. + (use "git push" to publish your local commits) + + nothing to commit, working tree clean + EOF + test_cmp expect actual +' + test_done
diff --git a/t/t6101-rev-parse-parents.sh b/t/t6101-rev-parse-parents.sh index 5f55ab9..7281889 100755 --- a/t/t6101-rev-parse-parents.sh +++ b/t/t6101-rev-parse-parents.sh
@@ -39,7 +39,8 @@ ' test_expect_success 'start is valid' ' - git rev-parse start | grep "^$OID_REGEX$" + git rev-parse start >actual && + test_grep "^$OID_REGEX$" actual ' test_expect_success 'start^0' '
diff --git a/t/t6112-rev-list-filters-objects.sh b/t/t6112-rev-list-filters-objects.sh index 0387f35..39211ef 100755 --- a/t/t6112-rev-list-filters-objects.sh +++ b/t/t6112-rev-list-filters-objects.sh
@@ -483,10 +483,6 @@ "must escape char in sub-filter-spec: .\~." ' -test_expect_success 'validate err msg for "combine:<valid-filter>+"' ' - expect_invalid_filter_spec combine:tree:2+ "expected .tree:<depth>." -' - test_expect_success 'combine:... with edge-case hex digits: Ff Aa 0 9' ' git -C r3 rev-list --objects --filter="combine:tree:2+bl%6Fb:n%6fne" \ HEAD >actual &&
diff --git a/t/t6403-merge-file.sh b/t/t6403-merge-file.sh index 06ab4d7..801284c 100755 --- a/t/t6403-merge-file.sh +++ b/t/t6403-merge-file.sh
@@ -428,6 +428,42 @@ test_cmp expect actual ' +test_expect_success 'merge.conflictStyle honored outside repo' ' + test_config_global merge.conflictStyle diff3 && + cat >nongit-base <<-\EOF && + line1 + original + line3 + EOF + cat >nongit-ours <<-\EOF && + line1 + ours + line3 + EOF + cat >nongit-theirs <<-\EOF && + line1 + theirs + line3 + EOF + cat >expect <<-\EOF && + line1 + <<<<<<< ours + ours + ||||||| base + original + ======= + theirs + >>>>>>> theirs + line3 + EOF + test_must_fail nongit git merge-file -p \ + -L ours -L base -L theirs \ + "$PWD/nongit-ours" \ + "$PWD/nongit-base" \ + "$PWD/nongit-theirs" >actual && + test_cmp expect actual +' + test_expect_success 'marker size' ' cat >expect <<-\EOF && Dominus regit me, @@ -506,6 +542,15 @@ grep "not a git repository" err ' +test_expect_success 'run in a linked worktree with --object-id' ' + empty="$(test_oid empty_blob)" && + git worktree add work && + git -C work merge-file --object-id $empty $empty $empty >actual && + git worktree remove work && + git merge-file --object-id $empty $empty $empty >expected && + test_cmp actual expected +' + test_expect_success 'merging C files with "myers" diff algorithm creates some spurious conflicts' ' cat >expect.c <<-\EOF && int g(size_t u)
diff --git a/t/t6422-merge-rename-corner-cases.sh b/t/t6422-merge-rename-corner-cases.sh index f14c0fb..e18d5a2 100755 --- a/t/t6422-merge-rename-corner-cases.sh +++ b/t/t6422-merge-rename-corner-cases.sh
@@ -1439,4 +1439,90 @@ ) ' +# Testcase preliminary submodule/directory conflict and submodule rename +# Commit O: <empty, or additional irrelevant stuff> +# Commit A1: introduce "folder" (as a tree) +# Commit B1: introduce "folder" (as a submodule) +# Commit A2: merge B1 into A1, but keep folder as a tree +# Commit B2: merge A1 into B1, but keep folder as a submodule +# Merge A2 & B2 +test_setup_submodule_directory_preliminary_conflict () { + git init submodule_directory_preliminary_conflict && + ( + cd submodule_directory_preliminary_conflict && + + # Trying to do the A2 and B2 merges above is slightly more + # challenging with a local submodule (because checking out + # another commit has the submodule in the way). Instead, + # first create the commits with the wrong parents but right + # trees, in the order A1, A2, B1, B2... + # + # Then go back and create new A2 & B2 with the correct + # parents and the same trees. + + git commit --allow-empty -m orig && + + git branch A && + git branch B && + + git checkout B && + mkdir folder && + echo A>folder/A && + echo B>folder/B && + echo C>folder/C && + echo D>folder/D && + echo E>folder/E && + git add folder && + git commit -m B1 && + + git commit --allow-empty -m B2 && + + git checkout A && + git init folder && + ( + cd folder && + >Z && + >Y && + git add Z Y && + git commit -m "original submodule commit" + ) && + git add folder && + git commit -m A1 && + + git commit --allow-empty -m A2 && + + NewA2=$(git commit-tree -p A^ -p B^ -m "Merge B into A" A^{tree}) && + NewB2=$(git commit-tree -p B^ -p A^ -m "Merge A into B" B^{tree}) && + git update-ref refs/heads/A $NewA2 && + git update-ref refs/heads/B $NewB2 + ) +} + +test_expect_success 'submodule/directory preliminary conflict' ' + test_setup_submodule_directory_preliminary_conflict && + ( + cd submodule_directory_preliminary_conflict && + + git checkout A^0 && + + test_expect_code 1 git merge B^0 && + + # Make sure the index has the right number of entries + git ls-files -s >actual && + test_line_count = 2 actual && + + # The "folder" as directory should have been resolved away + # as part of the merge. The "folder" as submodule got + # renamed to "folder~Temporary merge branch 2" in the + # virtual merge base, resulting in a + # "folder~Temporary merge branch 2" -> "folder" + # rename in the outermerge for the submodule, which then + # becomes part of a rename/delete conflict (because "folder" + # as a submodule was deleted in A2). + submod=$(git rev-parse A:folder) && + printf "160000 $submod 1\tfolder\n160000 $submod 2\tfolder\n" >expect && + test_cmp expect actual + ) +' + test_done
diff --git a/t/t6423-merge-rename-directories.sh b/t/t6423-merge-rename-directories.sh index 533ac85..53535a8 100755 --- a/t/t6423-merge-rename-directories.sh +++ b/t/t6423-merge-rename-directories.sh
@@ -5158,13 +5158,18 @@ git switch B && git rm dir/subdir/file && mkdir dir && - ln -s /dev/null dir/subdir && + if test_have_prereq MINGW + then + cmd //c 'mklink dir\subdir NUL' + else + ln -s /dev/null dir/subdir + fi && git add . && git commit -m "B" ) } -test_expect_success '12m: Change parent of renamed-dir to symlink on other side' ' +test_expect_success SYMLINKS '12m: Change parent of renamed-dir to symlink on other side' ' test_setup_12m && ( cd 12m &&
diff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh index 0f39ed0..15dd2d9 100755 --- a/t/t6429-merge-sequence-rename-caching.sh +++ b/t/t6429-merge-sequence-rename-caching.sh
@@ -11,14 +11,13 @@ # sure that we are triggering rename caching rather than rename # bypassing. # -# NOTE 2: this testfile uses 'test-tool fast-rebase' instead of either -# cherry-pick or rebase. sequencer.c is only superficially -# integrated with merge-ort; it calls merge_switch_to_result() -# after EACH merge, which updates the index and working copy AND -# throws away the cached results (because merge_switch_to_result() -# is only supposed to be called at the end of the sequence). -# Integrating them more deeply is a big task, so for now the tests -# use 'test-tool fast-rebase'. +# NOTE 2: this testfile uses replay instead of either cherry-pick or rebase. +# sequencer.c is only superficially integrated with merge-ort; it +# calls merge_switch_to_result() after EACH merge, which updates the +# index and working copy AND throws away the cached results (because +# merge_switch_to_result() is only supposed to be called at the end +# of the sequence). Integrating them more deeply is a big task, so +# for now the tests use 'git replay'. # @@ -769,4 +768,82 @@ ) ' +# +# In the following testcase: +# Base: olddir/{valuesX_1, valuesY_1, valuesZ_1} +# other/content +# Upstream: rename olddir/valuesX_1 -> newdir/valuesX_2 +# Topic_1: modify olddir/valuesX_1 -> olddir/valuesX_3 +# Topic_2: modify olddir/valuesY, +# modify other/content +# Expected Pick1: olddir/{valuesY, valuesZ}, newdir/valuesX, other/content +# Expected Pick2: olddir/{valuesY, valuesZ}, newdir/valuesX, other/content +# +# This testcase presents no problems for git traditionally, but the fact that +# olddir/valuesX -> newdir/valuesX +# gets cached after the first pick presents a problem for the second commit to +# be replayed, because it appears to be an irrelevant rename, so the trivial +# directory resolution will resolve newdir/ without recursing into it, giving +# us no way to apply the cached rename to anything. +# +test_expect_success 'rename a file, use it on first pick, but irrelevant on second' ' + git init rename_a_file_use_it_once_irrelevant_on_second && + ( + cd rename_a_file_use_it_once_irrelevant_on_second && + + mkdir olddir/ other/ && + test_seq 3 8 >olddir/valuesX && + test_seq 3 8 >olddir/valuesY && + test_seq 3 8 >olddir/valuesZ && + printf "%s\n" A B C D E F G >other/content && + git add olddir other && + git commit -m orig && + + git branch upstream && + git branch topic && + + git switch upstream && + test_seq 1 8 >olddir/valuesX && + git add olddir && + mkdir newdir && + git mv olddir/valuesX newdir && + git commit -m "Renamed (and modified) olddir/valuesX into newdir/" && + + git switch topic && + + test_seq 3 10 >olddir/valuesX && + git add olddir && + git commit -m A && + + test_seq 1 8 >olddir/valuesY && + printf "%s\n" A B C D E F G H I >other/content && + git add olddir/valuesY other && + git commit -m B && + + # + # Actual testing; mostly we want to verify that we do not hit + # git: merge-ort.c:3032: process_renames: Assertion `newinfo && !newinfo->merged.clean` failed. + # + + git switch upstream && + git config merge.directoryRenames true && + + git replay --onto HEAD upstream~1..topic >out && + + # + # ...but we may as well check that the replay gave us a reasonable result + # + + git update-ref --stdin <out && + git checkout topic && + + git ls-files >tracked && + test_line_count = 4 tracked && + test_path_is_file newdir/valuesX && + test_path_is_file olddir/valuesY && + test_path_is_file olddir/valuesZ && + test_path_is_file other/content + ) +' + test_done
diff --git a/t/t6500-gc.sh b/t/t6500-gc.sh index bef472c..ea9aaad 100755 --- a/t/t6500-gc.sh +++ b/t/t6500-gc.sh
@@ -11,6 +11,7 @@ # behavior, make sure we always pack everything to one pack by # default git config gc.bigPackThreshold 2g && + git config set --global maintenance.strategy gc && test_oid_init '
diff --git a/t/t6600-test-reach.sh b/t/t6600-test-reach.sh index 6638d1a..2613075 100755 --- a/t/t6600-test-reach.sh +++ b/t/t6600-test-reach.sh
@@ -762,4 +762,79 @@ --sort=refname --sort=-is-base:commit-2-3 ' +test_expect_success 'rev-list --maximal-only (all positive)' ' + # Only one maximal. + cat >input <<-\EOF && + refs/heads/commit-1-1 + refs/heads/commit-4-2 + refs/heads/commit-4-4 + refs/heads/commit-8-4 + EOF + + cat >expect <<-EOF && + $(git rev-parse refs/heads/commit-8-4) + EOF + run_all_modes git rev-list --maximal-only --stdin && + + # All maximal. + cat >input <<-\EOF && + refs/heads/commit-5-2 + refs/heads/commit-4-3 + refs/heads/commit-3-4 + refs/heads/commit-2-5 + EOF + + cat >expect <<-EOF && + $(git rev-parse refs/heads/commit-5-2) + $(git rev-parse refs/heads/commit-4-3) + $(git rev-parse refs/heads/commit-3-4) + $(git rev-parse refs/heads/commit-2-5) + EOF + run_all_modes git rev-list --maximal-only --stdin && + + # Mix of both. + cat >input <<-\EOF && + refs/heads/commit-5-2 + refs/heads/commit-3-2 + refs/heads/commit-2-5 + EOF + + cat >expect <<-EOF && + $(git rev-parse refs/heads/commit-5-2) + $(git rev-parse refs/heads/commit-2-5) + EOF + run_all_modes git rev-list --maximal-only --stdin +' + +test_expect_success 'rev-list --maximal-only (range)' ' + cat >input <<-\EOF && + refs/heads/commit-1-1 + refs/heads/commit-2-5 + refs/heads/commit-6-4 + ^refs/heads/commit-4-5 + EOF + + cat >expect <<-EOF && + $(git rev-parse refs/heads/commit-6-4) + EOF + run_all_modes git rev-list --maximal-only --stdin && + + # first-parent changes reachability: the first parent + # reduces the second coordinate to 1 before reducing the + # first coordinate. + cat >input <<-\EOF && + refs/heads/commit-1-1 + refs/heads/commit-2-5 + refs/heads/commit-6-4 + ^refs/heads/commit-4-5 + EOF + + cat >expect <<-EOF && + $(git rev-parse refs/heads/commit-6-4) + $(git rev-parse refs/heads/commit-2-5) + EOF + run_all_modes git rev-list --maximal-only --stdin \ + --first-parent --exclude-first-parent-only +' + test_done
diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index 5ab4d41..c475769 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh
@@ -92,8 +92,8 @@ test_expect_success 'test that the file was renamed' ' test D = "$(git show HEAD:doh --)" && - ! test -f D.t && - test -f doh && + test_path_is_missing D.t && + test_path_is_file doh && test D = "$(cat doh)" ' @@ -103,10 +103,10 @@ test_expect_success 'test that the directory was renamed' ' test dir/D = "$(git show HEAD:diroh/D.t --)" && - ! test -d dir && - test -d diroh && - ! test -d diroh/dir && - test -f diroh/D.t && + test_path_is_missing dir && + test_path_is_dir diroh && + test_path_is_missing diroh/dir && + test_path_is_file diroh/D.t && test dir/D = "$(cat diroh/D.t)" '
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 1083563..ce2ff2a 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh
@@ -2293,24 +2293,26 @@ # don't recurse down to tags for trees or blobs pointed to by *those* # commits. test_expect_success 'Does --[no-]contains stop at commits? Yes!' ' - cd no-contains && - blob=$(git rev-parse v0.3:v0.3.t) && - tree=$(git rev-parse v0.3^{tree}) && - git tag tag-blob $blob && - git tag tag-tree $tree && - git tag --contains v0.3 >actual && - cat >expected <<-\EOF && - v0.3 - v0.4 - v0.5 - EOF - test_cmp expected actual && - git tag --no-contains v0.3 >actual && - cat >expected <<-\EOF && - v0.1 - v0.2 - EOF - test_cmp expected actual + ( + cd no-contains && + blob=$(git rev-parse v0.3:v0.3.t) && + tree=$(git rev-parse v0.3^{tree}) && + git tag tag-blob $blob && + git tag tag-tree $tree && + git tag --contains v0.3 >actual && + cat >expected <<-\EOF && + v0.3 + v0.4 + v0.5 + EOF + test_cmp expected actual && + git tag --no-contains v0.3 >actual && + cat >expected <<-\EOF && + v0.1 + v0.2 + EOF + test_cmp expected actual + ) ' test_expect_success 'If tag is created then tag message file is unlinked' ' @@ -2332,4 +2334,24 @@ test_path_exists .git/TAG_EDITMSG ' +test_expect_success 'annotated tag version sort' ' + git tag -a -m "sample 1.0" vsample-1.0 && + git tag -a -m "sample 2.0" vsample-2.0 && + git tag -a -m "sample 10.0" vsample-10.0 && + cat >expect <<-EOF && + vsample-1.0 + vsample-2.0 + vsample-10.0 + EOF + + git tag --list --sort=version:tag vsample-\* >actual && + test_cmp expect actual && + + # Ensure that we also handle this case alright in the case we have the + # peeled values cached e.g. via the packed-refs file. + git pack-refs --all && + git tag --list --sort=version:tag vsample-\* && + test_cmp expect actual +' + test_done
diff --git a/t/t7101-reset-empty-subdirs.sh b/t/t7101-reset-empty-subdirs.sh index 33d5d5b..d1d3e23 100755 --- a/t/t7101-reset-empty-subdirs.sh +++ b/t/t7101-reset-empty-subdirs.sh
@@ -34,32 +34,32 @@ ' test_expect_success 'checking initial files exist after rewind' ' - test -d path0 && - test -f path0/COPYING + test_path_is_dir path0 && + test_path_is_file path0/COPYING ' test_expect_success 'checking lack of path1/path2/COPYING' ' - ! test -f path1/path2/COPYING + test_path_is_missing path1/path2/COPYING ' test_expect_success 'checking lack of path1/COPYING' ' - ! test -f path1/COPYING + test_path_is_missing path1/COPYING ' test_expect_success 'checking lack of COPYING' ' - ! test -f COPYING + test_path_is_missing COPYING ' -test_expect_success 'checking checking lack of path1/COPYING-TOO' ' - ! test -f path0/COPYING-TOO +test_expect_success 'checking lack of path0/COPYING-TOO' ' + test_path_is_missing path0/COPYING-TOO ' test_expect_success 'checking lack of path1/path2' ' - ! test -d path1/path2 + test_path_is_missing path1/path2 ' test_expect_success 'checking lack of path1' ' - ! test -d path1 + test_path_is_missing path1 ' test_done
diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index fd3e7e3..65fcfae 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh
@@ -48,6 +48,25 @@ git submodule deinit --all ' +test_expect_success 'submodule add with incomplete .gitmodules' ' + test_when_finished "rm -f expect actual" && + test_when_finished "git config remove-section submodule.one" && + test_when_finished "git rm -f one .gitmodules" && + git init one && + git -C one commit --allow-empty -m one-initial && + git config -f .gitmodules submodule.one.ignore all && + + git submodule add ./one && + + for var in ignore path url + do + git config -f .gitmodules --get "submodule.one.$var" || + return 1 + done >actual && + test_write_lines all one ./one >expect && + test_cmp expect actual +' + test_expect_success 'setup - initial commit' ' >t && git add t && @@ -407,6 +426,31 @@ test_grep toplevel output.err ' +test_expect_success 'submodule add of a different algorithm fails' ' + git init --object-format=sha256 sha256 && + ( + cd sha256 && + test_commit abc && + git init --object-format=sha1 submodule && + test_commit -C submodule def && + test_must_fail git submodule add "$submodurl" submodule 2>err && + test_grep "cannot add a submodule of a different hash algorithm" err && + git ls-files --stage >entries && + test_grep ! ^160000 entries + ) && + git init --object-format=sha1 sha1 && + ( + cd sha1 && + test_commit abc && + git init --object-format=sha256 submodule && + test_commit -C submodule def && + test_must_fail git submodule add "$submodurl" submodule 2>err && + test_grep "cannot add a submodule of a different hash algorithm" err && + git ls-files --stage >entries && + test_grep ! ^160000 entries + ) +' + test_expect_success 'setup - add an example entry to .gitmodules' ' git config --file=.gitmodules submodule.example.url git://example.com/init.git '
diff --git a/t/t7425-submodule-gitdir-path-extension.sh b/t/t7425-submodule-gitdir-path-extension.sh new file mode 100755 index 0000000..ea86ecf --- /dev/null +++ b/t/t7425-submodule-gitdir-path-extension.sh
@@ -0,0 +1,528 @@ +#!/bin/sh + +test_description='submodulePathConfig extension works as expected' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-verify-submodule-gitdir-path.sh + +test_expect_success 'setup: allow file protocol' ' + git config --global protocol.file.allow always +' + +test_expect_success 'create repo with mixed extension submodules' ' + git init -b main legacy-sub && + test_commit -C legacy-sub legacy-initial && + legacy_rev=$(git -C legacy-sub rev-parse HEAD) && + + git init -b main new-sub && + test_commit -C new-sub new-initial && + new_rev=$(git -C new-sub rev-parse HEAD) && + + git init -b main main && + ( + cd main && + git submodule add ../legacy-sub legacy && + test_commit legacy-sub && + + # trigger the "die_path_inside_submodule" check + test_must_fail git submodule add ../new-sub "legacy/nested" && + + git config core.repositoryformatversion 1 && + git config extensions.submodulePathConfig true && + + git submodule add ../new-sub "New Sub" && + test_commit new && + + # retrigger the "die_path_inside_submodule" check with encoding + test_must_fail git submodule add ../new-sub "New Sub/nested2" + ) +' + +test_expect_success 'verify new submodule gitdir config' ' + git -C main config submodule."New Sub".gitdir >actual && + echo ".git/modules/New Sub" >expect && + test_cmp expect actual && + verify_submodule_gitdir_path main "New Sub" "modules/New Sub" +' + +test_expect_success 'manual add and verify legacy submodule gitdir config' ' + # the legacy module should not contain a gitdir config, because it + # was added before the extension was enabled. Add and test it. + test_must_fail git -C main config submodule.legacy.gitdir && + git -C main config submodule.legacy.gitdir .git/modules/legacy && + git -C main config submodule.legacy.gitdir >actual && + echo ".git/modules/legacy" >expect && + test_cmp expect actual && + verify_submodule_gitdir_path main "legacy" "modules/legacy" +' + +test_expect_success 'gitdir config path is relative for both absolute and relative urls' ' + test_when_finished "rm -rf relative-cfg-path-test" && + git init -b main relative-cfg-path-test && + ( + cd relative-cfg-path-test && + git config core.repositoryformatversion 1 && + git config extensions.submodulePathConfig true && + + # Test with absolute URL + git submodule add "$TRASH_DIRECTORY/new-sub" sub-abs && + git config submodule.sub-abs.gitdir >actual && + echo ".git/modules/sub-abs" >expect && + test_cmp expect actual && + + # Test with relative URL + git submodule add ../new-sub sub-rel && + git config submodule.sub-rel.gitdir >actual && + echo ".git/modules/sub-rel" >expect && + test_cmp expect actual + ) +' + +test_expect_success 'clone from repo with both legacy and new-style submodules' ' + git clone --recurse-submodules main cloned-non-extension && + ( + cd cloned-non-extension && + + test_path_is_dir .git/modules/legacy && + test_path_is_dir .git/modules/"New Sub" && + + test_must_fail git config submodule.legacy.gitdir && + test_must_fail git config submodule."New Sub".gitdir && + + git submodule status >list && + test_grep "$legacy_rev legacy" list && + test_grep "$new_rev New Sub" list + ) && + + git clone -c extensions.submodulePathConfig=true --recurse-submodules main cloned-extension && + ( + cd cloned-extension && + + test_path_is_dir .git/modules/legacy && + test_path_is_dir ".git/modules/New Sub" && + + git config submodule.legacy.gitdir && + git config submodule."New Sub".gitdir && + + git submodule status >list && + test_grep "$legacy_rev legacy" list && + test_grep "$new_rev New Sub" list + ) +' + +test_expect_success 'commit and push changes to encoded submodules' ' + git -C legacy-sub config receive.denyCurrentBranch updateInstead && + git -C new-sub config receive.denyCurrentBranch updateInstead && + git -C main config receive.denyCurrentBranch updateInstead && + ( + cd cloned-extension && + + git -C legacy switch --track -C main origin/main && + test_commit -C legacy second-commit && + git -C legacy push && + + git -C "New Sub" switch --track -C main origin/main && + test_commit -C "New Sub" second-commit && + git -C "New Sub" push && + + # Stage and commit submodule changes in superproject + git switch --track -C main origin/main && + git add legacy "New Sub" && + git commit -m "update submodules" && + + # push superproject commit to main repo + git push + ) && + + # update expected legacy & new submodule checksums + legacy_rev=$(git -C legacy-sub rev-parse HEAD) && + new_rev=$(git -C new-sub rev-parse HEAD) +' + +test_expect_success 'fetch mixed submodule changes and verify updates' ' + ( + cd main && + + # only update submodules because superproject was + # pushed into at the end of last test + git submodule update --init --recursive && + + test_path_is_dir .git/modules/legacy && + test_path_is_dir ".git/modules/New Sub" && + + # Verify both submodules are at the expected commits + git submodule status >list && + test_grep "$legacy_rev legacy" list && + test_grep "$new_rev New Sub" list + ) +' + +test_expect_success '`git init` respects init.defaultSubmodulePathConfig' ' + test_config_global init.defaultSubmodulePathConfig true && + git init repo-init && + git -C repo-init config extensions.submodulePathConfig >actual && + echo true >expect && + test_cmp expect actual && + # create a submodule and check gitdir + ( + cd repo-init && + git init -b main sub && + test_commit -C sub sub-initial && + git submodule add ./sub sub && + git config submodule.sub.gitdir >actual && + echo ".git/modules/sub" >expect && + test_cmp expect actual + ) +' + +test_expect_success '`git init` does not set extension by default' ' + git init upstream && + test_commit -C upstream initial && + test_must_fail git -C upstream config extensions.submodulePathConfig && + # create a pair of submodules and check gitdir is not created + git init -b main sub && + test_commit -C sub sub-initial && + ( + cd upstream && + git submodule add ../sub sub1 && + test_path_is_dir .git/modules/sub1 && + test_must_fail git config submodule.sub1.gitdir && + git submodule add ../sub sub2 && + test_path_is_dir .git/modules/sub2 && + test_must_fail git config submodule.sub2.gitdir && + git commit -m "Add submodules" + ) +' + +test_expect_success '`git clone` does not set extension by default' ' + test_when_finished "rm -rf repo-clone-no-ext" && + git clone upstream repo-clone-no-ext && + ( + cd repo-clone-no-ext && + + test_must_fail git config extensions.submodulePathConfig && + test_path_is_missing .git/modules/sub1 && + test_path_is_missing .git/modules/sub2 && + + # create a submodule and check gitdir is not created + git submodule add ../sub sub3 && + test_must_fail git config submodule.sub3.gitdir + ) +' + +test_expect_success '`git clone --recurse-submodules` does not set extension by default' ' + test_when_finished "rm -rf repo-clone-no-ext" && + git clone --recurse-submodules upstream repo-clone-no-ext && + ( + cd repo-clone-no-ext && + + # verify that that submodules do not have gitdir set + test_must_fail git config extensions.submodulePathConfig && + test_path_is_dir .git/modules/sub1 && + test_must_fail git config submodule.sub1.gitdir && + test_path_is_dir .git/modules/sub2 && + test_must_fail git config submodule.sub2.gitdir && + + # create another submodule and check that gitdir is not created + git submodule add ../sub sub3 && + test_path_is_dir .git/modules/sub3 && + test_must_fail git config submodule.sub3.gitdir + ) + +' + +test_expect_success '`git clone` respects init.defaultSubmodulePathConfig' ' + test_when_finished "rm -rf repo-clone" && + test_config_global init.defaultSubmodulePathConfig true && + git clone upstream repo-clone && + ( + cd repo-clone && + + # verify new repo extension is inherited from global config + git config extensions.submodulePathConfig >actual && + echo true >expect && + test_cmp expect actual && + + # new submodule has a gitdir config + git submodule add ../sub sub && + test_path_is_dir .git/modules/sub && + git config submodule.sub.gitdir >actual && + echo ".git/modules/sub" >expect && + test_cmp expect actual + ) +' + +test_expect_success '`git clone --recurse-submodules` respects init.defaultSubmodulePathConfig' ' + test_when_finished "rm -rf repo-clone-recursive" && + test_config_global init.defaultSubmodulePathConfig true && + git clone --recurse-submodules upstream repo-clone-recursive && + ( + cd repo-clone-recursive && + + # verify new repo extension is inherited from global config + git config extensions.submodulePathConfig >actual && + echo true >expect && + test_cmp expect actual && + + # previous submodules should exist + git config submodule.sub1.gitdir && + git config submodule.sub2.gitdir && + test_path_is_dir .git/modules/sub1 && + test_path_is_dir .git/modules/sub2 && + + # create another submodule and check that gitdir is created + git submodule add ../sub new-sub && + test_path_is_dir .git/modules/new-sub && + git config submodule.new-sub.gitdir >actual && + echo ".git/modules/new-sub" >expect && + test_cmp expect actual + ) +' + +test_expect_success 'submodule--helper migrates legacy modules' ' + ( + cd upstream && + + # previous submodules exist and were not migrated yet + test_must_fail git config submodule.sub1.gitdir && + test_must_fail git config submodule.sub2.gitdir && + test_path_is_dir .git/modules/sub1 && + test_path_is_dir .git/modules/sub2 && + + # run migration + git submodule--helper migrate-gitdir-configs && + + # test that migration worked + git config submodule.sub1.gitdir >actual && + echo ".git/modules/sub1" >expect && + test_cmp expect actual && + git config submodule.sub2.gitdir >actual && + echo ".git/modules/sub2" >expect && + test_cmp expect actual && + + # repository extension is enabled after migration + git config extensions.submodulePathConfig >actual && + echo "true" >expect && + test_cmp expect actual + ) +' + +test_expect_success '`git clone --recurse-submodules` works after migration' ' + test_when_finished "rm -rf repo-clone-recursive" && + + # test with extension disabled after the upstream repo was migrated + git clone --recurse-submodules upstream repo-clone-recursive && + ( + cd repo-clone-recursive && + + # init.defaultSubmodulePathConfig was disabled before clone, so + # the repo extension config should also be off, the migration ignored + test_must_fail git config extensions.submodulePathConfig && + + # modules should look like there was no migration done + test_must_fail git config submodule.sub1.gitdir && + test_must_fail git config submodule.sub2.gitdir && + test_path_is_dir .git/modules/sub1 && + test_path_is_dir .git/modules/sub2 + ) && + rm -rf repo-clone-recursive && + + # enable the extension, then retry the clone + test_config_global init.defaultSubmodulePathConfig true && + git clone --recurse-submodules upstream repo-clone-recursive && + ( + cd repo-clone-recursive && + + # repository extension is enabled + git config extensions.submodulePathConfig >actual && + echo "true" >expect && + test_cmp expect actual && + + # gitdir configs exist for submodules + git config submodule.sub1.gitdir && + git config submodule.sub2.gitdir && + test_path_is_dir .git/modules/sub1 && + test_path_is_dir .git/modules/sub2 + ) +' + +test_expect_success 'setup submodules with nested git dirs' ' + git init nested && + test_commit -C nested nested && + ( + cd nested && + cat >.gitmodules <<-EOF && + [submodule "hippo"] + url = . + path = thing1 + [submodule "hippo/hooks"] + url = . + path = thing2 + EOF + git clone . thing1 && + git clone . thing2 && + git add .gitmodules thing1 thing2 && + test_tick && + git commit -m nested + ) +' + +test_expect_success 'git dirs of encoded sibling submodules must not be nested' ' + git clone -c extensions.submodulePathConfig=true --recurse-submodules nested clone_nested && + + verify_submodule_gitdir_path clone_nested hippo modules/hippo && + git -C clone_nested config submodule.hippo.gitdir >actual && + test_grep "\.git/modules/hippo$" actual && + + verify_submodule_gitdir_path clone_nested hippo/hooks modules/hippo%2fhooks && + git -C clone_nested config submodule.hippo/hooks.gitdir >actual && + test_grep "\.git/modules/hippo%2fhooks$" actual +' + +test_expect_success 'submodule git dir nesting detection must work with parallel cloning' ' + git clone -c extensions.submodulePathConfig=true --recurse-submodules --jobs=2 nested clone_parallel && + + verify_submodule_gitdir_path clone_parallel hippo modules/hippo && + git -C clone_nested config submodule.hippo.gitdir >actual && + test_grep "\.git/modules/hippo$" actual && + + verify_submodule_gitdir_path clone_parallel hippo/hooks modules/hippo%2fhooks && + git -C clone_nested config submodule.hippo/hooks.gitdir >actual && + test_grep "\.git/modules/hippo%2fhooks$" actual +' + +test_expect_success 'disabling extensions.submodulePathConfig prevents nested submodules' ' + ( + cd clone_nested && + # disable extension and verify failure + git config --replace-all extensions.submodulePathConfig false && + test_must_fail git submodule add ./thing2 hippo/foobar && + # re-enable extension and verify it works + git config --replace-all extensions.submodulePathConfig true && + git submodule add ./thing2 hippo/foobar + ) +' + +test_expect_success CASE_INSENSITIVE_FS 'verify case-folding conflicts are correctly encoded' ' + git clone -c extensions.submodulePathConfig=true main cloned-folding && + ( + cd cloned-folding && + + # conflict: the "folding" gitdir will already be taken + git submodule add ../new-sub "folding" && + test_commit lowercase && + git submodule add ../new-sub "FoldinG" && + test_commit uppercase && + + # conflict: the "foo" gitdir will already be taken + git submodule add ../new-sub "FOO" && + test_commit uppercase-foo && + git submodule add ../new-sub "foo" && + test_commit lowercase-foo && + + # create a multi conflict between foobar, fooBar and foo%42ar + # the "foo" gitdir will already be taken + git submodule add ../new-sub "foobar" && + test_commit lowercase-foobar && + git submodule add ../new-sub "foo%42ar" && + test_commit encoded-foo%42ar && + git submodule add ../new-sub "fooBar" && + test_commit mixed-fooBar + ) && + verify_submodule_gitdir_path cloned-folding "folding" "modules/folding" && + verify_submodule_gitdir_path cloned-folding "FoldinG" "modules/%46oldin%47" && + verify_submodule_gitdir_path cloned-folding "FOO" "modules/FOO" && + verify_submodule_gitdir_path cloned-folding "foo" "modules/foo0" && + verify_submodule_gitdir_path cloned-folding "foobar" "modules/foobar" && + verify_submodule_gitdir_path cloned-folding "foo%42ar" "modules/foo%42ar" && + verify_submodule_gitdir_path cloned-folding "fooBar" "modules/fooBar0" +' + +test_expect_success CASE_INSENSITIVE_FS 'verify hashing conflict resolution as a last resort' ' + git clone -c extensions.submodulePathConfig=true main cloned-hash && + ( + cd cloned-hash && + + # conflict: add all submodule conflicting variants until we reach the + # final hashing conflict resolution for submodule "foo" + git submodule add ../new-sub "foo" && + git submodule add ../new-sub "foo0" && + git submodule add ../new-sub "foo1" && + git submodule add ../new-sub "foo2" && + git submodule add ../new-sub "foo3" && + git submodule add ../new-sub "foo4" && + git submodule add ../new-sub "foo5" && + git submodule add ../new-sub "foo6" && + git submodule add ../new-sub "foo7" && + git submodule add ../new-sub "foo8" && + git submodule add ../new-sub "foo9" && + git submodule add ../new-sub "%46oo" && + git submodule add ../new-sub "%46oo0" && + git submodule add ../new-sub "%46oo1" && + git submodule add ../new-sub "%46oo2" && + git submodule add ../new-sub "%46oo3" && + git submodule add ../new-sub "%46oo4" && + git submodule add ../new-sub "%46oo5" && + git submodule add ../new-sub "%46oo6" && + git submodule add ../new-sub "%46oo7" && + git submodule add ../new-sub "%46oo8" && + git submodule add ../new-sub "%46oo9" && + test_commit add-foo-variants && + git submodule add ../new-sub "Foo" && + test_commit add-uppercase-foo + ) && + verify_submodule_gitdir_path cloned-hash "foo" "modules/foo" && + verify_submodule_gitdir_path cloned-hash "foo0" "modules/foo0" && + verify_submodule_gitdir_path cloned-hash "foo1" "modules/foo1" && + verify_submodule_gitdir_path cloned-hash "foo2" "modules/foo2" && + verify_submodule_gitdir_path cloned-hash "foo3" "modules/foo3" && + verify_submodule_gitdir_path cloned-hash "foo4" "modules/foo4" && + verify_submodule_gitdir_path cloned-hash "foo5" "modules/foo5" && + verify_submodule_gitdir_path cloned-hash "foo6" "modules/foo6" && + verify_submodule_gitdir_path cloned-hash "foo7" "modules/foo7" && + verify_submodule_gitdir_path cloned-hash "foo8" "modules/foo8" && + verify_submodule_gitdir_path cloned-hash "foo9" "modules/foo9" && + verify_submodule_gitdir_path cloned-hash "%46oo" "modules/%46oo" && + verify_submodule_gitdir_path cloned-hash "%46oo0" "modules/%46oo0" && + verify_submodule_gitdir_path cloned-hash "%46oo1" "modules/%46oo1" && + verify_submodule_gitdir_path cloned-hash "%46oo2" "modules/%46oo2" && + verify_submodule_gitdir_path cloned-hash "%46oo3" "modules/%46oo3" && + verify_submodule_gitdir_path cloned-hash "%46oo4" "modules/%46oo4" && + verify_submodule_gitdir_path cloned-hash "%46oo5" "modules/%46oo5" && + verify_submodule_gitdir_path cloned-hash "%46oo6" "modules/%46oo6" && + verify_submodule_gitdir_path cloned-hash "%46oo7" "modules/%46oo7" && + verify_submodule_gitdir_path cloned-hash "%46oo8" "modules/%46oo8" && + verify_submodule_gitdir_path cloned-hash "%46oo9" "modules/%46oo9" && + hash=$(printf "Foo" | git hash-object --stdin) && + verify_submodule_gitdir_path cloned-hash "Foo" "modules/${hash}" +' + +test_expect_success 'submodule gitdir conflicts with previously encoded name (local config)' ' + git init -b main super_with_encoded && + ( + cd super_with_encoded && + + git config core.repositoryformatversion 1 && + git config extensions.submodulePathConfig true && + + # Add a submodule with a nested path + git submodule add --name "nested/sub" ../sub nested/sub && + test_commit add-encoded-gitdir && + + verify_submodule_gitdir_path . "nested/sub" "modules/nested%2fsub" && + test_path_is_dir ".git/modules/nested%2fsub" + ) && + + # create a submodule that will conflict with the encoded gitdir name: + # the existing gitdir is ".git/modules/nested%2fsub", which is used + # by "nested/sub", so the new submod will get another (non-conflicting) + # name: "nested%252fsub". + ( + cd super_with_encoded && + git submodule add ../sub "nested%2fsub" && + verify_submodule_gitdir_path . "nested%2fsub" "modules/nested%252fsub" && + test_path_is_dir ".git/modules/nested%252fsub" + ) +' + +test_done
diff --git a/t/t7426-submodule-get-default-remote.sh b/t/t7426-submodule-get-default-remote.sh new file mode 100755 index 0000000..b842af9 --- /dev/null +++ b/t/t7426-submodule-get-default-remote.sh
@@ -0,0 +1,186 @@ +#!/bin/sh + +test_description='git submodule--helper get-default-remote' + +TEST_NO_CREATE_REPO=1 +. ./test-lib.sh + +test_expect_success 'setup' ' + git config --global protocol.file.allow always +' + +test_expect_success 'setup repositories' ' + # Create a repository to be used as submodule + git init sub && + test_commit --no-tag -C sub "initial commit in sub" file.txt "sub content" && + + # Create main repository + git init super && + ( + cd super && + mkdir subdir && + test_commit --no-tag -C subdir "initial commit in super" main.txt "super content" && + git submodule add ../sub subpath && + git commit -m "add submodule 'sub' at subpath" + ) +' + +test_expect_success 'get-default-remote returns origin for initialized submodule' ' + ( + cd super && + git submodule update --init && + echo "origin" >expect && + git submodule--helper get-default-remote subpath >actual && + test_cmp expect actual + ) +' + +test_expect_success 'get-default-remote works from subdirectory' ' + ( + cd super/subdir && + echo "origin" >expect && + git submodule--helper get-default-remote ../subpath >actual && + test_cmp expect actual + ) +' + +test_expect_success 'get-default-remote fails with non-existent path' ' + ( + cd super && + test_must_fail git submodule--helper get-default-remote nonexistent 2>err && + test_grep "could not get a repository handle" err + ) +' + +test_expect_success 'get-default-remote fails with non-submodule path' ' + ( + cd super && + test_must_fail git submodule--helper get-default-remote subdir 2>err && + test_grep "could not get a repository handle" err + ) +' + +test_expect_success 'get-default-remote fails without path argument' ' + ( + cd super && + test_must_fail git submodule--helper get-default-remote 2>err && + test_grep "usage:" err + ) +' + +test_expect_success 'get-default-remote fails with too many arguments' ' + ( + cd super && + test_must_fail git submodule--helper get-default-remote subpath subdir 2>err && + test_grep "usage:" err + ) +' + +test_expect_success 'setup submodule with non-origin default remote name' ' + # Create another submodule path with a different remote name + ( + cd super && + git submodule add ../sub upstream-subpath && + git commit -m "add second submodule in upstream-subpath" && + git submodule update --init upstream-subpath && + + # Change the remote name in the submodule + cd upstream-subpath && + git remote rename origin upstream + ) +' + +test_expect_success 'get-default-remote returns non-origin remote name' ' + ( + cd super && + echo "upstream" >expect && + git submodule--helper get-default-remote upstream-subpath >actual && + test_cmp expect actual + ) +' + +test_expect_success 'get-default-remote handles submodule with multiple remotes' ' + ( + cd super/subpath && + git remote add other-upstream ../../sub && + git remote add myfork ../../sub + ) && + + ( + cd super && + echo "origin" >expect && + git submodule--helper get-default-remote subpath >actual && + test_cmp expect actual + ) +' + +test_expect_success 'get-default-remote handles submodule with multiple remotes and none are origin' ' + ( + cd super/upstream-subpath && + git remote add yet-another-upstream ../../sub && + git remote add yourfork ../../sub + ) && + + ( + cd super && + echo "upstream" >expect && + git submodule--helper get-default-remote upstream-subpath >actual && + test_cmp expect actual + ) +' + +test_expect_success 'setup nested submodule with non-origin remote' ' + git init innersub && + test_commit --no-tag -C innersub "initial commit in innersub" inner.txt "innersub content" && + + ( + cd sub && + git submodule add ../innersub innersubpath && + git commit -m "add nested submodule at innersubpath" + ) && + + ( + cd super/upstream-subpath && + git pull upstream && + git submodule update --init --recursive . && + ( + cd innersubpath && + git remote rename origin another_upstream + ) + ) +' + +test_expect_success 'get-default-remote works with nested submodule' ' + ( + cd super && + echo "another_upstream" >expect && + git submodule--helper get-default-remote upstream-subpath/innersubpath >actual && + test_cmp expect actual + ) +' + +test_expect_success 'get-default-remote works with submodule that has no remotes' ' + # Create a submodule directory manually without remotes + ( + cd super && + git init no-remote-sub && + test_commit --no-tag -C no-remote-sub "local commit" local.txt "local content" + ) && + + # Add it as a submodule + ( + cd super && + git submodule add ./no-remote-sub && + git commit -m "add local submodule 'no-remote-sub'" + ) && + + ( + cd super && + # Should fall back to "origin" remote name when no remotes exist + echo "origin" >expect && + git submodule--helper get-default-remote no-remote-sub >actual && + test_cmp expect actual + ) +' + +test_done
diff --git a/t/t7508-status.sh b/t/t7508-status.sh index abad229..a5e21bf 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh
@@ -1576,7 +1576,7 @@ test_expect_success 'git commit --dry-run will show a staged but ignored submodule' ' git reset HEAD^ && - git add sm && + git add --force sm && cat >expect << EOF && On branch main Your branch and '\''upstream'\'' have diverged,
diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh index 25e8e97..08e82f7 100755 --- a/t/t7512-status-help.sh +++ b/t/t7512-status-help.sh
@@ -594,6 +594,15 @@ test_cmp expected actual ' +test_expect_success 'rebase in a linked worktree' ' + test_might_fail git rebase --abort && + git worktree add wt && + test_when_finished "test_might_fail git -C wt rebase --abort; + git worktree remove wt" && + GIT_SEQUENCE_EDITOR="echo break >" git -C wt rebase -i HEAD && + git -C wt status >actual && + test_grep "interactive rebase in progress" actual +' test_expect_success 'prepare am_session' ' git reset --hard main &&
diff --git a/t/t7519/fsmonitor-watchman b/t/t7519/fsmonitor-watchman index 264b9da..bcc055c 100755 --- a/t/t7519/fsmonitor-watchman +++ b/t/t7519/fsmonitor-watchman
@@ -38,8 +38,6 @@ $git_work_tree = Cwd::cwd(); } -my $retry = 1; - launch_watchman(); sub launch_watchman { @@ -92,9 +90,8 @@ my $o = $json_pkg->new->utf8->decode($response); - if ($retry > 0 and $o->{error} and $o->{error} =~ m/unable to resolve root .* directory (.*) is not watched/) { + if ($o->{error} and $o->{error} =~ m/unable to resolve root .* directory (.*) is not watched/) { print STDERR "Adding '$git_work_tree' to watchman's watch list.\n"; - $retry--; qx/watchman watch "$git_work_tree"/; die "Failed to make watchman watch '$git_work_tree'.\n" . "Falling back to scanning...\n" if $? != 0; @@ -109,7 +106,6 @@ close $fh; print "/\0"; - eval { launch_watchman() }; exit 0; }
diff --git a/t/t7519/fsmonitor-watchman-v2 b/t/t7519/fsmonitor-watchman-v2 index 14ed0aa..368604c 100755 --- a/t/t7519/fsmonitor-watchman-v2 +++ b/t/t7519/fsmonitor-watchman-v2
@@ -29,8 +29,6 @@ my $git_work_tree = get_working_dir(); -my $retry = 1; - my $json_pkg; eval { require JSON::XS; @@ -122,8 +120,7 @@ sub is_work_tree_watched { my ($output) = @_; my $error = $output->{error}; - if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { - $retry--; + if ($error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { my $response = qx/watchman watch "$git_work_tree"/; die "Failed to make watchman watch '$git_work_tree'.\n" . "Falling back to scanning...\n" if $? != 0; @@ -141,15 +138,12 @@ # Watchman query just to get it over with now so we won't pay # the cost in git to look up each individual file. my $o = watchman_clock(); - $error = $output->{error}; + $error = $o->{error}; die "Watchman: $error.\n" . "Falling back to scanning...\n" if $error; output_result($o->{clock}, ("/")); - $last_update_token = $o->{clock}; - - eval { launch_watchman() }; return 0; }
diff --git a/t/t7527-builtin-fsmonitor.sh b/t/t7527-builtin-fsmonitor.sh index 409cd0c..e7b4065 100755 --- a/t/t7527-builtin-fsmonitor.sh +++ b/t/t7527-builtin-fsmonitor.sh
@@ -408,9 +408,8 @@ # ensure we are getting the OS notifications and do not try to confirm what # is reported by `git status`. # -# We run a simple query after modifying the filesystem just to introduce -# a bit of a delay so that the trace logging from the daemon has time to -# get flushed to disk. +# We use retry_grep to handle races between the daemon writing events +# to the trace file and our check. # # We `reset` and `clean` at the bottom of each test (and before stopping the # daemon) because these commands might implicitly restart the daemon. @@ -422,6 +421,24 @@ rm -f .git/trace } +# Retry a grep up to RETRY_TIMEOUT times until it succeeds. +# +RETRY_TIMEOUT=5 + +retry_grep () { + nr_tries_left=$RETRY_TIMEOUT + until grep "$1" "$2" 2>/dev/null + do + if test $nr_tries_left -eq 0 + then + grep "$1" "$2" + return + fi + nr_tries_left=$(($nr_tries_left - 1)) + sleep 1 + done +} + test_expect_success 'edit some files' ' test_when_finished clean_up_repo_and_stop_daemon && @@ -429,12 +446,10 @@ edit_files && - test-tool fsmonitor-client query --token 0 && - - grep "^event: dir1/modified$" .git/trace && - grep "^event: dir2/modified$" .git/trace && - grep "^event: modified$" .git/trace && - grep "^event: dir1/untracked$" .git/trace + retry_grep "^event: dir1/modified$" .git/trace && + retry_grep "^event: dir2/modified$" .git/trace && + retry_grep "^event: modified$" .git/trace && + retry_grep "^event: dir1/untracked$" .git/trace ' test_expect_success 'create some files' ' @@ -444,11 +459,9 @@ create_files && - test-tool fsmonitor-client query --token 0 && - - grep "^event: dir1/new$" .git/trace && - grep "^event: dir2/new$" .git/trace && - grep "^event: new$" .git/trace + retry_grep "^event: dir1/new$" .git/trace && + retry_grep "^event: dir2/new$" .git/trace && + retry_grep "^event: new$" .git/trace ' test_expect_success 'delete some files' ' @@ -458,11 +471,9 @@ delete_files && - test-tool fsmonitor-client query --token 0 && - - grep "^event: dir1/delete$" .git/trace && - grep "^event: dir2/delete$" .git/trace && - grep "^event: delete$" .git/trace + retry_grep "^event: dir1/delete$" .git/trace && + retry_grep "^event: dir2/delete$" .git/trace && + retry_grep "^event: delete$" .git/trace ' test_expect_success 'rename some files' ' @@ -472,14 +483,12 @@ rename_files && - test-tool fsmonitor-client query --token 0 && - - grep "^event: dir1/rename$" .git/trace && - grep "^event: dir2/rename$" .git/trace && - grep "^event: rename$" .git/trace && - grep "^event: dir1/renamed$" .git/trace && - grep "^event: dir2/renamed$" .git/trace && - grep "^event: renamed$" .git/trace + retry_grep "^event: dir1/rename$" .git/trace && + retry_grep "^event: dir2/rename$" .git/trace && + retry_grep "^event: rename$" .git/trace && + retry_grep "^event: dir1/renamed$" .git/trace && + retry_grep "^event: dir2/renamed$" .git/trace && + retry_grep "^event: renamed$" .git/trace ' test_expect_success 'rename directory' ' @@ -489,10 +498,8 @@ mv dirtorename dirrenamed && - test-tool fsmonitor-client query --token 0 && - - grep "^event: dirtorename/*$" .git/trace && - grep "^event: dirrenamed/*$" .git/trace + retry_grep "^event: dirtorename/*$" .git/trace && + retry_grep "^event: dirrenamed/*$" .git/trace ' test_expect_success 'file changes to directory' ' @@ -502,10 +509,8 @@ file_to_directory && - test-tool fsmonitor-client query --token 0 && - - grep "^event: delete$" .git/trace && - grep "^event: delete/new$" .git/trace + retry_grep "^event: delete$" .git/trace && + retry_grep "^event: delete/new$" .git/trace ' test_expect_success 'directory changes to a file' ' @@ -515,9 +520,7 @@ directory_to_file && - test-tool fsmonitor-client query --token 0 && - - grep "^event: dir1$" .git/trace + retry_grep "^event: dir1$" .git/trace ' # The next few test cases exercise the token-resync code. When filesystem
diff --git a/t/t7605-merge-resolve.sh b/t/t7605-merge-resolve.sh index 5d56c38..44de97a 100755 --- a/t/t7605-merge-resolve.sh +++ b/t/t7605-merge-resolve.sh
@@ -34,9 +34,9 @@ test "$(git rev-parse c1)" = "$(git rev-parse HEAD^1)" && test "$(git rev-parse c2)" = "$(git rev-parse HEAD^2)" && git diff --exit-code && - test -f c0.c && - test -f c1.c && - test -f c2.c && + test_path_is_file c0.c && + test_path_is_file c1.c && + test_path_is_file c2.c && test 3 = $(git ls-tree -r HEAD | wc -l) && test 3 = $(git ls-files | wc -l) '
diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh index 73b78bd..63ef63f 100755 --- a/t/t7700-repack.sh +++ b/t/t7700-repack.sh
@@ -217,6 +217,7 @@ cd keep-pack && # avoid producing different packs due to delta/base choices git config pack.window 0 && + git config maintenance.auto false && P1=$(commit_and_pack 1) && P2=$(commit_and_pack 2) && P3=$(commit_and_pack 3) && @@ -260,6 +261,7 @@ # Avoid producing different packs due to delta/base choices git config pack.window 0 && + git config maintenance.auto false && P1=$(commit_and_pack 1) && P2=$(commit_and_pack 2) && P3=$(commit_and_pack 3) && @@ -319,7 +321,7 @@ test_expect_success 'auto-bitmaps do not complain if unavailable' ' test_config -C bare.git pack.packSizeLimit 1M && - blob=$(test-tool genrandom big $((1024*1024)) | + blob=$(test-tool genrandom big 1m | git -C bare.git hash-object -w --stdin) && git -C bare.git update-ref refs/tags/big $blob && @@ -495,9 +497,9 @@ cd max-pack-size && test_commit base && # two blobs which exceed the maximum pack size - test-tool genrandom foo 1048576 >foo && + test-tool genrandom foo 1m >foo && git hash-object -w foo && - test-tool genrandom bar 1048576 >bar && + test-tool genrandom bar 1m >bar && git hash-object -w bar && git add foo bar && git commit -m "adding foo and bar" @@ -534,6 +536,7 @@ ( cd midx && git config core.multiPackIndex true && + git config maintenance.auto false && test_commit base )
diff --git a/t/t7703-repack-geometric.sh b/t/t7703-repack-geometric.sh index 9fc1626..04d5d8f 100755 --- a/t/t7703-repack-geometric.sh +++ b/t/t7703-repack-geometric.sh
@@ -287,6 +287,41 @@ ) ' +test_expect_success '--geometric --write-midx retains up-to-date MIDX without bitmap index' ' + test_when_finished "rm -fr repo" && + git init repo && + ( + cd repo && + test_commit initial && + + test_path_is_missing .git/objects/pack/multi-pack-index && + git repack --geometric=2 --write-midx --no-write-bitmap-index && + test_path_is_file .git/objects/pack/multi-pack-index && + test-tool chmtime =0 .git/objects/pack/multi-pack-index && + + ls -l .git/objects/pack/ >expect && + git repack --geometric=2 --write-midx --no-write-bitmap-index && + ls -l .git/objects/pack/ >actual && + test_cmp expect actual + ) +' + +test_expect_success '--geometric --write-midx retains up-to-date MIDX with bitmap index' ' + test_when_finished "rm -fr repo" && + git init repo && + test_commit -C repo initial && + + test_path_is_missing repo/.git/objects/pack/multi-pack-index && + git -C repo repack --geometric=2 --write-midx --write-bitmap-index && + test_path_is_file repo/.git/objects/pack/multi-pack-index && + test-tool chmtime =0 repo/.git/objects/pack/multi-pack-index && + + ls -l repo/.git/objects/pack/ >expect && + git -C repo repack --geometric=2 --write-midx --write-bitmap-index && + ls -l repo/.git/objects/pack/ >actual && + test_cmp expect actual +' + test_expect_success '--geometric --write-midx with packfiles in main and alternate ODB' ' test_when_finished "rm -fr shared member" && @@ -445,4 +480,65 @@ test_path_is_file member/.git/objects/pack/multi-pack-index-*.bitmap ' +write_packfile () { + NR="$1" + PREFIX="$2" + + printf "blob\ndata <<EOB\n$PREFIX %s\nEOB\n" $(test_seq $NR) | + git fast-import && + git pack-objects --pack-loose-unreachable .git/objects/pack/pack && + git prune-packed +} + +write_promisor_packfile () { + PACKFILE=$(write_packfile "$@") && + touch .git/objects/pack/pack-$PACKFILE.promisor && + echo "$PACKFILE" +} + +test_expect_success 'geometric repack works with promisor packs' ' + test_when_finished "rm -fr repo" && + git init repo && + ( + cd repo && + git config set maintenance.auto false && + git remote add promisor garbage && + git config set remote.promisor.promisor true && + + # Packs A and B need to be merged. + NORMAL_A=$(write_packfile 2 normal-a) && + NORMAL_B=$(write_packfile 2 normal-b) && + NORMAL_C=$(write_packfile 14 normal-c) && + + # Packs A, B and C need to be merged. + PROMISOR_A=$(write_promisor_packfile 1 promisor-a) && + PROMISOR_B=$(write_promisor_packfile 3 promisor-b) && + PROMISOR_C=$(write_promisor_packfile 3 promisor-c) && + PROMISOR_D=$(write_promisor_packfile 20 promisor-d) && + PROMISOR_E=$(write_promisor_packfile 40 promisor-e) && + + git cat-file --batch-all-objects --batch-check="%(objectname)" >objects-expect && + + ls .git/objects/pack/*.pack >packs-before && + test_line_count = 8 packs-before && + git repack --geometric=2 -d && + ls .git/objects/pack/*.pack >packs-after && + test_line_count = 5 packs-after && + test_grep ! "$NORMAL_A" packs-after && + test_grep ! "$NORMAL_B" packs-after && + test_grep "$NORMAL_C" packs-after && + test_grep ! "$PROMISOR_A" packs-after && + test_grep ! "$PROMISOR_B" packs-after && + test_grep ! "$PROMISOR_C" packs-after && + test_grep "$PROMISOR_D" packs-after && + test_grep "$PROMISOR_E" packs-after && + + ls .git/objects/pack/*.promisor >promisors && + test_line_count = 3 promisors && + + git cat-file --batch-all-objects --batch-check="%(objectname)" >objects-actual && + test_cmp objects-expect objects-actual + ) +' + test_done
diff --git a/t/t7704-repack-cruft.sh b/t/t7704-repack-cruft.sh index aa2e2e6..9e03b04 100755 --- a/t/t7704-repack-cruft.sh +++ b/t/t7704-repack-cruft.sh
@@ -869,4 +869,26 @@ ) ' +test_expect_success 'repack rescues once-cruft objects above geometric split' ' + git config repack.midxMustContainCruft false && + + test_commit reachable && + test_commit unreachable && + + unreachable="$(git rev-parse HEAD)" && + + git reset --hard HEAD^ && + git tag -d unreachable && + git reflog expire --all --expire=all && + + git repack --cruft -d && + + echo $unreachable | git pack-objects .git/objects/pack/pack && + + test_commit new && + + git update-ref refs/heads/other $unreachable && + git repack --geometric=2 -d --write-midx --write-bitmap-index +' + test_done
diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 9b74db5..8a91ff3 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh
@@ -647,21 +647,21 @@ ' write_script modify-right-file <<\EOF -echo "new content" >"$2/file" +echo "modified content" >"$2/file" EOF run_dir_diff_test 'difftool --dir-diff syncs worktree with unstaged change' ' test_when_finished git reset --hard && echo "orig content" >file && git difftool -d $symlinks --extcmd "$PWD/modify-right-file" branch && - echo "new content" >expect && + echo "modified content" >expect && test_cmp expect file ' run_dir_diff_test 'difftool --dir-diff syncs worktree without unstaged change' ' test_when_finished git reset --hard && git difftool -d $symlinks --extcmd "$PWD/modify-right-file" branch && - echo "new content" >expect && + echo "modified content" >expect && test_cmp expect file ' @@ -752,11 +752,11 @@ c EOF git difftool --symlinks --dir-diff --extcmd ls >output && - grep -v ^/ output >actual && + grep -v ":\$" output >actual && test_cmp expect actual && git difftool --no-symlinks --dir-diff --extcmd ls >output && - grep -v ^/ output >actual && + grep -v ":\$" output >actual && test_cmp expect actual && # The left side contains symlink "c" that points to "b" @@ -786,11 +786,11 @@ EOF git difftool --symlinks --dir-diff --extcmd ls >output && - grep -v ^/ output >actual && + grep -v ":\$" output >actual && test_cmp expect actual && git difftool --no-symlinks --dir-diff --extcmd ls >output && - grep -v ^/ output >actual && + grep -v ":\$" output >actual && test_cmp expect actual '
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh index 614184a..4700bea 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh
@@ -7,6 +7,9 @@ GIT_TEST_COMMIT_GRAPH=0 GIT_TEST_MULTI_PACK_INDEX=0 +# Ensure that auto-maintenance detaches as usual. +sane_unset GIT_TEST_MAINT_AUTO_DETACH + test_lazy_prereq XMLLINT ' xmllint --version ' @@ -42,14 +45,17 @@ test_grep "usage: git maintenance" err ' -test_expect_success 'run [--auto|--quiet]' ' +test_expect_success 'run [--auto|--quiet] with gc strategy' ' + test_config maintenance.strategy gc && GIT_TRACE2_EVENT="$(pwd)/run-no-auto.txt" \ git maintenance run 2>/dev/null && GIT_TRACE2_EVENT="$(pwd)/run-auto.txt" \ git maintenance run --auto 2>/dev/null && GIT_TRACE2_EVENT="$(pwd)/run-no-quiet.txt" \ git maintenance run --no-quiet 2>/dev/null && + git maintenance is-needed && test_subcommand git gc --quiet --no-detach --skip-foreground-tasks <run-no-auto.txt && + ! git maintenance is-needed --auto && test_subcommand ! git gc --auto --quiet --no-detach --skip-foreground-tasks <run-auto.txt && test_subcommand git gc --no-quiet --no-detach --skip-foreground-tasks <run-no-quiet.txt ' @@ -180,6 +186,11 @@ test_commit first && + ! git -c maintenance.commit-graph.auto=0 \ + maintenance is-needed --auto --task=commit-graph && + git -c maintenance.commit-graph.auto=1 \ + maintenance is-needed --auto --task=commit-graph && + GIT_TRACE2_EVENT="$(pwd)/cg-zero-means-no.txt" \ git -c maintenance.commit-graph.auto=0 $COMMAND && GIT_TRACE2_EVENT="$(pwd)/cg-one-satisfied.txt" \ @@ -199,6 +210,31 @@ test_subcommand $COMMIT_GRAPH_WRITE <cg-two-satisfied.txt ' +test_expect_success 'commit-graph auto condition with merges' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + git config set maintenance.auto false && + test_commit initial && + git switch --create feature && + test_commit feature-1 && + test_commit feature-2 && + git switch - && + test_commit main-1 && + test_commit main-2 && + git merge feature && + + # We have 6 commits, none of which are covered by a commit + # graph. So this must be the boundary at which we start to + # perform maintenance. + test_must_fail git -c maintenance.commit-graph.auto=7 \ + maintenance is-needed --auto --task=commit-graph && + git -c maintenance.commit-graph.auto=6 \ + maintenance is-needed --auto --task=commit-graph + ) +' + test_expect_success 'run --task=bogus' ' test_must_fail git maintenance run --task=bogus 2>err && test_grep "is not a valid task" err @@ -290,16 +326,23 @@ git -c maintenance.loose-objects.auto=1 maintenance \ run --auto --task=loose-objects 2>/dev/null && test_subcommand ! git prune-packed --quiet <trace-lo1.txt && + printf data-A | git hash-object -t blob --stdin -w && + ! git -c maintenance.loose-objects.auto=2 \ + maintenance is-needed --auto --task=loose-objects && GIT_TRACE2_EVENT="$(pwd)/trace-loA" \ git -c maintenance.loose-objects.auto=2 \ maintenance run --auto --task=loose-objects 2>/dev/null && test_subcommand ! git prune-packed --quiet <trace-loA && + printf data-B | git hash-object -t blob --stdin -w && + git -c maintenance.loose-objects.auto=2 \ + maintenance is-needed --auto --task=loose-objects && GIT_TRACE2_EVENT="$(pwd)/trace-loB" \ git -c maintenance.loose-objects.auto=2 \ maintenance run --auto --task=loose-objects 2>/dev/null && test_subcommand git prune-packed --quiet <trace-loB && + GIT_TRACE2_EVENT="$(pwd)/trace-loC" \ git -c maintenance.loose-objects.auto=2 \ maintenance run --auto --task=loose-objects 2>/dev/null && @@ -421,10 +464,13 @@ test_commit A && git repack -adk && git multi-pack-index write && + ! git -c maintenance.incremental-repack.auto=1 \ + maintenance is-needed --auto --task=incremental-repack && GIT_TRACE2_EVENT="$(pwd)/midx-init.txt" git \ -c maintenance.incremental-repack.auto=1 \ maintenance run --auto --task=incremental-repack 2>/dev/null && test_subcommand ! git multi-pack-index write --no-progress <midx-init.txt && + test_commit B && git pack-objects --revs .git/objects/pack/pack <<-\EOF && HEAD @@ -434,11 +480,14 @@ -c maintenance.incremental-repack.auto=2 \ maintenance run --auto --task=incremental-repack 2>/dev/null && test_subcommand ! git multi-pack-index write --no-progress <trace-A && + test_commit C && git pack-objects --revs .git/objects/pack/pack <<-\EOF && HEAD ^HEAD~1 EOF + git -c maintenance.incremental-repack.auto=2 \ + maintenance is-needed --auto --task=incremental-repack && GIT_TRACE2_EVENT=$(pwd)/trace-B git \ -c maintenance.incremental-repack.auto=2 \ maintenance run --auto --task=incremental-repack 2>/dev/null && @@ -451,6 +500,7 @@ ( cd incremental-repack-true && git config core.multiPackIndex true && + git config maintenance.auto false && run_incremental_repack_and_verify ) ' @@ -461,6 +511,7 @@ ( cd incremental-repack-unset && test_unconfig core.multiPackIndex && + git config maintenance.auto false && run_incremental_repack_and_verify ) ' @@ -571,6 +622,7 @@ git init repo && ( cd repo && + git config set maintenance.auto false && # An empty repository does not need repacking, except when # explicitly told to do it. @@ -655,9 +707,15 @@ git reflog expire --all --expire=now && test_commit reflog-one && test_commit reflog-two && + + ! git -c maintenance.reflog-expire.auto=3 \ + maintenance is-needed --auto --task=reflog-expire && GIT_TRACE2_EVENT="$(pwd)/reflog-expire-auto.txt" \ git -c maintenance.reflog-expire.auto=3 maintenance run --auto --task=reflog-expire && test_subcommand ! git reflog expire --all <reflog-expire-auto.txt && + + git -c maintenance.reflog-expire.auto=2 \ + maintenance is-needed --auto --task=reflog-expire && GIT_TRACE2_EVENT="$(pwd)/reflog-expire-auto.txt" \ git -c maintenance.reflog-expire.auto=2 maintenance run --auto --task=reflog-expire && test_subcommand git reflog expire --all <reflog-expire-auto.txt @@ -684,6 +742,7 @@ test_expect_worktree_prune ! git maintenance run --auto --task=worktree-prune && mkdir .git/worktrees && : >.git/worktrees/abc && + git maintenance is-needed --auto --task=worktree-prune && test_expect_worktree_prune git maintenance run --auto --task=worktree-prune ' @@ -700,22 +759,7 @@ test_expect_worktree_prune ! git -c maintenance.worktree-prune.auto=0 maintenance run --auto --task=worktree-prune && # A positive value should require at least this many prunable worktrees. test_expect_worktree_prune ! git -c maintenance.worktree-prune.auto=4 maintenance run --auto --task=worktree-prune && - test_expect_worktree_prune git -c maintenance.worktree-prune.auto=3 maintenance run --auto --task=worktree-prune -' - -test_expect_success 'worktree-prune task with --auto honors maintenance.worktree-prune.auto' ' - # A negative value should always prune. - test_expect_worktree_prune git -c maintenance.worktree-prune.auto=-1 maintenance run --auto --task=worktree-prune && - - mkdir .git/worktrees && - : >.git/worktrees/first && - : >.git/worktrees/second && - : >.git/worktrees/third && - - # Zero should never prune. - test_expect_worktree_prune ! git -c maintenance.worktree-prune.auto=0 maintenance run --auto --task=worktree-prune && - # A positive value should require at least this many prunable worktrees. - test_expect_worktree_prune ! git -c maintenance.worktree-prune.auto=4 maintenance run --auto --task=worktree-prune && + git -c maintenance.worktree-prune.auto=3 maintenance is-needed --auto --task=worktree-prune && test_expect_worktree_prune git -c maintenance.worktree-prune.auto=3 maintenance run --auto --task=worktree-prune ' @@ -724,11 +768,13 @@ rm -rf worktree && rm -f worktree-prune.txt && + ! git -c gc.worktreePruneExpire=1.week.ago maintenance is-needed --auto --task=worktree-prune && GIT_TRACE2_EVENT="$(pwd)/worktree-prune.txt" git -c gc.worktreePruneExpire=1.week.ago maintenance run --auto --task=worktree-prune && test_subcommand ! git worktree prune --expire 1.week.ago <worktree-prune.txt && test_path_is_dir .git/worktrees/worktree && rm -f worktree-prune.txt && + git -c gc.worktreePruneExpire=now maintenance is-needed --auto --task=worktree-prune && GIT_TRACE2_EVENT="$(pwd)/worktree-prune.txt" git -c gc.worktreePruneExpire=now maintenance run --auto --task=worktree-prune && test_subcommand git worktree prune --expire now <worktree-prune.txt && test_path_is_missing .git/worktrees/worktree @@ -753,10 +799,13 @@ test_expect_success 'rerere-gc task with --auto only prunes with prunable entries' ' test_when_finished "rm -rf .git/rr-cache" && + ! git maintenance is-needed --auto --task=rerere-gc && test_expect_rerere_gc ! git maintenance run --auto --task=rerere-gc && mkdir .git/rr-cache && + ! git maintenance is-needed --auto --task=rerere-gc && test_expect_rerere_gc ! git maintenance run --auto --task=rerere-gc && : >.git/rr-cache/entry && + git maintenance is-needed --auto --task=rerere-gc && test_expect_rerere_gc git maintenance run --auto --task=rerere-gc ' @@ -764,17 +813,22 @@ test_when_finished "rm -rf .git/rr-cache" && # A negative value should always prune. + git -c maintenance.rerere-gc.auto=-1 maintenance is-needed --auto --task=rerere-gc && test_expect_rerere_gc git -c maintenance.rerere-gc.auto=-1 maintenance run --auto --task=rerere-gc && # A positive value prunes when there is at least one entry. + ! git -c maintenance.rerere-gc.auto=9000 maintenance is-needed --auto --task=rerere-gc && test_expect_rerere_gc ! git -c maintenance.rerere-gc.auto=9000 maintenance run --auto --task=rerere-gc && mkdir .git/rr-cache && + ! git -c maintenance.rerere-gc.auto=9000 maintenance is-needed --auto --task=rerere-gc && test_expect_rerere_gc ! git -c maintenance.rerere-gc.auto=9000 maintenance run --auto --task=rerere-gc && : >.git/rr-cache/entry-1 && + git -c maintenance.rerere-gc.auto=9000 maintenance is-needed --auto --task=rerere-gc && test_expect_rerere_gc git -c maintenance.rerere-gc.auto=9000 maintenance run --auto --task=rerere-gc && # Zero should never prune. : >.git/rr-cache/entry-1 && + ! git -c maintenance.rerere-gc.auto=0 maintenance is-needed --auto --task=rerere-gc && test_expect_rerere_gc ! git -c maintenance.rerere-gc.auto=0 maintenance run --auto --task=rerere-gc '
diff --git a/t/t8003-blame-corner-cases.sh b/t/t8003-blame-corner-cases.sh index 7312655..30e7960 100755 --- a/t/t8003-blame-corner-cases.sh +++ b/t/t8003-blame-corner-cases.sh
@@ -49,80 +49,69 @@ ' test_expect_success 'straight copy without -C' ' - - git blame uno | grep Second - + git blame uno >actual && + test_grep Second actual ' test_expect_success 'straight move without -C' ' - - git blame dos | grep Initial - + git blame dos >actual && + test_grep Initial actual ' test_expect_success 'straight copy with -C' ' - - git blame -C1 uno | grep Second - + git blame -C1 uno >actual && + test_grep Second actual ' test_expect_success 'straight move with -C' ' - - git blame -C1 dos | grep Initial - + git blame -C1 dos >actual && + test_grep Initial actual ' test_expect_success 'straight copy with -C -C' ' - - git blame -C -C1 uno | grep Initial - + git blame -C -C1 uno >actual && + test_grep Initial actual ' test_expect_success 'straight move with -C -C' ' - - git blame -C -C1 dos | grep Initial - + git blame -C -C1 dos >actual && + test_grep Initial actual ' test_expect_success 'append without -C' ' - - git blame -L2 tres | grep Second - + git blame -L2 tres >actual && + test_grep Second actual ' test_expect_success 'append with -C' ' - - git blame -L2 -C1 tres | grep Second - + git blame -L2 -C1 tres >actual && + test_grep Second actual ' test_expect_success 'append with -C -C' ' - - git blame -L2 -C -C1 tres | grep Second - + git blame -L2 -C -C1 tres >actual && + test_grep Second actual ' test_expect_success 'append with -C -C -C' ' - - git blame -L2 -C -C -C1 tres | grep Initial - + git blame -L2 -C -C -C1 tres >actual && + test_grep Initial actual ' test_expect_success 'blame wholesale copy' ' - - git blame -f -C -C1 HEAD^ -- cow | sed -e "$pick_fc" >current && + git blame -f -C -C1 HEAD^ -- cow >actual && + sed -e "$pick_fc" actual >current && cat >expected <<-\EOF && mouse-Initial mouse-Second mouse-Third EOF test_cmp expected current - ' test_expect_success 'blame wholesale copy and more' ' - - git blame -f -C -C1 HEAD -- cow | sed -e "$pick_fc" >current && + git blame -f -C -C1 HEAD -- cow >actual && + sed -e "$pick_fc" actual >current && cat >expected <<-\EOF && mouse-Initial mouse-Second @@ -130,11 +119,9 @@ mouse-Third EOF test_cmp expected current - ' test_expect_success 'blame wholesale copy and more in the index' ' - cat >horse <<-\EOF && ABC DEF @@ -144,7 +131,8 @@ EOF git add horse && test_when_finished "git rm -f horse" && - git blame -f -C -C1 -- horse | sed -e "$pick_fc" >current && + git blame -f -C -C1 -- horse >actual && + sed -e "$pick_fc" actual >current && cat >expected <<-\EOF && mouse-Initial mouse-Second @@ -153,11 +141,9 @@ mouse-Third EOF test_cmp expected current - ' test_expect_success 'blame during cherry-pick with file rename conflict' ' - test_when_finished "git reset --hard && git checkout main" && git checkout HEAD~3 && echo MOUSE >> mouse && @@ -168,7 +154,8 @@ (git cherry-pick HEAD@{1} || test $? -eq 1) && git show HEAD@{1}:rodent > rodent && git add rodent && - git blame -f -C -C1 rodent | sed -e "$pick_fc" >current && + git blame -f -C -C1 rodent >actual && + sed -e "$pick_fc" actual >current && cat >expected <<-\EOF && mouse-Initial mouse-Second @@ -246,14 +233,14 @@ test_expect_success 'blame file with CRLF core.autocrlf true' ' git config core.autocrlf true && git blame crlffile >actual && - grep "A U Thor" actual + test_grep "A U Thor" actual ' test_expect_success 'blame file with CRLF attributes text' ' git config core.autocrlf false && echo "crlffile text" >.gitattributes && git blame crlffile >actual && - grep "A U Thor" actual + test_grep "A U Thor" actual ' test_expect_success 'blame file with CRLF core.autocrlf=true' ' @@ -267,7 +254,7 @@ git checkout crlfinrepo && rm tmp && git blame crlfinrepo >actual && - grep "A U Thor" actual + test_grep "A U Thor" actual ' test_expect_success 'setup coalesce tests' '
diff --git a/t/t8012-blame-colors.sh b/t/t8012-blame-colors.sh index 3d77352..5562eba 100755 --- a/t/t8012-blame-colors.sh +++ b/t/t8012-blame-colors.sh
@@ -28,6 +28,20 @@ test_line_count = 3 H.expect ' +test_expect_success 'color lines becoming contiguous due to --ignore-rev' ' + mv hello.c hello.orig && + sed "s/ / /g" <hello.orig >hello.c && + git add hello.c && + git commit -m"tabs to spaces" && + git -c color.blame.repeatedLines=yellow blame --color-lines --ignore-rev=HEAD hello.c >actual.raw && + test_decode_color <actual.raw >actual && + grep "<YELLOW>" <actual >darkened && + grep "(F" darkened > F.expect && + grep "(H" darkened > H.expect && + test_line_count = 2 F.expect && + test_line_count = 3 H.expect +' + test_expect_success 'color by age consistently colors old code' ' git blame --color-by-age hello.c >actual.raw && git -c blame.coloring=highlightRecent blame hello.c >actual.raw.2 &&
diff --git a/t/t8015-blame-diff-algorithm.sh b/t/t8015-blame-diff-algorithm.sh new file mode 100755 index 0000000..cd70953 --- /dev/null +++ b/t/t8015-blame-diff-algorithm.sh
@@ -0,0 +1,203 @@ +#!/bin/sh + +test_description='git blame with specific diff algorithm' + +. ./test-lib.sh + +test_expect_success setup ' + cat >file.c <<-\EOF && + int f(int x, int y) + { + if (x == 0) + { + return y; + } + return x; + } + + int g(size_t u) + { + while (u < 30) + { + u++; + } + return u; + } + EOF + test_write_lines x x x x >file.txt && + git add file.c file.txt && + GIT_AUTHOR_NAME=Commit_1 git commit -m Commit_1 && + + cat >file.c <<-\EOF && + int g(size_t u) + { + while (u < 30) + { + u++; + } + return u; + } + + int h(int x, int y, int z) + { + if (z == 0) + { + return x; + } + return y; + } + EOF + test_write_lines x x x A B C D x E F G >file.txt && + git add file.c file.txt && + GIT_AUTHOR_NAME=Commit_2 git commit -m Commit_2 +' + +test_expect_success 'blame uses Myers diff algorithm by default' ' + cat >expected <<-\EOF && + Commit_2 int g(size_t u) + Commit_1 { + Commit_2 while (u < 30) + Commit_1 { + Commit_2 u++; + Commit_1 } + Commit_2 return u; + Commit_1 } + Commit_1 + Commit_2 int h(int x, int y, int z) + Commit_1 { + Commit_2 if (z == 0) + Commit_1 { + Commit_2 return x; + Commit_1 } + Commit_2 return y; + Commit_1 } + EOF + + git blame file.c >output && + sed -e "s/^[^ ]* (\([^ ]*\) [^)]*)/\1/g" output >without_varying_parts && + sed -e "s/ *$//g" without_varying_parts >actual && + test_cmp expected actual +' + +test_expect_success 'blame honors --diff-algorithm option' ' + cat >expected <<-\EOF && + Commit_1 int g(size_t u) + Commit_1 { + Commit_1 while (u < 30) + Commit_1 { + Commit_1 u++; + Commit_1 } + Commit_1 return u; + Commit_1 } + Commit_2 + Commit_2 int h(int x, int y, int z) + Commit_2 { + Commit_2 if (z == 0) + Commit_2 { + Commit_2 return x; + Commit_2 } + Commit_2 return y; + Commit_2 } + EOF + + git blame file.c --diff-algorithm histogram >output && + sed -e "s/^[^ ]* (\([^ ]*\) [^)]*)/\1/g" output >without_varying_parts && + sed -e "s/ *$//g" without_varying_parts >actual && + test_cmp expected actual +' + +test_expect_success 'blame honors diff.algorithm config variable' ' + cat >expected <<-\EOF && + Commit_1 int g(size_t u) + Commit_1 { + Commit_1 while (u < 30) + Commit_1 { + Commit_1 u++; + Commit_1 } + Commit_1 return u; + Commit_1 } + Commit_2 + Commit_2 int h(int x, int y, int z) + Commit_2 { + Commit_2 if (z == 0) + Commit_2 { + Commit_2 return x; + Commit_2 } + Commit_2 return y; + Commit_2 } + EOF + + git -c diff.algorithm=histogram blame file.c >output && + sed -e "s/^[^ ]* (\([^ ]*\) [^)]*)/\1/g" \ + -e "s/ *$//g" output >actual && + test_cmp expected actual +' + +test_expect_success 'blame gives priority to --diff-algorithm over diff.algorithm' ' + cat >expected <<-\EOF && + Commit_1 int g(size_t u) + Commit_1 { + Commit_1 while (u < 30) + Commit_1 { + Commit_1 u++; + Commit_1 } + Commit_1 return u; + Commit_1 } + Commit_2 + Commit_2 int h(int x, int y, int z) + Commit_2 { + Commit_2 if (z == 0) + Commit_2 { + Commit_2 return x; + Commit_2 } + Commit_2 return y; + Commit_2 } + EOF + + git -c diff.algorithm=myers blame file.c --diff-algorithm histogram >output && + sed -e "s/^[^ ]* (\([^ ]*\) [^)]*)/\1/g" \ + -e "s/ *$//g" output >actual && + test_cmp expected actual +' + +test_expect_success 'blame honors --minimal option' ' + cat >expected <<-\EOF && + Commit_1 x + Commit_1 x + Commit_1 x + Commit_2 A + Commit_2 B + Commit_2 C + Commit_2 D + Commit_1 x + Commit_2 E + Commit_2 F + Commit_2 G + EOF + + git blame file.txt --minimal >output && + sed -e "s/^[^ ]* (\([^ ]*\) [^)]*)/\1/g" output >actual && + test_cmp expected actual +' + +test_expect_success 'blame respects the order of diff options' ' + cat >expected <<-\EOF && + Commit_1 x + Commit_1 x + Commit_1 x + Commit_2 A + Commit_2 B + Commit_2 C + Commit_2 D + Commit_2 x + Commit_2 E + Commit_2 F + Commit_2 G + EOF + + git blame file.txt --minimal --diff-algorithm myers >output && + sed -e "s/^[^ ]* (\([^ ]*\) [^)]*)/\1/g" output >actual && + test_cmp expected actual +' + +test_done
diff --git a/t/t8020-last-modified.sh b/t/t8020-last-modified.sh index a4c1114..9dba4b9 100755 --- a/t/t8020-last-modified.sh +++ b/t/t8020-last-modified.sh
@@ -8,14 +8,11 @@ test_commit 1 file && mkdir a && test_commit 2 a/file && + git tag -mA t2 2 && mkdir a/b && test_commit 3 a/b/file ' -test_expect_success 'cannot run last-modified on two trees' ' - test_must_fail git last-modified HEAD HEAD~1 -' - check_last_modified() { local indir= && while test $# != 0 @@ -34,7 +31,7 @@ cat >expect && git ${indir:+-C "$indir"} last-modified "$@" >tmp.1 && - git name-rev --annotate-stdin --name-only --tags \ + git name-rev --annotate-stdin --name-only --tags --exclude=t2 \ <tmp.1 >tmp.2 && tr '\t' ' ' <tmp.2 >actual && test_cmp expect actual @@ -55,6 +52,13 @@ EOF ' +test_expect_success 'last-modified on annotated tag' ' + check_last_modified t2 <<-\EOF + 2 a + 1 file + EOF +' + test_expect_success 'last-modified recursive with show-trees' ' check_last_modified -r -t <<-\EOF 3 a/b @@ -78,6 +82,14 @@ EOF ' +test_expect_success 'last-modified in sparse checkout' ' + test_when_finished "git sparse-checkout disable" && + git sparse-checkout set b && + check_last_modified -- a <<-\EOF + 3 a + EOF +' + test_expect_success 'last-modified subdir recursive' ' check_last_modified -r a <<-\EOF 3 a/b/file @@ -85,6 +97,41 @@ EOF ' +test_expect_success 'last-modified subdir non-recursive' ' + check_last_modified a <<-\EOF + 3 a + EOF +' + +test_expect_success 'last-modified path in subdir non-recursive' ' + check_last_modified a/file <<-\EOF + 2 a/file + EOF +' + +test_expect_success 'last-modified subdir with wildcard non-recursive' ' + check_last_modified a/* <<-\EOF + 3 a/b + 2 a/file + EOF +' + +test_expect_success 'last-modified with negative max-depth' ' + check_last_modified --max-depth=-1 <<-\EOF + 3 a/b/file + 2 a/file + 1 file + EOF +' + +test_expect_success 'last-modified with max-depth of 1' ' + check_last_modified --max-depth=1 <<-\EOF + 3 a/b + 2 a/file + 1 file + EOF +' + test_expect_success 'last-modified from non-HEAD commit' ' check_last_modified HEAD^ <<-\EOF 2 a @@ -222,9 +269,19 @@ EOF ' +test_expect_success 'cannot run last-modified on two commits' ' + test_must_fail git last-modified HEAD HEAD~1 2>err && + test_grep "last-modified can only operate on one commit at a time" err +' + test_expect_success 'last-modified complains about unknown arguments' ' test_must_fail git last-modified --foo 2>err && - grep "unknown last-modified argument: --foo" err + test_grep "unknown last-modified argument: --foo" err +' + +test_expect_success 'last-modified expects commit-ish' ' + test_must_fail git last-modified HEAD^{tree} 2>err && + test_grep "revision argument ${SQ}HEAD^{tree}${SQ} is a tree, not a commit-ish" err ' test_done
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index e56e0c8..e7ab645 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh
@@ -1649,7 +1649,9 @@ ' test_expect_success $PREREQ 'setup expect' ' -cat >email-using-8bit <<\EOF +# NOTE: do not quote this heredoc, Dash 0.5.13 has a bug with heredocs +# that contain multibyte chars. +cat >email-using-8bit <<EOF From fe6ecc66ece37198fe5db91fa2fc41d9f4fe5cc4 Mon Sep 17 00:00:00 2001 Message-ID: <bogus-message-id@example.com> From: author@example.com @@ -1691,7 +1693,7 @@ email-using-8bit >stdout && grep "do not declare a Content-Transfer-Encoding" stdout && grep email-using-8bit stdout && - grep "Which 8bit encoding" stdout && + grep "Declare which 8bit encoding to use" stdout && grep -E "Content|MIME" msgtxt1 >actual && test_cmp content-type-decl actual ' @@ -1735,7 +1737,9 @@ ' test_expect_success $PREREQ 'setup expect' ' - cat >email-using-8bit <<-\EOF + # NOTE: do not quote this heredoc, Dash 0.5.13 has a bug with heredocs + # that contain multibyte chars. + cat >email-using-8bit <<-EOF From fe6ecc66ece37198fe5db91fa2fc41d9f4fe5cc4 Mon Sep 17 00:00:00 2001 Message-ID: <bogus-message-id@example.com> From: author@example.com @@ -1764,7 +1768,9 @@ ' test_expect_success $PREREQ 'setup expect' ' - cat >email-using-8bit <<-\EOF + # NOTE: do not quote this heredoc, Dash 0.5.13 has a bug with heredocs + # that contain multibyte chars. + cat >email-using-8bit <<-EOF From fe6ecc66ece37198fe5db91fa2fc41d9f4fe5cc4 Mon Sep 17 00:00:00 2001 Message-ID: <bogus-message-id@example.com> From: A U Thor <author@example.com>
diff --git a/t/t9123-git-svn-rebuild-with-rewriteroot.sh b/t/t9123-git-svn-rebuild-with-rewriteroot.sh index ead4045..8fa5940 100755 --- a/t/t9123-git-svn-rebuild-with-rewriteroot.sh +++ b/t/t9123-git-svn-rebuild-with-rewriteroot.sh
@@ -7,12 +7,15 @@ . ./lib-git-svn.sh -mkdir import -(cd import - touch foo - svn_cmd import -m 'import for git svn' . "$svnrepo" >/dev/null -) -rm -rf import +test_expect_success 'setup svn repository' ' + test_when_finished "rm -rf import" && + mkdir import && + ( + cd import && + touch foo && + svn_cmd import -m "import for git svn" . "$svnrepo" >/dev/null + ) + ' test_expect_success 'init, fetch and checkout repository' ' git svn init --rewrite-root=http://invalid.invalid/ "$svnrepo" &&
diff --git a/t/t9160-git-svn-preserve-empty-dirs.sh b/t/t9160-git-svn-preserve-empty-dirs.sh index 36c6b1a..de32cf2 100755 --- a/t/t9160-git-svn-preserve-empty-dirs.sh +++ b/t/t9160-git-svn-preserve-empty-dirs.sh
@@ -61,15 +61,15 @@ # "$GIT_REPO"/1 should only contain the placeholder file. test_expect_success 'directory empty from inception' ' - test -f "$GIT_REPO"/1/.gitignore && + test_path_is_file "$GIT_REPO"/1/.gitignore && test $(find "$GIT_REPO"/1 -type f | wc -l) = "1" ' # "$GIT_REPO"/2 and "$GIT_REPO"/3 should only contain the placeholder file. test_expect_success 'directory empty from subsequent svn commit' ' - test -f "$GIT_REPO"/2/.gitignore && + test_path_is_file "$GIT_REPO"/2/.gitignore && test $(find "$GIT_REPO"/2 -type f | wc -l) = "1" && - test -f "$GIT_REPO"/3/.gitignore && + test_path_is_file "$GIT_REPO"/3/.gitignore && test $(find "$GIT_REPO"/3 -type f | wc -l) = "1" ' @@ -77,7 +77,7 @@ # generated for every sub-directory at some point in the repo's history. test_expect_success 'add entry to previously empty directory' ' test $(find "$GIT_REPO"/4 -type f | wc -l) = "1" && - test -f "$GIT_REPO"/4/a/b/c/foo + test_path_is_file "$GIT_REPO"/4/a/b/c/foo ' # The HEAD~2 commit should not have introduced .gitignore placeholder files. @@ -102,14 +102,14 @@ # "$GIT_REPO"/5/.placeholder should be a file, and non-empty. test_expect_success 'placeholder namespace conflict with file' ' - test -s "$GIT_REPO"/5/.placeholder + test_file_not_empty "$GIT_REPO"/5/.placeholder ' # "$GIT_REPO"/6/.placeholder should be a directory, and the "$GIT_REPO"/6 tree # should only contain one file: the placeholder. test_expect_success 'placeholder namespace conflict with directory' ' - test -d "$GIT_REPO"/6/.placeholder && - test -f "$GIT_REPO"/6/.placeholder/.placeholder && + test_path_is_dir "$GIT_REPO"/6/.placeholder && + test_path_is_file "$GIT_REPO"/6/.placeholder/.placeholder && test $(find "$GIT_REPO"/6 -type f | wc -l) = "1" ' @@ -133,19 +133,19 @@ # Check that --preserve-empty-dirs and --placeholder-file flag state # stays persistent over multiple invocations. -test_expect_success 'flag persistence during subsqeuent rebase' ' - test -f "$GIT_REPO"/7/.placeholder && +test_expect_success 'flag persistence during subsequent rebase' ' + test_path_is_file "$GIT_REPO"/7/.placeholder && test $(find "$GIT_REPO"/7 -type f | wc -l) = "1" ' # Check that placeholder files are properly removed when unnecessary, # even across multiple invocations. -test_expect_success 'placeholder list persistence during subsqeuent rebase' ' - test -f "$GIT_REPO"/1/file1.txt && +test_expect_success 'placeholder list persistence during subsequent rebase' ' + test_path_is_file "$GIT_REPO"/1/file1.txt && test $(find "$GIT_REPO"/1 -type f | wc -l) = "1" && - test -f "$GIT_REPO"/5/file1.txt && - test -f "$GIT_REPO"/5/.placeholder && + test_path_is_file "$GIT_REPO"/5/file1.txt && + test_path_is_file "$GIT_REPO"/5/.placeholder && test $(find "$GIT_REPO"/5 -type f | wc -l) = "2" '
diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh index a44eabf..14cbe96 100755 --- a/t/t9200-git-cvsexportcommit.sh +++ b/t/t9200-git-cvsexportcommit.sh
@@ -30,13 +30,17 @@ rm -rf "$CVSROOT" "$CVSWORK" -cvs init && -test -d "$CVSROOT" && -cvs -Q co -d "$CVSWORK" . && -echo >empty && -git add empty && -git commit -q -a -m "Initial" 2>/dev/null || -exit 1 +if ! cvs init || ! test -d "$CVSROOT" || ! cvs -Q co -d "$CVSWORK" . +then + skip_all="cvs repository set-up fails" + test_done +fi + +test_expect_success 'git setup' ' + echo >empty && + git add empty && + git commit -q -a -m Initial +' check_entries () { # $1 == directory, $2 == expected @@ -303,7 +307,7 @@ git commit -m "Added attic_gremlin" && git cvsexportcommit -w "$CVSWORK" -c HEAD && (cd "$CVSWORK" && cvs -Q update -d) && - test -f "$CVSWORK/attic_gremlin" + test_path_is_file "$CVSWORK/attic_gremlin" ' # the state of the CVS sandbox may be indeterminate for ' space'
diff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh index bd6f0c4..009437a 100755 --- a/t/t9210-scalar.sh +++ b/t/t9210-scalar.sh
@@ -202,14 +202,17 @@ test_expect_success 'scalar reconfigure' ' git init one/src && scalar register one && - git -C one/src config core.preloadIndex false && + git -C one/src config unset gui.gcwarning && scalar reconfigure one && - test true = "$(git -C one/src config core.preloadIndex)" && - git -C one/src config core.preloadIndex false && + test false = "$(git -C one/src config gui.gcwarning)" && + git -C one/src config unset gui.gcwarning && rm one/src/cron.txt && GIT_TRACE2_EVENT="$(pwd)/reconfigure" scalar reconfigure -a && test_path_is_file one/src/cron.txt && - test true = "$(git -C one/src config core.preloadIndex)" && + test false = "$(git -C one/src config gui.gcwarning)" && + test_grep "GCWarning = false # set by scalar" one/src/.git/config && + test_grep "excludeDecoration = refs/prefetch/\* # set by scalar" one/src/.git/config && + test_subcommand git maintenance start <reconfigure && test_subcommand ! git maintenance unregister --force <reconfigure && @@ -231,25 +234,29 @@ git init $num/src && scalar register $num/src && git -C $num/src config includeif."onbranch:foo".path something && - git -C $num/src config core.preloadIndex false || return 1 + git -C $num/src config unset gui.gcwarning || return 1 done && scalar reconfigure --all && for num in $repos do - test true = "$(git -C $num/src config core.preloadIndex)" || return 1 + test false = "$(git -C $num/src config gui.gcwarning)" || return 1 done ' test_expect_success 'scalar reconfigure --all with detached HEADs' ' + # This test demonstrates an issue with index.skipHash=true and + # this test variable for the split index. Disable the test variable. + sane_unset GIT_TEST_SPLIT_INDEX && + repos="two three four" && for num in $repos do rm -rf $num/src && git init $num/src && scalar register $num/src && - git -C $num/src config core.preloadIndex false && + git -C $num/src config unset gui.gcwarning && test_commit -C $num/src initial && git -C $num/src switch --detach HEAD || return 1 done && @@ -258,7 +265,7 @@ for num in $repos do - test true = "$(git -C $num/src config core.preloadIndex)" || return 1 + test false = "$(git -C $num/src config gui.gcwarning)" || return 1 done ' @@ -290,7 +297,7 @@ git init sub && scalar -C sub -c status.aheadBehind=bogus register && test -z "$(git -C sub config --local status.aheadBehind)" && - test true = "$(git -C sub config core.preloadIndex)" + test false = "$(git -C sub config gui.gcwarning)" ' test_expect_success '`scalar [...] <dir>` errors out when dir is missing' '
diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 5685cce..4794377 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh
@@ -3635,25 +3635,21 @@ echo "progress checkpoint" ) >&8 & - error=1 ;# assume the worst - while read output <&9 - do - if test "$output" = "progress checkpoint" - then - error=0 - break - elif test "$output" = "UNEXPECTED" - then - break - fi - # otherwise ignore cruft - echo >&2 "cruft: $output" - done + last=$( + while read output <&9 + do + if test "$output" = "progress checkpoint" || test "$output" = "UNEXPECTED" + then + echo "$output" + break + else + # otherwise ignore cruft + echo >&2 "cruft: $output" + fi + done + ) - if test $error -eq 1 - then - false - fi + test "$last" = "progress checkpoint" } background_import_still_running () {
diff --git a/t/t9305-fast-import-signatures.sh b/t/t9305-fast-import-signatures.sh index c2b4271..18707b3 100755 --- a/t/t9305-fast-import-signatures.sh +++ b/t/t9305-fast-import-signatures.sh
@@ -70,7 +70,7 @@ test_must_be_empty log ' -test_expect_success GPG 'setup a commit with dual OpenPGP signatures on its SHA-1 and SHA-256 formats' ' +test_expect_success RUST,GPG 'setup a commit with dual OpenPGP signatures on its SHA-1 and SHA-256 formats' ' # Create a signed SHA-256 commit git init --object-format=sha256 explicit-sha256 && git -C explicit-sha256 config extensions.compatObjectFormat sha1 && @@ -79,7 +79,7 @@ echo B >explicit-sha256/B && git -C explicit-sha256 add B && test_tick && - git -C explicit-sha256 commit -S -m "signed" B && + git -C explicit-sha256 commit -S -m "signed commit" B && SHA256_B=$(git -C explicit-sha256 rev-parse dual-signed) && # Create the corresponding SHA-1 commit @@ -91,7 +91,7 @@ test_grep -E "^gpgsig-sha256 " out ' -test_expect_success GPG 'strip both OpenPGP signatures with --signed-commits=warn-strip' ' +test_expect_success RUST,GPG 'strip both OpenPGP signatures with --signed-commits=warn-strip' ' git -C explicit-sha256 fast-export --signed-commits=verbatim dual-signed >output && test_grep -E "^gpgsig sha1 openpgp" output && test_grep -E "^gpgsig sha256 openpgp" output && @@ -103,4 +103,111 @@ test_line_count = 2 out ' +for mode in strip-if-invalid sign-if-invalid +do + test_expect_success GPG "import commit with no signature with --signed-commits=$mode" ' + git fast-export main >output && + git -C new fast-import --quiet --signed-commits=$mode <output >log 2>&1 && + test_must_be_empty log + ' + + test_expect_success GPG "keep valid OpenPGP signature with --signed-commits=$mode" ' + rm -rf new && + git init new && + + git fast-export --signed-commits=verbatim openpgp-signing >output && + git -C new fast-import --quiet --signed-commits=$mode <output >log 2>&1 && + IMPORTED=$(git -C new rev-parse --verify refs/heads/openpgp-signing) && + test $OPENPGP_SIGNING = $IMPORTED && + git -C new cat-file commit "$IMPORTED" >actual && + test_grep -E "^gpgsig(-sha256)? " actual && + test_must_be_empty log + ' + + test_expect_success GPG "handle signature invalidated by message change with --signed-commits=$mode" ' + rm -rf new && + git init new && + + git fast-export --signed-commits=verbatim openpgp-signing >output && + + # Change the commit message, which invalidates the signature. + # The commit message length should not change though, otherwise the + # corresponding `data <length>` command would have to be changed too. + sed "s/OpenPGP signed commit/OpenPGP forged commit/" output >modified && + + git -C new fast-import --quiet --signed-commits=$mode <modified >log 2>&1 && + + IMPORTED=$(git -C new rev-parse --verify refs/heads/openpgp-signing) && + test $OPENPGP_SIGNING != $IMPORTED && + git -C new cat-file commit "$IMPORTED" >actual && + + if test "$mode" = strip-if-invalid + then + test_grep "stripping invalid signature" log && + test_grep ! -E "^gpgsig" actual + else + test_grep "replacing invalid signature" log && + test_grep -E "^gpgsig(-sha256)? " actual && + git -C new verify-commit "$IMPORTED" + fi + ' + + test_expect_success GPGSM "keep valid X.509 signature with --signed-commits=$mode" ' + rm -rf new && + git init new && + + git fast-export --signed-commits=verbatim x509-signing >output && + git -C new fast-import --quiet --signed-commits=$mode <output >log 2>&1 && + IMPORTED=$(git -C new rev-parse --verify refs/heads/x509-signing) && + test $X509_SIGNING = $IMPORTED && + git -C new cat-file commit "$IMPORTED" >actual && + test_grep -E "^gpgsig(-sha256)? " actual && + test_must_be_empty log + ' + + test_expect_success GPGSSH "keep valid SSH signature with --signed-commits=$mode" ' + rm -rf new && + git init new && + + test_config -C new gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + + git fast-export --signed-commits=verbatim ssh-signing >output && + git -C new fast-import --quiet --signed-commits=$mode <output >log 2>&1 && + IMPORTED=$(git -C new rev-parse --verify refs/heads/ssh-signing) && + test $SSH_SIGNING = $IMPORTED && + git -C new cat-file commit "$IMPORTED" >actual && + test_grep -E "^gpgsig(-sha256)? " actual && + test_must_be_empty log + ' +done + +test_expect_success GPGSSH "sign invalid commit with explicit keyid" ' + rm -rf new && + git init new && + + git fast-export --signed-commits=verbatim ssh-signing >output && + + # Change the commit message, which invalidates the signature. + # The commit message length should not change though, otherwise the + # corresponding `data <length>` command would have to be changed too. + sed "s/SSH signed commit/SSH forged commit/" output >modified && + + # Configure the target repository with an invalid default signing key. + test_config -C new user.signingkey "not-a-real-key-id" && + test_config -C new gpg.format ssh && + test_config -C new gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + test_must_fail git -C new fast-import --quiet \ + --signed-commits=sign-if-invalid <modified >/dev/null 2>&1 && + + # Import using explicitly provided signing key. + git -C new fast-import --quiet \ + --signed-commits=sign-if-invalid="${GPGSSH_KEY_PRIMARY}" <modified && + + IMPORTED=$(git -C new rev-parse --verify refs/heads/ssh-signing) && + test $SSH_SIGNING != $IMPORTED && + git -C new cat-file commit "$IMPORTED" >actual && + test_grep -E "^gpgsig(-sha256)? " actual && + git -C new verify-commit "$IMPORTED" +' + test_done
diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh index 3d153a4..784d68b 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh
@@ -972,7 +972,7 @@ test_cmp expect actual ' -test_expect_success GPG 'setup a commit with dual signatures on its SHA-1 and SHA-256 formats' ' +test_expect_success GPG,RUST 'setup a commit with dual signatures on its SHA-1 and SHA-256 formats' ' # Create a signed SHA-256 commit git init --object-format=sha256 explicit-sha256 && git -C explicit-sha256 config extensions.compatObjectFormat sha1 && @@ -993,7 +993,7 @@ test_grep -E "^gpgsig-sha256 " out ' -test_expect_success GPG 'export and import of doubly signed commit' ' +test_expect_success GPG,RUST 'export and import of doubly signed commit' ' git -C explicit-sha256 fast-export --signed-commits=verbatim dual-signed >output && test_grep -E "^gpgsig sha1 openpgp" output && test_grep -E "^gpgsig sha256 openpgp" output &&
diff --git a/t/t9700/test.pl b/t/t9700/test.pl index 58a9b32..f83e616 100755 --- a/t/t9700/test.pl +++ b/t/t9700/test.pl
@@ -117,7 +117,12 @@ unlink $tmpfile; # paths -is($r->repo_path, $abs_repo_dir . "/.git", "repo_path"); +my $abs_git_dir = $abs_repo_dir . "/.git"; +if ($^O eq 'msys') { + $abs_git_dir = `cygpath -am "$abs_repo_dir/.git"`; + $abs_git_dir =~ s/\r?\n?$//; +} +is($r->repo_path, $abs_git_dir, "repo_path"); is($r->wc_path, $abs_repo_dir . "/", "wc_path"); is($r->wc_subdir, "", "wc_subdir initial"); $r->wc_chdir("directory1"); @@ -127,7 +132,7 @@ # Object generation in sub directory chdir("directory2"); my $r2 = Git->repository(); -is($r2->repo_path, $abs_repo_dir . "/.git", "repo_path (2)"); +is($r2->repo_path, $abs_git_dir, "repo_path (2)"); is($r2->wc_path, $abs_repo_dir . "/", "wc_path (2)"); is($r2->wc_subdir, "directory2/", "wc_subdir initial (2)");
diff --git a/t/t9812-git-p4-wildcards.sh b/t/t9812-git-p4-wildcards.sh index 254a7c2..e91004e 100755 --- a/t/t9812-git-p4-wildcards.sh +++ b/t/t9812-git-p4-wildcards.sh
@@ -30,13 +30,13 @@ test_when_finished cleanup_git && ( cd "$git" && - test -f file-wild#hash && + test_path_is_file file-wild#hash && if test_have_prereq !MINGW,!CYGWIN then - test -f file-wild\*star + test_path_is_file file-wild\*star fi && - test -f file-wild@at && - test -f file-wild%percent + test_path_is_file file-wild@at && + test_path_is_file file-wild%percent ) '
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index 964e1f1..2f9a597 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh
@@ -2601,6 +2601,7 @@ --ignore-skip-worktree-bits Z --ignore-other-worktrees Z --recurse-submodules Z + --auto-advance Z --progress Z --guess Z --no-guess Z @@ -3053,6 +3054,7 @@ submodule.sub.fetchRecurseSubmodules Z submodule.sub.ignore Z submodule.sub.active Z + submodule.sub.gitdir Z EOF '
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 52d7759..f3af10f 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh
@@ -48,6 +48,9 @@ if (n == 2) return "FAINT"; if (n == 3) return "ITALIC"; if (n == 7) return "REVERSE"; + if (n == 22) return "NORMAL_INTENSITY"; + if (n == 23) return "NOITALIC"; + if (n == 27) return "NOREVERSE"; if (n == 30) return "BLACK"; if (n == 31) return "RED"; if (n == 32) return "GREEN"; @@ -1724,7 +1727,7 @@ esac } -# Detect the hash algorithm in use. +# Detect the ref format in use. test_detect_ref_format () { echo "${GIT_TEST_DEFAULT_REF_FORMAT:-files}" }
diff --git a/t/test-lib.sh b/t/test-lib.sh index ef0ab7e..70fd3e9 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh
@@ -77,6 +77,7 @@ # want that one to complain to stderr). prepend_var ASAN_OPTIONS : $GIT_SAN_OPTIONS prepend_var ASAN_OPTIONS : detect_leaks=0 +prepend_var ASAN_OPTIONS : strict_string_checks=1 export ASAN_OPTIONS prepend_var LSAN_OPTIONS : $GIT_SAN_OPTIONS @@ -1719,7 +1720,6 @@ ( COLUMNS=1 && test $COLUMNS = 1 ) && test_set_prereq COLUMNS_CAN_BE_1 test -z "$NO_CURL" && test_set_prereq LIBCURL test -z "$NO_GITWEB" && test_set_prereq GITWEB -test -z "$NO_ICONV" && test_set_prereq ICONV test -z "$NO_PERL" && test_set_prereq PERL test -z "$NO_PTHREADS" && test_set_prereq PTHREADS test -z "$NO_PYTHON" && test_set_prereq PYTHON @@ -1730,6 +1730,17 @@ test -n "$GIT_VALGRIND_ENABLED" && test_set_prereq VALGRIND test -n "$PERL_PATH" && test_set_prereq PERL_TEST_HELPERS +test_lazy_prereq ICONV ' + # We require Git to be built with iconv support, and we require the + # iconv binary to exist. + # + # NEEDSWORK: We might eventually want to split this up into two + # prerequisites: one for NO_ICONV, and one for the iconv(1) binary, as + # some tests only depend on either of these. + test -z "$NO_ICONV" && + iconv -f utf8 -t utf8 </dev/null +' + if test -z "$GIT_TEST_CHECK_CACHE_TREE" then GIT_TEST_CHECK_CACHE_TREE=true @@ -1890,6 +1901,10 @@ test 8 -le "$(build_option sizeof-long)" ' +test_lazy_prereq RUST ' + test "$(build_option rust)" = enabled +' + test_lazy_prereq TIME_IS_64BIT 'test-tool date is64bit' test_lazy_prereq TIME_T_IS_64BIT 'test-tool date time_t-is64bit' @@ -1946,6 +1961,10 @@ GIT_TEST_MAINT_SCHEDULER="none:exit 1" export GIT_TEST_MAINT_SCHEDULER +# Ensure that tests cannot race with background maintenance by default. +GIT_TEST_MAINT_AUTO_DETACH="false" +export GIT_TEST_MAINT_AUTO_DETACH + # Does this platform support `git fsmonitor--daemon` # test_lazy_prereq FSMONITOR_DAEMON '
diff --git a/t/unit-tests/clar/.github/workflows/ci.yml b/t/unit-tests/clar/.github/workflows/ci.yml index 4d47242..14cb4ed 100644 --- a/t/unit-tests/clar/.github/workflows/ci.yml +++ b/t/unit-tests/clar/.github/workflows/ci.yml
@@ -53,7 +53,7 @@ if: matrix.platform.image == 'i386/debian:latest' run: apt -q update && apt -q -y install cmake gcc libc6-amd64 lib64stdc++6 make python3 - name: Check out - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Build shell: bash run: |
diff --git a/t/unit-tests/clar/clar.c b/t/unit-tests/clar/clar.c index d6176e5..e959a5a 100644 --- a/t/unit-tests/clar/clar.c +++ b/t/unit-tests/clar/clar.c
@@ -24,6 +24,14 @@ #include <sys/types.h> #include <sys/stat.h> +#ifndef va_copy +# ifdef __va_copy +# define va_copy(dst, src) __va_copy(dst, src) +# else +# define va_copy(dst, src) ((dst) = (src)) +# endif +#endif + #if defined(__UCLIBC__) && ! defined(__UCLIBC_HAS_WCHAR__) /* * uClibc can optionally be built without wchar support, in which case @@ -76,8 +84,10 @@ # define S_ISDIR(x) ((x & _S_IFDIR) != 0) # endif # define p_snprintf(buf,sz,fmt,...) _snprintf_s(buf,sz,_TRUNCATE,fmt,__VA_ARGS__) +# define p_vsnprintf _vsnprintf # else # define p_snprintf snprintf +# define p_vsnprintf vsnprintf # endif # define localtime_r(timer, buf) (localtime_s(buf, timer) == 0 ? buf : NULL) @@ -86,6 +96,7 @@ # include <unistd.h> # define _MAIN_CC # define p_snprintf snprintf +# define p_vsnprintf vsnprintf typedef struct stat STAT_T; #endif @@ -699,13 +710,14 @@ void clar__skip(void) abort_test(); } -void clar__fail( +static void clar__failv( const char *file, const char *function, size_t line, + int should_abort, const char *error_msg, const char *description, - int should_abort) + va_list args) { struct clar_error *error; @@ -725,9 +737,19 @@ void clar__fail( error->line_number = _clar.invoke_line ? _clar.invoke_line : line; error->error_msg = error_msg; - if (description != NULL && - (error->description = strdup(description)) == NULL) - clar_abort("Failed to allocate description.\n"); + if (description != NULL) { + va_list args_copy; + int len; + + va_copy(args_copy, args); + if ((len = p_vsnprintf(NULL, 0, description, args_copy)) < 0) + clar_abort("Failed to compute description."); + va_end(args_copy); + + if ((error->description = calloc(1, len + 1)) == NULL) + clar_abort("Failed to allocate buffer."); + p_vsnprintf(error->description, len + 1, description, args); + } _clar.total_errors++; _clar.last_report->status = CL_TEST_FAILURE; @@ -736,6 +758,34 @@ void clar__fail( abort_test(); } +void clar__failf( + const char *file, + const char *function, + size_t line, + int should_abort, + const char *error_msg, + const char *description, + ...) +{ + va_list args; + va_start(args, description); + clar__failv(file, function, line, should_abort, error_msg, + description, args); + va_end(args); +} + +void clar__fail( + const char *file, + const char *function, + size_t line, + const char *error_msg, + const char *description, + int should_abort) +{ + clar__failf(file, function, line, should_abort, error_msg, + description ? "%s" : NULL, description); +} + void clar__assert( int condition, const char *file, @@ -889,6 +939,92 @@ void clar__assert_equal( clar__fail(file, function, line, err, buf, should_abort); } +void clar__assert_compare_i( + const char *file, + const char *func, + size_t line, + int should_abort, + enum clar_comparison cmp, + intmax_t value1, + intmax_t value2, + const char *error, + const char *description, + ...) +{ + int fulfilled; + switch (cmp) { + case CLAR_COMPARISON_EQ: + fulfilled = value1 == value2; + break; + case CLAR_COMPARISON_LT: + fulfilled = value1 < value2; + break; + case CLAR_COMPARISON_LE: + fulfilled = value1 <= value2; + break; + case CLAR_COMPARISON_GT: + fulfilled = value1 > value2; + break; + case CLAR_COMPARISON_GE: + fulfilled = value1 >= value2; + break; + default: + cl_assert(0); + return; + } + + if (!fulfilled) { + va_list args; + va_start(args, description); + clar__failv(file, func, line, should_abort, error, + description, args); + va_end(args); + } +} + +void clar__assert_compare_u( + const char *file, + const char *func, + size_t line, + int should_abort, + enum clar_comparison cmp, + uintmax_t value1, + uintmax_t value2, + const char *error, + const char *description, + ...) +{ + int fulfilled; + switch (cmp) { + case CLAR_COMPARISON_EQ: + fulfilled = value1 == value2; + break; + case CLAR_COMPARISON_LT: + fulfilled = value1 < value2; + break; + case CLAR_COMPARISON_LE: + fulfilled = value1 <= value2; + break; + case CLAR_COMPARISON_GT: + fulfilled = value1 > value2; + break; + case CLAR_COMPARISON_GE: + fulfilled = value1 >= value2; + break; + default: + cl_assert(0); + return; + } + + if (!fulfilled) { + va_list args; + va_start(args, description); + clar__failv(file, func, line, should_abort, error, + description, args); + va_end(args); + } +} + void cl_set_cleanup(void (*cleanup)(void *), void *opaque) { _clar.local_cleanup = cleanup;
diff --git a/t/unit-tests/clar/clar.h b/t/unit-tests/clar/clar.h index ca72292..9ea91d3 100644 --- a/t/unit-tests/clar/clar.h +++ b/t/unit-tests/clar/clar.h
@@ -7,6 +7,7 @@ #ifndef __CLAR_TEST_H__ #define __CLAR_TEST_H__ +#include <inttypes.h> #include <stdlib.h> #include <limits.h> @@ -14,8 +15,10 @@ # define CLAR_MAX_PATH 4096 #elif defined(_WIN32) # define CLAR_MAX_PATH MAX_PATH -#else +#elif defined(PATH_MAX) # define CLAR_MAX_PATH PATH_MAX +#else +# define CLAR_MAX_PATH 4096 #endif #ifndef CLAR_SELFTEST @@ -149,6 +152,7 @@ const char *cl_fixture_basename(const char *fixture_name); * Forced failure/warning */ #define cl_fail(desc) clar__fail(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Test failed.", desc, 1) +#define cl_failf(desc,...) clar__failf(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, 1, "Test failed.", desc, __VA_ARGS__) #define cl_warning(desc) clar__fail(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, "Warning during test execution:", desc, 0) #define cl_skip() clar__skip() @@ -168,9 +172,42 @@ const char *cl_fixture_basename(const char *fixture_name); #define cl_assert_equal_wcsn(wcs1,wcs2,len) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #wcs1 " != " #wcs2, 1, "%.*ls", (wcs1), (wcs2), (int)(len)) #define cl_assert_equal_wcsn_(wcs1,wcs2,len,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%.*ls", (wcs1), (wcs2), (int)(len)) -#define cl_assert_equal_i(i1,i2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2)) -#define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2)) -#define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2)) +#define cl_assert_compare_i_(i1, i2, cmp, error, ...) clar__assert_compare_i(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, 1, cmp, \ + (i1), (i2), "Expected comparison to hold: " error, __VA_ARGS__) +#define cl_assert_compare_i(i1, i2, cmp, error, fmt) do { \ + intmax_t v1 = (i1), v2 = (i2); \ + clar__assert_compare_i(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, 1, cmp, \ + v1, v2, "Expected comparison to hold: " error, fmt, v1, v2); \ +} while (0) +#define cl_assert_equal_i_(i1, i2, ...) cl_assert_compare_i_(i1, i2, CLAR_COMPARISON_EQ, #i1 " == " #i2, __VA_ARGS__) +#define cl_assert_equal_i(i1, i2) cl_assert_compare_i (i1, i2, CLAR_COMPARISON_EQ, #i1 " == " #i2, "%"PRIdMAX " != %"PRIdMAX) +#define cl_assert_equal_i_fmt(i1, i2, fmt) cl_assert_compare_i_(i1, i2, CLAR_COMPARISON_EQ, #i1 " == " #i2, fmt " != " fmt, (int)(i1), (int)(i2)) +#define cl_assert_lt_i_(i1, i2, ...) cl_assert_compare_i_(i1, i2, CLAR_COMPARISON_LT, #i1 " < " #i2, __VA_ARGS__) +#define cl_assert_lt_i(i1, i2) cl_assert_compare_i (i1, i2, CLAR_COMPARISON_LT, #i1 " < " #i2, "%"PRIdMAX " >= %"PRIdMAX) +#define cl_assert_le_i_(i1, i2, ...) cl_assert_compare_i_(i1, i2, CLAR_COMPARISON_LE, #i1 " <= " #i2, __VA_ARGS__) +#define cl_assert_le_i(i1, i2) cl_assert_compare_i (i1, i2, CLAR_COMPARISON_LE, #i1 " <= " #i2, "%"PRIdMAX " > %"PRIdMAX) +#define cl_assert_gt_i_(i1, i2, ...) cl_assert_compare_i_(i1, i2, CLAR_COMPARISON_GT, #i1 " > " #i2, __VA_ARGS__) +#define cl_assert_gt_i(i1, i2) cl_assert_compare_i (i1, i2, CLAR_COMPARISON_GT, #i1 " > " #i2, "%"PRIdMAX " <= %"PRIdMAX) +#define cl_assert_ge_i_(i1, i2, ...) cl_assert_compare_i_(i1, i2, CLAR_COMPARISON_GE, #i1 " >= " #i2, __VA_ARGS__) +#define cl_assert_ge_i(i1, i2) cl_assert_compare_i (i1, i2, CLAR_COMPARISON_GE, #i1 " >= " #i2, "%"PRIdMAX " < %"PRIdMAX) + +#define cl_assert_compare_u_(u1, u2, cmp, error, ...) clar__assert_compare_u(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, 1, cmp, \ + (u1), (u2), "Expected comparison to hold: " error, __VA_ARGS__) +#define cl_assert_compare_u(u1, u2, cmp, error, fmt) do { \ + uintmax_t v1 = (u1), v2 = (u2); \ + clar__assert_compare_u(CLAR_CURRENT_FILE, CLAR_CURRENT_FUNC, CLAR_CURRENT_LINE, 1, cmp, \ + v1, v2, "Expected comparison to hold: " error, fmt, v1, v2); \ +} while (0) +#define cl_assert_equal_u_(u1, u2, ...) cl_assert_compare_u_(u1, u2, CLAR_COMPARISON_EQ, #u1 " == " #u2, __VA_ARGS__) +#define cl_assert_equal_u(u1, u2) cl_assert_compare_u (u1, u2, CLAR_COMPARISON_EQ, #u1 " == " #u2, "%"PRIuMAX " != %"PRIuMAX) +#define cl_assert_lt_u_(u1, u2, ...) cl_assert_compare_u_(u1, u2, CLAR_COMPARISON_LT, #u1 " < " #u2, __VA_ARGS__) +#define cl_assert_lt_u(u1, u2) cl_assert_compare_u (u1, u2, CLAR_COMPARISON_LT, #u1 " < " #u2, "%"PRIuMAX " >= %"PRIuMAX) +#define cl_assert_le_u_(u1, u2, ...) cl_assert_compare_u_(u1, u2, CLAR_COMPARISON_LE, #u1 " <= " #u2, __VA_ARGS__) +#define cl_assert_le_u(u1, u2) cl_assert_compare_u (u1, u2, CLAR_COMPARISON_LE, #u1 " <= " #u2, "%"PRIuMAX " > %"PRIuMAX) +#define cl_assert_gt_u_(u1, u2, ...) cl_assert_compare_u_(u1, u2, CLAR_COMPARISON_GT, #u1 " > " #u2, __VA_ARGS__) +#define cl_assert_gt_u(u1, u2) cl_assert_compare_u (u1, u2, CLAR_COMPARISON_GT, #u1 " > " #u2, "%"PRIuMAX " <= %"PRIuMAX) +#define cl_assert_ge_u_(u1, u2, ...) cl_assert_compare_u_(u1, u2, CLAR_COMPARISON_GE, #u1 " >= " #u2, __VA_ARGS__) +#define cl_assert_ge_u(u1, u2) cl_assert_compare_u (u1, u2, CLAR_COMPARISON_GE, #u1 " >= " #u2, "%"PRIuMAX " < %"PRIuMAX) #define cl_assert_equal_b(b1,b2) clar__assert_equal(CLAR_CURRENT_FILE,CLAR_CURRENT_FUNC,CLAR_CURRENT_LINE,#b1 " != " #b2, 1, "%d", (int)((b1) != 0),(int)((b2) != 0)) @@ -186,6 +223,15 @@ void clar__fail( const char *description, int should_abort); +void clar__failf( + const char *file, + const char *func, + size_t line, + int should_abort, + const char *error, + const char *description, + ...); + void clar__assert( int condition, const char *file, @@ -204,6 +250,38 @@ void clar__assert_equal( const char *fmt, ...); +enum clar_comparison { + CLAR_COMPARISON_EQ, + CLAR_COMPARISON_LT, + CLAR_COMPARISON_LE, + CLAR_COMPARISON_GT, + CLAR_COMPARISON_GE, +}; + +void clar__assert_compare_i( + const char *file, + const char *func, + size_t line, + int should_abort, + enum clar_comparison cmp, + intmax_t value1, + intmax_t value2, + const char *error, + const char *description, + ...); + +void clar__assert_compare_u( + const char *file, + const char *func, + size_t line, + int should_abort, + enum clar_comparison cmp, + uintmax_t value1, + uintmax_t value2, + const char *error, + const char *description, + ...); + void clar__set_invokepoint( const char *file, const char *func,
diff --git a/t/unit-tests/clar/clar/print.h b/t/unit-tests/clar/clar/print.h index 89b6659..59b7dc1 100644 --- a/t/unit-tests/clar/clar/print.h +++ b/t/unit-tests/clar/clar/print.h
@@ -127,7 +127,7 @@ static void clar_print_tap_error(int num, const struct clar_report *report, cons static void print_escaped(const char *str) { - char *c; + const char *c; while ((c = strchr(str, '\'')) != NULL) { printf("%.*s", (int)(c - str), str); @@ -164,7 +164,7 @@ static void clar_print_tap_ontest(const char *suite_name, const char *test_name, printf(" file: '"); print_escaped(error->file); printf("'\n"); printf(" line: %" PRIuMAX "\n", error->line_number); printf(" function: '%s'\n", error->function); - printf(" ---\n"); + printf(" ...\n"); } break;
diff --git a/t/unit-tests/clar/generate.py b/t/unit-tests/clar/generate.py index fd2f0ee..2357b2d 100755 --- a/t/unit-tests/clar/generate.py +++ b/t/unit-tests/clar/generate.py
@@ -8,7 +8,7 @@ from __future__ import with_statement from string import Template -import re, fnmatch, os, sys, codecs, pickle +import re, fnmatch, os, sys, codecs, pickle, io class Module(object): class Template(object): @@ -147,7 +147,7 @@ def __init__(self, path, output): self.path = path self.output = output - def should_generate(self, path): + def maybe_generate(self, path): if not os.path.isfile(path): return True @@ -223,34 +223,85 @@ def callback_count(self): return sum(len(module.callbacks) for module in self.modules.values()) def write(self): - output = os.path.join(self.output, 'clar.suite') - os.makedirs(self.output, exist_ok=True) + if not os.path.exists(self.output): + os.makedirs(self.output) - if not self.should_generate(output): + wrote_suite = self.write_suite() + wrote_header = self.write_header() + + if wrote_suite or wrote_header: + self.save_cache() + return True + + return False + + def write_output(self, fn, data): + if not self.maybe_generate(fn): return False - with open(output, 'w') as data: + current = None + + try: + with open(fn, 'r') as input: + current = input.read() + except OSError: + pass + except IOError: + pass + + if current == data: + return False + + with open(fn, 'w') as output: + output.write(data) + + return True + + def write_suite(self): + suite_fn = os.path.join(self.output, 'clar.suite') + + with io.StringIO() as suite_file: modules = sorted(self.modules.values(), key=lambda module: module.name) for module in modules: t = Module.DeclarationTemplate(module) - data.write(t.render()) + suite_file.write(t.render()) for module in modules: t = Module.CallbacksTemplate(module) - data.write(t.render()) + suite_file.write(t.render()) suites = "static struct clar_suite _clar_suites[] = {" + ','.join( Module.InfoTemplate(module).render() for module in modules ) + "\n};\n" - data.write(suites) + suite_file.write(suites) - data.write("static const size_t _clar_suite_count = %d;\n" % self.suite_count()) - data.write("static const size_t _clar_callback_count = %d;\n" % self.callback_count()) + suite_file.write(u"static const size_t _clar_suite_count = %d;\n" % self.suite_count()) + suite_file.write(u"static const size_t _clar_callback_count = %d;\n" % self.callback_count()) - self.save_cache() - return True + return self.write_output(suite_fn, suite_file.getvalue()) + + return False + + def write_header(self): + header_fn = os.path.join(self.output, 'clar_suite.h') + + with io.StringIO() as header_file: + header_file.write(u"#ifndef _____clar_suite_h_____\n") + header_file.write(u"#define _____clar_suite_h_____\n") + + modules = sorted(self.modules.values(), key=lambda module: module.name) + + for module in modules: + t = Module.DeclarationTemplate(module) + header_file.write(t.render()) + + header_file.write(u"#endif\n") + + return self.write_output(header_fn, header_file.getvalue()) + + return False if __name__ == '__main__': from optparse import OptionParser @@ -275,4 +326,4 @@ def write(self): suite.load(options.force) suite.disable(options.excluded) if suite.write(): - print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count())) + print("Written `clar.suite`, `clar_suite.h` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
diff --git a/t/unit-tests/clar/test/expected/quiet b/t/unit-tests/clar/test/expected/quiet index 280c99d..a93273b 100644 --- a/t/unit-tests/clar/test/expected/quiet +++ b/t/unit-tests/clar/test/expected/quiet
@@ -18,27 +18,57 @@ 5) Failure: combined::int [file:42] - 101 != value ("extra note on failing test") + Expected comparison to hold: 101 == value 101 != 100 6) Failure: -combined::int_fmt [file:42] - 022 != value - 0022 != 0144 +combined::int_note [file:42] + Expected comparison to hold: 101 == value + extra note on failing test 7) Failure: +combined::int_fmt [file:42] + Expected comparison to hold: 022 == value + 0022 != 0144 + + 8) Failure: combined::bool [file:42] 0 != value 0 != 1 - 8) Failure: + 9) Failure: combined::multiline_description [file:42] Function call failed: -1 description line 1 description line 2 - 9) Failure: + 10) Failure: combined::null_string [file:42] String mismatch: "expected" != actual ("this one fails") 'expected' != NULL + 11) Failure: +combined::failf [file:42] + Test failed. + some reason: foo + + 12) Failure: +combined::compare_i [file:42] + Expected comparison to hold: two < 1 + 2 >= 1 + + 13) Failure: +combined::compare_i_with_format [file:42] + Expected comparison to hold: two < 1 + foo: bar + + 14) Failure: +combined::compare_u [file:42] + Expected comparison to hold: two < 1 + 2 >= 1 + + 15) Failure: +combined::compare_u_with_format [file:42] + Expected comparison to hold: two < 1 + foo: bar +
diff --git a/t/unit-tests/clar/test/expected/summary_with_filename b/t/unit-tests/clar/test/expected/summary_with_filename index 4601607..a9471cc 100644 --- a/t/unit-tests/clar/test/expected/summary_with_filename +++ b/t/unit-tests/clar/test/expected/summary_with_filename
@@ -1,6 +1,6 @@ Loaded 1 suites: Started (test status codes: OK='.' FAILURE='F' SKIPPED='S') -FFFFFFFFF +FFFFFFFFFFFFFFF 1) Failure: combined::1 [file:42] @@ -22,28 +22,58 @@ 5) Failure: combined::int [file:42] - 101 != value ("extra note on failing test") + Expected comparison to hold: 101 == value 101 != 100 6) Failure: -combined::int_fmt [file:42] - 022 != value - 0022 != 0144 +combined::int_note [file:42] + Expected comparison to hold: 101 == value + extra note on failing test 7) Failure: +combined::int_fmt [file:42] + Expected comparison to hold: 022 == value + 0022 != 0144 + + 8) Failure: combined::bool [file:42] 0 != value 0 != 1 - 8) Failure: + 9) Failure: combined::multiline_description [file:42] Function call failed: -1 description line 1 description line 2 - 9) Failure: + 10) Failure: combined::null_string [file:42] String mismatch: "expected" != actual ("this one fails") 'expected' != NULL + 11) Failure: +combined::failf [file:42] + Test failed. + some reason: foo + + 12) Failure: +combined::compare_i [file:42] + Expected comparison to hold: two < 1 + 2 >= 1 + + 13) Failure: +combined::compare_i_with_format [file:42] + Expected comparison to hold: two < 1 + foo: bar + + 14) Failure: +combined::compare_u [file:42] + Expected comparison to hold: two < 1 + 2 >= 1 + + 15) Failure: +combined::compare_u_with_format [file:42] + Expected comparison to hold: two < 1 + foo: bar + written summary file to different.xml
diff --git a/t/unit-tests/clar/test/expected/summary_without_filename b/t/unit-tests/clar/test/expected/summary_without_filename index 7874c1d..83ba770 100644 --- a/t/unit-tests/clar/test/expected/summary_without_filename +++ b/t/unit-tests/clar/test/expected/summary_without_filename
@@ -1,6 +1,6 @@ Loaded 1 suites: Started (test status codes: OK='.' FAILURE='F' SKIPPED='S') -FFFFFFFFF +FFFFFFFFFFFFFFF 1) Failure: combined::1 [file:42] @@ -22,28 +22,58 @@ 5) Failure: combined::int [file:42] - 101 != value ("extra note on failing test") + Expected comparison to hold: 101 == value 101 != 100 6) Failure: -combined::int_fmt [file:42] - 022 != value - 0022 != 0144 +combined::int_note [file:42] + Expected comparison to hold: 101 == value + extra note on failing test 7) Failure: +combined::int_fmt [file:42] + Expected comparison to hold: 022 == value + 0022 != 0144 + + 8) Failure: combined::bool [file:42] 0 != value 0 != 1 - 8) Failure: + 9) Failure: combined::multiline_description [file:42] Function call failed: -1 description line 1 description line 2 - 9) Failure: + 10) Failure: combined::null_string [file:42] String mismatch: "expected" != actual ("this one fails") 'expected' != NULL + 11) Failure: +combined::failf [file:42] + Test failed. + some reason: foo + + 12) Failure: +combined::compare_i [file:42] + Expected comparison to hold: two < 1 + 2 >= 1 + + 13) Failure: +combined::compare_i_with_format [file:42] + Expected comparison to hold: two < 1 + foo: bar + + 14) Failure: +combined::compare_u [file:42] + Expected comparison to hold: two < 1 + 2 >= 1 + + 15) Failure: +combined::compare_u_with_format [file:42] + Expected comparison to hold: two < 1 + foo: bar + written summary file to summary.xml
diff --git a/t/unit-tests/clar/test/expected/tap b/t/unit-tests/clar/test/expected/tap index bddbd5d..e67118d 100644 --- a/t/unit-tests/clar/test/expected/tap +++ b/t/unit-tests/clar/test/expected/tap
@@ -8,7 +8,7 @@ file: 'file' line: 42 function: 'func' - --- + ... not ok 2 - combined::2 --- reason: | @@ -17,7 +17,7 @@ file: 'file' line: 42 function: 'func' - --- + ... not ok 3 - combined::strings --- reason: | @@ -27,7 +27,7 @@ file: 'file' line: 42 function: 'func' - --- + ... not ok 4 - combined::strings_with_length --- reason: | @@ -37,28 +37,38 @@ file: 'file' line: 42 function: 'func' - --- + ... not ok 5 - combined::int --- reason: | - 101 != value ("extra note on failing test") + Expected comparison to hold: 101 == value 101 != 100 at: file: 'file' line: 42 function: 'func' - --- -not ok 6 - combined::int_fmt + ... +not ok 6 - combined::int_note --- reason: | - 022 != value + Expected comparison to hold: 101 == value + extra note on failing test + at: + file: 'file' + line: 42 + function: 'func' + ... +not ok 7 - combined::int_fmt + --- + reason: | + Expected comparison to hold: 022 == value 0022 != 0144 at: file: 'file' line: 42 function: 'func' - --- -not ok 7 - combined::bool + ... +not ok 8 - combined::bool --- reason: | 0 != value @@ -67,8 +77,8 @@ file: 'file' line: 42 function: 'func' - --- -not ok 8 - combined::multiline_description + ... +not ok 9 - combined::multiline_description --- reason: | Function call failed: -1 @@ -78,8 +88,8 @@ file: 'file' line: 42 function: 'func' - --- -not ok 9 - combined::null_string + ... +not ok 10 - combined::null_string --- reason: | String mismatch: "expected" != actual ("this one fails") @@ -88,5 +98,55 @@ file: 'file' line: 42 function: 'func' + ... +not ok 11 - combined::failf --- -1..9 + reason: | + Test failed. + some reason: foo + at: + file: 'file' + line: 42 + function: 'func' + ... +not ok 12 - combined::compare_i + --- + reason: | + Expected comparison to hold: two < 1 + 2 >= 1 + at: + file: 'file' + line: 42 + function: 'func' + ... +not ok 13 - combined::compare_i_with_format + --- + reason: | + Expected comparison to hold: two < 1 + foo: bar + at: + file: 'file' + line: 42 + function: 'func' + ... +not ok 14 - combined::compare_u + --- + reason: | + Expected comparison to hold: two < 1 + 2 >= 1 + at: + file: 'file' + line: 42 + function: 'func' + ... +not ok 15 - combined::compare_u_with_format + --- + reason: | + Expected comparison to hold: two < 1 + foo: bar + at: + file: 'file' + line: 42 + function: 'func' + ... +1..15
diff --git a/t/unit-tests/clar/test/expected/without_arguments b/t/unit-tests/clar/test/expected/without_arguments index 1111d41..9891f45 100644 --- a/t/unit-tests/clar/test/expected/without_arguments +++ b/t/unit-tests/clar/test/expected/without_arguments
@@ -1,6 +1,6 @@ Loaded 1 suites: Started (test status codes: OK='.' FAILURE='F' SKIPPED='S') -FFFFFFFFF +FFFFFFFFFFFFFFF 1) Failure: combined::1 [file:42] @@ -22,27 +22,57 @@ 5) Failure: combined::int [file:42] - 101 != value ("extra note on failing test") + Expected comparison to hold: 101 == value 101 != 100 6) Failure: -combined::int_fmt [file:42] - 022 != value - 0022 != 0144 +combined::int_note [file:42] + Expected comparison to hold: 101 == value + extra note on failing test 7) Failure: +combined::int_fmt [file:42] + Expected comparison to hold: 022 == value + 0022 != 0144 + + 8) Failure: combined::bool [file:42] 0 != value 0 != 1 - 8) Failure: + 9) Failure: combined::multiline_description [file:42] Function call failed: -1 description line 1 description line 2 - 9) Failure: + 10) Failure: combined::null_string [file:42] String mismatch: "expected" != actual ("this one fails") 'expected' != NULL + 11) Failure: +combined::failf [file:42] + Test failed. + some reason: foo + + 12) Failure: +combined::compare_i [file:42] + Expected comparison to hold: two < 1 + 2 >= 1 + + 13) Failure: +combined::compare_i_with_format [file:42] + Expected comparison to hold: two < 1 + foo: bar + + 14) Failure: +combined::compare_u [file:42] + Expected comparison to hold: two < 1 + 2 >= 1 + + 15) Failure: +combined::compare_u_with_format [file:42] + Expected comparison to hold: two < 1 + foo: bar +
diff --git a/t/unit-tests/clar/test/selftest.c b/t/unit-tests/clar/test/selftest.c index eed83e4..6eadc64 100644 --- a/t/unit-tests/clar/test/selftest.c +++ b/t/unit-tests/clar/test/selftest.c
@@ -298,7 +298,7 @@ void test_selftest__help(void) void test_selftest__without_arguments(void) { - cl_invoke(assert_output("combined", "without_arguments", 9, NULL)); + cl_invoke(assert_output("combined", "without_arguments", 15, NULL)); } void test_selftest__specific_test(void) @@ -313,12 +313,12 @@ void test_selftest__stop_on_failure(void) void test_selftest__quiet(void) { - cl_invoke(assert_output("combined", "quiet", 9, "-q", NULL)); + cl_invoke(assert_output("combined", "quiet", 15, "-q", NULL)); } void test_selftest__tap(void) { - cl_invoke(assert_output("combined", "tap", 9, "-t", NULL)); + cl_invoke(assert_output("combined", "tap", 15, "-t", NULL)); } void test_selftest__suite_names(void) @@ -329,7 +329,7 @@ void test_selftest__suite_names(void) void test_selftest__summary_without_filename(void) { struct stat st; - cl_invoke(assert_output("combined", "summary_without_filename", 9, "-r", NULL)); + cl_invoke(assert_output("combined", "summary_without_filename", 15, "-r", NULL)); /* The summary contains timestamps, so we cannot verify its contents. */ cl_must_pass(stat("summary.xml", &st)); } @@ -337,7 +337,7 @@ void test_selftest__summary_without_filename(void) void test_selftest__summary_with_filename(void) { struct stat st; - cl_invoke(assert_output("combined", "summary_with_filename", 9, "-rdifferent.xml", NULL)); + cl_invoke(assert_output("combined", "summary_with_filename", 15, "-rdifferent.xml", NULL)); /* The summary contains timestamps, so we cannot verify its contents. */ cl_must_pass(stat("different.xml", &st)); }
diff --git a/t/unit-tests/clar/test/suites/combined.c b/t/unit-tests/clar/test/suites/combined.c index e8b41c9..9e9dbc2 100644 --- a/t/unit-tests/clar/test/suites/combined.c +++ b/t/unit-tests/clar/test/suites/combined.c
@@ -55,7 +55,12 @@ void test_combined__strings_with_length(void) void test_combined__int(void) { int value = 100; - cl_assert_equal_i(100, value); + cl_assert_equal_i(101, value); +} + +void test_combined__int_note(void) +{ + int value = 100; cl_assert_equal_i_(101, value, "extra note on failing test"); } @@ -83,3 +88,61 @@ void test_combined__null_string(void) cl_assert_equal_s(actual, actual); cl_assert_equal_s_("expected", actual, "this one fails"); } + +void test_combined__failf(void) +{ + cl_failf("some reason: %s", "foo"); +} + +void test_combined__compare_i(void) +{ + int one = 1, two = 2; + + cl_assert_equal_i(one, 1); + cl_assert_equal_i(one, 1); + cl_assert_equal_i_(one, 1, "format"); + cl_assert_lt_i(one, 2); + cl_assert_lt_i_(one, 2, "format"); + cl_assert_le_i(one, 2); + cl_assert_le_i(two, 2); + cl_assert_le_i_(two, 2, "format"); + cl_assert_gt_i(two, 1); + cl_assert_gt_i_(two, 1, "format"); + cl_assert_ge_i(two, 2); + cl_assert_ge_i(3, two); + cl_assert_ge_i_(3, two, "format"); + + cl_assert_lt_i(two, 1); /* this one fails */ +} + +void test_combined__compare_i_with_format(void) +{ + int two = 2; + cl_assert_lt_i_(two, 1, "foo: %s", "bar"); +} + +void test_combined__compare_u(void) +{ + unsigned one = 1, two = 2; + + cl_assert_equal_u(one, 1); + cl_assert_equal_u_(one, 1, "format"); + cl_assert_lt_u(one, 2); + cl_assert_lt_u_(one, 2, "format"); + cl_assert_le_u(one, 2); + cl_assert_le_u(two, 2); + cl_assert_le_u_(two, 2, "format"); + cl_assert_gt_u(two, 1); + cl_assert_gt_u_(two, 1, "format"); + cl_assert_ge_u(two, 2); + cl_assert_ge_u(3, two); + cl_assert_ge_u_(3, two, "format"); + + cl_assert_lt_u(two, 1); /* this one fails */ +} + +void test_combined__compare_u_with_format(void) +{ + unsigned two = 2; + cl_assert_lt_u_(two, 1, "foo: %s", "bar"); +}
diff --git a/t/unit-tests/test-lib.c b/t/unit-tests/test-lib.c index 87e1f5c..72ee20a 100644 --- a/t/unit-tests/test-lib.c +++ b/t/unit-tests/test-lib.c
@@ -396,8 +396,23 @@ int check_uint_loc(const char *loc, const char *check, int ok, static void print_one_char(char ch, char quote) { if ((unsigned char)ch < 0x20u || ch == 0x7f) { - /* TODO: improve handling of \a, \b, \f ... */ - printf("\\%03o", (unsigned char)ch); + char esc; + switch (ch) { + case '\a': esc = 'a'; break; + case '\b': esc = 'b'; break; + case '\t': esc = 't'; break; + case '\n': esc = 'n'; break; + case '\v': esc = 'v'; break; + case '\f': esc = 'f'; break; + case '\r': esc = 'r'; break; + default: esc = 0; break; + } + if (esc) { + putc('\\', stdout); + putc(esc, stdout); + } else { + printf("\\%03o", (unsigned char)ch); + } } else { if (ch == '\\' || ch == quote) putc('\\', stdout);
diff --git a/t/unit-tests/u-list-objects-filter-options.c b/t/unit-tests/u-list-objects-filter-options.c new file mode 100644 index 0000000..f7d7370 --- /dev/null +++ b/t/unit-tests/u-list-objects-filter-options.c
@@ -0,0 +1,53 @@ +#include "unit-test.h" +#include "list-objects-filter-options.h" +#include "strbuf.h" + +/* Helper to test gently_parse_list_objects_filter() */ +static void check_gentle_parse(const char *filter_spec, + int expect_success, + int allow_auto, + enum list_objects_filter_choice expected_choice) +{ + struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT; + struct strbuf errbuf = STRBUF_INIT; + int ret; + + filter_options.allow_auto_filter = allow_auto; + + ret = gently_parse_list_objects_filter(&filter_options, filter_spec, &errbuf); + + if (expect_success) { + cl_assert_equal_i(ret, 0); + cl_assert_equal_i(expected_choice, filter_options.choice); + cl_assert_equal_i(errbuf.len, 0); + } else { + cl_assert(ret != 0); + cl_assert(errbuf.len > 0); + } + + strbuf_release(&errbuf); + list_objects_filter_release(&filter_options); +} + +void test_list_objects_filter_options__regular_filters(void) +{ + check_gentle_parse("blob:none", 1, 0, LOFC_BLOB_NONE); + check_gentle_parse("blob:none", 1, 1, LOFC_BLOB_NONE); + check_gentle_parse("blob:limit=5k", 1, 0, LOFC_BLOB_LIMIT); + check_gentle_parse("blob:limit=5k", 1, 1, LOFC_BLOB_LIMIT); + check_gentle_parse("combine:blob:none+tree:0", 1, 0, LOFC_COMBINE); + check_gentle_parse("combine:blob:none+tree:0", 1, 1, LOFC_COMBINE); +} + +void test_list_objects_filter_options__auto_allowed(void) +{ + check_gentle_parse("auto", 1, 1, LOFC_AUTO); + check_gentle_parse("auto", 0, 0, 0); +} + +void test_list_objects_filter_options__combine_auto_fails(void) +{ + check_gentle_parse("combine:auto+blob:none", 0, 1, 0); + check_gentle_parse("combine:blob:none+auto", 0, 1, 0); + check_gentle_parse("combine:auto+auto", 0, 1, 0); +}
diff --git a/t/unit-tests/u-oidmap.c b/t/unit-tests/u-oidmap.c index b23af44..00481df 100644 --- a/t/unit-tests/u-oidmap.c +++ b/t/unit-tests/u-oidmap.c
@@ -14,6 +14,13 @@ struct test_entry { char name[FLEX_ARRAY]; }; +static int freed; + +static void test_free_fn(void *p) { + freed++; + free(p); +} + static const char *const key_val[][2] = { { "11", "one" }, { "22", "two" }, { "33", "three" } }; @@ -134,3 +141,37 @@ void test_oidmap__iterate(void) cl_assert_equal_i(count, ARRAY_SIZE(key_val)); cl_assert_equal_i(hashmap_get_size(&map.map), ARRAY_SIZE(key_val)); } + +void test_oidmap__clear_without_free_callback(void) +{ + struct oidmap local_map = OIDMAP_INIT; + struct test_entry *entry; + + freed = 0; + + FLEX_ALLOC_STR(entry, name, "one"); + cl_parse_any_oid("11", &entry->entry.oid); + cl_assert(oidmap_put(&local_map, entry) == NULL); + + oidmap_clear_with_free(&local_map, NULL); + + cl_assert_equal_i(freed, 0); + + free(entry); +} + +void test_oidmap__clear_with_free_callback(void) +{ + struct oidmap local_map = OIDMAP_INIT; + struct test_entry *entry; + + freed = 0; + + FLEX_ALLOC_STR(entry, name, "one"); + cl_parse_any_oid("11", &entry->entry.oid); + cl_assert(oidmap_put(&local_map, entry) == NULL); + + oidmap_clear_with_free(&local_map, test_free_fn); + + cl_assert_equal_i(freed, 1); +}
diff --git a/t/unit-tests/u-oidtree.c b/t/unit-tests/u-oidtree.c index e6eede2..d4d05c7 100644 --- a/t/unit-tests/u-oidtree.c +++ b/t/unit-tests/u-oidtree.c
@@ -24,7 +24,7 @@ static int fill_tree_loc(struct oidtree *ot, const char *hexes[], size_t n) return 0; } -static void check_contains(struct oidtree *ot, const char *hex, int expected) +static void check_contains(struct oidtree *ot, const char *hex, bool expected) { struct object_id oid; @@ -38,7 +38,7 @@ struct expected_hex_iter { const char *query; }; -static enum cb_next check_each_cb(const struct object_id *oid, void *data) +static int check_each_cb(const struct object_id *oid, void *data) { struct expected_hex_iter *hex_iter = data; struct object_id expected; @@ -49,7 +49,7 @@ static enum cb_next check_each_cb(const struct object_id *oid, void *data) &expected); cl_assert_equal_s(oid_to_hex(oid), oid_to_hex(&expected)); hex_iter->i += 1; - return CB_CONTINUE; + return 0; } LAST_ARG_MUST_BE_NULL @@ -88,12 +88,12 @@ void test_oidtree__cleanup(void) void test_oidtree__contains(void) { FILL_TREE(&ot, "444", "1", "2", "3", "4", "5", "a", "b", "c", "d", "e"); - check_contains(&ot, "44", 0); - check_contains(&ot, "441", 0); - check_contains(&ot, "440", 0); - check_contains(&ot, "444", 1); - check_contains(&ot, "4440", 1); - check_contains(&ot, "4444", 0); + check_contains(&ot, "44", false); + check_contains(&ot, "441", false); + check_contains(&ot, "440", false); + check_contains(&ot, "444", true); + check_contains(&ot, "4440", true); + check_contains(&ot, "4444", false); } void test_oidtree__each(void)
diff --git a/t/unit-tests/u-reftable-record.c b/t/unit-tests/u-reftable-record.c index 6c8c0d5..1bf2e17 100644 --- a/t/unit-tests/u-reftable-record.c +++ b/t/unit-tests/u-reftable-record.c
@@ -51,10 +51,10 @@ void test_reftable_record__varint_roundtrip(void) int n = put_var_int(&out, in); uint64_t got = 0; - cl_assert(n > 0); + cl_assert_gt_i(n, 0); out.len = n; n = get_var_int(&got, &out); - cl_assert(n > 0); + cl_assert_gt_i(n, 0); cl_assert_equal_i(got, in); } @@ -110,7 +110,7 @@ void test_reftable_record__ref_record_comparison(void) cl_assert(reftable_record_equal(&in[1], &in[2], REFTABLE_HASH_SIZE_SHA1) == 0); cl_assert_equal_i(reftable_record_cmp(&in[1], &in[2], &cmp), 0); - cl_assert(cmp > 0); + cl_assert_gt_i(cmp, 0); in[1].u.ref.value_type = in[0].u.ref.value_type; cl_assert(reftable_record_equal(&in[0], &in[1], @@ -184,7 +184,7 @@ void test_reftable_record__ref_record_roundtrip(void) reftable_record_key(&in, &key); n = reftable_record_encode(&in, dest, REFTABLE_HASH_SIZE_SHA1); - cl_assert(n > 0); + cl_assert_gt_i(n, 0); /* decode into a non-zero reftable_record to test for leaks. */ m = reftable_record_decode(&out, key, i, dest, REFTABLE_HASH_SIZE_SHA1, &scratch); @@ -228,11 +228,11 @@ void test_reftable_record__log_record_comparison(void) cl_assert_equal_i(reftable_record_equal(&in[1], &in[2], REFTABLE_HASH_SIZE_SHA1), 0); cl_assert_equal_i(reftable_record_cmp(&in[1], &in[2], &cmp), 0); - cl_assert(cmp > 0); + cl_assert_gt_i(cmp, 0); /* comparison should be reversed for equal keys, because * comparison is now performed on the basis of update indices */ cl_assert_equal_i(reftable_record_cmp(&in[0], &in[1], &cmp), 0); - cl_assert(cmp < 0); + cl_assert_lt_i(cmp, 0); in[1].u.log.update_index = in[0].u.log.update_index; cl_assert(reftable_record_equal(&in[0], &in[1], @@ -344,7 +344,7 @@ void test_reftable_record__log_record_roundtrip(void) reftable_record_key(&rec, &key); n = reftable_record_encode(&rec, dest, REFTABLE_HASH_SIZE_SHA1); - cl_assert(n >= 0); + cl_assert_ge_i(n, 0); valtype = reftable_record_val_type(&rec); m = reftable_record_decode(&out, key, valtype, dest, REFTABLE_HASH_SIZE_SHA1, &scratch); @@ -382,7 +382,7 @@ void test_reftable_record__key_roundtrip(void) extra = 6; n = reftable_encode_key(&restart, dest, last_key, key, extra); cl_assert(!restart); - cl_assert(n > 0); + cl_assert_gt_i(n, 0); cl_assert_equal_i(reftable_buf_addstr(&roundtrip, "refs/heads/master"), 0); @@ -432,7 +432,7 @@ void test_reftable_record__obj_record_comparison(void) cl_assert_equal_i(reftable_record_equal(&in[1], &in[2], REFTABLE_HASH_SIZE_SHA1), 0); cl_assert_equal_i(reftable_record_cmp(&in[1], &in[2], &cmp), 0); - cl_assert(cmp > 0); + cl_assert_gt_i(cmp, 0); in[1].u.obj.offset_len = in[0].u.obj.offset_len; cl_assert(reftable_record_equal(&in[0], &in[1], REFTABLE_HASH_SIZE_SHA1) != 0); @@ -485,7 +485,7 @@ void test_reftable_record__obj_record_roundtrip(void) t_copy(&in); reftable_record_key(&in, &key); n = reftable_record_encode(&in, dest, REFTABLE_HASH_SIZE_SHA1); - cl_assert(n > 0); + cl_assert_gt_i(n, 0); extra = reftable_record_val_type(&in); m = reftable_record_decode(&out, key, extra, dest, REFTABLE_HASH_SIZE_SHA1, &scratch); @@ -535,7 +535,7 @@ void test_reftable_record__index_record_comparison(void) cl_assert_equal_i(reftable_record_equal(&in[1], &in[2], REFTABLE_HASH_SIZE_SHA1), 0); cl_assert_equal_i(reftable_record_cmp(&in[1], &in[2], &cmp), 0); - cl_assert(cmp > 0); + cl_assert_gt_i(cmp, 0); in[1].u.idx.offset = in[0].u.idx.offset; cl_assert(reftable_record_equal(&in[0], &in[1],
diff --git a/t/unit-tests/u-reftable-stack.c b/t/unit-tests/u-reftable-stack.c index a8b9181..b8110cd 100644 --- a/t/unit-tests/u-reftable-stack.c +++ b/t/unit-tests/u-reftable-stack.c
@@ -1067,6 +1067,7 @@ void test_reftable_stack__add_performs_auto_compaction(void) .value_type = REFTABLE_REF_SYMREF, .value.symref = (char *) "master", }; + bool required = false; char buf[128]; /* @@ -1087,10 +1088,17 @@ void test_reftable_stack__add_performs_auto_compaction(void) * auto compaction is disabled. When enabled, we should merge * all tables in the stack. */ - if (i != n) + cl_assert_equal_i(reftable_stack_compaction_required(st, true, &required), 0); + if (i != n) { cl_assert_equal_i(st->merged->tables_len, i + 1); - else + if (i < 1) + cl_assert_equal_b(required, false); + else + cl_assert_equal_b(required, true); + } else { cl_assert_equal_i(st->merged->tables_len, 1); + cl_assert_equal_b(required, false); + } } reftable_stack_destroy(st);
diff --git a/t/unit-tests/u-string-list.c b/t/unit-tests/u-string-list.c index a2457d7..7ad84cc 100644 --- a/t/unit-tests/u-string-list.c +++ b/t/unit-tests/u-string-list.c
@@ -243,6 +243,138 @@ void test_string_list__filter(void) t_string_list_clear(&list, 0); } +static void t_string_list_has_string( + struct string_list *list, + const char *string, + int expected) +{ + int has_string = string_list_has_string(list, string); + cl_assert_equal_i(has_string, expected); +} + +void test_string_list__has_string(void) +{ + struct string_list list = STRING_LIST_INIT_DUP; + + t_create_string_list_dup(&list, 0, NULL); + t_string_list_has_string(&list, "", 0); + + t_create_string_list_dup(&list, 0, "a", "b", "c", NULL); + t_string_list_has_string(&list, "a", 1); + t_string_list_has_string(&list, "b", 1); + t_string_list_has_string(&list, "c", 1); + t_string_list_has_string(&list, "d", 0); + + t_string_list_clear(&list, 0); +} + +static void t_string_list_insert(struct string_list *expected_strings, ...) +{ + struct string_list strings_to_insert = STRING_LIST_INIT_DUP; + struct string_list list = STRING_LIST_INIT_DUP; + va_list ap; + + va_start(ap, expected_strings); + t_vcreate_string_list_dup(&strings_to_insert, 0, ap); + va_end(ap); + + for (size_t i = 0; i < strings_to_insert.nr; i++) + string_list_insert(&list, strings_to_insert.items[i].string); + + t_string_list_equal(&list, expected_strings); + + string_list_clear(&strings_to_insert, 0); + string_list_clear(&list, 0); +} + +void test_string_list__insert(void) +{ + struct string_list expected_strings = STRING_LIST_INIT_DUP; + + t_create_string_list_dup(&expected_strings, 0, NULL); + t_string_list_insert(&expected_strings, NULL); + + t_create_string_list_dup(&expected_strings, 0, "a", "b", NULL); + t_string_list_insert(&expected_strings, "b", "a", "a", "b", NULL); + + t_create_string_list_dup(&expected_strings, 0, "a", "b", "c", NULL); + t_string_list_insert(&expected_strings, "c", "b", "a", "c", "b", NULL); + + t_create_string_list_dup(&expected_strings, 0, "", "a", NULL); + t_string_list_insert(&expected_strings, "a", "a", "a", "", NULL); + + t_string_list_clear(&expected_strings, 0); +} + +static void t_string_list_sort(struct string_list *list, ...) +{ + struct string_list expected_strings = STRING_LIST_INIT_DUP; + va_list ap; + + va_start(ap, list); + t_vcreate_string_list_dup(&expected_strings, 0, ap); + va_end(ap); + + string_list_sort(list); + t_string_list_equal(list, &expected_strings); + + string_list_clear(&expected_strings, 0); +} + +void test_string_list__sort(void) +{ + struct string_list list = STRING_LIST_INIT_DUP; + + t_create_string_list_dup(&list, 0, NULL); + t_string_list_sort(&list, NULL); + + t_create_string_list_dup(&list, 0, "b", "", "a", NULL); + t_string_list_sort(&list, "", "a", "b", NULL); + + t_create_string_list_dup(&list, 0, "c", "a", "b", "a", NULL); + t_string_list_sort(&list, "a", "a", "b", "c", NULL); + + t_string_list_clear(&list, 0); +} + +static void t_string_list_remove( + struct string_list *expected_strings, + struct string_list *list, + char const *str) +{ + string_list_remove(list, str, 0); + t_string_list_equal(list, expected_strings); +} + +void test_string_list__remove(void) +{ + struct string_list expected_strings = STRING_LIST_INIT_DUP; + struct string_list list = STRING_LIST_INIT_DUP; + + t_create_string_list_dup(&expected_strings, 0, NULL); + t_create_string_list_dup(&list, 0, NULL); + t_string_list_remove(&expected_strings, &list, ""); + + t_create_string_list_dup(&expected_strings, 0, "a", NULL); + t_create_string_list_dup(&list, 0, "a", "a", NULL); + t_string_list_remove(&expected_strings, &list, "a"); + + t_create_string_list_dup(&expected_strings, 0, "a", "b", "b", NULL); + t_create_string_list_dup(&list, 0, "a", "b", "b", "c", NULL); + t_string_list_remove(&expected_strings, &list, "c"); + + t_create_string_list_dup(&expected_strings, 0, "a", "b", "d", NULL); + t_create_string_list_dup(&list, 0, "a", "b", "c", "d", NULL); + t_string_list_remove(&expected_strings, &list, "c"); + + t_create_string_list_dup(&expected_strings, 0, "a", "b", "c", "d", NULL); + t_create_string_list_dup(&list, 0, "a", "b", "c", "d", NULL); + t_string_list_remove(&expected_strings, &list, "e"); + + t_string_list_clear(&expected_strings, 0); + t_string_list_clear(&list, 0); +} + static void t_string_list_remove_duplicates(struct string_list *list, ...) { struct string_list expected_strings = STRING_LIST_INIT_DUP; @@ -304,3 +436,114 @@ void test_string_list__remove_duplicates(void) t_string_list_clear(&list, 0); } + +static void t_string_list_sort_u(struct string_list *list, ...) +{ + struct string_list expected_strings = STRING_LIST_INIT_DUP; + va_list ap; + + va_start(ap, list); + t_vcreate_string_list_dup(&expected_strings, 0, ap); + va_end(ap); + + string_list_sort_u(list, 0); + t_string_list_equal(list, &expected_strings); + + string_list_clear(&expected_strings, 0); +} + +void test_string_list__sort_u(void) +{ + struct string_list list = STRING_LIST_INIT_DUP; + + t_create_string_list_dup(&list, 0, NULL); + t_string_list_sort_u(&list, NULL); + + t_create_string_list_dup(&list, 0, "", "", "", "", NULL); + t_string_list_sort_u(&list, "", NULL); + + t_create_string_list_dup(&list, 0, "b", "a", "a", "", NULL); + t_string_list_sort_u(&list, "", "a", "b", NULL); + + t_create_string_list_dup(&list, 0, "b", "a", "a", "d", "c", "c", NULL); + t_string_list_sort_u(&list, "a", "b", "c", "d", NULL); + + t_string_list_clear(&list, 0); +} + +static void t_string_list_remove_empty_items( + struct string_list *expected_strings, + struct string_list *list) +{ + string_list_remove_empty_items(list, 0); + t_string_list_equal(list, expected_strings); +} + +void test_string_list__remove_empty_items(void) +{ + struct string_list expected_strings = STRING_LIST_INIT_DUP; + struct string_list list = STRING_LIST_INIT_DUP; + + t_create_string_list_dup(&expected_strings, 0, NULL); + t_create_string_list_dup(&list, 0, "", "", "", NULL); + t_string_list_remove_empty_items(&expected_strings, &list); + + t_create_string_list_dup(&expected_strings, 0, "a", "b", NULL); + t_create_string_list_dup(&list, 0, "a", "", "b", "", NULL); + t_string_list_remove_empty_items(&expected_strings, &list); + + t_string_list_clear(&expected_strings, 0); + t_string_list_clear(&list, 0); +} + +static void t_string_list_unsorted_string_list_has_string( + struct string_list *list, + const char *str, int expected) +{ + int has_string = unsorted_string_list_has_string(list, str); + cl_assert_equal_i(has_string, expected); +} + +void test_string_list__unsorted_string_list_has_string(void) +{ + struct string_list list = STRING_LIST_INIT_DUP; + + t_create_string_list_dup(&list, 0, "b", "d", "a", NULL); + t_string_list_unsorted_string_list_has_string(&list, "a", 1); + t_string_list_unsorted_string_list_has_string(&list, "b", 1); + t_string_list_unsorted_string_list_has_string(&list, "c", 0); + t_string_list_unsorted_string_list_has_string(&list, "d", 1); + + t_string_list_clear(&list, 0); +} + +static void t_string_list_unsorted_string_list_delete_item( + struct string_list *expected_list, + struct string_list *list, + int i) +{ + unsorted_string_list_delete_item(list, i, 0); + + t_string_list_equal(list, expected_list); +} + +void test_string_list__unsorted_string_list_delete_item(void) +{ + struct string_list expected_strings = STRING_LIST_INIT_DUP; + struct string_list list = STRING_LIST_INIT_DUP; + + t_create_string_list_dup(&expected_strings, 0, "a", "c", "b", NULL); + t_create_string_list_dup(&list, 0, "a", "d", "b", "c", NULL); + t_string_list_unsorted_string_list_delete_item(&expected_strings, &list, 1); + + t_create_string_list_dup(&expected_strings, 0, NULL); + t_create_string_list_dup(&list, 0, "", NULL); + t_string_list_unsorted_string_list_delete_item(&expected_strings, &list, 0); + + t_create_string_list_dup(&expected_strings, 0, "a", "d", "c", "b", NULL); + t_create_string_list_dup(&list, 0, "a", "d", "c", "b", "d", NULL); + t_string_list_unsorted_string_list_delete_item(&expected_strings, &list, 4); + + t_string_list_clear(&expected_strings, 0); + t_string_list_clear(&list, 0); +}
diff --git a/t/unit-tests/u-utf8-width.c b/t/unit-tests/u-utf8-width.c new file mode 100644 index 0000000..86e09c3 --- /dev/null +++ b/t/unit-tests/u-utf8-width.c
@@ -0,0 +1,134 @@ +#include "unit-test.h" +#include "utf8.h" +#include "strbuf.h" + +/* + * Test utf8_strnwidth with various Chinese strings + * Chinese characters typically have a width of 2 columns when displayed + */ +void test_utf8_width__strnwidth_chinese(void) +{ + const char *str; + + /* Test basic ASCII - each character should have width 1 */ + cl_assert_equal_i(5, utf8_strnwidth("Hello", 5, 0)); + /* skip_ansi = 1 */ + cl_assert_equal_i(5, utf8_strnwidth("Hello", 5, 1)); + + /* Test simple Chinese characters - each should have width 2 */ + /* "你好" is 6 bytes (3 bytes per char in UTF-8), 4 display columns */ + cl_assert_equal_i(4, utf8_strnwidth("你好", 6, 0)); + + /* Test mixed ASCII and Chinese - ASCII = 1 column, Chinese = 2 columns */ + /* "h"(1) + "i"(1) + "你"(2) + "好"(2) = 6 */ + cl_assert_equal_i(6, utf8_strnwidth("Hi你好", 8, 0)); + + /* Test longer Chinese string */ + /* 5 Chinese chars = 10 display columns */ + cl_assert_equal_i(10, utf8_strnwidth("你好世界!", 15, 0)); + + /* Test individual Chinese character width */ + cl_assert_equal_i(2, utf8_strnwidth("中", 3, 0)); + + /* Test empty string */ + cl_assert_equal_i(0, utf8_strnwidth("", 0, 0)); + + /* Test length limiting */ + str = "你好世界"; + /* Only first char "你"(2 columns) within 3 bytes */ + cl_assert_equal_i(2, utf8_strnwidth(str, 3, 0)); + /* First two chars "你好"(4 columns) in 6 bytes */ + cl_assert_equal_i(4, utf8_strnwidth(str, 6, 0)); +} + +/* + * Tests for utf8_strwidth (simpler version without length limit) + */ +void test_utf8_width__strwidth_chinese(void) +{ + /* Test basic ASCII */ + cl_assert_equal_i(5, utf8_strwidth("Hello")); + + /* Test Chinese characters */ + /* 2 Chinese chars = 4 display columns */ + cl_assert_equal_i(4, utf8_strwidth("你好")); + + /* Test longer Chinese string */ + /* 5 Chinese chars = 10 display columns */ + cl_assert_equal_i(10, utf8_strwidth("你好世界!")); + + /* Test mixed ASCII and Chinese */ + /* 5 ASCII (5 cols) + 2 Chinese (4 cols) = 9 */ + cl_assert_equal_i(9, utf8_strwidth("Hello世界")); + /* 2 ASCII (2 cols) + 2 Chinese (4 cols) + 1 ASCII (1 col) = 7 */ + cl_assert_equal_i(7, utf8_strwidth("Hi世界!")); +} + +/* + * Additional tests with other East Asian characters + */ +void test_utf8_width__strnwidth_japanese_korean(void) +{ + /* Japanese characters (should also be 2 columns each) */ + /* 5 Japanese chars x 2 cols each = 10 display columns */ + cl_assert_equal_i(10, utf8_strnwidth("こんにちは", 15, 0)); + + /* Korean characters (should also be 2 columns each) */ + /* 5 Korean chars x 2 cols each = 10 display columns */ + cl_assert_equal_i(10, utf8_strnwidth("안녕하세요", 15, 0)); +} + +/* + * Test utf8_strnwidth with CJK strings and ANSI sequences + */ +void test_utf8_width__strnwidth_cjk_with_ansi(void) +{ + /* Test CJK with ANSI sequences */ + const char *ansi_test = "\033[1m你好\033[0m"; + int width = utf8_strnwidth(ansi_test, strlen(ansi_test), 1); + /* Should skip ANSI sequences and count "你好" as 4 columns */ + cl_assert_equal_i(4, width); + + /* Test mixed ASCII, CJK, and ANSI */ + ansi_test = "Hello\033[32m世界\033[0m!"; + width = utf8_strnwidth(ansi_test, strlen(ansi_test), 1); + /* "Hello"(5) + "世界"(4) + "!"(1) = 10 */ + cl_assert_equal_i(10, width); +} + +/* + * Test the strbuf_utf8_align function with CJK characters + */ +void test_utf8_width__strbuf_utf8_align(void) +{ + struct strbuf buf = STRBUF_INIT; + + /* Test left alignment with CJK */ + strbuf_utf8_align(&buf, ALIGN_LEFT, 10, "你好"); + /* Since "你好" is 4 display columns, we need 6 more spaces to reach 10 */ + cl_assert_equal_s("你好 ", buf.buf); + strbuf_reset(&buf); + + /* Test right alignment with CJK */ + strbuf_utf8_align(&buf, ALIGN_RIGHT, 8, "世界"); + /* "世界" is 4 display columns, so we need 4 leading spaces */ + cl_assert_equal_s(" 世界", buf.buf); + strbuf_reset(&buf); + + /* Test center alignment with CJK */ + strbuf_utf8_align(&buf, ALIGN_MIDDLE, 10, "中"); + /* "中" is 2 display columns, so (10-2)/2 = 4 spaces on left, 4 on right */ + cl_assert_equal_s(" 中 ", buf.buf); + strbuf_reset(&buf); + + strbuf_utf8_align(&buf, ALIGN_MIDDLE, 5, "中"); + /* "中" is 2 display columns, so (5-2)/2 = 1 spaces on left, 2 on right */ + cl_assert_equal_s(" 中 ", buf.buf); + strbuf_reset(&buf); + + /* Test alignment that is smaller than string width */ + strbuf_utf8_align(&buf, ALIGN_LEFT, 2, "你好"); + /* Since "你好" is 4 display columns, it should not be truncated */ + cl_assert_equal_s("你好", buf.buf); + strbuf_release(&buf); +}
diff --git a/t/unit-tests/unit-test.c b/t/unit-tests/unit-test.c index 5af6450..752fb38 100644 --- a/t/unit-tests/unit-test.c +++ b/t/unit-tests/unit-test.c
@@ -29,6 +29,7 @@ int cmd_main(int argc, const char **argv) OPT_NOOP_NOARG('d', "debug"), OPT_NOOP_NOARG(0, "github-workflow-markup"), OPT_NOOP_NOARG(0, "no-bin-wrappers"), + OPT_NOOP_ARG(0, "no-chain-lint"), OPT_NOOP_ARG(0, "root"), OPT_NOOP_ARG(0, "stress"), OPT_NOOP_NOARG(0, "tee"),
diff --git a/t/unit-tests/unit-test.h b/t/unit-tests/unit-test.h index 39a0b72..5398b44 100644 --- a/t/unit-tests/unit-test.h +++ b/t/unit-tests/unit-test.h
@@ -7,9 +7,3 @@ #else # include GIT_CLAR_DECLS_H #endif - -#define cl_failf(fmt, ...) do { \ - char desc[4096]; \ - snprintf(desc, sizeof(desc), fmt, __VA_ARGS__); \ - clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1); \ -} while (0)
diff --git a/tag.c b/tag.c index 1d52686..2f12e51 100644 --- a/tag.c +++ b/tag.c
@@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" @@ -13,6 +12,7 @@ #include "gpg-interface.h" #include "hex.h" #include "packfile.h" +#include "repository.h" const char *tag_type = "tag"; @@ -44,28 +44,28 @@ static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags) return ret; } -int gpg_verify_tag(const struct object_id *oid, const char *name_to_report, - unsigned flags) +int gpg_verify_tag(struct repository *r, const struct object_id *oid, + const char *name_to_report, unsigned flags) { enum object_type type; char *buf; unsigned long size; int ret; - type = odb_read_object_info(the_repository->objects, oid, NULL); + type = odb_read_object_info(r->objects, oid, NULL); if (type != OBJ_TAG) return error("%s: cannot verify a non-tag object of type %s.", name_to_report ? name_to_report : - repo_find_unique_abbrev(the_repository, oid, DEFAULT_ABBREV), + oid_to_hex(oid), type_name(type)); - buf = odb_read_object(the_repository->objects, oid, &type, &size); + buf = odb_read_object(r->objects, oid, &type, &size); if (!buf) return error("%s: unable to read file.", name_to_report ? name_to_report : - repo_find_unique_abbrev(the_repository, oid, DEFAULT_ABBREV)); + oid_to_hex(oid)); ret = run_gpg_verify(buf, size, flags); @@ -94,18 +94,6 @@ struct object *deref_tag(struct repository *r, struct object *o, const char *war return o; } -struct object *deref_tag_noverify(struct repository *r, struct object *o) -{ - while (o && o->type == OBJ_TAG) { - o = parse_object(r, &o->oid); - if (o && o->type == OBJ_TAG && ((struct tag *)o)->tagged) - o = ((struct tag *)o)->tagged; - else - o = NULL; - } - return o; -} - struct tag *lookup_tag(struct repository *r, const struct object_id *oid) { struct object *obj = lookup_object(r, oid); @@ -160,9 +148,11 @@ int parse_tag_buffer(struct repository *r, struct tag *item, const void *data, u FREE_AND_NULL(item->tag); } - if (size < the_hash_algo->hexsz + 24) + if (size < r->hash_algo->hexsz + 24) return -1; - if (memcmp("object ", bufptr, 7) || parse_oid_hex(bufptr + 7, &oid, &bufptr) || *bufptr++ != '\n') + if (memcmp("object ", bufptr, 7) || + parse_oid_hex_algop(bufptr + 7, &oid, &bufptr, r->hash_algo) || + *bufptr++ != '\n') return -1; if (!starts_with(bufptr, "type ")) @@ -213,7 +203,7 @@ int parse_tag_buffer(struct repository *r, struct tag *item, const void *data, u return 0; } -int parse_tag(struct tag *item) +int parse_tag(struct repository *r, struct tag *item) { enum object_type type; void *data; @@ -222,8 +212,7 @@ int parse_tag(struct tag *item) if (item->object.parsed) return 0; - data = odb_read_object(the_repository->objects, &item->object.oid, - &type, &size); + data = odb_read_object(r->objects, &item->object.oid, &type, &size); if (!data) return error("Could not read %s", oid_to_hex(&item->object.oid)); @@ -232,7 +221,7 @@ int parse_tag(struct tag *item) return error("Object %s not a tag", oid_to_hex(&item->object.oid)); } - ret = parse_tag_buffer(the_repository, item, data, size); + ret = parse_tag_buffer(r, item, data, size); free(data); return ret; }
diff --git a/tag.h b/tag.h index c49d7c1..534687c 100644 --- a/tag.h +++ b/tag.h
@@ -13,11 +13,10 @@ struct tag { }; struct tag *lookup_tag(struct repository *r, const struct object_id *oid); int parse_tag_buffer(struct repository *r, struct tag *item, const void *data, unsigned long size); -int parse_tag(struct tag *item); +int parse_tag(struct repository *r, struct tag *item); void release_tag_memory(struct tag *t); struct object *deref_tag(struct repository *r, struct object *, const char *, int); -struct object *deref_tag_noverify(struct repository *r, struct object *); -int gpg_verify_tag(const struct object_id *oid, +int gpg_verify_tag(struct repository *r, const struct object_id *oid, const char *name_to_report, unsigned flags); struct object_id *get_tagged_oid(struct tag *tag);
diff --git a/templates/hooks/commit-msg.sample b/templates/hooks/commit-msg.sample index b58d118..f7458ef 100755 --- a/templates/hooks/commit-msg.sample +++ b/templates/hooks/commit-msg.sample
@@ -15,10 +15,60 @@ # SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') # grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" -# This example catches duplicate Signed-off-by lines. +# This example catches duplicate Signed-off-by lines and messages that +# would confuse 'git am'. + +ret=0 test "" = "$(grep '^Signed-off-by: ' "$1" | sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { echo >&2 Duplicate Signed-off-by lines. - exit 1 + ret=1 } + +comment_re="$( + { + git config --get-regexp "^core\.comment(char|string)\$" || + echo '#' + } | sed -n -e ' + ${ + s/^[^ ]* // + s|[][*./\]|\\&|g + s/^auto$/[#;@!$%^&|:]/ + p + }' +)" +scissors_line="^${comment_re} -\{8,\} >8 -\{8,\}\$" +comment_line="^${comment_re}.*" +blank_line='^[ ]*$' +# Disallow lines starting with "diff -" or "Index: " in the body of the +# message. Stop looking if we see a scissors line. +line="$(sed -n -e " + # Skip comments and blank lines at the start of the file. + /${scissors_line}/q + /${comment_line}/d + /${blank_line}/d + # The first paragraph will become the subject header so + # does not need to be checked. + : subject + n + /${scissors_line}/q + /${blank_line}/!b subject + # Check the body of the message for problematic + # prefixes. + : body + n + /${scissors_line}/q + /${comment_line}/b body + /^diff -/{p;q;} + /^Index: /{p;q;} + b body + " "$1")" +if test -n "$line" +then + echo >&2 "Message contains a diff that will confuse 'git am'." + echo >&2 "To fix this indent the diff." + ret=1 +fi + +exit $ret
diff --git a/templates/hooks/fsmonitor-watchman.sample b/templates/hooks/fsmonitor-watchman.sample index 23e856f..429e0a5 100755 --- a/templates/hooks/fsmonitor-watchman.sample +++ b/templates/hooks/fsmonitor-watchman.sample
@@ -29,8 +29,6 @@ my $git_work_tree = get_working_dir(); -my $retry = 1; - my $json_pkg; eval { require JSON::XS; @@ -123,8 +121,7 @@ sub is_work_tree_watched { my ($output) = @_; my $error = $output->{error}; - if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { - $retry--; + if ($error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { my $response = qx/watchman watch "$git_work_tree"/; die "Failed to make watchman watch '$git_work_tree'.\n" . "Falling back to scanning...\n" if $? != 0; @@ -142,15 +139,12 @@ # Watchman query just to get it over with now so we won't pay # the cost in git to look up each individual file. my $o = watchman_clock(); - $error = $output->{error}; + $error = $o->{error}; die "Watchman: $error.\n" . "Falling back to scanning...\n" if $error; output_result($o->{clock}, ("/")); - $last_update_token = $o->{clock}; - - eval { launch_watchman() }; return 0; }
diff --git a/tmp-objdir.c b/tmp-objdir.c index 9f5a178..d199d39 100644 --- a/tmp-objdir.c +++ b/tmp-objdir.c
@@ -11,6 +11,7 @@ #include "strvec.h" #include "quote.h" #include "odb.h" +#include "odb/source.h" #include "repository.h" struct tmp_objdir { @@ -36,6 +37,21 @@ static void tmp_objdir_free(struct tmp_objdir *t) free(t); } +static void tmp_objdir_reparent(const char *name UNUSED, + const char *old_cwd, + const char *new_cwd, + void *cb_data) +{ + struct tmp_objdir *t = cb_data; + char *path; + + path = reparent_relative_path(old_cwd, new_cwd, + t->path.buf); + strbuf_reset(&t->path); + strbuf_addstr(&t->path, path); + free(path); +} + int tmp_objdir_destroy(struct tmp_objdir *t) { int err; @@ -51,6 +67,7 @@ int tmp_objdir_destroy(struct tmp_objdir *t) err = remove_dir_recursively(&t->path, 0); + chdir_notify_unregister(NULL, tmp_objdir_reparent, t); tmp_objdir_free(t); return err; @@ -137,6 +154,9 @@ struct tmp_objdir *tmp_objdir_create(struct repository *r, strbuf_addf(&t->path, "%s/tmp_objdir-%s-XXXXXX", repo_get_object_directory(r), prefix); + if (!is_absolute_path(t->path.buf)) + chdir_notify_register(NULL, tmp_objdir_reparent, t); + if (!mkdtemp(t->path.buf)) { /* free, not destroy, as we never touched the filesystem */ tmp_objdir_free(t); @@ -315,26 +335,3 @@ void tmp_objdir_replace_primary_odb(struct tmp_objdir *t, int will_destroy) t->path.buf, will_destroy); t->will_destroy = will_destroy; } - -struct tmp_objdir *tmp_objdir_unapply_primary_odb(void) -{ - if (!the_tmp_objdir || !the_tmp_objdir->prev_source) - return NULL; - - odb_restore_primary_source(the_tmp_objdir->repo->objects, - the_tmp_objdir->prev_source, the_tmp_objdir->path.buf); - the_tmp_objdir->prev_source = NULL; - return the_tmp_objdir; -} - -void tmp_objdir_reapply_primary_odb(struct tmp_objdir *t, const char *old_cwd, - const char *new_cwd) -{ - char *path; - - path = reparent_relative_path(old_cwd, new_cwd, t->path.buf); - strbuf_reset(&t->path); - strbuf_addstr(&t->path, path); - free(path); - tmp_objdir_replace_primary_odb(t, t->will_destroy); -}
diff --git a/tmp-objdir.h b/tmp-objdir.h index fceda14..ccf800f 100644 --- a/tmp-objdir.h +++ b/tmp-objdir.h
@@ -68,19 +68,4 @@ void tmp_objdir_add_as_alternate(const struct tmp_objdir *); */ void tmp_objdir_replace_primary_odb(struct tmp_objdir *, int will_destroy); -/* - * If the primary object database was replaced by a temporary object directory, - * restore it to its original value while keeping the directory contents around. - * Returns NULL if the primary object database was not replaced. - */ -struct tmp_objdir *tmp_objdir_unapply_primary_odb(void); - -/* - * Reapplies the former primary temporary object database, after potentially - * changing its relative path. - */ -void tmp_objdir_reapply_primary_odb(struct tmp_objdir *, const char *old_cwd, - const char *new_cwd); - - #endif /* TMP_OBJDIR_H */
diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 0000000..d732997 --- /dev/null +++ b/tools/README.md
@@ -0,0 +1,7 @@ +Developer Tooling +----------------- + +This directory is expected to contain all sorts of tooling that +relates to our build infrastructure. This includes scripts and +inputs required by our build systems, but also scripts that +developers are expected to run manually.
diff --git a/check-builtins.sh b/tools/check-builtins.sh similarity index 100% rename from check-builtins.sh rename to tools/check-builtins.sh
diff --git a/contrib/coccinelle/.gitignore b/tools/coccinelle/.gitignore similarity index 100% rename from contrib/coccinelle/.gitignore rename to tools/coccinelle/.gitignore
diff --git a/contrib/coccinelle/README b/tools/coccinelle/README similarity index 98% rename from contrib/coccinelle/README rename to tools/coccinelle/README index 055ad0e..fd0a543 100644 --- a/contrib/coccinelle/README +++ b/tools/coccinelle/README
@@ -38,7 +38,7 @@ So to aid these large scale refactorings, semantic patches can be used. However we do not want to store them in the same place as the checks for bad patterns, as then automated builds would fail. - That is why semantic patches 'contrib/coccinelle/*.pending.cocci' + That is why semantic patches 'tools/coccinelle/*.pending.cocci' are ignored for checks, and can be applied using 'make coccicheck-pending'. This allows to expose plans of pending large scale refactorings without
diff --git a/contrib/coccinelle/array.cocci b/tools/coccinelle/array.cocci similarity index 66% rename from contrib/coccinelle/array.cocci rename to tools/coccinelle/array.cocci index 27a3b47..e71baea 100644 --- a/contrib/coccinelle/array.cocci +++ b/tools/coccinelle/array.cocci
@@ -101,3 +101,47 @@ -ALLOC_ARRAY(dst, n); -COPY_ARRAY(dst, src, n); +DUP_ARRAY(dst, src, n); + +@@ +type T; +T *ptr; +expression n; +@@ +- memset(ptr, \( 0 \| '\0' \), \( (n) \| n \) * \( sizeof(T) +- \| sizeof(ptr[...]) +- \| sizeof(*ptr) +- \) ) ++ MEMZERO_ARRAY(ptr, n) + +@@ +type T; +T *ptr; +expression n; +@@ +- memset(ptr, \( 0 \| '\0' \), \( sizeof(T) +- \| sizeof(ptr[...]) +- \| sizeof(*ptr) +- \) * \( (n) \| n \) ) ++ MEMZERO_ARRAY(ptr, n) + +@@ +type T; +T[] ptr; +expression n; +@@ +- memset(ptr, \( 0 \| '\0' \), \( (n) \| n \) * \( sizeof(T) +- \| sizeof(ptr[...]) +- \| sizeof(*ptr) +- \) ) ++ MEMZERO_ARRAY(ptr, n) + +@@ +type T; +T[] ptr; +expression n; +@@ +- memset(ptr, \( 0 \| '\0' \), \( sizeof(T) +- \| sizeof(ptr[...]) +- \| sizeof(*ptr) +- \) * \( (n) \| n \) ) ++ MEMZERO_ARRAY(ptr, n)
diff --git a/contrib/coccinelle/commit.cocci b/tools/coccinelle/commit.cocci similarity index 92% rename from contrib/coccinelle/commit.cocci rename to tools/coccinelle/commit.cocci index c528460..4272516 100644 --- a/contrib/coccinelle/commit.cocci +++ b/tools/coccinelle/commit.cocci
@@ -26,7 +26,7 @@ // repo_get_commit_tree() on the LHS. @@ identifier f != { repo_get_commit_tree, get_commit_tree_in_graph_one, - load_tree_for_commit, set_commit_tree }; + load_tree_for_commit, set_commit_tree, repo_parse_commit_no_graph }; expression c; @@ f(...) {<...
diff --git a/contrib/coccinelle/config_fn_ctx.pending.cocci b/tools/coccinelle/config_fn_ctx.pending.cocci similarity index 100% rename from contrib/coccinelle/config_fn_ctx.pending.cocci rename to tools/coccinelle/config_fn_ctx.pending.cocci
diff --git a/contrib/coccinelle/equals-null.cocci b/tools/coccinelle/equals-null.cocci similarity index 100% rename from contrib/coccinelle/equals-null.cocci rename to tools/coccinelle/equals-null.cocci
diff --git a/contrib/coccinelle/flex_alloc.cocci b/tools/coccinelle/flex_alloc.cocci similarity index 100% rename from contrib/coccinelle/flex_alloc.cocci rename to tools/coccinelle/flex_alloc.cocci
diff --git a/contrib/coccinelle/free.cocci b/tools/coccinelle/free.cocci similarity index 74% rename from contrib/coccinelle/free.cocci rename to tools/coccinelle/free.cocci index 6fb9eb6..03799e1 100644 --- a/contrib/coccinelle/free.cocci +++ b/tools/coccinelle/free.cocci
@@ -5,7 +5,7 @@ ( free(E); | - free_commit_list(E); + commit_list_free(E); ) @@ @@ -15,7 +15,7 @@ ( free(E); | - free_commit_list(E); + commit_list_free(E); ) @@ @@ -30,7 +30,7 @@ @@ - if (E) - { - free_commit_list(E); + commit_list_free(E); E = NULL; - } @@ -41,5 +41,5 @@ - if (E) { + if (E) S - free_commit_list(E); + commit_list_free(E); - }
diff --git a/contrib/coccinelle/git_config_number.cocci b/tools/coccinelle/git_config_number.cocci similarity index 100% rename from contrib/coccinelle/git_config_number.cocci rename to tools/coccinelle/git_config_number.cocci
diff --git a/contrib/coccinelle/hashmap.cocci b/tools/coccinelle/hashmap.cocci similarity index 100% rename from contrib/coccinelle/hashmap.cocci rename to tools/coccinelle/hashmap.cocci
diff --git a/contrib/coccinelle/index-compatibility.cocci b/tools/coccinelle/index-compatibility.cocci similarity index 100% rename from contrib/coccinelle/index-compatibility.cocci rename to tools/coccinelle/index-compatibility.cocci
diff --git a/contrib/coccinelle/meson.build b/tools/coccinelle/meson.build similarity index 89% rename from contrib/coccinelle/meson.build rename to tools/coccinelle/meson.build index dc3f73c..ae7f5b5 100644 --- a/contrib/coccinelle/meson.build +++ b/tools/coccinelle/meson.build
@@ -50,6 +50,11 @@ coccinelle_headers += meson.project_source_root() / header endforeach +coccinelle_includes = [] +foreach path : ['compat', 'ewah', 'refs', 'sha256', 'trace2', 'win32', 'xdiff'] + coccinelle_includes += ['-I', meson.project_source_root() / path] +endforeach + patches = [ ] foreach source : coccinelle_sources patches += custom_target( @@ -58,6 +63,7 @@ '--all-includes', '--sp-file', concatenated_rules, '--patch', meson.project_source_root(), + coccinelle_includes, '@INPUT@', ], input: meson.project_source_root() / source,
diff --git a/contrib/coccinelle/object_id.cocci b/tools/coccinelle/object_id.cocci similarity index 100% rename from contrib/coccinelle/object_id.cocci rename to tools/coccinelle/object_id.cocci
diff --git a/contrib/coccinelle/preincr.cocci b/tools/coccinelle/preincr.cocci similarity index 100% rename from contrib/coccinelle/preincr.cocci rename to tools/coccinelle/preincr.cocci
diff --git a/contrib/coccinelle/qsort.cocci b/tools/coccinelle/qsort.cocci similarity index 100% rename from contrib/coccinelle/qsort.cocci rename to tools/coccinelle/qsort.cocci
diff --git a/contrib/coccinelle/refs.cocci b/tools/coccinelle/refs.cocci similarity index 100% rename from contrib/coccinelle/refs.cocci rename to tools/coccinelle/refs.cocci
diff --git a/contrib/coccinelle/spatchcache b/tools/coccinelle/spatchcache similarity index 96% rename from contrib/coccinelle/spatchcache rename to tools/coccinelle/spatchcache index 29e9352..efbcbc3 100755 --- a/contrib/coccinelle/spatchcache +++ b/tools/coccinelle/spatchcache
@@ -30,7 +30,7 @@ # out of control. # # This along with the general incremental "make" support for -# "contrib/coccinelle" makes it viable to (re-)run coccicheck +# "tools/coccinelle" makes it viable to (re-)run coccicheck # e.g. when merging integration branches. # # Note that the "--very-quiet" flag is currently critical. The cache @@ -42,7 +42,7 @@ # to change, so just supply "--very-quiet" for now. # # To use this, simply set SPATCH to -# contrib/coccinelle/spatchcache. Then optionally set: +# tools/coccinelle/spatchcache. Then optionally set: # # [spatchCache] # # Optional: path to a custom spatch @@ -65,7 +65,7 @@ # # redis-cli FLUSHALL # <make && make coccicheck, as above> -# grep -hore HIT -e MISS -e SET -e NOCACHE -e CANTCACHE .build/contrib/coccinelle | sort | uniq -c +# grep -hore HIT -e MISS -e SET -e NOCACHE -e CANTCACHE .build/tools/coccinelle | sort | uniq -c # 600 CANTCACHE # 7365 MISS # 7365 SET
diff --git a/contrib/coccinelle/strbuf.cocci b/tools/coccinelle/strbuf.cocci similarity index 78% rename from contrib/coccinelle/strbuf.cocci rename to tools/coccinelle/strbuf.cocci index 5f06105..f586128 100644 --- a/contrib/coccinelle/strbuf.cocci +++ b/tools/coccinelle/strbuf.cocci
@@ -60,3 +60,21 @@ @@ - strbuf_addstr(E1, real_path(E2)); + strbuf_add_real_path(E1, E2); + +@@ +identifier fn, param; +@@ + fn(..., +- struct strbuf param ++ struct strbuf *param + ,...) + { + ... + } + +// In modern codebase, .buf member of an empty strbuf is not NULL. +@@ +struct strbuf SB; +@@ +- SB.buf ? SB.buf : "" ++ SB.buf
diff --git a/tools/coccinelle/strvec.cocci b/tools/coccinelle/strvec.cocci new file mode 100644 index 0000000..64edb09 --- /dev/null +++ b/tools/coccinelle/strvec.cocci
@@ -0,0 +1,46 @@ +@@ +type T; +identifier i; +expression dst; +struct strvec *src_ptr; +struct strvec src_arr; +@@ +( +- for (T i = 0; i < src_ptr->nr; i++) { strvec_push(dst, src_ptr->v[i]); } ++ strvec_pushv(dst, src_ptr->v); +| +- for (T i = 0; i < src_arr.nr; i++) { strvec_push(dst, src_arr.v[i]); } ++ strvec_pushv(dst, src_arr.v); +) + +@ separate_loop_index @ +type T; +identifier i; +expression dst; +struct strvec *src_ptr; +struct strvec src_arr; +@@ + T i; + ... +( +- for (i = 0; i < src_ptr->nr; i++) { strvec_push(dst, src_ptr->v[i]); } ++ strvec_pushv(dst, src_ptr->v); +| +- for (i = 0; i < src_arr.nr; i++) { strvec_push(dst, src_arr.v[i]); } ++ strvec_pushv(dst, src_arr.v); +) + +@ unused_loop_index extends separate_loop_index @ +@@ + { + ... +- T i; + ... when != i + } + +@ depends on unused_loop_index @ +@@ + if (...) +- { + strvec_pushv(...); +- }
diff --git a/contrib/coccinelle/swap.cocci b/tools/coccinelle/swap.cocci similarity index 100% rename from contrib/coccinelle/swap.cocci rename to tools/coccinelle/swap.cocci
diff --git a/contrib/coccinelle/tests/free.c b/tools/coccinelle/tests/free.c similarity index 100% rename from contrib/coccinelle/tests/free.c rename to tools/coccinelle/tests/free.c
diff --git a/contrib/coccinelle/tests/free.res b/tools/coccinelle/tests/free.res similarity index 100% rename from contrib/coccinelle/tests/free.res rename to tools/coccinelle/tests/free.res
diff --git a/tools/coccinelle/the_repository.cocci b/tools/coccinelle/the_repository.cocci new file mode 100644 index 0000000..f1129f7 --- /dev/null +++ b/tools/coccinelle/the_repository.cocci
@@ -0,0 +1,18 @@ +// Fully migrated "the_repository" additions +@@ +@@ +( +// TODO: remove the rules below and the macros from tree.h after the +// next Git release. +- parse_tree ++ repo_parse_tree +| +- parse_tree_gently ++ repo_parse_tree_gently +| +- parse_tree_indirect ++ repo_parse_tree_indirect +) + ( ++ the_repository, + ...)
diff --git a/contrib/coccinelle/xcalloc.cocci b/tools/coccinelle/xcalloc.cocci similarity index 100% rename from contrib/coccinelle/xcalloc.cocci rename to tools/coccinelle/xcalloc.cocci
diff --git a/contrib/coccinelle/xopen.cocci b/tools/coccinelle/xopen.cocci similarity index 100% rename from contrib/coccinelle/xopen.cocci rename to tools/coccinelle/xopen.cocci
diff --git a/contrib/coccinelle/xstrdup_or_null.cocci b/tools/coccinelle/xstrdup_or_null.cocci similarity index 100% rename from contrib/coccinelle/xstrdup_or_null.cocci rename to tools/coccinelle/xstrdup_or_null.cocci
diff --git a/contrib/coccinelle/xstrncmpz.cocci b/tools/coccinelle/xstrncmpz.cocci similarity index 100% rename from contrib/coccinelle/xstrncmpz.cocci rename to tools/coccinelle/xstrncmpz.cocci
diff --git a/contrib/coverage-diff.sh b/tools/coverage-diff.sh similarity index 100% rename from contrib/coverage-diff.sh rename to tools/coverage-diff.sh
diff --git a/detect-compiler b/tools/detect-compiler similarity index 100% rename from detect-compiler rename to tools/detect-compiler
diff --git a/generate-cmdlist.sh b/tools/generate-cmdlist.sh similarity index 100% rename from generate-cmdlist.sh rename to tools/generate-cmdlist.sh
diff --git a/tools/generate-configlist.sh b/tools/generate-configlist.sh new file mode 100755 index 0000000..e28054f --- /dev/null +++ b/tools/generate-configlist.sh
@@ -0,0 +1,52 @@ +#!/bin/sh + +SOURCE_DIR="$1" +OUTPUT="$2" +DEPFILE="$3" + +if test -z "$SOURCE_DIR" || ! test -d "$SOURCE_DIR" || test -z "$OUTPUT" +then + echo >&2 "USAGE: $0 <SOURCE_DIR> <OUTPUT> [<DEPFILE>]" + exit 1 +fi + +print_config_list () { + cat <<EOF +static const char *config_name_list[] = { +EOF + sed -e ' + /^`*[a-zA-Z].*\..*`*::$/ { + /deprecated/d; + s/::$//; + s/`//g; + s/^.*$/ "&",/; + p;}; + d' \ + "$SOURCE_DIR"/Documentation/*config.adoc \ + "$SOURCE_DIR"/Documentation/config/*.adoc | + sort + cat <<EOF + NULL, +}; +EOF +} + +{ + echo "/* Automatically generated by generate-configlist.sh */" + echo + echo + print_config_list +} >"$OUTPUT" + +if test -n "$DEPFILE" +then + QUOTED_OUTPUT="$(printf '%s\n' "$OUTPUT" | sed 's,[&/\],\\&,g')" + { + printf '%s\n' "$SOURCE_DIR"/Documentation/*config.adoc \ + "$SOURCE_DIR"/Documentation/config/*.adoc | + sed -e 's/[# ]/\\&/g' -e "s/^/$QUOTED_OUTPUT: /" + printf '%s:\n' "$SOURCE_DIR"/Documentation/*config.adoc \ + "$SOURCE_DIR"/Documentation/config/*.adoc | + sed -e 's/[# ]/\\&/g' + } >"$DEPFILE" +fi
diff --git a/generate-hooklist.sh b/tools/generate-hooklist.sh similarity index 100% rename from generate-hooklist.sh rename to tools/generate-hooklist.sh
diff --git a/generate-perl.sh b/tools/generate-perl.sh similarity index 100% rename from generate-perl.sh rename to tools/generate-perl.sh
diff --git a/generate-python.sh b/tools/generate-python.sh similarity index 100% rename from generate-python.sh rename to tools/generate-python.sh
diff --git a/generate-script.sh b/tools/generate-script.sh similarity index 100% rename from generate-script.sh rename to tools/generate-script.sh
diff --git a/tools/meson.build b/tools/meson.build new file mode 100644 index 0000000..f731f74 --- /dev/null +++ b/tools/meson.build
@@ -0,0 +1 @@ +subdir('coccinelle')
diff --git a/tools/precompiled.h b/tools/precompiled.h new file mode 100644 index 0000000..b2bec0d --- /dev/null +++ b/tools/precompiled.h
@@ -0,0 +1 @@ +#include "git-compat-util.h"
diff --git a/contrib/update-unicode/.gitignore b/tools/update-unicode/.gitignore similarity index 100% rename from contrib/update-unicode/.gitignore rename to tools/update-unicode/.gitignore
diff --git a/contrib/update-unicode/README b/tools/update-unicode/README similarity index 100% rename from contrib/update-unicode/README rename to tools/update-unicode/README
diff --git a/contrib/update-unicode/update_unicode.sh b/tools/update-unicode/update_unicode.sh similarity index 100% rename from contrib/update-unicode/update_unicode.sh rename to tools/update-unicode/update_unicode.sh
diff --git a/trailer.c b/trailer.c index 911a81e..470f86a 100644 --- a/trailer.c +++ b/trailer.c
@@ -7,8 +7,11 @@ #include "string-list.h" #include "run-command.h" #include "commit.h" +#include "strvec.h" #include "trailer.h" #include "list.h" +#include "tempfile.h" + /* * Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org> */ @@ -772,6 +775,35 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head, free(cl_separators); } +int validate_trailer_args(const struct strvec *cli_args) +{ + char *cl_separators; + int ret = 0; + + trailer_config_init(); + + cl_separators = xstrfmt("=%s", separators); + + for (size_t i = 0; i < cli_args->nr; i++) { + const char *txt = cli_args->v[i]; + ssize_t separator_pos; + + if (!*txt) { + ret = error(_("empty --trailer argument")); + goto out; + } + separator_pos = find_separator(txt, cl_separators); + if (separator_pos == 0) { + ret = error(_("invalid trailer '%s': missing key before separator"), + txt); + goto out; + } + } +out: + free(cl_separators); + return ret; +} + static const char *next_line(const char *str) { const char *nl = strchrnul(str, '\n'); @@ -1009,7 +1041,7 @@ static struct trailer_block *trailer_block_get(const struct process_trailer_opti for (ptr = trailer_lines; *ptr; ptr++) { if (last && isspace((*ptr)->buf[0])) { struct strbuf sb = STRBUF_INIT; - strbuf_attach(&sb, *last, strlen(*last), strlen(*last)); + strbuf_attach(&sb, *last, strlen(*last), strlen(*last) + 1); strbuf_addbuf(&sb, *ptr); *last = strbuf_detach(&sb, NULL); continue; @@ -1224,14 +1256,146 @@ void trailer_iterator_release(struct trailer_iterator *iter) strbuf_release(&iter->key); } -int amend_file_with_trailers(const char *path, const struct strvec *trailer_args) +struct tempfile *trailer_create_in_place_tempfile(const char *file) { - struct child_process run_trailer = CHILD_PROCESS_INIT; + struct tempfile *tempfile = NULL; + struct stat st; + struct strbuf filename_template = STRBUF_INIT; + const char *tail; - run_trailer.git_cmd = 1; - strvec_pushl(&run_trailer.args, "interpret-trailers", - "--in-place", "--no-divider", - path, NULL); - strvec_pushv(&run_trailer.args, trailer_args->v); - return run_command(&run_trailer); + if (stat(file, &st)) { + error_errno(_("could not stat %s"), file); + return NULL; + } + if (!S_ISREG(st.st_mode)) { + error(_("file %s is not a regular file"), file); + return NULL; + } + if (!(st.st_mode & S_IWUSR)) { + error(_("file %s is not writable by user"), file); + return NULL; + } + /* Create temporary file in the same directory as the original */ + tail = find_last_dir_sep(file); + if (tail) + strbuf_add(&filename_template, file, tail - file + 1); + strbuf_addstr(&filename_template, "git-interpret-trailers-XXXXXX"); + + tempfile = mks_tempfile_m(filename_template.buf, st.st_mode); + + strbuf_release(&filename_template); + + return tempfile; +} + +int amend_strbuf_with_trailers(struct strbuf *buf, + const struct strvec *trailer_args) +{ + struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT; + LIST_HEAD(new_trailer_head); + struct strbuf out = STRBUF_INIT; + size_t i; + int ret = 0; + + opts.no_divider = 1; + + for (i = 0; i < trailer_args->nr; i++) { + const char *text = trailer_args->v[i]; + struct new_trailer_item *item; + + if (!*text) { + ret = error(_("empty --trailer argument")); + goto out; + } + item = xcalloc(1, sizeof(*item)); + item->text = xstrdup(text); + list_add_tail(&item->list, &new_trailer_head); + } + + process_trailers(&opts, &new_trailer_head, buf, &out); + + strbuf_swap(buf, &out); +out: + strbuf_release(&out); + free_trailers(&new_trailer_head); + + return ret; +} + +static int write_file_in_place(const char *path, const struct strbuf *buf) +{ + struct tempfile *tempfile = trailer_create_in_place_tempfile(path); + if (!tempfile) + return -1; + + if (write_in_full(tempfile->fd, buf->buf, buf->len) < 0) + return error_errno(_("could not write to temporary file")); + + if (rename_tempfile(&tempfile, path)) + return error_errno(_("could not rename temporary file to %s"), path); + + return 0; +} + +int amend_file_with_trailers(const char *path, + const struct strvec *trailer_args) +{ + struct strbuf buf = STRBUF_INIT; + int ret = 0; + + if (!trailer_args) + BUG("amend_file_with_trailers called with NULL trailer_args"); + if (!trailer_args->nr) + return 0; + + if (validate_trailer_args(trailer_args)) { + ret = -1; + goto out; + } + if (strbuf_read_file(&buf, path, 0) < 0) + ret = error_errno(_("could not read '%s'"), path); + else + amend_strbuf_with_trailers(&buf, trailer_args); + + if (!ret) + ret = write_file_in_place(path, &buf); +out: + strbuf_release(&buf); + return ret; +} + +void process_trailers(const struct process_trailer_options *opts, + struct list_head *new_trailer_head, + struct strbuf *input, struct strbuf *out) +{ + LIST_HEAD(head); + struct trailer_block *trailer_block; + + trailer_block = parse_trailers(opts, input->buf, &head); + + /* Print the lines before the trailer block */ + if (!opts->only_trailers) + strbuf_add(out, input->buf, trailer_block_start(trailer_block)); + + if (!opts->only_trailers && !blank_line_before_trailer_block(trailer_block)) + strbuf_addch(out, '\n'); + + if (!opts->only_input) { + LIST_HEAD(config_head); + LIST_HEAD(arg_head); + parse_trailers_from_config(&config_head); + parse_trailers_from_command_line_args(&arg_head, new_trailer_head); + list_splice(&config_head, &arg_head); + process_trailers_lists(&head, &arg_head); + } + + /* Print trailer block. */ + format_trailers(opts, &head, out); + free_trailers(&head); + + /* Print the lines after the trailer block as is. */ + if (!opts->only_trailers) + strbuf_add(out, input->buf + trailer_block_end(trailer_block), + input->len - trailer_block_end(trailer_block)); + trailer_block_release(trailer_block); }
diff --git a/trailer.h b/trailer.h index 4740549..b493388 100644 --- a/trailer.h +++ b/trailer.h
@@ -68,6 +68,8 @@ void parse_trailers_from_config(struct list_head *config_head); void parse_trailers_from_command_line_args(struct list_head *arg_head, struct list_head *new_trailer_head); +int validate_trailer_args(const struct strvec *cli_args); + void process_trailers_lists(struct list_head *head, struct list_head *arg_head); @@ -196,10 +198,38 @@ int trailer_iterator_advance(struct trailer_iterator *iter); void trailer_iterator_release(struct trailer_iterator *iter); /* - * Augment a file to add trailers to it by running git-interpret-trailers. - * This calls run_command() and its return value is the same (i.e. 0 for - * success, various non-zero for other errors). See run-command.h. + * Append trailers specified in trailer_args to buf in-place. + * + * Each element of trailer_args should be in the same format as the value + * accepted by --trailer=<trailer> (i.e., without the --trailer= prefix). + */ +int amend_strbuf_with_trailers(struct strbuf *buf, + const struct strvec *trailer_args); + +/* + * Augment a file by appending trailers specified in trailer_args. + * + * Each element of trailer_args should be in the same format as the value + * accepted by --trailer=<trailer> (i.e., without the --trailer= prefix). + * + * Returns 0 on success or a non-zero error code on failure. */ int amend_file_with_trailers(const char *path, const struct strvec *trailer_args); +/* + * Create a tempfile ""git-interpret-trailers-XXXXXX" in the same + * directory as file. + */ +struct tempfile *trailer_create_in_place_tempfile(const char *file); + +/* + * Rewrite the contents of input by processing its trailer block according to + * opts and (optionally) appending trailers from new_trailer_head. + * + * The rewritten message is appended to out (callers should strbuf_reset() + * first if needed). + */ +void process_trailers(const struct process_trailer_options *opts, + struct list_head *new_trailer_head, + struct strbuf *input, struct strbuf *out); #endif /* TRAILER_H */
diff --git a/transport-helper.c b/transport-helper.c index 4d95d84..570d7c6 100644 --- a/transport-helper.c +++ b/transport-helper.c
@@ -154,6 +154,8 @@ static struct child_process *get_helper(struct transport *transport) helper->trace2_child_class = helper->args.v[0]; /* "remote-<name>" */ + helper->clean_on_exit = 1; + helper->wait_after_clean = 1; code = start_command(helper); if (code < 0 && errno == ENOENT) die(_("unable to find remote helper for '%s'"), data->name);
diff --git a/transport.c b/transport.c index c7f06a7..e53936d 100644 --- a/transport.c +++ b/transport.c
@@ -47,21 +47,21 @@ static int transport_color_config(void) "color.transport.reset", "color.transport.rejected" }, *key = "color.transport"; - char *value; + const char *value; static int initialized; if (initialized) return 0; initialized = 1; - if (!repo_config_get_string(the_repository, key, &value)) + if (!repo_config_get_string_tmp(the_repository, key, &value)) transport_use_color = git_config_colorbool(key, value); if (!want_color_stderr(transport_use_color)) return 0; for (size_t i = 0; i < ARRAY_SIZE(keys); i++) - if (!repo_config_get_string(the_repository, keys[i], &value)) { + if (!repo_config_get_string_tmp(the_repository, keys[i], &value)) { if (!value) return config_error_nonbool(keys[i]); if (color_parse(value, transport_colors[i]) < 0) @@ -1219,6 +1219,7 @@ struct transport *transport_get(struct remote *remote, const char *url) */ struct git_transport_data *data = xcalloc(1, sizeof(*data)); list_objects_filter_init(&data->options.filter_options); + data->options.filter_options.allow_auto_filter = 1; ret->data = data; ret->vtable = &builtin_smart_vtable; ret->smart_options = &(data->options); @@ -1316,65 +1317,86 @@ static void die_with_unpushed_submodules(struct string_list *needs_pushing) die(_("Aborting.")); } +struct feed_pre_push_hook_data { + struct strbuf buf; + const struct ref *refs; +}; + +static int pre_push_hook_feed_stdin(int hook_stdin_fd, void *pp_cb UNUSED, void *pp_task_cb) +{ + struct feed_pre_push_hook_data *data = pp_task_cb; + const struct ref *r = data->refs; + int ret = 0; + + if (!r) + return 1; /* no more refs */ + + data->refs = r->next; + + switch (r->status) { + case REF_STATUS_REJECT_NONFASTFORWARD: + case REF_STATUS_REJECT_REMOTE_UPDATED: + case REF_STATUS_REJECT_STALE: + case REF_STATUS_UPTODATE: + return 0; /* skip refs which won't be pushed */ + default: + break; + } + + if (!r->peer_ref) + return 0; + + strbuf_reset(&data->buf); + strbuf_addf(&data->buf, "%s %s %s %s\n", + r->peer_ref->name, oid_to_hex(&r->new_oid), + r->name, oid_to_hex(&r->old_oid)); + + ret = write_in_full(hook_stdin_fd, data->buf.buf, data->buf.len); + if (ret < 0 && errno != EPIPE) + return ret; /* We do not mind if a hook does not read all refs. */ + + return 0; +} + +static void *pre_push_hook_data_alloc(void *feed_pipe_ctx) +{ + struct feed_pre_push_hook_data *data; + CALLOC_ARRAY(data, 1); + strbuf_init(&data->buf, 0); + data->refs = (struct ref *)feed_pipe_ctx; + return data; +} + +static void pre_push_hook_data_free(void *data) +{ + struct feed_pre_push_hook_data *d = data; + if (!d) + return; + strbuf_release(&d->buf); + free(d); +} + static int run_pre_push_hook(struct transport *transport, struct ref *remote_refs) { - int ret = 0, x; - struct ref *r; - struct child_process proc = CHILD_PROCESS_INIT; - struct strbuf buf; - const char *hook_path = find_hook(the_repository, "pre-push"); + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + int ret = 0; - if (!hook_path) - return 0; + strvec_push(&opt.args, transport->remote->name); + strvec_push(&opt.args, transport->url); - strvec_push(&proc.args, hook_path); - strvec_push(&proc.args, transport->remote->name); - strvec_push(&proc.args, transport->url); + opt.feed_pipe = pre_push_hook_feed_stdin; + opt.feed_pipe_ctx = remote_refs; + opt.feed_pipe_cb_data_alloc = pre_push_hook_data_alloc; + opt.feed_pipe_cb_data_free = pre_push_hook_data_free; - proc.in = -1; - proc.trace2_hook_name = "pre-push"; + /* + * pre-push hooks expect stdout & stderr to be separate, so don't merge + * them to keep backwards compatibility with existing hooks. + */ + opt.stdout_to_stderr = 0; - if (start_command(&proc)) { - finish_command(&proc); - return -1; - } - - sigchain_push(SIGPIPE, SIG_IGN); - - strbuf_init(&buf, 256); - - for (r = remote_refs; r; r = r->next) { - if (!r->peer_ref) continue; - if (r->status == REF_STATUS_REJECT_NONFASTFORWARD) continue; - if (r->status == REF_STATUS_REJECT_STALE) continue; - if (r->status == REF_STATUS_REJECT_REMOTE_UPDATED) continue; - if (r->status == REF_STATUS_UPTODATE) continue; - - strbuf_reset(&buf); - strbuf_addf( &buf, "%s %s %s %s\n", - r->peer_ref->name, oid_to_hex(&r->new_oid), - r->name, oid_to_hex(&r->old_oid)); - - if (write_in_full(proc.in, buf.buf, buf.len) < 0) { - /* We do not mind if a hook does not read all refs. */ - if (errno != EPIPE) - ret = -1; - break; - } - } - - strbuf_release(&buf); - - x = close(proc.in); - if (!ret) - ret = x; - - sigchain_pop(SIGPIPE); - - x = finish_command(&proc); - if (!ret) - ret = x; + ret = run_hooks_opt(the_repository, "pre-push", &opt); return ret; } @@ -1657,7 +1679,7 @@ int transport_disconnect(struct transport *transport) */ char *transport_anonymize_url(const char *url) { - char *scheme_prefix, *anon_part; + const char *scheme_prefix, *anon_part; size_t anon_len, prefix_len = 0; anon_part = strchr(url, '@');
diff --git a/tree-diff.c b/tree-diff.c index 5988148..2f5c956 100644 --- a/tree-diff.c +++ b/tree-diff.c
@@ -2,7 +2,6 @@ * Helper functions for tree diff generation */ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" @@ -11,7 +10,6 @@ #include "hash.h" #include "tree.h" #include "tree-walk.h" -#include "environment.h" #include "repository.h" #include "dir.h" @@ -253,7 +251,7 @@ static void emit_path(struct combine_diff_path ***tail, strbuf_add(base, path, pathlen); p = combine_diff_path_new(base->buf, base->len, mode, - oid ? oid : null_oid(the_hash_algo), + oid ? oid : null_oid(opt->repo->hash_algo), nparent); strbuf_setlen(base, old_baselen); @@ -278,7 +276,7 @@ static void emit_path(struct combine_diff_path ***tail, mode_i = tp[i].entry.mode; } else { - oid_i = null_oid(the_hash_algo); + oid_i = null_oid(opt->repo->hash_algo); mode_i = 0; } @@ -439,7 +437,7 @@ static void ll_diff_tree_paths( void *ttree, **tptree; int i; - if (depth > max_allowed_tree_depth) + if (depth > opt->repo->settings.max_allowed_tree_depth) die("exceeded maximum allowed tree depth"); FAST_ARRAY_ALLOC(tp, nparent);
diff --git a/tree-walk.c b/tree-walk.c index e449a13..7e1b956 100644 --- a/tree-walk.c +++ b/tree-walk.c
@@ -12,6 +12,7 @@ #include "pathspec.h" #include "json-writer.h" #include "environment.h" +#include "read-cache-ll.h" static int decode_tree_entry(struct tree_desc *desc, const char *buf, unsigned long size, struct strbuf *err) { @@ -441,8 +442,9 @@ int traverse_trees(struct index_state *istate, struct strbuf base = STRBUF_INIT; int interesting = 1; char *traverse_path; + struct repository *r = istate ? istate->repo : the_repository; - if (traverse_trees_cur_depth > max_allowed_tree_depth) + if (traverse_trees_cur_depth > r->settings.max_allowed_tree_depth) return error("exceeded maximum allowed tree depth"); traverse_trees_count++;
diff --git a/tree.c b/tree.c index 1ef743d..d703ab9 100644 --- a/tree.c +++ b/tree.c
@@ -1,5 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE - #include "git-compat-util.h" #include "hex.h" #include "tree.h" @@ -25,10 +23,10 @@ int read_tree_at(struct repository *r, int len, oldlen = base->len; enum interesting retval = entry_not_interesting; - if (depth > max_allowed_tree_depth) + if (depth > r->settings.max_allowed_tree_depth) return error("exceeded maximum allowed tree depth"); - if (parse_tree(tree)) + if (repo_parse_tree(r, tree)) return -1; init_tree_desc(&desc, &tree->object.oid, tree->buffer, tree->size); @@ -185,7 +183,8 @@ int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size) return 0; } -int parse_tree_gently(struct tree *item, int quiet_on_missing) +int repo_parse_tree_gently(struct repository *r, struct tree *item, + int quiet_on_missing) { enum object_type type; void *buffer; @@ -193,8 +192,7 @@ int parse_tree_gently(struct tree *item, int quiet_on_missing) if (item->object.parsed) return 0; - buffer = odb_read_object(the_repository->objects, &item->object.oid, - &type, &size); + buffer = odb_read_object(r->objects, &item->object.oid, &type, &size); if (!buffer) return quiet_on_missing ? -1 : error("Could not read %s", @@ -214,9 +212,9 @@ void free_tree_buffer(struct tree *tree) tree->object.parsed = 0; } -struct tree *parse_tree_indirect(const struct object_id *oid) +struct tree *repo_parse_tree_indirect(struct repository *r, + const struct object_id *oid) { - struct repository *r = the_repository; struct object *obj = parse_object(r, oid); return (struct tree *)repo_peel_to_type(r, NULL, 0, obj, OBJ_TREE); }
diff --git a/tree.h b/tree.h index cc6ddf5..677382e 100644 --- a/tree.h +++ b/tree.h
@@ -19,15 +19,20 @@ struct tree *lookup_tree(struct repository *r, const struct object_id *oid); int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size); -int parse_tree_gently(struct tree *tree, int quiet_on_missing); -static inline int parse_tree(struct tree *tree) +#define parse_tree_gently(t, q) repo_parse_tree_gently(the_repository, t, q) +int repo_parse_tree_gently(struct repository *r, struct tree *item, + int quiet_on_missing); +#define parse_tree(t) repo_parse_tree(the_repository, t) +static inline int repo_parse_tree(struct repository *r, struct tree *item) { - return parse_tree_gently(tree, 0); + return repo_parse_tree_gently(r, item, 0); } void free_tree_buffer(struct tree *tree); /* Parses and returns the tree in the given ent, chasing tags and commits. */ -struct tree *parse_tree_indirect(const struct object_id *oid); +#define parse_tree_indirect(o) repo_parse_tree_indirect(the_repository, o) +struct tree *repo_parse_tree_indirect(struct repository *r, + const struct object_id *oid); /* * Functions for comparing pathnames
diff --git a/unpack-trees.c b/unpack-trees.c index f38c761..998a1e6 100644 --- a/unpack-trees.c +++ b/unpack-trees.c
@@ -1888,6 +1888,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options struct pattern_list pl; int free_pattern_list = 0; struct dir_struct dir = DIR_INIT; + struct repo_config_values *cfg = repo_config_values(the_repository); if (o->reset == UNPACK_RESET_INVALID) BUG("o->reset had a value of 1; should be UNPACK_TREES_*_UNTRACKED"); @@ -1924,7 +1925,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options if (o->prefix) update_sparsity_for_prefix(o->prefix, o->src_index); - if (!core_apply_sparse_checkout || !o->update) + if (!cfg->apply_sparse_checkout || !o->update) o->skip_sparse_checkout = 1; if (!o->skip_sparse_checkout) { memset(&pl, 0, sizeof(pl));
diff --git a/upload-pack.c b/upload-pack.c index 1e87ae9..9f6d6fe 100644 --- a/upload-pack.c +++ b/upload-pack.c
@@ -29,6 +29,7 @@ #include "commit-graph.h" #include "commit-reach.h" #include "shallow.h" +#include "trace.h" #include "write-or-die.h" #include "json-writer.h" #include "strmap.h" @@ -218,7 +219,8 @@ struct output_state { }; static int relay_pack_data(int pack_objects_out, struct output_state *os, - int use_sideband, int write_packfile_line) + int use_sideband, int write_packfile_line, + bool *did_send_data) { /* * We keep the last byte to ourselves @@ -232,6 +234,8 @@ static int relay_pack_data(int pack_objects_out, struct output_state *os, */ ssize_t readsz; + *did_send_data = false; + readsz = xread(pack_objects_out, os->buffer + os->used, sizeof(os->buffer) - os->used); if (readsz < 0) { @@ -247,6 +251,7 @@ static int relay_pack_data(int pack_objects_out, struct output_state *os, if (os->packfile_uris_started) packet_delim(1); packet_write_fmt(1, "\1packfile\n"); + *did_send_data = true; } break; } @@ -259,6 +264,7 @@ static int relay_pack_data(int pack_objects_out, struct output_state *os, } *p = '\0'; packet_write_fmt(1, "\1%s\n", os->buffer); + *did_send_data = true; os->used -= p - os->buffer + 1; memmove(os->buffer, p + 1, os->used); @@ -270,6 +276,13 @@ static int relay_pack_data(int pack_objects_out, struct output_state *os, } } + /* + * Make sure that we buffer some data before sending it to the client. + * This significantly reduces the number of write(3p) syscalls. + */ + if (readsz && os->used < (sizeof(os->buffer) * 2 / 3)) + return readsz; + if (os->used > 1) { send_client_data(1, os->buffer, os->used - 1, use_sideband); os->buffer[0] = os->buffer[os->used - 1]; @@ -279,6 +292,7 @@ static int relay_pack_data(int pack_objects_out, struct output_state *os, os->used = 0; } + *did_send_data = true; return readsz; } @@ -290,6 +304,7 @@ static void create_pack_file(struct upload_pack_data *pack_data, char progress[128]; char abort_msg[] = "aborting due to possible repository " "corruption on the remote side."; + uint64_t last_sent_ms = 0; ssize_t sz; int i; FILE *pipe_fd; @@ -365,10 +380,14 @@ static void create_pack_file(struct upload_pack_data *pack_data, */ while (1) { + uint64_t now_ms = getnanotime() / 1000000; struct pollfd pfd[2]; - int pe, pu, pollsize, polltimeout; + int pe, pu, pollsize, polltimeout_ms; int ret; + if (!last_sent_ms) + last_sent_ms = now_ms; + reset_timeout(pack_data->timeout); pollsize = 0; @@ -390,11 +409,21 @@ static void create_pack_file(struct upload_pack_data *pack_data, if (!pollsize) break; - polltimeout = pack_data->keepalive < 0 - ? -1 - : 1000 * pack_data->keepalive; + if (pack_data->keepalive < 0) { + polltimeout_ms = -1; + } else { + /* + * The polling timeout needs to be adjusted based on + * the time we have sent our last package. The longer + * it's been in the past, the shorter the timeout + * becomes until we eventually don't block at all. + */ + polltimeout_ms = 1000 * pack_data->keepalive - (now_ms - last_sent_ms); + if (polltimeout_ms < 0) + polltimeout_ms = 0; + } - ret = poll(pfd, pollsize, polltimeout); + ret = poll(pfd, pollsize, polltimeout_ms); if (ret < 0) { if (errno != EINTR) { @@ -403,16 +432,18 @@ static void create_pack_file(struct upload_pack_data *pack_data, } continue; } + if (0 <= pe && (pfd[pe].revents & (POLLIN|POLLHUP))) { /* Status ready; we ship that in the side-band * or dump to the standard error. */ sz = xread(pack_objects.err, progress, sizeof(progress)); - if (0 < sz) + if (0 < sz) { send_client_data(2, progress, sz, pack_data->use_sideband); - else if (sz == 0) { + last_sent_ms = now_ms; + } else if (sz == 0) { close(pack_objects.err); pack_objects.err = -1; } @@ -421,11 +452,14 @@ static void create_pack_file(struct upload_pack_data *pack_data, /* give priority to status messages */ continue; } + if (0 <= pu && (pfd[pu].revents & (POLLIN|POLLHUP))) { + bool did_send_data; int result = relay_pack_data(pack_objects.out, output_state, pack_data->use_sideband, - !!uri_protocols); + !!uri_protocols, + &did_send_data); if (result == 0) { close(pack_objects.out); @@ -433,21 +467,34 @@ static void create_pack_file(struct upload_pack_data *pack_data, } else if (result < 0) { goto fail; } + + if (did_send_data) + last_sent_ms = now_ms; } /* - * We hit the keepalive timeout without saying anything; send - * an empty message on the data sideband just to let the other - * side know we're still working on it, but don't have any data - * yet. + * We hit the keepalive timeout without saying anything. If we + * have pending data we flush it out to the caller now. + * Otherwise, we send an empty message on the data sideband + * just to let the other side know we're still working on it, + * but don't have any data yet. * * If we don't have a sideband channel, there's no room in the * protocol to say anything, so those clients are just out of * luck. */ if (!ret && pack_data->use_sideband) { - static const char buf[] = "0005\1"; - write_or_die(1, buf, 5); + if (output_state->packfile_started && output_state->used > 1) { + send_client_data(1, output_state->buffer, output_state->used - 1, + pack_data->use_sideband); + output_state->buffer[0] = output_state->buffer[output_state->used - 1]; + output_state->used = 1; + } else { + static const char buf[] = "0005\1"; + write_or_die(1, buf, 5); + } + + last_sent_ms = now_ms; } } @@ -457,11 +504,9 @@ static void create_pack_file(struct upload_pack_data *pack_data, } /* flush the data */ - if (output_state->used > 0) { + if (output_state->used > 0) send_client_data(1, output_state->buffer, output_state->used, pack_data->use_sideband); - fprintf(stderr, "flushed.\n"); - } free(output_state); if (pack_data->use_sideband) packet_flush(1); @@ -607,10 +652,13 @@ static int allow_hidden_refs(enum allow_uor allow_uor) return !(allow_uor & (ALLOW_TIP_SHA1 | ALLOW_REACHABLE_SHA1)); } -static void for_each_namespaced_ref_1(each_ref_fn fn, +static void for_each_namespaced_ref_1(refs_for_each_cb fn, struct upload_pack_data *data) { - const char **excludes = NULL; + struct refs_for_each_ref_options opts = { + .namespace = get_git_namespace(), + }; + /* * If `data->allow_uor` allows fetching hidden refs, we need to * mark all references (including hidden ones), to check in @@ -621,10 +669,10 @@ static void for_each_namespaced_ref_1(each_ref_fn fn, * hidden references. */ if (allow_hidden_refs(data->allow_uor)) - excludes = hidden_refs_to_excludes(&data->hidden_refs); + opts.exclude_patterns = hidden_refs_to_excludes(&data->hidden_refs); - refs_for_each_namespaced_ref(get_main_ref_store(the_repository), - excludes, fn, data); + refs_for_each_ref_ext(get_main_ref_store(the_repository), + fn, data, &opts); } @@ -704,56 +752,6 @@ static int do_reachable_revlist(struct child_process *cmd, return -1; } -static int get_reachable_list(struct upload_pack_data *data, - struct object_array *reachable) -{ - struct child_process cmd = CHILD_PROCESS_INIT; - int i; - struct object *o; - char namebuf[GIT_MAX_HEXSZ + 2]; /* ^ + hash + LF */ - const unsigned hexsz = the_hash_algo->hexsz; - int ret; - - if (do_reachable_revlist(&cmd, &data->shallows, reachable, - data->allow_uor) < 0) { - ret = -1; - goto out; - } - - while ((i = read_in_full(cmd.out, namebuf, hexsz + 1)) == hexsz + 1) { - struct object_id oid; - const char *p; - - if (parse_oid_hex(namebuf, &oid, &p) || *p != '\n') - break; - - o = lookup_object(the_repository, &oid); - if (o && o->type == OBJ_COMMIT) { - o->flags &= ~TMP_MARK; - } - } - for (i = get_max_object_index(the_repository); 0 < i; i--) { - o = get_indexed_object(the_repository, i - 1); - if (o && o->type == OBJ_COMMIT && - (o->flags & TMP_MARK)) { - add_object_array(o, NULL, reachable); - o->flags &= ~TMP_MARK; - } - } - close(cmd.out); - - if (finish_command(&cmd)) { - ret = -1; - goto out; - } - - ret = 0; - -out: - child_process_clear(&cmd); - return ret; -} - static int has_unreachable(struct object_array *src, enum allow_uor allow_uor) { struct child_process cmd = CHILD_PROCESS_INIT; @@ -870,8 +868,8 @@ static void send_unshallow(struct upload_pack_data *data) } } -static int check_ref(const char *refname_full, const char *referent UNUSED, const struct object_id *oid, - int flag, void *cb_data); +static int check_ref(const struct reference *ref, void *cb_data); + static void deepen(struct upload_pack_data *data, int depth) { if (depth == INFINITE_DEPTH && !is_repository_shallow(the_repository)) { @@ -881,29 +879,11 @@ static void deepen(struct upload_pack_data *data, int depth) struct object *object = data->shallows.objects[i].item; object->flags |= NOT_SHALLOW; } - } else if (data->deepen_relative) { - struct object_array reachable_shallows = OBJECT_ARRAY_INIT; - struct commit_list *result; - - /* - * Checking for reachable shallows requires that our refs be - * marked with OUR_REF. - */ - refs_head_ref_namespaced(get_main_ref_store(the_repository), - check_ref, data); - for_each_namespaced_ref_1(check_ref, data); - - get_reachable_list(data, &reachable_shallows); - result = get_shallow_commits(&reachable_shallows, - depth + 1, - SHALLOW, NOT_SHALLOW); - send_shallow(data, result); - free_commit_list(result); - object_array_clear(&reachable_shallows); } else { struct commit_list *result; - result = get_shallow_commits(&data->want_obj, depth, + result = get_shallow_commits(&data->want_obj, &data->shallows, + data->deepen_relative, depth, SHALLOW, NOT_SHALLOW); send_shallow(data, result); free_commit_list(result); @@ -1224,13 +1204,12 @@ static int mark_our_ref(const char *refname, const char *refname_full, return 0; } -static int check_ref(const char *refname_full, const char *referent UNUSED,const struct object_id *oid, - int flag UNUSED, void *cb_data) +static int check_ref(const struct reference *ref, void *cb_data) { - const char *refname = strip_namespace(refname_full); + const char *refname = strip_namespace(ref->name); struct upload_pack_data *data = cb_data; - mark_our_ref(refname, refname_full, oid, &data->hidden_refs); + mark_our_ref(refname, ref->name, ref->oid, &data->hidden_refs); return 0; } @@ -1250,15 +1229,15 @@ static void format_session_id(struct strbuf *buf, struct upload_pack_data *d) { } static void write_v0_ref(struct upload_pack_data *data, - const char *refname, const char *refname_nons, - const struct object_id *oid) + const struct reference *ref, + const char *refname_nons) { static const char *capabilities = "multi_ack thin-pack side-band" " side-band-64k ofs-delta shallow deepen-since deepen-not" " deepen-relative no-progress include-tag multi_ack_detailed"; struct object_id peeled; - if (mark_our_ref(refname_nons, refname, oid, &data->hidden_refs)) + if (mark_our_ref(refname_nons, ref->name, ref->oid, &data->hidden_refs)) return; if (capabilities) { @@ -1268,7 +1247,7 @@ static void write_v0_ref(struct upload_pack_data *data, format_symref_info(&symref_info, &data->symref); format_session_id(&session_id, data); packet_fwrite_fmt(stdout, "%s %s%c%s%s%s%s%s%s%s object-format=%s agent=%s\n", - oid_to_hex(oid), refname_nons, + oid_to_hex(ref->oid), refname_nons, 0, capabilities, (data->allow_uor & ALLOW_TIP_SHA1) ? " allow-tip-sha1-in-want" : "", @@ -1284,35 +1263,33 @@ static void write_v0_ref(struct upload_pack_data *data, strbuf_release(&session_id); data->sent_capabilities = 1; } else { - packet_fwrite_fmt(stdout, "%s %s\n", oid_to_hex(oid), refname_nons); + packet_fwrite_fmt(stdout, "%s %s\n", oid_to_hex(ref->oid), refname_nons); } capabilities = NULL; - if (!peel_iterated_oid(the_repository, oid, &peeled)) + if (!reference_get_peeled_oid(the_repository, ref, &peeled)) packet_fwrite_fmt(stdout, "%s %s^{}\n", oid_to_hex(&peeled), refname_nons); return; } -static int send_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid, - int flag UNUSED, void *cb_data) +static int send_ref(const struct reference *ref, void *cb_data) { - write_v0_ref(cb_data, refname, strip_namespace(refname), oid); + write_v0_ref(cb_data, ref, strip_namespace(ref->name)); return 0; } -static int find_symref(const char *refname, const char *referent UNUSED, - const struct object_id *oid UNUSED, - int flag, void *cb_data) +static int find_symref(const struct reference *ref, void *cb_data) { const char *symref_target; struct string_list_item *item; + int flag; - if ((flag & REF_ISSYMREF) == 0) + if ((ref->flags & REF_ISSYMREF) == 0) return 0; symref_target = refs_resolve_ref_unsafe(get_main_ref_store(the_repository), - refname, 0, NULL, &flag); + ref->name, 0, NULL, &flag); if (!symref_target || (flag & REF_ISSYMREF) == 0) - die("'%s' is a symref but it is not?", refname); - item = string_list_append(cb_data, strip_namespace(refname)); + die("'%s' is a symref but it is not?", ref->name); + item = string_list_append(cb_data, strip_namespace(ref->name)); item->util = xstrdup(strip_namespace(symref_target)); return 0; } @@ -1445,8 +1422,12 @@ void upload_pack(const int advertise_refs, const int stateless_rpc, send_ref, &data); for_each_namespaced_ref_1(send_ref, &data); if (!data.sent_capabilities) { - const char *refname = "capabilities^{}"; - write_v0_ref(&data, refname, refname, null_oid(the_hash_algo)); + struct reference ref = { + .name = "capabilities^{}", + .oid = null_oid(the_hash_algo), + }; + + write_v0_ref(&data, &ref, ref.name); } /* * fflush stdout before calling advertise_shallow_grafts because send_ref
diff --git a/url.c b/url.c index 282b124..3ca5987 100644 --- a/url.c +++ b/url.c
@@ -3,6 +3,19 @@ #include "strbuf.h" #include "url.h" +int is_rfc3986_unreserved(char ch) +{ + return isalnum(ch) || + ch == '-' || ch == '_' || ch == '.' || ch == '~'; +} + +int is_casefolding_rfc3986_unreserved(char c) +{ + return (c >= 'a' && c <= 'z') || + (c >= '0' && c <= '9') || + c == '-' || c == '.' || c == '_' || c == '~'; +} + int is_urlschemechar(int first_flag, int ch) { /*
diff --git a/url.h b/url.h index 2a27c34..cd9140e 100644 --- a/url.h +++ b/url.h
@@ -21,4 +21,18 @@ char *url_decode_parameter_value(const char **query); void end_url_with_slash(struct strbuf *buf, const char *url); void str_end_url_with_slash(const char *url, char **dest); +/* + * The set of unreserved characters as per STD66 (RFC3986) is + * '[A-Za-z0-9-._~]'. These characters are safe to appear in URI + * components without percent-encoding. + */ +int is_rfc3986_unreserved(char ch); + +/* + * This is a variant of is_rfc3986_unreserved() that treats uppercase + * letters as "reserved". This forces them to be percent-encoded, allowing + * 'Foo' (%46oo) and 'foo' (foo) to be distinct on case-folding filesystems. + */ +int is_casefolding_rfc3986_unreserved(char c); + #endif /* URL_H */
diff --git a/utf8.c b/utf8.c index 35a0251..96460cc 100644 --- a/utf8.c +++ b/utf8.c
@@ -515,6 +515,19 @@ char *reencode_string_iconv(const char *in, size_t insz, iconv_t conv, out = xrealloc(out, outalloc); outpos = out + sofar; outsz = outalloc - sofar - 1; +#ifdef ICONV_RESTART_RESET + /* + * If iconv(3) messes up piecemeal conversions + * then restore the original pointers, sizes, + * and converter state, then retry converting + * the full string using the reallocated buffer. + */ + insz += cp - (iconv_ibp)in; /* Restore insz */ + cp = (iconv_ibp)in; /* original start value */ + outpos = out + bom_len; /* original start value */ + outsz = outalloc - bom_len - 1; /* new len */ + iconv(conv, NULL, NULL, NULL, NULL); /* reset iconv machinery */ +#endif } else { *outpos = '\0';
diff --git a/version.c b/version.c index 279269c..21ec7c0 100644 --- a/version.c +++ b/version.c
@@ -1,5 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE - #include "git-compat-util.h" #include "version.h" #include "strbuf.h"
diff --git a/version.h b/version.h index bbde6d3..7d502dc 100644 --- a/version.h +++ b/version.h
@@ -1,8 +1,6 @@ #ifndef VERSION_H #define VERSION_H -struct repository; - extern const char git_version_string[]; extern const char git_built_from_commit_string[];
diff --git a/walker.c b/walker.c index 8073754..9133253 100644 --- a/walker.c +++ b/walker.c
@@ -45,7 +45,7 @@ static int process_tree(struct walker *walker, struct tree *tree) struct tree_desc desc; struct name_entry entry; - if (parse_tree(tree)) + if (repo_parse_tree(the_repository, tree)) return -1; init_tree_desc(&desc, &tree->object.oid, tree->buffer, tree->size); @@ -115,7 +115,7 @@ static int process_commit(struct walker *walker, struct commit *commit) static int process_tag(struct walker *walker, struct tag *tag) { - if (parse_tag(tag)) + if (parse_tag(the_repository, tag)) return -1; return process(walker, tag->tagged); } @@ -226,14 +226,10 @@ static int interpret_target(struct walker *walker, char *target, struct object_i return -1; } -static int mark_complete(const char *path UNUSED, - const char *referent UNUSED, - const struct object_id *oid, - int flag UNUSED, - void *cb_data UNUSED) +static int mark_complete(const struct reference *ref, void *cb_data UNUSED) { struct commit *commit = lookup_commit_reference_gently(the_repository, - oid, 1); + ref->oid, 1); if (commit) { commit->object.flags |= COMPLETE;
diff --git a/worktree.c b/worktree.c index a2a5f51..d874e23 100644 --- a/worktree.c +++ b/worktree.c
@@ -58,7 +58,7 @@ static void add_head_info(struct worktree *wt) static int is_current_worktree(struct worktree *wt) { - char *git_dir = absolute_pathdup(repo_get_git_dir(the_repository)); + char *git_dir = absolute_pathdup(repo_get_git_dir(wt->repo)); char *wt_git_dir = get_worktree_git_dir(wt); int is_current = !fspathcmp(git_dir, absolute_path(wt_git_dir)); free(wt_git_dir); @@ -66,6 +66,26 @@ static int is_current_worktree(struct worktree *wt) return is_current; } +struct worktree *get_worktree_from_repository(struct repository *repo) +{ + struct worktree *wt = xcalloc(1, sizeof(*wt)); + char *gitdir = absolute_pathdup(repo->gitdir); + char *commondir = absolute_pathdup(repo->commondir); + + wt->repo = repo; + wt->path = absolute_pathdup(repo->worktree ? repo->worktree + : repo->gitdir); + wt->is_bare = !repo->worktree; + if (fspathcmp(gitdir, commondir)) + wt->id = xstrdup(find_last_dir_sep(gitdir) + 1); + wt->is_current = true; + add_head_info(wt); + + free(gitdir); + free(commondir); + return wt; +} + /* * When in a secondary worktree, and when extensions.worktreeConfig * is true, only $commondir/config and $commondir/worktrees/<id>/ @@ -207,11 +227,11 @@ struct worktree **get_worktrees_without_reading_head(void) char *get_worktree_git_dir(const struct worktree *wt) { if (!wt) - return xstrdup(repo_get_git_dir(the_repository)); + BUG("%s() called with NULL worktree", __func__); else if (!wt->id) - return xstrdup(repo_get_common_dir(the_repository)); + return xstrdup(repo_get_common_dir(wt->repo)); else - return repo_common_path(the_repository, "worktrees/%s", wt->id); + return repo_common_path(wt->repo, "worktrees/%s", wt->id); } static struct worktree *find_worktree_by_suffix(struct worktree **list, @@ -288,7 +308,7 @@ const char *worktree_lock_reason(struct worktree *wt) if (!wt->lock_reason_valid) { struct strbuf path = STRBUF_INIT; - strbuf_addstr(&path, worktree_git_path(the_repository, wt, "locked")); + strbuf_addstr(&path, worktree_git_path(wt, "locked")); if (file_exists(path.buf)) { struct strbuf lock_reason = STRBUF_INIT; if (strbuf_read_file(&lock_reason, path.buf, 0) < 0) @@ -425,7 +445,7 @@ void update_worktree_location(struct worktree *wt, const char *path_, strbuf_realpath(&path, path_, 1); strbuf_addf(&dotgit, "%s/.git", path.buf); if (fspathcmp(wt->path, path.buf)) { - write_worktree_linking_files(dotgit, gitdir, use_relative_paths); + write_worktree_linking_files(dotgit.buf, gitdir.buf, use_relative_paths); free(wt->path); wt->path = strbuf_detach(&path, NULL); @@ -575,7 +595,7 @@ void strbuf_worktree_ref(const struct worktree *wt, strbuf_addstr(sb, refname); } -int other_head_refs(each_ref_fn fn, void *cb_data) +int other_head_refs(refs_for_each_cb fn, void *cb_data) { struct worktree **worktrees, **p; struct strbuf refname = STRBUF_INIT; @@ -595,8 +615,15 @@ int other_head_refs(each_ref_fn fn, void *cb_data) if (refs_resolve_ref_unsafe(get_main_ref_store(the_repository), refname.buf, RESOLVE_REF_READING, - &oid, &flag)) - ret = fn(refname.buf, NULL, &oid, flag, cb_data); + &oid, &flag)) { + struct reference ref = { + .name = refname.buf, + .oid = &oid, + .flags = flag, + }; + + ret = fn(&ref, cb_data); + } if (ret) break; } @@ -646,7 +673,8 @@ static void repair_gitfile(struct worktree *wt, } } - if (err == READ_GITFILE_ERR_NOT_A_FILE) + if (err == READ_GITFILE_ERR_NOT_A_FILE || + err == READ_GITFILE_ERR_IS_A_DIR) fn(1, wt->path, _(".git is not a file"), cb_data); else if (err) repair = _(".git file broken"); @@ -657,7 +685,7 @@ static void repair_gitfile(struct worktree *wt, if (repair) { fn(0, wt->path, repair, cb_data); - write_worktree_linking_files(dotgit, gitdir, use_relative_paths); + write_worktree_linking_files(dotgit.buf, gitdir.buf, use_relative_paths); } done: @@ -715,7 +743,7 @@ void repair_worktree_after_gitdir_move(struct worktree *wt, const char *old_path if (!file_exists(dotgit.buf)) goto done; - write_worktree_linking_files(dotgit, gitdir, is_relative_path); + write_worktree_linking_files(dotgit.buf, gitdir.buf, is_relative_path); done: strbuf_release(&gitdir); strbuf_release(&dotgit); @@ -826,7 +854,8 @@ void repair_worktree_at_path(const char *path, strbuf_addstr(&backlink, dotgit_contents); strbuf_realpath_forgiving(&backlink, backlink.buf, 0); } - } else if (err == READ_GITFILE_ERR_NOT_A_FILE) { + } else if (err == READ_GITFILE_ERR_NOT_A_FILE || + err == READ_GITFILE_ERR_IS_A_DIR) { fn(1, dotgit.buf, _("unable to locate repository; .git is not a file"), cb_data); goto done; } else if (err == READ_GITFILE_ERR_NOT_A_REPO) { @@ -886,7 +915,7 @@ void repair_worktree_at_path(const char *path, if (repair) { fn(0, gitdir.buf, repair, cb_data); - write_worktree_linking_files(dotgit, gitdir, use_relative_paths); + write_worktree_linking_files(dotgit.buf, gitdir.buf, use_relative_paths); } done: free(dotgit_contents); @@ -1060,17 +1089,17 @@ int init_worktree_config(struct repository *r) return res; } -void write_worktree_linking_files(struct strbuf dotgit, struct strbuf gitdir, +void write_worktree_linking_files(const char *dotgit, const char *gitdir, int use_relative_paths) { struct strbuf path = STRBUF_INIT; struct strbuf repo = STRBUF_INIT; struct strbuf tmp = STRBUF_INIT; - strbuf_addbuf(&path, &dotgit); + strbuf_addstr(&path, dotgit); strbuf_strip_suffix(&path, "/.git"); strbuf_realpath(&path, path.buf, 1); - strbuf_addbuf(&repo, &gitdir); + strbuf_addstr(&repo, gitdir); strbuf_strip_suffix(&repo, "/gitdir"); strbuf_realpath(&repo, repo.buf, 1); @@ -1083,11 +1112,11 @@ void write_worktree_linking_files(struct strbuf dotgit, struct strbuf gitdir, } if (use_relative_paths) { - write_file(gitdir.buf, "%s/.git", relative_path(path.buf, repo.buf, &tmp)); - write_file(dotgit.buf, "gitdir: %s", relative_path(repo.buf, path.buf, &tmp)); + write_file(gitdir, "%s/.git", relative_path(path.buf, repo.buf, &tmp)); + write_file(dotgit, "gitdir: %s", relative_path(repo.buf, path.buf, &tmp)); } else { - write_file(gitdir.buf, "%s/.git", path.buf); - write_file(dotgit.buf, "gitdir: %s", repo.buf); + write_file(gitdir, "%s/.git", path.buf); + write_file(dotgit, "gitdir: %s", repo.buf); } strbuf_release(&path);
diff --git a/worktree.h b/worktree.h index e4bcccd..d19ec29 100644 --- a/worktree.h +++ b/worktree.h
@@ -16,7 +16,7 @@ struct worktree { struct object_id head_oid; int is_detached; int is_bare; - int is_current; + int is_current; /* does `path` match `repo->worktree` */ int lock_reason_valid; /* private */ int prune_reason_valid; /* private */ }; @@ -39,13 +39,18 @@ struct worktree **get_worktrees(void); struct worktree **get_worktrees_without_reading_head(void); /* + * Construct a struct worktree corresponding to repo->gitdir and + * repo->worktree. + */ +struct worktree *get_worktree_from_repository(struct repository *repo); + +/* * Returns 1 if linked worktrees exist, 0 otherwise. */ int submodule_uses_worktrees(const char *path); /* * Return git dir of the worktree. Note that the path may be relative. - * If wt is NULL, git dir of current worktree is returned. */ char *get_worktree_git_dir(const struct worktree *wt); @@ -191,7 +196,7 @@ int is_shared_symref(const struct worktree *wt, * Similar to head_ref() for all HEADs _except_ one from the current * worktree, which is covered by head_ref(). */ -int other_head_refs(each_ref_fn fn, void *cb_data); +int other_head_refs(refs_for_each_cb fn, void *cb_data); int is_worktree_being_rebased(const struct worktree *wt, const char *target); int is_worktree_being_bisected(const struct worktree *wt, const char *target); @@ -234,7 +239,7 @@ int init_worktree_config(struct repository *r); * dotgit: "/path/to/foo/.git" * gitdir: "/path/to/repo/worktrees/foo/gitdir" */ -void write_worktree_linking_files(struct strbuf dotgit, struct strbuf gitdir, +void write_worktree_linking_files(const char *dotgit, const char *gitdir, int use_relative_paths); #endif
diff --git a/wrapper.c b/wrapper.c index 3d507d4..be8fa57 100644 --- a/wrapper.c +++ b/wrapper.c
@@ -115,7 +115,7 @@ void *xmemdupz(const void *data, size_t len) char *xstrndup(const char *str, size_t len) { - char *p = memchr(str, '\0', len); + const char *p = memchr(str, '\0', len); return xmemdupz(str, p ? p - str : len); } @@ -323,6 +323,47 @@ ssize_t write_in_full(int fd, const void *buf, size_t count) return total; } +ssize_t writev_in_full(int fd, struct iovec *iov, int iovcnt) +{ + ssize_t total_written = 0; + + while (iovcnt) { + ssize_t bytes_written = writev(fd, iov, iovcnt); + if (bytes_written < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + return -1; + } + if (!bytes_written) { + errno = ENOSPC; + return -1; + } + + total_written += bytes_written; + + /* + * We first need to discard any iovec entities that have been + * fully written. + */ + while (iovcnt && (size_t)bytes_written >= iov->iov_len) { + bytes_written -= iov->iov_len; + iov++; + iovcnt--; + } + + /* + * Finally, we need to adjust the last iovec in case we have + * performed a partial write. + */ + if (iovcnt && bytes_written) { + iov->iov_base = (char *) iov->iov_base + bytes_written; + iov->iov_len -= bytes_written; + } + } + + return total_written; +} + ssize_t pread_in_full(int fd, void *buf, size_t count, off_t offset) { char *p = buf; @@ -421,24 +462,7 @@ FILE *fopen_or_warn(const char *path, const char *mode) int xmkstemp(char *filename_template) { - int fd; - char origtemplate[PATH_MAX]; - strlcpy(origtemplate, filename_template, sizeof(origtemplate)); - - fd = mkstemp(filename_template); - if (fd < 0) { - int saved_errno = errno; - const char *nonrelative_template; - - if (strlen(filename_template) != strlen(origtemplate)) - filename_template = origtemplate; - - nonrelative_template = absolute_path(filename_template); - errno = saved_errno; - die_errno("Unable to create temporary file '%s'", - nonrelative_template); - } - return fd; + return xmkstemp_mode(filename_template, 0600); } /* Adapted from libiberty's mkstemp.c. */ @@ -446,7 +470,11 @@ int xmkstemp(char *filename_template) #undef TMP_MAX #define TMP_MAX 16384 -int git_mkstemps_mode(char *pattern, int suffix_len, int mode) +/* + * Returns -1 on error, 0 if it created a directory, or an open file + * descriptor to the created regular file. + */ +static int git_mkdstemps_mode(char *pattern, int suffix_len, int mode, bool dir) { static const char letters[] = "abcdefghijklmnopqrstuvwxyz" @@ -488,7 +516,10 @@ int git_mkstemps_mode(char *pattern, int suffix_len, int mode) v /= num_letters; } - fd = open(pattern, O_CREAT | O_EXCL | O_RDWR, mode); + if (dir) + fd = mkdir(pattern, mode); + else + fd = open(pattern, O_CREAT | O_EXCL | O_RDWR, mode); if (fd >= 0) return fd; /* @@ -503,6 +534,16 @@ int git_mkstemps_mode(char *pattern, int suffix_len, int mode) return -1; } +char *git_mkdtemp(char *pattern) +{ + return git_mkdstemps_mode(pattern, 0, 0700, true) ? NULL : pattern; +} + +int git_mkstemps_mode(char *pattern, int suffix_len, int mode) +{ + return git_mkdstemps_mode(pattern, suffix_len, mode, false); +} + int git_mkstemp_mode(char *pattern, int mode) { /* mkstemp is just mkstemps with no suffix */
diff --git a/wrapper.h b/wrapper.h index 44a8597..27519b3 100644 --- a/wrapper.h +++ b/wrapper.h
@@ -37,6 +37,8 @@ int xsnprintf(char *dst, size_t max, const char *fmt, ...); int xgethostname(char *buf, size_t len); +char *git_mkdtemp(char *pattern); + /* set default permissions by passing mode arguments to open(2) */ int git_mkstemps_mode(char *pattern, int suffix_len, int mode); int git_mkstemp_mode(char *pattern, int mode); @@ -45,6 +47,15 @@ ssize_t read_in_full(int fd, void *buf, size_t count); ssize_t write_in_full(int fd, const void *buf, size_t count); ssize_t pread_in_full(int fd, void *buf, size_t count, off_t offset); +/* + * Try to write all iovecs. Returns -1 in case an error occurred with a proper + * errno set, the number of bytes written otherwise. + * + * Note that the iovec will be modified as a result of this call to adjust for + * partial writes! + */ +ssize_t writev_in_full(int fd, struct iovec *iov, int iovcnt); + static inline ssize_t write_str_in_full(int fd, const char *str) { return write_in_full(fd, str, strlen(str));
diff --git a/write-or-die.c b/write-or-die.c index 01a9a51..5f522fb 100644 --- a/write-or-die.c +++ b/write-or-die.c
@@ -96,6 +96,14 @@ void write_or_die(int fd, const void *buf, size_t count) } } +void writev_or_die(int fd, struct iovec *iov, int iovlen) +{ + if (writev_in_full(fd, iov, iovlen) < 0) { + check_pipe(errno); + die_errno("writev error"); + } +} + void fwrite_or_die(FILE *f, const void *buf, size_t count) { if (fwrite(buf, 1, count, f) != count)
diff --git a/write-or-die.h b/write-or-die.h index 65a5c42..a045bdf 100644 --- a/write-or-die.h +++ b/write-or-die.h
@@ -7,6 +7,7 @@ void fprintf_or_die(FILE *, const char *fmt, ...); void fwrite_or_die(FILE *f, const void *buf, size_t count); void fflush_or_die(FILE *f); void write_or_die(int fd, const void *buf, size_t count); +void writev_or_die(int fd, struct iovec *iov, int iovlen); /* * These values are used to help identify parts of a repository to fsync. @@ -21,6 +22,7 @@ enum fsync_component { FSYNC_COMPONENT_COMMIT_GRAPH = 1 << 3, FSYNC_COMPONENT_INDEX = 1 << 4, FSYNC_COMPONENT_REFERENCE = 1 << 5, + FSYNC_COMPONENT_OBJECT_MAP = 1 << 6, }; #define FSYNC_COMPONENTS_OBJECTS (FSYNC_COMPONENT_LOOSE_OBJECT | \ @@ -44,7 +46,8 @@ enum fsync_component { FSYNC_COMPONENT_PACK_METADATA | \ FSYNC_COMPONENT_COMMIT_GRAPH | \ FSYNC_COMPONENT_INDEX | \ - FSYNC_COMPONENT_REFERENCE) + FSYNC_COMPONENT_REFERENCE | \ + FSYNC_COMPONENT_OBJECT_MAP) #ifndef FSYNC_COMPONENTS_PLATFORM_DEFAULT #define FSYNC_COMPONENTS_PLATFORM_DEFAULT FSYNC_COMPONENTS_DEFAULT
diff --git a/ws.c b/ws.c index 70acee3..6cc2466 100644 --- a/ws.c +++ b/ws.c
@@ -26,6 +26,7 @@ static struct whitespace_rule { { "blank-at-eol", WS_BLANK_AT_EOL, 0 }, { "blank-at-eof", WS_BLANK_AT_EOF, 0 }, { "tab-in-indent", WS_TAB_IN_INDENT, 0, 1 }, + { "incomplete-line", WS_INCOMPLETE_LINE, 0, 0 }, }; unsigned parse_whitespace_rule(const char *string) @@ -139,6 +140,11 @@ char *whitespace_error_string(unsigned ws) strbuf_addstr(&err, ", "); strbuf_addstr(&err, "tab in indent"); } + if (ws & WS_INCOMPLETE_LINE) { + if (err.len) + strbuf_addstr(&err, ", "); + strbuf_addstr(&err, "no newline at the end of file"); + } return strbuf_detach(&err, NULL); } @@ -180,6 +186,9 @@ static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule, if (trailing_whitespace == -1) trailing_whitespace = len; + if (!trailing_newline && (ws_rule & WS_INCOMPLETE_LINE)) + result |= WS_INCOMPLETE_LINE; + /* Check indentation */ for (i = 0; i < trailing_whitespace; i++) { if (line[i] == ' ') @@ -292,6 +301,17 @@ void ws_fix_copy(struct strbuf *dst, const char *src, int len, unsigned ws_rule, int need_fix_leading_space = 0; /* + * Remembering that we need to add '\n' at the end + * is sufficient to fix an incomplete line. + */ + if (ws_rule & WS_INCOMPLETE_LINE) { + if (0 < len && src[len - 1] != '\n') { + fixed = 1; + add_nl_to_tail = 1; + } + } + + /* * Strip trailing whitespace */ if (ws_rule & WS_BLANK_AT_EOL) {
diff --git a/ws.h b/ws.h index 5ba676c..06d5cb7 100644 --- a/ws.h +++ b/ws.h
@@ -7,19 +7,23 @@ struct strbuf; /* * whitespace rules. * used by both diff and apply - * last two digits are tab width + * last two octal-digits are tab width (we support only up to 63). */ -#define WS_BLANK_AT_EOL 0100 -#define WS_SPACE_BEFORE_TAB 0200 -#define WS_INDENT_WITH_NON_TAB 0400 -#define WS_CR_AT_EOL 01000 -#define WS_BLANK_AT_EOF 02000 -#define WS_TAB_IN_INDENT 04000 -#define WS_TRAILING_SPACE (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF) +#define WS_BLANK_AT_EOL (1<<6) +#define WS_SPACE_BEFORE_TAB (1<<7) +#define WS_INDENT_WITH_NON_TAB (1<<8) +#define WS_CR_AT_EOL (1<<9) +#define WS_BLANK_AT_EOF (1<<10) +#define WS_TAB_IN_INDENT (1<<11) +#define WS_INCOMPLETE_LINE (1<<12) + +#define WS_TRAILING_SPACE (WS_BLANK_AT_EOL|WS_BLANK_AT_EOF) #define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB|8) -#define WS_TAB_WIDTH_MASK 077 -/* All WS_* -- when extended, adapt diff.c emit_symbol */ -#define WS_RULE_MASK 07777 +#define WS_TAB_WIDTH_MASK ((1<<6)-1) + +/* All WS_* -- when extended, adapt constants defined after diff.c:diff_symbol */ +#define WS_RULE_MASK ((1<<16)-1) + extern unsigned whitespace_rule_cfg; unsigned whitespace_rule(struct index_state *, const char *); unsigned parse_whitespace_rule(const char *);
diff --git a/wt-status.c b/wt-status.c index e12adb2..479ccc3 100644 --- a/wt-status.c +++ b/wt-status.c
@@ -150,11 +150,11 @@ void wt_status_prepare(struct repository *r, struct wt_status *s) s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; s->use_color = GIT_COLOR_UNKNOWN; s->relative_paths = 1; - s->branch = refs_resolve_refdup(get_main_ref_store(the_repository), + s->branch = refs_resolve_refdup(get_main_ref_store(r), "HEAD", 0, NULL, NULL); s->reference = "HEAD"; s->fp = stdout; - s->index_file = repo_get_index_file(the_repository); + s->index_file = repo_get_index_file(r); s->change.strdup_strings = 1; s->untracked.strdup_strings = 1; s->ignored.strdup_strings = 1; @@ -612,6 +612,30 @@ static void wt_status_collect_updated_cb(struct diff_queue_struct *q, } } +void wt_status_collect_changes_trees(struct wt_status *s, + const struct object_id *old_treeish, + const struct object_id *new_treeish) +{ + struct diff_options opts = { 0 }; + + repo_diff_setup(s->repo, &opts); + opts.output_format = DIFF_FORMAT_CALLBACK; + opts.format_callback = wt_status_collect_updated_cb; + opts.format_callback_data = s; + opts.detect_rename = s->detect_rename >= 0 ? s->detect_rename : opts.detect_rename; + opts.rename_limit = s->rename_limit >= 0 ? s->rename_limit : opts.rename_limit; + opts.rename_score = s->rename_score >= 0 ? s->rename_score : opts.rename_score; + opts.flags.recursive = 1; + diff_setup_done(&opts); + + diff_tree_oid(old_treeish, new_treeish, "", &opts); + diffcore_std(&opts); + diff_flush(&opts); + wt_status_get_state(s->repo, &s->state, 0); + + diff_free(&opts); +} + static void wt_status_collect_changes_worktree(struct wt_status *s) { struct rev_info rev; @@ -646,7 +670,7 @@ static void wt_status_collect_changes_index(struct wt_status *s) repo_init_revisions(s->repo, &rev, NULL); memset(&opt, 0, sizeof(opt)); - opt.def = s->is_initial ? empty_tree_oid_hex(the_repository->hash_algo) : s->reference; + opt.def = s->is_initial ? empty_tree_oid_hex(s->repo->hash_algo) : s->reference; setup_revisions(0, NULL, &rev, &opt); rev.diffopt.flags.override_submodule_config = 1; @@ -984,17 +1008,17 @@ static int stash_count_refs(const char *refname UNUSED, return 0; } -static int count_stash_entries(void) +static int count_stash_entries(struct repository *r) { int n = 0; - refs_for_each_reflog_ent(get_main_ref_store(the_repository), + refs_for_each_reflog_ent(get_main_ref_store(r), "refs/stash", stash_count_refs, &n); return n; } static void wt_longstatus_print_stash_summary(struct wt_status *s) { - int stash_count = count_stash_entries(); + int stash_count = count_stash_entries(s->repo); if (stash_count > 0) status_printf_ln(s, GIT_COLOR_NORMAL, @@ -1146,7 +1170,7 @@ static void wt_longstatus_print_verbose(struct wt_status *s) rev.diffopt.ita_invisible_in_index = 1; memset(&opt, 0, sizeof(opt)); - opt.def = s->is_initial ? empty_tree_oid_hex(the_repository->hash_algo) : s->reference; + opt.def = s->is_initial ? empty_tree_oid_hex(s->repo->hash_algo) : s->reference; setup_revisions(0, NULL, &rev, &opt); rev.diffopt.output_format |= DIFF_FORMAT_PATCH; @@ -1287,10 +1311,10 @@ static void show_am_in_progress(struct wt_status *s, wt_longstatus_print_trailer(s); } -static char *read_line_from_git_path(const char *filename) +static char *read_line_from_git_path(struct repository *r, const char *filename) { struct strbuf buf = STRBUF_INIT; - FILE *fp = fopen_or_warn(repo_git_path_append(the_repository, &buf, + FILE *fp = fopen_or_warn(repo_git_path_append(r, &buf, "%s", filename), "r"); if (!fp) { @@ -1317,16 +1341,16 @@ static int split_commit_in_progress(struct wt_status *s) !s->branch || strcmp(s->branch, "HEAD")) return 0; - if (refs_read_ref_full(get_main_ref_store(the_repository), "HEAD", RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE, + if (refs_read_ref_full(get_main_ref_store(s->repo), "HEAD", RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE, &head_oid, &head_flags) || - refs_read_ref_full(get_main_ref_store(the_repository), "ORIG_HEAD", RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE, + refs_read_ref_full(get_main_ref_store(s->repo), "ORIG_HEAD", RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE, &orig_head_oid, &orig_head_flags)) return 0; if (head_flags & REF_ISSYMREF || orig_head_flags & REF_ISSYMREF) return 0; - rebase_amend = read_line_from_git_path("rebase-merge/amend"); - rebase_orig_head = read_line_from_git_path("rebase-merge/orig-head"); + rebase_amend = read_line_from_git_path(s->repo, "rebase-merge/amend"); + rebase_orig_head = read_line_from_git_path(s->repo, "rebase-merge/orig-head"); if (!rebase_amend || !rebase_orig_head) ; /* fall through, no split in progress */ @@ -1350,7 +1374,7 @@ static int split_commit_in_progress(struct wt_status *s) * The function assumes that the line does not contain useless spaces * before or after the command. */ -static void abbrev_oid_in_line(struct strbuf *line) +static void abbrev_oid_in_line(struct repository *r, struct strbuf *line) { struct string_list split = STRING_LIST_INIT_DUP; struct object_id oid; @@ -1362,7 +1386,7 @@ static void abbrev_oid_in_line(struct strbuf *line) return; if ((2 <= string_list_split(&split, line->buf, " ", 2)) && - !repo_get_oid(the_repository, split.items[1].string, &oid)) { + !repo_get_oid(r, split.items[1].string, &oid)) { strbuf_reset(line); strbuf_addf(line, "%s ", split.items[0].string); strbuf_add_unique_abbrev(line, &oid, DEFAULT_ABBREV); @@ -1372,10 +1396,10 @@ static void abbrev_oid_in_line(struct strbuf *line) string_list_clear(&split, 0); } -static int read_rebase_todolist(const char *fname, struct string_list *lines) +static int read_rebase_todolist(struct repository *r, const char *fname, struct string_list *lines) { struct strbuf buf = STRBUF_INIT; - FILE *f = fopen(repo_git_path_append(the_repository, &buf, "%s", fname), "r"); + FILE *f = fopen(repo_git_path_append(r, &buf, "%s", fname), "r"); int ret; if (!f) { @@ -1384,7 +1408,7 @@ static int read_rebase_todolist(const char *fname, struct string_list *lines) goto out; } die_errno("Could not open file %s for reading", - repo_git_path_replace(the_repository, &buf, "%s", fname)); + repo_git_path_replace(r, &buf, "%s", fname)); } while (!strbuf_getline_lf(&buf, f)) { if (starts_with(buf.buf, comment_line_str)) @@ -1392,7 +1416,7 @@ static int read_rebase_todolist(const char *fname, struct string_list *lines) strbuf_trim(&buf); if (!buf.len) continue; - abbrev_oid_in_line(&buf); + abbrev_oid_in_line(r, &buf); string_list_append(lines, buf.buf); } fclose(f); @@ -1413,8 +1437,8 @@ static void show_rebase_information(struct wt_status *s, struct string_list have_done = STRING_LIST_INIT_DUP; struct string_list yet_to_do = STRING_LIST_INIT_DUP; - read_rebase_todolist("rebase-merge/done", &have_done); - if (read_rebase_todolist("rebase-merge/git-rebase-todo", + read_rebase_todolist(s->repo, "rebase-merge/done", &have_done); + if (read_rebase_todolist(s->repo, "rebase-merge/git-rebase-todo", &yet_to_do)) status_printf_ln(s, color, _("git-rebase-todo is missing.")); @@ -1432,7 +1456,7 @@ static void show_rebase_information(struct wt_status *s, i++) status_printf_ln(s, color, " %s", have_done.items[i].string); if (have_done.nr > nr_lines_to_show && s->hints) { - char *path = repo_git_path(the_repository, "rebase-merge/done"); + char *path = repo_git_path(s->repo, "rebase-merge/done"); status_printf_ln(s, color, _(" (see more in file %s)"), path); free(path); @@ -1534,7 +1558,7 @@ static void show_cherry_pick_in_progress(struct wt_status *s, else status_printf_ln(s, color, _("You are currently cherry-picking commit %s."), - repo_find_unique_abbrev(the_repository, &s->state.cherry_pick_head_oid, + repo_find_unique_abbrev(s->repo, &s->state.cherry_pick_head_oid, DEFAULT_ABBREV)); if (s->hints) { @@ -1564,7 +1588,7 @@ static void show_revert_in_progress(struct wt_status *s, else status_printf_ln(s, color, _("You are currently reverting commit %s."), - repo_find_unique_abbrev(the_repository, &s->state.revert_head_oid, + repo_find_unique_abbrev(s->repo, &s->state.revert_head_oid, DEFAULT_ABBREV)); if (s->hints) { if (has_unmerged(s)) @@ -1624,7 +1648,7 @@ static char *get_branch(const struct worktree *wt, const char *path) struct object_id oid; const char *branch_name; - if (strbuf_read_file(&sb, worktree_git_path(the_repository, wt, "%s", path), 0) <= 0) + if (strbuf_read_file(&sb, worktree_git_path(wt, "%s", path), 0) <= 0) goto got_nothing; while (sb.len && sb.buf[sb.len - 1] == '\n') @@ -1691,7 +1715,7 @@ static void wt_status_get_detached_from(struct repository *r, char *ref = NULL; strbuf_init(&cb.buf, 0); - if (refs_for_each_reflog_ent_reverse(get_main_ref_store(the_repository), "HEAD", grab_1st_switch, &cb) <= 0) { + if (refs_for_each_reflog_ent_reverse(get_main_ref_store(r), "HEAD", grab_1st_switch, &cb) <= 0) { strbuf_release(&cb.buf); return; } @@ -1723,18 +1747,21 @@ int wt_status_check_rebase(const struct worktree *wt, { struct stat st; - if (!stat(worktree_git_path(the_repository, wt, "rebase-apply"), &st)) { - if (!stat(worktree_git_path(the_repository, wt, "rebase-apply/applying"), &st)) { + if (!wt) + BUG("wt_status_check_rebase() called with NULL worktree"); + + if (!stat(worktree_git_path(wt, "rebase-apply"), &st)) { + if (!stat(worktree_git_path(wt, "rebase-apply/applying"), &st)) { state->am_in_progress = 1; - if (!stat(worktree_git_path(the_repository, wt, "rebase-apply/patch"), &st) && !st.st_size) + if (!stat(worktree_git_path(wt, "rebase-apply/patch"), &st) && !st.st_size) state->am_empty_patch = 1; } else { state->rebase_in_progress = 1; state->branch = get_branch(wt, "rebase-apply/head-name"); state->onto = get_branch(wt, "rebase-apply/onto"); } - } else if (!stat(worktree_git_path(the_repository, wt, "rebase-merge"), &st)) { - if (!stat(worktree_git_path(the_repository, wt, "rebase-merge/interactive"), &st)) + } else if (!stat(worktree_git_path(wt, "rebase-merge"), &st)) { + if (!stat(worktree_git_path(wt, "rebase-merge/interactive"), &st)) state->rebase_interactive_in_progress = 1; else state->rebase_in_progress = 1; @@ -1750,7 +1777,10 @@ int wt_status_check_bisect(const struct worktree *wt, { struct stat st; - if (!stat(worktree_git_path(the_repository, wt, "BISECT_LOG"), &st)) { + if (!wt) + BUG("wt_status_check_bisect() called with NULL worktree"); + + if (!stat(worktree_git_path(wt, "BISECT_LOG"), &st)) { state->bisect_in_progress = 1; state->bisecting_from = get_branch(wt, "BISECT_START"); return 1; @@ -1763,8 +1793,10 @@ static void wt_status_check_sparse_checkout(struct repository *r, { int skip_worktree = 0; int i; + struct repo_config_values *cfg = repo_config_values(the_repository); - if (!core_apply_sparse_checkout || r->index->cache_nr == 0) { + if (!cfg->apply_sparse_checkout || + r->index->cache_nr == 0) { /* * Don't compute percentage of checked out files if we * aren't in a sparse checkout or would get division by 0. @@ -1795,18 +1827,19 @@ void wt_status_get_state(struct repository *r, struct stat st; struct object_id oid; enum replay_action action; + struct worktree *wt = get_worktree_from_repository(r); if (!stat(git_path_merge_head(r), &st)) { - wt_status_check_rebase(NULL, state); + wt_status_check_rebase(wt, state); state->merge_in_progress = 1; - } else if (wt_status_check_rebase(NULL, state)) { + } else if (wt_status_check_rebase(wt, state)) { ; /* all set */ } else if (refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD") && !repo_get_oid(r, "CHERRY_PICK_HEAD", &oid)) { state->cherry_pick_in_progress = 1; oidcpy(&state->cherry_pick_head_oid, &oid); } - wt_status_check_bisect(NULL, state); + wt_status_check_bisect(wt, state); if (refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD") && !repo_get_oid(r, "REVERT_HEAD", &oid)) { state->revert_in_progress = 1; @@ -1815,15 +1848,17 @@ void wt_status_get_state(struct repository *r, if (!sequencer_get_last_command(r, &action)) { if (action == REPLAY_PICK && !state->cherry_pick_in_progress) { state->cherry_pick_in_progress = 1; - oidcpy(&state->cherry_pick_head_oid, null_oid(the_hash_algo)); + oidcpy(&state->cherry_pick_head_oid, null_oid(r->hash_algo)); } else if (action == REPLAY_REVERT && !state->revert_in_progress) { state->revert_in_progress = 1; - oidcpy(&state->revert_head_oid, null_oid(the_hash_algo)); + oidcpy(&state->revert_head_oid, null_oid(r->hash_algo)); } } if (get_detached_from) wt_status_get_detached_from(r, state); wt_status_check_sparse_checkout(r, state); + + free_worktree(wt); } static void wt_longstatus_print_state(struct wt_status *s) @@ -2099,7 +2134,7 @@ static void wt_shortstatus_print_tracking(struct wt_status *s) upstream_is_gone = 1; } - short_base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository), + short_base = refs_shorten_unambiguous_ref(get_main_ref_store(s->repo), base, 0); color_fprintf(s->fp, header_color, "..."); color_fprintf(s->fp, branch_color_remote, "%s", short_base); @@ -2233,7 +2268,7 @@ static void wt_porcelain_v2_print_tracking(struct wt_status *s) ab_info = stat_tracking_info(branch, &nr_ahead, &nr_behind, &base, 0, s->ahead_behind_flags); if (base) { - base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository), + base = refs_shorten_unambiguous_ref(get_main_ref_store(s->repo), base, 0); fprintf(s->fp, "# branch.upstream %s%c", base, eol); free((char *)base); @@ -2259,7 +2294,7 @@ static void wt_porcelain_v2_print_tracking(struct wt_status *s) */ static void wt_porcelain_v2_print_stash(struct wt_status *s) { - int stash_count = count_stash_entries(); + int stash_count = count_stash_entries(s->repo); char eol = s->null_termination ? '\0' : '\n'; if (stash_count > 0) @@ -2630,7 +2665,7 @@ int has_uncommitted_changes(struct repository *r, * We have no head (or it's corrupt); use the empty tree, * which will complain if the index is non-empty. */ - struct tree *tree = lookup_tree(r, the_hash_algo->empty_tree); + struct tree *tree = lookup_tree(r, r->hash_algo->empty_tree); add_pending_object(&rev_info, &tree->object, ""); }
diff --git a/wt-status.h b/wt-status.h index e40a272..e9fe32e 100644 --- a/wt-status.h +++ b/wt-status.h
@@ -153,6 +153,15 @@ void wt_status_add_cut_line(struct wt_status *s); void wt_status_prepare(struct repository *r, struct wt_status *s); void wt_status_print(struct wt_status *s); void wt_status_collect(struct wt_status *s); + +/* + * Collect all changes between the two trees. Changes will be displayed as if + * they were staged into the index. + */ +void wt_status_collect_changes_trees(struct wt_status *s, + const struct object_id *old_treeish, + const struct object_id *new_treeish); + /* * Frees the buffers allocated by wt_status_collect. */
diff --git a/xdiff-interface.c b/xdiff-interface.c index 4971f72..f043330 100644 --- a/xdiff-interface.c +++ b/xdiff-interface.c
@@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" @@ -177,18 +176,19 @@ int read_mmfile(mmfile_t *ptr, const char *filename) return 0; } -void read_mmblob(mmfile_t *ptr, const struct object_id *oid) +void read_mmblob(mmfile_t *ptr, struct object_database *odb, + const struct object_id *oid) { unsigned long size; enum object_type type; - if (oideq(oid, null_oid(the_hash_algo))) { + if (is_null_oid(oid)) { ptr->ptr = xstrdup(""); ptr->size = 0; return; } - ptr->ptr = odb_read_object(the_repository->objects, oid, &type, &size); + ptr->ptr = odb_read_object(odb, oid, &type, &size); if (!ptr->ptr || type != OBJ_BLOB) die("unable to read blob object %s", oid_to_hex(oid)); ptr->size = size; @@ -300,7 +300,7 @@ void xdiff_clear_find_func(xdemitconf_t *xecfg) unsigned long xdiff_hash_string(const char *s, size_t len, long flags) { - return xdl_hash_record(&s, s + len, flags); + return xdl_hash_record((uint8_t const**)&s, (uint8_t const*)s + len, flags); } int xdiff_compare_lines(const char *l1, long s1,
diff --git a/xdiff-interface.h b/xdiff-interface.h index dfc55da..fbc4cee 100644 --- a/xdiff-interface.h +++ b/xdiff-interface.h
@@ -4,6 +4,8 @@ #include "hash.h" #include "xdiff/xdiff.h" +struct object_database; + /* * xdiff isn't equipped to handle content over a gigabyte; * we make the cutoff 1GB - 1MB to give some breathing @@ -45,7 +47,8 @@ int xdi_diff_outf(mmfile_t *mf1, mmfile_t *mf2, void *consume_callback_data, xpparam_t const *xpp, xdemitconf_t const *xecfg); int read_mmfile(mmfile_t *ptr, const char *filename); -void read_mmblob(mmfile_t *ptr, const struct object_id *oid); +void read_mmblob(mmfile_t *ptr, struct object_database *odb, + const struct object_id *oid); int buffer_is_binary(const char *ptr, unsigned long size); void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);
diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index 2cecde5..dc37071 100644 --- a/xdiff/xdiff.h +++ b/xdiff/xdiff.h
@@ -43,7 +43,7 @@ extern "C" { #define XDF_PATIENCE_DIFF (1 << 14) #define XDF_HISTOGRAM_DIFF (1 << 15) -#define XDF_DIFF_ALGORITHM_MASK (XDF_PATIENCE_DIFF | XDF_HISTOGRAM_DIFF) +#define XDF_DIFF_ALGORITHM_MASK (XDF_PATIENCE_DIFF | XDF_HISTOGRAM_DIFF | XDF_NEED_MINIMAL) #define XDF_DIFF_ALG(x) ((x) & XDF_DIFF_ALGORITHM_MASK) #define XDF_INDENT_HEURISTIC (1 << 23)
diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c index 6f3998e..5455b46 100644 --- a/xdiff/xdiffi.c +++ b/xdiff/xdiffi.c
@@ -22,9 +22,9 @@ #include "xinclude.h" -static unsigned long get_hash(xdfile_t *xdf, long index) +static size_t get_hash(xdfile_t *xdf, long index) { - return xdf->recs[xdf->rindex[index]].ha; + return xdf->recs[xdf->reference_index[index]].minimal_perfect_hash; } #define XDL_MAX_COST_MIN 256 @@ -278,10 +278,10 @@ int xdl_recs_cmp(xdfile_t *xdf1, long off1, long lim1, */ if (off1 == lim1) { for (; off2 < lim2; off2++) - xdf2->changed[xdf2->rindex[off2]] = true; + xdf2->changed[xdf2->reference_index[off2]] = true; } else if (off2 == lim2) { for (; off1 < lim1; off1++) - xdf1->changed[xdf1->rindex[off1]] = true; + xdf1->changed[xdf1->reference_index[off1]] = true; } else { xdpsplit_t spl; spl.i1 = spl.i2 = 0; @@ -385,7 +385,7 @@ static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, static int recs_match(xrecord_t *rec1, xrecord_t *rec2) { - return (rec1->ha == rec2->ha); + return rec1->minimal_perfect_hash == rec2->minimal_perfect_hash; } /* @@ -403,11 +403,10 @@ static int recs_match(xrecord_t *rec1, xrecord_t *rec2) */ static int get_indent(xrecord_t *rec) { - long i; int ret = 0; - for (i = 0; i < rec->size; i++) { - char c = rec->ptr[i]; + for (size_t i = 0; i < rec->size; i++) { + char c = (char) rec->ptr[i]; if (!XDL_ISSPACE(c)) return ret; @@ -484,7 +483,7 @@ static void measure_split(const xdfile_t *xdf, long split, { long i; - if (split >= xdf->nrec) { + if (split >= (long)xdf->nrec) { m->end_of_file = 1; m->indent = -1; } else { @@ -507,7 +506,7 @@ static void measure_split(const xdfile_t *xdf, long split, m->post_blank = 0; m->post_indent = -1; - for (i = split + 1; i < xdf->nrec; i++) { + for (i = split + 1; i < (long)xdf->nrec; i++) { m->post_indent = get_indent(&xdf->recs[i]); if (m->post_indent != -1) break; @@ -718,7 +717,7 @@ static void group_init(xdfile_t *xdf, struct xdlgroup *g) */ static inline int group_next(xdfile_t *xdf, struct xdlgroup *g) { - if (g->end == xdf->nrec) + if (g->end == (long)xdf->nrec) return -1; g->start = g->end + 1; @@ -751,7 +750,7 @@ static inline int group_previous(xdfile_t *xdf, struct xdlgroup *g) */ static int group_slide_down(xdfile_t *xdf, struct xdlgroup *g) { - if (g->end < xdf->nrec && + if (g->end < (long)xdf->nrec && recs_match(&xdf->recs[g->start], &xdf->recs[g->end])) { xdf->changed[g->start++] = false; xdf->changed[g->end++] = true; @@ -793,6 +792,7 @@ static int group_slide_up(xdfile_t *xdf, struct xdlgroup *g) */ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { struct xdlgroup g, go; + struct xdlgroup g_orig; long earliest_end, end_matching_other; long groupsize; @@ -806,10 +806,12 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { if (g.end == g.start) goto next; + g_orig = g; + /* * Now shift the change up and then down as far as possible in * each direction. If it bumps into any other changes, merge - * them. + * them and restart the process. */ do { groupsize = g.end - g.start; @@ -862,7 +864,8 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { /* * Move the possibly merged group of changes back to * line up with the last group of changes from the - * other file that it can align with. + * other file that it can align with. This avoids breaking + * a single change into a separate addition/deletion. */ while (go.end == go.start) { if (group_slide_up(xdf, &g)) @@ -915,6 +918,45 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { } } + /* + * If we merged change groups during shifting, the new + * combined group could now have matching lines in both files, + * even if the original separate groups did not. Re-diff the + * new group to find these matching lines to mark them as + * unchanged. + * + * Only do this if the corresponding group in the other file is + * non-empty, as it's trivial otherwise. + * + * Only do this for histogram diff as its LCS algorithm allows + * for this scenario. In contrast, patience diff finds LCS + * of unique lines that groups cannot be shifted across. + * Myer's diff (standalone or used as fall-back in patience + * diff) already finds minimal edits so it is not possible for + * shifted groups to result in a smaller diff. (Without + * XDF_NEED_MINIMAL, Myer's isn't technically guaranteed to be + * minimal, but it should be so most of the time) + */ + if (go.end != go.start && + XDF_DIFF_ALG(flags) == XDF_HISTOGRAM_DIFF && + (g.start != g_orig.start || + g.end != g_orig.end)) { + xpparam_t xpp; + xdfenv_t xe; + + memset(&xpp, 0, sizeof(xpp)); + xpp.flags = flags & ~XDF_DIFF_ALGORITHM_MASK; + + xe.xdf1 = *xdf; + xe.xdf2 = *xdfo; + + if (xdl_fall_back_diff(&xe, &xpp, + g.start + 1, g.end - g.start, + go.start + 1, go.end - go.start)) { + return -1; + } + } + next: /* Move past the just-processed group: */ if (group_next(xdf, &g)) @@ -993,11 +1035,11 @@ static void xdl_mark_ignorable_lines(xdchange_t *xscr, xdfenv_t *xe, long flags) rec = &xe->xdf1.recs[xch->i1]; for (i = 0; i < xch->chg1 && ignore; i++) - ignore = xdl_blankline(rec[i].ptr, rec[i].size, flags); + ignore = xdl_blankline((const char *)rec[i].ptr, (long)rec[i].size, flags); rec = &xe->xdf2.recs[xch->i2]; for (i = 0; i < xch->chg2 && ignore; i++) - ignore = xdl_blankline(rec[i].ptr, rec[i].size, flags); + ignore = xdl_blankline((const char *)rec[i].ptr, (long)rec[i].size, flags); xch->ignore = ignore; } @@ -1008,7 +1050,7 @@ static int record_matches_regex(xrecord_t *rec, xpparam_t const *xpp) { size_t i; for (i = 0; i < xpp->ignore_regex_nr; i++) - if (!regexec_buf(xpp->ignore_regex[i], rec->ptr, rec->size, 1, + if (!regexec_buf(xpp->ignore_regex[i], (const char *)rec->ptr, rec->size, 1, ®match, 0)) return 1;
diff --git a/xdiff/xemit.c b/xdiff/xemit.c index b2f1f30..04f7e91 100644 --- a/xdiff/xemit.c +++ b/xdiff/xemit.c
@@ -27,7 +27,7 @@ static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t * { xrecord_t *rec = &xdf->recs[ri]; - if (xdl_emit_diffrec(rec->ptr, rec->size, pre, strlen(pre), ecb) < 0) + if (xdl_emit_diffrec((char const *)rec->ptr, (long)rec->size, pre, strlen(pre), ecb) < 0) return -1; return 0; @@ -113,8 +113,8 @@ static long match_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri, xrecord_t *rec = &xdf->recs[ri]; if (!xecfg->find_func) - return def_ff(rec->ptr, rec->size, buf, sz); - return xecfg->find_func(rec->ptr, rec->size, buf, sz, xecfg->find_func_priv); + return def_ff((const char *)rec->ptr, (long)rec->size, buf, sz); + return xecfg->find_func((const char *)rec->ptr, (long)rec->size, buf, sz, xecfg->find_func_priv); } static int is_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri) @@ -137,7 +137,7 @@ static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg, buf = func_line ? func_line->buf : dummy; size = func_line ? sizeof(func_line->buf) : sizeof(dummy); - for (l = start; l != limit && 0 <= l && l < xe->xdf1.nrec; l += step) { + for (l = start; l != limit && 0 <= l && l < (long)xe->xdf1.nrec; l += step) { long len = match_func_rec(&xe->xdf1, xecfg, l, buf, size); if (len >= 0) { if (func_line) @@ -151,7 +151,7 @@ static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg, static int is_empty_rec(xdfile_t *xdf, long ri) { xrecord_t *rec = &xdf->recs[ri]; - long i = 0; + size_t i = 0; for (; i < rec->size && XDL_ISSPACE(rec->ptr[i]); i++); @@ -179,14 +179,14 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, long fs1, i1 = xch->i1; /* Appended chunk? */ - if (i1 >= xe->xdf1.nrec) { + if (i1 >= (long)xe->xdf1.nrec) { long i2 = xch->i2; /* * We don't need additional context if * a whole function was added. */ - while (i2 < xe->xdf2.nrec) { + while (i2 < (long)xe->xdf2.nrec) { if (is_func_rec(&xe->xdf2, xecfg, i2)) goto post_context_calculation; i2++; @@ -196,7 +196,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, * Otherwise get more context from the * pre-image. */ - i1 = xe->xdf1.nrec - 1; + i1 = (long)xe->xdf1.nrec - 1; } fs1 = get_func_line(xe, xecfg, NULL, i1, -1); @@ -228,8 +228,8 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, post_context_calculation: lctx = xecfg->ctxlen; - lctx = XDL_MIN(lctx, xe->xdf1.nrec - (xche->i1 + xche->chg1)); - lctx = XDL_MIN(lctx, xe->xdf2.nrec - (xche->i2 + xche->chg2)); + lctx = XDL_MIN(lctx, (long)xe->xdf1.nrec - (xche->i1 + xche->chg1)); + lctx = XDL_MIN(lctx, (long)xe->xdf2.nrec - (xche->i2 + xche->chg2)); e1 = xche->i1 + xche->chg1 + lctx; e2 = xche->i2 + xche->chg2 + lctx; @@ -237,13 +237,13 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) { long fe1 = get_func_line(xe, xecfg, NULL, xche->i1 + xche->chg1, - xe->xdf1.nrec); + (long)xe->xdf1.nrec); while (fe1 > 0 && is_empty_rec(&xe->xdf1, fe1 - 1)) fe1--; if (fe1 < 0) - fe1 = xe->xdf1.nrec; + fe1 = (long)xe->xdf1.nrec; if (fe1 > e1) { - e2 = XDL_MIN(e2 + (fe1 - e1), xe->xdf2.nrec); + e2 = XDL_MIN(e2 + (fe1 - e1), (long)xe->xdf2.nrec); e1 = fe1; } @@ -254,7 +254,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, */ if (xche->next) { long l = XDL_MIN(xche->next->i1, - xe->xdf1.nrec - 1); + (long)xe->xdf1.nrec - 1); if (l - xecfg->ctxlen <= e1 || get_func_line(xe, xecfg, NULL, l, e1) < 0) { xche = xche->next;
diff --git a/xdiff/xhistogram.c b/xdiff/xhistogram.c index 6dc450b..5ae1282 100644 --- a/xdiff/xhistogram.c +++ b/xdiff/xhistogram.c
@@ -90,7 +90,7 @@ struct region { static int cmp_recs(xrecord_t *r1, xrecord_t *r2) { - return r1->ha == r2->ha; + return r1->minimal_perfect_hash == r2->minimal_perfect_hash; } @@ -98,7 +98,7 @@ static int cmp_recs(xrecord_t *r1, xrecord_t *r2) (cmp_recs(REC(i->env, s1, l1), REC(i->env, s2, l2))) #define TABLE_HASH(index, side, line) \ - XDL_HASHLONG((REC(index->env, side, line))->ha, index->table_bits) + XDL_HASHLONG((REC(index->env, side, line))->minimal_perfect_hash, index->table_bits) static int scanA(struct histindex *index, int line1, int count1) {
diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c index fd600cb..29dad98 100644 --- a/xdiff/xmerge.c +++ b/xdiff/xmerge.c
@@ -101,8 +101,8 @@ static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2, xrecord_t *rec2 = xe2->xdf2.recs + i2; for (i = 0; i < line_count; i++) { - int result = xdl_recmatch(rec1[i].ptr, rec1[i].size, - rec2[i].ptr, rec2[i].size, flags); + int result = xdl_recmatch((const char *)rec1[i].ptr, (long)rec1[i].size, + (const char *)rec2[i].ptr, (long)rec2[i].size, flags); if (!result) return -1; } @@ -119,11 +119,11 @@ static int xdl_recs_copy_0(int use_orig, xdfenv_t *xe, int i, int count, int nee if (count < 1) return 0; - for (i = 0; i < count; size += recs[i++].size) + for (i = 0; i < count; size += (int)recs[i++].size) if (dest) memcpy(dest + size, recs[i].ptr, recs[i].size); if (add_nl) { - i = recs[count - 1].size; + i = (int)recs[count - 1].size; if (i == 0 || recs[count - 1].ptr[i - 1] != '\n') { if (needs_cr) { if (dest) @@ -156,9 +156,9 @@ static int xdl_orig_copy(xdfenv_t *xe, int i, int count, int needs_cr, int add_n */ static int is_eol_crlf(xdfile_t *file, int i) { - long size; + size_t size; - if (i < file->nrec - 1) + if (i < (long)file->nrec - 1) /* All lines before the last *must* end in LF */ return (size = file->recs[i].size) > 1 && file->recs[i].ptr[size - 2] == '\r'; @@ -317,15 +317,15 @@ static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, continue; i = m->i1 + m->chg1; } - size += xdl_recs_copy(xe1, i, xe1->xdf2.nrec - i, 0, 0, + size += xdl_recs_copy(xe1, i, (int)xe1->xdf2.nrec - i, 0, 0, dest ? dest + size : NULL); return size; } static int recmatch(xrecord_t *rec1, xrecord_t *rec2, unsigned long flags) { - return xdl_recmatch(rec1->ptr, rec1->size, - rec2->ptr, rec2->size, flags); + return xdl_recmatch((const char *)rec1->ptr, (long)rec1->size, + (const char *)rec2->ptr, (long)rec2->size, flags); } /* @@ -382,10 +382,10 @@ static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m, * we have a very simple mmfile structure. */ t1.ptr = (char *)xe1->xdf2.recs[m->i1].ptr; - t1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1].ptr + t1.size = (char *)xe1->xdf2.recs[m->i1 + m->chg1 - 1].ptr + xe1->xdf2.recs[m->i1 + m->chg1 - 1].size - t1.ptr; t2.ptr = (char *)xe2->xdf2.recs[m->i2].ptr; - t2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1].ptr + t2.size = (char *)xe2->xdf2.recs[m->i2 + m->chg2 - 1].ptr + xe2->xdf2.recs[m->i2 + m->chg2 - 1].size - t2.ptr; if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0) return -1; @@ -440,8 +440,8 @@ static int line_contains_alnum(const char *ptr, long size) static int lines_contain_alnum(xdfenv_t *xe, int i, int chg) { for (; chg; chg--, i++) - if (line_contains_alnum(xe->xdf2.recs[i].ptr, - xe->xdf2.recs[i].size)) + if (line_contains_alnum((const char *)xe->xdf2.recs[i].ptr, + (long)xe->xdf2.recs[i].size)) return 1; return 0; } @@ -622,7 +622,7 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, changes = c; i0 = xscr1->i1; i1 = xscr1->i2; - i2 = xscr1->i1 + xe2->xdf2.nrec - xe2->xdf1.nrec; + i2 = xscr1->i1 + (long)xe2->xdf2.nrec - (long)xe2->xdf1.nrec; chg0 = xscr1->chg1; chg1 = xscr1->chg2; chg2 = xscr1->chg1; @@ -637,7 +637,7 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, if (!changes) changes = c; i0 = xscr2->i1; - i1 = xscr2->i1 + xe1->xdf2.nrec - xe1->xdf1.nrec; + i1 = xscr2->i1 + (long)xe1->xdf2.nrec - (long)xe1->xdf1.nrec; i2 = xscr2->i2; chg0 = xscr2->chg1; chg1 = xscr2->chg1;
diff --git a/xdiff/xpatience.c b/xdiff/xpatience.c index 669b653..7953490 100644 --- a/xdiff/xpatience.c +++ b/xdiff/xpatience.c
@@ -48,7 +48,7 @@ struct hashmap { int nr, alloc; struct entry { - unsigned long hash; + size_t minimal_perfect_hash; /* * 0 = unused entry, 1 = first line, 2 = second, etc. * line2 is NON_UNIQUE if the line is not unique @@ -61,12 +61,6 @@ struct hashmap { * initially, "next" reflects only the order in file1. */ struct entry *next, *previous; - - /* - * If 1, this entry can serve as an anchor. See - * Documentation/diff-options.adoc for more information. - */ - unsigned anchor : 1; } *entries, *first, *last; /* were common records found? */ unsigned long has_matches; @@ -85,8 +79,7 @@ static int is_anchor(xpparam_t const *xpp, const char *line) } /* The argument "pass" is 1 for the first file, 2 for the second. */ -static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map, - int pass) +static void insert_record(int line, struct hashmap *map, int pass) { xrecord_t *records = pass == 1 ? map->env->xdf1.recs : map->env->xdf2.recs; @@ -101,10 +94,10 @@ static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map, * So we multiply ha by 2 in the hope that the hashing was * "unique enough". */ - int index = (int)((record->ha << 1) % map->alloc); + int index = (int)((record->minimal_perfect_hash << 1) % map->alloc); while (map->entries[index].line1) { - if (map->entries[index].hash != record->ha) { + if (map->entries[index].minimal_perfect_hash != record->minimal_perfect_hash) { if (++index >= map->alloc) index = 0; continue; @@ -120,8 +113,7 @@ static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map, if (pass == 2) return; map->entries[index].line1 = line; - map->entries[index].hash = record->ha; - map->entries[index].anchor = is_anchor(xpp, map->env->xdf1.recs[line - 1].ptr); + map->entries[index].minimal_perfect_hash = record->minimal_perfect_hash; if (!map->first) map->first = map->entries + index; if (map->last) { @@ -153,11 +145,11 @@ static int fill_hashmap(xpparam_t const *xpp, xdfenv_t *env, /* First, fill with entries from the first file */ while (count1--) - insert_record(xpp, line1++, result, 1); + insert_record(line1++, result, 1); /* Then search for matches in the second file */ while (count2--) - insert_record(xpp, line2++, result, 2); + insert_record(line2++, result, 2); return 0; } @@ -194,6 +186,8 @@ static int binary_search(struct entry **sequence, int longest, */ static int find_longest_common_sequence(struct hashmap *map, struct entry **res) { + xpparam_t const *xpp = map->xpp; + xrecord_t const *recs = map->env->xdf2.recs; struct entry **sequence; int longest = 0, i; struct entry *entry; @@ -211,13 +205,16 @@ static int find_longest_common_sequence(struct hashmap *map, struct entry **res) for (entry = map->first; entry; entry = entry->next) { if (!entry->line2 || entry->line2 == NON_UNIQUE) continue; - i = binary_search(sequence, longest, entry); + if (longest == 0 || entry->line2 > sequence[longest - 1]->line2) + i = longest - 1; + else + i = binary_search(sequence, longest, entry); entry->previous = i < 0 ? NULL : sequence[i]; ++i; if (i <= anchor_i) continue; sequence[i] = entry; - if (entry->anchor) { + if (is_anchor(xpp, (const char*)recs[entry->line2 - 1].ptr)) { anchor_i = i; longest = anchor_i + 1; } else if (i == longest) { @@ -248,7 +245,7 @@ static int match(struct hashmap *map, int line1, int line2) { xrecord_t *record1 = &map->env->xdf1.recs[line1 - 1]; xrecord_t *record2 = &map->env->xdf2.recs[line2 - 1]; - return record1->ha == record2->ha; + return record1->minimal_perfect_hash == record2->minimal_perfect_hash; } static int patience_diff(xpparam_t const *xpp, xdfenv_t *env, @@ -370,5 +367,5 @@ static int patience_diff(xpparam_t const *xpp, xdfenv_t *env, int xdl_do_patience_diff(xpparam_t const *xpp, xdfenv_t *env) { - return patience_diff(xpp, env, 1, env->xdf1.nrec, 1, env->xdf2.nrec); + return patience_diff(xpp, env, 1, (int)env->xdf1.nrec, 1, (int)env->xdf2.nrec); }
diff --git a/xdiff/xprepare.c b/xdiff/xprepare.c index 192334f..cd4fc40 100644 --- a/xdiff/xprepare.c +++ b/xdiff/xprepare.c
@@ -34,8 +34,10 @@ #define INVESTIGATE 2 typedef struct s_xdlclass { + uint64_t line_hash; struct s_xdlclass *next; - xrecord_t rec; + const uint8_t *ptr; + size_t size; long idx; long len1, len2; } xdlclass_t; @@ -92,15 +94,16 @@ static void xdl_free_classifier(xdlclassifier_t *cf) { } -static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t *rec) { - long hi; +static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t *rec, + uint64_t line_hash) { + size_t hi; xdlclass_t *rcrec; - hi = (long) XDL_HASHLONG(rec->ha, cf->hbits); + hi = XDL_HASHLONG(line_hash, cf->hbits); for (rcrec = cf->rchash[hi]; rcrec; rcrec = rcrec->next) - if (rcrec->rec.ha == rec->ha && - xdl_recmatch(rcrec->rec.ptr, rcrec->rec.size, - rec->ptr, rec->size, cf->flags)) + if (rcrec->line_hash == line_hash && + xdl_recmatch((const char *)rcrec->ptr, (long)rcrec->size, + (const char *)rec->ptr, (long)rec->size, cf->flags)) break; if (!rcrec) { @@ -112,7 +115,9 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t if (XDL_ALLOC_GROW(cf->rcrecs, cf->count, cf->alloc)) return -1; cf->rcrecs[rcrec->idx] = rcrec; - rcrec->rec = *rec; + rcrec->line_hash = line_hash; + rcrec->ptr = rec->ptr; + rcrec->size = rec->size; rcrec->len1 = rcrec->len2 = 0; rcrec->next = cf->rchash[hi]; cf->rchash[hi] = rcrec; @@ -120,7 +125,7 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t (pass == 1) ? rcrec->len1++ : rcrec->len2++; - rec->ha = (unsigned long) rcrec->idx; + rec->minimal_perfect_hash = (size_t)rcrec->idx; return 0; } @@ -128,7 +133,7 @@ static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t static void xdl_free_ctx(xdfile_t *xdf) { - xdl_free(xdf->rindex); + xdl_free(xdf->reference_index); xdl_free(xdf->changed - 1); xdl_free(xdf->recs); } @@ -137,11 +142,11 @@ static void xdl_free_ctx(xdfile_t *xdf) static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp, xdlclassifier_t *cf, xdfile_t *xdf) { long bsize; - unsigned long hav; - char const *blk, *cur, *top, *prev; + uint64_t hav; + uint8_t const *blk, *cur, *top, *prev; xrecord_t *crec; - xdf->rindex = NULL; + xdf->reference_index = NULL; xdf->changed = NULL; xdf->recs = NULL; @@ -153,13 +158,12 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_ for (top = blk + bsize; cur < top; ) { prev = cur; hav = xdl_hash_record(&cur, top, xpp->flags); - if (XDL_ALLOC_GROW(xdf->recs, xdf->nrec + 1, narec)) + if (XDL_ALLOC_GROW(xdf->recs, (long)xdf->nrec + 1, narec)) goto abort; crec = &xdf->recs[xdf->nrec++]; crec->ptr = prev; - crec->size = (long) (cur - prev); - crec->ha = hav; - if (xdl_classify_record(pass, cf, crec) < 0) + crec->size = cur - prev; + if (xdl_classify_record(pass, cf, crec, hav) < 0) goto abort; } } @@ -169,7 +173,7 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_ if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) && (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF)) { - if (!XDL_ALLOC_ARRAY(xdf->rindex, xdf->nrec + 1)) + if (!XDL_ALLOC_ARRAY(xdf->reference_index, xdf->nrec + 1)) goto abort; } @@ -264,7 +268,7 @@ static bool xdl_clean_mmatch(uint8_t const *action, long i, long s, long e) { * might be potentially discarded if they appear in a run of discardable. */ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) { - long i, nm, nreff, mlim; + long i, nm, mlim; xrecord_t *recs; xdlclass_t *rcrec; uint8_t *action1 = NULL, *action2 = NULL; @@ -287,18 +291,18 @@ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd /* * Initialize temporary arrays with DISCARD, KEEP, or INVESTIGATE. */ - if ((mlim = xdl_bogosqrt(xdf1->nrec)) > XDL_MAX_EQLIMIT) + if ((mlim = xdl_bogosqrt((long)xdf1->nrec)) > XDL_MAX_EQLIMIT) mlim = XDL_MAX_EQLIMIT; for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) { - rcrec = cf->rcrecs[recs->ha]; + rcrec = cf->rcrecs[recs->minimal_perfect_hash]; nm = rcrec ? rcrec->len2 : 0; action1[i] = (nm == 0) ? DISCARD: (nm >= mlim && !need_min) ? INVESTIGATE: KEEP; } - if ((mlim = xdl_bogosqrt(xdf2->nrec)) > XDL_MAX_EQLIMIT) + if ((mlim = xdl_bogosqrt((long)xdf2->nrec)) > XDL_MAX_EQLIMIT) mlim = XDL_MAX_EQLIMIT; for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) { - rcrec = cf->rcrecs[recs->ha]; + rcrec = cf->rcrecs[recs->minimal_perfect_hash]; nm = rcrec ? rcrec->len1 : 0; action2[i] = (nm == 0) ? DISCARD: (nm >= mlim && !need_min) ? INVESTIGATE: KEEP; } @@ -307,29 +311,29 @@ static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xd * Use temporary arrays to decide if changed[i] should remain * false, or become true. */ - for (nreff = 0, i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; + xdf1->nreff = 0; + for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) { if (action1[i] == KEEP || (action1[i] == INVESTIGATE && !xdl_clean_mmatch(action1, i, xdf1->dstart, xdf1->dend))) { - xdf1->rindex[nreff++] = i; + xdf1->reference_index[xdf1->nreff++] = i; /* changed[i] remains false, i.e. keep */ } else xdf1->changed[i] = true; /* i.e. discard */ } - xdf1->nreff = nreff; - for (nreff = 0, i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; + xdf2->nreff = 0; + for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) { if (action2[i] == KEEP || (action2[i] == INVESTIGATE && !xdl_clean_mmatch(action2, i, xdf2->dstart, xdf2->dend))) { - xdf2->rindex[nreff++] = i; + xdf2->reference_index[xdf2->nreff++] = i; /* changed[i] remains false, i.e. keep */ } else xdf2->changed[i] = true; /* i.e. discard */ } - xdf2->nreff = nreff; cleanup: xdl_free(action1); @@ -348,9 +352,9 @@ static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) { recs1 = xdf1->recs; recs2 = xdf2->recs; - for (i = 0, lim = XDL_MIN(xdf1->nrec, xdf2->nrec); i < lim; + for (i = 0, lim = (long)XDL_MIN(xdf1->nrec, xdf2->nrec); i < lim; i++, recs1++, recs2++) - if (recs1->ha != recs2->ha) + if (recs1->minimal_perfect_hash != recs2->minimal_perfect_hash) break; xdf1->dstart = xdf2->dstart = i; @@ -358,11 +362,11 @@ static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) { recs1 = xdf1->recs + xdf1->nrec - 1; recs2 = xdf2->recs + xdf2->nrec - 1; for (lim -= i, i = 0; i < lim; i++, recs1--, recs2--) - if (recs1->ha != recs2->ha) + if (recs1->minimal_perfect_hash != recs2->minimal_perfect_hash) break; - xdf1->dend = xdf1->nrec - i - 1; - xdf2->dend = xdf2->nrec - i - 1; + xdf1->dend = (long)xdf1->nrec - i - 1; + xdf2->dend = (long)xdf2->nrec - i - 1; return 0; }
diff --git a/xdiff/xtypes.h b/xdiff/xtypes.h index f145abb..50aee77 100644 --- a/xdiff/xtypes.h +++ b/xdiff/xtypes.h
@@ -39,18 +39,18 @@ typedef struct s_chastore { } chastore_t; typedef struct s_xrecord { - char const *ptr; - long size; - unsigned long ha; + uint8_t const *ptr; + size_t size; + size_t minimal_perfect_hash; } xrecord_t; typedef struct s_xdfile { xrecord_t *recs; - long nrec; - long dstart, dend; + size_t nrec; + ptrdiff_t dstart, dend; bool *changed; - long *rindex; - long nreff; + size_t *reference_index; + size_t nreff; } xdfile_t; typedef struct s_xdfenv {
diff --git a/xdiff/xutils.c b/xdiff/xutils.c index 447e66c..77ee1ad 100644 --- a/xdiff/xutils.c +++ b/xdiff/xutils.c
@@ -249,11 +249,11 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) return 1; } -unsigned long xdl_hash_record_with_whitespace(char const **data, - char const *top, long flags) { - unsigned long ha = 5381; - char const *ptr = *data; - int cr_at_eol_only = (flags & XDF_WHITESPACE_FLAGS) == XDF_IGNORE_CR_AT_EOL; +uint64_t xdl_hash_record_with_whitespace(uint8_t const **data, + uint8_t const *top, uint64_t flags) { + uint64_t ha = 5381; + uint8_t const *ptr = *data; + bool cr_at_eol_only = (flags & XDF_WHITESPACE_FLAGS) == XDF_IGNORE_CR_AT_EOL; for (; ptr < top && *ptr != '\n'; ptr++) { if (cr_at_eol_only) { @@ -263,8 +263,8 @@ unsigned long xdl_hash_record_with_whitespace(char const **data, continue; } else if (XDL_ISSPACE(*ptr)) { - const char *ptr2 = ptr; - int at_eol; + const uint8_t *ptr2 = ptr; + bool at_eol; while (ptr + 1 < top && XDL_ISSPACE(ptr[1]) && ptr[1] != '\n') ptr++; @@ -274,20 +274,20 @@ unsigned long xdl_hash_record_with_whitespace(char const **data, else if (flags & XDF_IGNORE_WHITESPACE_CHANGE && !at_eol) { ha += (ha << 5); - ha ^= (unsigned long) ' '; + ha ^= (uint64_t) ' '; } else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL && !at_eol) { while (ptr2 != ptr + 1) { ha += (ha << 5); - ha ^= (unsigned long) *ptr2; + ha ^= (uint64_t) *ptr2; ptr2++; } } continue; } ha += (ha << 5); - ha ^= (unsigned long) *ptr; + ha ^= (uint64_t) *ptr; } *data = ptr < top ? ptr + 1: ptr; @@ -304,9 +304,9 @@ unsigned long xdl_hash_record_with_whitespace(char const **data, #define REASSOC_FENCE(x, y) #endif -unsigned long xdl_hash_record_verbatim(char const **data, char const *top) { - unsigned long ha = 5381, c0, c1; - char const *ptr = *data; +uint64_t xdl_hash_record_verbatim(uint8_t const **data, uint8_t const *top) { + uint64_t ha = 5381, c0, c1; + uint8_t const *ptr = *data; #if 0 /* * The baseline form of the optimized loop below. This is the djb2 @@ -314,7 +314,7 @@ unsigned long xdl_hash_record_verbatim(char const **data, char const *top) { */ for (; ptr < top && *ptr != '\n'; ptr++) { ha += (ha << 5); - ha += (unsigned long) *ptr; + ha += (uint64_t) *ptr; } *data = ptr < top ? ptr + 1: ptr; #else @@ -465,10 +465,10 @@ int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp, xdfenv_t env; subfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1].ptr; - subfile1.size = diff_env->xdf1.recs[line1 + count1 - 2].ptr + + subfile1.size = (char *)diff_env->xdf1.recs[line1 + count1 - 2].ptr + diff_env->xdf1.recs[line1 + count1 - 2].size - subfile1.ptr; subfile2.ptr = (char *)diff_env->xdf2.recs[line2 - 1].ptr; - subfile2.size = diff_env->xdf2.recs[line2 + count2 - 2].ptr + + subfile2.size = (char *)diff_env->xdf2.recs[line2 + count2 - 2].ptr + diff_env->xdf2.recs[line2 + count2 - 2].size - subfile2.ptr; if (xdl_do_diff(&subfile1, &subfile2, xpp, &env) < 0) return -1;
diff --git a/xdiff/xutils.h b/xdiff/xutils.h index 13f6831..615b4a9 100644 --- a/xdiff/xutils.h +++ b/xdiff/xutils.h
@@ -34,9 +34,9 @@ void *xdl_cha_alloc(chastore_t *cha); long xdl_guess_lines(mmfile_t *mf, long sample); int xdl_blankline(const char *line, long size, long flags); int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags); -unsigned long xdl_hash_record_verbatim(char const **data, char const *top); -unsigned long xdl_hash_record_with_whitespace(char const **data, char const *top, long flags); -static inline unsigned long xdl_hash_record(char const **data, char const *top, long flags) +uint64_t xdl_hash_record_verbatim(uint8_t const **data, uint8_t const *top); +uint64_t xdl_hash_record_with_whitespace(uint8_t const **data, uint8_t const *top, uint64_t flags); +static inline uint64_t xdl_hash_record(uint8_t const **data, uint8_t const *top, uint64_t flags) { if (flags & XDF_WHITESPACE_FLAGS) return xdl_hash_record_with_whitespace(data, top, flags);