From: Junio C Hamano Date: Thu, 8 Apr 2021 20:23:26 +0000 (-0700) Subject: Merge branch 'en/ort-perf-batch-9' X-Git-Tag: v2.32.0-rc0~97 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=1b31224e59750f515f7ceb7adab2a7609371327d;hp=e4fd06e7e2fa9c433597b76245e38e545eec9ec0;p=thirdparty%2Fgit.git Merge branch 'en/ort-perf-batch-9' The ort merge backend has been optimized by skipping irrelevant renames. * en/ort-perf-batch-9: diffcore-rename: avoid doing basename comparisons for irrelevant sources merge-ort: skip rename detection entirely if possible merge-ort: use relevant_sources to filter possible rename sources merge-ort: precompute whether directory rename detection is needed merge-ort: introduce wrappers for alternate tree traversal merge-ort: add data structures for an alternate tree traversal merge-ort: precompute subset of sources for which we need rename detection diffcore-rename: enable filtering possible rename sources --- diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5f2f884b92..73856bafc9 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -186,6 +186,11 @@ jobs: ## Unzip and remove the artifact unzip artifacts.zip rm artifacts.zip + - name: initialize vcpkg + uses: actions/checkout@v2 + with: + repository: 'microsoft/vcpkg' + path: 'compat/vcbuild/vcpkg' - name: download vcpkg artifacts shell: powershell run: | diff --git a/.mailmap b/.mailmap index bde7aba756..9c6a446bdf 100644 --- a/.mailmap +++ b/.mailmap @@ -220,6 +220,7 @@ Philipp A. Hartmann Philippe Bruhat Ralf Thielow Ramsay Jones +Ramkumar Ramachandra Randall S. Becker René Scharfe René Scharfe Rene Scharfe diff --git a/Documentation/Makefile b/Documentation/Makefile index 81d1bf7a04..874a01d7a8 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -76,6 +76,7 @@ SP_ARTICLES += howto/rebuild-from-update-hook SP_ARTICLES += howto/rebase-from-internal-branch SP_ARTICLES += howto/keep-canonical-history-correct SP_ARTICLES += howto/maintain-git +SP_ARTICLES += howto/coordinate-embargoed-releases API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technical/api-index.txt, $(wildcard technical/api-*.txt))) SP_ARTICLES += $(API_DOCS) diff --git a/Documentation/RelNotes/2.17.6.txt b/Documentation/RelNotes/2.17.6.txt new file mode 100644 index 0000000000..2f181e8064 --- /dev/null +++ b/Documentation/RelNotes/2.17.6.txt @@ -0,0 +1,16 @@ +Git v2.17.6 Release Notes +========================= + +This release addresses the security issues CVE-2021-21300. + +Fixes since v2.17.5 +------------------- + + * CVE-2021-21300: + On case-insensitive file systems with support for symbolic links, + if Git is configured globally to apply delay-capable clean/smudge + filters (such as Git LFS), Git could be fooled into running + remote code during a clone. + +Credit for finding and fixing this vulnerability goes to Matheus +Tavares, helped by Johannes Schindelin. diff --git a/Documentation/RelNotes/2.18.5.txt b/Documentation/RelNotes/2.18.5.txt new file mode 100644 index 0000000000..dfb1de4ceb --- /dev/null +++ b/Documentation/RelNotes/2.18.5.txt @@ -0,0 +1,6 @@ +Git v2.18.5 Release Notes +========================= + +This release merges up the fixes that appear in v2.17.6 to address +the security issue CVE-2021-21300; see the release notes for that +version for details. diff --git a/Documentation/RelNotes/2.19.6.txt b/Documentation/RelNotes/2.19.6.txt new file mode 100644 index 0000000000..bcca6cd258 --- /dev/null +++ b/Documentation/RelNotes/2.19.6.txt @@ -0,0 +1,6 @@ +Git v2.19.6 Release Notes +========================= + +This release merges up the fixes that appear in v2.17.6 and +v2.18.5 to address the security issue CVE-2021-21300; see the +release notes for these versions for details. diff --git a/Documentation/RelNotes/2.20.5.txt b/Documentation/RelNotes/2.20.5.txt new file mode 100644 index 0000000000..1dfb784ded --- /dev/null +++ b/Documentation/RelNotes/2.20.5.txt @@ -0,0 +1,6 @@ +Git v2.20.5 Release Notes +========================= + +This release merges up the fixes that appear in v2.17.6, v2.18.5 +and v2.19.6 to address the security issue CVE-2021-21300; see +the release notes for these versions for details. diff --git a/Documentation/RelNotes/2.21.4.txt b/Documentation/RelNotes/2.21.4.txt new file mode 100644 index 0000000000..0089dd6702 --- /dev/null +++ b/Documentation/RelNotes/2.21.4.txt @@ -0,0 +1,6 @@ +Git v2.21.4 Release Notes +========================= + +This release merges up the fixes that appear in v2.17.6, v2.18.5, +v2.19.6 and v2.20.5 to address the security issue CVE-2021-21300; +see the release notes for these versions for details. diff --git a/Documentation/RelNotes/2.22.5.txt b/Documentation/RelNotes/2.22.5.txt new file mode 100644 index 0000000000..6b280d9321 --- /dev/null +++ b/Documentation/RelNotes/2.22.5.txt @@ -0,0 +1,7 @@ +Git v2.22.5 Release Notes +========================= + +This release merges up the fixes that appear in v2.17.6, +v2.18.5, v2.19.6, v2.20.5 and v2.21.4 to address the security +issue CVE-2021-21300; see the release notes for these versions +for details. diff --git a/Documentation/RelNotes/2.23.4.txt b/Documentation/RelNotes/2.23.4.txt new file mode 100644 index 0000000000..6e5424d0da --- /dev/null +++ b/Documentation/RelNotes/2.23.4.txt @@ -0,0 +1,7 @@ +Git v2.23.4 Release Notes +========================= + +This release merges up the fixes that appear in v2.17.6, v2.18.5, +v2.19.6, v2.20.5, v2.21.4 and v2.22.5 to address the security +issue CVE-2021-21300; see the release notes for these versions +for details. diff --git a/Documentation/RelNotes/2.24.4.txt b/Documentation/RelNotes/2.24.4.txt new file mode 100644 index 0000000000..4e216eec2a --- /dev/null +++ b/Documentation/RelNotes/2.24.4.txt @@ -0,0 +1,7 @@ +Git v2.24.4 Release Notes +========================= + +This release merges up the fixes that appear in v2.17.6, v2.18.5, +v2.19.6, v2.20.5, v2.21.4, v2.22.5 and v2.23.4 to address the +security issue CVE-2021-21300; see the release notes for these +versions for details. diff --git a/Documentation/RelNotes/2.25.5.txt b/Documentation/RelNotes/2.25.5.txt new file mode 100644 index 0000000000..fcb9566b15 --- /dev/null +++ b/Documentation/RelNotes/2.25.5.txt @@ -0,0 +1,7 @@ +Git v2.25.5 Release Notes +========================= + +This release merges up the fixes that appear in v2.17.6, v2.18.5, +v2.19.6, v2.20.5, v2.21.4, v2.22.5, v2.23.4 and v2.24.4 to address +the security issue CVE-2021-21300; see the release notes for +these versions for details. diff --git a/Documentation/RelNotes/2.26.3.txt b/Documentation/RelNotes/2.26.3.txt new file mode 100644 index 0000000000..4111c38f0a --- /dev/null +++ b/Documentation/RelNotes/2.26.3.txt @@ -0,0 +1,7 @@ +Git v2.26.3 Release Notes +========================= + +This release merges up the fixes that appear in v2.17.6, v2.18.5, +v2.19.6, v2.20.5, v2.21.4, v2.22.5, v2.23.4, v2.24.4 and v2.25.5 +to address the security issue CVE-2021-21300; see the release +notes for these versions for details. diff --git a/Documentation/RelNotes/2.27.1.txt b/Documentation/RelNotes/2.27.1.txt new file mode 100644 index 0000000000..a1e08a9f72 --- /dev/null +++ b/Documentation/RelNotes/2.27.1.txt @@ -0,0 +1,7 @@ +Git v2.27.1 Release Notes +========================= + +This release merges up the fixes that appear in v2.17.6, v2.18.5, +v2.19.6, v2.20.5, v2.21.4, v2.22.5, v2.23.4, v2.24.4, v2.25.5 +and v2.26.3 to address the security issue CVE-2021-21300; see +the release notes for these versions for details. diff --git a/Documentation/RelNotes/2.28.1.txt b/Documentation/RelNotes/2.28.1.txt new file mode 100644 index 0000000000..8484c8297c --- /dev/null +++ b/Documentation/RelNotes/2.28.1.txt @@ -0,0 +1,7 @@ +Git v2.28.1 Release Notes +========================= + +This release merges up the fixes that appear in v2.17.6, v2.18.5, +v2.19.6, v2.20.5, v2.21.4, v2.22.5, v2.23.4, v2.24.4, v2.25.5, +v2.26.3 and v2.27.1 to address the security issue CVE-2021-21300; +see the release notes for these versions for details. diff --git a/Documentation/RelNotes/2.29.3.txt b/Documentation/RelNotes/2.29.3.txt new file mode 100644 index 0000000000..e10eedb35a --- /dev/null +++ b/Documentation/RelNotes/2.29.3.txt @@ -0,0 +1,8 @@ +Git v2.29.3 Release Notes +========================= + +This release merges up the fixes that appear in v2.17.6, +v2.18.5, v2.19.6, v2.20.5, v2.21.4, v2.22.5, v2.23.4, v2.24.4, +v2.25.5, v2.26.3, v2.27.1 and v2.28.1 to address the security +issue CVE-2021-21300; see the release notes for these versions +for details. diff --git a/Documentation/RelNotes/2.30.2.txt b/Documentation/RelNotes/2.30.2.txt new file mode 100644 index 0000000000..bada398501 --- /dev/null +++ b/Documentation/RelNotes/2.30.2.txt @@ -0,0 +1,8 @@ +Git v2.30.2 Release Notes +========================= + +This release merges up the fixes that appear in v2.17.6, v2.18.5, +v2.19.6, v2.20.5, v2.21.4, v2.22.5, v2.23.4, v2.24.4, v2.25.5, +v2.26.3, v2.27.1, v2.28.1 and v2.29.3 to address the security +issue CVE-2021-21300; see the release notes for these versions +for details. diff --git a/Documentation/RelNotes/2.31.0.txt b/Documentation/RelNotes/2.31.0.txt index 39efc411c7..cf0c7d8d40 100644 --- a/Documentation/RelNotes/2.31.0.txt +++ b/Documentation/RelNotes/2.31.0.txt @@ -16,6 +16,8 @@ Backward incompatible and other important changes * The support for deprecated PCRE1 library has been dropped. + * Fixes for CVE-2021-21300 in Git 2.30.2 (and earlier) is included. + UI, Workflows & Features @@ -60,6 +62,51 @@ UI, Workflows & Features locked and prunable attributes in --porcelain mode, and gained a --verbose option. + * "git clone" tries to locally check out the branch pointed at by + HEAD of the remote repository after it is done, but the protocol + did not convey the information necessary to do so when copying an + empty repository. The protocol v2 learned how to do so. + + * There are other ways than ".." for a single token to denote a + "commit range", namely "^!" and "^-", but "git + range-diff" did not understand them. + + * The "git range-diff" command learned "--(left|right)-only" option + to show only one side of the compared range. + + * "git mergetool" feeds three versions (base, local and remote) of + a conflicted path unmodified. The command learned to optionally + prepare these files with unconflicted parts already resolved. + + * The .mailmap is documented to be read only from the root level of a + working tree, but a stray file in a bare repository also was read + by accident, which has been corrected. + + * "git maintenance" tool learned a new "pack-refs" maintenance task. + + * The error message given when a configuration variable that is + expected to have a boolean value has been improved. + + * Signed commits and tags now allow verification of objects, whose + two object names (one in SHA-1, the other in SHA-256) are both + signed. + + * "git rev-list" command learned "--disk-usage" option. + + * "git {diff,log} --{skip,rotate}-to=" allows the user to + discard diff output for early paths or move them to the end of the + output. + + * "git difftool" learned "--skip-to=" option to restart an + interrupted session from an arbitrary path. + + * "git grep" has been tweaked to be limited to the sparse checkout + paths. + + * "git rebase --[no-]fork-point" gained a configuration variable + rebase.forkPoint so that users do not have to keep specifying a + non-default setting. + Performance, Internal Implementation, Development Support etc. @@ -136,6 +183,52 @@ Performance, Internal Implementation, Development Support etc. * Get rid of "GETTEXT_POISON" support altogether, which may or may not be controversial. + * Introduce an on-disk file to record revindex for packdata, which + traditionally was always created on the fly and only in-core. + + * The commit-graph learned to use corrected commit dates instead of + the generation number to help topological revision traversal. + + * Piecemeal of rewrite of "git bisect" in C continues. + + * When a pager spawned by us exited, the trace log did not record its + exit status correctly, which has been corrected. + + * Removal of GIT_TEST_GETTEXT_POISON continues. + + * The code to implement "git merge-base --independent" was poorly + done and was kept from the very beginning of the feature. + + * Preliminary changes to fsmonitor integration. + + * Performance improvements for rename detection. + + * The common code to deal with "chunked file format" that is shared + by the multi-pack-index and commit-graph files have been factored + out, to help codepaths for both filetypes to become more robust. + + * The approach to "fsck" the incoming objects in "index-pack" is + attractive for performance reasons (we have them already in core, + inflated and ready to be inspected), but fundamentally cannot be + applied fully when we receive more than one pack stream, as a tree + object in one pack may refer to a blob object in another pack as + ".gitmodules", when we want to inspect blobs that are used as + ".gitmodules" file, for example. Teach "index-pack" to emit + objects that must be inspected later and check them in the calling + "fetch-pack" process. + + * The logic to handle "trailer" related placeholders in the + "--format=" mechanisms in the "log" family and "for-each-ref" + family is getting unified. + + * Raise the buffer size used when writing the index file out from + (obviously too small) 8kB to (clearly sufficiently large) 128kB. + + * It is reported that open() on some platforms (e.g. macOS Big Sur) + can return EINTR even though our timers are set up with SA_RESTART. + A workaround has been implemented and enabled for macOS to rerun + open() transparently from the caller when this happens. + Fixes since v2.30 ----------------- @@ -188,5 +281,85 @@ Fixes since v2.30 processing. This has been corrected. (merge f7d42ceec5 js/rebase-i-commit-cleanup-fix later to maint). + * Fix in passing custom args from "git clone" to "upload-pack" on the + other side. + (merge ad6b5fefbd jv/upload-pack-filter-spec-quotefix later to maint). + + * The command line completion (in contrib/) completed "git branch -d" + with branch names, but "git branch -D" offered tagnames in addition, + which has been corrected. "git branch -M" had the same problem. + (merge 27dc071b9a jk/complete-branch-force-delete later to maint). + + * When commands are started from a subdirectory, they may have to + compare the path to the subdirectory (called prefix and found out + from $(pwd)) with the tracked paths. On macOS, $(pwd) and + readdir() yield decomposed path, while the tracked paths are + usually normalized to the precomposed form, causing mismatch. This + has been fixed by taking the same approach used to normalize the + command line arguments. + (merge 5c327502db tb/precompose-prefix-too later to maint). + + * Even though invocations of "die()" were logged to the trace2 + system, "BUG()"s were not, which has been corrected. + (merge 0a9dde4a04 jt/trace2-BUG later to maint). + + * "git grep --untracked" is meant to be "let's ALSO find in these + files on the filesystem" when looking for matches in the working + tree files, and does not make any sense if the primary search is + done against the index, or the tree objects. The "--cached" and + "--untracked" options have been marked as mutually incompatible. + (merge 0c5d83b248 mt/grep-cached-untracked later to maint). + + * Fix "git fsck --name-objects" which apparently has not been used by + anybody who is motivated enough to report breakage. + (merge e89f89361c js/fsck-name-objects-fix later to maint). + + * Avoid individual tests in t5411 from getting affected by each other + by forcing them to use separate output files during the test. + (merge 822ee894f6 jx/t5411-unique-filenames later to maint). + + * Test to make sure "git rev-parse one-thing one-thing" gives + the same thing twice (when one-thing is --since=X). + (merge a5cdca4520 ew/rev-parse-since-test later to maint). + + * When certain features (e.g. grafts) used in the repository are + incompatible with the use of the commit-graph, we used to silently + turned commit-graph off; we now tell the user what we are doing. + (merge c85eec7fc3 js/commit-graph-warning later to maint). + + * Objects that lost references can be pruned away, even when they + have notes attached to it (and these notes will become dangling, + which in turn can be pruned with "git notes prune"). This has been + clarified in the documentation. + (merge fa9ab027ba mz/doc-notes-are-not-anchors later to maint). + + * The error codepath around the "--temp/--prefix" feature of "git + checkout-index" has been improved. + (merge 3f7ba60350 mt/checkout-index-corner-cases later to maint). + + * The "git maintenance register" command had trouble registering bare + repositories, which had been corrected. + + * A handful of multi-word configuration variable names in + documentation that are spelled in all lowercase have been corrected + to use the more canonical camelCase. + (merge 7dd0eaa39c dl/doc-config-camelcase later to maint). + + * "git push $there --delete ''" should have been diagnosed as an + error, but instead turned into a matching push, which has been + corrected. + (merge 20e416409f jc/push-delete-nothing later to maint). + + * Test script modernization. + (merge 488acf15df sv/t7001-modernize later to maint). + + * An under-allocation for the untracked cache data has been corrected. + (merge 6347d649bc jh/untracked-cache-fix later to maint). + * Other code cleanup, docfix, build fix, etc. (merge e3f5da7e60 sg/t7800-difftool-robustify later to maint). + (merge 9d336655ba js/doc-proto-v2-response-end later to maint). + (merge 1b5b8cf072 jc/maint-column-doc-typofix later to maint). + (merge 3a837b58e3 cw/pack-config-doc later to maint). + (merge 01168a9d89 ug/doc-commit-approxidate later to maint). + (merge b865734760 js/params-vs-args later to maint). diff --git a/Documentation/RelNotes/2.31.1.txt b/Documentation/RelNotes/2.31.1.txt new file mode 100644 index 0000000000..f9b06b8e1b --- /dev/null +++ b/Documentation/RelNotes/2.31.1.txt @@ -0,0 +1,27 @@ +Git 2.31.1 Release Notes +======================== + +Fixes since v2.31 +----------------- + + * The fsmonitor interface read from its input without making sure + there is something to read from. This bug is new in 2.31 + timeframe. + + * The data structure used by fsmonitor interface was not properly + duplicated during an in-core merge, leading to use-after-free etc. + + * "git bisect" reimplemented more in C during 2.30 timeframe did not + take an annotated tag as a good/bad endpoint well. This regression + has been corrected. + + * Fix macros that can silently inject unintended null-statements. + + * CALLOC_ARRAY() macro replaces many uses of xcalloc(). + + * Update insn in Makefile comments to run fuzz-all target. + + * Fix a corner case bug in "git mv" on case insensitive systems, + which was introduced in 2.29 timeframe. + +Also contains various documentation updates and code clean-ups. diff --git a/Documentation/RelNotes/2.32.0.txt b/Documentation/RelNotes/2.32.0.txt new file mode 100644 index 0000000000..7c6aabeb1f --- /dev/null +++ b/Documentation/RelNotes/2.32.0.txt @@ -0,0 +1,170 @@ +Git 2.32 Release Notes +====================== + +Backward compatibility notes +---------------------------- + + * ".gitattributes", ".gitignore", and ".mailmap" files that are + symbolic links are ignored. + + +Updates since v2.31 +------------------- + +UI, Workflows & Features + + * It does not make sense to make ".gitattributes", ".gitignore" and + ".mailmap" symlinks, as they are supposed to be usable from the + object store (think: bare repositories where HEAD:.mailmap etc. are + used). When these files are symbolic links, we used to read the + contents of the files pointed by them by mistake, which has been + corrected. + + * "git stash show" learned to optionally show untracked part of the + stash. + + * "git log --format='...'" learned "%(describe)" placeholder. + + * "git repack" so far has been only capable of repacking everything + under the sun into a single pack (or split by size). A cleverer + strategy to reduce the cost of repacking a repository has been + introduced. + + * The http codepath learned to let the credential layer to cache the + password used to unlock a certificate that has successfully been + used. + + * "git commit --fixup=", which was to tweak the changes made + to the contents while keeping the original log message intact, + learned "--fixup=(amend|reword):", that can be used to + tweak both the message and the contents, and only the message, + respectively. + + * When accessing a server with a URL like https://user:pass@site/, we + did not to fall back to the basic authentication with the + credential material embedded in the URL after the "Negotiate" + authentication failed. Now we do. + + * "git send-email" learned to honor the core.hooksPath configuration. + + * "git format-patch -v" learned to allow a reroll count that is + not an integer. + + * "git commit" learned "--trailer [=]" option; together + with the interpret-trailers command, this will make it easier to + support custom trailers. + + +Performance, Internal Implementation, Development Support etc. + + * Rename detection rework continues. + + * GIT_TEST_FAIL_PREREQS is a mechanism to skip test pieces with + prerequisites to catch broken tests that depend on the side effects + of optional pieces, but did not work at all when negative + prerequisites were involved. + (merge 27d578d904 jk/fail-prereq-testfix later to maint). + + * "git diff-index" codepath has been taught to trust fsmonitor status + to reduce number of lstat() calls. + (merge 7e5aa13d2c nk/diff-index-fsmonitor later to maint). + + * Reorganize Makefile to allow building git.o and other essential + objects without extra stuff needed only for testing. + + * Preparatory API changes for parallel checkout. + + * A simple IPC interface gets introduced to build services like + fsmonitor on top. + + * Fsck API clean-up. + + * SECURITY.md that is facing individual contributors and end users + has been introduced. Also a procedure to follow when preparing + embargoed releases has been spelled out. + (merge 09420b7648 js/security-md later to maint). + + * Optimize "rev-list --use-bitmap-index --objects" corner case that + uses negative tags as the stopping points. + + * CMake update for vsbuild. + + +Fixes since v2.31 +----------------- + + * The fsmonitor interface read from its input without making sure + there is something to read from. This bug is new in 2.31 + timeframe. + + * The data structure used by fsmonitor interface was not properly + duplicated during an in-core merge, leading to use-after-free etc. + + * "git bisect" reimplemented more in C during 2.30 timeframe did not + take an annotated tag as a good/bad endpoint well. This regression + has been corrected. + + * Fix macros that can silently inject unintended null-statements. + + * CALLOC_ARRAY() macro replaces many uses of xcalloc(). + + * Update insn in Makefile comments to run fuzz-all target. + + * Fix a corner case bug in "git mv" on case insensitive systems, + which was introduced in 2.29 timeframe. + + * We had a code to diagnose and die cleanly when a required + clean/smudge filter is missing, but an assert before that + unnecessarily fired, hiding the end-user facing die() message. + (merge 6fab35f748 mt/cleanly-die-upon-missing-required-filter later to maint). + + * Update C code that sets a few configuration variables when a remote + is configured so that it spells configuration variable names in the + canonical camelCase. + (merge 0f1da600e6 ab/remote-write-config-in-camel-case later to maint). + + * A new configuration variable has been introduced to allow choosing + which version of the generation number gets used in the + commit-graph file. + (merge 702110aac6 ds/commit-graph-generation-config later to maint). + + * Perf test update to work better in secondary worktrees. + (merge 36e834abc1 jk/perf-in-worktrees later to maint). + + * Updates to memory allocation code around the use of pcre2 library. + (merge c1760352e0 ab/grep-pcre2-allocfix later to maint). + + * "git -c core.bare=false clone --bare ..." would have segfaulted, + which has been corrected. + (merge 75555676ad bc/clone-bare-with-conflicting-config later to maint). + + * When "git checkout" removes a path that does not exist in the + commit it is checking out, it wasn't careful enough not to follow + symbolic links, which has been corrected. + (merge fab78a0c3d mt/checkout-remove-nofollow later to maint). + + * A few option description strings started with capital letters, + which were corrected. + (merge 5ee90326dc cc/downcase-opt-help later to maint). + + * Plug or annotate remaining leaks that trigger while running the + very basic set of tests. + (merge 68ffe095a2 ah/plugleaks later to maint). + + * The hashwrite() API uses a buffering mechanism to avoid calling + write(2) too frequently. This logic has been refactored to be + easier to understand. + (merge ddaf1f62e3 ds/clarify-hashwrite later to maint). + + * Other code cleanup, docfix, build fix, etc. + (merge f451960708 dl/cat-file-doc-cleanup later to maint). + (merge 12604a8d0c sv/t9801-test-path-is-file-cleanup later to maint). + (merge ea7e63921c jr/doc-ignore-typofix later to maint). + (merge 23c781f173 ps/update-ref-trans-hook-doc later to maint). + (merge 42efa1231a jk/filter-branch-sha256 later to maint). + (merge 4c8e3dca6e tb/push-simple-uses-branch-merge-config later to maint). + (merge 6534d436a2 bs/asciidoctor-installation-hints later to maint). + (merge 47957485b3 ab/read-tree later to maint). + (merge 2be927f3d1 ab/diff-no-index-tests later to maint). + (merge 76593c09bb ab/detox-gettext-tests later to maint). + (merge 28e29ee38b jc/doc-format-patch-clarify later to maint). diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt index dc3bceb6d1..117f4cf806 100644 --- a/Documentation/blame-options.txt +++ b/Documentation/blame-options.txt @@ -1,6 +1,6 @@ -b:: Show blank SHA-1 for boundary commits. This can also - be controlled via the `blame.blankboundary` config option. + be controlled via the `blame.blankBoundary` config option. --root:: Do not treat root commits as boundaries. This can also be diff --git a/Documentation/config.txt b/Documentation/config.txt index 6ba50b1104..bf82766a6a 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -46,7 +46,7 @@ Subsection names are case sensitive and can contain any characters except newline and the null byte. Doublequote `"` and backslash can be included by escaping them as `\"` and `\\`, respectively. Backslashes preceding other characters are dropped when reading; for example, `\t` is read as -`t` and `\0` is read as `0` Section headers cannot span multiple lines. +`t` and `\0` is read as `0`. Section headers cannot span multiple lines. Variables may belong directly to a section or to a given subsection. You can have `[section]` if you have `[section "subsection"]`, but you don't need to. @@ -398,6 +398,8 @@ include::config/interactive.txt[] include::config/log.txt[] +include::config/lsrefs.txt[] + include::config/mailinfo.txt[] include::config/mailmap.txt[] diff --git a/Documentation/config/clone.txt b/Documentation/config/clone.txt index 47de36a5fe..7bcfbd18a5 100644 --- a/Documentation/config/clone.txt +++ b/Documentation/config/clone.txt @@ -2,3 +2,7 @@ clone.defaultRemoteName:: The name of the remote to create when cloning a repository. Defaults to `origin`, and can be overridden by passing the `--origin` command-line option to linkgit:git-clone[1]. + +clone.rejectShallow:: + Reject to clone a repository if it is a shallow one, can be overridden by + passing option `--reject-shallow` in command line. See linkgit:git-clone[1] diff --git a/Documentation/config/commitgraph.txt b/Documentation/config/commitgraph.txt index 4582c39fc4..30604e4a4c 100644 --- a/Documentation/config/commitgraph.txt +++ b/Documentation/config/commitgraph.txt @@ -1,3 +1,9 @@ +commitGraph.generationVersion:: + Specifies the type of generation number version to use when writing + or reading the commit-graph file. If version 1 is specified, then + the corrected commit dates will not be written or read. Defaults to + 2. + commitGraph.maxNewFilters:: Specifies the default value for the `--max-new-filters` option of `git commit-graph write` (c.f., linkgit:git-commit-graph[1]). diff --git a/Documentation/config/init.txt b/Documentation/config/init.txt index dc77f8c844..79c79d6617 100644 --- a/Documentation/config/init.txt +++ b/Documentation/config/init.txt @@ -4,4 +4,4 @@ init.templateDir:: init.defaultBranch:: Allows overriding the default branch name e.g. when initializing - a new repository or when cloning an empty repository. + a new repository. diff --git a/Documentation/config/lsrefs.txt b/Documentation/config/lsrefs.txt new file mode 100644 index 0000000000..adeda0f24d --- /dev/null +++ b/Documentation/config/lsrefs.txt @@ -0,0 +1,9 @@ +lsrefs.unborn:: + May be "advertise" (the default), "allow", or "ignore". If "advertise", + the server will respond to the client sending "unborn" (as described in + protocol-v2.txt) and will advertise support for this feature during the + protocol v2 capability advertisement. "allow" is the same as + "advertise" except that the server will not advertise support for this + feature; this is useful for load-balanced servers that cannot be + updated atomically (for example), since the administrator could + configure "allow", then after a delay, configure "advertise". diff --git a/Documentation/config/maintenance.txt b/Documentation/config/maintenance.txt index a5ead09e4b..18f0562131 100644 --- a/Documentation/config/maintenance.txt +++ b/Documentation/config/maintenance.txt @@ -15,8 +15,9 @@ maintenance.strategy:: * `none`: This default setting implies no task are run at any schedule. * `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 and the - `loose-objects` and `incremental-repack` tasks daily. + task, but runs the `prefetch` and `commit-graph` tasks hourly, the + `loose-objects` and `incremental-repack` tasks daily, and the `pack-refs` + task weekly. maintenance..enabled:: This boolean config option controls whether the maintenance task diff --git a/Documentation/config/mergetool.txt b/Documentation/config/mergetool.txt index 16a27443a3..cafbbef46a 100644 --- a/Documentation/config/mergetool.txt +++ b/Documentation/config/mergetool.txt @@ -13,6 +13,11 @@ mergetool..cmd:: merged; 'MERGED' contains the name of the file to which the merge tool should write the results of a successful merge. +mergetool..hideResolved:: + Allows the user to override the global `mergetool.hideResolved` value + for a specific tool. See `mergetool.hideResolved` for the full + description. + mergetool..trustExitCode:: For a custom merge command, specify whether the exit code of the merge command can be used to determine whether the merge was @@ -40,6 +45,16 @@ mergetool.meld.useAutoMerge:: value of `false` avoids using `--auto-merge` altogether, and is the default value. +mergetool.hideResolved:: + During a merge Git will automatically resolve as many conflicts as + possible and write the 'MERGED' file containing conflict markers around + any conflicts that it cannot resolve; 'LOCAL' and 'REMOTE' normally + represent the versions of the file from before Git's conflict + resolution. This flag causes 'LOCAL' and 'REMOTE' to be overwriten so + that only the unresolved conflicts are presented to the merge tool. Can + be configured per-tool via the `mergetool..hideResolved` + configuration variable. Defaults to `false`. + mergetool.keepBackup:: After performing a merge, the original file with conflict markers can be saved as a file with a `.orig` extension. If this variable diff --git a/Documentation/config/pack.txt b/Documentation/config/pack.txt index 837f1b1679..3da4ea98e2 100644 --- a/Documentation/config/pack.txt +++ b/Documentation/config/pack.txt @@ -133,3 +133,10 @@ pack.writeBitmapHashCache:: between an older, bitmapped pack and objects that have been pushed since the last gc). The downside is that it consumes 4 bytes per object of disk space. Defaults to true. + +pack.writeReverseIndex:: + When true, git will write a corresponding .rev file (see: + link:../technical/pack-format.html[Documentation/technical/pack-format.txt]) + for each new packfile that it writes in all places except for + linkgit:git-fast-import[1] and in the bulk checkin mechanism. + Defaults to false. diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt index 7f7a07d22f..8c979cb20f 100644 --- a/Documentation/config/rebase.txt +++ b/Documentation/config/rebase.txt @@ -1,10 +1,3 @@ -rebase.useBuiltin:: - Unused configuration variable. Used in Git versions 2.20 and - 2.21 as an escape hatch to enable the legacy shellscript - implementation of rebase. Now the built-in rewrite of it in C - is always used. Setting this will emit a warning, to alert any - remaining users that setting this now does nothing. - rebase.backend:: Default backend to use for rebasing. Possible choices are 'apply' or 'merge'. In the future, if the merge backend gains @@ -68,3 +61,6 @@ rebase.rescheduleFailedExec:: Automatically reschedule `exec` commands that failed. This only makes sense in interactive mode (or when an `--exec` option was provided). This is the same as specifying the `--reschedule-failed-exec` option. + +rebase.forkPoint:: + If set to false set `--no-fork-point` option by default. diff --git a/Documentation/config/stash.txt b/Documentation/config/stash.txt index 00eb35434e..413f907cba 100644 --- a/Documentation/config/stash.txt +++ b/Documentation/config/stash.txt @@ -5,6 +5,11 @@ stash.useBuiltin:: is always used. Setting this will emit a warning, to alert any remaining users that setting this now does nothing. +stash.showIncludeUntracked:: + If this is set to true, the `git stash show` command without an + option will show the untracked files of a stash entry. Defaults to + false. See description of 'show' command in linkgit:git-stash[1]. + stash.showPatch:: If this is set to true, the `git stash show` command without an option will show the stash entry in patch form. Defaults to false. diff --git a/Documentation/date-formats.txt b/Documentation/date-formats.txt index f1097fac69..99c455f51c 100644 --- a/Documentation/date-formats.txt +++ b/Documentation/date-formats.txt @@ -1,10 +1,7 @@ DATE FORMATS ------------ -The `GIT_AUTHOR_DATE`, `GIT_COMMITTER_DATE` environment variables -ifdef::git-commit[] -and the `--date` option -endif::git-commit[] +The `GIT_AUTHOR_DATE` and `GIT_COMMITTER_DATE` environment variables support the following date formats: Git internal format:: @@ -26,3 +23,9 @@ ISO 8601:: + NOTE: In addition, the date part is accepted in the following formats: `YYYY.MM.DD`, `MM/DD/YYYY` and `DD.MM.YYYY`. + +ifdef::git-commit[] +In addition to recognizing all date formats above, the `--date` option +will also try to make sense of other, more human-centric date formats, +such as relative dates like "yesterday" or "last Friday at noon". +endif::git-commit[] diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index e5733ccb2d..aa2b5c11f2 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -700,6 +700,14 @@ matches a pattern if removing any number of the final pathname components matches the pattern. For example, the pattern "`foo*bar`" matches "`fooasdfbar`" and "`foo/bar/baz/asdf`" but not "`foobarx`". +--skip-to=:: +--rotate-to=:: + Discard the files before the named from the output + (i.e. 'skip to'), or move them to the end of the output + (i.e. 'rotate to'). These were invented primarily for use + of the `git difftool` command, and may not be very useful + otherwise. + ifndef::git-format-patch[] -R:: Swap two inputs; that is, show differences from index or diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index 06bc063542..decd8ae122 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -79,7 +79,7 @@ OPTIONS Pass `-u` flag to 'git mailinfo' (see 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 project's + `i18n.commitEncoding` can be used to specify project's preferred encoding if it is not UTF-8). + This was optional in prior versions of git, but now it is the diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index adaa1782a8..94dc9a54f2 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -78,8 +78,8 @@ renaming. If exists, -M must be used to force the rename to happen. The `-c` and `-C` options have the exact same semantics as `-m` and -`-M`, except instead of the branch being renamed it along with its -config and reflog will be copied to a new name. +`-M`, except instead of the branch being renamed, it will be copied to a +new name, along with its config and reflog. With a `-d` or `-D` option, `` will be deleted. You may specify more than one branch for deletion. If the branch currently @@ -153,7 +153,7 @@ OPTIONS --column[=]:: --no-column:: Display branch listing in columns. See configuration variable - column.branch for option syntax.`--column` and `--no-column` + `column.branch` for option syntax. `--column` and `--no-column` without options are equivalent to 'always' and 'never' respectively. + This option is only applicable in non-verbose mode. diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt index 8e192d87db..4eb0421b3f 100644 --- a/Documentation/git-cat-file.txt +++ b/Documentation/git-cat-file.txt @@ -35,42 +35,42 @@ OPTIONS -t:: Instead of the content, show the object type identified by - . + ``. -s:: Instead of the content, show the object size identified by - . + ``. -e:: - Exit with zero status if exists and is a valid - object. If is of an invalid format exit with non-zero and + Exit with zero status if `` exists and is a valid + object. If `` is of an invalid format exit with non-zero and emits an error on stderr. -p:: - Pretty-print the contents of based on its type. + Pretty-print the contents of `` based on its type. :: - Typically this matches the real type of but asking + Typically this matches the real type of `` but asking for a type that can trivially be dereferenced from the given - is also permitted. An example is to ask for a - "tree" with being a commit object that contains it, - or to ask for a "blob" with being a tag object that + `` is also permitted. An example is to ask for a + "tree" with `` being a commit object that contains it, + or to ask for a "blob" with `` being a tag object that points at it. --textconv:: Show the content as transformed by a textconv filter. In this case, - has to be of the form :, or : in + `` has to be of the form `:`, or `:` in order to apply the filter to the content recorded in the index at - . + ``. --filters:: Show the content as converted by the filters configured in - the current working tree for the given (i.e. smudge filters, - end-of-line conversion, etc). In this case, has to be of - the form :, or :. + the current working tree for the given `` (i.e. smudge filters, + end-of-line conversion, etc). In this case, `` has to be of + the form `:`, or `:`. --path=:: - For use with --textconv or --filters, to allow specifying an object + For use with `--textconv` or `--filters`, to allow specifying an object name and a path separately, e.g. when it is difficult to figure out the revision from which the blob came. @@ -115,15 +115,15 @@ OPTIONS repository. --allow-unknown-type:: - Allow -s or -t to query broken/corrupt objects of unknown type. + Allow `-s` or `-t` to query broken/corrupt objects of unknown type. --follow-symlinks:: - With --batch or --batch-check, follow symlinks inside the + With `--batch` or `--batch-check`, follow symlinks inside the repository when requesting objects with extended SHA-1 expressions of the form tree-ish:path-in-tree. Instead of providing output about the link itself, provide output about the linked-to object. If a symlink points outside the - tree-ish (e.g. a link to /foo or a root-level link to ../foo), + tree-ish (e.g. a link to `/foo` or a root-level link to `../foo`), the portion of the link which is outside the tree will be printed. + @@ -175,15 +175,15 @@ respectively print: OUTPUT ------ -If `-t` is specified, one of the . +If `-t` is specified, one of the ``. -If `-s` is specified, the size of the in bytes. +If `-s` is specified, the size of the `` in bytes. -If `-e` is specified, no output, unless the is malformed. +If `-e` is specified, no output, unless the `` is malformed. -If `-p` is specified, the contents of are pretty-printed. +If `-p` is specified, the contents of `` are pretty-printed. -If is specified, the raw (though uncompressed) contents of the +If `` is specified, the raw (though uncompressed) contents of the `` will be returned. BATCH OUTPUT @@ -200,7 +200,7 @@ object, with placeholders of the form `%(atom)` expanded, followed by a newline. The available atoms are: `objectname`:: - The 40-hex object name of the object. + The full hex representation of the object name. `objecttype`:: The type of the object (the same as `cat-file -t` reports). @@ -215,8 +215,9 @@ newline. The available atoms are: `deltabase`:: If the object is stored as a delta on-disk, this expands to the - 40-hex sha1 of the delta base object. Otherwise, expands to the - null sha1 (40 zeroes). See `CAVEATS` below. + full hex representation of the delta base object name. + Otherwise, expands to the null OID (all zeroes). See `CAVEATS` + below. `rest`:: If this atom is used in the output string, input lines are split @@ -235,14 +236,14 @@ newline. For example, `--batch` without a custom format would produce: ------------ - SP SP LF + SP SP LF LF ------------ Whereas `--batch-check='%(objectname) %(objecttype)'` would produce: ------------ - SP LF + SP LF ------------ If a name is specified on stdin that cannot be resolved to an object in @@ -258,7 +259,7 @@ If a name is specified that might refer to more than one object (an ambiguous sh SP ambiguous LF ------------ -If --follow-symlinks is used, and a symlink in the repository points +If `--follow-symlinks` is used, and a symlink in the repository points outside the repository, then `cat-file` will ignore any custom format and print: @@ -267,11 +268,11 @@ symlink SP LF LF ------------ -The symlink will either be absolute (beginning with a /), or relative -to the tree root. For instance, if dir/link points to ../../foo, then - will be ../foo. is the size of the symlink in bytes. +The symlink will either be absolute (beginning with a `/`), or relative +to the tree root. For instance, if dir/link points to `../../foo`, then +`` will be `../foo`. `` is the size of the symlink in bytes. -If --follow-symlinks is used, the following error messages will be +If `--follow-symlinks` is used, the following error messages will be displayed: ------------ diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt index 02d9c19cec..3fe3810f1c 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -15,7 +15,7 @@ SYNOPSIS [--dissociate] [--separate-git-dir ] [--depth ] [--[no-]single-branch] [--no-tags] [--recurse-submodules[=]] [--[no-]shallow-submodules] - [--[no-]remote-submodules] [--jobs ] [--sparse] + [--[no-]remote-submodules] [--jobs ] [--sparse] [--[no-]reject-shallow] [--filter=] [--] [] @@ -149,6 +149,11 @@ objects from the source repository into a pack in the cloned repository. --no-checkout:: No checkout of HEAD is performed after the clone is complete. +--[no-]reject-shallow:: + Fail if the source repository is a shallow repository. + The 'clone.rejectShallow' configuration variable can be used to + specify the default. + --bare:: Make a 'bare' Git repository. That is, instead of creating `` and placing the administrative diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 17150fa7ea..340c5fbb48 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -9,12 +9,13 @@ SYNOPSIS -------- [verse] 'git commit' [-a | --interactive | --patch] [-s] [-v] [-u] [--amend] - [--dry-run] [(-c | -C | --fixup | --squash) ] + [--dry-run] [(-c | -C | --squash) | --fixup [(amend|reword):])] [-F | -m ] [--reset-author] [--allow-empty] [--allow-empty-message] [--no-verify] [-e] [--author=] [--date=] [--cleanup=] [--[no-]status] [-i | -o] [--pathspec-from-file= [--pathspec-file-nul]] - [-S[]] [--] [...] + [(--trailer [(=|:)])...] [-S[]] + [--] [...] DESCRIPTION ----------- @@ -86,11 +87,44 @@ OPTIONS Like '-C', but with `-c` the editor is invoked, so that the user can further edit the commit message. ---fixup=:: - Construct a commit message for use with `rebase --autosquash`. - The commit message will be the subject line from the specified - commit with a prefix of "fixup! ". See linkgit:git-rebase[1] - for details. +--fixup=[(amend|reword):]:: + Create a new commit which "fixes up" `` when applied with + `git rebase --autosquash`. Plain `--fixup=` creates a + "fixup!" commit which changes the content of `` but leaves + its log message untouched. `--fixup=amend:` is similar but + creates an "amend!" commit which also replaces the log message of + `` with the log message of the "amend!" commit. + `--fixup=reword:` creates an "amend!" commit which + replaces the log message of `` with its own log message + but makes no changes to the content of ``. ++ +The commit created by plain `--fixup=` has a subject +composed of "fixup!" followed by the subject line from , +and is recognized specially by `git rebase --autosquash`. The `-m` +option may be used to supplement the log message of the created +commit, but the additional commentary will be thrown away once the +"fixup!" commit is squashed into `` by +`git rebase --autosquash`. ++ +The commit created by `--fixup=amend:` is similar but its +subject is instead prefixed with "amend!". The log message of + is copied into the log message of the "amend!" commit and +opened in an editor so it can be refined. When `git rebase +--autosquash` squashes the "amend!" commit into ``, the +log message of `` is replaced by the refined log message +from the "amend!" commit. It is an error for the "amend!" commit's +log message to be empty unless `--allow-empty-message` is +specified. ++ +`--fixup=reword:` is shorthand for `--fixup=amend: +--only`. It creates an "amend!" commit with only a log message +(ignoring any changes staged in the index). When squashed by `git +rebase --autosquash`, it replaces the log message of `` +without making any other changes. ++ +Neither "fixup!" nor "amend!" commits change authorship of +`` when applied by `git rebase --autosquash`. +See linkgit:git-rebase[1] for details. --squash=:: Construct a commit message for use with `rebase --autosquash`. @@ -166,6 +200,17 @@ The `-m` option is mutually exclusive with `-c`, `-C`, and `-F`. include::signoff-option.txt[] +--trailer [(=|:)]:: + Specify a (, ) pair that should be applied as a + trailer. (e.g. `git commit --trailer "Signed-off-by:C O Mitter \ + " --trailer "Helped-by:C O Mitter \ + "` will add the "Signed-off-by" trailer + and the "Helped-by" trailer to the commit message.) + The `trailer.*` configuration variables + (linkgit:git-interpret-trailers[1]) can be used to define if + a duplicated trailer is omitted, where in the run of trailers + each trailer would appear, and other details. + -n:: --no-verify:: This option bypasses the pre-commit and commit-msg hooks. diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt index 484c485fd0..143b0c49d7 100644 --- a/Documentation/git-difftool.txt +++ b/Documentation/git-difftool.txt @@ -34,6 +34,14 @@ OPTIONS This is the default behaviour; the option is provided to override any configuration settings. +--rotate-to=:: + Start showing the diff for the given path, + the paths before it will move to end and output. + +--skip-to=:: + Start showing the diff for the given path, skipping all + the paths before it. + -t :: --tool=:: Use the diff tool specified by . Valid values include diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt index 2962f85a50..2ae2478de7 100644 --- a/Documentation/git-for-each-ref.txt +++ b/Documentation/git-for-each-ref.txt @@ -260,11 +260,9 @@ contents:lines=N:: The first `N` lines of the message. Additionally, the trailers as interpreted by linkgit:git-interpret-trailers[1] -are obtained as `trailers` (or by using the historical alias -`contents:trailers`). Non-trailer lines from the trailer block can be omitted -with `trailers:only`. Whitespace-continuations can be removed from trailers so -that each trailer appears on a line by itself with its full content with -`trailers:unfold`. Both can be used together as `trailers:unfold,only`. +are obtained as `trailers[:options]` (or by using the historical alias +`contents:trailers[:options]`). For valid [:option] values see `trailers` +section of linkgit:git-log[1]. For sorting purposes, fields with numeric values sort in numeric order (`objectsize`, `authordate`, `committerdate`, `creatordate`, `taggerdate`). diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 3e49bf2210..911da181a1 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -36,11 +36,28 @@ SYNOPSIS DESCRIPTION ----------- -Prepare each commit with its patch in -one file per commit, formatted to resemble UNIX mailbox format. +Prepare each commit with its "patch" in +one "message" per commit, formatted to resemble a UNIX mailbox. The output of this command is convenient for e-mail submission or for use with 'git am'. +A "message" generated by the command consists of three parts: + +* A brief metadata header that begins with `From ` + with a fixed `Mon Sep 17 00:00:00 2001` datestamp to help programs + like "file(1)" to recognize that the file is an output from this + command, fields that record the author identity, the author date, + and the title of the change (taken from the first paragraph of the + commit log message). + +* The second and subsequent paragraphs of the commit log message. + +* The "patch", which is the "diff -p --stat" output (see + linkgit:git-diff[1]) between the commit and its parent. + +The log message and the patch is separated by a line with a +three-dash line. + There are two ways to specify which commits to operate on. 1. A single commit, , specifies that the commits leading @@ -221,6 +238,11 @@ populated with placeholder text. `--subject-prefix` option) has ` v` appended to it. E.g. `--reroll-count=4` may produce `v4-0001-add-makefile.patch` file that has "Subject: [PATCH v4 1/20] Add makefile" in it. + `` does not have to be an integer (e.g. "--reroll-count=4.4", + or "--reroll-count=4rev2" are allowed), but the downside of + using such a reroll-count is that the range-diff/interdiff + with the previous version does not state exactly which + version the new interation is compared against. --to=:: Add a `To:` header to the email headers. This is in addition diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt index 0c114ad1ca..853967dea0 100644 --- a/Documentation/git-gc.txt +++ b/Documentation/git-gc.txt @@ -117,12 +117,14 @@ NOTES 'git gc' tries very hard not to delete objects that are referenced anywhere in your repository. In particular, it will keep not only objects referenced by your current set of branches and tags, but also -objects referenced by the index, remote-tracking branches, notes saved -by 'git notes' under refs/notes/, reflogs (which may reference commits -in branches that were later amended or rewound), and anything else in -the refs/* namespace. If you are expecting some objects to be deleted -and they aren't, check all of those locations and decide whether it -makes sense in your case to remove those references. +objects referenced by the index, remote-tracking branches, reflogs +(which may reference commits in branches that were later amended or +rewound), and anything else in the refs/* namespace. Note that a note +(of the kind created by 'git notes') attached to an object does not +contribute in keeping the object alive. If you are expecting some +objects to be deleted and they aren't, check all of those locations +and decide whether it makes sense in your case to remove those +references. On the other hand, when 'git gc' runs concurrently with another process, there is a risk of it deleting an object that the other process is using diff --git a/Documentation/git-http-fetch.txt b/Documentation/git-http-fetch.txt index 4deb4893f5..9fa17b60e4 100644 --- a/Documentation/git-http-fetch.txt +++ b/Documentation/git-http-fetch.txt @@ -41,11 +41,17 @@ commit-id:: ['\t'] --packfile=:: - Instead of a commit id on the command line (which is not expected in + For internal use only. Instead of a commit id on the command + line (which is not expected in this case), 'git http-fetch' fetches the packfile directly at the given URL and uses index-pack to generate corresponding .idx and .keep files. The hash is used to determine the name of the temporary file and is - arbitrary. The output of index-pack is printed to stdout. + arbitrary. The output of index-pack is printed to stdout. Requires + --index-pack-args. + +--index-pack-args=:: + For internal use only. The command to run on the contents of the + downloaded pack. Arguments are URL-encoded separated by spaces. --recover:: Verify that everything reachable from target is fetched. Used after diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt index af0c26232c..7fa74b9e79 100644 --- a/Documentation/git-index-pack.txt +++ b/Documentation/git-index-pack.txt @@ -9,17 +9,18 @@ git-index-pack - Build pack index file for an existing packed archive SYNOPSIS -------- [verse] -'git index-pack' [-v] [-o ] +'git index-pack' [-v] [-o ] [--[no-]rev-index] 'git index-pack' --stdin [--fix-thin] [--keep] [-v] [-o ] - [] + [--[no-]rev-index] [] DESCRIPTION ----------- Reads a packed archive (.pack) from the specified file, and -builds a pack index file (.idx) for it. The packed archive -together with the pack index can then be placed in the -objects/pack/ directory of a Git repository. +builds a pack index file (.idx) for it. Optionally writes a +reverse-index (.rev) for the specified pack. The packed +archive together with the pack index can then be placed in +the objects/pack/ directory of a Git repository. OPTIONS @@ -35,6 +36,13 @@ OPTIONS fails if the name of packed archive does not end with .pack). +--[no-]rev-index:: + When this flag is provided, generate a reverse index + (a `.rev` file) corresponding to the given pack. If + `--verify` is given, ensure that the existing + reverse index is correct. Takes precedence over + `pack.writeReverseIndex`. + --stdin:: When this flag is provided, the pack is read from stdin instead and a copy is then written to . If @@ -78,7 +86,12 @@ OPTIONS Die if the pack contains broken links. For internal use only. --fsck-objects:: - Die if the pack contains broken objects. For internal use only. + For internal use only. ++ +Die if the pack contains broken objects. If the pack contains a tree +pointing to a .gitmodules blob that does not exist, prints the hash of +that blob (for the caller to check) after the hash that goes into the +name of the pack/idx file (see "Notes"). --threads=:: Specifies the number of threads to spawn when resolving diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt index 7a6aed0e30..d343f040f5 100644 --- a/Documentation/git-mailinfo.txt +++ b/Documentation/git-mailinfo.txt @@ -53,7 +53,7 @@ character. The commit log message, author name and author email are taken from the e-mail, and after minimally decoding MIME transfer encoding, re-coded in the charset specified by - i18n.commitencoding (defaulting to UTF-8) by transliterating + `i18n.commitEncoding` (defaulting to UTF-8) by transliterating them. This used to be optional but now it is the default. + Note that the patch is always used as-is without charset @@ -61,7 +61,7 @@ conversion, even with this flag. --encoding=:: Similar to -u. But when re-coding, the charset specified here is - used instead of the one specified by i18n.commitencoding or UTF-8. + used instead of the one specified by `i18n.commitEncoding` or UTF-8. -n:: Disable all charset re-coding of the metadata. diff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt index 3b432171d6..80ddd33ceb 100644 --- a/Documentation/git-maintenance.txt +++ b/Documentation/git-maintenance.txt @@ -145,6 +145,12 @@ incremental-repack:: which is a special case that attempts to repack all pack-files into a single pack-file. +pack-refs:: + The `pack-refs` task collects the loose reference files and + collects them into a single file. This speeds up operations that + need to iterate across many references. See linkgit:git-pack-refs[1] + for more information. + OPTIONS ------- --auto:: diff --git a/Documentation/git-mergetool--lib.txt b/Documentation/git-mergetool--lib.txt index 4da9d24096..3e8f59ac0e 100644 --- a/Documentation/git-mergetool--lib.txt +++ b/Documentation/git-mergetool--lib.txt @@ -38,6 +38,10 @@ get_merge_tool_cmd:: get_merge_tool_path:: returns the custom path for a merge tool. +initialize_merge_tool:: + bring merge tool specific functions into scope so they can be used or + overridden. + run_merge_tool:: launches a merge tool given the tool name and a true/false flag to indicate whether a merge base is present. diff --git a/Documentation/git-mergetool.txt b/Documentation/git-mergetool.txt index 6b14702e78..e587c7763a 100644 --- a/Documentation/git-mergetool.txt +++ b/Documentation/git-mergetool.txt @@ -99,6 +99,10 @@ success of the resolution after the custom tool has exited. (see linkgit:git-config[1]). To cancel `diff.orderFile`, use `-O/dev/null`. +CONFIGURATION +------------- +include::config/mergetool.txt[] + TEMPORARY FILES --------------- `git mergetool` creates `*.orig` backup files while resolving merges. diff --git a/Documentation/git-multi-pack-index.txt b/Documentation/git-multi-pack-index.txt index eb0caa0439..ffd601bc17 100644 --- a/Documentation/git-multi-pack-index.txt +++ b/Documentation/git-multi-pack-index.txt @@ -9,7 +9,8 @@ git-multi-pack-index - Write and verify multi-pack-indexes SYNOPSIS -------- [verse] -'git multi-pack-index' [--object-dir=] [--[no-]progress] +'git multi-pack-index' [--object-dir=] [--[no-]progress] + [--preferred-pack=] DESCRIPTION ----------- @@ -30,7 +31,16 @@ OPTIONS The following subcommands are available: write:: - Write a new MIDX file. + Write a new MIDX file. The following options are available for + the `write` sub-command: ++ +-- + --preferred-pack=:: + Optionally specify the tie-breaking pack used when + multiple packs contain the same object. If not given, + ties are broken in favor of the pack with the lowest + mtime. +-- verify:: Verify the contents of the MIDX file. diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index 54d715ead1..25d9fbe37a 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -85,6 +85,16 @@ base-name:: reference was included in the resulting packfile. This can be useful to send new tags to native Git clients. +--stdin-packs:: + Read the basenames of packfiles (e.g., `pack-1234abcd.pack`) + from the standard input, instead of object names or revision + arguments. The resulting pack contains all objects listed in the + included packs (those not beginning with `^`), excluding any + objects listed in the excluded packs (beginning with `^`). ++ +Incompatible with `--revs`, or options that imply `--revs` (such as +`--all`), with the exception of `--unpacked`, which is compatible. + --window=:: --depth=:: These two options affect how the objects contained in @@ -400,6 +410,17 @@ Note that we pick a single island for each regex to go into, using "last one wins" ordering (which allows repo-specific config to take precedence over user-wide config, and so forth). + +CONFIGURATION +------------- + +Various configuration variables affect packing, see +linkgit:git-config[1] (search for "pack" and "delta"). + +Notably, delta compression is not used on objects larger than the +`core.bigFileThreshold` configuration variable and on files with the +attribute `delta` set to false. + SEE ALSO -------- linkgit:git-rev-list[1] diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index ab103c82cf..a953c7c387 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -600,7 +600,7 @@ EXAMPLES `git push origin`:: Without additional configuration, pushes the current branch to - the configured upstream (`remote.origin.merge` configuration + the configured upstream (`branch..merge` configuration variable) if it has the same name as the current branch, and errors out without pushing otherwise. + diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt index 9701c1e5fd..fe350d7f40 100644 --- a/Documentation/git-range-diff.txt +++ b/Documentation/git-range-diff.txt @@ -10,6 +10,7 @@ SYNOPSIS [verse] 'git range-diff' [--color=[]] [--no-color] [] [--no-dual-color] [--creation-factor=] + [--left-only | --right-only] ( | ... | ) DESCRIPTION @@ -28,6 +29,17 @@ Finally, the list of matching commits is shown in the order of the second commit range, with unmatched commits being inserted just after all of their ancestors have been shown. +There are three ways to specify the commit ranges: + +- ` `: Either commit range can be of the form + `..`, `^!` or `^-`. See `SPECIFYING RANGES` + in linkgit:gitrevisions[7] for more details. + +- `...`. This is equivalent to + `.. ..`. + +- ` `: This is equivalent to `.. + ..`. OPTIONS ------- @@ -57,6 +69,14 @@ to revert to color all lines according to the outer diff markers See the ``Algorithm`` section below for an explanation why this is needed. +--left-only:: + Suppress commits that are missing from the first specified range + (or the "left range" when using the `...` format). + +--right-only:: + Suppress commits that are missing from the second specified range + (or the "right range" when using the `...` format). + --[no-]notes[=]:: This flag is passed to the `git log` program (see linkgit:git-log[1]) that generates the patches. diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index a0487b5cc5..f08ae27e2a 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -593,16 +593,17 @@ See also INCOMPATIBLE OPTIONS below. --autosquash:: --no-autosquash:: - When the commit log message begins with "squash! ..." (or - "fixup! ..."), and there is already a commit in the todo list that - matches the same `...`, automatically modify the todo list of rebase - -i so that the commit marked for squashing comes right after the - commit to be modified, and change the action of the moved commit - from `pick` to `squash` (or `fixup`). A commit matches the `...` if - the commit subject matches, or if the `...` refers to the commit's - hash. As a fall-back, partial matches of the commit subject work, - too. The recommended way to create fixup/squash commits is by using - the `--fixup`/`--squash` options of linkgit:git-commit[1]. + When the commit log message begins with "squash! ..." or "fixup! ..." + or "amend! ...", and there is already a commit in the todo list that + matches the same `...`, automatically modify the todo list of + `rebase -i`, so that the commit marked for squashing comes right after + the commit to be modified, and change the action of the moved commit + from `pick` to `squash` or `fixup` or `fixup -C` respectively. A commit + matches the `...` if the commit subject matches, or if the `...` refers + to the commit's hash. As a fall-back, partial matches of the commit + subject work, too. The recommended way to create fixup/amend/squash + commits is by using the `--fixup`, `--fixup=amend:` or `--fixup=reword:` + and `--squash` options respectively of linkgit:git-commit[1]. + If the `--autosquash` option is enabled by default using the configuration variable `rebase.autoSquash`, this option can be @@ -887,9 +888,17 @@ If you want to fold two or more commits into one, replace the command "pick" for the second and subsequent commits with "squash" or "fixup". If the commits had different authors, the folded commit will be attributed to the author of the first commit. The suggested commit -message for the folded commit is the concatenation of the commit -messages of the first commit and of those with the "squash" command, -but omits the commit messages of commits with the "fixup" command. +message for the folded commit is the concatenation of the first +commit's message with those identified by "squash" commands, omitting the +messages of commits identified by "fixup" commands, unless "fixup -c" +is used. In that case the suggested commit message is only the message +of the "fixup -c" commit, and an editor is opened allowing you to edit +the message. The contents (patch) of the "fixup -c" commit are still +incorporated into the folded commit. If there is more than one "fixup -c" +commit, the message from the final one is used. You can also use +"fixup -C" to get the same behavior as "fixup -c" except without opening +an editor. + 'git rebase' will stop when "pick" has been replaced with "edit" or when a command fails due to merge errors. When you are done editing diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt index 92f146d27d..317d63cf0d 100644 --- a/Documentation/git-repack.txt +++ b/Documentation/git-repack.txt @@ -165,9 +165,35 @@ depth is 4095. Pass the `--delta-islands` option to `git-pack-objects`, see linkgit:git-pack-objects[1]. -Configuration +-g=:: +--geometric=:: + Arrange resulting pack structure so that each successive pack + contains at least `` times the number of objects as the + next-largest pack. ++ +`git repack` ensures this by determining a "cut" of packfiles that need +to be repacked into one in order to ensure a geometric progression. It +picks the smallest set of packfiles such that as many of the larger +packfiles (by count of objects contained in that pack) may be left +intact. ++ +Unlike other repack modes, the set of objects to pack is determined +uniquely by the set of packs being "rolled-up"; in other words, the +packs determined to need to be combined in order to restore a geometric +progression. ++ +When `--unpacked` is specified, loose objects are implicitly included in +this "roll-up", without respect to their reachability. This is subject +to change in the future. This option (implying a drastically different +repack mode) is not guaranteed to work with all other combinations of +option to `git repack`). + +CONFIGURATION ------------- +Various configuration variables affect packing, see +linkgit:git-config[1] (search for "pack" and "delta"). + By default, the command passes `--delta-base-offset` option to 'git pack-objects'; this typically results in slightly smaller packs, but the generated packs are incompatible with versions of Git older than @@ -178,6 +204,10 @@ need to set the configuration variable `repack.UseDeltaBaseOffset` to is unaffected by this option as the conversion is performed on the fly as needed in that case. +Delta compression is not used on objects larger than the +`core.bigFileThreshold` configuration variable and on files with the +attribute `delta` set to false. + SEE ALSO -------- linkgit:git-pack-objects[1] diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index 5da66232dc..20bb8e8217 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -31,6 +31,99 @@ include::rev-list-options.txt[] include::pretty-formats.txt[] +EXAMPLES +-------- + +* Print the list of commits reachable from the current branch. ++ +---------- +git rev-list HEAD +---------- + +* Print the list of commits on this branch, but not present in the + upstream branch. ++ +---------- +git rev-list @{upstream}..HEAD +---------- + +* Format commits with their author and commit message (see also the + porcelain linkgit:git-log[1]). ++ +---------- +git rev-list --format=medium HEAD +---------- + +* Format commits along with their diffs (see also the porcelain + linkgit:git-log[1], which can do this in a single process). ++ +---------- +git rev-list HEAD | +git diff-tree --stdin --format=medium -p +---------- + +* Print the list of commits on the current branch that touched any + file in the `Documentation` directory. ++ +---------- +git rev-list HEAD -- Documentation/ +---------- + +* Print the list of commits authored by you in the past year, on + any branch, tag, or other ref. ++ +---------- +git rev-list --author=you@example.com --since=1.year.ago --all +---------- + +* Print the list of objects reachable from the current branch (i.e., all + commits and the blobs and trees they contain). ++ +---------- +git rev-list --objects HEAD +---------- + +* Compare the disk size of all reachable objects, versus those + reachable from reflogs, versus the total packed size. This can tell + you whether running `git repack -ad` might reduce the repository size + (by dropping unreachable objects), and whether expiring reflogs might + help. ++ +---------- +# reachable objects +git rev-list --disk-usage --objects --all +# plus reflogs +git rev-list --disk-usage --objects --all --reflog +# total disk size used +du -c .git/objects/pack/*.pack .git/objects/??/* +# alternative to du: add up "size" and "size-pack" fields +git count-objects -v +---------- + +* Report the disk size of each branch, not including objects used by the + current branch. This can find outliers that are contributing to a + bloated repository size (e.g., because somebody accidentally committed + large build artifacts). ++ +---------- +git for-each-ref --format='%(refname)' | +while read branch +do + size=$(git rev-list --disk-usage --objects HEAD..$branch) + echo "$size $branch" +done | +sort -n +---------- + +* Compare the on-disk size of branches in one group of refs, excluding + another. If you co-mingle objects from multiple remotes in a single + repository, this can show which remotes are contributing to the + repository size (taking the size of `origin` as a baseline). ++ +---------- +git rev-list --disk-usage --objects --remotes=$suspect --not --remotes=origin +---------- + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt index c16cc3b608..c9c7f3065c 100644 --- a/Documentation/git-shortlog.txt +++ b/Documentation/git-shortlog.txt @@ -113,6 +113,10 @@ MAPPING AUTHORS See linkgit:gitmailmap[5]. +Note that if `git shortlog` is run outside of a repository (to process +log contents on standard input), it will look for a `.mailmap` file in +the current directory. + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 31f1beb65b..a8c8c32f1e 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -8,8 +8,8 @@ git-stash - Stash the changes in a dirty working directory away SYNOPSIS -------- [verse] -'git stash' list [] -'git stash' show [] [] +'git stash' list [] +'git stash' show [-u|--include-untracked|--only-untracked] [] [] 'git stash' drop [-q|--quiet] [] 'git stash' ( pop | apply ) [--index] [-q|--quiet] [] 'git stash' branch [] @@ -67,7 +67,7 @@ save [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q Instead, all non-option arguments are concatenated to form the stash message. -list []:: +list []:: List the stash entries that you currently have. Each 'stash entry' is listed with its name (e.g. `stash@{0}` is the latest entry, `stash@{1}` is @@ -83,7 +83,7 @@ stash@{1}: On master: 9cc0589... Add git-stash The command takes options applicable to the 'git log' command to control what is shown and how. See linkgit:git-log[1]. -show [] []:: +show [-u|--include-untracked|--only-untracked] [] []:: Show the changes recorded in the stash entry as a diff between the stashed contents and the commit back when the stash entry was first @@ -91,8 +91,8 @@ show [] []:: By default, the command shows the diffstat, but it will accept any format known to 'git diff' (e.g., `git stash show -p stash@{1}` to view the second most recent entry in patch form). - You can use stash.showStat and/or stash.showPatch config variables - to change the default behavior. + You can use stash.showIncludeUntracked, stash.showStat, and + stash.showPatch config variables to change the default behavior. pop [--index] [-q|--quiet] []:: @@ -160,10 +160,18 @@ up with `git clean`. -u:: --include-untracked:: - This option is only valid for `push` and `save` commands. +--no-include-untracked:: + When used with the `push` and `save` commands, + all untracked files are also stashed and then cleaned up with + `git clean`. ++ +When used with the `show` command, show the untracked files in the stash +entry as part of the diff. + +--only-untracked:: + This option is only valid for the `show` command. + -All untracked files are also stashed and then cleaned up with -`git clean`. +Show only the untracked files in the stash entry as part of the diff. --index:: This option is only valid for `pop` and `apply` commands. diff --git a/Documentation/git-status.txt b/Documentation/git-status.txt index c0764e850a..83f38e3198 100644 --- a/Documentation/git-status.txt +++ b/Documentation/git-status.txt @@ -130,7 +130,7 @@ ignored, then the directory is not shown, but all contents are shown. --column[=]:: --no-column:: Display untracked files in columns. See configuration variable - column.status for option syntax.`--column` and `--no-column` + `column.status` for option syntax. `--column` and `--no-column` without options are equivalent to 'always' and 'never' respectively. diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt index 56656d1be6..31a97a1b6c 100644 --- a/Documentation/git-tag.txt +++ b/Documentation/git-tag.txt @@ -134,7 +134,7 @@ options for details. --column[=]:: --no-column:: Display tag listing in columns. See configuration variable - column.tag for option syntax.`--column` and `--no-column` + `column.tag` for option syntax. `--column` and `--no-column` without options are equivalent to 'always' and 'never' respectively. + This option is only applicable when listing tags without annotation lines. diff --git a/Documentation/git.txt b/Documentation/git.txt index d36e6fd482..3a9c44987f 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -88,7 +88,7 @@ foo.bar= ...`) sets `foo.bar` to the empty string which `git config empty string, instead the environment variable itself must be set to the empty string. It is an error if the `` does not exist in the environment. `` may not contain an equals sign - to avoid ambiguity with ``s which contain one. + to avoid ambiguity with `` containing one. + This is useful for cases where you want to pass transitory configuration options to git, but are doing so on OS's where diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index e84e104f93..0a60472bb5 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -1174,7 +1174,8 @@ tag then no replacement will be done. The placeholders are the same as those for the option `--pretty=format:` of linkgit:git-log[1], except that they need to be wrapped like this: `$Format:PLACEHOLDERS$` in the file. E.g. the string `$Format:%H$` will be replaced by the -commit hash. +commit hash. However, only one `%(describe)` placeholder is expanded +per archive to avoid denial-of-service attacks. Packing objects diff --git a/Documentation/gitdiffcore.txt b/Documentation/gitdiffcore.txt index 8673a5c5b2..0d57f86abc 100644 --- a/Documentation/gitdiffcore.txt +++ b/Documentation/gitdiffcore.txt @@ -74,6 +74,7 @@ into another list. There are currently 5 such transformations: - diffcore-merge-broken - diffcore-pickaxe - diffcore-order +- diffcore-rotate These are applied in sequence. The set of filepairs 'git diff-{asterisk}' commands find are used as the input to diffcore-break, and @@ -296,6 +297,26 @@ Documentation t ------------------------------------------------ +diffcore-rotate: For Changing At Which Path Output Starts +--------------------------------------------------------- + +This transformation takes one pathname, and rotates the set of +filepairs so that the filepair for the given pathname comes first, +optionally discarding the paths that come before it. This is used +to implement the `--skip-to` and the `--rotate-to` options. It is +an error when the specified pathname is not in the set of filepairs, +but it is not useful to error out when used with "git log" family of +commands, because it is unreasonable to expect that a given path +would be modified by each and every commit shown by the "git log" +command. For this reason, when used with "git log", the filepair +that sorts the same as, or the first one that sorts after, the given +pathname is where the output starts. + +Use of this transformation combined with diffcore-order will produce +unexpected results, as the input to this transformation is likely +not sorted when diffcore-order is in effect. + + SEE ALSO -------- linkgit:git-diff[1], diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index 1f3b57d04d..b51959ff94 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -138,7 +138,7 @@ given); `template` (if a `-t` option was given or the configuration option `commit.template` is set); `merge` (if the commit is a merge or a `.git/MERGE_MSG` file exists); `squash` (if a `.git/SQUASH_MSG` file exists); or `commit`, followed by -a commit SHA-1 (if a `-c`, `-C` or `--amend` option was given). +a commit object name (if a `-c`, `-C` or `--amend` option was given). If the exit status is non-zero, `git commit` will abort. @@ -231,19 +231,19 @@ named remote is not being used both values will be the same. Information about what is to be pushed is provided on the hook's standard input with lines of the form: - SP SP SP LF + SP SP SP LF For instance, if the command +git push origin master:foreign+ were run the hook would receive a line like the following: refs/heads/master 67890 refs/heads/foreign 12345 -although the full, 40-character SHA-1s would be supplied. If the foreign ref -does not yet exist the `` will be 40 `0`. If a ref is to be -deleted, the `` will be supplied as `(delete)` and the `` will be 40 `0`. If the local commit was specified by something other -than a name which could be expanded (such as `HEAD~`, or a SHA-1) it will be -supplied as it was originally given. +although the full object name would be supplied. If the foreign ref does not +yet exist the `` will be the all-zeroes object name. If a +ref is to be deleted, the `` will be supplied as `(delete)` and the +`` will be the all-zeroes object name. If the local commit +was specified by something other than a name which could be expanded (such as +`HEAD~`, or an object name) it will be supplied as it was originally given. If this hook exits with a non-zero status, `git push` will abort without pushing anything. Information about why the push is rejected may be sent @@ -268,7 +268,7 @@ input a line of the format: where `` is the old object name stored in the ref, `` is the new object name to be stored in the ref and `` is the full name of the ref. -When creating a new ref, `` is 40 `0`. +When creating a new ref, `` is the all-zeroes object name. If the hook exits with non-zero status, none of the refs will be updated. If the hook exits with zero, updating of individual refs can @@ -473,7 +473,8 @@ 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. +committed or aborted and may thus get called multiple times. The hook +does not cover symbolic references (but that may change in the future). The hook takes exactly one argument, which is the current state the given reference transaction is in: @@ -492,6 +493,14 @@ receives on standard input a line of the format: SP SP LF +where `` is the old object name passed into the reference +transaction, `` is the new object name to be stored in the +ref and `` 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, `` is the all-zeroes object name. To +distinguish these cases, you can inspect the current value of +`` via `git rev-parse`. + 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 @@ -550,7 +559,7 @@ command-dependent arguments may be passed in the future. The hook receives a list of the rewritten commits on stdin, in the format - SP [ SP ] LF + SP [ SP ] LF The 'extra-info' is again command-dependent. If it is empty, the preceding SP is also omitted. Currently, no commands pass any @@ -566,7 +575,7 @@ rebase:: For the 'squash' and 'fixup' operation, all commits that were squashed are listed as being rewritten to the squashed commit. This means that there will be several lines sharing the same - 'new-sha1'. + 'new-object-name'. + The commits are guaranteed to be listed in the order that they were processed by rebase. diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt index d47b1ae296..5751603b13 100644 --- a/Documentation/gitignore.txt +++ b/Documentation/gitignore.txt @@ -153,7 +153,7 @@ EXAMPLES -------- - The pattern `hello.*` matches any file or folder - whose name begins with `hello`. If one wants to restrict + whose name begins with `hello.`. If one wants to restrict this only to the directory and not in its subdirectories, one can prepend the pattern with a slash, i.e. `/hello.*`; the pattern now matches `hello.txt`, `hello.c` but not diff --git a/Documentation/gitmailmap.txt b/Documentation/gitmailmap.txt index 052209b33b..3fb39f801f 100644 --- a/Documentation/gitmailmap.txt +++ b/Documentation/gitmailmap.txt @@ -50,9 +50,9 @@ which allows mailmap to replace both the name and the email of a commit matching both the specified commit name and email address. Both E-Mails and names are matched case-insensitively. For example -this would also match the 'Commit Name ' above: +this would also match the 'Commit Name ' above: -- -Proper Name CoMmIt NaMe + Proper Name CoMmIt NaMe -- EXAMPLES @@ -79,9 +79,9 @@ Jane Doe Jane Doe ------------ -Note that there's no need to map the name for 'jane@laptop.(none)' to +Note that there's no need to map the name for '' to only correct the names. However, leaving the obviously broken -`' and '' E-Mails as-is is +'' and '' E-Mails as-is is usually not what you want. A `.mailmap` file which also corrects those is: diff --git a/Documentation/howto/coordinate-embargoed-releases.txt b/Documentation/howto/coordinate-embargoed-releases.txt new file mode 100644 index 0000000000..601aae88e9 --- /dev/null +++ b/Documentation/howto/coordinate-embargoed-releases.txt @@ -0,0 +1,131 @@ +Content-type: text/asciidoc +Abstract: When a critical vulnerability is discovered and fixed, we follow this + script to coordinate a public release. + +How we coordinate embargoed releases +==================================== + +To protect Git users from critical vulnerabilities, we do not just release +fixed versions like regular maintenance releases. Instead, we coordinate +releases with packagers, keeping the fixes under an embargo until the release +date. That way, users will have a chance to upgrade on that date, no matter +what Operating System or distribution they run. + +Open a Security Advisory draft +------------------------------ + +The first step is to https://github.com/git/git/security/advisories/new[open an +advisory]. Technically, it is not necessary, but it is convenient and saves a +bit of hassle. This advisory can also be used to obtain the CVE number and it +will give us a private fork associated with it that can be used to collaborate +on a fix. + +Release date of the embargoed version +------------------------------------- + +If the vulnerability affects Windows users, we want to have our friends over at +Visual Studio on board. This means we need to target a "Patch Tuesday" (i.e. a +second Tuesday of the month), at the minimum three weeks from heads-up to +coordinated release. + +If the vulnerability affects the server side, or can benefit from scans on the +server side (i.e. if `git fsck` can detect an attack), it is important to give +all involved Git repository hosting sites enough time to scan all of those +repositories. + +Notifying the Linux distributions +--------------------------------- + +At most two weeks before release date, we need to send a notification to +distros@vs.openwall.org, preferably less than 7 days before the release date. +This will reach most (all?) Linux distributions. See an example below, and the +guidelines for this mailing list at +https://oss-security.openwall.org/wiki/mailing-lists/distros#how-to-use-the-lists[here]. + +Once the version has been published, we send a note about that to oss-security. +As an example, see https://www.openwall.com/lists/oss-security/2019/12/13/1[the +v2.24.1 mail]; +https://oss-security.openwall.org/wiki/mailing-lists/oss-security[Here] are +their guidelines. + +The mail to oss-security should also describe the exploit, and give credit to +the reporter(s): security researchers still receive too little respect for the +invaluable service they provide, and public credit goes a long way to keep them +paid by their respective organizations. + +Technically, describing any exploit can be delayed up to 7 days, but we usually +refrain from doing that, including it right away. + +As a courtesy we typically attach a Git bundle (as `.tar.xz` because the list +will drop `.bundle` attachments) in the mail to distros@ so that the involved +parties can take care of integrating/backporting them. This bundle is typically +created using a command like this: + + git bundle create cve-xxx.bundle ^origin/master vA.B.C vD.E.F + tar cJvf cve-xxx.bundle.tar.xz cve-xxx.bundle + +Example mail to distros@vs.openwall.org +--------------------------------------- + +.... +To: distros@vs.openwall.org +Cc: git-security@googlegroups.com, +Subject: [vs] Upcoming Git security fix release + +Team, + +The Git project will release new versions on at 10am Pacific Time or +soon thereafter. I have attached a Git bundle (embedded in a `.tar.xz` to avoid +it being dropped) which you can fetch into a clone of +https://github.com/git/git via `git fetch --tags /path/to/cve-xxx.bundle`, +containing the tags for versions . + +You can verify with `git tag -v ` that the versions were signed by +the Git maintainer, using the same GPG key as e.g. v2.24.0. + +Please use these tags to prepare `git` packages for your various +distributions, using the appropriate tagged versions. The added test cases +help verify the correctness. + +The addressed issues are: + + + +Credit for finding the vulnerability goes to , credit for fixing +it goes to . + +Thanks, + + +.... + +Example mail to oss-security@lists.openwall.com +----------------------------------------------- + +.... +To: oss-security@lists.openwall.com +Cc: git-security@googlegroups.com, +Subject: git: + +Team, + +The Git project released new versions on , addressing . + +All supported platforms are affected in one way or another, and all Git +versions all the way back to are affected. The fixed versions are: +. + +Link to the announcement: + +We highly recommend to upgrade. + +The addressed issues are: +* + +Credit for finding the vulnerability goes to , credit for fixing +it goes to . + +Thanks, + +.... diff --git a/Documentation/i18n.txt b/Documentation/i18n.txt index 7e36e5b55b..6c6baeeeb7 100644 --- a/Documentation/i18n.txt +++ b/Documentation/i18n.txt @@ -38,7 +38,7 @@ mind. a warning if the commit log message given to it does not look like a valid UTF-8 string, unless you explicitly say your project uses a legacy encoding. The way to say this is to - have i18n.commitencoding in `.git/config` file, like this: + have `i18n.commitEncoding` in `.git/config` file, like this: + ------------ [i18n] diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt index 6b59e28d44..45133066e4 100644 --- a/Documentation/pretty-formats.txt +++ b/Documentation/pretty-formats.txt @@ -208,6 +208,19 @@ The placeholders are: '%cs':: committer date, short format (`YYYY-MM-DD`) '%d':: ref names, like the --decorate option of linkgit:git-log[1] '%D':: ref names without the " (", ")" wrapping. +'%(describe[:options])':: human-readable name, like + linkgit:git-describe[1]; empty string for + 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. ++ +** 'match=': Only consider tags matching the given + `glob(7)` pattern, excluding the "refs/tags/" prefix. +** 'exclude=': 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 (like `git log --source`), only works with `git log` '%e':: encoding diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 96cc89d157..b1c8f86c6e 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -129,10 +129,10 @@ parents) and `--max-parents=-1` (negative numbers denote no upper limit). adjusting to updated upstream from time to time, and this option allows you to ignore the individual commits brought in to your history by such a merge. - ifdef::git-log[] - This option also changes default diff format for merge commits - to `first-parent`, see `--diff-merges=first-parent` for details. ++ +This option also changes default diff format for merge commits +to `first-parent`, see `--diff-merges=first-parent` for details. endif::git-log[] --not:: @@ -227,6 +227,15 @@ ifdef::git-rev-list[] test the exit status to see if a range of objects is fully connected (or not). It is faster than redirecting stdout to `/dev/null` as the output does not have to be formatted. + +--disk-usage:: + Suppress normal output; instead, print the sum of the bytes used + for on-disk storage by the selected commits or objects. This is + equivalent to piping the output into `git cat-file + --batch-check='%(objectsize:disk)'`, except that it runs much + faster (especially with `--use-bitmap-index`). See the `CAVEATS` + section in linkgit:git-cat-file[1] for the limitations of what + "on-disk storage" means. endif::git-rev-list[] --cherry-mark:: diff --git a/Documentation/technical/api-simple-ipc.txt b/Documentation/technical/api-simple-ipc.txt new file mode 100644 index 0000000000..d79ad323e6 --- /dev/null +++ b/Documentation/technical/api-simple-ipc.txt @@ -0,0 +1,105 @@ +Simple-IPC API +============== + +The Simple-IPC API is a collection of `ipc_` prefixed library routines +and a basic communication protocol that allow an IPC-client process to +send an application-specific IPC-request message to an IPC-server +process and receive an application-specific IPC-response message. + +Communication occurs over a named pipe on Windows and a Unix domain +socket on other platforms. IPC-clients and IPC-servers rendezvous at +a previously agreed-to application-specific pathname (which is outside +the scope of this design) that is local to the computer system. + +The IPC-server routines within the server application process create a +thread pool to listen for connections and receive request messages +from multiple concurrent IPC-clients. When received, these messages +are dispatched up to the server application callbacks for handling. +IPC-server routines then incrementally relay responses back to the +IPC-client. + +The IPC-client routines within a client application process connect +to the IPC-server and send a request message and wait for a response. +When received, the response is returned back the caller. + +For example, the `fsmonitor--daemon` feature will be built as a server +application on top of the IPC-server library routines. It will have +threads watching for file system events and a thread pool waiting for +client connections. Clients, such as `git status` will request a list +of file system events since a point in time and the server will +respond with a list of changed files and directories. The formats of +the request and response are application-specific; the IPC-client and +IPC-server routines treat them as opaque byte streams. + + +Comparison with sub-process model +--------------------------------- + +The Simple-IPC mechanism differs from the existing `sub-process.c` +model (Documentation/technical/long-running-process-protocol.txt) and +used by applications like Git-LFS. In the LFS-style sub-process model +the helper is started by the foreground process, communication happens +via a pair of file descriptors bound to the stdin/stdout of the +sub-process, the sub-process only serves the current foreground +process, and the sub-process exits when the foreground process +terminates. + +In the Simple-IPC model the server is a very long-running service. It +can service many clients at the same time and has a private socket or +named pipe connection to each active client. It might be started +(on-demand) by the current client process or it might have been +started by a previous client or by the OS at boot time. The server +process is not associated with a terminal and it persists after +clients terminate. Clients do not have access to the stdin/stdout of +the server process and therefore must communicate over sockets or +named pipes. + + +Server startup and shutdown +--------------------------- + +How an application server based upon IPC-server is started is also +outside the scope of the Simple-IPC design and is a property of the +application using it. For example, the server might be started or +restarted during routine maintenance operations, or it might be +started as a system service during the system boot-up sequence, or it +might be started on-demand by a foreground Git command when needed. + +Similarly, server shutdown is a property of the application using +the simple-ipc routines. For example, the server might decide to +shutdown when idle or only upon explicit request. + + +Simple-IPC protocol +------------------- + +The Simple-IPC protocol consists of a single request message from the +client and an optional response message from the server. Both the +client and server messages are unlimited in length and are terminated +with a flush packet. + +The pkt-line routines (Documentation/technical/protocol-common.txt) +are used to simplify buffer management during message generation, +transmission, and reception. A flush packet is used to mark the end +of the message. This allows the sender to incrementally generate and +transmit the message. It allows the receiver to incrementally receive +the message in chunks and to know when they have received the entire +message. + +The actual byte format of the client request and server response +messages are application specific. The IPC layer transmits and +receives them as opaque byte buffers without any concern for the +content within. It is the job of the calling application layer to +understand the contents of the request and response messages. + + +Summary +------- + +Conceptually, the Simple-IPC protocol is similar to an HTTP REST +request. Clients connect, make an application-specific and +stateless request, receive an application-specific +response, and disconnect. It is a one round trip facility for +querying the server. The Simple-IPC routines hide the socket, +named pipe, and thread pool details and allow the application +layer to focus on the application at hand. diff --git a/Documentation/technical/chunk-format.txt b/Documentation/technical/chunk-format.txt new file mode 100644 index 0000000000..593614fced --- /dev/null +++ b/Documentation/technical/chunk-format.txt @@ -0,0 +1,116 @@ +Chunk-based file formats +======================== + +Some file formats in Git use a common concept of "chunks" to describe +sections of the file. This allows structured access to a large file by +scanning a small "table of contents" for the remaining data. This common +format is used by the `commit-graph` and `multi-pack-index` files. See +link:technical/pack-format.html[the `multi-pack-index` format] and +link:technical/commit-graph-format.html[the `commit-graph` format] for +how they use the chunks to describe structured data. + +A chunk-based file format begins with some header information custom to +that format. That header should include enough information to identify +the file type, format version, and number of chunks in the file. From this +information, that file can determine the start of the chunk-based region. + +The chunk-based region starts with a table of contents describing where +each chunk starts and ends. This consists of (C+1) rows of 12 bytes each, +where C is the number of chunks. Consider the following table: + + | Chunk ID (4 bytes) | Chunk Offset (8 bytes) | + |--------------------|------------------------| + | ID[0] | OFFSET[0] | + | ... | ... | + | ID[C] | OFFSET[C] | + | 0x0000 | OFFSET[C+1] | + +Each row consists of a 4-byte chunk identifier (ID) and an 8-byte offset. +Each integer is stored in network-byte order. + +The chunk identifier `ID[i]` is a label for the data stored within this +fill from `OFFSET[i]` (inclusive) to `OFFSET[i+1]` (exclusive). Thus, the +size of the `i`th chunk is equal to the difference between `OFFSET[i+1]` +and `OFFSET[i]`. This requires that the chunk data appears contiguously +in the same order as the table of contents. + +The final entry in the table of contents must be four zero bytes. This +confirms that the table of contents is ending and provides the offset for +the end of the chunk-based data. + +Note: The chunk-based format expects that the file contains _at least_ a +trailing hash after `OFFSET[C+1]`. + +Functions for working with chunk-based file formats are declared in +`chunk-format.h`. Using these methods provide extra checks that assist +developers when creating new file formats. + +Writing chunk-based file formats +-------------------------------- + +To write a chunk-based file format, create a `struct chunkfile` by +calling `init_chunkfile()` and pass a `struct hashfile` pointer. The +caller is responsible for opening the `hashfile` and writing header +information so the file format is identifiable before the chunk-based +format begins. + +Then, call `add_chunk()` for each chunk that is intended for write. This +populates the `chunkfile` with information about the order and size of +each chunk to write. Provide a `chunk_write_fn` function pointer to +perform the write of the chunk data upon request. + +Call `write_chunkfile()` to write the table of contents to the `hashfile` +followed by each of the chunks. This will verify that each chunk wrote +the expected amount of data so the table of contents is correct. + +Finally, call `free_chunkfile()` to clear the `struct chunkfile` data. The +caller is responsible for finalizing the `hashfile` by writing the trailing +hash and closing the file. + +Reading chunk-based file formats +-------------------------------- + +To read a chunk-based file format, the file must be opened as a +memory-mapped region. The chunk-format API expects that the entire file +is mapped as a contiguous memory region. + +Initialize a `struct chunkfile` pointer with `init_chunkfile(NULL)`. + +After reading the header information from the beginning of the file, +including the chunk count, call `read_table_of_contents()` to populate +the `struct chunkfile` with the list of chunks, their offsets, and their +sizes. + +Extract the data information for each chunk using `pair_chunk()` or +`read_chunk()`: + +* `pair_chunk()` assigns a given pointer with the location inside the + memory-mapped file corresponding to that chunk's offset. If the chunk + does not exist, then the pointer is not modified. + +* `read_chunk()` takes a `chunk_read_fn` function pointer and calls it + with the appropriate initial pointer and size information. The function + is not called if the chunk does not exist. Use this method to read chunks + if you need to perform immediate parsing or if you need to execute logic + based on the size of the chunk. + +After calling these methods, call `free_chunkfile()` to clear the +`struct chunkfile` data. This will not close the memory-mapped region. +Callers are expected to own that data for the timeframe the pointers into +the region are needed. + +Examples +-------- + +These file formats use the chunk-format API, and can be used as examples +for future formats: + +* *commit-graph:* see `write_commit_graph_file()` and `parse_commit_graph()` + in `commit-graph.c` for how the chunk-format API is used to write and + parse the commit-graph file format documented in + link:technical/commit-graph-format.html[the commit-graph file format]. + +* *multi-pack-index:* see `write_midx_internal()` and `load_multi_pack_index()` + in `midx.c` for how the chunk-format API is used to write and + parse the multi-pack-index file format documented in + link:technical/pack-format.html[the multi-pack-index file format]. diff --git a/Documentation/technical/commit-graph-format.txt b/Documentation/technical/commit-graph-format.txt index b3b58880b9..87971c27dd 100644 --- a/Documentation/technical/commit-graph-format.txt +++ b/Documentation/technical/commit-graph-format.txt @@ -4,11 +4,7 @@ Git commit graph format The Git commit graph stores a list of commit OIDs and some associated metadata, including: -- The generation number of the commit. Commits with no parents have - generation number 1; commits with parents have generation number - one more than the maximum generation number of its parents. We - reserve zero as special, and can be used to mark a generation - number invalid or as "not computed". +- The generation number of the commit. - The root tree OID. @@ -65,6 +61,9 @@ CHUNK LOOKUP: the length using the next chunk position if necessary.) Each chunk ID appears at most once. + The CHUNK LOOKUP matches the table of contents from + link:technical/chunk-format.html[the chunk-based file format]. + The remaining data in the body is described one chunk at a time, and these chunks may be given in any order. Chunks are required unless otherwise specified. @@ -86,13 +85,33 @@ CHUNK DATA: position. If there are more than two parents, the second value has its most-significant bit on and the other bits store an array position into the Extra Edge List chunk. - * The next 8 bytes store the generation number of the commit and + * The next 8 bytes store the topological level (generation number v1) + of the commit and the commit time in seconds since EPOCH. The generation number uses the higher 30 bits of the first 4 bytes, while the commit time uses the 32 bits of the second 4 bytes, along with the lowest 2 bits of the lowest byte, storing the 33rd and 34th bit of the commit time. + Generation Data (ID: {'G', 'D', 'A', 'T' }) (N * 4 bytes) [Optional] + * This list of 4-byte values store corrected commit date offsets for the + commits, arranged in the same order as commit data chunk. + * If the corrected commit date offset cannot be stored within 31 bits, + the value has its most-significant bit on and the other bits store + the position of corrected commit date into the Generation Data Overflow + chunk. + * Generation Data chunk is present only when commit-graph file is written + by compatible versions of Git and in case of split commit-graph chains, + the topmost layer also has Generation Data chunk. + + Generation Data Overflow (ID: {'G', 'D', 'O', 'V' }) [Optional] + * This list of 8-byte values stores the corrected commit date offsets + for commits with corrected commit date offsets that cannot be + stored within 31 bits. + * Generation Data Overflow chunk is present only when Generation Data + chunk is present and atleast one corrected commit date offset cannot + be stored within 31 bits. + Extra Edge List (ID: {'E', 'D', 'G', 'E'}) [Optional] This list of 4-byte values store the second through nth parents for all octopus merges. The second parent value in the commit data stores diff --git a/Documentation/technical/commit-graph.txt b/Documentation/technical/commit-graph.txt index f14a7659aa..f05e7bda1a 100644 --- a/Documentation/technical/commit-graph.txt +++ b/Documentation/technical/commit-graph.txt @@ -38,14 +38,31 @@ A consumer may load the following info for a commit from the graph: Values 1-4 satisfy the requirements of parse_commit_gently(). -Define the "generation number" of a commit recursively as follows: +There are two definitions of generation number: +1. Corrected committer dates (generation number v2) +2. Topological levels (generation nummber v1) - * A commit with no parents (a root commit) has generation number one. +Define "corrected committer date" of a commit recursively as follows: - * A commit with at least one parent has generation number one more than - the largest generation number among its parents. + * A commit with no parents (a root commit) has corrected committer date + equal to its committer date. -Equivalently, the generation number of a commit A is one more than the + * A commit with at least one parent has corrected committer date equal to + the maximum of its commiter date and one more than the largest corrected + committer date among its parents. + + * As a special case, a root commit with timestamp zero has corrected commit + date of 1, to be able to distinguish it from GENERATION_NUMBER_ZERO + (that is, an uncomputed corrected commit date). + +Define the "topological level" of a commit recursively as follows: + + * A commit with no parents (a root commit) has topological level of one. + + * A commit with at least one parent has topological level one more than + the largest topological level among its parents. + +Equivalently, the topological level of a commit A is one more than the length of a longest path from A to a root commit. The recursive definition is easier to use for computation and observing the following property: @@ -60,6 +77,9 @@ is easier to use for computation and observing the following property: generation numbers, then we always expand the boundary commit with highest generation number and can easily detect the stopping condition. +The property applies to both versions of generation number, that is both +corrected committer dates and topological levels. + This property can be used to significantly reduce the time it takes to walk commits and determine topological relationships. Without generation numbers, the general heuristic is the following: @@ -67,7 +87,9 @@ numbers, the general heuristic is the following: If A and B are commits with commit time X and Y, respectively, and X < Y, then A _probably_ cannot reach B. -This heuristic is currently used whenever the computation is allowed to +In absence of corrected commit dates (for example, old versions of Git or +mixed generation graph chains), +this heuristic is currently used whenever the computation is allowed to violate topological relationships due to clock skew (such as "git log" with default order), but is not used when the topological order is required (such as merge base calculations, "git log --graph"). @@ -77,7 +99,7 @@ in the commit graph. We can treat these commits as having "infinite" generation number and walk until reaching commits with known generation number. -We use the macro GENERATION_NUMBER_INFINITY = 0xFFFFFFFF to mark commits not +We use the macro GENERATION_NUMBER_INFINITY to mark commits not in the commit-graph file. If a commit-graph file was written by a version of Git that did not compute generation numbers, then those commits will have generation number represented by the macro GENERATION_NUMBER_ZERO = 0. @@ -93,12 +115,12 @@ fully-computed generation numbers. Using strict inequality may result in walking a few extra commits, but the simplicity in dealing with commits with generation number *_INFINITY or *_ZERO is valuable. -We use the macro GENERATION_NUMBER_MAX = 0x3FFFFFFF to for commits whose -generation numbers are computed to be at least this value. We limit at -this value since it is the largest value that can be stored in the -commit-graph file using the 30 bits available to generation numbers. This -presents another case where a commit can have generation number equal to -that of a parent. +We use the macro GENERATION_NUMBER_V1_MAX = 0x3FFFFFFF for commits whose +topological levels (generation number v1) are computed to be at least +this value. We limit at this value since it is the largest value that +can be stored in the commit-graph file using the 30 bits available +to topological levels. This presents another case where a commit can +have generation number equal to that of a parent. Design Details -------------- @@ -267,6 +289,35 @@ The merge strategy values (2 for the size multiple, 64,000 for the maximum number of commits) could be extracted into config settings for full flexibility. +## Handling Mixed Generation Number Chains + +With the introduction of generation number v2 and generation data chunk, the +following scenario is possible: + +1. "New" Git writes a commit-graph with the corrected commit dates. +2. "Old" Git writes a split commit-graph on top without corrected commit dates. + +A naive approach of using the newest available generation number from +each layer would lead to violated expectations: the lower layer would +use corrected commit dates which are much larger than the topological +levels of the higher layer. For this reason, Git inspects the topmost +layer to see if the layer is missing corrected commit dates. In such a case +Git only uses topological level for generation numbers. + +When writing a new layer in split commit-graph, we write corrected commit +dates if the topmost layer has corrected commit dates written. This +guarantees that if a layer has corrected commit dates, all lower layers +must have corrected commit dates as well. + +When merging layers, we do not consider whether the merged layers had corrected +commit dates. Instead, the new layer will have corrected commit dates if the +layer below the new layer has corrected commit dates. + +While writing or merging layers, if the new layer is the only layer, it will +have corrected commit dates when written by compatible versions of Git. Thus, +rewriting split commit-graph as a single file (`--split=replace`) creates a +single layer with corrected commit dates. + ## Deleting graph-{hash} files After a new tip file is written, some `graph-{hash}` files may no longer diff --git a/Documentation/technical/hash-function-transition.txt b/Documentation/technical/hash-function-transition.txt index 6fd20ebbc2..7c1630bf83 100644 --- a/Documentation/technical/hash-function-transition.txt +++ b/Documentation/technical/hash-function-transition.txt @@ -33,16 +33,9 @@ researchers. On 23 February 2017 the SHAttered attack Git v2.13.0 and later subsequently moved to a hardened SHA-1 implementation by default, which isn't vulnerable to the SHAttered -attack. +attack, but SHA-1 is still weak. -Thus Git has in effect already migrated to a new hash that isn't SHA-1 -and doesn't share its vulnerabilities, its new hash function just -happens to produce exactly the same output for all known inputs, -except two PDFs published by the SHAttered researchers, and the new -implementation (written by those researchers) claims to detect future -cryptanalytic collision attacks. - -Regardless, it's considered prudent to move past any variant of SHA-1 +Thus it's considered prudent to move past any variant of SHA-1 to a new hash. There's no guarantee that future attacks on SHA-1 won't be published in the future, and those attacks may not have viable mitigations. @@ -57,6 +50,38 @@ SHA-1 still possesses the other properties such as fast object lookup and safe error checking, but other hash functions are equally suitable that are believed to be cryptographically secure. +Choice of Hash +-------------- +The hash to replace the hardened SHA-1 should be stronger than SHA-1 +was: we would like it to be trustworthy and useful in practice for at +least 10 years. + +Some other relevant properties: + +1. A 256-bit hash (long enough to match common security practice; not + excessively long to hurt performance and disk usage). + +2. High quality implementations should be widely available (e.g., in + OpenSSL and Apple CommonCrypto). + +3. The hash function's properties should match Git's needs (e.g. Git + requires collision and 2nd preimage resistance and does not require + length extension resistance). + +4. As a tiebreaker, the hash should be fast to compute (fortunately + many contenders are faster than SHA-1). + +There were several contenders for a successor hash to SHA-1, including +SHA-256, SHA-512/256, SHA-256x16, K12, and BLAKE2bp-256. + +In late 2018 the project picked SHA-256 as its successor hash. + +See 0ed8d8da374 (doc hash-function-transition: pick SHA-256 as +NewHash, 2018-08-04) and numerous mailing list threads at the time, +particularly the one starting at +https://lore.kernel.org/git/20180609224913.GC38834@genre.crustytoothpaste.net/ +for more information. + Goals ----- 1. The transition to SHA-256 can be done one local repository at a time. @@ -94,7 +119,7 @@ Overview -------- We introduce a new repository format extension. Repositories with this extension enabled use SHA-256 instead of SHA-1 to name their objects. -This affects both object names and object content --- both the names +This affects both object names and object content -- both the names of objects and all references to other objects within an object are switched to the new hash function. @@ -107,7 +132,7 @@ mapping to allow naming objects using either their SHA-1 and SHA-256 names interchangeably. "git cat-file" and "git hash-object" gain options to display an object -in its sha1 form and write an object given its sha1 form. This +in its SHA-1 form and write an object given its SHA-1 form. This requires all objects referenced by that object to be present in the object database so that they can be named using the appropriate name (using the bidirectional hash mapping). @@ -115,7 +140,7 @@ object database so that they can be named using the appropriate name Fetches from a SHA-1 based server convert the fetched objects into SHA-256 form and record the mapping in the bidirectional mapping table (see below for details). Pushes to a SHA-1 based server convert the -objects being pushed into sha1 form so the server does not have to be +objects being pushed into SHA-1 form so the server does not have to be aware of the hash function the client is using. Detailed Design @@ -151,38 +176,38 @@ repository extensions. Object names ~~~~~~~~~~~~ -Objects can be named by their 40 hexadecimal digit sha1-name or 64 -hexadecimal digit sha256-name, plus names derived from those (see +Objects can be named by their 40 hexadecimal digit SHA-1 name or 64 +hexadecimal digit SHA-256 name, plus names derived from those (see gitrevisions(7)). -The sha1-name of an object is the SHA-1 of the concatenation of its -type, length, a nul byte, and the object's sha1-content. This is the +The SHA-1 name of an object is the SHA-1 of the concatenation of its +type, length, a nul byte, and the object's SHA-1 content. This is the traditional used in Git to name objects. -The sha256-name of an object is the SHA-256 of the concatenation of its -type, length, a nul byte, and the object's sha256-content. +The SHA-256 name of an object is the SHA-256 of the concatenation of its +type, length, a nul byte, and the object's SHA-256 content. Object format ~~~~~~~~~~~~~ The content as a byte sequence of a tag, commit, or tree object named -by sha1 and sha256 differ because an object named by sha256-name refers to -other objects by their sha256-names and an object named by sha1-name -refers to other objects by their sha1-names. +by SHA-1 and SHA-256 differ because an object named by SHA-256 name refers to +other objects by their SHA-256 names and an object named by SHA-1 name +refers to other objects by their SHA-1 names. -The sha256-content of an object is the same as its sha1-content, except -that objects referenced by the object are named using their sha256-names -instead of sha1-names. Because a blob object does not refer to any -other object, its sha1-content and sha256-content are the same. +The SHA-256 content of an object is the same as its SHA-1 content, except +that objects referenced by the object are named using their SHA-256 names +instead of SHA-1 names. Because a blob object does not refer to any +other object, its SHA-1 content and SHA-256 content are the same. -The format allows round-trip conversion between sha256-content and -sha1-content. +The format allows round-trip conversion between SHA-256 content and +SHA-1 content. Object storage ~~~~~~~~~~~~~~ Loose objects use zlib compression and packed objects use the packed format described in Documentation/technical/pack-format.txt, just like -today. The content that is compressed and stored uses sha256-content -instead of sha1-content. +today. The content that is compressed and stored uses SHA-256 content +instead of SHA-1 content. Pack index ~~~~~~~~~~ @@ -191,21 +216,21 @@ hash functions. They have the following format (all integers are in network byte order): - A header appears at the beginning and consists of the following: - - The 4-byte pack index signature: '\377t0c' - - 4-byte version number: 3 - - 4-byte length of the header section, including the signature and + * The 4-byte pack index signature: '\377t0c' + * 4-byte version number: 3 + * 4-byte length of the header section, including the signature and version number - - 4-byte number of objects contained in the pack - - 4-byte number of object formats in this pack index: 2 - - For each object format: - - 4-byte format identifier (e.g., 'sha1' for SHA-1) - - 4-byte length in bytes of shortened object names. This is the + * 4-byte number of objects contained in the pack + * 4-byte number of object formats in this pack index: 2 + * For each object format: + ** 4-byte format identifier (e.g., 'sha1' for SHA-1) + ** 4-byte length in bytes of shortened object names. This is the shortest possible length needed to make names in the shortened object name table unambiguous. - - 4-byte integer, recording where tables relating to this format + ** 4-byte integer, recording where tables relating to this format are stored in this index file, as an offset from the beginning. - - 4-byte offset to the trailer from the beginning of this file. - - Zero or more additional key/value pairs (4-byte key, 4-byte + * 4-byte offset to the trailer from the beginning of this file. + * Zero or more additional key/value pairs (4-byte key, 4-byte value). Only one key is supported: 'PSRC'. See the "Loose objects and unreachable objects" section for supported values and how this is used. All other keys are reserved. Readers must ignore @@ -213,37 +238,36 @@ network byte order): - Zero or more NUL bytes. This can optionally be used to improve the alignment of the full object name table below. - Tables for the first object format: - - A sorted table of shortened object names. These are prefixes of + * A sorted table of shortened object names. These are prefixes of the names of all objects in this pack 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 pack order. This allows resolving + * A table of full object names in pack order. This allows resolving a reference to "the nth object in the pack file" (from a reachability bitmap or from the next table of another object format) to its object name. - - A table of 4-byte values mapping object name order to pack order. + * A table of 4-byte values mapping object name order to pack order. 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. - This can be used to look up the object in reachability bitmaps or to look up its name in another object format. - - A table of 4-byte CRC32 values of the packed object data, in the + * A table of 4-byte CRC32 values of the packed object data, in the order that the objects appear in the pack file. This is to allow compressed data to be copied directly from pack to pack during repacking without undetected data corruption. - - A table of 4-byte offset values. For an object in the table of + * A table of 4-byte offset values. For an object in the table of sorted shortened object names, the value at the corresponding index in this table indicates where that object can be found in the pack file. These are usually 31-bit pack file offsets, but large offsets are encoded as an index into the next table with the most significant bit set. - - A table of 8-byte offset entries (empty for pack files less than + * A table of 8-byte offset entries (empty for pack files less than 2 GiB). Pack files are organized with heavily used objects toward the front, so most object references should not need to refer to this table. @@ -252,10 +276,10 @@ network byte order): up to and not including the table of CRC32 values. - Zero or more NUL bytes. - The trailer consists of the following: - - A copy of the 20-byte SHA-256 checksum at the end of the + * A copy of the 20-byte SHA-256 checksum at the end of the corresponding packfile. - - 20-byte SHA-256 checksum of all of the above. + * 20-byte SHA-256 checksum of all of the above. Loose object index ~~~~~~~~~~~~~~~~~~ @@ -288,18 +312,18 @@ To remove entries (e.g. in "git pack-refs" or "git-prune"): Translation table ~~~~~~~~~~~~~~~~~ -The index files support a bidirectional mapping between sha1-names -and sha256-names. The lookup proceeds similarly to ordinary object -lookups. For example, to convert a sha1-name to a sha256-name: +The index files support a bidirectional mapping between SHA-1 names +and SHA-256 names. The lookup proceeds similarly to ordinary object +lookups. For example, to convert a SHA-1 name to a SHA-256 name: 1. Look for the object in idx files. If a match is present in the - idx's sorted list of truncated sha1-names, then: - a. Read the corresponding entry in the sha1-name order to pack + idx's sorted list of truncated SHA-1 names, then: + a. Read the corresponding entry in the SHA-1 name order to pack name order mapping. - b. Read the corresponding entry in the full sha1-name table to + b. Read the corresponding entry in the full SHA-1 name table to verify we found the right object. If it is, then - c. Read the corresponding entry in the full sha256-name table. - That is the object's sha256-name. + c. Read the corresponding entry in the full SHA-256 name table. + That is the object's SHA-256 name. 2. Check for a loose object. Read lines from loose-object-idx until we find a match. @@ -313,10 +337,10 @@ Since all operations that make new objects (e.g., "git commit") add the new objects to the corresponding index, this mapping is possible for all objects in the object store. -Reading an object's sha1-content -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The sha1-content of an object can be read by converting all sha256-names -its sha256-content references to sha1-names using the translation table. +Reading an object's SHA-1 content +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The SHA-1 content of an object can be read by converting all SHA-256 names +of its SHA-256 content references to SHA-1 names using the translation table. Fetch ~~~~~ @@ -339,7 +363,7 @@ the following steps: 1. index-pack: inflate each object in the packfile and compute its SHA-1. Objects can contain deltas in OBJ_REF_DELTA format against objects the client has locally. These objects can be looked up - using the translation table and their sha1-content read as + using the translation table and their SHA-1 content read as described above to resolve the deltas. 2. topological sort: starting at the "want"s from the negotiation phase, walk through objects in the pack and emit a list of them, @@ -348,12 +372,12 @@ the following steps: (This list only contains objects reachable from the "wants". If the pack from the server contained additional extraneous objects, then they will be discarded.) -3. convert to sha256: open a new (sha256) packfile. Read the topologically +3. convert to SHA-256: open a new SHA-256 packfile. Read the topologically sorted list just generated. For each object, inflate its - sha1-content, convert to sha256-content, and write it to the sha256 - pack. Record the new sha1<->sha256 mapping entry for use in the idx. + SHA-1 content, convert to SHA-256 content, and write it to the SHA-256 + pack. Record the new SHA-1<-->SHA-256 mapping entry for use in the idx. 4. sort: reorder entries in the new pack to match the order of objects - in the pack the server generated and include blobs. Write a sha256 idx + in the pack the server generated and include blobs. Write a SHA-256 idx file 5. clean up: remove the SHA-1 based pack file, index, and topologically sorted list obtained from the server in steps 1 @@ -378,19 +402,20 @@ experimenting to get this to perform well. Push ~~~~ Push is simpler than fetch because the objects referenced by the -pushed objects are already in the translation table. The sha1-content +pushed objects are already in the translation table. The SHA-1 content of each object being pushed can be read as described in the "Reading -an object's sha1-content" section to generate the pack written by git +an object's SHA-1 content" section to generate the pack written by git send-pack. Signed Commits ~~~~~~~~~~~~~~ We add a new field "gpgsig-sha256" to the commit object format to allow signing commits without relying on SHA-1. It is similar to the -existing "gpgsig" field. Its signed payload is the sha256-content of the +existing "gpgsig" field. Its signed payload is the SHA-256 content of the commit object with any "gpgsig" and "gpgsig-sha256" fields removed. This means commits can be signed + 1. using SHA-1 only, as in existing signed commit objects 2. using both SHA-1 and SHA-256, by using both gpgsig-sha256 and gpgsig fields. @@ -404,10 +429,11 @@ Signed Tags ~~~~~~~~~~~ We add a new field "gpgsig-sha256" to the tag object format to allow signing tags without relying on SHA-1. Its signed payload is the -sha256-content of the tag with its gpgsig-sha256 field and "-----BEGIN PGP +SHA-256 content of the tag with its gpgsig-sha256 field and "-----BEGIN PGP SIGNATURE-----" delimited in-body signature removed. This means tags can be signed + 1. using SHA-1 only, as in existing signed tag objects 2. using both SHA-1 and SHA-256, by using gpgsig-sha256 and an in-body signature. @@ -415,11 +441,11 @@ This means tags can be signed Mergetag embedding ~~~~~~~~~~~~~~~~~~ -The mergetag field in the sha1-content of a commit contains the -sha1-content of a tag that was merged by that commit. +The mergetag field in the SHA-1 content of a commit contains the +SHA-1 content of a tag that was merged by that commit. -The mergetag field in the sha256-content of the same commit contains the -sha256-content of the same tag. +The mergetag field in the SHA-256 content of the same commit contains the +SHA-256 content of the same tag. Submodules ~~~~~~~~~~ @@ -494,7 +520,7 @@ Caveats ------- Invalid objects ~~~~~~~~~~~~~~~ -The conversion from sha1-content to sha256-content retains any +The conversion from SHA-1 content to SHA-256 content retains any brokenness in the original object (e.g., tree entry modes encoded with leading 0, tree objects whose paths are not sorted correctly, and commit objects without an author or committer). This is a deliberate @@ -513,15 +539,15 @@ allow lifting this restriction. Alternates ~~~~~~~~~~ -For the same reason, a sha256 repository cannot borrow objects from a -sha1 repository using objects/info/alternates or +For the same reason, a SHA-256 repository cannot borrow objects from a +SHA-1 repository using objects/info/alternates or $GIT_ALTERNATE_OBJECT_REPOSITORIES. git notes ~~~~~~~~~ -The "git notes" tool annotates objects using their sha1-name as key. +The "git notes" tool annotates objects using their SHA-1 name as key. This design does not describe a way to migrate notes trees to use -sha256-names. That migration is expected to happen separately (for +SHA-256 names. That migration is expected to happen separately (for example using a file at the root of the notes tree to describe which hash it uses). @@ -555,7 +581,7 @@ unclear: Git 2.12 -Does this mean Git v2.12.0 is the commit with sha1-name +Does this mean Git v2.12.0 is the commit with SHA-1 name e7e07d5a4fcc2a203d9873968ad3e6bd4d7419d7 or the commit with new-40-digit-hash-name e7e07d5a4fcc2a203d9873968ad3e6bd4d7419d7? @@ -598,44 +624,12 @@ The user can also explicitly specify which format to use for a particular revision specifier and for output, overriding the mode. For example: -git --output-format=sha1 log abac87a^{sha1}..f787cac^{sha256} - -Choice of Hash --------------- -In early 2005, around the time that Git was written, Xiaoyun Wang, -Yiqun Lisa Yin, and Hongbo Yu announced an attack finding SHA-1 -collisions in 2^69 operations. In August they published details. -Luckily, no practical demonstrations of a collision in full SHA-1 were -published until 10 years later, in 2017. - -Git v2.13.0 and later subsequently moved to a hardened SHA-1 -implementation by default that mitigates the SHAttered attack, but -SHA-1 is still believed to be weak. - -The hash to replace this hardened SHA-1 should be stronger than SHA-1 -was: we would like it to be trustworthy and useful in practice for at -least 10 years. - -Some other relevant properties: - -1. A 256-bit hash (long enough to match common security practice; not - excessively long to hurt performance and disk usage). - -2. High quality implementations should be widely available (e.g., in - OpenSSL and Apple CommonCrypto). - -3. The hash function's properties should match Git's needs (e.g. Git - requires collision and 2nd preimage resistance and does not require - length extension resistance). - -4. As a tiebreaker, the hash should be fast to compute (fortunately - many contenders are faster than SHA-1). - -We choose SHA-256. + git --output-format=sha1 log abac87a^{sha1}..f787cac^{sha256} Transition plan --------------- Some initial steps can be implemented independently of one another: + - adding a hash function API (vtable) - teaching fsck to tolerate the gpgsig-sha256 field - excluding gpgsig-* from the fields copied by "git commit --amend" @@ -647,9 +641,9 @@ Some initial steps can be implemented independently of one another: - introducing index v3 - adding support for the PSRC field and safer object pruning - The first user-visible change is the introduction of the objectFormat extension (without compatObjectFormat). This requires: + - teaching fsck about this mode of operation - using the hash function API (vtable) when computing object names - signing objects and verifying signatures @@ -657,6 +651,7 @@ extension (without compatObjectFormat). This requires: repository Next comes introduction of compatObjectFormat: + - implementing the loose-object-idx - translating object names between object formats - translating object content between object formats @@ -669,10 +664,11 @@ Next comes introduction of compatObjectFormat: "Object names on the command line" above) The next step is supporting fetches and pushes to SHA-1 repositories: + - allow pushes to a repository using the compat format - generate a topologically sorted list of the SHA-1 names of fetched objects -- convert the fetched packfile to sha256 format and generate an idx +- convert the fetched packfile to SHA-256 format and generate an idx file - re-sort to match the order of objects in the fetched packfile @@ -734,6 +730,7 @@ Using hash functions in parallel Objects newly created would be addressed by the new hash, but inside such an object (e.g. commit) it is still possible to address objects using the old hash function. + * You cannot trust its history (needed for bisectability) in the future without further work * Maintenance burden as the number of supported hash functions grows @@ -743,36 +740,38 @@ using the old hash function. Signed objects with multiple hashes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Instead of introducing the gpgsig-sha256 field in commit and tag objects -for sha256-content based signatures, an earlier version of this design -added "hash sha256 " fields to strengthen the existing -sha1-content based signatures. +for SHA-256 content based signatures, an earlier version of this design +added "hash sha256 " fields to strengthen the existing +SHA-1 content based signatures. In other words, a single signature was used to attest to the object content using both hash functions. This had some advantages: + * Using one signature instead of two speeds up the signing process. * Having one signed payload with both hashes allows the signer to - attest to the sha1-name and sha256-name referring to the same object. + attest to the SHA-1 name and SHA-256 name referring to the same object. * All users consume the same signature. Broken signatures are likely to be detected quickly using current versions of git. However, it also came with disadvantages: -* Verifying a signed object requires access to the sha1-names of all + +* Verifying a signed object requires access to the SHA-1 names of all objects it references, even after the transition is complete and translation table is no longer needed for anything else. To support - this, the design added fields such as "hash sha1 tree " - and "hash sha1 parent " to the sha256-content of a signed + this, the design added fields such as "hash sha1 tree " + and "hash sha1 parent " to the SHA-256 content of a signed commit, complicating the conversion process. -* Allowing signed objects without a sha1 (for after the transition is +* Allowing signed objects without a SHA-1 (for after the transition is complete) complicated the design further, requiring a "nohash sha1" - field to suppress including "hash sha1" fields in the sha256-content + field to suppress including "hash sha1" fields in the SHA-256 content and signed payload. Lazily populated translation table ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Some of the work of building the translation table could be deferred to push time, but that would significantly complicate and slow down pushes. -Calculating the sha1-name at object creation time at the same time it is -being streamed to disk and having its sha256-name calculated should be +Calculating the SHA-1 name at object creation time at the same time it is +being streamed to disk and having its SHA-256 name calculated should be an acceptable cost. Document History @@ -782,18 +781,19 @@ Document History bmwill@google.com, jonathantanmy@google.com, jrnieder@gmail.com, sbeller@google.com -Initial version sent to -http://lore.kernel.org/git/20170304011251.GA26789@aiede.mtv.corp.google.com +* Initial version sent to https://lore.kernel.org/git/20170304011251.GA26789@aiede.mtv.corp.google.com 2017-03-03 jrnieder@gmail.com Incorporated suggestions from jonathantanmy and sbeller: -* describe purpose of signed objects with each hash type -* redefine signed object verification using object content under the + +* Describe purpose of signed objects with each hash type +* Redefine signed object verification using object content under the first hash function 2017-03-06 jrnieder@gmail.com + * Use SHA3-256 instead of SHA2 (thanks, Linus and brian m. carlson).[1][2] -* Make sha3-based signatures a separate field, avoiding the need for +* Make SHA3-based signatures a separate field, avoiding the need for "hash" and "nohash" fields (thanks to peff[3]). * Add a sorting phase to fetch (thanks to Junio for noticing the need for this). @@ -805,23 +805,26 @@ Incorporated suggestions from jonathantanmy and sbeller: especially Junio). 2017-09-27 jrnieder@gmail.com, sbeller@google.com -* use placeholder NewHash instead of SHA3-256 -* describe criteria for picking a hash function. -* include a transition plan (thanks especially to Brandon Williams + +* Use placeholder NewHash instead of SHA3-256 +* Describe criteria for picking a hash function. +* Include a transition plan (thanks especially to Brandon Williams for fleshing these ideas out) -* define the translation table (thanks, Shawn Pearce[5], Jonathan +* Define the translation table (thanks, Shawn Pearce[5], Jonathan Tan, and Masaya Suzuki) -* avoid loose object overhead by packing more aggressively in +* Avoid loose object overhead by packing more aggressively in "git gc --auto" Later history: - See the history of this file in git.git for the history of subsequent - edits. This document history is no longer being maintained as it - would now be superfluous to the commit log +* See the history of this file in git.git for the history of subsequent + edits. This document history is no longer being maintained as it + would now be superfluous to the commit log + +References: -[1] http://lore.kernel.org/git/CA+55aFzJtejiCjV0e43+9oR3QuJK2PiFiLQemytoLpyJWe6P9w@mail.gmail.com/ -[2] http://lore.kernel.org/git/CA+55aFz+gkAsDZ24zmePQuEs1XPS9BP_s8O7Q4wQ7LV7X5-oDA@mail.gmail.com/ -[3] http://lore.kernel.org/git/20170306084353.nrns455dvkdsfgo5@sigill.intra.peff.net/ -[4] http://lore.kernel.org/git/20170304224936.rqqtkdvfjgyezsht@genre.crustytoothpaste.net -[5] https://lore.kernel.org/git/CAJo=hJtoX9=AyLHHpUJS7fueV9ciZ_MNpnEPHUz8Whui6g9F0A@mail.gmail.com/ + [1] https://lore.kernel.org/git/CA+55aFzJtejiCjV0e43+9oR3QuJK2PiFiLQemytoLpyJWe6P9w@mail.gmail.com/ + [2] https://lore.kernel.org/git/CA+55aFz+gkAsDZ24zmePQuEs1XPS9BP_s8O7Q4wQ7LV7X5-oDA@mail.gmail.com/ + [3] https://lore.kernel.org/git/20170306084353.nrns455dvkdsfgo5@sigill.intra.peff.net/ + [4] https://lore.kernel.org/git/20170304224936.rqqtkdvfjgyezsht@genre.crustytoothpaste.net + [5] https://lore.kernel.org/git/CAJo=hJtoX9=AyLHHpUJS7fueV9ciZ_MNpnEPHUz8Whui6g9F0A@mail.gmail.com/ diff --git a/Documentation/technical/index-format.txt b/Documentation/technical/index-format.txt index b633482b1b..d363a71c37 100644 --- a/Documentation/technical/index-format.txt +++ b/Documentation/technical/index-format.txt @@ -273,14 +273,14 @@ Git index format - Stat data of $GIT_DIR/info/exclude. See "Index entry" section from ctime field until "file size". - - Stat data of core.excludesfile + - Stat data of core.excludesFile - 32-bit dir_flags (see struct dir_struct) - Hash of $GIT_DIR/info/exclude. A null hash means the file does not exist. - - Hash of core.excludesfile. A null hash means the file does + - Hash of core.excludesFile. A null hash means the file does not exist. - NUL-terminated string of per-dir exclude file name. This usually diff --git a/Documentation/technical/multi-pack-index.txt b/Documentation/technical/multi-pack-index.txt index e8e377a59f..fb688976c4 100644 --- a/Documentation/technical/multi-pack-index.txt +++ b/Documentation/technical/multi-pack-index.txt @@ -43,8 +43,9 @@ Design Details a change in format. - The MIDX keeps only one record per object ID. If an object appears - in multiple packfiles, then the MIDX selects the copy in the most- - recently modified packfile. + in multiple packfiles, then the MIDX selects the copy in the + preferred packfile, otherwise selecting from the most-recently + modified packfile. - If there exist packfiles in the pack directory not registered in the MIDX, then those packfiles are loaded into the `packed_git` diff --git a/Documentation/technical/pack-format.txt b/Documentation/technical/pack-format.txt index 96d2fc589f..8d2f42f29e 100644 --- a/Documentation/technical/pack-format.txt +++ b/Documentation/technical/pack-format.txt @@ -274,6 +274,26 @@ Pack file entry: <+ Index checksum of all of the above. +== pack-*.rev files have the format: + + - A 4-byte magic number '0x52494458' ('RIDX'). + + - A 4-byte version identifier (= 1). + + - A 4-byte hash function identifier (= 1 for SHA-1, 2 for SHA-256). + + - A table of index positions (one per packed object, num_objects in + total, each a 4-byte unsigned integer in network order), sorted by + their corresponding offsets in the packfile. + + - A trailer, containing a: + + checksum of the corresponding packfile, and + + a checksum of all of the above. + +All 4-byte numbers are in network order. + == multi-pack-index (MIDX) files have the following format: The multi-pack-index files refer to multiple pack-files and loose objects. @@ -316,6 +336,9 @@ CHUNK LOOKUP: (Chunks are provided in file-order, so you can infer the length using the next chunk position if necessary.) + The CHUNK LOOKUP matches the table of contents from + link:technical/chunk-format.html[the chunk-based file format]. + The remaining data in the body is described one chunk at a time, and these chunks may be given in any order. Chunks are required unless otherwise specified. @@ -356,3 +379,86 @@ CHUNK DATA: TRAILER: Index checksum of the above contents. + +== multi-pack-index reverse indexes + +Similar to the pack-based reverse index, the multi-pack index can also +be used to generate a reverse index. + +Instead of mapping between offset, pack-, and index position, this +reverse index maps between an object's position within the MIDX, and +that object's position within a pseudo-pack that the MIDX describes +(i.e., the ith entry of the multi-pack reverse index holds the MIDX +position of ith object in pseudo-pack order). + +To clarify the difference between these orderings, consider a multi-pack +reachability bitmap (which does not yet exist, but is what we are +building towards here). Each bit needs to correspond to an object in the +MIDX, and so we need an efficient mapping from bit position to MIDX +position. + +One solution is to let bits occupy the same position in the oid-sorted +index stored by the MIDX. But because oids are effectively random, their +resulting reachability bitmaps would have no locality, and thus compress +poorly. (This is the reason that single-pack bitmaps use the pack +ordering, and not the .idx ordering, for the same purpose.) + +So we'd like to define an ordering for the whole MIDX based around +pack ordering, which has far better locality (and thus compresses more +efficiently). We can think of a pseudo-pack created by the concatenation +of all of the packs in the MIDX. E.g., if we had a MIDX with three packs +(a, b, c), with 10, 15, and 20 objects respectively, we can imagine an +ordering of the objects like: + + |a,0|a,1|...|a,9|b,0|b,1|...|b,14|c,0|c,1|...|c,19| + +where the ordering of the packs is defined by the MIDX's pack list, +and then the ordering of objects within each pack is the same as the +order in the actual packfile. + +Given the list of packs and their counts of objects, you can +naïvely reconstruct that pseudo-pack ordering (e.g., the object at +position 27 must be (c,1) because packs "a" and "b" consumed 25 of the +slots). But there's a catch. Objects may be duplicated between packs, in +which case the MIDX only stores one pointer to the object (and thus we'd +want only one slot in the bitmap). + +Callers could handle duplicates themselves by reading objects in order +of their bit-position, but that's linear in the number of objects, and +much too expensive for ordinary bitmap lookups. Building a reverse index +solves this, since it is the logical inverse of the index, and that +index has already removed duplicates. But, building a reverse index on +the fly can be expensive. Since we already have an on-disk format for +pack-based reverse indexes, let's reuse it for the MIDX's pseudo-pack, +too. + +Objects from the MIDX are ordered as follows to string together the +pseudo-pack. Let `pack(o)` return the pack from which `o` was selected +by the MIDX, and define an ordering of packs based on their numeric ID +(as stored by the MIDX). Let `offset(o)` return the object offset of `o` +within `pack(o)`. Then, compare `o1` and `o2` as follows: + + - If one of `pack(o1)` and `pack(o2)` is preferred and the other + is not, then the preferred one sorts first. ++ +(This is a detail that allows the MIDX bitmap to determine which +pack should be used by the pack-reuse mechanism, since it can ask +the MIDX for the pack containing the object at bit position 0). + + - If `pack(o1) ≠ pack(o2)`, then sort the two objects in descending + order based on the pack ID. + + - Otherwise, `pack(o1) = pack(o2)`, and the objects are sorted in + pack-order (i.e., `o1` sorts ahead of `o2` exactly when `offset(o1) + < offset(o2)`). + +In short, a MIDX's pseudo-pack is the de-duplicated concatenation of +objects in packs stored by the MIDX, laid out in pack order, and the +packs arranged in MIDX order (with the preferred pack coming first). + +Finally, note that the MIDX's reverse index is not stored as a chunk in +the multi-pack-index itself. This is done because the reverse index +includes the checksum of the pack or MIDX to which it belongs, which +makes it impossible to write in the MIDX. To avoid races when rewriting +the MIDX, a MIDX reverse index includes the MIDX's checksum in its +filename (e.g., `multi-pack-index-xyz.rev`). diff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt index 85daeb5d9e..a7c806a73e 100644 --- a/Documentation/technical/protocol-v2.txt +++ b/Documentation/technical/protocol-v2.txt @@ -33,8 +33,8 @@ In protocol v2 these special packets will have the following semantics: * '0000' Flush Packet (flush-pkt) - indicates the end of a message * '0001' Delimiter Packet (delim-pkt) - separates sections of a message - * '0002' Message Packet (response-end-pkt) - indicates the end of a response - for stateless connections + * '0002' Response End Packet (response-end-pkt) - indicates the end of a + response for stateless connections Initial Client Request ---------------------- @@ -192,11 +192,20 @@ ls-refs takes in the following arguments: When specified, only references having a prefix matching one of the provided prefixes are displayed. +If the 'unborn' feature is advertised the following argument can be +included in the client's request. + + unborn + The server will send information about HEAD even if it is a symref + pointing to an unborn branch in the form "unborn HEAD + symref-target:". + The output of ls-refs is as follows: output = *ref flush-pkt - ref = PKT-LINE(obj-id SP refname *(SP ref-attribute) LF) + obj-id-or-unborn = (obj-id | "unborn") + ref = PKT-LINE(obj-id-or-unborn SP refname *(SP ref-attribute) LF) ref-attribute = (symref | peeled) symref = "symref-target:" symref-target peeled = "peeled:" obj-id diff --git a/Documentation/technical/reftable.txt b/Documentation/technical/reftable.txt index 8095ab2590..3ef169af27 100644 --- a/Documentation/technical/reftable.txt +++ b/Documentation/technical/reftable.txt @@ -872,17 +872,11 @@ A repository must set its `$GIT_DIR/config` to configure reftable: Layout ^^^^^^ -A collection of reftable files are stored in the `$GIT_DIR/reftable/` -directory: - -.... -00000001-00000001.log -00000002-00000002.ref -00000003-00000003.ref -.... - -where reftable files are named by a unique name such as produced by the -function `${min_update_index}-${max_update_index}.ref`. +A collection of reftable files are stored in the `$GIT_DIR/reftable/` directory. +Their names should have a random element, such that each filename is globally +unique; this helps avoid spurious failures on Windows, where open files cannot +be removed or overwritten. It suggested to use +`${min_update_index}-${max_update_index}-${random}.ref` as a naming convention. Log-only files use the `.log` extension, while ref-only and mixed ref and log files use `.ref`. extension. @@ -893,9 +887,9 @@ current files, one per line, in order, from oldest (base) to newest .... $ cat .git/reftable/tables.list -00000001-00000001.log -00000002-00000002.ref -00000003-00000003.ref +00000001-00000001-RANDOM1.log +00000002-00000002-RANDOM2.ref +00000003-00000003-RANDOM3.ref .... Readers must read `$GIT_DIR/reftable/tables.list` to determine which @@ -940,7 +934,7 @@ new reftable and atomically appending it to the stack: 3. Select `update_index` to be most recent file's `max_update_index + 1`. 4. Prepare temp reftable `tmp_XXXXXX`, including log entries. -5. Rename `tmp_XXXXXX` to `${update_index}-${update_index}.ref`. +5. Rename `tmp_XXXXXX` to `${update_index}-${update_index}-${random}.ref`. 6. Copy `tables.list` to `tables.list.lock`, appending file from (5). 7. Rename `tables.list.lock` to `tables.list`. @@ -993,7 +987,7 @@ prevents other processes from trying to compact these files. should always be the case, assuming that other processes are adhering to the locking protocol. 7. Rename `${min_update_index}-${max_update_index}_XXXXXX` to -`${min_update_index}-${max_update_index}.ref`. +`${min_update_index}-${max_update_index}-${random}.ref`. 8. Write the new stack to `tables.list.lock`, replacing `B` and `C` with the file from (4). 9. Rename `tables.list.lock` to `tables.list`. @@ -1005,6 +999,22 @@ This strategy permits compactions to proceed independently of updates. Each reftable (compacted or not) is uniquely identified by its name, so open reftables can be cached by their name. +Windows +^^^^^^^ + +On windows, and other systems that do not allow deleting or renaming to open +files, compaction may succeed, but other readers may prevent obsolete tables +from being deleted. + +On these platforms, the following strategy can be followed: on closing a +reftable stack, reload `tables.list`, and delete any tables no longer mentioned +in `tables.list`. + +Irregular program exit may still leave about unused files. In this case, a +cleanup operation can read `tables.list`, note its modification timestamp, and +delete any unreferenced `*.ref` files that are older. + + Alternatives considered ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 0327733794..d2ff91775e 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.30.GIT +DEF_VER=v2.31.GIT LF=' ' diff --git a/INSTALL b/INSTALL index 8474ad01bf..66389ce059 100644 --- a/INSTALL +++ b/INSTALL @@ -197,7 +197,9 @@ Issues of note: Building and installing the pdf file additionally requires dblatex. Version >= 0.2.7 is known to work. - All formats require at least asciidoc 8.4.1. + All formats require at least asciidoc 8.4.1. Alternatively, you can + use Asciidoctor (requires Ruby) by passing USE_ASCIIDOCTOR=YesPlease + to make. You need at least Asciidoctor version 1.5. There are also "make quick-install-doc", "make quick-install-man" and "make quick-install-html" which install preformatted man pages diff --git a/Makefile b/Makefile index 5a239cac20..a6a73c5741 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,9 @@ all:: # when attempting to read from an fopen'ed directory (or even to fopen # it at all). # +# Define OPEN_RETURNS_EINTR if your open() system call may return EINTR +# when a signal is received (as opposed to restarting). +# # Define NO_OPENSSL environment variable if you do not have OpenSSL. # # Define USE_LIBPCRE if you have and want to use libpcre. Various @@ -575,7 +578,9 @@ GENERATED_H = EXTRA_CPPFLAGS = FUZZ_OBJS = FUZZ_PROGRAMS = +GIT_OBJS = LIB_OBJS = +OBJECTS = PROGRAM_OBJS = PROGRAMS = EXCLUDED_PROGRAMS = @@ -584,6 +589,7 @@ SCRIPT_PYTHON = SCRIPT_SH = SCRIPT_LIB = TEST_BUILTINS_OBJS = +TEST_OBJS = TEST_PROGRAMS_NEED_X = THIRD_PARTY_SOURCES = @@ -659,6 +665,8 @@ ETAGS_TARGET = TAGS FUZZ_OBJS += fuzz-commit-graph.o FUZZ_OBJS += fuzz-pack-headers.o FUZZ_OBJS += fuzz-pack-idx.o +.PHONY: fuzz-objs +fuzz-objs: $(FUZZ_OBJS) # Always build fuzz objects even if not testing, to prevent bit-rot. all:: $(FUZZ_OBJS) @@ -676,6 +684,8 @@ PROGRAM_OBJS += http-backend.o PROGRAM_OBJS += imap-send.o PROGRAM_OBJS += sh-i18n--envsubst.o PROGRAM_OBJS += shell.o +.PHONY: program-objs +program-objs: $(PROGRAM_OBJS) # Binary suffix, set to .exe for Windows builds X = @@ -734,6 +744,7 @@ TEST_BUILTINS_OBJS += test-serve-v2.o TEST_BUILTINS_OBJS += test-sha1.o TEST_BUILTINS_OBJS += test-sha256.o TEST_BUILTINS_OBJS += test-sigchain.o +TEST_BUILTINS_OBJS += test-simple-ipc.o TEST_BUILTINS_OBJS += test-strcmp-offset.o TEST_BUILTINS_OBJS += test-string-list.o TEST_BUILTINS_OBJS += test-submodule-config.o @@ -834,6 +845,7 @@ LIB_OBJS += bundle.o LIB_OBJS += cache-tree.o LIB_OBJS += chdir-notify.o LIB_OBJS += checkout.o +LIB_OBJS += chunk-format.o LIB_OBJS += color.o LIB_OBJS += column.o LIB_OBJS += combine-diff.o @@ -863,6 +875,7 @@ LIB_OBJS += diffcore-delta.o LIB_OBJS += diffcore-order.o LIB_OBJS += diffcore-pickaxe.o LIB_OBJS += diffcore-rename.o +LIB_OBJS += diffcore-rotate.o LIB_OBJS += dir-iterator.o LIB_OBJS += dir.o LIB_OBJS += editor.o @@ -1537,6 +1550,10 @@ ifdef FREAD_READS_DIRECTORIES COMPAT_CFLAGS += -DFREAD_READS_DIRECTORIES COMPAT_OBJS += compat/fopen.o endif +ifdef OPEN_RETURNS_EINTR + COMPAT_CFLAGS += -DOPEN_RETURNS_EINTR + COMPAT_OBJS += compat/open.o +endif ifdef NO_SYMLINK_HEAD BASIC_CFLAGS += -DNO_SYMLINK_HEAD endif @@ -1663,6 +1680,14 @@ ifdef NO_UNIX_SOCKETS BASIC_CFLAGS += -DNO_UNIX_SOCKETS else LIB_OBJS += unix-socket.o + LIB_OBJS += unix-stream-server.o + LIB_OBJS += compat/simple-ipc/ipc-shared.o + LIB_OBJS += compat/simple-ipc/ipc-unix-socket.o +endif + +ifdef USE_WIN32_IPC + LIB_OBJS += compat/simple-ipc/ipc-shared.o + LIB_OBJS += compat/simple-ipc/ipc-win32.o endif ifdef NO_ICONV @@ -2369,16 +2394,30 @@ XDIFF_OBJS += xdiff/xmerge.o XDIFF_OBJS += xdiff/xpatience.o XDIFF_OBJS += xdiff/xprepare.o XDIFF_OBJS += xdiff/xutils.o +.PHONY: xdiff-objs +xdiff-objs: $(XDIFF_OBJS) TEST_OBJS := $(patsubst %$X,%.o,$(TEST_PROGRAMS)) $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS)) -OBJECTS := $(LIB_OBJS) $(BUILTIN_OBJS) $(PROGRAM_OBJS) $(TEST_OBJS) \ - $(XDIFF_OBJS) \ - $(FUZZ_OBJS) \ - common-main.o \ - git.o +.PHONY: test-objs +test-objs: $(TEST_OBJS) + +GIT_OBJS += $(LIB_OBJS) +GIT_OBJS += $(BUILTIN_OBJS) +GIT_OBJS += common-main.o +GIT_OBJS += git.o +.PHONY: git-objs +git-objs: $(GIT_OBJS) + +OBJECTS += $(GIT_OBJS) +OBJECTS += $(PROGRAM_OBJS) +OBJECTS += $(TEST_OBJS) +OBJECTS += $(XDIFF_OBJS) +OBJECTS += $(FUZZ_OBJS) ifndef NO_CURL OBJECTS += http.o http-walker.o remote-curl.o endif +.PHONY: objects +objects: $(OBJECTS) dep_files := $(foreach f,$(OBJECTS),$(dir $f).depend/$(notdir $f).d) dep_dirs := $(addsuffix .depend,$(sort $(dir $(OBJECTS)))) @@ -3290,11 +3329,11 @@ cover_db_html: cover_db # are not necessarily appropriate for general builds, and that vary greatly # depending on the compiler version used. # -# An example command to build against libFuzzer from LLVM 4.0.0: +# An example command to build against libFuzzer from LLVM 11.0.0: # # make CC=clang CXX=clang++ \ -# CFLAGS="-fsanitize-coverage=trace-pc-guard -fsanitize=address" \ -# LIB_FUZZING_ENGINE=/usr/lib/llvm-4.0/lib/libFuzzer.a \ +# CFLAGS="-fsanitize=fuzzer-no-link,address" \ +# LIB_FUZZING_ENGINE="-fsanitize=fuzzer" \ # fuzz-all # FUZZ_CXXFLAGS ?= $(CFLAGS) diff --git a/RelNotes b/RelNotes index 3324fc058d..aece21e8a4 120000 --- a/RelNotes +++ b/RelNotes @@ -1 +1 @@ -Documentation/RelNotes/2.31.0.txt \ No newline at end of file +Documentation/RelNotes/2.32.0.txt \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000..c720c2ae7f --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,51 @@ +# Security Policy + +## Reporting a vulnerability + +Please send a detailed mail to git-security@googlegroups.com to +report vulnerabilities in Git. + +Even when unsure whether the bug in question is an exploitable +vulnerability, it is recommended to send the report to +git-security@googlegroups.com (and obviously not to discuss the +issue anywhere else). + +Vulnerabilities are expected to be discussed _only_ on that +list, and not in public, until the official announcement on the +Git mailing list on the release date. + +Examples for details to include: + +- Ideally a short description (or a script) to demonstrate an + exploit. +- The affected platforms and scenarios (the vulnerability might + only affect setups with case-sensitive file systems, for + example). +- The name and affiliation of the security researchers who are + involved in the discovery, if any. +- Whether the vulnerability has already been disclosed. +- How long an embargo would be required to be safe. + +## Supported Versions + +There are no official "Long Term Support" versions in Git. +Instead, the maintenance track (i.e. the versions based on the +most recently published feature release, also known as ".0" +version) sees occasional updates with bug fixes. + +Fixes to vulnerabilities are made for the maintenance track for +the latest feature release and merged up to the in-development +branches. The Git project makes no formal guarantee for any +older maintenance tracks to receive updates. In practice, +though, critical vulnerability fixes are applied not only to the +most recent track, but to at least a couple more maintenance +tracks. + +This is typically done by making the fix on the oldest and still +relevant maintenance track, and merging it upwards to newer and +newer maintenance tracks. + +For example, v2.24.1 was released to address a couple of +[CVEs](https://cve.mitre.org/), and at the same time v2.14.6, +v2.15.4, v2.16.6, v2.17.3, v2.18.2, v2.19.3, v2.20.2, v2.21.1, +v2.22.2 and v2.23.1 were released. diff --git a/add-interactive.c b/add-interactive.c index 9b8cdb4a31..36ebdbdf7e 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -413,7 +413,7 @@ struct file_item { static void add_file_item(struct string_list *files, const char *name) { - struct file_item *item = xcalloc(sizeof(*item), 1); + struct file_item *item = xcalloc(1, sizeof(*item)); string_list_append(files, name)->util = item; } @@ -476,7 +476,7 @@ static void collect_changes_cb(struct diff_queue_struct *q, add_file_item(s->files, name); - entry = xcalloc(sizeof(*entry), 1); + CALLOC_ARRAY(entry, 1); hashmap_entry_init(&entry->ent, hash); entry->name = s->files->items[s->files->nr - 1].string; entry->item = s->files->items[s->files->nr - 1].util; @@ -1120,7 +1120,7 @@ int run_add_i(struct repository *r, const struct pathspec *ps) int res = 0; for (i = 0; i < ARRAY_SIZE(command_list); i++) { - struct command_item *util = xcalloc(sizeof(*util), 1); + struct command_item *util = xcalloc(1, sizeof(*util)); util->command = command_list[i].command; string_list_append(&commands.items, command_list[i].string) ->util = util; diff --git a/apply.c b/apply.c index 668b16e989..466f880d73 100644 --- a/apply.c +++ b/apply.c @@ -21,6 +21,7 @@ #include "quote.h" #include "rerere.h" #include "apply.h" +#include "entry.h" struct gitdiff_data { struct strbuf *root; @@ -1781,7 +1782,7 @@ static int parse_single_patch(struct apply_state *state, struct fragment *fragment; int len; - fragment = xcalloc(1, sizeof(*fragment)); + CALLOC_ARRAY(fragment, 1); fragment->linenr = state->linenr; len = parse_fragment(state, line, size, patch, fragment); if (len <= 0) { @@ -1959,7 +1960,7 @@ static struct fragment *parse_binary_hunk(struct apply_state *state, size -= llen; } - frag = xcalloc(1, sizeof(*frag)); + CALLOC_ARRAY(frag, 1); frag->patch = inflate_it(data, hunk_size, origlen); frag->free_patch = 1; if (!frag->patch) @@ -4681,7 +4682,7 @@ static int apply_patch(struct apply_state *state, struct patch *patch; int nr; - patch = xcalloc(1, sizeof(*patch)); + CALLOC_ARRAY(patch, 1); patch->inaccurate_eof = !!(options & APPLY_OPT_INACCURATE_EOF); patch->recount = !!(options & APPLY_OPT_RECOUNT); nr = parse_chunk(state, buf.buf + offset, buf.len - offset, patch); diff --git a/archive-tar.c b/archive-tar.c index a971fdc0f6..05d2455870 100644 --- a/archive-tar.c +++ b/archive-tar.c @@ -371,7 +371,7 @@ static int tar_filter_config(const char *var, const char *value, void *data) ar = find_tar_filter(name, namelen); if (!ar) { - ar = xcalloc(1, sizeof(*ar)); + CALLOC_ARRAY(ar, 1); ar->name = xmemdupz(name, namelen); ar->write_archive = write_tar_filter_archive; ar->flags = ARCHIVER_WANT_COMPRESSION_LEVELS | diff --git a/archive.c b/archive.c index 5919d9e505..295615580d 100644 --- a/archive.c +++ b/archive.c @@ -37,13 +37,10 @@ void init_archivers(void) static void format_subst(const struct commit *commit, const char *src, size_t len, - struct strbuf *buf) + struct strbuf *buf, struct pretty_print_context *ctx) { char *to_free = NULL; struct strbuf fmt = STRBUF_INIT; - struct pretty_print_context ctx = {0}; - ctx.date_mode.type = DATE_NORMAL; - ctx.abbrev = DEFAULT_ABBREV; if (src == buf->buf) to_free = strbuf_detach(buf, NULL); @@ -61,7 +58,7 @@ static void format_subst(const struct commit *commit, strbuf_add(&fmt, b + 8, c - b - 8); strbuf_add(buf, src, b - src); - format_commit_message(commit, fmt.buf, buf, &ctx); + format_commit_message(commit, fmt.buf, buf, ctx); len -= c + 1 - src; src = c + 1; } @@ -94,7 +91,7 @@ static void *object_file_to_archive(const struct archiver_args *args, strbuf_attach(&buf, buffer, *sizep, *sizep + 1); convert_to_working_tree(args->repo->index, path, buf.buf, buf.len, &buf, &meta); if (commit) - format_subst(commit, buf.buf, buf.len, &buf); + format_subst(commit, buf.buf, buf.len, &buf, args->pretty_ctx); buffer = strbuf_detach(&buf, &size); *sizep = size; } @@ -107,7 +104,6 @@ struct directory { struct object_id oid; int baselen, len; unsigned mode; - int stage; char path[FLEX_ARRAY]; }; @@ -138,7 +134,7 @@ static int check_attr_export_subst(const struct attr_check *check) } static int write_archive_entry(const struct object_id *oid, const char *base, - int baselen, const char *filename, unsigned mode, int stage, + int baselen, const char *filename, unsigned mode, void *context) { static struct strbuf path = STRBUF_INIT; @@ -197,7 +193,7 @@ static int write_archive_entry(const struct object_id *oid, const char *base, static void queue_directory(const unsigned char *sha1, struct strbuf *base, const char *filename, - unsigned mode, int stage, struct archiver_context *c) + unsigned mode, struct archiver_context *c) { struct directory *d; size_t len = st_add4(base->len, 1, strlen(filename), 1); @@ -205,7 +201,6 @@ static void queue_directory(const unsigned char *sha1, d->up = c->bottom; d->baselen = base->len; d->mode = mode; - d->stage = stage; c->bottom = d; d->len = xsnprintf(d->path, len, "%.*s%s/", (int)base->len, base->buf, filename); hashcpy(d->oid.hash, sha1); @@ -224,14 +219,14 @@ static int write_directory(struct archiver_context *c) write_directory(c) || write_archive_entry(&d->oid, d->path, d->baselen, d->path + d->baselen, d->mode, - d->stage, c) != READ_TREE_RECURSIVE; + c) != READ_TREE_RECURSIVE; free(d); return ret ? -1 : 0; } static int queue_or_write_archive_entry(const struct object_id *oid, struct strbuf *base, const char *filename, - unsigned mode, int stage, void *context) + unsigned mode, void *context) { struct archiver_context *c = context; @@ -256,14 +251,14 @@ static int queue_or_write_archive_entry(const struct object_id *oid, if (check_attr_export_ignore(check)) return 0; queue_directory(oid->hash, base, filename, - mode, stage, c); + mode, c); return READ_TREE_RECURSIVE; } if (write_directory(c)) return -1; return write_archive_entry(oid, base->buf, base->len, filename, mode, - stage, context); + context); } struct extra_file_info { @@ -316,10 +311,10 @@ int write_archive_entries(struct archiver_args *args, git_attr_set_direction(GIT_ATTR_INDEX); } - err = read_tree_recursive(args->repo, args->tree, "", - 0, 0, &args->pathspec, - queue_or_write_archive_entry, - &context); + err = read_tree(args->repo, args->tree, + &args->pathspec, + queue_or_write_archive_entry, + &context); if (err == READ_TREE_RECURSIVE) err = 0; while (context.bottom) { @@ -378,7 +373,7 @@ struct path_exists_context { static int reject_entry(const struct object_id *oid, struct strbuf *base, const char *filename, unsigned mode, - int stage, void *context) + void *context) { int ret = -1; struct path_exists_context *ctx = context; @@ -405,9 +400,9 @@ static int path_exists(struct archiver_args *args, const char *path) ctx.args = args; parse_pathspec(&ctx.pathspec, 0, 0, "", paths); ctx.pathspec.recursive = 1; - ret = read_tree_recursive(args->repo, args->tree, "", - 0, 0, &ctx.pathspec, - reject_entry, &ctx); + ret = read_tree(args->repo, args->tree, + &ctx.pathspec, + reject_entry, &ctx); clear_pathspec(&ctx.pathspec); return ret != 0; } @@ -633,12 +628,19 @@ int write_archive(int argc, const char **argv, const char *prefix, const char *name_hint, int remote) { const struct archiver *ar = NULL; + struct pretty_print_describe_status describe_status = {0}; + struct pretty_print_context ctx = {0}; struct archiver_args args; int rc; git_config_get_bool("uploadarchive.allowunreachable", &remote_allow_unreachable); git_config(git_default_config, NULL); + describe_status.max_invocations = 1; + ctx.date_mode.type = DATE_NORMAL; + ctx.abbrev = DEFAULT_ABBREV; + ctx.describe_status = &describe_status; + args.pretty_ctx = &ctx; args.repo = repo; args.prefix = prefix; string_list_init(&args.extra_files, 1); diff --git a/archive.h b/archive.h index 33551b7ee1..49fab71aaf 100644 --- a/archive.h +++ b/archive.h @@ -5,6 +5,7 @@ #include "pathspec.h" struct repository; +struct pretty_print_context; struct archiver_args { struct repository *repo; @@ -22,6 +23,7 @@ struct archiver_args { unsigned int convert : 1; int compression_level; struct string_list extra_files; + struct pretty_print_context *pretty_ctx; }; /* main api */ diff --git a/attr.c b/attr.c index 4ef85d668b..ac8ec7ce51 100644 --- a/attr.c +++ b/attr.c @@ -278,6 +278,10 @@ struct match_attr { static const char blank[] = " \t\r\n"; +/* Flags usable in read_attr() and parse_attr_line() family of functions. */ +#define READ_ATTR_MACRO_OK (1<<0) +#define READ_ATTR_NOFOLLOW (1<<1) + /* * Parse a whitespace-delimited attribute state (i.e., "attr", * "-attr", "!attr", or "attr=value") from the string starting at src. @@ -331,7 +335,7 @@ static const char *parse_attr(const char *src, int lineno, const char *cp, } static struct match_attr *parse_attr_line(const char *line, const char *src, - int lineno, int macro_ok) + int lineno, unsigned flags) { int namelen; int num_attr, i; @@ -355,7 +359,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src, if (strlen(ATTRIBUTE_MACRO_PREFIX) < namelen && starts_with(name, ATTRIBUTE_MACRO_PREFIX)) { - if (!macro_ok) { + if (!(flags & READ_ATTR_MACRO_OK)) { fprintf_ln(stderr, _("%s not allowed: %s:%d"), name, src, lineno); goto fail_return; @@ -569,7 +573,7 @@ struct attr_check *attr_check_initl(const char *one, ...) check = attr_check_alloc(); check->nr = cnt; check->alloc = cnt; - check->items = xcalloc(cnt, sizeof(struct attr_check_item)); + CALLOC_ARRAY(check->items, cnt); check->items[0].attr = git_attr(one); va_start(params, one); @@ -653,11 +657,11 @@ static void handle_attr_line(struct attr_stack *res, const char *line, const char *src, int lineno, - int macro_ok) + unsigned flags) { struct match_attr *a; - a = parse_attr_line(line, src, lineno, macro_ok); + a = parse_attr_line(line, src, lineno, flags); if (!a) return; ALLOC_GROW(res->attrs, res->num_matches + 1, res->alloc); @@ -670,9 +674,10 @@ static struct attr_stack *read_attr_from_array(const char **list) const char *line; int lineno = 0; - res = xcalloc(1, sizeof(*res)); + CALLOC_ARRAY(res, 1); while ((line = *(list++)) != NULL) - handle_attr_line(res, line, "[builtin]", ++lineno, 1); + handle_attr_line(res, line, "[builtin]", ++lineno, + READ_ATTR_MACRO_OK); return res; } @@ -698,21 +703,31 @@ void git_attr_set_direction(enum git_attr_direction new_direction) direction = new_direction; } -static struct attr_stack *read_attr_from_file(const char *path, int macro_ok) +static struct attr_stack *read_attr_from_file(const char *path, unsigned flags) { - FILE *fp = fopen_or_warn(path, "r"); + int fd; + FILE *fp; struct attr_stack *res; char buf[2048]; int lineno = 0; - if (!fp) + if (flags & READ_ATTR_NOFOLLOW) + fd = open_nofollow(path, O_RDONLY); + else + fd = open(path, O_RDONLY); + + if (fd < 0) { + warn_on_fopen_errors(path); return NULL; - res = xcalloc(1, sizeof(*res)); + } + fp = xfdopen(fd, "r"); + + CALLOC_ARRAY(res, 1); while (fgets(buf, sizeof(buf), fp)) { char *bufp = buf; if (!lineno) skip_utf8_bom(&bufp, strlen(bufp)); - handle_attr_line(res, bufp, path, ++lineno, macro_ok); + handle_attr_line(res, bufp, path, ++lineno, flags); } fclose(fp); return res; @@ -720,7 +735,7 @@ static struct attr_stack *read_attr_from_file(const char *path, int macro_ok) static struct attr_stack *read_attr_from_index(const struct index_state *istate, const char *path, - int macro_ok) + unsigned flags) { struct attr_stack *res; char *buf, *sp; @@ -733,7 +748,7 @@ static struct attr_stack *read_attr_from_index(const struct index_state *istate, if (!buf) return NULL; - res = xcalloc(1, sizeof(*res)); + CALLOC_ARRAY(res, 1); for (sp = buf; *sp; ) { char *ep; int more; @@ -741,7 +756,7 @@ static struct attr_stack *read_attr_from_index(const struct index_state *istate, ep = strchrnul(sp, '\n'); more = (*ep == '\n'); *ep = '\0'; - handle_attr_line(res, sp, path, ++lineno, macro_ok); + handle_attr_line(res, sp, path, ++lineno, flags); sp = ep + more; } free(buf); @@ -749,19 +764,19 @@ static struct attr_stack *read_attr_from_index(const struct index_state *istate, } static struct attr_stack *read_attr(const struct index_state *istate, - const char *path, int macro_ok) + const char *path, unsigned flags) { struct attr_stack *res = NULL; if (direction == GIT_ATTR_INDEX) { - res = read_attr_from_index(istate, path, macro_ok); + res = read_attr_from_index(istate, path, flags); } else if (!is_bare_repository()) { if (direction == GIT_ATTR_CHECKOUT) { - res = read_attr_from_index(istate, path, macro_ok); + res = read_attr_from_index(istate, path, flags); if (!res) - res = read_attr_from_file(path, macro_ok); + res = read_attr_from_file(path, flags); } else if (direction == GIT_ATTR_CHECKIN) { - res = read_attr_from_file(path, macro_ok); + res = read_attr_from_file(path, flags); if (!res) /* * There is no checked out .gitattributes file @@ -769,12 +784,12 @@ static struct attr_stack *read_attr(const struct index_state *istate, * We allow operation in a sparsely checked out * work tree, so read from it. */ - res = read_attr_from_index(istate, path, macro_ok); + res = read_attr_from_index(istate, path, flags); } } if (!res) - res = xcalloc(1, sizeof(*res)); + CALLOC_ARRAY(res, 1); return res; } @@ -844,6 +859,7 @@ static void bootstrap_attr_stack(const struct index_state *istate, struct attr_stack **stack) { struct attr_stack *e; + unsigned flags = READ_ATTR_MACRO_OK; if (*stack) return; @@ -854,27 +870,27 @@ static void bootstrap_attr_stack(const struct index_state *istate, /* system-wide frame */ if (git_attr_system()) { - e = read_attr_from_file(git_etc_gitattributes(), 1); + e = read_attr_from_file(git_etc_gitattributes(), flags); push_stack(stack, e, NULL, 0); } /* home directory */ if (get_home_gitattributes()) { - e = read_attr_from_file(get_home_gitattributes(), 1); + e = read_attr_from_file(get_home_gitattributes(), flags); push_stack(stack, e, NULL, 0); } /* root directory */ - e = read_attr(istate, GITATTRIBUTES_FILE, 1); + e = read_attr(istate, GITATTRIBUTES_FILE, flags | READ_ATTR_NOFOLLOW); push_stack(stack, e, xstrdup(""), 0); /* info frame */ if (startup_info->have_repository) - e = read_attr_from_file(git_path_info_attributes(), 1); + e = read_attr_from_file(git_path_info_attributes(), flags); else e = NULL; if (!e) - e = xcalloc(1, sizeof(struct attr_stack)); + CALLOC_ARRAY(e, 1); push_stack(stack, e, NULL, 0); } @@ -956,7 +972,7 @@ static void prepare_attr_stack(const struct index_state *istate, strbuf_add(&pathbuf, path + pathbuf.len, (len - pathbuf.len)); strbuf_addf(&pathbuf, "/%s", GITATTRIBUTES_FILE); - next = read_attr(istate, pathbuf.buf, 0); + next = read_attr(istate, pathbuf.buf, READ_ATTR_NOFOLLOW); /* reset the pathbuf to not include "/.gitattributes" */ strbuf_setlen(&pathbuf, len); diff --git a/bisect.c b/bisect.c index 75ea0eb57f..af2863d044 100644 --- a/bisect.c +++ b/bisect.c @@ -423,7 +423,7 @@ void find_bisection(struct commit_list **commit_list, int *reaches, show_list("bisection 2 sorted", 0, nr, list); *all = nr; - weights = xcalloc(on_list, sizeof(*weights)); + CALLOC_ARRAY(weights, on_list); /* Do the real work of finding bisection commit. */ best = do_find_bisection(list, nr, weights, bisect_flags); @@ -1064,7 +1064,7 @@ enum bisect_error bisect_next_all(struct repository *r, const char *prefix) if (!all) { fprintf(stderr, _("No testable commit found.\n" - "Maybe you started with bad path parameters?\n")); + "Maybe you started with bad path arguments?\n")); return BISECT_NO_TESTABLE_COMMIT; } diff --git a/blame.c b/blame.c index a5044fcfaa..5018bb8fb2 100644 --- a/blame.c +++ b/blame.c @@ -951,13 +951,13 @@ static int *fuzzy_find_matching_lines(struct blame_origin *parent, max_search_distance_b = ((2 * max_search_distance_a + 1) * length_b - 1) / length_a; - result = xcalloc(sizeof(int), length_b); - second_best_result = xcalloc(sizeof(int), length_b); - certainties = xcalloc(sizeof(int), length_b); + CALLOC_ARRAY(result, length_b); + CALLOC_ARRAY(second_best_result, length_b); + CALLOC_ARRAY(certainties, length_b); /* See get_similarity() for details of similarities. */ similarity_count = length_b * (max_search_distance_a * 2 + 1); - similarities = xcalloc(sizeof(int), similarity_count); + CALLOC_ARRAY(similarities, similarity_count); for (i = 0; i < length_b; ++i) { result[i] = -1; @@ -995,7 +995,7 @@ static void fill_origin_fingerprints(struct blame_origin *o) return; o->num_lines = find_line_starts(&line_starts, o->file.ptr, o->file.size); - o->fingerprints = xcalloc(sizeof(struct fingerprint), o->num_lines); + CALLOC_ARRAY(o->fingerprints, o->num_lines); get_line_fingerprints(o->fingerprints, o->file.ptr, line_starts, 0, o->num_lines); free(line_starts); @@ -1853,8 +1853,7 @@ static void blame_chunk(struct blame_entry ***dstq, struct blame_entry ***srcq, diffp = NULL; if (ignore_diffs && same - tlno > 0) { - line_blames = xcalloc(sizeof(struct blame_line_tracker), - same - tlno); + CALLOC_ARRAY(line_blames, same - tlno); guess_line_blames(parent, target, tlno, offset, same, parent_len, line_blames); } @@ -2216,7 +2215,7 @@ static struct blame_list *setup_blame_list(struct blame_entry *unblamed, for (e = unblamed, num_ents = 0; e; e = e->next) num_ents++; if (num_ents) { - blame_list = xcalloc(num_ents, sizeof(struct blame_list)); + CALLOC_ARRAY(blame_list, num_ents); for (e = unblamed, i = 0; e; e = e->next) blame_list[i++].ent = e; } @@ -2428,7 +2427,7 @@ static void pass_blame(struct blame_scoreboard *sb, struct blame_origin *origin, else if (num_sg < ARRAY_SIZE(sg_buf)) memset(sg_buf, 0, sizeof(sg_buf)); else - sg_origin = xcalloc(num_sg, sizeof(*sg_origin)); + CALLOC_ARRAY(sg_origin, num_sg); /* * The first pass looks for unrenamed path to optimize for diff --git a/block-sha1/sha1.c b/block-sha1/sha1.c index 8681031402..1bb6e7c069 100644 --- a/block-sha1/sha1.c +++ b/block-sha1/sha1.c @@ -70,7 +70,7 @@ * the input data, the next mix it from the 512-bit array. */ #define SHA_SRC(t) get_be32((unsigned char *) block + (t)*4) -#define SHA_MIX(t) SHA_ROL(W((t)+13) ^ W((t)+8) ^ W((t)+2) ^ W(t), 1); +#define SHA_MIX(t) SHA_ROL(W((t)+13) ^ W((t)+8) ^ W((t)+2) ^ W(t), 1) #define SHA_ROUND(t, input, fn, constant, A, B, C, D, E) do { \ unsigned int TEMP = input(t); setW(t, TEMP); \ diff --git a/bloom.c b/bloom.c index b176f28f53..52b87474c6 100644 --- a/bloom.c +++ b/bloom.c @@ -277,7 +277,7 @@ struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, *computed |= BLOOM_TRUNC_EMPTY; filter->len = 1; } - filter->data = xcalloc(filter->len, sizeof(unsigned char)); + CALLOC_ARRAY(filter->data, filter->len); hashmap_for_each_entry(&pathmap, &iter, e, entry) { struct bloom_key key; diff --git a/builtin/add.c b/builtin/add.c index a825887c50..ea762a41e3 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -38,19 +38,27 @@ struct update_callback_data { int add_errors; }; -static void chmod_pathspec(struct pathspec *pathspec, char flip) +static int chmod_pathspec(struct pathspec *pathspec, char flip, int show_only) { - int i; + int i, ret = 0; for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; + int err; if (pathspec && !ce_path_match(&the_index, ce, pathspec, NULL)) continue; - if (chmod_cache_entry(ce, flip) < 0) - fprintf(stderr, "cannot chmod %cx '%s'\n", flip, ce->name); + if (!show_only) + err = chmod_cache_entry(ce, flip); + else + err = S_ISREG(ce->ce_mode) ? 0 : -1; + + if (err < 0) + ret = error(_("cannot chmod %cx '%s'"), flip, ce->name); } + + return ret; } static int fix_unmerged_status(struct diff_filepair *p, @@ -609,7 +617,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) exit_status |= add_files(&dir, flags); if (chmod_arg && pathspec.nr) - chmod_pathspec(&pathspec, chmod_arg[0]); + exit_status |= chmod_pathspec(&pathspec, chmod_arg[0], show_only); unplug_bulk_checkin(); finish: diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index 709eb713a3..1fdb7d9d10 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -21,16 +21,15 @@ static GIT_PATH_FUNC(git_path_bisect_first_parent, "BISECT_FIRST_PARENT") static const char * const git_bisect_helper_usage[] = { N_("git bisect--helper --bisect-reset []"), - N_("git bisect--helper --bisect-write [--no-log] "), - N_("git bisect--helper --bisect-check-and-set-terms "), N_("git bisect--helper --bisect-next-check []"), N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"), N_("git bisect--helper --bisect-start [--term-{new,bad}= --term-{old,good}=]" " [--no-checkout] [--first-parent] [ [...]] [--] [...]"), N_("git bisect--helper --bisect-next"), - N_("git bisect--helper --bisect-auto-next"), N_("git bisect--helper --bisect-state (bad|new) []"), N_("git bisect--helper --bisect-state (good|old) [...]"), + N_("git bisect--helper --bisect-replay "), + N_("git bisect--helper --bisect-skip [(|)...]"), NULL }; @@ -875,12 +874,19 @@ static enum bisect_error bisect_state(struct bisect_terms *terms, const char **a */ for (; argc; argc--, argv++) { + struct commit *commit; + if (get_oid(*argv, &oid)){ error(_("Bad rev input: %s"), *argv); oid_array_clear(&revs); return BISECT_FAILED; } - oid_array_append(&revs, &oid); + + commit = lookup_commit_reference(the_repository, &oid); + if (!commit) + die(_("Bad rev input (not a commit): %s"), *argv); + + oid_array_append(&revs, &commit->object.oid); } if (strbuf_read_file(&buf, git_path_bisect_expected_rev(), 0) < the_hash_algo->hexsz || @@ -904,28 +910,148 @@ static enum bisect_error bisect_state(struct bisect_terms *terms, const char **a return bisect_auto_next(terms, NULL); } +static enum bisect_error bisect_log(void) +{ + int fd, status; + const char* filename = git_path_bisect_log(); + + if (is_empty_or_missing_file(filename)) + return error(_("We are not bisecting.")); + + fd = open(filename, O_RDONLY); + if (fd < 0) + return BISECT_FAILED; + + status = copy_fd(fd, STDOUT_FILENO); + close(fd); + return status ? BISECT_FAILED : BISECT_OK; +} + +static int process_replay_line(struct bisect_terms *terms, struct strbuf *line) +{ + const char *p = line->buf + strspn(line->buf, " \t"); + char *word_end, *rev; + + if ((!skip_prefix(p, "git bisect", &p) && + !skip_prefix(p, "git-bisect", &p)) || !isspace(*p)) + return 0; + p += strspn(p, " \t"); + + word_end = (char *)p + strcspn(p, " \t"); + rev = word_end + strspn(word_end, " \t"); + *word_end = '\0'; /* NUL-terminate the word */ + + get_terms(terms); + if (check_and_set_terms(terms, p)) + return -1; + + if (!strcmp(p, "start")) { + struct strvec argv = STRVEC_INIT; + int res; + sq_dequote_to_strvec(rev, &argv); + res = bisect_start(terms, argv.v, argv.nr); + strvec_clear(&argv); + return res; + } + + if (one_of(p, terms->term_good, + terms->term_bad, "skip", NULL)) + return bisect_write(p, rev, terms, 0); + + if (!strcmp(p, "terms")) { + struct strvec argv = STRVEC_INIT; + int res; + sq_dequote_to_strvec(rev, &argv); + res = bisect_terms(terms, argv.nr == 1 ? argv.v[0] : NULL); + strvec_clear(&argv); + return res; + } + error(_("'%s'?? what are you talking about?"), p); + + return -1; +} + +static enum bisect_error bisect_replay(struct bisect_terms *terms, const char *filename) +{ + FILE *fp = NULL; + enum bisect_error res = BISECT_OK; + struct strbuf line = STRBUF_INIT; + + if (is_empty_or_missing_file(filename)) + return error(_("cannot read file '%s' for replaying"), filename); + + if (bisect_reset(NULL)) + return BISECT_FAILED; + + fp = fopen(filename, "r"); + if (!fp) + return BISECT_FAILED; + + while ((strbuf_getline(&line, fp) != EOF) && !res) + res = process_replay_line(terms, &line); + + strbuf_release(&line); + fclose(fp); + + if (res) + return BISECT_FAILED; + + return bisect_auto_next(terms, NULL); +} + +static enum bisect_error bisect_skip(struct bisect_terms *terms, const char **argv, int argc) +{ + int i; + enum bisect_error res; + struct strvec argv_state = STRVEC_INIT; + + strvec_push(&argv_state, "skip"); + + for (i = 0; i < argc; i++) { + const char *dotdot = strstr(argv[i], ".."); + + if (dotdot) { + struct rev_info revs; + struct commit *commit; + + init_revisions(&revs, NULL); + setup_revisions(2, argv + i - 1, &revs, NULL); + + if (prepare_revision_walk(&revs)) + die(_("revision walk setup failed\n")); + while ((commit = get_revision(&revs)) != NULL) + strvec_push(&argv_state, + oid_to_hex(&commit->object.oid)); + + reset_revision_walk(); + } else { + strvec_push(&argv_state, argv[i]); + } + } + res = bisect_state(terms, argv_state.v, argv_state.nr); + + strvec_clear(&argv_state); + return res; +} + int cmd_bisect__helper(int argc, const char **argv, const char *prefix) { enum { BISECT_RESET = 1, - BISECT_WRITE, - CHECK_AND_SET_TERMS, BISECT_NEXT_CHECK, BISECT_TERMS, BISECT_START, BISECT_AUTOSTART, BISECT_NEXT, - BISECT_AUTO_NEXT, - BISECT_STATE + BISECT_STATE, + BISECT_LOG, + BISECT_REPLAY, + BISECT_SKIP } cmdmode = 0; int res = 0, nolog = 0; struct option options[] = { OPT_CMDMODE(0, "bisect-reset", &cmdmode, N_("reset the bisection state"), BISECT_RESET), - OPT_CMDMODE(0, "bisect-write", &cmdmode, - N_("write out the bisection state in BISECT_LOG"), BISECT_WRITE), - OPT_CMDMODE(0, "check-and-set-terms", &cmdmode, - N_("check and set terms in a bisection state"), CHECK_AND_SET_TERMS), OPT_CMDMODE(0, "bisect-next-check", &cmdmode, N_("check whether bad or good terms exist"), BISECT_NEXT_CHECK), OPT_CMDMODE(0, "bisect-terms", &cmdmode, @@ -934,10 +1060,14 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) N_("start the bisect session"), BISECT_START), OPT_CMDMODE(0, "bisect-next", &cmdmode, N_("find the next bisection commit"), BISECT_NEXT), - OPT_CMDMODE(0, "bisect-auto-next", &cmdmode, - N_("verify the next bisection state then checkout the next bisection commit"), BISECT_AUTO_NEXT), OPT_CMDMODE(0, "bisect-state", &cmdmode, N_("mark the state of ref (or refs)"), BISECT_STATE), + OPT_CMDMODE(0, "bisect-log", &cmdmode, + N_("list the bisection steps so far"), BISECT_LOG), + OPT_CMDMODE(0, "bisect-replay", &cmdmode, + N_("replay the bisection process from the given file"), BISECT_REPLAY), + OPT_CMDMODE(0, "bisect-skip", &cmdmode, + N_("skip some commits for checkout"), BISECT_SKIP), OPT_BOOL(0, "no-log", &nolog, N_("no log for BISECT_WRITE")), OPT_END() @@ -955,18 +1085,7 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) case BISECT_RESET: if (argc > 1) return error(_("--bisect-reset requires either no argument or a commit")); - return !!bisect_reset(argc ? argv[0] : NULL); - case BISECT_WRITE: - if (argc != 4 && argc != 5) - return error(_("--bisect-write requires either 4 or 5 arguments")); - set_terms(&terms, argv[3], argv[2]); - res = bisect_write(argv[0], argv[1], &terms, nolog); - break; - case CHECK_AND_SET_TERMS: - if (argc != 3) - return error(_("--check-and-set-terms requires 3 arguments")); - set_terms(&terms, argv[2], argv[1]); - res = check_and_set_terms(&terms, argv[0]); + res = bisect_reset(argc ? argv[0] : NULL); break; case BISECT_NEXT_CHECK: if (argc != 2 && argc != 3) @@ -989,17 +1108,26 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) get_terms(&terms); res = bisect_next(&terms, prefix); break; - case BISECT_AUTO_NEXT: - if (argc) - return error(_("--bisect-auto-next requires 0 arguments")); - get_terms(&terms); - res = bisect_auto_next(&terms, prefix); - break; case BISECT_STATE: set_terms(&terms, "bad", "good"); get_terms(&terms); res = bisect_state(&terms, argv, argc); break; + case BISECT_LOG: + if (argc) + return error(_("--bisect-log requires 0 arguments")); + res = bisect_log(); + break; + case BISECT_REPLAY: + if (argc != 1) + return error(_("no logfile given")); + set_terms(&terms, "bad", "good"); + res = bisect_replay(&terms, argv[0]); + break; + case BISECT_SKIP: + set_terms(&terms, "bad", "good"); + res = bisect_skip(&terms, argv, argc); + break; default: BUG("unknown subcommand %d", cmdmode); } diff --git a/builtin/blame.c b/builtin/blame.c index b66e938022..641523ff9a 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -425,13 +425,11 @@ static void setup_default_color_by_age(void) parse_color_fields("blue,12 month ago,white,1 month ago,red"); } -static void determine_line_heat(struct blame_entry *ent, const char **dest_color) +static void determine_line_heat(struct commit_info *ci, const char **dest_color) { int i = 0; - struct commit_info ci; - get_commit_info(ent->suspect->commit, &ci, 1); - while (i < colorfield_nr && ci.author_time > colorfield[i].hop) + while (i < colorfield_nr && ci->author_time > colorfield[i].hop) i++; *dest_color = colorfield[i].col; @@ -453,7 +451,7 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int cp = blame_nth_line(sb, ent->lno); if (opt & OUTPUT_SHOW_AGE_WITH_COLOR) { - determine_line_heat(ent, &default_color); + determine_line_heat(&ci, &default_color); color = default_color; reset = GIT_COLOR_RESET; } diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index 4bbfc92dce..c0bf4ac1b2 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -11,6 +11,7 @@ #include "quote.h" #include "cache-tree.h" #include "parse-options.h" +#include "entry.h" #define CHECKOUT_ALL 4 static int nul_term_line; @@ -23,22 +24,35 @@ static struct checkout state = CHECKOUT_INIT; static void write_tempfile_record(const char *name, const char *prefix) { int i; + int have_tempname = 0; if (CHECKOUT_ALL == checkout_stage) { - for (i = 1; i < 4; i++) { - if (i > 1) - putchar(' '); - if (topath[i][0]) - fputs(topath[i], stdout); - else - putchar('.'); + for (i = 1; i < 4; i++) + if (topath[i][0]) { + have_tempname = 1; + break; + } + + if (have_tempname) { + for (i = 1; i < 4; i++) { + if (i > 1) + putchar(' '); + if (topath[i][0]) + fputs(topath[i], stdout); + else + putchar('.'); + } } - } else + } else if (topath[checkout_stage][0]) { + have_tempname = 1; fputs(topath[checkout_stage], stdout); + } - putchar('\t'); - write_name_quoted_relative(name, prefix, stdout, - nul_term_line ? '\0' : '\n'); + if (have_tempname) { + putchar('\t'); + write_name_quoted_relative(name, prefix, stdout, + nul_term_line ? '\0' : '\n'); + } for (i = 0; i < 4; i++) { topath[i][0] = 0; diff --git a/builtin/checkout.c b/builtin/checkout.c index 2d6550bc3c..4c696ef480 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -26,6 +26,7 @@ #include "unpack-trees.h" #include "wt-status.h" #include "xdiff-interface.h" +#include "entry.h" static const char * const checkout_usage[] = { N_("git checkout [] "), @@ -114,7 +115,7 @@ static int post_checkout_hook(struct commit *old_commit, struct commit *new_comm } static int update_some(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, int stage, void *context) + const char *pathname, unsigned mode, void *context) { int len; struct cache_entry *ce; @@ -155,8 +156,8 @@ static int update_some(const struct object_id *oid, struct strbuf *base, static int read_tree_some(struct tree *tree, const struct pathspec *pathspec) { - read_tree_recursive(the_repository, tree, "", 0, 0, - pathspec, update_some, NULL); + read_tree(the_repository, tree, + pathspec, update_some, NULL); /* update the index with the given tree's info * for all args, expanding wildcards, and exit @@ -322,7 +323,7 @@ static void mark_ce_for_checkout_overlay(struct cache_entry *ce, * If it comes from the tree-ish, we already know it * matches the pathspec and could just stamp * CE_MATCHED to it from update_some(). But we still - * need ps_matched and read_tree_recursive (and + * need ps_matched and read_tree (and * eventually tree_entry_interesting) cannot fill * ps_matched yet. Once it can, we can avoid calling * match_pathspec() for _all_ entries when diff --git a/builtin/clean.c b/builtin/clean.c index 687ab473c2..995053b791 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -623,7 +623,7 @@ static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff) nr += chosen[i]; } - result = xcalloc(st_add(nr, 1), sizeof(int)); + CALLOC_ARRAY(result, st_add(nr, 1)); for (i = 0; i < stuff->nr && j < nr; i++) { if (chosen[i]) result[j++] = i; diff --git a/builtin/clone.c b/builtin/clone.c index e335734b4c..f6b0c48bed 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -50,6 +50,8 @@ static int option_no_checkout, option_bare, option_mirror, option_single_branch static int option_local = -1, option_no_hardlinks, option_shared; static int option_no_tags; static int option_shallow_submodules; +static int option_reject_shallow = -1; /* unspecified */ +static int config_reject_shallow = -1; /* unspecified */ static int deepen; static char *option_template, *option_depth, *option_since; static char *option_origin = NULL; @@ -90,6 +92,8 @@ static struct option builtin_clone_options[] = { OPT__VERBOSITY(&option_verbosity), OPT_BOOL(0, "progress", &option_progress, N_("force progress reporting")), + OPT_BOOL(0, "reject-shallow", &option_reject_shallow, + N_("don't clone shallow repository")), OPT_BOOL('n', "no-checkout", &option_no_checkout, N_("don't create a checkout")), OPT_BOOL(0, "bare", &option_bare, N_("create a bare repository")), @@ -858,6 +862,9 @@ static int git_clone_config(const char *k, const char *v, void *cb) free(remote_name); remote_name = xstrdup(v); } + if (!strcmp(k, "clone.rejectshallow")) + config_reject_shallow = git_config_bool(k, v); + return git_default_config(k, v, cb); } @@ -963,11 +970,12 @@ static int path_exists(const char *path) int cmd_clone(int argc, const char **argv, const char *prefix) { int is_bundle = 0, is_local; + int reject_shallow = 0; const char *repo_name, *repo, *work_tree, *git_dir; - char *path, *dir, *display_repo = NULL; + char *path = NULL, *dir, *display_repo = NULL; int dest_exists, real_dest_exists = 0; const struct ref *refs, *remote_head; - const struct ref *remote_head_points_at; + struct ref *remote_head_points_at = NULL; const struct ref *our_head_points_at; struct ref *mapped_refs; const struct ref *ref; @@ -979,7 +987,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) int err = 0, complete_refs_before_fetch = 1; int submodule_progress; - struct strvec ref_prefixes = STRVEC_INIT; + struct transport_ls_refs_options transport_ls_refs_options = + TRANSPORT_LS_REFS_OPTIONS_INIT; packet_trace_identity("clone"); @@ -1016,9 +1025,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) repo_name = argv[0]; path = get_repo_path(repo_name, &is_bundle); - if (path) + if (path) { + FREE_AND_NULL(path); repo = absolute_pathdup(repo_name); - else if (strchr(repo_name, ':')) { + } else if (strchr(repo_name, ':')) { repo = repo_name; display_repo = transport_anonymize_url(repo); } else @@ -1155,6 +1165,15 @@ int cmd_clone(int argc, const char **argv, const char *prefix) */ git_config(git_clone_config, NULL); + /* + * If option_reject_shallow is specified from CLI option, + * ignore config_reject_shallow from git_clone_config. + */ + if (config_reject_shallow != -1) + reject_shallow = config_reject_shallow; + if (option_reject_shallow != -1) + reject_shallow = option_reject_shallow; + /* * apply the remote name provided by --origin only after this second * call to git_config, to ensure it overrides all config-based values. @@ -1215,6 +1234,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (filter_options.choice) warning(_("--filter is ignored in local clones; use file:// instead.")); if (!access(mkpath("%s/shallow", path), F_OK)) { + if (reject_shallow) + die(_("source repository is shallow, reject to clone.")); if (option_local > 0) warning(_("source repository is shallow, ignoring --local")); is_local = 0; @@ -1226,6 +1247,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) transport_set_option(transport, TRANS_OPT_KEEP, "yes"); + if (reject_shallow) + transport_set_option(transport, TRANS_OPT_REJECT_SHALLOW, "1"); if (option_depth) transport_set_option(transport, TRANS_OPT_DEPTH, option_depth); @@ -1257,14 +1280,17 @@ int cmd_clone(int argc, const char **argv, const char *prefix) transport->smart_options->check_self_contained_and_connected = 1; - strvec_push(&ref_prefixes, "HEAD"); - refspec_ref_prefixes(&remote->fetch, &ref_prefixes); + strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD"); + refspec_ref_prefixes(&remote->fetch, + &transport_ls_refs_options.ref_prefixes); if (option_branch) - expand_ref_prefix(&ref_prefixes, option_branch); + expand_ref_prefix(&transport_ls_refs_options.ref_prefixes, + option_branch); if (!option_no_tags) - strvec_push(&ref_prefixes, "refs/tags/"); + strvec_push(&transport_ls_refs_options.ref_prefixes, + "refs/tags/"); - refs = transport_get_remote_refs(transport, &ref_prefixes); + refs = transport_get_remote_refs(transport, &transport_ls_refs_options); if (refs) { int hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport)); @@ -1326,8 +1352,19 @@ int cmd_clone(int argc, const char **argv, const char *prefix) remote_head = NULL; option_no_checkout = 1; if (!option_bare) { - const char *branch = git_default_branch_name(0); - char *ref = xstrfmt("refs/heads/%s", branch); + const char *branch; + char *ref; + + if (transport_ls_refs_options.unborn_head_target && + skip_prefix(transport_ls_refs_options.unborn_head_target, + "refs/heads/", &branch)) { + ref = transport_ls_refs_options.unborn_head_target; + transport_ls_refs_options.unborn_head_target = NULL; + create_symref("HEAD", ref, reflog_msg.buf); + } else { + branch = git_default_branch_name(0); + ref = xstrfmt("refs/heads/%s", branch); + } install_branch_config(0, branch, remote_name, ref); free(ref); @@ -1378,8 +1415,14 @@ cleanup: strbuf_release(&reflog_msg); strbuf_release(&branch_top); strbuf_release(&key); + free_refs(mapped_refs); + free_refs(remote_head_points_at); + free(dir); + free(path); + UNLEAK(repo); junk_mode = JUNK_LEAVE_ALL; - strvec_clear(&ref_prefixes); + strvec_clear(&transport_ls_refs_options.ref_prefixes); + free(transport_ls_refs_options.unborn_head_target); return err; } diff --git a/builtin/column.c b/builtin/column.c index e815e148aa..40d4b3bee2 100644 --- a/builtin/column.c +++ b/builtin/column.c @@ -27,10 +27,10 @@ int cmd_column(int argc, const char **argv, const char *prefix) OPT_STRING(0, "command", &real_command, N_("name"), N_("lookup config vars")), OPT_COLUMN(0, "mode", &colopts, N_("layout to use")), OPT_INTEGER(0, "raw-mode", &colopts, N_("layout to use")), - OPT_INTEGER(0, "width", &copts.width, N_("Maximum width")), - OPT_STRING(0, "indent", &copts.indent, N_("string"), N_("Padding space on left border")), - OPT_INTEGER(0, "nl", &copts.nl, N_("Padding space on right border")), - OPT_INTEGER(0, "padding", &copts.padding, N_("Padding space between columns")), + OPT_INTEGER(0, "width", &copts.width, N_("maximum width")), + OPT_STRING(0, "indent", &copts.indent, N_("string"), N_("padding space on left border")), + OPT_INTEGER(0, "nl", &copts.nl, N_("padding space on right border")), + OPT_INTEGER(0, "padding", &copts.padding, N_("padding space between columns")), OPT_END() }; diff --git a/builtin/commit.c b/builtin/commit.c index 739110c5a7..55d50a8891 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -105,7 +105,8 @@ static const char *template_file; */ static const char *author_message, *author_message_buffer; static char *edit_message, *use_message; -static char *fixup_message, *squash_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 int edit_flag = -1; /* unspecified */ static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; @@ -113,6 +114,7 @@ static int config_commit_verbose = -1; /* unspecified */ static int no_post_rewrite, allow_empty_message, pathspec_file_nul; static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg; static char *sign_commit, *pathspec_from_file; +static struct strvec trailer_args = STRVEC_INIT; /* * The default commit message cleanup mode will remove the lines @@ -131,6 +133,14 @@ static struct strbuf message = STRBUF_INIT; static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED; +static int opt_pass_trailer(const struct option *opt, const char *arg, int unset) +{ + BUG_ON_OPT_NEG(unset); + + strvec_pushl(&trailer_args, "--trailer", arg, NULL); + return 0; +} + static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset) { enum wt_status_format *value = (enum wt_status_format *)opt->value; @@ -357,7 +367,8 @@ static const char *prepare_index(const char **argv, const char *prefix, die(_("--pathspec-file-nul requires --pathspec-from-file")); } - if (!pathspec.nr && (also || (only && !amend && !allow_empty))) + if (!pathspec.nr && (also || (only && !allow_empty && + (!amend || (fixup_message && strcmp(fixup_prefix, "amend")))))) die(_("No paths with --include/--only does not make sense.")); if (read_cache_preload(&pathspec) < 0) @@ -681,6 +692,22 @@ static void adjust_comment_line_char(const struct strbuf *sb) comment_line_char = *p; } +static void prepare_amend_commit(struct commit *commit, struct strbuf *sb, + struct pretty_print_context *ctx) +{ + const char *buffer, *subject, *fmt; + + buffer = get_commit_buffer(commit, NULL); + find_commit_subject(buffer, &subject); + /* + * If we amend the 'amend!' commit then we don't want to + * duplicate the subject line. + */ + fmt = starts_with(subject, "amend!") ? "%b" : "%B"; + format_commit_message(commit, fmt, sb, ctx); + unuse_commit_buffer(commit, buffer); +} + static int prepare_to_commit(const char *index_file, const char *prefix, struct commit *current_head, struct wt_status *s, @@ -745,15 +772,33 @@ static int prepare_to_commit(const char *index_file, const char *prefix, } else if (fixup_message) { struct pretty_print_context ctx = {0}; struct commit *commit; - commit = lookup_commit_reference_by_name(fixup_message); + char *fmt; + commit = lookup_commit_reference_by_name(fixup_commit); if (!commit) - die(_("could not lookup commit %s"), fixup_message); + die(_("could not lookup commit %s"), fixup_commit); ctx.output_encoding = get_commit_output_encoding(); - format_commit_message(commit, "fixup! %s\n\n", - &sb, &ctx); - if (have_option_m) - strbuf_addbuf(&sb, &message); + fmt = xstrfmt("%s! %%s\n\n", fixup_prefix); + format_commit_message(commit, fmt, &sb, &ctx); + free(fmt); hook_arg1 = "message"; + + /* + * Only `-m` commit message option is checked here, as + * it supports `--fixup` to append the commit message. + * + * The other commit message options `-c`/`-C`/`-F` are + * incompatible with all the forms of `--fixup` and + * have already errored out while parsing the `git commit` + * options. + */ + if (have_option_m && !strcmp(fixup_prefix, "fixup")) + strbuf_addbuf(&sb, &message); + + if (!strcmp(fixup_prefix, "amend")) { + if (have_option_m) + die(_("cannot combine -m with --fixup:%s"), fixup_message); + prepare_amend_commit(commit, &sb, &ctx); + } } else if (!stat(git_path_merge_msg(the_repository), &statbuf)) { size_t merge_msg_start; @@ -958,6 +1003,18 @@ static int prepare_to_commit(const char *index_file, const char *prefix, fclose(s->fp); + if (trailer_args.nr) { + struct child_process run_trailer = CHILD_PROCESS_INIT; + + strvec_pushl(&run_trailer.args, "interpret-trailers", + "--in-place", git_path_commit_editmsg(), NULL); + strvec_pushv(&run_trailer.args, trailer_args.v); + run_trailer.git_cmd = 1; + if (run_command(&run_trailer)) + die(_("unable to pass trailers to --trailers")); + strvec_clear(&trailer_args); + } + /* * Reject an attempt to record a non-merge empty commit without * explicit --allow-empty. In the cherry-pick case, it may be @@ -1152,6 +1209,19 @@ static void finalize_deferred_config(struct wt_status *s) s->ahead_behind_flags = AHEAD_BEHIND_FULL; } +static void check_fixup_reword_options(int argc, const char *argv[]) { + if (whence != FROM_COMMIT) { + if (whence == FROM_MERGE) + die(_("You are in the middle of a merge -- cannot reword.")); + else if (is_from_cherry_pick(whence)) + die(_("You are in the middle of a cherry-pick -- cannot reword.")); + } + if (argc) + die(_("cannot combine reword option of --fixup with path '%s'"), *argv); + if (patch_interactive || interactive || all || also || only) + die(_("reword option of --fixup is mutually exclusive with --patch/--interactive/--all/--include/--only")); +} + static int parse_and_validate_options(int argc, const char *argv[], const struct option *options, const char * const usage[], @@ -1170,7 +1240,7 @@ static int parse_and_validate_options(int argc, const char *argv[], if (force_author && renew_authorship) die(_("Using both --reset-author and --author does not make sense")); - if (logfile || have_option_m || use_message || fixup_message) + if (logfile || have_option_m || use_message) use_editor = 0; if (0 <= edit_flag) use_editor = edit_flag; @@ -1227,6 +1297,42 @@ static int parse_and_validate_options(int argc, const char *argv[], if (also + only + all + interactive > 1) die(_("Only one of --include/--only/--all/--interactive/--patch can be used.")); + + if (fixup_message) { + /* + * We limit --fixup's suboptions to only alpha characters. + * If the first character after a run of alpha is colon, + * then the part before the colon may be a known suboption + * name like `amend` or `reword`, or a misspelt suboption + * name. In either case, we treat it as + * --fixup=:. + * + * Otherwise, we are dealing with --fixup=. + */ + char *p = fixup_message; + while (isalpha(*p)) + p++; + if (p > fixup_message && *p == ':') { + *p = '\0'; + fixup_commit = p + 1; + if (!strcmp("amend", fixup_message) || + !strcmp("reword", fixup_message)) { + fixup_prefix = "amend"; + allow_empty = 1; + if (*fixup_message == 'r') { + check_fixup_reword_options(argc, argv); + only = 1; + } + } else { + die(_("unknown option: --fixup=%s:%s"), fixup_message, fixup_commit); + } + } else { + fixup_commit = fixup_message; + fixup_prefix = "fixup"; + use_editor = 0; + } + } + cleanup_mode = get_cleanup_mode(cleanup_arg, use_editor); handle_untracked_files_arg(s); @@ -1504,9 +1610,14 @@ int cmd_commit(int argc, const char **argv, const char *prefix) OPT_CALLBACK('m', "message", &message, N_("message"), N_("commit message"), opt_parse_m), OPT_STRING('c', "reedit-message", &edit_message, N_("commit"), N_("reuse and edit message from specified commit")), OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")), - OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")), + /* + * TRANSLATORS: Leave "[(amend|reword):]" as-is, + * and only translate . + */ + 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_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG, opt_pass_trailer), 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")), @@ -1663,6 +1774,19 @@ int cmd_commit(int argc, const char **argv, const char *prefix) exit(1); } + if (fixup_message && starts_with(sb.buf, "amend! ") && + !allow_empty_message) { + struct strbuf body = STRBUF_INIT; + size_t len = commit_subject_length(sb.buf); + strbuf_addstr(&body, sb.buf + len); + if (message_is_empty(&body, cleanup_mode)) { + rollback_index_files(); + fprintf(stderr, _("Aborting commit due to empty commit message body.\n")); + exit(1); + } + strbuf_release(&body); + } + if (amend) { const char *exclude_gpgsig[3] = { "gpgsig", "gpgsig-sha256", NULL }; extra = read_commit_extra_headers(current_head, exclude_gpgsig); diff --git a/builtin/credential-cache--daemon.c b/builtin/credential-cache--daemon.c index c61f123a3b..4c6c89ab0d 100644 --- a/builtin/credential-cache--daemon.c +++ b/builtin/credential-cache--daemon.c @@ -203,9 +203,10 @@ static int serve_cache_loop(int fd) static void serve_cache(const char *socket_path, int debug) { + struct unix_stream_listen_opts opts = UNIX_STREAM_LISTEN_OPTS_INIT; int fd; - fd = unix_stream_listen(socket_path); + fd = unix_stream_listen(socket_path, &opts); if (fd < 0) die_errno("unable to bind to '%s'", socket_path); diff --git a/builtin/credential-cache.c b/builtin/credential-cache.c index 9b3f709905..76a6ba3722 100644 --- a/builtin/credential-cache.c +++ b/builtin/credential-cache.c @@ -14,7 +14,7 @@ static int send_request(const char *socket, const struct strbuf *out) { int got_data = 0; - int fd = unix_stream_connect(socket); + int fd = unix_stream_connect(socket, 0); if (fd < 0) return -1; diff --git a/builtin/diff-files.c b/builtin/diff-files.c index 4742a4559b..70103c4095 100644 --- a/builtin/diff-files.c +++ b/builtin/diff-files.c @@ -36,7 +36,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) */ rev.diffopt.ita_invisible_in_index = 1; - precompose_argv(argc, argv); + prefix = precompose_argv_prefix(argc, argv, prefix); argc = setup_revisions(argc, argv, &rev, NULL); while (1 < argc && argv[1][0] == '-') { @@ -54,6 +54,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) } if (!rev.diffopt.output_format) rev.diffopt.output_format = DIFF_FORMAT_RAW; + rev.diffopt.rotate_to_strict = 1; /* * Make sure there are NO revision (i.e. pending object) parameter, diff --git a/builtin/diff-index.c b/builtin/diff-index.c index 7f5281c461..176fe7ff2b 100644 --- a/builtin/diff-index.c +++ b/builtin/diff-index.c @@ -25,7 +25,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ repo_init_revisions(the_repository, &rev, prefix); rev.abbrev = 0; - precompose_argv(argc, argv); + prefix = precompose_argv_prefix(argc, argv, prefix); argc = setup_revisions(argc, argv, &rev, NULL); for (i = 1; i < argc; i++) { @@ -41,6 +41,8 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) if (!rev.diffopt.output_format) rev.diffopt.output_format = DIFF_FORMAT_RAW; + rev.diffopt.rotate_to_strict = 1; + /* * Make sure there is one revision (i.e. pending object), * and there is no revision filtering parameters. diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index 9fc95e959f..f33d30d57b 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -126,7 +126,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) memset(&s_r_opt, 0, sizeof(s_r_opt)); s_r_opt.tweak = diff_tree_tweak_rev; - precompose_argv(argc, argv); + prefix = precompose_argv_prefix(argc, argv, prefix); argc = setup_revisions(argc, argv, opt, &s_r_opt); memset(&w, 0, sizeof(w)); @@ -156,6 +156,8 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) if (merge_base && opt->pending.nr != 2) die(_("--merge-base only works with two commits")); + opt->diffopt.rotate_to_strict = 1; + /* * NOTE! We expect "a..b" to expand to "^a b" but it is * perfectly valid for revision range parser to yield "b ^a", @@ -192,6 +194,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) int saved_nrl = 0; int saved_dcctc = 0; + opt->diffopt.rotate_to_strict = 0; if (opt->diffopt.detect_rename) { if (!the_index.cache) repo_read_index(the_repository); diff --git a/builtin/diff.c b/builtin/diff.c index 5cfe1717e8..617b9a4101 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -453,7 +453,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) init_diff_ui_defaults(); git_config(git_diff_ui_config, NULL); - precompose_argv(argc, argv); + prefix = precompose_argv_prefix(argc, argv, prefix); repo_init_revisions(the_repository, &rev, prefix); @@ -491,6 +491,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) } rev.diffopt.flags.recursive = 1; + rev.diffopt.rotate_to_strict = 1; setup_diff_pager(&rev.diffopt); diff --git a/builtin/difftool.c b/builtin/difftool.c index 6e18e623fd..ef25729d49 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -23,6 +23,7 @@ #include "lockfile.h" #include "object-store.h" #include "dir.h" +#include "entry.h" static int trust_exit_code; diff --git a/builtin/fast-import.c b/builtin/fast-import.c index dd4d09cece..3afa81cf9a 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c @@ -3322,7 +3322,7 @@ static void option_rewrite_submodules(const char *arg, struct string_list *list) die(_("Expected format name:filename for submodule rewrite option")); *f = '\0'; f++; - ms = xcalloc(1, sizeof(*ms)); + CALLOC_ARRAY(ms, 1); fp = fopen(f, "r"); if (!fp) @@ -3519,9 +3519,9 @@ int cmd_fast_import(int argc, const char **argv, const char *prefix) alloc_objects(object_entry_alloc); strbuf_init(&command_buf, 0); - atom_table = xcalloc(atom_table_sz, sizeof(struct atom_str*)); - branch_table = xcalloc(branch_table_sz, sizeof(struct branch*)); - avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*)); + CALLOC_ARRAY(atom_table, atom_table_sz); + CALLOC_ARRAY(branch_table, branch_table_sz); + CALLOC_ARRAY(avail_tree_table, avail_tree_table_sz); marks = mem_pool_calloc(&fi_mem_pool, 1, sizeof(struct mark_set)); hashmap_init(&object_table, object_entry_hashcmp, NULL, 0); diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index 58b7c1fbdc..c2d96f4c89 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -220,7 +220,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) version = discover_version(&reader); switch (version) { case protocol_v2: - get_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL, args.stateless_rpc); + get_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL, + args.stateless_rpc); break; case protocol_v1: case protocol_v0: diff --git a/builtin/fetch.c b/builtin/fetch.c index 91f3d20696..0b90de87c7 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -1455,7 +1455,8 @@ static int do_fetch(struct transport *transport, int autotags = (transport->remote->fetch_tags == 1); int retcode = 0; const struct ref *remote_refs; - struct strvec ref_prefixes = STRVEC_INIT; + struct transport_ls_refs_options transport_ls_refs_options = + TRANSPORT_LS_REFS_OPTIONS_INIT; int must_list_refs = 1; if (tags == TAGS_DEFAULT) { @@ -1475,7 +1476,7 @@ static int do_fetch(struct transport *transport, if (rs->nr) { int i; - refspec_ref_prefixes(rs, &ref_prefixes); + refspec_ref_prefixes(rs, &transport_ls_refs_options.ref_prefixes); /* * We can avoid listing refs if all of them are exact @@ -1489,22 +1490,25 @@ static int do_fetch(struct transport *transport, } } } else if (transport->remote && transport->remote->fetch.nr) - refspec_ref_prefixes(&transport->remote->fetch, &ref_prefixes); + refspec_ref_prefixes(&transport->remote->fetch, + &transport_ls_refs_options.ref_prefixes); if (tags == TAGS_SET || tags == TAGS_DEFAULT) { must_list_refs = 1; - if (ref_prefixes.nr) - strvec_push(&ref_prefixes, "refs/tags/"); + if (transport_ls_refs_options.ref_prefixes.nr) + strvec_push(&transport_ls_refs_options.ref_prefixes, + "refs/tags/"); } if (must_list_refs) { trace2_region_enter("fetch", "remote_refs", the_repository); - remote_refs = transport_get_remote_refs(transport, &ref_prefixes); + remote_refs = transport_get_remote_refs(transport, + &transport_ls_refs_options); trace2_region_leave("fetch", "remote_refs", the_repository); } else remote_refs = NULL; - strvec_clear(&ref_prefixes); + strvec_clear(&transport_ls_refs_options.ref_prefixes); ref_map = get_ref_map(transport->remote, remote_refs, rs, tags, &autotags); diff --git a/builtin/fsck.c b/builtin/fsck.c index 821e7798c7..70ff95837a 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -71,11 +71,6 @@ static const char *printable_type(const struct object_id *oid, return ret; } -static int fsck_config(const char *var, const char *value, void *cb) -{ - return fsck_config_internal(var, value, cb, &fsck_obj_options); -} - static int objerror(struct object *obj, const char *err) { errors_found |= ERROR_OBJECT; @@ -89,7 +84,9 @@ static int objerror(struct object *obj, const char *err) static int fsck_error_func(struct fsck_options *o, const struct object_id *oid, enum object_type object_type, - int msg_type, const char *message) + enum fsck_msg_type msg_type, + enum fsck_msg_id msg_id, + const char *message) { switch (msg_type) { case FSCK_WARN: @@ -197,7 +194,8 @@ static int traverse_reachable(void) return !!result; } -static int mark_used(struct object *obj, int type, void *data, struct fsck_options *options) +static int mark_used(struct object *obj, enum object_type object_type, + void *data, struct fsck_options *options) { if (!obj) return 1; @@ -803,7 +801,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) if (name_objects) fsck_enable_object_names(&fsck_walk_options); - git_config(fsck_config, NULL); + git_config(git_fsck_config, &fsck_obj_options); if (connectivity_only) { for_each_loose_object(mark_loose_for_connectivity, NULL, 0); diff --git a/builtin/gc.c b/builtin/gc.c index 4c40594d66..ef7226d7bc 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -54,7 +54,6 @@ static const char *prune_worktrees_expire = "3.months.ago"; static unsigned long big_pack_threshold; static unsigned long max_delta_cache_size = DEFAULT_DELTA_CACHE_SIZE; -static struct strvec pack_refs_cmd = STRVEC_INIT; static struct strvec reflog = STRVEC_INIT; static struct strvec repack = STRVEC_INIT; static struct strvec prune = STRVEC_INIT; @@ -163,6 +162,15 @@ static void gc_config(void) git_config(git_default_config, NULL); } +struct maintenance_run_opts; +static int maintenance_task_pack_refs(MAYBE_UNUSED struct maintenance_run_opts *opts) +{ + struct strvec pack_refs_cmd = STRVEC_INIT; + strvec_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL); + + return run_command_v_opt(pack_refs_cmd.v, RUN_GIT_CMD); +} + static int too_many_loose_objects(void) { /* @@ -518,8 +526,8 @@ static void gc_before_repack(void) if (done++) return; - if (pack_refs && run_command_v_opt(pack_refs_cmd.v, RUN_GIT_CMD)) - die(FAILED_RUN, pack_refs_cmd.v[0]); + if (pack_refs && maintenance_task_pack_refs(NULL)) + die(FAILED_RUN, "pack-refs"); if (prune_reflogs && run_command_v_opt(reflog.v, RUN_GIT_CMD)) die(FAILED_RUN, reflog.v[0]); @@ -556,7 +564,6 @@ int cmd_gc(int argc, const char **argv, const char *prefix) if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_gc_usage, builtin_gc_options); - strvec_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL); strvec_pushl(&reflog, "reflog", "expire", "--all", NULL); strvec_pushl(&repack, "repack", "-d", "-l", NULL); strvec_pushl(&prune, "prune", "--expire", NULL); @@ -1224,6 +1231,7 @@ enum maintenance_task_label { TASK_INCREMENTAL_REPACK, TASK_GC, TASK_COMMIT_GRAPH, + TASK_PACK_REFS, /* Leave as final value */ TASK__COUNT @@ -1255,6 +1263,11 @@ static struct maintenance_task tasks[] = { maintenance_task_commit_graph, should_write_commit_graph, }, + [TASK_PACK_REFS] = { + "pack-refs", + maintenance_task_pack_refs, + NULL, + }, }; static int compare_tasks_by_selection(const void *a_, const void *b_) @@ -1339,6 +1352,8 @@ static void initialize_maintenance_strategy(void) tasks[TASK_INCREMENTAL_REPACK].schedule = SCHEDULE_DAILY; tasks[TASK_LOOSE_OBJECTS].enabled = 1; tasks[TASK_LOOSE_OBJECTS].schedule = SCHEDULE_DAILY; + tasks[TASK_PACK_REFS].enabled = 1; + tasks[TASK_PACK_REFS].schedule = SCHEDULE_WEEKLY; } } @@ -1446,11 +1461,23 @@ static int maintenance_run(int argc, const char **argv, const char *prefix) return maintenance_run_tasks(&opts); } +static char *get_maintpath(void) +{ + struct strbuf sb = STRBUF_INIT; + const char *p = the_repository->worktree ? + the_repository->worktree : the_repository->gitdir; + + strbuf_realpath(&sb, p, 1); + return strbuf_detach(&sb, NULL); +} + static int maintenance_register(void) { + int rc; char *config_value; struct child_process config_set = CHILD_PROCESS_INIT; struct child_process config_get = CHILD_PROCESS_INIT; + char *maintpath = get_maintpath(); /* Disable foreground maintenance */ git_config_set("maintenance.auto", "false"); @@ -1463,40 +1490,44 @@ static int maintenance_register(void) config_get.git_cmd = 1; strvec_pushl(&config_get.args, "config", "--global", "--get", - "--fixed-value", "maintenance.repo", - the_repository->worktree ? the_repository->worktree - : the_repository->gitdir, - NULL); + "--fixed-value", "maintenance.repo", maintpath, NULL); config_get.out = -1; - if (start_command(&config_get)) - return error(_("failed to run 'git config'")); + if (start_command(&config_get)) { + rc = error(_("failed to run 'git config'")); + goto done; + } /* We already have this value in our config! */ - if (!finish_command(&config_get)) - return 0; + if (!finish_command(&config_get)) { + rc = 0; + goto done; + } config_set.git_cmd = 1; strvec_pushl(&config_set.args, "config", "--add", "--global", "maintenance.repo", - the_repository->worktree ? the_repository->worktree - : the_repository->gitdir, - NULL); + maintpath, NULL); - return run_command(&config_set); + rc = run_command(&config_set); + +done: + free(maintpath); + return rc; } static int maintenance_unregister(void) { + int rc; struct child_process config_unset = CHILD_PROCESS_INIT; + char *maintpath = get_maintpath(); config_unset.git_cmd = 1; strvec_pushl(&config_unset.args, "config", "--global", "--unset", - "--fixed-value", "maintenance.repo", - the_repository->worktree ? the_repository->worktree - : the_repository->gitdir, - NULL); + "--fixed-value", "maintenance.repo", maintpath, NULL); - return run_command(&config_unset); + rc = run_command(&config_unset); + free(maintpath); + return rc; } static const char *get_frequency(enum schedule_priority schedule) diff --git a/builtin/grep.c b/builtin/grep.c index 55d06c9513..5de725f904 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -211,7 +211,7 @@ static void start_threads(struct grep_opt *opt) strbuf_init(&todo[i].out, 0); } - threads = xcalloc(num_threads, sizeof(*threads)); + CALLOC_ARRAY(threads, num_threads); for (i = 0; i < num_threads; i++) { int err; struct grep_opt *o = grep_opt_dup(opt); @@ -506,6 +506,10 @@ static int grep_cache(struct grep_opt *opt, for (nr = 0; nr < repo->index->cache_nr; nr++) { const struct cache_entry *ce = repo->index->cache[nr]; + + if (!cached && ce_skip_worktree(ce)) + continue; + strbuf_setlen(&name, name_base_len); strbuf_addstr(&name, ce->name); @@ -518,8 +522,7 @@ static int grep_cache(struct grep_opt *opt, * cache entry are identical, even if worktree file has * been modified, so use cache version instead */ - if (cached || (ce->ce_flags & CE_VALID) || - ce_skip_worktree(ce)) { + if (cached || (ce->ce_flags & CE_VALID)) { if (ce_stage(ce) || ce_intent_to_add(ce)) continue; hit |= grep_oid(opt, &ce->oid, name.buf, @@ -1152,6 +1155,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (!use_index && (untracked || cached)) die(_("--cached or --untracked cannot be used with --no-index")); + if (untracked && cached) + die(_("--untracked cannot be used with --cached")); + if (!use_index || untracked) { int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude; hit = grep_directory(&opt, &pathspec, use_exclude, use_index); @@ -1175,6 +1181,5 @@ int cmd_grep(int argc, const char **argv, const char *prefix) run_pager(&opt, prefix); clear_pathspec(&pathspec); free_grep_patterns(&opt); - grep_destroy(); return !hit; } diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 557bd2f348..15507b5cff 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -17,7 +17,7 @@ #include "promisor-remote.h" static const char index_pack_usage[] = -"git index-pack [-v] [-o ] [--keep | --keep=] [--verify] [--strict] ( | --stdin [--fix-thin] [])"; +"git index-pack [-v] [-o ] [--keep | --keep=] [--[no-]rev-index] [--verify] [--strict] ( | --stdin [--fix-thin] [])"; struct object_entry { struct pack_idx_entry idx; @@ -120,7 +120,7 @@ static int nr_threads; static int from_stdin; static int strict; static int do_fsck_object; -static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT; +static struct fsck_options fsck_options = FSCK_OPTIONS_MISSING_GITMODULES; static int verbose; static int show_resolving_progress; static int show_stat; @@ -185,7 +185,7 @@ static void init_thread(void) if (show_stat) pthread_mutex_init(&deepest_delta_mutex, NULL); pthread_key_create(&key, NULL); - thread_data = xcalloc(nr_threads, sizeof(*thread_data)); + CALLOC_ARRAY(thread_data, nr_threads); for (i = 0; i < nr_threads; i++) { thread_data[i].pack_fd = open(curr_pack, O_RDONLY); if (thread_data[i].pack_fd == -1) @@ -212,7 +212,8 @@ static void cleanup_thread(void) free(thread_data); } -static int mark_link(struct object *obj, int type, void *data, struct fsck_options *options) +static int mark_link(struct object *obj, enum object_type type, + void *data, struct fsck_options *options) { if (!obj) return -1; @@ -1436,15 +1437,15 @@ static void fix_unresolved_deltas(struct hashfile *f) free(sorted_by_pos); } -static const char *derive_filename(const char *pack_name, const char *suffix, - struct strbuf *buf) +static const char *derive_filename(const char *pack_name, const char *strip, + const char *suffix, struct strbuf *buf) { size_t len; - if (!strip_suffix(pack_name, ".pack", &len)) - die(_("packfile name '%s' does not end with '.pack'"), - pack_name); + if (!strip_suffix(pack_name, strip, &len) || !len || + pack_name[len - 1] != '.') + die(_("packfile name '%s' does not end with '.%s'"), + pack_name, strip); strbuf_add(buf, pack_name, len); - strbuf_addch(buf, '.'); strbuf_addstr(buf, suffix); return buf->buf; } @@ -1459,7 +1460,7 @@ static void write_special_file(const char *suffix, const char *msg, int msg_len = strlen(msg); if (pack_name) - filename = derive_filename(pack_name, suffix, &name_buf); + filename = derive_filename(pack_name, "pack", suffix, &name_buf); else filename = odb_pack_name(&name_buf, hash, suffix); @@ -1484,12 +1485,14 @@ static void write_special_file(const char *suffix, const char *msg, static void final(const char *final_pack_name, const char *curr_pack_name, const char *final_index_name, const char *curr_index_name, + const char *final_rev_index_name, const char *curr_rev_index_name, const char *keep_msg, const char *promisor_msg, unsigned char *hash) { const char *report = "pack"; struct strbuf pack_name = STRBUF_INIT; struct strbuf index_name = STRBUF_INIT; + struct strbuf rev_index_name = STRBUF_INIT; int err; if (!from_stdin) { @@ -1524,6 +1527,16 @@ static void final(const char *final_pack_name, const char *curr_pack_name, } else chmod(final_index_name, 0444); + if (curr_rev_index_name) { + if (final_rev_index_name != curr_rev_index_name) { + if (!final_rev_index_name) + final_rev_index_name = odb_pack_name(&rev_index_name, hash, "rev"); + if (finalize_object_file(curr_rev_index_name, final_rev_index_name)) + die(_("cannot store reverse index file")); + } else + chmod(final_rev_index_name, 0444); + } + if (do_fsck_object) { struct packed_git *p; p = add_packed_git(final_index_name, strlen(final_index_name), 0); @@ -1553,6 +1566,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name, } } + strbuf_release(&rev_index_name); strbuf_release(&index_name); strbuf_release(&pack_name); } @@ -1578,6 +1592,12 @@ static int git_index_pack_config(const char *k, const char *v, void *cb) } return 0; } + if (!strcmp(k, "pack.writereverseindex")) { + if (git_config_bool(k, v)) + opts->flags |= WRITE_REV; + else + opts->flags &= ~WRITE_REV; + } return git_default_config(k, v, cb); } @@ -1655,7 +1675,7 @@ static void show_pack_info(int stat_only) unsigned long *chain_histogram = NULL; if (deepest_delta) - chain_histogram = xcalloc(deepest_delta, sizeof(unsigned long)); + CALLOC_ARRAY(chain_histogram, deepest_delta); for (i = 0; i < nr_objects; i++) { struct object_entry *obj = &objects[i]; @@ -1695,12 +1715,14 @@ static void show_pack_info(int stat_only) int cmd_index_pack(int argc, const char **argv, const char *prefix) { - int i, fix_thin_pack = 0, verify = 0, stat_only = 0; + int i, fix_thin_pack = 0, verify = 0, stat_only = 0, rev_index; const char *curr_index; - const char *index_name = NULL, *pack_name = NULL; + const char *curr_rev_index = NULL; + const char *index_name = NULL, *pack_name = NULL, *rev_index_name = NULL; const char *keep_msg = NULL; const char *promisor_msg = NULL; struct strbuf index_name_buf = STRBUF_INIT; + struct strbuf rev_index_name_buf = STRBUF_INIT; struct pack_idx_entry **idx_objects; struct pack_idx_option opts; unsigned char pack_hash[GIT_MAX_RAWSZ]; @@ -1727,6 +1749,11 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (prefix && chdir(prefix)) die(_("Cannot come back to cwd")); + if (git_env_bool(GIT_TEST_WRITE_REV_INDEX, 0)) + rev_index = 1; + else + rev_index = !!(opts.flags & (WRITE_REV_VERIFY | WRITE_REV)); + for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -1805,6 +1832,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (hash_algo == GIT_HASH_UNKNOWN) die(_("unknown hash algorithm '%s'"), arg); repo_set_hash_algo(the_repository, hash_algo); + } else if (!strcmp(arg, "--rev-index")) { + rev_index = 1; + } else if (!strcmp(arg, "--no-rev-index")) { + rev_index = 0; } else usage(index_pack_usage); continue; @@ -1824,7 +1855,16 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (from_stdin && hash_algo) die(_("--object-format cannot be used with --stdin")); if (!index_name && pack_name) - index_name = derive_filename(pack_name, "idx", &index_name_buf); + index_name = derive_filename(pack_name, "pack", "idx", &index_name_buf); + + opts.flags &= ~(WRITE_REV | WRITE_REV_VERIFY); + if (rev_index) { + opts.flags |= verify ? WRITE_REV_VERIFY : WRITE_REV; + if (index_name) + rev_index_name = derive_filename(index_name, + "idx", "rev", + &rev_index_name_buf); + } if (verify) { if (!index_name) @@ -1857,10 +1897,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) curr_pack = open_pack_file(pack_name); parse_pack_header(); - objects = xcalloc(st_add(nr_objects, 1), sizeof(struct object_entry)); + CALLOC_ARRAY(objects, st_add(nr_objects, 1)); if (show_stat) - obj_stat = xcalloc(st_add(nr_objects, 1), sizeof(struct object_stat)); - ofs_deltas = xcalloc(nr_objects, sizeof(struct ofs_delta_entry)); + CALLOC_ARRAY(obj_stat, st_add(nr_objects, 1)); + CALLOC_ARRAY(ofs_deltas, nr_objects); parse_pack_objects(pack_hash); if (report_end_of_input) write_in_full(2, "\0", 1); @@ -1878,11 +1918,16 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) for (i = 0; i < nr_objects; i++) idx_objects[i] = &objects[i].idx; curr_index = write_idx_file(index_name, idx_objects, nr_objects, &opts, pack_hash); + if (rev_index) + curr_rev_index = write_rev_file(rev_index_name, idx_objects, + nr_objects, pack_hash, + opts.flags); free(idx_objects); if (!verify) final(pack_name, curr_pack, index_name, curr_index, + rev_index_name, curr_rev_index, keep_msg, promisor_msg, pack_hash); else @@ -1893,10 +1938,13 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) free(objects); strbuf_release(&index_name_buf); + strbuf_release(&rev_index_name_buf); if (pack_name == NULL) free((void *) curr_pack); if (index_name == NULL) free((void *) curr_index); + if (rev_index_name == NULL) + free((void *) curr_rev_index); /* * Let the caller know this pack is not self contained diff --git a/builtin/init-db.c b/builtin/init-db.c index dcc45bef51..c19b35f1e6 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -25,7 +25,6 @@ static int init_is_bare_repository = 0; static int init_shared_repository = -1; -static const char *init_db_template_dir; static void copy_templates_1(struct strbuf *path, struct strbuf *template_path, DIR *dir) @@ -94,7 +93,7 @@ static void copy_templates_1(struct strbuf *path, struct strbuf *template_path, } } -static void copy_templates(const char *template_dir) +static void copy_templates(const char *template_dir, const char *init_template_dir) { struct strbuf path = STRBUF_INIT; struct strbuf template_path = STRBUF_INIT; @@ -107,7 +106,7 @@ static void copy_templates(const char *template_dir) if (!template_dir) template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT); if (!template_dir) - template_dir = init_db_template_dir; + template_dir = init_template_dir; if (!template_dir) template_dir = to_free = system_path(DEFAULT_GIT_TEMPLATE_DIR); if (!template_dir[0]) { @@ -154,17 +153,6 @@ free_return: clear_repository_format(&template_format); } -static int git_init_db_config(const char *k, const char *v, void *cb) -{ - if (!strcmp(k, "init.templatedir")) - return git_config_pathname(&init_db_template_dir, k, v); - - if (starts_with(k, "core.")) - return platform_core_config(k, v, cb); - - return 0; -} - /* * If the git_dir is not directly inside the working tree, then git will not * find it by default, and we need to set the worktree explicitly. @@ -212,10 +200,8 @@ static int create_default_files(const char *template_path, int reinit; int filemode; struct strbuf err = STRBUF_INIT; - - /* Just look for `init.templatedir` */ - init_db_template_dir = NULL; /* re-set in case it was set before */ - git_config(git_init_db_config, NULL); + const char *init_template_dir = NULL; + const char *work_tree = get_git_work_tree(); /* * First copy the templates -- we might have the default @@ -226,7 +212,8 @@ static int create_default_files(const char *template_path, * values (since we've just potentially changed what's available on * disk). */ - copy_templates(template_path); + git_config_get_value("init.templatedir", &init_template_dir); + copy_templates(template_path, init_template_dir); git_config_clear(); reset_shared_repository(); git_config(git_default_config, NULL); @@ -235,7 +222,7 @@ static int create_default_files(const char *template_path, * We must make sure command-line options continue to override any * values we might have just re-read from the config. */ - is_bare_repository_cfg = init_is_bare_repository; + is_bare_repository_cfg = init_is_bare_repository || !work_tree; if (init_shared_repository != -1) set_shared_repository(init_shared_repository); @@ -299,7 +286,6 @@ static int create_default_files(const char *template_path, if (is_bare_repository()) git_config_set("core.bare", "true"); else { - const char *work_tree = get_git_work_tree(); git_config_set("core.bare", "false"); /* allow template config file to override the default */ if (log_all_ref_updates == LOG_REFS_UNSET) @@ -422,8 +408,8 @@ int init_db(const char *git_dir, const char *real_git_dir, } startup_info->have_repository = 1; - /* Just look for `core.hidedotfiles` */ - git_config(git_init_db_config, NULL); + /* Ensure `core.hidedotfiles` is processed */ + git_config(platform_core_config, NULL); safe_create_dir(git_dir, 0); @@ -575,8 +561,10 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) if (real_git_dir && !is_absolute_path(real_git_dir)) real_git_dir = real_pathdup(real_git_dir, 1); - if (template_dir && *template_dir && !is_absolute_path(template_dir)) + if (template_dir && *template_dir && !is_absolute_path(template_dir)) { template_dir = absolute_pathdup(template_dir); + UNLEAK(template_dir); + } if (argc == 1) { int mkdir_tried = 0; diff --git a/builtin/log.c b/builtin/log.c index d0cbaaf68a..8acd285daf 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -307,10 +307,11 @@ static struct itimerval early_output_timer; static void log_show_early(struct rev_info *revs, struct commit_list *list) { - int i = revs->early_output, close_file = revs->diffopt.close_file; + int i = revs->early_output; int show_header = 1; + int no_free = revs->diffopt.no_free; - revs->diffopt.close_file = 0; + revs->diffopt.no_free = 0; sort_in_topological_order(&list, revs->sort_order); while (list && i) { struct commit *commit = list->item; @@ -327,8 +328,8 @@ static void log_show_early(struct rev_info *revs, struct commit_list *list) case commit_ignore: break; case commit_error: - if (close_file) - fclose(revs->diffopt.file); + revs->diffopt.no_free = no_free; + diff_free(&revs->diffopt); return; } list = list->next; @@ -336,8 +337,8 @@ static void log_show_early(struct rev_info *revs, struct commit_list *list) /* Did we already get enough commits for the early output? */ if (!i) { - if (close_file) - fclose(revs->diffopt.file); + revs->diffopt.no_free = 0; + diff_free(&revs->diffopt); return; } @@ -401,7 +402,7 @@ static int cmd_log_walk(struct rev_info *rev) { struct commit *commit; int saved_nrl = 0; - int saved_dcctc = 0, close_file = rev->diffopt.close_file; + int saved_dcctc = 0; if (rev->early_output) setup_early_output(); @@ -417,7 +418,7 @@ static int cmd_log_walk(struct rev_info *rev) * and HAS_CHANGES being accumulated in rev->diffopt, so be careful to * retain that state information if replacing rev->diffopt in this loop */ - rev->diffopt.close_file = 0; + rev->diffopt.no_free = 1; while ((commit = get_revision(rev)) != NULL) { if (!log_tree_commit(rev, commit) && rev->max_count >= 0) /* @@ -442,8 +443,8 @@ static int cmd_log_walk(struct rev_info *rev) } rev->diffopt.degraded_cc_to_c = saved_dcctc; rev->diffopt.needed_rename_limit = saved_nrl; - if (close_file) - fclose(rev->diffopt.file); + rev->diffopt.no_free = 0; + diff_free(&rev->diffopt); if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF && rev->diffopt.flags.check_failed) { @@ -598,7 +599,7 @@ static int show_tag_object(const struct object_id *oid, struct rev_info *rev) static int show_tree_object(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, int stage, void *context) + const char *pathname, unsigned mode, void *context) { FILE *file = context; fprintf(file, "%s%s\n", pathname, S_ISDIR(mode) ? "/" : ""); @@ -680,9 +681,9 @@ int cmd_show(int argc, const char **argv, const char *prefix) diff_get_color_opt(&rev.diffopt, DIFF_COMMIT), name, diff_get_color_opt(&rev.diffopt, DIFF_RESET)); - read_tree_recursive(the_repository, (struct tree *)o, "", - 0, 0, &match_all, show_tree_object, - rev.diffopt.file); + read_tree(the_repository, (struct tree *)o, + &match_all, show_tree_object, + rev.diffopt.file); rev.shown_one = 1; break; case OBJ_COMMIT: @@ -1223,14 +1224,20 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file, */ struct diff_options opts; struct strvec other_arg = STRVEC_INIT; + struct range_diff_options range_diff_opts = { + .creation_factor = rev->creation_factor, + .dual_color = 1, + .diffopt = &opts, + .other_arg = &other_arg + }; + diff_setup(&opts); opts.file = rev->diffopt.file; opts.use_color = rev->diffopt.use_color; diff_setup_done(&opts); fprintf_ln(rev->diffopt.file, "%s", rev->rdiff_title); get_notes_args(&other_arg, rev); - show_range_diff(rev->rdiff1, rev->rdiff2, - rev->creation_factor, 1, &opts, &other_arg); + show_range_diff(rev->rdiff1, rev->rdiff2, &range_diff_opts); strvec_clear(&other_arg); } } @@ -1655,13 +1662,19 @@ static void print_bases(struct base_tree_info *bases, FILE *file) oidclr(&bases->base_commit); } -static const char *diff_title(struct strbuf *sb, int reroll_count, - const char *generic, const char *rerolled) +static const char *diff_title(struct strbuf *sb, + const char *reroll_count, + const char *generic, + const char *rerolled) { - if (reroll_count <= 0) + int v; + + /* RFC may be v0, so allow -v1 to diff against v0 */ + if (reroll_count && !strtol_i(reroll_count, 10, &v) && + v >= 1) + strbuf_addf(sb, rerolled, v - 1); + else strbuf_addstr(sb, generic); - else /* RFC may be v0, so allow -v1 to diff against v0 */ - strbuf_addf(sb, rerolled, reroll_count - 1); return sb->buf; } @@ -1672,7 +1685,7 @@ static void infer_range_diff_ranges(struct strbuf *r1, struct commit *head) { const char *head_oid = oid_to_hex(&head->object.oid); - int prev_is_range = !!strstr(prev, ".."); + int prev_is_range = is_range_diff_range(prev); if (prev_is_range) strbuf_addstr(r1, prev); @@ -1710,7 +1723,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) struct strbuf buf = STRBUF_INIT; int use_patch_format = 0; int quiet = 0; - int reroll_count = -1; + const char *reroll_count = NULL; char *cover_from_description_arg = NULL; char *branch_name = NULL; char *base_commit = NULL; @@ -1744,7 +1757,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("use instead of '.patch'")), OPT_INTEGER(0, "start-number", &start_number, N_("start numbering patches at instead of 1")), - OPT_INTEGER('v', "reroll-count", &reroll_count, + OPT_STRING('v', "reroll-count", &reroll_count, N_("reroll-count"), N_("mark the series as Nth re-roll")), OPT_INTEGER(0, "filename-max-length", &fmt_patch_name_max, N_("max length of output filename")), @@ -1855,9 +1868,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (cover_from_description_arg) cover_from_description_mode = parse_cover_from_description(cover_from_description_arg); - if (0 < reroll_count) { + if (reroll_count) { struct strbuf sprefix = STRBUF_INIT; - strbuf_addf(&sprefix, "%s v%d", + + strbuf_addf(&sprefix, "%s v%s", rev.subject_prefix, reroll_count); rev.reroll_count = reroll_count; rev.subject_prefix = strbuf_detach(&sprefix, NULL); @@ -1955,7 +1969,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) * file, but but we must instruct it not to close after each * diff. */ - rev.diffopt.close_file = 0; + rev.diffopt.no_free = 1; } else { int saved; diff --git a/builtin/ls-files.c b/builtin/ls-files.c index f6f9e483b2..60a2913a01 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -12,6 +12,7 @@ #include "dir.h" #include "builtin.h" #include "tree.h" +#include "cache-tree.h" #include "parse-options.h" #include "resolve-undo.h" #include "string-list.h" @@ -420,6 +421,53 @@ static int get_common_prefix_len(const char *common_prefix) return common_prefix_len; } +static int read_one_entry_opt(struct index_state *istate, + const struct object_id *oid, + struct strbuf *base, + const char *pathname, + unsigned mode, int opt) +{ + int len; + struct cache_entry *ce; + + if (S_ISDIR(mode)) + return READ_TREE_RECURSIVE; + + len = strlen(pathname); + ce = make_empty_cache_entry(istate, base->len + len); + + ce->ce_mode = create_ce_mode(mode); + ce->ce_flags = create_ce_flags(1); + ce->ce_namelen = base->len + len; + memcpy(ce->name, base->buf, base->len); + memcpy(ce->name + base->len, pathname, len+1); + oidcpy(&ce->oid, oid); + return add_index_entry(istate, ce, opt); +} + +static int read_one_entry(const struct object_id *oid, struct strbuf *base, + const char *pathname, unsigned mode, + void *context) +{ + struct index_state *istate = context; + return read_one_entry_opt(istate, oid, base, pathname, + mode, + ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK); +} + +/* + * This is used when the caller knows there is no existing entries at + * the stage that will conflict with the entry being added. + */ +static int read_one_entry_quick(const struct object_id *oid, struct strbuf *base, + const char *pathname, unsigned mode, + void *context) +{ + struct index_state *istate = context; + return read_one_entry_opt(istate, oid, base, pathname, + mode, ADD_CACHE_JUST_APPEND); +} + /* * Read the tree specified with --with-tree option * (typically, HEAD) into stage #1 and then @@ -436,6 +484,8 @@ void overlay_tree_on_index(struct index_state *istate, struct pathspec pathspec; struct cache_entry *last_stage0 = NULL; int i; + read_tree_fn_t fn = NULL; + int err; if (get_oid(tree_name, &oid)) die("tree-ish %s not found.", tree_name); @@ -458,9 +508,32 @@ void overlay_tree_on_index(struct index_state *istate, PATHSPEC_PREFER_CWD, prefix, matchbuf); } else memset(&pathspec, 0, sizeof(pathspec)); - if (read_tree(the_repository, tree, 1, &pathspec, istate)) + + /* + * See if we have cache entry at the stage. If so, + * do it the original slow way, otherwise, append and then + * sort at the end. + */ + for (i = 0; !fn && i < istate->cache_nr; i++) { + const struct cache_entry *ce = istate->cache[i]; + if (ce_stage(ce) == 1) + fn = read_one_entry; + } + + if (!fn) + fn = read_one_entry_quick; + err = read_tree(the_repository, tree, &pathspec, fn, istate); + if (err) die("unable to read tree entries %s", tree_name); + /* + * Sort the cache entry -- we need to nuke the cache tree, though. + */ + if (fn == read_one_entry_quick) { + cache_tree_free(&istate->cache_tree); + QSORT(istate->cache, istate->cache_nr, cmp_cache_name_compare); + } + for (i = 0; i < istate->cache_nr; i++) { struct cache_entry *ce = istate->cache[i]; switch (ce_stage(ce)) { diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 092917eca2..1794548c71 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -45,7 +45,8 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) int show_symref_target = 0; const char *uploadpack = NULL; const char **pattern = NULL; - struct strvec ref_prefixes = STRVEC_INIT; + struct transport_ls_refs_options transport_options = + TRANSPORT_LS_REFS_OPTIONS_INIT; int i; struct string_list server_options = STRING_LIST_INIT_DUP; @@ -87,16 +88,16 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) if (argc > 1) { int i; - pattern = xcalloc(argc, sizeof(const char *)); + CALLOC_ARRAY(pattern, argc); for (i = 1; i < argc; i++) { pattern[i - 1] = xstrfmt("*/%s", argv[i]); } } if (flags & REF_TAGS) - strvec_push(&ref_prefixes, "refs/tags/"); + strvec_push(&transport_options.ref_prefixes, "refs/tags/"); if (flags & REF_HEADS) - strvec_push(&ref_prefixes, "refs/heads/"); + strvec_push(&transport_options.ref_prefixes, "refs/heads/"); remote = remote_get(dest); if (!remote) { @@ -118,13 +119,11 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) if (server_options.nr) transport->server_options = &server_options; - ref = transport_get_remote_refs(transport, &ref_prefixes); + ref = transport_get_remote_refs(transport, &transport_options); if (ref) { int hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport)); repo_set_hash_algo(the_repository, hash_algo); } - if (transport_disconnect(transport)) - return 1; if (!dest && !quiet) fprintf(stderr, "From %s\n", *remote->url); @@ -150,5 +149,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) } ref_array_clear(&ref_array); + if (transport_disconnect(transport)) + return 1; return status; } diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 7cad3f24eb..3a442631c7 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -62,7 +62,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname) } static int show_tree(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, int stage, void *context) + const char *pathname, unsigned mode, void *context) { int retval = 0; int baselen; @@ -185,6 +185,6 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) tree = parse_tree_indirect(&oid); if (!tree) die("not a tree object"); - return !!read_tree_recursive(the_repository, tree, "", 0, 0, - &pathspec, show_tree, NULL); + return !!read_tree(the_repository, tree, + &pathspec, show_tree, NULL); } diff --git a/builtin/merge.c b/builtin/merge.c index eb00b273e6..388619536a 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -210,7 +210,7 @@ static struct strategy *get_strategy(const char *name) exit(1); } - ret = xcalloc(1, sizeof(struct strategy)); + CALLOC_ARRAY(ret, 1); ret->name = xstrdup(name); ret->attr = NO_TRIVIAL; return ret; diff --git a/builtin/mktag.c b/builtin/mktag.c index 41a399a69e..dddcccdd36 100644 --- a/builtin/mktag.c +++ b/builtin/mktag.c @@ -14,15 +14,12 @@ static int option_strict = 1; static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT; -static int mktag_config(const char *var, const char *value, void *cb) -{ - return fsck_config_internal(var, value, cb, &fsck_options); -} - static int mktag_fsck_error_func(struct fsck_options *o, const struct object_id *oid, enum object_type object_type, - int msg_type, const char *message) + enum fsck_msg_type msg_type, + enum fsck_msg_id msg_id, + const char *message) { switch (msg_type) { case FSCK_WARN: @@ -91,9 +88,10 @@ int cmd_mktag(int argc, const char **argv, const char *prefix) die_errno(_("could not read from stdin")); fsck_options.error_func = mktag_fsck_error_func; - fsck_set_msg_type(&fsck_options, "extraheaderentry", "warn"); + fsck_set_msg_type_from_ids(&fsck_options, FSCK_MSG_EXTRA_HEADER_ENTRY, + FSCK_WARN); /* config might set fsck.extraHeaderEntry=* again */ - git_config(mktag_config, NULL); + git_config(git_fsck_config, &fsck_options); if (fsck_tag_standalone(NULL, buf.buf, buf.len, &fsck_options, &tagged_oid, &tagged_type)) die(_("tag on stdin did not pass our strict fsck check")); diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c index 5bf88cd2a8..5d3ea445fd 100644 --- a/builtin/multi-pack-index.c +++ b/builtin/multi-pack-index.c @@ -4,67 +4,181 @@ #include "parse-options.h" #include "midx.h" #include "trace2.h" +#include "object-store.h" +#define BUILTIN_MIDX_WRITE_USAGE \ + N_("git multi-pack-index [] write [--preferred-pack=]") + +#define BUILTIN_MIDX_VERIFY_USAGE \ + N_("git multi-pack-index [] verify") + +#define BUILTIN_MIDX_EXPIRE_USAGE \ + N_("git multi-pack-index [] expire") + +#define BUILTIN_MIDX_REPACK_USAGE \ + N_("git multi-pack-index [] repack [--batch-size=]") + +static char const * const builtin_multi_pack_index_write_usage[] = { + BUILTIN_MIDX_WRITE_USAGE, + NULL +}; +static char const * const builtin_multi_pack_index_verify_usage[] = { + BUILTIN_MIDX_VERIFY_USAGE, + NULL +}; +static char const * const builtin_multi_pack_index_expire_usage[] = { + BUILTIN_MIDX_EXPIRE_USAGE, + NULL +}; +static char const * const builtin_multi_pack_index_repack_usage[] = { + BUILTIN_MIDX_REPACK_USAGE, + NULL +}; static char const * const builtin_multi_pack_index_usage[] = { - N_("git multi-pack-index [] (write|verify|expire|repack --batch-size=)"), + BUILTIN_MIDX_WRITE_USAGE, + BUILTIN_MIDX_VERIFY_USAGE, + BUILTIN_MIDX_EXPIRE_USAGE, + BUILTIN_MIDX_REPACK_USAGE, NULL }; static struct opts_multi_pack_index { const char *object_dir; + const char *preferred_pack; unsigned long batch_size; - int progress; + unsigned flags; } opts; -int cmd_multi_pack_index(int argc, const char **argv, - const char *prefix) +static struct option common_opts[] = { + OPT_FILENAME(0, "object-dir", &opts.object_dir, + N_("object directory containing set of packfile and pack-index pairs")), + OPT_BIT(0, "progress", &opts.flags, N_("force progress reporting"), MIDX_PROGRESS), + OPT_END(), +}; + +static struct option *add_common_options(struct option *prev) { - unsigned flags = 0; + return parse_options_concat(common_opts, prev); +} + +static int cmd_multi_pack_index_write(int argc, const char **argv) +{ + struct option *options; + static struct option builtin_multi_pack_index_write_options[] = { + OPT_STRING(0, "preferred-pack", &opts.preferred_pack, + N_("preferred-pack"), + N_("pack for reuse when computing a multi-pack bitmap")), + OPT_END(), + }; + + options = add_common_options(builtin_multi_pack_index_write_options); + + trace2_cmd_mode(argv[0]); - static struct option builtin_multi_pack_index_options[] = { - OPT_FILENAME(0, "object-dir", &opts.object_dir, - N_("object directory containing set of packfile and pack-index pairs")), - OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")), + argc = parse_options(argc, argv, NULL, + options, builtin_multi_pack_index_write_usage, + PARSE_OPT_KEEP_UNKNOWN); + if (argc) + usage_with_options(builtin_multi_pack_index_write_usage, + options); + + FREE_AND_NULL(options); + + return write_midx_file(opts.object_dir, opts.preferred_pack, + opts.flags); +} + +static int cmd_multi_pack_index_verify(int argc, const char **argv) +{ + struct option *options = common_opts; + + trace2_cmd_mode(argv[0]); + + argc = parse_options(argc, argv, NULL, + options, builtin_multi_pack_index_verify_usage, + PARSE_OPT_KEEP_UNKNOWN); + if (argc) + usage_with_options(builtin_multi_pack_index_verify_usage, + options); + + return verify_midx_file(the_repository, opts.object_dir, opts.flags); +} + +static int cmd_multi_pack_index_expire(int argc, const char **argv) +{ + struct option *options = common_opts; + + trace2_cmd_mode(argv[0]); + + argc = parse_options(argc, argv, NULL, + options, builtin_multi_pack_index_expire_usage, + PARSE_OPT_KEEP_UNKNOWN); + if (argc) + usage_with_options(builtin_multi_pack_index_expire_usage, + options); + + return expire_midx_packs(the_repository, opts.object_dir, opts.flags); +} + +static int cmd_multi_pack_index_repack(int argc, const char **argv) +{ + struct option *options; + static struct option builtin_multi_pack_index_repack_options[] = { OPT_MAGNITUDE(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_END(), }; + options = add_common_options(builtin_multi_pack_index_repack_options); + + trace2_cmd_mode(argv[0]); + + argc = parse_options(argc, argv, NULL, + options, + builtin_multi_pack_index_repack_usage, + PARSE_OPT_KEEP_UNKNOWN); + if (argc) + usage_with_options(builtin_multi_pack_index_repack_usage, + options); + + FREE_AND_NULL(options); + + return midx_repack(the_repository, opts.object_dir, + (size_t)opts.batch_size, opts.flags); +} + +int cmd_multi_pack_index(int argc, const char **argv, + const char *prefix) +{ + struct option *builtin_multi_pack_index_options = common_opts; + git_config(git_default_config, NULL); - opts.progress = isatty(2); + if (isatty(2)) + opts.flags |= MIDX_PROGRESS; argc = parse_options(argc, argv, prefix, builtin_multi_pack_index_options, - builtin_multi_pack_index_usage, 0); + builtin_multi_pack_index_usage, + PARSE_OPT_STOP_AT_NON_OPTION); if (!opts.object_dir) opts.object_dir = get_object_directory(); - if (opts.progress) - flags |= MIDX_PROGRESS; if (argc == 0) + goto usage; + + if (!strcmp(argv[0], "repack")) + return cmd_multi_pack_index_repack(argc, argv); + else if (!strcmp(argv[0], "write")) + return cmd_multi_pack_index_write(argc, argv); + else if (!strcmp(argv[0], "verify")) + return cmd_multi_pack_index_verify(argc, argv); + else if (!strcmp(argv[0], "expire")) + return cmd_multi_pack_index_expire(argc, argv); + else { +usage: + error(_("unrecognized subcommand: %s"), argv[0]); usage_with_options(builtin_multi_pack_index_usage, builtin_multi_pack_index_options); - - if (argc > 1) { - die(_("too many arguments")); - return 1; } - - trace2_cmd_mode(argv[0]); - - if (!strcmp(argv[0], "repack")) - return midx_repack(the_repository, opts.object_dir, - (size_t)opts.batch_size, flags); - if (opts.batch_size) - die(_("--batch-size option is only for 'repack' subcommand")); - - if (!strcmp(argv[0], "write")) - return write_midx_file(opts.object_dir, flags); - if (!strcmp(argv[0], "verify")) - return verify_midx_file(the_repository, opts.object_dir, flags); - if (!strcmp(argv[0], "expire")) - return expire_midx_packs(the_repository, opts.object_dir, flags); - - die(_("unrecognized subcommand: %s"), argv[0]); } diff --git a/builtin/mv.c b/builtin/mv.c index 7dac714af9..3fccdcb645 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -221,7 +221,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) } argc += last - first; } - } else if (!(ce = cache_file_exists(src, length, ignore_case))) { + } else if (!(ce = cache_file_exists(src, length, 0))) { bad = _("not under version control"); } else if (ce_stage(ce)) { bad = _("conflicted"); diff --git a/builtin/notes.c b/builtin/notes.c index 2987c08a2e..74bba39ca8 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -373,7 +373,7 @@ static int list(int argc, const char **argv, const char *prefix) git_notes_list_usage, 0); if (1 < argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(git_notes_list_usage, options); } @@ -428,7 +428,7 @@ static int add(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_ARGV0); if (2 < argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(git_notes_add_usage, options); } @@ -506,7 +506,7 @@ static int copy(int argc, const char **argv, const char *prefix) if (from_stdin || rewrite_cmd) { if (argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(git_notes_copy_usage, options); } else { return notes_copy_from_stdin(force, rewrite_cmd); @@ -514,11 +514,11 @@ static int copy(int argc, const char **argv, const char *prefix) } if (argc < 1) { - error(_("too few parameters")); + error(_("too few arguments")); usage_with_options(git_notes_copy_usage, options); } if (2 < argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(git_notes_copy_usage, options); } @@ -595,7 +595,7 @@ static int append_edit(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_ARGV0); if (2 < argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(usage, options); } @@ -662,7 +662,7 @@ static int show(int argc, const char **argv, const char *prefix) 0); if (1 < argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(git_notes_show_usage, options); } @@ -730,7 +730,7 @@ static int merge_commit(struct notes_merge_options *o) else oidclr(&parent_oid); - t = xcalloc(1, sizeof(struct notes_tree)); + CALLOC_ARRAY(t, 1); init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0); o->local_ref = local_ref_to_free = @@ -812,7 +812,7 @@ static int merge(int argc, const char **argv, const char *prefix) error(_("must specify a notes ref to merge")); usage_with_options(git_notes_merge_usage, options); } else if (!do_merge && argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(git_notes_merge_usage, options); } @@ -960,7 +960,7 @@ static int prune(int argc, const char **argv, const char *prefix) 0); if (argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(git_notes_prune_usage, options); } @@ -982,7 +982,7 @@ static int get_ref(int argc, const char **argv, const char *prefix) git_notes_get_ref_usage, 0); if (argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(git_notes_get_ref_usage, options); } diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 13cde5896a..525c2d8552 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -815,8 +815,8 @@ static struct reused_chunk { /* The offset of the first object of this chunk in the original * packfile. */ off_t original; - /* The offset of the first object of this chunk in the generated - * packfile minus "original". */ + /* The difference for "original" minus the offset of the first object of + * this chunk in the generated packfile. */ off_t difference; } *reused_chunks; static int reused_chunks_nr; @@ -1188,7 +1188,8 @@ static int have_duplicate_entry(const struct object_id *oid, return 1; } -static int want_found_object(int exclude, struct packed_git *p) +static int want_found_object(const struct object_id *oid, int exclude, + struct packed_git *p) { if (exclude) return 1; @@ -1204,27 +1205,82 @@ static int want_found_object(int exclude, struct packed_git *p) * make sure no copy of this object appears in _any_ pack that makes us * to omit the object, so we need to check all the packs. * - * We can however first check whether these options can possible matter; + * We can however first check whether these options can possibly matter; * if they do not matter we know we want the object in generated pack. * Otherwise, we signal "-1" at the end to tell the caller that we do * not know either way, and it needs to check more packs. */ - if (!ignore_packed_keep_on_disk && - !ignore_packed_keep_in_core && - (!local || !have_non_local_packs)) - return 1; + /* + * Objects in packs borrowed from elsewhere are discarded regardless of + * if they appear in other packs that weren't borrowed. + */ if (local && !p->pack_local) return 0; - if (p->pack_local && - ((ignore_packed_keep_on_disk && p->pack_keep) || - (ignore_packed_keep_in_core && p->pack_keep_in_core))) - return 0; + + /* + * Then handle .keep first, as we have a fast(er) path there. + */ + if (ignore_packed_keep_on_disk || ignore_packed_keep_in_core) { + /* + * Set the flags for the kept-pack cache to be the ones we want + * to ignore. + * + * That is, if we are ignoring objects in on-disk keep packs, + * then we want to search through the on-disk keep and ignore + * the in-core ones. + */ + unsigned flags = 0; + if (ignore_packed_keep_on_disk) + flags |= ON_DISK_KEEP_PACKS; + if (ignore_packed_keep_in_core) + flags |= IN_CORE_KEEP_PACKS; + + if (ignore_packed_keep_on_disk && p->pack_keep) + return 0; + if (ignore_packed_keep_in_core && p->pack_keep_in_core) + return 0; + if (has_object_kept_pack(oid, flags)) + return 0; + } + + /* + * At this point we know definitively that either we don't care about + * keep-packs, or the object is not in one. Keep checking other + * conditions... + */ + if (!local || !have_non_local_packs) + return 1; /* we don't know yet; keep looking for more packs */ return -1; } +static int want_object_in_pack_one(struct packed_git *p, + const struct object_id *oid, + int exclude, + struct packed_git **found_pack, + off_t *found_offset) +{ + off_t offset; + + if (p == *found_pack) + offset = *found_offset; + else + offset = find_pack_entry_one(oid->hash, p); + + if (offset) { + if (!*found_pack) { + if (!is_pack_valid(p)) + return -1; + *found_offset = offset; + *found_pack = p; + } + return want_found_object(oid, exclude, p); + } + return -1; +} + /* * Check whether we want the object in the pack (e.g., we do not want * objects found in non-local stores if the "--local" option was used). @@ -1252,7 +1308,7 @@ static int want_object_in_pack(const struct object_id *oid, * are present we will determine the answer right now. */ if (*found_pack) { - want = want_found_object(exclude, *found_pack); + want = want_found_object(oid, exclude, *found_pack); if (want != -1) return want; } @@ -1260,51 +1316,20 @@ static int want_object_in_pack(const struct object_id *oid, for (m = get_multi_pack_index(the_repository); m; m = m->next) { struct pack_entry e; if (fill_midx_entry(the_repository, oid, &e, m)) { - struct packed_git *p = e.p; - off_t offset; - - if (p == *found_pack) - offset = *found_offset; - else - offset = find_pack_entry_one(oid->hash, p); - - if (offset) { - if (!*found_pack) { - if (!is_pack_valid(p)) - continue; - *found_offset = offset; - *found_pack = p; - } - want = want_found_object(exclude, p); - if (want != -1) - return want; - } + want = want_object_in_pack_one(e.p, oid, exclude, found_pack, found_offset); + if (want != -1) + return want; } } list_for_each(pos, get_packed_git_mru(the_repository)) { struct packed_git *p = list_entry(pos, struct packed_git, mru); - off_t offset; - - if (p == *found_pack) - offset = *found_offset; - else - offset = find_pack_entry_one(oid->hash, p); - - if (offset) { - if (!*found_pack) { - if (!is_pack_valid(p)) - continue; - *found_offset = offset; - *found_pack = p; - } - want = want_found_object(exclude, p); - if (!exclude && want > 0) - list_move(&p->mru, - get_packed_git_mru(the_repository)); - if (want != -1) - return want; - } + want = want_object_in_pack_one(p, oid, exclude, found_pack, found_offset); + if (!exclude && want > 0) + list_move(&p->mru, + get_packed_git_mru(the_repository)); + if (want != -1) + return want; } if (uri_protocols.nr) { @@ -1635,7 +1660,7 @@ static void add_preferred_base(struct object_id *oid) } } - it = xcalloc(1, sizeof(*it)); + CALLOC_ARRAY(it, 1); it->next = pbase_tree; pbase_tree = it; @@ -2096,7 +2121,7 @@ static void get_object_details(void) progress_state = start_progress(_("Counting objects"), to_pack.nr_objects); - sorted_by_offset = xcalloc(to_pack.nr_objects, sizeof(struct object_entry *)); + CALLOC_ARRAY(sorted_by_offset, to_pack.nr_objects); for (i = 0; i < to_pack.nr_objects; i++) sorted_by_offset[i] = to_pack.objects + i; QSORT(sorted_by_offset, to_pack.nr_objects, pack_offset_sort); @@ -2428,7 +2453,7 @@ static void find_deltas(struct object_entry **list, unsigned *list_size, struct unpacked *array; unsigned long mem_usage = 0; - array = xcalloc(window, sizeof(struct unpacked)); + CALLOC_ARRAY(array, window); for (;;) { struct object_entry *entry; @@ -2665,7 +2690,7 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, if (progress > pack_to_stdout) fprintf_ln(stderr, _("Delta compression using up to %d threads"), delta_search_threads); - p = xcalloc(delta_search_threads, sizeof(*p)); + CALLOC_ARRAY(p, delta_search_threads); /* Partition the work amongst work threads. */ for (i = 0; i < delta_search_threads; i++) { @@ -2953,6 +2978,13 @@ static int git_pack_config(const char *k, const char *v, void *cb) pack_idx_opts.version); return 0; } + if (!strcmp(k, "pack.writereverseindex")) { + if (git_config_bool(k, v)) + pack_idx_opts.flags |= WRITE_REV; + else + pack_idx_opts.flags &= ~WRITE_REV; + return 0; + } if (!strcmp(k, "uploadpack.blobpackfileuri")) { struct configured_exclusion *ex = xmalloc(sizeof(*ex)); const char *oid_end, *pack_end; @@ -2979,6 +3011,191 @@ static int git_pack_config(const char *k, const char *v, void *cb) return git_default_config(k, v, cb); } +/* Counters for trace2 output when in --stdin-packs mode. */ +static int stdin_packs_found_nr; +static int stdin_packs_hints_nr; + +static int add_object_entry_from_pack(const struct object_id *oid, + struct packed_git *p, + uint32_t pos, + void *_data) +{ + struct rev_info *revs = _data; + struct object_info oi = OBJECT_INFO_INIT; + off_t ofs; + enum object_type type; + + display_progress(progress_state, ++nr_seen); + + 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; + + oi.typep = &type; + if (packed_object_info(the_repository, p, ofs, &oi) < 0) + die(_("could not get type of object %s in pack %s"), + oid_to_hex(oid), p->pack_name); + else if (type == OBJ_COMMIT) { + /* + * commits in included packs are used as starting points for the + * subsequent revision walk + */ + add_pending_oid(revs, NULL, oid, 0); + } + + stdin_packs_found_nr++; + + create_object_entry(oid, type, 0, 0, 0, p, ofs); + + return 0; +} + +static void show_commit_pack_hint(struct commit *commit, void *_data) +{ + /* nothing to do; commits don't have a namehash */ +} + +static void show_object_pack_hint(struct object *object, const char *name, + void *_data) +{ + struct object_entry *oe = packlist_find(&to_pack, &object->oid); + if (!oe) + return; + + /* + * Our 'to_pack' list was constructed by iterating all objects packed in + * included packs, and so doesn't have a non-zero hash field that you + * would typically pick up during a reachability traversal. + * + * Make a best-effort attempt to fill in the ->hash and ->no_try_delta + * here using a now in order to perhaps improve the delta selection + * process. + */ + oe->hash = pack_name_hash(name); + oe->no_try_delta = name && no_try_delta(name); + + stdin_packs_hints_nr++; +} + +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; + + /* + * order packs by descending mtime so that objects are laid out + * roughly as newest-to-oldest + */ + if (a->mtime < b->mtime) + return 1; + else if (b->mtime < a->mtime) + return -1; + else + return 0; +} + +static void read_packs_list_from_stdin(void) +{ + struct strbuf buf = STRBUF_INIT; + struct string_list include_packs = STRING_LIST_INIT_DUP; + struct string_list exclude_packs = STRING_LIST_INIT_DUP; + struct string_list_item *item = NULL; + + struct packed_git *p; + struct rev_info revs; + + repo_init_revisions(the_repository, &revs, NULL); + /* + * Use a revision walk to fill in the namehash of objects in the include + * packs. To save time, we'll avoid traversing through objects that are + * in excluded packs. + * + * That may cause us to avoid populating all of the namehash fields of + * all included objects, but our goal is best-effort, since this is only + * an optimization during delta selection. + */ + revs.no_kept_objects = 1; + revs.keep_pack_cache_flags |= IN_CORE_KEEP_PACKS; + revs.blob_objects = 1; + revs.tree_objects = 1; + revs.tag_objects = 1; + revs.ignore_missing_links = 1; + + while (strbuf_getline(&buf, stdin) != EOF) { + if (!buf.len) + continue; + + if (*buf.buf == '^') + string_list_append(&exclude_packs, buf.buf + 1); + else + string_list_append(&include_packs, buf.buf); + + strbuf_reset(&buf); + } + + string_list_sort(&include_packs); + string_list_sort(&exclude_packs); + + for (p = get_all_packs(the_repository); p; p = p->next) { + const char *pack_name = pack_basename(p); + + item = string_list_lookup(&include_packs, pack_name); + if (!item) + item = string_list_lookup(&exclude_packs, pack_name); + + if (item) + item->util = p; + } + + /* + * First 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; + } + + /* + * 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); + + for_each_string_list_item(item, &include_packs) { + struct packed_git *p = item->util; + if (!p) + die(_("could not find pack '%s'"), item->string); + for_each_object_in_pack(p, + add_object_entry_from_pack, + &revs, + FOR_EACH_OBJECT_PACK_ORDER); + } + + if (prepare_revision_walk(&revs)) + die(_("revision walk setup failed")); + traverse_commit_list(&revs, + show_commit_pack_hint, + show_object_pack_hint, + NULL); + + 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); + + strbuf_release(&buf); + string_list_clear(&include_packs, 0); + string_list_clear(&exclude_packs, 0); +} + static void read_object_list_from_stdin(void) { char line[GIT_MAX_HEXSZ + 1 + PATH_MAX + 2]; @@ -3482,6 +3699,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) struct strvec rp = STRVEC_INIT; int rev_list_unpacked = 0, rev_list_all = 0, rev_list_reflog = 0; int rev_list_index = 0; + int stdin_packs = 0; struct string_list keep_pack_list = STRING_LIST_INIT_NODUP; struct option pack_objects_options[] = { OPT_SET_INT('q', "quiet", &progress, @@ -3532,6 +3750,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) OPT_SET_INT_F(0, "indexed-objects", &rev_list_index, N_("include objects referred to by the index"), 1, PARSE_OPT_NONEG), + 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, @@ -3592,6 +3812,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) reset_pack_idx_option(&pack_idx_opts); git_config(git_pack_config, NULL); + if (git_env_bool(GIT_TEST_WRITE_REV_INDEX, 0)) + pack_idx_opts.flags |= WRITE_REV; progress = isatty(2); argc = parse_options(argc, argv, prefix, pack_objects_options, @@ -3636,7 +3858,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) use_internal_rev_list = 1; strvec_push(&rp, "--indexed-objects"); } - if (rev_list_unpacked) { + if (rev_list_unpacked && !stdin_packs) { use_internal_rev_list = 1; strvec_push(&rp, "--unpacked"); } @@ -3681,8 +3903,13 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (filter_options.choice) { if (!pack_to_stdout) die(_("cannot use --filter without --stdout")); + if (stdin_packs) + die(_("cannot use --filter with --stdin-packs")); } + if (stdin_packs && use_internal_rev_list) + die(_("cannot use internal rev list with --stdin-packs")); + /* * "soft" reasons not to use bitmaps - for on-disk repack by default we want * @@ -3741,7 +3968,13 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (progress) progress_state = start_progress(_("Enumerating objects"), 0); - if (!use_internal_rev_list) + if (stdin_packs) { + /* avoids adding objects in excluded packs */ + ignore_packed_keep_in_core = 1; + read_packs_list_from_stdin(); + if (rev_list_unpacked) + add_unreachable_loose_objects(); + } else if (!use_internal_rev_list) read_object_list_from_stdin(); else { get_object_list(rp.nr, rp.v); diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c index 6e115a811a..7102996c75 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c @@ -373,7 +373,7 @@ static void sort_pack_list(struct pack_list **pl) return; /* prepare an array of packed_list for easier sorting */ - ary = xcalloc(n, sizeof(struct pack_list *)); + CALLOC_ARRAY(ary, n); for (n = 0, p = *pl; p; p = p->next) ary[n++] = p; diff --git a/builtin/push.c b/builtin/push.c index 03adb58602..194967ed79 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -115,7 +115,7 @@ static void set_refspecs(const char **refs, int nr, const char *repo) else refspec_appendf(&rs, "refs/tags/%s", ref); } else if (deleterefs) { - if (strchr(ref, ':')) + if (strchr(ref, ':') || !*ref) die(_("--delete only accepts plain target ref names")); refspec_appendf(&rs, ":%s", ref); } else if (!strchr(ref, ':')) { diff --git a/builtin/range-diff.c b/builtin/range-diff.c index 24c4162f74..50318849d6 100644 --- a/builtin/range-diff.c +++ b/builtin/range-diff.c @@ -3,6 +3,7 @@ #include "parse-options.h" #include "range-diff.h" #include "config.h" +#include "revision.h" static const char * const builtin_range_diff_usage[] = { N_("git range-diff [] .. .."), @@ -13,18 +14,27 @@ NULL int cmd_range_diff(int argc, const char **argv, const char *prefix) { - int creation_factor = RANGE_DIFF_CREATION_FACTOR_DEFAULT; struct diff_options diffopt = { NULL }; struct strvec other_arg = STRVEC_INIT; - int simple_color = -1; + struct range_diff_options range_diff_opts = { + .creation_factor = RANGE_DIFF_CREATION_FACTOR_DEFAULT, + .diffopt = &diffopt, + .other_arg = &other_arg + }; + int simple_color = -1, left_only = 0, right_only = 0; struct option range_diff_options[] = { - OPT_INTEGER(0, "creation-factor", &creation_factor, - N_("Percentage by which creation is weighted")), + OPT_INTEGER(0, "creation-factor", + &range_diff_opts.creation_factor, + N_("percentage by which creation is weighted")), OPT_BOOL(0, "no-dual-color", &simple_color, N_("use simple diff colors")), OPT_PASSTHRU_ARGV(0, "notes", &other_arg, N_("notes"), N_("passed to 'git log'"), PARSE_OPT_OPTARG), + OPT_BOOL(0, "left-only", &left_only, + N_("only emit output related to the first range")), + OPT_BOOL(0, "right-only", &right_only, + N_("only emit output related to the second range")), OPT_END() }; struct option *options; @@ -46,12 +56,12 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix) diffopt.use_color = 1; if (argc == 2) { - if (!strstr(argv[0], "..")) - die(_("no .. in range: '%s'"), argv[0]); + if (!is_range_diff_range(argv[0])) + die(_("not a commit range: '%s'"), argv[0]); strbuf_addstr(&range1, argv[0]); - if (!strstr(argv[1], "..")) - die(_("no .. in range: '%s'"), argv[1]); + if (!is_range_diff_range(argv[1])) + die(_("not a commit range: '%s'"), argv[1]); strbuf_addstr(&range2, argv[1]); } else if (argc == 3) { strbuf_addf(&range1, "%s..%s", argv[0], argv[1]); @@ -81,8 +91,10 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix) } FREE_AND_NULL(options); - res = show_range_diff(range1.buf, range2.buf, creation_factor, - simple_color < 1, &diffopt, &other_arg); + range_diff_opts.dual_color = simple_color < 1; + range_diff_opts.left_only = left_only; + range_diff_opts.right_only = right_only; + res = show_range_diff(range1.buf, range2.buf, &range_diff_opts); strvec_clear(&other_arg); strbuf_release(&range1); diff --git a/builtin/rebase.c b/builtin/rebase.c index 840dbd7eb7..783b526f6e 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -100,8 +100,8 @@ struct rebase_options { char *strategy, *strategy_opts; struct strbuf git_format_patch_opt; int reschedule_failed_exec; - int use_legacy_rebase; int reapply_cherry_picks; + int fork_point; }; #define REBASE_OPTIONS_INIT { \ @@ -111,7 +111,8 @@ struct rebase_options { .default_backend = "merge", \ .flags = REBASE_NO_QUIET, \ .git_am_opts = STRVEC_INIT, \ - .git_format_patch_opt = STRBUF_INIT \ + .git_format_patch_opt = STRBUF_INIT, \ + .fork_point = -1, \ } static struct replay_opts get_replay_opts(const struct rebase_options *opts) @@ -1095,8 +1096,8 @@ static int rebase_config(const char *var, const char *value, void *data) return 0; } - if (!strcmp(var, "rebase.usebuiltin")) { - opts->use_legacy_rebase = !git_config_bool(var, value); + if (!strcmp(var, "rebase.forkpoint")) { + opts->fork_point = git_config_bool(var, value) ? -1 : 0; return 0; } @@ -1306,7 +1307,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) const char *gpg_sign = NULL; struct string_list exec = STRING_LIST_INIT_NODUP; const char *rebase_merges = NULL; - int fork_point = -1; struct string_list strategy_options = STRING_LIST_INIT_NODUP; struct object_id squash_onto; char *squash_onto_name = NULL; @@ -1406,7 +1406,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("mode"), N_("try to rebase merges instead of skipping them"), PARSE_OPT_OPTARG, NULL, (intptr_t)""}, - OPT_BOOL(0, "fork-point", &fork_point, + OPT_BOOL(0, "fork-point", &options.fork_point, N_("use 'merge-base --fork-point' to refine upstream")), OPT_STRING('s', "strategy", &options.strategy, N_("strategy"), N_("use the given merge strategy")), @@ -1435,11 +1435,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) gpg_sign = options.gpg_sign_opt ? "" : NULL; FREE_AND_NULL(options.gpg_sign_opt); - if (options.use_legacy_rebase || - !git_env_bool("GIT_TEST_REBASE_USE_BUILTIN", -1)) - warning(_("the rebase.useBuiltin support has been removed!\n" - "See its entry in 'git help config' for details.")); - strbuf_reset(&buf); strbuf_addf(&buf, "%s/applying", apply_dir()); if(file_exists(buf.buf)) @@ -1494,7 +1489,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die(_("cannot combine '--keep-base' with '--root'")); } - if (options.root && fork_point > 0) + if (options.root && options.fork_point > 0) die(_("cannot combine '--root' with '--fork-point'")); if (action != ACTION_NONE && !in_progress) @@ -1840,8 +1835,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) NULL); if (!options.upstream_name) error_on_missing_default_upstream(); - if (fork_point < 0) - fork_point = 1; + if (options.fork_point < 0) + options.fork_point = 1; } else { options.upstream_name = argv[0]; argc--; @@ -1945,7 +1940,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } else BUG("unexpected number of arguments left to parse"); - if (fork_point > 0) { + if (options.fork_point > 0) { struct commit *head = lookup_commit_reference(the_repository, &options.orig_head); diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index d49d050e6e..6bc12c828a 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -358,7 +358,7 @@ static void proc_receive_ref_append(const char *prefix) char *p; int len; - ref_pattern = xcalloc(1, sizeof(struct proc_receive_ref)); + CALLOC_ARRAY(ref_pattern, 1); p = strchr(prefix, ':'); if (p) { while (prefix < p) { @@ -764,7 +764,7 @@ static void prepare_push_cert_sha1(struct child_process *proc) memset(&sigcheck, '\0', sizeof(sigcheck)); - bogs = parse_signature(push_cert.buf, push_cert.len); + bogs = parse_signed_buffer(push_cert.buf, push_cert.len); check_signature(push_cert.buf, bogs, push_cert.buf + bogs, push_cert.len - bogs, &sigcheck); @@ -1024,7 +1024,7 @@ static int read_proc_receive_report(struct packet_reader *reader, } if (new_report) { if (!hint->report) { - hint->report = xcalloc(1, sizeof(struct ref_push_report)); + CALLOC_ARRAY(hint->report, 1); report = hint->report; } else { report = hint->report; @@ -2050,7 +2050,7 @@ static void queue_commands_from_cert(struct command **tail, die("malformed push certificate %.*s", 100, push_cert->buf); else boc += 2; - eoc = push_cert->buf + parse_signature(push_cert->buf, push_cert->len); + eoc = push_cert->buf + parse_signed_buffer(push_cert->buf, push_cert->len); while (boc < eoc) { const char *eol = memchr(boc, '\n', eoc - boc); @@ -2275,7 +2275,7 @@ static const char *unpack(int err_fd, struct shallow_info *si) status = start_command(&child); if (status) return "index-pack fork failed"; - pack_lockfile = index_pack_lockfile(child.out); + pack_lockfile = index_pack_lockfile(child.out, NULL); close(child.out); status = finish_command(&child); if (status) @@ -2313,11 +2313,9 @@ static void prepare_shallow_update(struct shallow_info *si) ALLOC_ARRAY(si->used_shallow, si->shallow->nr); assign_shallow_commits_to_refs(si, si->used_shallow, NULL); - si->need_reachability_test = - xcalloc(si->shallow->nr, sizeof(*si->need_reachability_test)); - si->reachable = - xcalloc(si->shallow->nr, sizeof(*si->reachable)); - si->shallow_ref = xcalloc(si->ref->nr, sizeof(*si->shallow_ref)); + CALLOC_ARRAY(si->need_reachability_test, si->shallow->nr); + CALLOC_ARRAY(si->reachable, si->shallow->nr); + CALLOC_ARRAY(si->shallow_ref, si->ref->nr); for (i = 0; i < si->nr_ours; i++) si->need_reachability_test[si->ours[i]] = 1; diff --git a/builtin/reflog.c b/builtin/reflog.c index ca1d8079f3..09541d1c80 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -602,6 +602,9 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) */ if (cb.cmd.stalefix) { repo_init_revisions(the_repository, &cb.cmd.revs, prefix); + cb.cmd.revs.do_not_die_on_missing_tree = 1; + cb.cmd.revs.ignore_missing = 1; + cb.cmd.revs.ignore_missing_links = 1; if (flags & EXPIRE_REFLOGS_VERBOSE) printf(_("Marking reachable objects...")); mark_reachable_objects(&cb.cmd.revs, 0, 0, NULL); diff --git a/builtin/remote.c b/builtin/remote.c index d11a5589e4..7f88e6ce9d 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -221,7 +221,7 @@ static int add(int argc, const char **argv) if (fetch_tags != TAGS_DEFAULT) { strbuf_reset(&buf); - strbuf_addf(&buf, "remote.%s.tagopt", name); + strbuf_addf(&buf, "remote.%s.tagOpt", name); git_config_set(buf.buf, fetch_tags == TAGS_SET ? "--tags" : "--no-tags"); } @@ -746,7 +746,7 @@ static int mv(int argc, const char **argv) } if (info->push_remote_name && !strcmp(info->push_remote_name, rename.old_name)) { strbuf_reset(&buf); - strbuf_addf(&buf, "branch.%s.pushremote", item->string); + strbuf_addf(&buf, "branch.%s.pushRemote", item->string); git_config_set(buf.buf, rename.new_name); } } @@ -938,9 +938,6 @@ static int get_remote_ref_states(const char *name, struct ref_states *states, int query) { - struct transport *transport; - const struct ref *remote_refs; - states->remote = remote_get(name); if (!states->remote) return error(_("No such remote: '%s'"), name); @@ -948,10 +945,12 @@ static int get_remote_ref_states(const char *name, read_branches(); if (query) { + struct transport *transport; + const struct ref *remote_refs; + transport = transport_get(states->remote, states->remote->url_nr > 0 ? states->remote->url[0] : NULL); remote_refs = transport_get_remote_refs(transport, NULL); - transport_disconnect(transport); states->queried = 1; if (query & GET_REF_STATES) @@ -960,6 +959,7 @@ static int get_remote_ref_states(const char *name, get_head_names(remote_refs, states); if (query & GET_PUSH_REF_STATES) get_push_ref_states(remote_refs, states); + transport_disconnect(transport); } else { for_each_ref(append_ref_to_tracked_list, states); string_list_sort(&states->tracked); diff --git a/builtin/repack.c b/builtin/repack.c index 2158b48f4c..2847fdfbab 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -209,6 +209,7 @@ static struct { } exts[] = { {".pack"}, {".idx"}, + {".rev", 1}, {".bitmap", 1}, {".promisor", 1}, }; @@ -296,6 +297,142 @@ static void repack_promisor_objects(const struct pack_objects_args *args, #define ALL_INTO_ONE 1 #define LOOSEN_UNREACHABLE 2 +struct pack_geometry { + struct packed_git **pack; + uint32_t pack_nr, pack_alloc; + uint32_t split; +}; + +static uint32_t geometry_pack_weight(struct packed_git *p) +{ + if (open_pack_index(p)) + die(_("cannot open index for %s"), p->pack_name); + return p->num_objects; +} + +static int geometry_cmp(const void *va, const void *vb) +{ + uint32_t aw = geometry_pack_weight(*(struct packed_git **)va), + bw = geometry_pack_weight(*(struct packed_git **)vb); + + if (aw < bw) + return -1; + if (aw > bw) + return 1; + return 0; +} + +static void init_pack_geometry(struct pack_geometry **geometry_p) +{ + struct packed_git *p; + struct pack_geometry *geometry; + + *geometry_p = xcalloc(1, sizeof(struct pack_geometry)); + geometry = *geometry_p; + + for (p = get_all_packs(the_repository); p; p = p->next) { + if (!pack_kept_objects && p->pack_keep) + continue; + + ALLOC_GROW(geometry->pack, + geometry->pack_nr + 1, + geometry->pack_alloc); + + geometry->pack[geometry->pack_nr] = p; + geometry->pack_nr++; + } + + QSORT(geometry->pack, geometry->pack_nr, geometry_cmp); +} + +static void split_pack_geometry(struct pack_geometry *geometry, int factor) +{ + uint32_t i; + uint32_t split; + off_t total_size = 0; + + if (!geometry->pack_nr) { + geometry->split = geometry->pack_nr; + return; + } + + /* + * First, count the number of packs (in descending order of size) which + * already form a geometric progression. + */ + for (i = geometry->pack_nr - 1; i > 0; i--) { + struct packed_git *ours = geometry->pack[i]; + struct packed_git *prev = geometry->pack[i - 1]; + + if (unsigned_mult_overflows(factor, geometry_pack_weight(prev))) + die(_("pack %s too large to consider in geometric " + "progression"), + prev->pack_name); + + if (geometry_pack_weight(ours) < factor * geometry_pack_weight(prev)) + break; + } + + split = i; + + if (split) { + /* + * Move the split one to the right, since the top element in the + * last-compared pair can't be in the progression. Only do this + * when we split in the middle of the array (otherwise if we got + * to the end, then the split is in the right place). + */ + split++; + } + + /* + * Then, anything to the left of 'split' must be in a new pack. But, + * creating that new pack may cause packs in the heavy half to no longer + * form a geometric progression. + * + * Compute an expected size of the new pack, and then determine how many + * packs in the heavy half need to be joined into it (if any) to restore + * the geometric progression. + */ + for (i = 0; i < split; i++) { + struct packed_git *p = geometry->pack[i]; + + if (unsigned_add_overflows(total_size, geometry_pack_weight(p))) + die(_("pack %s too large to roll up"), p->pack_name); + total_size += geometry_pack_weight(p); + } + for (i = split; i < geometry->pack_nr; i++) { + struct packed_git *ours = geometry->pack[i]; + + if (unsigned_mult_overflows(factor, total_size)) + die(_("pack %s too large to roll up"), ours->pack_name); + + if (geometry_pack_weight(ours) < factor * total_size) { + if (unsigned_add_overflows(total_size, + geometry_pack_weight(ours))) + die(_("pack %s too large to roll up"), + ours->pack_name); + + split++; + total_size += geometry_pack_weight(ours); + } else + break; + } + + geometry->split = split; +} + +static void clear_pack_geometry(struct pack_geometry *geometry) +{ + if (!geometry) + return; + + free(geometry->pack); + geometry->pack_nr = 0; + geometry->pack_alloc = 0; + geometry->split = 0; +} + int cmd_repack(int argc, const char **argv, const char *prefix) { struct child_process cmd = CHILD_PROCESS_INIT; @@ -303,6 +440,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) struct string_list names = STRING_LIST_INIT_DUP; struct string_list rollback = STRING_LIST_INIT_NODUP; struct string_list existing_packs = STRING_LIST_INIT_DUP; + struct pack_geometry *geometry = NULL; struct strbuf line = STRBUF_INIT; int i, ext, ret; FILE *out; @@ -315,6 +453,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) struct string_list keep_pack_list = STRING_LIST_INIT_NODUP; int no_update_server_info = 0; struct pack_objects_args po_args = {NULL}; + int geometric_factor = 0; struct option builtin_repack_options[] = { OPT_BIT('a', NULL, &pack_everything, @@ -355,6 +494,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) N_("repack objects in packs marked with .keep")), OPT_STRING_LIST(0, "keep-pack", &keep_pack_list, N_("name"), N_("do not repack this pack")), + OPT_INTEGER('g', "geometric", &geometric_factor, + N_("find a geometric progression with factor ")), OPT_END() }; @@ -381,6 +522,13 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (write_bitmaps && !(pack_everything & ALL_INTO_ONE)) die(_(incremental_bitmap_conflict_error)); + if (geometric_factor) { + if (pack_everything) + die(_("--geometric is incompatible with -A, -a")); + init_pack_geometry(&geometry); + split_pack_geometry(geometry, geometric_factor); + } + packdir = mkpathdup("%s/pack", get_object_directory()); packtmp = mkpathdup("%s/.tmp-%d-pack", packdir, (int)getpid()); @@ -395,9 +543,21 @@ int cmd_repack(int argc, const char **argv, const char *prefix) strvec_pushf(&cmd.args, "--keep-pack=%s", keep_pack_list.items[i].string); strvec_push(&cmd.args, "--non-empty"); - strvec_push(&cmd.args, "--all"); - strvec_push(&cmd.args, "--reflog"); - strvec_push(&cmd.args, "--indexed-objects"); + if (!geometry) { + /* + * We need to grab all reachable objects, including those that + * are reachable from reflogs and the index. + * + * When repacking into a geometric progression of packs, + * however, we ask 'git pack-objects --stdin-packs', and it is + * not about packing objects based on reachability but about + * repacking all the objects in specified packs and loose ones + * (indeed, --stdin-packs is incompatible with these options). + */ + strvec_push(&cmd.args, "--all"); + strvec_push(&cmd.args, "--reflog"); + strvec_push(&cmd.args, "--indexed-objects"); + } if (has_promisor_remote()) strvec_push(&cmd.args, "--exclude-promisor-objects"); if (write_bitmaps > 0) @@ -428,17 +588,37 @@ int cmd_repack(int argc, const char **argv, const char *prefix) strvec_push(&cmd.env_array, "GIT_REF_PARANOIA=1"); } } + } else if (geometry) { + strvec_push(&cmd.args, "--stdin-packs"); + strvec_push(&cmd.args, "--unpacked"); } else { strvec_push(&cmd.args, "--unpacked"); strvec_push(&cmd.args, "--incremental"); } - cmd.no_stdin = 1; + if (geometry) + cmd.in = -1; + else + cmd.no_stdin = 1; ret = start_command(&cmd); if (ret) return ret; + if (geometry) { + FILE *in = xfdopen(cmd.in, "w"); + /* + * The resulting pack should contain all objects in packs that + * are going to be rolled up, but exclude objects in packs which + * are being left alone. + */ + 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])); + fclose(in); + } + out = xfdopen(cmd.out, "r"); while (strbuf_getline_lf(&line, out) != EOF) { if (line.len != the_hash_algo->hexsz) @@ -506,6 +686,25 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (!string_list_has_string(&names, sha1)) remove_redundant_pack(packdir, item->string); } + + if (geometry) { + struct strbuf buf = STRBUF_INIT; + + uint32_t i; + for (i = 0; i < geometry->split; i++) { + struct packed_git *p = geometry->pack[i]; + if (string_list_has_string(&names, + hash_to_hex(p->hash))) + continue; + + strbuf_reset(&buf); + strbuf_addstr(&buf, pack_basename(p)); + strbuf_strip_suffix(&buf, ".pack"); + + remove_redundant_pack(packdir, buf.buf); + } + strbuf_release(&buf); + } if (!po_args.quiet && isatty(2)) opts |= PRUNE_PACKED_VERBOSE; prune_packed_objects(opts); @@ -522,11 +721,12 @@ int cmd_repack(int argc, const char **argv, const char *prefix) remove_temporary_files(); if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0)) - write_midx_file(get_object_directory(), 0); + write_midx_file(get_object_directory(), NULL, 0); string_list_clear(&names, 0); string_list_clear(&rollback, 0); string_list_clear(&existing_packs, 0); + clear_pack_geometry(geometry); strbuf_release(&line); return 0; diff --git a/builtin/reset.c b/builtin/reset.c index c635b062c3..43e855cb88 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -425,7 +425,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) dwim_ref(rev, strlen(rev), &dummy, &ref, 0); if (ref && !starts_with(ref, "refs/")) - ref = NULL; + FREE_AND_NULL(ref); err = reset_index(ref, &oid, reset_type, quiet); if (reset_type == KEEP && !err) diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 25c6c3b38d..b4d8ea0a35 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -80,6 +80,19 @@ static int arg_show_object_names = 1; #define DEFAULT_OIDSET_SIZE (16*1024) +static int show_disk_usage; +static off_t total_disk_usage; + +static off_t get_object_disk_usage(struct object *obj) +{ + off_t size; + struct object_info oi = OBJECT_INFO_INIT; + oi.disk_sizep = &size; + if (oid_object_info_extended(the_repository, &obj->oid, &oi, 0) < 0) + die(_("unable to get disk usage of %s"), oid_to_hex(&obj->oid)); + return size; +} + static void finish_commit(struct commit *commit); static void show_commit(struct commit *commit, void *data) { @@ -88,6 +101,9 @@ static void show_commit(struct commit *commit, void *data) display_progress(progress, ++progress_counter); + if (show_disk_usage) + total_disk_usage += get_object_disk_usage(&commit->object); + if (info->flags & REV_LIST_QUIET) { finish_commit(commit); return; @@ -258,6 +274,8 @@ static void show_object(struct object *obj, const char *name, void *cb_data) if (finish_object(obj, name, cb_data)) return; display_progress(progress, ++progress_counter); + if (show_disk_usage) + total_disk_usage += get_object_disk_usage(obj); if (info->flags & REV_LIST_QUIET) return; @@ -452,6 +470,23 @@ static int try_bitmap_traversal(struct rev_info *revs, return 0; } +static int try_bitmap_disk_usage(struct rev_info *revs, + struct list_objects_filter_options *filter) +{ + struct bitmap_index *bitmap_git; + + if (!show_disk_usage) + return -1; + + bitmap_git = prepare_bitmap_walk(revs, filter); + if (!bitmap_git) + return -1; + + printf("%"PRIuMAX"\n", + (uintmax_t)get_disk_usage_from_bitmap(bitmap_git, revs)); + return 0; +} + int cmd_rev_list(int argc, const char **argv, const char *prefix) { struct rev_info revs; @@ -584,6 +619,12 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) continue; } + if (!strcmp(arg, "--disk-usage")) { + show_disk_usage = 1; + info.flags |= REV_LIST_QUIET; + continue; + } + usage(rev_list_usage); } @@ -626,6 +667,8 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (use_bitmap_index) { if (!try_bitmap_count(&revs, &filter_options)) return 0; + if (!try_bitmap_disk_usage(&revs, &filter_options)) + return 0; if (!try_bitmap_traversal(&revs, &filter_options)) return 0; } @@ -690,5 +733,8 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) printf("%d\n", revs.count_left + revs.count_right); } + if (show_disk_usage) + printf("%"PRIuMAX"\n", (uintmax_t)total_disk_usage); + return 0; } diff --git a/builtin/revert.c b/builtin/revert.c index 314a86c562..237f2f18d4 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -182,7 +182,7 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts) "--signoff", opts->signoff, "--no-commit", opts->no_commit, "-x", opts->record_origin, - "--edit", opts->edit, + "--edit", opts->edit > 0, NULL); if (cmd) { @@ -230,8 +230,6 @@ int cmd_revert(int argc, const char **argv, const char *prefix) struct replay_opts opts = REPLAY_OPTS_INIT; int res; - if (isatty(0)) - opts.edit = 1; opts.action = REPLAY_REVERT; sequencer_init_config(&opts); res = run_sequencer(argc, argv, &opts); diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index 2306a9ad98..d7da50ada5 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -64,7 +64,7 @@ static int sparse_checkout_list(int argc, const char **argv) pl.use_cone_patterns = core_sparse_checkout_cone; sparse_filename = get_sparse_checkout_filename(); - res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL); + res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL, 0); free(sparse_filename); if (res < 0) { @@ -321,7 +321,7 @@ static int sparse_checkout_init(int argc, const char **argv) memset(&pl, 0, sizeof(pl)); sparse_filename = get_sparse_checkout_filename(); - res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL); + res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL, 0); /* If we already have a sparse-checkout file, use it. */ if (res >= 0) { @@ -483,7 +483,7 @@ static void add_patterns_cone_mode(int argc, const char **argv, existing.use_cone_patterns = core_sparse_checkout_cone; if (add_patterns_from_file_to_list(sparse_filename, "", 0, - &existing, NULL)) + &existing, NULL, 0)) die(_("unable to load existing sparse-checkout patterns")); free(sparse_filename); @@ -507,7 +507,7 @@ static void add_patterns_literal(int argc, const char **argv, { char *sparse_filename = get_sparse_checkout_filename(); if (add_patterns_from_file_to_list(sparse_filename, "", 0, - pl, NULL)) + pl, NULL, 0)) die(_("unable to load existing sparse-checkout patterns")); free(sparse_filename); add_patterns_from_input(pl, argc, argv); diff --git a/builtin/stash.c b/builtin/stash.c index 9bc85f91cd..c56fed3354 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -10,11 +10,13 @@ #include "strvec.h" #include "run-command.h" #include "dir.h" +#include "entry.h" #include "rerere.h" #include "revision.h" #include "log-tree.h" #include "diffcore.h" #include "exec-cmd.h" +#include "entry.h" #define INCLUDE_ALL_FILES 2 @@ -87,7 +89,7 @@ static const char * const git_stash_save_usage[] = { NULL }; -static const char *ref_stash = "refs/stash"; +static const char ref_stash[] = "refs/stash"; static struct strbuf stash_index_path = STRBUF_INIT; /* @@ -222,7 +224,7 @@ static int clear_stash(int argc, const char **argv, const char *prefix) PARSE_OPT_STOP_AT_NON_OPTION); if (argc) - return error(_("git stash clear with parameters is " + return error(_("git stash clear with arguments is " "unimplemented")); return do_clear_stash(); @@ -768,6 +770,7 @@ static int list_stash(int argc, const char **argv, const char *prefix) static int show_stat = 1; static int show_patch; +static int show_include_untracked; static int use_legacy_stash; static int git_stash_config(const char *var, const char *value, void *cb) @@ -780,6 +783,10 @@ static int git_stash_config(const char *var, const char *value, void *cb) show_patch = git_config_bool(var, value); return 0; } + if (!strcmp(var, "stash.showincludeuntracked")) { + show_include_untracked = git_config_bool(var, value); + return 0; + } if (!strcmp(var, "stash.usebuiltin")) { use_legacy_stash = !git_config_bool(var, value); return 0; @@ -787,6 +794,33 @@ static int git_stash_config(const char *var, const char *value, void *cb) return git_diff_basic_config(var, value, cb); } +static void diff_include_untracked(const struct stash_info *info, struct diff_options *diff_opt) +{ + const struct object_id *oid[] = { &info->w_commit, &info->u_tree }; + struct tree *tree[ARRAY_SIZE(oid)]; + struct tree_desc tree_desc[ARRAY_SIZE(oid)]; + struct unpack_trees_options unpack_tree_opt = { 0 }; + int i; + + for (i = 0; i < ARRAY_SIZE(oid); i++) { + tree[i] = parse_tree_indirect(oid[i]); + if (parse_tree(tree[i]) < 0) + die(_("failed to parse tree")); + init_tree_desc(&tree_desc[i], tree[i]->buffer, tree[i]->size); + } + + unpack_tree_opt.head_idx = -1; + unpack_tree_opt.src_index = &the_index; + unpack_tree_opt.dst_index = &the_index; + unpack_tree_opt.merge = 1; + unpack_tree_opt.fn = stash_worktree_untracked_merge; + + if (unpack_trees(ARRAY_SIZE(tree_desc), tree_desc, &unpack_tree_opt)) + die(_("failed to unpack trees")); + + do_diff_cache(&info->b_commit, diff_opt); +} + static int show_stash(int argc, const char **argv, const char *prefix) { int i; @@ -795,7 +829,18 @@ static int show_stash(int argc, const char **argv, const char *prefix) struct rev_info rev; struct strvec stash_args = STRVEC_INIT; struct strvec revision_args = STRVEC_INIT; + enum { + UNTRACKED_NONE, + UNTRACKED_INCLUDE, + UNTRACKED_ONLY + } show_untracked = UNTRACKED_NONE; struct option options[] = { + OPT_SET_INT('u', "include-untracked", &show_untracked, + N_("include untracked files in the stash"), + UNTRACKED_INCLUDE), + OPT_SET_INT_F(0, "only-untracked", &show_untracked, + N_("only show untracked files in the stash"), + UNTRACKED_ONLY, PARSE_OPT_NONEG), OPT_END() }; @@ -803,6 +848,10 @@ static int show_stash(int argc, const char **argv, const char *prefix) git_config(git_diff_ui_config, NULL); init_revisions(&rev, prefix); + argc = parse_options(argc, argv, prefix, options, git_stash_show_usage, + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_KEEP_DASHDASH); + strvec_push(&revision_args, argv[0]); for (i = 1; i < argc; i++) { if (argv[i][0] != '-') @@ -827,6 +876,9 @@ static int show_stash(int argc, const char **argv, const char *prefix) if (show_patch) rev.diffopt.output_format |= DIFF_FORMAT_PATCH; + if (show_include_untracked) + show_untracked = UNTRACKED_INCLUDE; + if (!show_stat && !show_patch) { free_stash_info(&info); return 0; @@ -845,7 +897,17 @@ static int show_stash(int argc, const char **argv, const char *prefix) rev.diffopt.flags.recursive = 1; setup_diff_pager(&rev.diffopt); - diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt); + switch (show_untracked) { + case UNTRACKED_NONE: + diff_tree_oid(&info.b_commit, &info.w_commit, "", &rev.diffopt); + break; + case UNTRACKED_ONLY: + diff_root_tree_oid(&info.u_tree, "", &rev.diffopt); + break; + case UNTRACKED_INCLUDE: + diff_include_untracked(&info, &rev.diffopt); + break; + } log_tree_diff_flush(&rev); free_stash_info(&info); diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index c2bd882d17..9d505a6329 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -1257,7 +1257,7 @@ static int compute_summary_module_list(struct object_id *head_oid, git_config(git_diff_basic_config, NULL); init_revisions(&rev, info->prefix); rev.abbrev = 0; - precompose_argv(diff_args.nr, diff_args.v); + precompose_argv_prefix(diff_args.nr, diff_args.v, NULL); setup_revisions(diff_args.nr, diff_args.v, &rev, NULL); rev.diffopt.output_format = DIFF_FORMAT_NO_OUTPUT | DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = submodule_summary_callback; diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c index 80237f0df1..e547a08d6c 100644 --- a/builtin/symbolic-ref.c +++ b/builtin/symbolic-ref.c @@ -24,9 +24,11 @@ static int check_symref(const char *HEAD, int quiet, int shorten, int print) return 1; } if (print) { + char *to_free = NULL; if (shorten) - refname = shorten_unambiguous_ref(refname, 0); + refname = to_free = shorten_unambiguous_ref(refname, 0); puts(refname); + free(to_free); } return 0; } diff --git a/builtin/tag.c b/builtin/tag.c index e8b85eefd8..d403417b56 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -198,11 +198,17 @@ static void write_tag_body(int fd, const struct object_id *oid) { unsigned long size; enum object_type type; - char *buf, *sp; + char *buf, *sp, *orig; + struct strbuf payload = STRBUF_INIT; + struct strbuf signature = STRBUF_INIT; - buf = read_object_file(oid, &type, &size); + orig = buf = read_object_file(oid, &type, &size); if (!buf) return; + if (parse_signature(buf, size, &payload, &signature)) { + buf = payload.buf; + size = payload.len; + } /* skip header */ sp = strstr(buf, "\n\n"); @@ -211,9 +217,11 @@ static void write_tag_body(int fd, const struct object_id *oid) return; } sp += 2; /* skip the 2 LFs */ - write_or_die(fd, sp, parse_signature(sp, buf + size - sp)); + write_or_die(fd, sp, buf + size - sp); - free(buf); + free(orig); + strbuf_release(&payload); + strbuf_release(&signature); } static int build_tag_object(struct strbuf *buf, int sign, struct object_id *result) @@ -564,7 +572,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) object_ref = argc == 2 ? argv[1] : "HEAD"; if (argc > 2) - die(_("too many params")); + die(_("too many arguments")); if (get_oid(object_ref, &object)) die(_("Failed to resolve '%s' as a valid ref."), object_ref); diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index dd4a75e030..4a70b17f8f 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -46,7 +46,7 @@ static struct obj_buffer *lookup_object_buffer(struct object *base) static void add_object_buffer(struct object *object, char *buffer, unsigned long size) { struct obj_buffer *obj; - obj = xcalloc(1, sizeof(struct obj_buffer)); + CALLOC_ARRAY(obj, 1); obj->buffer = buffer; obj->size = size; if (add_decoration(&obj_decorate, object, obj)) @@ -187,7 +187,8 @@ static void write_cached_object(struct object *obj, struct obj_buffer *obj_buf) * that have reachability requirements and calls this function. * Verify its reachability and validity recursively and write it out. */ -static int check_object(struct object *obj, int type, void *data, struct fsck_options *options) +static int check_object(struct object *obj, enum object_type type, + void *data, struct fsck_options *options) { struct obj_buffer *obj_buf; @@ -500,7 +501,7 @@ static void unpack_all(void) if (!quiet) progress = start_progress(_("Unpacking objects"), nr_objects); - obj_list = xcalloc(nr_objects, sizeof(*obj_list)); + CALLOC_ARRAY(obj_list, nr_objects); for (i = 0; i < nr_objects; i++) { unpack_one(i); display_progress(progress, i + 1); diff --git a/builtin/worktree.c b/builtin/worktree.c index 1cd5c2016e..8771453493 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -446,16 +446,18 @@ static void print_preparing_worktree_line(int detach, static const char *dwim_branch(const char *path, const char **new_branch) { int n; + int branch_exists; const char *s = worktree_basename(path, &n); const char *branchname = xstrndup(s, n); struct strbuf ref = STRBUF_INIT; UNLEAK(branchname); - if (!strbuf_check_branch_ref(&ref, branchname) && - ref_exists(ref.buf)) { - strbuf_release(&ref); + + branch_exists = !strbuf_check_branch_ref(&ref, branchname) && + ref_exists(ref.buf); + strbuf_release(&ref); + if (branch_exists) return branchname; - } *new_branch = branchname; if (guess_remote) { diff --git a/bulk-checkin.c b/bulk-checkin.c index 583aacb9e3..6f3c97cd34 100644 --- a/bulk-checkin.c +++ b/bulk-checkin.c @@ -211,7 +211,7 @@ static int deflate_to_pack(struct bulk_checkin_state *state, /* Note: idx is non-NULL when we are writing */ if ((flags & HASH_WRITE_OBJECT) != 0) - idx = xcalloc(1, sizeof(*idx)); + CALLOC_ARRAY(idx, 1); already_hashed_to = 0; diff --git a/cache-tree.c b/cache-tree.c index 2fb483d3c0..add1f07713 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -564,7 +564,7 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p) * hence +2. */ it->subtree_alloc = subtree_nr + 2; - it->down = xcalloc(it->subtree_alloc, sizeof(struct cache_tree_sub *)); + CALLOC_ARRAY(it->down, it->subtree_alloc); for (i = 0; i < subtree_nr; i++) { /* read each subtree */ struct cache_tree *sub; diff --git a/cache.h b/cache.h index d928149614..148d9ab5f1 100644 --- a/cache.h +++ b/cache.h @@ -803,7 +803,7 @@ static inline int index_pos_to_insert_pos(uintmax_t pos) #define ADD_CACHE_OK_TO_ADD 1 /* Ok to add */ #define ADD_CACHE_OK_TO_REPLACE 2 /* Ok to replace file/directory */ #define ADD_CACHE_SKIP_DFCHECK 4 /* Ok to skip DF conflict checks */ -#define ADD_CACHE_JUST_APPEND 8 /* Append only; tree.c::read_tree() */ +#define ADD_CACHE_JUST_APPEND 8 /* Append only */ #define ADD_CACHE_NEW_ONLY 16 /* Do not replace existing ones */ #define ADD_CACHE_KEEP_CACHE_TREE 32 /* Do not invalidate cache-tree */ #define ADD_CACHE_RENORMALIZE 64 /* Pass along HASH_RENORMALIZE */ @@ -1621,30 +1621,6 @@ const char *show_ident_date(const struct ident_split *id, */ int ident_cmp(const struct ident_split *, const struct ident_split *); -struct checkout { - struct index_state *istate; - const char *base_dir; - int base_dir_len; - struct delayed_checkout *delayed_checkout; - struct checkout_metadata meta; - unsigned force:1, - quiet:1, - not_new:1, - clone:1, - refresh_cache:1; -}; -#define CHECKOUT_INIT { NULL, "" } - -#define TEMPORARY_FILENAME_LENGTH 25 -int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath, int *nr_checkouts); -void enable_delayed_checkout(struct checkout *state); -int finish_delayed_checkout(struct checkout *state, int *nr_checkouts); -/* - * Unlink the last component and schedule the leading directories for - * removal, such that empty directories get removed. - */ -void unlink_entry(const struct cache_entry *ce); - struct cache_def { struct strbuf path; int flags; @@ -1659,8 +1635,9 @@ static inline void cache_def_clear(struct cache_def *cache) int has_symlink_leading_path(const char *name, int len); int threaded_has_symlink_leading_path(struct cache_def *, const char *, int); -int check_leading_path(const char *name, int len); +int check_leading_path(const char *name, int len, int warn_on_lstat_err); int has_dirs_only_path(const char *name, int len, int prefix_len); +void invalidate_lstat_cache(void); void schedule_dir_for_removal(const char *name, int len); void remove_scheduled_dirs(void); diff --git a/chunk-format.c b/chunk-format.c new file mode 100644 index 0000000000..da191e59a2 --- /dev/null +++ b/chunk-format.c @@ -0,0 +1,179 @@ +#include "cache.h" +#include "chunk-format.h" +#include "csum-file.h" + +/* + * When writing a chunk-based file format, collect the chunks in + * an array of chunk_info structs. The size stores the _expected_ + * amount of data that will be written by write_fn. + */ +struct chunk_info { + uint32_t id; + uint64_t size; + chunk_write_fn write_fn; + + const void *start; +}; + +struct chunkfile { + struct hashfile *f; + + struct chunk_info *chunks; + size_t chunks_nr; + size_t chunks_alloc; +}; + +struct chunkfile *init_chunkfile(struct hashfile *f) +{ + struct chunkfile *cf = xcalloc(1, sizeof(*cf)); + cf->f = f; + return cf; +} + +void free_chunkfile(struct chunkfile *cf) +{ + if (!cf) + return; + free(cf->chunks); + free(cf); +} + +int get_num_chunks(struct chunkfile *cf) +{ + return cf->chunks_nr; +} + +void add_chunk(struct chunkfile *cf, + uint32_t id, + size_t size, + chunk_write_fn fn) +{ + ALLOC_GROW(cf->chunks, cf->chunks_nr + 1, cf->chunks_alloc); + + cf->chunks[cf->chunks_nr].id = id; + cf->chunks[cf->chunks_nr].write_fn = fn; + cf->chunks[cf->chunks_nr].size = size; + cf->chunks_nr++; +} + +int write_chunkfile(struct chunkfile *cf, void *data) +{ + int i; + uint64_t cur_offset = hashfile_total(cf->f); + + /* Add the table of contents to the current offset */ + cur_offset += (cf->chunks_nr + 1) * CHUNK_TOC_ENTRY_SIZE; + + for (i = 0; i < cf->chunks_nr; i++) { + hashwrite_be32(cf->f, cf->chunks[i].id); + hashwrite_be64(cf->f, cur_offset); + + cur_offset += cf->chunks[i].size; + } + + /* Trailing entry marks the end of the chunks */ + hashwrite_be32(cf->f, 0); + hashwrite_be64(cf->f, cur_offset); + + for (i = 0; i < cf->chunks_nr; i++) { + off_t start_offset = hashfile_total(cf->f); + int result = cf->chunks[i].write_fn(cf->f, data); + + if (result) + return result; + + if (hashfile_total(cf->f) - start_offset != cf->chunks[i].size) + BUG("expected to write %"PRId64" bytes to chunk %"PRIx32", but wrote %"PRId64" instead", + cf->chunks[i].size, cf->chunks[i].id, + hashfile_total(cf->f) - start_offset); + } + + return 0; +} + +int read_table_of_contents(struct chunkfile *cf, + const unsigned char *mfile, + size_t mfile_size, + uint64_t toc_offset, + int toc_length) +{ + int i; + uint32_t chunk_id; + const unsigned char *table_of_contents = mfile + toc_offset; + + ALLOC_GROW(cf->chunks, toc_length, cf->chunks_alloc); + + while (toc_length--) { + uint64_t chunk_offset, next_chunk_offset; + + chunk_id = get_be32(table_of_contents); + chunk_offset = get_be64(table_of_contents + 4); + + if (!chunk_id) { + error(_("terminating chunk id appears earlier than expected")); + return 1; + } + + table_of_contents += CHUNK_TOC_ENTRY_SIZE; + next_chunk_offset = get_be64(table_of_contents + 4); + + if (next_chunk_offset < chunk_offset || + next_chunk_offset > mfile_size - the_hash_algo->rawsz) { + error(_("improper chunk offset(s) %"PRIx64" and %"PRIx64""), + chunk_offset, next_chunk_offset); + return -1; + } + + for (i = 0; i < cf->chunks_nr; i++) { + if (cf->chunks[i].id == chunk_id) { + error(_("duplicate chunk ID %"PRIx32" found"), + chunk_id); + return -1; + } + } + + cf->chunks[cf->chunks_nr].id = chunk_id; + cf->chunks[cf->chunks_nr].start = mfile + chunk_offset; + cf->chunks[cf->chunks_nr].size = next_chunk_offset - chunk_offset; + cf->chunks_nr++; + } + + chunk_id = get_be32(table_of_contents); + if (chunk_id) { + error(_("final chunk has non-zero id %"PRIx32""), chunk_id); + return -1; + } + + return 0; +} + +static int pair_chunk_fn(const unsigned char *chunk_start, + size_t chunk_size, + void *data) +{ + const unsigned char **p = data; + *p = chunk_start; + return 0; +} + +int pair_chunk(struct chunkfile *cf, + uint32_t chunk_id, + const unsigned char **p) +{ + return read_chunk(cf, chunk_id, pair_chunk_fn, p); +} + +int read_chunk(struct chunkfile *cf, + uint32_t chunk_id, + chunk_read_fn fn, + void *data) +{ + int i; + + for (i = 0; i < cf->chunks_nr; i++) { + if (cf->chunks[i].id == chunk_id) + return fn(cf->chunks[i].start, cf->chunks[i].size, data); + } + + return CHUNK_NOT_FOUND; +} diff --git a/chunk-format.h b/chunk-format.h new file mode 100644 index 0000000000..9ccbe00377 --- /dev/null +++ b/chunk-format.h @@ -0,0 +1,68 @@ +#ifndef CHUNK_FORMAT_H +#define CHUNK_FORMAT_H + +#include "git-compat-util.h" + +struct hashfile; +struct chunkfile; + +#define CHUNK_TOC_ENTRY_SIZE (sizeof(uint32_t) + sizeof(uint64_t)) + +/* + * Initialize a 'struct chunkfile' for writing _or_ reading a file + * with the chunk format. + * + * If writing a file, supply a non-NULL 'struct hashfile *' that will + * be used to write. + * + * If reading a file, use a NULL 'struct hashfile *' and then call + * read_table_of_contents(). Supply the memory-mapped data to the + * pair_chunk() or read_chunk() methods, as appropriate. + * + * DO NOT MIX THESE MODES. Use different 'struct chunkfile' instances + * for reading and writing. + */ +struct chunkfile *init_chunkfile(struct hashfile *f); +void free_chunkfile(struct chunkfile *cf); +int get_num_chunks(struct chunkfile *cf); +typedef int (*chunk_write_fn)(struct hashfile *f, void *data); +void add_chunk(struct chunkfile *cf, + uint32_t id, + size_t size, + chunk_write_fn fn); +int write_chunkfile(struct chunkfile *cf, void *data); + +int read_table_of_contents(struct chunkfile *cf, + const unsigned char *mfile, + size_t mfile_size, + uint64_t toc_offset, + int toc_length); + +#define CHUNK_NOT_FOUND (-2) + +/* + * Find 'chunk_id' in the given chunkfile and assign the + * given pointer to the position in the mmap'd file where + * that chunk begins. + * + * Returns CHUNK_NOT_FOUND if the chunk does not exist. + */ +int pair_chunk(struct chunkfile *cf, + uint32_t chunk_id, + const unsigned char **p); + +typedef int (*chunk_read_fn)(const unsigned char *chunk_start, + size_t chunk_size, void *data); +/* + * Find 'chunk_id' in the given chunkfile and call the + * given chunk_read_fn method with the information for + * that chunk. + * + * Returns CHUNK_NOT_FOUND if the chunk does not exist. + */ +int read_chunk(struct chunkfile *cf, + uint32_t chunk_id, + chunk_read_fn fn, + void *data); + +#endif diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh index 50e0b90073..a66b5e8c75 100755 --- a/ci/run-build-and-tests.sh +++ b/ci/run-build-and-tests.sh @@ -24,6 +24,7 @@ linux-gcc) export GIT_TEST_MULTI_PACK_INDEX=1 export GIT_TEST_ADD_I_USE_BUILTIN=1 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master + export GIT_TEST_WRITE_REV_INDEX=1 make test ;; linux-clang) diff --git a/combine-diff.c b/combine-diff.c index 9228aebc16..06635f91bc 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -214,11 +214,11 @@ static struct lline *coalesce_lines(struct lline *base, int *lenbase, * - Else if we have NEW, insert newend lline into base and * consume newend */ - lcs = xcalloc(st_add(origbaselen, 1), sizeof(int*)); - directions = xcalloc(st_add(origbaselen, 1), sizeof(enum coalesce_direction*)); + CALLOC_ARRAY(lcs, st_add(origbaselen, 1)); + CALLOC_ARRAY(directions, st_add(origbaselen, 1)); for (i = 0; i < origbaselen + 1; i++) { - lcs[i] = xcalloc(st_add(lennew, 1), sizeof(int)); - directions[i] = xcalloc(st_add(lennew, 1), sizeof(enum coalesce_direction)); + CALLOC_ARRAY(lcs[i], st_add(lennew, 1)); + CALLOC_ARRAY(directions[i], st_add(lennew, 1)); directions[i][0] = BASE; } for (j = 1; j < lennew + 1; j++) @@ -398,8 +398,8 @@ static void consume_hunk(void *state_, state->lost_bucket = &state->sline[state->nb-1]; } if (!state->sline[state->nb-1].p_lno) - state->sline[state->nb-1].p_lno = - xcalloc(state->num_parent, sizeof(unsigned long)); + CALLOC_ARRAY(state->sline[state->nb - 1].p_lno, + state->num_parent); state->sline[state->nb-1].p_lno[state->n] = state->ob; } @@ -1159,7 +1159,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, if (result_size && result[result_size-1] != '\n') cnt++; /* incomplete line */ - sline = xcalloc(st_add(cnt, 2), sizeof(*sline)); + CALLOC_ARRAY(sline, st_add(cnt, 2)); sline[0].bol = result; for (lno = 0, cp = result; cp < result + result_size; cp++) { if (*cp == '\n') { @@ -1178,7 +1178,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, /* Even p_lno[cnt+1] is valid -- that is for the end line number * for deletion hunk at the end. */ - sline[0].p_lno = xcalloc(st_mult(st_add(cnt, 2), num_parent), sizeof(unsigned long)); + CALLOC_ARRAY(sline[0].p_lno, st_mult(st_add(cnt, 2), num_parent)); for (lno = 0; lno <= cnt; lno++) sline[lno+1].p_lno = sline[lno].p_lno + num_parent; @@ -1319,7 +1319,7 @@ static struct diff_filepair *combined_pair(struct combine_diff_path *p, struct diff_filespec *pool; pair = xmalloc(sizeof(*pair)); - pool = xcalloc(st_add(num_parent, 1), sizeof(struct diff_filespec)); + CALLOC_ARRAY(pool, st_add(num_parent, 1)); pair->one = pool + 1; pair->two = pool; @@ -1348,7 +1348,7 @@ static void handle_combined_callback(struct diff_options *opt, struct diff_queue_struct q; int i; - q.queue = xcalloc(num_paths, sizeof(struct diff_filepair *)); + CALLOC_ARRAY(q.queue, num_paths); q.alloc = num_paths; q.nr = num_paths; for (i = 0, p = paths; p; p = p->next) diff --git a/commit-graph.c b/commit-graph.c index 6541060271..f18380b922 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -19,6 +19,7 @@ #include "shallow.h" #include "json-writer.h" #include "trace2.h" +#include "chunk-format.h" void git_test_write_commit_graph_or_die(void) { @@ -38,11 +39,12 @@ void git_test_write_commit_graph_or_die(void) #define GRAPH_CHUNKID_OIDFANOUT 0x4f494446 /* "OIDF" */ #define GRAPH_CHUNKID_OIDLOOKUP 0x4f49444c /* "OIDL" */ #define GRAPH_CHUNKID_DATA 0x43444154 /* "CDAT" */ +#define GRAPH_CHUNKID_GENERATION_DATA 0x47444154 /* "GDAT" */ +#define GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW 0x47444f56 /* "GDOV" */ #define GRAPH_CHUNKID_EXTRAEDGES 0x45444745 /* "EDGE" */ #define GRAPH_CHUNKID_BLOOMINDEXES 0x42494458 /* "BIDX" */ #define GRAPH_CHUNKID_BLOOMDATA 0x42444154 /* "BDAT" */ #define GRAPH_CHUNKID_BASE 0x42415345 /* "BASE" */ -#define MAX_NUM_CHUNKS 7 #define GRAPH_DATA_WIDTH (the_hash_algo->rawsz + 16) @@ -57,13 +59,16 @@ void git_test_write_commit_graph_or_die(void) #define GRAPH_HEADER_SIZE 8 #define GRAPH_FANOUT_SIZE (4 * 256) -#define GRAPH_CHUNKLOOKUP_WIDTH 12 -#define GRAPH_MIN_SIZE (GRAPH_HEADER_SIZE + 4 * GRAPH_CHUNKLOOKUP_WIDTH \ +#define GRAPH_MIN_SIZE (GRAPH_HEADER_SIZE + 4 * CHUNK_TOC_ENTRY_SIZE \ + GRAPH_FANOUT_SIZE + the_hash_algo->rawsz) +#define CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW (1ULL << 31) + /* Remember to update object flag allocation in object.h */ #define REACHABLE (1u<<15) +define_commit_slab(topo_level_slab, uint32_t); + /* Keep track of the order in which commits are added to our list. */ define_commit_slab(commit_pos, int); static struct commit_pos commit_pos = COMMIT_SLAB_INIT(1, commit_pos); @@ -91,6 +96,13 @@ define_commit_slab(commit_graph_data_slab, struct commit_graph_data); static struct commit_graph_data_slab commit_graph_data_slab = COMMIT_SLAB_INIT(1, commit_graph_data_slab); +static int get_configured_generation_version(struct repository *r) +{ + int version = 2; + repo_config_get_int(r, "commitgraph.generationversion", &version); + return version; +} + uint32_t commit_graph_position(const struct commit *c) { struct commit_graph_data *data = @@ -99,7 +111,7 @@ uint32_t commit_graph_position(const struct commit *c) return data ? data->graph_pos : COMMIT_NOT_FROM_GRAPH; } -uint32_t commit_graph_generation(const struct commit *c) +timestamp_t commit_graph_generation(const struct commit *c) { struct commit_graph_data *data = commit_graph_data_slab_peek(&commit_graph_data_slab, c); @@ -139,13 +151,17 @@ static struct commit_graph_data *commit_graph_data_at(const struct commit *c) return data; } +/* + * Should be used only while writing commit-graph as it compares + * generation value of commits by directly accessing commit-slab. + */ static int commit_gen_cmp(const void *va, const void *vb) { const struct commit *a = *(const struct commit **)va; const struct commit *b = *(const struct commit **)vb; - uint32_t generation_a = commit_graph_generation(a); - uint32_t generation_b = commit_graph_generation(b); + const timestamp_t generation_a = commit_graph_data_at(a)->generation; + const timestamp_t generation_b = commit_graph_data_at(b)->generation; /* lower generation commits first */ if (generation_a < generation_b) return -1; @@ -288,15 +304,43 @@ static int verify_commit_graph_lite(struct commit_graph *g) return 0; } +static int graph_read_oid_lookup(const unsigned char *chunk_start, + size_t chunk_size, void *data) +{ + struct commit_graph *g = data; + g->chunk_oid_lookup = chunk_start; + g->num_commits = chunk_size / g->hash_len; + return 0; +} + +static int graph_read_bloom_data(const unsigned char *chunk_start, + size_t chunk_size, void *data) +{ + struct commit_graph *g = data; + uint32_t hash_version; + g->chunk_bloom_data = chunk_start; + hash_version = get_be32(chunk_start); + + if (hash_version != 1) + return 0; + + g->bloom_filter_settings = xmalloc(sizeof(struct bloom_filter_settings)); + g->bloom_filter_settings->hash_version = hash_version; + g->bloom_filter_settings->num_hashes = get_be32(chunk_start + 4); + g->bloom_filter_settings->bits_per_entry = get_be32(chunk_start + 8); + g->bloom_filter_settings->max_changed_paths = DEFAULT_BLOOM_MAX_CHANGES; + + return 0; +} + struct commit_graph *parse_commit_graph(struct repository *r, void *graph_map, size_t graph_size) { - const unsigned char *data, *chunk_lookup; - uint32_t i; + const unsigned char *data; struct commit_graph *graph; - uint64_t next_chunk_offset; uint32_t graph_signature; unsigned char graph_version, hash_version; + struct chunkfile *cf = NULL; if (!graph_map) return NULL; @@ -337,7 +381,7 @@ struct commit_graph *parse_commit_graph(struct repository *r, graph->data_len = graph_size; if (graph_size < GRAPH_HEADER_SIZE + - (graph->num_chunks + 1) * GRAPH_CHUNKLOOKUP_WIDTH + + (graph->num_chunks + 1) * CHUNK_TOC_ENTRY_SIZE + GRAPH_FANOUT_SIZE + the_hash_algo->rawsz) { error(_("commit-graph file is too small to hold %u chunks"), graph->num_chunks); @@ -345,94 +389,31 @@ struct commit_graph *parse_commit_graph(struct repository *r, return NULL; } - chunk_lookup = data + 8; - next_chunk_offset = get_be64(chunk_lookup + 4); - for (i = 0; i < graph->num_chunks; i++) { - uint32_t chunk_id; - uint64_t chunk_offset = next_chunk_offset; - int chunk_repeated = 0; - - chunk_id = get_be32(chunk_lookup + 0); - - chunk_lookup += GRAPH_CHUNKLOOKUP_WIDTH; - next_chunk_offset = get_be64(chunk_lookup + 4); + cf = init_chunkfile(NULL); - if (chunk_offset > graph_size - the_hash_algo->rawsz) { - error(_("commit-graph improper chunk offset %08x%08x"), (uint32_t)(chunk_offset >> 32), - (uint32_t)chunk_offset); - goto free_and_return; - } - - switch (chunk_id) { - case GRAPH_CHUNKID_OIDFANOUT: - if (graph->chunk_oid_fanout) - chunk_repeated = 1; - else - graph->chunk_oid_fanout = (uint32_t*)(data + chunk_offset); - break; - - case GRAPH_CHUNKID_OIDLOOKUP: - if (graph->chunk_oid_lookup) - chunk_repeated = 1; - else { - graph->chunk_oid_lookup = data + chunk_offset; - graph->num_commits = (next_chunk_offset - chunk_offset) - / graph->hash_len; - } - break; - - case GRAPH_CHUNKID_DATA: - if (graph->chunk_commit_data) - chunk_repeated = 1; - else - graph->chunk_commit_data = data + chunk_offset; - break; - - case GRAPH_CHUNKID_EXTRAEDGES: - if (graph->chunk_extra_edges) - chunk_repeated = 1; - else - graph->chunk_extra_edges = data + chunk_offset; - break; - - case GRAPH_CHUNKID_BASE: - if (graph->chunk_base_graphs) - chunk_repeated = 1; - else - graph->chunk_base_graphs = data + chunk_offset; - break; - - case GRAPH_CHUNKID_BLOOMINDEXES: - if (graph->chunk_bloom_indexes) - chunk_repeated = 1; - else if (r->settings.commit_graph_read_changed_paths) - graph->chunk_bloom_indexes = data + chunk_offset; - break; - - case GRAPH_CHUNKID_BLOOMDATA: - if (graph->chunk_bloom_data) - chunk_repeated = 1; - else if (r->settings.commit_graph_read_changed_paths) { - uint32_t hash_version; - graph->chunk_bloom_data = data + chunk_offset; - hash_version = get_be32(data + chunk_offset); + if (read_table_of_contents(cf, graph->data, graph_size, + GRAPH_HEADER_SIZE, graph->num_chunks)) + goto free_and_return; - if (hash_version != 1) - break; + pair_chunk(cf, GRAPH_CHUNKID_OIDFANOUT, + (const unsigned char **)&graph->chunk_oid_fanout); + read_chunk(cf, GRAPH_CHUNKID_OIDLOOKUP, graph_read_oid_lookup, graph); + pair_chunk(cf, GRAPH_CHUNKID_DATA, &graph->chunk_commit_data); + pair_chunk(cf, GRAPH_CHUNKID_EXTRAEDGES, &graph->chunk_extra_edges); + pair_chunk(cf, GRAPH_CHUNKID_BASE, &graph->chunk_base_graphs); - graph->bloom_filter_settings = xmalloc(sizeof(struct bloom_filter_settings)); - graph->bloom_filter_settings->hash_version = hash_version; - graph->bloom_filter_settings->num_hashes = get_be32(data + chunk_offset + 4); - graph->bloom_filter_settings->bits_per_entry = get_be32(data + chunk_offset + 8); - graph->bloom_filter_settings->max_changed_paths = DEFAULT_BLOOM_MAX_CHANGES; - } - break; - } + if (get_configured_generation_version(r) >= 2) { + pair_chunk(cf, GRAPH_CHUNKID_GENERATION_DATA, + &graph->chunk_generation_data); + pair_chunk(cf, GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW, + &graph->chunk_generation_data_overflow); + } - if (chunk_repeated) { - error(_("commit-graph chunk id %08x appears multiple times"), chunk_id); - goto free_and_return; - } + if (r->settings.commit_graph_read_changed_paths) { + pair_chunk(cf, GRAPH_CHUNKID_BLOOMINDEXES, + &graph->chunk_bloom_indexes); + read_chunk(cf, GRAPH_CHUNKID_BLOOMDATA, + graph_read_bloom_data, graph); } if (graph->chunk_bloom_indexes && graph->chunk_bloom_data) { @@ -449,9 +430,11 @@ struct commit_graph *parse_commit_graph(struct repository *r, if (verify_commit_graph_lite(graph)) goto free_and_return; + free_chunkfile(cf); return graph; free_and_return: + free_chunkfile(cf); free(graph->bloom_filter_settings); free(graph); return NULL; @@ -543,7 +526,7 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r, return NULL; count = st.st_size / (the_hash_algo->hexsz + 1); - oids = xcalloc(count, sizeof(struct object_id)); + CALLOC_ARRAY(oids, count); prepare_alt_odb(r); @@ -590,6 +573,31 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r, return graph_chain; } +/* + * returns 1 if and only if all graphs in the chain have + * corrected commit dates stored in the generation_data chunk. + */ +static int validate_mixed_generation_chain(struct commit_graph *g) +{ + int read_generation_data = 1; + struct commit_graph *p = g; + + while (read_generation_data && p) { + read_generation_data = p->read_generation_data; + p = p->base_graph; + } + + if (read_generation_data) + return 1; + + while (g) { + g->read_generation_data = 0; + g = g->base_graph; + } + + return 0; +} + struct commit_graph *read_commit_graph_one(struct repository *r, struct object_directory *odb) { @@ -598,6 +606,8 @@ struct commit_graph *read_commit_graph_one(struct repository *r, if (!g) g = load_commit_graph_chain(r, odb); + validate_mixed_generation_chain(g); + return g; } @@ -673,6 +683,20 @@ int generation_numbers_enabled(struct repository *r) return !!first_generation; } +int corrected_commit_dates_enabled(struct repository *r) +{ + struct commit_graph *g; + if (!prepare_commit_graph(r)) + return 0; + + g = r->objects->commit_graph; + + if (!g->num_commits) + return 0; + + return g->read_generation_data; +} + struct bloom_filter_settings *get_bloom_filter_settings(struct repository *r) { struct commit_graph *g = r->objects->commit_graph; @@ -748,17 +772,41 @@ static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, { const unsigned char *commit_data; struct commit_graph_data *graph_data; - uint32_t lex_index; + uint32_t lex_index, offset_pos; + uint64_t date_high, date_low, offset; while (pos < g->num_commits_in_base) g = g->base_graph; + if (pos >= g->num_commits + g->num_commits_in_base) + die(_("invalid commit position. commit-graph is likely corrupt")); + lex_index = pos - g->num_commits_in_base; commit_data = g->chunk_commit_data + GRAPH_DATA_WIDTH * lex_index; graph_data = commit_graph_data_at(item); graph_data->graph_pos = pos; - graph_data->generation = get_be32(commit_data + g->hash_len + 8) >> 2; + + date_high = get_be32(commit_data + g->hash_len + 8) & 0x3; + date_low = get_be32(commit_data + g->hash_len + 12); + item->date = (timestamp_t)((date_high << 32) | date_low); + + if (g->read_generation_data) { + offset = (timestamp_t)get_be32(g->chunk_generation_data + sizeof(uint32_t) * lex_index); + + if (offset & CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW) { + if (!g->chunk_generation_data_overflow) + die(_("commit-graph requires overflow generation data but has none")); + + offset_pos = offset ^ CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW; + graph_data->generation = get_be64(g->chunk_generation_data_overflow + 8 * offset_pos); + } else + graph_data->generation = item->date + offset; + } else + graph_data->generation = get_be32(commit_data + g->hash_len + 8) >> 2; + + if (g->topo_levels) + *topo_level_slab_at(g->topo_levels, item) = get_be32(commit_data + g->hash_len + 8) >> 2; } static inline void set_commit_tree(struct commit *c, struct tree *t) @@ -772,38 +820,22 @@ static int fill_commit_in_graph(struct repository *r, { uint32_t edge_value; uint32_t *parent_data_ptr; - uint64_t date_low, date_high; struct commit_list **pptr; - struct commit_graph_data *graph_data; const unsigned char *commit_data; uint32_t lex_index; while (pos < g->num_commits_in_base) g = g->base_graph; - if (pos >= g->num_commits + g->num_commits_in_base) - die(_("invalid commit position. commit-graph is likely corrupt")); + fill_commit_graph_info(item, g, pos); - /* - * Store the "full" position, but then use the - * "local" position for the rest of the calculation. - */ - graph_data = commit_graph_data_at(item); - graph_data->graph_pos = pos; lex_index = pos - g->num_commits_in_base; - commit_data = g->chunk_commit_data + (g->hash_len + 16) * lex_index; item->object.parsed = 1; set_commit_tree(item, NULL); - date_high = get_be32(commit_data + g->hash_len + 8) & 0x3; - date_low = get_be32(commit_data + g->hash_len + 12); - item->date = (timestamp_t)((date_high << 32) | date_low); - - graph_data->generation = get_be32(commit_data + g->hash_len + 8) >> 2; - pptr = &item->parents; edge_value = get_be32(commit_data + g->hash_len); @@ -943,6 +975,7 @@ struct write_commit_graph_context { struct oid_array oids; struct packed_commit_list commits; int num_extra_edges; + int num_generation_data_overflows; unsigned long approx_nr_objects; struct progress *progress; int progress_done; @@ -961,8 +994,11 @@ struct write_commit_graph_context { report_progress:1, split:1, changed_paths:1, - order_by_pack:1; + order_by_pack:1, + write_generation_data:1, + trust_generation_numbers:1; + struct topo_level_slab *topo_levels; const struct commit_graph_opts *opts; size_t total_bloom_filter_data_size; const struct bloom_filter_settings *bloom_settings; @@ -974,8 +1010,9 @@ struct write_commit_graph_context { }; static int write_graph_chunk_fanout(struct hashfile *f, - struct write_commit_graph_context *ctx) + void *data) { + struct write_commit_graph_context *ctx = data; int i, count = 0; struct commit **list = ctx->commits.list; @@ -1000,8 +1037,9 @@ static int write_graph_chunk_fanout(struct hashfile *f, } static int write_graph_chunk_oids(struct hashfile *f, - struct write_commit_graph_context *ctx) + void *data) { + struct write_commit_graph_context *ctx = data; struct commit **list = ctx->commits.list; int count; for (count = 0; count < ctx->commits.nr; count++, list++) { @@ -1019,8 +1057,9 @@ static const struct object_id *commit_to_oid(size_t index, const void *table) } static int write_graph_chunk_data(struct hashfile *f, - struct write_commit_graph_context *ctx) + void *data) { + struct write_commit_graph_context *ctx = data; struct commit **list = ctx->commits.list; struct commit **last = ctx->commits.list + ctx->commits.nr; uint32_t num_extra_edges = 0; @@ -1032,7 +1071,7 @@ static int write_graph_chunk_data(struct hashfile *f, uint32_t packedDate[2]; display_progress(ctx->progress, ++ctx->progress_cnt); - if (parse_commit_no_graph(*list)) + if (repo_parse_commit_no_graph(ctx->r, *list)) die(_("unable to parse commit %s"), oid_to_hex(&(*list)->object.oid)); tree = get_commit_tree_oid(*list); @@ -1109,7 +1148,7 @@ static int write_graph_chunk_data(struct hashfile *f, else packedDate[0] = 0; - packedDate[0] |= htonl(commit_graph_data_at(*list)->generation << 2); + packedDate[0] |= htonl(*topo_level_slab_at(ctx->topo_levels, *list) << 2); packedDate[1] = htonl((*list)->date); hashwrite(f, packedDate, 8); @@ -1120,9 +1159,53 @@ static int write_graph_chunk_data(struct hashfile *f, return 0; } +static int write_graph_chunk_generation_data(struct hashfile *f, + void *data) +{ + struct write_commit_graph_context *ctx = data; + int i, num_generation_data_overflows = 0; + + for (i = 0; i < ctx->commits.nr; i++) { + struct commit *c = ctx->commits.list[i]; + timestamp_t offset; + repo_parse_commit(ctx->r, c); + offset = commit_graph_data_at(c)->generation - c->date; + display_progress(ctx->progress, ++ctx->progress_cnt); + + if (offset > GENERATION_NUMBER_V2_OFFSET_MAX) { + offset = CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW | num_generation_data_overflows; + num_generation_data_overflows++; + } + + hashwrite_be32(f, offset); + } + + return 0; +} + +static int write_graph_chunk_generation_data_overflow(struct hashfile *f, + void *data) +{ + struct write_commit_graph_context *ctx = data; + int i; + for (i = 0; i < ctx->commits.nr; i++) { + struct commit *c = ctx->commits.list[i]; + timestamp_t offset = commit_graph_data_at(c)->generation - c->date; + display_progress(ctx->progress, ++ctx->progress_cnt); + + if (offset > GENERATION_NUMBER_V2_OFFSET_MAX) { + hashwrite_be32(f, offset >> 32); + hashwrite_be32(f, (uint32_t) offset); + } + } + + return 0; +} + static int write_graph_chunk_extra_edges(struct hashfile *f, - struct write_commit_graph_context *ctx) + void *data) { + struct write_commit_graph_context *ctx = data; struct commit **list = ctx->commits.list; struct commit **last = ctx->commits.list + ctx->commits.nr; struct commit_list *parent; @@ -1175,8 +1258,9 @@ static int write_graph_chunk_extra_edges(struct hashfile *f, } static int write_graph_chunk_bloom_indexes(struct hashfile *f, - struct write_commit_graph_context *ctx) + void *data) { + struct write_commit_graph_context *ctx = data; struct commit **list = ctx->commits.list; struct commit **last = ctx->commits.list + ctx->commits.nr; uint32_t cur_pos = 0; @@ -1210,8 +1294,9 @@ static void trace2_bloom_filter_settings(struct write_commit_graph_context *ctx) } static int write_graph_chunk_bloom_data(struct hashfile *f, - struct write_commit_graph_context *ctx) + void *data) { + struct write_commit_graph_context *ctx = data; struct commit **list = ctx->commits.list; struct commit **last = ctx->commits.list + ctx->commits.nr; @@ -1306,11 +1391,11 @@ static void close_reachable(struct write_commit_graph_context *ctx) if (!commit) continue; if (ctx->split) { - if ((!parse_commit(commit) && + if ((!repo_parse_commit(ctx->r, commit) && commit_graph_position(commit) == COMMIT_NOT_FROM_GRAPH) || flags == COMMIT_GRAPH_SPLIT_REPLACE) add_missing_parents(ctx, commit); - } else if (!parse_commit_no_graph(commit)) + } else if (!repo_parse_commit_no_graph(ctx->r, commit)) add_missing_parents(ctx, commit); } stop_progress(&ctx->progress); @@ -1329,6 +1414,59 @@ static void close_reachable(struct write_commit_graph_context *ctx) stop_progress(&ctx->progress); } +static void compute_topological_levels(struct write_commit_graph_context *ctx) +{ + int i; + struct commit_list *list = NULL; + + if (ctx->report_progress) + ctx->progress = start_delayed_progress( + _("Computing commit graph topological levels"), + ctx->commits.nr); + for (i = 0; i < ctx->commits.nr; i++) { + struct commit *c = ctx->commits.list[i]; + uint32_t level; + + repo_parse_commit(ctx->r, c); + level = *topo_level_slab_at(ctx->topo_levels, c); + + display_progress(ctx->progress, i + 1); + if (level != GENERATION_NUMBER_ZERO) + continue; + + commit_list_insert(c, &list); + while (list) { + struct commit *current = list->item; + struct commit_list *parent; + int all_parents_computed = 1; + uint32_t max_level = 0; + + for (parent = current->parents; parent; parent = parent->next) { + repo_parse_commit(ctx->r, parent->item); + level = *topo_level_slab_at(ctx->topo_levels, parent->item); + + if (level == GENERATION_NUMBER_ZERO) { + all_parents_computed = 0; + commit_list_insert(parent->item, &list); + break; + } + + if (level > max_level) + max_level = level; + } + + if (all_parents_computed) { + pop_commit(&list); + + if (max_level > GENERATION_NUMBER_V1_MAX - 1) + max_level = GENERATION_NUMBER_V1_MAX - 1; + *topo_level_slab_at(ctx->topo_levels, current) = max_level + 1; + } + } + } + stop_progress(&ctx->progress); +} + static void compute_generation_numbers(struct write_commit_graph_context *ctx) { int i; @@ -1338,42 +1476,56 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx) ctx->progress = start_delayed_progress( _("Computing commit graph generation numbers"), ctx->commits.nr); + + if (!ctx->trust_generation_numbers) { + for (i = 0; i < ctx->commits.nr; i++) { + struct commit *c = ctx->commits.list[i]; + repo_parse_commit(ctx->r, c); + commit_graph_data_at(c)->generation = GENERATION_NUMBER_ZERO; + } + } + for (i = 0; i < ctx->commits.nr; i++) { - uint32_t generation = commit_graph_data_at(ctx->commits.list[i])->generation; + struct commit *c = ctx->commits.list[i]; + timestamp_t corrected_commit_date; + + repo_parse_commit(ctx->r, c); + corrected_commit_date = commit_graph_data_at(c)->generation; display_progress(ctx->progress, i + 1); - if (generation != GENERATION_NUMBER_INFINITY && - generation != GENERATION_NUMBER_ZERO) + if (corrected_commit_date != GENERATION_NUMBER_ZERO) continue; - commit_list_insert(ctx->commits.list[i], &list); + commit_list_insert(c, &list); while (list) { struct commit *current = list->item; struct commit_list *parent; int all_parents_computed = 1; - uint32_t max_generation = 0; + timestamp_t max_corrected_commit_date = 0; for (parent = current->parents; parent; parent = parent->next) { - generation = commit_graph_data_at(parent->item)->generation; + repo_parse_commit(ctx->r, parent->item); + corrected_commit_date = commit_graph_data_at(parent->item)->generation; - if (generation == GENERATION_NUMBER_INFINITY || - generation == GENERATION_NUMBER_ZERO) { + if (corrected_commit_date == GENERATION_NUMBER_ZERO) { all_parents_computed = 0; commit_list_insert(parent->item, &list); break; - } else if (generation > max_generation) { - max_generation = generation; } + + if (corrected_commit_date > max_corrected_commit_date) + max_corrected_commit_date = corrected_commit_date; } if (all_parents_computed) { - struct commit_graph_data *data = commit_graph_data_at(current); - - data->generation = max_generation + 1; pop_commit(&list); - if (data->generation > GENERATION_NUMBER_MAX) - data->generation = GENERATION_NUMBER_MAX; + if (current->date && current->date > max_corrected_commit_date) + max_corrected_commit_date = current->date - 1; + commit_graph_data_at(current)->generation = max_corrected_commit_date + 1; + + if (commit_graph_data_at(current)->generation - current->date > GENERATION_NUMBER_V2_OFFSET_MAX) + ctx->num_generation_data_overflows++; } } } @@ -1593,9 +1745,9 @@ static void copy_oids_to_commits(struct write_commit_graph_context *ctx) continue; if (ctx->split && flags == COMMIT_GRAPH_SPLIT_REPLACE) - parse_commit(ctx->commits.list[ctx->commits.nr]); + repo_parse_commit(ctx->r, ctx->commits.list[ctx->commits.nr]); else - parse_commit_no_graph(ctx->commits.list[ctx->commits.nr]); + repo_parse_commit_no_graph(ctx->r, ctx->commits.list[ctx->commits.nr]); num_parents = commit_list_count(ctx->commits.list[ctx->commits.nr]->parents); if (num_parents > 2) @@ -1620,8 +1772,9 @@ static int write_graph_chunk_base_1(struct hashfile *f, } static int write_graph_chunk_base(struct hashfile *f, - struct write_commit_graph_context *ctx) + void *data) { + struct write_commit_graph_context *ctx = data; int num = write_graph_chunk_base_1(f, ctx->new_base_graph); if (num != ctx->num_commit_graphs_after - 1) { @@ -1632,27 +1785,16 @@ static int write_graph_chunk_base(struct hashfile *f, return 0; } -typedef int (*chunk_write_fn)(struct hashfile *f, - struct write_commit_graph_context *ctx); - -struct chunk_info { - uint32_t id; - uint64_t size; - chunk_write_fn write_fn; -}; - static int write_commit_graph_file(struct write_commit_graph_context *ctx) { uint32_t i; int fd; struct hashfile *f; struct lock_file lk = LOCK_INIT; - struct chunk_info chunks[MAX_NUM_CHUNKS + 1]; const unsigned hashsz = the_hash_algo->rawsz; struct strbuf progress_title = STRBUF_INIT; - int num_chunks = 3; - uint64_t chunk_offset; struct object_id file_hash; + struct chunkfile *cf; if (ctx->split) { struct strbuf tmp_file = STRBUF_INIT; @@ -1698,83 +1840,60 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx) f = hashfd(fd, get_lock_file_path(&lk)); } - chunks[0].id = GRAPH_CHUNKID_OIDFANOUT; - chunks[0].size = GRAPH_FANOUT_SIZE; - chunks[0].write_fn = write_graph_chunk_fanout; - chunks[1].id = GRAPH_CHUNKID_OIDLOOKUP; - chunks[1].size = hashsz * ctx->commits.nr; - chunks[1].write_fn = write_graph_chunk_oids; - chunks[2].id = GRAPH_CHUNKID_DATA; - chunks[2].size = (hashsz + 16) * ctx->commits.nr; - chunks[2].write_fn = write_graph_chunk_data; - if (ctx->num_extra_edges) { - chunks[num_chunks].id = GRAPH_CHUNKID_EXTRAEDGES; - chunks[num_chunks].size = 4 * ctx->num_extra_edges; - chunks[num_chunks].write_fn = write_graph_chunk_extra_edges; - num_chunks++; - } + cf = init_chunkfile(f); + + add_chunk(cf, GRAPH_CHUNKID_OIDFANOUT, GRAPH_FANOUT_SIZE, + write_graph_chunk_fanout); + add_chunk(cf, GRAPH_CHUNKID_OIDLOOKUP, hashsz * ctx->commits.nr, + write_graph_chunk_oids); + add_chunk(cf, GRAPH_CHUNKID_DATA, (hashsz + 16) * ctx->commits.nr, + write_graph_chunk_data); + + if (ctx->write_generation_data) + add_chunk(cf, GRAPH_CHUNKID_GENERATION_DATA, + sizeof(uint32_t) * ctx->commits.nr, + write_graph_chunk_generation_data); + if (ctx->num_generation_data_overflows) + add_chunk(cf, GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW, + sizeof(timestamp_t) * ctx->num_generation_data_overflows, + write_graph_chunk_generation_data_overflow); + if (ctx->num_extra_edges) + add_chunk(cf, GRAPH_CHUNKID_EXTRAEDGES, + 4 * ctx->num_extra_edges, + write_graph_chunk_extra_edges); if (ctx->changed_paths) { - chunks[num_chunks].id = GRAPH_CHUNKID_BLOOMINDEXES; - chunks[num_chunks].size = sizeof(uint32_t) * ctx->commits.nr; - chunks[num_chunks].write_fn = write_graph_chunk_bloom_indexes; - num_chunks++; - chunks[num_chunks].id = GRAPH_CHUNKID_BLOOMDATA; - chunks[num_chunks].size = sizeof(uint32_t) * 3 - + ctx->total_bloom_filter_data_size; - chunks[num_chunks].write_fn = write_graph_chunk_bloom_data; - num_chunks++; - } - if (ctx->num_commit_graphs_after > 1) { - chunks[num_chunks].id = GRAPH_CHUNKID_BASE; - chunks[num_chunks].size = hashsz * (ctx->num_commit_graphs_after - 1); - chunks[num_chunks].write_fn = write_graph_chunk_base; - num_chunks++; - } - - chunks[num_chunks].id = 0; - chunks[num_chunks].size = 0; + add_chunk(cf, GRAPH_CHUNKID_BLOOMINDEXES, + sizeof(uint32_t) * ctx->commits.nr, + write_graph_chunk_bloom_indexes); + add_chunk(cf, GRAPH_CHUNKID_BLOOMDATA, + sizeof(uint32_t) * 3 + + ctx->total_bloom_filter_data_size, + write_graph_chunk_bloom_data); + } + if (ctx->num_commit_graphs_after > 1) + add_chunk(cf, GRAPH_CHUNKID_BASE, + hashsz * (ctx->num_commit_graphs_after - 1), + write_graph_chunk_base); hashwrite_be32(f, GRAPH_SIGNATURE); hashwrite_u8(f, GRAPH_VERSION); hashwrite_u8(f, oid_version()); - hashwrite_u8(f, num_chunks); + hashwrite_u8(f, get_num_chunks(cf)); hashwrite_u8(f, ctx->num_commit_graphs_after - 1); - chunk_offset = 8 + (num_chunks + 1) * GRAPH_CHUNKLOOKUP_WIDTH; - for (i = 0; i <= num_chunks; i++) { - uint32_t chunk_write[3]; - - chunk_write[0] = htonl(chunks[i].id); - chunk_write[1] = htonl(chunk_offset >> 32); - chunk_write[2] = htonl(chunk_offset & 0xffffffff); - hashwrite(f, chunk_write, 12); - - chunk_offset += chunks[i].size; - } - if (ctx->report_progress) { strbuf_addf(&progress_title, Q_("Writing out commit graph in %d pass", "Writing out commit graph in %d passes", - num_chunks), - num_chunks); + get_num_chunks(cf)), + get_num_chunks(cf)); ctx->progress = start_delayed_progress( progress_title.buf, - num_chunks * ctx->commits.nr); + get_num_chunks(cf) * ctx->commits.nr); } - for (i = 0; i < num_chunks; i++) { - uint64_t start_offset = f->total + f->offset; - - if (chunks[i].write_fn(f, ctx)) - return -1; - - if (f->total + f->offset != start_offset + chunks[i].size) - BUG("expected to write %"PRId64" bytes to chunk %"PRIx32", but wrote %"PRId64" instead", - chunks[i].size, chunks[i].id, - f->total + f->offset - start_offset); - } + write_chunkfile(cf, ctx); stop_progress(&ctx->progress); strbuf_release(&progress_title); @@ -1791,6 +1910,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx) close_commit_graph(ctx->r->objects); finalize_hashfile(f, file_hash.hash, CSUM_HASH_IN_STREAM | CSUM_FSYNC); + free_chunkfile(cf); if (ctx->split) { FILE *chainf = fdopen_lock_file(&lk, "w"); @@ -1918,6 +2038,13 @@ static void split_graph_merge_strategy(struct write_commit_graph_context *ctx) if (i < ctx->num_commit_graphs_after) ctx->commit_graph_hash_after[i] = xstrdup(oid_to_hex(&g->oid)); + /* + * If the topmost remaining layer has generation data chunk, the + * resultant layer also has generation data chunk. + */ + if (i == ctx->num_commit_graphs_after - 2) + ctx->write_generation_data = !!g->chunk_generation_data; + i--; g = g->base_graph; } @@ -2104,28 +2231,32 @@ int write_commit_graph(struct object_directory *odb, enum commit_graph_write_flags flags, const struct commit_graph_opts *opts) { + struct repository *r = the_repository; struct write_commit_graph_context *ctx; uint32_t i; int res = 0; int replace = 0; struct bloom_filter_settings bloom_settings = DEFAULT_BLOOM_FILTER_SETTINGS; + struct topo_level_slab topo_levels; - prepare_repo_settings(the_repository); - if (!the_repository->settings.core_commit_graph) { + prepare_repo_settings(r); + if (!r->settings.core_commit_graph) { warning(_("attempting to write a commit-graph, but 'core.commitGraph' is disabled")); return 0; } - if (!commit_graph_compatible(the_repository)) + if (!commit_graph_compatible(r)) return 0; - ctx = xcalloc(1, sizeof(struct write_commit_graph_context)); - ctx->r = the_repository; + CALLOC_ARRAY(ctx, 1); + ctx->r = r; ctx->odb = odb; ctx->append = flags & COMMIT_GRAPH_WRITE_APPEND ? 1 : 0; ctx->report_progress = flags & COMMIT_GRAPH_WRITE_PROGRESS ? 1 : 0; ctx->split = flags & COMMIT_GRAPH_WRITE_SPLIT ? 1 : 0; ctx->opts = opts; ctx->total_bloom_filter_data_size = 0; + ctx->write_generation_data = (get_configured_generation_version(r) == 2); + ctx->num_generation_data_overflows = 0; bloom_settings.bits_per_entry = git_env_ulong("GIT_TEST_BLOOM_SETTINGS_BITS_PER_ENTRY", bloom_settings.bits_per_entry); @@ -2135,11 +2266,23 @@ int write_commit_graph(struct object_directory *odb, bloom_settings.max_changed_paths); ctx->bloom_settings = &bloom_settings; + init_topo_level_slab(&topo_levels); + ctx->topo_levels = &topo_levels; + + prepare_commit_graph(ctx->r); + if (ctx->r->objects->commit_graph) { + struct commit_graph *g = ctx->r->objects->commit_graph; + + while (g) { + g->topo_levels = &topo_levels; + g = g->base_graph; + } + } + if (flags & COMMIT_GRAPH_WRITE_BLOOM_FILTERS) ctx->changed_paths = 1; if (!(flags & COMMIT_GRAPH_NO_WRITE_BLOOM_FILTERS)) { struct commit_graph *g; - prepare_commit_graph_one(ctx->r, ctx->odb); g = ctx->r->objects->commit_graph; @@ -2151,10 +2294,7 @@ int write_commit_graph(struct object_directory *odb, } if (ctx->split) { - struct commit_graph *g; - prepare_commit_graph(ctx->r); - - g = ctx->r->objects->commit_graph; + struct commit_graph *g = ctx->r->objects->commit_graph; while (g) { ctx->num_commit_graphs_before++; @@ -2178,9 +2318,6 @@ int write_commit_graph(struct object_directory *odb, ctx->approx_nr_objects = approximate_object_count(); - if (ctx->append) - prepare_commit_graph_one(ctx->r, ctx->odb); - if (ctx->append && ctx->r->objects->commit_graph) { struct commit_graph *g = ctx->r->objects->commit_graph; for (i = 0; i < g->num_commits; i++) { @@ -2227,7 +2364,11 @@ int write_commit_graph(struct object_directory *odb, } else ctx->num_commit_graphs_after = 1; - compute_generation_numbers(ctx); + ctx->trust_generation_numbers = validate_mixed_generation_chain(ctx->r->objects->commit_graph); + + compute_topological_levels(ctx); + if (ctx->write_generation_data) + compute_generation_numbers(ctx); if (ctx->changed_paths) compute_bloom_filters(ctx); @@ -2243,6 +2384,7 @@ cleanup: free(ctx->graph_name); free(ctx->commits.list); oid_array_clear(&ctx->oids); + clear_topo_level_slab(&topo_levels); if (ctx->commit_graph_filenames_after) { for (i = 0; i < ctx->num_commit_graphs_after; i++) { @@ -2355,8 +2497,8 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags) for (i = 0; i < g->num_commits; i++) { struct commit *graph_commit, *odb_commit; struct commit_list *graph_parents, *odb_parents; - uint32_t max_generation = 0; - uint32_t generation; + timestamp_t max_generation = 0; + timestamp_t generation; display_progress(progress, i + 1); hashcpy(cur_oid.hash, g->chunk_oid_lookup + g->hash_len * i); @@ -2420,16 +2562,17 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags) continue; /* - * If one of our parents has generation GENERATION_NUMBER_MAX, then - * our generation is also GENERATION_NUMBER_MAX. Decrement to avoid - * extra logic in the following condition. + * If we are using topological level and one of our parents has + * generation GENERATION_NUMBER_V1_MAX, then our generation is + * also GENERATION_NUMBER_V1_MAX. Decrement to avoid extra logic + * in the following condition. */ - if (max_generation == GENERATION_NUMBER_MAX) + if (!g->read_generation_data && max_generation == GENERATION_NUMBER_V1_MAX) max_generation--; generation = commit_graph_generation(graph_commit); - if (generation != max_generation + 1) - graph_report(_("commit-graph generation for commit %s is %u != %u"), + if (generation < max_generation + 1) + graph_report(_("commit-graph generation for commit %s is %"PRItime" < %"PRItime), oid_to_hex(&cur_oid), generation, max_generation + 1); diff --git a/commit-graph.h b/commit-graph.h index f8e92500c6..96c24fb577 100644 --- a/commit-graph.h +++ b/commit-graph.h @@ -63,16 +63,20 @@ struct commit_graph { struct object_directory *odb; uint32_t num_commits_in_base; + unsigned int read_generation_data; struct commit_graph *base_graph; const uint32_t *chunk_oid_fanout; const unsigned char *chunk_oid_lookup; const unsigned char *chunk_commit_data; + const unsigned char *chunk_generation_data; + const unsigned char *chunk_generation_data_overflow; const unsigned char *chunk_extra_edges; const unsigned char *chunk_base_graphs; const unsigned char *chunk_bloom_indexes; const unsigned char *chunk_bloom_data; + struct topo_level_slab *topo_levels; struct bloom_filter_settings *bloom_filter_settings; }; @@ -90,6 +94,12 @@ struct commit_graph *parse_commit_graph(struct repository *r, */ int generation_numbers_enabled(struct repository *r); +/* + * Return 1 if and only if the repository has a commit-graph + * file and generation data chunk has been written for the file. + */ +int corrected_commit_dates_enabled(struct repository *r); + struct bloom_filter_settings *get_bloom_filter_settings(struct repository *r); enum commit_graph_write_flags { @@ -144,12 +154,12 @@ void disable_commit_graph(struct repository *r); struct commit_graph_data { uint32_t graph_pos; - uint32_t generation; + timestamp_t generation; }; /* * Commits should be parsed before accessing generation, graph positions. */ -uint32_t commit_graph_generation(const struct commit *); +timestamp_t commit_graph_generation(const struct commit *); uint32_t commit_graph_position(const struct commit *); #endif diff --git a/commit-reach.c b/commit-reach.c index 50175b159e..c226ee3da4 100644 --- a/commit-reach.c +++ b/commit-reach.c @@ -17,6 +17,25 @@ static const unsigned all_flags = (PARENT1 | PARENT2 | STALE | RESULT); +static int compare_commits_by_gen(const void *_a, const void *_b) +{ + const struct commit *a = *(const struct commit * const *)_a; + const struct commit *b = *(const struct commit * const *)_b; + + timestamp_t generation_a = commit_graph_generation(a); + timestamp_t generation_b = commit_graph_generation(b); + + if (generation_a < generation_b) + return -1; + if (generation_a > generation_b) + return 1; + if (a->date < b->date) + return -1; + if (a->date > b->date) + return 1; + return 0; +} + static int queue_has_nonstale(struct prio_queue *queue) { int i; @@ -32,14 +51,14 @@ static int queue_has_nonstale(struct prio_queue *queue) static struct commit_list *paint_down_to_common(struct repository *r, struct commit *one, int n, struct commit **twos, - int min_generation) + timestamp_t min_generation) { struct prio_queue queue = { compare_commits_by_gen_then_commit_date }; struct commit_list *result = NULL; int i; - uint32_t last_gen = GENERATION_NUMBER_INFINITY; + timestamp_t last_gen = GENERATION_NUMBER_INFINITY; - if (!min_generation) + if (!min_generation && !corrected_commit_dates_enabled(r)) queue.compare = compare_commits_by_commit_date; one->object.flags |= PARENT1; @@ -58,10 +77,10 @@ static struct commit_list *paint_down_to_common(struct repository *r, struct commit *commit = prio_queue_get(&queue); struct commit_list *parents; int flags; - uint32_t generation = commit_graph_generation(commit); + timestamp_t generation = commit_graph_generation(commit); if (min_generation && generation > last_gen) - BUG("bad generation skip %8x > %8x at %s", + BUG("bad generation skip %"PRItime" > %"PRItime" at %s", generation, last_gen, oid_to_hex(&commit->object.oid)); last_gen = generation; @@ -156,20 +175,15 @@ struct commit_list *get_octopus_merge_bases(struct commit_list *in) return ret; } -static int remove_redundant(struct repository *r, struct commit **array, int cnt) +static int remove_redundant_no_gen(struct repository *r, + struct commit **array, int cnt) { - /* - * Some commit in the array may be an ancestor of - * another commit. Move such commit to the end of - * the array, and return the number of commits that - * are independent from each other. - */ struct commit **work; unsigned char *redundant; int *filled_index; int i, j, filled; - work = xcalloc(cnt, sizeof(*work)); + CALLOC_ARRAY(work, cnt); redundant = xcalloc(cnt, 1); ALLOC_ARRAY(filled_index, cnt - 1); @@ -177,12 +191,12 @@ static int remove_redundant(struct repository *r, struct commit **array, int cnt repo_parse_commit(r, array[i]); for (i = 0; i < cnt; i++) { struct commit_list *common; - uint32_t min_generation = commit_graph_generation(array[i]); + timestamp_t min_generation = commit_graph_generation(array[i]); if (redundant[i]) continue; for (j = filled = 0; j < cnt; j++) { - uint32_t curr_generation; + timestamp_t curr_generation; if (i == j || redundant[j]) continue; filled_index[filled] = j; @@ -209,15 +223,156 @@ static int remove_redundant(struct repository *r, struct commit **array, int cnt for (i = filled = 0; i < cnt; i++) if (!redundant[i]) array[filled++] = work[i]; - for (j = filled, i = 0; i < cnt; i++) - if (redundant[i]) - array[j++] = work[i]; free(work); free(redundant); free(filled_index); return filled; } +static int remove_redundant_with_gen(struct repository *r, + struct commit **array, int cnt) +{ + int i, count_non_stale = 0, count_still_independent = cnt; + timestamp_t min_generation = GENERATION_NUMBER_INFINITY; + struct commit **walk_start, **sorted; + size_t walk_start_nr = 0, walk_start_alloc = cnt; + int min_gen_pos = 0; + + /* + * Sort the input by generation number, ascending. This allows + * us to increase the "min_generation" limit when we discover + * the commit with lowest generation is STALE. The index + * min_gen_pos points to the current position within 'array' + * that is not yet known to be STALE. + */ + ALLOC_ARRAY(sorted, cnt); + COPY_ARRAY(sorted, array, cnt); + QSORT(sorted, cnt, compare_commits_by_gen); + min_generation = commit_graph_generation(sorted[0]); + + ALLOC_ARRAY(walk_start, walk_start_alloc); + + /* Mark all parents of the input as STALE */ + for (i = 0; i < cnt; i++) { + struct commit_list *parents; + + repo_parse_commit(r, array[i]); + array[i]->object.flags |= RESULT; + parents = array[i]->parents; + + while (parents) { + repo_parse_commit(r, parents->item); + if (!(parents->item->object.flags & STALE)) { + parents->item->object.flags |= STALE; + ALLOC_GROW(walk_start, walk_start_nr + 1, walk_start_alloc); + walk_start[walk_start_nr++] = parents->item; + } + parents = parents->next; + } + } + + QSORT(walk_start, walk_start_nr, compare_commits_by_gen); + + /* remove STALE bit for now to allow walking through parents */ + for (i = 0; i < walk_start_nr; i++) + walk_start[i]->object.flags &= ~STALE; + + /* + * Start walking from the highest generation. Hopefully, it will + * find all other items during the first-parent walk, and we can + * terminate early. Otherwise, we will do the same amount of work + * as before. + */ + for (i = walk_start_nr - 1; i >= 0 && count_still_independent > 1; i--) { + /* push the STALE bits up to min generation */ + struct commit_list *stack = NULL; + + commit_list_insert(walk_start[i], &stack); + walk_start[i]->object.flags |= STALE; + + while (stack) { + struct commit_list *parents; + struct commit *c = stack->item; + + repo_parse_commit(r, c); + + if (c->object.flags & RESULT) { + c->object.flags &= ~RESULT; + if (--count_still_independent <= 1) + break; + if (oideq(&c->object.oid, &sorted[min_gen_pos]->object.oid)) { + while (min_gen_pos < cnt - 1 && + (sorted[min_gen_pos]->object.flags & STALE)) + min_gen_pos++; + min_generation = commit_graph_generation(sorted[min_gen_pos]); + } + } + + if (commit_graph_generation(c) < min_generation) { + pop_commit(&stack); + continue; + } + + parents = c->parents; + while (parents) { + if (!(parents->item->object.flags & STALE)) { + parents->item->object.flags |= STALE; + commit_list_insert(parents->item, &stack); + break; + } + parents = parents->next; + } + + /* pop if all parents have been visited already */ + if (!parents) + pop_commit(&stack); + } + free_commit_list(stack); + } + free(sorted); + + /* clear result */ + for (i = 0; i < cnt; i++) + array[i]->object.flags &= ~RESULT; + + /* rearrange array */ + for (i = count_non_stale = 0; i < cnt; i++) { + if (!(array[i]->object.flags & STALE)) + array[count_non_stale++] = array[i]; + } + + /* clear marks */ + clear_commit_marks_many(walk_start_nr, walk_start, STALE); + free(walk_start); + + return count_non_stale; +} + +static int remove_redundant(struct repository *r, struct commit **array, int cnt) +{ + /* + * Some commit in the array may be an ancestor of + * another commit. Move the independent commits to the + * beginning of 'array' and return their number. Callers + * should not rely upon the contents of 'array' after + * that number. + */ + if (generation_numbers_enabled(r)) { + int i; + + /* + * If we have a single commit with finite generation + * number, then the _with_gen algorithm is preferred. + */ + for (i = 0; i < cnt; i++) { + if (commit_graph_generation(array[i]) < GENERATION_NUMBER_INFINITY) + return remove_redundant_with_gen(r, array, cnt); + } + } + + return remove_redundant_no_gen(r, array, cnt); +} + static struct commit_list *get_merge_bases_many_0(struct repository *r, struct commit *one, int n, @@ -244,7 +399,7 @@ static struct commit_list *get_merge_bases_many_0(struct repository *r, /* There are more than one */ cnt = commit_list_count(result); - rslt = xcalloc(cnt, sizeof(*rslt)); + CALLOC_ARRAY(rslt, cnt); for (list = result, i = 0; list; list = list->next) rslt[i++] = list->item; free_commit_list(result); @@ -321,7 +476,7 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit, { struct commit_list *bases; int ret = 0, i; - uint32_t generation, max_generation = GENERATION_NUMBER_ZERO; + timestamp_t generation, max_generation = GENERATION_NUMBER_ZERO; if (repo_parse_commit(r, commit)) return ret; @@ -386,7 +541,7 @@ struct commit_list *reduce_heads(struct commit_list *heads) p->item->object.flags |= STALE; num_head++; } - array = xcalloc(num_head, sizeof(*array)); + CALLOC_ARRAY(array, num_head); for (p = heads, i = 0; p; p = p->next) { if (p->item->object.flags & STALE) { array[i++] = p->item; @@ -470,7 +625,7 @@ static int in_commit_list(const struct commit_list *want, struct commit *c) static enum contains_result contains_test(struct commit *candidate, const struct commit_list *want, struct contains_cache *cache, - uint32_t cutoff) + timestamp_t cutoff) { enum contains_result *cached = contains_cache_at(cache, candidate); @@ -506,11 +661,11 @@ static enum contains_result contains_tag_algo(struct commit *candidate, { struct contains_stack contains_stack = { 0, 0, NULL }; enum contains_result result; - uint32_t cutoff = GENERATION_NUMBER_INFINITY; + timestamp_t cutoff = GENERATION_NUMBER_INFINITY; const struct commit_list *p; for (p = want; p; p = p->next) { - uint32_t generation; + timestamp_t generation; struct commit *c = p->item; load_commit_graph_info(the_repository, c); generation = commit_graph_generation(c); @@ -561,26 +716,11 @@ int commit_contains(struct ref_filter *filter, struct commit *commit, return repo_is_descendant_of(the_repository, commit, list); } -static int compare_commits_by_gen(const void *_a, const void *_b) -{ - const struct commit *a = *(const struct commit * const *)_a; - const struct commit *b = *(const struct commit * const *)_b; - - uint32_t generation_a = commit_graph_generation(a); - uint32_t generation_b = commit_graph_generation(b); - - if (generation_a < generation_b) - return -1; - if (generation_a > generation_b) - return 1; - return 0; -} - int can_all_from_reach_with_flag(struct object_array *from, unsigned int with_flag, unsigned int assign_flag, time_t min_commit_date, - uint32_t min_generation) + timestamp_t min_generation) { struct commit **list = NULL; int i; @@ -681,13 +821,13 @@ int can_all_from_reach(struct commit_list *from, struct commit_list *to, time_t min_commit_date = cutoff_by_min_date ? from->item->date : 0; struct commit_list *from_iter = from, *to_iter = to; int result; - uint32_t min_generation = GENERATION_NUMBER_INFINITY; + timestamp_t min_generation = GENERATION_NUMBER_INFINITY; while (from_iter) { add_object_array(&from_iter->item->object, NULL, &from_objs); if (!parse_commit(from_iter->item)) { - uint32_t generation; + timestamp_t generation; if (from_iter->item->date < min_commit_date) min_commit_date = from_iter->item->date; @@ -701,7 +841,7 @@ int can_all_from_reach(struct commit_list *from, struct commit_list *to, while (to_iter) { if (!parse_commit(to_iter->item)) { - uint32_t generation; + timestamp_t generation; if (to_iter->item->date < min_commit_date) min_commit_date = to_iter->item->date; @@ -741,13 +881,13 @@ struct commit_list *get_reachable_subset(struct commit **from, int nr_from, struct commit_list *found_commits = NULL; struct commit **to_last = to + nr_to; struct commit **from_last = from + nr_from; - uint32_t min_generation = GENERATION_NUMBER_INFINITY; + timestamp_t min_generation = GENERATION_NUMBER_INFINITY; int num_to_find = 0; struct prio_queue queue = { compare_commits_by_gen_then_commit_date }; for (item = to; item < to_last; item++) { - uint32_t generation; + timestamp_t generation; struct commit *c = *item; parse_commit(c); diff --git a/commit-reach.h b/commit-reach.h index b49ad71a31..148b56fea5 100644 --- a/commit-reach.h +++ b/commit-reach.h @@ -87,7 +87,7 @@ int can_all_from_reach_with_flag(struct object_array *from, unsigned int with_flag, unsigned int assign_flag, time_t min_commit_date, - uint32_t min_generation); + timestamp_t min_generation); int can_all_from_reach(struct commit_list *from, struct commit_list *to, int commit_date_cutoff); diff --git a/commit.c b/commit.c index fd2831dad3..8ea55a447f 100644 --- a/commit.c +++ b/commit.c @@ -535,6 +535,20 @@ int find_commit_subject(const char *commit_buffer, const char **subject) return eol - p; } +size_t commit_subject_length(const char *body) +{ + const char *p = body; + while (*p) { + const char *next = skip_blank_lines(p); + if (next != p) + break; + p = strchrnul(p, '\n'); + if (*p) + p++; + } + return p - body; +} + struct commit_list *commit_list_insert(struct commit *item, struct commit_list **list_p) { struct commit_list *new_list = xmalloc(sizeof(struct commit_list)); @@ -753,8 +767,8 @@ int compare_commits_by_author_date(const void *a_, const void *b_, int compare_commits_by_gen_then_commit_date(const void *a_, const void *b_, void *unused) { const struct commit *a = a_, *b = b_; - const uint32_t generation_a = commit_graph_generation(a), - generation_b = commit_graph_generation(b); + const timestamp_t generation_a = commit_graph_generation(a), + generation_b = commit_graph_generation(b); /* newer commits first */ if (generation_a < generation_b) @@ -995,7 +1009,7 @@ static const char *gpg_sig_headers[] = { "gpgsig-sha256", }; -static int do_sign_commit(struct strbuf *buf, const char *keyid) +int sign_with_header(struct strbuf *buf, const char *keyid) { struct strbuf sig = STRBUF_INIT; int inspos, copypos; @@ -1035,21 +1049,32 @@ static int do_sign_commit(struct strbuf *buf, const char *keyid) return 0; } + + int parse_signed_commit(const struct commit *commit, - struct strbuf *payload, struct strbuf *signature) + struct strbuf *payload, struct strbuf *signature, + const struct git_hash_algo *algop) { - unsigned long size; const char *buffer = get_commit_buffer(commit, &size); - int in_signature, saw_signature = -1; - const char *line, *tail; - const char *gpg_sig_header = gpg_sig_headers[hash_algo_by_ptr(the_hash_algo)]; - int gpg_sig_header_len = strlen(gpg_sig_header); + int ret = parse_buffer_signed_by_header(buffer, size, payload, signature, algop); + + unuse_commit_buffer(commit, buffer); + return ret; +} + +int parse_buffer_signed_by_header(const char *buffer, + unsigned long size, + struct strbuf *payload, + struct strbuf *signature, + const struct git_hash_algo *algop) +{ + int in_signature = 0, saw_signature = 0, other_signature = 0; + const char *line, *tail, *p; + const char *gpg_sig_header = gpg_sig_headers[hash_algo_by_ptr(algop)]; line = buffer; tail = buffer + size; - in_signature = 0; - saw_signature = 0; while (line < tail) { const char *sig = NULL; const char *next = memchr(line, '\n', tail - line); @@ -1057,9 +1082,15 @@ int parse_signed_commit(const struct commit *commit, next = next ? next + 1 : tail; if (in_signature && line[0] == ' ') sig = line + 1; - else if (starts_with(line, gpg_sig_header) && - line[gpg_sig_header_len] == ' ') - sig = line + gpg_sig_header_len + 1; + else if (skip_prefix(line, gpg_sig_header, &p) && + *p == ' ') { + sig = line + strlen(gpg_sig_header) + 1; + other_signature = 0; + } + else if (starts_with(line, "gpgsig")) + other_signature = 1; + else if (other_signature && line[0] != ' ') + other_signature = 0; if (sig) { strbuf_add(signature, sig, next - sig); saw_signature = 1; @@ -1068,12 +1099,12 @@ int parse_signed_commit(const struct commit *commit, if (*line == '\n') /* dump the whole remainder of the buffer */ next = tail; - strbuf_add(payload, line, next - line); + if (!other_signature) + strbuf_add(payload, line, next - line); in_signature = 0; } line = next; } - unuse_commit_buffer(commit, buffer); return saw_signature; } @@ -1082,23 +1113,29 @@ int remove_signature(struct strbuf *buf) const char *line = buf->buf; const char *tail = buf->buf + buf->len; int in_signature = 0; - const char *sig_start = NULL; - const char *sig_end = NULL; + struct sigbuf { + const char *start; + const char *end; + } sigs[2], *sigp = &sigs[0]; + int i; + const char *orig_buf = buf->buf; + + memset(sigs, 0, sizeof(sigs)); while (line < tail) { const char *next = memchr(line, '\n', tail - line); next = next ? next + 1 : tail; if (in_signature && line[0] == ' ') - sig_end = next; + sigp->end = next; else if (starts_with(line, "gpgsig")) { int i; for (i = 1; i < GIT_HASH_NALGOS; i++) { const char *p; if (skip_prefix(line, gpg_sig_headers[i], &p) && *p == ' ') { - sig_start = line; - sig_end = next; + sigp->start = line; + sigp->end = next; in_signature = 1; } } @@ -1106,15 +1143,18 @@ int remove_signature(struct strbuf *buf) if (*line == '\n') /* dump the whole remainder of the buffer */ next = tail; + if (in_signature && sigp - sigs != ARRAY_SIZE(sigs)) + sigp++; in_signature = 0; } line = next; } - if (sig_start) - strbuf_remove(buf, sig_start - buf->buf, sig_end - sig_start); + for (i = ARRAY_SIZE(sigs) - 1; i >= 0; i--) + if (sigs[i].start) + strbuf_remove(buf, sigs[i].start - orig_buf, sigs[i].end - sigs[i].start); - return sig_start != NULL; + return sigs[0].start != NULL; } static void handle_signed_tag(struct commit *parent, struct commit_extra_header ***tail) @@ -1122,8 +1162,10 @@ static void handle_signed_tag(struct commit *parent, struct commit_extra_header struct merge_remote_desc *desc; struct commit_extra_header *mergetag; char *buf; - unsigned long size, len; + unsigned long size; enum object_type type; + struct strbuf payload = STRBUF_INIT; + struct strbuf signature = STRBUF_INIT; desc = merge_remote_util(parent); if (!desc || !desc->obj) @@ -1131,8 +1173,7 @@ static void handle_signed_tag(struct commit *parent, struct commit_extra_header buf = read_object_file(&desc->obj->oid, &type, &size); if (!buf || type != OBJ_TAG) goto free_return; - len = parse_signature(buf, size); - if (size == len) + if (!parse_signature(buf, size, &payload, &signature)) goto free_return; /* * We could verify this signature and either omit the tag when @@ -1144,13 +1185,15 @@ static void handle_signed_tag(struct commit *parent, struct commit_extra_header * if (verify_signed_buffer(buf, len, buf + len, size - len, ...)) * warn("warning: signed tag unverified."); */ - mergetag = xcalloc(1, sizeof(*mergetag)); + CALLOC_ARRAY(mergetag, 1); mergetag->key = xstrdup("mergetag"); mergetag->value = buf; mergetag->len = size; **tail = mergetag; *tail = &mergetag->next; + strbuf_release(&payload); + strbuf_release(&signature); return; free_return: @@ -1165,7 +1208,7 @@ int check_commit_signature(const struct commit *commit, struct signature_check * sigc->result = 'N'; - if (parse_signed_commit(commit, &payload, &signature) <= 0) + if (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0) goto out; ret = check_signature(payload.buf, payload.len, signature.buf, signature.len, sigc); @@ -1307,7 +1350,7 @@ static struct commit_extra_header *read_commit_extra_header_lines( excluded_header_field(line, eof - line, exclude)) continue; - it = xcalloc(1, sizeof(*it)); + CALLOC_ARRAY(it, 1); it->key = xmemdupz(line, eof-line); *tail = it; tail = &it->next; @@ -1515,7 +1558,7 @@ int commit_tree_extended(const char *msg, size_t msg_len, if (encoding_is_utf8 && !verify_utf8(&buffer)) fprintf(stderr, _(commit_utf8_warn)); - if (sign_commit && do_sign_commit(&buffer, sign_commit)) { + if (sign_commit && sign_with_header(&buffer, sign_commit)) { result = -1; goto out; } diff --git a/commit.h b/commit.h index ecacf9ade3..df42eb434f 100644 --- a/commit.h +++ b/commit.h @@ -11,9 +11,10 @@ #include "commit-slab.h" #define COMMIT_NOT_FROM_GRAPH 0xFFFFFFFF -#define GENERATION_NUMBER_INFINITY 0xFFFFFFFF -#define GENERATION_NUMBER_MAX 0x3FFFFFFF +#define GENERATION_NUMBER_INFINITY ((1ULL << 63) - 1) +#define GENERATION_NUMBER_V1_MAX 0x3FFFFFFF #define GENERATION_NUMBER_ZERO 0 +#define GENERATION_NUMBER_V2_OFFSET_MAX ((1ULL << 31) - 1) struct commit_list { struct commit *item; @@ -88,9 +89,10 @@ static inline int repo_parse_commit(struct repository *r, struct commit *item) return repo_parse_commit_gently(r, item, 0); } -static inline int parse_commit_no_graph(struct commit *commit) +static inline int repo_parse_commit_no_graph(struct repository *r, + struct commit *commit) { - return repo_parse_commit_internal(the_repository, commit, 0, 0); + return repo_parse_commit_internal(r, commit, 0, 0); } #ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS @@ -165,6 +167,9 @@ const void *detach_commit_buffer(struct commit *, unsigned long *sizep); /* Find beginning and length of commit subject. */ int find_commit_subject(const char *commit_buffer, const char **subject); +/* Return length of the commit subject from commit log message. */ +size_t commit_subject_length(const char *body); + struct commit_list *commit_list_insert(struct commit *item, struct commit_list **list); int commit_list_contains(struct commit *item, @@ -317,7 +322,8 @@ void set_merge_remote_desc(struct commit *commit, struct commit *get_merge_parent(const char *name); int parse_signed_commit(const struct commit *commit, - struct strbuf *message, struct strbuf *signature); + struct strbuf *message, struct strbuf *signature, + const struct git_hash_algo *algop); int remove_signature(struct strbuf *buf); /* @@ -359,4 +365,13 @@ int compare_commits_by_gen_then_commit_date(const void *a_, const void *b_, void LAST_ARG_MUST_BE_NULL int run_commit_hook(int editor_is_used, const char *index_file, 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, + struct strbuf *payload, + struct strbuf *signature, + const struct git_hash_algo *algop); + #endif /* COMMIT_H */ diff --git a/compat/mingw.c b/compat/mingw.c index a00f331230..a43599841c 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -367,6 +367,8 @@ int mingw_rmdir(const char *pathname) ask_yes_no_if_possible("Deletion of directory '%s' failed. " "Should I try again?", pathname)) ret = _wrmdir(wpathname); + if (!ret) + invalidate_lstat_cache(); return ret; } diff --git a/compat/mingw.h b/compat/mingw.h index af8eddd73e..c9a52ad64a 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -227,6 +227,7 @@ int mingw_rmdir(const char *path); int mingw_open (const char *filename, int oflags, ...); #define open mingw_open +#undef OPEN_RETURNS_EINTR int mingw_fgetc(FILE *stream); #define fgetc mingw_fgetc diff --git a/compat/open.c b/compat/open.c new file mode 100644 index 0000000000..eb3754a23b --- /dev/null +++ b/compat/open.c @@ -0,0 +1,25 @@ +#include "git-compat-util.h" + +#undef open +int git_open_with_retry(const char *path, int flags, ...) +{ + mode_t mode = 0; + int ret; + + /* + * Also O_TMPFILE would take a mode, but it isn't defined everywhere. + * And anyway, we don't use it in our code base. + */ + if (flags & O_CREAT) { + va_list ap; + va_start(ap, flags); + mode = va_arg(ap, int); + va_end(ap); + } + + do { + ret = open(path, flags, mode); + } while (ret < 0 && errno == EINTR); + + return ret; +} diff --git a/compat/precompose_utf8.c b/compat/precompose_utf8.c index 136250fbf6..ec560565a8 100644 --- a/compat/precompose_utf8.c +++ b/compat/precompose_utf8.c @@ -60,32 +60,46 @@ void probe_utf8_pathname_composition(void) strbuf_release(&path); } - -void precompose_argv(int argc, const char **argv) +static inline const char *precompose_string_if_needed(const char *in) { - int i = 0; - const char *oldarg; - char *newarg; - iconv_t ic_precompose; + size_t inlen; + size_t outlen; + if (has_non_ascii(in, (size_t)-1, &inlen)) { + iconv_t ic_prec; + char *out; + if (precomposed_unicode < 0) + git_config_get_bool("core.precomposeunicode", &precomposed_unicode); + if (precomposed_unicode != 1) + return in; + ic_prec = iconv_open(repo_encoding, path_encoding); + if (ic_prec == (iconv_t) -1) + return in; + + out = reencode_string_iconv(in, inlen, ic_prec, 0, &outlen); + if (out) { + if (outlen == inlen && !memcmp(in, out, outlen)) + free(out); /* no need to return indentical */ + else + in = out; + } + iconv_close(ic_prec); - if (precomposed_unicode != 1) - return; + } + return in; +} - ic_precompose = iconv_open(repo_encoding, path_encoding); - if (ic_precompose == (iconv_t) -1) - return; +const char *precompose_argv_prefix(int argc, const char **argv, const char *prefix) +{ + int i = 0; while (i < argc) { - size_t namelen; - oldarg = argv[i]; - if (has_non_ascii(oldarg, (size_t)-1, &namelen)) { - newarg = reencode_string_iconv(oldarg, namelen, ic_precompose, 0, NULL); - if (newarg) - argv[i] = newarg; - } + argv[i] = precompose_string_if_needed(argv[i]); i++; } - iconv_close(ic_precompose); + if (prefix) { + prefix = precompose_string_if_needed(prefix); + } + return prefix; } diff --git a/compat/precompose_utf8.h b/compat/precompose_utf8.h index 6f843d3e1a..d70b84665c 100644 --- a/compat/precompose_utf8.h +++ b/compat/precompose_utf8.h @@ -28,7 +28,7 @@ typedef struct { struct dirent_prec_psx *dirent_nfc; } PREC_DIR; -void precompose_argv(int argc, const char **argv); +const char *precompose_argv_prefix(int argc, const char **argv, const char *prefix); void probe_utf8_pathname_composition(void); PREC_DIR *precompose_utf8_opendir(const char *dirname); diff --git a/compat/simple-ipc/ipc-shared.c b/compat/simple-ipc/ipc-shared.c new file mode 100644 index 0000000000..1edec81595 --- /dev/null +++ b/compat/simple-ipc/ipc-shared.c @@ -0,0 +1,28 @@ +#include "cache.h" +#include "simple-ipc.h" +#include "strbuf.h" +#include "pkt-line.h" +#include "thread-utils.h" + +#ifdef SUPPORTS_SIMPLE_IPC + +int ipc_server_run(const char *path, const struct ipc_server_opts *opts, + ipc_server_application_cb *application_cb, + void *application_data) +{ + struct ipc_server_data *server_data = NULL; + int ret; + + ret = ipc_server_run_async(&server_data, path, opts, + application_cb, application_data); + if (ret) + return ret; + + ret = ipc_server_await(server_data); + + ipc_server_free(server_data); + + return ret; +} + +#endif /* SUPPORTS_SIMPLE_IPC */ diff --git a/compat/simple-ipc/ipc-unix-socket.c b/compat/simple-ipc/ipc-unix-socket.c new file mode 100644 index 0000000000..38689b278d --- /dev/null +++ b/compat/simple-ipc/ipc-unix-socket.c @@ -0,0 +1,999 @@ +#include "cache.h" +#include "simple-ipc.h" +#include "strbuf.h" +#include "pkt-line.h" +#include "thread-utils.h" +#include "unix-socket.h" +#include "unix-stream-server.h" + +#ifdef NO_UNIX_SOCKETS +#error compat/simple-ipc/ipc-unix-socket.c requires Unix sockets +#endif + +enum ipc_active_state ipc_get_active_state(const char *path) +{ + enum ipc_active_state state = IPC_STATE__OTHER_ERROR; + struct ipc_client_connect_options options + = IPC_CLIENT_CONNECT_OPTIONS_INIT; + struct stat st; + struct ipc_client_connection *connection_test = NULL; + + options.wait_if_busy = 0; + options.wait_if_not_found = 0; + + if (lstat(path, &st) == -1) { + switch (errno) { + case ENOENT: + case ENOTDIR: + return IPC_STATE__NOT_LISTENING; + default: + return IPC_STATE__INVALID_PATH; + } + } + + /* also complain if a plain file is in the way */ + if ((st.st_mode & S_IFMT) != S_IFSOCK) + return IPC_STATE__INVALID_PATH; + + /* + * Just because the filesystem has a S_IFSOCK type inode + * at `path`, doesn't mean it that there is a server listening. + * Ping it to be sure. + */ + state = ipc_client_try_connect(path, &options, &connection_test); + ipc_client_close_connection(connection_test); + + return state; +} + +/* + * Retry frequency when trying to connect to a server. + * + * This value should be short enough that we don't seriously delay our + * caller, but not fast enough that our spinning puts pressure on the + * system. + */ +#define WAIT_STEP_MS (50) + +/* + * Try to connect to the server. If the server is just starting up or + * is very busy, we may not get a connection the first time. + */ +static enum ipc_active_state connect_to_server( + const char *path, + int timeout_ms, + const struct ipc_client_connect_options *options, + int *pfd) +{ + int k; + + *pfd = -1; + + for (k = 0; k < timeout_ms; k += WAIT_STEP_MS) { + int fd = unix_stream_connect(path, options->uds_disallow_chdir); + + if (fd != -1) { + *pfd = fd; + return IPC_STATE__LISTENING; + } + + if (errno == ENOENT) { + if (!options->wait_if_not_found) + return IPC_STATE__PATH_NOT_FOUND; + + goto sleep_and_try_again; + } + + if (errno == ETIMEDOUT) { + if (!options->wait_if_busy) + return IPC_STATE__NOT_LISTENING; + + goto sleep_and_try_again; + } + + if (errno == ECONNREFUSED) { + if (!options->wait_if_busy) + return IPC_STATE__NOT_LISTENING; + + goto sleep_and_try_again; + } + + return IPC_STATE__OTHER_ERROR; + + sleep_and_try_again: + sleep_millisec(WAIT_STEP_MS); + } + + return IPC_STATE__NOT_LISTENING; +} + +/* + * The total amount of time that we are willing to wait when trying to + * connect to a server. + * + * When the server is first started, it might take a little while for + * it to become ready to service requests. Likewise, the server may + * be very (temporarily) busy and not respond to our connections. + * + * We should gracefully and silently handle those conditions and try + * again for a reasonable time period. + * + * The value chosen here should be long enough for the server + * to reliably heal from the above conditions. + */ +#define MY_CONNECTION_TIMEOUT_MS (1000) + +enum ipc_active_state ipc_client_try_connect( + const char *path, + const struct ipc_client_connect_options *options, + struct ipc_client_connection **p_connection) +{ + enum ipc_active_state state = IPC_STATE__OTHER_ERROR; + int fd = -1; + + *p_connection = NULL; + + trace2_region_enter("ipc-client", "try-connect", NULL); + trace2_data_string("ipc-client", NULL, "try-connect/path", path); + + state = connect_to_server(path, MY_CONNECTION_TIMEOUT_MS, + options, &fd); + + trace2_data_intmax("ipc-client", NULL, "try-connect/state", + (intmax_t)state); + trace2_region_leave("ipc-client", "try-connect", NULL); + + if (state == IPC_STATE__LISTENING) { + (*p_connection) = xcalloc(1, sizeof(struct ipc_client_connection)); + (*p_connection)->fd = fd; + } + + return state; +} + +void ipc_client_close_connection(struct ipc_client_connection *connection) +{ + if (!connection) + return; + + if (connection->fd != -1) + close(connection->fd); + + free(connection); +} + +int ipc_client_send_command_to_connection( + struct ipc_client_connection *connection, + const char *message, struct strbuf *answer) +{ + int ret = 0; + + strbuf_setlen(answer, 0); + + trace2_region_enter("ipc-client", "send-command", NULL); + + if (write_packetized_from_buf_no_flush(message, strlen(message), + connection->fd) < 0 || + packet_flush_gently(connection->fd) < 0) { + ret = error(_("could not send IPC command")); + goto done; + } + + if (read_packetized_to_strbuf( + connection->fd, answer, + PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR) < 0) { + ret = error(_("could not read IPC response")); + goto done; + } + +done: + trace2_region_leave("ipc-client", "send-command", NULL); + return ret; +} + +int ipc_client_send_command(const char *path, + const struct ipc_client_connect_options *options, + const char *message, struct strbuf *answer) +{ + int ret = -1; + enum ipc_active_state state; + struct ipc_client_connection *connection = NULL; + + state = ipc_client_try_connect(path, options, &connection); + + if (state != IPC_STATE__LISTENING) + return ret; + + ret = ipc_client_send_command_to_connection(connection, message, answer); + + ipc_client_close_connection(connection); + + return ret; +} + +static int set_socket_blocking_flag(int fd, int make_nonblocking) +{ + int flags; + + flags = fcntl(fd, F_GETFL, NULL); + + if (flags < 0) + return -1; + + if (make_nonblocking) + flags |= O_NONBLOCK; + else + flags &= ~O_NONBLOCK; + + return fcntl(fd, F_SETFL, flags); +} + +/* + * Magic numbers used to annotate callback instance data. + * These are used to help guard against accidentally passing the + * wrong instance data across multiple levels of callbacks (which + * is easy to do if there are `void*` arguments). + */ +enum magic { + MAGIC_SERVER_REPLY_DATA, + MAGIC_WORKER_THREAD_DATA, + MAGIC_ACCEPT_THREAD_DATA, + MAGIC_SERVER_DATA, +}; + +struct ipc_server_reply_data { + enum magic magic; + int fd; + struct ipc_worker_thread_data *worker_thread_data; +}; + +struct ipc_worker_thread_data { + enum magic magic; + struct ipc_worker_thread_data *next_thread; + struct ipc_server_data *server_data; + pthread_t pthread_id; +}; + +struct ipc_accept_thread_data { + enum magic magic; + struct ipc_server_data *server_data; + + struct unix_ss_socket *server_socket; + + int fd_send_shutdown; + int fd_wait_shutdown; + pthread_t pthread_id; +}; + +/* + * With unix-sockets, the conceptual "ipc-server" is implemented as a single + * controller "accept-thread" thread and a pool of "worker-thread" threads. + * The former does the usual `accept()` loop and dispatches connections + * to an idle worker thread. The worker threads wait in an idle loop for + * a new connection, communicate with the client and relay data to/from + * the `application_cb` and then wait for another connection from the + * server thread. This avoids the overhead of constantly creating and + * destroying threads. + */ +struct ipc_server_data { + enum magic magic; + ipc_server_application_cb *application_cb; + void *application_data; + struct strbuf buf_path; + + struct ipc_accept_thread_data *accept_thread; + struct ipc_worker_thread_data *worker_thread_list; + + pthread_mutex_t work_available_mutex; + pthread_cond_t work_available_cond; + + /* + * Accepted but not yet processed client connections are kept + * in a circular buffer FIFO. The queue is empty when the + * positions are equal. + */ + int *fifo_fds; + int queue_size; + int back_pos; + int front_pos; + + int shutdown_requested; + int is_stopped; +}; + +/* + * Remove and return the oldest queued connection. + * + * Returns -1 if empty. + */ +static int fifo_dequeue(struct ipc_server_data *server_data) +{ + /* ASSERT holding mutex */ + + int fd; + + if (server_data->back_pos == server_data->front_pos) + return -1; + + fd = server_data->fifo_fds[server_data->front_pos]; + server_data->fifo_fds[server_data->front_pos] = -1; + + server_data->front_pos++; + if (server_data->front_pos == server_data->queue_size) + server_data->front_pos = 0; + + return fd; +} + +/* + * Push a new fd onto the back of the queue. + * + * Drop it and return -1 if queue is already full. + */ +static int fifo_enqueue(struct ipc_server_data *server_data, int fd) +{ + /* ASSERT holding mutex */ + + int next_back_pos; + + next_back_pos = server_data->back_pos + 1; + if (next_back_pos == server_data->queue_size) + next_back_pos = 0; + + if (next_back_pos == server_data->front_pos) { + /* Queue is full. Just drop it. */ + close(fd); + return -1; + } + + server_data->fifo_fds[server_data->back_pos] = fd; + server_data->back_pos = next_back_pos; + + return fd; +} + +/* + * Wait for a connection to be queued to the FIFO and return it. + * + * Returns -1 if someone has already requested a shutdown. + */ +static int worker_thread__wait_for_connection( + struct ipc_worker_thread_data *worker_thread_data) +{ + /* ASSERT NOT holding mutex */ + + struct ipc_server_data *server_data = worker_thread_data->server_data; + int fd = -1; + + pthread_mutex_lock(&server_data->work_available_mutex); + for (;;) { + if (server_data->shutdown_requested) + break; + + fd = fifo_dequeue(server_data); + if (fd >= 0) + break; + + pthread_cond_wait(&server_data->work_available_cond, + &server_data->work_available_mutex); + } + pthread_mutex_unlock(&server_data->work_available_mutex); + + return fd; +} + +/* + * Forward declare our reply callback function so that any compiler + * errors are reported when we actually define the function (in addition + * to any errors reported when we try to pass this callback function as + * a parameter in a function call). The former are easier to understand. + */ +static ipc_server_reply_cb do_io_reply_callback; + +/* + * Relay application's response message to the client process. + * (We do not flush at this point because we allow the caller + * to chunk data to the client thru us.) + */ +static int do_io_reply_callback(struct ipc_server_reply_data *reply_data, + const char *response, size_t response_len) +{ + if (reply_data->magic != MAGIC_SERVER_REPLY_DATA) + BUG("reply_cb called with wrong instance data"); + + return write_packetized_from_buf_no_flush(response, response_len, + reply_data->fd); +} + +/* A randomly chosen value. */ +#define MY_WAIT_POLL_TIMEOUT_MS (10) + +/* + * If the client hangs up without sending any data on the wire, just + * quietly close the socket and ignore this client. + * + * This worker thread is committed to reading the IPC request data + * from the client at the other end of this fd. Wait here for the + * client to actually put something on the wire -- because if the + * client just does a ping (connect and hangup without sending any + * data), our use of the pkt-line read routines will spew an error + * message. + * + * Return -1 if the client hung up. + * Return 0 if data (possibly incomplete) is ready. + */ +static int worker_thread__wait_for_io_start( + struct ipc_worker_thread_data *worker_thread_data, + int fd) +{ + struct ipc_server_data *server_data = worker_thread_data->server_data; + struct pollfd pollfd[1]; + int result; + + for (;;) { + pollfd[0].fd = fd; + pollfd[0].events = POLLIN; + + result = poll(pollfd, 1, MY_WAIT_POLL_TIMEOUT_MS); + if (result < 0) { + if (errno == EINTR) + continue; + goto cleanup; + } + + if (result == 0) { + /* a timeout */ + + int in_shutdown; + + pthread_mutex_lock(&server_data->work_available_mutex); + in_shutdown = server_data->shutdown_requested; + pthread_mutex_unlock(&server_data->work_available_mutex); + + /* + * If a shutdown is already in progress and this + * client has not started talking yet, just drop it. + */ + if (in_shutdown) + goto cleanup; + continue; + } + + if (pollfd[0].revents & POLLHUP) + goto cleanup; + + if (pollfd[0].revents & POLLIN) + return 0; + + goto cleanup; + } + +cleanup: + close(fd); + return -1; +} + +/* + * Receive the request/command from the client and pass it to the + * registered request-callback. The request-callback will compose + * a response and call our reply-callback to send it to the client. + */ +static int worker_thread__do_io( + struct ipc_worker_thread_data *worker_thread_data, + int fd) +{ + /* ASSERT NOT holding lock */ + + struct strbuf buf = STRBUF_INIT; + struct ipc_server_reply_data reply_data; + int ret = 0; + + reply_data.magic = MAGIC_SERVER_REPLY_DATA; + reply_data.worker_thread_data = worker_thread_data; + + reply_data.fd = fd; + + ret = read_packetized_to_strbuf( + reply_data.fd, &buf, + PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR); + if (ret >= 0) { + ret = worker_thread_data->server_data->application_cb( + worker_thread_data->server_data->application_data, + buf.buf, do_io_reply_callback, &reply_data); + + packet_flush_gently(reply_data.fd); + } + else { + /* + * The client probably disconnected/shutdown before it + * could send a well-formed message. Ignore it. + */ + } + + strbuf_release(&buf); + close(reply_data.fd); + + return ret; +} + +/* + * Block SIGPIPE on the current thread (so that we get EPIPE from + * write() rather than an actual signal). + * + * Note that using sigchain_push() and _pop() to control SIGPIPE + * around our IO calls is not thread safe: + * [] It uses a global stack of handler frames. + * [] It uses ALLOC_GROW() to resize it. + * [] Finally, according to the `signal(2)` man-page: + * "The effects of `signal()` in a multithreaded process are unspecified." + */ +static void thread_block_sigpipe(sigset_t *old_set) +{ + sigset_t new_set; + + sigemptyset(&new_set); + sigaddset(&new_set, SIGPIPE); + + sigemptyset(old_set); + pthread_sigmask(SIG_BLOCK, &new_set, old_set); +} + +/* + * Thread proc for an IPC worker thread. It handles a series of + * connections from clients. It pulls the next fd from the queue + * processes it, and then waits for the next client. + * + * Block SIGPIPE in this worker thread for the life of the thread. + * This avoids stray (and sometimes delayed) SIGPIPE signals caused + * by client errors and/or when we are under extremely heavy IO load. + * + * This means that the application callback will have SIGPIPE blocked. + * The callback should not change it. + */ +static void *worker_thread_proc(void *_worker_thread_data) +{ + struct ipc_worker_thread_data *worker_thread_data = _worker_thread_data; + struct ipc_server_data *server_data = worker_thread_data->server_data; + sigset_t old_set; + int fd, io; + int ret; + + trace2_thread_start("ipc-worker"); + + thread_block_sigpipe(&old_set); + + for (;;) { + fd = worker_thread__wait_for_connection(worker_thread_data); + if (fd == -1) + break; /* in shutdown */ + + io = worker_thread__wait_for_io_start(worker_thread_data, fd); + if (io == -1) + continue; /* client hung up without sending anything */ + + ret = worker_thread__do_io(worker_thread_data, fd); + + if (ret == SIMPLE_IPC_QUIT) { + trace2_data_string("ipc-worker", NULL, "queue_stop_async", + "application_quit"); + /* + * The application layer is telling the ipc-server + * layer to shutdown. + * + * We DO NOT have a response to send to the client. + * + * Queue an async stop (to stop the other threads) and + * allow this worker thread to exit now (no sense waiting + * for the thread-pool shutdown signal). + * + * Other non-idle worker threads are allowed to finish + * responding to their current clients. + */ + ipc_server_stop_async(server_data); + break; + } + } + + trace2_thread_exit(); + return NULL; +} + +/* A randomly chosen value. */ +#define MY_ACCEPT_POLL_TIMEOUT_MS (60 * 1000) + +/* + * Accept a new client connection on our socket. This uses non-blocking + * IO so that we can also wait for shutdown requests on our socket-pair + * without actually spinning on a fast timeout. + */ +static int accept_thread__wait_for_connection( + struct ipc_accept_thread_data *accept_thread_data) +{ + struct pollfd pollfd[2]; + int result; + + for (;;) { + pollfd[0].fd = accept_thread_data->fd_wait_shutdown; + pollfd[0].events = POLLIN; + + pollfd[1].fd = accept_thread_data->server_socket->fd_socket; + pollfd[1].events = POLLIN; + + result = poll(pollfd, 2, MY_ACCEPT_POLL_TIMEOUT_MS); + if (result < 0) { + if (errno == EINTR) + continue; + return result; + } + + if (result == 0) { + /* a timeout */ + + /* + * If someone deletes or force-creates a new unix + * domain socket at our path, all future clients + * will be routed elsewhere and we silently starve. + * If that happens, just queue a shutdown. + */ + if (unix_ss_was_stolen( + accept_thread_data->server_socket)) { + trace2_data_string("ipc-accept", NULL, + "queue_stop_async", + "socket_stolen"); + ipc_server_stop_async( + accept_thread_data->server_data); + } + continue; + } + + if (pollfd[0].revents & POLLIN) { + /* shutdown message queued to socketpair */ + return -1; + } + + if (pollfd[1].revents & POLLIN) { + /* a connection is available on server_socket */ + + int client_fd = + accept(accept_thread_data->server_socket->fd_socket, + NULL, NULL); + if (client_fd >= 0) + return client_fd; + + /* + * An error here is unlikely -- it probably + * indicates that the connecting process has + * already dropped the connection. + */ + continue; + } + + BUG("unandled poll result errno=%d r[0]=%d r[1]=%d", + errno, pollfd[0].revents, pollfd[1].revents); + } +} + +/* + * Thread proc for the IPC server "accept thread". This waits for + * an incoming socket connection, appends it to the queue of available + * connections, and notifies a worker thread to process it. + * + * Block SIGPIPE in this thread for the life of the thread. This + * avoids any stray SIGPIPE signals when closing pipe fds under + * extremely heavy loads (such as when the fifo queue is full and we + * drop incomming connections). + */ +static void *accept_thread_proc(void *_accept_thread_data) +{ + struct ipc_accept_thread_data *accept_thread_data = _accept_thread_data; + struct ipc_server_data *server_data = accept_thread_data->server_data; + sigset_t old_set; + + trace2_thread_start("ipc-accept"); + + thread_block_sigpipe(&old_set); + + for (;;) { + int client_fd = accept_thread__wait_for_connection( + accept_thread_data); + + pthread_mutex_lock(&server_data->work_available_mutex); + if (server_data->shutdown_requested) { + pthread_mutex_unlock(&server_data->work_available_mutex); + if (client_fd >= 0) + close(client_fd); + break; + } + + if (client_fd < 0) { + /* ignore transient accept() errors */ + } + else { + fifo_enqueue(server_data, client_fd); + pthread_cond_broadcast(&server_data->work_available_cond); + } + pthread_mutex_unlock(&server_data->work_available_mutex); + } + + trace2_thread_exit(); + return NULL; +} + +/* + * We can't predict the connection arrival rate relative to the worker + * processing rate, therefore we allow the "accept-thread" to queue up + * a generous number of connections, since we'd rather have the client + * not unnecessarily timeout if we can avoid it. (The assumption is + * that this will be used for FSMonitor and a few second wait on a + * connection is better than having the client timeout and do the full + * computation itself.) + * + * The FIFO queue size is set to a multiple of the worker pool size. + * This value chosen at random. + */ +#define FIFO_SCALE (100) + +/* + * The backlog value for `listen(2)`. This doesn't need to huge, + * rather just large enough for our "accept-thread" to wake up and + * queue incoming connections onto the FIFO without the kernel + * dropping any. + * + * This value chosen at random. + */ +#define LISTEN_BACKLOG (50) + +static int create_listener_socket( + const char *path, + const struct ipc_server_opts *ipc_opts, + struct unix_ss_socket **new_server_socket) +{ + struct unix_ss_socket *server_socket = NULL; + struct unix_stream_listen_opts uslg_opts = UNIX_STREAM_LISTEN_OPTS_INIT; + int ret; + + uslg_opts.listen_backlog_size = LISTEN_BACKLOG; + uslg_opts.disallow_chdir = ipc_opts->uds_disallow_chdir; + + ret = unix_ss_create(path, &uslg_opts, -1, &server_socket); + if (ret) + return ret; + + if (set_socket_blocking_flag(server_socket->fd_socket, 1)) { + int saved_errno = errno; + unix_ss_free(server_socket); + errno = saved_errno; + return -1; + } + + *new_server_socket = server_socket; + + trace2_data_string("ipc-server", NULL, "listen-with-lock", path); + return 0; +} + +static int setup_listener_socket( + const char *path, + const struct ipc_server_opts *ipc_opts, + struct unix_ss_socket **new_server_socket) +{ + int ret, saved_errno; + + trace2_region_enter("ipc-server", "create-listener_socket", NULL); + + ret = create_listener_socket(path, ipc_opts, new_server_socket); + + saved_errno = errno; + trace2_region_leave("ipc-server", "create-listener_socket", NULL); + errno = saved_errno; + + return ret; +} + +/* + * Start IPC server in a pool of background threads. + */ +int ipc_server_run_async(struct ipc_server_data **returned_server_data, + const char *path, const struct ipc_server_opts *opts, + ipc_server_application_cb *application_cb, + void *application_data) +{ + struct unix_ss_socket *server_socket = NULL; + struct ipc_server_data *server_data; + int sv[2]; + int k; + int ret; + int nr_threads = opts->nr_threads; + + *returned_server_data = NULL; + + /* + * Create a socketpair and set sv[1] to non-blocking. This + * will used to send a shutdown message to the accept-thread + * and allows the accept-thread to wait on EITHER a client + * connection or a shutdown request without spinning. + */ + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) < 0) + return -1; + + if (set_socket_blocking_flag(sv[1], 1)) { + int saved_errno = errno; + close(sv[0]); + close(sv[1]); + errno = saved_errno; + return -1; + } + + ret = setup_listener_socket(path, opts, &server_socket); + if (ret) { + int saved_errno = errno; + close(sv[0]); + close(sv[1]); + errno = saved_errno; + return ret; + } + + server_data = xcalloc(1, sizeof(*server_data)); + server_data->magic = MAGIC_SERVER_DATA; + server_data->application_cb = application_cb; + server_data->application_data = application_data; + strbuf_init(&server_data->buf_path, 0); + strbuf_addstr(&server_data->buf_path, path); + + if (nr_threads < 1) + nr_threads = 1; + + pthread_mutex_init(&server_data->work_available_mutex, NULL); + pthread_cond_init(&server_data->work_available_cond, NULL); + + server_data->queue_size = nr_threads * FIFO_SCALE; + CALLOC_ARRAY(server_data->fifo_fds, server_data->queue_size); + + server_data->accept_thread = + xcalloc(1, sizeof(*server_data->accept_thread)); + server_data->accept_thread->magic = MAGIC_ACCEPT_THREAD_DATA; + server_data->accept_thread->server_data = server_data; + server_data->accept_thread->server_socket = server_socket; + server_data->accept_thread->fd_send_shutdown = sv[0]; + server_data->accept_thread->fd_wait_shutdown = sv[1]; + + if (pthread_create(&server_data->accept_thread->pthread_id, NULL, + accept_thread_proc, server_data->accept_thread)) + die_errno(_("could not start accept_thread '%s'"), path); + + for (k = 0; k < nr_threads; k++) { + struct ipc_worker_thread_data *wtd; + + wtd = xcalloc(1, sizeof(*wtd)); + wtd->magic = MAGIC_WORKER_THREAD_DATA; + wtd->server_data = server_data; + + if (pthread_create(&wtd->pthread_id, NULL, worker_thread_proc, + wtd)) { + if (k == 0) + die(_("could not start worker[0] for '%s'"), + path); + /* + * Limp along with the thread pool that we have. + */ + break; + } + + wtd->next_thread = server_data->worker_thread_list; + server_data->worker_thread_list = wtd; + } + + *returned_server_data = server_data; + return 0; +} + +/* + * Gently tell the IPC server treads to shutdown. + * Can be run on any thread. + */ +int ipc_server_stop_async(struct ipc_server_data *server_data) +{ + /* ASSERT NOT holding mutex */ + + int fd; + + if (!server_data) + return 0; + + trace2_region_enter("ipc-server", "server-stop-async", NULL); + + pthread_mutex_lock(&server_data->work_available_mutex); + + server_data->shutdown_requested = 1; + + /* + * Write a byte to the shutdown socket pair to wake up the + * accept-thread. + */ + if (write(server_data->accept_thread->fd_send_shutdown, "Q", 1) < 0) + error_errno("could not write to fd_send_shutdown"); + + /* + * Drain the queue of existing connections. + */ + while ((fd = fifo_dequeue(server_data)) != -1) + close(fd); + + /* + * Gently tell worker threads to stop processing new connections + * and exit. (This does not abort in-process conversations.) + */ + pthread_cond_broadcast(&server_data->work_available_cond); + + pthread_mutex_unlock(&server_data->work_available_mutex); + + trace2_region_leave("ipc-server", "server-stop-async", NULL); + + return 0; +} + +/* + * Wait for all IPC server threads to stop. + */ +int ipc_server_await(struct ipc_server_data *server_data) +{ + pthread_join(server_data->accept_thread->pthread_id, NULL); + + if (!server_data->shutdown_requested) + BUG("ipc-server: accept-thread stopped for '%s'", + server_data->buf_path.buf); + + while (server_data->worker_thread_list) { + struct ipc_worker_thread_data *wtd = + server_data->worker_thread_list; + + pthread_join(wtd->pthread_id, NULL); + + server_data->worker_thread_list = wtd->next_thread; + free(wtd); + } + + server_data->is_stopped = 1; + + return 0; +} + +void ipc_server_free(struct ipc_server_data *server_data) +{ + struct ipc_accept_thread_data * accept_thread_data; + + if (!server_data) + return; + + if (!server_data->is_stopped) + BUG("cannot free ipc-server while running for '%s'", + server_data->buf_path.buf); + + accept_thread_data = server_data->accept_thread; + if (accept_thread_data) { + unix_ss_free(accept_thread_data->server_socket); + + if (accept_thread_data->fd_send_shutdown != -1) + close(accept_thread_data->fd_send_shutdown); + if (accept_thread_data->fd_wait_shutdown != -1) + close(accept_thread_data->fd_wait_shutdown); + + free(server_data->accept_thread); + } + + while (server_data->worker_thread_list) { + struct ipc_worker_thread_data *wtd = + server_data->worker_thread_list; + + server_data->worker_thread_list = wtd->next_thread; + free(wtd); + } + + pthread_cond_destroy(&server_data->work_available_cond); + pthread_mutex_destroy(&server_data->work_available_mutex); + + strbuf_release(&server_data->buf_path); + + free(server_data->fifo_fds); + free(server_data); +} diff --git a/compat/simple-ipc/ipc-win32.c b/compat/simple-ipc/ipc-win32.c new file mode 100644 index 0000000000..8f89c02037 --- /dev/null +++ b/compat/simple-ipc/ipc-win32.c @@ -0,0 +1,751 @@ +#include "cache.h" +#include "simple-ipc.h" +#include "strbuf.h" +#include "pkt-line.h" +#include "thread-utils.h" + +#ifndef GIT_WINDOWS_NATIVE +#error This file can only be compiled on Windows +#endif + +static int initialize_pipe_name(const char *path, wchar_t *wpath, size_t alloc) +{ + int off = 0; + struct strbuf realpath = STRBUF_INIT; + + if (!strbuf_realpath(&realpath, path, 0)) + return -1; + + off = swprintf(wpath, alloc, L"\\\\.\\pipe\\"); + if (xutftowcs(wpath + off, realpath.buf, alloc - off) < 0) + return -1; + + /* Handle drive prefix */ + if (wpath[off] && wpath[off + 1] == L':') { + wpath[off + 1] = L'_'; + off += 2; + } + + for (; wpath[off]; off++) + if (wpath[off] == L'/') + wpath[off] = L'\\'; + + strbuf_release(&realpath); + return 0; +} + +static enum ipc_active_state get_active_state(wchar_t *pipe_path) +{ + if (WaitNamedPipeW(pipe_path, NMPWAIT_USE_DEFAULT_WAIT)) + return IPC_STATE__LISTENING; + + if (GetLastError() == ERROR_SEM_TIMEOUT) + return IPC_STATE__NOT_LISTENING; + + if (GetLastError() == ERROR_FILE_NOT_FOUND) + return IPC_STATE__PATH_NOT_FOUND; + + return IPC_STATE__OTHER_ERROR; +} + +enum ipc_active_state ipc_get_active_state(const char *path) +{ + wchar_t pipe_path[MAX_PATH]; + + if (initialize_pipe_name(path, pipe_path, ARRAY_SIZE(pipe_path)) < 0) + return IPC_STATE__INVALID_PATH; + + return get_active_state(pipe_path); +} + +#define WAIT_STEP_MS (50) + +static enum ipc_active_state connect_to_server( + const wchar_t *wpath, + DWORD timeout_ms, + const struct ipc_client_connect_options *options, + int *pfd) +{ + DWORD t_start_ms, t_waited_ms; + DWORD step_ms; + HANDLE hPipe = INVALID_HANDLE_VALUE; + DWORD mode = PIPE_READMODE_BYTE; + DWORD gle; + + *pfd = -1; + + for (;;) { + hPipe = CreateFileW(wpath, GENERIC_READ | GENERIC_WRITE, + 0, NULL, OPEN_EXISTING, 0, NULL); + if (hPipe != INVALID_HANDLE_VALUE) + break; + + gle = GetLastError(); + + switch (gle) { + case ERROR_FILE_NOT_FOUND: + if (!options->wait_if_not_found) + return IPC_STATE__PATH_NOT_FOUND; + if (!timeout_ms) + return IPC_STATE__PATH_NOT_FOUND; + + step_ms = (timeout_ms < WAIT_STEP_MS) ? + timeout_ms : WAIT_STEP_MS; + sleep_millisec(step_ms); + + timeout_ms -= step_ms; + break; /* try again */ + + case ERROR_PIPE_BUSY: + if (!options->wait_if_busy) + return IPC_STATE__NOT_LISTENING; + if (!timeout_ms) + return IPC_STATE__NOT_LISTENING; + + t_start_ms = (DWORD)(getnanotime() / 1000000); + + if (!WaitNamedPipeW(wpath, timeout_ms)) { + if (GetLastError() == ERROR_SEM_TIMEOUT) + return IPC_STATE__NOT_LISTENING; + + return IPC_STATE__OTHER_ERROR; + } + + /* + * A pipe server instance became available. + * Race other client processes to connect to + * it. + * + * But first decrement our overall timeout so + * that we don't starve if we keep losing the + * race. But also guard against special + * NPMWAIT_ values (0 and -1). + */ + t_waited_ms = (DWORD)(getnanotime() / 1000000) - t_start_ms; + if (t_waited_ms < timeout_ms) + timeout_ms -= t_waited_ms; + else + timeout_ms = 1; + break; /* try again */ + + default: + return IPC_STATE__OTHER_ERROR; + } + } + + if (!SetNamedPipeHandleState(hPipe, &mode, NULL, NULL)) { + CloseHandle(hPipe); + return IPC_STATE__OTHER_ERROR; + } + + *pfd = _open_osfhandle((intptr_t)hPipe, O_RDWR|O_BINARY); + if (*pfd < 0) { + CloseHandle(hPipe); + return IPC_STATE__OTHER_ERROR; + } + + /* fd now owns hPipe */ + + return IPC_STATE__LISTENING; +} + +/* + * The default connection timeout for Windows clients. + * + * This is not currently part of the ipc_ API (nor the config settings) + * because of differences between Windows and other platforms. + * + * This value was chosen at random. + */ +#define WINDOWS_CONNECTION_TIMEOUT_MS (30000) + +enum ipc_active_state ipc_client_try_connect( + const char *path, + const struct ipc_client_connect_options *options, + struct ipc_client_connection **p_connection) +{ + wchar_t wpath[MAX_PATH]; + enum ipc_active_state state = IPC_STATE__OTHER_ERROR; + int fd = -1; + + *p_connection = NULL; + + trace2_region_enter("ipc-client", "try-connect", NULL); + trace2_data_string("ipc-client", NULL, "try-connect/path", path); + + if (initialize_pipe_name(path, wpath, ARRAY_SIZE(wpath)) < 0) + state = IPC_STATE__INVALID_PATH; + else + state = connect_to_server(wpath, WINDOWS_CONNECTION_TIMEOUT_MS, + options, &fd); + + trace2_data_intmax("ipc-client", NULL, "try-connect/state", + (intmax_t)state); + trace2_region_leave("ipc-client", "try-connect", NULL); + + if (state == IPC_STATE__LISTENING) { + (*p_connection) = xcalloc(1, sizeof(struct ipc_client_connection)); + (*p_connection)->fd = fd; + } + + return state; +} + +void ipc_client_close_connection(struct ipc_client_connection *connection) +{ + if (!connection) + return; + + if (connection->fd != -1) + close(connection->fd); + + free(connection); +} + +int ipc_client_send_command_to_connection( + struct ipc_client_connection *connection, + const char *message, struct strbuf *answer) +{ + int ret = 0; + + strbuf_setlen(answer, 0); + + trace2_region_enter("ipc-client", "send-command", NULL); + + if (write_packetized_from_buf_no_flush(message, strlen(message), + connection->fd) < 0 || + packet_flush_gently(connection->fd) < 0) { + ret = error(_("could not send IPC command")); + goto done; + } + + FlushFileBuffers((HANDLE)_get_osfhandle(connection->fd)); + + if (read_packetized_to_strbuf( + connection->fd, answer, + PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR) < 0) { + ret = error(_("could not read IPC response")); + goto done; + } + +done: + trace2_region_leave("ipc-client", "send-command", NULL); + return ret; +} + +int ipc_client_send_command(const char *path, + const struct ipc_client_connect_options *options, + const char *message, struct strbuf *response) +{ + int ret = -1; + enum ipc_active_state state; + struct ipc_client_connection *connection = NULL; + + state = ipc_client_try_connect(path, options, &connection); + + if (state != IPC_STATE__LISTENING) + return ret; + + ret = ipc_client_send_command_to_connection(connection, message, response); + + ipc_client_close_connection(connection); + + return ret; +} + +/* + * Duplicate the given pipe handle and wrap it in a file descriptor so + * that we can use pkt-line on it. + */ +static int dup_fd_from_pipe(const HANDLE pipe) +{ + HANDLE process = GetCurrentProcess(); + HANDLE handle; + int fd; + + if (!DuplicateHandle(process, pipe, process, &handle, 0, FALSE, + DUPLICATE_SAME_ACCESS)) { + errno = err_win_to_posix(GetLastError()); + return -1; + } + + fd = _open_osfhandle((intptr_t)handle, O_RDWR|O_BINARY); + if (fd < 0) { + errno = err_win_to_posix(GetLastError()); + CloseHandle(handle); + return -1; + } + + /* + * `handle` is now owned by `fd` and will be automatically closed + * when the descriptor is closed. + */ + + return fd; +} + +/* + * Magic numbers used to annotate callback instance data. + * These are used to help guard against accidentally passing the + * wrong instance data across multiple levels of callbacks (which + * is easy to do if there are `void*` arguments). + */ +enum magic { + MAGIC_SERVER_REPLY_DATA, + MAGIC_SERVER_THREAD_DATA, + MAGIC_SERVER_DATA, +}; + +struct ipc_server_reply_data { + enum magic magic; + int fd; + struct ipc_server_thread_data *server_thread_data; +}; + +struct ipc_server_thread_data { + enum magic magic; + struct ipc_server_thread_data *next_thread; + struct ipc_server_data *server_data; + pthread_t pthread_id; + HANDLE hPipe; +}; + +/* + * On Windows, the conceptual "ipc-server" is implemented as a pool of + * n idential/peer "server-thread" threads. That is, there is no + * hierarchy of threads; and therefore no controller thread managing + * the pool. Each thread has an independent handle to the named pipe, + * receives incoming connections, processes the client, and re-uses + * the pipe for the next client connection. + * + * Therefore, the "ipc-server" only needs to maintain a list of the + * spawned threads for eventual "join" purposes. + * + * A single "stop-event" is visible to all of the server threads to + * tell them to shutdown (when idle). + */ +struct ipc_server_data { + enum magic magic; + ipc_server_application_cb *application_cb; + void *application_data; + struct strbuf buf_path; + wchar_t wpath[MAX_PATH]; + + HANDLE hEventStopRequested; + struct ipc_server_thread_data *thread_list; + int is_stopped; +}; + +enum connect_result { + CR_CONNECTED = 0, + CR_CONNECT_PENDING, + CR_CONNECT_ERROR, + CR_WAIT_ERROR, + CR_SHUTDOWN, +}; + +static enum connect_result queue_overlapped_connect( + struct ipc_server_thread_data *server_thread_data, + OVERLAPPED *lpo) +{ + if (ConnectNamedPipe(server_thread_data->hPipe, lpo)) + goto failed; + + switch (GetLastError()) { + case ERROR_IO_PENDING: + return CR_CONNECT_PENDING; + + case ERROR_PIPE_CONNECTED: + SetEvent(lpo->hEvent); + return CR_CONNECTED; + + default: + break; + } + +failed: + error(_("ConnectNamedPipe failed for '%s' (%lu)"), + server_thread_data->server_data->buf_path.buf, + GetLastError()); + return CR_CONNECT_ERROR; +} + +/* + * Use Windows Overlapped IO to wait for a connection or for our event + * to be signalled. + */ +static enum connect_result wait_for_connection( + struct ipc_server_thread_data *server_thread_data, + OVERLAPPED *lpo) +{ + enum connect_result r; + HANDLE waitHandles[2]; + DWORD dwWaitResult; + + r = queue_overlapped_connect(server_thread_data, lpo); + if (r != CR_CONNECT_PENDING) + return r; + + waitHandles[0] = server_thread_data->server_data->hEventStopRequested; + waitHandles[1] = lpo->hEvent; + + dwWaitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE); + switch (dwWaitResult) { + case WAIT_OBJECT_0 + 0: + return CR_SHUTDOWN; + + case WAIT_OBJECT_0 + 1: + ResetEvent(lpo->hEvent); + return CR_CONNECTED; + + default: + return CR_WAIT_ERROR; + } +} + +/* + * Forward declare our reply callback function so that any compiler + * errors are reported when we actually define the function (in addition + * to any errors reported when we try to pass this callback function as + * a parameter in a function call). The former are easier to understand. + */ +static ipc_server_reply_cb do_io_reply_callback; + +/* + * Relay application's response message to the client process. + * (We do not flush at this point because we allow the caller + * to chunk data to the client thru us.) + */ +static int do_io_reply_callback(struct ipc_server_reply_data *reply_data, + const char *response, size_t response_len) +{ + if (reply_data->magic != MAGIC_SERVER_REPLY_DATA) + BUG("reply_cb called with wrong instance data"); + + return write_packetized_from_buf_no_flush(response, response_len, + reply_data->fd); +} + +/* + * Receive the request/command from the client and pass it to the + * registered request-callback. The request-callback will compose + * a response and call our reply-callback to send it to the client. + * + * Simple-IPC only contains one round trip, so we flush and close + * here after the response. + */ +static int do_io(struct ipc_server_thread_data *server_thread_data) +{ + struct strbuf buf = STRBUF_INIT; + struct ipc_server_reply_data reply_data; + int ret = 0; + + reply_data.magic = MAGIC_SERVER_REPLY_DATA; + reply_data.server_thread_data = server_thread_data; + + reply_data.fd = dup_fd_from_pipe(server_thread_data->hPipe); + if (reply_data.fd < 0) + return error(_("could not create fd from pipe for '%s'"), + server_thread_data->server_data->buf_path.buf); + + ret = read_packetized_to_strbuf( + reply_data.fd, &buf, + PACKET_READ_GENTLE_ON_EOF | PACKET_READ_GENTLE_ON_READ_ERROR); + if (ret >= 0) { + ret = server_thread_data->server_data->application_cb( + server_thread_data->server_data->application_data, + buf.buf, do_io_reply_callback, &reply_data); + + packet_flush_gently(reply_data.fd); + + FlushFileBuffers((HANDLE)_get_osfhandle((reply_data.fd))); + } + else { + /* + * The client probably disconnected/shutdown before it + * could send a well-formed message. Ignore it. + */ + } + + strbuf_release(&buf); + close(reply_data.fd); + + return ret; +} + +/* + * Handle IPC request and response with this connected client. And reset + * the pipe to prepare for the next client. + */ +static int use_connection(struct ipc_server_thread_data *server_thread_data) +{ + int ret; + + ret = do_io(server_thread_data); + + FlushFileBuffers(server_thread_data->hPipe); + DisconnectNamedPipe(server_thread_data->hPipe); + + return ret; +} + +/* + * Thread proc for an IPC server worker thread. It handles a series of + * connections from clients. It cleans and reuses the hPipe between each + * client. + */ +static void *server_thread_proc(void *_server_thread_data) +{ + struct ipc_server_thread_data *server_thread_data = _server_thread_data; + HANDLE hEventConnected = INVALID_HANDLE_VALUE; + OVERLAPPED oConnect; + enum connect_result cr; + int ret; + + assert(server_thread_data->hPipe != INVALID_HANDLE_VALUE); + + trace2_thread_start("ipc-server"); + trace2_data_string("ipc-server", NULL, "pipe", + server_thread_data->server_data->buf_path.buf); + + hEventConnected = CreateEventW(NULL, TRUE, FALSE, NULL); + + memset(&oConnect, 0, sizeof(oConnect)); + oConnect.hEvent = hEventConnected; + + for (;;) { + cr = wait_for_connection(server_thread_data, &oConnect); + + switch (cr) { + case CR_SHUTDOWN: + goto finished; + + case CR_CONNECTED: + ret = use_connection(server_thread_data); + if (ret == SIMPLE_IPC_QUIT) { + ipc_server_stop_async( + server_thread_data->server_data); + goto finished; + } + if (ret > 0) { + /* + * Ignore (transient) IO errors with this + * client and reset for the next client. + */ + } + break; + + case CR_CONNECT_PENDING: + /* By construction, this should not happen. */ + BUG("ipc-server[%s]: unexpeced CR_CONNECT_PENDING", + server_thread_data->server_data->buf_path.buf); + + case CR_CONNECT_ERROR: + case CR_WAIT_ERROR: + /* + * Ignore these theoretical errors. + */ + DisconnectNamedPipe(server_thread_data->hPipe); + break; + + default: + BUG("unandled case after wait_for_connection"); + } + } + +finished: + CloseHandle(server_thread_data->hPipe); + CloseHandle(hEventConnected); + + trace2_thread_exit(); + return NULL; +} + +static HANDLE create_new_pipe(wchar_t *wpath, int is_first) +{ + HANDLE hPipe; + DWORD dwOpenMode, dwPipeMode; + LPSECURITY_ATTRIBUTES lpsa = NULL; + + dwOpenMode = PIPE_ACCESS_INBOUND | PIPE_ACCESS_OUTBOUND | + FILE_FLAG_OVERLAPPED; + + dwPipeMode = PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE | PIPE_WAIT | + PIPE_REJECT_REMOTE_CLIENTS; + + if (is_first) { + dwOpenMode |= FILE_FLAG_FIRST_PIPE_INSTANCE; + + /* + * On Windows, the first server pipe instance gets to + * set the ACL / Security Attributes on the named + * pipe; subsequent instances inherit and cannot + * change them. + * + * TODO Should we allow the application layer to + * specify security attributes, such as `LocalService` + * or `LocalSystem`, when we create the named pipe? + * This question is probably not important when the + * daemon is started by a foreground user process and + * only needs to talk to the current user, but may be + * if the daemon is run via the Control Panel as a + * System Service. + */ + } + + hPipe = CreateNamedPipeW(wpath, dwOpenMode, dwPipeMode, + PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, lpsa); + + return hPipe; +} + +int ipc_server_run_async(struct ipc_server_data **returned_server_data, + const char *path, const struct ipc_server_opts *opts, + ipc_server_application_cb *application_cb, + void *application_data) +{ + struct ipc_server_data *server_data; + wchar_t wpath[MAX_PATH]; + HANDLE hPipeFirst = INVALID_HANDLE_VALUE; + int k; + int ret = 0; + int nr_threads = opts->nr_threads; + + *returned_server_data = NULL; + + ret = initialize_pipe_name(path, wpath, ARRAY_SIZE(wpath)); + if (ret < 0) { + errno = EINVAL; + return -1; + } + + hPipeFirst = create_new_pipe(wpath, 1); + if (hPipeFirst == INVALID_HANDLE_VALUE) { + errno = EADDRINUSE; + return -2; + } + + server_data = xcalloc(1, sizeof(*server_data)); + server_data->magic = MAGIC_SERVER_DATA; + server_data->application_cb = application_cb; + server_data->application_data = application_data; + server_data->hEventStopRequested = CreateEvent(NULL, TRUE, FALSE, NULL); + strbuf_init(&server_data->buf_path, 0); + strbuf_addstr(&server_data->buf_path, path); + wcscpy(server_data->wpath, wpath); + + if (nr_threads < 1) + nr_threads = 1; + + for (k = 0; k < nr_threads; k++) { + struct ipc_server_thread_data *std; + + std = xcalloc(1, sizeof(*std)); + std->magic = MAGIC_SERVER_THREAD_DATA; + std->server_data = server_data; + std->hPipe = INVALID_HANDLE_VALUE; + + std->hPipe = (k == 0) + ? hPipeFirst + : create_new_pipe(server_data->wpath, 0); + + if (std->hPipe == INVALID_HANDLE_VALUE) { + /* + * If we've reached a pipe instance limit for + * this path, just use fewer threads. + */ + free(std); + break; + } + + if (pthread_create(&std->pthread_id, NULL, + server_thread_proc, std)) { + /* + * Likewise, if we're out of threads, just use + * fewer threads than requested. + * + * However, we just give up if we can't even get + * one thread. This should not happen. + */ + if (k == 0) + die(_("could not start thread[0] for '%s'"), + path); + + CloseHandle(std->hPipe); + free(std); + break; + } + + std->next_thread = server_data->thread_list; + server_data->thread_list = std; + } + + *returned_server_data = server_data; + return 0; +} + +int ipc_server_stop_async(struct ipc_server_data *server_data) +{ + if (!server_data) + return 0; + + /* + * Gently tell all of the ipc_server threads to shutdown. + * This will be seen the next time they are idle (and waiting + * for a connection). + * + * We DO NOT attempt to force them to drop an active connection. + */ + SetEvent(server_data->hEventStopRequested); + return 0; +} + +int ipc_server_await(struct ipc_server_data *server_data) +{ + DWORD dwWaitResult; + + if (!server_data) + return 0; + + dwWaitResult = WaitForSingleObject(server_data->hEventStopRequested, INFINITE); + if (dwWaitResult != WAIT_OBJECT_0) + return error(_("wait for hEvent failed for '%s'"), + server_data->buf_path.buf); + + while (server_data->thread_list) { + struct ipc_server_thread_data *std = server_data->thread_list; + + pthread_join(std->pthread_id, NULL); + + server_data->thread_list = std->next_thread; + free(std); + } + + server_data->is_stopped = 1; + + return 0; +} + +void ipc_server_free(struct ipc_server_data *server_data) +{ + if (!server_data) + return; + + if (!server_data->is_stopped) + BUG("cannot free ipc-server while running for '%s'", + server_data->buf_path.buf); + + strbuf_release(&server_data->buf_path); + + if (server_data->hEventStopRequested != INVALID_HANDLE_VALUE) + CloseHandle(server_data->hEventStopRequested); + + while (server_data->thread_list) { + struct ipc_server_thread_data *std = server_data->thread_list; + + server_data->thread_list = std->next_thread; + free(std); + } + + free(server_data); +} diff --git a/config.c b/config.c index b922b4f285..6428393a41 100644 --- a/config.c +++ b/config.c @@ -1180,6 +1180,20 @@ static void die_bad_number(const char *name, const char *value) } } +NORETURN +static void die_bad_bool(const char *name, const char *value) +{ + if (!strcmp(name, "GIT_TEST_GETTEXT_POISON")) + /* + * We explicitly *don't* use _() here since it would + * cause an infinite loop with _() needing to call + * use_gettext_poison(). + */ + die("bad boolean config value '%s' for '%s'", value, name); + else + die(_("bad boolean config value '%s' for '%s'"), value, name); +} + int git_config_int(const char *name, const char *value) { int ret; @@ -1252,8 +1266,10 @@ int git_config_bool_or_int(const char *name, const char *value, int *is_bool) int git_config_bool(const char *name, const char *value) { - int discard; - return !!git_config_bool_or_int(name, value, &discard); + int v = git_parse_maybe_bool(value); + if (v < 0) + die_bad_bool(name, value); + return v; } int git_config_string(const char **dest, const char *var, const char *value) @@ -2253,7 +2269,7 @@ static void repo_read_config(struct repository *repo) opts.git_dir = repo->gitdir; if (!repo->config) - repo->config = xcalloc(1, sizeof(struct config_set)); + CALLOC_ARRAY(repo->config, 1); else git_configset_clear(repo->config); diff --git a/config.mak.uname b/config.mak.uname index e22d4b6d67..cb443b4e02 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -124,6 +124,9 @@ ifeq ($(uname_S),Darwin) ifeq ($(shell test "`expr "$(uname_R)" : '\([0-9][0-9]*\)\.'`" -ge 11 && echo 1),1) HAVE_GETDELIM = YesPlease endif + ifeq ($(shell test "`expr "$(uname_R)" : '\([0-9][0-9]*\)\.'`" -ge 20 && echo 1),1) + OPEN_RETURNS_EINTR = UnfortunatelyYes + endif NO_MEMMEM = YesPlease USE_ST_TIMESPEC = YesPlease HAVE_DEV_TTY = YesPlease @@ -421,6 +424,7 @@ ifeq ($(uname_S),Windows) RUNTIME_PREFIX = YesPlease HAVE_WPGMPTR = YesWeDo NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease + USE_WIN32_IPC = YesPlease USE_WIN32_MMAP = YesPlease MMAP_PREVENTS_DELETE = UnfortunatelyYes # USE_NED_ALLOCATOR = YesPlease @@ -597,6 +601,7 @@ ifneq (,$(findstring MINGW,$(uname_S))) RUNTIME_PREFIX = YesPlease HAVE_WPGMPTR = YesWeDo NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease + USE_WIN32_IPC = YesPlease USE_WIN32_MMAP = YesPlease MMAP_PREVENTS_DELETE = UnfortunatelyYes USE_NED_ALLOCATOR = YesPlease diff --git a/connect.c b/connect.c index 9c97fee430..40b5c15f81 100644 --- a/connect.c +++ b/connect.c @@ -376,7 +376,8 @@ struct ref **get_remote_heads(struct packet_reader *reader, } /* Returns 1 when a valid ref has been added to `list`, 0 otherwise */ -static int process_ref_v2(struct packet_reader *reader, struct ref ***list) +static int process_ref_v2(struct packet_reader *reader, struct ref ***list, + char **unborn_head_target) { int ret = 1; int i = 0; @@ -397,6 +398,25 @@ static int process_ref_v2(struct packet_reader *reader, struct ref ***list) goto out; } + if (!strcmp("unborn", line_sections.items[i].string)) { + i++; + if (unborn_head_target && + !strcmp("HEAD", line_sections.items[i++].string)) { + /* + * Look for the symref target (if any). If found, + * return it to the caller. + */ + for (; i < line_sections.nr; i++) { + const char *arg = line_sections.items[i].string; + + if (skip_prefix(arg, "symref-target:", &arg)) { + *unborn_head_target = xstrdup(arg); + break; + } + } + } + goto out; + } if (parse_oid_hex_algop(line_sections.items[i++].string, &old_oid, &end, reader->hash_algo) || *end) { ret = 0; @@ -453,12 +473,16 @@ void check_stateless_delimiter(int stateless_rpc, struct ref **get_remote_refs(int fd_out, struct packet_reader *reader, struct ref **list, int for_push, - const struct strvec *ref_prefixes, + struct transport_ls_refs_options *transport_options, const struct string_list *server_options, int stateless_rpc) { int i; const char *hash_name; + struct strvec *ref_prefixes = transport_options ? + &transport_options->ref_prefixes : NULL; + char **unborn_head_target = transport_options ? + &transport_options->unborn_head_target : NULL; *list = NULL; if (server_supports_v2("ls-refs", 1)) @@ -488,6 +512,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader, if (!for_push) packet_write_fmt(fd_out, "peel\n"); packet_write_fmt(fd_out, "symrefs\n"); + if (server_supports_feature("ls-refs", "unborn", 0)) + packet_write_fmt(fd_out, "unborn\n"); for (i = 0; ref_prefixes && i < ref_prefixes->nr; i++) { packet_write_fmt(fd_out, "ref-prefix %s\n", ref_prefixes->v[i]); @@ -496,7 +522,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader, /* Process response from server */ while (packet_reader_read(reader) == PACKET_READ_NORMAL) { - if (!process_ref_v2(reader, &list)) + if (!process_ref_v2(reader, &list, unborn_head_target)) die(_("invalid ls-refs response: %s"), reader->line); } diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt index ac3dbc079a..75ed198a6a 100644 --- a/contrib/buildsystems/CMakeLists.txt +++ b/contrib/buildsystems/CMakeLists.txt @@ -58,6 +58,10 @@ if(WIN32) # In the vcpkg edition, we need this to be able to link to libcurl set(CURL_NO_CURL_CMAKE ON) + + # Copy the necessary vcpkg DLLs (like iconv) to the install dir + set(X_VCPKG_APPLOCAL_DEPS_INSTALL ON) + set(CMAKE_TOOLCHAIN_FILE ${VCPKG_DIR}/scripts/buildsystems/vcpkg.cmake CACHE STRING "Vcpkg toolchain file") endif() find_program(SH_EXE sh PATHS "C:/Program Files/Git/bin") @@ -243,7 +247,13 @@ 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) + list(APPEND compat_SOURCES unix-socket.c unix-stream-server.c) +endif() + +if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + list(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-win32.c) +else() + list(APPEND compat_SOURCES compat/simple-ipc/ipc-shared.c compat/simple-ipc/ipc-unix-socket.c) endif() set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX}) @@ -685,13 +695,17 @@ endif() parse_makefile_for_executables(git_builtin_extra "BUILT_INS") +option(SKIP_DASHED_BUILT_INS "Skip hardlinking the dashed versions of the built-ins") + #Creating hardlinks +if(NOT SKIP_DASHED_BUILT_INS) foreach(s ${git_SOURCES} ${git_builtin_extra}) string(REPLACE "${CMAKE_SOURCE_DIR}/builtin/" "" s ${s}) string(REPLACE ".c" "" s ${s}) file(APPEND ${CMAKE_BINARY_DIR}/CreateLinks.cmake "file(CREATE_LINK git${EXE_EXTENSION} git-${s}${EXE_EXTENSION})\n") list(APPEND git_links ${CMAKE_BINARY_DIR}/git-${s}${EXE_EXTENSION}) endforeach() +endif() if(CURL_FOUND) set(remote_exes @@ -807,15 +821,19 @@ list(TRANSFORM git_shell_scripts PREPEND "${CMAKE_BINARY_DIR}/") list(TRANSFORM git_perl_scripts PREPEND "${CMAKE_BINARY_DIR}/") #install -install(TARGETS git git-shell +foreach(program ${PROGRAMS_BUILT}) +if(program STREQUAL "git" OR program STREQUAL "git-shell") +install(TARGETS ${program} RUNTIME DESTINATION bin) +else() +install(TARGETS ${program} + RUNTIME DESTINATION libexec/git-core) +endif() +endforeach() + install(PROGRAMS ${CMAKE_BINARY_DIR}/git-cvsserver DESTINATION bin) -list(REMOVE_ITEM PROGRAMS_BUILT git git-shell) -install(TARGETS ${PROGRAMS_BUILT} - RUNTIME DESTINATION libexec/git-core) - set(bin_links git-receive-pack git-upload-archive git-upload-pack) @@ -828,12 +846,12 @@ install(CODE "file(CREATE_LINK ${CMAKE_INSTALL_PREFIX}/bin/git-shell${EXE_EXTENS foreach(b ${git_links}) string(REPLACE "${CMAKE_BINARY_DIR}" "" b ${b}) - install(CODE "file(CREATE_LINK ${CMAKE_INSTALL_PREFIX}/bin/git${EXE_EXTENSION} ${CMAKE_INSTALL_PREFIX}/libexec/git-core/${b}${EXE_EXTENSION})") + install(CODE "file(CREATE_LINK ${CMAKE_INSTALL_PREFIX}/bin/git${EXE_EXTENSION} ${CMAKE_INSTALL_PREFIX}/libexec/git-core/${b})") endforeach() foreach(b ${git_http_links}) string(REPLACE "${CMAKE_BINARY_DIR}" "" b ${b}) - install(CODE "file(CREATE_LINK ${CMAKE_INSTALL_PREFIX}/libexec/git-core/git-remote-http${EXE_EXTENSION} ${CMAKE_INSTALL_PREFIX}/libexec/git-core/${b}${EXE_EXTENSION})") + install(CODE "file(CREATE_LINK ${CMAKE_INSTALL_PREFIX}/libexec/git-core/git-remote-http${EXE_EXTENSION} ${CMAKE_INSTALL_PREFIX}/libexec/git-core/${b})") endforeach() install(PROGRAMS ${git_shell_scripts} ${git_perl_scripts} ${CMAKE_BINARY_DIR}/git-p4 diff --git a/contrib/coccinelle/array.cocci b/contrib/coccinelle/array.cocci index 46b8d2ee11..9a4f00cb1b 100644 --- a/contrib/coccinelle/array.cocci +++ b/contrib/coccinelle/array.cocci @@ -88,3 +88,11 @@ expression n; @@ - ptr = xmalloc((n) * sizeof(T)); + ALLOC_ARRAY(ptr, n); + +@@ +type T; +T *ptr; +expression n != 1; +@@ +- ptr = xcalloc(n, \( sizeof(*ptr) \| sizeof(T) \) ) ++ CALLOC_ARRAY(ptr, n) diff --git a/contrib/coccinelle/xcalloc.cocci b/contrib/coccinelle/xcalloc.cocci new file mode 100644 index 0000000000..c291011607 --- /dev/null +++ b/contrib/coccinelle/xcalloc.cocci @@ -0,0 +1,10 @@ +@@ +type T; +T *ptr; +expression n; +@@ + xcalloc( ++ n, + \( sizeof(T) \| sizeof(*ptr) \) +- , n + ) diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 4b1f4264a6..e1a66954fe 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1447,8 +1447,10 @@ _git_branch () while [ $c -lt $cword ]; do i="${words[c]}" case "$i" in - -d|--delete|-m|--move) only_local_ref="y" ;; - -r|--remotes) has_r="y" ;; + -d|-D|--delete|-m|-M|--move|-c|-C|--copy) + only_local_ref="y" ;; + -r|--remotes) + has_r="y" ;; esac ((c++)) done @@ -3051,7 +3053,7 @@ _git_stash () __gitcomp "--name-status --oneline --patch-with-stat" ;; show,--*) - __gitcomp "$__git_diff_common_options" + __gitcomp "--include-untracked --only-untracked $__git_diff_common_options" ;; branch,--*) ;; diff --git a/convert.c b/convert.c index ee360c2f07..45ac75f80c 100644 --- a/convert.c +++ b/convert.c @@ -24,17 +24,6 @@ #define CONVERT_STAT_BITS_TXT_CRLF 0x2 #define CONVERT_STAT_BITS_BIN 0x4 -enum crlf_action { - CRLF_UNDEFINED, - CRLF_BINARY, - CRLF_TEXT, - CRLF_TEXT_INPUT, - CRLF_TEXT_CRLF, - CRLF_AUTO, - CRLF_AUTO_INPUT, - CRLF_AUTO_CRLF -}; - struct text_stat { /* NUL, CR, LF and CRLF counts */ unsigned nul, lonecr, lonelf, crlf; @@ -172,7 +161,7 @@ static int text_eol_is_crlf(void) return 0; } -static enum eol output_eol(enum crlf_action crlf_action) +static enum eol output_eol(enum convert_crlf_action crlf_action) { switch (crlf_action) { case CRLF_BINARY: @@ -246,7 +235,7 @@ static int has_crlf_in_index(const struct index_state *istate, const char *path) } static int will_convert_lf_to_crlf(struct text_stat *stats, - enum crlf_action crlf_action) + enum convert_crlf_action crlf_action) { if (output_eol(crlf_action) != EOL_CRLF) return 0; @@ -499,7 +488,7 @@ static int encode_to_worktree(const char *path, const char *src, size_t src_len, static int crlf_to_git(const struct index_state *istate, const char *path, const char *src, size_t len, struct strbuf *buf, - enum crlf_action crlf_action, int conv_flags) + enum convert_crlf_action crlf_action, int conv_flags) { struct text_stat stats; char *dst; @@ -585,8 +574,8 @@ static int crlf_to_git(const struct index_state *istate, return 1; } -static int crlf_to_worktree(const char *src, size_t len, - struct strbuf *buf, enum crlf_action crlf_action) +static int crlf_to_worktree(const char *src, size_t len, struct strbuf *buf, + enum convert_crlf_action crlf_action) { char *to_free = NULL; struct text_stat stats; @@ -884,9 +873,13 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len goto done; if (fd >= 0) - err = write_packetized_from_fd(fd, process->in); + err = write_packetized_from_fd_no_flush(fd, process->in); else - err = write_packetized_from_buf(src, len, process->in); + err = write_packetized_from_buf_no_flush(src, len, process->in); + if (err) + goto done; + + err = packet_flush_gently(process->in); if (err) goto done; @@ -903,7 +896,8 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len if (err) goto done; - err = read_packetized_to_strbuf(process->out, &nbuf) < 0; + err = read_packetized_to_strbuf(process->out, &nbuf, + PACKET_READ_GENTLE_ON_EOF) < 0; if (err) goto done; @@ -1028,7 +1022,7 @@ static int read_convert_config(const char *var, const char *value, void *cb) if (!strncmp(drv->name, name, namelen) && !drv->name[namelen]) break; if (!drv) { - drv = xcalloc(1, sizeof(struct convert_driver)); + CALLOC_ARRAY(drv, 1); drv->name = xmemdupz(name, namelen); *user_convert_tail = drv; user_convert_tail = &(drv->next); @@ -1247,7 +1241,7 @@ static const char *git_path_check_encoding(struct attr_check_item *check) return value; } -static enum crlf_action git_path_check_crlf(struct attr_check_item *check) +static enum convert_crlf_action git_path_check_crlf(struct attr_check_item *check) { const char *value = check->value; @@ -1297,18 +1291,10 @@ static int git_path_check_ident(struct attr_check_item *check) return !!ATTR_TRUE(value); } -struct conv_attrs { - struct convert_driver *drv; - enum crlf_action attr_action; /* What attr says */ - enum crlf_action crlf_action; /* When no attr is set, use core.autocrlf */ - int ident; - const char *working_tree_encoding; /* Supported encoding or default encoding if NULL */ -}; - static struct attr_check *check; -static void convert_attrs(const struct index_state *istate, - struct conv_attrs *ca, const char *path) +void convert_attrs(const struct index_state *istate, + struct conv_attrs *ca, const char *path) { struct attr_check_item *ccheck = NULL; @@ -1456,7 +1442,6 @@ void convert_to_git_filter_fd(const struct index_state *istate, convert_attrs(istate, &ca, path); assert(ca.drv); - assert(ca.drv->clean || ca.drv->process); if (!apply_filter(path, NULL, 0, fd, dst, ca.drv, CAP_CLEAN, NULL, NULL)) die(_("%s: clean filter '%s' failed"), path, ca.drv->name); @@ -1466,19 +1451,16 @@ void convert_to_git_filter_fd(const struct index_state *istate, ident_to_git(dst->buf, dst->len, dst, ca.ident); } -static int convert_to_working_tree_internal(const struct index_state *istate, - const char *path, const char *src, - size_t len, struct strbuf *dst, - int normalizing, - const struct checkout_metadata *meta, - struct delayed_checkout *dco) +static int convert_to_working_tree_ca_internal(const struct conv_attrs *ca, + const char *path, const char *src, + size_t len, struct strbuf *dst, + int normalizing, + const struct checkout_metadata *meta, + struct delayed_checkout *dco) { int ret = 0, ret_filter = 0; - struct conv_attrs ca; - convert_attrs(istate, &ca, path); - - ret |= ident_to_worktree(src, len, dst, ca.ident); + ret |= ident_to_worktree(src, len, dst, ca->ident); if (ret) { src = dst->buf; len = dst->len; @@ -1488,49 +1470,56 @@ static int convert_to_working_tree_internal(const struct index_state *istate, * is a smudge or process filter (even if the process filter doesn't * support smudge). The filters might expect CRLFs. */ - if ((ca.drv && (ca.drv->smudge || ca.drv->process)) || !normalizing) { - ret |= crlf_to_worktree(src, len, dst, ca.crlf_action); + if ((ca->drv && (ca->drv->smudge || ca->drv->process)) || !normalizing) { + ret |= crlf_to_worktree(src, len, dst, ca->crlf_action); if (ret) { src = dst->buf; len = dst->len; } } - ret |= encode_to_worktree(path, src, len, dst, ca.working_tree_encoding); + ret |= encode_to_worktree(path, src, len, dst, ca->working_tree_encoding); if (ret) { src = dst->buf; len = dst->len; } ret_filter = apply_filter( - path, src, len, -1, dst, ca.drv, CAP_SMUDGE, meta, dco); - if (!ret_filter && ca.drv && ca.drv->required) - die(_("%s: smudge filter %s failed"), path, ca.drv->name); + path, src, len, -1, dst, ca->drv, CAP_SMUDGE, meta, dco); + if (!ret_filter && ca->drv && ca->drv->required) + die(_("%s: smudge filter %s failed"), path, ca->drv->name); return ret | ret_filter; } -int async_convert_to_working_tree(const struct index_state *istate, - const char *path, const char *src, - size_t len, struct strbuf *dst, - const struct checkout_metadata *meta, - void *dco) +int async_convert_to_working_tree_ca(const struct conv_attrs *ca, + const char *path, const char *src, + size_t len, struct strbuf *dst, + const struct checkout_metadata *meta, + void *dco) { - return convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, dco); + return convert_to_working_tree_ca_internal(ca, path, src, len, dst, 0, + meta, dco); } -int convert_to_working_tree(const struct index_state *istate, - const char *path, const char *src, - size_t len, struct strbuf *dst, - const struct checkout_metadata *meta) +int convert_to_working_tree_ca(const struct conv_attrs *ca, + const char *path, const char *src, + size_t len, struct strbuf *dst, + const struct checkout_metadata *meta) { - return convert_to_working_tree_internal(istate, path, src, len, dst, 0, meta, NULL); + return convert_to_working_tree_ca_internal(ca, path, src, len, dst, 0, + meta, NULL); } int renormalize_buffer(const struct index_state *istate, const char *path, const char *src, size_t len, struct strbuf *dst) { - int ret = convert_to_working_tree_internal(istate, path, src, len, dst, 1, NULL, NULL); + struct conv_attrs ca; + int ret; + + convert_attrs(istate, &ca, path); + ret = convert_to_working_tree_ca_internal(&ca, path, src, len, dst, 1, + NULL, NULL); if (ret) { src = dst->buf; len = dst->len; @@ -1957,34 +1946,25 @@ static struct stream_filter *ident_filter(const struct object_id *oid) } /* - * Return an appropriately constructed filter for the path, or NULL if + * Return an appropriately constructed filter for the given ca, or NULL if * the contents cannot be filtered without reading the whole thing * in-core. * * Note that you would be crazy to set CRLF, smudge/clean or ident to a * large binary blob you would want us not to slurp into the memory! */ -struct stream_filter *get_stream_filter(const struct index_state *istate, - const char *path, - const struct object_id *oid) +struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca, + const struct object_id *oid) { - struct conv_attrs ca; struct stream_filter *filter = NULL; - convert_attrs(istate, &ca, path); - if (ca.drv && (ca.drv->process || ca.drv->smudge || ca.drv->clean)) + if (classify_conv_attrs(ca) != CA_CLASS_STREAMABLE) return NULL; - if (ca.working_tree_encoding) - return NULL; - - if (ca.crlf_action == CRLF_AUTO || ca.crlf_action == CRLF_AUTO_CRLF) - return NULL; - - if (ca.ident) + if (ca->ident) filter = ident_filter(oid); - if (output_eol(ca.crlf_action) == EOL_CRLF) + if (output_eol(ca->crlf_action) == EOL_CRLF) filter = cascade_filter(filter, lf_to_crlf_filter()); else filter = cascade_filter(filter, &null_filter_singleton); @@ -1992,6 +1972,15 @@ struct stream_filter *get_stream_filter(const struct index_state *istate, return filter; } +struct stream_filter *get_stream_filter(const struct index_state *istate, + const char *path, + const struct object_id *oid) +{ + struct conv_attrs ca; + convert_attrs(istate, &ca, path); + return get_stream_filter_ca(&ca, oid); +} + void free_stream_filter(struct stream_filter *filter) { filter->vtbl->free(filter); @@ -2025,3 +2014,21 @@ void clone_checkout_metadata(struct checkout_metadata *dst, if (blob) oidcpy(&dst->blob, blob); } + +enum conv_attrs_classification classify_conv_attrs(const struct conv_attrs *ca) +{ + if (ca->drv) { + if (ca->drv->process) + return CA_CLASS_INCORE_PROCESS; + if (ca->drv->smudge || ca->drv->clean) + return CA_CLASS_INCORE_FILTER; + } + + if (ca->working_tree_encoding) + return CA_CLASS_INCORE; + + if (ca->crlf_action == CRLF_AUTO || ca->crlf_action == CRLF_AUTO_CRLF) + return CA_CLASS_INCORE; + + return CA_CLASS_STREAMABLE; +} diff --git a/convert.h b/convert.h index e29d1026a6..43e567a59b 100644 --- a/convert.h +++ b/convert.h @@ -63,6 +63,30 @@ struct checkout_metadata { struct object_id blob; }; +enum convert_crlf_action { + CRLF_UNDEFINED, + CRLF_BINARY, + CRLF_TEXT, + CRLF_TEXT_INPUT, + CRLF_TEXT_CRLF, + CRLF_AUTO, + CRLF_AUTO_INPUT, + CRLF_AUTO_CRLF +}; + +struct convert_driver; + +struct conv_attrs { + struct convert_driver *drv; + enum convert_crlf_action attr_action; /* What attr says */ + enum convert_crlf_action crlf_action; /* When no attr is set, use core.autocrlf */ + int ident; + const char *working_tree_encoding; /* Supported encoding or default encoding if NULL */ +}; + +void convert_attrs(const struct index_state *istate, + struct conv_attrs *ca, const char *path); + extern enum eol core_eol; extern char *check_roundtrip_encoding; const char *get_cached_convert_stats_ascii(const struct index_state *istate, @@ -75,15 +99,34 @@ const char *get_convert_attr_ascii(const struct index_state *istate, int convert_to_git(const struct index_state *istate, const char *path, const char *src, size_t len, struct strbuf *dst, int conv_flags); -int convert_to_working_tree(const struct index_state *istate, - const char *path, const char *src, - size_t len, struct strbuf *dst, - const struct checkout_metadata *meta); -int async_convert_to_working_tree(const struct index_state *istate, - const char *path, const char *src, - size_t len, struct strbuf *dst, - const struct checkout_metadata *meta, - void *dco); +int convert_to_working_tree_ca(const struct conv_attrs *ca, + const char *path, const char *src, + size_t len, struct strbuf *dst, + const struct checkout_metadata *meta); +int async_convert_to_working_tree_ca(const struct conv_attrs *ca, + const char *path, const char *src, + size_t len, struct strbuf *dst, + const struct checkout_metadata *meta, + void *dco); +static inline int convert_to_working_tree(const struct index_state *istate, + const char *path, const char *src, + size_t len, struct strbuf *dst, + const struct checkout_metadata *meta) +{ + struct conv_attrs ca; + convert_attrs(istate, &ca, path); + return convert_to_working_tree_ca(&ca, path, src, len, dst, meta); +} +static inline int async_convert_to_working_tree(const struct index_state *istate, + const char *path, const char *src, + size_t len, struct strbuf *dst, + const struct checkout_metadata *meta, + void *dco) +{ + struct conv_attrs ca; + convert_attrs(istate, &ca, path); + return async_convert_to_working_tree_ca(&ca, path, src, len, dst, meta, dco); +} int async_query_available_blobs(const char *cmd, struct string_list *available_paths); int renormalize_buffer(const struct index_state *istate, @@ -136,6 +179,8 @@ struct stream_filter; /* opaque */ struct stream_filter *get_stream_filter(const struct index_state *istate, const char *path, const struct object_id *); +struct stream_filter *get_stream_filter_ca(const struct conv_attrs *ca, + const struct object_id *oid); void free_stream_filter(struct stream_filter *); int is_null_stream_filter(struct stream_filter *); @@ -155,4 +200,37 @@ int stream_filter(struct stream_filter *, const char *input, size_t *isize_p, char *output, size_t *osize_p); +enum conv_attrs_classification { + /* + * The blob must be loaded into a buffer before it can be + * smudged. All smudging is done in-proc. + */ + CA_CLASS_INCORE, + + /* + * The blob must be loaded into a buffer, but uses a + * single-file driver filter, such as rot13. + */ + CA_CLASS_INCORE_FILTER, + + /* + * The blob must be loaded into a buffer, but uses a + * long-running driver process, such as LFS. This might or + * might not use delayed operations. (The important thing is + * that there is a single subordinate long-running process + * handling all associated blobs and in case of delayed + * operations, may hold per-blob state.) + */ + CA_CLASS_INCORE_PROCESS, + + /* + * The blob can be streamed and smudged without needing to + * completely read it into a buffer. + */ + CA_CLASS_STREAMABLE, +}; + +enum conv_attrs_classification classify_conv_attrs( + const struct conv_attrs *ca); + #endif /* CONVERT_H */ diff --git a/csum-file.c b/csum-file.c index 0f35fa5ee4..7510950fa3 100644 --- a/csum-file.c +++ b/csum-file.c @@ -89,32 +89,35 @@ int finalize_hashfile(struct hashfile *f, unsigned char *result, unsigned int fl void hashwrite(struct hashfile *f, const void *buf, unsigned int count) { while (count) { - unsigned offset = f->offset; - unsigned left = sizeof(f->buffer) - offset; + unsigned left = sizeof(f->buffer) - f->offset; unsigned nr = count > left ? left : count; - const void *data; if (f->do_crc) f->crc32 = crc32(f->crc32, buf, nr); if (nr == sizeof(f->buffer)) { - /* process full buffer directly without copy */ - data = buf; + /* + * Flush a full batch worth of data directly + * from the input, skipping the memcpy() to + * the hashfile's buffer. In this block, + * f->offset is necessarily zero. + */ + the_hash_algo->update_fn(&f->ctx, buf, nr); + flush(f, buf, nr); } else { - memcpy(f->buffer + offset, buf, nr); - data = f->buffer; + /* + * Copy to the hashfile's buffer, flushing only + * if it became full. + */ + memcpy(f->buffer + f->offset, buf, nr); + f->offset += nr; + left -= nr; + if (!left) + hashflush(f); } count -= nr; - offset += nr; buf = (char *) buf + nr; - left -= nr; - if (!left) { - the_hash_algo->update_fn(&f->ctx, data, offset); - flush(f, data, offset); - offset = 0; - } - f->offset = offset; } } diff --git a/daemon.c b/daemon.c index 2ab7ea82eb..343531980d 100644 --- a/daemon.c +++ b/daemon.c @@ -840,7 +840,7 @@ static void add_child(struct child_process *cld, struct sockaddr *addr, socklen_ { struct child *newborn, **cradle; - newborn = xcalloc(1, sizeof(*newborn)); + CALLOC_ARRAY(newborn, 1); live_children++; memcpy(&newborn->cld, cld, sizeof(*cld)); memcpy(&newborn->address, addr, addrlen); @@ -1148,7 +1148,7 @@ static int service_loop(struct socketlist *socklist) struct pollfd *pfd; int i; - pfd = xcalloc(socklist->nr, sizeof(struct pollfd)); + CALLOC_ARRAY(pfd, socklist->nr); for (i = 0; i < socklist->nr; i++) { pfd[i].fd = socklist->list[i]; diff --git a/decorate.c b/decorate.c index a605b1b5f4..2036d15967 100644 --- a/decorate.c +++ b/decorate.c @@ -39,7 +39,7 @@ static void grow_decoration(struct decoration *n) struct decoration_entry *old_entries = n->entries; n->size = (old_size + 1000) * 3 / 2; - n->entries = xcalloc(n->size, sizeof(struct decoration_entry)); + CALLOC_ARRAY(n->entries, n->size); n->nr = 0; for (i = 0; i < old_size; i++) { diff --git a/diff-lib.c b/diff-lib.c index b73cc1859a..e5a58c9259 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -28,9 +28,10 @@ * exists for ce that is a submodule -- it is a submodule that is not * checked out). Return negative for an error. */ -static int check_removed(const struct cache_entry *ce, struct stat *st) +static int check_removed(const struct index_state *istate, const struct cache_entry *ce, struct stat *st) { - if (lstat(ce->name, st) < 0) { + assert(is_fsmonitor_refreshed(istate)); + if (!(ce->ce_flags & CE_FSMONITOR_VALID) && lstat(ce->name, st) < 0) { if (!is_missing_file_error(errno)) return -1; return 1; @@ -136,7 +137,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) memset(&(dpath->parent[0]), 0, sizeof(struct combine_diff_parent)*5); - changed = check_removed(ce, &st); + changed = check_removed(istate, ce, &st); if (!changed) wt_mode = ce_mode_from_stat(ce, st.st_mode); else { @@ -216,7 +217,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option) } else { struct stat st; - changed = check_removed(ce, &st); + changed = check_removed(istate, ce, &st); if (changed) { if (changed < 0) { perror(ce->name); @@ -278,7 +279,8 @@ static void diff_index_show_file(struct rev_info *revs, oid, oid_valid, ce->name, dirty_submodule); } -static int get_stat_data(const struct cache_entry *ce, +static int get_stat_data(const struct index_state *istate, + const struct cache_entry *ce, const struct object_id **oidp, unsigned int *modep, int cached, int match_missing, @@ -290,7 +292,7 @@ static int get_stat_data(const struct cache_entry *ce, if (!cached && !ce_uptodate(ce)) { int changed; struct stat st; - changed = check_removed(ce, &st); + changed = check_removed(istate, ce, &st); if (changed < 0) return -1; else if (changed) { @@ -321,12 +323,13 @@ static void show_new_file(struct rev_info *revs, const struct object_id *oid; unsigned int mode; unsigned dirty_submodule = 0; + struct index_state *istate = revs->diffopt.repo->index; /* * New file in the index: it might actually be different in * the working tree. */ - if (get_stat_data(new_file, &oid, &mode, cached, match_missing, + if (get_stat_data(istate, new_file, &oid, &mode, cached, match_missing, &dirty_submodule, &revs->diffopt) < 0) return; @@ -342,8 +345,9 @@ static int show_modified(struct rev_info *revs, unsigned int mode, oldmode; const struct object_id *oid; unsigned dirty_submodule = 0; + struct index_state *istate = revs->diffopt.repo->index; - if (get_stat_data(new_entry, &oid, &mode, cached, match_missing, + if (get_stat_data(istate, new_entry, &oid, &mode, cached, match_missing, &dirty_submodule, &revs->diffopt) < 0) { if (report_missing) diff_index_show_file(revs, "-", old_entry, @@ -574,6 +578,7 @@ int run_diff_index(struct rev_info *revs, unsigned int option) struct object_id oid; const char *name; char merge_base_hex[GIT_MAX_HEXSZ + 1]; + struct index_state *istate = revs->diffopt.repo->index; if (revs->pending.nr != 1) BUG("run_diff_index must be passed exactly one tree"); @@ -581,6 +586,8 @@ int run_diff_index(struct rev_info *revs, unsigned int option) trace_performance_enter(); ent = revs->pending.objects; + refresh_fsmonitor(istate); + if (merge_base) { diff_get_merge_base(revs, &oid); name = oid_to_hex_r(merge_base_hex, &oid); diff --git a/diff.c b/diff.c index 69e3bc00ed..4acccd9d7e 100644 --- a/diff.c +++ b/diff.c @@ -2233,14 +2233,12 @@ static void init_diff_words_data(struct emit_callback *ecbdata, struct diff_options *o = xmalloc(sizeof(struct diff_options)); memcpy(o, orig_opts, sizeof(struct diff_options)); - ecbdata->diff_words = - xcalloc(1, sizeof(struct diff_words_data)); + CALLOC_ARRAY(ecbdata->diff_words, 1); ecbdata->diff_words->type = o->word_diff; ecbdata->diff_words->opt = o; if (orig_opts->emitted_symbols) - o->emitted_symbols = - xcalloc(1, sizeof(struct emitted_diff_symbols)); + CALLOC_ARRAY(o->emitted_symbols, 1); if (!o->word_regex) o->word_regex = userdiff_word_regex(one, o->repo->index); @@ -2509,7 +2507,7 @@ static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat, const char *name_b) { struct diffstat_file *x; - x = xcalloc(1, sizeof(*x)); + CALLOC_ARRAY(x, 1); ALLOC_GROW(diffstat->files, diffstat->nr + 1, diffstat->alloc); diffstat->files[diffstat->nr++] = x; if (name_b) { @@ -4918,7 +4916,7 @@ static int diff_opt_find_object(const struct option *option, return error(_("unable to resolve '%s'"), arg); if (!opt->objfind) - opt->objfind = xcalloc(1, sizeof(*opt->objfind)); + CALLOC_ARRAY(opt->objfind, 1); opt->pickaxe_opts |= DIFF_PICKAXE_KIND_OBJFIND; opt->flags.recursive = 1; @@ -5348,6 +5346,19 @@ static int diff_opt_word_diff_regex(const struct option *opt, return 0; } +static int diff_opt_rotate_to(const struct option *opt, const char *arg, int unset) +{ + struct diff_options *options = opt->value; + + BUG_ON_OPT_NEG(unset); + if (!strcmp(opt->long_name, "skip-to")) + options->skip_instead_of_rotate = 1; + else + options->skip_instead_of_rotate = 0; + options->rotate_to = arg; + return 0; +} + static void prep_parse_options(struct diff_options *options) { struct option parseopts[] = { @@ -5599,6 +5610,12 @@ static void prep_parse_options(struct diff_options *options) DIFF_PICKAXE_REGEX, PARSE_OPT_NONEG), OPT_FILENAME('O', NULL, &options->orderfile, N_("control the order in which files appear in the output")), + OPT_CALLBACK_F(0, "rotate-to", options, N_(""), + N_("show the change in the specified path first"), + PARSE_OPT_NONEG, diff_opt_rotate_to), + OPT_CALLBACK_F(0, "skip-to", options, N_(""), + N_("skip the output to the specified path"), + PARSE_OPT_NONEG, diff_opt_rotate_to), OPT_CALLBACK_F(0, "find-object", options, N_(""), N_("look for differences that change the number of occurrences of the specified object"), PARSE_OPT_NONEG, diff_opt_find_object), @@ -6336,6 +6353,32 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o) } } +static void diff_free_file(struct diff_options *options) +{ + if (options->close_file) + fclose(options->file); +} + +static void diff_free_ignore_regex(struct diff_options *options) +{ + int i; + + for (i = 0; i < options->ignore_regex_nr; i++) { + regfree(options->ignore_regex[i]); + free(options->ignore_regex[i]); + } + free(options->ignore_regex); +} + +void diff_free(struct diff_options *options) +{ + if (options->no_free) + return; + + diff_free_file(options); + diff_free_ignore_regex(options); +} + void diff_flush(struct diff_options *options) { struct diff_queue_struct *q = &diff_queued_diff; @@ -6399,8 +6442,7 @@ void diff_flush(struct diff_options *options) * options->file to /dev/null should be safe, because we * aren't supposed to produce any output anyway. */ - if (options->close_file) - fclose(options->file); + diff_free_file(options); options->file = xfopen("/dev/null", "w"); options->close_file = 1; options->color_moved = 0; @@ -6433,8 +6475,7 @@ void diff_flush(struct diff_options *options) free_queue: free(q->queue); DIFF_QUEUE_CLEAR(q); - if (options->close_file) - fclose(options->file); + diff_free(options); /* * Report the content-level differences with HAS_CHANGES; @@ -6669,6 +6710,8 @@ void diffcore_std(struct diff_options *options) diffcore_pickaxe(options); if (options->orderfile) diffcore_order(options->orderfile); + if (options->rotate_to) + diffcore_rotate(options); if (!options->found_follow) /* See try_to_follow_renames() in tree-diff.c */ diff_resolve_rename_copy(); diff --git a/diff.h b/diff.h index 2ff2b1c7f2..c8f3faea8a 100644 --- a/diff.h +++ b/diff.h @@ -49,7 +49,17 @@ * - Once you finish feeding the pairs of files, call `diffcore_std()`. * This will tell the diffcore library to go ahead and do its work. * - * - Calling `diff_flush()` will produce the output. + * - Calling `diff_flush()` will produce the output, it will call + * `diff_free()` to free any resources, e.g. those allocated in + * `diff_opt_parse()`. + * + * - Set `.no_free = 1` before calling `diff_flush()` to defer the + * freeing of allocated memory in diff_options. This is useful when + * `diff_flush()` is being called in a loop, rather than as a + * one-off. When setting `.no_free = 1` you must ensure that + * `diff_free()` is called at the end, either by flipping the flag + * before the last `diff_flush()` call, or by flipping it before + * calling `diff_free()` yourself. */ struct combine_diff_path; @@ -227,6 +237,27 @@ enum diff_submodule_format { struct diff_options { const char *orderfile; + /* + * "--rotate-to=" would start showing at and when + * the output reaches the end, wrap around by default. + * Setting skip_instead_of_rotate to true stops the output at the + * end, effectively discarding the earlier part of the output + * before 's diff (this is used to implement the + * "--skip-to=" option). + * + * When rotate_to_strict is set, it is an error if there is no + * in the diff. Otherwise, the output starts at the + * path that is the same as, or first path that sorts after, + * . Because it is unreasonable to require the exact + * match for "git log -p --rotate-to=" (i.e. not all + * commit would touch that single ), "git log" sets it + * to false. "git diff" sets it to true to detect an error + * in the command line option. + */ + const char *rotate_to; + int skip_instead_of_rotate; + int rotate_to_strict; + /** * A constant string (can and typically does contain newlines to look for * a block of text, not just a single line) to filter out the filepairs @@ -365,6 +396,8 @@ struct diff_options { struct repository *repo; struct option *parseopts; + + int no_free; }; unsigned diff_filter_bit(char status); @@ -559,6 +592,7 @@ void diffcore_fix_diff_index(void); int diff_queue_is_empty(void); void diff_flush(struct diff_options*); +void diff_free(struct diff_options*); void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc); /* diff-raw status letters */ diff --git a/diffcore-rename.c b/diffcore-rename.c index e8508541be..36a98f9c49 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -1241,8 +1241,7 @@ void diffcore_rename_extended(struct diff_options *options, (uint64_t)num_destinations * (uint64_t)num_sources); } - mx = xcalloc(st_mult(NUM_CANDIDATE_PER_DST, num_destinations), - sizeof(*mx)); + CALLOC_ARRAY(mx, st_mult(NUM_CANDIDATE_PER_DST, num_destinations)); for (dst_cnt = i = 0; i < rename_dst_nr; i++) { struct diff_filespec *two = rename_dst[i].p->two; struct diff_score *m; diff --git a/diffcore-rotate.c b/diffcore-rotate.c new file mode 100644 index 0000000000..445f060ab0 --- /dev/null +++ b/diffcore-rotate.c @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2021, Google LLC. + * Based on diffcore-order.c, which is Copyright (C) 2005, Junio C Hamano + */ +#include "cache.h" +#include "diff.h" +#include "diffcore.h" + +void diffcore_rotate(struct diff_options *opt) +{ + struct diff_queue_struct *q = &diff_queued_diff; + struct diff_queue_struct outq; + int rotate_to, i; + + if (!q->nr) + return; + + for (i = 0; i < q->nr; i++) { + int cmp = strcmp(opt->rotate_to, q->queue[i]->two->path); + if (!cmp) + break; /* exact match */ + if (!opt->rotate_to_strict && cmp < 0) + break; /* q->queue[i] is now past the target pathname */ + } + + if (q->nr <= i) { + /* we did not find the specified path */ + if (opt->rotate_to_strict) + die(_("No such path '%s' in the diff"), opt->rotate_to); + return; + } + + DIFF_QUEUE_CLEAR(&outq); + rotate_to = i; + + for (i = rotate_to; i < q->nr; i++) + diff_q(&outq, q->queue[i]); + for (i = 0; i < rotate_to; i++) { + if (opt->skip_instead_of_rotate) + diff_free_filepair(q->queue[i]); + else + diff_q(&outq, q->queue[i]); + } + free(q->queue); + *q = outq; +} diff --git a/diffcore.h b/diffcore.h index 737c93a6cc..d76982f220 100644 --- a/diffcore.h +++ b/diffcore.h @@ -172,6 +172,7 @@ void diffcore_rename_extended(struct diff_options *options, void diffcore_merge_broken(void); void diffcore_pickaxe(struct diff_options *); void diffcore_order(const char *orderfile); +void diffcore_rotate(struct diff_options *); /* low-level interface to diffcore_order */ struct obj_order { diff --git a/dir.c b/dir.c index d153a63bbd..3474e67e8f 100644 --- a/dir.c +++ b/dir.c @@ -1035,6 +1035,9 @@ static int add_patterns_from_buffer(char *buf, size_t size, const char *base, int baselen, struct pattern_list *pl); +/* Flags for add_patterns() */ +#define PATTERN_NOFOLLOW (1<<0) + /* * Given a file with name "fname", read it (either from disk, or from * an index if 'istate' is non-null), parse it and store the @@ -1046,7 +1049,7 @@ static int add_patterns_from_buffer(char *buf, size_t size, */ static int add_patterns(const char *fname, const char *base, int baselen, struct pattern_list *pl, struct index_state *istate, - struct oid_stat *oid_stat) + unsigned flags, struct oid_stat *oid_stat) { struct stat st; int r; @@ -1054,7 +1057,11 @@ static int add_patterns(const char *fname, const char *base, int baselen, size_t size = 0; char *buf; - fd = open(fname, O_RDONLY); + if (flags & PATTERN_NOFOLLOW) + fd = open_nofollow(fname, O_RDONLY); + else + fd = open(fname, O_RDONLY); + if (fd < 0 || fstat(fd, &st) < 0) { if (fd < 0) warn_on_fopen_errors(fname); @@ -1143,9 +1150,10 @@ static int add_patterns_from_buffer(char *buf, size_t size, int add_patterns_from_file_to_list(const char *fname, const char *base, int baselen, struct pattern_list *pl, - struct index_state *istate) + struct index_state *istate, + unsigned flags) { - return add_patterns(fname, base, baselen, pl, istate, NULL); + return add_patterns(fname, base, baselen, pl, istate, flags, NULL); } int add_patterns_from_blob_to_list( @@ -1194,7 +1202,7 @@ static void add_patterns_from_file_1(struct dir_struct *dir, const char *fname, if (!dir->untracked) dir->unmanaged_exclude_files++; pl = add_pattern_list(dir, EXC_FILE, fname); - if (add_patterns(fname, "", 0, pl, NULL, oid_stat) < 0) + if (add_patterns(fname, "", 0, pl, NULL, 0, oid_stat) < 0) die(_("cannot use %s as an exclude file"), fname); } @@ -1488,7 +1496,7 @@ static void prep_exclude(struct dir_struct *dir, const char *cp; struct oid_stat oid_stat; - stk = xcalloc(1, sizeof(*stk)); + CALLOC_ARRAY(stk, 1); if (current < 0) { cp = base; current = 0; @@ -1558,6 +1566,7 @@ static void prep_exclude(struct dir_struct *dir, strbuf_addstr(&sb, dir->exclude_per_dir); pl->src = strbuf_detach(&sb, NULL); add_patterns(pl->src, pl->src, stk->baselen, pl, istate, + PATTERN_NOFOLLOW, untracked ? &oid_stat : NULL); } /* @@ -2730,11 +2739,8 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d return NULL; } - if (!dir->untracked->root) { - const int len = sizeof(*dir->untracked->root); - dir->untracked->root = xmalloc(len); - memset(dir->untracked->root, 0, len); - } + if (!dir->untracked->root) + FLEX_ALLOC_STR(dir->untracked->root, name, ""); /* Validate $GIT_DIR/info/exclude and core.excludesfile */ root = dir->untracked->root; @@ -3009,7 +3015,7 @@ int get_sparse_checkout_patterns(struct pattern_list *pl) char *sparse_filename = get_sparse_checkout_filename(); pl->use_cone_patterns = core_sparse_checkout_cone; - res = add_patterns_from_file_to_list(sparse_filename, "", 0, pl, NULL); + res = add_patterns_from_file_to_list(sparse_filename, "", 0, pl, NULL, 0); free(sparse_filename); return res; @@ -3165,7 +3171,7 @@ void write_untracked_extension(struct strbuf *out, struct untracked_cache *untra int varint_len; const unsigned hashsz = the_hash_algo->rawsz; - ouc = xcalloc(1, sizeof(*ouc)); + CALLOC_ARRAY(ouc, 1); stat_data_to_disk(&ouc->info_exclude_stat, &untracked->ss_info_exclude.stat); stat_data_to_disk(&ouc->excludes_file_stat, &untracked->ss_excludes_file.stat); ouc->dir_flags = htonl(untracked->dir_flags); @@ -3376,7 +3382,7 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long if (next + exclude_per_dir_offset + 1 > end) return NULL; - uc = xcalloc(1, sizeof(*uc)); + CALLOC_ARRAY(uc, 1); strbuf_init(&uc->ident, ident_len); strbuf_add(&uc->ident, ident, ident_len); load_oid_stat(&uc->ss_info_exclude, diff --git a/dir.h b/dir.h index facfae4740..04d886cfce 100644 --- a/dir.h +++ b/dir.h @@ -420,7 +420,8 @@ int hashmap_contains_parent(struct hashmap *map, struct pattern_list *add_pattern_list(struct dir_struct *dir, int group_type, const char *src); int add_patterns_from_file_to_list(const char *fname, const char *base, int baselen, - struct pattern_list *pl, struct index_state *istate); + struct pattern_list *pl, struct index_state *istate, + unsigned flags); void add_patterns_from_file(struct dir_struct *, const char *fname); int add_patterns_from_blob_to_list(struct object_id *oid, const char *base, int baselen, diff --git a/entry.c b/entry.c index a0532f1f00..2dc94ba5cc 100644 --- a/entry.c +++ b/entry.c @@ -6,6 +6,7 @@ #include "submodule.h" #include "progress.h" #include "fsmonitor.h" +#include "entry.h" static void create_directories(const char *path, int path_len, const struct checkout *state) @@ -83,7 +84,7 @@ static int create_file(const char *path, unsigned int mode) return open(path, O_WRONLY | O_CREAT | O_EXCL, mode); } -static void *read_blob_entry(const struct cache_entry *ce, unsigned long *size) +void *read_blob_entry(const struct cache_entry *ce, unsigned long *size) { enum object_type type; void *blob_data = read_object_file(&ce->oid, &type, size); @@ -108,7 +109,7 @@ static int open_output_fd(char *path, const struct cache_entry *ce, int to_tempf } } -static int fstat_output(int fd, const struct checkout *state, struct stat *st) +int fstat_checkout_output(int fd, const struct checkout *state, struct stat *st) { /* use fstat() only when path == ce->name */ if (fstat_is_reliable() && @@ -131,7 +132,7 @@ static int streaming_write_entry(const struct cache_entry *ce, char *path, return -1; result |= stream_blob_to_fd(fd, &ce->oid, filter, 1); - *fstat_done = fstat_output(fd, state, statbuf); + *fstat_done = fstat_checkout_output(fd, state, statbuf); result |= close(fd); if (result) @@ -250,8 +251,21 @@ int finish_delayed_checkout(struct checkout *state, int *nr_checkouts) return errs; } -static int write_entry(struct cache_entry *ce, - char *path, const struct checkout *state, int to_tempfile) +void update_ce_after_write(const struct checkout *state, struct cache_entry *ce, + struct stat *st) +{ + if (state->refresh_cache) { + assert(state->istate); + fill_stat_cache_info(state->istate, ce, st); + ce->ce_flags |= CE_UPDATE_IN_BASE; + mark_fsmonitor_invalid(state->istate, ce); + state->istate->cache_changed |= CE_ENTRY_CHANGED; + } +} + +/* Note: ca is used (and required) iff the entry refers to a regular file. */ +static int write_entry(struct cache_entry *ce, char *path, struct conv_attrs *ca, + const struct checkout *state, int to_tempfile) { unsigned int ce_mode_s_ifmt = ce->ce_mode & S_IFMT; struct delayed_checkout *dco = state->delayed_checkout; @@ -268,8 +282,7 @@ static int write_entry(struct cache_entry *ce, clone_checkout_metadata(&meta, &state->meta, &ce->oid); if (ce_mode_s_ifmt == S_IFREG) { - struct stream_filter *filter = get_stream_filter(state->istate, ce->name, - &ce->oid); + struct stream_filter *filter = get_stream_filter_ca(ca, &ce->oid); if (filter && !streaming_write_entry(ce, path, filter, state, to_tempfile, @@ -282,7 +295,7 @@ static int write_entry(struct cache_entry *ce, new_blob = read_blob_entry(ce, &size); if (!new_blob) return error("unable to read sha1 file of %s (%s)", - path, oid_to_hex(&ce->oid)); + ce->name, oid_to_hex(&ce->oid)); /* * We can't make a real symlink; write out a regular file entry @@ -309,21 +322,24 @@ static int write_entry(struct cache_entry *ce, new_blob = read_blob_entry(ce, &size); if (!new_blob) return error("unable to read sha1 file of %s (%s)", - path, oid_to_hex(&ce->oid)); + ce->name, oid_to_hex(&ce->oid)); } /* * Convert from git internal format to working tree format */ if (dco && dco->state != CE_NO_DELAY) { - ret = async_convert_to_working_tree(state->istate, ce->name, new_blob, - size, &buf, &meta, dco); + ret = async_convert_to_working_tree_ca(ca, ce->name, + new_blob, size, + &buf, &meta, dco); if (ret && string_list_has_string(&dco->paths, ce->name)) { free(new_blob); goto delayed; } - } else - ret = convert_to_working_tree(state->istate, ce->name, new_blob, size, &buf, &meta); + } else { + ret = convert_to_working_tree_ca(ca, ce->name, new_blob, + size, &buf, &meta); + } if (ret) { free(new_blob); @@ -345,7 +361,7 @@ static int write_entry(struct cache_entry *ce, wrote = write_in_full(fd, new_blob, size); if (!to_tempfile) - fstat_done = fstat_output(fd, state, &st); + fstat_done = fstat_checkout_output(fd, state, &st); close(fd); free(new_blob); if (wrote < 0) @@ -354,7 +370,7 @@ static int write_entry(struct cache_entry *ce, case S_IFGITLINK: if (to_tempfile) - return error("cannot create temporary submodule %s", path); + return error("cannot create temporary submodule %s", ce->name); if (mkdir(path, 0777) < 0) return error("cannot create submodule directory %s", path); sub = submodule_from_ce(ce); @@ -365,20 +381,15 @@ static int write_entry(struct cache_entry *ce, break; default: - return error("unknown file mode for %s in index", path); + return error("unknown file mode for %s in index", ce->name); } finish: if (state->refresh_cache) { - assert(state->istate); - if (!fstat_done) - if (lstat(ce->name, &st) < 0) - return error_errno("unable to stat just-written file %s", - ce->name); - fill_stat_cache_info(state->istate, ce, &st); - ce->ce_flags |= CE_UPDATE_IN_BASE; - mark_fsmonitor_invalid(state->istate, ce); - state->istate->cache_changed |= CE_ENTRY_CHANGED; + if (!fstat_done && lstat(ce->name, &st) < 0) + return error_errno("unable to stat just-written file %s", + ce->name); + update_ce_after_write(state, ce , &st); } delayed: return 0; @@ -429,19 +440,13 @@ static void mark_colliding_entries(const struct checkout *state, } } -/* - * Write the contents from ce out to the working tree. - * - * When topath[] is not NULL, instead of writing to the working tree - * file named by ce, a temporary file is created by this function and - * its name is returned in topath[], which must be able to hold at - * least TEMPORARY_FILENAME_LENGTH bytes long. - */ -int checkout_entry(struct cache_entry *ce, const struct checkout *state, - char *topath, int *nr_checkouts) +int checkout_entry_ca(struct cache_entry *ce, struct conv_attrs *ca, + const struct checkout *state, char *topath, + int *nr_checkouts) { static struct strbuf path = STRBUF_INIT; struct stat st; + struct conv_attrs ca_buf; if (ce->ce_flags & CE_WT_REMOVE) { if (topath) @@ -454,8 +459,13 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, return 0; } - if (topath) - return write_entry(ce, topath, state, 1); + if (topath) { + if (S_ISREG(ce->ce_mode) && !ca) { + convert_attrs(state->istate, &ca_buf, ce->name); + ca = &ca_buf; + } + return write_entry(ce, topath, ca, state, 1); + } strbuf_reset(&path); strbuf_add(&path, state->base_dir, state->base_dir_len); @@ -517,9 +527,16 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, return 0; create_directories(path.buf, path.len, state); + if (nr_checkouts) (*nr_checkouts)++; - return write_entry(ce, path.buf, state, 0); + + if (S_ISREG(ce->ce_mode) && !ca) { + convert_attrs(state->istate, &ca_buf, ce->name); + ca = &ca_buf; + } + + return write_entry(ce, path.buf, ca, state, 0); } void unlink_entry(const struct cache_entry *ce) @@ -530,7 +547,7 @@ void unlink_entry(const struct cache_entry *ce) submodule_move_head(ce->name, "HEAD", NULL, SUBMODULE_MOVE_HEAD_FORCE); } - if (!check_leading_path(ce->name, ce_namelen(ce))) + if (check_leading_path(ce->name, ce_namelen(ce), 1) >= 0) return; if (remove_or_warn(ce->ce_mode, ce->name)) return; diff --git a/entry.h b/entry.h new file mode 100644 index 0000000000..b8c0e170dc --- /dev/null +++ b/entry.h @@ -0,0 +1,59 @@ +#ifndef ENTRY_H +#define ENTRY_H + +#include "cache.h" +#include "convert.h" + +struct checkout { + struct index_state *istate; + const char *base_dir; + int base_dir_len; + struct delayed_checkout *delayed_checkout; + struct checkout_metadata meta; + unsigned force:1, + quiet:1, + not_new:1, + clone:1, + refresh_cache:1; +}; +#define CHECKOUT_INIT { NULL, "" } + +#define TEMPORARY_FILENAME_LENGTH 25 +/* + * Write the contents from ce out to the working tree. + * + * When topath[] is not NULL, instead of writing to the working tree + * file named by ce, a temporary file is created by this function and + * its name is returned in topath[], which must be able to hold at + * least TEMPORARY_FILENAME_LENGTH bytes long. + * + * With checkout_entry_ca(), callers can optionally pass a preloaded + * conv_attrs struct (to avoid reloading it), when ce refers to a + * regular file. If ca is NULL, the attributes will be loaded + * internally when (and if) needed. + */ +int checkout_entry_ca(struct cache_entry *ce, struct conv_attrs *ca, + const struct checkout *state, char *topath, + int *nr_checkouts); +static inline int checkout_entry(struct cache_entry *ce, + const struct checkout *state, char *topath, + int *nr_checkouts) +{ + return checkout_entry_ca(ce, NULL, state, topath, nr_checkouts); +} + +void enable_delayed_checkout(struct checkout *state); +int finish_delayed_checkout(struct checkout *state, int *nr_checkouts); + +/* + * Unlink the last component and schedule the leading directories for + * removal, such that empty directories get removed. + */ +void unlink_entry(const struct cache_entry *ce); + +void *read_blob_entry(const struct cache_entry *ce, unsigned long *size); +int fstat_checkout_output(int fd, const struct checkout *state, struct stat *st); +void update_ce_after_write(const struct checkout *state, struct cache_entry *ce, + struct stat *st); + +#endif /* ENTRY_H */ diff --git a/ewah/bitmap.c b/ewah/bitmap.c index 0d31cdc866..38a47c44db 100644 --- a/ewah/bitmap.c +++ b/ewah/bitmap.c @@ -25,7 +25,7 @@ struct bitmap *bitmap_word_alloc(size_t word_alloc) { struct bitmap *bitmap = xmalloc(sizeof(struct bitmap)); - bitmap->words = xcalloc(word_alloc, sizeof(eword_t)); + CALLOC_ARRAY(bitmap->words, word_alloc); bitmap->word_alloc = word_alloc; return bitmap; } diff --git a/fetch-pack.c b/fetch-pack.c index 1eaedcb5dc..6e68276aa3 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -38,6 +38,7 @@ static int server_supports_filtering; static int advertise_sid; static struct shallow_lock shallow_lock; static const char *alternate_shallow_file; +static struct fsck_options fsck_options = FSCK_OPTIONS_MISSING_GITMODULES; static struct strbuf fsck_msg_types = STRBUF_INIT; static struct string_list uri_protocols = STRING_LIST_INIT_DUP; @@ -790,14 +791,36 @@ static void create_promisor_file(const char *keep_name, strbuf_release(&promisor_name); } +static void parse_gitmodules_oids(int fd, struct oidset *gitmodules_oids) +{ + int len = the_hash_algo->hexsz + 1; /* hash + NL */ + + do { + char hex_hash[GIT_MAX_HEXSZ + 1]; + int read_len = read_in_full(fd, hex_hash, len); + struct object_id oid; + const char *end; + + if (!read_len) + return; + if (read_len != len) + die("invalid length read %d", read_len); + if (parse_oid_hex(hex_hash, &oid, &end) || *end != '\n') + die("invalid hash"); + oidset_insert(gitmodules_oids, &oid); + } while (1); +} + /* - * Pass 1 as "only_packfile" if the pack received is the only pack in this - * fetch request (that is, if there were no packfile URIs provided). + * If packfile URIs were provided, pass a non-NULL pointer to index_pack_args. + * The strings to pass as the --index-pack-arg arguments to http-fetch will be + * stored there. (It must be freed by the caller.) */ static int get_pack(struct fetch_pack_args *args, int xd[2], struct string_list *pack_lockfiles, - int only_packfile, - struct ref **sought, int nr_sought) + struct strvec *index_pack_args, + struct ref **sought, int nr_sought, + struct oidset *gitmodules_oids) { struct async demux; int do_keep = args->keep_pack; @@ -805,6 +828,7 @@ static int get_pack(struct fetch_pack_args *args, struct pack_header header; int pass_header = 0; struct child_process cmd = CHILD_PROCESS_INIT; + int fsck_objects = 0; int ret; memset(&demux, 0, sizeof(demux)); @@ -823,7 +847,7 @@ static int get_pack(struct fetch_pack_args *args, else demux.out = xd[0]; - if (!args->keep_pack && unpack_limit) { + if (!args->keep_pack && unpack_limit && !index_pack_args) { if (read_pack_header(demux.out, &header)) die(_("protocol error: bad pack header")); @@ -839,8 +863,15 @@ static int get_pack(struct fetch_pack_args *args, strvec_push(&cmd.args, alternate_shallow_file); } - if (do_keep || args->from_promisor) { - if (pack_lockfiles) + if (fetch_fsck_objects >= 0 + ? fetch_fsck_objects + : transfer_fsck_objects >= 0 + ? transfer_fsck_objects + : 0) + fsck_objects = 1; + + if (do_keep || args->from_promisor || index_pack_args || fsck_objects) { + if (pack_lockfiles || fsck_objects) cmd.out = -1; cmd_name = "index-pack"; strvec_push(&cmd.args, cmd_name); @@ -849,7 +880,7 @@ static int get_pack(struct fetch_pack_args *args, strvec_push(&cmd.args, "-v"); if (args->use_thin_pack) strvec_push(&cmd.args, "--fix-thin"); - if (do_keep && (args->lock_pack || unpack_limit)) { + if ((do_keep || index_pack_args) && (args->lock_pack || unpack_limit)) { char hostname[HOST_NAME_MAX + 1]; if (xgethostname(hostname, sizeof(hostname))) xsnprintf(hostname, sizeof(hostname), "localhost"); @@ -857,7 +888,7 @@ static int get_pack(struct fetch_pack_args *args, "--keep=fetch-pack %"PRIuMAX " on %s", (uintmax_t)getpid(), hostname); } - if (only_packfile && args->check_self_contained_and_connected) + if (!index_pack_args && args->check_self_contained_and_connected) strvec_push(&cmd.args, "--check-self-contained-and-connected"); else /* @@ -890,12 +921,8 @@ static int get_pack(struct fetch_pack_args *args, strvec_pushf(&cmd.args, "--pack_header=%"PRIu32",%"PRIu32, ntohl(header.hdr_version), ntohl(header.hdr_entries)); - if (fetch_fsck_objects >= 0 - ? fetch_fsck_objects - : transfer_fsck_objects >= 0 - ? transfer_fsck_objects - : 0) { - if (args->from_promisor || !only_packfile) + if (fsck_objects) { + if (args->from_promisor || index_pack_args) /* * We cannot use --strict in index-pack because it * checks both broken objects and links, but we only @@ -907,14 +934,26 @@ 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]); + } + cmd.in = demux.out; cmd.git_cmd = 1; if (start_command(&cmd)) die(_("fetch-pack: unable to fork off %s"), cmd_name); - if (do_keep && pack_lockfiles) { - char *pack_lockfile = index_pack_lockfile(cmd.out); + if (do_keep && (pack_lockfiles || fsck_objects)) { + int is_well_formed; + char *pack_lockfile = index_pack_lockfile(cmd.out, &is_well_formed); + + if (!is_well_formed) + die(_("fetch-pack: invalid index-pack output")); if (pack_lockfile) string_list_append_nodup(pack_lockfiles, pack_lockfile); + parse_gitmodules_oids(cmd.out, gitmodules_oids); close(cmd.out); } @@ -1074,12 +1113,17 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args, if (args->deepen) setup_alternate_shallow(&shallow_lock, &alternate_shallow_file, NULL); - else if (si->nr_ours || si->nr_theirs) + else if (si->nr_ours || si->nr_theirs) { + if (args->reject_shallow_remote) + die(_("source repository is shallow, reject to clone.")); alternate_shallow_file = setup_temporary_shallow(si->shallow); - else + } else alternate_shallow_file = NULL; - if (get_pack(args, fd, pack_lockfiles, 1, sought, nr_sought)) + if (get_pack(args, fd, pack_lockfiles, NULL, sought, nr_sought, + &fsck_options.gitmodules_found)) die(_("git fetch-pack: fetch failed.")); + if (fsck_finish(&fsck_options)) + die("fsck failed"); all_done: if (negotiator) @@ -1441,10 +1485,12 @@ static void receive_shallow_info(struct fetch_pack_args *args, * rejected (unless --update-shallow is set); do the same. */ prepare_shallow_info(si, shallows); - if (si->nr_ours || si->nr_theirs) + if (si->nr_ours || si->nr_theirs) { + if (args->reject_shallow_remote) + die(_("source repository is shallow, reject to clone.")); alternate_shallow_file = setup_temporary_shallow(si->shallow); - else + } else alternate_shallow_file = NULL; } else { alternate_shallow_file = NULL; @@ -1529,6 +1575,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args, int seen_ack = 0; struct string_list packfile_uris = STRING_LIST_INIT_DUP; int i; + struct strvec index_pack_args = STRVEC_INIT; negotiator = &negotiator_alloc; fetch_negotiator_init(r, negotiator); @@ -1618,7 +1665,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args, receive_packfile_uris(&reader, &packfile_uris); process_section_header(&reader, "packfile", 0); if (get_pack(args, fd, pack_lockfiles, - !packfile_uris.nr, sought, nr_sought)) + packfile_uris.nr ? &index_pack_args : NULL, + sought, nr_sought, &fsck_options.gitmodules_found)) die(_("git fetch-pack: fetch failed.")); do_check_stateless_delimiter(args, &reader); @@ -1630,6 +1678,7 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args, } for (i = 0; i < packfile_uris.nr; i++) { + int j; struct child_process cmd = CHILD_PROCESS_INIT; char packname[GIT_MAX_HEXSZ + 1]; const char *uri = packfile_uris.items[i].string + @@ -1639,6 +1688,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args, strvec_pushf(&cmd.args, "--packfile=%.*s", (int) the_hash_algo->hexsz, packfile_uris.items[i].string); + for (j = 0; j < index_pack_args.nr; j++) + strvec_pushf(&cmd.args, "--index-pack-arg=%s", + index_pack_args.v[j]); strvec_push(&cmd.args, uri); cmd.git_cmd = 1; cmd.no_stdin = 1; @@ -1657,6 +1709,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args, packname[the_hash_algo->hexsz] = '\0'; + parse_gitmodules_oids(cmd.out, &fsck_options.gitmodules_found); + close(cmd.out); if (finish_command(&cmd)) @@ -1674,6 +1728,10 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args, packname)); } string_list_clear(&packfile_uris, 0); + strvec_clear(&index_pack_args); + + if (fsck_finish(&fsck_options)) + die("fsck failed"); if (negotiator) negotiator->release(negotiator); @@ -1847,7 +1905,7 @@ static void update_shallow(struct fetch_pack_args *args, * remote is also shallow, check what ref is safe to update * without updating .git/shallow */ - status = xcalloc(nr_sought, sizeof(*status)); + CALLOC_ARRAY(status, nr_sought); assign_shallow_commits_to_refs(si, NULL, status); if (si->nr_ours || si->nr_theirs) { for (i = 0; i < nr_sought; i++) diff --git a/fetch-pack.h b/fetch-pack.h index 736a3dae46..f114d72775 100644 --- a/fetch-pack.h +++ b/fetch-pack.h @@ -39,6 +39,7 @@ struct fetch_pack_args { unsigned self_contained_and_connected:1; unsigned cloning:1; unsigned update_shallow:1; + unsigned reject_shallow_remote:1; unsigned deepen:1; /* diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c index 46f6015c44..0f66818e0f 100644 --- a/fmt-merge-msg.c +++ b/fmt-merge-msg.c @@ -130,7 +130,7 @@ static int handle_line(char *line, struct merge_parents *merge_parents) if (!find_merge_parent(merge_parents, &oid, NULL)) return 0; /* subsumed by other parents */ - origin_data = xcalloc(1, sizeof(struct origin_data)); + CALLOC_ARRAY(origin_data, 1); oidcpy(&origin_data->oid, &oid); if (line[len - 1] == '\n') @@ -510,22 +510,28 @@ static void fmt_merge_msg_sigs(struct strbuf *out) for (i = 0; i < origins.nr; i++) { struct object_id *oid = origins.items[i].util; enum object_type type; - unsigned long size, len; + unsigned long size; char *buf = read_object_file(oid, &type, &size); + char *origbuf = buf; + unsigned long len = size; struct signature_check sigc = { NULL }; - struct strbuf sig = STRBUF_INIT; + struct strbuf payload = STRBUF_INIT, sig = STRBUF_INIT; if (!buf || type != OBJ_TAG) goto next; - len = parse_signature(buf, size); - if (size == len) - ; /* merely annotated */ - else if (check_signature(buf, len, buf + len, size - len, &sigc) && - !sigc.gpg_output) - strbuf_addstr(&sig, "gpg verification failed.\n"); - else - strbuf_addstr(&sig, sigc.gpg_output); + if (!parse_signature(buf, size, &payload, &sig)) + ;/* merely annotated */ + else { + buf = payload.buf; + len = payload.len; + if (check_signature(payload.buf, payload.len, sig.buf, + sig.len, &sigc) && + !sigc.gpg_output) + strbuf_addstr(&sig, "gpg verification failed.\n"); + else + strbuf_addstr(&sig, sigc.gpg_output); + } signature_check_clear(&sigc); if (!tag_number++) { @@ -548,9 +554,10 @@ static void fmt_merge_msg_sigs(struct strbuf *out) strlen(origins.items[i].string)); fmt_tag_signature(&tagbuf, &sig, buf, len); } + strbuf_release(&payload); strbuf_release(&sig); next: - free(buf); + free(origbuf); } if (tagbuf.len) { strbuf_addch(out, '\n'); diff --git a/fsck.c b/fsck.c index 4b7f0b73d7..f5ed6a2635 100644 --- a/fsck.c +++ b/fsck.c @@ -19,90 +19,19 @@ #include "credential.h" #include "help.h" -static struct oidset gitmodules_found = OIDSET_INIT; -static struct oidset gitmodules_done = OIDSET_INIT; - -#define FSCK_FATAL -1 -#define FSCK_INFO -2 - -#define FOREACH_MSG_ID(FUNC) \ - /* fatal errors */ \ - FUNC(NUL_IN_HEADER, FATAL) \ - FUNC(UNTERMINATED_HEADER, FATAL) \ - /* errors */ \ - FUNC(BAD_DATE, ERROR) \ - FUNC(BAD_DATE_OVERFLOW, ERROR) \ - FUNC(BAD_EMAIL, ERROR) \ - FUNC(BAD_NAME, ERROR) \ - FUNC(BAD_OBJECT_SHA1, ERROR) \ - FUNC(BAD_PARENT_SHA1, ERROR) \ - FUNC(BAD_TAG_OBJECT, ERROR) \ - FUNC(BAD_TIMEZONE, ERROR) \ - FUNC(BAD_TREE, ERROR) \ - FUNC(BAD_TREE_SHA1, ERROR) \ - FUNC(BAD_TYPE, ERROR) \ - FUNC(DUPLICATE_ENTRIES, ERROR) \ - FUNC(MISSING_AUTHOR, ERROR) \ - FUNC(MISSING_COMMITTER, ERROR) \ - FUNC(MISSING_EMAIL, ERROR) \ - FUNC(MISSING_NAME_BEFORE_EMAIL, ERROR) \ - FUNC(MISSING_OBJECT, ERROR) \ - FUNC(MISSING_SPACE_BEFORE_DATE, ERROR) \ - FUNC(MISSING_SPACE_BEFORE_EMAIL, ERROR) \ - FUNC(MISSING_TAG, ERROR) \ - FUNC(MISSING_TAG_ENTRY, ERROR) \ - FUNC(MISSING_TREE, ERROR) \ - FUNC(MISSING_TREE_OBJECT, ERROR) \ - FUNC(MISSING_TYPE, ERROR) \ - FUNC(MISSING_TYPE_ENTRY, ERROR) \ - FUNC(MULTIPLE_AUTHORS, ERROR) \ - FUNC(TREE_NOT_SORTED, ERROR) \ - FUNC(UNKNOWN_TYPE, ERROR) \ - FUNC(ZERO_PADDED_DATE, ERROR) \ - FUNC(GITMODULES_MISSING, ERROR) \ - FUNC(GITMODULES_BLOB, ERROR) \ - FUNC(GITMODULES_LARGE, ERROR) \ - FUNC(GITMODULES_NAME, ERROR) \ - FUNC(GITMODULES_SYMLINK, ERROR) \ - FUNC(GITMODULES_URL, ERROR) \ - FUNC(GITMODULES_PATH, ERROR) \ - FUNC(GITMODULES_UPDATE, ERROR) \ - /* warnings */ \ - FUNC(BAD_FILEMODE, WARN) \ - FUNC(EMPTY_NAME, WARN) \ - FUNC(FULL_PATHNAME, WARN) \ - FUNC(HAS_DOT, WARN) \ - FUNC(HAS_DOTDOT, WARN) \ - FUNC(HAS_DOTGIT, WARN) \ - FUNC(NULL_SHA1, WARN) \ - FUNC(ZERO_PADDED_FILEMODE, WARN) \ - FUNC(NUL_IN_COMMIT, WARN) \ - /* infos (reported as warnings, but ignored by default) */ \ - FUNC(GITMODULES_PARSE, INFO) \ - FUNC(BAD_TAG_NAME, INFO) \ - FUNC(MISSING_TAGGER_ENTRY, INFO) \ - /* ignored (elevated when requested) */ \ - FUNC(EXTRA_HEADER_ENTRY, IGNORE) - -#define MSG_ID(id, msg_type) FSCK_MSG_##id, -enum fsck_msg_id { - FOREACH_MSG_ID(MSG_ID) - FSCK_MSG_MAX -}; -#undef MSG_ID - #define STR(x) #x #define MSG_ID(id, msg_type) { STR(id), NULL, NULL, FSCK_##msg_type }, static struct { const char *id_string; const char *downcased; const char *camelcased; - int msg_type; + enum fsck_msg_type msg_type; } msg_id_info[FSCK_MSG_MAX + 1] = { - FOREACH_MSG_ID(MSG_ID) + FOREACH_FSCK_MSG_ID(MSG_ID) { NULL, NULL, NULL, -1 } }; #undef MSG_ID +#undef STR static void prepare_msg_ids(void) { @@ -164,25 +93,23 @@ void list_config_fsck_msg_ids(struct string_list *list, const char *prefix) list_config_item(list, prefix, msg_id_info[i].camelcased); } -static int fsck_msg_type(enum fsck_msg_id msg_id, +static enum fsck_msg_type fsck_msg_type(enum fsck_msg_id msg_id, struct fsck_options *options) { - int msg_type; - assert(msg_id >= 0 && msg_id < FSCK_MSG_MAX); - if (options->msg_type) - msg_type = options->msg_type[msg_id]; - else { - msg_type = msg_id_info[msg_id].msg_type; + if (!options->msg_type) { + enum fsck_msg_type msg_type = msg_id_info[msg_id].msg_type; + if (options->strict && msg_type == FSCK_WARN) msg_type = FSCK_ERROR; + return msg_type; } - return msg_type; + return options->msg_type[msg_id]; } -static int parse_msg_type(const char *str) +static enum fsck_msg_type parse_msg_type(const char *str) { if (!strcmp(str, "error")) return FSCK_ERROR; @@ -202,28 +129,35 @@ int is_valid_msg_type(const char *msg_id, const char *msg_type) return 1; } -void fsck_set_msg_type(struct fsck_options *options, - const char *msg_id, const char *msg_type) +void fsck_set_msg_type_from_ids(struct fsck_options *options, + enum fsck_msg_id msg_id, + enum fsck_msg_type msg_type) { - int id = parse_msg_id(msg_id), type; - - if (id < 0) - die("Unhandled message id: %s", msg_id); - type = parse_msg_type(msg_type); - - if (type != FSCK_ERROR && msg_id_info[id].msg_type == FSCK_FATAL) - die("Cannot demote %s to %s", msg_id, msg_type); - if (!options->msg_type) { int i; - int *msg_type; - ALLOC_ARRAY(msg_type, FSCK_MSG_MAX); + enum fsck_msg_type *severity; + ALLOC_ARRAY(severity, FSCK_MSG_MAX); for (i = 0; i < FSCK_MSG_MAX; i++) - msg_type[i] = fsck_msg_type(i, options); - options->msg_type = msg_type; + severity[i] = fsck_msg_type(i, options); + options->msg_type = severity; } - options->msg_type[id] = type; + options->msg_type[msg_id] = msg_type; +} + +void fsck_set_msg_type(struct fsck_options *options, + const char *msg_id_str, const char *msg_type_str) +{ + int msg_id = parse_msg_id(msg_id_str); + enum fsck_msg_type msg_type = parse_msg_type(msg_type_str); + + if (msg_id < 0) + die("Unhandled message id: %s", msg_id_str); + + if (msg_type != FSCK_ERROR && msg_id_info[msg_id].msg_type == FSCK_FATAL) + die("Cannot demote %s to %s", msg_id_str, msg_type_str); + + fsck_set_msg_type_from_ids(options, msg_id, msg_type); } void fsck_set_msg_types(struct fsck_options *options, const char *values) @@ -264,24 +198,6 @@ void fsck_set_msg_types(struct fsck_options *options, const char *values) free(to_free); } -static void append_msg_id(struct strbuf *sb, const char *msg_id) -{ - for (;;) { - char c = *(msg_id)++; - - if (!c) - break; - if (c != '_') - strbuf_addch(sb, tolower(c)); - else { - assert(*msg_id); - strbuf_addch(sb, *(msg_id)++); - } - } - - strbuf_addstr(sb, ": "); -} - static int object_on_skiplist(struct fsck_options *opts, const struct object_id *oid) { @@ -291,11 +207,12 @@ static int object_on_skiplist(struct fsck_options *opts, __attribute__((format (printf, 5, 6))) static int report(struct fsck_options *options, const struct object_id *oid, enum object_type object_type, - enum fsck_msg_id id, const char *fmt, ...) + enum fsck_msg_id msg_id, const char *fmt, ...) { va_list ap; struct strbuf sb = STRBUF_INIT; - int msg_type = fsck_msg_type(id, options), result; + enum fsck_msg_type msg_type = fsck_msg_type(msg_id, options); + int result; if (msg_type == FSCK_IGNORE) return 0; @@ -308,12 +225,13 @@ static int report(struct fsck_options *options, else if (msg_type == FSCK_INFO) msg_type = FSCK_WARN; - append_msg_id(&sb, msg_id_info[id].id_string); + prepare_msg_ids(); + strbuf_addf(&sb, "%s: ", msg_id_info[msg_id].camelcased); va_start(ap, fmt); strbuf_vaddf(&sb, fmt, ap); result = options->error_func(options, oid, object_type, - msg_type, sb.buf); + msg_type, msg_id, sb.buf); strbuf_release(&sb); va_end(ap); @@ -463,6 +381,11 @@ static int fsck_walk_commit(struct commit *commit, void *data, struct fsck_optio generation += power * (name[--len] - '0'); if (power > 1 && len && name[len - 1] == '~') name_prefix_len = len - 1; + else { + /* Maybe a non-first parent, e.g. HEAD^2 */ + generation = 0; + name_prefix_len = len; + } } } @@ -680,7 +603,7 @@ static int fsck_tree(const struct object_id *oid, if (is_hfs_dotgitmodules(name) || is_ntfs_dotgitmodules(name)) { if (!S_ISLNK(mode)) - oidset_insert(&gitmodules_found, oid); + oidset_insert(&options->gitmodules_found, oid); else retval += report(options, oid, OBJ_TREE, @@ -694,7 +617,7 @@ static int fsck_tree(const struct object_id *oid, has_dotgit |= is_ntfs_dotgit(backslash); if (is_ntfs_dotgitmodules(backslash)) { if (!S_ISLNK(mode)) - oidset_insert(&gitmodules_found, oid); + oidset_insert(&options->gitmodules_found, oid); else retval += report(options, oid, OBJ_TREE, FSCK_MSG_GITMODULES_SYMLINK, @@ -1206,9 +1129,9 @@ static int fsck_blob(const struct object_id *oid, const char *buf, struct fsck_gitmodules_data data; struct config_options config_opts = { 0 }; - if (!oidset_contains(&gitmodules_found, oid)) + if (!oidset_contains(&options->gitmodules_found, oid)) return 0; - oidset_insert(&gitmodules_done, oid); + oidset_insert(&options->gitmodules_done, oid); if (object_on_skiplist(options, oid)) return 0; @@ -1261,7 +1184,9 @@ int fsck_object(struct object *obj, void *data, unsigned long size, int fsck_error_function(struct fsck_options *o, const struct object_id *oid, enum object_type object_type, - int msg_type, const char *message) + enum fsck_msg_type msg_type, + enum fsck_msg_id msg_id, + const char *message) { if (msg_type == FSCK_WARN) { warning("object %s: %s", fsck_describe_object(o, oid), message); @@ -1277,13 +1202,13 @@ int fsck_finish(struct fsck_options *options) struct oidset_iter iter; const struct object_id *oid; - oidset_iter_init(&gitmodules_found, &iter); + oidset_iter_init(&options->gitmodules_found, &iter); while ((oid = oidset_iter_next(&iter))) { enum object_type type; unsigned long size; char *buf; - if (oidset_contains(&gitmodules_done, oid)) + if (oidset_contains(&options->gitmodules_done, oid)) continue; buf = read_object_file(oid, &type, &size); @@ -1308,14 +1233,14 @@ int fsck_finish(struct fsck_options *options) } - oidset_clear(&gitmodules_found); - oidset_clear(&gitmodules_done); + oidset_clear(&options->gitmodules_found); + oidset_clear(&options->gitmodules_done); return ret; } -int fsck_config_internal(const char *var, const char *value, void *cb, - struct fsck_options *options) +int git_fsck_config(const char *var, const char *value, void *cb) { + struct fsck_options *options = cb; if (strcmp(var, "fsck.skiplist") == 0) { const char *path; struct strbuf sb = STRBUF_INIT; @@ -1336,3 +1261,21 @@ int fsck_config_internal(const char *var, const char *value, void *cb, return git_default_config(var, value, cb); } + +/* + * Custom error callbacks that are used in more than one place. + */ + +int fsck_error_cb_print_missing_gitmodules(struct fsck_options *o, + const struct object_id *oid, + enum object_type object_type, + enum fsck_msg_type msg_type, + enum fsck_msg_id msg_id, + const char *message) +{ + if (msg_id == FSCK_MSG_GITMODULES_MISSING) { + puts(oid_to_hex(oid)); + return 0; + } + return fsck_error_function(o, oid, object_type, msg_type, msg_id, message); +} diff --git a/fsck.h b/fsck.h index 423c467feb..7202c3c87e 100644 --- a/fsck.h +++ b/fsck.h @@ -3,15 +3,90 @@ #include "oidset.h" -#define FSCK_ERROR 1 -#define FSCK_WARN 2 -#define FSCK_IGNORE 3 +enum fsck_msg_type { + /* for internal use only */ + FSCK_IGNORE, + FSCK_INFO, + FSCK_FATAL, + /* "public", fed to e.g. error_func callbacks */ + FSCK_ERROR, + FSCK_WARN, +}; + +#define FOREACH_FSCK_MSG_ID(FUNC) \ + /* fatal errors */ \ + FUNC(NUL_IN_HEADER, FATAL) \ + FUNC(UNTERMINATED_HEADER, FATAL) \ + /* errors */ \ + FUNC(BAD_DATE, ERROR) \ + FUNC(BAD_DATE_OVERFLOW, ERROR) \ + FUNC(BAD_EMAIL, ERROR) \ + FUNC(BAD_NAME, ERROR) \ + FUNC(BAD_OBJECT_SHA1, ERROR) \ + FUNC(BAD_PARENT_SHA1, ERROR) \ + FUNC(BAD_TAG_OBJECT, ERROR) \ + FUNC(BAD_TIMEZONE, ERROR) \ + FUNC(BAD_TREE, ERROR) \ + FUNC(BAD_TREE_SHA1, ERROR) \ + FUNC(BAD_TYPE, ERROR) \ + FUNC(DUPLICATE_ENTRIES, ERROR) \ + FUNC(MISSING_AUTHOR, ERROR) \ + FUNC(MISSING_COMMITTER, ERROR) \ + FUNC(MISSING_EMAIL, ERROR) \ + FUNC(MISSING_NAME_BEFORE_EMAIL, ERROR) \ + FUNC(MISSING_OBJECT, ERROR) \ + FUNC(MISSING_SPACE_BEFORE_DATE, ERROR) \ + FUNC(MISSING_SPACE_BEFORE_EMAIL, ERROR) \ + FUNC(MISSING_TAG, ERROR) \ + FUNC(MISSING_TAG_ENTRY, ERROR) \ + FUNC(MISSING_TREE, ERROR) \ + FUNC(MISSING_TREE_OBJECT, ERROR) \ + FUNC(MISSING_TYPE, ERROR) \ + FUNC(MISSING_TYPE_ENTRY, ERROR) \ + FUNC(MULTIPLE_AUTHORS, ERROR) \ + FUNC(TREE_NOT_SORTED, ERROR) \ + FUNC(UNKNOWN_TYPE, ERROR) \ + FUNC(ZERO_PADDED_DATE, ERROR) \ + FUNC(GITMODULES_MISSING, ERROR) \ + FUNC(GITMODULES_BLOB, ERROR) \ + FUNC(GITMODULES_LARGE, ERROR) \ + FUNC(GITMODULES_NAME, ERROR) \ + FUNC(GITMODULES_SYMLINK, ERROR) \ + FUNC(GITMODULES_URL, ERROR) \ + FUNC(GITMODULES_PATH, ERROR) \ + FUNC(GITMODULES_UPDATE, ERROR) \ + /* warnings */ \ + FUNC(BAD_FILEMODE, WARN) \ + FUNC(EMPTY_NAME, WARN) \ + FUNC(FULL_PATHNAME, WARN) \ + FUNC(HAS_DOT, WARN) \ + FUNC(HAS_DOTDOT, WARN) \ + FUNC(HAS_DOTGIT, WARN) \ + FUNC(NULL_SHA1, WARN) \ + FUNC(ZERO_PADDED_FILEMODE, WARN) \ + FUNC(NUL_IN_COMMIT, WARN) \ + /* infos (reported as warnings, but ignored by default) */ \ + FUNC(GITMODULES_PARSE, INFO) \ + FUNC(BAD_TAG_NAME, INFO) \ + FUNC(MISSING_TAGGER_ENTRY, INFO) \ + /* ignored (elevated when requested) */ \ + FUNC(EXTRA_HEADER_ENTRY, IGNORE) + +#define MSG_ID(id, msg_type) FSCK_MSG_##id, +enum fsck_msg_id { + FOREACH_FSCK_MSG_ID(MSG_ID) + FSCK_MSG_MAX +}; +#undef MSG_ID struct fsck_options; struct object; +void fsck_set_msg_type_from_ids(struct fsck_options *options, + enum fsck_msg_id msg_id, + enum fsck_msg_type msg_type); void fsck_set_msg_type(struct fsck_options *options, - const char *msg_id, const char *msg_type); + const char *msg_id, const char *msg_type); void fsck_set_msg_types(struct fsck_options *options, const char *values); int is_valid_msg_type(const char *msg_id, const char *msg_type); @@ -23,28 +98,55 @@ int is_valid_msg_type(const char *msg_id, const char *msg_type); * <0 error signaled and abort * >0 error signaled and do not abort */ -typedef int (*fsck_walk_func)(struct object *obj, int type, void *data, struct fsck_options *options); +typedef int (*fsck_walk_func)(struct object *obj, enum object_type object_type, + void *data, struct fsck_options *options); /* callback for fsck_object, type is FSCK_ERROR or FSCK_WARN */ typedef int (*fsck_error)(struct fsck_options *o, const struct object_id *oid, enum object_type object_type, - int msg_type, const char *message); + enum fsck_msg_type msg_type, enum fsck_msg_id msg_id, + const char *message); int fsck_error_function(struct fsck_options *o, const struct object_id *oid, enum object_type object_type, - int msg_type, const char *message); + enum fsck_msg_type msg_type, enum fsck_msg_id msg_id, + const char *message); +int fsck_error_cb_print_missing_gitmodules(struct fsck_options *o, + const struct object_id *oid, + enum object_type object_type, + enum fsck_msg_type msg_type, + enum fsck_msg_id msg_id, + const char *message); struct fsck_options { fsck_walk_func walk; fsck_error error_func; unsigned strict:1; - int *msg_type; + enum fsck_msg_type *msg_type; struct oidset skiplist; + struct oidset gitmodules_found; + struct oidset gitmodules_done; kh_oid_map_t *object_names; }; -#define FSCK_OPTIONS_DEFAULT { NULL, fsck_error_function, 0, NULL, OIDSET_INIT } -#define FSCK_OPTIONS_STRICT { NULL, fsck_error_function, 1, NULL, OIDSET_INIT } +#define FSCK_OPTIONS_DEFAULT { \ + .skiplist = OIDSET_INIT, \ + .gitmodules_found = OIDSET_INIT, \ + .gitmodules_done = OIDSET_INIT, \ + .error_func = fsck_error_function \ +} +#define FSCK_OPTIONS_STRICT { \ + .strict = 1, \ + .gitmodules_found = OIDSET_INIT, \ + .gitmodules_done = OIDSET_INIT, \ + .error_func = fsck_error_function, \ +} +#define FSCK_OPTIONS_MISSING_GITMODULES { \ + .strict = 1, \ + .gitmodules_found = OIDSET_INIT, \ + .gitmodules_done = OIDSET_INIT, \ + .error_func = fsck_error_cb_print_missing_gitmodules, \ +} /* descend in all linked child objects * the return value is: @@ -107,7 +209,6 @@ const char *fsck_describe_object(struct fsck_options *options, * git_config() callback for use by fsck-y tools that want to support * fsck. fsck.skipList etc. */ -int fsck_config_internal(const char *var, const char *value, void *cb, - struct fsck_options *options); +int git_fsck_config(const char *var, const char *value, void *cb); #endif diff --git a/fsmonitor.c b/fsmonitor.c index fe9e9d7baf..ab9bfc60b3 100644 --- a/fsmonitor.c +++ b/fsmonitor.c @@ -90,7 +90,11 @@ int read_fsmonitor_extension(struct index_state *istate, const void *data, if (!istate->split_index) assert_index_minimum(istate, istate->fsmonitor_dirty->bit_size); - trace_printf_key(&trace_fsmonitor, "read fsmonitor extension successful"); + trace2_data_string("index", NULL, "extension/fsmn/read/token", + istate->fsmonitor_last_update); + trace_printf_key(&trace_fsmonitor, + "read fsmonitor extension successful '%s'", + istate->fsmonitor_last_update); return 0; } @@ -134,7 +138,11 @@ void write_fsmonitor_extension(struct strbuf *sb, struct index_state *istate) put_be32(&ewah_size, sb->len - ewah_start); memcpy(sb->buf + fixup, &ewah_size, sizeof(uint32_t)); - trace_printf_key(&trace_fsmonitor, "write fsmonitor extension successful"); + trace2_data_string("index", NULL, "extension/fsmn/write/token", + istate->fsmonitor_last_update); + trace_printf_key(&trace_fsmonitor, + "write fsmonitor extension successful '%s'", + istate->fsmonitor_last_update); } /* @@ -143,6 +151,7 @@ void write_fsmonitor_extension(struct strbuf *sb, struct index_state *istate) static int query_fsmonitor(int version, const char *last_update, struct strbuf *query_result) { struct child_process cp = CHILD_PROCESS_INIT; + int result; if (!core_fsmonitor) return -1; @@ -153,16 +162,63 @@ static int query_fsmonitor(int version, const char *last_update, struct strbuf * cp.use_shell = 1; cp.dir = get_git_work_tree(); - return capture_command(&cp, query_result, 1024); + trace2_region_enter("fsm_hook", "query", NULL); + + result = capture_command(&cp, query_result, 1024); + + if (result) + trace2_data_intmax("fsm_hook", NULL, "query/failed", result); + else { + trace2_data_intmax("fsm_hook", NULL, "query/response-length", + query_result->len); + + if (fsmonitor_is_trivial_response(query_result)) + trace2_data_intmax("fsm_hook", NULL, + "query/trivial-response", 1); + } + + trace2_region_leave("fsm_hook", "query", NULL); + + return result; } -static void fsmonitor_refresh_callback(struct index_state *istate, const char *name) +int fsmonitor_is_trivial_response(const struct strbuf *query_result) { - int pos = index_name_pos(istate, name, strlen(name)); + static char trivial_response[3] = { '\0', '/', '\0' }; + + return query_result->len >= 3 && + !memcmp(trivial_response, + &query_result->buf[query_result->len - 3], 3); +} + +static void fsmonitor_refresh_callback(struct index_state *istate, char *name) +{ + int i, len = strlen(name); + if (name[len - 1] == '/') { + + /* + * TODO We should binary search to find the first path with + * TODO this directory prefix. Then linearly update entries + * TODO while the prefix matches. Taking care to search without + * TODO the trailing slash -- because '/' sorts after a few + * TODO interesting special chars, like '.' and ' '. + */ + + /* Mark all entries for the folder invalid */ + for (i = 0; i < istate->cache_nr; i++) { + if (istate->cache[i]->ce_flags & CE_FSMONITOR_VALID && + starts_with(istate->cache[i]->name, name)) + istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID; + } + /* Need to remove the / from the path for the untracked cache */ + name[len - 1] = '\0'; + } else { + int pos = index_name_pos(istate, name, strlen(name)); - if (pos >= 0) { - struct cache_entry *ce = istate->cache[pos]; - ce->ce_flags &= ~CE_FSMONITOR_VALID; + if (pos >= 0) { + struct cache_entry *ce = istate->cache[pos]; + ce->ce_flags &= ~CE_FSMONITOR_VALID; + } } /* @@ -288,16 +344,45 @@ void refresh_fsmonitor(struct index_state *istate) istate->fsmonitor_last_update = strbuf_detach(&last_update_token, NULL); } +/* + * The caller wants to turn on FSMonitor. And when the caller writes + * the index to disk, a FSMonitor extension should be included. This + * requires that `istate->fsmonitor_last_update` not be NULL. But we + * have not actually talked to a FSMonitor process yet, so we don't + * have an initial value for this field. + * + * For a protocol V1 FSMonitor process, this field is a formatted + * "nanoseconds since epoch" field. However, for a protocol V2 + * FSMonitor process, this field is an opaque token. + * + * Historically, `add_fsmonitor()` has initialized this field to the + * current time for protocol V1 processes. There are lots of race + * conditions here, but that code has shipped... + * + * The only true solution is to use a V2 FSMonitor and get a current + * or default token value (that it understands), but we cannot do that + * until we have actually talked to an instance of the FSMonitor process + * (but the protocol requires that we send a token first...). + * + * For simplicity, just initialize like we have a V1 process and require + * that V2 processes adapt. + */ +static void initialize_fsmonitor_last_update(struct index_state *istate) +{ + struct strbuf last_update = STRBUF_INIT; + + strbuf_addf(&last_update, "%"PRIu64"", getnanotime()); + istate->fsmonitor_last_update = strbuf_detach(&last_update, NULL); +} + void add_fsmonitor(struct index_state *istate) { unsigned int i; - struct strbuf last_update = STRBUF_INIT; if (!istate->fsmonitor_last_update) { trace_printf_key(&trace_fsmonitor, "add fsmonitor"); istate->cache_changed |= FSMONITOR_CHANGED; - strbuf_addf(&last_update, "%"PRIu64"", getnanotime()); - istate->fsmonitor_last_update = strbuf_detach(&last_update, NULL); + initialize_fsmonitor_last_update(istate); /* reset the fsmonitor state */ for (i = 0; i < istate->cache_nr; i++) diff --git a/fsmonitor.h b/fsmonitor.h index 739318ab6d..f20d72631d 100644 --- a/fsmonitor.h +++ b/fsmonitor.h @@ -44,6 +44,22 @@ void tweak_fsmonitor(struct index_state *istate); */ void refresh_fsmonitor(struct index_state *istate); +/* + * Does the received result contain the "trivial" response? + */ +int fsmonitor_is_trivial_response(const struct strbuf *query_result); + +/* + * Check if refresh_fsmonitor has been called at least once. + * refresh_fsmonitor is idempotent. Returns true if fsmonitor is + * not enabled (since the state will be "fresh" w/ CE_FSMONITOR_VALID unset) + * This version is useful for assertions + */ +static inline int is_fsmonitor_refreshed(const struct index_state *istate) +{ + return !core_fsmonitor || istate->fsmonitor_has_run_once; +} + /* * Set the given cache entries CE_FSMONITOR_VALID bit. This should be * called any time the cache entry has been updated to reflect the diff --git a/git-bisect.sh b/git-bisect.sh index 1f3f6e9fc5..6a7afaea8d 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -39,21 +39,6 @@ _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40" TERM_BAD=bad TERM_GOOD=good -bisect_skip() { - all='' - for arg in "$@" - do - case "$arg" in - *..*) - revs=$(git rev-list "$arg") || die "$(eval_gettext "Bad rev input: \$arg")" ;; - *) - revs=$(git rev-parse --sq-quote "$arg") ;; - esac - all="$all $revs" - done - eval git bisect--helper --bisect-state 'skip' $all -} - bisect_visualize() { git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD fail || exit @@ -77,38 +62,6 @@ bisect_visualize() { eval '"$@"' --bisect -- $(cat "$GIT_DIR/BISECT_NAMES") } -bisect_replay () { - file="$1" - test "$#" -eq 1 || die "$(gettext "No logfile given")" - test -r "$file" || die "$(eval_gettext "cannot read \$file for replaying")" - git bisect--helper --bisect-reset || exit - oIFS="$IFS" IFS="$IFS$(printf '\015')" - while read git bisect command rev tail - do - test "$git $bisect" = "git bisect" || test "$git" = "git-bisect" || continue - if test "$git" = "git-bisect" - then - rev="$command" - command="$bisect" - fi - get_terms - git bisect--helper --check-and-set-terms "$command" "$TERM_GOOD" "$TERM_BAD" || exit - get_terms - case "$command" in - start) - eval "git bisect--helper --bisect-start $rev $tail" ;; - "$TERM_GOOD"|"$TERM_BAD"|skip) - git bisect--helper --bisect-write "$command" "$rev" "$TERM_GOOD" "$TERM_BAD" || exit;; - terms) - git bisect--helper --bisect-terms $rev || exit;; - *) - die "$(gettext "?? what are you talking about?")" ;; - esac - done <"$file" - IFS="$oIFS" - git bisect--helper --bisect-auto-next || exit -} - bisect_run () { git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD fail || exit @@ -169,11 +122,6 @@ exit code \$res from '\$command' is < 0 or >= 128" >&2 done } -bisect_log () { - test -s "$GIT_DIR/BISECT_LOG" || die "$(gettext "We are not bisecting.")" - cat "$GIT_DIR/BISECT_LOG" -} - get_terms () { if test -s "$GIT_DIR/BISECT_TERMS" then @@ -199,7 +147,7 @@ case "$#" in bad|good|new|old|"$TERM_BAD"|"$TERM_GOOD") git bisect--helper --bisect-state "$cmd" "$@" ;; skip) - bisect_skip "$@" ;; + git bisect--helper --bisect-skip "$@" || exit;; next) # Not sure we want "next" at the UI level anymore. git bisect--helper --bisect-next "$@" || exit ;; @@ -208,9 +156,9 @@ case "$#" in reset) git bisect--helper --bisect-reset "$@" ;; replay) - bisect_replay "$@" ;; + git bisect--helper --bisect-replay "$@" || exit;; log) - bisect_log ;; + git bisect--helper --bisect-log || exit ;; run) bisect_run "$@" ;; terms) diff --git a/git-compat-util.h b/git-compat-util.h index 5d5e47fbe2..9ddf9d7044 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -252,9 +252,9 @@ typedef unsigned long uintptr_t; #ifdef PRECOMPOSE_UNICODE #include "compat/precompose_utf8.h" #else -static inline void precompose_argv(int argc, const char **argv) +static inline const char *precompose_argv_prefix(int argc, const char **argv, const char *prefix) { - ; /* nothing */ + return prefix; } #define probe_utf8_pathname_composition() #endif @@ -349,6 +349,11 @@ static inline int noop_core_config(const char *var, const char *value, void *cb) #define platform_core_config noop_core_config #endif +int lstat_cache_aware_rmdir(const char *path); +#if !defined(__MINGW32__) && !defined(_MSC_VER) +#define rmdir lstat_cache_aware_rmdir +#endif + #ifndef has_dos_drive_prefix static inline int git_has_dos_drive_prefix(const char *path) { @@ -788,6 +793,12 @@ int git_vsnprintf(char *str, size_t maxsize, const char *format, va_list ap); #endif +#ifdef OPEN_RETURNS_EINTR +#undef open +#define open git_open_with_retry +int git_open_with_retry(const char *path, int flag, ...); +#endif + #ifdef __GLIBC_PREREQ #if __GLIBC_PREREQ(2, 1) #define HAVE_STRCHRNUL @@ -887,7 +898,7 @@ int xstrncmpz(const char *s, const char *t, size_t len); #define FREE_AND_NULL(p) do { free(p); (p) = NULL; } while (0) #define ALLOC_ARRAY(x, alloc) (x) = xmalloc(st_mult(sizeof(*(x)), (alloc))) -#define CALLOC_ARRAY(x, alloc) (x) = xcalloc((alloc), sizeof(*(x))); +#define CALLOC_ARRAY(x, alloc) (x) = xcalloc((alloc), sizeof(*(x))) #define REALLOC_ARRAY(x, alloc) (x) = xrealloc((x), st_mult(sizeof(*(x)), (alloc))) #define COPY_ARRAY(dst, src, n) copy_array((dst), (src), (n), sizeof(*(dst)) + \ @@ -1231,6 +1242,13 @@ int access_or_die(const char *path, int mode, unsigned flag); /* Warn on an inaccessible file if errno indicates this is an error */ int warn_on_fopen_errors(const char *path); +/* + * Open with O_NOFOLLOW, or equivalent. Note that the fallback equivalent + * may be racy. Do not use this as protection against an attacker who can + * simultaneously create paths. + */ +int open_nofollow(const char *path, int flags); + #if !defined(USE_PARENS_AROUND_GETTEXT_N) && defined(__GNUC__) #define USE_PARENS_AROUND_GETTEXT_N 1 #endif diff --git a/git-difftool--helper.sh b/git-difftool--helper.sh index 46af3e60b7..992124cc67 100755 --- a/git-difftool--helper.sh +++ b/git-difftool--helper.sh @@ -61,6 +61,9 @@ launch_merge_tool () { export BASE eval $GIT_DIFFTOOL_EXTCMD '"$LOCAL"' '"$REMOTE"' else + initialize_merge_tool "$merge_tool" + # ignore the error from the above --- run_merge_tool + # will diagnose unusable tool by itself run_merge_tool "$merge_tool" fi } @@ -79,6 +82,9 @@ if test -n "$GIT_DIFFTOOL_DIRDIFF" then LOCAL="$1" REMOTE="$2" + initialize_merge_tool "$merge_tool" + # ignore the error from the above --- run_merge_tool + # will diagnose unusable tool by itself run_merge_tool "$merge_tool" false else # Launch the merge tool on each path provided by 'git diff' diff --git a/git-filter-branch.sh b/git-filter-branch.sh index fea7964617..cb89372813 100755 --- a/git-filter-branch.sh +++ b/git-filter-branch.sh @@ -492,14 +492,12 @@ then sha1=$(git rev-parse "$ref"^0) test -f "$workdir"/../map/$sha1 && continue ancestor=$(git rev-list --simplify-merges -1 "$ref" "$@") - test "$ancestor" && echo $(map $ancestor) >> "$workdir"/../map/$sha1 + test "$ancestor" && echo $(map $ancestor) >"$workdir"/../map/$sha1 done < "$tempdir"/heads fi # Finally update the refs -_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]' -_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40" echo while read ref do @@ -519,7 +517,7 @@ do git update-ref -m "filter-branch: delete" -d "$ref" $sha1 || die "Could not delete $ref" ;; - $_x40) + *) echo "Ref '$ref' was rewritten" if ! git update-ref -m "filter-branch: rewrite" \ "$ref" $rewritten $sha1 2>/dev/null; then @@ -533,16 +531,6 @@ do fi fi ;; - *) - # NEEDSWORK: possibly add -Werror, making this an error - warn "WARNING: '$ref' was rewritten into multiple commits:" - warn "$rewritten" - warn "WARNING: Ref '$ref' points to the first one now." - rewritten=$(echo "$rewritten" | head -n 1) - git update-ref -m "filter-branch: rewrite to first" \ - "$ref" $rewritten $sha1 || - die "Could not rewrite $ref" - ;; esac git update-ref -m "filter-branch: backup" "$orig_namespace$ref" $sha1 || exit diff --git a/git-gui/po/ru.po b/git-gui/po/ru.po index 161ee1ac8c..7aebaf809d 100644 --- a/git-gui/po/ru.po +++ b/git-gui/po/ru.po @@ -331,7 +331,7 @@ msgstr "Добавить изменённые файлы в индекс" #: git-gui.sh:2936 msgid "Unstage From Commit" -msgstr "Убрать из издекса" +msgstr "Убрать из индекса" #: git-gui.sh:2942 lib/index.tcl:521 msgid "Revert Changes" diff --git a/git-mergetool--lib.sh b/git-mergetool--lib.sh index 78f3647ed9..542a6a75eb 100644 --- a/git-mergetool--lib.sh +++ b/git-mergetool--lib.sh @@ -166,6 +166,10 @@ setup_tool () { return 1 } + hide_resolved_enabled () { + return 0 + } + translate_merge_tool_path () { echo "$1" } @@ -250,6 +254,10 @@ trust_exit_code () { fi } +initialize_merge_tool () { + # Bring tool-specific functions into scope + setup_tool "$1" || return 1 +} # Entry point for running tools run_merge_tool () { @@ -261,9 +269,6 @@ run_merge_tool () { merge_tool_path=$(get_merge_tool_path "$1") || exit base_present="$2" - # Bring tool-specific functions into scope - setup_tool "$1" || return 1 - if merge_mode then run_merge_cmd "$1" diff --git a/git-mergetool.sh b/git-mergetool.sh index e3f6d543fb..f751d9cfe2 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -239,6 +239,13 @@ checkout_staged_file () { fi } +hide_resolved () { + git merge-file --ours -q -p "$LOCAL" "$BASE" "$REMOTE" >"$LCONFL" + git merge-file --theirs -q -p "$LOCAL" "$BASE" "$REMOTE" >"$RCONFL" + mv -- "$LCONFL" "$LOCAL" + mv -- "$RCONFL" "$REMOTE" +} + merge_file () { MERGED="$1" @@ -265,6 +272,8 @@ merge_file () { ext= esac + initialize_merge_tool "$merge_tool" || return + mergetool_tmpdir_init if test "$MERGETOOL_TMPDIR" != "." @@ -276,7 +285,9 @@ merge_file () { BACKUP="$MERGETOOL_TMPDIR/${BASE}_BACKUP_$$$ext" LOCAL="$MERGETOOL_TMPDIR/${BASE}_LOCAL_$$$ext" + LCONFL="$MERGETOOL_TMPDIR/${BASE}_LOCAL_LCONFL_$$$ext" REMOTE="$MERGETOOL_TMPDIR/${BASE}_REMOTE_$$$ext" + RCONFL="$MERGETOOL_TMPDIR/${BASE}_REMOTE_RCONFL_$$$ext" BASE="$MERGETOOL_TMPDIR/${BASE}_BASE_$$$ext" base_mode= local_mode= remote_mode= @@ -322,6 +333,40 @@ merge_file () { checkout_staged_file 2 "$MERGED" "$LOCAL" checkout_staged_file 3 "$MERGED" "$REMOTE" + # hideResolved preferences hierarchy. + global_config="mergetool.hideResolved" + tool_config="mergetool.${merge_tool}.hideResolved" + + if enabled=$(git config --type=bool "$tool_config") + then + # The user has a specific preference for a specific tool and no + # other preferences should override that. + : ; + elif enabled=$(git config --type=bool "$global_config") + then + # The user has a general preference for all tools. + # + # 'true' means the user likes the feature so we should use it + # where possible but tool authors can still override. + # + # 'false' means the user doesn't like the feature so we should + # not use it anywhere. + if test "$enabled" = true && hide_resolved_enabled + then + enabled=true + else + enabled=false + fi + else + # The user does not have a preference. Default to disabled. + enabled=false + fi + + if test "$enabled" = true + then + hide_resolved + fi + if test -z "$local_mode" || test -z "$remote_mode" then echo "Deleted merge conflict for '$MERGED':" diff --git a/git-send-email.perl b/git-send-email.perl index 1f425c0809..f5bbf1647e 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -1942,7 +1942,7 @@ sub validate_patch { my ($fn, $xfer_encoding) = @_; if ($repo) { - my $validate_hook = catfile(catdir($repo->repo_path(), 'hooks'), + my $validate_hook = catfile($repo->hooks_path(), 'sendemail-validate'); my $hook_error; if (-x $validate_hook) { diff --git a/git.c b/git.c index b5f63d346b..9bc077a025 100644 --- a/git.c +++ b/git.c @@ -423,7 +423,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv) int nongit_ok; prefix = setup_git_directory_gently(&nongit_ok); } - + prefix = precompose_argv_prefix(argc, argv, prefix); if (use_pager == -1 && p->option & (RUN_SETUP | RUN_SETUP_GENTLY) && !(p->option & DELAY_PAGER_CONFIG)) use_pager = check_pager_config(p->cmd); diff --git a/gpg-interface.c b/gpg-interface.c index b499270836..127aecfc2b 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "commit.h" #include "config.h" #include "run-command.h" #include "strbuf.h" @@ -345,7 +346,7 @@ void print_signature_buffer(const struct signature_check *sigc, unsigned flags) fputs(output, stderr); } -size_t parse_signature(const char *buf, size_t size) +size_t parse_signed_buffer(const char *buf, size_t size) { size_t len = 0; size_t match = size; @@ -361,6 +362,18 @@ size_t parse_signature(const char *buf, size_t size) return match; } +int parse_signature(const char *buf, size_t size, struct strbuf *payload, struct strbuf *signature) +{ + size_t match = parse_signed_buffer(buf, size); + if (match != size) { + strbuf_add(payload, buf, match); + remove_signature(payload); + strbuf_add(signature, buf + match, size - match); + return 1; + } + return 0; +} + void set_signing_key(const char *key) { free(configured_signing_key); diff --git a/gpg-interface.h b/gpg-interface.h index f4e9b4f371..80567e4894 100644 --- a/gpg-interface.h +++ b/gpg-interface.h @@ -37,13 +37,20 @@ struct signature_check { void signature_check_clear(struct signature_check *sigc); +/* + * Look at a GPG signed tag object. If such a signature exists, store it in + * signature and the signed content in payload. Return 1 if a signature was + * found, and 0 otherwise. + */ +int parse_signature(const char *buf, size_t size, struct strbuf *payload, struct strbuf *signature); + /* * Look at GPG signed content (e.g. a signed tag object), whose * payload is followed by a detached signature on it. Return the * offset where the embedded detached signature begins, or the end of * the data when there is no such signature. */ -size_t parse_signature(const char *buf, size_t size); +size_t parse_signed_buffer(const char *buf, size_t size); /* * Create a detached signature for the contents of "buffer" and append diff --git a/grep.c b/grep.c index aabfaaa4c3..c5c348be55 100644 --- a/grep.c +++ b/grep.c @@ -40,20 +40,6 @@ static struct grep_opt grep_defaults = { .output = std_output, }; -#ifdef USE_LIBPCRE2 -static pcre2_general_context *pcre2_global_context; - -static void *pcre2_malloc(PCRE2_SIZE size, MAYBE_UNUSED void *memory_data) -{ - return malloc(size); -} - -static void pcre2_free(void *pointer, MAYBE_UNUSED void *memory_data) -{ - free(pointer); -} -#endif - static const char *color_grep_slots[] = { [GREP_COLOR_CONTEXT] = "context", [GREP_COLOR_FILENAME] = "filename", @@ -152,20 +138,9 @@ int grep_config(const char *var, const char *value, void *cb) * Initialize one instance of grep_opt and copy the * default values from the template we read the configuration * information in an earlier call to git_config(grep_config). - * - * If using PCRE, make sure that the library is configured - * to use the same allocator as Git (e.g. nedmalloc on Windows). - * - * Any allocated memory needs to be released in grep_destroy(). */ void grep_init(struct grep_opt *opt, struct repository *repo, const char *prefix) { -#if defined(USE_LIBPCRE2) - if (!pcre2_global_context) - pcre2_global_context = pcre2_general_context_create( - pcre2_malloc, pcre2_free, NULL); -#endif - *opt = grep_defaults; opt->repo = repo; @@ -175,13 +150,6 @@ void grep_init(struct grep_opt *opt, struct repository *repo, const char *prefix opt->header_tail = &opt->header_list; } -void grep_destroy(void) -{ -#ifdef USE_LIBPCRE2 - pcre2_general_context_free(pcre2_global_context); -#endif -} - static void grep_set_pattern_type_option(enum grep_pattern_type pattern_type, struct grep_opt *opt) { /* @@ -363,6 +331,28 @@ static int is_fixed(const char *s, size_t len) } #ifdef USE_LIBPCRE2 +#define GREP_PCRE2_DEBUG_MALLOC 0 + +static void *pcre2_malloc(PCRE2_SIZE size, MAYBE_UNUSED void *memory_data) +{ + void *pointer = malloc(size); +#if GREP_PCRE2_DEBUG_MALLOC + static int count = 1; + fprintf(stderr, "PCRE2:%p -> #%02d: alloc(%lu)\n", pointer, count++, size); +#endif + return pointer; +} + +static void pcre2_free(void *pointer, MAYBE_UNUSED void *memory_data) +{ +#if GREP_PCRE2_DEBUG_MALLOC + static int count = 1; + if (pointer) + fprintf(stderr, "PCRE2:%p -> #%02d: free()\n", pointer, count++); +#endif + free(pointer); +} + static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt) { int error; @@ -373,17 +363,20 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt int patinforet; size_t jitsizearg; - assert(opt->pcre2); - - p->pcre2_compile_context = NULL; + /* + * Call pcre2_general_context_create() before calling any + * other pcre2_*(). It sets up our malloc()/free() functions + * with which everything else is allocated. + */ + p->pcre2_general_context = pcre2_general_context_create( + pcre2_malloc, pcre2_free, NULL); + if (!p->pcre2_general_context) + die("Couldn't allocate PCRE2 general context"); - /* pcre2_global_context is initialized in append_grep_pattern */ if (opt->ignore_case) { if (!opt->ignore_locale && has_non_ascii(p->pattern)) { - if (!pcre2_global_context) - BUG("pcre2_global_context uninitialized"); - p->pcre2_tables = pcre2_maketables(pcre2_global_context); - p->pcre2_compile_context = pcre2_compile_context_create(NULL); + p->pcre2_tables = pcre2_maketables(p->pcre2_general_context); + p->pcre2_compile_context = pcre2_compile_context_create(p->pcre2_general_context); pcre2_set_character_tables(p->pcre2_compile_context, p->pcre2_tables); } @@ -393,28 +386,18 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt !(!opt->ignore_case && (p->fixed || p->is_fixed))) options |= (PCRE2_UTF | PCRE2_MATCH_INVALID_UTF); +#ifdef GIT_PCRE2_VERSION_10_36_OR_HIGHER /* Work around https://bugs.exim.org/show_bug.cgi?id=2642 fixed in 10.36 */ - if (PCRE2_MATCH_INVALID_UTF && options & (PCRE2_UTF | PCRE2_CASELESS)) { - struct strbuf buf; - int len; - int err; - - if ((len = pcre2_config(PCRE2_CONFIG_VERSION, NULL)) < 0) - BUG("pcre2_config(..., NULL) failed: %d", len); - strbuf_init(&buf, len + 1); - if ((err = pcre2_config(PCRE2_CONFIG_VERSION, buf.buf)) < 0) - BUG("pcre2_config(..., buf.buf) failed: %d", err); - if (versioncmp(buf.buf, "10.36") < 0) - options |= PCRE2_NO_START_OPTIMIZE; - strbuf_release(&buf); - } + if (PCRE2_MATCH_INVALID_UTF && options & (PCRE2_UTF | PCRE2_CASELESS)) + options |= PCRE2_NO_START_OPTIMIZE; +#endif p->pcre2_pattern = pcre2_compile((PCRE2_SPTR)p->pattern, p->patternlen, options, &error, &erroffset, p->pcre2_compile_context); if (p->pcre2_pattern) { - p->pcre2_match_data = pcre2_match_data_create_from_pattern(p->pcre2_pattern, NULL); + p->pcre2_match_data = pcre2_match_data_create_from_pattern(p->pcre2_pattern, p->pcre2_general_context); if (!p->pcre2_match_data) die("Couldn't allocate PCRE2 match data"); } else { @@ -493,7 +476,12 @@ static void free_pcre2_pattern(struct grep_pat *p) pcre2_compile_context_free(p->pcre2_compile_context); pcre2_code_free(p->pcre2_pattern); pcre2_match_data_free(p->pcre2_match_data); +#ifdef GIT_PCRE2_VERSION_10_34_OR_HIGHER + pcre2_maketables_free(p->pcre2_general_context, p->pcre2_tables); +#else free((void *)p->pcre2_tables); +#endif + pcre2_general_context_free(p->pcre2_general_context); } #else /* !USE_LIBPCRE2 */ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt) @@ -555,7 +543,6 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) #endif if (p->fixed || p->is_fixed) { #ifdef USE_LIBPCRE2 - opt->pcre2 = 1; if (p->is_fixed) { compile_pcre2_pattern(p, opt); } else { @@ -621,7 +608,7 @@ static struct grep_expr *compile_pattern_atom(struct grep_pat **list) case GREP_PATTERN: /* atom */ case GREP_PATTERN_HEAD: case GREP_PATTERN_BODY: - x = xcalloc(1, sizeof (struct grep_expr)); + CALLOC_ARRAY(x, 1); x->node = GREP_NODE_ATOM; x->u.atom = p; *list = p->next; @@ -651,7 +638,7 @@ static struct grep_expr *compile_pattern_not(struct grep_pat **list) if (!p->next) die("--not not followed by pattern expression"); *list = p->next; - x = xcalloc(1, sizeof (struct grep_expr)); + CALLOC_ARRAY(x, 1); x->node = GREP_NODE_NOT; x->u.unary = compile_pattern_not(list); if (!x->u.unary) @@ -676,7 +663,7 @@ static struct grep_expr *compile_pattern_and(struct grep_pat **list) y = compile_pattern_and(list); if (!y) die("--and not followed by pattern expression"); - z = xcalloc(1, sizeof (struct grep_expr)); + CALLOC_ARRAY(z, 1); z->node = GREP_NODE_AND; z->u.binary.left = x; z->u.binary.right = y; @@ -696,7 +683,7 @@ static struct grep_expr *compile_pattern_or(struct grep_pat **list) y = compile_pattern_or(list); if (!y) die("not a pattern expression %s", p->pattern); - z = xcalloc(1, sizeof (struct grep_expr)); + CALLOC_ARRAY(z, 1); z->node = GREP_NODE_OR; z->u.binary.left = x; z->u.binary.right = y; diff --git a/grep.h b/grep.h index ae89d6254b..72f82b1e30 100644 --- a/grep.h +++ b/grep.h @@ -4,10 +4,17 @@ #ifdef USE_LIBPCRE2 #define PCRE2_CODE_UNIT_WIDTH 8 #include +#if (PCRE2_MAJOR >= 10 && PCRE2_MINOR >= 36) || PCRE2_MAJOR >= 11 +#define GIT_PCRE2_VERSION_10_36_OR_HIGHER +#endif +#if (PCRE2_MAJOR >= 10 && PCRE2_MINOR >= 34) || PCRE2_MAJOR >= 11 +#define GIT_PCRE2_VERSION_10_34_OR_HIGHER +#endif #else typedef int pcre2_code; typedef int pcre2_match_data; typedef int pcre2_compile_context; +typedef int pcre2_general_context; #endif #ifndef PCRE2_MATCH_INVALID_UTF /* PCRE2_MATCH_* dummy also with !USE_LIBPCRE2, for test-pcre2-config.c */ @@ -69,6 +76,7 @@ struct grep_pat { pcre2_code *pcre2_pattern; pcre2_match_data *pcre2_match_data; pcre2_compile_context *pcre2_compile_context; + pcre2_general_context *pcre2_general_context; const uint8_t *pcre2_tables; uint32_t pcre2_jit_on; unsigned fixed:1; @@ -161,7 +169,6 @@ struct grep_opt { int grep_config(const char *var, const char *value, void *); void grep_init(struct grep_opt *, struct repository *repo, const char *prefix); -void grep_destroy(void); void grep_commit_pattern_type(enum grep_pattern_type, struct grep_opt *opt); void append_grep_pat(struct grep_opt *opt, const char *pat, size_t patlen, const char *origin, int no, enum grep_pat_token t); diff --git a/hashmap.c b/hashmap.c index 5009471800..134d2eec80 100644 --- a/hashmap.c +++ b/hashmap.c @@ -76,7 +76,7 @@ unsigned int memihash_cont(unsigned int hash_seed, const void *buf, size_t len) static void alloc_table(struct hashmap *map, unsigned int size) { map->tablesize = size; - map->table = xcalloc(size, sizeof(struct hashmap_entry *)); + CALLOC_ARRAY(map->table, size); /* calculate resize thresholds for new size */ map->grow_at = (unsigned int) ((uint64_t) size * HASHMAP_LOAD_FACTOR / 100); diff --git a/http-backend.c b/http-backend.c index a03b4bae22..b329bf63f0 100644 --- a/http-backend.c +++ b/http-backend.c @@ -39,7 +39,7 @@ static struct string_list *get_parameters(void) if (!query_params) { const char *query = getenv("QUERY_STRING"); - query_params = xcalloc(1, sizeof(*query_params)); + CALLOC_ARRAY(query_params, 1); while (query && *query) { char *name = url_decode_parameter_name(&query); char *value = url_decode_parameter_value(&query); diff --git a/http-fetch.c b/http-fetch.c index c4ccc5fea9..fa642462a9 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -3,6 +3,7 @@ #include "exec-cmd.h" #include "http.h" #include "walker.h" +#include "strvec.h" static const char http_fetch_usage[] = "git http-fetch " "[-c] [-t] [-a] [-v] [--recover] [-w ref] [--stdin | --packfile=hash | commit-id] url"; @@ -44,7 +45,8 @@ static int fetch_using_walker(const char *raw_url, int get_verbosely, } static void fetch_single_packfile(struct object_id *packfile_hash, - const char *url) { + const char *url, + const char **index_pack_args) { struct http_pack_request *preq; struct slot_results results; int ret; @@ -55,7 +57,8 @@ static void fetch_single_packfile(struct object_id *packfile_hash, if (preq == NULL) die("couldn't create http pack request"); preq->slot->results = &results; - preq->generate_keep = 1; + preq->index_pack_args = index_pack_args; + preq->preserve_index_pack_stdout = 1; if (start_active_slot(preq->slot)) { run_active_slot(preq->slot); @@ -86,6 +89,7 @@ int cmd_main(int argc, const char **argv) int packfile = 0; int nongit; struct object_id packfile_hash; + struct strvec index_pack_args = STRVEC_INIT; setup_git_directory_gently(&nongit); @@ -112,6 +116,8 @@ int cmd_main(int argc, const char **argv) packfile = 1; if (parse_oid_hex(p, &packfile_hash, &end) || *end) die(_("argument to --packfile must be a valid hash (got '%s')"), p); + } else if (skip_prefix(argv[arg], "--index-pack-arg=", &p)) { + strvec_push(&index_pack_args, p); } arg++; } @@ -124,10 +130,18 @@ int cmd_main(int argc, const char **argv) git_config(git_default_config, NULL); if (packfile) { - fetch_single_packfile(&packfile_hash, argv[arg]); + if (!index_pack_args.nr) + die(_("--packfile requires --index-pack-args")); + + fetch_single_packfile(&packfile_hash, argv[arg], + index_pack_args.v); + return 0; } + if (index_pack_args.nr) + die(_("--index-pack-args can only be used with --packfile")); + if (commits_on_stdin) { commits = walker_targets_stdin(&commit_id, &write_ref); } else { diff --git a/http-push.c b/http-push.c index 6a4a43e07f..b60d5fcc85 100644 --- a/http-push.c +++ b/http-push.c @@ -896,7 +896,7 @@ static struct remote_lock *lock_remote(const char *path, long timeout) curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer); - lock = xcalloc(1, sizeof(*lock)); + CALLOC_ARRAY(lock, 1); lock->timeout = -1; if (start_active_slot(slot)) { @@ -1713,7 +1713,7 @@ int cmd_main(int argc, const char **argv) int new_refs; struct ref *ref, *local_refs; - repo = xcalloc(1, sizeof(*repo)); + CALLOC_ARRAY(repo, 1); argv++; for (i = 1; i < argc; i++, argv++) { diff --git a/http.c b/http.c index 8b23a546af..406410f884 100644 --- a/http.c +++ b/http.c @@ -1635,23 +1635,33 @@ static int handle_curl_result(struct slot_results *results) if (results->curl_result == CURLE_OK) { credential_approve(&http_auth); - if (proxy_auth.password) - credential_approve(&proxy_auth); + credential_approve(&proxy_auth); + credential_approve(&cert_auth); return HTTP_OK; + } else if (results->curl_result == CURLE_SSL_CERTPROBLEM) { + /* + * We can't tell from here whether it's a bad path, bad + * certificate, bad password, or something else wrong + * with the certificate. So we reject the credential to + * avoid caching or saving a bad password. + */ + credential_reject(&cert_auth); + return HTTP_NOAUTH; } else if (missing_target(results)) return HTTP_MISSING_TARGET; else if (results->http_code == 401) { +#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY + http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE; + if (results->auth_avail) { + http_auth_methods &= results->auth_avail; + http_auth_methods_restricted = 1; + return HTTP_REAUTH; + } +#endif if (http_auth.username && http_auth.password) { credential_reject(&http_auth); return HTTP_NOAUTH; } else { -#ifdef LIBCURL_CAN_HANDLE_AUTH_ANY - http_auth_methods &= ~CURLAUTH_GSSNEGOTIATE; - if (results->auth_avail) { - http_auth_methods &= results->auth_avail; - http_auth_methods_restricted = 1; - } -#endif return HTTP_REAUTH; } } else { @@ -2259,6 +2269,9 @@ void release_http_pack_request(struct http_pack_request *preq) free(preq); } +static const char *default_index_pack_args[] = + {"index-pack", "--stdin", NULL}; + int finish_http_pack_request(struct http_pack_request *preq) { struct child_process ip = CHILD_PROCESS_INIT; @@ -2270,17 +2283,15 @@ int finish_http_pack_request(struct http_pack_request *preq) tmpfile_fd = xopen(preq->tmpfile.buf, O_RDONLY); - strvec_push(&ip.args, "index-pack"); - strvec_push(&ip.args, "--stdin"); ip.git_cmd = 1; ip.in = tmpfile_fd; - if (preq->generate_keep) { - strvec_pushf(&ip.args, "--keep=git %"PRIuMAX, - (uintmax_t)getpid()); + ip.argv = preq->index_pack_args ? preq->index_pack_args + : default_index_pack_args; + + if (preq->preserve_index_pack_stdout) ip.out = 0; - } else { + else ip.no_stdout = 1; - } if (run_command(&ip)) { ret = -1; @@ -2323,7 +2334,7 @@ struct http_pack_request *new_direct_http_pack_request( off_t prev_posn = 0; struct http_pack_request *preq; - preq = xcalloc(1, sizeof(*preq)); + CALLOC_ARRAY(preq, 1); strbuf_init(&preq->tmpfile, 0); preq->url = url; @@ -2418,7 +2429,7 @@ struct http_object_request *new_http_object_request(const char *base_url, off_t prev_posn = 0; struct http_object_request *freq; - freq = xcalloc(1, sizeof(*freq)); + CALLOC_ARRAY(freq, 1); strbuf_init(&freq->tmpfile, 0); oidcpy(&freq->oid, oid); freq->localfile = -1; diff --git a/http.h b/http.h index 5de792ef3f..bf3d1270ad 100644 --- a/http.h +++ b/http.h @@ -218,12 +218,12 @@ struct http_pack_request { char *url; /* - * If this is true, finish_http_pack_request() will pass "--keep" to - * index-pack, resulting in the creation of a keep file, and will not - * suppress its stdout (that is, the "keep\t\n" line will be - * printed to stdout). + * index-pack command to run. Must be terminated by NULL. + * + * If NULL, defaults to {"index-pack", "--stdin", NULL}. */ - unsigned generate_keep : 1; + const char **index_pack_args; + unsigned preserve_index_pack_stdout : 1; FILE *packfile; struct strbuf tmpfile; diff --git a/imap-send.c b/imap-send.c index d0b94f911e..bb085d66d1 100644 --- a/imap-send.c +++ b/imap-send.c @@ -963,9 +963,9 @@ static struct imap_store *imap_open_store(struct imap_server_conf *srvc, const c char *arg, *rsp; int s = -1, preauth; - ctx = xcalloc(1, sizeof(*ctx)); + CALLOC_ARRAY(ctx, 1); - ctx->imap = imap = xcalloc(1, sizeof(*imap)); + ctx->imap = CALLOC_ARRAY(imap, 1); imap->buf.sock.fd[0] = imap->buf.sock.fd[1] = -1; imap->in_progress_append = &imap->in_progress; diff --git a/line-log.c b/line-log.c index 75c8b1acff..51d93310a4 100644 --- a/line-log.c +++ b/line-log.c @@ -296,7 +296,7 @@ static void line_log_data_insert(struct line_log_data **list, return; } - p = xcalloc(1, sizeof(struct line_log_data)); + CALLOC_ARRAY(p, 1); p->path = path; range_set_append(&p->ranges, begin, end); if (ip) { diff --git a/line-range.c b/line-range.c index 9b50583dc0..955a8a9535 100644 --- a/line-range.c +++ b/line-range.c @@ -202,7 +202,7 @@ static const char *parse_range_funcname( drv = userdiff_find_by_path(istate, path); if (drv && drv->funcname.pattern) { const struct userdiff_funcname *pe = &drv->funcname; - xecfg = xcalloc(1, sizeof(*xecfg)); + CALLOC_ARRAY(xecfg, 1); xdiff_set_find_func(xecfg, pe->pattern, pe->cflags); } diff --git a/list-objects-filter.c b/list-objects-filter.c index 4ec0041cfb..39e2f15333 100644 --- a/list-objects-filter.c +++ b/list-objects-filter.c @@ -186,7 +186,7 @@ static enum list_objects_filter_result filter_trees_depth( seen_info = oidmap_get( &filter_data->seen_at_depth, &obj->oid); if (!seen_info) { - seen_info = xcalloc(1, sizeof(*seen_info)); + CALLOC_ARRAY(seen_info, 1); oidcpy(&seen_info->base.oid, &obj->oid); seen_info->depth = filter_data->current_depth; oidmap_put(&filter_data->seen_at_depth, seen_info); @@ -626,7 +626,7 @@ static void filter_combine__init( size_t sub; d->nr = filter_options->sub_nr; - d->sub = xcalloc(d->nr, sizeof(*d->sub)); + CALLOC_ARRAY(d->sub, d->nr); for (sub = 0; sub < d->nr; sub++) d->sub[sub].filter = list_objects_filter__init( filter->omits ? &d->sub[sub].omits : NULL, @@ -674,7 +674,7 @@ struct filter *list_objects_filter__init( if (!init_fn) return NULL; - filter = xcalloc(1, sizeof(*filter)); + CALLOC_ARRAY(filter, 1); filter->omits = omitted; init_fn(filter_options, filter); return filter; diff --git a/ll-merge.c b/ll-merge.c index 1ec0b959e0..9a8a2c365c 100644 --- a/ll-merge.c +++ b/ll-merge.c @@ -268,7 +268,7 @@ static int read_merge_config(const char *var, const char *value, void *cb) if (!strncmp(fn->name, name, namelen) && !fn->name[namelen]) break; if (!fn) { - fn = xcalloc(1, sizeof(struct ll_merge_driver)); + CALLOC_ARRAY(fn, 1); fn->name = xmemdupz(name, namelen); fn->fn = ll_ext_merge; *ll_user_merge_tail = fn; diff --git a/log-tree.c b/log-tree.c index e048467650..f3178a66a9 100644 --- a/log-tree.c +++ b/log-tree.c @@ -369,8 +369,14 @@ void fmt_output_subject(struct strbuf *filename, int start_len = filename->len; int max_len = start_len + info->patch_name_max - (strlen(suffix) + 1); - if (0 < info->reroll_count) - strbuf_addf(filename, "v%d-", info->reroll_count); + if (info->reroll_count) { + struct strbuf temp = STRBUF_INIT; + + strbuf_addf(&temp, "v%s", info->reroll_count); + format_sanitized_subject(filename, temp.buf, temp.len); + strbuf_addstr(filename, "-"); + strbuf_release(&temp); + } strbuf_addf(filename, "%04d-%s", nr, subject); if (max_len < filename->len) @@ -502,7 +508,7 @@ static void show_signature(struct rev_info *opt, struct commit *commit) struct signature_check sigc = { 0 }; int status; - if (parse_signed_commit(commit, &payload, &signature) <= 0) + if (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0) goto out; status = check_signature(payload.buf, payload.len, signature.buf, @@ -548,7 +554,8 @@ static int show_one_mergetag(struct commit *commit, struct strbuf verify_message; struct signature_check sigc = { 0 }; int status, nth; - size_t payload_size; + struct strbuf payload = STRBUF_INIT; + struct strbuf signature = STRBUF_INIT; hash_object_file(the_hash_algo, extra->value, extra->len, type_name(OBJ_TAG), &oid); @@ -571,13 +578,11 @@ static int show_one_mergetag(struct commit *commit, strbuf_addf(&verify_message, "parent #%d, tagged '%s'\n", nth + 1, tag->tag); - payload_size = parse_signature(extra->value, extra->len); status = -1; - if (extra->len > payload_size) { + if (parse_signature(extra->value, extra->len, &payload, &signature)) { /* could have a good signature */ - status = check_signature(extra->value, payload_size, - extra->value + payload_size, - extra->len - payload_size, &sigc); + status = check_signature(payload.buf, payload.len, + signature.buf, signature.len, &sigc); if (sigc.gpg_output) strbuf_addstr(&verify_message, sigc.gpg_output); else @@ -588,6 +593,8 @@ static int show_one_mergetag(struct commit *commit, show_sig_lines(opt, status, verify_message.buf); strbuf_release(&verify_message); + strbuf_release(&payload); + strbuf_release(&signature); return 0; } @@ -808,6 +815,11 @@ void show_log(struct rev_info *opt) if (cmit_fmt_is_mail(ctx.fmt) && opt->rdiff1) { struct diff_queue_struct dq; struct diff_options opts; + struct range_diff_options range_diff_opts = { + .creation_factor = opt->creation_factor, + .dual_color = 1, + .diffopt = &opts + }; memcpy(&dq, &diff_queued_diff, sizeof(diff_queued_diff)); DIFF_QUEUE_CLEAR(&diff_queued_diff); @@ -822,8 +834,7 @@ void show_log(struct rev_info *opt) opts.file = opt->diffopt.file; opts.use_color = opt->diffopt.use_color; diff_setup_done(&opts); - show_range_diff(opt->rdiff1, opt->rdiff2, - opt->creation_factor, 1, &opts, NULL); + show_range_diff(opt->rdiff1, opt->rdiff2, &range_diff_opts); memcpy(&diff_queued_diff, &dq, sizeof(diff_queued_diff)); } @@ -958,12 +969,14 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log int log_tree_commit(struct rev_info *opt, struct commit *commit) { struct log_info log; - int shown, close_file = opt->diffopt.close_file; + int shown; + /* maybe called by e.g. cmd_log_walk(), maybe stand-alone */ + int no_free = opt->diffopt.no_free; log.commit = commit; log.parent = NULL; opt->loginfo = &log; - opt->diffopt.close_file = 0; + opt->diffopt.no_free = 1; if (opt->line_level_traverse) return line_log_print(opt, commit); @@ -980,7 +993,7 @@ int log_tree_commit(struct rev_info *opt, struct commit *commit) fprintf(opt->diffopt.file, "\n%s\n", opt->break_bar); opt->loginfo = NULL; maybe_flush_or_die(opt->diffopt.file, "stdout"); - if (close_file) - fclose(opt->diffopt.file); + opt->diffopt.no_free = no_free; + diff_free(&opt->diffopt); return shown; } diff --git a/ls-refs.c b/ls-refs.c index 5ff5473869..88f6c3f60d 100644 --- a/ls-refs.c +++ b/ls-refs.c @@ -7,6 +7,39 @@ #include "pkt-line.h" #include "config.h" +static int config_read; +static int advertise_unborn; +static int allow_unborn; + +static void ensure_config_read(void) +{ + const char *str = NULL; + + if (config_read) + return; + + if (repo_config_get_string_tmp(the_repository, "lsrefs.unborn", &str)) { + /* + * If there is no such config, advertise and allow it by + * default. + */ + advertise_unborn = 1; + allow_unborn = 1; + } else { + if (!strcmp(str, "advertise")) { + advertise_unborn = 1; + allow_unborn = 1; + } else if (!strcmp(str, "allow")) { + allow_unborn = 1; + } else if (!strcmp(str, "ignore")) { + /* do nothing */ + } else { + die(_("invalid value '%s' for lsrefs.unborn"), str); + } + } + config_read = 1; +} + /* * Check if one of the prefixes is a prefix of the ref. * If no prefixes were provided, all refs match. @@ -32,6 +65,7 @@ struct ls_refs_data { unsigned peel; unsigned symrefs; struct strvec prefixes; + unsigned unborn : 1; }; static int send_ref(const char *refname, const struct object_id *oid, @@ -47,7 +81,10 @@ static int send_ref(const char *refname, const struct object_id *oid, if (!ref_match(&data->prefixes, refname_nons)) return 0; - strbuf_addf(&refline, "%s %s", oid_to_hex(oid), refname_nons); + if (oid) + strbuf_addf(&refline, "%s %s", oid_to_hex(oid), refname_nons); + else + strbuf_addf(&refline, "unborn %s", refname_nons); if (data->symrefs && flag & REF_ISSYMREF) { struct object_id unused; const char *symref_target = resolve_ref_unsafe(refname, 0, @@ -61,7 +98,7 @@ static int send_ref(const char *refname, const struct object_id *oid, strip_namespace(symref_target)); } - if (data->peel) { + if (data->peel && oid) { struct object_id peeled; if (!peel_iterated_oid(oid, &peeled)) strbuf_addf(&refline, " peeled:%s", oid_to_hex(&peeled)); @@ -74,6 +111,23 @@ static int send_ref(const char *refname, const struct object_id *oid, return 0; } +static void send_possibly_unborn_head(struct ls_refs_data *data) +{ + struct strbuf namespaced = STRBUF_INIT; + struct object_id oid; + int flag; + int oid_is_null; + + strbuf_addf(&namespaced, "%sHEAD", get_git_namespace()); + if (!resolve_ref_unsafe(namespaced.buf, 0, &oid, &flag)) + return; /* bad ref */ + oid_is_null = is_null_oid(&oid); + if (!oid_is_null || + (data->unborn && data->symrefs && (flag & REF_ISSYMREF))) + send_ref(namespaced.buf, oid_is_null ? NULL : &oid, flag, data); + strbuf_release(&namespaced); +} + static int ls_refs_config(const char *var, const char *value, void *data) { /* @@ -92,6 +146,7 @@ int ls_refs(struct repository *r, struct strvec *keys, memset(&data, 0, sizeof(data)); strvec_init(&data.prefixes); + ensure_config_read(); git_config(ls_refs_config, NULL); while (packet_reader_read(request) == PACKET_READ_NORMAL) { @@ -104,12 +159,14 @@ int ls_refs(struct repository *r, struct strvec *keys, data.symrefs = 1; else if (skip_prefix(arg, "ref-prefix ", &out)) strvec_push(&data.prefixes, out); + else if (!strcmp("unborn", arg)) + data.unborn = allow_unborn; } if (request->status != PACKET_READ_FLUSH) die(_("expected flush after ls-refs arguments")); - head_ref_namespaced(send_ref, &data); + send_possibly_unborn_head(&data); if (!data.prefixes.nr) strvec_push(&data.prefixes, ""); for_each_fullref_in_prefixes(get_git_namespace(), data.prefixes.v, @@ -118,3 +175,14 @@ int ls_refs(struct repository *r, struct strvec *keys, strvec_clear(&data.prefixes); return 0; } + +int ls_refs_advertise(struct repository *r, struct strbuf *value) +{ + if (value) { + ensure_config_read(); + if (advertise_unborn) + strbuf_addstr(value, "unborn"); + } + + return 1; +} diff --git a/ls-refs.h b/ls-refs.h index 7b33a7c6b8..a99e4be0bd 100644 --- a/ls-refs.h +++ b/ls-refs.h @@ -6,5 +6,6 @@ struct strvec; struct packet_reader; int ls_refs(struct repository *r, struct strvec *keys, struct packet_reader *request); +int ls_refs_advertise(struct repository *r, struct strbuf *value); #endif /* LS_REFS_H */ diff --git a/mailmap.c b/mailmap.c index eb77c6e77c..d1f7c0d272 100644 --- a/mailmap.c +++ b/mailmap.c @@ -83,7 +83,7 @@ static void add_mapping(struct string_list *map, if (item->util) { me = (struct mailmap_entry *)item->util; } else { - me = xcalloc(1, sizeof(struct mailmap_entry)); + CALLOC_ARRAY(me, 1); me->namemap.strdup_strings = 1; me->namemap.cmp = namemap_cmp; item->util = me; @@ -157,20 +157,30 @@ static void read_mailmap_line(struct string_list *map, char *buffer) add_mapping(map, name1, email1, name2, email2); } -static int read_mailmap_file(struct string_list *map, const char *filename) +/* Flags for read_mailmap_file() */ +#define MAILMAP_NOFOLLOW (1<<0) + +static int read_mailmap_file(struct string_list *map, const char *filename, + unsigned flags) { char buffer[1024]; FILE *f; + int fd; if (!filename) return 0; - f = fopen(filename, "r"); - if (!f) { + if (flags & MAILMAP_NOFOLLOW) + fd = open_nofollow(filename, O_RDONLY); + else + fd = open(filename, O_RDONLY); + + if (fd < 0) { if (errno == ENOENT) return 0; return error_errno("unable to open mailmap at %s", filename); } + f = xfdopen(fd, "r"); while (fgets(buffer, sizeof(buffer), f) != NULL) read_mailmap_line(map, buffer); @@ -225,10 +235,13 @@ int read_mailmap(struct string_list *map) if (!git_mailmap_blob && is_bare_repository()) git_mailmap_blob = "HEAD:.mailmap"; - err |= read_mailmap_file(map, ".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); + err |= read_mailmap_file(map, git_mailmap_file, 0); return err; } diff --git a/mem-pool.c b/mem-pool.c index 8401761dda..ccdcad2e3d 100644 --- a/mem-pool.c +++ b/mem-pool.c @@ -5,7 +5,7 @@ #include "cache.h" #include "mem-pool.h" -#define BLOCK_GROWTH_SIZE 1024*1024 - sizeof(struct mp_block); +#define BLOCK_GROWTH_SIZE (1024 * 1024 - sizeof(struct mp_block)) /* * Allocate a new mp_block and insert it after the block specified in diff --git a/merge-ort.c b/merge-ort.c index bd089cb9a7..5e118a85ee 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -1621,8 +1621,7 @@ static void compute_collisions(struct strmap *collisions, if (collision_info) { free(new_path); } else { - collision_info = xcalloc(1, - sizeof(struct collision_info)); + CALLOC_ARRAY(collision_info, 1); string_list_init(&collision_info->source_files, 0); strmap_put(collisions, new_path, collision_info); } @@ -1763,7 +1762,7 @@ static void apply_directory_rename_modifications(struct merge_options *opt, struct conflict_info *dir_ci; char *cur_dir = dirs_to_insert.items[i].string; - dir_ci = xcalloc(1, sizeof(*dir_ci)); + CALLOC_ARRAY(dir_ci, 1); dir_ci->merged.directory_name = parent_name; len = strlen(parent_name); @@ -2795,7 +2794,7 @@ static void process_entry(struct merge_options *opt, * the directory to remain here, so we need to move this * path to some new location. */ - new_ci = xcalloc(1, sizeof(*new_ci)); + CALLOC_ARRAY(new_ci, 1); /* We don't really want new_ci->merged.result copied, but it'll * be overwritten below so it doesn't matter. We also don't * want any directory mode/oid values copied, but we'll zero @@ -3175,7 +3174,7 @@ static int checkout(struct merge_options *opt, unpack_opts.verbose_update = (opt->verbosity > 2); unpack_opts.fn = twoway_merge; if (1/* FIXME: opts->overwrite_ignore*/) { - unpack_opts.dir = xcalloc(1, sizeof(*unpack_opts.dir)); + CALLOC_ARRAY(unpack_opts.dir, 1); unpack_opts.dir->flags |= DIR_SHOW_IGNORED; setup_standard_excludes(unpack_opts.dir); } diff --git a/merge-recursive.c b/merge-recursive.c index b052974f19..ed31f9496c 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -303,7 +303,7 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type, return; } - ci = xcalloc(1, sizeof(struct rename_conflict_info)); + CALLOC_ARRAY(ci, 1); ci->rename_type = rename_type; ci->ren1 = ren1; ci->ren2 = ren2; @@ -453,7 +453,7 @@ static void unpack_trees_finish(struct merge_options *opt) static int save_files_dirs(const struct object_id *oid, struct strbuf *base, const char *path, - unsigned int mode, int stage, void *context) + unsigned int mode, void *context) { struct path_hashmap_entry *entry; int baselen = base->len; @@ -473,8 +473,8 @@ static void get_files_dirs(struct merge_options *opt, struct tree *tree) { struct pathspec match_all; memset(&match_all, 0, sizeof(match_all)); - read_tree_recursive(opt->repo, tree, "", 0, 0, - &match_all, save_files_dirs, opt); + read_tree(opt->repo, tree, + &match_all, save_files_dirs, opt); } static int get_tree_entry_if_blob(struct repository *r, @@ -2389,8 +2389,7 @@ static void compute_collisions(struct hashmap *collisions, continue; collision_ent = collision_find_entry(collisions, new_path); if (!collision_ent) { - collision_ent = xcalloc(1, - sizeof(struct collision_entry)); + CALLOC_ARRAY(collision_ent, 1); hashmap_entry_init(&collision_ent->ent, strhash(new_path)); hashmap_put(collisions, &collision_ent->ent); @@ -2594,7 +2593,7 @@ static struct string_list *get_renames(struct merge_options *opt, struct string_list *renames; compute_collisions(&collisions, dir_renames, pairs); - renames = xcalloc(1, sizeof(struct string_list)); + CALLOC_ARRAY(renames, 1); for (i = 0; i < pairs->nr; ++i) { struct string_list_item *item; @@ -3664,7 +3663,7 @@ static int merge_start(struct merge_options *opt, struct tree *head) return -1; } - opt->priv = xcalloc(1, sizeof(*opt->priv)); + CALLOC_ARRAY(opt->priv, 1); string_list_init(&opt->priv->df_conflict_file_set, 1); return 0; } diff --git a/mergetools/vimdiff b/mergetools/vimdiff index abc8ce4ec4..96f6209a04 100644 --- a/mergetools/vimdiff +++ b/mergetools/vimdiff @@ -15,6 +15,17 @@ merge_cmd () { "$LOCAL" "$MERGED" "$REMOTE" fi ;; + *vimdiff1) + "$merge_tool_path" -f -d \ + -c 'echon "Resolve conflicts leftward then save. Use :cq to abort."' \ + "$LOCAL" "$REMOTE" + ret="$?" + if test "$ret" -eq 0 + then + cp -- "$LOCAL" "$MERGED" + fi + return "$ret" + ;; *vimdiff2) "$merge_tool_path" -f -d -c 'wincmd l' \ "$LOCAL" "$MERGED" "$REMOTE" @@ -52,7 +63,7 @@ exit_code_trustable () { list_tool_variants () { for prefix in '' g n; do - for suffix in '' 2 3; do + for suffix in '' 1 2 3; do echo "${prefix}vimdiff${suffix}" done done diff --git a/midx.c b/midx.c index 05c40a98e0..9e86583172 100644 --- a/midx.c +++ b/midx.c @@ -11,6 +11,8 @@ #include "trace2.h" #include "run-command.h" #include "repository.h" +#include "chunk-format.h" +#include "pack.h" #define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */ #define MIDX_VERSION 1 @@ -21,14 +23,12 @@ #define MIDX_HEADER_SIZE 12 #define MIDX_MIN_SIZE (MIDX_HEADER_SIZE + the_hash_algo->rawsz) -#define MIDX_MAX_CHUNKS 5 #define MIDX_CHUNK_ALIGNMENT 4 #define MIDX_CHUNKID_PACKNAMES 0x504e414d /* "PNAM" */ #define MIDX_CHUNKID_OIDFANOUT 0x4f494446 /* "OIDF" */ #define MIDX_CHUNKID_OIDLOOKUP 0x4f49444c /* "OIDL" */ #define MIDX_CHUNKID_OBJECTOFFSETS 0x4f4f4646 /* "OOFF" */ #define MIDX_CHUNKID_LARGEOFFSETS 0x4c4f4646 /* "LOFF" */ -#define MIDX_CHUNKLOOKUP_WIDTH (sizeof(uint32_t) + sizeof(uint64_t)) #define MIDX_CHUNK_FANOUT_SIZE (sizeof(uint32_t) * 256) #define MIDX_CHUNK_OFFSET_WIDTH (2 * sizeof(uint32_t)) #define MIDX_CHUNK_LARGE_OFFSET_WIDTH (sizeof(uint64_t)) @@ -48,11 +48,35 @@ static uint8_t oid_version(void) } } +static const unsigned char *get_midx_checksum(struct multi_pack_index *m) +{ + return m->data + m->data_len - the_hash_algo->rawsz; +} + static char *get_midx_filename(const char *object_dir) { return xstrfmt("%s/pack/multi-pack-index", object_dir); } +char *get_midx_rev_filename(struct multi_pack_index *m) +{ + return xstrfmt("%s/pack/multi-pack-index-%s.rev", + m->object_dir, hash_to_hex(get_midx_checksum(m))); +} + +static int midx_read_oid_fanout(const unsigned char *chunk_start, + size_t chunk_size, void *data) +{ + struct multi_pack_index *m = data; + m->chunk_oid_fanout = (uint32_t *)chunk_start; + + if (chunk_size != 4 * 256) { + error(_("multi-pack-index OID fanout is of the wrong size")); + return 1; + } + return 0; +} + struct multi_pack_index *load_multi_pack_index(const char *object_dir, int local) { struct multi_pack_index *m = NULL; @@ -64,6 +88,7 @@ struct multi_pack_index *load_multi_pack_index(const char *object_dir, int local char *midx_name = get_midx_filename(object_dir); uint32_t i; const char *cur_pack_name; + struct chunkfile *cf = NULL; fd = git_open(midx_name); @@ -113,62 +138,27 @@ struct multi_pack_index *load_multi_pack_index(const char *object_dir, int local m->num_packs = get_be32(m->data + MIDX_BYTE_NUM_PACKS); - for (i = 0; i < m->num_chunks; i++) { - uint32_t chunk_id = get_be32(m->data + MIDX_HEADER_SIZE + - MIDX_CHUNKLOOKUP_WIDTH * i); - uint64_t chunk_offset = get_be64(m->data + MIDX_HEADER_SIZE + 4 + - MIDX_CHUNKLOOKUP_WIDTH * i); + cf = init_chunkfile(NULL); - if (chunk_offset >= m->data_len) - die(_("invalid chunk offset (too large)")); - - switch (chunk_id) { - case MIDX_CHUNKID_PACKNAMES: - m->chunk_pack_names = m->data + chunk_offset; - break; - - case MIDX_CHUNKID_OIDFANOUT: - m->chunk_oid_fanout = (uint32_t *)(m->data + chunk_offset); - break; - - case MIDX_CHUNKID_OIDLOOKUP: - m->chunk_oid_lookup = m->data + chunk_offset; - break; - - case MIDX_CHUNKID_OBJECTOFFSETS: - m->chunk_object_offsets = m->data + chunk_offset; - break; - - case MIDX_CHUNKID_LARGEOFFSETS: - m->chunk_large_offsets = m->data + chunk_offset; - break; - - case 0: - die(_("terminating multi-pack-index chunk id appears earlier than expected")); - break; - - default: - /* - * Do nothing on unrecognized chunks, allowing future - * extensions to add optional chunks. - */ - break; - } - } + if (read_table_of_contents(cf, m->data, midx_size, + MIDX_HEADER_SIZE, m->num_chunks)) + goto cleanup_fail; - if (!m->chunk_pack_names) + if (pair_chunk(cf, MIDX_CHUNKID_PACKNAMES, &m->chunk_pack_names) == CHUNK_NOT_FOUND) die(_("multi-pack-index missing required pack-name chunk")); - if (!m->chunk_oid_fanout) + if (read_chunk(cf, MIDX_CHUNKID_OIDFANOUT, midx_read_oid_fanout, m) == CHUNK_NOT_FOUND) die(_("multi-pack-index missing required OID fanout chunk")); - if (!m->chunk_oid_lookup) + if (pair_chunk(cf, MIDX_CHUNKID_OIDLOOKUP, &m->chunk_oid_lookup) == CHUNK_NOT_FOUND) die(_("multi-pack-index missing required OID lookup chunk")); - if (!m->chunk_object_offsets) + if (pair_chunk(cf, MIDX_CHUNKID_OBJECTOFFSETS, &m->chunk_object_offsets) == CHUNK_NOT_FOUND) die(_("multi-pack-index missing required object offsets chunk")); + pair_chunk(cf, MIDX_CHUNKID_LARGEOFFSETS, &m->chunk_large_offsets); + m->num_objects = ntohl(m->chunk_oid_fanout[255]); - m->pack_names = xcalloc(m->num_packs, sizeof(*m->pack_names)); - m->packs = xcalloc(m->num_packs, sizeof(*m->packs)); + CALLOC_ARRAY(m->pack_names, m->num_packs); + CALLOC_ARRAY(m->packs, m->num_packs); cur_pack_name = (const char *)m->chunk_pack_names; for (i = 0; i < m->num_packs; i++) { @@ -190,6 +180,7 @@ struct multi_pack_index *load_multi_pack_index(const char *object_dir, int local cleanup_fail: free(m); free(midx_name); + free(cf); if (midx_map) munmap(midx_map, midx_size); if (0 <= fd) @@ -260,12 +251,12 @@ struct object_id *nth_midxed_object_oid(struct object_id *oid, return oid; } -static off_t nth_midxed_offset(struct multi_pack_index *m, uint32_t pos) +off_t nth_midxed_offset(struct multi_pack_index *m, uint32_t pos) { const unsigned char *offset_data; uint32_t offset32; - offset_data = m->chunk_object_offsets + pos * MIDX_CHUNK_OFFSET_WIDTH; + offset_data = m->chunk_object_offsets + (off_t)pos * MIDX_CHUNK_OFFSET_WIDTH; offset32 = get_be32(offset_data + sizeof(uint32_t)); if (m->chunk_large_offsets && offset32 & MIDX_LARGE_OFFSET_NEEDED) { @@ -279,9 +270,10 @@ static off_t nth_midxed_offset(struct multi_pack_index *m, uint32_t pos) return offset32; } -static uint32_t nth_midxed_pack_int_id(struct multi_pack_index *m, uint32_t pos) +uint32_t nth_midxed_pack_int_id(struct multi_pack_index *m, uint32_t pos) { - return get_be32(m->chunk_object_offsets + pos * MIDX_CHUNK_OFFSET_WIDTH); + return get_be32(m->chunk_object_offsets + + (off_t)pos * MIDX_CHUNK_OFFSET_WIDTH); } static int nth_midxed_pack_entry(struct repository *r, @@ -451,49 +443,67 @@ static int pack_info_compare(const void *_a, const void *_b) return strcmp(a->pack_name, b->pack_name); } -struct pack_list { +static int idx_or_pack_name_cmp(const void *_va, const void *_vb) +{ + const char *pack_name = _va; + const struct pack_info *compar = _vb; + + return cmp_idx_or_pack_name(pack_name, compar->pack_name); +} + +struct write_midx_context { struct pack_info *info; uint32_t nr; uint32_t alloc; struct multi_pack_index *m; struct progress *progress; unsigned pack_paths_checked; + + struct pack_midx_entry *entries; + uint32_t entries_nr; + + uint32_t *pack_perm; + uint32_t *pack_order; + unsigned large_offsets_needed:1; + uint32_t num_large_offsets; + + int preferred_pack_idx; }; static void add_pack_to_midx(const char *full_path, size_t full_path_len, const char *file_name, void *data) { - struct pack_list *packs = (struct pack_list *)data; + struct write_midx_context *ctx = data; if (ends_with(file_name, ".idx")) { - display_progress(packs->progress, ++packs->pack_paths_checked); - if (packs->m && midx_contains_pack(packs->m, file_name)) + display_progress(ctx->progress, ++ctx->pack_paths_checked); + if (ctx->m && midx_contains_pack(ctx->m, file_name)) return; - ALLOC_GROW(packs->info, packs->nr + 1, packs->alloc); + ALLOC_GROW(ctx->info, ctx->nr + 1, ctx->alloc); - packs->info[packs->nr].p = add_packed_git(full_path, - full_path_len, - 0); + ctx->info[ctx->nr].p = add_packed_git(full_path, + full_path_len, + 0); - if (!packs->info[packs->nr].p) { + if (!ctx->info[ctx->nr].p) { warning(_("failed to add packfile '%s'"), full_path); return; } - if (open_pack_index(packs->info[packs->nr].p)) { + if (open_pack_index(ctx->info[ctx->nr].p)) { warning(_("failed to open pack-index '%s'"), full_path); - close_pack(packs->info[packs->nr].p); - FREE_AND_NULL(packs->info[packs->nr].p); + close_pack(ctx->info[ctx->nr].p); + FREE_AND_NULL(ctx->info[ctx->nr].p); return; } - packs->info[packs->nr].pack_name = xstrdup(file_name); - packs->info[packs->nr].orig_pack_int_id = packs->nr; - packs->info[packs->nr].expired = 0; - packs->nr++; + ctx->info[ctx->nr].pack_name = xstrdup(file_name); + ctx->info[ctx->nr].orig_pack_int_id = ctx->nr; + ctx->info[ctx->nr].expired = 0; + ctx->nr++; } } @@ -502,6 +512,7 @@ struct pack_midx_entry { uint32_t pack_int_id; time_t pack_mtime; uint64_t offset; + unsigned preferred : 1; }; static int midx_oid_compare(const void *_a, const void *_b) @@ -513,6 +524,12 @@ static int midx_oid_compare(const void *_a, const void *_b) if (cmp) return cmp; + /* Sort objects in a preferred pack first when multiple copies exist. */ + if (a->preferred > b->preferred) + return -1; + if (a->preferred < b->preferred) + return 1; + if (a->pack_mtime > b->pack_mtime) return -1; else if (a->pack_mtime < b->pack_mtime) @@ -540,7 +557,8 @@ static int nth_midxed_pack_midx_entry(struct multi_pack_index *m, static void fill_pack_entry(uint32_t pack_int_id, struct packed_git *p, uint32_t cur_object, - struct pack_midx_entry *entry) + struct pack_midx_entry *entry, + int preferred) { if (nth_packed_object_id(&entry->oid, p, cur_object) < 0) die(_("failed to locate object %d in packfile"), cur_object); @@ -549,6 +567,7 @@ static void fill_pack_entry(uint32_t pack_int_id, entry->pack_mtime = p->mtime; entry->offset = nth_packed_object_offset(p, cur_object); + entry->preferred = !!preferred; } /* @@ -565,7 +584,8 @@ static void fill_pack_entry(uint32_t pack_int_id, static struct pack_midx_entry *get_sorted_entries(struct multi_pack_index *m, struct pack_info *info, uint32_t nr_packs, - uint32_t *nr_objects) + uint32_t *nr_objects, + int preferred_pack) { uint32_t cur_fanout, cur_pack, cur_object; uint32_t alloc_fanout, alloc_objects, total_objects = 0; @@ -602,12 +622,17 @@ static struct pack_midx_entry *get_sorted_entries(struct multi_pack_index *m, nth_midxed_pack_midx_entry(m, &entries_by_fanout[nr_fanout], cur_object); + if (nth_midxed_pack_int_id(m, cur_object) == preferred_pack) + entries_by_fanout[nr_fanout].preferred = 1; + else + entries_by_fanout[nr_fanout].preferred = 0; nr_fanout++; } } for (cur_pack = start_pack; cur_pack < nr_packs; cur_pack++) { uint32_t start = 0, end; + int preferred = cur_pack == preferred_pack; if (cur_fanout) start = get_pack_fanout(info[cur_pack].p, cur_fanout - 1); @@ -615,7 +640,11 @@ static struct pack_midx_entry *get_sorted_entries(struct multi_pack_index *m, for (cur_object = start; cur_object < end; cur_object++) { ALLOC_GROW(entries_by_fanout, nr_fanout + 1, alloc_fanout); - fill_pack_entry(cur_pack, info[cur_pack].p, cur_object, &entries_by_fanout[nr_fanout]); + fill_pack_entry(cur_pack, + info[cur_pack].p, + cur_object, + &entries_by_fanout[nr_fanout], + preferred); nr_fanout++; } } @@ -643,27 +672,26 @@ static struct pack_midx_entry *get_sorted_entries(struct multi_pack_index *m, return deduplicated_entries; } -static size_t write_midx_pack_names(struct hashfile *f, - struct pack_info *info, - uint32_t num_packs) +static int write_midx_pack_names(struct hashfile *f, void *data) { + struct write_midx_context *ctx = data; uint32_t i; unsigned char padding[MIDX_CHUNK_ALIGNMENT]; size_t written = 0; - for (i = 0; i < num_packs; i++) { + for (i = 0; i < ctx->nr; i++) { size_t writelen; - if (info[i].expired) + if (ctx->info[i].expired) continue; - if (i && strcmp(info[i].pack_name, info[i - 1].pack_name) <= 0) + if (i && strcmp(ctx->info[i].pack_name, ctx->info[i - 1].pack_name) <= 0) BUG("incorrect pack-file order: %s before %s", - info[i - 1].pack_name, - info[i].pack_name); + ctx->info[i - 1].pack_name, + ctx->info[i].pack_name); - writelen = strlen(info[i].pack_name) + 1; - hashwrite(f, info[i].pack_name, writelen); + writelen = strlen(ctx->info[i].pack_name) + 1; + hashwrite(f, ctx->info[i].pack_name, writelen); written += writelen; } @@ -672,18 +700,17 @@ static size_t write_midx_pack_names(struct hashfile *f, if (i < MIDX_CHUNK_ALIGNMENT) { memset(padding, 0, sizeof(padding)); hashwrite(f, padding, i); - written += i; } - return written; + return 0; } -static size_t write_midx_oid_fanout(struct hashfile *f, - struct pack_midx_entry *objects, - uint32_t nr_objects) +static int write_midx_oid_fanout(struct hashfile *f, + void *data) { - struct pack_midx_entry *list = objects; - struct pack_midx_entry *last = objects + nr_objects; + struct write_midx_context *ctx = data; + struct pack_midx_entry *list = ctx->entries; + struct pack_midx_entry *last = ctx->entries + ctx->entries_nr; uint32_t count = 0; uint32_t i; @@ -704,21 +731,21 @@ static size_t write_midx_oid_fanout(struct hashfile *f, list = next; } - return MIDX_CHUNK_FANOUT_SIZE; + return 0; } -static size_t write_midx_oid_lookup(struct hashfile *f, unsigned char hash_len, - struct pack_midx_entry *objects, - uint32_t nr_objects) +static int write_midx_oid_lookup(struct hashfile *f, + void *data) { - struct pack_midx_entry *list = objects; + struct write_midx_context *ctx = data; + unsigned char hash_len = the_hash_algo->rawsz; + struct pack_midx_entry *list = ctx->entries; uint32_t i; - size_t written = 0; - for (i = 0; i < nr_objects; i++) { + for (i = 0; i < ctx->entries_nr; i++) { struct pack_midx_entry *obj = list++; - if (i < nr_objects - 1) { + if (i < ctx->entries_nr - 1) { struct pack_midx_entry *next = list; if (oidcmp(&obj->oid, &next->oid) >= 0) BUG("OIDs not in order: %s >= %s", @@ -727,50 +754,48 @@ static size_t write_midx_oid_lookup(struct hashfile *f, unsigned char hash_len, } hashwrite(f, obj->oid.hash, (int)hash_len); - written += hash_len; } - return written; + return 0; } -static size_t write_midx_object_offsets(struct hashfile *f, int large_offset_needed, - uint32_t *perm, - struct pack_midx_entry *objects, uint32_t nr_objects) +static int write_midx_object_offsets(struct hashfile *f, + void *data) { - struct pack_midx_entry *list = objects; + struct write_midx_context *ctx = data; + struct pack_midx_entry *list = ctx->entries; uint32_t i, nr_large_offset = 0; - size_t written = 0; - for (i = 0; i < nr_objects; i++) { + for (i = 0; i < ctx->entries_nr; i++) { struct pack_midx_entry *obj = list++; - if (perm[obj->pack_int_id] == PACK_EXPIRED) + if (ctx->pack_perm[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, perm[obj->pack_int_id]); + hashwrite_be32(f, ctx->pack_perm[obj->pack_int_id]); - if (large_offset_needed && obj->offset >> 31) + if (ctx->large_offsets_needed && obj->offset >> 31) hashwrite_be32(f, MIDX_LARGE_OFFSET_NEEDED | nr_large_offset++); - else if (!large_offset_needed && obj->offset >> 32) + else if (!ctx->large_offsets_needed && obj->offset >> 32) BUG("object %s requires a large offset (%"PRIx64") but the MIDX is not writing large offsets!", oid_to_hex(&obj->oid), obj->offset); else hashwrite_be32(f, (uint32_t)obj->offset); - - written += MIDX_CHUNK_OFFSET_WIDTH; } - return written; + return 0; } -static size_t write_midx_large_offsets(struct hashfile *f, uint32_t nr_large_offset, - struct pack_midx_entry *objects, uint32_t nr_objects) +static int write_midx_large_offsets(struct hashfile *f, + void *data) { - struct pack_midx_entry *list = objects, *end = objects + nr_objects; - size_t written = 0; + struct write_midx_context *ctx = data; + struct pack_midx_entry *list = ctx->entries; + struct pack_midx_entry *end = ctx->entries + ctx->entries_nr; + uint32_t nr_large_offset = ctx->num_large_offsets; while (nr_large_offset) { struct pack_midx_entry *obj; @@ -785,34 +810,96 @@ static size_t write_midx_large_offsets(struct hashfile *f, uint32_t nr_large_off if (!(offset >> 31)) continue; - written += hashwrite_be64(f, offset); + hashwrite_be64(f, offset); nr_large_offset--; } - return written; + return 0; +} + +struct midx_pack_order_data { + uint32_t nr; + uint32_t pack; + off_t offset; +}; + +static int midx_pack_order_cmp(const void *va, const void *vb) +{ + const struct midx_pack_order_data *a = va, *b = vb; + if (a->pack < b->pack) + return -1; + else if (a->pack > b->pack) + return 1; + else if (a->offset < b->offset) + return -1; + else if (a->offset > b->offset) + return 1; + else + return 0; } +static uint32_t *midx_pack_order(struct write_midx_context *ctx) +{ + struct midx_pack_order_data *data; + uint32_t *pack_order; + uint32_t i; + + ALLOC_ARRAY(data, ctx->entries_nr); + 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 |= (1U << 31); + data[i].offset = e->offset; + } + + QSORT(data, ctx->entries_nr, midx_pack_order_cmp); + + ALLOC_ARRAY(pack_order, ctx->entries_nr); + for (i = 0; i < ctx->entries_nr; i++) + pack_order[i] = data[i].nr; + free(data); + + return pack_order; +} + +static void write_midx_reverse_index(char *midx_name, unsigned char *midx_hash, + struct write_midx_context *ctx) +{ + struct strbuf buf = STRBUF_INIT; + const char *tmp_file; + + strbuf_addf(&buf, "%s-%s.rev", midx_name, hash_to_hex(midx_hash)); + + tmp_file = write_rev_file_order(NULL, ctx->pack_order, ctx->entries_nr, + midx_hash, WRITE_REV); + + if (finalize_object_file(tmp_file, buf.buf)) + die(_("cannot store reverse index file")); + + strbuf_release(&buf); +} + +static void clear_midx_files_ext(struct repository *r, const char *ext, + unsigned char *keep_hash); + static int write_midx_internal(const char *object_dir, struct multi_pack_index *m, - struct string_list *packs_to_drop, unsigned flags) + struct string_list *packs_to_drop, + const char *preferred_pack_name, + unsigned flags) { - unsigned char cur_chunk, num_chunks = 0; char *midx_name; + unsigned char midx_hash[GIT_MAX_RAWSZ]; uint32_t i; struct hashfile *f = NULL; struct lock_file lk; - struct pack_list packs; - uint32_t *pack_perm = NULL; - uint64_t written = 0; - uint32_t chunk_ids[MIDX_MAX_CHUNKS + 1]; - uint64_t chunk_offsets[MIDX_MAX_CHUNKS + 1]; - uint32_t nr_entries, num_large_offsets = 0; - struct pack_midx_entry *entries = NULL; - struct progress *progress = NULL; - int large_offsets_needed = 0; + struct write_midx_context ctx = { 0 }; int pack_name_concat_len = 0; int dropped_packs = 0; int result = 0; + struct chunkfile *cf; midx_name = get_midx_filename(object_dir); if (safe_create_leading_directories(midx_name)) @@ -820,61 +907,74 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index * midx_name); if (m) - packs.m = m; + ctx.m = m; else - packs.m = load_multi_pack_index(object_dir, 1); - - packs.nr = 0; - packs.alloc = packs.m ? packs.m->num_packs : 16; - packs.info = NULL; - ALLOC_ARRAY(packs.info, packs.alloc); - - if (packs.m) { - for (i = 0; i < packs.m->num_packs; i++) { - ALLOC_GROW(packs.info, packs.nr + 1, packs.alloc); - - packs.info[packs.nr].orig_pack_int_id = i; - packs.info[packs.nr].pack_name = xstrdup(packs.m->pack_names[i]); - packs.info[packs.nr].p = NULL; - packs.info[packs.nr].expired = 0; - packs.nr++; + ctx.m = load_multi_pack_index(object_dir, 1); + + ctx.nr = 0; + ctx.alloc = ctx.m ? ctx.m->num_packs : 16; + ctx.info = NULL; + ALLOC_ARRAY(ctx.info, ctx.alloc); + + if (ctx.m) { + for (i = 0; i < ctx.m->num_packs; i++) { + ALLOC_GROW(ctx.info, ctx.nr + 1, ctx.alloc); + + ctx.info[ctx.nr].orig_pack_int_id = i; + ctx.info[ctx.nr].pack_name = xstrdup(ctx.m->pack_names[i]); + ctx.info[ctx.nr].p = NULL; + ctx.info[ctx.nr].expired = 0; + ctx.nr++; } } - packs.pack_paths_checked = 0; + ctx.pack_paths_checked = 0; if (flags & MIDX_PROGRESS) - packs.progress = start_delayed_progress(_("Adding packfiles to multi-pack-index"), 0); + ctx.progress = start_delayed_progress(_("Adding packfiles to multi-pack-index"), 0); else - packs.progress = NULL; + ctx.progress = NULL; - for_each_file_in_pack_dir(object_dir, add_pack_to_midx, &packs); - stop_progress(&packs.progress); + for_each_file_in_pack_dir(object_dir, add_pack_to_midx, &ctx); + stop_progress(&ctx.progress); - if (packs.m && packs.nr == packs.m->num_packs && !packs_to_drop) + if (ctx.m && ctx.nr == ctx.m->num_packs && !packs_to_drop) goto cleanup; - entries = get_sorted_entries(packs.m, packs.info, packs.nr, &nr_entries); + ctx.preferred_pack_idx = -1; + if (preferred_pack_name) { + for (i = 0; i < ctx.nr; i++) { + if (!cmp_idx_or_pack_name(preferred_pack_name, + ctx.info[i].pack_name)) { + ctx.preferred_pack_idx = i; + break; + } + } + } + + ctx.entries = get_sorted_entries(ctx.m, ctx.info, ctx.nr, &ctx.entries_nr, + ctx.preferred_pack_idx); - for (i = 0; i < nr_entries; i++) { - if (entries[i].offset > 0x7fffffff) - num_large_offsets++; - if (entries[i].offset > 0xffffffff) - large_offsets_needed = 1; + ctx.large_offsets_needed = 0; + for (i = 0; i < ctx.entries_nr; i++) { + if (ctx.entries[i].offset > 0x7fffffff) + ctx.num_large_offsets++; + if (ctx.entries[i].offset > 0xffffffff) + ctx.large_offsets_needed = 1; } - QSORT(packs.info, packs.nr, pack_info_compare); + QSORT(ctx.info, ctx.nr, pack_info_compare); if (packs_to_drop && packs_to_drop->nr) { int drop_index = 0; int missing_drops = 0; - for (i = 0; i < packs.nr && drop_index < packs_to_drop->nr; i++) { - int cmp = strcmp(packs.info[i].pack_name, + for (i = 0; i < ctx.nr && drop_index < packs_to_drop->nr; i++) { + int cmp = strcmp(ctx.info[i].pack_name, packs_to_drop->items[drop_index].string); if (!cmp) { drop_index++; - packs.info[i].expired = 1; + 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); @@ -882,7 +982,7 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index * missing_drops++; i--; } else { - packs.info[i].expired = 0; + ctx.info[i].expired = 0; } } @@ -898,19 +998,37 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index * * * pack_perm[old_id] = new_id */ - ALLOC_ARRAY(pack_perm, packs.nr); - for (i = 0; i < packs.nr; i++) { - if (packs.info[i].expired) { + ALLOC_ARRAY(ctx.pack_perm, ctx.nr); + for (i = 0; i < ctx.nr; i++) { + if (ctx.info[i].expired) { dropped_packs++; - pack_perm[packs.info[i].orig_pack_int_id] = PACK_EXPIRED; + ctx.pack_perm[ctx.info[i].orig_pack_int_id] = PACK_EXPIRED; } else { - pack_perm[packs.info[i].orig_pack_int_id] = i - dropped_packs; + ctx.pack_perm[ctx.info[i].orig_pack_int_id] = i - dropped_packs; } } - for (i = 0; i < packs.nr; i++) { - if (!packs.info[i].expired) - pack_name_concat_len += strlen(packs.info[i].pack_name) + 1; + for (i = 0; i < ctx.nr; i++) { + if (!ctx.info[i].expired) + pack_name_concat_len += strlen(ctx.info[i].pack_name) + 1; + } + + /* Check that the preferred pack wasn't expired (if given). */ + if (preferred_pack_name) { + struct pack_info *preferred = bsearch(preferred_pack_name, + ctx.info, ctx.nr, + sizeof(*ctx.info), + idx_or_pack_name_cmp); + + if (!preferred) + warning(_("unknown preferred pack: '%s'"), + preferred_pack_name); + else { + uint32_t perm = ctx.pack_perm[preferred->orig_pack_int_id]; + if (perm == PACK_EXPIRED) + warning(_("preferred pack '%s' is expired"), + preferred_pack_name); + } } if (pack_name_concat_len % MIDX_CHUNK_ALIGNMENT) @@ -919,132 +1037,110 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index * hold_lock_file_for_update(&lk, midx_name, LOCK_DIE_ON_ERROR); f = hashfd(get_lock_file_fd(&lk), get_lock_file_path(&lk)); - FREE_AND_NULL(midx_name); - if (packs.m) - close_midx(packs.m); + if (ctx.m) + close_midx(ctx.m); - cur_chunk = 0; - num_chunks = large_offsets_needed ? 5 : 4; - - if (packs.nr - dropped_packs == 0) { + if (ctx.nr - dropped_packs == 0) { error(_("no pack files to index.")); result = 1; goto cleanup; } - written = write_midx_header(f, num_chunks, packs.nr - dropped_packs); - - chunk_ids[cur_chunk] = MIDX_CHUNKID_PACKNAMES; - chunk_offsets[cur_chunk] = written + (num_chunks + 1) * MIDX_CHUNKLOOKUP_WIDTH; - - cur_chunk++; - chunk_ids[cur_chunk] = MIDX_CHUNKID_OIDFANOUT; - chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + pack_name_concat_len; - - cur_chunk++; - chunk_ids[cur_chunk] = MIDX_CHUNKID_OIDLOOKUP; - chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + MIDX_CHUNK_FANOUT_SIZE; + cf = init_chunkfile(f); - cur_chunk++; - chunk_ids[cur_chunk] = MIDX_CHUNKID_OBJECTOFFSETS; - chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + nr_entries * the_hash_algo->rawsz; + add_chunk(cf, MIDX_CHUNKID_PACKNAMES, pack_name_concat_len, + write_midx_pack_names); + add_chunk(cf, MIDX_CHUNKID_OIDFANOUT, MIDX_CHUNK_FANOUT_SIZE, + write_midx_oid_fanout); + add_chunk(cf, MIDX_CHUNKID_OIDLOOKUP, + (size_t)ctx.entries_nr * the_hash_algo->rawsz, + write_midx_oid_lookup); + add_chunk(cf, MIDX_CHUNKID_OBJECTOFFSETS, + (size_t)ctx.entries_nr * MIDX_CHUNK_OFFSET_WIDTH, + write_midx_object_offsets); - cur_chunk++; - chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + nr_entries * MIDX_CHUNK_OFFSET_WIDTH; - if (large_offsets_needed) { - chunk_ids[cur_chunk] = MIDX_CHUNKID_LARGEOFFSETS; + if (ctx.large_offsets_needed) + add_chunk(cf, MIDX_CHUNKID_LARGEOFFSETS, + (size_t)ctx.num_large_offsets * MIDX_CHUNK_LARGE_OFFSET_WIDTH, + write_midx_large_offsets); - cur_chunk++; - chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + - num_large_offsets * MIDX_CHUNK_LARGE_OFFSET_WIDTH; - } + write_midx_header(f, get_num_chunks(cf), ctx.nr - dropped_packs); + write_chunkfile(cf, &ctx); - chunk_ids[cur_chunk] = 0; + finalize_hashfile(f, midx_hash, CSUM_FSYNC | CSUM_HASH_IN_STREAM); + free_chunkfile(cf); - for (i = 0; i <= num_chunks; i++) { - if (i && chunk_offsets[i] < chunk_offsets[i - 1]) - BUG("incorrect chunk offsets: %"PRIu64" before %"PRIu64, - chunk_offsets[i - 1], - chunk_offsets[i]); + if (flags & MIDX_WRITE_REV_INDEX) + ctx.pack_order = midx_pack_order(&ctx); - if (chunk_offsets[i] % MIDX_CHUNK_ALIGNMENT) - BUG("chunk offset %"PRIu64" is not properly aligned", - chunk_offsets[i]); + if (flags & MIDX_WRITE_REV_INDEX) + write_midx_reverse_index(midx_name, midx_hash, &ctx); + clear_midx_files_ext(the_repository, ".rev", midx_hash); - hashwrite_be32(f, chunk_ids[i]); - hashwrite_be64(f, chunk_offsets[i]); + commit_lock_file(&lk); - written += MIDX_CHUNKLOOKUP_WIDTH; +cleanup: + for (i = 0; i < ctx.nr; i++) { + if (ctx.info[i].p) { + close_pack(ctx.info[i].p); + free(ctx.info[i].p); + } + free(ctx.info[i].pack_name); } - if (flags & MIDX_PROGRESS) - progress = start_delayed_progress(_("Writing chunks to multi-pack-index"), - num_chunks); - for (i = 0; i < num_chunks; i++) { - if (written != chunk_offsets[i]) - BUG("incorrect chunk offset (%"PRIu64" != %"PRIu64") for chunk id %"PRIx32, - chunk_offsets[i], - written, - chunk_ids[i]); - - switch (chunk_ids[i]) { - case MIDX_CHUNKID_PACKNAMES: - written += write_midx_pack_names(f, packs.info, packs.nr); - break; - - case MIDX_CHUNKID_OIDFANOUT: - written += write_midx_oid_fanout(f, entries, nr_entries); - break; - - case MIDX_CHUNKID_OIDLOOKUP: - written += write_midx_oid_lookup(f, the_hash_algo->rawsz, entries, nr_entries); - break; + free(ctx.info); + free(ctx.entries); + free(ctx.pack_perm); + free(ctx.pack_order); + free(midx_name); + return result; +} - case MIDX_CHUNKID_OBJECTOFFSETS: - written += write_midx_object_offsets(f, large_offsets_needed, pack_perm, entries, nr_entries); - break; +int write_midx_file(const char *object_dir, + const char *preferred_pack_name, + unsigned flags) +{ + return write_midx_internal(object_dir, NULL, NULL, preferred_pack_name, + flags); +} - case MIDX_CHUNKID_LARGEOFFSETS: - written += write_midx_large_offsets(f, num_large_offsets, entries, nr_entries); - break; +struct clear_midx_data { + char *keep; + const char *ext; +}; - default: - BUG("trying to write unknown chunk id %"PRIx32, - chunk_ids[i]); - } +static void clear_midx_file_ext(const char *full_path, size_t full_path_len, + const char *file_name, void *_data) +{ + struct clear_midx_data *data = _data; - display_progress(progress, i + 1); - } - stop_progress(&progress); + if (!(starts_with(file_name, "multi-pack-index-") && + ends_with(file_name, data->ext))) + return; + if (data->keep && !strcmp(data->keep, file_name)) + return; - if (written != chunk_offsets[num_chunks]) - BUG("incorrect final offset %"PRIu64" != %"PRIu64, - written, - chunk_offsets[num_chunks]); + if (unlink(full_path)) + die_errno(_("failed to remove %s"), full_path); +} - finalize_hashfile(f, NULL, CSUM_FSYNC | CSUM_HASH_IN_STREAM); - commit_lock_file(&lk); +static void clear_midx_files_ext(struct repository *r, const char *ext, + unsigned char *keep_hash) +{ + struct clear_midx_data data; + memset(&data, 0, sizeof(struct clear_midx_data)); -cleanup: - for (i = 0; i < packs.nr; i++) { - if (packs.info[i].p) { - close_pack(packs.info[i].p); - free(packs.info[i].p); - } - free(packs.info[i].pack_name); - } + if (keep_hash) + data.keep = xstrfmt("multi-pack-index-%s%s", + hash_to_hex(keep_hash), ext); + data.ext = ext; - free(packs.info); - free(entries); - free(pack_perm); - free(midx_name); - return result; -} + for_each_file_in_pack_dir(r->objects->odb->path, + clear_midx_file_ext, + &data); -int write_midx_file(const char *object_dir, unsigned flags) -{ - return write_midx_internal(object_dir, NULL, NULL, flags); + free(data.keep); } void clear_midx_file(struct repository *r) @@ -1059,6 +1155,8 @@ void clear_midx_file(struct repository *r) if (remove_path(midx)) die(_("failed to clear multi-pack-index at %s"), midx); + clear_midx_files_ext(r, ".rev", NULL); + free(midx); } @@ -1239,7 +1337,7 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned fla if (!m) return 0; - count = xcalloc(m->num_packs, sizeof(uint32_t)); + CALLOC_ARRAY(count, m->num_packs); if (flags & MIDX_PROGRESS) progress = start_delayed_progress(_("Counting referenced objects"), @@ -1279,7 +1377,7 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned fla free(count); if (packs_to_drop.nr) - result = write_midx_internal(object_dir, m, &packs_to_drop, flags); + result = write_midx_internal(object_dir, m, &packs_to_drop, NULL, flags); string_list_clear(&packs_to_drop, 0); return result; @@ -1410,7 +1508,7 @@ int midx_repack(struct repository *r, const char *object_dir, size_t batch_size, if (!m) return 0; - include_pack = xcalloc(m->num_packs, sizeof(unsigned char)); + CALLOC_ARRAY(include_pack, m->num_packs); if (batch_size) { if (fill_included_packs_batch(r, m, include_pack, batch_size)) @@ -1468,7 +1566,7 @@ int midx_repack(struct repository *r, const char *object_dir, size_t batch_size, goto cleanup; } - result = write_midx_internal(object_dir, m, NULL, flags); + result = write_midx_internal(object_dir, m, NULL, NULL, flags); m = NULL; cleanup: diff --git a/midx.h b/midx.h index b18cf53bc4..8684cf0fef 100644 --- a/midx.h +++ b/midx.h @@ -15,6 +15,10 @@ struct multi_pack_index { const unsigned char *data; size_t data_len; + const uint32_t *revindex_data; + const uint32_t *revindex_map; + size_t revindex_len; + uint32_t signature; unsigned char version; unsigned char hash_len; @@ -36,10 +40,15 @@ struct multi_pack_index { }; #define MIDX_PROGRESS (1 << 0) +#define MIDX_WRITE_REV_INDEX (1 << 1) + +char *get_midx_rev_filename(struct multi_pack_index *m); struct multi_pack_index *load_multi_pack_index(const char *object_dir, int local); int prepare_midx_pack(struct repository *r, struct multi_pack_index *m, uint32_t pack_int_id); int bsearch_midx(const struct object_id *oid, struct multi_pack_index *m, uint32_t *result); +off_t nth_midxed_offset(struct multi_pack_index *m, uint32_t pos); +uint32_t nth_midxed_pack_int_id(struct multi_pack_index *m, uint32_t pos); struct object_id *nth_midxed_object_oid(struct object_id *oid, struct multi_pack_index *m, uint32_t n); @@ -47,7 +56,7 @@ int fill_midx_entry(struct repository *r, const struct object_id *oid, struct pa int midx_contains_pack(struct multi_pack_index *m, const char *idx_or_pack_name); int prepare_multi_pack_index_one(struct repository *r, const char *object_dir, int local); -int write_midx_file(const char *object_dir, unsigned flags); +int write_midx_file(const char *object_dir, const char *preferred_pack_name, unsigned flags); void clear_midx_file(struct repository *r); int verify_midx_file(struct repository *r, const char *object_dir, unsigned flags); int expire_midx_packs(struct repository *r, const char *object_dir, unsigned flags); diff --git a/name-hash.c b/name-hash.c index 4e03fac9bb..ce28f3f070 100644 --- a/name-hash.c +++ b/name-hash.c @@ -225,7 +225,7 @@ static void init_dir_mutex(void) { int j; - lazy_dir_mutex_array = xcalloc(LAZY_MAX_MUTEX, sizeof(pthread_mutex_t)); + CALLOC_ARRAY(lazy_dir_mutex_array, LAZY_MAX_MUTEX); for (j = 0; j < LAZY_MAX_MUTEX; j++) init_recursive_mutex(&lazy_dir_mutex_array[j]); @@ -514,9 +514,9 @@ static void threaded_lazy_init_name_hash( k_start = 0; nr_each = DIV_ROUND_UP(istate->cache_nr, lazy_nr_dir_threads); - lazy_entries = xcalloc(istate->cache_nr, sizeof(struct lazy_entry)); - td_dir = xcalloc(lazy_nr_dir_threads, sizeof(struct lazy_dir_thread_data)); - td_name = xcalloc(1, sizeof(struct lazy_name_thread_data)); + CALLOC_ARRAY(lazy_entries, istate->cache_nr); + CALLOC_ARRAY(td_dir, lazy_nr_dir_threads); + CALLOC_ARRAY(td_name, 1); init_dir_mutex(); diff --git a/negotiator/default.c b/negotiator/default.c index 4b78f6bf36..434189ae5d 100644 --- a/negotiator/default.c +++ b/negotiator/default.c @@ -167,7 +167,7 @@ void default_negotiator_init(struct fetch_negotiator *negotiator) negotiator->next = next; negotiator->ack = ack; negotiator->release = release; - negotiator->data = ns = xcalloc(1, sizeof(*ns)); + negotiator->data = CALLOC_ARRAY(ns, 1); ns->rev_list.compare = compare_commits_by_commit_date; if (marked) diff --git a/negotiator/skipping.c b/negotiator/skipping.c index dffbc76c49..1236e79224 100644 --- a/negotiator/skipping.c +++ b/negotiator/skipping.c @@ -62,7 +62,7 @@ static struct entry *rev_list_push(struct data *data, struct commit *commit, int struct entry *entry; commit->object.flags |= mark | SEEN; - entry = xcalloc(1, sizeof(*entry)); + CALLOC_ARRAY(entry, 1); entry->commit = commit; prio_queue_put(&data->rev_list, entry); @@ -241,7 +241,7 @@ void skipping_negotiator_init(struct fetch_negotiator *negotiator) negotiator->next = next; negotiator->ack = ack; negotiator->release = release; - negotiator->data = data = xcalloc(1, sizeof(*data)); + negotiator->data = CALLOC_ARRAY(data, 1); data->rev_list.compare = compare; if (marked) diff --git a/notes-merge.c b/notes-merge.c index 2fe724f1cf..d2771fa3d4 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -136,7 +136,7 @@ static struct notes_merge_pair *diff_tree_remote(struct notes_merge_options *o, diff_tree_oid(base, remote, "", &opt); diffcore_std(&opt); - changes = xcalloc(diff_queued_diff.nr, sizeof(struct notes_merge_pair)); + CALLOC_ARRAY(changes, diff_queued_diff.nr); for (i = 0; i < diff_queued_diff.nr; i++) { struct diff_filepair *p = diff_queued_diff.queue[i]; diff --git a/notes-utils.c b/notes-utils.c index 4bf4888d8c..d7d18e30f5 100644 --- a/notes-utils.c +++ b/notes-utils.c @@ -129,7 +129,7 @@ struct notes_rewrite_cfg *init_copy_notes_for_rewrite(const char *cmd) c->cmd = cmd; c->enabled = 1; c->combine = combine_notes_concatenate; - c->refs = xcalloc(1, sizeof(struct string_list)); + CALLOC_ARRAY(c->refs, 1); c->refs->strdup_strings = 1; c->refs_from_env = 0; c->mode_from_env = 0; diff --git a/notes.c b/notes.c index d5ac081e76..a19e4ad794 100644 --- a/notes.c +++ b/notes.c @@ -452,7 +452,7 @@ static void load_subtree(struct notes_tree *t, struct leaf_node *subtree, goto handle_non_note; } - l = xcalloc(1, sizeof(*l)); + CALLOC_ARRAY(l, 1); oidcpy(&l->key_oid, &object_oid); oidcpy(&l->val_oid, &entry.oid); if (note_tree_insert(t, node, n, l, type, diff --git a/object-file.c b/object-file.c index 5bcfde8471..624af408cd 100644 --- a/object-file.c +++ b/object-file.c @@ -546,7 +546,7 @@ static int link_alt_odb_entry(struct repository *r, const char *entry, return -1; } - ent = xcalloc(1, sizeof(*ent)); + CALLOC_ARRAY(ent, 1); ent->path = xstrdup(pathbuf.buf); /* add the alternate entry */ diff --git a/object-store.h b/object-store.h index c4fc9dd74e..ec32c23dcb 100644 --- a/object-store.h +++ b/object-store.h @@ -85,6 +85,9 @@ struct packed_git { multi_pack_index:1; unsigned char hash[GIT_MAX_RAWSZ]; struct revindex_entry *revindex; + const uint32_t *revindex_data; + const uint32_t *revindex_map; + size_t revindex_size; /* something like ".git/objects/pack/xxxxx.pack" */ char pack_name[FLEX_ARRAY]; /* more */ }; @@ -150,6 +153,11 @@ struct raw_object_store { /* A most-recently-used ordered version of the packed_git list. */ struct list_head packed_git_mru; + struct { + struct packed_git **packs; + unsigned flags; + } kept_pack_cache; + /* * A map of packfiles to packed_git structs for tracking which * packs have been loaded already. diff --git a/object.c b/object.c index 98017bed8e..78343781ae 100644 --- a/object.c +++ b/object.c @@ -127,7 +127,7 @@ static void grow_object_hash(struct repository *r) int new_hash_size = r->parsed_objects->obj_hash_size < 32 ? 32 : 2 * r->parsed_objects->obj_hash_size; struct object **new_hash; - new_hash = xcalloc(new_hash_size, sizeof(struct object *)); + CALLOC_ARRAY(new_hash, new_hash_size); for (i = 0; i < r->parsed_objects->obj_hash_size; i++) { struct object *obj = r->parsed_objects->obj_hash[i]; @@ -478,7 +478,7 @@ struct parsed_object_pool *parsed_object_pool_new(void) o->object_state = allocate_alloc_state(); o->is_shallow = -1; - o->shallow_stat = xcalloc(1, sizeof(*o->shallow_stat)); + CALLOC_ARRAY(o->shallow_stat, 1); o->buffer_slab = allocate_commit_buffer_slab(); diff --git a/pack-bitmap.c b/pack-bitmap.c index 60fe20fb87..b4513f8672 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -978,7 +978,7 @@ struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs, /* try to open a bitmapped pack, but don't parse it yet * because we may not need to use it */ - bitmap_git = xcalloc(1, sizeof(*bitmap_git)); + CALLOC_ARRAY(bitmap_git, 1); if (open_pack_bitmap(revs->repo, bitmap_git) < 0) goto cleanup; @@ -997,6 +997,7 @@ struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs, object_list_insert(object, &wants); object = parse_object_or_die(get_tagged_oid(tag), NULL); + object->flags |= (tag->object.flags & UNINTERESTING); } if (object->flags & UNINTERESTING) @@ -1388,7 +1389,7 @@ uint32_t *create_bitmap_mapping(struct bitmap_index *bitmap_git, uint32_t *reposition; num_objects = bitmap_git->pack->num_objects; - reposition = xcalloc(num_objects, sizeof(uint32_t)); + CALLOC_ARRAY(reposition, num_objects); for (i = 0; i < num_objects; ++i) { struct object_id oid; @@ -1430,3 +1431,84 @@ int bitmap_has_oid_in_uninteresting(struct bitmap_index *bitmap_git, return bitmap_git && bitmap_walk_contains(bitmap_git, bitmap_git->haves, oid); } + +static off_t get_disk_usage_for_type(struct bitmap_index *bitmap_git, + enum object_type object_type) +{ + struct bitmap *result = bitmap_git->result; + struct packed_git *pack = bitmap_git->pack; + off_t total = 0; + struct ewah_iterator it; + eword_t filter; + size_t i; + + init_type_iterator(&it, bitmap_git, object_type); + for (i = 0; i < result->word_alloc && + ewah_iterator_next(&filter, &it); i++) { + eword_t word = result->words[i] & filter; + size_t base = (i * BITS_IN_EWORD); + unsigned offset; + + if (!word) + continue; + + for (offset = 0; offset < BITS_IN_EWORD; offset++) { + size_t pos; + + if ((word >> offset) == 0) + break; + + offset += ewah_bit_ctz64(word >> offset); + pos = base + offset; + total += pack_pos_to_offset(pack, pos + 1) - + pack_pos_to_offset(pack, pos); + } + } + + return total; +} + +static off_t get_disk_usage_for_extended(struct bitmap_index *bitmap_git) +{ + struct bitmap *result = bitmap_git->result; + struct packed_git *pack = bitmap_git->pack; + struct eindex *eindex = &bitmap_git->ext_index; + off_t total = 0; + struct object_info oi = OBJECT_INFO_INIT; + off_t object_size; + size_t i; + + oi.disk_sizep = &object_size; + + for (i = 0; i < eindex->count; i++) { + struct object *obj = eindex->objects[i]; + + if (!bitmap_get(result, pack->num_objects + i)) + continue; + + if (oid_object_info_extended(the_repository, &obj->oid, &oi, 0) < 0) + die(_("unable to get disk usage of %s"), + oid_to_hex(&obj->oid)); + + total += object_size; + } + return total; +} + +off_t get_disk_usage_from_bitmap(struct bitmap_index *bitmap_git, + struct rev_info *revs) +{ + off_t total = 0; + + total += get_disk_usage_for_type(bitmap_git, OBJ_COMMIT); + if (revs->tree_objects) + total += get_disk_usage_for_type(bitmap_git, OBJ_TREE); + if (revs->blob_objects) + total += get_disk_usage_for_type(bitmap_git, OBJ_BLOB); + if (revs->tag_objects) + total += get_disk_usage_for_type(bitmap_git, OBJ_TAG); + + total += get_disk_usage_for_extended(bitmap_git); + + return total; +} diff --git a/pack-bitmap.h b/pack-bitmap.h index 25dfcf5615..36d99930d8 100644 --- a/pack-bitmap.h +++ b/pack-bitmap.h @@ -68,6 +68,8 @@ int bitmap_walk_contains(struct bitmap_index *, */ int bitmap_has_oid_in_uninteresting(struct bitmap_index *, const struct object_id *oid); +off_t get_disk_usage_from_bitmap(struct bitmap_index *, struct rev_info *); + void bitmap_writer_show_progress(int show); void bitmap_writer_set_checksum(unsigned char *sha1); void bitmap_writer_build_type_index(struct packing_data *to_pack, diff --git a/pack-objects.c b/pack-objects.c index f2a433885a..fe2a4eace9 100644 --- a/pack-objects.c +++ b/pack-objects.c @@ -49,7 +49,7 @@ static void rehash_objects(struct packing_data *pdata) pdata->index_size = 1024; free(pdata->index); - pdata->index = xcalloc(pdata->index_size, sizeof(*pdata->index)); + CALLOC_ARRAY(pdata->index, pdata->index_size); entry = pdata->objects; diff --git a/pack-revindex.c b/pack-revindex.c index 5e69bc7372..0e4a31d9db 100644 --- a/pack-revindex.c +++ b/pack-revindex.c @@ -2,6 +2,8 @@ #include "pack-revindex.h" #include "object-store.h" #include "packfile.h" +#include "config.h" +#include "midx.h" struct revindex_entry { off_t offset; @@ -164,13 +166,168 @@ static void create_pack_revindex(struct packed_git *p) sort_revindex(p->revindex, num_ent, p->pack_size); } -int load_pack_revindex(struct packed_git *p) +static int create_pack_revindex_in_memory(struct packed_git *p) +{ + if (git_env_bool(GIT_TEST_REV_INDEX_DIE_IN_MEMORY, 0)) + die("dying as requested by '%s'", + GIT_TEST_REV_INDEX_DIE_IN_MEMORY); + if (open_pack_index(p)) + return -1; + create_pack_revindex(p); + return 0; +} + +static char *pack_revindex_filename(struct packed_git *p) +{ + size_t len; + if (!strip_suffix(p->pack_name, ".pack", &len)) + BUG("pack_name does not end in .pack"); + return xstrfmt("%.*s.rev", (int)len, p->pack_name); +} + +#define RIDX_HEADER_SIZE (12) +#define RIDX_MIN_SIZE (RIDX_HEADER_SIZE + (2 * the_hash_algo->rawsz)) + +struct revindex_header { + uint32_t signature; + uint32_t version; + uint32_t hash_id; +}; + +static int load_revindex_from_disk(char *revindex_name, + uint32_t num_objects, + const uint32_t **data_p, size_t *len_p) { - if (!p->revindex) { - if (open_pack_index(p)) - return -1; - create_pack_revindex(p); + int fd, ret = 0; + struct stat st; + void *data = NULL; + size_t revindex_size; + struct revindex_header *hdr; + + fd = git_open(revindex_name); + + if (fd < 0) { + ret = -1; + goto cleanup; + } + if (fstat(fd, &st)) { + ret = error_errno(_("failed to read %s"), revindex_name); + goto cleanup; + } + + revindex_size = xsize_t(st.st_size); + + if (revindex_size < RIDX_MIN_SIZE) { + ret = error(_("reverse-index file %s is too small"), revindex_name); + goto cleanup; + } + + if (revindex_size - RIDX_MIN_SIZE != st_mult(sizeof(uint32_t), num_objects)) { + ret = error(_("reverse-index file %s is corrupt"), revindex_name); + goto cleanup; + } + + data = xmmap(NULL, revindex_size, PROT_READ, MAP_PRIVATE, fd, 0); + hdr = data; + + if (ntohl(hdr->signature) != RIDX_SIGNATURE) { + ret = error(_("reverse-index file %s has unknown signature"), revindex_name); + goto cleanup; } + if (ntohl(hdr->version) != 1) { + ret = error(_("reverse-index file %s has unsupported version %"PRIu32), + revindex_name, ntohl(hdr->version)); + goto cleanup; + } + if (!(ntohl(hdr->hash_id) == 1 || ntohl(hdr->hash_id) == 2)) { + ret = error(_("reverse-index file %s has unsupported hash id %"PRIu32), + revindex_name, ntohl(hdr->hash_id)); + goto cleanup; + } + +cleanup: + if (ret) { + if (data) + munmap(data, revindex_size); + } else { + *len_p = revindex_size; + *data_p = (const uint32_t *)data; + } + + if (fd >= 0) + close(fd); + return ret; +} + +static int load_pack_revindex_from_disk(struct packed_git *p) +{ + char *revindex_name; + int ret; + if (open_pack_index(p)) + return -1; + + revindex_name = pack_revindex_filename(p); + + ret = load_revindex_from_disk(revindex_name, + p->num_objects, + &p->revindex_map, + &p->revindex_size); + if (ret) + goto cleanup; + + p->revindex_data = (const uint32_t *)((const char *)p->revindex_map + RIDX_HEADER_SIZE); + +cleanup: + free(revindex_name); + return ret; +} + +int load_pack_revindex(struct packed_git *p) +{ + if (p->revindex || p->revindex_data) + return 0; + + if (!load_pack_revindex_from_disk(p)) + return 0; + else if (!create_pack_revindex_in_memory(p)) + return 0; + return -1; +} + +int load_midx_revindex(struct multi_pack_index *m) +{ + char *revindex_name; + int ret; + if (m->revindex_data) + return 0; + + revindex_name = get_midx_rev_filename(m); + + ret = load_revindex_from_disk(revindex_name, + m->num_objects, + &m->revindex_map, + &m->revindex_len); + if (ret) + goto cleanup; + + m->revindex_data = (const uint32_t *)((const char *)m->revindex_map + RIDX_HEADER_SIZE); + +cleanup: + free(revindex_name); + return ret; +} + +int close_midx_revindex(struct multi_pack_index *m) +{ + if (!m || !m->revindex_map) + return 0; + + munmap((void*)m->revindex_map, m->revindex_len); + + m->revindex_map = NULL; + m->revindex_data = NULL; + m->revindex_len = 0; + return 0; } @@ -203,18 +360,116 @@ int offset_to_pack_pos(struct packed_git *p, off_t ofs, uint32_t *pos) uint32_t pack_pos_to_index(struct packed_git *p, uint32_t pos) { - if (!p->revindex) + if (!(p->revindex || p->revindex_data)) BUG("pack_pos_to_index: reverse index not yet loaded"); if (p->num_objects <= pos) BUG("pack_pos_to_index: out-of-bounds object at %"PRIu32, pos); - return p->revindex[pos].nr; + + if (p->revindex) + return p->revindex[pos].nr; + else + return get_be32(p->revindex_data + pos); } off_t pack_pos_to_offset(struct packed_git *p, uint32_t pos) { - if (!p->revindex) + if (!(p->revindex || p->revindex_data)) BUG("pack_pos_to_index: reverse index not yet loaded"); if (p->num_objects < pos) BUG("pack_pos_to_offset: out-of-bounds object at %"PRIu32, pos); - return p->revindex[pos].offset; + + if (p->revindex) + return p->revindex[pos].offset; + else if (pos == p->num_objects) + return p->pack_size - the_hash_algo->rawsz; + else + return nth_packed_object_offset(p, pack_pos_to_index(p, pos)); +} + +uint32_t pack_pos_to_midx(struct multi_pack_index *m, uint32_t pos) +{ + if (!m->revindex_data) + BUG("pack_pos_to_midx: reverse index not yet loaded"); + if (m->num_objects <= pos) + BUG("pack_pos_to_midx: out-of-bounds object at %"PRIu32, pos); + return get_be32(m->revindex_data + pos); +} + +struct midx_pack_key { + uint32_t pack; + off_t offset; + + uint32_t preferred_pack; + struct multi_pack_index *midx; +}; + +static int midx_pack_order_cmp(const void *va, const void *vb) +{ + const struct midx_pack_key *key = va; + struct multi_pack_index *midx = key->midx; + + uint32_t versus = pack_pos_to_midx(midx, (uint32_t*)vb - (const uint32_t *)midx->revindex_data); + uint32_t versus_pack = nth_midxed_pack_int_id(midx, versus); + off_t versus_offset; + + uint32_t key_preferred = key->pack == key->preferred_pack; + uint32_t versus_preferred = versus_pack == key->preferred_pack; + + /* + * First, compare the preferred-ness, noting that the preferred pack + * comes first. + */ + if (key_preferred && !versus_preferred) + return -1; + else if (!key_preferred && versus_preferred) + return 1; + + /* Then, break ties first by comparing the pack IDs. */ + if (key->pack < versus_pack) + return -1; + else if (key->pack > versus_pack) + return 1; + + /* Finally, break ties by comparing offsets within a pack. */ + versus_offset = nth_midxed_offset(midx, versus); + if (key->offset < versus_offset) + return -1; + else if (key->offset > versus_offset) + return 1; + + return 0; +} + +int midx_to_pack_pos(struct multi_pack_index *m, uint32_t at, uint32_t *pos) +{ + struct midx_pack_key key; + uint32_t *found; + + if (!m->revindex_data) + BUG("midx_to_pack_pos: reverse index not yet loaded"); + if (m->num_objects <= at) + BUG("midx_to_pack_pos: out-of-bounds object at %"PRIu32, at); + + key.pack = nth_midxed_pack_int_id(m, at); + key.offset = nth_midxed_offset(m, at); + key.midx = m; + /* + * The preferred pack sorts first, so determine its identifier by + * looking at the first object in pseudo-pack order. + * + * Note that if no --preferred-pack is explicitly given when writing a + * multi-pack index, then whichever pack has the lowest identifier + * implicitly is preferred (and includes all its objects, since ties are + * broken first by pack identifier). + */ + key.preferred_pack = nth_midxed_pack_int_id(m, pack_pos_to_midx(m, 0)); + + found = bsearch(&key, m->revindex_data, m->num_objects, + sizeof(*m->revindex_data), midx_pack_order_cmp); + + if (!found) + return error("bad offset for revindex"); + + *pos = found - m->revindex_data; + return 0; } diff --git a/pack-revindex.h b/pack-revindex.h index 6e0320b08b..479b8f2f9c 100644 --- a/pack-revindex.h +++ b/pack-revindex.h @@ -14,16 +14,57 @@ * * - offset: the byte offset within the .pack file at which the object contents * can be found + * + * The revindex can also be used with a multi-pack index (MIDX). In this + * setting: + * + * - index position refers to an object's numeric position within the MIDX + * + * - pack position refers to an object's position within a non-existent pack + * described by the MIDX. The pack structure is described in + * Documentation/technical/pack-format.txt. + * + * It is effectively a concatanation of all packs in the MIDX (ordered by + * their numeric ID within the MIDX) in their original order within each + * pack), removing duplicates, and placing the preferred pack (if any) + * first. */ + +#define RIDX_SIGNATURE 0x52494458 /* "RIDX" */ +#define RIDX_VERSION 1 + +#define GIT_TEST_WRITE_REV_INDEX "GIT_TEST_WRITE_REV_INDEX" +#define GIT_TEST_REV_INDEX_DIE_IN_MEMORY "GIT_TEST_REV_INDEX_DIE_IN_MEMORY" + struct packed_git; +struct multi_pack_index; /* * load_pack_revindex populates the revindex's internal data-structures for the * given pack, returning zero on success and a negative value otherwise. + * + * If a '.rev' file is present it is mmap'd, and pointers are assigned into it + * (instead of using the in-memory variant). */ int load_pack_revindex(struct packed_git *p); +/* + * load_midx_revindex loads the '.rev' file corresponding to the given + * multi-pack index by mmap-ing it and assigning pointers in the + * multi_pack_index to point at it. + * + * A negative number is returned on error. + */ +int load_midx_revindex(struct multi_pack_index *m); + +/* + * Frees resources associated with a multi-pack reverse index. + * + * A negative number is returned on error. + */ +int close_midx_revindex(struct multi_pack_index *m); + /* * offset_to_pack_pos converts an object offset to a pack position. This * function returns zero on success, and a negative number otherwise. The @@ -55,8 +96,32 @@ uint32_t pack_pos_to_index(struct packed_git *p, uint32_t pos); * If the reverse index has not yet been loaded, or the position is out of * bounds, this function aborts. * - * This function runs in constant time. + * This function runs in constant time under both in-memory and on-disk reverse + * indexes, but an additional step is taken to consult the corresponding .idx + * file when using the on-disk format. */ off_t pack_pos_to_offset(struct packed_git *p, uint32_t pos); +/* + * pack_pos_to_midx converts the object at position "pos" within the MIDX + * pseudo-pack into a MIDX position. + * + * If the reverse index has not yet been loaded, or the position is out of + * bounds, this function aborts. + * + * This function runs in time O(log N) with the number of objects in the MIDX. + */ +uint32_t pack_pos_to_midx(struct multi_pack_index *m, uint32_t pos); + +/* + * midx_to_pack_pos converts from the MIDX-relative position at "at" to the + * corresponding pack position. + * + * If the reverse index has not yet been loaded, or the position is out of + * bounds, this function aborts. + * + * This function runs in constant time. + */ +int midx_to_pack_pos(struct multi_pack_index *midx, uint32_t at, uint32_t *pos); + #endif diff --git a/pack-write.c b/pack-write.c index e9bb3fd949..f1fc3ecafa 100644 --- a/pack-write.c +++ b/pack-write.c @@ -167,6 +167,127 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec return index_name; } +static int pack_order_cmp(const void *va, const void *vb, void *ctx) +{ + struct pack_idx_entry **objects = ctx; + + off_t oa = objects[*(uint32_t*)va]->offset; + off_t ob = objects[*(uint32_t*)vb]->offset; + + if (oa < ob) + return -1; + if (oa > ob) + return 1; + return 0; +} + +static void write_rev_header(struct hashfile *f) +{ + uint32_t oid_version; + switch (hash_algo_by_ptr(the_hash_algo)) { + case GIT_HASH_SHA1: + oid_version = 1; + break; + case GIT_HASH_SHA256: + oid_version = 2; + break; + default: + die("write_rev_header: unknown hash version"); + } + + hashwrite_be32(f, RIDX_SIGNATURE); + hashwrite_be32(f, RIDX_VERSION); + hashwrite_be32(f, oid_version); +} + +static void write_rev_index_positions(struct hashfile *f, + uint32_t *pack_order, + uint32_t nr_objects) +{ + uint32_t i; + for (i = 0; i < nr_objects; i++) + hashwrite_be32(f, pack_order[i]); +} + +static void write_rev_trailer(struct hashfile *f, const unsigned char *hash) +{ + hashwrite(f, hash, the_hash_algo->rawsz); +} + +const char *write_rev_file(const char *rev_name, + struct pack_idx_entry **objects, + uint32_t nr_objects, + const unsigned char *hash, + unsigned flags) +{ + uint32_t *pack_order; + uint32_t i; + const char *ret; + + ALLOC_ARRAY(pack_order, nr_objects); + for (i = 0; i < nr_objects; i++) + pack_order[i] = i; + QSORT_S(pack_order, nr_objects, pack_order_cmp, objects); + + ret = write_rev_file_order(rev_name, pack_order, nr_objects, hash, + flags); + + free(pack_order); + + return ret; +} + +const char *write_rev_file_order(const char *rev_name, + uint32_t *pack_order, + uint32_t nr_objects, + const unsigned char *hash, + unsigned flags) +{ + struct hashfile *f; + int fd; + + if ((flags & WRITE_REV) && (flags & WRITE_REV_VERIFY)) + die(_("cannot both write and verify reverse index")); + + if (flags & WRITE_REV) { + if (!rev_name) { + struct strbuf tmp_file = STRBUF_INIT; + fd = odb_mkstemp(&tmp_file, "pack/tmp_rev_XXXXXX"); + rev_name = strbuf_detach(&tmp_file, NULL); + } else { + unlink(rev_name); + fd = open(rev_name, O_CREAT|O_EXCL|O_WRONLY, 0600); + if (fd < 0) + die_errno("unable to create '%s'", rev_name); + } + f = hashfd(fd, rev_name); + } else if (flags & WRITE_REV_VERIFY) { + struct stat statbuf; + if (stat(rev_name, &statbuf)) { + if (errno == ENOENT) { + /* .rev files are optional */ + return NULL; + } else + die_errno(_("could not stat: %s"), rev_name); + } + f = hashfd_check(rev_name); + } else + return NULL; + + write_rev_header(f); + + write_rev_index_positions(f, pack_order, nr_objects); + write_rev_trailer(f, hash); + + if (rev_name && adjust_shared_perm(rev_name) < 0) + die(_("failed to make %s readable"), rev_name); + + finalize_hashfile(f, NULL, CSUM_HASH_IN_STREAM | CSUM_CLOSE | + ((flags & WRITE_IDX_VERIFY) ? 0 : CSUM_FSYNC)); + + return rev_name; +} + off_t write_pack_header(struct hashfile *f, uint32_t nr_entries) { struct pack_header hdr; @@ -273,7 +394,7 @@ void fixup_pack_header_footer(int pack_fd, fsync_or_die(pack_fd, pack_name); } -char *index_pack_lockfile(int ip_out) +char *index_pack_lockfile(int ip_out, int *is_well_formed) { char packname[GIT_MAX_HEXSZ + 6]; const int len = the_hash_algo->hexsz + 6; @@ -287,11 +408,17 @@ char *index_pack_lockfile(int ip_out) */ if (read_in_full(ip_out, packname, len) == len && packname[len-1] == '\n') { const char *name; + + if (is_well_formed) + *is_well_formed = 1; packname[len-1] = 0; if (skip_prefix(packname, "keep\t", &name)) return xstrfmt("%s/pack/pack-%s.keep", get_object_directory(), name); + return NULL; } + if (is_well_formed) + *is_well_formed = 0; return NULL; } @@ -342,7 +469,7 @@ void finish_tmp_packfile(struct strbuf *name_buffer, struct pack_idx_option *pack_idx_opts, unsigned char hash[]) { - const char *idx_tmp_name; + const char *idx_tmp_name, *rev_tmp_name = NULL; int basename_len = name_buffer->len; if (adjust_shared_perm(pack_tmp_name)) @@ -353,6 +480,9 @@ void finish_tmp_packfile(struct strbuf *name_buffer, if (adjust_shared_perm(idx_tmp_name)) die_errno("unable to make temporary index file readable"); + rev_tmp_name = write_rev_file(NULL, written_list, nr_written, hash, + pack_idx_opts->flags); + strbuf_addf(name_buffer, "%s.pack", hash_to_hex(hash)); if (rename(pack_tmp_name, name_buffer->buf)) @@ -366,6 +496,14 @@ void finish_tmp_packfile(struct strbuf *name_buffer, strbuf_setlen(name_buffer, basename_len); + if (rev_tmp_name) { + strbuf_addf(name_buffer, "%s.rev", hash_to_hex(hash)); + if (rename(rev_tmp_name, name_buffer->buf)) + die_errno("unable to rename temporary reverse-index file"); + } + + strbuf_setlen(name_buffer, basename_len); + free((void *)idx_tmp_name); } diff --git a/pack.h b/pack.h index 9ae640f417..fa13954526 100644 --- a/pack.h +++ b/pack.h @@ -42,6 +42,8 @@ struct pack_idx_option { /* flag bits */ #define WRITE_IDX_VERIFY 01 /* verify only, do not write the idx file */ #define WRITE_IDX_STRICT 02 +#define WRITE_REV 04 +#define WRITE_REV_VERIFY 010 uint32_t version; uint32_t off32_limit; @@ -85,12 +87,15 @@ int verify_pack_index(struct packed_git *); int verify_pack(struct repository *, struct packed_git *, verify_fn fn, struct progress *, uint32_t); off_t write_pack_header(struct hashfile *f, uint32_t); void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t, unsigned char *, off_t); -char *index_pack_lockfile(int fd); +char *index_pack_lockfile(int fd, int *is_well_formed); struct ref; void write_promisor_file(const char *promisor_name, struct ref **sought, int nr_sought); +const char *write_rev_file(const char *rev_name, struct pack_idx_entry **objects, uint32_t nr_objects, const unsigned char *hash, unsigned flags); +const char *write_rev_file_order(const char *rev_name, uint32_t *pack_order, uint32_t nr_objects, const unsigned char *hash, unsigned flags); + /* * The "hdr" output buffer should be at least this big, which will handle sizes * up to 2^67. diff --git a/packfile.c b/packfile.c index 4b938b4372..8668345d93 100644 --- a/packfile.c +++ b/packfile.c @@ -324,11 +324,21 @@ void close_pack_index(struct packed_git *p) } } +void close_pack_revindex(struct packed_git *p) { + if (!p->revindex_map) + return; + + munmap((void *)p->revindex_map, p->revindex_size); + p->revindex_map = NULL; + p->revindex_data = NULL; +} + void close_pack(struct packed_git *p) { close_pack_windows(p); close_pack_fd(p); close_pack_index(p); + close_pack_revindex(p); } void close_object_store(struct raw_object_store *o) @@ -351,7 +361,7 @@ void close_object_store(struct raw_object_store *o) void unlink_pack_path(const char *pack_name, int force_delete) { - static const char *exts[] = {".pack", ".idx", ".keep", ".bitmap", ".promisor"}; + static const char *exts[] = {".pack", ".idx", ".rev", ".keep", ".bitmap", ".promisor"}; int i; struct strbuf buf = STRBUF_INIT; size_t plen; @@ -628,7 +638,7 @@ unsigned char *use_pack(struct packed_git *p, if (p->pack_fd == -1 && open_packed_git(p)) die("packfile %s cannot be accessed", p->pack_name); - win = xcalloc(1, sizeof(*win)); + CALLOC_ARRAY(win, 1); win->offset = (offset / window_align) * window_align; len = p->pack_size - win->offset; if (len > packed_git_window_size) @@ -852,7 +862,11 @@ static void prepare_pack(const char *full_name, size_t full_name_len, if (!strcmp(file_name, "multi-pack-index")) return; + if (starts_with(file_name, "multi-pack-index") && + ends_with(file_name, ".rev")) + return; if (ends_with(file_name, ".idx") || + ends_with(file_name, ".rev") || ends_with(file_name, ".pack") || ends_with(file_name, ".bitmap") || ends_with(file_name, ".keep") || @@ -2055,12 +2069,79 @@ int find_pack_entry(struct repository *r, const struct object_id *oid, struct pa return 0; } +static void maybe_invalidate_kept_pack_cache(struct repository *r, + unsigned flags) +{ + if (!r->objects->kept_pack_cache.packs) + return; + if (r->objects->kept_pack_cache.flags == flags) + return; + FREE_AND_NULL(r->objects->kept_pack_cache.packs); + r->objects->kept_pack_cache.flags = 0; +} + +static struct packed_git **kept_pack_cache(struct repository *r, unsigned flags) +{ + maybe_invalidate_kept_pack_cache(r, flags); + + if (!r->objects->kept_pack_cache.packs) { + struct packed_git **packs = NULL; + size_t nr = 0, alloc = 0; + struct packed_git *p; + + /* + * We want "all" packs here, because we need to cover ones that + * are used by a midx, as well. We need to look in every one of + * them (instead of the midx itself) to cover duplicates. It's + * possible that an object is found in two packs that the midx + * covers, one kept and one not kept, but the midx returns only + * the non-kept version. + */ + for (p = get_all_packs(r); p; p = p->next) { + if ((p->pack_keep && (flags & ON_DISK_KEEP_PACKS)) || + (p->pack_keep_in_core && (flags & IN_CORE_KEEP_PACKS))) { + ALLOC_GROW(packs, nr + 1, alloc); + packs[nr++] = p; + } + } + ALLOC_GROW(packs, nr + 1, alloc); + packs[nr] = NULL; + + r->objects->kept_pack_cache.packs = packs; + r->objects->kept_pack_cache.flags = flags; + } + + return r->objects->kept_pack_cache.packs; +} + +int find_kept_pack_entry(struct repository *r, + const struct object_id *oid, + unsigned flags, + struct pack_entry *e) +{ + struct packed_git **cache; + + for (cache = kept_pack_cache(r, flags); *cache; cache++) { + struct packed_git *p = *cache; + if (fill_pack_entry(oid, e, p)) + return 1; + } + + return 0; +} + int has_object_pack(const struct object_id *oid) { struct pack_entry e; return find_pack_entry(the_repository, oid, &e); } +int has_object_kept_pack(const struct object_id *oid, unsigned flags) +{ + struct pack_entry e; + return find_kept_pack_entry(the_repository, oid, flags, &e); +} + int has_pack_index(const unsigned char *sha1) { struct stat st; diff --git a/packfile.h b/packfile.h index a58fc738e0..3ae117a8ae 100644 --- a/packfile.h +++ b/packfile.h @@ -90,6 +90,7 @@ uint32_t get_pack_fanout(struct packed_git *p, uint32_t value); unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned long *); void close_pack_windows(struct packed_git *); +void close_pack_revindex(struct packed_git *); void close_pack(struct packed_git *); void close_object_store(struct raw_object_store *o); void unuse_pack(struct pack_window **); @@ -161,13 +162,18 @@ int packed_object_info(struct repository *r, void mark_bad_packed_object(struct packed_git *p, const unsigned char *sha1); const struct packed_git *has_packed_and_bad(struct repository *r, const unsigned char *sha1); +#define ON_DISK_KEEP_PACKS 1 +#define IN_CORE_KEEP_PACKS 2 + /* * Iff a pack file in the given repository contains the object named by sha1, * return true and store its location to e. */ int find_pack_entry(struct repository *r, const struct object_id *oid, struct pack_entry *e); +int find_kept_pack_entry(struct repository *r, const struct object_id *oid, unsigned flags, struct pack_entry *e); int has_object_pack(const struct object_id *oid); +int has_object_kept_pack(const struct object_id *oid, unsigned flags); int has_pack_index(const unsigned char *sha1); diff --git a/pager.c b/pager.c index ee435de675..3d37dd7ada 100644 --- a/pager.c +++ b/pager.c @@ -11,29 +11,25 @@ static struct child_process pager_process = CHILD_PROCESS_INIT; static const char *pager_program; -static void wait_for_pager(int in_signal) +static void close_pager_fds(void) { - if (!in_signal) { - fflush(stdout); - fflush(stderr); - } /* signal EOF to pager */ close(1); close(2); - if (in_signal) - finish_command_in_signal(&pager_process); - else - finish_command(&pager_process); } static void wait_for_pager_atexit(void) { - wait_for_pager(0); + fflush(stdout); + fflush(stderr); + close_pager_fds(); + finish_command(&pager_process); } static void wait_for_pager_signal(int signo) { - wait_for_pager(1); + close_pager_fds(); + finish_command_in_signal(&pager_process); sigchain_pop(signo); raise(signo); } diff --git a/parse-options.c b/parse-options.c index f0507432ee..e6f56768ca 100644 --- a/parse-options.c +++ b/parse-options.c @@ -625,6 +625,8 @@ static int show_gitcomp(const struct option *opts, int show_all) * * Right now this is only used to preprocess and substitute * OPTION_ALIAS. + * + * The returned options should be freed using free_preprocessed_options. */ static struct option *preprocess_options(struct parse_opt_ctx_t *ctx, const struct option *options) @@ -678,6 +680,7 @@ static struct option *preprocess_options(struct parse_opt_ctx_t *ctx, newopt[i].short_name = short_name; newopt[i].long_name = long_name; newopt[i].help = strbuf_detach(&help, NULL); + newopt[i].flags |= PARSE_OPT_FROM_ALIAS; break; } @@ -693,6 +696,20 @@ static struct option *preprocess_options(struct parse_opt_ctx_t *ctx, return newopt; } +static void free_preprocessed_options(struct option *options) +{ + int i; + + if (!options) + return; + + for (i = 0; options[i].type != OPTION_END; i++) { + if (options[i].flags & PARSE_OPT_FROM_ALIAS) + free((void *)options[i].help); + } + free(options); +} + static int usage_with_options_internal(struct parse_opt_ctx_t *, const char * const *, const struct option *, int, int); @@ -869,8 +886,8 @@ int parse_options(int argc, const char **argv, const char *prefix, usage_with_options(usagestr, options); } - precompose_argv(argc, argv); - free(real_options); + precompose_argv_prefix(argc, argv, NULL); + free_preprocessed_options(real_options); free(ctx.alias_groups); return parse_options_end(&ctx); } diff --git a/parse-options.h b/parse-options.h index ff6506a504..a845a9d952 100644 --- a/parse-options.h +++ b/parse-options.h @@ -28,26 +28,27 @@ enum parse_opt_type { }; enum parse_opt_flags { - PARSE_OPT_KEEP_DASHDASH = 1, - PARSE_OPT_STOP_AT_NON_OPTION = 2, - PARSE_OPT_KEEP_ARGV0 = 4, - PARSE_OPT_KEEP_UNKNOWN = 8, - PARSE_OPT_NO_INTERNAL_HELP = 16, - PARSE_OPT_ONE_SHOT = 32 + PARSE_OPT_KEEP_DASHDASH = 1 << 0, + PARSE_OPT_STOP_AT_NON_OPTION = 1 << 1, + PARSE_OPT_KEEP_ARGV0 = 1 << 2, + PARSE_OPT_KEEP_UNKNOWN = 1 << 3, + PARSE_OPT_NO_INTERNAL_HELP = 1 << 4, + PARSE_OPT_ONE_SHOT = 1 << 5, }; enum parse_opt_option_flags { - PARSE_OPT_OPTARG = 1, - PARSE_OPT_NOARG = 2, - PARSE_OPT_NONEG = 4, - PARSE_OPT_HIDDEN = 8, - PARSE_OPT_LASTARG_DEFAULT = 16, - PARSE_OPT_NODASH = 32, - PARSE_OPT_LITERAL_ARGHELP = 64, - PARSE_OPT_SHELL_EVAL = 256, - PARSE_OPT_NOCOMPLETE = 512, - PARSE_OPT_COMP_ARG = 1024, - PARSE_OPT_CMDMODE = 2048 + PARSE_OPT_OPTARG = 1 << 0, + PARSE_OPT_NOARG = 1 << 1, + PARSE_OPT_NONEG = 1 << 2, + PARSE_OPT_HIDDEN = 1 << 3, + PARSE_OPT_LASTARG_DEFAULT = 1 << 4, + PARSE_OPT_NODASH = 1 << 5, + PARSE_OPT_LITERAL_ARGHELP = 1 << 6, + PARSE_OPT_FROM_ALIAS = 1 << 7, + PARSE_OPT_SHELL_EVAL = 1 << 8, + PARSE_OPT_NOCOMPLETE = 1 << 9, + PARSE_OPT_COMP_ARG = 1 << 10, + PARSE_OPT_CMDMODE = 1 << 11, }; enum parse_opt_result { diff --git a/patch-ids.c b/patch-ids.c index 3f404e4b0b..8bf425555d 100644 --- a/patch-ids.c +++ b/patch-ids.c @@ -124,7 +124,7 @@ struct patch_id *add_commit_patch_id(struct commit *commit, if (!patch_id_defined(commit)) return NULL; - key = xcalloc(1, sizeof(*key)); + CALLOC_ARRAY(key, 1); if (init_patch_id_entry(key, commit, ids)) { free(key); return NULL; diff --git a/pathspec.c b/pathspec.c index 7a229d8d22..18b3be362a 100644 --- a/pathspec.c +++ b/pathspec.c @@ -154,7 +154,7 @@ static void parse_pathspec_attr_match(struct pathspec_item *item, const char *va string_list_remove_empty_items(&list, 0); item->attr_check = attr_check_alloc(); - item->attr_match = xcalloc(list.nr, sizeof(struct attr_match)); + CALLOC_ARRAY(item->attr_match, list.nr); for_each_string_list_item(si, &list) { size_t attr_len; @@ -561,7 +561,7 @@ void parse_pathspec(struct pathspec *pathspec, if (!(flags & PATHSPEC_PREFER_CWD)) BUG("PATHSPEC_PREFER_CWD requires arguments"); - pathspec->items = item = xcalloc(1, sizeof(*item)); + pathspec->items = CALLOC_ARRAY(item, 1); item->match = xstrdup(prefix); item->original = xstrdup(prefix); item->nowildcard_len = item->len = strlen(prefix); diff --git a/perl/Git.pm b/perl/Git.pm index 02eacef0c2..73ebbf80cc 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -619,6 +619,19 @@ Return path to the git repository. Must be called on a repository instance. sub repo_path { $_[0]->{opts}->{Repository} } +=item hooks_path () + +Return path to the hooks directory. Must be called on a repository instance. + +=cut + +sub hooks_path { + my ($self) = @_; + + my $dir = $self->command_oneline('rev-parse', '--git-path', 'hooks'); + my $abs = abs_path($dir); + return $abs; +} =item wc_path () diff --git a/pkt-line.c b/pkt-line.c index d633005ef7..0194137528 100644 --- a/pkt-line.c +++ b/pkt-line.c @@ -196,17 +196,26 @@ int packet_write_fmt_gently(int fd, const char *fmt, ...) static int packet_write_gently(const int fd_out, const char *buf, size_t size) { - static char packet_write_buffer[LARGE_PACKET_MAX]; + char header[4]; size_t packet_size; - if (size > sizeof(packet_write_buffer) - 4) + if (size > LARGE_PACKET_DATA_MAX) return error(_("packet write failed - data exceeds max packet size")); packet_trace(buf, size, 1); packet_size = size + 4; - set_packet_header(packet_write_buffer, packet_size); - memcpy(packet_write_buffer + 4, buf, size); - if (write_in_full(fd_out, packet_write_buffer, packet_size) < 0) + + set_packet_header(header, packet_size); + + /* + * Write the header and the buffer in 2 parts so that we do + * not need to allocate a buffer or rely on a static buffer. + * This also avoids putting a large buffer on the stack which + * might have multi-threading issues. + */ + + if (write_in_full(fd_out, header, 4) < 0 || + write_in_full(fd_out, buf, size) < 0) return error(_("packet write failed")); return 0; } @@ -242,26 +251,27 @@ void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len) packet_trace(data, len, 1); } -int write_packetized_from_fd(int fd_in, int fd_out) +int write_packetized_from_fd_no_flush(int fd_in, int fd_out) { - static char buf[LARGE_PACKET_DATA_MAX]; + char *buf = xmalloc(LARGE_PACKET_DATA_MAX); int err = 0; ssize_t bytes_to_write; while (!err) { - bytes_to_write = xread(fd_in, buf, sizeof(buf)); - if (bytes_to_write < 0) + bytes_to_write = xread(fd_in, buf, LARGE_PACKET_DATA_MAX); + if (bytes_to_write < 0) { + free(buf); return COPY_READ_ERROR; + } if (bytes_to_write == 0) break; err = packet_write_gently(fd_out, buf, bytes_to_write); } - if (!err) - err = packet_flush_gently(fd_out); + free(buf); return err; } -int write_packetized_from_buf(const char *src_in, size_t len, int fd_out) +int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out) { int err = 0; size_t bytes_written = 0; @@ -277,8 +287,6 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out) err = packet_write_gently(fd_out, src_in + bytes_written, bytes_to_write); bytes_written += bytes_to_write; } - if (!err) - err = packet_flush_gently(fd_out); return err; } @@ -298,8 +306,11 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size, *src_size -= ret; } else { ret = read_in_full(fd, dst, size); - if (ret < 0) + if (ret < 0) { + if (options & PACKET_READ_GENTLE_ON_READ_ERROR) + return error_errno(_("read error")); die_errno(_("read error")); + } } /* And complain if we didn't get enough bytes to satisfy the read. */ @@ -307,6 +318,8 @@ static int get_packet_data(int fd, char **src_buf, size_t *src_size, if (options & PACKET_READ_GENTLE_ON_EOF) return -1; + if (options & PACKET_READ_GENTLE_ON_READ_ERROR) + return error(_("the remote end hung up unexpectedly")); die(_("the remote end hung up unexpectedly")); } @@ -335,6 +348,9 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer, len = packet_length(linelen); if (len < 0) { + if (options & PACKET_READ_GENTLE_ON_READ_ERROR) + return error(_("protocol error: bad line length " + "character: %.4s"), linelen); die(_("protocol error: bad line length character: %.4s"), linelen); } else if (!len) { packet_trace("0000", 4, 0); @@ -349,12 +365,19 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer, *pktlen = 0; return PACKET_READ_RESPONSE_END; } else if (len < 4) { + if (options & PACKET_READ_GENTLE_ON_READ_ERROR) + return error(_("protocol error: bad line length %d"), + len); die(_("protocol error: bad line length %d"), len); } len -= 4; - if ((unsigned)len >= size) + if ((unsigned)len >= size) { + if (options & PACKET_READ_GENTLE_ON_READ_ERROR) + return error(_("protocol error: bad line length %d"), + len); die(_("protocol error: bad line length %d"), len); + } if (get_packet_data(fd, src_buffer, src_len, buffer, len, options) < 0) { *pktlen = -1; @@ -421,7 +444,7 @@ char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len) return packet_read_line_generic(-1, src, src_len, dst_len); } -ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out) +ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out, int options) { int packet_len; @@ -437,7 +460,7 @@ ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out) * that there is already room for the extra byte. */ sb_out->buf + sb_out->len, LARGE_PACKET_DATA_MAX+1, - PACKET_READ_GENTLE_ON_EOF); + options); if (packet_len <= 0) break; sb_out->len += packet_len; diff --git a/pkt-line.h b/pkt-line.h index 8c90daa59e..5af5f45687 100644 --- a/pkt-line.h +++ b/pkt-line.h @@ -32,8 +32,8 @@ void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((f void packet_buf_write_len(struct strbuf *buf, const char *data, size_t len); int packet_flush_gently(int fd); int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3))); -int write_packetized_from_fd(int fd_in, int fd_out); -int write_packetized_from_buf(const char *src_in, size_t len, int fd_out); +int write_packetized_from_fd_no_flush(int fd_in, int fd_out); +int write_packetized_from_buf_no_flush(const char *src_in, size_t len, int fd_out); /* * Read a packetized line into the buffer, which must be at least size bytes @@ -68,10 +68,15 @@ int write_packetized_from_buf(const char *src_in, size_t len, int fd_out); * * If options contains PACKET_READ_DIE_ON_ERR_PACKET, it dies when it sees an * ERR packet. + * + * If options contains PACKET_READ_GENTLE_ON_READ_ERROR, we will not die + * on read errors, but instead return -1. However, we may still die on an + * ERR packet (if requested). */ -#define PACKET_READ_GENTLE_ON_EOF (1u<<0) -#define PACKET_READ_CHOMP_NEWLINE (1u<<1) -#define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2) +#define PACKET_READ_GENTLE_ON_EOF (1u<<0) +#define PACKET_READ_CHOMP_NEWLINE (1u<<1) +#define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2) +#define PACKET_READ_GENTLE_ON_READ_ERROR (1u<<3) int packet_read(int fd, char **src_buffer, size_t *src_len, char *buffer, unsigned size, int options); @@ -131,7 +136,7 @@ char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size); /* * Reads a stream of variable sized packets until a flush packet is detected. */ -ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out); +ssize_t read_packetized_to_strbuf(int fd_in, struct strbuf *sb_out, int options); /* * Receive multiplexed output stream over git native protocol. diff --git a/po/TEAMS b/po/TEAMS index 677cece102..a32beb6742 100644 --- a/po/TEAMS +++ b/po/TEAMS @@ -29,6 +29,10 @@ Repository: https://github.com/jnavila/git Leader: Jean-Noël Avila Members: Sébastien Helleu +Language: id (Indonesian) +Repository: https://github.com/bagasme/git-po +Leader: Bagas Sanjaya + Language: is (Icelandic) Leader: Ævar Arnfjörð Bjarmason diff --git a/po/bg.po b/po/bg.po index d73e84c8f8..529ea97bd9 100644 --- a/po/bg.po +++ b/po/bg.po @@ -1,7 +1,7 @@ # Bulgarian translation of git po-file. -# Copyright (C) 2014, 2015, 2016, 2017, 2018, 2019, 2020 Alexander Shopov . +# Copyright (C) 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Alexander Shopov . # This file is distributed under the same license as the git package. -# Alexander Shopov , 2014, 2015, 2016, 2017, 2018, 2019, 2020. +# Alexander Shopov , 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021. # # ======================== # DICTIONARY TO MERGE IN GIT GUI @@ -38,6 +38,7 @@ # graft присадка # grafted repository хранилище с присаждане # replace refs заместващи указатели +# replace objects заместващи обекти # embedded repository вградено/вътрешно хранилище (добавянето му е грешка) # thin pack съкратен пакет # pack file пакетен файл @@ -149,6 +150,8 @@ # identity самоличност, информация за # boundary commit гранично подаване # integrate (changes) внасяне (на промени) +# overflow data данни за отместването +# reverse index обратен индекс (а не обърнат, за да не се бърка с reverse key index) # ------------------------ # „$var“ - може да не сработва за shell има gettext и eval_gettext - проверка - намират се лесно по „$ # ------------------------ @@ -165,10 +168,10 @@ # for i in `sort -u FILES`; do cnt=`grep $i FILES | wc -l`; echo $cnt $i ;done | sort -n msgid "" msgstr "" -"Project-Id-Version: git 2.30\n" +"Project-Id-Version: git 2.31\n" "Report-Msgid-Bugs-To: Git Mailing List \n" -"POT-Creation-Date: 2020-12-21 07:10+0800\n" -"PO-Revision-Date: 2020-12-22 17:48+0100\n" +"POT-Creation-Date: 2021-03-04 22:41+0800\n" +"PO-Revision-Date: 2021-03-05 12:11+0100\n" "Last-Translator: Alexander Shopov \n" "Language-Team: Bulgarian \n" "Language: bg\n" @@ -182,9 +185,9 @@ msgstr "" msgid "Huh (%s)?" msgstr "Неуспешен анализ — „%s“." -#: add-interactive.c:529 add-interactive.c:830 reset.c:65 sequencer.c:3284 -#: sequencer.c:3735 sequencer.c:3890 builtin/rebase.c:1532 -#: builtin/rebase.c:1955 +#: add-interactive.c:529 add-interactive.c:830 reset.c:65 sequencer.c:3292 +#: sequencer.c:3743 sequencer.c:3898 builtin/rebase.c:1538 +#: builtin/rebase.c:1963 msgid "could not read index" msgstr "индексът не може да бъде прочетен" @@ -212,7 +215,7 @@ msgstr "Обновяване" msgid "could not stage '%s'" msgstr "неуспешно добавяне в индекса на „%s“" -#: add-interactive.c:703 add-interactive.c:892 reset.c:89 sequencer.c:3478 +#: add-interactive.c:703 add-interactive.c:892 reset.c:89 sequencer.c:3486 msgid "could not write index" msgstr "индексът не може да бъде записан" @@ -370,7 +373,7 @@ msgstr "извън индекса" #: add-interactive.c:1144 apply.c:4987 apply.c:4990 builtin/am.c:2257 #: builtin/am.c:2260 builtin/bugreport.c:134 builtin/clone.c:124 -#: builtin/fetch.c:147 builtin/merge.c:284 builtin/pull.c:190 +#: builtin/fetch.c:150 builtin/merge.c:285 builtin/pull.c:190 #: builtin/submodule--helper.c:409 builtin/submodule--helper.c:1818 #: builtin/submodule--helper.c:1821 builtin/submodule--helper.c:2326 #: builtin/submodule--helper.c:2329 builtin/submodule--helper.c:2572 @@ -969,7 +972,7 @@ msgstr "" msgid "Exiting because of an unresolved conflict." msgstr "Изход от програмата заради некоригиран конфликт." -#: advice.c:281 builtin/merge.c:1369 +#: advice.c:281 builtin/merge.c:1370 msgid "You have not concluded your merge (MERGE_HEAD exists)." msgstr "Не сте завършили сливане. (Указателят „MERGE_HEAD“ съществува)." @@ -1285,7 +1288,8 @@ msgstr "неуспешно прилагане на кръпка: „%s:%ld“" msgid "cannot checkout %s" msgstr "„%s“ не може да се изтегли" -#: apply.c:3405 apply.c:3416 apply.c:3462 midx.c:73 setup.c:308 +#: apply.c:3405 apply.c:3416 apply.c:3462 midx.c:86 pack-revindex.c:213 +#: setup.c:308 #, c-format msgid "failed to read %s" msgstr "файлът „%s“ не може да бъде прочетен" @@ -1450,7 +1454,7 @@ msgstr "" msgid "unable to add cache entry for %s" msgstr "не може да се добави запис в кеша за „%s“" -#: apply.c:4374 builtin/bisect--helper.c:524 +#: apply.c:4374 builtin/bisect--helper.c:523 #, c-format msgid "failed to write to '%s'" msgstr "в „%s“ не може да се пише" @@ -1486,7 +1490,7 @@ msgstr[1] "Прилагане на кръпката „%%s“ с %d отхвър msgid "truncating .rej filename to %.*s.rej" msgstr "съкращаване на името на файла с отхвърлените парчета на „ %.*s.rej“" -#: apply.c:4576 builtin/fetch.c:927 builtin/fetch.c:1228 +#: apply.c:4576 builtin/fetch.c:933 builtin/fetch.c:1334 #, c-format msgid "cannot open %s" msgstr "„%s“ не може да бъде отворен" @@ -1542,7 +1546,7 @@ msgstr[0] "" msgstr[1] "" "Добавени са %d реда след корекцията на грешките в знаците за интервали." -#: apply.c:4960 builtin/add.c:618 builtin/mv.c:304 builtin/rm.c:406 +#: apply.c:4960 builtin/add.c:626 builtin/mv.c:304 builtin/rm.c:406 msgid "Unable to write new index file" msgstr "Новият индекс не може да бъде записан" @@ -1613,7 +1617,7 @@ msgid "build a temporary index based on embedded index information" msgstr "" "създаване на временен индекс на база на включената информация за индекса" -#: apply.c:5025 builtin/checkout-index.c:182 builtin/ls-files.c:525 +#: apply.c:5025 builtin/checkout-index.c:195 builtin/ls-files.c:540 msgid "paths are separated with NUL character" msgstr "разделяне на пътищата с нулевия знак „NUL“" @@ -1623,7 +1627,7 @@ msgstr "да се осигури контекст от поне такъв БР #: apply.c:5028 builtin/am.c:2245 builtin/interpret-trailers.c:98 #: builtin/interpret-trailers.c:100 builtin/interpret-trailers.c:102 -#: builtin/pack-objects.c:3562 builtin/rebase.c:1346 +#: builtin/pack-objects.c:3577 builtin/rebase.c:1352 msgid "action" msgstr "действие" @@ -1652,9 +1656,9 @@ msgstr "оставяне на отхвърлените парчета във ф msgid "allow overlapping hunks" msgstr "позволяване на застъпващи се парчета" -#: apply.c:5045 builtin/add.c:329 builtin/check-ignore.c:22 -#: builtin/commit.c:1364 builtin/count-objects.c:98 builtin/fsck.c:775 -#: builtin/log.c:2287 builtin/mv.c:123 builtin/read-tree.c:128 +#: apply.c:5045 builtin/add.c:337 builtin/check-ignore.c:22 +#: builtin/commit.c:1364 builtin/count-objects.c:98 builtin/fsck.c:757 +#: builtin/log.c:2286 builtin/mv.c:123 builtin/read-tree.c:128 msgid "be verbose" msgstr "повече подробности" @@ -1742,14 +1746,14 @@ msgstr "git archive --remote ХРАНИЛИЩЕ [--exec КОМАНДА] --list" msgid "cannot read %s" msgstr "обектът „%s“ не може да бъде прочетен" -#: archive.c:345 sequencer.c:459 sequencer.c:1736 sequencer.c:2886 -#: sequencer.c:3327 sequencer.c:3436 builtin/am.c:249 builtin/commit.c:786 -#: builtin/merge.c:1138 +#: archive.c:345 sequencer.c:459 sequencer.c:1744 sequencer.c:2894 +#: sequencer.c:3335 sequencer.c:3444 builtin/am.c:249 builtin/commit.c:786 +#: builtin/merge.c:1139 #, c-format msgid "could not read '%s'" msgstr "файлът „%s“ не може да бъде прочетен" -#: archive.c:430 builtin/add.c:181 builtin/add.c:594 builtin/rm.c:315 +#: archive.c:430 builtin/add.c:189 builtin/add.c:602 builtin/rm.c:315 #, c-format msgid "pathspec '%s' did not match any files" msgstr "пътят „%s“ не съвпада с никой файл" @@ -1791,7 +1795,7 @@ msgstr "ФОРМАТ" msgid "archive format" msgstr "ФОРМАТ на архива" -#: archive.c:556 builtin/log.c:1765 +#: archive.c:556 builtin/log.c:1764 msgid "prefix" msgstr "ПРЕФИКС" @@ -1799,11 +1803,11 @@ msgstr "ПРЕФИКС" msgid "prepend prefix to each pathname in the archive" msgstr "добавяне на този ПРЕФИКС към всеки път в архива" -#: archive.c:558 archive.c:561 builtin/blame.c:886 builtin/blame.c:890 -#: builtin/blame.c:891 builtin/commit-tree.c:117 builtin/config.c:135 +#: archive.c:558 archive.c:561 builtin/blame.c:884 builtin/blame.c:888 +#: builtin/blame.c:889 builtin/commit-tree.c:117 builtin/config.c:135 #: builtin/fast-export.c:1207 builtin/fast-export.c:1209 -#: builtin/fast-export.c:1213 builtin/grep.c:919 builtin/hash-object.c:105 -#: builtin/ls-files.c:561 builtin/ls-files.c:564 builtin/notes.c:412 +#: builtin/fast-export.c:1213 builtin/grep.c:920 builtin/hash-object.c:105 +#: builtin/ls-files.c:576 builtin/ls-files.c:579 builtin/notes.c:412 #: builtin/notes.c:578 builtin/read-tree.c:123 parse-options.h:190 msgid "file" msgstr "ФАЙЛ" @@ -1968,12 +1972,12 @@ msgstr "Двоично търсене: трябва да се провери б msgid "a %s revision is needed" msgstr "необходима е версия „%s“" -#: bisect.c:941 builtin/notes.c:177 builtin/tag.c:255 +#: bisect.c:941 builtin/notes.c:177 builtin/tag.c:287 #, c-format msgid "could not create file '%s'" msgstr "файлът „%s“ не може да бъде създаден" -#: bisect.c:987 builtin/merge.c:152 +#: bisect.c:987 builtin/merge.c:153 #, c-format msgid "could not read file '%s'" msgstr "файлът „%s“ не може да бъде прочетен" @@ -1991,7 +1995,7 @@ msgstr "„%s“ e както „%s“, така и „%s“\n" #, c-format msgid "" "No testable commit found.\n" -"Maybe you started with bad path parameters?\n" +"Maybe you started with bad path arguments?\n" msgstr "" "Липсва подходящо за тестване подаване.\n" "Проверете параметрите за пътищата.\n" @@ -2027,11 +2031,11 @@ msgstr "" "Едновременното задаване на опциите „--reverse“ и „--first-parent“ изисква " "указването на крайно подаване" -#: blame.c:2821 bundle.c:213 ref-filter.c:2272 remote.c:2031 sequencer.c:2138 -#: sequencer.c:4633 submodule.c:855 builtin/commit.c:1045 builtin/log.c:409 -#: builtin/log.c:1023 builtin/log.c:1625 builtin/log.c:2046 builtin/log.c:2336 -#: builtin/merge.c:423 builtin/pack-objects.c:3380 builtin/pack-objects.c:3395 -#: builtin/shortlog.c:267 +#: blame.c:2821 bundle.c:213 ref-filter.c:2206 remote.c:2041 sequencer.c:2146 +#: sequencer.c:4641 submodule.c:856 builtin/commit.c:1045 builtin/log.c:411 +#: builtin/log.c:1016 builtin/log.c:1624 builtin/log.c:2045 builtin/log.c:2335 +#: builtin/merge.c:424 builtin/pack-objects.c:3395 builtin/pack-objects.c:3410 +#: builtin/shortlog.c:255 msgid "revision walk setup failed" msgstr "неуспешно настройване на обхождането на версиите" @@ -2210,7 +2214,7 @@ msgstr "Файлът „%s“ не изглежда да е пратка на gi msgid "unrecognized header: %s%s (%d)" msgstr "непозната заглавна част: %s%s (%d)" -#: bundle.c:136 rerere.c:480 rerere.c:690 sequencer.c:2390 sequencer.c:3176 +#: bundle.c:136 rerere.c:464 rerere.c:674 sequencer.c:2398 sequencer.c:3184 #: builtin/commit.c:814 #, c-format msgid "could not open '%s'" @@ -2254,286 +2258,301 @@ msgstr "Командата „git pack-objects“ не може да бъде с msgid "pack-objects died" msgstr "Командата „git pack-objects“ не завърши успешно" -#: bundle.c:379 -msgid "rev-list died" -msgstr "Командата „git rev-list“ не завърши успешно" - -#: bundle.c:428 +#: bundle.c:386 #, c-format msgid "ref '%s' is excluded by the rev-list options" msgstr "" "указателят „%s“ не е бил включен поради опциите зададени на „git rev-list“" -#: bundle.c:498 +#: bundle.c:490 #, c-format msgid "unsupported bundle version %d" msgstr "неподдържана версия на индекса %d" -#: bundle.c:500 +#: bundle.c:492 #, c-format msgid "cannot write bundle version %d with algorithm %s" msgstr "пратка %d не може да се запише с алгоритъм %s" -#: bundle.c:522 builtin/log.c:209 builtin/log.c:1927 builtin/shortlog.c:408 +#: bundle.c:510 builtin/log.c:210 builtin/log.c:1926 builtin/shortlog.c:396 #, c-format msgid "unrecognized argument: %s" msgstr "непознат аргумент: %s" -#: bundle.c:530 +#: bundle.c:539 msgid "Refusing to create empty bundle." msgstr "Създаването на празна пратка е невъзможно." -#: bundle.c:540 +#: bundle.c:549 #, c-format msgid "cannot create '%s'" -msgstr "Файлът „%s“ не може да бъде създаден" +msgstr "файлът „%s“ не може да бъде създаден" -#: bundle.c:565 +#: bundle.c:574 msgid "index-pack died" -msgstr "Командата „git index-pack“ не завърши успешно" +msgstr "командата „git index-pack“ не завърши успешно" + +#: chunk-format.c:113 +msgid "terminating chunk id appears earlier than expected" +msgstr "идентификаторът за краен откъс се явява по-рано от очакваното" + +#: chunk-format.c:122 +#, c-format +msgid "improper chunk offset(s) % and %" +msgstr "неправилно отместване на откъс/и % и %" + +#: chunk-format.c:129 +#, c-format +msgid "duplicate chunk ID % found" +msgstr "повтарящ се идентификатор на откъс %" + +#: chunk-format.c:143 +#, c-format +msgid "final chunk has non-zero id %" +msgstr "ненулев идентификатор за краен откъс %" #: color.c:329 #, c-format msgid "invalid color value: %.*s" msgstr "неправилна стойност за цвят: %.*s" -#: commit-graph.c:188 midx.c:47 +#: commit-graph.c:197 midx.c:46 msgid "invalid hash version" msgstr "неправилна версия на контролна сума" -#: commit-graph.c:246 +#: commit-graph.c:255 msgid "commit-graph file is too small" -msgstr "файлът с гра̀фа на подаванията е твърде малък" +msgstr "файлът за гра̀фа с подаванията е твърде малък" -#: commit-graph.c:311 +#: commit-graph.c:348 #, c-format msgid "commit-graph signature %X does not match signature %X" msgstr "отпечатъкът на гра̀фа с подаванията %X не съвпада с %X" -#: commit-graph.c:318 +#: commit-graph.c:355 #, c-format msgid "commit-graph version %X does not match version %X" msgstr "версията на гра̀фа с подаванията %X не съвпада с %X" -#: commit-graph.c:325 +#: commit-graph.c:362 #, c-format msgid "commit-graph hash version %X does not match version %X" msgstr "версията на контролната сума на гра̀фа с подаванията %X не съвпада с %X" -#: commit-graph.c:342 +#: commit-graph.c:379 #, c-format msgid "commit-graph file is too small to hold %u chunks" msgstr "файлът с гра̀фа на подаванията е твърде малък, за да съдържа %u откъси" -#: commit-graph.c:361 -#, c-format -msgid "commit-graph improper chunk offset %08x%08x" -msgstr "неправилно отместване на откъс: %08x%08x" - -#: commit-graph.c:433 -#, c-format -msgid "commit-graph chunk id %08x appears multiple times" -msgstr "откъсът %08x се явява многократно" - -#: commit-graph.c:499 +#: commit-graph.c:472 msgid "commit-graph has no base graphs chunk" msgstr "базовият откъс липсва в гра̀фа с подаванията" -#: commit-graph.c:509 +#: commit-graph.c:482 msgid "commit-graph chain does not match" msgstr "веригата на гра̀фа с подаванията не съвпада" -#: commit-graph.c:557 +#: commit-graph.c:530 #, c-format msgid "invalid commit-graph chain: line '%s' not a hash" msgstr "" "грешка във веригата на гра̀фа с подаванията: ред „%s“ не е контролна сума" -#: commit-graph.c:581 +#: commit-graph.c:554 msgid "unable to find all commit-graph files" msgstr "някои файлове на гра̀фа с подаванията не могат да бъдат открити" -#: commit-graph.c:721 commit-graph.c:785 +#: commit-graph.c:735 commit-graph.c:772 msgid "invalid commit position. commit-graph is likely corrupt" msgstr "" "неправилна позиция на подаването. Вероятно графът с подаванията е повреден" -#: commit-graph.c:742 +#: commit-graph.c:756 #, c-format msgid "could not find commit %s" msgstr "подаването „%s“ не може да бъде открито" -#: commit-graph.c:1036 builtin/am.c:1292 +#: commit-graph.c:789 +msgid "commit-graph requires overflow generation data but has none" +msgstr "" +"графът с подаванията изисква генериране на данни за отместването, но такива " +"липсват" + +#: commit-graph.c:1065 builtin/am.c:1292 #, c-format msgid "unable to parse commit %s" msgstr "подаването не може да бъде анализирано: %s" -#: commit-graph.c:1252 builtin/pack-objects.c:2864 +#: commit-graph.c:1327 builtin/pack-objects.c:2872 #, c-format msgid "unable to get type of object %s" msgstr "видът на обекта „%s“ не може да бъде определен" -#: commit-graph.c:1283 +#: commit-graph.c:1358 msgid "Loading known commits in commit graph" msgstr "Зареждане на познатите подавания в гра̀фа с подаванията" -#: commit-graph.c:1300 +#: commit-graph.c:1375 msgid "Expanding reachable commits in commit graph" msgstr "Разширяване на достижимите подавания в гра̀фа" -#: commit-graph.c:1320 +#: commit-graph.c:1395 msgid "Clearing commit marks in commit graph" msgstr "Изчистване на отбелязванията на подаванията в гра̀фа с подаванията" -#: commit-graph.c:1339 +#: commit-graph.c:1414 +msgid "Computing commit graph topological levels" +msgstr "Изчисляване на топологичните нива в гра̀фа с подаванията" + +#: commit-graph.c:1467 msgid "Computing commit graph generation numbers" msgstr "Изчисляване на номерата на поколенията в гра̀фа с подаванията" -#: commit-graph.c:1406 +#: commit-graph.c:1548 msgid "Computing commit changed paths Bloom filters" msgstr "Изчисляване на филтрите на Блум на пътищата с промяна при подаването" -#: commit-graph.c:1483 +#: commit-graph.c:1625 msgid "Collecting referenced commits" msgstr "Събиране на свързаните подавания" -#: commit-graph.c:1508 +#: commit-graph.c:1650 #, c-format msgid "Finding commits for commit graph in %d pack" msgid_plural "Finding commits for commit graph in %d packs" msgstr[0] "Откриване на подаванията в гра̀фа в %d пакетен файл" msgstr[1] "Откриване на подаванията в гра̀фа в %d пакетни файла" -#: commit-graph.c:1521 +#: commit-graph.c:1663 #, c-format msgid "error adding pack %s" msgstr "грешка при добавяне на пакетен файл „%s“" -#: commit-graph.c:1525 +#: commit-graph.c:1667 #, c-format msgid "error opening index for %s" msgstr "грешка при отваряне на индекса на „%s“" -#: commit-graph.c:1562 +#: commit-graph.c:1704 msgid "Finding commits for commit graph among packed objects" msgstr "Откриване на подаванията в гра̀фа измежду пакетираните обекти" -#: commit-graph.c:1580 +#: commit-graph.c:1722 msgid "Finding extra edges in commit graph" msgstr "Откриване на още върхове в гра̀фа с подаванията" -#: commit-graph.c:1628 +#: commit-graph.c:1771 msgid "failed to write correct number of base graph ids" msgstr "правилният брой на базовите идентификатори не може да се запише" -#: commit-graph.c:1670 midx.c:819 +#: commit-graph.c:1802 midx.c:794 #, c-format msgid "unable to create leading directories of %s" msgstr "родителските директории на „%s“ не могат да бъдат създадени" -#: commit-graph.c:1683 +#: commit-graph.c:1815 msgid "unable to create temporary graph layer" msgstr "не може да бъде създаден временен слой за гра̀фа с подаванията" -#: commit-graph.c:1688 +#: commit-graph.c:1820 #, c-format msgid "unable to adjust shared permissions for '%s'" msgstr "правата за споделен достъп до „%s“ не могат да бъдат зададени" -#: commit-graph.c:1758 +#: commit-graph.c:1879 #, c-format msgid "Writing out commit graph in %d pass" msgid_plural "Writing out commit graph in %d passes" msgstr[0] "Запазване на гра̀фа с подаванията в %d пас" msgstr[1] "Запазване на гра̀фа с подаванията в %d паса" -#: commit-graph.c:1803 +#: commit-graph.c:1915 msgid "unable to open commit-graph chain file" msgstr "файлът с веригата на гра̀фа с подаванията не може да се отвори" -#: commit-graph.c:1819 +#: commit-graph.c:1931 msgid "failed to rename base commit-graph file" msgstr "основният файл на гра̀фа с подаванията не може да бъде преименуван" -#: commit-graph.c:1839 +#: commit-graph.c:1951 msgid "failed to rename temporary commit-graph file" msgstr "временният файл на гра̀фа с подаванията не може да бъде преименуван" -#: commit-graph.c:1965 +#: commit-graph.c:2084 msgid "Scanning merged commits" msgstr "Търсене на подаванията със сливания" -#: commit-graph.c:2009 +#: commit-graph.c:2128 msgid "Merging commit-graph" msgstr "Сливане на гра̀фа с подаванията" -#: commit-graph.c:2115 +#: commit-graph.c:2235 msgid "attempting to write a commit-graph, but 'core.commitGraph' is disabled" msgstr "" "опит за запис на гра̀фа с подаванията, но настройката „core.commitGraph“ е " "изключена" -#: commit-graph.c:2214 +#: commit-graph.c:2342 msgid "too many commits to write graph" msgstr "прекалено много подавания за записване на гра̀фа" -#: commit-graph.c:2307 +#: commit-graph.c:2440 msgid "the commit-graph file has incorrect checksum and is likely corrupt" msgstr "графът с подаванията е с грешна сума за проверка — вероятно е повреден" -#: commit-graph.c:2317 +#: commit-graph.c:2450 #, c-format msgid "commit-graph has incorrect OID order: %s then %s" msgstr "" "неправилна подредба на обектите по идентификатор в гра̀фа с подаванията: „%s“ " "е преди „%s“, а не трябва" -#: commit-graph.c:2327 commit-graph.c:2342 +#: commit-graph.c:2460 commit-graph.c:2475 #, c-format msgid "commit-graph has incorrect fanout value: fanout[%d] = %u != %u" msgstr "" "неправилна стойност за откъс в гра̀фа с подаванията: fanout[%d] = %u, а " "трябва да е %u" -#: commit-graph.c:2334 +#: commit-graph.c:2467 #, c-format msgid "failed to parse commit %s from commit-graph" msgstr "подаване „%s“ в гра̀фа с подаванията не може да се анализира" -#: commit-graph.c:2352 +#: commit-graph.c:2485 msgid "Verifying commits in commit graph" msgstr "Проверка на подаванията в гра̀фа" -#: commit-graph.c:2367 +#: commit-graph.c:2500 #, c-format msgid "failed to parse commit %s from object database for commit-graph" msgstr "" "подаване „%s“ в базата от данни към гра̀фа с подаванията не може да се " "анализира" -#: commit-graph.c:2374 +#: commit-graph.c:2507 #, c-format msgid "root tree OID for commit %s in commit-graph is %s != %s" msgstr "" "идентификаторът на обект за кореновото дърво за подаване „%s“ в гра̀фа с " "подаванията е „%s“, а трябва да е „%s“" -#: commit-graph.c:2384 +#: commit-graph.c:2517 #, c-format msgid "commit-graph parent list for commit %s is too long" msgstr "списъкът с родители на „%s“ в гра̀фа с подаванията е прекалено дълъг" -#: commit-graph.c:2393 +#: commit-graph.c:2526 #, c-format msgid "commit-graph parent for %s is %s != %s" msgstr "родителят на „%s“ в гра̀фа с подаванията е „%s“, а трябва да е „%s“" -#: commit-graph.c:2407 +#: commit-graph.c:2540 #, c-format msgid "commit-graph parent list for commit %s terminates early" msgstr "списъкът с родители на „%s“ в гра̀фа с подаванията е прекалено къс" -#: commit-graph.c:2412 +#: commit-graph.c:2545 #, c-format msgid "" "commit-graph has generation number zero for commit %s, but non-zero elsewhere" @@ -2541,7 +2560,7 @@ msgstr "" "номерът на поколението на подаване „%s“ в гра̀фа с подаванията е 0, а другаде " "не е" -#: commit-graph.c:2416 +#: commit-graph.c:2549 #, c-format msgid "" "commit-graph has non-zero generation number for commit %s, but zero elsewhere" @@ -2549,21 +2568,21 @@ msgstr "" "номерът на поколението на подаване „%s“ в гра̀фа с подаванията не е 0, а " "другаде е" -#: commit-graph.c:2432 +#: commit-graph.c:2566 #, c-format -msgid "commit-graph generation for commit %s is %u != %u" +msgid "commit-graph generation for commit %s is % < %" msgstr "" -"номерът на поколението на подаване „%s“ в гра̀фа с подаванията е %u, а " -"другаде е %u" +"номерът на поколението на подаване „%s“ в гра̀фа с подаванията е % < " +"%" -#: commit-graph.c:2438 +#: commit-graph.c:2572 #, c-format msgid "commit date for commit %s in commit-graph is % != %" msgstr "" "датата на подаване на „%s“ в гра̀фа с подаванията е %, а трябва да е " "%" -#: commit.c:52 sequencer.c:2879 builtin/am.c:359 builtin/am.c:403 +#: commit.c:52 sequencer.c:2887 builtin/am.c:359 builtin/am.c:403 #: builtin/am.c:1371 builtin/am.c:2018 builtin/replace.c:457 #, c-format msgid "could not parse %s" @@ -2597,29 +2616,29 @@ msgstr "" "\n" " git config advice.graftFileDeprecated false" -#: commit.c:1172 +#: commit.c:1223 #, c-format msgid "Commit %s has an untrusted GPG signature, allegedly by %s." msgstr "" "Подаването „%s“ е с недоверен подпис от GPG, който твърди, че е на „%s“." -#: commit.c:1176 +#: commit.c:1227 #, c-format msgid "Commit %s has a bad GPG signature allegedly by %s." msgstr "" "Подаването „%s“ е с неправилен подпис от GPG, който твърди, че е на „%s“." -#: commit.c:1179 +#: commit.c:1230 #, c-format msgid "Commit %s does not have a GPG signature." msgstr "Подаването „%s“ е без подпис от GPG." -#: commit.c:1182 +#: commit.c:1233 #, c-format msgid "Commit %s has a good GPG signature by %s\n" msgstr "Подаването „%s“ е с коректен подпис от GPG на „%s“.\n" -#: commit.c:1436 +#: commit.c:1487 msgid "" "Warning: commit message did not conform to UTF-8.\n" "You may want to amend it after fixing the message, or set the config\n" @@ -2633,7 +2652,7 @@ msgstr "" msgid "memory exhausted" msgstr "паметта свърши" -#: config.c:125 +#: config.c:126 #, c-format msgid "" "exceeded maximum include depth (%d) while including\n" @@ -2648,162 +2667,206 @@ msgstr "" " %s\n" "Това може да се дължи на зацикляне при вмъкването." -#: config.c:141 +#: config.c:142 #, c-format msgid "could not expand include path '%s'" msgstr "пътят за вмъкване „%s“не може да бъде разширен" -#: config.c:152 +#: config.c:153 msgid "relative config includes must come from files" msgstr "относителните вмъквания на конфигурации трябва да идват от файлове" -#: config.c:198 +#: config.c:199 msgid "relative config include conditionals must come from files" msgstr "относителните условни изрази за вмъкване трябва да идват от файлове" -#: config.c:378 +#: config.c:396 +#, c-format +msgid "invalid config format: %s" +msgstr "неправилен формат на настройка: %s" + +#: config.c:400 +#, c-format +msgid "missing environment variable name for configuration '%.*s'" +msgstr "липсва име на променлива на средата за настройката „%.*s“" + +#: config.c:405 +#, c-format +msgid "missing environment variable '%s' for configuration '%.*s'" +msgstr "липсва променлива на средата „%s“ за настройката „%.*s“" + +#: config.c:442 #, c-format msgid "key does not contain a section: %s" msgstr "ключът не съдържа раздел: „%s“" -#: config.c:384 +#: config.c:448 #, c-format msgid "key does not contain variable name: %s" msgstr "ключът не съдържа име на променлива: „%s“" -#: config.c:408 sequencer.c:2580 +#: config.c:472 sequencer.c:2588 #, c-format msgid "invalid key: %s" msgstr "неправилен ключ: „%s“" -#: config.c:414 +#: config.c:478 #, c-format msgid "invalid key (newline): %s" msgstr "неправилен ключ (нов ред): „%s“" -#: config.c:450 config.c:462 +#: config.c:511 +msgid "empty config key" +msgstr "празен ключ за настройка" + +#: config.c:529 config.c:541 #, c-format msgid "bogus config parameter: %s" msgstr "неправилен конфигурационен параметър: „%s“" -#: config.c:497 +#: config.c:555 config.c:572 config.c:579 config.c:588 #, c-format msgid "bogus format in %s" msgstr "неправилен формат в „%s“" -#: config.c:836 +#: config.c:622 +#, c-format +msgid "bogus count in %s" +msgstr "неправилен брой в „%s“" + +#: config.c:626 +#, c-format +msgid "too many entries in %s" +msgstr "прекалено много записи в „%s“" + +#: config.c:636 +#, c-format +msgid "missing config key %s" +msgstr "ключът за настройка „%s“ липсва" + +#: config.c:644 +#, c-format +msgid "missing config value %s" +msgstr "стойността за настройка „%s“ липсва" + +#: config.c:995 #, c-format msgid "bad config line %d in blob %s" msgstr "неправилен ред за настройки %d в BLOB „%s“" -#: config.c:840 +#: config.c:999 #, c-format msgid "bad config line %d in file %s" msgstr "неправилен ред за настройки %d във файла „%s“" -#: config.c:844 +#: config.c:1003 #, c-format msgid "bad config line %d in standard input" msgstr "неправилен ред за настройки %d на стандартния вход" -#: config.c:848 +#: config.c:1007 #, c-format msgid "bad config line %d in submodule-blob %s" msgstr "неправилен ред за настройки %d в BLOB за подмодул „%s“" -#: config.c:852 +#: config.c:1011 #, c-format msgid "bad config line %d in command line %s" msgstr "неправилен ред за настройки %d на командния ред „%s“" -#: config.c:856 +#: config.c:1015 #, c-format msgid "bad config line %d in %s" msgstr "неправилен ред за настройки %d в „%s“" -#: config.c:993 +#: config.c:1152 msgid "out of range" msgstr "извън диапазона" -#: config.c:993 +#: config.c:1152 msgid "invalid unit" msgstr "неправилна мерна единица" -#: config.c:994 +#: config.c:1153 #, c-format msgid "bad numeric config value '%s' for '%s': %s" msgstr "неправилна числова стойност „%s“ за „%s“: %s" -#: config.c:1013 +#: config.c:1163 #, c-format msgid "bad numeric config value '%s' for '%s' in blob %s: %s" msgstr "неправилна числова стойност „%s“ за „%s“ в BLOB „%s“: %s" -#: config.c:1016 +#: config.c:1166 #, c-format msgid "bad numeric config value '%s' for '%s' in file %s: %s" msgstr "неправилна числова стойност „%s“ за „%s“ във файла „%s“: %s" -#: config.c:1019 +#: config.c:1169 #, c-format msgid "bad numeric config value '%s' for '%s' in standard input: %s" msgstr "неправилна числова стойност „%s“ за „%s“ на стандартния вход: %s" -#: config.c:1022 +#: config.c:1172 #, c-format msgid "bad numeric config value '%s' for '%s' in submodule-blob %s: %s" msgstr "неправилна числова стойност „%s“ за „%s“ в BLOB от подмодул „%s“: %s" -#: config.c:1025 +#: config.c:1175 #, c-format msgid "bad numeric config value '%s' for '%s' in command line %s: %s" msgstr "неправилна числова стойност „%s“ за „%s“ на командния ред „%s“: %s" -#: config.c:1028 +#: config.c:1178 #, c-format msgid "bad numeric config value '%s' for '%s' in %s: %s" msgstr "неправилна числова стойност „%s“ за „%s“ в %s: %s" -#: config.c:1123 +#: config.c:1194 +#, c-format +msgid "bad boolean config value '%s' for '%s'" +msgstr "неправилна булева стойност „%s“ за „%s“" + +#: config.c:1289 #, c-format msgid "failed to expand user dir in: '%s'" msgstr "домашната папка на потребителя не може да бъде открита: „%s“" -#: config.c:1132 +#: config.c:1298 #, c-format msgid "'%s' for '%s' is not a valid timestamp" msgstr "„%s“ не е правилна стойност за време за „%s“" -#: config.c:1223 +#: config.c:1391 #, c-format msgid "abbrev length out of range: %d" msgstr "дължината на съкращаване е извън интервала ([4; 40]): %d" -#: config.c:1237 config.c:1248 +#: config.c:1405 config.c:1416 #, c-format msgid "bad zlib compression level %d" msgstr "неправилно ниво на компресиране: %d" -#: config.c:1340 +#: config.c:1508 msgid "core.commentChar should only be one character" msgstr "настройката „core.commentChar“ трябва да е само един знак" -#: config.c:1373 +#: config.c:1541 #, c-format msgid "invalid mode for object creation: %s" msgstr "неправилен режим за създаването на обекти: %s" -#: config.c:1445 +#: config.c:1613 #, c-format msgid "malformed value for %s" msgstr "неправилна стойност за „%s“" -#: config.c:1471 +#: config.c:1639 #, c-format msgid "malformed value for %s: %s" msgstr "неправилна стойност за „%s“: „%s“" -#: config.c:1472 +#: config.c:1640 msgid "must be one of nothing, matching, simple, upstream or current" msgstr "" "трябва да е една от следните стойности: „nothing“ (без изтласкване при липса " @@ -2811,132 +2874,132 @@ msgstr "" "„simple“ (клонът със същото име, от който се издърпва), „upstream“ (клонът, " "от който се издърпва) или „current“ (клонът със същото име)" -#: config.c:1533 builtin/pack-objects.c:3649 +#: config.c:1701 builtin/pack-objects.c:3666 #, c-format msgid "bad pack compression level %d" msgstr "неправилно ниво на компресиране при пакетиране: %d" -#: config.c:1655 +#: config.c:1823 #, c-format msgid "unable to load config blob object '%s'" msgstr "обектът-BLOB „%s“ с конфигурации не може да се зареди" -#: config.c:1658 +#: config.c:1826 #, c-format msgid "reference '%s' does not point to a blob" msgstr "указателят „%s“ не сочи към обект-BLOB" -#: config.c:1675 +#: config.c:1843 #, c-format msgid "unable to resolve config blob '%s'" msgstr "обектът-BLOB „%s“ с конфигурации не може да бъде открит" -#: config.c:1705 +#: config.c:1873 #, c-format msgid "failed to parse %s" msgstr "„%s“ не може да бъде анализиран" -#: config.c:1759 +#: config.c:1927 msgid "unable to parse command-line config" msgstr "неправилни настройки от командния ред" -#: config.c:2122 +#: config.c:2290 msgid "unknown error occurred while reading the configuration files" msgstr "неочаквана грешка при изчитането на конфигурационните файлове" -#: config.c:2296 +#: config.c:2464 #, c-format msgid "Invalid %s: '%s'" msgstr "Неправилен %s: „%s“" -#: config.c:2341 +#: config.c:2509 #, c-format msgid "splitIndex.maxPercentChange value '%d' should be between 0 and 100" msgstr "" "стойността на „splitIndex.maxPercentChange“ трябва да е между 1 и 100, а не " "%d" -#: config.c:2387 +#: config.c:2555 #, c-format msgid "unable to parse '%s' from command-line config" msgstr "неразпозната стойност „%s“ от командния ред" -#: config.c:2389 +#: config.c:2557 #, c-format msgid "bad config variable '%s' in file '%s' at line %d" msgstr "неправилна настройка „%s“ във файла „%s“ на ред №%d" -#: config.c:2473 +#: config.c:2641 #, c-format msgid "invalid section name '%s'" msgstr "неправилно име на раздел: „%s“" -#: config.c:2505 +#: config.c:2673 #, c-format msgid "%s has multiple values" msgstr "зададени са няколко стойности за „%s“" -#: config.c:2534 +#: config.c:2702 #, c-format msgid "failed to write new configuration file %s" msgstr "новият конфигурационен файл „%s“ не може да бъде запазен" -#: config.c:2786 config.c:3112 +#: config.c:2954 config.c:3280 #, c-format msgid "could not lock config file %s" msgstr "конфигурационният файл „%s“ не може да бъде заключен" -#: config.c:2797 +#: config.c:2965 #, c-format msgid "opening %s" msgstr "отваряне на „%s“" -#: config.c:2834 builtin/config.c:361 +#: config.c:3002 builtin/config.c:361 #, c-format msgid "invalid pattern: %s" msgstr "неправилен шаблон: %s" -#: config.c:2859 +#: config.c:3027 #, c-format msgid "invalid config file %s" msgstr "неправилен конфигурационен файл: „%s“" -#: config.c:2872 config.c:3125 +#: config.c:3040 config.c:3293 #, c-format msgid "fstat on %s failed" msgstr "неуспешно изпълнение на „fstat“ върху „%s“" -#: config.c:2883 +#: config.c:3051 #, c-format msgid "unable to mmap '%s'" msgstr "неуспешно изпълнение на „mmap“ върху „%s“" -#: config.c:2892 config.c:3130 +#: config.c:3060 config.c:3298 #, c-format msgid "chmod on %s failed" msgstr "неуспешна смяна на права с „chmod“ върху „%s“" -#: config.c:2977 config.c:3227 +#: config.c:3145 config.c:3395 #, c-format msgid "could not write config file %s" msgstr "конфигурационният файл „%s“ не може да бъде записан" -#: config.c:3011 +#: config.c:3179 #, c-format msgid "could not set '%s' to '%s'" msgstr "„%s“ не може да се зададе да е „%s“" -#: config.c:3013 builtin/remote.c:657 builtin/remote.c:855 builtin/remote.c:863 +#: config.c:3181 builtin/remote.c:657 builtin/remote.c:855 builtin/remote.c:863 #, c-format msgid "could not unset '%s'" msgstr "„%s“ не може да се премахне" -#: config.c:3103 +#: config.c:3271 #, c-format msgid "invalid section name: %s" msgstr "неправилно име на раздел: %s" -#: config.c:3270 +#: config.c:3438 #, c-format msgid "missing value for '%s'" msgstr "липсва стойност за „%s“" @@ -2999,45 +3062,45 @@ msgstr "неправилен пакет" msgid "protocol error: unexpected '%s'" msgstr "протоколна грешка: неочаквано „%s“" -#: connect.c:473 +#: connect.c:497 #, c-format msgid "unknown object format '%s' specified by server" msgstr "сървърът указа непознат формат на обект: „%s“" -#: connect.c:500 +#: connect.c:526 #, c-format msgid "invalid ls-refs response: %s" msgstr "неправилен отговор на „ls-refs“: „%s“" -#: connect.c:504 +#: connect.c:530 msgid "expected flush after ref listing" msgstr "след изброяването на указателите се очаква изчистване на буферите" -#: connect.c:507 +#: connect.c:533 msgid "expected response end packet after ref listing" msgstr "след изброяването на указателите се очаква пакет за край" -#: connect.c:640 +#: connect.c:666 #, c-format msgid "protocol '%s' is not supported" msgstr "протокол „%s“ не се поддържа" -#: connect.c:691 +#: connect.c:717 msgid "unable to set SO_KEEPALIVE on socket" msgstr "неуспешно задаване на „SO_KEEPALIVE“ на гнездо" -#: connect.c:731 connect.c:794 +#: connect.c:757 connect.c:820 #, c-format msgid "Looking up %s ... " msgstr "Търсене на „%s“… " -#: connect.c:735 +#: connect.c:761 #, c-format msgid "unable to look up %s (port %s) (%s)" msgstr "„%s“ (порт %s) не може да се открие („%s“)" #. TRANSLATORS: this is the end of "Looking up %s ... " -#: connect.c:739 connect.c:810 +#: connect.c:765 connect.c:836 #, c-format msgid "" "done.\n" @@ -3046,7 +3109,7 @@ msgstr "" "готово.\n" "Свързване към „%s“ (порт %s)…" -#: connect.c:761 connect.c:838 +#: connect.c:787 connect.c:864 #, c-format msgid "" "unable to connect to %s:\n" @@ -3056,65 +3119,71 @@ msgstr "" "%s" #. TRANSLATORS: this is the end of "Connecting to %s (port %s) ... " -#: connect.c:767 connect.c:844 +#: connect.c:793 connect.c:870 msgid "done." msgstr "действието завърши." -#: connect.c:798 +#: connect.c:824 #, c-format msgid "unable to look up %s (%s)" msgstr "„%s“ не може да се открие (%s)" -#: connect.c:804 +#: connect.c:830 #, c-format msgid "unknown port %s" msgstr "непознат порт „%s“" -#: connect.c:941 connect.c:1271 +#: connect.c:967 connect.c:1299 #, c-format msgid "strange hostname '%s' blocked" msgstr "необичайното име на хост „%s“ е блокирано" -#: connect.c:943 +#: connect.c:969 #, c-format msgid "strange port '%s' blocked" msgstr "необичайният порт „%s“ е блокиран" -#: connect.c:953 +#: connect.c:979 #, c-format msgid "cannot start proxy %s" msgstr "посредникът „%s“ не може да се стартира" -#: connect.c:1024 +#: connect.c:1050 msgid "no path specified; see 'git help pull' for valid url syntax" msgstr "" "не е указан път. Проверете синтаксиса с командата:\n" "\n" " git help pull" -#: connect.c:1219 +#: connect.c:1190 +msgid "newline is forbidden in git:// hosts and repo paths" +msgstr "" +"знакът за нов ред не е позволен в адресите и в пътищата до хранилищата " +"„git://“" + +#: connect.c:1247 msgid "ssh variant 'simple' does not support -4" msgstr "вариантът за „ssh“ — „simple“ (опростен), не поддържа опцията „-4“" -#: connect.c:1231 +#: connect.c:1259 msgid "ssh variant 'simple' does not support -6" msgstr "вариантът за „ssh“ — „simple“ (опростен), не поддържа опцията „-6“" -#: connect.c:1248 +#: connect.c:1276 msgid "ssh variant 'simple' does not support setting port" msgstr "" "вариантът за „ssh“ — „simple“ (опростен), не поддържа задаването на порт" -#: connect.c:1360 +#: connect.c:1388 #, c-format msgid "strange pathname '%s' blocked" msgstr "необичайният път „%s“ е блокиран" -#: connect.c:1408 +#: connect.c:1436 msgid "unable to fork" msgstr "неуспешно създаване на процес" -#: connected.c:108 builtin/fsck.c:209 builtin/prune.c:45 +#: connected.c:108 builtin/fsck.c:191 builtin/prune.c:45 msgid "Checking connectivity" msgstr "Проверка на свързаността" @@ -3389,6 +3458,11 @@ msgstr "" msgid "Marked %d islands, done.\n" msgstr "Отбелязани са %d групи, работата приключи.\n" +#: diff-merges.c:70 +#, c-format +msgid "unknown value for --diff-merges: %s" +msgstr "непозната стойност за опцията „--diff-merges“: „%s“" + #: diff-lib.c:534 msgid "--merge-base does not work with ranges" msgstr "опцията „--merge-base“ не работи с диапазони" @@ -3483,32 +3557,32 @@ msgid "external diff died, stopping at %s" msgstr "" "външната програма за разлики завърши неуспешно. Спиране на работата при „%s“" -#: diff.c:4625 +#: diff.c:4628 msgid "--name-only, --name-status, --check and -s are mutually exclusive" msgstr "" "Опциите „--name-only“, „--name-status“, „--check“ и „-s“ са несъвместими " "една с друга" -#: diff.c:4628 +#: diff.c:4631 msgid "-G, -S and --find-object are mutually exclusive" msgstr "Опциите „-G“, „-S“ и „--find-object“ са несъвместими една с друга" -#: diff.c:4707 +#: diff.c:4710 msgid "--follow requires exactly one pathspec" msgstr "Опцията „--follow“ изисква точно един път" -#: diff.c:4755 +#: diff.c:4758 #, c-format msgid "invalid --stat value: %s" msgstr "неправилна стойност за „--stat“: %s" -#: diff.c:4760 diff.c:4765 diff.c:4770 diff.c:4775 diff.c:5303 +#: diff.c:4763 diff.c:4768 diff.c:4773 diff.c:4778 diff.c:5306 #: parse-options.c:197 parse-options.c:201 builtin/commit-graph.c:180 #, c-format msgid "%s expects a numerical value" msgstr "опцията „%s“ очаква число за аргумент" -#: diff.c:4792 +#: diff.c:4795 #, c-format msgid "" "Failed to parse --dirstat/-X option parameter:\n" @@ -3517,44 +3591,44 @@ msgstr "" "Неразпознат параметър към опцията „--dirstat/-X“:\n" "%s" -#: diff.c:4877 +#: diff.c:4880 #, c-format msgid "unknown change class '%c' in --diff-filter=%s" msgstr "непознат вид промяна: „%c“ в „--diff-filter=%s“" -#: diff.c:4901 +#: diff.c:4904 #, c-format msgid "unknown value after ws-error-highlight=%.*s" msgstr "непозната стойност след „ws-error-highlight=%.*s“" -#: diff.c:4915 +#: diff.c:4918 #, c-format msgid "unable to resolve '%s'" msgstr "„%s“ не може да се открие" -#: diff.c:4965 diff.c:4971 +#: diff.c:4968 diff.c:4974 #, c-format msgid "%s expects / form" msgstr "" "опцията „%s“ изисква стойности за МИНИМАЛЕН_%%_ПРОМЯНА_ЗА_ИЗТОЧНИК_/" "МАКСИМАЛЕН_%%_ПРОМЯНА_ЗА_ЗАМЯНА от" -#: diff.c:4983 +#: diff.c:4986 #, c-format msgid "%s expects a character, got '%s'" msgstr "опцията „%s“ изисква знак, а не: „%s“" -#: diff.c:5004 +#: diff.c:5007 #, c-format msgid "bad --color-moved argument: %s" msgstr "неправилен аргумент за „--color-moved“: „%s“" -#: diff.c:5023 +#: diff.c:5026 #, c-format msgid "invalid mode '%s' in --color-moved-ws" msgstr "неправилен режим „%s“ за „ --color-moved-ws“" -#: diff.c:5063 +#: diff.c:5066 msgid "" "option diff-algorithm accepts \"myers\", \"minimal\", \"patience\" and " "\"histogram\"" @@ -3563,158 +3637,158 @@ msgstr "" "Майерс), „minimal“ (минимизиране на разликите), „patience“ (пасианс) и " "„histogram“ (хистограмен)" -#: diff.c:5099 diff.c:5119 +#: diff.c:5102 diff.c:5122 #, c-format msgid "invalid argument to %s" msgstr "неправилен аргумент към „%s“" -#: diff.c:5223 +#: diff.c:5226 #, c-format msgid "invalid regex given to -I: '%s'" msgstr "неправилен регулярен израз подаден към „-I“: „%s“" -#: diff.c:5272 +#: diff.c:5275 #, c-format msgid "failed to parse --submodule option parameter: '%s'" msgstr "неразпознат параметър към опцията „--submodule“: „%s“" -#: diff.c:5328 +#: diff.c:5331 #, c-format msgid "bad --word-diff argument: %s" msgstr "неправилен аргумент към „--word-diff“: „%s“" -#: diff.c:5351 +#: diff.c:5367 msgid "Diff output format options" msgstr "Формат на изхода за разликите" -#: diff.c:5353 diff.c:5359 +#: diff.c:5369 diff.c:5375 msgid "generate patch" msgstr "създаване на кръпки" -#: diff.c:5356 builtin/log.c:178 +#: diff.c:5372 builtin/log.c:179 msgid "suppress diff output" msgstr "без извеждане на разликите" -#: diff.c:5361 diff.c:5475 diff.c:5482 +#: diff.c:5377 diff.c:5491 diff.c:5498 msgid "" msgstr "БРОЙ" -#: diff.c:5362 diff.c:5365 +#: diff.c:5378 diff.c:5381 msgid "generate diffs with lines context" msgstr "файловете с разлики да са с контекст с такъв БРОЙ редове" -#: diff.c:5367 +#: diff.c:5383 msgid "generate the diff in raw format" msgstr "файловете с разлики да са в суров формат" -#: diff.c:5370 +#: diff.c:5386 msgid "synonym for '-p --raw'" msgstr "псевдоним на „-p --stat“" -#: diff.c:5374 +#: diff.c:5390 msgid "synonym for '-p --stat'" msgstr "псевдоним на „-p --stat“" -#: diff.c:5378 +#: diff.c:5394 msgid "machine friendly --stat" msgstr "„--stat“ във формат за четене от програма" -#: diff.c:5381 +#: diff.c:5397 msgid "output only the last line of --stat" msgstr "извеждане само на последния ред на „--stat“" -#: diff.c:5383 diff.c:5391 +#: diff.c:5399 diff.c:5407 msgid "..." msgstr "ПАРАМЕТЪР_1, ПАРАМЕТЪР_2, …" -#: diff.c:5384 +#: diff.c:5400 msgid "" "output the distribution of relative amount of changes for each sub-directory" msgstr "извеждане на разпределението на промените за всяка поддиректория" -#: diff.c:5388 +#: diff.c:5404 msgid "synonym for --dirstat=cumulative" msgstr "псевдоним на „--dirstat=cumulative“" -#: diff.c:5392 +#: diff.c:5408 msgid "synonym for --dirstat=files,param1,param2..." msgstr "псевдоним на „--dirstat=ФАЙЛОВЕ,ПАРАМЕТЪР_1,ПАРАМЕТЪР_2,…“" -#: diff.c:5396 +#: diff.c:5412 msgid "warn if changes introduce conflict markers or whitespace errors" msgstr "" "предупреждаване, ако промените водят до маркери за конфликт или грешки в " "празните знаци" -#: diff.c:5399 +#: diff.c:5415 msgid "condensed summary such as creations, renames and mode changes" msgstr "" "съкратено резюме на създадените, преименуваните и файловете с промяна на " "режима на достъп" -#: diff.c:5402 +#: diff.c:5418 msgid "show only names of changed files" msgstr "извеждане само на имената на променените файлове" -#: diff.c:5405 +#: diff.c:5421 msgid "show only names and status of changed files" msgstr "извеждане само на имената и статистиката за променените файлове" -#: diff.c:5407 +#: diff.c:5423 msgid "[,[,]]" msgstr "ШИРОЧИНА[,ИМЕ-ШИРОЧИНА[,БРОЙ]]" -#: diff.c:5408 +#: diff.c:5424 msgid "generate diffstat" msgstr "извеждане на статистика за промените" -#: diff.c:5410 diff.c:5413 diff.c:5416 +#: diff.c:5426 diff.c:5429 diff.c:5432 msgid "" msgstr "ШИРОЧИНА" -#: diff.c:5411 +#: diff.c:5427 msgid "generate diffstat with a given width" msgstr "статистика с такава ШИРОЧИНА за промените" -#: diff.c:5414 +#: diff.c:5430 msgid "generate diffstat with a given name width" msgstr "статистика за промените с такава ШИРОЧИНА на имената" -#: diff.c:5417 +#: diff.c:5433 msgid "generate diffstat with a given graph width" msgstr "статистика за промените с такава ШИРОЧИНА на гра̀фа" -#: diff.c:5419 +#: diff.c:5435 msgid "" msgstr "БРОЙ" -#: diff.c:5420 +#: diff.c:5436 msgid "generate diffstat with limited lines" msgstr "ограничаване на БРОя на редовете в статистиката за промените" -#: diff.c:5423 +#: diff.c:5439 msgid "generate compact summary in diffstat" msgstr "кратко резюме в статистиката за промените" -#: diff.c:5426 +#: diff.c:5442 msgid "output a binary diff that can be applied" msgstr "извеждане на двоична разлика във вид за прилагане" -#: diff.c:5429 +#: diff.c:5445 msgid "show full pre- and post-image object names on the \"index\" lines" msgstr "" "показване на пълните имена на обекти в редовете за индекса при вариантите " "преди и след промяната" -#: diff.c:5431 +#: diff.c:5447 msgid "show colored diff" msgstr "разлики в цвят" -#: diff.c:5432 +#: diff.c:5448 msgid "" msgstr "ВИД" -#: diff.c:5433 +#: diff.c:5449 msgid "" "highlight whitespace errors in the 'context', 'old' or 'new' lines in the " "diff" @@ -3722,7 +3796,7 @@ msgstr "" "грешките в празните знаци да се указват в редовете за контекста, вариантите " "преди и след разликата," -#: diff.c:5436 +#: diff.c:5452 msgid "" "do not munge pathnames and use NULs as output field terminators in --raw or " "--numstat" @@ -3730,261 +3804,261 @@ msgstr "" "без преименуване на пътищата. Да се използват нулеви байтове за разделители " "на полета в изхода при ползване на опцията „--raw“ или „--numstat“" -#: diff.c:5439 diff.c:5442 diff.c:5445 diff.c:5554 +#: diff.c:5455 diff.c:5458 diff.c:5461 diff.c:5570 msgid "" msgstr "ПРЕФИКС" -#: diff.c:5440 +#: diff.c:5456 msgid "show the given source prefix instead of \"a/\"" msgstr "префикс вместо „a/“ за източник" -#: diff.c:5443 +#: diff.c:5459 msgid "show the given destination prefix instead of \"b/\"" msgstr "префикс вместо „b/“ за цел" -#: diff.c:5446 +#: diff.c:5462 msgid "prepend an additional prefix to every line of output" msgstr "добавяне на допълнителен префикс за всеки ред на изхода" -#: diff.c:5449 +#: diff.c:5465 msgid "do not show any source or destination prefix" msgstr "без префикс за източника и целта" -#: diff.c:5452 +#: diff.c:5468 msgid "show context between diff hunks up to the specified number of lines" msgstr "" "извеждане на контекст между последователните парчета с разлики от указания " "БРОЙ редове" -#: diff.c:5456 diff.c:5461 diff.c:5466 +#: diff.c:5472 diff.c:5477 diff.c:5482 msgid "" msgstr "ЗНАК" -#: diff.c:5457 +#: diff.c:5473 msgid "specify the character to indicate a new line instead of '+'" msgstr "знак вместо „+“ за нов вариант на ред" -#: diff.c:5462 +#: diff.c:5478 msgid "specify the character to indicate an old line instead of '-'" msgstr "знак вместо „-“ за стар вариант на ред" -#: diff.c:5467 +#: diff.c:5483 msgid "specify the character to indicate a context instead of ' '" msgstr "знак вместо „ “ за контекст" -#: diff.c:5470 +#: diff.c:5486 msgid "Diff rename options" msgstr "Настройки за разлики с преименуване" -#: diff.c:5471 +#: diff.c:5487 msgid "[/]" msgstr "МИНИМАЛЕН_%_ПРОМЯНА_ЗА_ИЗТОЧНИК[/МАКСИМАЛEН_%_ПРОМЯНА_ЗА_ЗАМЯНА]" -#: diff.c:5472 +#: diff.c:5488 msgid "break complete rewrite changes into pairs of delete and create" msgstr "" "заместване на пълните промени с последователност от изтриване и създаване" -#: diff.c:5476 +#: diff.c:5492 msgid "detect renames" msgstr "засичане на преименуванията" -#: diff.c:5480 +#: diff.c:5496 msgid "omit the preimage for deletes" msgstr "без предварителен вариант при изтриване" -#: diff.c:5483 +#: diff.c:5499 msgid "detect copies" msgstr "засичане на копиранията" -#: diff.c:5487 +#: diff.c:5503 msgid "use unmodified files as source to find copies" msgstr "търсене на копирано и от непроменените файлове" -#: diff.c:5489 +#: diff.c:5505 msgid "disable rename detection" msgstr "без търсене на преименувания" -#: diff.c:5492 +#: diff.c:5508 msgid "use empty blobs as rename source" msgstr "празни обекти като източник при преименувания" -#: diff.c:5494 +#: diff.c:5510 msgid "continue listing the history of a file beyond renames" msgstr "" "продължаване на извеждането на историята — без отрязването при преименувания " "на файл" -#: diff.c:5497 +#: diff.c:5513 msgid "" "prevent rename/copy detection if the number of rename/copy targets exceeds " "given limit" msgstr "" "без засичане на преименувания/копирания, ако броят им надвишава тази стойност" -#: diff.c:5499 +#: diff.c:5515 msgid "Diff algorithm options" msgstr "Опции към алгоритъма за разлики" -#: diff.c:5501 +#: diff.c:5517 msgid "produce the smallest possible diff" msgstr "търсене на възможно най-малка разлика" -#: diff.c:5504 +#: diff.c:5520 msgid "ignore whitespace when comparing lines" msgstr "без промени в празните знаци при сравняване на редове" -#: diff.c:5507 +#: diff.c:5523 msgid "ignore changes in amount of whitespace" msgstr "без промени в празните знаци" -#: diff.c:5510 +#: diff.c:5526 msgid "ignore changes in whitespace at EOL" msgstr "без промени в празните знаци в края на редовете" -#: diff.c:5513 +#: diff.c:5529 msgid "ignore carrier-return at the end of line" msgstr "без промени в знаците за край на ред" -#: diff.c:5516 +#: diff.c:5532 msgid "ignore changes whose lines are all blank" msgstr "без промени в редовете, които са изцяло от празни знаци" -#: diff.c:5518 diff.c:5540 diff.c:5543 diff.c:5588 +#: diff.c:5534 diff.c:5556 diff.c:5559 diff.c:5604 msgid "" msgstr "РЕГУЛЯРЕН_ИЗРАЗ" -#: diff.c:5519 +#: diff.c:5535 msgid "ignore changes whose all lines match " msgstr "без промени в редовете, които напасват РЕГУЛЯРНия_ИЗРАЗ" -#: diff.c:5522 +#: diff.c:5538 msgid "heuristic to shift diff hunk boundaries for easy reading" msgstr "" "евристика за преместване на границите на парчетата за улесняване на четенето" -#: diff.c:5525 +#: diff.c:5541 msgid "generate diff using the \"patience diff\" algorithm" msgstr "разлика чрез алгоритъм за подредба като пасианс" -#: diff.c:5529 +#: diff.c:5545 msgid "generate diff using the \"histogram diff\" algorithm" msgstr "разлика по хистограмния алгоритъм" -#: diff.c:5531 +#: diff.c:5547 msgid "" msgstr "АЛГОРИТЪМ" -#: diff.c:5532 +#: diff.c:5548 msgid "choose a diff algorithm" msgstr "избор на АЛГОРИТЪМа за разлики" -#: diff.c:5534 +#: diff.c:5550 msgid "" msgstr "ТЕКСТ" -#: diff.c:5535 +#: diff.c:5551 msgid "generate diff using the \"anchored diff\" algorithm" msgstr "разлика чрез алгоритъма със закотвяне" -#: diff.c:5537 diff.c:5546 diff.c:5549 +#: diff.c:5553 diff.c:5562 diff.c:5565 msgid "" msgstr "РЕЖИМ" -#: diff.c:5538 +#: diff.c:5554 msgid "show word diff, using to delimit changed words" msgstr "" "разлика по думи, като се ползва този РЕЖИМ за отделянето на променените думи" -#: diff.c:5541 +#: diff.c:5557 msgid "use to decide what a word is" msgstr "РЕГУЛЯРЕН_ИЗРАЗ за разделяне по думи" -#: diff.c:5544 +#: diff.c:5560 msgid "equivalent to --word-diff=color --word-diff-regex=" msgstr "псевдоним на „--word-diff=color --word-diff-regex=РЕГУЛЯРЕН_ИЗРАЗ“" -#: diff.c:5547 +#: diff.c:5563 msgid "moved lines of code are colored differently" msgstr "различен цвят за извеждане на преместените редове" -#: diff.c:5550 +#: diff.c:5566 msgid "how white spaces are ignored in --color-moved" msgstr "" "режим за прескачането на празните знаци при задаването на „--color-moved“" -#: diff.c:5553 +#: diff.c:5569 msgid "Other diff options" msgstr "Други опции за разлики" -#: diff.c:5555 +#: diff.c:5571 msgid "when run from subdir, exclude changes outside and show relative paths" msgstr "" "при изпълнение от поддиректория да се пренебрегват разликите извън нея и да " "се ползват относителни пътища" -#: diff.c:5559 +#: diff.c:5575 msgid "treat all files as text" msgstr "обработка на всички файлове като текстови" -#: diff.c:5561 +#: diff.c:5577 msgid "swap two inputs, reverse the diff" msgstr "размяна на двата входа — обръщане на разликата" -#: diff.c:5563 +#: diff.c:5579 msgid "exit with 1 if there were differences, 0 otherwise" msgstr "" "завършване с код за състояние 1 при наличието на разлики, а в противен " "случай — с 0" -#: diff.c:5565 +#: diff.c:5581 msgid "disable all output of the program" msgstr "без всякакъв изход от програмата" -#: diff.c:5567 +#: diff.c:5583 msgid "allow an external diff helper to be executed" msgstr "позволяване на изпълнение на външна помощна програма за разлики" -#: diff.c:5569 +#: diff.c:5585 msgid "run external text conversion filters when comparing binary files" msgstr "" "изпълнение на външни програми-филтри при сравнението на двоични файлове" -#: diff.c:5571 +#: diff.c:5587 msgid "" msgstr "КОГА" -#: diff.c:5572 +#: diff.c:5588 msgid "ignore changes to submodules in the diff generation" msgstr "игнориране на промените в подмодулите при извеждането на разликите" -#: diff.c:5575 +#: diff.c:5591 msgid "" msgstr "ФОРМАТ" -#: diff.c:5576 +#: diff.c:5592 msgid "specify how differences in submodules are shown" msgstr "начин за извеждане на промените в подмодулите" -#: diff.c:5580 +#: diff.c:5596 msgid "hide 'git add -N' entries from the index" msgstr "без включване в индекса на записите, добавени с „git add -N“" -#: diff.c:5583 +#: diff.c:5599 msgid "treat 'git add -N' entries as real in the index" msgstr "включване в индекса на записите, добавени с „git add -N“" -#: diff.c:5585 +#: diff.c:5601 msgid "" msgstr "НИЗ" -#: diff.c:5586 +#: diff.c:5602 msgid "" "look for differences that change the number of occurrences of the specified " "string" msgstr "търсене на разлики, които променят броя на поява на указаните низове" -#: diff.c:5589 +#: diff.c:5605 msgid "" "look for differences that change the number of occurrences of the specified " "regex" @@ -3992,57 +4066,69 @@ msgstr "" "търсене на разлики, които променят броя на поява на низовете, които напасват " "на регулярния израз" -#: diff.c:5592 +#: diff.c:5608 msgid "show all changes in the changeset with -S or -G" msgstr "извеждане на всички промени с „-G“/„-S“" -#: diff.c:5595 +#: diff.c:5611 msgid "treat in -S as extended POSIX regular expression" msgstr "НИЗът към „-S“ да се тълкува като разширен регулярен израз по POSIX" -#: diff.c:5598 +#: diff.c:5614 msgid "control the order in which files appear in the output" msgstr "управление на подредбата на файловете в изхода" -#: diff.c:5599 +#: diff.c:5615 diff.c:5618 +msgid "" +msgstr "ПЪТ" + +#: diff.c:5616 +msgid "show the change in the specified path first" +msgstr "първо извеждане на промяната в указания път" + +#: diff.c:5619 +msgid "skip the output to the specified path" +msgstr "прескачане на изхода към указания път" + +#: diff.c:5621 msgid "" msgstr "ИДЕНТИФИКАТОР_НА_ОБЕКТ" -#: diff.c:5600 +#: diff.c:5622 msgid "" "look for differences that change the number of occurrences of the specified " "object" msgstr "търсене на разлики, които променят броя на поява на указания обект" -#: diff.c:5602 +#: diff.c:5624 msgid "[(A|C|D|M|R|T|U|X|B)...[*]]" msgstr "[(A|C|D|M|R|T|U|X|B)…[*]]" -#: diff.c:5603 +#: diff.c:5625 msgid "select files by diff type" msgstr "избор на файловете по вид разлика" -#: diff.c:5605 +#: diff.c:5627 msgid "" msgstr "ФАЙЛ" -#: diff.c:5606 +#: diff.c:5628 msgid "Output to a specific file" msgstr "Изход към указания файл" -#: diff.c:6263 +#: diff.c:6285 msgid "inexact rename detection was skipped due to too many files." msgstr "" "търсенето на преименувания на обекти съчетани с промени се прескача поради " "многото файлове." -#: diff.c:6266 +#: diff.c:6288 msgid "only found copies from modified paths due to too many files." msgstr "" "установени са само точните копия на променените пътища поради многото " "файлове." -#: diff.c:6269 +#: diff.c:6291 #, c-format msgid "" "you may want to set your %s variable to at least %d and retry the command." @@ -4053,10 +4139,15 @@ msgstr "задайте променливата „%s“ да е поне %d и msgid "failed to read orderfile '%s'" msgstr "файлът с подредбата на съответствията „%s“ не може да бъде прочетен" -#: diffcore-rename.c:592 +#: diffcore-rename.c:786 msgid "Performing inexact rename detection" msgstr "Търсене на преименувания на обекти съчетани с промени" +#: diffcore-rotate.c:29 +#, c-format +msgid "No such path '%s' in the diff" +msgstr "Няма път на име „%s“ в разликата" + #: dir.c:578 #, c-format msgid "pathspec '%s' did not match any file(s) known to git" @@ -4102,17 +4193,17 @@ msgid "untracked cache is disabled on this system or location" msgstr "" "кешът за неследените файлове е изключен на тази система или местоположение" -#: dir.c:3520 +#: dir.c:3534 #, c-format msgid "index file corrupt in repo %s" msgstr "файлът с индекса е повреден в хранилището „%s“" -#: dir.c:3565 dir.c:3570 +#: dir.c:3579 dir.c:3584 #, c-format msgid "could not create directories for %s" msgstr "директориите за „%s“ не може да бъдат създадени" -#: dir.c:3599 +#: dir.c:3613 #, c-format msgid "could not migrate git directory from '%s' to '%s'" msgstr "директорията на git не може да се мигрира от „%s“ до „%s“" @@ -4131,12 +4222,12 @@ msgstr "Филтриране на съдържанието" msgid "could not stat file '%s'" msgstr "неуспешно изпълнение на „stat“ върху файла „%s“" -#: environment.c:150 +#: environment.c:152 #, c-format msgid "bad git namespace path \"%s\"" msgstr "неправилен път към пространства от имена „%s“" -#: environment.c:337 +#: environment.c:335 #, c-format msgid "could not set GIT_DIR to '%s'" msgstr "GIT_DIR не може да се зададе да е „%s“" @@ -4172,32 +4263,32 @@ msgstr "невъзможно писане към отдалечено храни msgid "--stateless-rpc requires multi_ack_detailed" msgstr "опцията „--stateless-rpc“ изисква „multi_ack_detailed“" -#: fetch-pack.c:378 fetch-pack.c:1406 +#: fetch-pack.c:378 fetch-pack.c:1457 #, c-format msgid "invalid shallow line: %s" msgstr "неправилен плитък ред: „%s“" -#: fetch-pack.c:384 fetch-pack.c:1412 +#: fetch-pack.c:384 fetch-pack.c:1463 #, c-format msgid "invalid unshallow line: %s" msgstr "неправилен неплитък ред: „%s“" -#: fetch-pack.c:386 fetch-pack.c:1414 +#: fetch-pack.c:386 fetch-pack.c:1465 #, c-format msgid "object not found: %s" msgstr "обектът „%s“ липсва" -#: fetch-pack.c:389 fetch-pack.c:1417 +#: fetch-pack.c:389 fetch-pack.c:1468 #, c-format msgid "error in object: %s" msgstr "грешка в обекта: „%s“" -#: fetch-pack.c:391 fetch-pack.c:1419 +#: fetch-pack.c:391 fetch-pack.c:1470 #, c-format msgid "no shallow found: %s" msgstr "не е открит плитък обект: %s" -#: fetch-pack.c:394 fetch-pack.c:1423 +#: fetch-pack.c:394 fetch-pack.c:1474 #, c-format msgid "expected shallow/unshallow, got %s" msgstr "очаква се плитък или не обект, а бе получено: „%s“" @@ -4235,174 +4326,178 @@ msgstr "Отбелязване на „%s“ като пълно" msgid "already have %s (%s)" msgstr "вече има „%s“ (%s)" -#: fetch-pack.c:827 +#: fetch-pack.c:844 msgid "fetch-pack: unable to fork off sideband demultiplexer" msgstr "fetch-pack: не може да се създаде процес за демултиплексора" -#: fetch-pack.c:835 +#: fetch-pack.c:852 msgid "protocol error: bad pack header" msgstr "протоколна грешка: неправилна заглавна част на пакет" -#: fetch-pack.c:919 +#: fetch-pack.c:946 #, c-format msgid "fetch-pack: unable to fork off %s" msgstr "fetch-pack: не може да се създаде процес за „%s“" -#: fetch-pack.c:937 +#: fetch-pack.c:952 +msgid "fetch-pack: invalid index-pack output" +msgstr "fetch-pack: неправилен изход от командата „index-pack“" + +#: fetch-pack.c:969 #, c-format msgid "%s failed" msgstr "неуспешно изпълнение на „%s“" -#: fetch-pack.c:939 +#: fetch-pack.c:971 msgid "error in sideband demultiplexer" msgstr "грешка в демултиплексора" -#: fetch-pack.c:982 +#: fetch-pack.c:1031 #, c-format msgid "Server version is %.*s" msgstr "Версията на сървъра е: %.*s" -#: fetch-pack.c:990 fetch-pack.c:996 fetch-pack.c:999 fetch-pack.c:1005 -#: fetch-pack.c:1009 fetch-pack.c:1013 fetch-pack.c:1017 fetch-pack.c:1021 -#: fetch-pack.c:1025 fetch-pack.c:1029 fetch-pack.c:1033 fetch-pack.c:1037 -#: fetch-pack.c:1043 fetch-pack.c:1049 fetch-pack.c:1054 fetch-pack.c:1059 +#: fetch-pack.c:1039 fetch-pack.c:1045 fetch-pack.c:1048 fetch-pack.c:1054 +#: fetch-pack.c:1058 fetch-pack.c:1062 fetch-pack.c:1066 fetch-pack.c:1070 +#: fetch-pack.c:1074 fetch-pack.c:1078 fetch-pack.c:1082 fetch-pack.c:1086 +#: fetch-pack.c:1092 fetch-pack.c:1098 fetch-pack.c:1103 fetch-pack.c:1108 #, c-format msgid "Server supports %s" msgstr "Сървърът поддържа „%s“" -#: fetch-pack.c:992 +#: fetch-pack.c:1041 msgid "Server does not support shallow clients" msgstr "Сървърът не поддържа плитки клиенти" -#: fetch-pack.c:1052 +#: fetch-pack.c:1101 msgid "Server does not support --shallow-since" msgstr "Сървърът не поддържа опцията „--shallow-since“" -#: fetch-pack.c:1057 +#: fetch-pack.c:1106 msgid "Server does not support --shallow-exclude" msgstr "Сървърът не поддържа опцията „--shallow-exclude“" -#: fetch-pack.c:1061 +#: fetch-pack.c:1110 msgid "Server does not support --deepen" msgstr "Сървърът не поддържа опцията „--deepen“" -#: fetch-pack.c:1063 +#: fetch-pack.c:1112 msgid "Server does not support this repository's object format" msgstr "Сървърът не поддържа форма̀та на обектите на това хранилище" -#: fetch-pack.c:1076 +#: fetch-pack.c:1125 msgid "no common commits" msgstr "няма общи подавания" -#: fetch-pack.c:1088 fetch-pack.c:1628 +#: fetch-pack.c:1138 fetch-pack.c:1682 msgid "git fetch-pack: fetch failed." msgstr "git fetch-pack: неуспешно доставяне." -#: fetch-pack.c:1214 +#: fetch-pack.c:1265 #, c-format msgid "mismatched algorithms: client %s; server %s" msgstr "различни алгоритми — на клиента: „%s“, на сървъра: „%s“" -#: fetch-pack.c:1218 +#: fetch-pack.c:1269 #, c-format msgid "the server does not support algorithm '%s'" msgstr "сървърът не поддържа алгоритъм „%s“" -#: fetch-pack.c:1238 +#: fetch-pack.c:1289 msgid "Server does not support shallow requests" msgstr "Сървърът не поддържа плитки заявки" -#: fetch-pack.c:1245 +#: fetch-pack.c:1296 msgid "Server supports filter" msgstr "Сървърът поддържа филтри" -#: fetch-pack.c:1284 +#: fetch-pack.c:1335 msgid "unable to write request to remote" msgstr "невъзможно писане към отдалечено хранилище" -#: fetch-pack.c:1302 +#: fetch-pack.c:1353 #, c-format msgid "error reading section header '%s'" msgstr "грешка при прочитане на заглавната част на раздел „%s“" -#: fetch-pack.c:1308 +#: fetch-pack.c:1359 #, c-format msgid "expected '%s', received '%s'" msgstr "очаква се „%s“, а бе получено „%s“" -#: fetch-pack.c:1369 +#: fetch-pack.c:1420 #, c-format msgid "unexpected acknowledgment line: '%s'" msgstr "неочакван ред за потвърждение: „%s“" -#: fetch-pack.c:1374 +#: fetch-pack.c:1425 #, c-format msgid "error processing acks: %d" msgstr "грешка при обработка на потвържденията: %d" -#: fetch-pack.c:1384 +#: fetch-pack.c:1435 msgid "expected packfile to be sent after 'ready'" msgstr "" "очакваше се пакетният файл да бъде изпратен след отговор за готовност (ready)" -#: fetch-pack.c:1386 +#: fetch-pack.c:1437 msgid "expected no other sections to be sent after no 'ready'" msgstr "" "очакваше се след липса на отговор за готовност (ready) да не се се пращат " "други раздели" -#: fetch-pack.c:1428 +#: fetch-pack.c:1479 #, c-format msgid "error processing shallow info: %d" msgstr "грешка при обработка на информация за дълбочината/плиткостта: %d" -#: fetch-pack.c:1475 +#: fetch-pack.c:1526 #, c-format msgid "expected wanted-ref, got '%s'" msgstr "очаква се искан указател, а бе получено: „%s“" -#: fetch-pack.c:1480 +#: fetch-pack.c:1531 #, c-format msgid "unexpected wanted-ref: '%s'" msgstr "неочакван искан указател: „%s“" -#: fetch-pack.c:1485 +#: fetch-pack.c:1536 #, c-format msgid "error processing wanted refs: %d" msgstr "грешка при обработката на исканите указатели: %d" -#: fetch-pack.c:1515 +#: fetch-pack.c:1566 msgid "git fetch-pack: expected response end packet" msgstr "git fetch-pack: очаква се пакет за край на отговора" -#: fetch-pack.c:1897 +#: fetch-pack.c:1960 msgid "no matching remote head" msgstr "не може да бъде открит подходящ връх от отдалеченото хранилище" -#: fetch-pack.c:1920 builtin/clone.c:693 +#: fetch-pack.c:1983 builtin/clone.c:693 msgid "remote did not send all necessary objects" msgstr "отдалеченото хранилище не изпрати всички необходими обекти." -#: fetch-pack.c:1947 +#: fetch-pack.c:2010 #, c-format msgid "no such remote ref %s" msgstr "такъв отдалечен указател няма: %s" -#: fetch-pack.c:1950 +#: fetch-pack.c:2013 #, c-format msgid "Server does not allow request for unadvertised object %s" msgstr "Сървърът не позволява заявка за необявен обект „%s“" -#: gpg-interface.c:272 +#: gpg-interface.c:273 msgid "could not create temporary file" msgstr "не може да се създаде временен файл" -#: gpg-interface.c:275 +#: gpg-interface.c:276 #, c-format msgid "failed writing detached signature to '%s'" msgstr "Програмата не успя да запише самостоятелния подпис в „%s“" -#: gpg-interface.c:457 +#: gpg-interface.c:470 msgid "gpg failed to sign the data" msgstr "Програмата „gpg“ не подписа данните." @@ -4411,7 +4506,7 @@ msgstr "Програмата „gpg“ не подписа данните." msgid "ignore invalid color '%.*s' in log.graphColors" msgstr "прескачане на неправилния цвят „%.*s“ в „log.graphColors“" -#: grep.c:640 +#: grep.c:543 msgid "" "given pattern contains NULL byte (via -f ). This is only supported " "with -P under PCRE v2" @@ -4419,18 +4514,18 @@ msgstr "" "зададеният шаблон съдържа нулев знак (идва от -f „ФАЙЛ“). Това се поддържа " "в комбинация с „-P“ само при ползването на „PCRE v2“" -#: grep.c:2100 +#: grep.c:1906 #, c-format msgid "'%s': unable to read %s" msgstr "„%s“: файлът сочен от „%s“ не може да бъде прочетен" -#: grep.c:2117 setup.c:176 builtin/clone.c:412 builtin/diff.c:89 +#: grep.c:1923 setup.c:176 builtin/clone.c:412 builtin/diff.c:90 #: builtin/rm.c:135 #, c-format msgid "failed to stat '%s'" msgstr "не може да бъде получена информация чрез „stat“ за „%s“" -#: grep.c:2128 +#: grep.c:1934 #, c-format msgid "'%s': short read" msgstr "„%s“: изчитането върна по-малко байтове от очакваното" @@ -4500,7 +4595,7 @@ msgstr "команди на git от други директории от „$PA msgid "These are common Git commands used in various situations:" msgstr "Това са най-често използваните команди на Git:" -#: help.c:365 git.c:99 +#: help.c:365 git.c:100 #, c-format msgid "unsupported command listing type '%s'" msgstr "неподдържан списък от команди „%s“" @@ -4748,134 +4843,52 @@ msgstr "" msgid "Unable to create '%s.lock': %s" msgstr "Файлът-ключалка „%s.lock“ не може да бъде създаден: %s" -#: ls-refs.c:109 -msgid "expected flush after ls-refs arguments" -msgstr "след аргументите към „ls-refs“ се очаква изчистване на буферите" - -#: merge-ort-wrappers.c:13 merge-recursive.c:3672 -#, c-format -msgid "" -"Your local changes to the following files would be overwritten by merge:\n" -" %s" -msgstr "" -"Сливането ще презапише локалните промени на тези файлове:\n" -" %s" - -#: merge-ort-wrappers.c:33 merge-recursive.c:3436 -#, c-format -msgid "Already up to date!" -msgstr "Вече е обновено!" - -#: merge-recursive.c:356 -msgid "(bad commit)\n" -msgstr "(лошо подаване)\n" - -#: merge-recursive.c:379 -#, c-format -msgid "add_cacheinfo failed for path '%s'; merge aborting." -msgstr "" -"неуспешно изпълнение на „add_cacheinfo“ за пътя „%s“. Сливането е " -"преустановено." - -#: merge-recursive.c:388 -#, c-format -msgid "add_cacheinfo failed to refresh for path '%s'; merge aborting." -msgstr "" -"неуспешно изпълнение на „add_cacheinfo“ за обновяването на пътя „%s“. " -"Сливането е преустановено." - -#: merge-recursive.c:874 -#, c-format -msgid "failed to create path '%s'%s" -msgstr "грешка при създаването на пътя „%s“%s" - -#: merge-recursive.c:885 -#, c-format -msgid "Removing %s to make room for subdirectory\n" -msgstr "Изтриване на „%s“, за да се освободи място за поддиректория\n" - -#: merge-recursive.c:899 merge-recursive.c:918 -msgid ": perhaps a D/F conflict?" -msgstr ": възможно е да има конфликт директория/файл." - -#: merge-recursive.c:908 -#, c-format -msgid "refusing to lose untracked file at '%s'" -msgstr "" -"преустановяване на действието, за да не се изтрие неследеният файл „%s“" - -#: merge-recursive.c:949 builtin/cat-file.c:41 -#, c-format -msgid "cannot read object %s '%s'" -msgstr "обектът „%s“ (%s) не може да бъде прочетен" - -#: merge-recursive.c:954 -#, c-format -msgid "blob expected for %s '%s'" -msgstr "обектът „%s“ (%s) се очакваше да е BLOB, а не е" - -#: merge-recursive.c:979 -#, c-format -msgid "failed to open '%s': %s" -msgstr "„%s“ не може да се отвори: %s" - -#: merge-recursive.c:990 +#: ls-refs.c:37 #, c-format -msgid "failed to symlink '%s': %s" -msgstr "неуспешно създаване на символната връзка „%s“: %s" +msgid "invalid value '%s' for lsrefs.unborn" +msgstr "неправилна стойност „%s“ за „lsrefs.unborn“" -#: merge-recursive.c:995 -#, c-format -msgid "do not know what to do with %06o %s '%s'" -msgstr "" -"не е ясно какво да се прави с обекта „%2$s“ (%3$s) с права за достъп „%1$06o“" +#: ls-refs.c:167 +msgid "expected flush after ls-refs arguments" +msgstr "след аргументите към „ls-refs“ се очаква изчистване на буферите" -#: merge-recursive.c:1191 +#: merge-ort.c:888 merge-recursive.c:1191 #, c-format msgid "Failed to merge submodule %s (not checked out)" msgstr "Неуспешно сливане на подмодула „%s“ (не е изтеглен)" -#: merge-recursive.c:1198 +#: merge-ort.c:897 merge-recursive.c:1198 #, c-format msgid "Failed to merge submodule %s (commits not present)" msgstr "Неуспешно сливане на подмодула „%s“ (няма подавания)" -#: merge-recursive.c:1205 +#: merge-ort.c:906 merge-recursive.c:1205 #, c-format msgid "Failed to merge submodule %s (commits don't follow merge-base)" msgstr "" "Подмодулът „%s“ не може да бъде слят (базата за сливане не предшества " "подаванията)" -#: merge-recursive.c:1213 merge-recursive.c:1225 -#, c-format -msgid "Fast-forwarding submodule %s to the following commit:" -msgstr "Превъртане на подмодула „%s“ до следното подаване:" - -#: merge-recursive.c:1216 merge-recursive.c:1228 +#: merge-ort.c:916 merge-ort.c:923 #, c-format -msgid "Fast-forwarding submodule %s" -msgstr "Превъртане на подмодула „%s“" +msgid "Note: Fast-forwarding submodule %s to %s" +msgstr "Бележка: Превъртане на подмодула „%s“ към „%s“" -#: merge-recursive.c:1251 +#: merge-ort.c:944 #, c-format -msgid "Failed to merge submodule %s (merge following commits not found)" -msgstr "" -"Неуспешно сливане на подмодула „%s“ (липсва сливането, което се предшества " -"от подаванията)" +msgid "Failed to merge submodule %s" +msgstr "Неуспешно сливане на подмодула „%s“" -#: merge-recursive.c:1255 +#: merge-ort.c:951 #, c-format -msgid "Failed to merge submodule %s (not fast-forward)" -msgstr "Неуспешно сливане на подмодула „%s“ (не е превъртане)" - -#: merge-recursive.c:1256 -msgid "Found a possible merge resolution for the submodule:\n" +msgid "" +"Failed to merge submodule %s, but a possible merge resolution exists:\n" +"%s\n" msgstr "" -"Открито е сливане, което може да решава проблема със сливането на " -"подмодула:\n" +"Неуспешно сливане на подмодула „%s“, но е открито възможно решение:\n" +"%s\n" -#: merge-recursive.c:1259 +#: merge-ort.c:955 merge-recursive.c:1259 #, c-format msgid "" "If this is correct simply add it to the index for example\n" @@ -4891,61 +4904,337 @@ msgstr "" "\n" "Това приема предложеното.\n" -#: merge-recursive.c:1268 +#: merge-ort.c:968 #, c-format -msgid "Failed to merge submodule %s (multiple merges found)" -msgstr "Неуспешно сливане на подмодула „%s“ (открити са множество сливания)" +msgid "" +"Failed to merge submodule %s, but multiple possible merges exist:\n" +"%s" +msgstr "" +"Неуспешно сливане на подмодула „%s“, но са открити множество решения:\n" +"%s" -#: merge-recursive.c:1341 +#: merge-ort.c:1127 merge-recursive.c:1341 msgid "Failed to execute internal merge" msgstr "Неуспешно вътрешно сливане" -#: merge-recursive.c:1346 +#: merge-ort.c:1132 merge-recursive.c:1346 #, c-format msgid "Unable to add %s to database" msgstr "„%s“ не може да се добави в базата с данни" -#: merge-recursive.c:1378 +#: merge-ort.c:1139 merge-recursive.c:1378 #, c-format msgid "Auto-merging %s" msgstr "Автоматично сливане на „%s“" -#: merge-recursive.c:1402 +#: merge-ort.c:1278 merge-recursive.c:2100 #, c-format -msgid "Error: Refusing to lose untracked file at %s; writing to %s instead." -msgstr "Грешка: за да не се изтрие неследеният файл „%s“, се записва в „%s“." +msgid "" +"CONFLICT (implicit dir rename): Existing file/dir at %s in the way of " +"implicit directory rename(s) putting the following path(s) there: %s." +msgstr "" +"КОНФЛИКТ (косвено преименуване на директория): следният файл или директория " +"„%s“ не позволяват косвеното преименуване на следния път/ища: %s." -#: merge-recursive.c:1474 +#: merge-ort.c:1288 merge-recursive.c:2110 #, c-format msgid "" -"CONFLICT (%s/delete): %s deleted in %s and %s in %s. Version %s of %s left " -"in tree." +"CONFLICT (implicit dir rename): Cannot map more than one path to %s; " +"implicit directory renames tried to put these paths there: %s" msgstr "" -"КОНФЛИКТ (%s/изтриване): „%s“ е изтрит в %s, а „%s“ в %s. Версия %s на „%s“ " -"е оставена в дървото." +"КОНФЛИКТ (косвено преименуване на директория): повече от един път " +"съответства на „%s“. Косвено преименуване на директория води до поставянето " +"на тези пътища там: %s." -#: merge-recursive.c:1479 +#: merge-ort.c:1471 #, c-format msgid "" -"CONFLICT (%s/delete): %s deleted in %s and %s to %s in %s. Version %s of %s " -"left in tree." +"CONFLICT (directory rename split): Unclear where to rename %s to; it was " +"renamed to multiple other directories, with no destination getting a " +"majority of the files." msgstr "" -"КОНФЛИКТ (%s/изтриване): „%s“ е изтрит в %s, а „%s“ е преименуван на „%s“ в " -"%s. Версия %s на „%s“ е оставена в дървото." +"КОНФЛИКТ (раздвояване при преименуване на директория): Не е ясно как и къде " +"да се преименува „%s“, защото е преместен в няколко нови директории, без " +"никоя от тях да е по-честа цел." -#: merge-recursive.c:1486 +#: merge-ort.c:1637 merge-recursive.c:2447 #, c-format msgid "" -"CONFLICT (%s/delete): %s deleted in %s and %s in %s. Version %s of %s left " -"in tree at %s." +"WARNING: Avoiding applying %s -> %s rename to %s, because %s itself was " +"renamed." msgstr "" -"КОНФЛИКТ (%s/изтриване): „%s“ е изтрит в %s, а „%s“ в %s. Версия %s на „%s“ " -"е оставена в дървото: %s." +"ПРЕДУПРЕЖДЕНИЕ: прескачане на преименуването на „%s“ на „%s“ в „%s“, защото " +"„%s“ също е с променено име." -#: merge-recursive.c:1491 +#: merge-ort.c:1781 merge-recursive.c:3215 #, c-format msgid "" -"CONFLICT (%s/delete): %s deleted in %s and %s to %s in %s. Version %s of %s " +"Path updated: %s added in %s inside a directory that was renamed in %s; " +"moving it to %s." +msgstr "" +"Обновен път: „%s“ е добавен в „%s“ в директория, която е преименувана в " +"„%s“. Обектът се мести в „%s“." + +#: merge-ort.c:1788 merge-recursive.c:3222 +#, c-format +msgid "" +"Path updated: %s renamed to %s in %s, inside a directory that was renamed in " +"%s; moving it to %s." +msgstr "" +"Обновен път: „%s“ е преименуван на „%s“ в „%s“ в директория, която е " +"преименувана в „%s“. Обектът се мести в „%s“." + +#: merge-ort.c:1801 merge-recursive.c:3218 +#, c-format +msgid "" +"CONFLICT (file location): %s added in %s inside a directory that was renamed " +"in %s, suggesting it should perhaps be moved to %s." +msgstr "" +"КОНФЛИКТ (места на файлове): „%s“ е добавен в „%s“ в директория, която е " +"преименувана в „%s“. Предложението е да преместите обекта в „%s“." + +#: merge-ort.c:1809 merge-recursive.c:3225 +#, c-format +msgid "" +"CONFLICT (file location): %s renamed to %s in %s, inside a directory that " +"was renamed in %s, suggesting it should perhaps be moved to %s." +msgstr "" +"КОНФЛИКТ (места на файлове): „%s“ е преименуван на „%s“ в „%s“ в директория, " +"която е преименувана в „%s“. Предложението е да преместите обекта в „%s“." + +#: merge-ort.c:1952 +#, c-format +msgid "CONFLICT (rename/rename): %s renamed to %s in %s and to %s in %s." +msgstr "" +"КОНФЛИКТ (преименуване/преименуване): „%s“ е преименуван на „%s“ в клон „%s“ " +"и на „%s“ в „%s“." + +#: merge-ort.c:2047 +#, c-format +msgid "" +"CONFLICT (rename involved in collision): rename of %s -> %s has content " +"conflicts AND collides with another path; this may result in nested conflict " +"markers." +msgstr "" +"КОНФЛИКТ (има и преименуване в промените): „%s“ е преименуван на „%s“, но " +"има и промени в съдържанието, а и има съвпадение на пътя. Може да се " +"получат вложени маркери за конфликт." + +#: merge-ort.c:2066 merge-ort.c:2090 +#, c-format +msgid "CONFLICT (rename/delete): %s renamed to %s in %s, but deleted in %s." +msgstr "" +"КОНФЛИКТ (преименуване/добавяне): „%s“ е преименуван на „%s“ в клон „%s“, а " +"е изтрит в „%s“." + +#: merge-ort.c:2735 +#, c-format +msgid "" +"CONFLICT (file/directory): directory in the way of %s from %s; moving it to " +"%s instead." +msgstr "" +"КОНФЛИКТ (файл/директория): директория на мястото на „%s“ от „%s“, вместо " +"това се извършва преместване в „%s“." + +#: merge-ort.c:2808 +#, c-format +msgid "" +"CONFLICT (distinct types): %s had different types on each side; renamed %s " +"of them so each can be recorded somewhere." +msgstr "" +"КОНФЛИКТ (различни обекти): „%s“ е различен вид обект в двата варианта, " +"затова се извършва преименуване на %s, за да може и двата варианта да са " +"отразени." + +#: merge-ort.c:2812 +msgid "both" +msgstr "двата" + +#: merge-ort.c:2812 +msgid "one" +msgstr "единия" + +#: merge-ort.c:2907 merge-recursive.c:3052 +msgid "content" +msgstr "съдържание" + +#: merge-ort.c:2909 merge-recursive.c:3056 +msgid "add/add" +msgstr "добавяне/добавяне" + +#: merge-ort.c:2911 merge-recursive.c:3101 +msgid "submodule" +msgstr "ПОДМОДУЛ" + +#: merge-ort.c:2913 merge-recursive.c:3102 +#, c-format +msgid "CONFLICT (%s): Merge conflict in %s" +msgstr "КОНФЛИКТ (%s): Конфликт при сливане на „%s“" + +#: merge-ort.c:2938 +#, c-format +msgid "" +"CONFLICT (modify/delete): %s deleted in %s and modified in %s. Version %s " +"of %s left in tree." +msgstr "" +"КОНФЛИКТ (промяна/изтриване): „%s“ е изтрит в %s, а е променен в %s. Версия " +"%s на „%s“ е оставена в дървото." + +#. TRANSLATORS: The %s arguments are: 1) tree hash of a merge +#. base, and 2-3) the trees for the two trees we're merging. +#. +#: merge-ort.c:3406 +#, c-format +msgid "collecting merge info failed for trees %s, %s, %s" +msgstr "неуспешно събиране на информацията за сливането на „%s“, „%s“ и „%s“" + +#: merge-ort-wrappers.c:13 merge-recursive.c:3661 +#, c-format +msgid "" +"Your local changes to the following files would be overwritten by merge:\n" +" %s" +msgstr "" +"Сливането ще презапише локалните промени на тези файлове:\n" +" %s" + +#: merge-ort-wrappers.c:33 merge-recursive.c:3436 +#, c-format +msgid "Already up to date!" +msgstr "Вече е обновено!" + +#: merge-recursive.c:356 +msgid "(bad commit)\n" +msgstr "(лошо подаване)\n" + +#: merge-recursive.c:379 +#, c-format +msgid "add_cacheinfo failed for path '%s'; merge aborting." +msgstr "" +"неуспешно изпълнение на „add_cacheinfo“ за пътя „%s“. Сливането е " +"преустановено." + +#: merge-recursive.c:388 +#, c-format +msgid "add_cacheinfo failed to refresh for path '%s'; merge aborting." +msgstr "" +"неуспешно изпълнение на „add_cacheinfo“ за обновяването на пътя „%s“. " +"Сливането е преустановено." + +#: merge-recursive.c:874 +#, c-format +msgid "failed to create path '%s'%s" +msgstr "грешка при създаването на пътя „%s“%s" + +#: merge-recursive.c:885 +#, c-format +msgid "Removing %s to make room for subdirectory\n" +msgstr "Изтриване на „%s“, за да се освободи място за поддиректория\n" + +#: merge-recursive.c:899 merge-recursive.c:918 +msgid ": perhaps a D/F conflict?" +msgstr ": възможно е да има конфликт директория/файл." + +#: merge-recursive.c:908 +#, c-format +msgid "refusing to lose untracked file at '%s'" +msgstr "" +"преустановяване на действието, за да не се изтрие неследеният файл „%s“" + +#: merge-recursive.c:949 builtin/cat-file.c:41 +#, c-format +msgid "cannot read object %s '%s'" +msgstr "обектът „%s“ (%s) не може да бъде прочетен" + +#: merge-recursive.c:954 +#, c-format +msgid "blob expected for %s '%s'" +msgstr "обектът „%s“ (%s) се очакваше да е BLOB, а не е" + +#: merge-recursive.c:979 +#, c-format +msgid "failed to open '%s': %s" +msgstr "„%s“ не може да се отвори: %s" + +#: merge-recursive.c:990 +#, c-format +msgid "failed to symlink '%s': %s" +msgstr "неуспешно създаване на символната връзка „%s“: %s" + +#: merge-recursive.c:995 +#, c-format +msgid "do not know what to do with %06o %s '%s'" +msgstr "" +"не е ясно какво да се прави с обекта „%2$s“ (%3$s) с права за достъп „%1$06o“" + +#: merge-recursive.c:1213 merge-recursive.c:1225 +#, c-format +msgid "Fast-forwarding submodule %s to the following commit:" +msgstr "Превъртане на подмодула „%s“ до следното подаване:" + +#: merge-recursive.c:1216 merge-recursive.c:1228 +#, c-format +msgid "Fast-forwarding submodule %s" +msgstr "Превъртане на подмодула „%s“" + +#: merge-recursive.c:1251 +#, c-format +msgid "Failed to merge submodule %s (merge following commits not found)" +msgstr "" +"Неуспешно сливане на подмодула „%s“ (липсва сливането, което се предшества " +"от подаванията)" + +#: merge-recursive.c:1255 +#, c-format +msgid "Failed to merge submodule %s (not fast-forward)" +msgstr "Неуспешно сливане на подмодула „%s“ (не е превъртане)" + +#: merge-recursive.c:1256 +msgid "Found a possible merge resolution for the submodule:\n" +msgstr "" +"Открито е сливане, което може да решава проблема със сливането на " +"подмодула:\n" + +#: merge-recursive.c:1268 +#, c-format +msgid "Failed to merge submodule %s (multiple merges found)" +msgstr "Неуспешно сливане на подмодула „%s“ (открити са множество сливания)" + +#: merge-recursive.c:1402 +#, c-format +msgid "Error: Refusing to lose untracked file at %s; writing to %s instead." +msgstr "Грешка: за да не се изтрие неследеният файл „%s“, се записва в „%s“." + +#: merge-recursive.c:1474 +#, c-format +msgid "" +"CONFLICT (%s/delete): %s deleted in %s and %s in %s. Version %s of %s left " +"in tree." +msgstr "" +"КОНФЛИКТ (%s/изтриване): „%s“ е изтрит в %s, а „%s“ в %s. Версия %s на „%s“ " +"е оставена в дървото." + +#: merge-recursive.c:1479 +#, c-format +msgid "" +"CONFLICT (%s/delete): %s deleted in %s and %s to %s in %s. Version %s of %s " +"left in tree." +msgstr "" +"КОНФЛИКТ (%s/изтриване): „%s“ е изтрит в %s, а „%s“ е преименуван на „%s“ в " +"%s. Версия %s на „%s“ е оставена в дървото." + +#: merge-recursive.c:1486 +#, c-format +msgid "" +"CONFLICT (%s/delete): %s deleted in %s and %s in %s. Version %s of %s left " +"in tree at %s." +msgstr "" +"КОНФЛИКТ (%s/изтриване): „%s“ е изтрит в %s, а „%s“ в %s. Версия %s на „%s“ " +"е оставена в дървото: %s." + +#: merge-recursive.c:1491 +#, c-format +msgid "" +"CONFLICT (%s/delete): %s deleted in %s and %s to %s in %s. Version %s of %s " "left in tree at %s." msgstr "" "КОНФЛИКТ (%s/изтриване): „%s“ е изтрит в %s, а „%s“ е преименуван на „%s“ в " @@ -5020,25 +5309,6 @@ msgstr "" "постави „%s“, защото няколко нови директории поделят съдържанието на " "директория „%s“, като никоя не съдържа мнозинство от файловете ѝ." -#: merge-recursive.c:2100 -#, c-format -msgid "" -"CONFLICT (implicit dir rename): Existing file/dir at %s in the way of " -"implicit directory rename(s) putting the following path(s) there: %s." -msgstr "" -"КОНФЛИКТ (косвено преименуване на директория): следният файл или директория " -"„%s“ не позволяват косвеното преименуване на следния път/ища: %s." - -#: merge-recursive.c:2110 -#, c-format -msgid "" -"CONFLICT (implicit dir rename): Cannot map more than one path to %s; " -"implicit directory renames tried to put these paths there: %s" -msgstr "" -"КОНФЛИКТ (косвено преименуване на директория): повече от един път " -"съответства на „%s“. Косвено преименуване на директория води до поставянето " -"на тези пътища там: %s." - #: merge-recursive.c:2202 #, c-format msgid "" @@ -5048,15 +5318,6 @@ msgstr "" "КОНФЛИКТ (преименуване/преименуване): „%s“ е преименуван на „%s“ в клон " "„%s“, а „%s“ е преименуван на „%s“ в „%s“" -#: merge-recursive.c:2447 -#, c-format -msgid "" -"WARNING: Avoiding applying %s -> %s rename to %s, because %s itself was " -"renamed." -msgstr "" -"ПРЕДУПРЕЖДЕНИЕ: прескачане на преименуването на „%s“ на „%s“ в „%s“, защото " -"„%s“ също е с променено име." - #: merge-recursive.c:2973 #, c-format msgid "cannot read object %s" @@ -5075,70 +5336,17 @@ msgstr "промяна" msgid "modified" msgstr "променен" -#: merge-recursive.c:3052 -msgid "content" -msgstr "съдържание" - -#: merge-recursive.c:3056 -msgid "add/add" -msgstr "добавяне/добавяне" - #: merge-recursive.c:3079 #, c-format msgid "Skipped %s (merged same as existing)" msgstr "Прескачане на „%s“ (слетият резултат е идентичен със сегашния)" -#: merge-recursive.c:3101 -msgid "submodule" -msgstr "ПОДМОДУЛ" - -#: merge-recursive.c:3102 -#, c-format -msgid "CONFLICT (%s): Merge conflict in %s" -msgstr "КОНФЛИКТ (%s): Конфликт при сливане на „%s“" - #: merge-recursive.c:3132 #, c-format msgid "Adding as %s instead" msgstr "Добавяне като „%s“" -#: merge-recursive.c:3215 -#, c-format -msgid "" -"Path updated: %s added in %s inside a directory that was renamed in %s; " -"moving it to %s." -msgstr "" -"Обновен път: „%s“ е добавен в „%s“ в директория, която е преименувана в " -"„%s“. Обектът се мести в „%s“." - -#: merge-recursive.c:3218 -#, c-format -msgid "" -"CONFLICT (file location): %s added in %s inside a directory that was renamed " -"in %s, suggesting it should perhaps be moved to %s." -msgstr "" -"КОНФЛИКТ (места на файлове): „%s“ е добавен в „%s“ в директория, която е " -"преименувана в „%s“. Предложението е да преместите обекта в „%s“." - -#: merge-recursive.c:3222 -#, c-format -msgid "" -"Path updated: %s renamed to %s in %s, inside a directory that was renamed in " -"%s; moving it to %s." -msgstr "" -"Обновен път: „%s“ е преименуван на „%s“ в „%s“ в директория, която е " -"преименувана в „%s“. Обектът се мести в „%s“." - -#: merge-recursive.c:3225 -#, c-format -msgid "" -"CONFLICT (file location): %s renamed to %s in %s, inside a directory that " -"was renamed in %s, suggesting it should perhaps be moved to %s." -msgstr "" -"КОНФЛИКТ (места на файлове): „%s“ е преименуван на „%s“ в „%s“ в директория, " -"която е преименувана в „%s“. Предложението е да преместите обекта в „%s“." - -#: merge-recursive.c:3339 +#: merge-recursive.c:3339 #, c-format msgid "Removing %s" msgstr "Изтриване на „%s“" @@ -5173,27 +5381,28 @@ msgstr "КОНФЛИКТ (добавяне/добавяне): Конфликт msgid "merging of trees %s and %s failed" msgstr "неуспешно сливане на дърветата „%s“ и „%s“" -#: merge-recursive.c:3550 +#: merge-recursive.c:3539 msgid "Merging:" msgstr "Сливане:" -#: merge-recursive.c:3563 +#: merge-recursive.c:3552 #, c-format msgid "found %u common ancestor:" msgid_plural "found %u common ancestors:" msgstr[0] "открит е %u общ предшественик:" msgstr[1] "открити са %u общи предшественици:" -#: merge-recursive.c:3613 +#: merge-recursive.c:3602 msgid "merge returned no commit" msgstr "сливането не върна подаване" -#: merge-recursive.c:3769 +#: merge-recursive.c:3758 #, c-format msgid "Could not parse object '%s'" msgstr "Неуспешен анализ на обекта „%s“" -#: merge-recursive.c:3787 builtin/merge.c:711 builtin/merge.c:895 +#: merge-recursive.c:3776 builtin/merge.c:712 builtin/merge.c:896 +#: builtin/stash.c:471 msgid "Unable to write index." msgstr "Индексът не може да бъде прочетен" @@ -5201,125 +5410,115 @@ msgstr "Индексът не може да бъде прочетен" msgid "failed to read the cache" msgstr "кешът не може да бъде прочетен" -#: merge.c:109 rerere.c:720 builtin/am.c:1883 builtin/am.c:1917 -#: builtin/checkout.c:573 builtin/checkout.c:829 builtin/clone.c:817 +#: merge.c:109 rerere.c:704 builtin/am.c:1883 builtin/am.c:1917 +#: builtin/checkout.c:575 builtin/checkout.c:828 builtin/clone.c:817 #: builtin/stash.c:265 msgid "unable to write new index file" msgstr "неуспешно записване на новия индекс" -#: midx.c:80 +#: midx.c:62 +msgid "multi-pack-index OID fanout is of the wrong size" +msgstr "неправилен размер на откъс (OID fanout) на индекса за множество пакети" + +#: midx.c:93 #, c-format msgid "multi-pack-index file %s is too small" msgstr "файлът с индекса за множество пакети „%s“ е твърде малък" -#: midx.c:96 +#: midx.c:109 #, c-format msgid "multi-pack-index signature 0x%08x does not match signature 0x%08x" msgstr "отпечатъкът на индекса за множество пакети 0x%08x не съвпада с 0x%08x" -#: midx.c:101 +#: midx.c:114 #, c-format msgid "multi-pack-index version %d not recognized" msgstr "непозната версия на индекс за множество пакети — %d" -#: midx.c:106 +#: midx.c:119 #, c-format msgid "multi-pack-index hash version %u does not match version %u" msgstr "" "версията на контролната сума на индекса за множество пакети %u не съвпада с " "%u" -#: midx.c:123 -msgid "invalid chunk offset (too large)" -msgstr "неправилно (прекалено голямо) отместване на откъс" - -#: midx.c:147 -msgid "terminating multi-pack-index chunk id appears earlier than expected" -msgstr "" -"идентификаторът за краен откъс на индекс за множество пакети се явява по-" -"рано от очакваното" - -#: midx.c:160 +#: midx.c:136 msgid "multi-pack-index missing required pack-name chunk" msgstr "липсва откъс (pack-name) от индекс за множество пакети" -#: midx.c:162 +#: midx.c:138 msgid "multi-pack-index missing required OID fanout chunk" msgstr "липсва откъс (OID fanout) от индекс за множество пакети" -#: midx.c:164 +#: midx.c:140 msgid "multi-pack-index missing required OID lookup chunk" msgstr "липсва откъс (OID lookup) от индекс за множество пакети" -#: midx.c:166 +#: midx.c:142 msgid "multi-pack-index missing required object offsets chunk" msgstr "липсва откъс за отместванията на обекти от индекс за множество пакети" -#: midx.c:180 +#: midx.c:158 #, c-format msgid "multi-pack-index pack names out of order: '%s' before '%s'" msgstr "" "неправилна подредба на имената в индекс за множество пакети: „%s“ се появи " "преди „%s“" -#: midx.c:223 +#: midx.c:202 #, c-format msgid "bad pack-int-id: %u (%u total packs)" msgstr "" "неправилен идентификатор на пакет (pack-int-id): %u (от общо %u пакети)" -#: midx.c:273 +#: midx.c:252 msgid "multi-pack-index stores a 64-bit offset, but off_t is too small" msgstr "" "индексът за множество пакети съдържа 64-битови отмествания, но размерът на " "„off_t“ е недостатъчен" -#: midx.c:480 +#: midx.c:467 #, c-format msgid "failed to add packfile '%s'" msgstr "пакетният файл „%s“ не може да бъде добавен" -#: midx.c:486 +#: midx.c:473 #, c-format msgid "failed to open pack-index '%s'" msgstr "индексът за пакети „%s“ не може да бъде отворен" -#: midx.c:546 +#: midx.c:533 #, c-format msgid "failed to locate object %d in packfile" msgstr "обект %d в пакетния файл липсва" -#: midx.c:846 +#: midx.c:821 msgid "Adding packfiles to multi-pack-index" msgstr "Добавяне на пакетни файлове към индекс за множество пакети" -#: midx.c:879 +#: midx.c:855 #, c-format msgid "did not see pack-file %s to drop" msgstr "пакетният файл за триене „%s“ не може да се открие" -#: midx.c:931 +#: midx.c:904 msgid "no pack files to index." msgstr "няма пакетни файлове за индексиране" -#: midx.c:982 -msgid "Writing chunks to multi-pack-index" -msgstr "Запис на откъси към индекс за множество пакети" - -#: midx.c:1060 +#: midx.c:965 #, c-format msgid "failed to clear multi-pack-index at %s" msgstr "индексът за множество пакети не може да бъде изчистен при „%s“" -#: midx.c:1116 +#: midx.c:1021 msgid "multi-pack-index file exists, but failed to parse" msgstr "файлът с индекса за множество пакети, но не може да бъде анализиран" -#: midx.c:1124 +#: midx.c:1029 msgid "Looking for referenced packfiles" msgstr "Търсене на указаните пакетни файлове" -#: midx.c:1139 +#: midx.c:1044 #, c-format msgid "" "oid fanout out of order: fanout[%d] = % > % = fanout[%d]" @@ -5327,72 +5526,72 @@ msgstr "" "неправилна подредба на откъси (OID fanout): fanout[%d] = % > " "% = fanout[%d]" -#: midx.c:1144 +#: midx.c:1049 msgid "the midx contains no oid" msgstr "във файла с индекса за множество пакети няма идентификатори на обекти" -#: midx.c:1153 +#: midx.c:1058 msgid "Verifying OID order in multi-pack-index" msgstr "" "Проверка на подредбата на идентификатори на обекти във файл с индекс към " "множество пакетни файлове" -#: midx.c:1162 +#: midx.c:1067 #, c-format msgid "oid lookup out of order: oid[%d] = %s >= %s = oid[%d]" msgstr "" "неправилна подредба на откъси (OID lookup): oid[%d] = %s >= %s = oid[%d]" -#: midx.c:1182 +#: midx.c:1087 msgid "Sorting objects by packfile" msgstr "Подредба на обектите по пакетни файлове" -#: midx.c:1189 +#: midx.c:1094 msgid "Verifying object offsets" msgstr "Проверка на отместването на обекти" -#: midx.c:1205 +#: midx.c:1110 #, c-format msgid "failed to load pack entry for oid[%d] = %s" msgstr "записът в пакета за обекта oid[%d] = %s не може да бъде зареден" -#: midx.c:1211 +#: midx.c:1116 #, c-format msgid "failed to load pack-index for packfile %s" msgstr "индексът на пакета „%s“ не може да бъде зареден" -#: midx.c:1220 +#: midx.c:1125 #, c-format msgid "incorrect object offset for oid[%d] = %s: % != %" msgstr "неправилно отместване на обект за oid[%d] = %s: % != %" -#: midx.c:1245 +#: midx.c:1150 msgid "Counting referenced objects" msgstr "Преброяване на свързаните обекти" -#: midx.c:1255 +#: midx.c:1160 msgid "Finding and deleting unreferenced packfiles" msgstr "Търсене и изтриване на несвързаните пакетни файлове" -#: midx.c:1446 +#: midx.c:1351 msgid "could not start pack-objects" msgstr "командата „pack-objects“ не може да бъде стартирана" -#: midx.c:1466 +#: midx.c:1371 msgid "could not finish pack-objects" msgstr "командата „pack-objects“ не може да бъде завършена" -#: name-hash.c:537 +#: name-hash.c:538 #, c-format msgid "unable to create lazy_dir thread: %s" msgstr "не може да се създаде нишка за директории: %s" -#: name-hash.c:559 +#: name-hash.c:560 #, c-format msgid "unable to create lazy_name thread: %s" msgstr "не може да се създаде нишка за имена: %s" -#: name-hash.c:565 +#: name-hash.c:566 #, c-format msgid "unable to join lazy_name thread: %s" msgstr "не може да се изчака нишка за имена: %s" @@ -5445,618 +5644,1035 @@ msgstr "" msgid "Bad %s value: '%s'" msgstr "Зададена е лоша стойност на променливата „%s“: „%s“" -#: object.c:53 +#: object-file.c:480 #, c-format -msgid "invalid object type \"%s\"" -msgstr "неправилен вид обект: „%s“" +msgid "object directory %s does not exist; check .git/objects/info/alternates" +msgstr "" +"директорията за обекти „%s“ не съществува, проверете „.git/objects/info/" +"alternates“" -#: object.c:173 +#: object-file.c:531 #, c-format -msgid "object %s is a %s, not a %s" -msgstr "обектът „%s“ е %s, а не %s" +msgid "unable to normalize alternate object path: %s" +msgstr "алтернативният път към обекти не може да бъде нормализиран: „%s“" -#: object.c:233 +#: object-file.c:603 #, c-format -msgid "object %s has unknown type id %d" -msgstr "обектът „%s“ е непознат вид: %d" +msgid "%s: ignoring alternate object stores, nesting too deep" +msgstr "" +"%s: алтернативните хранилища за обекти се пренебрегват поради прекалено " +"дълбоко влагане" -#: object.c:246 +#: object-file.c:610 #, c-format -msgid "unable to parse object: %s" -msgstr "обектът „%s“ не може да бъде анализиран" +msgid "unable to normalize object directory: %s" +msgstr "директорията за обекти „%s“ не може да бъде нормализирана" -#: object.c:266 object.c:278 -#, c-format -msgid "hash mismatch %s" -msgstr "разлика в контролната сума: „%s“" +#: object-file.c:653 +msgid "unable to fdopen alternates lockfile" +msgstr "заключващият файл за алтернативите не може да се отвори с „fdopen“" -#: pack-bitmap.c:815 pack-bitmap.c:821 builtin/pack-objects.c:2216 -#, c-format -msgid "unable to get size of %s" -msgstr "размерът на „%s“ не може да бъде получен" +#: object-file.c:671 +msgid "unable to read alternates file" +msgstr "файлът с алтернативите не може да бъде прочетен" -#: packfile.c:615 -msgid "offset before end of packfile (broken .idx?)" -msgstr "" -"отместване преди края на пакетния файл (възможно е индексът да е повреден)" +#: object-file.c:678 +msgid "unable to move new alternates file into place" +msgstr "новият файл с алтернативите не може да бъде преместен на мястото му" -#: packfile.c:1907 +#: object-file.c:713 #, c-format -msgid "offset before start of pack index for %s (corrupt index?)" -msgstr "" -"отместване преди началото на индекса на пакетния файл „%s“ (възможно е " -"индексът да е повреден)" +msgid "path '%s' does not exist" +msgstr "пътят „%s“ не съществува." -#: packfile.c:1911 +#: object-file.c:734 #, c-format -msgid "offset beyond end of pack index for %s (truncated index?)" -msgstr "" -"отместване преди края на индекса на пакетния файл „%s“ (възможно е индексът " -"да е отрязан)" +msgid "reference repository '%s' as a linked checkout is not supported yet." +msgstr "все още не се поддържа еталонно хранилище „%s“ като свързано." -#: parse-options-cb.c:20 parse-options-cb.c:24 +#: object-file.c:740 #, c-format -msgid "option `%s' expects a numerical value" -msgstr "опцията „%s“ очаква число за аргумент" +msgid "reference repository '%s' is not a local repository." +msgstr "еталонното хранилище „%s“ не е локално" -#: parse-options-cb.c:41 +#: object-file.c:746 #, c-format -msgid "malformed expiration date '%s'" -msgstr "неправилна дата на срок: „%s“" +msgid "reference repository '%s' is shallow" +msgstr "еталонното хранилище „%s“ е плитко" -#: parse-options-cb.c:54 +#: object-file.c:754 #, c-format -msgid "option `%s' expects \"always\", \"auto\", or \"never\"" -msgstr "" -"опцията „%s“ изисква някоя от стойностите: „always“ (винаги), " -"„auto“ (автоматично) или „never“ (никога)" +msgid "reference repository '%s' is grafted" +msgstr "еталонното хранилище „%s“ е с присаждане" -#: parse-options-cb.c:132 parse-options-cb.c:149 +#: object-file.c:814 #, c-format -msgid "malformed object name '%s'" -msgstr "неправилно име на обект „%s“" +msgid "invalid line while parsing alternate refs: %s" +msgstr "неправилен ред при анализа на алтернативните указатели: „%s“" -#: parse-options.c:38 +#: object-file.c:964 #, c-format -msgid "%s requires a value" -msgstr "опцията „%s“ изисква аргумент" +msgid "attempting to mmap % over limit %" +msgstr "" +"неуспешен опит за „mmap“ %, което е над позволеното %" -#: parse-options.c:73 -#, c-format -msgid "%s is incompatible with %s" -msgstr "опциите „%s“ и „%s“ са несъвместими" +#: object-file.c:985 +msgid "mmap failed" +msgstr "неуспешно изпълнение на „mmap“" -#: parse-options.c:78 +#: object-file.c:1149 #, c-format -msgid "%s : incompatible with something else" -msgstr "опцията „%s“ е несъвместима с нещо" +msgid "object file %s is empty" +msgstr "файлът с обектите „%s“ е празен" -#: parse-options.c:92 parse-options.c:96 parse-options.c:317 +#: object-file.c:1284 object-file.c:2477 #, c-format -msgid "%s takes no value" -msgstr "опцията „%s“ не приема аргументи" +msgid "corrupt loose object '%s'" +msgstr "непакетираният обект „%s“ е повреден" -#: parse-options.c:94 +#: object-file.c:1286 object-file.c:2481 #, c-format -msgid "%s isn't available" -msgstr "опцията „%s“ не е налична" +msgid "garbage at end of loose object '%s'" +msgstr "грешни данни в края на непакетирания обект „%s“" -#: parse-options.c:217 +#: object-file.c:1328 +msgid "invalid object type" +msgstr "неправилен вид обект" + +#: object-file.c:1412 #, c-format -msgid "%s expects a non-negative integer value with an optional k/m/g suffix" +msgid "unable to unpack %s header with --allow-unknown-type" msgstr "" -"„%s“ очаква неотрицателно цяло число, евентуално със суфикс „k“/„m“/„g“" +"заглавната част „%s“ не може да се разпакетира с опцията „--allow-unknown-" +"type“" -#: parse-options.c:386 +#: object-file.c:1415 #, c-format -msgid "ambiguous option: %s (could be --%s%s or --%s%s)" -msgstr "нееднозначна опция: „%s“ (може да е „--%s%s“ или „--%s%s“)" +msgid "unable to unpack %s header" +msgstr "заглавната част на „%s“ не може да бъде разпакетирана" -#: parse-options.c:420 parse-options.c:428 +#: object-file.c:1421 #, c-format -msgid "did you mean `--%s` (with two dashes)?" -msgstr "„--%s“ (с 2 тирета) ли имахте предвид?" +msgid "unable to parse %s header with --allow-unknown-type" +msgstr "" +"заглавната част „%s“ не може да се анализира с опцията „--allow-unknown-type“" -#: parse-options.c:666 parse-options.c:971 +#: object-file.c:1424 #, c-format -msgid "alias of --%s" -msgstr "псевдоним на „--%s“" +msgid "unable to parse %s header" +msgstr "заглавната част на „%s“ не може да бъде анализирана" -#: parse-options.c:862 +#: object-file.c:1651 #, c-format -msgid "unknown option `%s'" -msgstr "непозната опция: „%s“" +msgid "failed to read object %s" +msgstr "обектът „%s“ не може да бъде прочетен" -#: parse-options.c:864 +#: object-file.c:1655 #, c-format -msgid "unknown switch `%c'" -msgstr "непознат флаг „%c“" +msgid "replacement %s not found for %s" +msgstr "заместителят „%s“ на „%s“ не може да бъде открит" -#: parse-options.c:866 +#: object-file.c:1659 #, c-format -msgid "unknown non-ascii option in string: `%s'" -msgstr "непозната стойност извън „ascii“ в низа: „%s“" - -#: parse-options.c:890 -msgid "..." -msgstr "…" +msgid "loose object %s (stored in %s) is corrupt" +msgstr "непакетираният обект „%s“ (в „%s“) е повреден" -#: parse-options.c:909 +#: object-file.c:1663 #, c-format -msgid "usage: %s" -msgstr "употреба: %s" +msgid "packed object %s (stored in %s) is corrupt" +msgstr "пакетираният обект „%s“ (в „%s“) е повреден" -#. TRANSLATORS: the colon here should align with the -#. one in "usage: %s" translation. -#. -#: parse-options.c:915 +#: object-file.c:1768 #, c-format -msgid " or: %s" -msgstr " или: %s" +msgid "unable to write file %s" +msgstr "файлът „%s“ не може да бъде записан" -#: parse-options.c:918 +#: object-file.c:1775 #, c-format -msgid " %s" -msgstr " %s" +msgid "unable to set permission to '%s'" +msgstr "правата за достъп до „%s“ не могат да бъдат зададени" -#: parse-options.c:957 -msgid "-NUM" -msgstr "-ЧИСЛО" +#: object-file.c:1782 +msgid "file write error" +msgstr "грешка при запис на файл" -#: path.c:915 -#, c-format -msgid "Could not make %s writable by group" -msgstr "Не могат да се дадат права за запис в директорията „%s“ на групата" +#: object-file.c:1802 +msgid "error when closing loose object file" +msgstr "грешка при затварянето на файла с непакетиран обект" -#: pathspec.c:130 -msgid "Escape character '\\' not allowed as last character in attr value" +#: object-file.c:1867 +#, c-format +msgid "insufficient permission for adding an object to repository database %s" msgstr "" -"Екраниращият знак „\\“не може да е последен знак в стойността на атрибут" +"няма права за добавяне на обект към базата от данни на хранилището „%s“" -#: pathspec.c:148 -msgid "Only one 'attr:' specification is allowed." -msgstr "Позволено е само едно указване на „attr:“." +#: object-file.c:1869 +msgid "unable to create temporary file" +msgstr "не може да бъде създаден временен файл" -#: pathspec.c:151 -msgid "attr spec must not be empty" -msgstr "„attr:“ трябва да указва стойност" +#: object-file.c:1893 +msgid "unable to write loose object file" +msgstr "грешка при записа на файла с непакетиран обект" -#: pathspec.c:194 +#: object-file.c:1899 #, c-format -msgid "invalid attribute name %s" -msgstr "неправилно име на атрибут: „%s“" - -#: pathspec.c:259 -msgid "global 'glob' and 'noglob' pathspec settings are incompatible" -msgstr "глобалните настройки за пътища „glob“ и „noglob“ са несъвместими" +msgid "unable to deflate new object %s (%d)" +msgstr "новият обект „%s“ не може да се компресира с „deflate“: %d" -#: pathspec.c:266 -msgid "" -"global 'literal' pathspec setting is incompatible with all other global " -"pathspec settings" -msgstr "" -"глобалната настройка за дословни пътища „literal“ е несъвместима с всички " -"други глобални настройки за пътища" +#: object-file.c:1903 +#, c-format +msgid "deflateEnd on object %s failed (%d)" +msgstr "неуспешно приключване на „deflate“ върху „%s“: %d" -#: pathspec.c:306 -msgid "invalid parameter for pathspec magic 'prefix'" -msgstr "неправилен параметър за опцията за магически пътища „prefix“" +#: object-file.c:1907 +#, c-format +msgid "confused by unstable object source data for %s" +msgstr "грешка поради нестабилния източник данни за обектите „%s“" -#: pathspec.c:327 +#: object-file.c:1917 builtin/pack-objects.c:1097 #, c-format -msgid "Invalid pathspec magic '%.*s' in '%s'" -msgstr "Неправилна стойност за опцията за магически пътища „%.*s“ в „%s“" +msgid "failed utime() on %s" +msgstr "неуспешно задаване на време на достъп/създаване чрез „utime“ на „%s“" -#: pathspec.c:332 +#: object-file.c:1994 #, c-format -msgid "Missing ')' at the end of pathspec magic in '%s'" -msgstr "Знакът „)“ липсва в опцията за магически пътища в „%s“" +msgid "cannot read object for %s" +msgstr "обектът за „%s“ не може да се прочете" -#: pathspec.c:370 +#: object-file.c:2045 +msgid "corrupt commit" +msgstr "повредено подаване" + +#: object-file.c:2053 +msgid "corrupt tag" +msgstr "повреден етикет" + +#: object-file.c:2153 #, c-format -msgid "Unimplemented pathspec magic '%c' in '%s'" -msgstr "Магическите пътища „%c“ са без реализация за „%s“" +msgid "read error while indexing %s" +msgstr "грешка при четене по време на индексиране на „%s“" -#: pathspec.c:429 +#: object-file.c:2156 #, c-format -msgid "%s: 'literal' and 'glob' are incompatible" -msgstr "%s: опциите „literal“ и „glob“ са несъвместими" +msgid "short read while indexing %s" +msgstr "непълно прочитане по време на индексиране на „%s“" -#: pathspec.c:445 +#: object-file.c:2229 object-file.c:2239 #, c-format -msgid "%s: '%s' is outside repository at '%s'" -msgstr "%s: „%s“ е извън хранилището при „%s“" +msgid "%s: failed to insert into database" +msgstr "„%s“ не може да се вмъкне в базата от данни" -#: pathspec.c:521 +#: object-file.c:2245 #, c-format -msgid "'%s' (mnemonic: '%c')" -msgstr "„%s“ (клавиш: „%c“)" +msgid "%s: unsupported file type" +msgstr "неподдържан вид файл: „%s“" -#: pathspec.c:531 +#: object-file.c:2269 #, c-format -msgid "%s: pathspec magic not supported by this command: %s" -msgstr "%s: магическите пътища не се поддържат от командата „%s“" +msgid "%s is not a valid object" +msgstr "„%s“ е неправилен обект" -#: pathspec.c:598 +#: object-file.c:2271 #, c-format -msgid "pathspec '%s' is beyond a symbolic link" -msgstr "пътят „%s“ е след символна връзка" +msgid "%s is not a valid '%s' object" +msgstr "„%s“ е неправилен обект от вид „%s“" -#: pathspec.c:643 +#: object-file.c:2298 builtin/index-pack.c:192 #, c-format -msgid "line is badly quoted: %s" -msgstr "неправилно цитиран ред: %s" +msgid "unable to open %s" +msgstr "обектът „%s“ не може да бъде отворен" -#: pkt-line.c:92 -msgid "unable to write flush packet" -msgstr "пакетът за изчистване на буферите не може да се запише" +#: object-file.c:2488 object-file.c:2541 +#, c-format +msgid "hash mismatch for %s (expected %s)" +msgstr "неправилна контролна сума за „%s“ (трябва да е %s)" -#: pkt-line.c:99 -msgid "unable to write delim packet" -msgstr "разделящият пакет не може да се запише" +#: object-file.c:2512 +#, c-format +msgid "unable to mmap %s" +msgstr "неуспешно изпълнение на „mmap“ върху „%s“" -#: pkt-line.c:106 -msgid "unable to write stateless separator packet" -msgstr "разделящият пакет без запазване на състояние не може да се запише" +#: object-file.c:2517 +#, c-format +msgid "unable to unpack header of %s" +msgstr "заглавната част на „%s“ не може да бъде разпакетирана" -#: pkt-line.c:113 -msgid "flush packet write failed" -msgstr "неуспешно изчистване на буферите при запис на пакет" +#: object-file.c:2523 +#, c-format +msgid "unable to parse header of %s" +msgstr "заглавната част на „%s“ не може да бъде анализирана" -#: pkt-line.c:153 pkt-line.c:239 -msgid "protocol error: impossibly long line" -msgstr "протоколна грешка: прекалено дълъг ред" +#: object-file.c:2534 +#, c-format +msgid "unable to unpack contents of %s" +msgstr "съдържанието на „%s“ не може да бъде разпакетирано" -#: pkt-line.c:169 pkt-line.c:171 -msgid "packet write with format failed" -msgstr "неуспешен запис на пакет с формат" +#: object-name.c:486 +#, c-format +msgid "short object ID %s is ambiguous" +msgstr "късият идентификатор на обект „%s“ не е еднозначен" -#: pkt-line.c:203 -msgid "packet write failed - data exceeds max packet size" -msgstr "" -"неуспешен запис на пакетен файл — данните надвишават максималният размер на " -"пакет" +#: object-name.c:497 +msgid "The candidates are:" +msgstr "Възможностите са:" -#: pkt-line.c:210 pkt-line.c:217 -msgid "packet write failed" -msgstr "неуспешен запис на пакет" +#: object-name.c:796 +msgid "" +"Git normally never creates a ref that ends with 40 hex characters\n" +"because it will be ignored when you just specify 40-hex. These refs\n" +"may be created by mistake. For example,\n" +"\n" +" git switch -c $br $(git rev-parse ...)\n" +"\n" +"where \"$br\" is somehow empty and a 40-hex ref is created. Please\n" +"examine these refs and maybe delete them. Turn this message off by\n" +"running \"git config advice.objectNameWarning false\"" +msgstr "" +"При нормална работа Git никога не създава указатели, които завършват\n" +"с 40 шестнадесетични знака, защото стандартно те ще бъдат прескачани.\n" +"Възможно е такива указатели да са създадени случайно. Например:\n" +"\n" +" git switch -c $BRANCH $(git rev-parse …)\n" +"\n" +"където стойността на променливата на средата BRANCH е празна, при което\n" +"се създава подобен указател. Прегледайте тези указатели и ги изтрийте.\n" +"За да изключите това съобщение, изпълнете:\n" +"\n" +" git config advice.objectNameWarning false" -#: pkt-line.c:302 -msgid "read error" -msgstr "грешка при четене" +#: object-name.c:916 +#, c-format +msgid "log for '%.*s' only goes back to %s" +msgstr "журналът за „%.*s“ стига само до „%s“" -#: pkt-line.c:310 -msgid "the remote end hung up unexpectedly" -msgstr "отдалеченото хранилище неочаквано прекъсна връзката" +#: object-name.c:924 +#, c-format +msgid "log for '%.*s' only has %d entries" +msgstr "журналът за „%.*s“ съдържа само %d записа" -#: pkt-line.c:338 +#: object-name.c:1702 #, c-format -msgid "protocol error: bad line length character: %.4s" -msgstr "протоколна грешка: неправилeн знак за дължина на ред: %.4s" +msgid "path '%s' exists on disk, but not in '%.*s'" +msgstr "пътят „%s“ съществува на диска, но не и в „%.*s“" -#: pkt-line.c:352 pkt-line.c:357 +#: object-name.c:1708 #, c-format -msgid "protocol error: bad line length %d" -msgstr "протоколна грешка: неправилна дължина на ред: %d" +msgid "" +"path '%s' exists, but not '%s'\n" +"hint: Did you mean '%.*s:%s' aka '%.*s:./%s'?" +msgstr "" +"пътят „%s“ съществува на диска, но не е в „%s“\n" +"Пробвайте с „%.*s:%s“, което е същото като „%.*s:./%s“." -#: pkt-line.c:373 sideband.c:165 +#: object-name.c:1717 #, c-format -msgid "remote error: %s" -msgstr "отдалечена грешка: %s" +msgid "path '%s' does not exist in '%.*s'" +msgstr "пътят „%s“ не съществува в „%.*s“" -#: preload-index.c:119 -msgid "Refreshing index" -msgstr "Обновяване на индекса" +#: object-name.c:1745 +#, c-format +msgid "" +"path '%s' is in the index, but not at stage %d\n" +"hint: Did you mean ':%d:%s'?" +msgstr "" +"пътят „%s“ е в индекса, но не версия %d\n" +"Пробвайте с „%d:%s“." -#: preload-index.c:138 +#: object-name.c:1761 #, c-format -msgid "unable to create threaded lstat: %s" -msgstr "не може да се създаде нишка за изпълнението на „lstat“: %s" +msgid "" +"path '%s' is in the index, but not '%s'\n" +"hint: Did you mean ':%d:%s' aka ':%d:./%s'?" +msgstr "" +"пътят „%s“ е в индекса, но не в „%s“\n" +"Пробвайте с „%d:%s“, което е същото като „%d:./%s“." -#: pretty.c:983 -msgid "unable to parse --pretty format" -msgstr "аргументът към опцията „--pretty“ не може да се анализира" +#: object-name.c:1769 +#, c-format +msgid "path '%s' exists on disk, but not in the index" +msgstr "пътят „%s“ съществува на диска, но не е в индекса" -#: promisor-remote.c:30 -msgid "promisor-remote: unable to fork off fetch subprocess" -msgstr "хранилище-гарант: неуспешно създаване на процес за доставяне" +#: object-name.c:1771 +#, c-format +msgid "path '%s' does not exist (neither on disk nor in the index)" +msgstr "пътят „%s“ не съществува нито на диска, нито в индекса" -#: promisor-remote.c:35 promisor-remote.c:37 -msgid "promisor-remote: could not write to fetch subprocess" -msgstr "хранилище-гарант: не може да се пише към процеса за доставяне" +#: object-name.c:1784 +msgid "relative path syntax can't be used outside working tree" +msgstr "относителен път не може да се ползва извън работното дърво" -#: promisor-remote.c:41 -msgid "promisor-remote: could not close stdin to fetch subprocess" -msgstr "" -"хранилище-гарант: стандартният вход на процеса за доставяне не може да се " -"затвори" +#: object-name.c:1922 +#, c-format +msgid "invalid object name '%.*s'." +msgstr "неправилно име на обект: „%.*s“" -#: promisor-remote.c:53 +#: object.c:53 #, c-format -msgid "promisor remote name cannot begin with '/': %s" -msgstr "" -"името отдалеченото хранилище-гарант не може за започва със знака „/“: %s" +msgid "invalid object type \"%s\"" +msgstr "неправилен вид обект: „%s“" -#: prune-packed.c:35 -msgid "Removing duplicate objects" -msgstr "Изтриване на повтарящите се обекти" +#: object.c:173 +#, c-format +msgid "object %s is a %s, not a %s" +msgstr "обектът „%s“ е %s, а не %s" -#: range-diff.c:77 -msgid "could not start `log`" -msgstr "командата за журнала с подавания „log“ не може да се стартира" +#: object.c:233 +#, c-format +msgid "object %s has unknown type id %d" +msgstr "обектът „%s“ е непознат вид: %d" -#: range-diff.c:79 -msgid "could not read `log` output" -msgstr "" -"изходът от командата за журнала с подавания „log“ не може да се прочете" +#: object.c:246 +#, c-format +msgid "unable to parse object: %s" +msgstr "обектът „%s“ не може да бъде анализиран" -#: range-diff.c:98 sequencer.c:5310 +#: object.c:266 object.c:278 #, c-format -msgid "could not parse commit '%s'" -msgstr "подаването „%s“ не може да бъде анализирано" +msgid "hash mismatch %s" +msgstr "разлика в контролната сума: „%s“" -#: range-diff.c:112 +#: pack-bitmap.c:843 pack-bitmap.c:849 builtin/pack-objects.c:2226 #, c-format -msgid "" -"could not parse first line of `log` output: did not start with 'commit ': " -"'%s'" -msgstr "" -"първият ред от изхода на командата „log“ не може да се анализира, защото не " -"започва с „commit “: „%s“" +msgid "unable to get size of %s" +msgstr "размерът на „%s“ не може да бъде получен" -#: range-diff.c:137 +#: pack-bitmap.c:1489 builtin/rev-list.c:92 #, c-format -msgid "could not parse git header '%.*s'" -msgstr "заглавната част на git „%.*s“ не може да се анализира" +msgid "unable to get disk usage of %s" +msgstr "използваното място за „%s“ не може да бъде получено" -#: range-diff.c:299 -msgid "failed to generate diff" -msgstr "неуспешно търсене на разлика" +#: pack-revindex.c:220 +#, c-format +msgid "reverse-index file %s is too small" +msgstr "файлът с обратния индекс „%s“ е твърде малък" -#: range-diff.c:532 range-diff.c:534 +#: pack-revindex.c:225 #, c-format -msgid "could not parse log for '%s'" -msgstr "журналът с подаванията на „%s“ не може да бъде анализиран" +msgid "reverse-index file %s is corrupt" +msgstr "файлът с обратния индекс „%s“ е повреден" -#: read-cache.c:682 +#: pack-revindex.c:233 #, c-format -msgid "will not add file alias '%s' ('%s' already exists in index)" -msgstr "" -"няма да бъде добавен псевдоним за файл „%s“ („%s“ вече съществува в индекса)" +msgid "reverse-index file %s has unknown signature" +msgstr "непознат подпис за файла на обратния индекс „%s“" -#: read-cache.c:698 -msgid "cannot create an empty blob in the object database" -msgstr "в базата от данни за обектите не може да се създаде празен обект-BLOB" +#: pack-revindex.c:237 +#, c-format +msgid "reverse-index file %s has unsupported version %" +msgstr "версия %2$ на файла с обратен индекс „%1$s“ не се поддържа" -#: read-cache.c:720 +#: pack-revindex.c:242 #, c-format -msgid "%s: can only add regular files, symbolic links or git-directories" +msgid "reverse-index file %s has unsupported hash id %" msgstr "" -"%s: може да добавяте само обикновени файлове, символни връзки и директории " -"на git" +"идентификатор на контролна сума %2$ на файла с обратен индекс „%1$s“ " +"не се поддържа" -#: read-cache.c:725 +#: pack-write.c:236 +msgid "cannot both write and verify reverse index" +msgstr "обратният индекс не може едновременно да се записва и да се проверява" + +#: pack-write.c:257 #, c-format -msgid "'%s' does not have a commit checked out" -msgstr "не е изтеглено подаване в „%s“" +msgid "could not stat: %s" +msgstr "не може да се получи информация чрез „stat“ за „%s“" -#: read-cache.c:777 +#: pack-write.c:269 #, c-format -msgid "unable to index file '%s'" -msgstr "файлът „%s“ не може да бъде индексиран" +msgid "failed to make %s readable" +msgstr "не може да се дадат права за четене на „%s“" -#: read-cache.c:796 +#: pack-write.c:508 #, c-format -msgid "unable to add '%s' to index" -msgstr "„%s“ не може да се добави в индекса" +msgid "could not write '%s' promisor file" +msgstr "гарантиращият файл „%s“ не може да се запише" -#: read-cache.c:807 +#: packfile.c:625 +msgid "offset before end of packfile (broken .idx?)" +msgstr "" +"отместване преди края на пакетния файл (възможно е индексът да е повреден)" + +#: packfile.c:1934 #, c-format -msgid "unable to stat '%s'" -msgstr "„stat“ не може да се изпълни върху „%s“" +msgid "offset before start of pack index for %s (corrupt index?)" +msgstr "" +"отместване преди началото на индекса на пакетния файл „%s“ (възможно е " +"индексът да е повреден)" -#: read-cache.c:1318 +#: packfile.c:1938 #, c-format -msgid "'%s' appears as both a file and as a directory" -msgstr "„%s“ съществува и като файл, и като директория" +msgid "offset beyond end of pack index for %s (truncated index?)" +msgstr "" +"отместване преди края на индекса на пакетния файл „%s“ (възможно е индексът " +"да е отрязан)" -#: read-cache.c:1524 -msgid "Refresh index" -msgstr "Обновяване на индекса" +#: parse-options-cb.c:20 parse-options-cb.c:24 +#, c-format +msgid "option `%s' expects a numerical value" +msgstr "опцията „%s“ очаква число за аргумент" -#: read-cache.c:1639 +#: parse-options-cb.c:41 #, c-format -msgid "" -"index.version set, but the value is invalid.\n" -"Using version %i" -msgstr "" -"Зададена е неправилна стойност на настройката „index.version“.\n" -"Ще се ползва версия %i" +msgid "malformed expiration date '%s'" +msgstr "неправилна дата на срок: „%s“" -#: read-cache.c:1649 +#: parse-options-cb.c:54 #, c-format -msgid "" -"GIT_INDEX_VERSION set, but the value is invalid.\n" -"Using version %i" +msgid "option `%s' expects \"always\", \"auto\", or \"never\"" msgstr "" -"Зададена е неправилна стойност на променливата на средата " -"„GIT_INDEX_VERSION“.\n" -"Ще се ползва версия %i" +"опцията „%s“ изисква някоя от стойностите: „always“ (винаги), " +"„auto“ (автоматично) или „never“ (никога)" -#: read-cache.c:1705 +#: parse-options-cb.c:132 parse-options-cb.c:149 #, c-format -msgid "bad signature 0x%08x" -msgstr "неправилен подпис: „0x%08x“" +msgid "malformed object name '%s'" +msgstr "неправилно име на обект „%s“" -#: read-cache.c:1708 +#: parse-options.c:38 #, c-format -msgid "bad index version %d" -msgstr "неправилна версия на индекса %d" - -#: read-cache.c:1717 -msgid "bad index file sha1 signature" -msgstr "неправилен подпис за контролна сума по SHA1 на файла на индекса" +msgid "%s requires a value" +msgstr "опцията „%s“ изисква аргумент" -#: read-cache.c:1747 +#: parse-options.c:73 #, c-format -msgid "index uses %.4s extension, which we do not understand" -msgstr "" -"индексът ползва разширение „%.4s“, което не се поддържа от тази версия на git" +msgid "%s is incompatible with %s" +msgstr "опциите „%s“ и „%s“ са несъвместими" -#: read-cache.c:1749 +#: parse-options.c:78 #, c-format -msgid "ignoring %.4s extension" -msgstr "игнориране на разширението „%.4s“" +msgid "%s : incompatible with something else" +msgstr "опцията „%s“ е несъвместима с нещо" -#: read-cache.c:1786 +#: parse-options.c:92 parse-options.c:96 parse-options.c:317 #, c-format -msgid "unknown index entry format 0x%08x" -msgstr "непознат формат на запис в индекса: „0x%08x“" +msgid "%s takes no value" +msgstr "опцията „%s“ не приема аргументи" -#: read-cache.c:1802 +#: parse-options.c:94 #, c-format -msgid "malformed name field in the index, near path '%s'" -msgstr "неправилно име на поле в индекса близо до пътя „%s“" +msgid "%s isn't available" +msgstr "опцията „%s“ не е налична" -#: read-cache.c:1859 -msgid "unordered stage entries in index" -msgstr "неподредени записи в индекса" +#: parse-options.c:217 +#, c-format +msgid "%s expects a non-negative integer value with an optional k/m/g suffix" +msgstr "" +"„%s“ очаква неотрицателно цяло число, евентуално със суфикс „k“/„m“/„g“" -#: read-cache.c:1862 +#: parse-options.c:386 #, c-format -msgid "multiple stage entries for merged file '%s'" -msgstr "множество записи за слетия файл „%s“" +msgid "ambiguous option: %s (could be --%s%s or --%s%s)" +msgstr "нееднозначна опция: „%s“ (може да е „--%s%s“ или „--%s%s“)" -#: read-cache.c:1865 +#: parse-options.c:420 parse-options.c:428 #, c-format -msgid "unordered stage entries for '%s'" -msgstr "неподредени записи за „%s“" +msgid "did you mean `--%s` (with two dashes)?" +msgstr "„--%s“ (с 2 тирета) ли имахте предвид?" -#: read-cache.c:1971 read-cache.c:2262 rerere.c:565 rerere.c:599 rerere.c:1111 -#: submodule.c:1628 builtin/add.c:538 builtin/check-ignore.c:181 -#: builtin/checkout.c:502 builtin/checkout.c:688 builtin/clean.c:991 -#: builtin/commit.c:364 builtin/diff-tree.c:122 builtin/grep.c:507 -#: builtin/mv.c:146 builtin/reset.c:247 builtin/rm.c:290 -#: builtin/submodule--helper.c:332 -msgid "index file corrupt" -msgstr "файлът с индекса е повреден" +#: parse-options.c:666 parse-options.c:971 +#, c-format +msgid "alias of --%s" +msgstr "псевдоним на „--%s“" -#: read-cache.c:2115 +#: parse-options.c:862 #, c-format -msgid "unable to create load_cache_entries thread: %s" -msgstr "не може да се създаде нишка за зареждане на обектите от кеша: %s" +msgid "unknown option `%s'" +msgstr "непозната опция: „%s“" -#: read-cache.c:2128 +#: parse-options.c:864 #, c-format -msgid "unable to join load_cache_entries thread: %s" -msgstr "не може да се изчака нишка за зареждане на обектите от кеша: %s" +msgid "unknown switch `%c'" +msgstr "непознат флаг „%c“" -#: read-cache.c:2161 +#: parse-options.c:866 #, c-format -msgid "%s: index file open failed" -msgstr "%s: неуспешно отваряне на файла на индекса" +msgid "unknown non-ascii option in string: `%s'" +msgstr "непозната стойност извън „ascii“ в низа: „%s“" + +#: parse-options.c:890 +msgid "..." +msgstr "…" -#: read-cache.c:2165 +#: parse-options.c:909 #, c-format -msgid "%s: cannot stat the open index" -msgstr "%s: не може да се получи информация за отворения индекс със „stat“" +msgid "usage: %s" +msgstr "употреба: %s" -#: read-cache.c:2169 +#. TRANSLATORS: the colon here should align with the +#. one in "usage: %s" translation. +#. +#: parse-options.c:915 #, c-format -msgid "%s: index file smaller than expected" -msgstr "%s: файлът на индекса е по-малък от очакваното" +msgid " or: %s" +msgstr " или: %s" -#: read-cache.c:2173 +#: parse-options.c:918 #, c-format -msgid "%s: unable to map index file" -msgstr "%s: неуспешно заделяне на съответстваща памет чрез „mmap“ на индекса" +msgid " %s" +msgstr " %s" + +#: parse-options.c:957 +msgid "-NUM" +msgstr "-ЧИСЛО" -#: read-cache.c:2215 +#: path.c:915 #, c-format -msgid "unable to create load_index_extensions thread: %s" +msgid "Could not make %s writable by group" +msgstr "Не могат да се дадат права за запис в директорията „%s“ на групата" + +#: pathspec.c:130 +msgid "Escape character '\\' not allowed as last character in attr value" msgstr "" -"не може да се създаде нишка за зареждане на разширенията на индекса: %s" +"Екраниращият знак „\\“не може да е последен знак в стойността на атрибут" + +#: pathspec.c:148 +msgid "Only one 'attr:' specification is allowed." +msgstr "Позволено е само едно указване на „attr:“." + +#: pathspec.c:151 +msgid "attr spec must not be empty" +msgstr "„attr:“ трябва да указва стойност" -#: read-cache.c:2242 +#: pathspec.c:194 #, c-format -msgid "unable to join load_index_extensions thread: %s" +msgid "invalid attribute name %s" +msgstr "неправилно име на атрибут: „%s“" + +#: pathspec.c:259 +msgid "global 'glob' and 'noglob' pathspec settings are incompatible" +msgstr "глобалните настройки за пътища „glob“ и „noglob“ са несъвместими" + +#: pathspec.c:266 +msgid "" +"global 'literal' pathspec setting is incompatible with all other global " +"pathspec settings" msgstr "" -"не може да се създаде нишка за зареждане на разширенията на индекса: %s" +"глобалната настройка за дословни пътища „literal“ е несъвместима с всички " +"други глобални настройки за пътища" -#: read-cache.c:2274 -#, c-format -msgid "could not freshen shared index '%s'" -msgstr "споделеният индекс „%s“ не може да се обнови" +#: pathspec.c:306 +msgid "invalid parameter for pathspec magic 'prefix'" +msgstr "неправилен параметър за опцията за магически пътища „prefix“" -#: read-cache.c:2321 +#: pathspec.c:327 #, c-format -msgid "broken index, expect %s in %s, got %s" -msgstr "грешки в индекса — в „%2$s“ се очаква „%1$s“, а бе получено „%3$s“" +msgid "Invalid pathspec magic '%.*s' in '%s'" +msgstr "Неправилна стойност за опцията за магически пътища „%.*s“ в „%s“" -#: read-cache.c:3017 strbuf.c:1171 wrapper.c:633 builtin/merge.c:1140 +#: pathspec.c:332 #, c-format -msgid "could not close '%s'" -msgstr "„%s“ не може да се затвори" +msgid "Missing ')' at the end of pathspec magic in '%s'" +msgstr "Знакът „)“ липсва в опцията за магически пътища в „%s“" -#: read-cache.c:3120 sequencer.c:2479 sequencer.c:4231 +#: pathspec.c:370 #, c-format -msgid "could not stat '%s'" -msgstr "неуспешно изпълнение на „stat“ върху „%s“" +msgid "Unimplemented pathspec magic '%c' in '%s'" +msgstr "Магическите пътища „%c“ са без реализация за „%s“" -#: read-cache.c:3133 +#: pathspec.c:429 #, c-format -msgid "unable to open git dir: %s" -msgstr "не може да се отвори директорията на git: %s" +msgid "%s: 'literal' and 'glob' are incompatible" +msgstr "%s: опциите „literal“ и „glob“ са несъвместими" -#: read-cache.c:3145 +#: pathspec.c:445 #, c-format -msgid "unable to unlink: %s" -msgstr "неуспешно изтриване на „%s“" +msgid "%s: '%s' is outside repository at '%s'" +msgstr "%s: „%s“ е извън хранилището при „%s“" -#: read-cache.c:3170 +#: pathspec.c:521 #, c-format -msgid "cannot fix permission bits on '%s'" -msgstr "правата за достъп до „%s“ не могат да бъдат поправени" +msgid "'%s' (mnemonic: '%c')" +msgstr "„%s“ (клавиш: „%c“)" -#: read-cache.c:3319 +#: pathspec.c:531 #, c-format -msgid "%s: cannot drop to stage #0" -msgstr "%s: не може да се премине към етап №0" +msgid "%s: pathspec magic not supported by this command: %s" +msgstr "%s: магическите пътища не се поддържат от командата „%s“" -#: rebase-interactive.c:11 -msgid "" -"You can fix this with 'git rebase --edit-todo' and then run 'git rebase --" -"continue'.\n" -"Or you can abort the rebase with 'git rebase --abort'.\n" -msgstr "" -"Може да промените това с командите „git rebase --edit-todo“ и „git rebase --" -"continue“ след това.\n" -"Може и да преустановите пребазирането с командата „git rebase --abort“.\n" +#: pathspec.c:598 +#, c-format +msgid "pathspec '%s' is beyond a symbolic link" +msgstr "пътят „%s“ е след символна връзка" -#: rebase-interactive.c:33 +#: pathspec.c:643 #, c-format -msgid "" -"unrecognized setting %s for option rebase.missingCommitsCheck. Ignoring." -msgstr "" -"Непозната стойност „%s“ за настройката „rebase.missingCommitsCheck“. " -"Настройката се прескача." +msgid "line is badly quoted: %s" +msgstr "неправилно цитиран ред: %s" -#: rebase-interactive.c:42 -msgid "" -"\n" -"Commands:\n" -"p, pick = use commit\n" -"r, reword = use commit, but edit the commit message\n" -"e, edit = use commit, but stop for amending\n" -"s, squash = use commit, but meld into previous commit\n" -"f, fixup = like \"squash\", but discard this commit's log message\n" -"x, exec = run command (the rest of the line) using shell\n" -"b, break = stop here (continue rebase later with 'git rebase --continue')\n" -"d, drop = remove commit\n" -"l, label