diff --git a/.mailmap b/.mailmap index bde7aba756ea74..9c6a446bdfb092 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/RelNotes/2.17.6.txt b/Documentation/RelNotes/2.17.6.txt new file mode 100644 index 00000000000000..2f181e8064250e --- /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 00000000000000..dfb1de4cebf026 --- /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 00000000000000..bcca6cd2581d39 --- /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 00000000000000..1dfb784dedb70c --- /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 00000000000000..0089dd6702f7a0 --- /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 00000000000000..6b280d93211c7f --- /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 00000000000000..6e5424d0da2e22 --- /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 00000000000000..4e216eec2a8de7 --- /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 00000000000000..fcb9566b15f27c --- /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 00000000000000..4111c38f0a36d0 --- /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 00000000000000..a1e08a9f7282b0 --- /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 00000000000000..8484c8297c56f5 --- /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 00000000000000..e10eedb35add31 --- /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 00000000000000..bada3985019330 --- /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 175a644f82cc3c..cf0c7d8d40fca6 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 @@ -85,6 +87,26 @@ UI, Workflows & Features * 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. @@ -169,6 +191,44 @@ Performance, Internal Implementation, Development Support etc. * 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 ----------------- @@ -267,5 +327,39 @@ Fixes since v2.30 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 00000000000000..f9b06b8e1b2151 --- /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 00000000000000..6f69a521accd84 --- /dev/null +++ b/Documentation/RelNotes/2.32.0.txt @@ -0,0 +1,129 @@ +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. + + +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. + + + +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. + (merge 097ea2c848 jh/fsmonitor-prework later to maint). + + * The data structure used by fsmonitor interface was not properly + duplicated during an in-core merge, leading to use-after-free etc. + (merge 4abc57848d js/fsmonitor-unpack-fix later to maint). + + * "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. + (merge 7730f85594 jk/bisect-peel-tag-fix later to maint). + + * Fix macros that can silently inject unintended null-statements. + (merge 116affac3f rs/avoid-null-statement-after-macro-call later to maint). + + * CALLOC_ARRAY() macro replaces many uses of xcalloc(). + (merge 1c57cc70ec rs/calloc-array later to maint). + + * Update insn in Makefile comments to run fuzz-all target. + (merge 68b5c3aa48 ah/make-fuzz-all-doc-update later to maint). + + * Fix a corner case bug in "git mv" on case insensitive systems, + which was introduced in 2.29 timeframe. + (merge 93c3d297b5 tb/git-mv-icase-fix later to maint). + + * 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). + + * Other code cleanup, docfix, build fix, etc. + (merge 486f4bd183 jc/calloc-fix later to maint). + (merge 5f70859c15 jt/clone-unborn-head later to maint). + (merge cfd409ed09 km/config-doc-typofix later to maint). + (merge 8588aa8657 jk/slimmed-down later to maint). + (merge 241b5d3ebe rs/xcalloc-takes-nelem-first later to maint). + (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). diff --git a/Documentation/blame-options.txt b/Documentation/blame-options.txt index dc3bceb6d1cad4..117f4cf80645e1 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 d08e83a1482ed4..bf82766a6a272d 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. diff --git a/Documentation/config/commitgraph.txt b/Documentation/config/commitgraph.txt index 4582c39fc46275..30604e4a4c2967 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/mergetool.txt b/Documentation/config/mergetool.txt index 90f76f5b9ba8dd..cafbbef46ae9c1 100644 --- a/Documentation/config/mergetool.txt +++ b/Documentation/config/mergetool.txt @@ -53,7 +53,7 @@ mergetool.hideResolved:: 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 `true`. + configuration variable. Defaults to `false`. mergetool.keepBackup:: After performing a merge, the original file with conflict markers diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt index 7f7a07d22f86bd..8c979cb20f2a57 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 00eb35434e883d..413f907cba059d 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 f1097fac69a6bb..99c455f51c044f 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 e5733ccb2d1a7a..aa2b5c11f20bf3 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 06bc063542f304..decd8ae12278ca 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 eb815c22484ef6..94dc9a54f2d726 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -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 8e192d87db4c6f..4eb0421b3fd946 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-commit.txt b/Documentation/git-commit.txt index 17150fa7eabe80..3c69f461c9af1d 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -9,7 +9,7 @@ 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] @@ -86,11 +86,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`. diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt index 484c485fd06c9d..143b0c49d739ae 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 2962f85a502a34..2ae2478de706ce 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 3e49bf221087c0..911da181a108d2 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 0c114ad1ca2618..853967dea01d07 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 4deb4893f517c3..9fa17b60e43838 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 69ba904d449195..7fa74b9e79876b 100644 --- a/Documentation/git-index-pack.txt +++ b/Documentation/git-index-pack.txt @@ -86,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 7a6aed0e30c198..d343f040f5373d 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-mergetool.txt b/Documentation/git-mergetool.txt index 6b14702e78498e..e587c7763a7c21 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-pack-objects.txt b/Documentation/git-pack-objects.txt index 54d715ead1373d..25d9fbe37ae811 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 ab103c82cfdc38..a953c7c38790a8 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-rebase.txt b/Documentation/git-rebase.txt index a0487b5cc58816..f08ae27e2aefc0 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 92f146d27dc363..317d63cf0d3ae0 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 5da66232dc3f7b..20bb8e82176b89 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-stash.txt b/Documentation/git-stash.txt index 31f1beb65baf21..a8c8c32f1e5157 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 c0764e850a44ad..83f38e31981420 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 56656d1be60331..31a97a1b6c5b22 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 d36e6fd482530b..3a9c44987f998c 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 e84e104f932552..0a60472bb599fa 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 c970d9fe438a09..0d57f86abc4a51 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 @@ -168,6 +169,26 @@ a similarity score different from the default of 50% by giving a number after the "-M" or "-C" option (e.g. "-M8" to tell it to use 8/10 = 80%). +Note that when rename detection is on but both copy and break +detection are off, rename detection adds a preliminary step that first +checks if files are moved across directories while keeping their +filename the same. If there is a file added to a directory whose +contents is sufficiently similar to a file with the same name that got +deleted from a different directory, it will mark them as renames and +exclude them from the later quadratic step (the one that pairwise +compares all unmatched files to find the "best" matches, determined by +the highest content similarity). So, for example, if a deleted +docs/ext.txt and an added docs/config/ext.txt are similar enough, they +will be marked as a rename and prevent an added docs/ext.md that may +be even more similar to the deleted docs/ext.txt from being considered +as the rename destination in the later step. For this reason, the +preliminary "match same filename" step uses a bit higher threshold to +mark a file pair as a rename and stop considering other candidates for +better matches. At most, one comparison is done per file in this +preliminary pass; so if there are several remaining ext.txt files +throughout the directory hierarchy after exact rename detection, this +preliminary step may be skipped for those files. + Note. When the "-C" option is used with `--find-copies-harder` option, 'git diff-{asterisk}' commands feed unmodified filepairs to diffcore mechanism as well as modified ones. This lets the copy @@ -276,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 1f3b57d04db6ca..b51959ff9418fd 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 d47b1ae2963726..5751603b13f40c 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 052209b33b4ffa..3fb39f801fb13c 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/i18n.txt b/Documentation/i18n.txt index 7e36e5b55b1ab8..6c6baeeeb75bd8 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 6b59e28d444f8e..45133066e41220 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 96cc89d157d21f..b1c8f86c6efc12 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 00000000000000..d79ad323e67530 --- /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 00000000000000..593614fcedab47 --- /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 b6658eff188209..87971c27dd73f4 100644 --- a/Documentation/technical/commit-graph-format.txt +++ b/Documentation/technical/commit-graph-format.txt @@ -61,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. diff --git a/Documentation/technical/hash-function-transition.txt b/Documentation/technical/hash-function-transition.txt index 6fd20ebbc25447..7c1630bf832491 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 b633482b1bdff1..d363a71c37ec46 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/pack-format.txt b/Documentation/technical/pack-format.txt index 8833b71c8b93a6..1faa949bf63b02 100644 --- a/Documentation/technical/pack-format.txt +++ b/Documentation/technical/pack-format.txt @@ -336,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. diff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt index f772d90eaf98ce..a7c806a73e0b5d 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 ---------------------- diff --git a/Documentation/technical/reftable.txt b/Documentation/technical/reftable.txt index 8095ab2590c878..3ef169af27d816 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 0327733794e402..d2ff91775effdd 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 8474ad01bf9db8..66389ce05915d7 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 5a239cac20e334..a6a73c57419179 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 3324fc058d6c9c..aece21e8a40e44 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/add-interactive.c b/add-interactive.c index 9b8cdb4a31a6ab..36ebdbdf7e2c8d 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 668b16e9893d2f..6695a931e979a9 100644 --- a/apply.c +++ b/apply.c @@ -1781,7 +1781,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 +1959,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 +4681,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 a971fdc0f6326e..05d2455870d7fa 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 5919d9e5050884..295615580d7106 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 33551b7ee17be6..49fab71aaf649e 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 4ef85d668b5496..ac8ec7ce51e39e 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 75ea0eb57ff189..af2863d044b704 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 a5044fcfaa6264..5018bb8fb2ce81 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 8681031402fb93..1bb6e7c069035b 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 b176f28f531ecc..52b87474c6eb5a 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 a825887c503dd3..ea762a41e3a2d2 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 d69e13335d9c79..1fdb7d9d1060b4 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -874,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 || diff --git a/builtin/blame.c b/builtin/blame.c index b66e938022bc5f..641523ff9af693 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 4bbfc92dce5a0e..023e49e271c2e3 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -23,22 +23,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 2d6550bc3c8638..0e663905200155 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -114,7 +114,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 +155,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 +322,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 687ab473c20c6b..995053b79173e1 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/commit.c b/builtin/commit.c index 739110c5a7f60d..d5138582187e7a 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; @@ -357,7 +358,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 +683,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 +763,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; @@ -1152,6 +1188,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 +1219,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 +1276,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,7 +1589,11 @@ 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_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")), @@ -1663,6 +1752,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 c61f123a3b81be..4c6c89ab0de2eb 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 9b3f709905979e..76a6ba37223fa9 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 bb852661020121..70103c40952093 100644 --- a/builtin/diff-files.c +++ b/builtin/diff-files.c @@ -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 c33d7af47859cc..176fe7ff2b4e15 100644 --- a/builtin/diff-index.c +++ b/builtin/diff-index.c @@ -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 178d12f07f9593..f33d30d57bff2e 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -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 0f4859abf75b6b..617b9a4101dbd8 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -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/fast-import.c b/builtin/fast-import.c index dd4d09cecebd57..3afa81cf9acdba 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/gc.c b/builtin/gc.c index 6db9cb39e6797f..ef7226d7bca46d 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -1461,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"); @@ -1478,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); + + rc = run_command(&config_set); - return 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 e348e6bb326469..5de725f904419e 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, @@ -1178,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 54f74c48741df8..21899687e2ceb6 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -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) @@ -1674,7 +1674,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]; @@ -1712,6 +1712,22 @@ static void show_pack_info(int stat_only) } } +static int print_dangling_gitmodules(struct fsck_options *o, + const struct object_id *oid, + enum object_type object_type, + int msg_type, const char *message) +{ + /* + * NEEDSWORK: Plumb the MSG_ID (from fsck.c) here and use it + * instead of relying on this string check. + */ + if (starts_with(message, "gitmodulesMissing")) { + printf("%s\n", oid_to_hex(oid)); + return 0; + } + return fsck_error_function(o, oid, object_type, msg_type, message); +} + int cmd_index_pack(int argc, const char **argv, const char *prefix) { int i, fix_thin_pack = 0, verify = 0, stat_only = 0, rev_index; @@ -1896,10 +1912,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); @@ -1932,8 +1948,13 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) else close(input_fd); - if (do_fsck_object && fsck_finish(&fsck_options)) - die(_("fsck error in pack objects")); + if (do_fsck_object) { + struct fsck_options fo = fsck_options; + + fo.error_func = print_dangling_gitmodules; + if (fsck_finish(&fo)) + die(_("fsck error in pack objects")); + } free(objects); strbuf_release(&index_name_buf); diff --git a/builtin/init-db.c b/builtin/init-db.c index dcc45bef514811..f82efe4aff6dce 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -212,6 +212,7 @@ static int create_default_files(const char *template_path, int reinit; int filemode; struct strbuf err = STRBUF_INIT; + const char *work_tree = get_git_work_tree(); /* Just look for `init.templatedir` */ init_db_template_dir = NULL; /* re-set in case it was set before */ @@ -235,7 +236,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 +300,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) diff --git a/builtin/log.c b/builtin/log.c index be29c3f89fc0b6..8acd285dafd874 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: @@ -1661,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; } @@ -1716,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; @@ -1750,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")), @@ -1861,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); @@ -1961,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 f6f9e483b27e18..60a2913a01e9d0 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 ef604752a044a4..abfa9847374fdd 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -88,7 +88,7 @@ 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]); } diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 7cad3f24ebd084..3a442631c71a07 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 eb00b273e668e2..388619536aa1d2 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/mv.c b/builtin/mv.c index 7dac714af90878..3fccdcb64522b4 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 2987c08a2e920d..74bba39ca82932 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 6d62aaf59a030d..525c2d85529f41 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++) { @@ -2986,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]; @@ -3489,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, @@ -3539,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, @@ -3645,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"); } @@ -3690,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 * @@ -3750,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 6e115a811aa29e..7102996c75e7e7 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 03adb58602971d..194967ed79dc99 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/rebase.c b/builtin/rebase.c index 840dbd7eb7772e..783b526f6e758a 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 d49d050e6e57c0..6bc12c828aa351 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/remote.c b/builtin/remote.c index d11a5589e49dcc..717b662d4554f7 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); } } diff --git a/builtin/repack.c b/builtin/repack.c index 01440de2d5a703..6ce2556c9e1651 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -297,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; @@ -304,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; @@ -316,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, @@ -356,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() }; @@ -382,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()); @@ -396,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) @@ -429,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) @@ -507,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); @@ -528,6 +726,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) 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/rev-list.c b/builtin/rev-list.c index 25c6c3b38d4b12..b4d8ea0a35b5b2 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/sparse-checkout.c b/builtin/sparse-checkout.c index 2306a9ad98e08a..d7da50ada5b75b 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 9bc85f91cd0040..3477e940e352d0 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -87,7 +87,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 +222,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 +768,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 +781,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 +792,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 +827,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 +846,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 +874,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 +895,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/tag.c b/builtin/tag.c index e8b85eefd878b5..d403417b562595 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 dd4a75e030d219..a4ba2ebac692d9 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)) @@ -500,7 +500,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/bulk-checkin.c b/bulk-checkin.c index 583aacb9e36bde..6f3c97cd347b59 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 2fb483d3c08389..add1f077131768 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 d92814961405dc..57f2285bba9f0e 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 */ @@ -1659,8 +1659,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 00000000000000..da191e59a29db6 --- /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 00000000000000..9ccbe0037792c3 --- /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/combine-diff.c b/combine-diff.c index 9228aebc16b698..06635f91bc21a5 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 ed31843fa522ee..f18380b922c122 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) { @@ -44,7 +45,6 @@ void git_test_write_commit_graph_or_die(void) #define GRAPH_CHUNKID_BLOOMINDEXES 0x42494458 /* "BIDX" */ #define GRAPH_CHUNKID_BLOOMDATA 0x42444154 /* "BDAT" */ #define GRAPH_CHUNKID_BASE 0x42415345 /* "BASE" */ -#define MAX_NUM_CHUNKS 9 #define GRAPH_DATA_WIDTH (the_hash_algo->rawsz + 16) @@ -59,8 +59,7 @@ 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) @@ -97,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 = @@ -215,24 +221,16 @@ static int commit_graph_compatible(struct repository *r) if (read_replace_refs) { prepare_replace_object(r); - if (hashmap_get_size(&r->objects->replace_map->map)) { - warning(_("repository contains replace objects; " - "skipping commit-graph")); + if (hashmap_get_size(&r->objects->replace_map->map)) return 0; - } } prepare_commit_graft(r); if (r->parsed_objects && - (r->parsed_objects->grafts_nr || r->parsed_objects->substituted_parent)) { - warning(_("repository contains (deprecated) grafts; " - "skipping commit-graph")); + (r->parsed_objects->grafts_nr || r->parsed_objects->substituted_parent)) return 0; - } - if (is_repository_shallow(r)) { - warning(_("repository is shallow; skipping commit-graph")); + if (is_repository_shallow(r)) return 0; - } return 1; } @@ -306,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; @@ -355,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); @@ -363,108 +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); - - 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_GENERATION_DATA: - if (graph->chunk_generation_data) - chunk_repeated = 1; - else - graph->chunk_generation_data = data + chunk_offset; - break; - - case GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW: - if (graph->chunk_generation_data_overflow) - chunk_repeated = 1; - else - graph->chunk_generation_data_overflow = 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; + cf = init_chunkfile(NULL); - 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) { @@ -481,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; @@ -575,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); @@ -1059,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; @@ -1085,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++) { @@ -1104,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; @@ -1206,8 +1160,9 @@ static int write_graph_chunk_data(struct hashfile *f, } static int write_graph_chunk_generation_data(struct hashfile *f, - struct write_commit_graph_context *ctx) + void *data) { + struct write_commit_graph_context *ctx = data; int i, num_generation_data_overflows = 0; for (i = 0; i < ctx->commits.nr; i++) { @@ -1229,8 +1184,9 @@ static int write_graph_chunk_generation_data(struct hashfile *f, } static int write_graph_chunk_generation_data_overflow(struct hashfile *f, - struct write_commit_graph_context *ctx) + 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]; @@ -1247,8 +1203,9 @@ static int write_graph_chunk_generation_data_overflow(struct hashfile *f, } 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; @@ -1301,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; @@ -1336,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; @@ -1813,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) { @@ -1825,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; @@ -1891,98 +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 (git_env_bool(GIT_TEST_COMMIT_GRAPH_NO_GDAT, 0)) - ctx->write_generation_data = 0; - if (ctx->write_generation_data) { - chunks[num_chunks].id = GRAPH_CHUNKID_GENERATION_DATA; - chunks[num_chunks].size = sizeof(uint32_t) * ctx->commits.nr; - chunks[num_chunks].write_fn = write_graph_chunk_generation_data; - num_chunks++; - } - if (ctx->num_generation_data_overflows) { - chunks[num_chunks].id = GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW; - chunks[num_chunks].size = sizeof(timestamp_t) * ctx->num_generation_data_overflows; - chunks[num_chunks].write_fn = write_graph_chunk_generation_data_overflow; - num_chunks++; - } - 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); @@ -1999,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"); @@ -2319,6 +2231,7 @@ 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; @@ -2326,23 +2239,23 @@ int write_commit_graph(struct object_directory *odb, 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 = 1; + 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", @@ -2471,6 +2384,7 @@ int write_commit_graph(struct object_directory *odb, 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++) { diff --git a/commit-graph.h b/commit-graph.h index 97f3497c2790c5..96c24fb5777de4 100644 --- a/commit-graph.h +++ b/commit-graph.h @@ -6,7 +6,6 @@ #include "oidset.h" #define GIT_TEST_COMMIT_GRAPH "GIT_TEST_COMMIT_GRAPH" -#define GIT_TEST_COMMIT_GRAPH_NO_GDAT "GIT_TEST_COMMIT_GRAPH_NO_GDAT" #define GIT_TEST_COMMIT_GRAPH_DIE_ON_PARSE "GIT_TEST_COMMIT_GRAPH_DIE_ON_PARSE" #define GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS "GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS" diff --git a/commit-reach.c b/commit-reach.c index e38771ca5a1f63..c226ee3da469c5 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; @@ -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); @@ -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); @@ -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; @@ -561,21 +716,6 @@ 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; - - 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; - return 0; -} - int can_all_from_reach_with_flag(struct object_array *from, unsigned int with_flag, unsigned int assign_flag, diff --git a/commit.c b/commit.c index 4694c4cf9bca07..8ea55a447fa9e6 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)); @@ -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 9e0c157bea3a95..df42eb434f314b 100644 --- a/commit.h +++ b/commit.h @@ -167,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, @@ -319,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); /* @@ -361,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 a00f3312300ac3..a43599841c6c6b 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 af8eddd73edb2b..c9a52ad64a681f 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 00000000000000..eb3754a23b8f62 --- /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/simple-ipc/ipc-shared.c b/compat/simple-ipc/ipc-shared.c new file mode 100644 index 00000000000000..1edec8159532eb --- /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 00000000000000..38689b278df30e --- /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 00000000000000..8f89c02037e36c --- /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 f90b633dba21f3..6428393a4143b7 100644 --- a/config.c +++ b/config.c @@ -2269,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 e22d4b6d67a382..cb443b4e023adb 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/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt index ac3dbc079af831..9897fcc8ea2a67 100644 --- a/contrib/buildsystems/CMakeLists.txt +++ b/contrib/buildsystems/CMakeLists.txt @@ -243,7 +243,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}) diff --git a/contrib/coccinelle/array.cocci b/contrib/coccinelle/array.cocci index 46b8d2ee11151b..9a4f00cb1bdc2e 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 00000000000000..c291011607ee34 --- /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 7dc6cd8eb8381d..c926ca26c607b4 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1474,12 +1474,12 @@ _git_branch () _git_bundle () { - local cmd="${words[2]}" + local cmd="${words[__git_subcommand_idx+1]}" case "$cword" in - 2) + $((__git_subcommand_idx+1))) __gitcomp "create list-heads verify unbundle" ;; - 3) + $((__git_subcommand_idx+2))) # looking for a file ;; *) @@ -1894,7 +1894,7 @@ _git_grep () esac case "$cword,$prev" in - 2,*|*,-*) + $((__git_subcommand_idx+1)),*|*,-*) __git_complete_symbol && return ;; esac @@ -3013,66 +3013,65 @@ _git_sparse_checkout () _git_stash () { - local save_opts='--all --keep-index --no-keep-index --quiet --patch --include-untracked' local subcommands='push list show apply clear drop pop create branch' local subcommand="$(__git_find_on_cmdline "$subcommands save")" - if [ -z "$subcommand" -a -n "$(__git_find_on_cmdline "-p")" ]; then - subcommand="push" - fi + if [ -z "$subcommand" ]; then - case "$cur" in - --*) - __gitcomp "$save_opts" + case "$((cword - __git_subcommand_idx)),$cur" in + *,--*) + __gitcomp_builtin stash_push ;; - sa*) - if [ -z "$(__git_find_on_cmdline "$save_opts")" ]; then - __gitcomp "save" - fi + 1,sa*) + __gitcomp "save" ;; - *) - if [ -z "$(__git_find_on_cmdline "$save_opts")" ]; then - __gitcomp "$subcommands" - fi + 1,*) + __gitcomp "$subcommands" ;; esac - else - case "$subcommand,$cur" in - push,--*) - __gitcomp "$save_opts --message" - ;; - save,--*) - __gitcomp "$save_opts" - ;; - apply,--*|pop,--*) - __gitcomp "--index --quiet" - ;; - drop,--*) - __gitcomp "--quiet" - ;; - list,--*) - __gitcomp "--name-status --oneline --patch-with-stat" - ;; - show,--*) - __gitcomp "$__git_diff_common_options" - ;; - branch,--*) - ;; - branch,*) - if [ $cword -eq 3 ]; then - __git_complete_refs - else - __gitcomp_nl "$(__git stash list \ - | sed -n -e 's/:.*//p')" - fi - ;; - show,*|apply,*|drop,*|pop,*) + return + fi + + case "$subcommand,$cur" in + push,--*) + __gitcomp_builtin stash_push + ;; + save,--*) + __gitcomp_builtin stash_save + ;; + pop,--*) + __gitcomp_builtin stash_pop + ;; + apply,--*) + __gitcomp_builtin stash_apply + ;; + drop,--*) + __gitcomp_builtin stash_drop + ;; + list,--*) + # NEEDSWORK: can we somehow unify this with the options in _git_log() and _git_show() + __gitcomp_builtin stash_list "$__git_log_common_options $__git_diff_common_options" + ;; + show,--*) + __gitcomp_builtin stash_show "$__git_diff_common_options" + ;; + branch,--*) + __gitcomp_builtin stash_branch + ;; + branch,*) + if [ $cword -eq $((__git_subcommand_idx+2)) ]; then + __git_complete_refs + else __gitcomp_nl "$(__git stash list \ | sed -n -e 's/:.*//p')" - ;; - *) - ;; - esac - fi + fi + ;; + show,*|apply,*|drop,*|pop,*) + __gitcomp_nl "$(__git stash list \ + | sed -n -e 's/:.*//p')" + ;; + *) + ;; + esac } _git_submodule () @@ -3277,11 +3276,9 @@ __git_complete_worktree_paths () _git_worktree () { local subcommands="add list lock move prune remove unlock" - local subcommand subcommand_idx + local subcommand - subcommand="$(__git_find_on_cmdline --show-idx "$subcommands")" - subcommand_idx="${subcommand% *}" - subcommand="${subcommand#* }" + subcommand="$(__git_find_on_cmdline "$subcommands")" case "$subcommand,$cur" in ,*) @@ -3306,7 +3303,7 @@ _git_worktree () # be either the 'add' subcommand, the unstuck # argument of an option (e.g. branch for -b|-B), or # the path for the new worktree. - if [ $cword -eq $((subcommand_idx+1)) ]; then + if [ $cword -eq $((__git_subcommand_idx+2)) ]; then # Right after the 'add' subcommand: have to # complete the path, so fall back to Bash # filename completion. @@ -3330,7 +3327,7 @@ _git_worktree () __git_complete_worktree_paths ;; move,*) - if [ $cword -eq $((subcommand_idx+1)) ]; then + if [ $cword -eq $((__git_subcommand_idx+2)) ]; then # The first parameter must be an existing working # tree to be moved. __git_complete_worktree_paths @@ -3398,6 +3395,7 @@ __git_main () { local i c=1 command __git_dir __git_repo_path local __git_C_args C_args_count=0 + local __git_subcommand_idx while [ $c -lt $cword ]; do i="${words[c]}" @@ -3412,7 +3410,7 @@ __git_main () __git_C_args[C_args_count++]="${words[c]}" ;; -*) ;; - *) command="$i"; break ;; + *) command="$i"; __git_subcommand_idx="$c"; break ;; esac ((c++)) done diff --git a/convert.c b/convert.c index ee360c2f07ced0..2fb25a9e9e9f7f 100644 --- a/convert.c +++ b/convert.c @@ -884,9 +884,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 +907,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 +1033,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); @@ -1456,7 +1461,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); diff --git a/daemon.c b/daemon.c index 2ab7ea82eb0b4f..343531980dea11 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 a605b1b5f4ac78..2036d159671253 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 b73cc1859a49eb..e5a58c9259cfdf 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 69e3bc00ed8f72..4acccd9d7edbb9 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 2ff2b1c7f2ca05..c8f3faea8aa9ad 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 8fe6c9384bcb5b..e2ed64817654ec 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -367,6 +367,535 @@ static int find_exact_renames(struct diff_options *options) return renames; } +struct dir_rename_info { + struct strintmap idx_map; + struct strmap dir_rename_guess; + struct strmap *dir_rename_count; + struct strset *relevant_source_dirs; + unsigned setup; +}; + +static char *get_dirname(const char *filename) +{ + char *slash = strrchr(filename, '/'); + return slash ? xstrndup(filename, slash - filename) : xstrdup(""); +} + +static void dirname_munge(char *filename) +{ + char *slash = strrchr(filename, '/'); + if (!slash) + slash = filename; + *slash = '\0'; +} + +static const char *get_highest_rename_path(struct strintmap *counts) +{ + int highest_count = 0; + const char *highest_destination_dir = NULL; + struct hashmap_iter iter; + struct strmap_entry *entry; + + strintmap_for_each_entry(counts, &iter, entry) { + const char *destination_dir = entry->key; + intptr_t count = (intptr_t)entry->value; + if (count > highest_count) { + highest_count = count; + highest_destination_dir = destination_dir; + } + } + return highest_destination_dir; +} + +static void increment_count(struct dir_rename_info *info, + char *old_dir, + char *new_dir) +{ + struct strintmap *counts; + struct strmap_entry *e; + + /* Get the {new_dirs -> counts} mapping using old_dir */ + e = strmap_get_entry(info->dir_rename_count, old_dir); + if (e) { + counts = e->value; + } else { + counts = xmalloc(sizeof(*counts)); + strintmap_init_with_options(counts, 0, NULL, 1); + strmap_put(info->dir_rename_count, old_dir, counts); + } + + /* Increment the count for new_dir */ + strintmap_incr(counts, new_dir, 1); +} + +static void update_dir_rename_counts(struct dir_rename_info *info, + struct strset *dirs_removed, + const char *oldname, + const char *newname) +{ + char *old_dir = xstrdup(oldname); + char *new_dir = xstrdup(newname); + char new_dir_first_char = new_dir[0]; + int first_time_in_loop = 1; + + if (!info->setup) + /* + * info->setup is 0 here in two cases: (1) all auxiliary + * vars (like dirs_removed) were NULL so + * initialize_dir_rename_info() returned early, or (2) + * either break detection or copy detection are active so + * that we never called initialize_dir_rename_info(). In + * the former case, we don't have enough info to know if + * directories were renamed (because dirs_removed lets us + * know about a necessary prerequisite, namely if they were + * removed), and in the latter, we don't care about + * directory renames or find_basename_matches. + * + * This matters because both basename and inexact matching + * will also call update_dir_rename_counts(). In either of + * the above two cases info->dir_rename_counts will not + * have been properly initialized which prevents us from + * updating it, but in these two cases we don't care about + * dir_rename_counts anyway, so we can just exit early. + */ + return; + + while (1) { + /* Get old_dir, skip if its directory isn't relevant. */ + dirname_munge(old_dir); + if (info->relevant_source_dirs && + !strset_contains(info->relevant_source_dirs, old_dir)) + break; + + /* Get new_dir */ + dirname_munge(new_dir); + + /* + * When renaming + * "a/b/c/d/e/foo.c" -> "a/b/some/thing/else/e/foo.c" + * then this suggests that both + * a/b/c/d/e/ => a/b/some/thing/else/e/ + * a/b/c/d/ => a/b/some/thing/else/ + * so we want to increment counters for both. We do NOT, + * however, also want to suggest that there was the following + * rename: + * a/b/c/ => a/b/some/thing/ + * so we need to quit at that point. + * + * Note the when first_time_in_loop, we only strip off the + * basename, and we don't care if that's different. + */ + if (!first_time_in_loop) { + char *old_sub_dir = strchr(old_dir, '\0')+1; + char *new_sub_dir = strchr(new_dir, '\0')+1; + if (!*new_dir) { + /* + * Special case when renaming to root directory, + * i.e. when new_dir == "". In this case, we had + * something like + * a/b/subdir => subdir + * and so dirname_munge() sets things up so that + * old_dir = "a/b\0subdir\0" + * new_dir = "\0ubdir\0" + * We didn't have a '/' to overwrite a '\0' onto + * in new_dir, so we have to compare differently. + */ + if (new_dir_first_char != old_sub_dir[0] || + strcmp(old_sub_dir+1, new_sub_dir)) + break; + } else { + if (strcmp(old_sub_dir, new_sub_dir)) + break; + } + } + + if (strset_contains(dirs_removed, old_dir)) + increment_count(info, old_dir, new_dir); + else + break; + + /* If we hit toplevel directory ("") for old or new dir, quit */ + if (!*old_dir || !*new_dir) + break; + + first_time_in_loop = 0; + } + + /* Free resources we don't need anymore */ + free(old_dir); + free(new_dir); +} + +static void initialize_dir_rename_info(struct dir_rename_info *info, + struct strset *dirs_removed, + struct strmap *dir_rename_count) +{ + struct hashmap_iter iter; + struct strmap_entry *entry; + int i; + + if (!dirs_removed) { + info->setup = 0; + return; + } + info->setup = 1; + + info->dir_rename_count = dir_rename_count; + if (!info->dir_rename_count) { + info->dir_rename_count = xmalloc(sizeof(*dir_rename_count)); + strmap_init(info->dir_rename_count); + } + strintmap_init_with_options(&info->idx_map, -1, NULL, 0); + strmap_init_with_options(&info->dir_rename_guess, NULL, 0); + + /* Setup info->relevant_source_dirs */ + info->relevant_source_dirs = dirs_removed; + + /* + * Loop setting up both info->idx_map, and doing setup of + * info->dir_rename_count. + */ + for (i = 0; i < rename_dst_nr; ++i) { + /* + * For non-renamed files, make idx_map contain mapping of + * filename -> index (index within rename_dst, that is) + */ + if (!rename_dst[i].is_rename) { + char *filename = rename_dst[i].p->two->path; + strintmap_set(&info->idx_map, filename, i); + continue; + } + + /* + * For everything else (i.e. renamed files), make + * dir_rename_count contain a map of a map: + * old_directory -> {new_directory -> count} + * In other words, for every pair look at the directories for + * the old filename and the new filename and count how many + * times that pairing occurs. + */ + update_dir_rename_counts(info, dirs_removed, + rename_dst[i].p->one->path, + rename_dst[i].p->two->path); + } + + /* + * Now we collapse + * dir_rename_count: old_directory -> {new_directory -> count} + * down to + * dir_rename_guess: old_directory -> best_new_directory + * where best_new_directory is the one with the highest count. + */ + strmap_for_each_entry(info->dir_rename_count, &iter, entry) { + /* entry->key is source_dir */ + struct strintmap *counts = entry->value; + char *best_newdir; + + best_newdir = xstrdup(get_highest_rename_path(counts)); + strmap_put(&info->dir_rename_guess, entry->key, + best_newdir); + } +} + +void partial_clear_dir_rename_count(struct strmap *dir_rename_count) +{ + struct hashmap_iter iter; + struct strmap_entry *entry; + + strmap_for_each_entry(dir_rename_count, &iter, entry) { + struct strintmap *counts = entry->value; + strintmap_clear(counts); + } + strmap_partial_clear(dir_rename_count, 1); +} + +static void cleanup_dir_rename_info(struct dir_rename_info *info, + struct strset *dirs_removed, + int keep_dir_rename_count) +{ + struct hashmap_iter iter; + struct strmap_entry *entry; + struct string_list to_remove = STRING_LIST_INIT_NODUP; + int i; + + if (!info->setup) + return; + + /* idx_map */ + strintmap_clear(&info->idx_map); + + /* dir_rename_guess */ + strmap_clear(&info->dir_rename_guess, 1); + + /* dir_rename_count */ + if (!keep_dir_rename_count) { + partial_clear_dir_rename_count(info->dir_rename_count); + strmap_clear(info->dir_rename_count, 1); + FREE_AND_NULL(info->dir_rename_count); + return; + } + + /* + * Although dir_rename_count was passed in + * diffcore_rename_extended() and we want to keep it around and + * return it to that caller, we first want to remove any data + * associated with directories that weren't renamed. + */ + strmap_for_each_entry(info->dir_rename_count, &iter, entry) { + const char *source_dir = entry->key; + struct strintmap *counts = entry->value; + + if (!strset_contains(dirs_removed, source_dir)) { + string_list_append(&to_remove, source_dir); + strintmap_clear(counts); + continue; + } + } + for (i = 0; i < to_remove.nr; ++i) + strmap_remove(info->dir_rename_count, + to_remove.items[i].string, 1); + string_list_clear(&to_remove, 0); +} + +static const char *get_basename(const char *filename) +{ + /* + * gitbasename() has to worry about special drives, multiple + * directory separator characters, trailing slashes, NULL or + * empty strings, etc. We only work on filenames as stored in + * git, and thus get to ignore all those complications. + */ + const char *base = strrchr(filename, '/'); + return base ? base + 1 : filename; +} + +static int idx_possible_rename(char *filename, struct dir_rename_info *info) +{ + /* + * Our comparison of files with the same basename (see + * find_basename_matches() below), is only helpful when after exact + * rename detection we have exactly one file with a given basename + * among the rename sources and also only exactly one file with + * that basename among the rename destinations. When we have + * multiple files with the same basename in either set, we do not + * know which to compare against. However, there are some + * filenames that occur in large numbers (particularly + * build-related filenames such as 'Makefile', '.gitignore', or + * 'build.gradle' that potentially exist within every single + * subdirectory), and for performance we want to be able to quickly + * find renames for these files too. + * + * The reason basename comparisons are a useful heuristic was that it + * is common for people to move files across directories while keeping + * their filename the same. If we had a way of determining or even + * making a good educated guess about which directory these non-unique + * basename files had moved the file to, we could check it. + * Luckily... + * + * When an entire directory is in fact renamed, we have two factors + * helping us out: + * (a) the original directory disappeared giving us a hint + * about when we can apply an extra heuristic. + * (a) we often have several files within that directory and + * subdirectories that are renamed without changes + * So, rules for a heuristic: + * (0) If there basename matches are non-unique (the condition under + * which this function is called) AND + * (1) the directory in which the file was found has disappeared + * (i.e. dirs_removed is non-NULL and has a relevant entry) THEN + * (2) use exact renames of files within the directory to determine + * where the directory is likely to have been renamed to. IF + * there is at least one exact rename from within that + * directory, we can proceed. + * (3) If there are multiple places the directory could have been + * renamed to based on exact renames, ignore all but one of them. + * Just use the destination with the most renames going to it. + * (4) Check if applying that directory rename to the original file + * would result in a destination filename that is in the + * potential rename set. If so, return the index of the + * destination file (the index within rename_dst). + * (5) Compare the original file and returned destination for + * similarity, and if they are sufficiently similar, record the + * rename. + * + * This function, idx_possible_rename(), is only responsible for (4). + * The conditions/steps in (1)-(3) are handled via setting up + * dir_rename_count and dir_rename_guess in + * initialize_dir_rename_info(). Steps (0) and (5) are handled by + * the caller of this function. + */ + char *old_dir, *new_dir; + struct strbuf new_path = STRBUF_INIT; + int idx; + + if (!info->setup) + return -1; + + old_dir = get_dirname(filename); + new_dir = strmap_get(&info->dir_rename_guess, old_dir); + free(old_dir); + if (!new_dir) + return -1; + + strbuf_addstr(&new_path, new_dir); + strbuf_addch(&new_path, '/'); + strbuf_addstr(&new_path, get_basename(filename)); + + idx = strintmap_get(&info->idx_map, new_path.buf); + strbuf_release(&new_path); + return idx; +} + +static int find_basename_matches(struct diff_options *options, + int minimum_score, + struct dir_rename_info *info, + struct strset *dirs_removed) +{ + /* + * When I checked in early 2020, over 76% of file renames in linux + * just moved files to a different directory but kept the same + * basename. gcc did that with over 64% of renames, gecko did it + * with over 79%, and WebKit did it with over 89%. + * + * Therefore we can bypass the normal exhaustive NxM matrix + * comparison of similarities between all potential rename sources + * and destinations by instead using file basename as a hint (i.e. + * the portion of the filename after the last '/'), checking for + * similarity between files with the same basename, and if we find + * a pair that are sufficiently similar, record the rename pair and + * exclude those two from the NxM matrix. + * + * This *might* cause us to find a less than optimal pairing (if + * there is another file that we are even more similar to but has a + * different basename). Given the huge performance advantage + * basename matching provides, and given the frequency with which + * people use the same basename in real world projects, that's a + * trade-off we are willing to accept when doing just rename + * detection. + * + * If someone wants copy detection that implies they are willing to + * spend more cycles to find similarities between files, so it may + * be less likely that this heuristic is wanted. If someone is + * doing break detection, that means they do not want filename + * similarity to imply any form of content similiarity, and thus + * this heuristic would definitely be incompatible. + */ + + int i, renames = 0; + struct strintmap sources; + struct strintmap dests; + + /* + * The prefeteching stuff wants to know if it can skip prefetching + * blobs that are unmodified...and will then do a little extra work + * to verify that the oids are indeed different before prefetching. + * Unmodified blobs are only relevant when doing copy detection; + * when limiting to rename detection, diffcore_rename[_extended]() + * will never be called with unmodified source paths fed to us, so + * the extra work necessary to check if rename_src entries are + * unmodified would be a small waste. + */ + int skip_unmodified = 0; + + /* + * Create maps of basename -> fullname(s) for remaining sources and + * dests. + */ + strintmap_init_with_options(&sources, -1, NULL, 0); + strintmap_init_with_options(&dests, -1, NULL, 0); + for (i = 0; i < rename_src_nr; ++i) { + char *filename = rename_src[i].p->one->path; + const char *base; + + /* exact renames removed in remove_unneeded_paths_from_src() */ + assert(!rename_src[i].p->one->rename_used); + + /* Record index within rename_src (i) if basename is unique */ + base = get_basename(filename); + if (strintmap_contains(&sources, base)) + strintmap_set(&sources, base, -1); + else + strintmap_set(&sources, base, i); + } + for (i = 0; i < rename_dst_nr; ++i) { + char *filename = rename_dst[i].p->two->path; + const char *base; + + if (rename_dst[i].is_rename) + continue; /* involved in exact match already. */ + + /* Record index within rename_dst (i) if basename is unique */ + base = get_basename(filename); + if (strintmap_contains(&dests, base)) + strintmap_set(&dests, base, -1); + else + strintmap_set(&dests, base, i); + } + + /* Now look for basename matchups and do similarity estimation */ + for (i = 0; i < rename_src_nr; ++i) { + char *filename = rename_src[i].p->one->path; + const char *base = NULL; + intptr_t src_index; + intptr_t dst_index; + + /* + * If the basename is unique among remaining sources, then + * src_index will equal 'i' and we can attempt to match it + * to a unique basename in the destinations. Otherwise, + * use directory rename heuristics, if possible. + */ + base = get_basename(filename); + src_index = strintmap_get(&sources, base); + assert(src_index == -1 || src_index == i); + + if (strintmap_contains(&dests, base)) { + struct diff_filespec *one, *two; + int score; + + /* Find a matching destination, if possible */ + dst_index = strintmap_get(&dests, base); + if (src_index == -1 || dst_index == -1) { + src_index = i; + dst_index = idx_possible_rename(filename, info); + } + if (dst_index == -1) + continue; + + /* Ignore this dest if already used in a rename */ + if (rename_dst[dst_index].is_rename) + continue; /* already used previously */ + + /* Estimate the similarity */ + one = rename_src[src_index].p->one; + two = rename_dst[dst_index].p->two; + score = estimate_similarity(options->repo, one, two, + minimum_score, skip_unmodified); + + /* If sufficiently similar, record as rename pair */ + if (score < minimum_score) + continue; + record_rename_pair(dst_index, src_index, score); + renames++; + update_dir_rename_counts(info, dirs_removed, + one->path, two->path); + + /* + * Found a rename so don't need text anymore; if we + * didn't find a rename, the filespec_blob would get + * re-used when doing the matrix of comparisons. + */ + diff_free_filespec_blob(one); + diff_free_filespec_blob(two); + } + } + + strintmap_clear(&sources); + strintmap_clear(&dests); + + return renames; +} + #define NUM_CANDIDATE_PER_DST 4 static void record_if_better(struct diff_score m[], struct diff_score *o) { @@ -433,7 +962,12 @@ static int too_many_rename_candidates(int num_destinations, int num_sources, return 1; } -static int find_renames(struct diff_score *mx, int dst_cnt, int minimum_score, int copies) +static int find_renames(struct diff_score *mx, + int dst_cnt, + int minimum_score, + int copies, + struct dir_rename_info *info, + struct strset *dirs_removed) { int count = 0, i; @@ -450,11 +984,64 @@ static int find_renames(struct diff_score *mx, int dst_cnt, int minimum_score, i continue; record_rename_pair(mx[i].dst, mx[i].src, mx[i].score); count++; + update_dir_rename_counts(info, dirs_removed, + rename_src[mx[i].src].p->one->path, + rename_dst[mx[i].dst].p->two->path); } return count; } -void diffcore_rename(struct diff_options *options) +static void remove_unneeded_paths_from_src(int detecting_copies) +{ + int i, new_num_src; + + if (detecting_copies) + return; /* nothing to remove */ + if (break_idx) + return; /* culling incompatible with break detection */ + + /* + * Note on reasons why we cull unneeded sources but not destinations: + * 1) Pairings are stored in rename_dst (not rename_src), which we + * need to keep around. So, we just can't cull rename_dst even + * if we wanted to. But doing so wouldn't help because... + * + * 2) There is a matrix pairwise comparison that follows the + * "Performing inexact rename detection" progress message. + * Iterating over the destinations is done in the outer loop, + * hence we only iterate over each of those once and we can + * easily skip the outer loop early if the destination isn't + * relevant. That's only one check per destination path to + * skip. + * + * By contrast, the sources are iterated in the inner loop; if + * we check whether a source can be skipped, then we'll be + * checking it N separate times, once for each destination. + * We don't want to have to iterate over known-not-needed + * sources N times each, so avoid that by removing the sources + * from rename_src here. + */ + for (i = 0, new_num_src = 0; i < rename_src_nr; i++) { + /* + * renames are stored in rename_dst, so if a rename has + * already been detected using this source, we can just + * remove the source knowing rename_dst has its info. + */ + if (rename_src[i].p->one->rename_used) + continue; + + if (new_num_src < i) + memcpy(&rename_src[new_num_src], &rename_src[i], + sizeof(struct diff_rename_src)); + new_num_src++; + } + + rename_src_nr = new_num_src; +} + +void diffcore_rename_extended(struct diff_options *options, + struct strset *dirs_removed, + struct strmap *dir_rename_count) { int detect_rename = options->detect_rename; int minimum_score = options->rename_score; @@ -463,9 +1050,16 @@ void diffcore_rename(struct diff_options *options) struct diff_score *mx; int i, j, rename_count, skip_unmodified = 0; int num_destinations, dst_cnt; + int num_sources, want_copies; struct progress *progress = NULL; + struct dir_rename_info info; trace2_region_enter("diff", "setup", options->repo); + info.setup = 0; + assert(!dir_rename_count || strmap_empty(dir_rename_count)); + want_copies = (detect_rename == DIFF_DETECT_COPY); + if (dirs_removed && (break_idx || want_copies)) + BUG("dirs_removed incompatible with break/copy detection"); if (!minimum_score) minimum_score = DEFAULT_RENAME_SCORE; @@ -502,7 +1096,7 @@ void diffcore_rename(struct diff_options *options) p->one->rename_used++; register_rename_src(p); } - else if (detect_rename == DIFF_DETECT_COPY) { + else if (want_copies) { /* * Increment the "rename_used" score by * one, to indicate ourselves as a user. @@ -527,17 +1121,67 @@ void diffcore_rename(struct diff_options *options) if (minimum_score == MAX_SCORE) goto cleanup; - /* - * Calculate how many renames are left (but all the source - * files still remain as options for rename/copies!) - */ + num_sources = rename_src_nr; + + if (want_copies || break_idx) { + /* + * Cull sources: + * - remove ones corresponding to exact renames + */ + trace2_region_enter("diff", "cull after exact", options->repo); + remove_unneeded_paths_from_src(want_copies); + trace2_region_leave("diff", "cull after exact", options->repo); + } else { + /* Determine minimum score to match basenames */ + double factor = 0.5; + char *basename_factor = getenv("GIT_BASENAME_FACTOR"); + int min_basename_score; + + if (basename_factor) + factor = strtol(basename_factor, NULL, 10)/100.0; + assert(factor >= 0.0 && factor <= 1.0); + min_basename_score = minimum_score + + (int)(factor * (MAX_SCORE - minimum_score)); + + /* + * Cull sources: + * - remove ones involved in renames (found via exact match) + */ + trace2_region_enter("diff", "cull after exact", options->repo); + remove_unneeded_paths_from_src(want_copies); + trace2_region_leave("diff", "cull after exact", options->repo); + + /* Preparation for basename-driven matching. */ + trace2_region_enter("diff", "dir rename setup", options->repo); + initialize_dir_rename_info(&info, + dirs_removed, dir_rename_count); + trace2_region_leave("diff", "dir rename setup", options->repo); + + /* Utilize file basenames to quickly find renames. */ + trace2_region_enter("diff", "basename matches", options->repo); + rename_count += find_basename_matches(options, + min_basename_score, + &info, dirs_removed); + trace2_region_leave("diff", "basename matches", options->repo); + + /* + * Cull sources, again: + * - remove ones involved in renames (found via basenames) + */ + trace2_region_enter("diff", "cull basename", options->repo); + remove_unneeded_paths_from_src(want_copies); + trace2_region_leave("diff", "cull basename", options->repo); + } + + /* Calculate how many rename destinations are left */ num_destinations = (rename_dst_nr - rename_count); + num_sources = rename_src_nr; /* rename_src_nr reflects lower number */ /* All done? */ - if (!num_destinations) + if (!num_destinations || !num_sources) goto cleanup; - switch (too_many_rename_candidates(num_destinations, rename_src_nr, + switch (too_many_rename_candidates(num_destinations, num_sources, options)) { case 1: goto cleanup; @@ -553,17 +1197,16 @@ void diffcore_rename(struct diff_options *options) if (options->show_rename_progress) { progress = start_delayed_progress( _("Performing inexact rename detection"), - (uint64_t)num_destinations * (uint64_t)rename_src_nr); + (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; if (rename_dst[i].is_rename) - continue; /* dealt with exact match already. */ + continue; /* exact or basename match already handled */ m = &mx[dst_cnt * NUM_CANDIDATE_PER_DST]; for (j = 0; j < NUM_CANDIDATE_PER_DST; j++) @@ -573,6 +1216,8 @@ void diffcore_rename(struct diff_options *options) struct diff_filespec *one = rename_src[j].p->one; struct diff_score this_src; + assert(!one->rename_used || want_copies || break_idx); + if (skip_unmodified && diff_unmodified_pair(rename_src[j].p)) continue; @@ -594,16 +1239,18 @@ void diffcore_rename(struct diff_options *options) } dst_cnt++; display_progress(progress, - (uint64_t)dst_cnt * (uint64_t)rename_src_nr); + (uint64_t)dst_cnt * (uint64_t)num_sources); } stop_progress(&progress); /* cost matrix sorted by most to least similar pair */ STABLE_QSORT(mx, dst_cnt * NUM_CANDIDATE_PER_DST, score_compare); - rename_count += find_renames(mx, dst_cnt, minimum_score, 0); - if (detect_rename == DIFF_DETECT_COPY) - rename_count += find_renames(mx, dst_cnt, minimum_score, 1); + rename_count += find_renames(mx, dst_cnt, minimum_score, 0, + &info, dirs_removed); + if (want_copies) + rename_count += find_renames(mx, dst_cnt, minimum_score, 1, + &info, dirs_removed); free(mx); trace2_region_leave("diff", "inexact renames", options->repo); @@ -679,6 +1326,7 @@ void diffcore_rename(struct diff_options *options) if (rename_dst[i].filespec_to_free) free_filespec(rename_dst[i].filespec_to_free); + cleanup_dir_rename_info(&info, dirs_removed, dir_rename_count != NULL); FREE_AND_NULL(rename_dst); rename_dst_nr = rename_dst_alloc = 0; FREE_AND_NULL(rename_src); @@ -690,3 +1338,8 @@ void diffcore_rename(struct diff_options *options) trace2_region_leave("diff", "write back to queue", options->repo); return; } + +void diffcore_rename(struct diff_options *options) +{ + diffcore_rename_extended(options, NULL, NULL); +} diff --git a/diffcore-rotate.c b/diffcore-rotate.c new file mode 100644 index 00000000000000..445f060ab0010e --- /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 d2a63c5c71f4e3..b9a230ab7fe6d0 100644 --- a/diffcore.h +++ b/diffcore.h @@ -8,6 +8,8 @@ struct diff_options; struct repository; +struct strmap; +struct strset; struct userdiff_driver; /* This header file is internal between diff.c and its diff transformers @@ -159,11 +161,17 @@ struct diff_filepair *diff_queue(struct diff_queue_struct *, struct diff_filespec *); void diff_q(struct diff_queue_struct *, struct diff_filepair *); +void partial_clear_dir_rename_count(struct strmap *dir_rename_count); + void diffcore_break(struct repository *, int); void diffcore_rename(struct diff_options *); +void diffcore_rename_extended(struct diff_options *options, + struct strset *dirs_removed, + struct strmap *dir_rename_count); 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 d153a63bbd14e1..3474e67e8f3c0f 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 facfae47402adc..04d886cfce75ed 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 a0532f1f00007b..33ce80ca91709b 100644 --- a/entry.c +++ b/entry.c @@ -282,7 +282,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,7 +309,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)); } /* @@ -354,7 +354,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,7 +365,7 @@ 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: @@ -530,7 +530,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/ewah/bitmap.c b/ewah/bitmap.c index 0d31cdc866c77d..38a47c44db4c6c 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 1eaedcb5dc2ed4..fb04a76ca26304 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -790,14 +790,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 +827,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 +846,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 +862,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 +879,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 +887,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 +920,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 +933,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); } @@ -949,6 +987,22 @@ static int cmp_ref_by_name(const void *a_, const void *b_) return strcmp(a->name, b->name); } +static void fsck_gitmodules_oids(struct oidset *gitmodules_oids) +{ + struct oidset_iter iter; + const struct object_id *oid; + struct fsck_options fo = FSCK_OPTIONS_STRICT; + + if (!oidset_size(gitmodules_oids)) + return; + + oidset_iter_init(gitmodules_oids, &iter); + while ((oid = oidset_iter_next(&iter))) + register_found_gitmodules(oid); + if (fsck_finish(&fo)) + die("fsck failed"); +} + static struct ref *do_fetch_pack(struct fetch_pack_args *args, int fd[2], const struct ref *orig_ref, @@ -963,6 +1017,7 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args, int agent_len; struct fetch_negotiator negotiator_alloc; struct fetch_negotiator *negotiator; + struct oidset gitmodules_oids = OIDSET_INIT; negotiator = &negotiator_alloc; fetch_negotiator_init(r, negotiator); @@ -1078,8 +1133,10 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args, alternate_shallow_file = setup_temporary_shallow(si->shallow); 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, + &gitmodules_oids)) die(_("git fetch-pack: fetch failed.")); + fsck_gitmodules_oids(&gitmodules_oids); all_done: if (negotiator) @@ -1529,6 +1586,8 @@ 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; + struct oidset gitmodules_oids = OIDSET_INIT; negotiator = &negotiator_alloc; fetch_negotiator_init(r, negotiator); @@ -1618,7 +1677,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, &gitmodules_oids)) die(_("git fetch-pack: fetch failed.")); do_check_stateless_delimiter(args, &reader); @@ -1630,6 +1690,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 +1700,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 +1721,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args, packname[the_hash_algo->hexsz] = '\0'; + parse_gitmodules_oids(cmd.out, &gitmodules_oids); + close(cmd.out); if (finish_command(&cmd)) @@ -1674,6 +1740,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args, packname)); } string_list_clear(&packfile_uris, 0); + strvec_clear(&index_pack_args); + + fsck_gitmodules_oids(&gitmodules_oids); if (negotiator) negotiator->release(negotiator); @@ -1847,7 +1916,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/fmt-merge-msg.c b/fmt-merge-msg.c index 46f6015c447dad..0f66818e0f839f 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 71134fdefaa561..e3030f3b358045 100644 --- a/fsck.c +++ b/fsck.c @@ -1276,6 +1276,11 @@ int fsck_error_function(struct fsck_options *o, return 1; } +void register_found_gitmodules(const struct object_id *oid) +{ + oidset_insert(&gitmodules_found, oid); +} + int fsck_finish(struct fsck_options *options) { int ret = 0; diff --git a/fsck.h b/fsck.h index 423c467feb7030..733378f1260a89 100644 --- a/fsck.h +++ b/fsck.h @@ -62,6 +62,8 @@ int fsck_walk(struct object *obj, void *data, struct fsck_options *options); int fsck_object(struct object *obj, void *data, unsigned long size, struct fsck_options *options); +void register_found_gitmodules(const struct object_id *oid); + /* * fsck a tag, and pass info about it back to the caller. This is * exposed fsck_object() internals for git-mktag(1). diff --git a/fsmonitor.c b/fsmonitor.c index fe9e9d7baf4450..ab9bfc60b34e31 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 739318ab6d1060..f20d72631d76b4 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-compat-util.h b/git-compat-util.h index 838246289c2902..9ddf9d7044bb65 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -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-filter-branch.sh b/git-filter-branch.sh index fea79646172184..cb893728136be3 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 161ee1ac8cb06a..7aebaf809d0e85 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.sh b/git-mergetool.sh index 911470a5b2c7aa..f751d9cfe20904 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -358,13 +358,8 @@ merge_file () { enabled=false fi else - # The user does not have a preference. Ask the tool. - if hide_resolved_enabled - then - enabled=true - else - enabled=false - fi + # The user does not have a preference. Default to disabled. + enabled=false fi if test "$enabled" = true diff --git a/git-send-email.perl b/git-send-email.perl index 1f425c08091d40..f5bbf1647e3bda 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/gpg-interface.c b/gpg-interface.c index b49927083661c8..127aecfc2b071f 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 f4e9b4f3715a0b..80567e4894868d 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 aabfaaa4c32e48..c5c348be55ddf0 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 ae89d6254b3e93..72f82b1e302397 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 5009471800e8c6..134d2eec804c2e 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 a03b4bae2221fc..b329bf63f097fa 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 c4ccc5fea93cff..fa642462a9e638 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 6a4a43e07f2cd4..b60d5fcc85dd18 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 8b23a546afdf40..406410f884e2e5 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 5de792ef3fe1dd..bf3d1270ad8e2f 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 d0b94f911eebfb..bb085d66d10509 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 75c8b1acfff865..51d93310a4dee1 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 9b50583dc05147..955a8a9535575f 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 4ec0041cfb4eca..39e2f153336949 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 1ec0b959e015b2..9a8a2c365c7a33 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 e7dab999508399..f3178a66a95bb1 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; } @@ -962,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); @@ -984,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/mailmap.c b/mailmap.c index 9bb9cf8b3038aa..d1f7c0d272d81c 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); @@ -226,10 +236,12 @@ int read_mailmap(struct string_list *map) git_mailmap_blob = "HEAD:.mailmap"; if (!startup_info->have_repository || !is_bare_repository()) - err |= read_mailmap_file(map, ".mailmap"); + 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 8401761dda0a2a..ccdcad2e3d622f 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 931b91438cf1bb..ba35600d4e0d93 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -351,17 +351,11 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti, /* Free memory used by various renames maps */ for (i = MERGE_SIDE1; i <= MERGE_SIDE2; ++i) { - struct hashmap_iter iter; - struct strmap_entry *entry; - strset_func(&renames->dirs_removed[i]); - strmap_for_each_entry(&renames->dir_rename_count[i], - &iter, entry) { - struct strintmap *counts = entry->value; - strintmap_clear(counts); - } - strmap_func(&renames->dir_rename_count[i], 1); + partial_clear_dir_rename_count(&renames->dir_rename_count[i]); + if (!reinitialize) + strmap_clear(&renames->dir_rename_count[i], 1); strmap_func(&renames->dir_renames[i], 0); } @@ -535,6 +529,23 @@ static void setup_path_info(struct merge_options *opt, result->util = mi; } +static void add_pair(struct merge_options *opt, + struct name_entry *names, + const char *pathname, + unsigned side, + unsigned is_add /* if false, is_delete */) +{ + struct diff_filespec *one, *two; + struct rename_info *renames = &opt->priv->renames; + int names_idx = is_add ? side : 0; + + one = alloc_filespec(pathname); + two = alloc_filespec(pathname); + fill_filespec(is_add ? two : one, + &names[names_idx].oid, 1, names[names_idx].mode); + diff_queue(&renames->pairs[side], one, two); +} + static void collect_rename_info(struct merge_options *opt, struct name_entry *names, const char *dirname, @@ -544,6 +555,7 @@ static void collect_rename_info(struct merge_options *opt, unsigned match_mask) { struct rename_info *renames = &opt->priv->renames; + unsigned side; /* Update dirs_removed, as needed */ if (dirmask == 1 || dirmask == 3 || dirmask == 5) { @@ -554,6 +566,21 @@ static void collect_rename_info(struct merge_options *opt, if (sides & 2) strset_add(&renames->dirs_removed[2], fullname); } + + if (filemask == 0 || filemask == 7) + return; + + for (side = MERGE_SIDE1; side <= MERGE_SIDE2; ++side) { + unsigned side_mask = (1 << side); + + /* Check for deletion on side */ + if ((filemask & 1) && !(filemask & side_mask)) + add_pair(opt, names, fullname, side, 0 /* delete */); + + /* Check for addition on side */ + if (!(filemask & 1) && (filemask & side_mask)) + add_pair(opt, names, fullname, side, 1 /* add */); + } } static int collect_merge_info_callback(int n, @@ -1269,131 +1296,6 @@ static char *handle_path_level_conflicts(struct merge_options *opt, return new_path; } -static void dirname_munge(char *filename) -{ - char *slash = strrchr(filename, '/'); - if (!slash) - slash = filename; - *slash = '\0'; -} - -static void increment_count(struct strmap *dir_rename_count, - char *old_dir, - char *new_dir) -{ - struct strintmap *counts; - struct strmap_entry *e; - - /* Get the {new_dirs -> counts} mapping using old_dir */ - e = strmap_get_entry(dir_rename_count, old_dir); - if (e) { - counts = e->value; - } else { - counts = xmalloc(sizeof(*counts)); - strintmap_init_with_options(counts, 0, NULL, 1); - strmap_put(dir_rename_count, old_dir, counts); - } - - /* Increment the count for new_dir */ - strintmap_incr(counts, new_dir, 1); -} - -static void update_dir_rename_counts(struct strmap *dir_rename_count, - struct strset *dirs_removed, - const char *oldname, - const char *newname) -{ - char *old_dir = xstrdup(oldname); - char *new_dir = xstrdup(newname); - char new_dir_first_char = new_dir[0]; - int first_time_in_loop = 1; - - while (1) { - dirname_munge(old_dir); - dirname_munge(new_dir); - - /* - * When renaming - * "a/b/c/d/e/foo.c" -> "a/b/some/thing/else/e/foo.c" - * then this suggests that both - * a/b/c/d/e/ => a/b/some/thing/else/e/ - * a/b/c/d/ => a/b/some/thing/else/ - * so we want to increment counters for both. We do NOT, - * however, also want to suggest that there was the following - * rename: - * a/b/c/ => a/b/some/thing/ - * so we need to quit at that point. - * - * Note the when first_time_in_loop, we only strip off the - * basename, and we don't care if that's different. - */ - if (!first_time_in_loop) { - char *old_sub_dir = strchr(old_dir, '\0')+1; - char *new_sub_dir = strchr(new_dir, '\0')+1; - if (!*new_dir) { - /* - * Special case when renaming to root directory, - * i.e. when new_dir == "". In this case, we had - * something like - * a/b/subdir => subdir - * and so dirname_munge() sets things up so that - * old_dir = "a/b\0subdir\0" - * new_dir = "\0ubdir\0" - * We didn't have a '/' to overwrite a '\0' onto - * in new_dir, so we have to compare differently. - */ - if (new_dir_first_char != old_sub_dir[0] || - strcmp(old_sub_dir+1, new_sub_dir)) - break; - } else { - if (strcmp(old_sub_dir, new_sub_dir)) - break; - } - } - - if (strset_contains(dirs_removed, old_dir)) - increment_count(dir_rename_count, old_dir, new_dir); - else - break; - - /* If we hit toplevel directory ("") for old or new dir, quit */ - if (!*old_dir || !*new_dir) - break; - - first_time_in_loop = 0; - } - - /* Free resources we don't need anymore */ - free(old_dir); - free(new_dir); -} - -static void compute_rename_counts(struct diff_queue_struct *pairs, - struct strmap *dir_rename_count, - struct strset *dirs_removed) -{ - int i; - - for (i = 0; i < pairs->nr; ++i) { - struct diff_filepair *pair = pairs->queue[i]; - - /* File not part of directory rename if it wasn't renamed */ - if (pair->status != 'R') - continue; - - /* - * Make dir_rename_count contain a map of a map: - * old_directory -> {new_directory -> count} - * In other words, for every pair look at the directories for - * the old filename and the new filename and count how many - * times that pairing occurs. - */ - update_dir_rename_counts(dir_rename_count, dirs_removed, - pair->one->path, - pair->two->path); - } -} - static void get_provisional_directory_renames(struct merge_options *opt, unsigned side, int *clean) @@ -1402,9 +1304,6 @@ static void get_provisional_directory_renames(struct merge_options *opt, struct strmap_entry *entry; struct rename_info *renames = &opt->priv->renames; - compute_rename_counts(&renames->pairs[side], - &renames->dir_rename_count[side], - &renames->dirs_removed[side]); /* * Collapse * dir_rename_count: old_directory -> {new_directory -> count} @@ -1543,8 +1442,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); } @@ -1685,7 +1583,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); @@ -2079,6 +1977,27 @@ static int process_renames(struct merge_options *opt, return clean_merge; } +static void resolve_diffpair_statuses(struct diff_queue_struct *q) +{ + /* + * A simplified version of diff_resolve_rename_copy(); would probably + * just use that function but it's static... + */ + int i; + struct diff_filepair *p; + + for (i = 0; i < q->nr; ++i) { + p = q->queue[i]; + p->status = 0; /* undecided */ + if (!DIFF_FILE_VALID(p->one)) + p->status = DIFF_STATUS_ADDED; + else if (!DIFF_FILE_VALID(p->two)) + p->status = DIFF_STATUS_DELETED; + else if (DIFF_PAIR_RENAME(p)) + p->status = DIFF_STATUS_RENAMED; + } +} + static int compare_pairs(const void *a_, const void *b_) { const struct diff_filepair *a = *((const struct diff_filepair **)a_); @@ -2089,8 +2008,6 @@ static int compare_pairs(const void *a_, const void *b_) /* Call diffcore_rename() to compute which files have changed on given side */ static void detect_regular_renames(struct merge_options *opt, - struct tree *merge_base, - struct tree *side, unsigned side_index) { struct diff_options diff_opts; @@ -2108,11 +2025,13 @@ static void detect_regular_renames(struct merge_options *opt, diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; diff_setup_done(&diff_opts); + diff_queued_diff = renames->pairs[side_index]; trace2_region_enter("diff", "diffcore_rename", opt->repo); - diff_tree_oid(&merge_base->object.oid, &side->object.oid, "", - &diff_opts); - diffcore_std(&diff_opts); + diffcore_rename_extended(&diff_opts, + &renames->dirs_removed[side_index], + &renames->dir_rename_count[side_index]); trace2_region_leave("diff", "diffcore_rename", opt->repo); + resolve_diffpair_statuses(&diff_queued_diff); if (diff_opts.needed_rename_limit > renames->needed_limit) renames->needed_limit = diff_opts.needed_rename_limit; @@ -2212,8 +2131,8 @@ static int detect_and_process_renames(struct merge_options *opt, memset(&combined, 0, sizeof(combined)); trace2_region_enter("merge", "regular renames", opt->repo); - detect_regular_renames(opt, merge_base, side1, MERGE_SIDE1); - detect_regular_renames(opt, merge_base, side2, MERGE_SIDE2); + detect_regular_renames(opt, MERGE_SIDE1); + detect_regular_renames(opt, MERGE_SIDE2); trace2_region_leave("merge", "regular renames", opt->repo); trace2_region_enter("merge", "directory renames", opt->repo); @@ -2651,7 +2570,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 @@ -3031,7 +2950,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 b052974f191cd8..ed31f9496cbcb2 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 abc8ce4ec44e19..96f6209a04106f 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 05c40a98e05390..becfafe65e8029 100644 --- a/midx.c +++ b/midx.c @@ -11,6 +11,7 @@ #include "trace2.h" #include "run-command.h" #include "repository.h" +#include "chunk-format.h" #define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */ #define MIDX_VERSION 1 @@ -21,14 +22,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)) @@ -53,6 +52,19 @@ static char *get_midx_filename(const char *object_dir) return xstrfmt("%s/pack/multi-pack-index", object_dir); } +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 +76,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 +126,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); - - 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; - } - } + cf = init_chunkfile(NULL); + + 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 +168,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) @@ -265,7 +244,7 @@ static 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) { @@ -281,7 +260,8 @@ static off_t nth_midxed_offset(struct multi_pack_index *m, uint32_t pos) static 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 +431,56 @@ static int pack_info_compare(const void *_a, const void *_b) return strcmp(a->pack_name, b->pack_name); } -struct pack_list { +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; + unsigned large_offsets_needed:1; + uint32_t num_large_offsets; }; 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++; } } @@ -643,27 +630,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 +658,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 +689,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 +712,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 +768,26 @@ 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; } static int write_midx_internal(const char *object_dir, struct multi_pack_index *m, struct string_list *packs_to_drop, unsigned flags) { - unsigned char cur_chunk, num_chunks = 0; char *midx_name; 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 +795,62 @@ 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.entries = get_sorted_entries(ctx.m, ctx.info, ctx.nr, &ctx.entries_nr); - 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 +858,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 +874,19 @@ 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; } if (pack_name_concat_len % MIDX_CHUNK_ALIGNMENT) @@ -921,123 +897,52 @@ static int write_midx_internal(const char *object_dir, struct multi_pack_index * f = hashfd(get_lock_file_fd(&lk), get_lock_file_path(&lk)); FREE_AND_NULL(midx_name); - if (packs.m) - close_midx(packs.m); - - cur_chunk = 0; - num_chunks = large_offsets_needed ? 5 : 4; + if (ctx.m) + close_midx(ctx.m); - 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; - - cur_chunk++; - chunk_ids[cur_chunk] = MIDX_CHUNKID_OBJECTOFFSETS; - chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + nr_entries * the_hash_algo->rawsz; - - 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; - - cur_chunk++; - chunk_offsets[cur_chunk] = chunk_offsets[cur_chunk - 1] + - num_large_offsets * MIDX_CHUNK_LARGE_OFFSET_WIDTH; - } - - chunk_ids[cur_chunk] = 0; + cf = init_chunkfile(f); - 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]); + 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); - if (chunk_offsets[i] % MIDX_CHUNK_ALIGNMENT) - BUG("chunk offset %"PRIu64" is not properly aligned", - chunk_offsets[i]); - - hashwrite_be32(f, chunk_ids[i]); - hashwrite_be64(f, chunk_offsets[i]); - - written += MIDX_CHUNKLOOKUP_WIDTH; - } - - 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; - - case MIDX_CHUNKID_OBJECTOFFSETS: - written += write_midx_object_offsets(f, large_offsets_needed, pack_perm, entries, nr_entries); - break; - - case MIDX_CHUNKID_LARGEOFFSETS: - written += write_midx_large_offsets(f, num_large_offsets, entries, nr_entries); - break; - - default: - BUG("trying to write unknown chunk id %"PRIx32, - chunk_ids[i]); - } - - display_progress(progress, i + 1); - } - stop_progress(&progress); + 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); - if (written != chunk_offsets[num_chunks]) - BUG("incorrect final offset %"PRIu64" != %"PRIu64, - written, - chunk_offsets[num_chunks]); + write_midx_header(f, get_num_chunks(cf), ctx.nr - dropped_packs); + write_chunkfile(cf, &ctx); finalize_hashfile(f, NULL, CSUM_FSYNC | CSUM_HASH_IN_STREAM); + free_chunkfile(cf); commit_lock_file(&lk); cleanup: - for (i = 0; i < packs.nr; i++) { - if (packs.info[i].p) { - close_pack(packs.info[i].p); - free(packs.info[i].p); + for (i = 0; i < ctx.nr; i++) { + if (ctx.info[i].p) { + close_pack(ctx.info[i].p); + free(ctx.info[i].p); } - free(packs.info[i].pack_name); + free(ctx.info[i].pack_name); } - free(packs.info); - free(entries); - free(pack_perm); + free(ctx.info); + free(ctx.entries); + free(ctx.pack_perm); free(midx_name); return result; } @@ -1239,7 +1144,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"), @@ -1410,7 +1315,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)) diff --git a/name-hash.c b/name-hash.c index 4e03fac9bb1259..ce28f3f070827f 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 4b78f6bf36a047..434189ae5dc64a 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 dffbc76c49ef79..1236e7922484a2 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 2fe724f1cf8dc3..d2771fa3d43ced 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 4bf4888d8c1f2c..d7d18e30f5a281 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 d5ac081e76df9c..a19e4ad7943433 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 5bcfde84718868..624af408cdcd2a 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 541dab0858623b..ec32c23dcb5615 100644 --- a/object-store.h +++ b/object-store.h @@ -153,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 98017bed8efb7e..78343781ae77d6 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 60fe20fb87a42e..1ebe0c81628a35 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; @@ -1388,7 +1388,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 +1430,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 25dfcf56156bf8..36d99930d8d528 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 f2a433885ac80e..fe2a4eace9910b 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 83fe4de77382a1..4262530449aa05 100644 --- a/pack-revindex.c +++ b/pack-revindex.c @@ -253,7 +253,8 @@ static int load_revindex_from_disk(char *revindex_name, *data_p = (const uint32_t *)data; } - close(fd); + if (fd >= 0) + close(fd); return ret; } diff --git a/pack-write.c b/pack-write.c index 680c36755dd2f1..2ca85a9d1667b8 100644 --- a/pack-write.c +++ b/pack-write.c @@ -380,7 +380,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; @@ -394,11 +394,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; } diff --git a/pack.h b/pack.h index afdcf8f5c74df6..857cbd5bd4b55a 100644 --- a/pack.h +++ b/pack.h @@ -87,7 +87,7 @@ 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; diff --git a/packfile.c b/packfile.c index 1fec12ac5f4c74..6661f3325a461f 100644 --- a/packfile.c +++ b/packfile.c @@ -638,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) @@ -2066,12 +2066,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 4cfec9e8d3cc3e..3ae117a8aef0fa 100644 --- a/packfile.h +++ b/packfile.h @@ -162,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 ee435de67562dc..3d37dd7adaa27e 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/patch-ids.c b/patch-ids.c index 3f404e4b0b4cc2..8bf425555de252 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 7a229d8d22f2f6..18b3be362ae988 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 02eacef0c2a4a4..73ebbf80cc6f42 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 d633005ef746ad..0194137528c3ce 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 8c90daa59ef0ca..5af5f456876841 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 677cece1024d73..a32beb67423996 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 d73e84c8f8ffe4..529ea97bd9fbc1 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