diff options
608 files changed, 25784 insertions, 11276 deletions
diff --git a/.editorconfig b/.editorconfig index 2d3929b591..82e121a417 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,6 +8,10 @@ insert_final_newline = true 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 38b1c52fe0..556322be01 100644 --- a/.gitattributes +++ b/.gitattributes @@ -18,3 +18,4 @@ CODE_OF_CONDUCT.md -whitespace /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 c8755e38de..93042128d6 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -10,7 +10,8 @@ conveniently send your Pull Requests commits to our mailing list. 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/main.yml b/.github/workflows/main.yml index f2e93f5461..826f2f5d3a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -150,7 +150,7 @@ jobs: - 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 @@ jobs: 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,8 @@ jobs: name: windows-meson-artifacts path: build - name: Test - shell: pwsh - run: ci/run-test-slice-meson.sh build ${{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 diff --git a/.gitignore b/.gitignore index 78a45cb5be..24635cf2d6 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 b419a84e2c..83ec786c5a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -101,13 +101,13 @@ test:osx: 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 @@ test:mingw64: parallel: 10 .msvc-meson: + variables: + TEST_OUTPUT_DIRECTORY: "C:/Git-Test" tags: - saas-windows-medium-amd64 before_script: @@ -164,12 +166,13 @@ test:mingw64: - 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 @@ test:msvc-meson: - 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 @@ -224,7 +224,8 @@ Peter Krefting <peter@softwolves.pp.se> <peter@softwolves.pp.se> 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> @@ -282,6 +283,7 @@ Thomas Ackermann <th.acker@arcor.de> <th.acker66@arcor.de> 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 df72fe0177..b8670751f5 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines @@ -33,6 +33,16 @@ Git in general, a few rough rules are: 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 @@ For C programs: */ _("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 @@ For C programs: 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/MyFirstContribution.adoc b/Documentation/MyFirstContribution.adoc index f186dfbc89..b9fdefce02 100644 --- a/Documentation/MyFirstContribution.adoc +++ b/Documentation/MyFirstContribution.adoc @@ -331,7 +331,8 @@ on the command line, including the name of our command. (If `prefix` is empty 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 @@ function body: 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 @@ Add the following includes: ---- #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 @@ git-psuh - Delight users' typo with a shy horse SYNOPSIS -------- -[verse] -'git-psuh [<arg>...]' +[synopsis] +git psuh [<arg>...] DESCRIPTION ----------- @@ -726,9 +728,10 @@ $ prove -j$(nproc) --shuffle t[0-9]*.sh ---- 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.54.0.adoc b/Documentation/RelNotes/2.54.0.adoc new file mode 100644 index 0000000000..c692dddb4a --- /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 e270ccbe85..d570184ec8 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -37,11 +37,36 @@ most likely to be knowledgeable enough to help you, but 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 ff9ea0a294..31b883a72c 100644 --- a/Documentation/asciidoc.conf.in +++ b/Documentation/asciidoc.conf.in @@ -81,12 +81,18 @@ endif::backend-xhtml11[] 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/config/alias.adoc b/Documentation/config/alias.adoc index 80ce17d2de..dc6ca0ee08 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 5bcad2efb1..e9561e12d7 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 9bc9de29d9..a0ebf03e2e 100644 --- a/Documentation/config/core.adoc +++ b/Documentation/config/core.adoc @@ -348,6 +348,17 @@ confusion unless you know what you are doing (e.g. you are creating a 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 diff --git a/Documentation/config/extensions.adoc b/Documentation/config/extensions.adoc index 532456644b..be6678bb5b 100644 --- a/Documentation/config/extensions.adoc +++ b/Documentation/config/extensions.adoc @@ -57,10 +57,24 @@ For historical reasons, this extension is respected regardless of the `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 @@ relativeWorktrees::: 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/format.adoc b/Documentation/config/format.adoc index ab0710e86a..dbd186290b 100644 --- a/Documentation/config/format.adoc +++ b/Documentation/config/format.adoc @@ -101,6 +101,11 @@ format.coverLetter:: 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/hook.adoc b/Documentation/config/hook.adoc new file mode 100644 index 0000000000..9e78f26439 --- /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 9da5c298cc..849c89f36c 100644 --- a/Documentation/config/http.adoc +++ b/Documentation/config/http.adoc @@ -315,6 +315,32 @@ http.keepAliveCount:: 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 e45b2a8121..7b4abdaf8b 100644 --- a/Documentation/config/init.adoc +++ b/Documentation/config/init.adoc @@ -18,3 +18,9 @@ endif::[] 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 d0c38f03fa..b578856dde 100644 --- a/Documentation/config/maintenance.adoc +++ b/Documentation/config/maintenance.adoc @@ -30,8 +30,7 @@ The possible strategies are: + * `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 @@ The possible strategies are: 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 75402d5579..fa997c8597 100644 --- a/Documentation/config/pack.adoc +++ b/Documentation/config/pack.adoc @@ -160,12 +160,13 @@ pack.usePathWalk:: 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 93e5e0d9b5..b0fa43b839 100644 --- a/Documentation/config/promisor.adoc +++ b/Documentation/config/promisor.adoc @@ -89,3 +89,36 @@ variable. The fields are checked only if the `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/sendemail.adoc b/Documentation/config/sendemail.adoc index 90164c734d..6560ecc5ab 100644 --- a/Documentation/config/sendemail.adoc +++ b/Documentation/config/sendemail.adoc @@ -12,6 +12,22 @@ sendemail.smtpSSLCertPath:: 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 8caf90f51c..b5dd85b761 100644 --- a/Documentation/config/status.adoc +++ b/Documentation/config/status.adoc @@ -17,6 +17,31 @@ status.aheadBehind:: `--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 0672d99117..8dacb852c7 100644 --- a/Documentation/config/submodule.adoc +++ b/Documentation/config/submodule.adoc @@ -32,15 +32,16 @@ submodule.<name>.fetchRecurseSubmodules:: 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.<name>.active:: 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 60bc221c88..1bc70192d3 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 @@ If it is `start`, then each new trailer will appear at the start, 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 `add`, a new trailer will be added. + 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-context-options.adoc b/Documentation/diff-context-options.adoc index e161260358..b9ace2aa4b 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 9cdad6f72a..8a63b5e164 100644 --- a/Documentation/diff-options.adoc +++ b/Documentation/diff-options.adoc @@ -127,8 +127,10 @@ endif::git-log[] `-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[] @@ -859,10 +861,18 @@ endif::git-format-patch[] 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 fcba46ee9e..81a9d7f9bb 100644 --- a/Documentation/fetch-options.adoc +++ b/Documentation/fetch-options.adoc @@ -88,6 +88,25 @@ linkgit:git-config[1]. 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`:: @@ -234,6 +253,8 @@ endif::git-pull[] `--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 parallel. To control them independently, use the config settings diff --git a/Documentation/for-each-ref-options.adoc b/Documentation/for-each-ref-options.adoc index f13efb5f25..54e2fa95c2 100644 --- a/Documentation/for-each-ref-options.adoc +++ b/Documentation/for-each-ref-options.adoc @@ -30,8 +30,8 @@ TAB %(refname)`. `--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 0000000000..807a65b885 --- /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 0000000000..ec1ef79f5e --- /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/git-add.adoc b/Documentation/git-add.adoc index 6192daeb03..941135dc63 100644 --- a/Documentation/git-add.adoc +++ b/Documentation/git-add.adoc @@ -75,7 +75,10 @@ in linkgit:gitglossary[7]. `-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 0c94776e29..384e0cd7f9 100644 --- a/Documentation/git-am.adoc +++ b/Documentation/git-am.adoc @@ -9,7 +9,7 @@ git-am - Apply a series of patches from a mailbox 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 @@ OPTIONS -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 @@ OPTIONS 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 @@ OPTIONS -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 @@ OPTIONS -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 @@ This was optional in prior versions of git, but now it is the 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 @@ include::rerere-options.adoc[] --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 @@ Valid <action> for the `--whitespace` option are: --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 @@ -205,7 +208,8 @@ applying. 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. @@ -223,7 +227,7 @@ applying. 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`. @@ -233,6 +237,7 @@ applying. create an empty commit with the contents of the e-mail message as its log message. +[[discussion]] DISCUSSION ---------- @@ -252,16 +257,13 @@ where the patch begins. Excess whitespace at the end of each line is automatically stripped. The patch is expected to be inline, directly following the -message. 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: " +message. +include::format-patch-end-of-commit-message.adoc[] -is taken as the beginning of a patch, and the commit log message -is terminated before the first occurrence of such a line. +This means that the contents of the commit message can inadvertently +interrupt the processing (see the <<caveats,CAVEATS>> section below). -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: @@ -279,16 +281,25 @@ names. 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 b8394dcf22..246ab417c2 100644 --- a/Documentation/git-backfill.adoc +++ b/Documentation/git-backfill.adoc @@ -63,9 +63,12 @@ OPTIONS 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-clone.adoc b/Documentation/git-clone.adoc index 57cdfb7620..b6e1f8ada2 100644 --- a/Documentation/git-clone.adoc +++ b/Documentation/git-clone.adoc @@ -84,7 +84,7 @@ _<src>_. 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 @@ If you want to break the dependency of a repository cloned with `--shared` on 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 @@ objects from the source repository into a pack in the cloned repository. 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 @@ objects from the source repository into a pack in the cloned repository. 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 @@ objects from the source repository into a pack in the cloned repository. 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 @@ objects from the source repository into a pack in the cloned repository. 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 @@ objects from the source repository into a pack in the cloned repository. 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 @@ Configuration variables known to not take effect are: `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 @@ Specify the given ref storage format for the repository. The valid values are: + 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-config.adoc b/Documentation/git-config.adoc index ac3b536a15..00545b2054 100644 --- a/Documentation/git-config.adoc +++ b/Documentation/git-config.adoc @@ -221,7 +221,7 @@ Use `--no-value` to unset _<pattern>_. + 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 @@ Valid `<type>`'s include: 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:: diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc index 479c4081da..b3f42d4637 100644 --- a/Documentation/git-fast-import.adoc +++ b/Documentation/git-fast-import.adoc @@ -86,6 +86,10 @@ already trusted to run their own code. * `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-format-patch.adoc b/Documentation/git-format-patch.adoc index 9a7807ca71..5662382450 100644 --- a/Documentation/git-format-patch.adoc +++ b/Documentation/git-format-patch.adoc @@ -24,6 +24,7 @@ SYNOPSIS [(--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 @@ e.g., `--rfc='-(WIP)'` results in "PATCH (WIP)". --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 @@ feeding the result to `git send-email`. --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 @@ with configuration variables. signOff = true outputDirectory = <directory> coverLetter = auto + commitListFormat = shortlog coverFromDescription = auto ------------ @@ -798,6 +813,10 @@ if they are part of the requested range. A simple "patch" does not 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 0000000000..24dc907033 --- /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 f6cc72d2ca..318c637bd8 100644 --- a/Documentation/git-hook.adoc +++ b/Documentation/git-hook.adoc @@ -8,7 +8,8 @@ git-hook - Run git hooks 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 @@ DESCRIPTION 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 @@ OPTIONS 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 fd335fe772..77b4f63b05 100644 --- a/Documentation/git-interpret-trailers.adoc +++ b/Documentation/git-interpret-trailers.adoc @@ -7,14 +7,14 @@ git-interpret-trailers - Add or parse structured information in commit messages 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: Alice <alice@example.com> 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 @@ are applied to each input and the way any existing trailer in 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 one Git-generated or user-configured trailer and consists of at 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 @@ rules for RFC 822 headers. For example they do not follow the encoding rule. 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 @@ include::config/trailer.adoc[] 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 @@ Signed-off-by: Bob <bob@example.com> 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 format-patch -1 $ 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 @@ body text 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 @@ subject 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: Junio C Hamano <gitster@pobox.com> 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 @@ body text 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 @@ See-also: fe3187489d69c4 (subject of related commit) * 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 602843e095..d7d16fc4f7 100644 --- a/Documentation/git-last-modified.adoc +++ b/Documentation/git-last-modified.adoc @@ -9,7 +9,8 @@ git-last-modified - EXPERIMENTAL: Show when files were last modified 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 @@ OPTIONS `-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 @@ OPTIONS 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-merge-file.adoc b/Documentation/git-merge-file.adoc index 71915a00fa..9dc5d8a370 100644 --- a/Documentation/git-merge-file.adoc +++ b/Documentation/git-merge-file.adoc @@ -85,6 +85,9 @@ object store and the object ID of its blob is written to standard output. --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 2f642697e9..6125683014 100644 --- a/Documentation/git-multi-pack-index.adoc +++ b/Documentation/git-multi-pack-index.adoc @@ -9,7 +9,14 @@ git-multi-pack-index - Write and verify multi-pack-indexes 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 @@ Write or verify a multi-pack-index (MIDX) file. 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 @@ marker). 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 71b9682485..b78175fbe1 100644 --- a/Documentation/git-pack-objects.adoc +++ b/Documentation/git-pack-objects.adoc @@ -94,13 +94,24 @@ base-name:: 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 013e1a6190..05859990c8 100644 --- a/Documentation/git-patch-id.adoc +++ b/Documentation/git-patch-id.adoc @@ -3,7 +3,7 @@ git-patch-id(1) NAME ---- -git-patch-id - Compute unique ID for a patch +git-patch-id - Compute unique IDs for patches SYNOPSIS -------- @@ -12,7 +12,7 @@ git patch-id [--stable | --unstable | --verbatim] 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 @@ -25,7 +25,8 @@ 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 ------- @@ -67,6 +68,50 @@ This is the default if `patchid.stable` is set to `true`. + 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-rebase.adoc b/Documentation/git-rebase.adoc index e177808004..f6c22d1598 100644 --- a/Documentation/git-rebase.adoc +++ b/Documentation/git-rebase.adoc @@ -497,6 +497,13 @@ See also INCOMPATIBLE OPTIONS below. + 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 @@ -653,6 +660,7 @@ are incompatible with the following options: * --[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-replay.adoc b/Documentation/git-replay.adoc index c3b214ec69..997097e420 100644 --- a/Documentation/git-replay.adoc +++ b/Documentation/git-replay.adoc @@ -9,7 +9,7 @@ git-replay - EXPERIMENTAL: Replay commits on a new base, works with bare repos t SYNOPSIS -------- [verse] -(EXPERIMENTAL!) 'git replay' ([--contained] --onto <newbase> | --advance <branch>) [--ref-action[=<mode>]] <revision-range> +(EXPERIMENTAL!) 'git replay' ([--contained] --onto <newbase> | --advance <branch> | --revert <branch>) [--ref-action[=<mode>]] <revision-range> DESCRIPTION ----------- @@ -42,6 +42,25 @@ 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`. @@ -60,9 +79,12 @@ The default mode can be configured via the `replay.refAction` configuration vari <revision-range>:: Range of commits to replay; see "Specifying Ranges" in - linkgit:git-rev-parse[1]. In `--advance <branch>` mode, the - range should have a single tip, so that it's clear to which tip the - advanced <branch> should point. + 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[] @@ -82,9 +104,10 @@ When using `--ref-action=print`, the output is usable as input to 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. @@ -150,6 +173,21 @@ all commits they have since `base`, playing them on top of `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 7d70270dfa..42262c1983 100644 --- a/Documentation/git-repo.adoc +++ b/Documentation/git-repo.adoc @@ -8,8 +8,9 @@ git-repo - Retrieve information about the repository SYNOPSIS -------- [synopsis] -git repo info [--format=(keyvalue|nul) | -z] [--all | <key>...] -git repo structure [--format=(table|keyvalue|nul) | -z] +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,7 +20,7 @@ THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE. COMMANDS -------- -`info [--format=(keyvalue|nul) | -z] [--all | <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). @@ -30,21 +31,32 @@ 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) | -z]`:: +`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: + @@ -52,6 +64,7 @@ supported: * 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: @@ -61,17 +74,17 @@ supported: 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`. diff --git a/Documentation/git-rerere.adoc b/Documentation/git-rerere.adoc index 992b469270..99f967b7a4 100644 --- a/Documentation/git-rerere.adoc +++ b/Documentation/git-rerere.adoc @@ -32,7 +32,7 @@ COMMANDS -------- 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 @@ will automatically invoke this command. '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-send-email.adoc b/Documentation/git-send-email.adoc index ebe8853e9f..dea3b86460 100644 --- a/Documentation/git-send-email.adoc +++ b/Documentation/git-send-email.adoc @@ -290,6 +290,25 @@ must be used for each option. 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`), @@ -435,7 +454,7 @@ Automating + 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:: @@ -692,6 +711,11 @@ Links of a few such community maintained helpers are: - 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 aa92800c69..a11b57c1cd 100644 --- a/Documentation/git-shortlog.adoc +++ b/Documentation/git-shortlog.adoc @@ -64,9 +64,6 @@ Each pretty-printed commit will be rewrapped before it is shown. 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 @@ Shortlog will attempt to parse each trailer value as a `name <email>` 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 51044c814f..3b180e8c7a 100644 --- a/Documentation/git-show.adoc +++ b/Documentation/git-show.adoc @@ -8,8 +8,8 @@ git-show - Show various types of objects SYNOPSIS -------- -[verse] -'git show' [<options>] [<object>...] +[synopsis] +git show [<options>] [<object>...] DESCRIPTION ----------- @@ -17,16 +17,16 @@ Shows one or more objects (blobs, trees, tags and commits). 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 @@ This manual page describes only the most frequently used options. 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-stash.adoc b/Documentation/git-stash.adoc index 235d57ddd8..b05c990ecd 100644 --- a/Documentation/git-stash.adoc +++ b/Documentation/git-stash.adoc @@ -14,10 +14,10 @@ git stash drop [-q | --quiet] [<stash>] 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 @@ COMMANDS 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-submodule.adoc b/Documentation/git-submodule.adoc index 95beaee561..722d827908 100644 --- a/Documentation/git-submodule.adoc +++ b/Documentation/git-submodule.adoc @@ -8,19 +8,19 @@ git-submodule - Initialize, update or inspect submodules 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 @@ COMMANDS 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 @@ URL in `.gitmodules`. 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 @@ If `--recursive` is specified, this command will recurse into nested 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 @@ init [--] [<path>...]:: 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 @@ that is set to a custom command is *not* copied for security reasons. 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 @@ If you really want to remove a submodule from the repository and commit 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 @@ in submodules and updating the working tree of 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 @@ If `--filter <filter-spec>` is specified, the given partial clone filter will be 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 @@ summary [--cached|--files] [(-n|--summary-limit) <n>] [commit] [--] [<path>...]: 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 @@ checked out commit for each submodule: 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 @@ This command is recursive by default. 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 @@ SHA-1. If you don't want to fetch, you should use `submodule update --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 @@ to distribute the default upstream branch with the superproject and `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 '/'. +`--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>:: - This option is only valid for add and update commands. These - commands sometimes need to clone a remote repository. In this case, +`--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-worktree.adoc b/Documentation/git-worktree.adoc index d74ad7b0e9..fbf8426cd9 100644 --- a/Documentation/git-worktree.adoc +++ b/Documentation/git-worktree.adoc @@ -131,7 +131,13 @@ with linked worktrees if you move the main worktree manually.) `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 @@ mismatch, even if the links are correct. 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 ce099e78b8..8a5cdd3b3d 100644 --- a/Documentation/git.adoc +++ b/Documentation/git.adoc @@ -235,7 +235,10 @@ GIT COMMANDS ------------ 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 @@ System `$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 @@ double-quotes and respecting backslash escapes. E.g., the value 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/gitformat-loose.adoc b/Documentation/gitformat-loose.adoc index 947993663e..b0b569761b 100644 --- a/Documentation/gitformat-loose.adoc +++ b/Documentation/gitformat-loose.adoc @@ -10,6 +10,7 @@ SYNOPSIS -------- [verse] $GIT_DIR/objects/[0-9a-f][0-9a-f]/* +$GIT_DIR/objects/object-map/map-*.map DESCRIPTION ----------- @@ -48,6 +49,83 @@ stored under 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 1b4db4aa61..3416edceab 100644 --- a/Documentation/gitformat-pack.adoc +++ b/Documentation/gitformat-pack.adoc @@ -374,7 +374,9 @@ HEADER: 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 @@ CHUNK DATA: 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 056553788d..ed045940d1 100644 --- a/Documentation/githooks.adoc +++ b/Documentation/githooks.adoc @@ -484,13 +484,16 @@ reference-transaction ~~~~~~~~~~~~~~~~~~~~~ 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. @@ -511,16 +514,18 @@ ref and `<ref-name>` is the full name of the ref. When force updating 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 9fccab4ae8..a3d24e5c34 100644 --- a/Documentation/gitignore.adoc +++ b/Documentation/gitignore.adoc @@ -96,6 +96,11 @@ PATTERN FORMAT 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 d9bec8b187..3792da96aa 100644 --- a/Documentation/gitmodules.adoc +++ b/Documentation/gitmodules.adoc @@ -70,7 +70,10 @@ submodule.<name>.ignore:: -- 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-pack.adoc b/Documentation/gitprotocol-pack.adoc index 837b691c89..633deecf2d 100644 --- a/Documentation/gitprotocol-pack.adoc +++ b/Documentation/gitprotocol-pack.adoc @@ -47,7 +47,9 @@ process defined in this protocol is terminated. 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 @@ Extra Parameters ---------------- 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 @@ process on the server side over the Git protocol is this: 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 @@ two commands, or even just one of them. 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 @@ supports passing environment variables as an argument. 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 @@ out of what the server said it could do with the first 'want' line. 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 @@ In multi_ack_detailed mode: 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 c7db103299..f985cb4c47 100644 --- a/Documentation/gitprotocol-v2.adoc +++ b/Documentation/gitprotocol-v2.adoc @@ -812,10 +812,15 @@ MUST appear first in each pr-fields, in that order. 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 @@ are case-sensitive and MUST be transmitted exactly as specified 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 @@ the server advertised, the client shouldn't advertise the On the server side, the "promisor.advertise" and "promisor.sendFields" configuration options can be used to control what it advertises. On the client side, the "promisor.acceptFromServer" configuration option -can be used to control what it accepts. 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/meson.build b/Documentation/meson.build index f02dbc20cb..d6365b888b 100644 --- a/Documentation/meson.build +++ b/Documentation/meson.build @@ -64,6 +64,7 @@ manpages = { '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, @@ -353,13 +354,10 @@ foreach mode : [ 'diff', 'merge' ] 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 2121e8e1df..2ae0eb11a9 100644 --- a/Documentation/pretty-formats.adoc +++ b/Documentation/pretty-formats.adoc @@ -18,54 +18,72 @@ config option to either another format name, or a 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` - - commit <hash> - Author: <author> - - <title-line> - -* `medium` - - commit <hash> - Author: <author> - Date: <author-date> - - <title-line> +`short`:: ++ +[synopsis] +-- +commit <hash> +Author: <author> - <full-commit-message> + <title-line> +-- -* `full` +`medium`:: ++ +[synopsis] +-- +commit <hash> +Author: <author> +Date: <author-date> - commit <hash> - Author: <author> - Commit: <committer> + <title-line> - <title-line> + <full-commit-message> +-- - <full-commit-message> +`full`:: ++ +[synopsis] +-- +commit <hash> +Author: <author> +Commit: <committer> -* `fuller` + <title-line> - commit <hash> - Author: <author> - AuthorDate: <author-date> - Commit: <committer> - CommitDate: <committer-date> + <full-commit-message> +-- - <title-line> +`fuller`:: ++ +[synopsis] +-- +commit <hash> +Author: <author> +AuthorDate: <author-date> +Commit: <committer> +CommitDate: <committer-date> - <full-commit-message> + <title-line> -* `reference` + <full-commit-message> +-- - <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 @@ is explicitly specified. As with any `format:` with format 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> +`email`:: ++ +[synopsis] +-- +From <hash> <date> +From: <author> +Date: <author-date> +Subject: [PATCH] <title-line> - <full-commit-message> +<full-commit-message> +-- -* `mboxrd` -+ +`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 @@ commits are displayed, but not the way the diff is shown e.g. with `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 title was >>t4119: test autocomputing -p<n> for traditional diff input.<< 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 @@ The placeholders are: ++%><|(++_<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 @@ The placeholders are: 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 @@ undescribable commits. The `describe` string may be followed by a colon and 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 @@ linkgit:git-interpret-trailers[1]. The `trailers` string may be followed by 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 @@ multiple times, the last occurrence wins. 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 @@ placeholder expands to an empty string. 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/ref-storage-format.adoc b/Documentation/ref-storage-format.adoc index 14fff8a9c6..6a8db4712b 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 b0b920144a..4395fe0535 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 c4d7a6b989..2d195a1474 100644 --- a/Documentation/rev-list-options.adoc +++ b/Documentation/rev-list-options.adoc @@ -148,6 +148,10 @@ endif::git-log[] 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`. diff --git a/Documentation/user-manual.adoc b/Documentation/user-manual.adoc index 7696987117..64009baf37 100644 --- a/Documentation/user-manual.adoc +++ b/Documentation/user-manual.adoc @@ -4466,7 +4466,7 @@ $ git show # most recent commit $ 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 86730d0da7..92ea811ae6 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,6 +1,6 @@ #!/bin/sh -DEF_VER=v2.53.0 +DEF_VER=v2.54.0-rc0 LF=' ' @@ -342,6 +342,9 @@ include shared.mak # 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. @@ -1002,8 +1005,8 @@ SPATCH_TEST_FLAGS = # 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: @@ -1022,7 +1025,7 @@ SPATCH_USE_O_DEPENDENCIES = YesPlease # 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 @@ -1063,11 +1066,13 @@ SOURCES_CMD = ( \ '*.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 \) \ @@ -1211,6 +1216,8 @@ LIB_OBJS += object-file.o 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 @@ -1285,6 +1292,7 @@ LIB_OBJS += repack-geometry.o 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 @@ -1417,6 +1425,7 @@ BUILTIN_OBJS += builtin/get-tar-commit-id.o 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 @@ -1516,6 +1525,7 @@ CLAR_TEST_SUITES += u-dir 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 @@ -1545,7 +1555,10 @@ CLAR_TEST_OBJS += $(UNIT_TEST_DIR)/unit-test.o 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 @@ -1594,6 +1607,7 @@ BASIC_CFLAGS += -DSHA1DC_FORCE_ALIGNED_ACCESS endif ifneq ($(filter leak,$(SANITIZERS)),) BASIC_CFLAGS += -O0 +NO_MMAP = CatchMapLeaks SANITIZE_LEAK = YesCompiledWithIt endif ifneq ($(filter address,$(SANITIZERS)),) @@ -2015,6 +2029,10 @@ ifdef NO_PREAD 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 @@ -2657,6 +2675,7 @@ git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS) 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 = \ @@ -2681,20 +2700,21 @@ $(BUILT_INS): git$X 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):\ @@ -2707,8 +2727,8 @@ GIT-SCRIPT-DEFINES: FORCE 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 @@ -2748,8 +2768,8 @@ endif 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)) @@ -2777,8 +2797,8 @@ GIT-PERL-HEADER: $(PERL_HEADER_TEMPLATE) GIT-PERL-DEFINES Makefile 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 @@ -2795,9 +2815,9 @@ endif # NO_PERL $(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) \ @@ -2874,6 +2894,10 @@ objects: $(OBJECTS) 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 $@ @@ -3007,7 +3031,7 @@ scalar$X: scalar.o GIT-LDFLAGS $(GITLIBS) $(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 @@ -3213,9 +3237,9 @@ endif 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) @@ -3369,6 +3393,9 @@ export TEST_NO_MALLOC_CHECK test: all $(MAKE) -C t/ all +ifdef TEST_CONTRIB_TOO + $(MAKE) -C contrib/ test +endif perf: all $(MAKE) -C t/perf/ all @@ -3441,15 +3468,15 @@ check: 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)) @@ -3464,20 +3491,20 @@ COCCICHECK_PATCHES_PENDING_INTREE = $(COCCICHECK_PATCHES_PENDING:.build/%=%) # 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) >$@ @@ -3491,12 +3518,12 @@ endif 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) @@ -3522,13 +3549,13 @@ endif 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 @@ -3542,9 +3569,9 @@ $(COCCI_TEST_RES_GEN): GIT-SPATCH-DEFINES $(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) \ @@ -3560,14 +3587,14 @@ coccicheck-test: $(COCCI_TEST_RES_GEN) 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 ^ $(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) @@ -3841,8 +3868,8 @@ profile-clean: 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) @@ -3920,7 +3947,7 @@ check-docs:: ### 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 # @@ -4058,3 +4085,20 @@ $(LIBGIT_HIDDEN_EXPORT): $(LIBGIT_PARTIAL_EXPORT) contrib/libgit-sys/libgitpub.a: $(LIBGIT_HIDDEN_EXPORT) $(AR) $(ARFLAGS) $@ $^ + +contrib/credential/osxkeychain/git-credential-osxkeychain: contrib/credential/osxkeychain/git-credential-osxkeychain.o $(LIB_FILE) GIT-LDFLAGS + $(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 @@ -1 +1 @@ -Documentation/RelNotes/2.53.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 95ec5a89f8..3cf8a1dbf8 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) { @@ -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 da49502b76..eefa2edc7c 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 173a53241e..4e28e5c187 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 @@ soft_increment: 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 @@ soft_increment: 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 @@ soft_increment: } 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 @@ soft_increment: * 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 @@ soft_increment: } } - /* Any hunk to be used? */ - for (i = 0; i < file_diff->hunk_nr; i++) - if (file_diff->hunk[i].use == USE_HUNK) - break; + if (s->cfg.auto_advance) + apply_patch(s, file_diff); - 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); + putchar('\n'); + return patch_update_resp; +} - 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")); +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 (repo_read_index(s->s.r) >= 0) - repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0, - 1, NULL, NULL, NULL); + if ((i = patch_update_file(state, i, flags)) == state->file_diff_nr) + break; } - putchar('\n'); - return quit; + 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 0000000000..fb6d975b68 --- /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 @@ -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; @@ -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 @@ done: } 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) @@ -1725,6 +1769,26 @@ static int parse_fragment(struct apply_state *state, 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; @@ -1855,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; @@ -2045,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; } @@ -2082,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; @@ -2244,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; } } @@ -3568,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", @@ -4144,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 && @@ -4805,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; @@ -4961,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; } @@ -5002,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; } @@ -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, @@ -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) @@ -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; } @@ -473,8 +473,12 @@ static int register_ref(const struct reference *ref, void *cb_data UNUSED) 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") @@ -559,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) { @@ -879,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; } @@ -1142,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; @@ -1180,26 +1184,26 @@ int estimate_bisect_steps(int all) static int mark_for_removal(const struct reference *ref, void *cb_data) { struct string_list *refs = cb_data; - char *bisect_ref = xstrfmt("refs/bisect%s", ref->name); - string_list_append(refs, bisect_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()); @@ -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; @@ -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); @@ -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 0000000000..3724b3a930 --- /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"); +} @@ -196,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 32709794b3..7737ab878b 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 b66a33d8a8..fe6e087eee 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 @@ next: */ if (!state->rebasing) { am_destroy(state); - run_auto_maintenance(state->quiet); + run_auto_maintenance(the_repository, state->quiet); } } diff --git a/builtin/backfill.c b/builtin/backfill.c index e80fc1b694..2c5ce56fb7 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 4cc118fb57..4520e585d0 100644 --- a/builtin/bisect.c +++ b/builtin/bisect.c @@ -422,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); @@ -562,6 +566,10 @@ static int add_bisect_ref(const struct reference *ref, void *cb) 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); @@ -581,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")); @@ -1191,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 6044973462..f3a11eff44 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; } } } @@ -1248,7 +1252,7 @@ parse_done: 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 c577b5d20f..1572a4f9ef 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; @@ -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 df8e87a81f..cd13a3a89f 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -806,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); } @@ -845,9 +848,25 @@ static void batch_each_object(struct batch_options *opt, .callback = callback, .payload = _payload, }; + 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 (opt->objects_filter.choice != LOFC_DISABLED && (bitmap = prepare_bitmap_git(the_repository)) && @@ -863,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); @@ -924,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 { @@ -1105,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 9cc5c59830..3f2a39cae0 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 0ba4f03f2e..e031e61886 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); @@ -901,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, @@ -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; @@ -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 b40cee5968..fba3c9c508 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; @@ -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 @@ -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 5189e685a7..30535db131 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 8e901fe8db..a3e52ac9ca 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 */ @@ -357,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) @@ -408,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; @@ -433,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"); } @@ -456,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); @@ -816,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)); @@ -1155,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")); @@ -1720,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")), @@ -1743,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")), @@ -1820,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; @@ -1850,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; @@ -1958,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) { @@ -1979,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 288ebdfdaa..cf4ba0f7cc 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,104 +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) +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) { - const struct config_display_options *opts = cb; - const struct key_value_info *kvi = ctx->kvi; + 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); + } - 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); + 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_); } - if (!opts->omit_values && value_) - printf("%s%c%s%c", key_, opts->delim, value_, opts->term); + + 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 - printf("%s%c", key_, opts->term); + strbuf_addf(buf, "%d", v); + return 0; } -struct strbuf_list { - struct strbuf *items; - int nr; - int alloc; -}; +/* 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; - if (v) - strbuf_addstr(buf, v); - else - return 1; /* :(optional)no-such-file */ - 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; } @@ -372,7 +504,7 @@ static int collect_config(const char *key_, const char *value_, strbuf_init(&values->items[values->nr], 0); status = format_config(data->display_opts, &values->items[values->nr++], - key_, value_, kvi); + key_, value_, kvi, 0); if (status < 0) return status; if (status) { @@ -463,7 +595,7 @@ static int get_value(const struct config_location_options *opts, strbuf_init(item, 0); status = format_config(display_opts, item, key_, - display_opts->default_value, &kvi); + display_opts->default_value, &kvi, 0); if (status < 0) die(_("failed to format default config value: %s"), display_opts->default_value); @@ -706,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; @@ -719,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; @@ -743,7 +876,7 @@ static int get_urlmatch(const struct config_location_options *opts, status = format_config(&display_opts, &buf, item->string, matched->value_is_null ? NULL : matched->value.buf, - &matched->kvi); + &matched->kvi, 0); if (!status) fwrite(buf.buf, 1, buf.len, stdout); strbuf_release(&buf); @@ -754,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; } @@ -868,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) { @@ -886,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); @@ -1317,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 b74e06cc93..bc1453c6b2 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 989a78d715..bffeed13a3 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -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 740d9a791c..8b8f8b54e4 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); } diff --git a/builtin/fast-export.c b/builtin/fast-export.c index b90da5e616..13621b0d6a 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; @@ -825,6 +825,9 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, 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); } @@ -970,6 +973,9 @@ static void handle_tag(const char *name, struct tag *tag) 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); } @@ -1118,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 b8a7757cfd..570fd048d7 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->sources->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; @@ -982,7 +983,9 @@ static int store_object( } for (source = the_repository->objects->sources; source; source = source->next) { - if (!packfile_list_find_oid(packfile_store_get_packs(source->packfiles), &oid)) + 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; @@ -1187,7 +1190,9 @@ static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark) } for (source = the_repository->objects->sources; source; source = source->next) { - if (!packfile_list_find_oid(packfile_store_get_packs(source->packfiles), &oid)) + 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; @@ -2836,10 +2841,46 @@ static void finalize_commit_buffer(struct strbuf *new_data, strbuf_addbuf(new_data, msg); } -static void handle_strip_if_invalid(struct strbuf *new_data, - struct signature_data *sig_sha1, - struct signature_data *sig_sha256, - struct strbuf *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 }; @@ -2851,20 +2892,34 @@ static void handle_strip_if_invalid(struct strbuf *new_data, ret = verify_commit_buffer(tmp_buf.buf, tmp_buf.len, &signature_check); if (ret) { - const char *signer = signature_check.signer ? - signature_check.signer : _("unknown"); - const char *subject; - int subject_len = find_commit_subject(msg->buf, &subject); + warn_invalid_signature(&signature_check, msg->buf, mode); - 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); + 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 { @@ -2927,6 +2982,7 @@ static void parse_new_commit(const char *arg) /* fallthru */ case SIGN_VERBATIM: case SIGN_STRIP_IF_INVALID: + case SIGN_SIGN_IF_INVALID: import_one_signature(&sig_sha1, &sig_sha256, v); break; @@ -3011,9 +3067,11 @@ static void parse_new_commit(const char *arg) "encoding %s\n", encoding); - if (signed_commit_mode == SIGN_STRIP_IF_INVALID && + if ((signed_commit_mode == SIGN_STRIP_IF_INVALID || + signed_commit_mode == SIGN_SIGN_IF_INVALID) && (sig_sha1.hash_algo || sig_sha256.hash_algo)) - handle_strip_if_invalid(&new_data, &sig_sha1, &sig_sha256, &msg); + 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); @@ -3060,6 +3118,9 @@ static void handle_tag_signature(struct strbuf *msg, const char *name) 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); @@ -3246,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); @@ -3649,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 288d3772ea..4795b2a13c 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -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; @@ -722,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)) @@ -861,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; @@ -877,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; } } @@ -921,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; @@ -941,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; } @@ -960,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; @@ -967,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; } } @@ -1103,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; @@ -1218,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) { /* @@ -1227,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); } } } @@ -1300,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; @@ -1322,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: @@ -1429,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; @@ -1440,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); @@ -1449,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; @@ -1473,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"); } @@ -1493,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; @@ -1507,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; } @@ -1515,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); @@ -1641,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, @@ -1649,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; @@ -1673,11 +1793,14 @@ 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; } @@ -1687,6 +1810,7 @@ static void ref_transaction_rejection_handler(const char *refname, */ 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); @@ -1698,6 +1822,7 @@ static int commit_ref_transaction(struct ref_transaction **transaction, .conflict_msg_shown = 0, .remote_name = remote_name, .retcode = &retcode, + .rejected_refs = rejected_refs, }; ref_transaction_for_each_rejected_update(*transaction, @@ -1713,7 +1838,8 @@ out: 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; @@ -1726,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) @@ -1850,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; } @@ -1873,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; } @@ -1883,8 +2012,12 @@ static int do_fetch(struct transport *transport, if (retcode) goto cleanup; + if (verbosity >= 0) + summary_width = transport_summary_width(ref_map); + retcode = commit_ref_transaction(&transaction, atomic_fetch, - transport->remote->name, &err); + 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. @@ -1962,7 +2095,17 @@ cleanup: */ if (retcode && !atomic_fetch && transaction) commit_ref_transaction(&transaction, false, - transport->remote->name, &err); + 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) { @@ -1977,6 +2120,9 @@ cleanup: 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); @@ -2198,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; /* @@ -2220,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; } @@ -2230,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; @@ -2249,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 */ @@ -2304,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); @@ -2329,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; @@ -2434,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 */ @@ -2594,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 { @@ -2616,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; @@ -2722,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 325a7925f1..927d3d92da 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 0512f78a87..9bab32effe 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -162,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), @@ -219,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 @@ -244,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; } @@ -395,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.. */ @@ -598,6 +582,9 @@ static int fsck_handle_ref(const struct reference *ref, void *cb_data UNUSED) 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; @@ -623,8 +610,8 @@ static void snapshot_refs(struct snapshot *snap, int argc, const char **argv) return; } - refs_for_each_rawref(get_main_ref_store(the_repository), - snapshot_ref, snap); + refs_for_each_ref_ext(get_main_ref_store(the_repository), + snapshot_ref, snap, &opts); worktrees = get_worktrees(); for (p = worktrees; *p; p++) { @@ -900,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; } @@ -1068,10 +1041,8 @@ int cmd_fsck(int argc, 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) @@ -1137,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); diff --git a/builtin/gc.c b/builtin/gc.c index 17ff68cbd9..3a71e314c9 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -467,37 +467,19 @@ out: 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, @@ -592,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; @@ -1030,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]); @@ -1168,7 +1154,7 @@ static int dfs_on_ref(const struct reference *ref, void *cb_data) } } - free_commit_list(stack); + commit_list_free(stack); return result; } @@ -1199,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); @@ -1268,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) @@ -1484,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) @@ -1542,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) @@ -1980,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; } diff --git a/builtin/grep.c b/builtin/grep.c index 5b8b87b1ac..e33285e5e6 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 @@ -1218,8 +1218,10 @@ int cmd_grep(int argc, struct odb_source *source; odb_prepare_alternates(the_repository->objects); - for (source = the_repository->objects->sources; source; source = source->next) - packfile_store_prepare(source->packfiles); + 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); diff --git a/builtin/help.c b/builtin/help.c index c09cbc8912..c0aece4da3 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 0000000000..568dc75ee7 --- /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 7afec380d2..c0585587e5 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 b67fb0256c..d1e47279a8 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -1637,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 && startup_info->have_repository) - packfile_store_load_pack(the_repository->objects->sources->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)); diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c index 41b0750e5a..e7e86e9523 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 c80f0535f6..8900ceece1 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, @@ -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,16 +522,20 @@ 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() }; diff --git a/builtin/log.c b/builtin/log.c index 5c9a8ef363..8c0939dd42 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -24,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" @@ -40,6 +39,8 @@ #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) @@ -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) @@ -1906,6 +1964,7 @@ int cmd_format_patch(int argc, 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"), @@ -2289,6 +2350,15 @@ int cmd_format_patch(int argc, /* nothing to do */ goto done; 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); @@ -2375,12 +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, list.nr, list.items, - description_file, branch_name, quiet, &cfg); + description_file, branch_name, quiet, &cfg, + cover_letter_fmt); print_bases(&bases, rev.diffopt.file); print_signature(signature, rev.diffopt.file); total++; @@ -2610,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/merge-base.c b/builtin/merge-base.c index 3f82781245..c7ee97fa6a 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 46775d0c79..59a9792208 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 97b8a792c7..405b2989f7 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 a6e6d5b555..312b595d1e 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -486,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 50001b4c59..2cbce56f8d 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) { @@ -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 12772303f5..4084e32476 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 5f364aa816..0f72d96c02 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 d43925097b..2215d34e31 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 @@ remove_entry: 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 6188cf98ce..d6594ada53 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,7 +177,7 @@ 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_stack parents_to_queue = COMMIT_STACK_INIT; struct rev_name *start_name; @@ -197,10 +196,9 @@ 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; @@ -241,13 +239,13 @@ static void name_rev(struct commit *start_commit, } } - /* The first parent must come out first from the prio_queue */ + /* The first parent must come out first from the stack */ while (parents_to_queue.nr) - prio_queue_put(&queue, - commit_stack_pop(&parents_to_queue)); + commit_stack_push(&stack, + commit_stack_pop(&parents_to_queue)); } - clear_prio_queue(&queue); + commit_stack_clear(&stack); commit_stack_clear(&parents_to_queue); } diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 5846b6a293..dd2480a73d 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -28,6 +28,7 @@ #include "reachable.h" #include "oid-array.h" #include "strvec.h" +#include "strmap.h" #include "list.h" #include "packfile.h" #include "object-file.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; @@ -1331,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); @@ -1532,7 +1548,8 @@ static int want_cruft_object_mtime(struct repository *r, struct odb_source *source; for (source = r->objects->sources; source; source = source->next) { - struct packed_git **cache = packfile_store_get_kept_pack_cache(source->packfiles, flags); + struct odb_source_files *files = odb_source_files_downcast(source); + struct packed_git **cache = packfile_store_get_kept_pack_cache(files->packed, flags); for (; *cache; cache++) { struct packed_git *p = *cache; @@ -1617,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. @@ -1631,6 +1649,8 @@ static int want_found_object(const struct object_id *oid, int exclude, flags |= KEPT_PACK_ON_DISK; if (ignore_packed_keep_in_core) 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 @@ -1642,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 { @@ -1754,11 +1776,13 @@ static int want_object_in_pack_mtime(const struct object_id *oid, } for (source = the_repository->objects->sources; source; source = source->next) { - for (e = source->packfiles->packs.head; e; e = e->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(&source->packfiles->packs, p); + packfile_list_prepend(&files->packed->packs, p); if (want != -1) return want; } @@ -3739,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); @@ -3746,29 +3771,34 @@ static int add_object_entry_from_pack(const struct object_id *oid, if (have_duplicate_entry(oid, 0)) return 0; - ofs = nth_packed_object_offset(p, pos); - if (!want_object_in_pack(oid, 0, &p, &ofs)) - return 0; + stdin_packs_found_nr++; - if (p) { - struct object_info oi = OBJECT_INFO_INIT; - - 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 - */ - add_pending_oid(revs, NULL, oid, 0); - } + ofs = nth_packed_object_offset(p, pos); - stdin_packs_found_nr++; + 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; + create_object_entry(oid, type, 0, 0, 0, p, ofs); return 0; @@ -3818,117 +3848,213 @@ 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) +{ + return !has_object_kept_pack(to_pack.repo, &obj->oid, + KEPT_PACK_IN_CORE); +} + +static int stdin_packs_include_check(struct commit *commit, void *data) +{ + return stdin_packs_include_check_obj((struct object *)commit, data); +} + +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; + + 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_append(&keys, entry->key)->util = info; + } + + /* + * Order packs by ascending mtime; use QSORT directly to access the + * string_list_item's ->util pointer, which string_list_sort() does not + * provide. + */ + QSORT(keys.items, keys.nr, pack_mtime_cmp); + + 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 string_list include_packs = STRING_LIST_INIT_DUP; - struct string_list exclude_packs = STRING_LIST_INIT_DUP; - struct string_list_item *item = NULL; + struct strmap packs = STRMAP_INIT; struct packed_git *p; while (strbuf_getline(&buf, stdin) != EOF) { - if (!buf.len) + 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 (*buf.buf == '^') - string_list_append(&exclude_packs, buf.buf + 1); - else - string_list_append(&include_packs, buf.buf); + 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); } - 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); + struct stdin_pack_info *info; + + info = strmap_get(&packs, pack_basename(p)); + if (!info) + continue; - if ((item = string_list_lookup(&include_packs, pack_name))) { + 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); - 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); - } + /* + * 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); + } - /* - * 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; - } + 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; + } - /* - * Order packs by ascending mtime; use QSORT directly to access the - * string_list_item's ->util pointer, which string_list_sort() does not - * provide. - */ - QSORT(include_packs.items, include_packs.nr, pack_mtime_cmp); + 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; + } - 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); + 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 @@ -3949,7 +4075,15 @@ static void read_stdin_packs(enum stdin_packs_mode mode, int rev_list_unpacked) /* 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); @@ -3960,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, @@ -4318,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); } @@ -4345,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, @@ -4554,22 +4694,6 @@ static int mark_bitmap_preferred_tip(const struct reference *ref, void *data UNU 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) { @@ -4710,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; @@ -4932,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, diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c index e4ecf774ca..86749bb7e7 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/pull.c b/builtin/pull.c index 3ff748e0b3..7e67fdce97 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -454,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); } @@ -704,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; } @@ -803,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; @@ -828,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) diff --git a/builtin/push.c b/builtin/push.c index 5b6cebbb85..7100ffba5d 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/rebase.c b/builtin/rebase.c index c468828189..fa4f5d9306 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 9c49174616..cb3656a034 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -3,46 +3,45 @@ #include "builtin.h" #include "abspath.h" - +#include "commit.h" +#include "commit-reach.h" #include "config.h" +#include "connect.h" +#include "connected.h" #include "environment.h" +#include "exec-cmd.h" +#include "fsck.h" #include "gettext.h" +#include "gpg-interface.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 "lockfile.h" #include "object.h" -#include "remote.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 "fsck.h" -#include "tmp-objdir.h" -#include "oidset.h" -#include "packfile.h" #include "object-file.h" #include "object-name.h" #include "odb.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 "setup.h" -#include "parse-options.h" static const char * const receive_pack_usage[] = { N_("git receive-pack <git-dir>"), @@ -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; + + if (!hook_exists(the_repository, hook_name)) + return 0; - strbuf_init(&state.buf, 0); - state.cmd = commands; - state.skip_broken = skip_broken; - state.report = NULL; - if (feed_receive_hook(&state, NULL, NULL)) + /* 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; - 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 (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 @@ out: 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 7ffc14ba15..0fddaa1773 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; @@ -912,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) @@ -923,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); diff --git a/builtin/repack.c b/builtin/repack.c index f6bb04bef7..4c5a82c2c8 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -369,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); } diff --git a/builtin/replay.c b/builtin/replay.c index 1960bbbee8..a0879b020f 100644 --- a/builtin/replay.c +++ b/builtin/replay.c @@ -2,257 +2,22 @@ * "git replay" builtin command */ -#define USE_THE_REPOSITORY_VARIABLE -#define DISABLE_SIGN_COMPARE_WARNINGS - #include "git-compat-util.h" #include "builtin.h" #include "config.h" -#include "environment.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> enum ref_action_mode { REF_ACTION_UPDATE, REF_ACTION_PRINT, }; -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 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", "gpgsig-sha256", 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; -}; - -static void get_ref_information(struct repository *repo, - struct rev_cmdline_info *cmd_info, - struct ref_info *ref_info) -{ - 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); - } -} - -static void set_up_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) -{ - struct ref_info rinfo; - - get_ref_information(repo, cmd_info, &rinfo); - if (!rinfo.positive_refexprs) - die(_("need some commits to replay")); - - die_for_incompatible_opt2(!!onto_name, "--onto", - !!*advance_name, "--advance"); - 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 { - struct object_id oid; - char *fullname = NULL; - - if (!*advance_name) - BUG("expected either onto_name or *advance_name in this function"); - - 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")); - } - *onto = peel_committish(repo, *advance_name, "--advance"); - if (rinfo.positive_refexprs > 1) - die(_("cannot advance target with multiple sources because ordering would be ill-defined")); - } - 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 = 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); -} - static enum ref_action_mode parse_ref_action_mode(const char *ref_action, const char *source) { if (!ref_action || !strcmp(ref_action, "update")) @@ -306,41 +71,35 @@ 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[] = { N_("(EXPERIMENTAL!) git replay " - "([--contained] --onto <newbase> | --advance <branch>) " + "([--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, + 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)")), @@ -350,18 +109,30 @@ int cmd_replay(int argc, 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); } - die_for_incompatible_opt2(!!advance_name_opt, "--advance", - contained, "--contained"); + 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); - advance_name = xstrdup_or_null(advance_name_opt); + /* + * 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); @@ -374,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; @@ -389,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 " @@ -414,18 +185,21 @@ int cmd_replay(int argc, revs.simplify_history = 0; } - set_up_replay_mode(repo, &revs.cmdline, - onto_name, &advance_name, - &onto, &update_refs); - - /* FIXME: Should allow replaying commits with the first as a root commit */ + ret = replay_revisions(&revs, &opts, &result); + if (ret) + goto cleanup; /* Build reflog message */ - if (advance_name_opt) - strbuf_addf(&reflog_msg, "replay --advance %s", advance_name_opt); - else - strbuf_addf(&reflog_msg, "replay --onto %s", - oid_to_hex(&onto->object.oid)); + 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)); + } /* Initialize ref transaction if using update mode */ if (ref_mode == REF_ACTION_UPDATE) { @@ -438,78 +212,19 @@ int cmd_replay(int argc, } } - if (prepare_revision_walk(&revs) < 0) { - ret = error(_("error preparing revisions")); - goto cleanup; - } - - 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 from 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))) { - if (handle_ref_update(ref_mode, transaction, - decoration->name, - &last_commit->object.oid, - &commit->object.oid, - reflog_msg.buf, - &transaction_err) < 0) { - ret = error(_("failed to update ref '%s': %s"), - decoration->name, transaction_err.buf); - goto cleanup; - } - } - decoration = decoration->next; - } - } - - /* In --advance mode, advance the target ref */ - if (result.clean == 1 && advance_name) { - if (handle_ref_update(ref_mode, transaction, advance_name, - &last_commit->object.oid, - &onto->object.oid, - reflog_msg.buf, - &transaction_err) < 0) { + 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"), - advance_name, transaction_err.buf); + result.updates[i].refname, transaction_err.buf); goto cleanup; } } /* Commit the ref transaction if we have one */ - if (transaction && result.clean == 1) { + if (transaction) { if (ref_transaction_commit(transaction, &transaction_err)) { ret = error(_("failed to commit ref transaction: %s"), transaction_err.buf); @@ -517,24 +232,18 @@ int cmd_replay(int argc, } } - merge_finalize(&merge_opt, &result); - kh_destroy_oid_map(replayed_commits); - if (update_refs) { - strset_clear(update_refs); - free(update_refs); - } - 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 0ea045abc1..71a5c1c29c 100644 --- a/builtin/repo.c +++ b/builtin/repo.c @@ -1,7 +1,9 @@ #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" @@ -14,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] [--all | <key>...]", - "git repo structure [--format=(table|keyvalue|nul) | -z]", - 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; }; @@ -61,37 +82,39 @@ 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_KEYVALUE: + case FORMAT_NEWLINE_TERMINATED: printf("%s=", key); quote_c_style(value, NULL, stdout, 0); putchar('\n'); @@ -112,18 +135,16 @@ static int print_fields(int argc, const char **argv, struct strbuf valbuf = 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); - get_value(repo, &valbuf); + field->get_value(repo, &valbuf); print_field(format, key, valbuf.buf); } @@ -136,8 +157,8 @@ static int print_all_fields(struct repository *repo, { struct strbuf valbuf = STRBUF_INIT; - for (size_t i = 0; i < ARRAY_SIZE(repo_info_fields); i++) { - const struct field *field = &repo_info_fields[i]; + 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); @@ -148,6 +169,29 @@ static int print_all_fields(struct repository *repo, 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) { @@ -157,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 @@ -170,8 +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"), @@ -181,11 +226,19 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix, 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")); if (all_keys && argc) @@ -197,6 +250,21 @@ static int cmd_repo_info(int argc, const char **argv, const char *prefix, 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; @@ -215,6 +283,7 @@ 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 { @@ -224,6 +293,7 @@ struct repo_structure { struct stats_table { struct string_list rows; + struct string_list annotations; int name_col_width; int value_col_width; @@ -236,6 +306,8 @@ 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, @@ -258,6 +330,12 @@ static void stats_table_vaddf(struct stats_table *table, table->name_col_width = name_width; 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) @@ -268,6 +346,8 @@ static void stats_table_vaddf(struct stats_table *table, 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, ...) @@ -293,6 +373,27 @@ static void stats_table_count_addf(struct stats_table *table, size_t value, 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, ...) { @@ -307,6 +408,27 @@ static void stats_table_size_addf(struct stats_table *table, size_t value, 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); + va_end(ap); +} + static inline size_t get_total_reference_count(struct ref_stats *stats) { return stats->branches + stats->remotes + stats->tags + stats->others; @@ -371,8 +493,41 @@ static void stats_table_setup_structure(struct stats_table *table, " * %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"); @@ -391,7 +546,8 @@ static void stats_table_print_structure(const struct stats_table *table) value_col_width = title_value_width - unit_col_width; strbuf_addstr(&buf, "| "); - strbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width, name_col_title); + 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); @@ -399,7 +555,7 @@ static void stats_table_print_structure(const struct stats_table *table) 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 + unit_col_width + 1; i++) @@ -412,7 +568,6 @@ static void stats_table_print_structure(const struct stats_table *table) const char *unit = ""; if (entry) { - struct stats_table_entry *entry = item->util; value = entry->value; if (entry->unit) unit = entry->unit; @@ -421,6 +576,13 @@ static void stats_table_print_structure(const struct stats_table *table) 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, ' '); @@ -429,6 +591,12 @@ static void stats_table_print_structure(const struct stats_table *table) 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); } @@ -444,46 +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); + + 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); - printf("objects.commits.count%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.type_counts.commits, value_delim); - printf("objects.trees.count%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.type_counts.trees, value_delim); - printf("objects.blobs.count%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.type_counts.blobs, value_delim); - printf("objects.tags.count%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.type_counts.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); - printf("objects.commits.inflated_size%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.inflated_sizes.commits, value_delim); - printf("objects.trees.inflated_size%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.inflated_sizes.trees, value_delim); - printf("objects.blobs.inflated_size%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.inflated_sizes.blobs, value_delim); - printf("objects.tags.inflated_size%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.inflated_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); - printf("objects.commits.disk_size%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.disk_sizes.commits, value_delim); - printf("objects.trees.disk_size%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.disk_sizes.trees, value_delim); - printf("objects.blobs.disk_size%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.disk_sizes.blobs, value_delim); - printf("objects.tags.disk_size%c%" PRIuMAX "%c", key_delim, - (uintmax_t)stats->objects.disk_sizes.tags, 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); } @@ -553,55 +751,97 @@ struct count_objects_data { 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) { struct count_objects_data *data = cb_data; struct object_stats *stats = data->stats; - size_t inflated_total = 0; - size_t disk_total = 0; size_t object_count; 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; - inflated_total += inflated; - disk_total += disk; - } + obj = parse_object_buffer(the_repository, &oids->oid[i], type, + inflated, content, &eaten); - switch (type) { - case OBJ_TAG: - stats->type_counts.tags += oids->nr; - stats->inflated_sizes.tags += inflated_total; - stats->disk_sizes.tags += disk_total; - break; - case OBJ_COMMIT: - stats->type_counts.commits += oids->nr; - stats->inflated_sizes.commits += inflated_total; - stats->disk_sizes.commits += disk_total; - break; - case OBJ_TREE: - stats->type_counts.trees += oids->nr; - stats->inflated_sizes.trees += inflated_total; - stats->disk_sizes.trees += disk_total; - break; - case OBJ_BLOB: - stats->type_counts.blobs += oids->nr; - stats->inflated_sizes.blobs += inflated_total; - stats->disk_sizes.blobs += disk_total; - break; - default: - BUG("invalid object type"); + 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_values(&stats->type_counts); @@ -637,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 }; @@ -654,7 +895,7 @@ static int cmd_repo_structure(int argc, const char **argv, const char *prefix, 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")); @@ -671,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 c48d9845f8..3590be57a5 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -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), @@ -423,9 +425,9 @@ int cmd_reset(int argc, 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 99f876ba85..854d82ece3 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 9032cc6327..218b5f34d6 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -267,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) @@ -295,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; } @@ -308,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) @@ -318,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; @@ -335,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; } @@ -387,7 +387,7 @@ static int try_parent_shorthands(const char *arg) free(name); } - *dotdot = '^'; + free(to_free); return 1; } @@ -613,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); } @@ -931,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 b91acf45c8..6b2a0b93b5 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 f3ebc1d4ea..f02831b085 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -1008,7 +1008,7 @@ 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); + commit_list_free(seen); clear_prio_queue(&queue); free(args_copy); free(head); diff --git a/builtin/show-index.c b/builtin/show-index.c index 2c3e2940ce..24f0230967 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 4d4984e4e0..5d31acea7c 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -215,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 15d51e60a8..f4aa405da9 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 193e3ea47a..0d27b2fb1f 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>]") @@ -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 @@ done: 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 @@ done: } 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 @@ done: 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 d537ab087a..2f589e3b37 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); @@ -1059,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; @@ -1204,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; @@ -1699,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)) @@ -1789,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); @@ -3126,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) @@ -3190,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); @@ -3210,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; @@ -3222,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" @@ -3241,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; @@ -3569,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); @@ -3594,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), @@ -3608,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 aeb04c487f..d51c2e3349 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -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/update-ref.c b/builtin/update-ref.c index 195437e7c6..2d68c40ecb 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/worktree.c b/builtin/worktree.c index fbdaf2eb2e..4fd6f7575f 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); /* @@ -657,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; @@ -1070,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() @@ -1191,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/cache-tree.c b/cache-tree.c index 16c3a36b48..60bcc07c3b 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -723,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); diff --git a/cache-tree.h b/cache-tree.h index b82c4963e7..f8bddae523 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 *); @@ -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); } @@ -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/ci/install-dependencies.sh b/ci/install-dependencies.sh index 6ee8216a05..c55441d9df 100755 --- a/ci/install-dependencies.sh +++ b/ci/install-dependencies.sh @@ -76,6 +76,17 @@ ubuntu-*|i386/ubuntu-*|debian-*) 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" @@ -231,6 +231,10 @@ then 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" diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh index 8bda62b921..28cfe730ee 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-sha256) 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 9e9c72681d..ba67e80b4d 100755 --- a/ci/run-static-analysis.sh +++ b/ci/run-static-analysis.sh @@ -10,7 +10,7 @@ make coccicheck 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 index 961c94fba0..a6df927ba5 100755 --- a/ci/run-test-slice-meson.sh +++ b/ci/run-test-slice-meson.sh @@ -9,5 +9,5 @@ group "Run tests" \ meson test -C "$1" --no-rebuild --print-errorlogs \ - --test-args="$GIT_TEST_OPTS" --slice "$((1+$2))/$3" || + --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 0444c79c02..ff948e397f 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 @@ -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) { @@ -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 accd3d0c4b..f9005cf459 100644 --- a/command-list.txt +++ b/command-list.txt @@ -115,6 +115,7 @@ git-grep mainporcelain info 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 ad3582451d..9abe62bd5a 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; @@ -1510,30 +1510,38 @@ static int write_graph_chunk_bloom_data(struct hashfile *f, return 0; } +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; + + if (ctx->progress) + display_progress(ctx->progress, ++ctx->progress_done); + + if (*oi->typep != OBJ_COMMIT) + return 0; + + oid_array_append(&ctx->oids, oid); + set_commit_pos(ctx->r, oid); + + return 0; +} + static int add_packed_commits(const struct object_id *oid, struct packed_git *pack, uint32_t pos, 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(pack, offset, &oi) < 0) die(_("unable to get type of object %s"), oid_to_hex(oid)); - if (type != OBJ_COMMIT) - return 0; - - oid_array_append(&ctx->oids, oid); - set_commit_pos(ctx->r, oid); - - return 0; + return add_packed_commits_oi(oid, &oi, data); } static void add_missing_parents(struct write_commit_graph_context *ctx, struct commit *commit) @@ -1958,7 +1966,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); } @@ -1990,13 +1998,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); @@ -2618,7 +2641,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++) { diff --git a/commit-reach.c b/commit-reach.c index e7d9b3208f..d3a9b3ed6f 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 */ @@ -374,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); @@ -451,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); @@ -510,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) { @@ -561,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; } @@ -578,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; } @@ -626,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; } @@ -661,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; } @@ -1117,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); } @@ -1236,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); } /* @@ -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 **); @@ -213,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; } @@ -458,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; } @@ -502,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; @@ -702,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; @@ -713,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) { @@ -724,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); @@ -999,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; @@ -1037,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; }; @@ -1056,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; } @@ -1082,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; @@ -1096,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); /* @@ -1119,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; } @@ -1174,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) @@ -1763,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; } @@ -1795,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; } @@ -196,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 */ @@ -268,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; @@ -381,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, diff --git a/compat/darwin/procinfo.c b/compat/darwin/procinfo.c new file mode 100644 index 0000000000..c8954f02d7 --- /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.c b/compat/mingw.c index 628a3941d2..2023c16db6 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -1394,6 +1394,9 @@ revert_attrs: 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; @@ -1404,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); @@ -2220,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; @@ -2450,7 +2464,7 @@ 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. diff --git a/compat/nedmalloc/malloc.c.h b/compat/nedmalloc/malloc.c.h index 814845d4b3..e0c567586c 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 a2becd16cd..ea362b4a8e 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 245386fa4a..94699a03fa 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,6 +326,17 @@ 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); diff --git a/compat/regcomp_enhanced.c b/compat/regcomp_enhanced.c index 84193ce53b..29c74eee99 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/win32/flush.c b/compat/win32/flush.c index 291f90ea94..7244ff69ac 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/trace2_win32_process_info.c b/compat/win32/trace2_win32_process_info.c index f147da706a..6a6a396078 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 ac2ffb7869..3ce1900939 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 0000000000..3a94870a2f --- /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; +} @@ -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")); diff --git a/config.mak.dev b/config.mak.dev index e86b6e1b34..c8dcf78779 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 3c35ae33a3..ccb3f71881 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -149,6 +149,8 @@ ifeq ($(uname_S),Darwin) 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 ifeq ($(uname_M),arm64) HOMEBREW_PREFIX = /opt/homebrew @@ -457,6 +459,7 @@ ifeq ($(uname_S),Windows) 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 @@ -672,6 +675,7 @@ ifeq ($(uname_S),MINGW) pathsep = ; HAVE_ALLOCA_H = YesPlease NO_PREAD = YesPlease + NO_WRITEV = YesPlease NEEDS_CRYPTO_WITH_SSL = YesPlease NO_LIBGEN_H = YesPlease NO_POLL = YesPlease @@ -505,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); @@ -1053,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 */ @@ -1514,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 79403108dd..6718503649 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 @@ no_promisor_pack_found: 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 @@ no_promisor_pack_found: 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 0000000000..787cd07f52 --- /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 28877feb9d..d7a087e584 100644 --- a/contrib/buildsystems/CMakeLists.txt +++ b/contrib/buildsystems/CMakeLists.txt @@ -274,6 +274,8 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows") 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 @@ endif() #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) @@ -419,6 +421,10 @@ if(NOT HAVE_MEMMEM) 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) @@ -634,7 +640,7 @@ set(EXCLUSION_PROGS_CACHE ${EXCLUSION_PROGS} CACHE STRING "Programs not built" F 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") @@ -642,14 +648,14 @@ endif() 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() @@ -830,11 +836,11 @@ foreach(script ${git_shell_scripts}) 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}) @@ -873,13 +879,13 @@ foreach(script ${git_perl_scripts} ${perl_modules}) 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" @@ -890,11 +896,11 @@ add_custom_target(perl-gen ALL DEPENDS ${perl_gen}) # 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/completion/git-completion.bash b/contrib/completion/git-completion.bash index 538dff1ee5..a8e7c6ddbf 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -3465,7 +3465,7 @@ _git_sparse_checkout () _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 @@ -3491,6 +3491,9 @@ _git_stash () show,--*) __gitcomp_builtin stash_show "$__git_diff_common_options" ;; + export,--*) + __gitcomp_builtin stash_export "--print --to-ref" + ;; *,--*) __gitcomp_builtin "stash_$subcommand" ;; @@ -3502,7 +3505,10 @@ _git_stash () | 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 f5877bd7a1..c32186a977 100644 --- a/contrib/completion/git-completion.zsh +++ b/contrib/completion/git-completion.zsh @@ -202,7 +202,7 @@ __git_zsh_cmd_common () __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 c68445b82d..219b0d7f49 100644 --- a/contrib/credential/osxkeychain/Makefile +++ b/contrib/credential/osxkeychain/Makefile @@ -1,66 +1,13 @@ # The default target of this Makefile is... all:: git-credential-osxkeychain -include ../../../config.mak.uname --include ../../../config.mak.autogen --include ../../../config.mak +git-credential-osxkeychain: + $(MAKE) -C ../../.. contrib/credential/osxkeychain/git-credential-osxkeychain -ifdef ZLIB_NG - BASIC_CFLAGS += -DHAVE_ZLIB_NG - ifdef ZLIB_NG_PATH - BASIC_CFLAGS += -I$(ZLIB_NG_PATH)/include - EXTLIBS += $(call libpath_template,$(ZLIB_NG_PATH)/$(lib)) - endif - EXTLIBS += -lz-ng -else - ifdef ZLIB_PATH - BASIC_CFLAGS += -I$(ZLIB_PATH)/include - EXTLIBS += $(call libpath_template,$(ZLIB_PATH)/$(lib)) - endif - EXTLIBS += -lz -endif -ifndef NO_ICONV - ifdef NEEDS_LIBICONV - ifdef ICONVDIR - BASIC_CFLAGS += -I$(ICONVDIR)/include - ICONV_LINK = $(call libpath_template,$(ICONVDIR)/$(lib)) - else - ICONV_LINK = - endif - ifdef NEEDS_LIBINTL_BEFORE_LIBICONV - ICONV_LINK += -lintl - endif - EXTLIBS += $(ICONV_LINK) -liconv - endif -endif -ifndef LIBC_CONTAINS_LIBINTL - EXTLIBS += -lintl -endif - -prefix ?= /usr/local -gitexecdir ?= $(prefix)/libexec/git-core - -CC ?= gcc -CFLAGS ?= -g -O2 -Wall -I../../.. $(BASIC_CFLAGS) -LDFLAGS ?= $(BASIC_LDFLAGS) $(EXTLIBS) -INSTALL ?= install -RM ?= rm -f - -%.o: %.c - $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ -c $< - -git-credential-osxkeychain: git-credential-osxkeychain.o ../../../libgit.a - $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) \ - -framework Security -framework CoreFoundation - -install: git-credential-osxkeychain - $(INSTALL) -d -m 755 $(DESTDIR)$(gitexecdir) - $(INSTALL) -m 755 $< $(DESTDIR)$(gitexecdir) - -../../../libgit.a: - cd ../../..; make libgit.a +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/diff-highlight/DiffHighlight.pm b/contrib/diff-highlight/DiffHighlight.pm index 3d061bc0b7..abe457882e 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 @@ use File::Spec; 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 @@ sub highlight_stdin { # 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 @@ sub show_hunk { $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 @@ sub highlight_pair { } 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 @@ sub highlight_line { # 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 1db4440e68..ed8d876a18 100644 --- a/contrib/diff-highlight/README +++ b/contrib/diff-highlight/README @@ -39,10 +39,21 @@ visually distracting. Non-diff lines and existing diff coloration is 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 @@ Your script may set up one or more of the following variables: 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 f6f5195d00..b38fe2196a 100755 --- a/contrib/diff-highlight/t/t9400-diff-highlight.sh +++ b/contrib/diff-highlight/t/t9400-diff-highlight.sh @@ -7,9 +7,8 @@ TEST_OUTPUT_DIRECTORY=$(pwd) 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 @@ dh_test () { 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 @@ test_expect_success 'diff-highlight highlights the beginning of a line' ' 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 @@ test_expect_success 'diff-highlight highlights the end of a line' ' 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 @@ test_expect_success 'diff-highlight highlights the middle of a line' ' 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 @@ test_expect_failure 'diff-highlight highlights mismatched hunk size' ' 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 @@ test_expect_success 'diff-highlight treats multibyte utf-8 as a unit' ' 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 @@ test_expect_failure 'diff-highlight treats combining code points as a unit' ' 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 @@ test_expect_success 'diff-highlight handles --graph with leading dash' ' +++ 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 a88c5dfe09..569c23ee76 100644 --- a/contrib/meson.build +++ b/contrib/meson.build @@ -2,5 +2,4 @@ foreach feature : get_option('contrib') subdir(feature) endforeach -subdir('coccinelle') subdir('credential') diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh index 17106d1a72..791fd8260c 100755 --- a/contrib/subtree/git-subtree.sh +++ b/contrib/subtree/git-subtree.sh @@ -257,6 +257,9 @@ main () { 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 @@ ensure_valid_ref_format () { 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 @@ cmd_split () { 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 @@ cmd_split () { 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 161435abeb..804c315894 100644 --- a/contrib/subtree/meson.build +++ b/contrib/subtree/meson.build @@ -3,7 +3,7 @@ git_subtree = custom_target( 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 316dc5269e..18d2b56448 100755 --- a/contrib/subtree/t/t7900-subtree.sh +++ b/contrib/subtree/t/t7900-subtree.sh @@ -368,6 +368,28 @@ test_expect_success 'split requires path given by option --prefix must exist' ' ) ' +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 @@ test_expect_success 'split sub dir/ with --rejoin' ' 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 @@ test_expect_success 'split sub dir/ with --rejoin' ' # - 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 @@ test_expect_success 'split with multiple subtrees' ' 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 @@ do 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 @@ do --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 @@ do ' 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 @@ test_expect_success 'push split to subproj' ' ) ' +# --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 'push split to subproj' ' 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 @@ test_expect_success 'subtree descendant check' ' ( 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_expect_success 'subtree descendant check' ' 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 ) @@ -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 6e21e3cac8..9558177a11 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 07ae11024a..a9b390d336 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/diff-lib.c b/diff-lib.c index 506000761d..ae91027a02 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -615,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) @@ -1837,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; @@ -1859,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; @@ -1961,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)); @@ -2749,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) @@ -2776,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 ? @@ -2859,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; @@ -3049,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; @@ -3764,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) { @@ -3775,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); @@ -3786,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; @@ -3993,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"); @@ -5215,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); @@ -7181,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); diff --git a/diffcore-break.c b/diffcore-break.c index c4c2173f30..17b5ad1fed 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 2de9e9ccff..2b7db39983 100644 --- a/diffcore-delta.c +++ b/diffcore-delta.c @@ -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); - MEMZERO_ARRAY(hash->data, ((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 7723bc3334..c797d8ed2f 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; } @@ -1551,7 +1551,9 @@ done: 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/environment.c b/environment.c index 8ffbf92d50..fc3ed8bb1c 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,7 +74,6 @@ enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED; #endif enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE; int grafts_keep_true_parents; -int core_apply_sparse_checkout; int core_sparse_checkout_cone; int sparse_expect_files_outside_of_patterns; int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */ @@ -303,6 +301,8 @@ next_name: 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); @@ -340,8 +340,8 @@ 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")) { @@ -508,6 +508,11 @@ 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); @@ -521,7 +526,7 @@ 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; } @@ -578,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")) { @@ -641,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")) { @@ -691,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); @@ -727,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 27f657af04..123a71cdc8 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. @@ -109,6 +123,8 @@ int git_default_config(const char *, const char *, int git_default_core_config(const char *var, const char *value, const struct config_context *ctx, void *cb); +void repo_config_values_init(struct repo_config_values *cfg); + /* * TODO: All the below state either explicitly or implicitly relies on * `the_repository`. We should eventually get rid of these and make the @@ -154,7 +170,6 @@ 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; @@ -163,7 +178,6 @@ 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 bf878bf876..c378e0ab78 100644 --- a/ewah/bitmap.c +++ b/ewah/bitmap.c @@ -46,7 +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); - MEMZERO_ARRAY(self->words + old_size, (self->word_alloc - old_size)); + MEMZERO_ARRAY(self->words + old_size, self->word_alloc - old_size); } void bitmap_set(struct bitmap *self, size_t pos) @@ -192,7 +192,7 @@ void bitmap_or_ewah(struct bitmap *self, struct ewah_bitmap *other) self->word_alloc = other_final; REALLOC_ARRAY(self->words, self->word_alloc); MEMZERO_ARRAY(self->words + original_size, - (self->word_alloc - original_size)); + self->word_alloc - original_size); } ewah_iterator_init(&it, other); diff --git a/fetch-pack.c b/fetch-pack.c index 40316c9a34..a32224ed02 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; @@ -292,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; } @@ -792,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); @@ -1016,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); @@ -1661,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) diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c index c9085edc40..45d8b20e97 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; @@ -1026,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; diff --git a/git-compat-util.h b/git-compat-util.h index bebcf9f698..4b4ea2498f 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -34,10 +34,6 @@ struct strbuf; # define DISABLE_WARNING(warning) #endif -#ifdef DISABLE_SIGN_COMPARE_WARNINGS -DISABLE_WARNING(-Wsign-compare) -#endif - #undef FLEX_ARRAY #define FLEX_ARRAY /* empty - weather balloon to require C99 FAM */ @@ -1099,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 659e5a3875..dccdd4d6e5 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/.gitignore b/git-gui/.gitignore index 5130b4f018..38a41ebc58 100644 --- a/git-gui/.gitignore +++ b/git-gui/.gitignore @@ -5,4 +5,5 @@ GIT-GUI-BUILD-OPTIONS 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 c2767b4136..2f729de4bb 100755 --- a/git-gui/GIT-VERSION-GEN +++ b/git-gui/GIT-VERSION-GEN @@ -5,19 +5,27 @@ DEF_VER=0.21.GITGUI 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 69b0b84435..ca01068810 100644 --- a/git-gui/Makefile +++ b/git-gui/Makefile @@ -9,7 +9,7 @@ all:: # 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-BUILD-OPTIONS: FORCE 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 @@ dist-version: GIT-VERSION-FILE @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 index 142d1bc3de..142d1bc3de 100755 --- a/git-gui/git-gui--askyesno +++ b/git-gui/git-gui--askyesno.sh diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index d3d3aa14a9..23fe76e498 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -3900,6 +3900,24 @@ if {[winfo exists $ui_comm]} { 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 442737ba4f..8be1a613fb 100644 --- a/git-gui/lib/diff.tcl +++ b/git-gui/lib/diff.tcl @@ -385,6 +385,8 @@ proc read_diff {fd conflict_size cont_info} { # 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 320ba09ecf..a8119aa29f 100644 --- a/git-gui/meson.build +++ b/git-gui/meson.build @@ -4,7 +4,7 @@ project('git-gui', 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 @@ version_file = custom_target( '@INPUT@', meson.current_source_dir(), '@OUTPUT@', + get_option('parent_project_dir'), ], build_always_stale: true, ) @@ -53,19 +54,21 @@ if target_machine.system() == 'windows' ) 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 0000000000..7591a34218 --- /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-send-email.perl b/git-send-email.perl index cd4b316ddc..bb8ddd1eef 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -23,6 +23,7 @@ use Getopt::Long; 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 @@ git send-email --translate-aliases --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 ($cover_cc, $cover_to); 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_settings = ( 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 @@ my %options = ( "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 @@ if (!defined $auto_8bit_encoding && scalar %broken_encoding) { 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 @@ sub smtp_auth_maybe { 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 handle_smtp_error { } 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 @@ sub ssl_verify_params { 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 { @@ -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 d7ebcaf366..15f96aad5e 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 cc32dcab4b..41116d8a14 100644 --- a/gitk-git/Makefile +++ b/gitk-git/Makefile @@ -68,9 +68,12 @@ gitk-wish: gitk GIT-TCLTK-VARS $(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 7f62c8041d..2730274966 100755 --- a/gitk-git/gitk +++ b/gitk-git/gitk @@ -6831,17 +6831,19 @@ proc drawtags {id x xt y1} { } 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}] @@ -6850,7 +6852,8 @@ proc drawtags {id x xt y1} { -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 @@ -11796,7 +11799,7 @@ proc prefspage_general {notebook} { 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] @@ -11873,6 +11876,11 @@ proc prefspage_colors {notebook} { -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 @@ -11880,7 +11888,7 @@ proc prefspage_colors {notebook} { 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 @@ -11891,6 +11899,7 @@ proc prefspage_set_colorswatches {page} { $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 ca3c0cec58..aecc068d30 100644 --- a/gitk-git/meson.build +++ b/gitk-git/meson.build @@ -25,6 +25,6 @@ custom_target( 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 0000000000..938309e6f4 --- /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 d1e7d92425..e7e2f87321 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 87dfc18b44..d588d0990f 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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "\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 @@ msgstr "" "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 @@ msgstr "" " és el mateix pedaç que\n" " " -#: gitk:9043 msgid "" " differs from\n" " " @@ -904,7 +696,6 @@ msgstr "" " difereix de\n" " " -#: gitk:9045 msgid "" "Diff of commits:\n" "\n" @@ -912,131 +703,101 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 5db3824828..fea18fa16e 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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" " ist das gleiche Patch wie\n" " " -#: gitk:9043 msgid "" " differs from\n" " " @@ -901,7 +693,6 @@ msgstr "" " ist unterschiedlich von\n" " " -#: gitk:9045 msgid "" "Diff of commits:\n" "\n" @@ -909,131 +700,101 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 fef3bbafee..25260c6571 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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 e4fac932e5..f1c54b6b6a 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 @@ msgstr "" "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 @@ msgstr "" "\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 @@ msgstr "" "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 @@ msgstr "" "est le même patch que \n" " " -#: gitk:9062 msgid "" " differs from\n" " " @@ -919,146 +710,118 @@ msgstr "" " 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 @@ msgstr "" "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 @@ msgstr "" "É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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 79ec5a5656..9ab3f9617d 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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "\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 @@ msgstr "" "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 @@ msgstr "" " Ugyanaz a patch mint\n" " " -#: gitk:9043 msgid "" " differs from\n" " " @@ -911,7 +703,6 @@ msgstr "" " különbözik innentől\n" " " -#: gitk:9045 msgid "" "Diff of commits:\n" "\n" @@ -919,132 +710,102 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 b58d23eb2b..aa34a34280 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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" " ha le stesse differenze di\n" " " -#: gitk:9043 msgid "" " differs from\n" " " @@ -911,7 +703,6 @@ msgstr "" " è diversa da\n" " " -#: gitk:9045 msgid "" "Diff of commits:\n" "\n" @@ -919,129 +710,99 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 ca3c29b457..2c40b76dc0 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 @@ msgstr "" "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 @@ msgstr "" "ファイル未選択: --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 @@ msgstr "" "\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 @@ msgstr "" "コミット日時 (\"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 @@ msgstr "" " は下記のパッチと同等\n" " " -#: gitk:9044 msgid "" " differs from\n" " " @@ -919,7 +709,6 @@ msgstr "" " 下記からのdiff\n" " " -#: gitk:9046 msgid "" "Diff of commits:\n" "\n" @@ -927,130 +716,100 @@ msgstr "" "コミットの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 @@ msgstr "" "あなたの変更に 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 @@ msgstr "" "マージの衝突によってチェリーピックは失敗しました。\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 @@ msgstr "" "マージの衝突によって撤回は失敗しました。\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 @@ msgstr "" "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 @@ msgstr "" "ブランチ %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 @@ msgstr "" "コミット構造情報読み込みエラー; ブランチ及び上位/下位のタグ情報が不完全である" "ようです。" -#: 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 @@ msgstr "" "申し訳ありませんが、このバージョンの 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 1feb34854b..e78fb26a2e 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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "\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 @@ msgstr "" "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 @@ msgstr "" "é 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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 f680ea86aa..66d3159d47 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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "\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 @@ msgstr "" "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 @@ msgstr "" " é o mesmo patch que\n" " " -#: gitk:9062 msgid "" " differs from\n" " " @@ -916,7 +704,6 @@ msgstr "" " difere de\n" " " -#: gitk:9064 msgid "" "Diff of commits:\n" "\n" @@ -924,129 +711,99 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 9b08c263ea..e102fb7200 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 @@ msgid "" "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 5afbe6da1d..c9290625c0 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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "\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 @@ msgstr "" "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 @@ msgstr "" " är samma patch som\n" " " -#: gitk:9130 msgid "" " differs from\n" " " @@ -927,7 +713,6 @@ msgstr "" " skiljer sig från\n" " " -#: gitk:9132 msgid "" "Diff of commits:\n" "\n" @@ -935,148 +720,114 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 0e390c5153..9b9176a4aa 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 @@ msgstr "" "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 @@ msgstr "" "\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 @@ msgstr "" "'%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 @@ msgstr "" "ஒன்றிணைக்கும் மோதல் காரணமாக கனி-எடு தோல்வியடைந்தது. \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 @@ msgstr "" "ஒன்றிணைக்கும் மோதல் காரணமாக மீட்டெடு தோல்வியடைந்தது. \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 @@ msgstr "" "கடினம்: வேலை செய்யும் மரம் மற்றும் குறியீட்டை மீட்டமை \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 @@ msgstr "" "கிளை %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 @@ msgstr "" "மன்னிக்கவும், டிசிஎல்/டிகேயின் இந்த பதிப்பைக் கொண்டு அறிவிலிகே இயக்க முடியாது. \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 5967498660..52c9d09a09 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 @@ msgstr "" "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 @@ 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 " "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 @@ msgstr "" "\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 @@ msgstr "" "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 @@ msgstr "" " là cùng một miếng vá với\n" " " -#: gitk:9043 msgid "" " differs from\n" " " @@ -905,7 +697,6 @@ msgstr "" " khác biệt từ\n" " " -#: gitk:9045 msgid "" "Diff of commits:\n" "\n" @@ -913,131 +704,101 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 @@ msgstr "" "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 17b7f899da..71a9878318 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 @@ msgstr "" "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 @@ msgid "" "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 b5490dfecf..fde804593b 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -4214,6 +4214,7 @@ sub git_header_html { <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 48d2e51015..e2e6dd96a2 100644 --- a/gitweb/static/gitweb.css +++ b/gitweb/static/gitweb.css @@ -42,7 +42,7 @@ a.list img.avatar { } div.page_header { - height: 25px; + min-height: 25px; padding: 8px; font-size: 150%; font-weight: bold; @@ -73,11 +73,17 @@ div.page_path { } 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.title_text { div.log_body { padding: 8px 8px 8px 150px; + overflow-wrap: anywhere; } span.age { @@ -684,3 +691,66 @@ div.remote { .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 47222bf31b..d517425034 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; @@ -973,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; } /* @@ -1142,21 +1152,28 @@ out: 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 if (!strcmp(arg, "strip-if-invalid")) + } else if (!strcmp(arg, "strip-if-invalid")) { *mode = SIGN_STRIP_IF_INVALID; - else + } 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 789d1ffac4..a365586ce1 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 @@ -112,12 +120,15 @@ enum sign_mode { 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 @@ -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; +} @@ -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); @@ -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; } @@ -857,7 +855,7 @@ struct similar_ref_cb { 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(ref->name, '/') + 1; + const char *branch = strrchr(ref->name, '/') + 1; /* A remote branch of the same name is deemed similar */ if (starts_with(ref->name, "refs/remotes/") && @@ -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; } @@ -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 0122146df6..1a171c5c5a 100644 --- a/http-backend.c +++ b/http-backend.c @@ -565,9 +565,13 @@ 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); diff --git a/http-push.c b/http-push.c index cc0f809346..9ae6062198 100644 --- a/http-push.c +++ b/http-push.c @@ -1768,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) { @@ -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)) @@ -2543,8 +2645,9 @@ cleanup: void http_install_packfile(struct packed_git *p, struct packfile_list *list_to_remove_from) { + 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(the_repository->objects->sources->packfiles, p); + packfile_store_add_pack(files->packed, p); } struct http_pack_request *new_http_pack_request( @@ -20,6 +20,7 @@ struct slot_results { long http_code; long auth_avail; long http_connectcode; + long retry_after; }; struct active_request_slot { @@ -157,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_*() */ @@ -167,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. diff --git a/imap-send.c b/imap-send.c index 26dda7f328..af02c6a689 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 8bd422148d..eeaf68454e 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/list-objects-filter-options.c b/list-objects-filter-options.c index 7420bf81fe..cef67e5919 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); + if (result) + goto cleanup; - result = has_reserved_character(subspec, errbuf) || - gently_parse_list_objects_filter( + 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 7b2108b986..77d7bbc846 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 acd65ebb73..78316e7f90 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/lockfile.c b/lockfile.c index 67082a9caa..7add2f136a 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 @@ -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); + + get_lock_path(&lock_path, base_path.buf); + get_pid_path(&pid_path, base_path.buf); - strbuf_addstr(&filename, LOCK_SUFFIX); - lk->tempfile = create_tempfile_mode(filename.buf, mode); - strbuf_release(&filename); + 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 1bb9926497..e7233f28de 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 1729b0c201..7e048701d0 100644 --- a/log-tree.c +++ b/log-tree.c @@ -1077,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); @@ -3,6 +3,7 @@ #include "path.h" #include "object-file.h" #include "odb.h" +#include "odb/source-files.h" #include "hex.h" #include "repository.h" #include "wrapper.h" @@ -49,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->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->cache) { - ALLOC_ARRAY(source->loose->cache, 1); - oidtree_init(source->loose->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); @@ -125,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; @@ -231,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) ? @@ -160,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)); @@ -201,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 99ac596e09..13949ff31e 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); @@ -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; } @@ -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 62441dcc71..8bc77cb0e8 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 c54d56b344..2110844f53 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 e80e4f735a..00923ce3cd 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 @@ -1732,9 +1740,9 @@ static int collect_merge_info(struct merge_options *opt, info.data = opt; info.show_all_errors = 1; - if (repo_parse_tree(the_repository, merge_base) < 0 || - repo_parse_tree(the_repository, side1) < 0 || - repo_parse_tree(the_repository, 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); @@ -1857,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)"), @@ -2136,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, @@ -2240,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); @@ -2254,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, @@ -2272,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; @@ -2731,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, @@ -2786,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 @@ -2799,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. */ @@ -3214,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; @@ -3713,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, @@ -3822,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; @@ -3856,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); @@ -4026,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; } @@ -4101,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) { /* @@ -4148,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)); } /* @@ -4270,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; } @@ -4282,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; } @@ -4414,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; } @@ -4438,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) { @@ -4573,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); @@ -4619,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 (repo_parse_tree(the_repository, 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 (repo_parse_tree(the_repository, next) < 0) + if (repo_parse_tree(opt->repo, next) < 0) return -1; init_tree_desc(&trees[1], &next->object.oid, next->buffer, next->size); @@ -5280,7 +5288,7 @@ redo: if (result->clean >= 0) { if (!opt->mergeability_only) { - result->tree = repo_parse_tree_indirect(the_repository, + result->tree = repo_parse_tree_indirect(opt->repo, &working_tree_oid); if (!result->tree) die(_("unable to read tree (%s)"), @@ -5302,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); @@ -5383,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, @@ -5440,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 ? @@ -5466,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); @@ -5474,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, diff --git a/meson.build b/meson.build index dd52efd1c8..8309942d18 100644 --- a/meson.build +++ b/meson.build @@ -239,7 +239,9 @@ git = find_program('git', dirs: program_path, native: true, required: false) 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 @@ version_gen_environment.set('GIT_VERSION', get_option('version')) 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 @@ libgit_sources = [ '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,8 @@ libgit_sources = [ 'object-name.c', 'object.c', 'odb.c', + 'odb/source.c', + 'odb/source-files.c', 'odb/streaming.c', 'oid-array.c', 'oidmap.c', @@ -471,6 +478,7 @@ libgit_sources = [ 'repack-midx.c', 'repack-promisor.c', 'replace-object.c', + 'replay.c', 'repo-settings.c', 'repository.c', 'rerere.c', @@ -551,7 +559,7 @@ libgit_sources = [ 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_sources = [ '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 @@ endif 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 @@ builtin_sources += custom_target( 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 @@ if iconv.found() 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 meson.can_run_host_binaries() and 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 @@ endif 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 @@ endif # 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 @@ if host_machine.system() != 'windows' 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 @@ else 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 @@ elif host_machine.system() == 'windows' 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 @@ endif # 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 @@ if fsmonitor_backend != '' 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 not get_option('b_sanitize').contains('address') and get_option('regex').allo 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 @@ elif not get_option('regex').enabled() '-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') @@ -1405,6 +1429,7 @@ checkfuncs = { 'initgroups' : [], 'strtoumax' : ['strtoumax.c', 'strtoimax.c'], 'pread' : ['pread.c'], + 'writev' : ['writev.c'], } if host_machine.system() == 'windows' @@ -1417,9 +1442,9 @@ else 'getpagesize' : [], } - if get_option('b_sanitize').contains('address') + if get_option('b_sanitize').contains('address') or get_option('b_sanitize').contains('leak') libgit_c_args += '-DNO_MMAP' - libgit_sources += 'compat/mmap.c' + compat_sources += 'compat/mmap.c' else checkfuncs += { 'mmap': ['mmap.c'] } endif @@ -1429,7 +1454,7 @@ 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 @@ -1440,13 +1465,13 @@ endif 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' @@ -1502,7 +1527,7 @@ if meson.can_run_host_binaries() and compiler.run(''' } ''', 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') @@ -1736,14 +1761,23 @@ else 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, @@ -1800,6 +1834,7 @@ test_dependencies = [ ] git_builtin = executable('git', sources: builtin_sources + 'git.c', + c_pch: 'tools/precompiled.h', dependencies: [libgit_commonmain], install: true, install_dir: git_exec_path, @@ -1950,7 +1985,7 @@ foreach script : scripts_sh 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', @@ -1999,7 +2034,7 @@ if perl_features_enabled 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, @@ -2048,7 +2083,7 @@ if target_python.found() 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@', @@ -2140,6 +2175,7 @@ else 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. @@ -2250,6 +2286,16 @@ configure_file( 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. @@ -2276,6 +2322,8 @@ summary({ '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 e0be260ae1..659cbb218f 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -43,6 +43,10 @@ option('expat', type: 'feature', value: 'enabled', 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 6485cb6706..0ff2e45aa7 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,7 +746,7 @@ 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); @@ -900,6 +955,21 @@ cleanup: 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; @@ -907,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; @@ -946,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); @@ -1026,6 +1165,12 @@ static bool midx_needs_update(struct multi_pack_index *midx, struct write_midx_c 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. @@ -1033,6 +1178,9 @@ static bool midx_needs_update(struct multi_pack_index *midx, struct write_midx_c 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 @@ -1078,14 +1226,31 @@ out: return needed; } -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 int midx_hashcmp(const struct multi_pack_index *a, + const struct multi_pack_index *b, + const struct git_hash_algo *algop) { - struct repository *r = source->odb->repo; + 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; @@ -1094,6 +1259,7 @@ 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; @@ -1101,27 +1267,45 @@ static int write_midx_internal(struct odb_source *source, 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; + + 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.incremental = !!(flags & MIDX_WRITE_INCREMENTAL); + 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; @@ -1136,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; @@ -1149,34 +1340,42 @@ 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 (!packs_to_drop) { + 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 @@ -1189,7 +1388,7 @@ static int write_midx_internal(struct odb_source *source, if (midx && !midx_needs_update(midx, &ctx)) { struct bitmap_index *bitmap_git; int bitmap_exists; - int want_bitmap = flags & MIDX_WRITE_BITMAP; + int want_bitmap = opts->flags & MIDX_WRITE_BITMAP; bitmap_git = prepare_midx_bitmap_git(midx); bitmap_exists = bitmap_git && bitmap_is_midx(bitmap_git); @@ -1201,7 +1400,7 @@ static int write_midx_internal(struct odb_source *source, * corresponding bitmap (or one wasn't requested). */ if (!want_bitmap) - clear_midx_files_ext(source, "bitmap", NULL); + clear_midx_files_ext(ctx.source, "bitmap", NULL); result = 0; goto cleanup; } @@ -1216,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; @@ -1229,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; @@ -1242,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"); /* @@ -1302,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--; @@ -1338,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++) { @@ -1354,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); } } @@ -1377,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); @@ -1428,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)), @@ -1439,18 +1654,18 @@ 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_stack commits = COMMIT_STACK_INIT; @@ -1459,7 +1674,7 @@ static int write_midx_internal(struct odb_source *source, prepare_midx_packing_data(&pdata, &ctx); - find_commits_for_midx_bitmap(&commits, refs_snapshot, &ctx); + find_commits_for_midx_bitmap(&commits, opts->refs_snapshot, &ctx); /* * The previous steps translated the information from @@ -1470,8 +1685,8 @@ 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.items, 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); commit_stack_clear(&commits); @@ -1489,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"); @@ -1504,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) { @@ -1514,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] = @@ -1538,8 +1799,7 @@ static int write_midx_internal(struct odb_source *source, 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; @@ -1557,7 +1817,7 @@ cleanup: 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); } @@ -1573,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, @@ -1583,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) @@ -1643,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); @@ -1778,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 @@ -1852,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); @@ -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->packfiles); - return source->packfiles->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,6 +456,7 @@ static uint32_t midx_for_pack(struct multi_pack_index **_m, int prepare_midx_pack(struct multi_pack_index *m, uint32_t pack_int_id) { + struct odb_source_files *files = odb_source_files_downcast(m->source); struct strbuf pack_name = STRBUF_INIT; struct packed_git *p; @@ -457,10 +467,10 @@ int prepare_midx_pack(struct multi_pack_index *m, if (m->packs[pack_int_id]) return 0; - strbuf_addf(&pack_name, "%s/pack/%s", m->source->path, + strbuf_addf(&pack_name, "%s/pack/%s", files->base.path, m->pack_names[pack_int_id]); - p = packfile_store_load_pack(m->source->packfiles, - pack_name.buf, m->source->local); + p = packfile_store_load_pack(files->packed, + pack_name.buf, files->base.local); strbuf_release(&pack_name); if (!p) { @@ -649,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; @@ -703,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->packfiles->midx) + if (files->packed->midx) return 1; - source->packfiles->midx = load_multi_pack_index(source); + files->packed->midx = load_multi_pack_index(source); - return !!source->packfiles->midx; + return !!files->packed->midx; } int midx_checksum_valid(struct multi_pack_index *m) @@ -803,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->packfiles->midx) - close_midx(source->packfiles->midx); - source->packfiles->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; } } @@ -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 116dedcf83..3cac0476a7 100644 --- a/negotiator/default.c +++ b/negotiator/default.c @@ -57,19 +57,19 @@ static int clear_marks(const struct reference *ref, void *cb_data UNUSED) 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)) @@ -94,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 0a272130fb..fe4126ca4d 100644 --- a/negotiator/skipping.c +++ b/negotiator/skipping.c @@ -91,15 +91,15 @@ static int clear_marks(const struct reference *ref, void *cb_data UNUSED) */ 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)) @@ -113,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 586939939f..b9322abbcb 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 6a50c6d564..5c1c75d5b8 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) @@ -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, @@ -953,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 7ab875afe6..63ee18630b 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 (!srcoid->algo) { + oidcpy(&temp, srcoid); + temp.algo = hash_algo_by_ptr(repo->hash_algo); + src = &temp; + } - if (from == to) { + if (from == to || !to) { if (src != dest) oidcpy(dest, src); return 0; diff --git a/object-file.c b/object-file.c index e7e4c3348f..4f77ce0982 100644 --- a/object-file.c +++ b/object-file.c @@ -33,6 +33,9 @@ /* 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) @@ -129,18 +132,15 @@ 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; - struct odb_read_stream *st; struct git_hash_ctx c; char hdr[MAX_HEADER_LEN]; int hdrlen; - st = odb_read_stream_open(r->objects, oid, NULL); - if (!st) - return -1; - /* Generate the header */ hdrlen = format_object_header(hdr, sizeof(hdr), st->type, st->size); @@ -160,35 +160,17 @@ 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); - odb_read_stream_close(st); return !oideq(oid, &real_oid) ? -1 : 0; } /* - * Find "oid" as a loose object in given source. - * 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 odb_source_loose *loose, - const struct object_id *oid, - struct stat *st, const char **path) -{ - static struct strbuf buf = STRBUF_INIT; - - *path = odb_loose_path(loose->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 odb_source_loose *loose, const struct object_id *oid, const char **path) { @@ -236,8 +218,9 @@ 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(source->loose, oid, &p); + int fd = open_loose_object(files->loose, oid, &p); if (fd < 0) return NULL; @@ -412,19 +395,22 @@ static int parse_loose_header(const char *hdr, struct object_info *oi) return 0; } -int odb_source_loose_read_object_info(struct odb_source *source, +static int read_object_info_from_path(struct odb_source *source, + const char *path, const struct object_id *oid, - struct object_info *oi, int flags) + struct object_info *oi, + enum object_info_flags flags) { + struct odb_source_files *files = odb_source_files_downcast(source); int ret; int fd; unsigned long mapsize; - const char *path; void *map = NULL; git_zstream stream, *stream_to_end = NULL; char hdr[MAX_HEADER_LEN]; unsigned long size_scratch; enum object_type type_scratch; + struct stat st; /* * If we don't care about type or size, then we don't @@ -437,24 +423,28 @@ int odb_source_loose_read_object_info(struct odb_source *source, if (!oi || (!oi->typep && !oi->sizep && !oi->contentp)) { struct stat st; - if ((!oi || !oi->disk_sizep) && (flags & OBJECT_INFO_QUICK)) { - ret = quick_has_loose(source->loose, oid) ? 0 : -1; + if ((!oi || (!oi->disk_sizep && !oi->mtimep)) && (flags & OBJECT_INFO_QUICK)) { + ret = quick_has_loose(files->loose, oid) ? 0 : -1; goto out; } - if (stat_loose_object(source->loose, oid, &st, &path) < 0) { + if (lstat(path, &st) < 0) { ret = -1; goto out; } - if (oi && oi->disk_sizep) - *oi->disk_sizep = st.st_size; + 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(source->loose, oid, &path); + fd = git_open(path); if (fd < 0) { if (errno != ENOENT) error_errno(_("unable to open loose object %s"), oid_to_hex(oid)); @@ -462,7 +452,21 @@ int odb_source_loose_read_object_info(struct odb_source *source, goto out; } - map = map_fd(fd, path, &mapsize); + 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; @@ -470,6 +474,8 @@ int odb_source_loose_read_object_info(struct odb_source *source, if (oi->disk_sizep) *oi->disk_sizep = mapsize; + if (oi->mtimep) + *oi->mtimep = st.st_mtime; stream_to_end = &stream; @@ -533,6 +539,26 @@ out: return ret; } +int odb_source_loose_read_object_info(struct odb_source *source, + const struct object_id *oid, + struct object_info *oi, + enum object_info_flags flags) +{ + static struct strbuf buf = STRBUF_INIT; + + /* + * The second read shouldn't cause new loose objects to show up, unless + * there was a race condition with a secondary process. We don't care + * about this case though, so we simply skip reading loose objects a + * second time. + */ + if (flags & OBJECT_INFO_SECOND_READ) + return -1; + + odb_loose_path(source, &buf, oid); + return read_object_info_from_path(source, buf.buf, oid, oi, flags); +} + static void hash_object_body(const struct git_hash_algo *algo, struct git_hash_ctx *c, const void *buf, unsigned long len, struct object_id *oid, @@ -710,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 @@ -754,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; @@ -772,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); @@ -1340,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; @@ -1358,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); @@ -1466,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; @@ -1494,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, @@ -1534,7 +1566,7 @@ clear_exit: * 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) @@ -1553,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 */ @@ -1629,11 +1661,15 @@ int index_fd(struct index_state *istate, struct object_id *oid, ret = index_core(istate, oid, fd, xsize_t(st->st_size), type, path, flags); } else { + struct 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); @@ -1787,26 +1823,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) @@ -1815,36 +1998,37 @@ static int append_loose_object(const struct object_id *oid, return 0; } -struct oidtree *odb_source_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->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->subdir_seen)) + (size_t) subdir_nr >= bitsizeof(files->loose->subdir_seen)) BUG("subdir_nr out of range"); - bitmap = &source->loose->subdir_seen[word_index]; + bitmap = &files->loose->subdir_seen[word_index]; if (*bitmap & mask) - return source->loose->cache; - if (!source->loose->cache) { - ALLOC_ARRAY(source->loose->cache, 1); - oidtree_init(source->loose->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->cache); + files->loose->cache); *bitmap |= mask; strbuf_release(&buf); - return source->loose->cache; + return files->loose->cache; } static void odb_source_loose_clear_cache(struct odb_source_loose *loose) @@ -1857,7 +2041,8 @@ static void odb_source_loose_clear_cache(struct odb_source_loose *loose) void odb_source_loose_reprepare(struct odb_source *source) { - odb_source_loose_clear_cache(source->loose); + 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, @@ -1985,33 +2170,28 @@ out: return ret; } -struct odb_transaction *object_file_transaction_begin(struct odb_source *source) +static void odb_transaction_files_commit(struct odb_transaction *base) { - struct object_database *odb = source->odb; - - if (odb->transaction) - return NULL; + struct odb_transaction_files *transaction = + container_of(base, struct odb_transaction_files, base); - CALLOC_ARRAY(odb->transaction, 1); - odb->transaction->odb = odb; - - return odb->transaction; + flush_loose_object_transaction(transaction); + flush_packfile_transaction(transaction); } -void object_file_transaction_commit(struct odb_transaction *transaction) +struct odb_transaction *odb_transaction_files_begin(struct odb_source *source) { - if (!transaction) - return; + struct odb_transaction_files *transaction; + struct object_database *odb = source->odb; - /* - * Ensure the transaction ending matches the pending transaction. - */ - ASSERT(transaction == transaction->odb->transaction); + if (odb->transaction) + return NULL; - flush_loose_object_transaction(transaction); - flush_packfile_transaction(transaction); - transaction->odb->transaction = NULL; - free(transaction); + transaction = xcalloc(1, sizeof(*transaction)); + transaction->base.source = source; + transaction->base.commit = odb_transaction_files_commit; + + return &transaction->base; } struct odb_source_loose *odb_source_loose_new(struct odb_source *source) @@ -2048,7 +2228,8 @@ struct odb_loose_read_stream { static ssize_t read_istream_loose(struct odb_read_stream *_st, char *buf, size_t sz) { - struct odb_loose_read_stream *st = (struct odb_loose_read_stream *)_st; + struct odb_loose_read_stream *st = + container_of(_st, struct odb_loose_read_stream, base); size_t total_read = 0; switch (st->z_state) { @@ -2094,7 +2275,9 @@ static ssize_t read_istream_loose(struct odb_read_stream *_st, char *buf, size_t static int close_istream_loose(struct odb_read_stream *_st) { - struct odb_loose_read_stream *st = (struct odb_loose_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); @@ -2150,7 +2333,7 @@ int odb_source_loose_read_object_stream(struct odb_read_stream **out, return 0; error: git_inflate_end(&st->z); - munmap(st->mapped, st->mapsize); + munmap(mapped, mapsize); free(st); return -1; } diff --git a/object-file.h b/object-file.h index 1229d5f675..3686f182e4 100644 --- a/object-file.h +++ b/object-file.h @@ -47,7 +47,8 @@ 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, int flags); + 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, @@ -74,13 +75,6 @@ int odb_source_loose_write_stream(struct odb_source *source, struct object_id *oid); /* - * Populate and return the loose object cache array corresponding to the - * given object ID. - */ -struct oidtree *odb_source_loose_cache(struct odb_source *source, - const struct object_id *oid); - -/* * Put in `buf` the name of the file in the local object database that * would be used to store a loose object with the specified oid. */ @@ -126,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. + * Iterate through all loose objects in the given object database source and + * invoke the callback function for each of them. If an object info request is + * given, then the object info will be read for every individual object and + * passed to the callback as if `odb_source_loose_read_object_info()` was + * called for the object. + */ +int odb_source_loose_for_each_object(struct odb_source *source, + const struct object_info *request, + odb_for_each_object_cb cb, + void *cb_data, + const struct odb_for_each_object_options *opts); + +/* + * Count the number of loose objects in this source. + * + * The object count is approximated by opening a single sharding directory for + * loose objects and scanning its contents. The result is then extrapolated by + * 256. This should generally work as a reasonable estimate given that the + * object hash is supposed to be indistinguishable from random. * - * Any flags specific to packs are ignored. + * Returns 0 on success, a negative error code otherwise. */ -int for_each_loose_object(struct object_database *odb, - each_loose_object_fn, void *, - enum for_each_object_flags flags); +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 @@ -164,7 +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 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, @@ -202,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 8b862c124e..21dcdc4a0e 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_source_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; } @@ -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; - - 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[len] = 0; - 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; } @@ -1623,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) @@ -1632,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; } @@ -1660,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; @@ -1692,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; @@ -1756,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; @@ -2052,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 cda4934cd5..167a9154ea 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 @@ -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" @@ -343,9 +344,21 @@ struct object *parse_object_with_flags(struct repository *r, 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); @@ -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 @@ -86,7 +86,7 @@ void object_array_init(struct object_array *array); * builtin/unpack-objects.c: 2021 * pack-bitmap.h: 2122 */ -#define FLAG_BITS 28 +#define FLAG_BITS 29 #define TYPE_BITS 3 @@ -1,6 +1,5 @@ #include "git-compat-util.h" #include "abspath.h" -#include "chdir-notify.h" #include "commit-graph.h" #include "config.h" #include "dir.h" @@ -13,6 +12,7 @@ #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" @@ -132,10 +132,10 @@ out: return usable; } -static void parse_alternates(const char *string, - int sep, - const char *relative_base, - struct strvec *out) +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; @@ -199,41 +199,6 @@ static void parse_alternates(const char *string, strbuf_release(&buf); } -static void odb_source_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; - } - parse_alternates(buf.buf, '\n', source->path, out); - - strbuf_release(&buf); - free(path); -} - - -static struct odb_source *odb_source_new(struct object_database *odb, - const char *path, - bool local) -{ - struct odb_source *source; - - CALLOC_ARRAY(source, 1); - source->odb = odb; - source->local = local; - source->path = xstrdup(path); - source->loose = odb_source_loose_new(source); - source->packfiles = packfile_store_new(source); - - return source; -} - static struct odb_source *odb_add_alternate_recursively(struct object_database *odb, const char *source, int depth) @@ -272,58 +237,6 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database * return alternate; } -static int odb_source_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; -} - void odb_add_to_alternates_file(struct object_database *odb, const char *dir) { @@ -373,14 +286,6 @@ struct odb_source *odb_set_temporary_primary_source(struct object_database *odb, return source->next; } -static void odb_source_free(struct odb_source *source) -{ - free(source->path); - odb_source_loose_free(source->loose); - packfile_store_free(source->packfiles); - free(source); -} - void odb_restore_primary_source(struct object_database *odb, struct odb_source *restore_source, const char *old_path) @@ -702,6 +607,8 @@ static int do_oid_object_info_extended(struct object_database *odb, 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; @@ -712,18 +619,19 @@ static int do_oid_object_info_extended(struct object_database *odb, while (1) { struct odb_source *source; - /* Most likely it's a loose object. */ - for (source = odb->sources; source; source = source->next) { - if (!packfile_store_read_object_info(source->packfiles, real, oi, flags) || - !odb_source_loose_read_object_info(source, real, oi, flags)) + 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); for (source = odb->sources; source; source = source->next) - if (!packfile_store_read_object_info(source->packfiles, real, oi, flags)) + if (!odb_source_read_object_info(source, real, oi, + flags | OBJECT_INFO_SECOND_READ)) return 0; } @@ -842,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; @@ -964,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; @@ -982,19 +890,153 @@ 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 (packfile_store_freshen_object(source->packfiles, oid)) + for (source = odb->sources; source; source = source->next) + if (odb_source_freshen_object(source, oid)) return 1; + return 0; +} - if (odb_source_loose_freshen_object(source, oid)) - return 1; +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) { @@ -1013,47 +1055,15 @@ int odb_write_object_ext(struct object_database *odb, struct object_id *compat_oid, unsigned flags) { - return odb_source_loose_write_object(odb->sources, buf, len, type, - oid, compat_oid, flags); + return odb_source_write_object(odb->sources, buf, len, type, + oid, compat_oid, flags); } int odb_write_object_stream(struct object_database *odb, struct odb_write_stream *stream, size_t len, struct object_id *oid) { - return odb_source_loose_write_stream(odb->sources, stream, len, oid); -} - -static void odb_update_commondir(const char *name UNUSED, - const char *old_cwd, - const char *new_cwd, - void *cb_data) -{ - struct object_database *odb = cb_data; - struct tmp_objdir *tmp_objdir; - struct odb_source *source; - - tmp_objdir = tmp_objdir_unapply_primary_odb(); - - /* - * In theory, we only have to do this for the primary object source, as - * alternates' paths are always resolved to an absolute path. - */ - for (source = odb->sources; source; source = source->next) { - char *path; - - if (is_absolute_path(source->path)) - continue; - - path = reparent_relative_path(old_cwd, new_cwd, - source->path); - - free(source->path); - source->path = path; - } - - if (tmp_objdir) - tmp_objdir_reapply_primary_odb(tmp_objdir, old_cwd, new_cwd); + return odb_source_write_object_stream(odb->sources, stream, len, oid); } struct object_database *odb_new(struct repository *repo, @@ -1076,8 +1086,6 @@ struct object_database *odb_new(struct repository *repo, free(to_free); - chdir_notify_register(NULL, odb_update_commondir, o); - return o; } @@ -1085,7 +1093,7 @@ void odb_close(struct object_database *o) { struct odb_source *source; for (source = o->sources; source; source = source->next) - packfile_store_close(source->packfiles); + odb_source_close(source); close_commit_graph(o); } @@ -1121,8 +1129,6 @@ void odb_free(struct object_database *o) string_list_clear(&o->submodule_source_paths, 0); - chdir_notify_unregister(NULL, odb_update_commondir, o); - free(o); } @@ -1141,22 +1147,35 @@ void odb_reprepare(struct object_database *o) o->loaded_alternates = 0; odb_prepare_alternates(o); - for (source = o->sources; source; source = source->next) { - odb_source_loose_reprepare(source); - packfile_store_reprepare(source->packfiles); - } + for (source = o->sources; source; source = source->next) + odb_source_reprepare(source); - o->approximate_object_count_valid = 0; + 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); } @@ -11,6 +11,7 @@ struct oidmap; struct oidtree; struct strbuf; +struct strvec; struct repository; struct multi_pack_index; @@ -30,55 +31,28 @@ extern int fetch_if_missing; */ char *compute_alternate_path(const char *path, struct strbuf *err); +struct packed_git; +struct packfile_store; +struct cached_object_entry; + /* - * 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. + * 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. * - * - 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. + * Each ODB source is expected to implement its own transaction handling. */ -struct odb_source { - struct odb_source *next; - - /* Object database that owns this object source. */ - struct object_database *odb; - - /* Private state for loose objects. */ - struct odb_source_loose *loose; - - /* Should only be accessed directly by packfile.c and midx.c. */ - struct packfile_store *packfiles; - - /* - * 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; +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; - /* - * Path to the source. If this is a relative path, it is relative to - * the current working directory. - */ - char *path; + /* The ODB source specific callback invoked to commit a transaction. */ + odb_transaction_commit_fn commit; }; -struct packed_git; -struct packfile_store; -struct cached_object_entry; -struct odb_transaction; - /* * The object database encapsulates access to objects in a repository. It * manages one or more sources that store the actual objects which are @@ -136,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 @@ -318,6 +293,19 @@ 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, @@ -352,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), + + /* 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. */ -#define OBJECT_INFO_DIE_IF_CORRUPT 32 + /* 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` @@ -377,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 @@ -389,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. */ @@ -402,7 +408,7 @@ 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); @@ -442,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 @@ -510,4 +609,9 @@ 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 0000000000..76797569de --- /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 0000000000..23a3b4e04b --- /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 0000000000..7993dcbd65 --- /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 0000000000..a9d7d0b96f --- /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 index 4a4474f891..5927a12954 100644 --- a/odb/streaming.c +++ b/odb/streaming.c @@ -6,11 +6,10 @@ #include "convert.h" #include "environment.h" #include "repository.h" -#include "object-file.h" #include "odb.h" +#include "odb/source.h" #include "odb/streaming.h" #include "replace-object.h" -#include "packfile.h" #define FILTER_BUFFER (1024*16) @@ -186,11 +185,9 @@ static int istream_source(struct odb_read_stream **out, struct odb_source *source; odb_prepare_alternates(odb); - for (source = odb->sources; source; source = source->next) { - if (!packfile_store_read_object_stream(out, source->packfiles, oid) || - !odb_source_loose_read_object_stream(out, source, oid)) + 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); } @@ -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) @@ -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. * @@ -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); } @@ -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 fb8b8787a4..59bbb849d1 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 625fa92b2f..8338d7217e 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); } @@ -414,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; } @@ -445,7 +445,7 @@ next: "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) diff --git a/pack-bitmap.c b/pack-bitmap.c index 972203f12b..f6ec18d83a 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -441,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); @@ -502,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; @@ -2819,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, @@ -3314,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; @@ -3323,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 1bd7a791e2..a95e1c2d11 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 67cb2cf72f..7378c80730 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-revindex.c b/pack-revindex.c index 8598b941c8..1b67863606 100644 --- a/pack-revindex.c +++ b/pack-revindex.c @@ -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/packfile.c b/packfile.c index 402c3b5dc7..48c88748b6 100644 --- a/packfile.c +++ b/packfile.c @@ -362,9 +362,11 @@ static int unuse_one_window(struct object_database *odb) struct packed_git *lru_p = NULL; struct pack_window *lru_w = NULL, *lru_l = NULL; - for (source = odb->sources; source; source = source->next) - for (e = source->packfiles->packs.head; e; e = e->next) + 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); @@ -537,7 +539,8 @@ static int close_one_pack(struct repository *r) int accept_windows_inuse = 1; for (source = r->objects->sources; source; source = source->next) { - for (e = source->packfiles->packs.head; e; e = e->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); @@ -987,13 +990,14 @@ 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->source->packfiles->midx && - midx_contains_pack(data->source->packfiles->midx, 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->source->packfiles, + packfile_store_load_pack(files->packed, trimmed_path, data->source->local); free(trimmed_path); } @@ -1097,37 +1101,35 @@ struct packfile_list_entry *packfile_store_get_packs(struct packfile_store *stor return store->packs.head; } -/* - * 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) +int packfile_store_count_objects(struct packfile_store *store, + enum odb_count_objects_flags flags UNUSED, + unsigned long *out) { - if (!r->objects->approximate_object_count_valid) { - struct odb_source *source; - unsigned long count = 0; - struct packed_git *p; + struct packfile_list_entry *e; + struct multi_pack_index *m; + unsigned long count = 0; + int ret; - odb_prepare_alternates(r->objects); + m = get_multi_pack_index(store->source); + if (m) + count += m->num_objects + m->num_objects_in_base; - 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 + m->num_objects_in_base; + for (e = packfile_store_get_packs(store); e; e = e->next) { + if (e->pack->multi_pack_index) + continue; + if (open_pack_index(e->pack)) { + ret = -1; + goto out; } - repo_for_each_pack(r, p) { - if (p->multi_pack_index || 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, @@ -1247,8 +1249,10 @@ const struct packed_git *has_packed_and_bad(struct repository *r, struct odb_source *source; 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 = source->packfiles->packs.head; e; e = e->next) + + for (e = files->packed->packs.head; e; e = e->next) if (oidset_contains(&e->pack->bad_objects, oid)) return e->pack; } @@ -1578,13 +1582,14 @@ 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 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 = OBJ_NONE; + uint32_t pack_pos; int ret; /* @@ -1619,16 +1624,35 @@ int packed_object_info(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); ret = -1; goto out; } + } + + if (oi->disk_sizep) + *oi->disk_sizep = pack_pos_to_offset(p, pack_pos + 1) - obj_offset; - *oi->disk_sizep = pack_pos_to_offset(p, 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) { @@ -1681,6 +1705,12 @@ out: 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, struct pack_window **w_curs, off_t curpos, @@ -2149,11 +2179,19 @@ int packfile_store_freshen_object(struct packfile_store *store, int packfile_store_read_object_info(struct packfile_store *store, const struct object_id *oid, struct object_info *oi, - unsigned flags UNUSED) + enum object_info_flags flags) { struct pack_entry e; int ret; + /* + * In case the first read didn't surface the object, we have to reload + * packfiles. This may cause us to discover new packfiles that have + * been added since the last time we have prepared the packfile store. + */ + if (flags & OBJECT_INFO_SECOND_READ) + packfile_store_reprepare(store); + if (!find_pack_entry(store, oid, &e)) return 1; @@ -2206,7 +2244,8 @@ struct packed_git **packfile_store_get_kept_pack_cache(struct packfile_store *st 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 && (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; } @@ -2228,7 +2267,8 @@ int has_object_pack(struct repository *r, const struct object_id *oid) odb_prepare_alternates(r->objects); for (source = r->objects->sources; source; source = source->next) { - int ret = find_pack_entry(source->packfiles, oid, &e); + struct odb_source_files *files = odb_source_files_downcast(source); + int ret = find_pack_entry(files->packed, oid, &e); if (ret) return ret; } @@ -2243,9 +2283,10 @@ int has_object_kept_pack(struct repository *r, const struct object_id *oid, struct pack_entry 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(source->packfiles, flags); + cache = packfile_store_get_kept_pack_cache(files->packed, flags); for (; *cache; cache++) { struct packed_git *p = *cache; @@ -2259,12 +2300,12 @@ int has_object_kept_pack(struct repository *r, const struct object_id *oid, 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; } @@ -2285,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; @@ -2301,75 +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 packfile_store_for_each_object_wrapper_data { + struct packfile_store *store; + const struct object_info *request; + odb_for_each_object_cb cb; + void *cb_data; +}; + +static int packfile_store_for_each_object_wrapper(const struct object_id *oid, + struct packed_git *pack, + uint32_t index_pos, + void *cb_data) { - struct odb_source *source; - int r = 0; - int pack_errors = 0; + struct packfile_store_for_each_object_wrapper_data *data = cb_data; - odb_prepare_alternates(repo->objects); + if (data->request) { + off_t offset = nth_packed_object_offset(pack, index_pos); + struct object_info oi = *data->request; - for (source = repo->objects->sources; source; source = source->next) { - struct packfile_list_entry *e; + if (packed_object_info_with_index_pos(pack, offset, + &index_pos, &oi) < 0) { + mark_bad_packed_object(pack, oid); + return -1; + } - source->packfiles->skip_mru_updates = true; + return data->cb(oid, &oi, data->cb_data); + } else { + return data->cb(oid, NULL, data->cb_data); + } +} - for (e = packfile_store_get_packs(source->packfiles); e; e = e->next) { - struct packed_git *p = e->pack; +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; +} - if ((flags & FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local) - continue; - if ((flags & FOR_EACH_OBJECT_PROMISOR_ONLY) && - !p->pack_promisor) - continue; - if ((flags & FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) && - p->pack_keep_in_core) - continue; - if ((flags & FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) && - p->pack_keep) - continue; - if (open_pack_index(p)) { - pack_errors = 1; - continue; - } +static int for_each_prefixed_object_in_midx( + struct packfile_store *store, + struct multi_pack_index *m, + const struct odb_for_each_object_options *opts, + struct packfile_store_for_each_object_wrapper_data *data) +{ + int ret; + + for (; m; m = m->base_midx) { + uint32_t num, i, first = 0; + int len = opts->prefix_hex_len > m->source->odb->repo->hash_algo->hexsz ? + m->source->odb->repo->hash_algo->hexsz : opts->prefix_hex_len; + + if (!m->num_objects) + continue; + + num = m->num_objects + m->num_objects_in_base; - r = for_each_object_in_pack(p, cb, data, flags); - if (r) + 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; + } } + } - source->packfiles->skip_mru_updates = false; + ret = 0; - if (r) +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; } - return r ? r : pack_errors; + ret = 0; + +out: + store->skip_mru_updates = false; + if (!ret && pack_errors) + ret = -1; + return ret; } +int packfile_store_for_each_object(struct packfile_store *store, + const struct object_info *request, + odb_for_each_object_cb cb, + void *cb_data, + const struct odb_for_each_object_options *opts) +{ + struct packfile_store_for_each_object_wrapper_data data = { + .store = store, + .request = request, + .cb = cb, + .cb_data = cb_data, + }; + struct packfile_list_entry *e; + int pack_errors = 0, ret; + + if (opts->prefix) + return packfile_store_for_each_prefixed_object(store, opts, &data); + + store->skip_mru_updates = true; + + for (e = packfile_store_get_packs(store); e; e = e->next) { + struct packed_git *p = e->pack; + + if ((opts->flags & ODB_FOR_EACH_OBJECT_LOCAL_ONLY) && !p->pack_local) + continue; + if ((opts->flags & ODB_FOR_EACH_OBJECT_PROMISOR_ONLY) && + !p->pack_promisor) + continue; + if ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS) && + p->pack_keep_in_core) + continue; + if ((opts->flags & ODB_FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS) && + p->pack_keep) + continue; + if (open_pack_index(p)) { + pack_errors = 1; + continue; + } + + ret = for_each_object_in_pack(p, packfile_store_for_each_object_wrapper, + &data, opts->flags); + if (ret) + goto out; + } + + ret = 0; + +out: + store->skip_mru_updates = false; + + if (!ret && pack_errors) + ret = -1; + return ret; +} + +static int extend_abbrev_len(const struct object_id *a, + const struct object_id *b, + unsigned *out) +{ + unsigned len = oid_common_prefix_hexlen(a, b); + if (len != hash_algos[a->algo].hexsz && len >= *out) + *out = len + 1; + return 0; +} + +static void find_abbrev_len_for_midx(struct multi_pack_index *m, + const struct object_id *oid, + unsigned min_len, + unsigned *out) +{ + unsigned len = min_len; + + for (; m; m = m->base_midx) { + int match = 0; + uint32_t num, first = 0; + struct object_id found_oid; + + if (!m->num_objects) + continue; + + num = m->num_objects + m->num_objects_in_base; + match = bsearch_one_midx(oid, m, &first); + + /* + * first is now the position in the packfile where we + * would insert the object ID if it does not exist (or the + * position of the object ID if it does exist). Hence, we + * consider a maximum of two objects nearby for the + * abbreviation length. + */ + + if (!match) { + if (nth_midxed_object_oid(&found_oid, m, first)) + extend_abbrev_len(&found_oid, oid, &len); + } else if (first < num - 1) { + if (nth_midxed_object_oid(&found_oid, m, first + 1)) + extend_abbrev_len(&found_oid, oid, &len); + } + if (first > 0) { + if (nth_midxed_object_oid(&found_oid, m, first - 1)) + extend_abbrev_len(&found_oid, oid, &len); + } + } + + *out = len; +} + +static void find_abbrev_len_for_pack(struct packed_git *p, + const struct object_id *oid, + unsigned min_len, + unsigned *out) +{ + int match; + uint32_t num, first = 0; + struct object_id found_oid; + unsigned len = min_len; + + num = p->num_objects; + match = bsearch_pack(oid, p, &first); + + /* + * first is now the position in the packfile where we would insert + * the object ID if it does not exist (or the position of mad->hash if + * it does exist). Hence, we consider a maximum of two objects + * nearby for the abbreviation length. + */ + if (!match) { + if (!nth_packed_object_id(&found_oid, p, first)) + extend_abbrev_len(&found_oid, oid, &len); + } else if (first < num - 1) { + if (!nth_packed_object_id(&found_oid, p, first + 1)) + extend_abbrev_len(&found_oid, oid, &len); + } + if (first > 0) { + if (!nth_packed_object_id(&found_oid, p, first - 1)) + extend_abbrev_len(&found_oid, oid, &len); + } + + *out = len; +} + +int packfile_store_find_abbrev_len(struct packfile_store *store, + const struct object_id *oid, + unsigned min_len, + unsigned *out) +{ + struct packfile_list_entry *e; + struct multi_pack_index *m; + + m = get_multi_pack_index(store->source); + if (m) + find_abbrev_len_for_midx(m, oid, min_len, &min_len); + + for (e = packfile_store_get_packs(store); e; e = e->next) { + if (e->pack->multi_pack_index) + continue; + if (open_pack_index(e->pack) || !e->pack->num_objects) + continue; + + find_abbrev_len_for_pack(e->pack, oid, min_len, &min_len); + } + + *out = min_len; + return 0; +} + +struct add_promisor_object_data { + struct repository *repo; + struct oidset *set; +}; + static int add_promisor_object(const struct object_id *oid, - struct packed_git *pack, - uint32_t pos UNUSED, - void *set_) + struct object_info *oi UNUSED, + void *cb_data) { - struct oidset *set = set_; + 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_with_flags(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 @@ -2387,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; } @@ -2411,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; } @@ -2553,32 +2921,28 @@ static int close_istream_pack_non_delta(struct odb_read_stream *_st) return 0; } -int packfile_store_read_object_stream(struct odb_read_stream **out, - struct packfile_store *store, - const struct object_id *oid) +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; - struct object_info oi = OBJECT_INFO_INIT; enum object_type in_pack_type; unsigned long size; - oi.sizep = &size; + in_pack_type = unpack_object_header(pack, &window, &offset, &size); + unuse_pack(&window); - if (packfile_store_read_object_info(store, oid, &oi, 0) || - oi.u.packed.type == PACKED_OBJECT_TYPE_REF_DELTA || - oi.u.packed.type == PACKED_OBJECT_TYPE_OFS_DELTA || - repo_settings_get_big_file_threshold(store->source->odb->repo) >= size) + if (repo_settings_get_big_file_threshold(pack->repo) >= size) return -1; - in_pack_type = unpack_object_header(oi.u.packed.pack, - &window, - &oi.u.packed.offset, - &size); - unuse_pack(&window); 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: @@ -2592,10 +2956,22 @@ int packfile_store_read_object_stream(struct odb_read_stream **out, stream->base.type = in_pack_type; stream->base.size = size; stream->z_state = ODB_PACKED_READ_STREAM_UNINITIALIZED; - stream->pack = oi.u.packed.pack; - stream->pos = oi.u.packed.offset; + 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 acc5c55ad5..6e8802e2ed 100644 --- a/packfile.h +++ b/packfile.h @@ -4,6 +4,7 @@ #include "list.h" #include "object.h" #include "odb.h" +#include "odb/source-files.h" #include "oidset.h" #include "repository.h" #include "strmap.h" @@ -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, @@ -192,7 +194,8 @@ static inline struct repo_for_each_pack_data repo_for_eack_pack_data_init(struct odb_prepare_alternates(repo->objects); for (struct odb_source *source = repo->objects->sources; source; source = source->next) { - struct packfile_list_entry *entry = packfile_store_get_packs(source->packfiles); + 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; @@ -212,7 +215,8 @@ static inline void repo_for_each_pack_data_next(struct repo_for_each_pack_data * return; for (source = data->source->next; source; source = source->next) { - struct packfile_list_entry *entry = packfile_store_get_packs(source->packfiles); + 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; @@ -247,7 +251,7 @@ int packfile_store_read_object_stream(struct odb_read_stream **out, int packfile_store_read_object_info(struct packfile_store *store, const struct object_id *oid, struct object_info *oi, - unsigned flags); + enum object_info_flags flags); /* * Open the packfile and add it to the store if it isn't yet known. Returns @@ -263,9 +267,20 @@ int packfile_store_freshen_object(struct packfile_store *store, 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. @@ -339,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 @@ -349,12 +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); - void pack_report(struct repository *repo); /* @@ -436,6 +463,11 @@ 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 */ diff --git a/parse-options.c b/parse-options.c index c9cafc21b9..a676da86f5 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 a5683b462c..1fbc88cbec 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 364e4cfa19..2aa3e7d8a4 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) @@ -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; @@ -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; } @@ -742,18 +740,18 @@ 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; @@ -1112,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; @@ -1129,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; @@ -1150,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]) { @@ -1161,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; } } @@ -1182,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. */ @@ -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); /* diff --git a/pkt-line.c b/pkt-line.c index fc583feb26..3fc3e9ea70 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; @@ -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 77ebf537e2..96fa215b06 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"); +} + +static struct string_list *fields_stored(void) +{ + static struct string_list fields_list = STRING_LIST_INIT_NODUP; + static int initialized; - return &fields_list; + 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 263d331a55..3d4d2de018 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 ecdd0dc58d..35072ed60b 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/range-diff.c b/range-diff.c index 57edff40a8..2712a9a107 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 4b532039d5..101cfc2727 100644 --- a/reachable.c +++ b/reachable.c @@ -191,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) { - struct object *obj; - enum object_type type; + if (data->ignore_in_core_kept_packs && + has_object_kept_pack(data->revs->repo, oid, KEPT_PACK_IN_CORE)) + return 0; + return 1; +} - if (!obj_is_recent(oid, mtime, data)) - return; +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; - /* - * 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)); + if (!want_recent_object(data, oid) || + !obj_is_recent(oid, *oi->mtimep, data)) + return 0; - switch (type) { + switch (*oi->typep) { case OBJ_TAG: case OBJ_COMMIT: obj = parse_object_or_die(the_repository, oid, NULL); @@ -227,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)); - - 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, KEPT_PACK_IN_CORE)) + if (obj->flags & SEEN) 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)); + add_pending_object(data->revs, obj, ""); + 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; } @@ -307,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; @@ -318,16 +266,13 @@ 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); - if (r) - goto done; - - flags = FOR_EACH_OBJECT_LOCAL_ONLY | FOR_EACH_OBJECT_PACK_ORDER; + flags = ODB_FOR_EACH_OBJECT_LOCAL_ONLY | ODB_FOR_EACH_OBJECT_PACK_ORDER; if (ignore_in_core_kept_packs) - flags |= FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS; + flags |= ODB_FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS; - r = for_each_packed_object(revs->repo, add_recent_packed, &data, flags); + r = odb_for_each_object(revs->repo->objects, &oi, add_recent_object, &data, flags); + if (r) + goto done; done: oidset_clear(&data.extra_recent_oids); diff --git a/read-cache-ll.h b/read-cache-ll.h index 71b49d9af4..2c8b4b21b1 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 0c07c3aef7..b1074fbf06 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 */ @@ -2306,13 +2309,9 @@ int do_read_index(struct index_state *istate, const char *path, int must_exist) } munmap((void *)mmap, mmap_size); - /* - * TODO trace2: replace "the_repository" with the actual repo instance - * that is associated with the given "istate". - */ - trace2_data_intmax("index", the_repository, "read/version", + trace2_data_intmax("index", istate->repo, "read/version", istate->version); - trace2_data_intmax("index", the_repository, "read/cache_nr", + trace2_data_intmax("index", istate->repo, "read/cache_nr", istate->cache_nr); /* @@ -2357,16 +2356,12 @@ int read_index_from(struct index_state *istate, const char *path, if (istate->initialized) return istate->cache_nr; - /* - * TODO trace2: replace "the_repository" with the actual repo instance - * that is associated with the given "istate". - */ - trace2_region_enter_printf("index", "do_read_index", the_repository, + trace2_region_enter_printf("index", "do_read_index", istate->repo, "%s", path); trace_performance_enter(); ret = do_read_index(istate, path, 0); trace_performance_leave("read cache %s", path); - trace2_region_leave_printf("index", "do_read_index", the_repository, + trace2_region_leave_printf("index", "do_read_index", istate->repo, "%s", path); split_index = istate->split_index; @@ -3093,13 +3088,9 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, istate->timestamp.nsec = ST_MTIME_NSEC(st); trace_performance_since(start, "write index, changed mask = %x", istate->cache_changed); - /* - * TODO trace2: replace "the_repository" with the actual repo instance - * that is associated with the given "istate". - */ - trace2_data_intmax("index", the_repository, "write/version", + trace2_data_intmax("index", istate->repo, "write/version", istate->version); - trace2_data_intmax("index", the_repository, "write/cache_nr", + trace2_data_intmax("index", istate->repo, "write/cache_nr", istate->cache_nr); ret = 0; @@ -3141,14 +3132,10 @@ static int do_write_locked_index(struct index_state *istate, return ret; } - /* - * TODO trace2: replace "the_repository" with the actual repo instance - * that is associated with the given "istate". - */ - trace2_region_enter_printf("index", "do_write_index", the_repository, + trace2_region_enter_printf("index", "do_write_index", istate->repo, "%s", get_lock_file_path(lock)); ret = do_write_index(istate, lock->tempfile, write_extensions, flags); - trace2_region_leave_printf("index", "do_write_index", the_repository, + trace2_region_leave_printf("index", "do_write_index", istate->repo, "%s", get_lock_file_path(lock)); if (was_full) @@ -3889,9 +3876,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, @@ -3915,8 +3905,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; @@ -3926,7 +3976,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)) { @@ -3934,6 +3984,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")); @@ -3954,7 +4009,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; @@ -3964,6 +4019,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 c318f9ca0e..1da4c0e60d 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -1753,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); @@ -2173,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; + + for (const char *p = refname; *p; p++) { + if (*p == '/') + slashes++; + } - /* 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; + 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--; @@ -2206,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) @@ -2781,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; @@ -2804,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) { @@ -2836,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); } /* @@ -3303,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; @@ -3782,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/reflog-walk.c b/reflog-walk.c index 4f1ce04749..4dbeaa93a7 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; @@ -493,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); @@ -501,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, @@ -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 @@ -444,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; }; @@ -475,9 +477,11 @@ static int for_each_filter_refs(const struct reference *ref, void *data) if (wildmatch(filter->pattern, ref->name, 0)) return 0; - if (filter->prefix) { + if (filter->trim_prefix) { struct reference skipped = *ref; - skip_prefix(skipped.name, filter->prefix, &skipped.name); + 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); @@ -524,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; @@ -590,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) || @@ -760,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 = { @@ -1267,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]); } @@ -1281,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"); @@ -1307,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); @@ -1785,7 +1772,7 @@ 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; @@ -1809,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; @@ -1831,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; } } @@ -1855,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) +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 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; if (!refs) - return 0; + BUG("no ref store passed"); - iter = refs_ref_iterator_begin(refs, prefix, exclude_patterns, trim, - flags); + if (opts->trim_prefix) { + size_t prefix_len; - return do_for_each_ref_iterator(iter, fn, cb_data); -} + if (!opts->prefix) + BUG("trimming only allowed with a prefix"); -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); -} + 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"); + } -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); -} + 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); -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); -} + 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, '*'); + } -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); -} + filter.pattern = real_pattern.buf; + filter.trim_prefix = opts->trim_prefix; + filter.fn = cb; + filter.cb_data = cb_data; -int refs_for_each_namespaced_ref(struct ref_store *refs, - const char **exclude_patterns, - each_ref_fn fn, void *cb_data) -{ - struct strvec namespaced_exclude_patterns = STRVEC_INIT; - struct strbuf prefix = STRBUF_INIT; - int ret; + /* + * We need to trim the prefix in the callback function as the + * pattern is expected to match on the full refname. + */ + trim_prefix = 0; - exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns, - get_git_namespace(), - &namespaced_exclude_patterns); + cb = for_each_filter_refs; + cb_data = &filter; + } - strbuf_addf(&prefix, "%srefs/", get_git_namespace()); - ret = do_for_each_ref(refs, prefix.buf, exclude_patterns, fn, 0, 0, cb_data); + 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) -{ - return refs_for_each_rawref_in(refs, "", fn, cb_data); -} - -int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix, - 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 do_for_each_ref(refs, prefix, NULL, fn, 0, - DO_FOR_EACH_INCLUDE_BROKEN, 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_include_root_refs(struct ref_store *refs, 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, "", 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) @@ -1994,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; } @@ -2163,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, @@ -2224,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; } @@ -2465,68 +2545,87 @@ static int ref_update_reject_duplicates(struct string_list *refnames, return 0; } -static int run_transaction_hook(struct ref_transaction *transaction, - const char *state) +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 child_process proc = CHILD_PROCESS_INIT; - struct strbuf buf = STRBUF_INIT; - const char *hook; - int ret = 0; + 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; - hook = find_hook(transaction->ref_store->repo, "reference-transaction"); - if (!hook) - return ret; + if (i >= transaction->nr) + return 1; /* No more refs to process */ - strvec_pushl(&proc.args, hook, state, NULL); - proc.in = -1; - proc.stdout_to_stderr = 1; - proc.trace2_hook_name = "reference-transaction"; + update = transaction->updates[i]; - ret = start_command(&proc); - if (ret) - return ret; + if (update->flags & REF_LOG_ONLY) + return 0; - sigchain_push(SIGPIPE, SIG_IGN); + strbuf_reset(buf); - for (size_t i = 0; i < transaction->nr; i++) { - struct ref_update *update = transaction->updates[i]; + 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_LOG_ONLY) - continue; + 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_reset(&buf); + strbuf_addf(buf, "%s\n", update->refname); - 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)); + ret = write_in_full(hook_stdin_fd, buf->buf, buf->len); + if (ret < 0 && errno != EPIPE) + return ret; - 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)); + return 0; /* no more input to feed */ +} - strbuf_addf(&buf, "%s\n", update->refname); +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; +} - 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; - } - } +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); +} - close(proc.in); - sigchain_pop(SIGPIPE); - strbuf_release(&buf); +static int run_transaction_hook(struct ref_transaction *transaction, + const char *state) +{ + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + int ret = 0; + + strvec_push(&opt.args, state); + + 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 = run_hooks_opt(transaction->ref_store->repo, "reference-transaction", &opt); - ret |= finish_command(&proc); return ret; } @@ -2561,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; @@ -2568,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; @@ -2698,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))) { + &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)) { + REF_TRANSACTION_ERROR_NAME_CONFLICT, err)) { strset_remove(&dirnames, dirname.buf); strset_add(&conflicting_dirnames, dirname.buf); - continue; + goto next_ref; } - strbuf_addf(err, _("'%s' exists; cannot create '%s'"), - dirname.buf, refname); goto cleanup; } if (extras && string_list_has_string(extras, dirname.buf)) { + 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)) { + REF_TRANSACTION_ERROR_NAME_CONFLICT, err)) { strset_remove(&dirnames, dirname.buf); - continue; + goto next_ref; } - strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"), - refname, dirname.buf); goto cleanup; } } @@ -2742,7 +2851,7 @@ 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; @@ -2751,14 +2860,14 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs if (skip && 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->ref.name, refname); goto cleanup; } @@ -2768,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) { + 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)) - continue; + REF_TRANSACTION_ERROR_NAME_CONFLICT, err)) + goto next_ref; - strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"), - refname, extra_refname); goto cleanup; } +next_ref:; } ret = 0; @@ -2905,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); } } @@ -3186,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 = { @@ -3269,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. * @@ -3279,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; @@ -3402,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); +} @@ -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,7 +330,7 @@ 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: */ @@ -400,7 +401,44 @@ int reference_get_peeled_oid(struct repository *repo, * argument is only guaranteed to be valid for the duration of a * single callback invocation. */ -typedef int each_ref_fn(const struct reference *ref, 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 @@ -412,70 +450,75 @@ typedef int each_ref_fn(const struct reference *ref, void *cb_data); * 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); -/** - * iterate all refs in "patterns" by partitioning patterns 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); +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; -/* 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); + /* + * 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; -int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn, - const char *pattern, const char *prefix, void *cb_data); + /* + * 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; -int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_data); + /* + * 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; -/* - * 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); + /* Flags that change which refs will be included. */ + enum refs_for_each_flag flags; +}; -/* 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); +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); -/* - * Iterates over all refs including root refs, i.e. pseudorefs and HEAD. +/** + * Iterate all refs in "prefixes" by partitioning prefixes into disjoint sets + * and iterating the longest-common prefix of each set. */ -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. @@ -993,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, @@ -1331,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. @@ -1376,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. @@ -1425,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/files-backend.c b/refs/files-backend.c index 240d3c3b26..0537a72b2a 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; + + refs_compute_filesystem_location(gitdir, payload, &is_worktree, &refdir, + &ref_common_dir); - base_ref_store_init(ref_store, repo, gitdir, &refs_be_files); + 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; } @@ -439,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); /* @@ -955,17 +962,17 @@ 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 && + 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) && + 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) && + if (!(iter->flags & REFS_FOR_EACH_INCLUDE_BROKEN) && !ref_resolves_to_object(iter->iter0->ref.name, iter->repo, iter->iter0->ref.oid, @@ -1012,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"); @@ -1050,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); @@ -1806,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 @@ -2978,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; @@ -3150,6 +3156,9 @@ 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; @@ -3174,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, @@ -3700,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); diff --git a/refs/iterator.c b/refs/iterator.c index d79aa5ec82..d5cacde51b 100644 --- a/refs/iterator.c +++ b/refs/iterator.c @@ -423,7 +423,7 @@ struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0, } 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; diff --git a/refs/packed-backend.c b/refs/packed-backend.c index 4ea0c12299..23ed62984b 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) { @@ -982,11 +987,11 @@ static int packed_ref_iterator_advance(struct ref_iterator *ref_iterator) const char *refname = iter->base.ref.name; const char *prefix = iter->prefix; - if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY && + 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) && + if (!(iter->flags & REFS_FOR_EACH_INCLUDE_BROKEN) && !ref_resolves_to_object(iter->base.ref.name, iter->repo, &iter->oid, iter->flags)) continue; @@ -1159,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"); @@ -1401,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; @@ -1437,8 +1442,8 @@ 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; } @@ -1452,8 +1457,8 @@ static enum ref_transaction_error write_with_updates(struct packed_ref_store *re 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; } @@ -1496,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; } diff --git a/refs/packed-backend.h b/refs/packed-backend.h index 9481d5e7c2..2c2377a356 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/refs-internal.h b/refs/refs-internal.h index c7d2a6e50b..d79e35fd26 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 @@ -389,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); /* @@ -666,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 fe74af73af..b124404663 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -372,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); @@ -419,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, @@ -443,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; @@ -456,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; } @@ -491,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; } @@ -529,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; @@ -662,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; } @@ -676,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; @@ -714,12 +681,12 @@ 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; @@ -871,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"); @@ -1418,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; @@ -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/remote-curl.c b/remote-curl.c index 69f919454a..aba60d5712 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; @@ -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); } @@ -1497,7 +1504,7 @@ static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***ds clear_commit_marks_many(src_commits.nr, src_commits.items, reachable_flag); commit_stack_clear(&src_commits); - free_commit_list(found_commits); + commit_list_free(found_commits); } string_list_clear(&src_tag, 0); @@ -1831,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; @@ -1869,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; @@ -1883,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; @@ -1899,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) @@ -1924,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) @@ -1937,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; } } @@ -2230,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) { @@ -2274,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) { @@ -2285,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 { @@ -2298,14 +2314,106 @@ 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; +} + +/* + * 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) @@ -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-midx.c b/repack-midx.c index 74bdfa3a6e..0682b80c42 100644 --- a/repack-midx.c +++ b/repack-midx.c @@ -40,7 +40,6 @@ static int midx_snapshot_ref_one(const struct reference *ref, void *_data) 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; @@ -51,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 73af57bce3..90318ce150 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; @@ -98,8 +98,8 @@ void repack_promisor_objects(struct repository *repo, */ ctx.cmd = &cmd; ctx.algop = repo->hash_algo; - for_each_packed_object(repo, write_oid, &ctx, - FOR_EACH_OBJECT_PROMISOR_ONLY); + 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 */ diff --git a/replay.c b/replay.c new file mode 100644 index 0000000000..cf1f0bcba0 --- /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 0000000000..e916a5f975 --- /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/repository.c b/repository.c index c7e75215ac..9e5537f539 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,13 +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) { + 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 @@ -178,24 +195,32 @@ void repo_set_gitdir(struct repository *repo, 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); } /* @@ -277,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; @@ -369,6 +396,7 @@ 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_free(repo->objects); repo->objects = NULL; @@ -393,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 6063c4b846..078059a6e0 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; @@ -148,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; @@ -157,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; @@ -165,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 @@ -201,10 +220,11 @@ struct set_gitdir_args { 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); @@ -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/revision.c b/revision.c index 9b131670f7..fda405bf65 100644 --- a/revision.c +++ b/revision.c @@ -1048,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); @@ -1083,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 */ @@ -1150,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) @@ -1204,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) @@ -1405,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); } /* @@ -1508,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; } @@ -1646,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; @@ -1847,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); @@ -2011,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) @@ -2037,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"; @@ -2080,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 */ @@ -2102,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; @@ -2131,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); @@ -2150,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; @@ -2177,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) { @@ -2202,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; @@ -2239,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; } @@ -2377,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")) { @@ -2728,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); } @@ -2814,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))) { @@ -2827,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); @@ -3147,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) @@ -3221,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); @@ -3335,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; } @@ -3626,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; @@ -3936,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); @@ -4125,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) { @@ -4224,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; } @@ -4232,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) @@ -4293,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) @@ -4382,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; /* @@ -4504,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; @@ -4522,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 b36acfc2d9..584f1338b5 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 e3e02475cc..32c290ee6a 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 @@ fail_pipe: fflush(NULL); - if (cmd->close_object_store) - odb_close(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) +{ + /* 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) { - while (poll(pp->pfd, opts->processes, output_timeout) < 0) { + /* 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 0df25e445f..8ca496d7bd 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); @@ -393,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")); diff --git a/send-pack.c b/send-pack.c index 67d6987b1c..07ecfae4de 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 1f492f8460..b7d8dca47f 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); } @@ -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; + strbuf_addf(&sb, "%s %s\n", oid_to_hex(oldoid), oid_to_hex(newoid)); - strvec_pushl(&proc.args, hook_path, "amend", NULL); - proc.in = -1; - proc.stdout_to_stderr = 1; - proc.trace2_hook_name = "post-rewrite"; + 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); - 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); 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; } @@ -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 @@ out: 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 719684c8a9..a6fa670c7c 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 */ @@ -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) @@ -25,7 +25,6 @@ #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; @@ -632,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. */ @@ -674,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); @@ -686,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; } @@ -850,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); } @@ -895,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: @@ -939,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)) { @@ -1576,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. @@ -1815,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; /* @@ -1942,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; @@ -1971,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); @@ -2042,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 = @@ -2303,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 @@ -2312,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) @@ -2321,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; @@ -2341,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); @@ -2359,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); @@ -2600,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); @@ -2645,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, @@ -2701,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)) { @@ -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) @@ -167,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; @@ -240,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!! @@ -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 @@ -36,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); diff --git a/sideband.c b/sideband.c index ea7c25211e..1ed6614eaf 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 76f90da5f5..13629c075d 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 4c74c4adda..6ba210738c 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 38728a3711..75f3cd1265 100755 --- a/src/cargo-meson.sh +++ b/src/cargo-meson.sh @@ -19,20 +19,25 @@ do esac done -cargo build --lib --quiet --manifest-path="$SOURCE_DIR/Cargo.toml" --target-dir="$BUILD_DIR" "$@" -RET=$? -if test $RET -ne 0 -then - exit $RET -fi - 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 +then + exit $RET +fi + 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 0000000000..7f2c6c4fcb --- /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 0000000000..dea2998de4 --- /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 9da70d8b57..0c598298b1 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 0000000000..24accf9c33 --- /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 25b9ad5a14..45739957b4 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', ] @@ -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; } @@ -1119,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/string-list.c b/string-list.c index 08dc00984c..d260b873c8 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 fa6ba07853..b86ee7c099 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 1f19fe2077..72a46b7a54 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 40a5c6fb9d..b1a0363f9d 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) @@ -1706,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); @@ -1719,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); @@ -1813,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", @@ -1840,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 */ @@ -2158,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); @@ -2250,12 +2259,155 @@ out: 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'", @@ -2291,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); @@ -2349,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); @@ -2413,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; @@ -2578,26 +2735,37 @@ cleanup: 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/git-gui b/subprojects/git-gui new file mode 120000 index 0000000000..c6d917088b --- /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 0000000000..b66ad18ae5 --- /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 9cc090d42c..9e01ab3bc8 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 7ae3d5b856..25bf04f54f 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 6ee7700eb4..18d944b810 100755 --- a/t/check-non-portable-shell.pl +++ b/t/check-non-portable-shell.pl @@ -36,7 +36,7 @@ while (<>) { $_ = $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 4593be5fd5..bd2d45c971 100644 --- a/t/for-each-ref-tests.sh +++ b/t/for-each-ref-tests.sh @@ -1744,6 +1744,15 @@ test_expect_success ':remotename and :remoteref' ' ) ' +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 && diff --git a/t/helper/test-example-tap.c b/t/helper/test-example-tap.c index 229d495ecf..998a1f0b42 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 51b67f2f87..d681961abb 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-path-utils.c b/t/helper/test-path-utils.c index f5f33751da..874542ec34 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 feabeb29c2..3131b54a87 100644 --- a/t/helper/test-reach.c +++ b/t/helper/test-reach.c @@ -120,12 +120,12 @@ int cmd__reach(int ac, const char **av) 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")) { @@ -172,13 +172,13 @@ 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); + 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 6de5d1665a..388d29e2b5 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 b1215947c5..74edf2029a 100644 --- a/t/helper/test-ref-store.c +++ b/t/helper/test-ref-store.c @@ -163,17 +163,22 @@ static int each_ref(const struct reference *ref, void *cb_data UNUSED) 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-run-command.c b/t/helper/test-run-command.c index 3719f23cc2..4a56456894 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-trace2.c b/t/helper/test-trace2.c index 415df078c1..3b12f4173e 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-httpd.sh b/t/lib-httpd.sh index 5091db949b..4c76e813e3 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -167,6 +167,7 @@ prepare_httpd() { 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 @@ setup_askpass_helper() { ' } -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 e631ab0eb5..40a690b0bb 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -139,6 +139,10 @@ SetEnv PERL_PATH ${PERL_PATH} 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 /broken_smart/ broken-smart-http.sh/ 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 @@ ScriptAliasMatch /custom_auth/(.*) nph-custom-auth.sh/$1 <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 @@ SSLEngine On 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 0000000000..c97b16145b --- /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 d9c122f348..3bab7b6423 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 36f767cb74..f591de6120 100644 --- a/t/lib-submodule-update.sh +++ b/t/lib-submodule-update.sh @@ -95,14 +95,14 @@ create_lib_submodule_repo () { 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 @@ create_lib_submodule_repo () { 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 aed0a4dd44..201ab9b24c 100755 --- a/t/lib-unicode-nfc-nfd.sh +++ b/t/lib-unicode-nfc-nfd.sh @@ -75,7 +75,7 @@ test_lazy_prereq UNICODE_NFD_PRESERVED ' # # 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 0000000000..4e0cfdc605 --- /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 459c52a489..7528e5cda5 100644 --- a/t/meson.build +++ b/t/meson.build @@ -4,6 +4,7 @@ clar_test_suites = [ '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', @@ -80,6 +81,7 @@ integration_tests = [ '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', @@ -98,6 +100,7 @@ integration_tests = [ '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', @@ -132,6 +135,7 @@ integration_tests = [ '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', @@ -210,6 +214,7 @@ integration_tests = [ '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', @@ -239,7 +244,7 @@ integration_tests = [ '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', @@ -292,6 +297,7 @@ integration_tests = [ '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', @@ -387,6 +393,10 @@ integration_tests = [ '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', @@ -498,6 +508,8 @@ integration_tests = [ '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', @@ -614,6 +626,7 @@ integration_tests = [ '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', @@ -700,6 +713,7 @@ integration_tests = [ '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', @@ -887,6 +901,8 @@ integration_tests = [ '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', @@ -1206,6 +1222,7 @@ endif 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 81086c3690..d76b087b09 100644 --- a/t/pack-refs-tests.sh +++ b/t/pack-refs-tests.sh @@ -61,13 +61,13 @@ test_expect_success 'see if a branch still exists after git ${pack_refs} --prune 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 @@ test_expect_success 'test excluded refs are not packed' ' 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 @@ do # 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 @@ do 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 @@ do # 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,8 +416,8 @@ do # 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 ) diff --git a/t/perf/p3400-rebase.sh b/t/perf/p3400-rebase.sh index e6b0277729..56dc3e1d31 100755 --- a/t/perf/p3400-rebase.sh +++ b/t/perf/p3400-rebase.sh @@ -9,21 +9,44 @@ test_expect_success 'setup rebasing on top of a lot of changes' ' 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/t0008-ignores.sh b/t/t0008-ignores.sh index db8bde280e..e716b5cdfa 100755 --- a/t/t0008-ignores.sh +++ b/t/t0008-ignores.sh @@ -946,7 +946,7 @@ test_expect_success SYMLINKS 'symlinks respected in info/exclude' ' ' 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 SYMLINKS 'symlinks not respected in-tree' ' 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 0000000000..33d21ed9ea --- /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 d3a0967e9d..c33501bdcd 100755 --- a/t/t0012-help.sh +++ b/t/t0012-help.sh @@ -141,20 +141,23 @@ test_expect_success 'git help -c' ' '\''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-for-completion' ' 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 07a53e7366..68b4903cbf 100755 --- a/t/t0014-alias.sh +++ b/t/t0014-alias.sh @@ -112,4 +112,89 @@ test_expect_success 'cannot alias-shadow a sample of regular builtins' ' 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 0000000000..8ef87addf5 --- /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 76d4936a87..60cfe65979 100755 --- a/t/t0061-run-command.sh +++ b/t/t0061-run-command.sh @@ -164,6 +164,37 @@ test_expect_success 'run_command runs ungrouped in parallel with more tasks than 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 @@ test_expect_success MINGW 'can spawn .bat with argv[0] containing spaces' ' 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 f2f3e50031..80b163ea99 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 @@ test_expect_success 'run based on configured value' ' 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 @@ test_expect_success 'run based on configured value' ' 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 bad config keys' ' ' 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 'error on NULL value for config keys' ' ' 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_expect_success '--keep-going' ' 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 3db10f095c..66838a00b2 100755 --- a/t/t0080-unit-test-output.sh +++ b/t/t0080-unit-test-output.sh @@ -6,10 +6,10 @@ test_description='Test the output of the unit test framework' 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 @@ test_expect_success 'TAP output from unit tests' - <<\EOT 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 5a628bf735..26f017422d 100755 --- a/t/t0081-find-pack.sh +++ b/t/t0081-find-pack.sh @@ -68,6 +68,7 @@ test_expect_success 'add more packfiles' ' ' 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 96c68f65df..7e1e7af862 100755 --- a/t/t0210-trace2-normal.sh +++ b/t/t0210-trace2-normal.sh @@ -74,8 +74,9 @@ scrub_normal () { # 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 0000000000..a2b9536da8 --- /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 07aa834d33..64ead1571a 100755 --- a/t/t0300-credentials.sh +++ b/t/t0300-credentials.sh @@ -675,7 +675,9 @@ test_expect_success 'match percent-encoded values' ' 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/t0410-partial-clone.sh b/t/t0410-partial-clone.sh index 2a5bdbeeb8..52e19728a3 100755 --- a/t/t0410-partial-clone.sh +++ b/t/t0410-partial-clone.sh @@ -11,7 +11,10 @@ test_description='partial clone' 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/adoc-help-mismatches b/t/t0450/adoc-help-mismatches index 8ee2d3f7c8..e8d6c13ccd 100644 --- a/t/t0450/adoc-help-mismatches +++ b/t/t0450/adoc-help-mismatches @@ -33,7 +33,6 @@ merge merge-file merge-index merge-one-file -multi-pack-index name-rev notes push diff --git a/t/t0613-reftable-write-options.sh b/t/t0613-reftable-write-options.sh index e334751759..26b716c75f 100755 --- a/t/t0613-reftable-write-options.sh +++ b/t/t0613-reftable-write-options.sh @@ -68,8 +68,8 @@ test_expect_success 'many refs results in multiple blocks' ' ( 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 @@ test_expect_success 'restart interval at every single record' ' ( 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 @@ test_expect_success 'object index gets written by default with ref index' ' ( 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 @@ test_expect_success 'object index can be disabled' ' ( 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/t1006-cat-file.sh b/t/t1006-cat-file.sh index 0eee3bb878..8e2c52652c 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh @@ -241,10 +241,16 @@ hello_content="Hello World" 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 @@ run_blob_tests () { ' } -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 @@ test_expect_success '--batch-check without %(rest) considers whole line' ' ' 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 @@ flush" } 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 'object reference via commit text search' ' ' 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 && @@ -1236,7 +1261,10 @@ test_expect_success 'batch-check with a submodule' ' 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 0efce53f3a..92d48b96a1 100755 --- a/t/t1016-compatObjectFormat.sh +++ b/t/t1016-compatObjectFormat.sh @@ -8,6 +8,12 @@ test_description='Test how well compatObjectFormat works' . ./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 5be273611a..7d40d08521 100755 --- a/t/t1050-large.sh +++ b/t/t1050-large.sh @@ -104,9 +104,9 @@ test_expect_success 'packsize limit' ' # 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 b2da4feaef..cd0aed9975 100755 --- a/t/t1091-sparse-checkout-builtin.sh +++ b/t/t1091-sparse-checkout-builtin.sh @@ -817,6 +817,54 @@ test_expect_success 'cone mode clears ignored subdirectories' ' 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 b0f691c151..d98cb4ac11 100755 --- a/t/t1092-sparse-checkout-compatibility.sh +++ b/t/t1092-sparse-checkout-compatibility.sh @@ -2559,4 +2559,18 @@ test_expect_success 'cat-file --batch' ' 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 9850fcd5b5..128971ee12 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -2459,9 +2459,15 @@ done 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_expect_success 'unset type specifiers may be reset to conflicting ones' ' 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/t1400-update-ref.sh b/t/t1400-update-ref.sh index db7f5444da..b2858a9061 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -1380,16 +1380,16 @@ test_expect_success 'fails with duplicate ref update via symref' ' 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 @@ do 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 @@ do 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 @@ do 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 @@ do 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 @@ do 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 @@ do 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 @@ do 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 @@ do 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 @@ do 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 @@ do 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 @@ do 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 @@ do 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 @@ do 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 @@ do 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/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh index d91dd3a3b5..4fe9d9b234 100755 --- a/t/t1416-ref-transaction-hooks.sh +++ b/t/t1416-ref-transaction-hooks.sh @@ -20,6 +20,7 @@ test_expect_success 'hook allows updating ref if successful' ' echo "$*" >>actual EOF cat >expect <<-EOF && + preparing prepared committed EOF @@ -27,6 +28,18 @@ test_expect_success 'hook allows updating ref if successful' ' 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 @@ test_expect_success 'hook aborts updating ref in prepared state' ' 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 @@ test_expect_success 'interleaving hook calls succeed' ' 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 @@ test_expect_success 'hook captures git-symbolic-ref updates' ' 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 @@ test_expect_success 'hook gets all queued symref updates' ' # 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/t1423-ref-backend.sh b/t/t1423-ref-backend.sh new file mode 100755 index 0000000000..fd47d77e8e --- /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 3fae05f9d9..54e81c2636 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -852,6 +852,44 @@ test_expect_success 'fsck errors in packed objects' ' ! 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 trailing loose garbage (large blob)' ' 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 0e1116a319..5246468024 100755 --- a/t/t1460-refs-migrate.sh +++ b/t/t1460-refs-migrate.sh @@ -276,11 +276,11 @@ test_expect_success 'multiple reftable blocks with multiple entries' ' 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/t1500-rev-parse.sh b/t/t1500-rev-parse.sh index 7739ab611b..98c5a772bd 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' ' ' -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 4feaf0d7be..96749fc06d 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 @@ test_expect_success 'git hook run: basic' ' 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 @@ test_expect_success 'git hook run: stdout and stderr both write to our stderr' ' 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 @@ do 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 @@ test_expect_success 'git hook run -- pass arguments' ' 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 && + + 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 test_must_fail git hook run test-hook + 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_expect_success 'git -c core.hooksPath=<PATH> hook run' ' # 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_expect_success TTY 'git commit: stdout and stderr are connected to a TTY' ' 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_success 'git hook run a hook with a bad shebang' ' 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 'git hook run a hook with a bad shebang' ' ' 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 @@ test_expect_success 'stdin to hooks' ' 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 index 51d55f11a5..39bb77dda0 100755 --- a/t/t1900-repo.sh +++ b/t/t1900-repo-info.sh @@ -4,15 +4,6 @@ test_description='test git repo-info' . ./test-lib.sh -# git-repo-info keys. It must contain the same keys listed in the const -# repo_info_fields, in lexicographical order. -REPO_INFO_KEYS=' - layout.bare - layout.shallow - object.format - references.format -' - # Test whether a key-value pair is correctly returned # # Usage: test_repo_info <label> <init command> <repo_name> <key> <expected value> @@ -34,7 +25,7 @@ test_repo_info () { 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 @@ -115,12 +106,12 @@ test_expect_success '-z uses nul-terminated format' ' 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 returns all key-value pairs' ' - git repo info $REPO_INFO_KEYS >expect && +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 ' @@ -131,4 +122,37 @@ test_expect_success 'git repo info --all <key> aborts' ' 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 17ff164b05..10050abd70 100755 --- a/t/t1901-repo-structure.sh +++ b/t/t1901-repo-structure.sh @@ -27,31 +27,43 @@ test_expect_success 'empty repository' ' ( 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 | - | * 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 | + | 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 && @@ -79,31 +91,50 @@ test_expect_success SHA1 'repository with references and objects' ' # 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 | + | 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 && @@ -113,7 +144,7 @@ test_expect_success SHA1 'repository with references and objects' ' ) ' -test_expect_success SHA1 'keyvalue and nul format' ' +test_expect_success SHA1 'lines and nul format' ' test_when_finished "rm -rf repo" && git init repo && ( @@ -138,25 +169,37 @@ test_expect_success SHA1 'keyvalue and nul format' ' 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_nul out && + test_cmp expect actual && test_line_count = 0 err ) ' @@ -181,4 +224,10 @@ test_expect_success 'progress meter option' ' ) ' +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 f18616ad2b..af199d8191 100755 --- a/t/t2000-conflict-when-checking-files-out.sh +++ b/t/t2000-conflict-when-checking-files-out.sh @@ -35,30 +35,30 @@ show_files() { sed -e 's/^\([0-9]*\) [^ ]* [0-9a-f]* /tr: \1 /' } -date >path0 -mkdir path1 -date >path1/file1 - -test_expect_success \ - 'git update-index --add various paths.' \ - 'git update-index --add path0 path1/file1' - -rm -fr path0 path1 -mkdir path0 -date >path0/file0 -date >path1 +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 checkout-index without -f should fail on conflicting work tree.' \ - 'test_must_fail git checkout-index -a' +test_expect_success 'prepare working tree files with D/F conflicts' ' + rm -fr path0 path1 && + mkdir path0 && + date >path0/file0 && + date >path1 +' -test_expect_success \ - 'git checkout-index with -f should succeed.' \ - 'git checkout-index -f -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 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 @@ test_expect_success SYMLINKS 'checkout-index -f twice with --prefix' ' # 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 ff163cf675..19166adf0f 100755 --- a/t/t2003-checkout-cache-mkdir.sh +++ b/t/t2003-checkout-cache-mkdir.sh @@ -24,27 +24,28 @@ test_expect_success SYMLINKS 'have symlink in place where dir is expected.' ' 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 @@ test_expect_success 'use --prefix=tmp- but with a conflicting file and dir' ' 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 @@ test_expect_success SYMLINKS 'use --prefix=tmp/orary/ where tmp is a symlink' ' 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 @@ test_expect_success SYMLINKS 'use --prefix=tmp/orary- where tmp is a symlink' ' 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 @@ test_expect_success SYMLINKS 'use --prefix=tmp- where tmp-path1 is a symlink' ' 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 b92d96fdc4..0afe0ff7ca 100755 --- a/t/t2004-checkout-cache-temp.sh +++ b/t/t2004-checkout-cache-temp.sh @@ -42,7 +42,7 @@ test_expect_success 'checkout one stage 0 to temporary file' ' 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 @@ test_expect_success 'checkout all stage 0 to temporary files' ' 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_expect_success 'checkout one stage 2 to temporary file' ' 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 @@ test_expect_success 'checkout all stage 2 to temporary files' ' 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_expect_success 'checkout all stages/one file to temporary files' ' 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_expect_success 'checkout some stages/one file to temporary files' ' 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 '-- path0: no entry' ' 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_expect_success '-- path2: no stage 1, have stage 2 and 3' ' 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 '-- path2: no stage 1, have stage 2 and 3' ' 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 '-- path3: no stage 2, have stage 1 and 3' ' 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 '-- path4: no stage 3, have stage 1 and 3' ' 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_expect_success 'checkout --temp within subdir' ' 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_expect_success 'checkout --temp symlink' ' 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/t2027-checkout-track.sh b/t/t2027-checkout-track.sh index a397790df5..c01f1cd617 100755 --- a/t/t2027-checkout-track.sh +++ b/t/t2027-checkout-track.sh @@ -47,4 +47,22 @@ test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' ' 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 cc72ead79f..3bffe5da8a 100755 --- a/t/t2107-update-index-basic.sh +++ b/t/t2107-update-index-basic.sh @@ -86,7 +86,7 @@ test_expect_success '.lock files cleaned up' ' # 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 192ad14b5f..44c1936e4d 100755 --- a/t/t2203-add-intent.sh +++ b/t/t2203-add-intent.sh @@ -16,7 +16,8 @@ test_expect_success 'intent to add' ' ' 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' ' ' 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 @@ test_expect_success 'rename detection finds the right names' ' 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 @@ test_expect_success 'double rename detection in status' ' 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_expect_success 'double rename detection in status' ' 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 0000000000..e581e87ab2 --- /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 023e1301c8..58b4445cc4 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -987,7 +987,7 @@ test_dwim_orphan () { 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 @@ test_dwim_orphan () { 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/t3200-branch.sh b/t/t3200-branch.sh index c58e505c43..e7829c2c4b 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -1494,7 +1494,8 @@ test_expect_success 'refuse --edit-description on unborn branch for now' ' ' 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' ' diff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh index 597df5ebc0..0bb366fdb8 100755 --- a/t/t3310-notes-merge-manual-resolve.sh +++ b/t/t3310-notes-merge-manual-resolve.sh @@ -227,7 +227,8 @@ test_expect_success 'merge z into m (== y) with default ("manual") resolver => C # 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 'change notes in z' ' ' 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 @@ w notes on 1st commit 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 @@ EOF 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 @@ test_expect_success 'redo merge of z into m (== y) with default ("manual") resol # 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 @@ test_expect_success 'redo merge of z into m (== y) with default ("manual") resol # 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 @@ EOF 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 @@ test_expect_success 'redo merge of z into m (== y) with default ("manual") resol # 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 @@ cp expect_log_w expect_log_m 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 @@ EOF # 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 @@ test_expect_success 'resolve situation by aborting the notes merge' ' 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_expect_success 'switch cwd before committing notes merge' ' 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 e778dd8ae4..3e44562afa 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -31,6 +31,12 @@ Initial setup: . "$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 a1d7fa7f7c..bc51a9d3a7 100755 --- a/t/t3406-rebase-message.sh +++ b/t/t3406-rebase-message.sh @@ -8,6 +8,12 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./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 cc627e34a7..84b2d0e664 100755 --- a/t/t3430-rebase-merges.sh +++ b/t/t3430-rebase-merges.sh @@ -507,9 +507,11 @@ test_expect_success 'octopus merges' ' 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 be09fc78c1..4336f417c2 100755 --- a/t/t3431-rebase-fork-point.sh +++ b/t/t3431-rebase-fork-point.sh @@ -17,6 +17,12 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME # 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 5086e14c02..181d19dcc1 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -11,6 +11,12 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./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 0000000000..8b47579566 --- /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 0000000000..f513463b92 --- /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 0000000000..de7b357685 --- /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 0000000000..8ed0cebb50 --- /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 307101eeb9..89819ad4d2 100755 --- a/t/t3650-replay-basics.sh +++ b/t/t3650-replay-basics.sh @@ -25,6 +25,8 @@ test_expect_success 'setup' ' 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 && @@ -72,29 +74,31 @@ test_expect_success '--onto with invalid commit-ish' ' test_cmp expect actual ' -test_expect_success 'option --onto or --advance is mandatory' ' - echo "error: option --onto or --advance is mandatory" >expect && +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 'no base or negative ref gives no-replaying down to root error' ' - echo "fatal: replaying down from root commit is not supported yet!" >expect && - test_must_fail git replay --onto=topic1 topic2 2>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 'options --advance and --contained cannot be used together' ' - printf "fatal: options ${SQ}--advance${SQ} " >expect && - printf "and ${SQ}--contained${SQ} cannot be used together\n" >>expect && +test_expect_success '--advance and --contained cannot be used together' ' test_must_fail git replay --advance=main --contained \ topic1..topic2 2>actual && - test_cmp expect actual + test_grep "cannot be used together" actual ' test_expect_success 'cannot advance target ... ordering would be ill-defined' ' - echo "fatal: cannot advance target with multiple sources because ordering would be ill-defined" >expect && + 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 ' @@ -160,6 +164,25 @@ test_expect_success 'using replay on bare repo to perform basic cherry-pick' ' 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 ' @@ -249,6 +272,15 @@ test_expect_success 'using replay on bare repo to rebase multiple divergent bran 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 && @@ -368,4 +400,103 @@ test_expect_success 'invalid replay.refAction value' ' 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 af93e53c12..2947bf9a6b 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -38,7 +38,8 @@ test_expect_success 'Test with no pathspecs' ' ' 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 @@ test_expect_success 'error out when attempting to add ignored ones but add other 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 @@ test_expect_success 'git add with filemode=0, symlinks=0, and unmerged entries' 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 @@ test_expect_success 'git add with filemode=0, symlinks=0 prefers stage 2 over st 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_expect_success 'git add --refresh with pathspec' ' 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 @@ test_expect_success POSIXPERM,SANITY 'git add should fail atomically upon an unr 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 @@ test_expect_success POSIXPERM,SANITY 'git add --ignore-errors' ' 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 @@ test_expect_success POSIXPERM,SANITY 'git add (add.ignore-errors)' ' 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 @@ test_expect_success POSIXPERM,SANITY 'git add (add.ignore-errors = false)' ' 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 @@ test_expect_success POSIXPERM,SANITY '--no-ignore-errors overrides config' ' 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 @@ test_expect_success BSLASHPSPEC "git add 'fo\\[ou\\]bar' ignores foobar" ' 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 'git add to resolve conflicts on otherwise ignored path' ' 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' ' @@ -536,9 +547,11 @@ test_expect_success 'all statuses changed in folder if . is given' ' 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 ) ' diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 4285314f35..6e120a4001 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -527,7 +527,7 @@ test_expect_success 'goto hunk 1 with "g 1"' ' _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 @@ test_expect_success 'goto hunk 1 with "g1"' ' _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 @@ test_expect_success 'navigate to hunk via regex /pattern' ' _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 @@ test_expect_success 'navigate to hunk via regex / pattern' ' _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 @@ test_expect_success 'print again the hunk' ' 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 @@ test_expect_success TTY 'print again the hunk (PAGER)' ' 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 @@ test_expect_success 'colors can be overridden' ' <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_expect_success 'EOF quits' ' 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 f528008c36..8660ec5cb0 100755 --- a/t/t3902-quoted.sh +++ b/t/t3902-quoted.sh @@ -60,16 +60,18 @@ With SP in it "\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/t4012-diff-binary.sh b/t/t4012-diff-binary.sh index d1d30ac2a9..97b5ac0407 100755 --- a/t/t4012-diff-binary.sh +++ b/t/t4012-diff-binary.sh @@ -68,7 +68,7 @@ test_expect_success 'apply detecting corrupt patch correctly' ' 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 @@ test_expect_success 'apply detecting corrupt patch correctly' ' 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 21d6d0cd9e..0b89d127b5 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -380,6 +380,131 @@ test_expect_success 'filename limit applies only to basename' ' 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 && @@ -1285,7 +1410,9 @@ test_expect_success 'format-patch wraps extremely long from-header (rfc2047)' ' 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_expect_success 'format-patch wraps extremely long from-header (non-ASCII wi 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_expect_success 'subject lines are unencoded with --no-encode-email-headers' 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_expect_success '--from uses committer ident' ' 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_expect_success 'in-body headers trigger content encoding' ' 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 @@ test_expect_success 'format-patch respects format.noprefix' ' 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 3c8eb02e4f..b691d29479 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -90,6 +90,32 @@ test_expect_success "new incomplete line in post-image" ' 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 { diff --git a/t/t4041-diff-submodule-option.sh b/t/t4041-diff-submodule-option.sh index 4d4aa1650f..4dd4954260 100755 --- a/t/t4041-diff-submodule-option.sh +++ b/t/t4041-diff-submodule-option.sh @@ -37,8 +37,12 @@ add_file () { 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 740bb97091..7c749062e2 100755 --- a/t/t4052-stat-output.sh +++ b/t/t4052-stat-output.sh @@ -413,4 +413,36 @@ test_expect_success 'merge --stat respects COLUMNS with long name' ' 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 69599279e9..15076dfe0d 100755 --- a/t/t4053-diff-no-index.sh +++ b/t/t4053-diff-no-index.sh @@ -76,6 +76,16 @@ test_expect_success 'git diff --no-index executed outside repo gives correct err ) ' +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 0fe81056d5..bb902ce94d 100755 --- a/t/t4059-diff-submodule-not-initialized.sh +++ b/t/t4059-diff-submodule-not-initialized.sh @@ -35,8 +35,12 @@ add_file () { 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 dbfeb7470b..d8f9213255 100755 --- a/t/t4060-diff-submodule-option-diff-format.sh +++ b/t/t4060-diff-submodule-option-diff-format.sh @@ -35,8 +35,12 @@ add_file () { 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 72f25de449..30813109ac 100755 --- a/t/t4067-diff-partial-clone.sh +++ b/t/t4067-diff-partial-clone.sh @@ -132,6 +132,37 @@ test_expect_success 'diff with rename detection batches blobs' ' 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" && diff --git a/t/t4073-diff-stat-name-width.sh b/t/t4073-diff-stat-name-width.sh new file mode 100755 index 0000000000..ec5d3c3c1f --- /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 0000000000..d77fa3b79d --- /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 a5664f3eb3..8393076469 100755 --- a/t/t4100-apply-stat.sh +++ b/t/t4100-apply-stat.sh @@ -48,7 +48,93 @@ test_expect_success 'applying a hunk header which overflows fails' ' +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 8e302a5a57..f2d41e06bc 100755 --- a/t/t4103-apply-binary.sh +++ b/t/t4103-apply-binary.sh @@ -179,6 +179,24 @@ test_expect_success PERL_TEST_HELPERS 'reject truncated binary diff' ' " <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 697e86c0ff..c960fdf622 100755 --- a/t/t4120-apply-popt.sh +++ b/t/t4120-apply-popt.sh @@ -23,6 +23,47 @@ test_expect_success setup ' 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 115a0f8579..29ea7d4268 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -743,4 +743,90 @@ test_expect_success 'incomplete line modified at the end (error)' ' 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 f6db5a79dd..5eba15fa66 100755 --- a/t/t4128-apply-root.sh +++ b/t/t4128-apply-root.sh @@ -43,6 +43,47 @@ test_expect_success 'apply --directory -p (2) ' ' ' +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 204325f4d5..1717f407c8 100755 --- a/t/t4200-rerere.sh +++ b/t/t4200-rerere.sh @@ -72,7 +72,7 @@ test_expect_success 'nothing recorded without rerere' ' 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 @@ test_expect_success 'activate rerere, old style (conflicting merge)' ' 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 'set up rr-cache' ' 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 @@ test_expect_success 'first postimage wins' ' 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 @@ test_expect_success 'rerere clear' ' 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 @@ test_expect_success 'missing preimage' ' 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 'set up for garbage collection tests' ' 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 5f23fc147b..9f41d56d9a 100755 --- a/t/t4201-shortlog.sh +++ b/t/t4201-shortlog.sh @@ -105,7 +105,9 @@ test_expect_success 'output from user-defined format is re-wrapped' ' ' 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 8f2ba98963..3865f6abc7 100755 --- a/t/t4205-log-pretty-formats.sh +++ b/t/t4205-log-pretty-formats.sh @@ -9,7 +9,12 @@ test_description='Test pretty formats' . ./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 @@ commit_msg () { # (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_expect_success 'alias loop' ' 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 @@ test_expect_success 'setup more commits' ' 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_expect_success ICONV 'left alignment formatting. i18n.logOutputEncoding' ' 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_expect_success ICONV 'left alignment formatting at the nth column' ' 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_expect_success ICONV 'left alignment formatting at the nth column. i18n.log 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_expect_success ICONV 'left alignment formatting with no padding' ' 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_expect_success 'left alignment formatting with no padding. i18n.logOutputEn 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_expect_success ICONV 'left alignment formatting with trunc. i18n.logOutputE 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_expect_success ICONV 'left alignment formatting with ltrunc. i18n.logOutput 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_expect_success ICONV 'left alignment formatting with mtrunc. i18n.logOutput 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_expect_success ICONV 'right alignment formatting. i18n.logOutputEncoding' ' 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_expect_success ICONV 'right alignment formatting at the nth column' ' 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_expect_success ICONV 'right alignment formatting at the nth column with --g 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_expect_success ICONV 'right alignment formatting with no padding' ' 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_expect_success ICONV 'right alignment formatting with no padding. i18n.logO 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 @@ test_expect_success ICONV 'center alignment formatting. i18n.logOutputEncoding' 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_expect_success ICONV 'center alignment formatting at the nth column' ' 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_expect_success ICONV 'center alignment formatting at the nth column. i18n.l 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 ae0a56cf5e..96ddf3c53a 100755 --- a/t/t4254-am-corrupt.sh +++ b/t/t4254-am-corrupt.sh @@ -65,9 +65,8 @@ test_expect_success setup ' 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/t5004-archive-corner-cases.sh b/t/t5004-archive-corner-cases.sh index 027dedd976..df513a4269 100755 --- a/t/t5004-archive-corner-cases.sh +++ b/t/t5004-archive-corner-cases.sh @@ -176,8 +176,7 @@ test_expect_success EXPENSIVE,UNZIP,UNZIP_ZIP64_SUPPORT \ 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 ff6b5159a3..3c3666b278 100755 --- a/t/t5301-sliding-window.sh +++ b/t/t5301-sliding-window.sh @@ -12,7 +12,7 @@ test_expect_success 'setup' ' 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/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh index 6718fb98c0..f693cb5669 100755 --- a/t/t5310-pack-bitmaps.sh +++ b/t/t5310-pack-bitmaps.sh @@ -242,7 +242,7 @@ test_bitmap_cases () { ' 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_bitmap_cases () { ) ' + 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 8bacd96275..d0feab17b4 100755 --- a/t/t5315-pack-objects-compression.sh +++ b/t/t5315-pack-objects-compression.sh @@ -10,7 +10,7 @@ test_expect_success setup ' # 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 03dfb7a61e..8a067a45cb 100755 --- a/t/t5316-pack-delta-depth.sh +++ b/t/t5316-pack-delta-depth.sh @@ -48,6 +48,7 @@ test_description='pack-objects breaks long cross-pack delta chains' # 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 faae98c7e7..58e0b685b1 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -21,7 +21,7 @@ midx_read_expect () { 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: @@ -512,12 +512,7 @@ test_expect_success 'verify invalid chunk offset' ' "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" ' @@ -578,6 +573,15 @@ test_expect_success 'verify incorrect checksum' ' $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 @@ -1315,6 +1319,7 @@ test_expect_success 'bitmapped packs are stored via the BTMP chunk' ' git init repo && ( cd repo && + git config set maintenance.auto false && for i in 1 2 3 4 5 do @@ -1345,4 +1350,47 @@ test_expect_success 'bitmapped packs are stored via the BTMP chunk' ' ) ' +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 892aeb09e4..62bd973d92 100755 --- a/t/t5326-multi-pack-bitmaps.sh +++ b/t/t5326-multi-pack-bitmaps.sh @@ -93,7 +93,8 @@ test_midx_bitmap_cases () { 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 9cac03a94b..cfa12de2a8 100755 --- a/t/t5327-multi-pack-bitmaps-rev.sh +++ b/t/t5327-multi-pack-bitmaps-rev.sh @@ -30,7 +30,8 @@ test_midx_bitmap_rev () { 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 cd949025b9..c74b5861af 100755 --- a/t/t5331-pack-objects-stdin.sh +++ b/t/t5331-pack-objects-stdin.sh @@ -14,6 +14,7 @@ packed_objects () { 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 @@ test_expect_success '--stdin-packs=follow walks into unknown packs' ' git init repo && ( cd repo && + git config set maintenance.auto false && for c in A B C D do @@ -358,6 +360,24 @@ test_expect_success '--stdin-packs with promisors' ' ) ' +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 && @@ -395,4 +415,109 @@ test_expect_success '--stdin-packs=follow tolerates missing commits' ' 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 395d09444c..881ce668e1 100755 --- a/t/t5332-multi-pack-reuse.sh +++ b/t/t5332-multi-pack-reuse.sh @@ -59,6 +59,7 @@ test_pack_objects_reused () { 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 d30d7253d6..99c7d44d8e 100755 --- a/t/t5334-incremental-multi-pack-index.sh +++ b/t/t5334-incremental-multi-pack-index.sh @@ -15,6 +15,7 @@ midx_chain=$midxdir/multi-pack-index-chain 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 0000000000..40f3844282 --- /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 83b42ff073..b32a0a6aa7 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -187,6 +187,7 @@ test_expect_success 'receive-pack runs auto-gc in remote repo' ' 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 17a46fd3ba..44ec875aef 100755 --- a/t/t5401-update-hooks.sh +++ b/t/t5401-update-hooks.sh @@ -134,8 +134,8 @@ test_expect_success 'pre-receive hook that forgets to read its input' ' 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 1462e3365b..cb0300b2d2 100755 --- a/t/t5403-post-checkout-hook.sh +++ b/t/t5403-post-checkout-hook.sh @@ -9,9 +9,20 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./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 setup ' 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 @@ test_rebase () { 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 @@ test_rebase () { 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 @@ test_rebase () { 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_rebase --merge 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_path_is_file 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 2677cd5faa..649a615ec9 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -154,7 +154,8 @@ test_expect_success 'clone shallow depth 1 with fsck' ' ' 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_expect_success 'shallow since with commit graph and already-seen commit' ' 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' ' ) ' +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 ce1c23684e..6fe21e2b3a 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -469,12 +469,17 @@ test_expect_success 'fetch --atomic executes a single reference transaction only 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 @@ test_expect_success 'fetch --atomic aborts all reference updates if hook aborts' 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 @@ test_expect_success 'fetching with auto-gc does not lock up' ' 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 @@ test_expect_success REFFILES 'existing reference lock in repo' ' 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 @@ test_expect_success CASE_INSENSITIVE_FS,REFFILES 'F/D conflict on case insensiti 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 @@ test_expect_success CASE_INSENSITIVE_FS,REFFILES 'D/F conflict on case insensiti 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 @@ -1658,7 +1664,7 @@ test_expect_success REFFILES "FETCH_HEAD is updated even if ref updates fail" ' git remote add origin ../base && >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 && test_grep "branch ${SQ}branch${SQ} of ../base" FETCH_HEAD && test_grep "branch ${SQ}foo${SQ} of ../base" FETCH_HEAD ) diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 46926e7bbd..29e2f17608 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -1882,4 +1882,20 @@ test_expect_success 'push with F/D conflict with deletion and creation' ' 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 5e566205ba..1242ee9185 100755 --- a/t/t5526-fetch-submodules.sh +++ b/t/t5526-fetch-submodules.sh @@ -834,11 +834,18 @@ test_expect_success "fetch new submodule commits on-demand outside standard refs 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 commit intermittently referenced by sup ) ' +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 ed0ad66fad..9d0a7f5c4b 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh @@ -102,6 +102,31 @@ test_expect_success 'cloning password-protected repository can fail' ' 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 'fetch can handle previously-fetched .idx files' ' ' 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 @@ ja;q=0.95, zh;q=0.94, sv;q=0.93, pt;q=0.92, nb;q=0.91, *;q=0.90" \ 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 73cf531580..a26b6c2844 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh @@ -782,4 +782,11 @@ test_expect_success 'tag following always works over v0 http' ' 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 c1febbae9d..0063581615 100755 --- a/t/t5563-simple-http-auth.sh +++ b/t/t5563-simple-http-auth.sh @@ -605,6 +605,51 @@ test_expect_success 'access using bearer auth with invalid credentials' ' 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/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh index 45f384dd32..42d14328b6 100755 --- a/t/t5572-pull-submodule.sh +++ b/t/t5572-pull-submodule.sh @@ -257,7 +257,26 @@ test_expect_success 'fetch submodule remote of different name from superproject' 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 0000000000..a22007b2cf --- /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 1e354e057f..1c2805acca 100755 --- a/t/t5616-partial-clone.sh +++ b/t/t5616-partial-clone.sh @@ -229,7 +229,7 @@ test_expect_success 'fetch --refetch triggers repacking' ' 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 @@ test_expect_success 'fetch --refetch triggers repacking' ' -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 @@ test_expect_success 'fetch --refetch triggers repacking' ' -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 @@ test_expect_success 'verify fetch downloads only one pack when updating refs' ' 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 58c81556e7..2c347a91fe 100755 --- a/t/t5620-backfill.sh +++ b/t/t5620-backfill.sh @@ -7,6 +7,14 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./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 @@ test_expect_success 'setup repo for object creation' ' 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 @@ test_expect_success 'setup repo for object creation' ' 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 @@ test_expect_success 'setup bare clone for server' ' 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_expect_success 'backfill --sparse without cone mode (negative)' ' 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 023735d6a8..357822c01a 100755 --- a/t/t5710-promisor-remote-capability.sh +++ b/t/t5710-promisor-remote-capability.sh @@ -20,7 +20,7 @@ test_expect_success 'setup: create "template" repository' ' 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 @@ test_expect_success "clone with promisor.checkFields" ' 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 "clone with promisor.advertise set to 'true' but don't delet 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/t6000-rev-list-misc.sh b/t/t6000-rev-list-misc.sh index fec16448cf..d0a2a86610 100755 --- a/t/t6000-rev-list-misc.sh +++ b/t/t6000-rev-list-misc.sh @@ -248,4 +248,19 @@ test_expect_success 'rev-list -z --boundary' ' 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 eb93d68d7d..581984467d 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -378,15 +378,23 @@ test_expect_success 'rev-list %C(auto,...) respects --color' ' test_cmp expect actual ' -iconv -f utf-8 -t $test_encoding > commit-msg <<EOF -Test printing of complex bodies +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 + 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 && -test_expect_success 'setup complex body' ' 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 @@ test_expect_success 'setup expected messages (for test %b)' ' 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 cdc0270640..1ba9ca219e 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -402,7 +402,7 @@ test_expect_success 'git bisect run: negative exit code' " 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 0b719bbae6..0242b5bf7a 100755 --- a/t/t6040-tracking-info.sh +++ b/t/t6040-tracking-info.sh @@ -292,4 +292,358 @@ test_expect_success '--set-upstream-to @{-1}' ' 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 5f55ab98d3..7281889717 100755 --- a/t/t6101-rev-parse-parents.sh +++ b/t/t6101-rev-parse-parents.sh @@ -39,7 +39,8 @@ test_expect_success 'setup' ' ' 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 0387f35a32..39211ef989 100755 --- a/t/t6112-rev-list-filters-objects.sh +++ b/t/t6112-rev-list-filters-objects.sh @@ -483,10 +483,6 @@ test_expect_success 'combine:... with non-encoded reserved chars' ' "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 06ab4d7aed..801284cf8f 100755 --- a/t/t6403-merge-file.sh +++ b/t/t6403-merge-file.sh @@ -428,6 +428,42 @@ test_expect_success '"diff3 -m" style output (2)' ' 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 @@ test_expect_success '--object-id fails without repository' ' 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/t6500-gc.sh b/t/t6500-gc.sh index bef472cb8d..ea9aaad470 100755 --- a/t/t6500-gc.sh +++ b/t/t6500-gc.sh @@ -11,6 +11,7 @@ test_expect_success 'setup' ' # 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 6638d1aa1d..2613075894 100755 --- a/t/t6600-test-reach.sh +++ b/t/t6600-test-reach.sh @@ -762,4 +762,79 @@ test_expect_success 'for-each-ref is-base: --sort' ' --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 5ab4d41ee7..c475769858 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -92,8 +92,8 @@ test_expect_success 'rewrite, renaming a specific file' ' 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 'rewrite, renaming a specific directory' ' 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/t7425-submodule-gitdir-path-extension.sh b/t/t7425-submodule-gitdir-path-extension.sh new file mode 100755 index 0000000000..ea86ecf7ee --- /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 0000000000..b842af9a2d --- /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 abad229e9d..a5e21bf8bf 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -1576,7 +1576,7 @@ test_expect_success 'git commit will commit a staged but ignored submodule' ' 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 25e8e9711f..08e82f7914 100755 --- a/t/t7512-status-help.sh +++ b/t/t7512-status-help.sh @@ -594,6 +594,15 @@ EOF 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 264b9daf83..bcc055c1e0 100755 --- a/t/t7519/fsmonitor-watchman +++ b/t/t7519/fsmonitor-watchman @@ -38,8 +38,6 @@ if ($^O =~ 'msys' || $^O =~ 'cygwin') { $git_work_tree = Cwd::cwd(); } -my $retry = 1; - launch_watchman(); sub launch_watchman { @@ -92,9 +90,8 @@ sub launch_watchman { 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 @@ sub launch_watchman { close $fh; print "/\0"; - eval { launch_watchman() }; exit 0; } diff --git a/t/t7519/fsmonitor-watchman-v2 b/t/t7519/fsmonitor-watchman-v2 index 14ed0aa42d..368604c278 100755 --- a/t/t7519/fsmonitor-watchman-v2 +++ b/t/t7519/fsmonitor-watchman-v2 @@ -29,8 +29,6 @@ if ($version ne 2) { my $git_work_tree = get_working_dir(); -my $retry = 1; - my $json_pkg; eval { require JSON::XS; @@ -122,8 +120,7 @@ sub watchman_query { 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 @@ sub is_work_tree_watched { # 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 409cd0cd12..e7b4065469 100755 --- a/t/t7527-builtin-fsmonitor.sh +++ b/t/t7527-builtin-fsmonitor.sh @@ -408,9 +408,8 @@ move_directory() { # 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 @@ clean_up_repo_and_stop_daemon () { 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 @@ test_expect_success 'edit some files' ' 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 @@ test_expect_success 'create some files' ' 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 @@ test_expect_success 'delete some files' ' 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 @@ test_expect_success 'rename some files' ' 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 @@ test_expect_success 'rename directory' ' 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 @@ test_expect_success 'file changes to directory' ' 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 @@ test_expect_success 'directory changes to a file' ' 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 5d56c38546..44de97a480 100755 --- a/t/t7605-merge-resolve.sh +++ b/t/t7605-merge-resolve.sh @@ -34,9 +34,9 @@ merge_c1_to_c2_cmds=' 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 73b78bdd88..63ef63fc50 100755 --- a/t/t7700-repack.sh +++ b/t/t7700-repack.sh @@ -217,6 +217,7 @@ test_expect_success 'repack --keep-pack' ' 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 @@ test_expect_success 'repacking fails when missing .pack actually means missing o # 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 'no bitmaps created if .keep files present' ' 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 @@ test_expect_success '--filter works with --max-pack-size' ' 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 @@ test_expect_success 'setup for --write-midx tests' ' ( cd midx && git config core.multiPackIndex true && + git config maintenance.auto false && test_commit base ) diff --git a/t/t7704-repack-cruft.sh b/t/t7704-repack-cruft.sh index aa2e2e6ad8..9e03b04315 100755 --- a/t/t7704-repack-cruft.sh +++ b/t/t7704-repack-cruft.sh @@ -869,4 +869,26 @@ test_expect_success 'repack --write-midx includes cruft when already geometric' ) ' +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/t7900-maintenance.sh b/t/t7900-maintenance.sh index 7cc0ce57f8..4700beacc1 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh @@ -7,6 +7,9 @@ test_description='git maintenance builtin' 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,7 +45,8 @@ test_expect_success 'help text' ' 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" \ @@ -496,6 +500,7 @@ test_expect_success 'maintenance.incremental-repack.auto' ' ( cd incremental-repack-true && git config core.multiPackIndex true && + git config maintenance.auto false && run_incremental_repack_and_verify ) ' @@ -506,6 +511,7 @@ test_expect_success 'maintenance.incremental-repack.auto (when config is unset)' ( cd incremental-repack-unset && test_unconfig core.multiPackIndex && + git config maintenance.auto false && run_incremental_repack_and_verify ) ' @@ -616,6 +622,7 @@ test_expect_success 'geometric repacking with --auto' ' 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. diff --git a/t/t8003-blame-corner-cases.sh b/t/t8003-blame-corner-cases.sh index 731265541a..30e7960ace 100755 --- a/t/t8003-blame-corner-cases.sh +++ b/t/t8003-blame-corner-cases.sh @@ -49,80 +49,69 @@ test_expect_success setup ' ' 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 @@ test_expect_success 'blame wholesale copy and more' ' 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 @@ test_expect_success 'blame wholesale copy and more in the index' ' 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 @@ test_expect_success 'blame wholesale copy and more in the index' ' 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 @@ test_expect_success 'blame during cherry-pick with file rename conflict' ' (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 'setup file with CRLF newlines' ' 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 @@ test_expect_success 'blame file with CRLF core.autocrlf=true' ' 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 3d77352650..5562eba436 100755 --- a/t/t8012-blame-colors.sh +++ b/t/t8012-blame-colors.sh @@ -28,6 +28,20 @@ test_expect_success 'colored blame colors contiguous lines' ' 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/t8020-last-modified.sh b/t/t8020-last-modified.sh index 50f4312f71..9dba4b9d90 100755 --- a/t/t8020-last-modified.sh +++ b/t/t8020-last-modified.sh @@ -8,14 +8,11 @@ test_expect_success 'setup' ' 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 @@ check_last_modified() { 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 @@ test_expect_success 'last-modified recursive' ' 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 @@ -93,6 +97,41 @@ test_expect_success 'last-modified subdir recursive' ' 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 @@ -230,9 +269,19 @@ test_expect_success 'last-modified merge undoes changes' ' 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 e56e0c8d77..e7ab645a3d 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -1649,7 +1649,9 @@ test_expect_success $PREREQ 'To headers from files reset each patch' ' ' 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 @@ test_expect_success $PREREQ 'asks about and fixes 8bit encodings' ' 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 '--8bit-encoding overrides sendemail.8bitEncoding' ' ' 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 '--8bit-encoding also treats subject' ' ' 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 ead404589e..8fa5940abe 100755 --- a/t/t9123-git-svn-rebuild-with-rewriteroot.sh +++ b/t/t9123-git-svn-rebuild-with-rewriteroot.sh @@ -7,12 +7,15 @@ test_description='git svn respects rewriteRoot during rebuild' . ./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 36c6b1a12f..de32cf2542 100755 --- a/t/t9160-git-svn-preserve-empty-dirs.sh +++ b/t/t9160-git-svn-preserve-empty-dirs.sh @@ -61,15 +61,15 @@ test_expect_success 'clone svn repo with --preserve-empty-dirs' ' # "$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 @@ test_expect_success 'directory empty from subsequent svn commit' ' # 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 @@ test_expect_success 'clone svn repo with --placeholder-file specified' ' # "$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 @@ test_expect_success 'second set of svn commits and rebase' ' # 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 a44eabf0d8..14cbe96527 100755 --- a/t/t9200-git-cvsexportcommit.sh +++ b/t/t9200-git-cvsexportcommit.sh @@ -30,13 +30,17 @@ export CVSROOT CVSWORK GIT_DIR 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 @@ test_expect_success 're-commit a removed filename which remains in CVS attic' ' 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/t9300-fast-import.sh b/t/t9300-fast-import.sh index 5685cce6fe..479437760b 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -3635,25 +3635,21 @@ background_import_then_checkpoint () { 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 022dae02e4..18707b3f6c 100755 --- a/t/t9305-fast-import-signatures.sh +++ b/t/t9305-fast-import-signatures.sh @@ -70,7 +70,7 @@ test_expect_success GPGSSH 'strip SSH signature with --signed-commits=strip' ' 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 && @@ -91,7 +91,7 @@ test_expect_success GPG 'setup a commit with dual OpenPGP signatures on its SHA- 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,71 +103,111 @@ test_expect_success GPG 'strip both OpenPGP signatures with --signed-commits=war test_line_count = 2 out ' -test_expect_success GPG 'import commit with no signature with --signed-commits=strip-if-invalid' ' - git fast-export main >output && - git -C new fast-import --quiet --signed-commits=strip-if-invalid <output >log 2>&1 && - test_must_be_empty log -' +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=strip-if-invalid' ' - rm -rf new && - git init new && + 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=strip-if-invalid <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 -' + 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 'strip signature invalidated by message change with --signed-commits=strip-if-invalid' ' - rm -rf new && - git init new && + 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 && + 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 && + # 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=strip-if-invalid <modified >log 2>&1 && + 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 && - test_grep ! -E "^gpgsig" actual && - test_grep "stripping invalid signature" log -' + IMPORTED=$(git -C new rev-parse --verify refs/heads/openpgp-signing) && + test $OPENPGP_SIGNING != $IMPORTED && + git -C new cat-file commit "$IMPORTED" >actual && -test_expect_success GPGSM 'keep valid X.509 signature with --signed-commits=strip-if-invalid' ' - rm -rf new && - git init new && + 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 + ' - git fast-export --signed-commits=verbatim x509-signing >output && - git -C new fast-import --quiet --signed-commits=strip-if-invalid <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 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}" && -test_expect_success GPGSSH 'keep valid SSH signature with --signed-commits=strip-if-invalid' ' + 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 && - git fast-export --signed-commits=verbatim ssh-signing >output && - git -C new fast-import --quiet --signed-commits=strip-if-invalid <output >log 2>&1 && IMPORTED=$(git -C new rev-parse --verify refs/heads/ssh-signing) && - test $SSH_SIGNING = $IMPORTED && + test $SSH_SIGNING != $IMPORTED && git -C new cat-file commit "$IMPORTED" >actual && test_grep -E "^gpgsig(-sha256)? " actual && - test_must_be_empty log + git -C new verify-commit "$IMPORTED" ' test_done diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh index 3d153a4805..784d68b6e5 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh @@ -972,7 +972,7 @@ test_expect_success 'fast-export handles --end-of-options' ' 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_expect_success GPG 'setup a commit with dual signatures on its SHA-1 and SH 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/t9812-git-p4-wildcards.sh b/t/t9812-git-p4-wildcards.sh index 254a7c2446..e91004ee79 100755 --- a/t/t9812-git-p4-wildcards.sh +++ b/t/t9812-git-p4-wildcards.sh @@ -30,13 +30,13 @@ test_expect_success 'wildcard files git p4 clone' ' 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 964e1f1569..2f9a597ec7 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -2601,6 +2601,7 @@ test_expect_success 'double dash "git checkout"' ' --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 @@ test_expect_success 'git config set - variable name - __git_compute_second_level 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 14e238d24d..f3af10fb7e 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -48,6 +48,9 @@ test_decode_color () { 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"; diff --git a/t/test-lib.sh b/t/test-lib.sh index 0fb76f7d11..70fd3e9baf 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -1720,7 +1720,6 @@ esac ( 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 @@ -1731,6 +1730,17 @@ test -n "$SANITIZE_LEAK" && test_set_prereq SANITIZE_LEAK 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 @@ -1891,6 +1901,10 @@ test_lazy_prereq LONG_IS_64BIT ' 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' @@ -1947,6 +1961,10 @@ test_lazy_prereq COMPAT_HASH ' 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/clar.h b/t/unit-tests/clar/clar.h index f7e4363022..9ea91d3d0e 100644 --- a/t/unit-tests/clar/clar.h +++ b/t/unit-tests/clar/clar.h @@ -15,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 diff --git a/t/unit-tests/clar/clar/print.h b/t/unit-tests/clar/clar/print.h index 6a2321b399..59b7dc14a1 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); diff --git a/t/unit-tests/clar/generate.py b/t/unit-tests/clar/generate.py index fd2f0ee83b..2357b2d6d2 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 @@ class TestSuite(object): 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 @@ class TestSuite(object): 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 + + 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(output, 'w') as data: + 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 @@ if __name__ == '__main__': 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/test-lib.c b/t/unit-tests/test-lib.c index 87e1f5c201..72ee20a06f 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 0000000000..f7d73701b5 --- /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 b23af449f6..00481df63f 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 e6eede2740..d4d05c7dc3 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-string-list.c b/t/unit-tests/u-string-list.c index a2457d7b1e..7ad84cc1cd 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/templates/hooks/commit-msg.sample b/templates/hooks/commit-msg.sample index b58d1184a9..f7458efe62 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 23e856f5de..429e0a51c1 100755 --- a/templates/hooks/fsmonitor-watchman.sample +++ b/templates/hooks/fsmonitor-watchman.sample @@ -29,8 +29,6 @@ if ($version ne 2) { my $git_work_tree = get_working_dir(); -my $retry = 1; - my $json_pkg; eval { require JSON::XS; @@ -123,8 +121,7 @@ sub watchman_query { 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 @@ sub is_work_tree_watched { # 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 9f5a1788cd..d199d39e7c 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 fceda14979..ccf800faa7 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 0000000000..d732997136 --- /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 index a0aaf3a347..a0aaf3a347 100755 --- a/check-builtins.sh +++ b/tools/check-builtins.sh diff --git a/contrib/coccinelle/.gitignore b/tools/coccinelle/.gitignore index 1d45c0a40c..1d45c0a40c 100644 --- a/contrib/coccinelle/.gitignore +++ b/tools/coccinelle/.gitignore diff --git a/contrib/coccinelle/README b/tools/coccinelle/README index 055ad0e06a..fd0a543cc2 100644 --- a/contrib/coccinelle/README +++ b/tools/coccinelle/README @@ -38,7 +38,7 @@ that might be useful to developers. 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 index d306f6a21e..e71baea00b 100644 --- a/contrib/coccinelle/array.cocci +++ b/tools/coccinelle/array.cocci @@ -107,9 +107,32 @@ type T; T *ptr; expression n; @@ -- memset(ptr, \( 0x0 \| 0 \), n * \( sizeof(T) -- \| sizeof(*ptr) -- \) ) +- 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) @@ @@ -117,7 +140,8 @@ type T; T[] ptr; expression n; @@ -- memset(ptr, \( 0x0 \| 0 \), n * \( sizeof(T) -- \| sizeof(*ptr) -- \) ) +- 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 index 42725161e9..42725161e9 100644 --- a/contrib/coccinelle/commit.cocci +++ b/tools/coccinelle/commit.cocci diff --git a/contrib/coccinelle/config_fn_ctx.pending.cocci b/tools/coccinelle/config_fn_ctx.pending.cocci index 54f09fcbcd..54f09fcbcd 100644 --- a/contrib/coccinelle/config_fn_ctx.pending.cocci +++ b/tools/coccinelle/config_fn_ctx.pending.cocci diff --git a/contrib/coccinelle/equals-null.cocci b/tools/coccinelle/equals-null.cocci index 92c7054013..92c7054013 100644 --- a/contrib/coccinelle/equals-null.cocci +++ b/tools/coccinelle/equals-null.cocci diff --git a/contrib/coccinelle/flex_alloc.cocci b/tools/coccinelle/flex_alloc.cocci index e9f7f6d861..e9f7f6d861 100644 --- a/contrib/coccinelle/flex_alloc.cocci +++ b/tools/coccinelle/flex_alloc.cocci diff --git a/contrib/coccinelle/free.cocci b/tools/coccinelle/free.cocci index 6fb9eb6e88..03799e1908 100644 --- a/contrib/coccinelle/free.cocci +++ b/tools/coccinelle/free.cocci @@ -5,7 +5,7 @@ expression E; ( free(E); | - free_commit_list(E); + commit_list_free(E); ) @@ @@ -15,7 +15,7 @@ expression E; ( free(E); | - free_commit_list(E); + commit_list_free(E); ) @@ @@ -30,7 +30,7 @@ expression E; @@ - if (E) - { - free_commit_list(E); + commit_list_free(E); E = NULL; - } @@ -41,5 +41,5 @@ statement S; - 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 index 7b57dceefe..7b57dceefe 100644 --- a/contrib/coccinelle/git_config_number.cocci +++ b/tools/coccinelle/git_config_number.cocci diff --git a/contrib/coccinelle/hashmap.cocci b/tools/coccinelle/hashmap.cocci index c5dbb4557b..c5dbb4557b 100644 --- a/contrib/coccinelle/hashmap.cocci +++ b/tools/coccinelle/hashmap.cocci diff --git a/contrib/coccinelle/index-compatibility.cocci b/tools/coccinelle/index-compatibility.cocci index 31e36cf3c4..31e36cf3c4 100644 --- a/contrib/coccinelle/index-compatibility.cocci +++ b/tools/coccinelle/index-compatibility.cocci diff --git a/contrib/coccinelle/meson.build b/tools/coccinelle/meson.build index ae7f5b5460..ae7f5b5460 100644 --- a/contrib/coccinelle/meson.build +++ b/tools/coccinelle/meson.build diff --git a/contrib/coccinelle/object_id.cocci b/tools/coccinelle/object_id.cocci index 01f8d6935b..01f8d6935b 100644 --- a/contrib/coccinelle/object_id.cocci +++ b/tools/coccinelle/object_id.cocci diff --git a/contrib/coccinelle/preincr.cocci b/tools/coccinelle/preincr.cocci index ae42cb0730..ae42cb0730 100644 --- a/contrib/coccinelle/preincr.cocci +++ b/tools/coccinelle/preincr.cocci diff --git a/contrib/coccinelle/qsort.cocci b/tools/coccinelle/qsort.cocci index 22b93a9966..22b93a9966 100644 --- a/contrib/coccinelle/qsort.cocci +++ b/tools/coccinelle/qsort.cocci diff --git a/contrib/coccinelle/refs.cocci b/tools/coccinelle/refs.cocci index 31d9cad8f3..31d9cad8f3 100644 --- a/contrib/coccinelle/refs.cocci +++ b/tools/coccinelle/refs.cocci diff --git a/contrib/coccinelle/spatchcache b/tools/coccinelle/spatchcache index 29e9352d8a..efbcbc3827 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 index 5f06105df6..f586128329 100644 --- a/contrib/coccinelle/strbuf.cocci +++ b/tools/coccinelle/strbuf.cocci @@ -60,3 +60,21 @@ expression E1, E2; @@ - 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 0000000000..64edb09f1c --- /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 index 522177afb6..522177afb6 100644 --- a/contrib/coccinelle/swap.cocci +++ b/tools/coccinelle/swap.cocci diff --git a/contrib/coccinelle/tests/free.c b/tools/coccinelle/tests/free.c index 96d4abc0c7..96d4abc0c7 100644 --- a/contrib/coccinelle/tests/free.c +++ b/tools/coccinelle/tests/free.c diff --git a/contrib/coccinelle/tests/free.res b/tools/coccinelle/tests/free.res index f90fd9f48e..f90fd9f48e 100644 --- a/contrib/coccinelle/tests/free.res +++ b/tools/coccinelle/tests/free.res diff --git a/contrib/coccinelle/the_repository.cocci b/tools/coccinelle/the_repository.cocci index f1129f7985..f1129f7985 100644 --- a/contrib/coccinelle/the_repository.cocci +++ b/tools/coccinelle/the_repository.cocci diff --git a/contrib/coccinelle/xcalloc.cocci b/tools/coccinelle/xcalloc.cocci index c291011607..c291011607 100644 --- a/contrib/coccinelle/xcalloc.cocci +++ b/tools/coccinelle/xcalloc.cocci diff --git a/contrib/coccinelle/xopen.cocci b/tools/coccinelle/xopen.cocci index b71db67019..b71db67019 100644 --- a/contrib/coccinelle/xopen.cocci +++ b/tools/coccinelle/xopen.cocci diff --git a/contrib/coccinelle/xstrdup_or_null.cocci b/tools/coccinelle/xstrdup_or_null.cocci index 9c1d2939b6..9c1d2939b6 100644 --- a/contrib/coccinelle/xstrdup_or_null.cocci +++ b/tools/coccinelle/xstrdup_or_null.cocci diff --git a/contrib/coccinelle/xstrncmpz.cocci b/tools/coccinelle/xstrncmpz.cocci index ccb39e2bc0..ccb39e2bc0 100644 --- a/contrib/coccinelle/xstrncmpz.cocci +++ b/tools/coccinelle/xstrncmpz.cocci diff --git a/contrib/coverage-diff.sh b/tools/coverage-diff.sh index 6ce9603568..6ce9603568 100755 --- a/contrib/coverage-diff.sh +++ b/tools/coverage-diff.sh diff --git a/detect-compiler b/tools/detect-compiler index 124ebdd4c9..124ebdd4c9 100755 --- a/detect-compiler +++ b/tools/detect-compiler diff --git a/generate-cmdlist.sh b/tools/generate-cmdlist.sh index 0ed39c4c5d..0ed39c4c5d 100755 --- a/generate-cmdlist.sh +++ b/tools/generate-cmdlist.sh diff --git a/generate-configlist.sh b/tools/generate-configlist.sh index 75c39ade20..e28054f9e0 100755 --- a/generate-configlist.sh +++ b/tools/generate-configlist.sh @@ -2,10 +2,11 @@ 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>" + echo >&2 "USAGE: $0 <SOURCE_DIR> <OUTPUT> [<DEPFILE>]" exit 1 fi @@ -36,3 +37,16 @@ EOF 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 index e0cdf26944..e0cdf26944 100755 --- a/generate-hooklist.sh +++ b/tools/generate-hooklist.sh diff --git a/generate-perl.sh b/tools/generate-perl.sh index 796d835932..796d835932 100755 --- a/generate-perl.sh +++ b/tools/generate-perl.sh diff --git a/generate-python.sh b/tools/generate-python.sh index 31ac115689..31ac115689 100755 --- a/generate-python.sh +++ b/tools/generate-python.sh diff --git a/generate-script.sh b/tools/generate-script.sh index a149e4f0ba..a149e4f0ba 100755 --- a/generate-script.sh +++ b/tools/generate-script.sh diff --git a/tools/meson.build b/tools/meson.build new file mode 100644 index 0000000000..f731f74312 --- /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 0000000000..b2bec0d2b4 --- /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 index b0ebc6aad2..b0ebc6aad2 100644 --- a/contrib/update-unicode/.gitignore +++ b/tools/update-unicode/.gitignore diff --git a/contrib/update-unicode/README b/tools/update-unicode/README index 151a197041..151a197041 100644 --- a/contrib/update-unicode/README +++ b/tools/update-unicode/README diff --git a/contrib/update-unicode/update_unicode.sh b/tools/update-unicode/update_unicode.sh index aa90865bef..aa90865bef 100755 --- a/contrib/update-unicode/update_unicode.sh +++ b/tools/update-unicode/update_unicode.sh @@ -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 tempfile *tempfile = NULL; + struct stat st; + struct strbuf filename_template = STRBUF_INIT; + const char *tail; + + 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 child_process run_trailer = CHILD_PROCESS_INIT; + 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); + } - 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); + 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); } @@ -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 4d95d84f9e..570d7c6439 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 c7f06a7382..e53936d87b 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.")); } -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 feed_pre_push_hook_data { struct strbuf buf; - const char *hook_path = find_hook(the_repository, "pre-push"); + const struct ref *refs; +}; - if (!hook_path) - return 0; +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; - strvec_push(&proc.args, hook_path); - strvec_push(&proc.args, transport->remote->name); - strvec_push(&proc.args, transport->url); + if (!r) + return 1; /* no more refs */ - proc.in = -1; - proc.trace2_hook_name = "pre-push"; + data->refs = r->next; - if (start_command(&proc)) { - finish_command(&proc); - return -1; + 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; } - sigchain_push(SIGPIPE, SIG_IGN); + 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. */ - strbuf_init(&buf, 256); + return 0; +} - 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; +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; +} - 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)); +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); +} - 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; - } - } +static int run_pre_push_hook(struct transport *transport, + struct ref *remote_refs) +{ + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + int ret = 0; - strbuf_release(&buf); + strvec_push(&opt.args, transport->remote->name); + strvec_push(&opt.args, transport->url); - x = close(proc.in); - if (!ret) - ret = x; + 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; - sigchain_pop(SIGPIPE); + /* + * 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; - 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 631ea86812..2f5c956d02 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; } diff --git a/unpack-trees.c b/unpack-trees.c index f38c761ab9..998a1e6dc7 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 2d2b70cbf2..9f6d6fe48c 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 @@ error: 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; @@ -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); @@ -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) { /* @@ -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 */ @@ -1,5 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE - #include "git-compat-util.h" #include "version.h" #include "strbuf.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/worktree.c b/worktree.c index 9308389cb6..d874e23b4e 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; @@ -653,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"); @@ -664,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: @@ -722,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); @@ -833,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) { @@ -893,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); @@ -1067,17 +1089,17 @@ cleanup: 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); @@ -1090,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 e4bcccdc0a..d19ec29dbb 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 @@ -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; @@ -47,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 01a9a51fa2..5f522fb728 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 65a5c42a47..a045bdfaef 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/wt-status.c b/wt-status.c index e12adb26b9..479ccc3304 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 e40a27214a..e9fe32e98c 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 1a35556380..f043330f2a 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; diff --git a/xdiff-interface.h b/xdiff-interface.h index dfc55daddf..fbc4ceec40 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/xdiffi.c b/xdiff/xdiffi.c index 4376f943db..5455b4690d 100644 --- a/xdiff/xdiffi.c +++ b/xdiff/xdiffi.c @@ -792,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; @@ -805,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; @@ -861,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)) @@ -914,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)) diff --git a/xdiff/xpatience.c b/xdiff/xpatience.c index 9580d18032..7953490ed0 100644 --- a/xdiff/xpatience.c +++ b/xdiff/xpatience.c @@ -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; @@ -121,7 +114,6 @@ static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map, return; map->entries[index].line1 = line; map->entries[index].minimal_perfect_hash = record->minimal_perfect_hash; - map->entries[index].anchor = is_anchor(xpp, (const char *)map->env->xdf1.recs[line - 1].ptr); 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; @@ -220,7 +214,7 @@ static int find_longest_common_sequence(struct hashmap *map, struct entry **res) 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) { diff --git a/xdiff/xprepare.c b/xdiff/xprepare.c index 34c82e4f8e..cd4fc405eb 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,14 +94,15 @@ static void xdl_free_classifier(xdlclassifier_t *cf) { } -static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t *rec) { +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 = XDL_HASHLONG(rec->line_hash, cf->hbits); + hi = XDL_HASHLONG(line_hash, cf->hbits); for (rcrec = cf->rchash[hi]; rcrec; rcrec = rcrec->next) - if (rcrec->rec.line_hash == rec->line_hash && - xdl_recmatch((const char *)rcrec->rec.ptr, (long)rcrec->rec.size, + 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; @@ -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; @@ -158,8 +163,7 @@ static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_ crec = &xdf->recs[xdf->nrec++]; crec->ptr = prev; crec->size = cur - prev; - crec->line_hash = hav; - if (xdl_classify_record(pass, cf, crec) < 0) + if (xdl_classify_record(pass, cf, crec, hav) < 0) goto abort; } } diff --git a/xdiff/xtypes.h b/xdiff/xtypes.h index 979586f20a..50aee779be 100644 --- a/xdiff/xtypes.h +++ b/xdiff/xtypes.h @@ -41,7 +41,6 @@ typedef struct s_chastore { typedef struct s_xrecord { uint8_t const *ptr; size_t size; - uint64_t line_hash; size_t minimal_perfect_hash; } xrecord_t; |
