diff options
286 files changed, 10030 insertions, 5113 deletions
diff --git a/.gitignore b/.gitignore index 04c444404e..802ce70e48 100644 --- a/.gitignore +++ b/.gitignore @@ -87,6 +87,7 @@ /git-init-db /git-interpret-trailers /git-instaweb +/git-last-modified /git-log /git-ls-files /git-ls-remote @@ -139,6 +140,7 @@ /git-repack /git-replace /git-replay +/git-repo /git-request-pull /git-rerere /git-reset diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index af10ebb59a..cf122e706f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -119,6 +119,7 @@ build:mingw64: variables: NO_PERL: 1 before_script: + - Set-MpPreference -DisableRealtimeMonitoring $true - ./ci/install-sdk.ps1 -directory "git-sdk" script: - git-sdk/usr/bin/bash.exe -l -c 'ci/make-test-artifacts.sh artifacts' @@ -135,6 +136,7 @@ test:mingw64: - job: "build:mingw64" artifacts: true before_script: + - Set-MpPreference -DisableRealtimeMonitoring $true - git-sdk/usr/bin/bash.exe -l -c 'tar xf artifacts/artifacts.tar.gz' - New-Item -Path .git/info -ItemType Directory - New-Item .git/info/exclude -ItemType File -Value "/git-sdk" @@ -148,6 +150,7 @@ test:mingw64: tags: - saas-windows-medium-amd64 before_script: + - Set-MpPreference -DisableRealtimeMonitoring $true - choco install -y git meson ninja openssl - Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1 - refreshenv @@ -81,6 +81,8 @@ Fredrik Kuivinen <frekui@gmail.com> <freku045@student.liu.se> Frédéric Heitzmann <frederic.heitzmann@gmail.com> Garry Dolley <gdolley@ucla.edu> <gdolley@arpnetworks.com> Glen Choo <glencbz@gmail.com> <chooglen@google.com> +Greg Hurrell <greg@hurrell.net> <greg.hurrell@datadoghq.com> +Greg Hurrell <greg@hurrell.net> <win@wincent.com> Greg Price <price@mit.edu> <price@MIT.EDU> Greg Price <price@mit.edu> <price@ksplice.com> Heiko Voigt <hvoigt@hvoigt.net> <git-list@hvoigt.net> diff --git a/Documentation/BreakingChanges.adoc b/Documentation/BreakingChanges.adoc index f8d2eba061..344ce50060 100644 --- a/Documentation/BreakingChanges.adoc +++ b/Documentation/BreakingChanges.adoc @@ -239,6 +239,11 @@ These features will be removed. + The command will be removed. +* Support for `core.commentString=auto` has been deprecated and will + be removed in Git 3.0. ++ +cf. <xmqqa59i45wc.fsf@gitster.g> + == Superseded features that will not be deprecated Some features have gained newer replacements that aim to improve the design in diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index 224f0978a8..df72fe0177 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines @@ -650,6 +650,12 @@ For C programs: cases. However, it is recommended to find a more descriptive name wherever possible to improve the readability and maintainability of the code. + - Bit fields should be defined without a space around the colon. E.g. + + unsigned my_field:1; + unsigned other_field:1; + unsigned field_with_longer_name:1; + For Perl programs: - Most of the C guidelines above apply. diff --git a/Documentation/Makefile b/Documentation/Makefile index df2ce187eb..6fb83d0c6e 100644 --- a/Documentation/Makefile +++ b/Documentation/Makefile @@ -497,9 +497,26 @@ $(LINT_DOCS_FSCK_MSGIDS): ../fsck.h fsck-msgids.adoc $(call mkdir_p_parent_template) $(QUIET_GEN)$(PERL_PATH) lint-fsck-msgids.perl \ ../fsck.h fsck-msgids.adoc $@ - lint-docs-fsck-msgids: $(LINT_DOCS_FSCK_MSGIDS) +## Lint: delimited sections +LINT_DOCS_DELIMITED_SECTIONS = $(patsubst %.adoc,.build/lint-docs/delimited-sections/%.ok,$(MAN_TXT)) +$(LINT_DOCS_DELIMITED_SECTIONS): lint-delimited-sections.perl +$(LINT_DOCS_DELIMITED_SECTIONS): .build/lint-docs/delimited-sections/%.ok: %.adoc + $(call mkdir_p_parent_template) + $(QUIET_LINT_DELIMSEC)$(PERL_PATH) lint-delimited-sections.perl $< >$@ +.PHONY: lint-docs-delimited-sections +lint-docs-delimited-sections: $(LINT_DOCS_DELIMITED_SECTIONS) + +## Lint: Documentation style +LINT_DOCS_DOC_STYLE = $(patsubst %.adoc,.build/lint-docs/doc-style/%.ok,$(DOC_DEP_TXT)) +$(LINT_DOCS_DOC_STYLE): lint-documentation-style.perl +$(LINT_DOCS_DOC_STYLE): .build/lint-docs/doc-style/%.ok: %.adoc + $(call mkdir_p_parent_template) + $(QUIET_LINT_DOCSTYLE)$(PERL_PATH) lint-documentation-style.perl $< >$@ +.PHONY: lint-docs-doc-style +lint-docs-doc-style: $(LINT_DOCS_DOC_STYLE) + lint-docs-manpages: $(QUIET_GEN)./lint-manpages.sh @@ -528,6 +545,8 @@ lint-docs: lint-docs-fsck-msgids lint-docs: lint-docs-gitlink lint-docs: lint-docs-man-end-blurb lint-docs: lint-docs-man-section-order +lint-docs: lint-docs-delimited-sections +lint-docs: lint-docs-doc-style lint-docs: lint-docs-manpages lint-docs: lint-docs-meson diff --git a/Documentation/MyFirstContribution.adoc b/Documentation/MyFirstContribution.adoc index aca7212cfe..02ba8ba5f6 100644 --- a/Documentation/MyFirstContribution.adoc +++ b/Documentation/MyFirstContribution.adoc @@ -52,6 +52,15 @@ respond to you. It's better to ask your questions in the channel so that you can be answered if you disconnect and so that others can learn from the conversation. +==== https://discord.gg/GRFVkzgxRd[#discord] on Discord +This is an unofficial Git Discord server for everyone, from people just +starting out with Git to those who develop it. It's a great place to ask +questions, share tips, and connect with the broader Git community in real time. + +The server has channels for general discussions and specific channels for those +who use Git and those who develop it. The server's search functionality also +allows you to find previous conversations and answers to common questions. + [[getting-started]] == Getting Started @@ -908,10 +917,13 @@ Now you should be able to go and check out your newly created branch on GitHub. === Sending a PR to GitGitGadget In order to have your code tested and formatted for review, you need to start by -opening a Pull Request against `gitgitgadget/git`. Head to -https://github.com/gitgitgadget/git and open a PR either with the "New pull -request" button or the convenient "Compare & pull request" button that may -appear with the name of your newly pushed branch. +opening a Pull Request against either `gitgitgadget/git` or `git/git`. Head to +https://github.com/gitgitgadget/git or https://github.com/git/git and open a PR +either with the "New pull request" button or the convenient "Compare & pull +request" button that may appear with the name of your newly pushed branch. + +The differences between using `gitgitgadget/git` and `git/git` as your base can +be found [here](https://gitgitgadget.github.io/#should-i-use-gitgitgadget-on-gitgitgadgets-git-fork-or-on-gits-github-mirror) Review the PR's title and description, as they're used by GitGitGadget respectively as the subject and body of the cover letter for your change. Refer diff --git a/Documentation/RelNotes/1.6.2.4.adoc b/Documentation/RelNotes/1.6.2.4.adoc index f4bf1d0986..053dbb604d 100644 --- a/Documentation/RelNotes/1.6.2.4.adoc +++ b/Documentation/RelNotes/1.6.2.4.adoc @@ -37,3 +37,4 @@ exec >/var/tmp/1 echo O=$(git describe maint) O=v1.6.2.3-38-g318b847 git shortlog --no-merges $O..maint +--- diff --git a/Documentation/RelNotes/2.52.0.adoc b/Documentation/RelNotes/2.52.0.adoc new file mode 100644 index 0000000000..c4fc561631 --- /dev/null +++ b/Documentation/RelNotes/2.52.0.adoc @@ -0,0 +1,244 @@ +Git v2.52 Release Notes +======================= + +UI, Workflows & Features +------------------------ + + * The "list" subcommand of "git refs" acts as a front-end for + "git for-each-ref". + + * "git cmd --help-all" now works outside repositories. + + * "git diff-tree" learned "--max-depth" option. + + * A new subcommand "git repo" gives users a way to grab various + repository characteristics. + + * A new command "git last-modified" has been added to show the closest + ancestor commit that touched each path. + + * "git refs exists" that works like "git show-ref --exists" has been + added. + + * "repo info" learns a short-hand option "-z" that is the same as + "--format=nul", and learns to report the objects format used in the + repository. + + * "core.commentChar=auto" that attempts to dynamically pick a + suitable comment character is non-workable, as it is too much + trouble to support for little benefit, and is marked as deprecated. + + * "git send-email" learned to drive "git imap-send" to store already + sent e-mails in an IMAP folder. + + * The "promisor-remote" capability mechanism has been updated to + allow the "partialCloneFilter" settings and the "token" value to be + communicated from the server side. + + +Performance, Internal Implementation, Development Support etc. +-------------------------------------------------------------- + + * string_list_split*() family of functions have been extended to + simplify common use cases. + + * Arrays of strbuf is often a wrong data structure to use, and + strbuf_split*() family of functions that create them often have + better alternatives. Update several code paths and replace + strbuf_split*(). + + * Revision traversal limited with pathspec, like "git log dir/*", + used to ignore changed-paths Bloom filter when the pathspec + contained wildcards; now they take advantage of the filter when + they can. + + * Doc lint updates to encourage the newer and easier-to-use + `synopsis` format, with fixes to a handful of existing uses. + + * Remove dependency on the_repository and other globals from the + commit-graph code, and other changes unrelated to de-globaling. + + * Discord has been added to the first contribution documentation as + another way to ask for help. + + * Inspired by Ezekiel's recent effort to showcase Rust interface, the + hash function implementation used to hash lines have been updated + to the one used for ELF symbol lookup by Glibc. + + * Instead of scanning for the remaining items to see if there are + still commits to be explored in the queue, use khash to remember + which items are still on the queue (an unacceptable alternative is + to reserve one object flag bits). + + * The bulk-checkin code used to depend on a file-scope static + singleton variable, which has been updated to pass an instance + throughout the callchain. + + * CodingGuidelines now spells out how bitfields are to be written. + + * Adjust to the way newer versions of cURL selectivel enables tracing + options, so that our tests can continue to work. + (merge 1b5a6bfff3 jk/curl-global-trace-components later to maint). + + * The clear_alloc_state() API function was not fully clearing the + structure for reuse, but since nobody reuses it, replace it with a + variant that frees the structure as well, making the callers simpler. + + * "git range-diff" learned a way to limit the memory consumed by + O(N*N) cost matrix. + + +Fixes since v2.51 +----------------- + +Unless otherwise noted, all the changes in 2.51.X maintenance track, +including security updates, are included in this release. + + * During interactive rebase, using 'drop' on a merge commit lead to + an error, which was incorrect. + (merge 4d491ade8f js/rebase-i-allow-drop-on-a-merge later to maint). + + * "git refs migrate" to migrate the reflog entries from a refs + backend to another had a handful of bugs squashed. + (merge 465eff81de ps/reflog-migrate-fixes later to maint). + + * "git remote rename origin upstream" failed to move origin/HEAD to + upstream/HEAD when origin/HEAD is unborn and performed other + renames extremely inefficiently, which has been corrected. + (merge 16c4fa26b9 ps/remote-rename-fix later to maint). + + * "git describe" has been optimized by using better data structure. + (merge 08bb69d70f rs/describe-with-prio-queue later to maint). + + * "git push" had a code path that led to BUG() but it should have + been a die(), as it is a response to a usual but invalid end-user + action to attempt pushing an object that does not exist. + (merge dfbfc2221b dl/push-missing-object-error later to maint). + + * Various bugs about rename handling in "ort" merge strategy have + been fixed. + (merge f6ecb603ff en/ort-rename-fixes later to maint). + + * "git jump" (in contrib/) fails to parse the diff header correctly + when a file has a space in its name, which has been corrected. + (merge 621ce9c1c6 gh/git-jump-pathname-with-sp later to maint). + + * "git diff --no-index" run inside a subdirectory under control of a + Git repository operated at the top of the working tree and stripped + the prefix from the output, and oddballs like "-" (stdin) did not + work correctly because of it. Correct the set-up by undoing what + the set-up sequence did to cwd and prefix. + (merge e1d3d61a45 jc/diff-no-index-in-subdir later to maint). + + * Various options to "git diff" that makes comparison ignore certain + aspects of the differences (like "space changes are ignored", + "differences in lines that match these regular expressions are + ignored") did not work well with "--name-only" and friends. + (merge b55e6d36eb ly/diff-name-only-with-diff-from-content later to maint). + + * Documentation for "git rebase" has been updated. + (merge 3f7f2b0359 je/doc-rebase later to maint). + + * The start_delayed_progress() function in the progress eye-candy API + did not clear its internal state, making an initial delay value + larger than 1 second ineffective, which has been corrected. + (merge 457534d041 js/progress-delay-fix later to maint). + + * The compatObjectFormat extension is used to hide an incomplete + feature that is not yet usable for any purpose other than + developing the feature further. Document it as such to discourage + its use by mere mortals. + (merge 716d905792 bc/doc-compat-object-format-not-working later to maint). + + * "git log -L..." compared trees of multiple parents with the tree of the + merge result in an unnecessarily inefficient way. + (merge 0a15bb634c sg/line-log-merge-optim later to maint). + + * Under a race against another process that is repacking the + repository, especially a partially cloned one, "git fetch" may + mistakenly think some objects we do have are missing, which has + been corrected. + (merge 8f32a5a6c0 jk/fetch-check-graph-objects-fix later to maint). + + * "git fetch" can clobber a symref that is dangling when the + remote-tracking HEAD is set to auto update, which has been + corrected. + + * "git describe <blob>" misbehaves and/or crashes in some corner + cases, which has been taught to exit with failure gracefully. + (merge 7c10e48e81 jk/describe-blob later to maint). + + * Manual page for "gitk" is updated with the current maintainer's + name. + (merge bcb20dda83 js/doc-gitk-history later to maint). + + * Update the instruction to use of GGG in the MyFirstContribution + document to say that a GitHub PR could be made against `git/git` + instead of `gitgitgadget/git`. + (merge 37001cdbc4 ds/doc-ggg-pr-fork-clarify later to maint). + + * Makefile tried to run multiple "cargo build" which would not work + very well; serialize their execution to work it around. + (merge 0eeacde50e da/cargo-serialize later to maint). + + * "git repack --path-walk" lost objects in some corner cases, which + has been corrected. + (merge 93afe9b060 ds/path-walk-repack-fix later to maint). + + * "git ls-files <pathspec>..." should not necessarily have to expand + the index fully if a sparsified directory is excluded by the + pathspec; the code is taught to expand the index on demand to avoid + this. + (merge 681f26bccc ds/ls-files-lazy-unsparse later to maint). + + * Windows "real-time monitoring" interferes with the execution of + tests and affects negatively in both correctness and performance, + which has been disabled in Gitlab CI. + (merge 608cf5b793 ps/gitlab-ci-disable-windows-monitoring later to maint). + + * A broken or malicious "git fetch" can say that it has the same + object for many many times, and the upload-pack serving it can + exhaust memory storing them redundantly, which has been corrected. + (merge 88a2dc68c8 ps/upload-pack-oom-protection later to maint). + + * A corner case bug in "git log -L..." has been corrected. + (merge e3106998ff sg/line-log-boundary-fixes later to maint). + + * "git rev-parse --short" and friends failed to disambiguate two + objects with object names that share common prefix longer than 32 + characters, which has been fixed. + (merge 8655908b9e jc/longer-disambiguation-fix later to maint). + + * Some among "git add -p" and friends ignored color.diff and/or + color.ui configuration variables, which is an old regression, which + has been corrected. + (merge 1092cd6435 jk/add-i-color later to maint). + + * "git subtree" (in contrib/) did not work correctly when splitting + squashed subtrees, which has been improved. + + * Other code cleanup, docfix, build fix, etc. + (merge 823d537fa7 kh/doc-git-log-markup-fix later to maint). + (merge cf7efa4f33 rj/t6137-cygwin-fix later to maint). + (merge 529a60a885 ua/t1517-short-help-tests later to maint). + (merge 22d421fed9 ac/deglobal-fmt-merge-log-config later to maint). + (merge 741f36c7d9 kr/clone-synopsis-fix later to maint). + (merge a60203a015 dk/t7005-editor-updates later to maint). + (merge 7d4a5fef7d ds/doc-count-objects-fix later to maint). + (merge 16684b6fae ps/reftable-libgit2-cleanup later to maint). + (merge f38786baa7 ja/asciidoc-doctor-verbatim-fixes later to maint). + (merge 374579c6d4 kh/doc-interpret-trailers-markup-fix later to maint). + (merge 44dce6541c kh/doc-config-typofix later to maint). + (merge 785628b173 js/doc-sending-patch-via-thunderbird later to maint). + (merge e5c27bd3d8 je/doc-add later to maint). + (merge 13296ac909 ps/object-store-midx-dedup-info later to maint). + (merge 2f4bf83ffc km/alias-doc-markup-fix later to maint). + (merge b0d97aac19 kh/doc-markup-fixes later to maint). + (merge f9a6705d9a tc/t0450-harden later to maint). + (merge c25651aefd ds/midx-write-fixes later to maint). + (merge 069c15d256 rs/object-name-extend-abbrev-len-update later to maint). + (merge bf5c224537 mm/worktree-doc-typofix later to maint). + (merge 31397bc4f7 kh/doc-fast-import-markup-fix later to maint). + (merge ac7096723b jc/doc-includeif-hasconfig-remote-url-fix later to maint). + (merge fafc9b08b8 ag/doc-sendmail-gmail-example-update later to maint). + (merge a66fc22bf9 rs/get-oid-with-flags-cleanup later to maint). diff --git a/Documentation/blame-options.adoc b/Documentation/blame-options.adoc index 19ea187238..1fb948fc76 100644 --- a/Documentation/blame-options.adoc +++ b/Documentation/blame-options.adoc @@ -75,7 +75,8 @@ include::line-range-format.adoc[] iso format is used. For supported values, see the discussion of the --date option at linkgit:git-log[1]. ---[no-]progress:: +--progress:: +--no-progress:: Progress status is reported on the standard error stream by default when it is attached to a terminal. This flag enables progress reporting even if not attached to a diff --git a/Documentation/config.adoc b/Documentation/config.adoc index cc769251be..05f1ca7293 100644 --- a/Documentation/config.adoc +++ b/Documentation/config.adoc @@ -114,8 +114,7 @@ whose format and meaning depends on the keyword. Supported keywords are: `gitdir`:: - - The data that follows the keyword `gitdir:` is used as a glob + The data that follows the keyword `gitdir` and a colon is used as a glob pattern. If the location of the .git directory matches the pattern, the include condition is met. + @@ -148,7 +147,7 @@ refer to linkgit:gitignore[5] for details. For convenience: case-insensitively (e.g. on case-insensitive file systems) `onbranch`:: - The data that follows the keyword `onbranch:` is taken to be a + The data that follows the keyword `onbranch` and a colon is taken to be a pattern with standard globbing wildcards and two additional ones, `**/` and `/**`, that can match multiple path components. If we are in a worktree where the name of the branch that is @@ -161,8 +160,8 @@ all branches that begin with `foo/`. This is useful if your branches are organized hierarchically and you would like to apply a configuration to all the branches in that hierarchy. -`hasconfig:remote.*.url:`:: - The data that follows this keyword is taken to +`hasconfig:remote.*.url`:: + The data that follows this keyword and a colon is taken to be a pattern with standard globbing wildcards and two additional ones, `**/` and `/**`, that can match multiple components. The first time this keyword is seen, the rest of diff --git a/Documentation/config/alias.adoc b/Documentation/config/alias.adoc index 2c5db0ad84..95825354bf 100644 --- a/Documentation/config/alias.adoc +++ b/Documentation/config/alias.adoc @@ -38,6 +38,6 @@ it will be treated as a shell command. For example, defining ** A convenient way to deal with this is to write your script operations in an inline function that is then called with any arguments from the command-line. For example `alias.cmd = "!c() { - echo $1 | grep $2 ; }; c" will correctly execute the prior example. + echo $1 | grep $2 ; }; c"` will correctly execute the prior example. ** Setting `GIT_TRACE=1` can help you debug the command being run for your alias. diff --git a/Documentation/config/core.adoc b/Documentation/config/core.adoc index 3fbe83eef1..08739bb9d4 100644 --- a/Documentation/config/core.adoc +++ b/Documentation/config/core.adoc @@ -531,9 +531,25 @@ core.commentString:: commented, and removes them after the editor returns (default '#'). + -If set to "auto", `git-commit` would select a character that is not +ifndef::with-breaking-changes[] +If set to "auto", `git-commit` will select a character that is not the beginning character of any line in existing commit messages. -+ +Support for this value is deprecated and will be removed in Git 3.0 +due to the following limitations: ++ +-- +* It is incompatible with adding comments in a commit message + template. This includes the conflicts comments added to + the commit message by `cherry-pick`, `merge`, `rebase` and + `revert`. +* It is incompatible with adding comments to the commit message + in the `prepare-commit-msg` hook. +* It is incompatible with the `fixup` and `squash` commands when + rebasing, +* It is not respected by `git notes` +-- ++ +endif::with-breaking-changes[] Note that these two variables are aliases of each other, and in modern versions of Git you are free to use a string (e.g., `//` or `⁑⁕⁑`) with `commentChar`. Versions of Git prior to v2.45.0 will ignore diff --git a/Documentation/config/extensions.adoc b/Documentation/config/extensions.adoc index 9e2f321a6d..829f2523fc 100644 --- a/Documentation/config/extensions.adoc +++ b/Documentation/config/extensions.adoc @@ -14,6 +14,10 @@ compatObjectFormat:: compatObjectFormat. As well as being able to use oids encoded in compatObjectFormat in addition to oids encoded with objectFormat to locally specify objects. ++ +Note that the functionality enabled by this extension is incomplete and subject +to change. It currently exists only to allow development and testing of +the underlying feature and is not designed to be enabled by end users. noop:: This extension does not change git's behavior at all. It is useful only diff --git a/Documentation/config/log.adoc b/Documentation/config/log.adoc index 16e00e8d29..f20cc25cd7 100644 --- a/Documentation/config/log.adoc +++ b/Documentation/config/log.adoc @@ -23,14 +23,14 @@ be used. Print out the ref names of any commits that are shown by the log command. Possible values are: + ----- +-- `short`;; the ref name prefixes `refs/heads/`, `refs/tags/` and `refs/remotes/` are not printed. `full`;; the full ref name (including prefix) are printed. `auto`;; if the output is going to a terminal, the ref names are shown as if `short` were given, otherwise no ref names are shown. ----- +-- + This is the same as the `--decorate` option of the `git log`. diff --git a/Documentation/config/mergetool.adoc b/Documentation/config/mergetool.adoc index 6be506145c..7064f5a462 100644 --- a/Documentation/config/mergetool.adoc +++ b/Documentation/config/mergetool.adoc @@ -65,7 +65,7 @@ endif::[] During a merge, Git will automatically resolve as many conflicts as possible and write the `$MERGED` file containing conflict markers around any conflicts that it cannot resolve; `$LOCAL` and `$REMOTE` normally - are the versions of the file from before Git`s conflict + are the versions of the file from before Git's conflict resolution. This flag causes `$LOCAL` and `$REMOTE` to be overwritten so that only the unresolved conflicts are presented to the merge tool. Can be configured per-tool via the `mergetool.<tool>.hideResolved` diff --git a/Documentation/config/promisor.adoc b/Documentation/config/promisor.adoc index 2638b01f83..93e5e0d9b5 100644 --- a/Documentation/config/promisor.adoc +++ b/Documentation/config/promisor.adoc @@ -9,6 +9,28 @@ promisor.advertise:: "false", which means the "promisor-remote" capability is not advertised. +promisor.sendFields:: + A comma or space separated list of additional remote related + field names. A server sends these field names and the + associated field values from its configuration when + advertising its promisor remotes using the "promisor-remote" + capability, see linkgit:gitprotocol-v2[5]. Currently, only the + "partialCloneFilter" and "token" field names are supported. ++ +`partialCloneFilter`:: contains the partial clone filter +used for the remote. ++ +`token`:: contains an authentication token for the remote. ++ +When a field name is part of this list and a corresponding +"remote.foo.<field-name>" config variable is set on the server to a +non-empty value, then the field name and value are sent when +advertising the promisor remote "foo". ++ +This list has no effect unless the "promisor.advertise" config +variable is set to "true", and the "name" and "url" fields are always +advertised regardless of this setting. + promisor.acceptFromServer:: If set to "all", a client will accept all the promisor remotes a server might advertise using the "promisor-remote" @@ -28,3 +50,42 @@ promisor.acceptFromServer:: lazily fetchable from this promisor remote from its responses to "fetch" and "clone" requests from the client. Name and URL comparisons are case sensitive. See linkgit:gitprotocol-v2[5]. + +promisor.checkFields:: + A comma or space separated list of additional remote related + field names. A client checks if the values of these fields + transmitted by a server correspond to the values of these + fields in its own configuration before accepting a promisor + remote. Currently, "partialCloneFilter" and "token" are the + only supported field names. ++ +If one of these field names (e.g., "token") is being checked for an +advertised promisor remote (e.g., "foo"), three conditions must be met +for the check of this specific field to pass: ++ +1. The corresponding local configuration (e.g., `remote.foo.token`) + must be set. +2. The server must advertise the "token" field for remote "foo". +3. The value of the locally configured `remote.foo.token` must exactly + match the value advertised by the server for the "token" field. ++ +If any of these conditions is not met for any field name listed in +`promisor.checkFields`, the advertised remote "foo" is rejected. ++ +For the "partialCloneFilter" field, this allows the client to ensure +that the server's filter matches what it expects locally, preventing +inconsistencies in filtering behavior. For the "token" field, this can +be used to verify that authentication credentials match expected +values. ++ +Field values are compared case-sensitively. ++ +The "name" and "url" fields are always checked according to the +`promisor.acceptFromServer` policy, independently of this setting. ++ +The field names and values should be passed by the server through the +"promisor-remote" capability by using the `promisor.sendFields` config +variable. The fields are checked only if the +`promisor.acceptFromServer` config variable is not set to "None". If +set to "None", this config variable has no effect. See +linkgit:gitprotocol-v2[5]. diff --git a/Documentation/config/sendemail.adoc b/Documentation/config/sendemail.adoc index 4722334657..90164c734d 100644 --- a/Documentation/config/sendemail.adoc +++ b/Documentation/config/sendemail.adoc @@ -88,6 +88,8 @@ sendemail.smtpServer:: sendemail.smtpServerPort:: sendemail.smtpServerOption:: sendemail.smtpUser:: +sendemail.imapSentFolder:: +sendemail.useImapOnly:: sendemail.thread:: sendemail.transferEncoding:: sendemail.validate:: diff --git a/Documentation/config/worktree.adoc b/Documentation/config/worktree.adoc index 5e35c7d018..9e3f84f748 100644 --- a/Documentation/config/worktree.adoc +++ b/Documentation/config/worktree.adoc @@ -15,5 +15,5 @@ worktree.useRelativePaths:: different locations or environments. Defaults to "false". + Note that setting `worktree.useRelativePaths` to "true" implies enabling the -`extension.relativeWorktrees` config (see linkgit:git-config[1]), +`extensions.relativeWorktrees` config (see linkgit:git-config[1]), thus making it incompatible with older versions of Git. diff --git a/Documentation/diff-format.adoc b/Documentation/diff-format.adoc index 80e36e153d..9f7e988241 100644 --- a/Documentation/diff-format.adoc +++ b/Documentation/diff-format.adoc @@ -103,6 +103,7 @@ if the file was renamed on any side of history. With followed by the name of the path in the merge commit. Examples for `-c` and `--cc` without `--combined-all-paths`: + ------------------------------------------------ ::100644 100644 100644 fabadb8 cc95eb0 4866510 MM desc.c ::100755 100755 100755 52b7a2d 6d1ac04 d2ac7d7 RM bar.sh diff --git a/Documentation/diff-options.adoc b/Documentation/diff-options.adoc index f3a35d8141..ae31520f7f 100644 --- a/Documentation/diff-options.adoc +++ b/Documentation/diff-options.adoc @@ -505,7 +505,8 @@ endif::git-format-patch[] Turn off rename detection, even when the configuration file gives the default to do so. -`--[no-]rename-empty`:: +`--rename-empty`:: +`--no-rename-empty`:: Whether to use empty blobs as rename source. ifndef::git-format-patch[] @@ -893,5 +894,33 @@ endif::git-format-patch[] reverted with `--ita-visible-in-index`. Both options are experimental and could be removed in future. +--max-depth=<depth>:: + For each pathspec given on command line, descend at most `<depth>` + levels of directories. A value of `-1` means no limit. + Cannot be combined with wildcards in the pathspec. + Given a tree containing `foo/bar/baz`, the following list shows the + matches generated by each set of options: ++ +-- + - `--max-depth=0 -- foo`: `foo` + + - `--max-depth=1 -- foo`: `foo/bar` + + - `--max-depth=1 -- foo/bar`: `foo/bar/baz` + + - `--max-depth=1 -- foo foo/bar`: `foo/bar/baz` + + - `--max-depth=2 -- foo`: `foo/bar/baz` +-- ++ +If no pathspec is given, the depth is measured as if all +top-level entries were specified. Note that this is different +than measuring from the root, in that `--max-depth=0` would +still return `foo`. This allows you to still limit depth while +asking for a subset of the top-level entries. ++ +Note that this option is only supported for diffs between tree objects, +not against the index or working tree. + For more detailed explanation on these common options, see also linkgit:gitdiffcore[7]. diff --git a/Documentation/fetch-options.adoc b/Documentation/fetch-options.adoc index b01372e4b3..ad1e1f49be 100644 --- a/Documentation/fetch-options.adoc +++ b/Documentation/fetch-options.adoc @@ -1,7 +1,8 @@ ---[no-]all:: +--all:: +--no-all:: Fetch all remotes, except for the ones that has the `remote.<name>.skipFetchAll` configuration variable set. - This overrides the configuration variable fetch.all`. + This overrides the configuration variable `fetch.all`. -a:: --append:: @@ -88,7 +89,8 @@ This is incompatible with `--recurse-submodules=[yes|on-demand]` and takes precedence over the `fetch.output` config option. ifndef::git-pull[] ---[no-]write-fetch-head:: +--write-fetch-head:: +--no-write-fetch-head:: Write the list of remote refs fetched in the `FETCH_HEAD` file directly under `$GIT_DIR`. This is the default. Passing `--no-write-fetch-head` from the command line tells @@ -118,13 +120,16 @@ ifndef::git-pull[] Allow several <repository> and <group> arguments to be specified. No <refspec>s may be specified. ---[no-]auto-maintenance:: ---[no-]auto-gc:: +--auto-maintenance:: +--no-auto-maintenance:: +--auto-gc:: +--no-auto-gc:: Run `git maintenance run --auto` at the end to perform automatic repository maintenance if needed. (`--[no-]auto-gc` is a synonym.) This is enabled by default. ---[no-]write-commit-graph:: +--write-commit-graph:: +--no-write-commit-graph:: Write a commit-graph after fetching. This overrides the config setting `fetch.writeCommitGraph`. endif::git-pull[] diff --git a/Documentation/for-each-ref-options.adoc b/Documentation/for-each-ref-options.adoc new file mode 100644 index 0000000000..f13efb5f25 --- /dev/null +++ b/Documentation/for-each-ref-options.adoc @@ -0,0 +1,85 @@ +`<pattern>...`:: + If one or more _<pattern>_ parameters are given, only refs are shown that + match against at least one pattern, either using `fnmatch`(3) or + literally, in the latter case matching completely or from the + beginning up to a slash. + +`--stdin`:: + The list of patterns is read from standard input instead of from + the argument list. + +`--count=<count>`:: + Stop after showing _<count>_ refs. + +`--sort=<key>`:: + Sort on the field name _<key>_. Prefix `-` to sort in + descending order of the value. When unspecified, + `refname` is used. You may use the `--sort=<key>` option + multiple times, in which case the last key becomes the primary + key. + +`--format[=<format>]`:: + A string that interpolates `%(fieldname)` from a ref being shown and + the object it points at. In addition, the string literal `%%` + renders as `%` and `%xx` - where `xx` are hex digits - renders as + the character with hex code `xx`. For example, `%00` interpolates to + `\0` (_NUL_), `%09` to `\t` (_TAB_), and `%0a` to `\n` (_LF_). + +When unspecified, _<format>_ defaults to `%(objectname) SPC %(objecttype) +TAB %(refname)`. + +`--color[=<when>]`:: + Respect any colors specified in the `--format` option. The + _<when__ field must be one of `always`, `never`, or `auto` (if + `<when>` is absent, behave as if `always` was given). + +`--shell`:: +`--perl`:: +`--python`:: +`--tcl`:: + If given, strings that substitute `%(fieldname)` + placeholders are quoted as string literals suitable for + the specified host language. This is meant to produce + a scriptlet that can directly be "eval"ed. + +`--points-at=<object>`:: + Only list refs which points at the given object. + +`--merged[=<object>]`:: + Only list refs whose tips are reachable from the + specified commit (`HEAD` if not specified). + +`--no-merged[=<object>]`:: + Only list refs whose tips are not reachable from _<object>_(`HEAD` if not + specified). + +`--contains[=<object>]`:: + Only list refs which contain _<object>_(`HEAD` if not specified). + +`--no-contains[=<object>]`:: + Only list refs which don't contain _<object>_ (`HEAD` + if not specified). + +`--ignore-case`:: + Sorting and filtering refs are case insensitive. + +`--omit-empty`:: + Do not print a newline after formatted refs where the format expands + to the empty string. + +`--exclude=<excluded-pattern>`:: + If one or more `--exclude` options are given, only refs which do not + match any _<excluded-pattern>_ parameters are shown. Matching is done + using the same rules as _<pattern>_ above. + +`--include-root-refs`:: + List root refs (`HEAD` and pseudorefs) apart from regular refs. + +`--start-after=<marker>`:: + Allows paginating the output by skipping references up to and including the + specified marker. When paging, it should be noted that references may be + deleted, modified or added between invocations. Output will only yield those + references which follow the marker lexicographically. Output begins from the + first reference that would come after the marker alphabetically. Cannot be + used with `--sort=<key>` or `--stdin` options, or the _<pattern>_ argument(s) + to limit the refs. diff --git a/Documentation/git-add.adoc b/Documentation/git-add.adoc index b7a735824d..ad629c46c5 100644 --- a/Documentation/git-add.adoc +++ b/Documentation/git-add.adoc @@ -16,18 +16,18 @@ git add [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [- DESCRIPTION ----------- -This command updates the index using the current content found in -the working tree, to prepare the content staged for the next commit. -It typically adds the current content of existing paths as a whole, -but with some options it can also be used to add content with -only part of the changes made to the working tree files applied, or -remove paths that do not exist in the working tree anymore. - -The "index" holds a snapshot of the content of the working tree, and it -is this snapshot that is taken as the contents of the next commit. Thus -after making any changes to the working tree, and before running -the commit command, you must use the `add` command to add any new or -modified files to the index. +Add contents of new or changed files to the index. The "index" (also +known as the "staging area") is what you use to prepare the contents of +the next commit. + +When you run `git commit` without any other arguments, it will only +commit staged changes. For example, if you've edited `file.c` and want +to commit your changes to that file, you can run: + + git add file.c + git commit + +You can also add only part of your changes to a file with `git add -p`. This command can be performed multiple times before a commit. It only adds the content of the specified file(s) at the time the add command is @@ -37,12 +37,10 @@ you must run `git add` again to add the new content to the index. The `git status` command can be used to obtain a summary of which files have changes that are staged for the next commit. -The `git add` command will not add ignored files by default. If any -ignored files were explicitly specified on the command line, `git add` -will fail with a list of ignored files. Ignored files reached by -directory recursion or filename globbing performed by Git (quote your -globs before the shell) will be silently ignored. The `git add` command can -be used to add ignored files with the `-f` (force) option. +The `git add` command will not add ignored files by default. You can +use the `--force` option to add ignored files. If you specify the exact +filename of an ignored file, `git add` will fail with a list of ignored +files. Otherwise it will silently ignore the file. Please see linkgit:git-commit[1] for alternative ways to add content to a commit. diff --git a/Documentation/git-am.adoc b/Documentation/git-am.adoc index 221070de48..b23b4fba20 100644 --- a/Documentation/git-am.adoc +++ b/Documentation/git-am.adoc @@ -48,7 +48,8 @@ OPTIONS --keep-non-patch:: Pass `-b` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]). ---[no-]keep-cr:: +--keep-cr:: +--no-keep-cr:: With `--keep-cr`, call 'git mailsplit' (see linkgit:git-mailsplit[1]) with the same option, to prevent it from stripping CR at the end of lines. `am.keepcr` configuration variable can be used to specify the diff --git a/Documentation/git-backfill.adoc b/Documentation/git-backfill.adoc index 95623051f7..b8394dcf22 100644 --- a/Documentation/git-backfill.adoc +++ b/Documentation/git-backfill.adoc @@ -57,7 +57,8 @@ OPTIONS blobs seen at a given path. The default minimum batch size is 50,000. -`--[no-]sparse`:: +`--sparse`:: +`--no-sparse`:: Only download objects if they appear at a path that matches the current sparse-checkout. If the sparse-checkout feature is enabled, then `--sparse` is assumed and can be disabled with `--no-sparse`. diff --git a/Documentation/git-cat-file.adoc b/Documentation/git-cat-file.adoc index 180d1ad363..c139f55a16 100644 --- a/Documentation/git-cat-file.adoc +++ b/Documentation/git-cat-file.adoc @@ -62,8 +62,10 @@ OPTIONS or to ask for a "blob" with `<object>` being a tag object that points at it. ---[no-]mailmap:: ---[no-]use-mailmap:: +--mailmap:: +--no-mailmap:: +--use-mailmap:: +--no-use-mailmap:: Use mailmap file to map author, committer and tagger names and email addresses to canonical real names and email addresses. See linkgit:git-shortlog[1]. diff --git a/Documentation/git-check-attr.adoc b/Documentation/git-check-attr.adoc index 503b644657..15a37a38e3 100644 --- a/Documentation/git-check-attr.adoc +++ b/Documentation/git-check-attr.adoc @@ -19,7 +19,8 @@ For every pathname, this command will list if each attribute is 'unspecified', OPTIONS ------- --a, --all:: +-a:: +--all:: List all attributes that are associated with the specified paths. If this option is used, then 'unspecified' attributes will not be included in the output. diff --git a/Documentation/git-check-ignore.adoc b/Documentation/git-check-ignore.adoc index 3e3b4e3446..a6c6c1b6e5 100644 --- a/Documentation/git-check-ignore.adoc +++ b/Documentation/git-check-ignore.adoc @@ -25,11 +25,13 @@ subject to exclude rules; but see `--no-index'. OPTIONS ------- --q, --quiet:: +-q:: +--quiet:: Don't output anything, just set exit status. This is only valid with a single pathname. --v, --verbose:: +-v:: +--verbose:: Instead of printing the paths that are excluded, for each path that matches an exclude pattern, print the exclude pattern together with the path. (Matching an exclude pattern usually @@ -49,7 +51,8 @@ linkgit:gitignore[5]. below). If `--stdin` is also given, input paths are separated with a NUL character instead of a linefeed character. --n, --non-matching:: +-n:: +--non-matching:: Show given paths which don't match any pattern. This only makes sense when `--verbose` is enabled, otherwise it would not be possible to distinguish between paths which match a diff --git a/Documentation/git-check-ref-format.adoc b/Documentation/git-check-ref-format.adoc index 2aacfd1808..0c3abf9146 100644 --- a/Documentation/git-check-ref-format.adoc +++ b/Documentation/git-check-ref-format.adoc @@ -98,7 +98,8 @@ a branch. OPTIONS ------- ---[no-]allow-onelevel:: +--allow-onelevel:: +--no-allow-onelevel:: Controls whether one-level refnames are accepted (i.e., refnames that do not contain multiple `/`-separated components). The default is `--no-allow-onelevel`. diff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc index 40e02cfd65..ff1cb29bc1 100644 --- a/Documentation/git-checkout.adoc +++ b/Documentation/git-checkout.adoc @@ -334,7 +334,7 @@ include::diff-context-options.adoc[] separated with _NUL_ character and all other characters are taken literally (including newlines and quotes). -<branch>:: +`<branch>`:: Branch to checkout; if it refers to a branch (i.e., a name that, when prepended with "refs/heads/", is a valid ref), then that branch is checked out. Otherwise, if it refers to a valid diff --git a/Documentation/git-clone.adoc b/Documentation/git-clone.adoc index 222d558290..57cdfb7620 100644 --- a/Documentation/git-clone.adoc +++ b/Documentation/git-clone.adoc @@ -16,7 +16,7 @@ git clone [--template=<template-directory>] [--depth <depth>] [--[no-]single-branch] [--[no-]tags] [--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules] [--[no-]remote-submodules] [--jobs <n>] [--sparse] [--[no-]reject-shallow] - [--filter=<filter-spec>] [--also-filter-submodules]] [--] <repository> + [--filter=<filter-spec> [--also-filter-submodules]] [--] <repository> [<directory>] DESCRIPTION @@ -272,7 +272,8 @@ corresponding `--mirror` and `--no-tags` options instead. reachable from a specified remote branch or tag. This option can be specified multiple times. -`--[no-]single-branch`:: +`--single-branch`:: +`--no-single-branch`:: Clone only the history leading to the tip of a single branch, either specified by the `--branch` option or the primary branch remote's `HEAD` points at. @@ -282,7 +283,8 @@ corresponding `--mirror` and `--no-tags` options instead. branch when `--single-branch` clone was made, no remote-tracking branch is created. -`--[no-]tags`:: +`--tags`:: +`--no-tags`:: Control whether or not tags will be cloned. When `--no-tags` is given, the option will be become permanent by setting the `remote.<remote>.tagOpt=--no-tags` configuration. This ensures that @@ -313,10 +315,12 @@ the clone is finished. This option is ignored if the cloned repository does not have a worktree/checkout (i.e. if any of `--no-checkout`/`-n`, `--bare`, or `--mirror` is given) -`--[no-]shallow-submodules`:: +`--shallow-submodules`:: +`--no-shallow-submodules`:: All submodules which are cloned will be shallow with a depth of 1. -`--[no-]remote-submodules`:: +`--remote-submodules`:: +`--no-remote-submodules`:: All submodules which are cloned will use the status of the submodule's remote-tracking branch to update the submodule, rather than the superproject's recorded SHA-1. Equivalent to passing `--remote` to diff --git a/Documentation/git-commit-graph.adoc b/Documentation/git-commit-graph.adoc index 50b5016804..e9558173c0 100644 --- a/Documentation/git-commit-graph.adoc +++ b/Documentation/git-commit-graph.adoc @@ -34,7 +34,8 @@ OPTIONS object directory, `git commit-graph ...` will exit with non-zero status. ---[no-]progress:: +--progress:: +--no-progress:: Turn progress on/off explicitly. If neither is specified, progress is shown if standard error is connected to a terminal. diff --git a/Documentation/git-commit.adoc b/Documentation/git-commit.adoc index ae988a883b..54c207ad45 100644 --- a/Documentation/git-commit.adoc +++ b/Documentation/git-commit.adoc @@ -214,7 +214,8 @@ include::signoff-option.adoc[] each trailer would appear, and other details. `-n`:: -`--[no-]verify`:: +`--verify`:: +`--no-verify`:: Bypass the `pre-commit` and `commit-msg` hooks. See also linkgit:githooks[5]. @@ -281,6 +282,7 @@ variable (see linkgit:git-config[1]). + -- It is a rough equivalent for: + ------ $ git reset --soft HEAD^ $ ... do something else to come up with the right tree ... diff --git a/Documentation/git-config.adoc b/Documentation/git-config.adoc index 511b2e26bf..36d2845152 100644 --- a/Documentation/git-config.adoc +++ b/Documentation/git-config.adoc @@ -295,7 +295,8 @@ Valid `<type>`'s include: When the color setting for `name` is undefined, the command uses `color.ui` as fallback. ---[no-]includes:: +--includes:: +--no-includes:: Respect `include.*` directives in config files when looking up values. Defaults to `off` when a specific file is given (e.g., using `--file`, `--global`, etc) and `on` when searching all diff --git a/Documentation/git-count-objects.adoc b/Documentation/git-count-objects.adoc index 97f9f12610..eeee6b9f7f 100644 --- a/Documentation/git-count-objects.adoc +++ b/Documentation/git-count-objects.adoc @@ -28,6 +28,8 @@ size: disk space consumed by loose objects, in KiB (unless -H is specified) + in-pack: the number of in-pack objects + +packs: the number of pack files ++ size-pack: disk space consumed by the packs, in KiB (unless -H is specified) + prune-packable: the number of loose objects that are also present in diff --git a/Documentation/git-difftool.adoc b/Documentation/git-difftool.adoc index d596205eaf..064bc68347 100644 --- a/Documentation/git-difftool.adoc +++ b/Documentation/git-difftool.adoc @@ -77,7 +77,8 @@ with custom merge tool commands and has the same value as `$MERGED`. --tool-help:: Print a list of diff tools that may be used with `--tool`. ---[no-]symlinks:: +--symlinks:: +--no-symlinks:: 'git difftool''s default behavior is to create symlinks to the working tree when run in `--dir-diff` mode and the right-hand side of the comparison yields the same content as the file in @@ -94,7 +95,8 @@ instead. `--no-symlinks` is the default on Windows. Additionally, `$BASE` is set in the environment. -g:: ---[no-]gui:: +--gui:: +--no-gui:: When 'git-difftool' is invoked with the `-g` or `--gui` option the default diff tool will be read from the configured `diff.guitool` variable instead of `diff.tool`. This may be @@ -104,7 +106,8 @@ instead. `--no-symlinks` is the default on Windows. fallback in the order of `merge.guitool`, `diff.tool`, `merge.tool` until a tool is found. ---[no-]trust-exit-code:: +--trust-exit-code:: +--no-trust-exit-code:: Errors reported by the diff tool are ignored by default. Use `--trust-exit-code` to make 'git-difftool' exit when an invoked diff tool returns a non-zero exit code. diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc index 6f9763c11b..6e095b02a1 100644 --- a/Documentation/git-fast-import.adoc +++ b/Documentation/git-fast-import.adoc @@ -61,10 +61,10 @@ OPTIONS currently impacts only the `export-marks`, `import-marks`, and `import-marks-if-exists` feature commands. + - Only enable this option if you trust the program generating the - fast-import stream! This option is enabled automatically for - remote-helpers that use the `import` capability, as they are - already trusted to run their own code. +Only enable this option if you trust the program generating the +fast-import stream! This option is enabled automatically for +remote-helpers that use the `import` capability, as they are +already trusted to run their own code. Options for Frontends ~~~~~~~~~~~~~~~~~~~~~ @@ -111,7 +111,8 @@ Locations of Marks Files Like --import-marks but instead of erroring out, silently skips the file if it does not exist. ---[no-]relative-marks:: +--relative-marks:: +--no-relative-marks:: After specifying --relative-marks the paths specified with --import-marks= and --export-marks= are relative to an internal directory in the current repository. @@ -605,9 +606,11 @@ Marks must be declared (via `mark`) before they can be used. The special case of restarting an incremental import from the current branch value should be written as: + ---- from refs/heads/branch^0 ---- + The `^0` suffix is necessary as fast-import does not permit a branch to start from itself, and the branch is created in memory before the `from` command is even read from the input. Adding `^0` will force @@ -644,7 +647,7 @@ External data format:: + Here usually `<dataref>` must be either a mark reference (`:<idnum>`) set by a prior `blob` command, or a full 40-byte SHA-1 of an -existing Git blob object. If `<mode>` is `040000`` then +existing Git blob object. If `<mode>` is `040000` then `<dataref>` must be the full 40-byte SHA-1 of an existing Git tree object or a mark reference set with `--import-marks`. diff --git a/Documentation/git-fmt-merge-msg.adoc b/Documentation/git-fmt-merge-msg.adoc index 0f3328956d..6d91620be9 100644 --- a/Documentation/git-fmt-merge-msg.adoc +++ b/Documentation/git-fmt-merge-msg.adoc @@ -35,7 +35,8 @@ OPTIONS Do not list one-line descriptions from the actual commits being merged. ---[no-]summary:: +--summary:: +--no-summary:: Synonyms to --log and --no-log; these are deprecated and will be removed in the future. diff --git a/Documentation/git-for-each-ref.adoc b/Documentation/git-for-each-ref.adoc index 060940904d..c02cb7f886 100644 --- a/Documentation/git-for-each-ref.adoc +++ b/Documentation/git-for-each-ref.adoc @@ -14,108 +14,21 @@ git for-each-ref [--count=<count>] [--shell|--perl|--python|--tcl] [--merged[=<object>]] [--no-merged[=<object>]] [--contains[=<object>]] [--no-contains[=<object>]] [(--exclude=<pattern>)...] [--start-after=<marker>] - [ --stdin | <pattern>... ] + [ --stdin | (<pattern>...)] DESCRIPTION ----------- -Iterate over all refs that match `<pattern>` and show them -according to the given `<format>`, after sorting them according -to the given set of `<key>`. If `<count>` is given, stop after -showing that many refs. The interpolated values in `<format>` +Iterate over all refs that match _<pattern>_ and show them +according to the given _<format>_, after sorting them according +to the given set of _<key>_. If _<count>_ is given, stop after +showing that many refs. The interpolated values in _<format>_ can optionally be quoted as string literals in the specified host language allowing their direct evaluation in that language. OPTIONS ------- -<pattern>...:: - If one or more patterns are given, only refs are shown that - match against at least one pattern, either using fnmatch(3) or - literally, in the latter case matching completely or from the - beginning up to a slash. - ---stdin:: - If `--stdin` is supplied, then the list of patterns is read from - standard input instead of from the argument list. - ---count=<count>:: - By default the command shows all refs that match - `<pattern>`. This option makes it stop after showing - that many refs. - ---sort=<key>:: - A field name to sort on. Prefix `-` to sort in - descending order of the value. When unspecified, - `refname` is used. You may use the --sort=<key> option - multiple times, in which case the last key becomes the primary - key. - ---format=<format>:: - A string that interpolates `%(fieldname)` from a ref being shown and - the object it points at. In addition, the string literal `%%` - renders as `%` and `%xx` - where `xx` are hex digits - renders as - the character with hex code `xx`. For example, `%00` interpolates to - `\0` (NUL), `%09` to `\t` (TAB), and `%0a` to `\n` (LF). -+ -When unspecified, `<format>` defaults to `%(objectname) SPC %(objecttype) -TAB %(refname)`. - ---color[=<when>]:: - Respect any colors specified in the `--format` option. The - `<when>` field must be one of `always`, `never`, or `auto` (if - `<when>` is absent, behave as if `always` was given). - ---shell:: ---perl:: ---python:: ---tcl:: - If given, strings that substitute `%(fieldname)` - placeholders are quoted as string literals suitable for - the specified host language. This is meant to produce - a scriptlet that can directly be `eval`ed. - ---points-at=<object>:: - Only list refs which points at the given object. - ---merged[=<object>]:: - Only list refs whose tips are reachable from the - specified commit (HEAD if not specified). - ---no-merged[=<object>]:: - Only list refs whose tips are not reachable from the - specified commit (HEAD if not specified). - ---contains[=<object>]:: - Only list refs which contain the specified commit (HEAD if not - specified). - ---no-contains[=<object>]:: - Only list refs which don't contain the specified commit (HEAD - if not specified). - ---ignore-case:: - Sorting and filtering refs are case insensitive. - ---omit-empty:: - Do not print a newline after formatted refs where the format expands - to the empty string. - ---exclude=<pattern>:: - If one or more patterns are given, only refs which do not match - any excluded pattern(s) are shown. Matching is done using the - same rules as `<pattern>` above. - ---include-root-refs:: - List root refs (HEAD and pseudorefs) apart from regular refs. - ---start-after=<marker>:: - Allows paginating the output by skipping references up to and including the - specified marker. When paging, it should be noted that references may be - deleted, modified or added between invocations. Output will only yield those - references which follow the marker lexicographically. Output begins from the - first reference that would come after the marker alphabetically. Cannot be - used with `--sort=<key>` or `--stdin` options, or the _<pattern>_ argument(s) - to limit the refs. +include::for-each-ref-options.adoc[] FIELD NAMES ----------- @@ -126,44 +39,44 @@ keys. For all objects, the following names can be used: -refname:: - The name of the ref (the part after $GIT_DIR/). +`refname`:: + The name of the ref (the part after `$GIT_DIR/`). For a non-ambiguous short name of the ref append `:short`. - The option core.warnAmbiguousRefs is used to select the strict - abbreviation mode. If `lstrip=<N>` (`rstrip=<N>`) is appended, strips `<N>` + The option `core.warnAmbiguousRefs` is used to select the strict + abbreviation mode. If `lstrip=<n>` (`rstrip=<n>`) is appended, strip _<n>_ slash-separated path components from the front (back) of the refname (e.g. `%(refname:lstrip=2)` turns `refs/tags/foo` into `foo` and `%(refname:rstrip=2)` turns `refs/tags/foo` into `refs`). - If `<N>` is a negative number, strip as many path components as - necessary from the specified end to leave `-<N>` path components + If _<n>_ is a negative number, strip as many path components as + necessary from the specified end to leave `-<n>` path components (e.g. `%(refname:lstrip=-2)` turns `refs/tags/foo` into `tags/foo` and `%(refname:rstrip=-1)` turns `refs/tags/foo` into `refs`). When the ref does not have enough components, the result becomes an empty string if - stripping with positive <N>, or it becomes the full refname if - stripping with negative <N>. Neither is an error. + stripping with positive _<n>_, or it becomes the full refname if + stripping with negative _<N>_. Neither is an error. + `strip` can be used as a synonym to `lstrip`. -objecttype:: +`objecttype`:: The type of the object (`blob`, `tree`, `commit`, `tag`). -objectsize:: +`objectsize`:: The size of the object (the same as 'git cat-file -s' reports). Append `:disk` to get the size, in bytes, that the object takes up on - disk. See the note about on-disk sizes in the `CAVEATS` section below. -objectname:: + disk. See the note about on-disk sizes in the 'CAVEATS' section below. +`objectname`:: The object name (aka SHA-1). For a non-ambiguous abbreviation of the object name append `:short`. For an abbreviation of the object name with desired length append - `:short=<length>`, where the minimum length is MINIMUM_ABBREV. The + `:short=<length>`, where the minimum length is `MINIMUM_ABBREV`. The length may be exceeded to ensure unique object names. -deltabase:: +`deltabase`:: This expands to the object name of the delta base for the given object, if it is stored as a delta. Otherwise it expands to the null object name (all zeroes). -upstream:: +`upstream`:: The name of a local ref which can be considered ``upstream'' from the displayed ref. Respects `:short`, `:lstrip` and `:rstrip` in the same way as `refname` above. Additionally @@ -185,100 +98,103 @@ Has no effect if the ref does not have tracking information associated with it. All the options apart from `nobracket` are mutually exclusive, but if used together the last option is selected. -push:: +`push`:: The name of a local ref which represents the `@{push}` location for the displayed ref. Respects `:short`, `:lstrip`, `:rstrip`, `:track`, `:trackshort`, `:remotename`, and `:remoteref` options as `upstream` does. Produces an empty string if no `@{push}` ref is configured. -HEAD:: - '*' if HEAD matches current ref (the checked out branch), ' ' +`HEAD`:: + `*` if `HEAD` matches current ref (the checked out branch), ' ' otherwise. -color:: +`color`:: Change output color. Followed by `:<colorname>`, where color names are described under Values in the "CONFIGURATION FILE" section of linkgit:git-config[1]. For example, `%(color:bold red)`. -align:: +`align`:: Left-, middle-, or right-align the content between - %(align:...) and %(end). The "align:" is followed by + `%(align:...)` and `%(end)`. The "`align:`" is followed by `width=<width>` and `position=<position>` in any order - separated by a comma, where the `<position>` is either left, - right or middle, default being left and `<width>` is the total + separated by a comma, where the _<position>_ is either `left`, + `right` or `middle`, default being `left` and _<width>_ is the total length of the content with alignment. For brevity, the "width=" and/or "position=" prefixes may be omitted, and bare - <width> and <position> used instead. For instance, + _<width>_ and _<position>_ used instead. For instance, `%(align:<width>,<position>)`. If the contents length is more than the width then no alignment is performed. If used with - `--quote` everything in between %(align:...) and %(end) is + `--quote` everything in between `%(align:...)` and `%(end)` is quoted, but if nested then only the topmost level performs quoting. -if:: - Used as %(if)...%(then)...%(end) or - %(if)...%(then)...%(else)...%(end). If there is an atom with - value or string literal after the %(if) then everything after - the %(then) is printed, else if the %(else) atom is used, then +`if`:: + Used as `%(if)...%(then)...%(end)` or + `%(if)...%(then)...%(else)...%(end)`. If there is an atom with + value or string literal after the `%(if)` then everything after + the `%(then)` is printed, else if the `%(else)` atom is used, then everything after %(else) is printed. We ignore space when - evaluating the string before %(then), this is useful when we - use the %(HEAD) atom which prints either "*" or " " and we - want to apply the 'if' condition only on the 'HEAD' ref. - Append ":equals=<string>" or ":notequals=<string>" to compare - the value between the %(if:...) and %(then) atoms with the + evaluating the string before `%(then)`, this is useful when we + use the `%(HEAD)` atom which prints either "`*`" or " " and we + want to apply the 'if' condition only on the `HEAD` ref. + Append "`:equals=<string>`" or "`:notequals=<string>`" to compare + the value between the `%(if:...)` and `%(then)` atoms with the given string. -symref:: +`symref`:: The ref which the given symbolic ref refers to. If not a symbolic ref, nothing is printed. Respects the `:short`, `:lstrip` and `:rstrip` options in the same way as `refname` above. -signature:: +`signature`:: The GPG signature of a commit. -signature:grade:: - Show "G" for a good (valid) signature, "B" for a bad - signature, "U" for a good signature with unknown validity, "X" - for a good signature that has expired, "Y" for a good - signature made by an expired key, "R" for a good signature - made by a revoked key, "E" if the signature cannot be - checked (e.g. missing key) and "N" for no signature. - -signature:signer:: +`signature:grade`:: + Show +`G`;; for a good (valid) signature +`B`;; for a bad signature +`U`;; for a good signature with unknown validity +`X`;; for a good signature that has expired +`Y`;; for a good signature made by an expired key +`R`;; for a good signature made by a revoked key +`E`;; if the signature cannot be checked (e.g. missing key) +`N`;; for no signature. + +`signature:signer`:: The signer of the GPG signature of a commit. -signature:key:: +`signature:key`:: The key of the GPG signature of a commit. -signature:fingerprint:: +`signature:fingerprint`:: The fingerprint of the GPG signature of a commit. -signature:primarykeyfingerprint:: +`signature:primarykeyfingerprint`:: The primary key fingerprint of the GPG signature of a commit. -signature:trustlevel:: +`signature:trustlevel`:: The trust level of the GPG signature of a commit. Possible outputs are `ultimate`, `fully`, `marginal`, `never` and `undefined`. -worktreepath:: +`worktreepath`:: The absolute path to the worktree in which the ref is checked out, if it is checked out in any linked worktree. Empty string otherwise. -ahead-behind:<committish>:: +`ahead-behind:<commit-ish>`:: Two integers, separated by a space, demonstrating the number of commits ahead and behind, respectively, when comparing the output - ref to the `<committish>` specified in the format. + ref to the _<committish>_ specified in the format. -is-base:<committish>:: - In at most one row, `(<committish>)` will appear to indicate the ref +`is-base:<commit-ish>`:: + In at most one row, `(<commit-ish>)` will appear to indicate the ref that is most likely the ref used as a starting point for the branch - that produced `<committish>`. This choice is made using a heuristic: + that produced _<commit-ish>_. This choice is made using a heuristic: choose the ref that minimizes the number of commits in the - first-parent history of `<committish>` and not in the first-parent + first-parent history of _<commit-ish>_ and not in the first-parent history of the ref. + For example, consider the following figure of first-parent histories of @@ -312,29 +228,29 @@ common first-parent ancestor of `B` and `C` and ties are broken by the earliest ref in the sorted order. + Note that this token will not appear if the first-parent history of -`<committish>` does not intersect the first-parent histories of the +_<commit-ish>_ does not intersect the first-parent histories of the filtered refs. -describe[:options]:: +`describe[:<option>,...]`:: A human-readable name, like linkgit:git-describe[1]; empty string for undescribable commits. The `describe` string may be followed by a colon and one or more comma-separated options. + -- -tags=<bool-value>;; +`tags=<bool-value>`;; Instead of only considering annotated tags, consider lightweight tags as well; see the corresponding option in linkgit:git-describe[1] for details. -abbrev=<number>;; - Use at least <number> hexadecimal digits; see the corresponding +`abbrev=<number>`;; + Use at least _<number>_ hexadecimal digits; see the corresponding option in linkgit:git-describe[1] for details. -match=<pattern>;; - Only consider tags matching the given `glob(7)` pattern, - excluding the "refs/tags/" prefix; see the corresponding option +`match=<pattern>`;; + Only consider tags matching the `glob`(7) _<pattern>_, + excluding the `refs/tags/` prefix; see the corresponding option in linkgit:git-describe[1] for details. -exclude=<pattern>;; - Do not consider tags matching the given `glob(7)` pattern, - excluding the "refs/tags/" prefix; see the corresponding option +`exclude=<pattern>`;; + Do not consider tags matching the `glob`(7) _<pattern>_, + excluding the `refs/tags/` prefix; see the corresponding option in linkgit:git-describe[1] for details. -- @@ -366,7 +282,7 @@ variable (see linkgit:gitmailmap[5]). The raw data in an object is `raw`. -raw:size:: +`raw:size`:: The raw data size of the object. Note that `--format=%(raw)` can not be used with `--python`, `--shell`, `--tcl`, @@ -376,10 +292,10 @@ variable type. The message in a commit or a tag object is `contents`, from which `contents:<part>` can be used to extract various parts out of: -contents:size:: +`contents:size`:: The size in bytes of the commit or tag message. -contents:subject:: +`contents:subject`:: The first paragraph of the message, which typically is a single line, is taken as the "subject" of the commit or the tag message. @@ -387,19 +303,19 @@ contents:subject:: obtain same results. `:sanitize` can be appended to `subject` for subject line suitable for filename. -contents:body:: +`contents:body`:: The remainder of the commit or the tag message that follows the "subject". -contents:signature:: +`contents:signature`:: The optional GPG signature of the tag. -contents:lines=N:: - The first `N` lines of the message. +`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[:options]` (or by using the historical alias -`contents:trailers[:options]`). For valid [:option] values see `trailers` +are obtained as `trailers[:<option>,...]` (or by using the historical alias +`contents:trailers[:<option>,...]`). For valid _<option>_ values see `trailers` section of linkgit:git-log[1]. For sorting purposes, fields with numeric values sort in numeric order @@ -419,8 +335,8 @@ option to linkgit:git-rev-list[1] takes). If this formatting is provided in a `--sort` key, references will be sorted according to the byte-value of the formatted string rather than the numeric value of the underlying timestamp. -Some atoms like %(align) and %(if) always require a matching %(end). -We call them "opening atoms" and sometimes denote them as %($open). +Some atoms like `%(align)` and `%(if)` always require a matching `%(end)`. +We call them "opening atoms" and sometimes denote them as `%($open)`. When a scripting language specific quoting is in effect, everything between a top-level opening atom and its matching %(end) is evaluated @@ -438,7 +354,7 @@ An example directly producing formatted text. Show the most recent #!/bin/sh git for-each-ref --count=3 --sort='-*authordate' \ ---format='From: %(*authorname) %(*authoremail) +`--format='From: %(*authorname) %(*authoremail) Subject: %(*subject) Date: %(*authordate) Ref: %(*refname) @@ -449,7 +365,7 @@ Ref: %(*refname) A simple example showing the use of shell eval on the output, -demonstrating the use of --shell. List the prefixes of all heads: +demonstrating the use of `--shell`. List the prefixes of all heads: ------------ #!/bin/sh @@ -517,7 +433,7 @@ eval "$eval" ------------ -An example to show the usage of %(if)...%(then)...%(else)...%(end). +An example to show the usage of `%(if)...%(then)...%(else)...%(end)`. This prefixes the current branch with a star. ------------ @@ -525,7 +441,7 @@ git for-each-ref --format="%(if)%(HEAD)%(then)* %(else) %(end)%(refname:short)" ------------ -An example to show the usage of %(if)...%(then)...%(end). +An example to show the usage of `%(if)...%(then)...%(end)`. This prints the authorname, if present. ------------ diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc index a8b53db9a6..9a7807ca71 100644 --- a/Documentation/git-format-patch.adoc +++ b/Documentation/git-format-patch.adoc @@ -295,7 +295,8 @@ header). Note also that `git send-email` already handles this transformation for you, and this option should not be used if you are feeding the result to `git send-email`. ---[no-]force-in-body-from:: +--force-in-body-from:: +--no-force-in-body-from:: With the e-mail sender specified via the `--from` option, by default, an in-body "From:" to identify the real author of the commit is added at the top of the commit log message if @@ -314,7 +315,8 @@ feeding the result to `git send-email`. `Cc:`, and custom) headers added so far from config or command line. ---[no-]cover-letter:: +--cover-letter:: +--no-cover-letter:: In addition to the patches, generate a cover letter file containing the branch description, shortlog and the overall diffstat. You can fill in a description in the file before sending it out. @@ -379,7 +381,8 @@ configuration options in linkgit:git-notes[1] to use this workflow). The default is `--no-notes`, unless the `format.notes` configuration is set. ---[no-]signature=<signature>:: +--signature=<signature>:: +--no-signature:: Add a signature to each message produced. Per RFC 3676 the signature is separated from the body by a line with '-- ' on it. If the signature option is omitted the signature defaults to the Git version @@ -411,7 +414,8 @@ you can use `--suffix=-patch` to get `0001-description-of-my-change-patch`. Output an all-zero hash in each patch's From header instead of the hash of the commit. ---[no-]base[=<commit>]:: +--no-base:: +--base[=<commit>]:: Record the base tree information to identify the state the patch series applies to. See the BASE TREE INFORMATION section below for details. If <commit> is "auto", a base commit is @@ -587,13 +591,19 @@ an external editor to keep Thunderbird from mangling the patches. Approach #1 (add-on) ^^^^^^^^^^^^^^^^^^^^ -Install the Toggle Word Wrap add-on that is available from -https://addons.mozilla.org/thunderbird/addon/toggle-word-wrap/ -It adds a menu entry "Enable Word Wrap" in the composer's "Options" menu +Install the Toggle Line Wrap add-on that is available from +https://addons.thunderbird.net/thunderbird/addon/toggle-line-wrap +It adds a button "Line Wrap" to the composer's toolbar that you can tick off. Now you can compose the message as you otherwise do (cut + paste, 'git format-patch' | 'git imap-send', etc), but you have to insert line breaks manually in any text that you type. +As a bonus feature, the add-on can detect patch text in the composer +and warns when line wrapping has not yet been turned off. + +The add-on requires a few tweaks of the advanced configuration +(about:config). These are listed on the download page. + Approach #2 (configuration) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Three steps: diff --git a/Documentation/git-fsck.adoc b/Documentation/git-fsck.adoc index 11203ba925..1751f692d4 100644 --- a/Documentation/git-fsck.adoc +++ b/Documentation/git-fsck.adoc @@ -31,7 +31,8 @@ index file, all SHA-1 references in the `refs` namespace, and all reflogs Print out objects that exist but that aren't reachable from any of the reference nodes. ---[no-]dangling:: +--dangling:: +--no-dangling:: Print objects that exist but that are never 'directly' used (default). `--no-dangling` can be used to omit this information from the output. @@ -97,14 +98,16 @@ care about this output and want to speed it up further. compatible with linkgit:git-rev-parse[1], e.g. `HEAD@{1234567890}~25^2:src/`. ---[no-]progress:: +--progress:: +--no-progress:: Progress status is reported on the standard error stream by default when it is attached to a terminal, unless --no-progress or --verbose is specified. --progress forces progress status even if the standard error stream is not directed to a terminal. ---[no-]references:: +--references:: +--no-references:: Control whether to check the references database consistency via 'git refs verify'. See linkgit:git-refs[1] for details. The default is to check the references database. diff --git a/Documentation/git-gc.adoc b/Documentation/git-gc.adoc index 526ce01463..6fed646dd8 100644 --- a/Documentation/git-gc.adoc +++ b/Documentation/git-gc.adoc @@ -53,11 +53,13 @@ configuration options such as `gc.auto` and `gc.autoPackLimit`, all other housekeeping tasks (e.g. rerere, working trees, reflog...) will be performed as well. ---[no-]detach:: +--detach:: +--no-detach:: Run in the background if the system supports it. This option overrides the `gc.autoDetach` config. ---[no-]cruft:: +--cruft:: +--no-cruft:: When expiring unreachable objects, pack them separately into a cruft pack instead of storing them as loose objects. `--cruft` is on by default. diff --git a/Documentation/git-http-fetch.adoc b/Documentation/git-http-fetch.adoc index 4ec7c68d3b..2200f073c4 100644 --- a/Documentation/git-http-fetch.adoc +++ b/Documentation/git-http-fetch.adoc @@ -25,8 +25,11 @@ commit-id:: Either the hash or the filename under [URL]/refs/ to pull. --a, -c, -t:: +-a:: +-c:: +-t:: These options are ignored for historical reasons. + -v:: Report what is downloaded. diff --git a/Documentation/git-index-pack.adoc b/Documentation/git-index-pack.adoc index 270056cf63..18036953c0 100644 --- a/Documentation/git-index-pack.adoc +++ b/Documentation/git-index-pack.adoc @@ -36,7 +36,8 @@ OPTIONS fails if the name of packed archive does not end with .pack). ---[no-]rev-index:: +--rev-index:: +--no-rev-index:: When this flag is provided, generate a reverse index (a `.rev` file) corresponding to the given pack. If `--verify` is given, ensure that the existing diff --git a/Documentation/git-interpret-trailers.adoc b/Documentation/git-interpret-trailers.adoc index 82c8780d93..fd335fe772 100644 --- a/Documentation/git-interpret-trailers.adoc +++ b/Documentation/git-interpret-trailers.adoc @@ -142,8 +142,8 @@ OPTIONS provided with '--if-exists' overrides the `trailer.ifExists` and any applicable `trailer.<keyAlias>.ifExists` configuration variables and applies to all '--trailer' options until the next occurrence of - '--if-exists' or '--no-if-exists'. Upon encountering '--no-if-exists, clear the - effect of any previous use of '--if-exists, such that the relevant configuration + '--if-exists' or '--no-if-exists'. Upon encountering '--no-if-exists', clear the + effect of any previous use of '--if-exists', such that the relevant configuration variables are no longer overridden. Possible actions are `addIfDifferent`, `addIfDifferentNeighbor`, `add`, `replace` and `doNothing`. @@ -154,8 +154,8 @@ OPTIONS provided with '--if-missing' overrides the `trailer.ifMissing` and any applicable `trailer.<keyAlias>.ifMissing` configuration variables and applies to all '--trailer' options until the next occurrence of - '--if-missing' or '--no-if-missing'. Upon encountering '--no-if-missing, - clear the effect of any previous use of '--if-missing, such that the relevant + '--if-missing' or '--no-if-missing'. Upon encountering '--no-if-missing', + clear the effect of any previous use of '--if-missing', such that the relevant configuration variables are no longer overridden. Possible actions are `doNothing` or `add`. diff --git a/Documentation/git-last-modified.adoc b/Documentation/git-last-modified.adoc new file mode 100644 index 0000000000..602843e095 --- /dev/null +++ b/Documentation/git-last-modified.adoc @@ -0,0 +1,54 @@ +git-last-modified(1) +==================== + +NAME +---- +git-last-modified - EXPERIMENTAL: Show when files were last modified + + +SYNOPSIS +-------- +[synopsis] +git last-modified [--recursive] [--show-trees] [<revision-range>] [[--] <path>...] + +DESCRIPTION +----------- + +Shows which commit last modified each of the relevant files and subdirectories. +A commit renaming a path, or changing it's mode is also taken into account. + +THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE. + +OPTIONS +------- + +`-r`:: +`--recursive`:: + Instead of showing tree entries, step into subtrees and show all entries + inside them recursively. + +`-t`:: +`--show-trees`:: + Show tree entries even when recursing into them. It has no effect + without `--recursive`. + +`<revision-range>`:: + Only traverse commits in the specified revision range. When no + `<revision-range>` is specified, it defaults to `HEAD` (i.e. the whole + history leading to the current commit). For a complete list of ways to + spell `<revision-range>`, see the 'Specifying Ranges' section of + linkgit:gitrevisions[7]. + +`[--] <path>...`:: + For each _<path>_ given, the commit which last modified it is returned. + Without an optional path parameter, all files and subdirectories + in path traversal the are included in the output. + +SEE ALSO +-------- +linkgit:git-blame[1], +linkgit:git-log[1]. + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-log.adoc b/Documentation/git-log.adoc index b6f3d92c43..e304739c5e 100644 --- a/Documentation/git-log.adoc +++ b/Documentation/git-log.adoc @@ -73,8 +73,10 @@ used as decoration if they match `HEAD`, `refs/heads/`, `refs/remotes/`, Print out the ref name given on the command line by which each commit was reached. -`--[no-]mailmap`:: -`--[no-]use-mailmap`:: +`--mailmap`:: +`--no-mailmap`:: +`--use-mailmap`:: +`--no-use-mailmap`:: Use mailmap file to map author and committer names and email addresses to canonical real names and email addresses. See linkgit:git-shortlog[1]. diff --git a/Documentation/git-merge-tree.adoc b/Documentation/git-merge-tree.adoc index f824eea61f..271ab220e8 100644 --- a/Documentation/git-merge-tree.adoc +++ b/Documentation/git-merge-tree.adoc @@ -59,7 +59,8 @@ OPTIONS do not list filenames multiple times if they have multiple conflicting stages). ---[no-]messages:: +--messages:: +--no-messages:: Write any informational messages such as "Auto-merging <path>" or CONFLICT notices to the end of stdout. If unspecified, the default is to include these messages if there are merge diff --git a/Documentation/git-multi-pack-index.adoc b/Documentation/git-multi-pack-index.adoc index b6cd0d7f85..2f642697e9 100644 --- a/Documentation/git-multi-pack-index.adoc +++ b/Documentation/git-multi-pack-index.adoc @@ -25,10 +25,11 @@ OPTIONS + `<dir>` must be an alternate of the current repository. ---[no-]progress:: +--progress:: +--no-progress:: Turn progress on/off explicitly. If neither is specified, progress is shown if standard error is connected to a terminal. Supported by - sub-commands `write`, `verify`, `expire`, and `repack. + sub-commands `write`, `verify`, `expire`, and `repack`. The following subcommands are available: diff --git a/Documentation/git-p4.adoc b/Documentation/git-p4.adoc index f97b786bf9..59edd24134 100644 --- a/Documentation/git-p4.adoc +++ b/Documentation/git-p4.adoc @@ -66,6 +66,7 @@ Clone ~~~~~ Generally, 'git p4 clone' is used to create a new Git directory from an existing p4 repository: + ------------ $ git p4 clone //depot/path/project ------------ diff --git a/Documentation/git-pack-objects.adoc b/Documentation/git-pack-objects.adoc index eba014c406..71b9682485 100644 --- a/Documentation/git-pack-objects.adoc +++ b/Documentation/git-pack-objects.adoc @@ -243,7 +243,8 @@ depth is 4095. Add --no-reuse-object if you want to force a uniform compression level on all data no matter the source. ---[no-]sparse:: +--sparse:: +--no-sparse:: Toggle the "sparse" algorithm to determine which objects to include in the pack, when combined with the "--revs" option. This algorithm only walks trees that appear in paths that introduce new objects. diff --git a/Documentation/git-pull.adoc b/Documentation/git-pull.adoc index 3f4ecc4730..48e924a10a 100644 --- a/Documentation/git-pull.adoc +++ b/Documentation/git-pull.adoc @@ -87,7 +87,8 @@ OPTIONS --verbose:: Pass --verbose to git-fetch and git-merge. ---[no-]recurse-submodules[=(yes|on-demand|no)]:: +--recurse-submodules[=(yes|on-demand|no)]:: +--no-recurse-submodules:: This option controls if new commits of populated submodules should be fetched, and if the working trees of active submodules should be updated, too (see linkgit:git-fetch[1], linkgit:git-config[1] and diff --git a/Documentation/git-push.adoc b/Documentation/git-push.adoc index d1978650d6..5f5408e2c0 100644 --- a/Documentation/git-push.adoc +++ b/Documentation/git-push.adoc @@ -197,7 +197,8 @@ already exists on the remote side. with configuration variable `push.followTags`. For more information, see `push.followTags` in linkgit:git-config[1]. ---[no-]signed:: +--signed:: +--no-signed:: --signed=(true|false|if-asked):: GPG-sign the push request to update refs on the receiving side, to allow it to be checked by the hooks and/or be @@ -208,7 +209,8 @@ already exists on the remote side. will also fail if the actual call to `gpg --sign` fails. See linkgit:git-receive-pack[1] for the details on the receiving end. ---[no-]atomic:: +--atomic:: +--no-atomic:: Use an atomic transaction on the remote side if available. Either all refs are updated, or on error, no refs are updated. If the server does not support atomic pushes the push will fail. @@ -232,7 +234,8 @@ already exists on the remote side. repository over ssh, and you do not have the program in a directory on the default $PATH. ---[no-]force-with-lease:: +--force-with-lease:: +--no-force-with-lease:: --force-with-lease=<refname>:: --force-with-lease=<refname>:<expect>:: Usually, "git push" refuses to update a remote ref that is @@ -350,7 +353,8 @@ one branch, use a `+` in front of the refspec to push (e.g `git push origin +master` to force a push to the `master` branch). See the `<refspec>...` section above for details. ---[no-]force-if-includes:: +--force-if-includes:: +--no-force-if-includes:: Force an update only if the tip of the remote-tracking ref has been integrated locally. + @@ -377,7 +381,8 @@ Specifying `--no-force-if-includes` disables this behavior. linkgit:git-pull[1] and other commands. For more information, see `branch.<name>.merge` in linkgit:git-config[1]. ---[no-]thin:: +--thin:: +--no-thin:: These options are passed to linkgit:git-send-pack[1]. A thin transfer significantly reduces the amount of sent data when the sender and receiver share many of the same objects in common. The default is @@ -419,7 +424,8 @@ When using 'on-demand' or 'only', if a submodule has a "push.recurseSubmodules={on-demand,only}" or "submodule.recurse" configuration, further recursion will occur. In this case, "only" is treated as "on-demand". ---[no-]verify:: +--verify:: +--no-verify:: Toggle the pre-push hook (see linkgit:githooks[5]). The default is --verify, giving the hook a chance to prevent the push. With --no-verify, the hook is bypassed completely. diff --git a/Documentation/git-range-diff.adoc b/Documentation/git-range-diff.adoc index db0e4279b5..b5e85d37f1 100644 --- a/Documentation/git-range-diff.adoc +++ b/Documentation/git-range-diff.adoc @@ -96,7 +96,8 @@ diff. --remerge-diff:: Convenience option, equivalent to `--diff-merges=remerge`. ---[no-]notes[=<ref>]:: +--notes[=<ref>]:: +--no-notes:: This flag is passed to the `git log` program (see linkgit:git-log[1]) that generates the patches. diff --git a/Documentation/git-read-tree.adoc b/Documentation/git-read-tree.adoc index 1c48c28996..1c04bba2b7 100644 --- a/Documentation/git-read-tree.adoc +++ b/Documentation/git-read-tree.adoc @@ -100,7 +100,8 @@ OPTIONS directories the index file and index output file are located in. ---[no-]recurse-submodules:: +--recurse-submodules:: +--no-recurse-submodules:: Using --recurse-submodules will update the content of all active submodules according to the commit recorded in the superproject by calling read-tree recursively, also setting the submodules' HEAD to be diff --git a/Documentation/git-rebase.adoc b/Documentation/git-rebase.adoc index 956d3048f5..005caf6164 100644 --- a/Documentation/git-rebase.adoc +++ b/Documentation/git-rebase.adoc @@ -16,49 +16,12 @@ SYNOPSIS DESCRIPTION ----------- -If `<branch>` is specified, `git rebase` will perform an automatic -`git switch <branch>` before doing anything else. Otherwise -it remains on the current branch. +Transplant a series of commits onto a different starting point. +You can also use `git rebase` to reorder or combine commits: see INTERACTIVE +MODE below for how to do that. -If `<upstream>` is not specified, the upstream configured in -`branch.<name>.remote` and `branch.<name>.merge` options will be used (see -linkgit:git-config[1] for details) and the `--fork-point` option is -assumed. If you are currently not on any branch or if the current -branch does not have a configured upstream, the rebase will abort. - -All changes made by commits in the current branch but that are not -in `<upstream>` are saved to a temporary area. This is the same set -of commits that would be shown by `git log <upstream>..HEAD`; or by -`git log 'fork_point'..HEAD`, if `--fork-point` is active (see the -description on `--fork-point` below); or by `git log HEAD`, if the -`--root` option is specified. - -The current branch is reset to `<upstream>` or `<newbase>` if the -`--onto` option was supplied. This has the exact same effect as -`git reset --hard <upstream>` (or `<newbase>`). `ORIG_HEAD` is set -to point at the tip of the branch before the reset. - -[NOTE] -`ORIG_HEAD` is not guaranteed to still point to the previous branch tip -at the end of the rebase if other commands that write that pseudo-ref -(e.g. `git reset`) are used during the rebase. The previous branch tip, -however, is accessible using the reflog of the current branch -(i.e. `@{1}`, see linkgit:gitrevisions[7]). - -The commits that were previously saved into the temporary area are -then reapplied to the current branch, one by one, in order. Note that -any commits in `HEAD` which introduce the same textual changes as a commit -in `HEAD..<upstream>` are omitted (i.e., a patch already accepted upstream -with a different commit message or timestamp will be skipped). - -It is possible that a merge failure will prevent this process from being -completely automatic. You will have to resolve any such merge failure -and run `git rebase --continue`. Another option is to bypass the commit -that caused the merge failure with `git rebase --skip`. To check out the -original `<branch>` and remove the `.git/rebase-apply` working files, use -the command `git rebase --abort` instead. - -Assume the following history exists and the current branch is "topic": +For example, imagine that you have been working on the `topic` branch in this +history, and you want to "catch up" to the work done on the `master` branch. ------------ A---B---C topic @@ -66,13 +29,11 @@ Assume the following history exists and the current branch is "topic": D---E---F---G master ------------ -From this point, the result of either of the following commands: - - - git rebase master - git rebase master topic - -would be: +You want to transplant the commits you made on `topic` since it diverged from +`master` (i.e. A, B, and C), on top of the current `master`. You can do this +by running `git rebase master` while the `topic` branch is checked out. If you +want to rebase `topic` while on another branch, `git rebase master topic` is a +shortcut for `git checkout topic && git rebase master`. ------------ A'--B'--C' topic @@ -80,30 +41,56 @@ would be: D---E---F---G master ------------ -*NOTE:* The latter form is just a short-hand of `git checkout topic` -followed by `git rebase master`. When rebase exits `topic` will -remain the checked-out branch. -If the upstream branch already contains a change you have made (e.g., -because you mailed a patch which was applied upstream), then that commit -will be skipped and warnings will be issued (if the 'merge' backend is -used). For example, running `git rebase master` on the following -history (in which `A'` and `A` introduce the same set of changes, but -have different committer information): +If there is a merge conflict during this process, `git rebase` will stop at the +first problematic commit and leave conflict markers. If this happens, you can do +one of these things: ------------- - A---B---C topic - / - D---E---A'---F master ------------- +1. Resolve the conflict. You can use `git diff` to find the markers (<<<<<<) + and make edits to resolve the conflict. For each file you edit, you need to + tell Git that the conflict has been resolved. You can mark the conflict as + resolved with `git add <filename>`. After resolving all of the conflicts, + you can continue the rebasing process with -will result in: + git rebase --continue ------------- - B'---C' topic - / - D---E---A'---F master ------------- +2. Stop the `git rebase` and return your branch to its original state with + + git rebase --abort + +3. Skip the commit that caused the merge conflict with + + git rebase --skip + +If you don't specify an `<upstream>` to rebase onto, the upstream configured in +`branch.<name>.remote` and `branch.<name>.merge` options will be used (see +linkgit:git-config[1] for details) and the `--fork-point` option is +assumed. If you are currently not on any branch or if the current +branch does not have a configured upstream, the rebase will abort. + +Here is a simplified description of what `git rebase <upstream>` does: + +1. Make a list of all commits on your current branch since it branched + off from `<upstream>` that do not have an equivalent commit in + `<upstream>`. +2. Check out `<upstream>` with the equivalent of + `git checkout --detach <upstream>`. +3. Replay the commits, one by one, in order. This is similar to running + `git cherry-pick <commit>` for each commit. See REBASING MERGES for how merges + are handled. +4. Update your branch to point to the final commit with the equivalent + of `git checkout -B <branch>`. + +[NOTE] +When starting the rebase, `ORIG_HEAD` is set to point to the commit at the tip +of the to-be-rebased branch. However, `ORIG_HEAD` is not guaranteed to still +point to that commit at the end of the rebase if other commands that change +`ORIG_HEAD` (like `git reset`) are used during the rebase. The previous branch +tip, however, is accessible using the reflog of the current branch (i.e. `@{1}`, +see linkgit:gitrevisions[7]. + +TRANSPLANTING A TOPIC BRANCH WITH --ONTO +---------------------------------------- Here is how you would transplant a topic branch based on one branch to another, to pretend that you forked the topic branch @@ -186,28 +173,6 @@ This is useful if F and G were flawed in some way, or should not be part of topicA. Note that the argument to `--onto` and the `<upstream>` parameter can be any valid commit-ish. -In case of conflict, `git rebase` will stop at the first problematic commit -and leave conflict markers in the tree. You can use `git diff` to locate -the markers (<<<<<<) and make edits to resolve the conflict. For each -file you edit, you need to tell Git that the conflict has been resolved, -typically this would be done with - - - git add <filename> - - -After resolving the conflict manually and updating the index with the -desired resolution, you can continue the rebasing process with - - - git rebase --continue - - -Alternatively, you can undo the 'git rebase' with - - - git rebase --abort - MODE OPTIONS ------------ @@ -253,6 +218,8 @@ As a special case, you may use "A\...B" as a shortcut for the merge base of A and B if there is exactly one merge base. You can leave out at most one of A and B, in which case it defaults to HEAD. +See TRANSPLANTING A TOPIC BRANCH WITH --ONTO above for examples. + --keep-base:: Set the starting point at which to create the new commits to the merge base of `<upstream>` and `<branch>`. Running @@ -687,7 +654,7 @@ In addition, the following pairs of options are incompatible: * --fork-point and --root BEHAVIORAL DIFFERENCES ------------------------ +---------------------- `git rebase` has two primary backends: 'apply' and 'merge'. (The 'apply' backend used to be known as the 'am' backend, but the name led to diff --git a/Documentation/git-reflog.adoc b/Documentation/git-reflog.adoc index 412f06b8fe..38af0c977a 100644 --- a/Documentation/git-reflog.adoc +++ b/Documentation/git-reflog.adoc @@ -8,16 +8,17 @@ git-reflog - Manage reflog information SYNOPSIS -------- -[verse] -'git reflog' [show] [<log-options>] [<ref>] -'git reflog list' -'git reflog expire' [--expire=<time>] [--expire-unreachable=<time>] +[synopsis] +git reflog [show] [<log-options>] [<ref>] +git reflog list +git reflog exists <ref> +git reflog write <ref> <old-oid> <new-oid> <message> +git reflog delete [--rewrite] [--updateref] + [--dry-run | -n] [--verbose] <ref>@{<specifier>}... +git reflog drop [--all [--single-worktree] | <refs>...] +git reflog expire [--expire=<time>] [--expire-unreachable=<time>] [--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...] -'git reflog delete' [--rewrite] [--updateref] - [--dry-run | -n] [--verbose] <ref>@{<specifier>}... -'git reflog drop' [--all [--single-worktree] | <refs>...] -'git reflog exists' <ref> DESCRIPTION ----------- @@ -43,11 +44,15 @@ actions, and in addition the `HEAD` reflog records branch switching. The "list" subcommand lists all refs which have a corresponding reflog. -The "expire" subcommand prunes older reflog entries. Entries older -than `expire` time, or entries older than `expire-unreachable` time -and not reachable from the current tip, are removed from the reflog. -This is typically not used directly by end users -- instead, see -linkgit:git-gc[1]. +The "exists" subcommand checks whether a ref has a reflog. It exits +with zero status if the reflog exists, and non-zero status if it does +not. + +The "write" subcommand writes a single entry to the reflog of a given +reference. This new entry is appended to the reflog and will thus become +the most recent entry. The reference name must be fully qualified. Both the old +and new object IDs must not be abbreviated and must point to existing objects. +The reflog message gets normalized. The "delete" subcommand deletes single entries from the reflog, but not the reflog itself. Its argument must be an _exact_ entry (e.g. "`git @@ -58,9 +63,11 @@ The "drop" subcommand completely removes the reflog for the specified references. This is in contrast to "expire" and "delete", both of which can be used to delete reflog entries, but not the reflog itself. -The "exists" subcommand checks whether a ref has a reflog. It exits -with zero status if the reflog exists, and non-zero status if it does -not. +The "expire" subcommand prunes older reflog entries. Entries older +than `expire` time, or entries older than `expire-unreachable` time +and not reachable from the current tip, are removed from the reflog. +This is typically not used directly by end users -- instead, see +linkgit:git-gc[1]. OPTIONS ------- @@ -71,18 +78,37 @@ Options for `show` `git reflog show` accepts any of the options accepted by `git log`. +Options for `delete` +~~~~~~~~~~~~~~~~~~~~ + +`git reflog delete` accepts options `--updateref`, `--rewrite`, `-n`, +`--dry-run`, and `--verbose`, with the same meanings as when they are +used with `expire`. + +Options for `drop` +~~~~~~~~~~~~~~~~~~ + +`--all`:: + Drop the reflogs of all references from all worktrees. + +`--single-worktree`:: + By default when `--all` is specified, reflogs from all working + trees are dropped. This option limits the processing to reflogs + from the current working tree only. + + Options for `expire` ~~~~~~~~~~~~~~~~~~~~ ---all:: +`--all`:: Process the reflogs of all references. ---single-worktree:: +`--single-worktree`:: By default when `--all` is specified, reflogs from all working trees are processed. This option limits the processing to reflogs from the current working tree only. ---expire=<time>:: +`--expire=<time>`:: Prune entries older than the specified time. If this option is not specified, the expiration time is taken from the configuration setting `gc.reflogExpire`, which in turn @@ -90,7 +116,7 @@ Options for `expire` of their age; `--expire=never` turns off pruning of reachable entries (but see `--expire-unreachable`). ---expire-unreachable=<time>:: +`--expire-unreachable=<time>`:: Prune entries older than `<time>` that are not reachable from the current tip of the branch. If this option is not specified, the expiration time is taken from the configuration @@ -100,17 +126,17 @@ Options for `expire` turns off early pruning of unreachable entries (but see `--expire`). ---updateref:: +`--updateref`:: Update the reference to the value of the top reflog entry (i.e. <ref>@\{0\}) if the previous top entry was pruned. (This option is ignored for symbolic references.) ---rewrite:: +`--rewrite`:: If a reflog entry's predecessor is pruned, adjust its "old" SHA-1 to be equal to the "new" SHA-1 field of the entry that now precedes it. ---stale-fix:: +`--stale-fix`:: Prune any reflog entries that point to "broken commits". A broken commit is a commit that is not reachable from any of the reference tips and that refers, directly or indirectly, to @@ -121,33 +147,15 @@ has the same cost as 'git prune'. It is primarily intended to fix corruption caused by garbage collecting using older versions of Git, which didn't protect objects referred to by reflogs. --n:: ---dry-run:: +`-n`:: +`--dry-run`:: Do not actually prune any entries; just show what would have been pruned. ---verbose:: +`--verbose`:: Print extra information on screen. -Options for `delete` -~~~~~~~~~~~~~~~~~~~~ - -`git reflog delete` accepts options `--updateref`, `--rewrite`, `-n`, -`--dry-run`, and `--verbose`, with the same meanings as when they are -used with `expire`. - -Options for `drop` -~~~~~~~~~~~~~~~~~~ - ---all:: - Drop the reflogs of all references from all worktrees. - ---single-worktree:: - By default when `--all` is specified, reflogs from all working - trees are dropped. This option limits the processing to reflogs - from the current working tree only. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-refs.adoc b/Documentation/git-refs.adoc index 4d6dc994f9..bfa9b3ea2d 100644 --- a/Documentation/git-refs.adoc +++ b/Documentation/git-refs.adoc @@ -11,6 +11,14 @@ SYNOPSIS [synopsis] git refs migrate --ref-format=<format> [--no-reflog] [--dry-run] git refs verify [--strict] [--verbose] +git refs list [--count=<count>] [--shell|--perl|--python|--tcl] + [(--sort=<key>)...] [--format=<format>] + [--include-root-refs] [--points-at=<object>] + [--merged[=<object>]] [--no-merged[=<object>]] + [--contains[=<object>]] [--no-contains[=<object>]] + [(--exclude=<pattern>)...] [--start-after=<marker>] + [ --stdin | (<pattern>...)] +git refs exists <ref> DESCRIPTION ----------- @@ -20,43 +28,58 @@ This command provides low-level access to refs. COMMANDS -------- -migrate:: +`migrate`:: Migrate ref store between different formats. -verify:: +`verify`:: Verify reference database consistency. +list:: + List references in the repository with support for filtering, + formatting, and sorting. This subcommand is an alias for + linkgit:git-for-each-ref[1] and offers identical functionality. + +exists:: + Check whether the given reference exists. Returns an exit code of 0 if + it does, 2 if it is missing, and 1 in case looking up the reference + failed with an error other than the reference being missing. This does + not verify whether the reference resolves to an actual object. + OPTIONS ------- -The following options are specific to 'git refs migrate': +The following options are specific to `git refs migrate`: ---ref-format=<format>:: +`--ref-format=<format>`:: The ref format to migrate the ref store to. Can be one of: + include::ref-storage-format.adoc[] ---dry-run:: +`--dry-run`:: Perform the migration, but do not modify the repository. The migrated refs will be written into a separate directory that can be inspected separately. The name of the directory will be reported on stdout. This can be used to double check that the migration works as expected before performing the actual migration. ---reflog:: ---no-reflog:: +`--reflog`:: +`--no-reflog`:: Choose between migrating the reflog data to the new backend, and discarding them. The default is "--reflog", to migrate. -The following options are specific to 'git refs verify': +The following options are specific to `git refs verify`: ---strict:: +`--strict`:: Enable stricter error checking. This will cause warnings to be reported as errors. See linkgit:git-fsck[1]. ---verbose:: +`--verbose`:: When verifying the reference database consistency, be chatty. +The following options are specific to 'git refs list': + +include::for-each-ref-options.adoc[] + KNOWN LIMITATIONS ----------------- diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc new file mode 100644 index 0000000000..209afd1b61 --- /dev/null +++ b/Documentation/git-repo.adoc @@ -0,0 +1,89 @@ +git-repo(1) +=========== + +NAME +---- +git-repo - Retrieve information about the repository + +SYNOPSIS +-------- +[synopsis] +git repo info [--format=(keyvalue|nul)] [-z] [<key>...] + +DESCRIPTION +----------- +Retrieve information about the repository. + +THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE. + +COMMANDS +-------- +`info [--format=(keyvalue|nul)] [-z] [<key>...]`:: + Retrieve metadata-related information about the current repository. Only + the requested data will be returned based on their keys (see "INFO KEYS" + section below). ++ +The values are returned in the same order in which their respective keys were +requested. ++ +The output format can be chosen through the flag `--format`. Two formats are +supported: ++ +`keyvalue`::: + output key-value pairs one per line using the `=` character as + the delimiter between the key and the value. Values containing "unusual" + characters are quoted as explained for the configuration variable + `core.quotePath` (see linkgit:git-config[1]). This is the default. + +`nul`::: + similar to `keyvalue`, but using a newline character as the delimiter + between the key and the value and using a NUL character after each value. + This format is better suited for being parsed by another applications than + `keyvalue`. Unlike in the `keyvalue` format, the values are never quoted. ++ +`-z` is an alias for `--format=nul`. + +INFO KEYS +--------- +In order to obtain a set of values from `git repo info`, you should provide +the keys that identify them. Here's a list of the available keys and the +values that they return: + +`layout.bare`:: + `true` if this is a bare repository, otherwise `false`. + +`layout.shallow`:: + `true` if this is a shallow repository, otherwise `false`. + +`object.format`:: + The object format (hash algorithm) used in the repository. + +`references.format`:: + The reference storage format. The valid values are: ++ +include::ref-storage-format.adoc[] + +EXAMPLES +-------- + +* Retrieves the reference format of the current repository: ++ +------------ +git repo info references.format +------------ ++ + +* Retrieves whether the current repository is bare and whether it is shallow +using the `nul` format: ++ +------------ +git repo info --format=nul layout.bare layout.shallow +------------ + +SEE ALSO +-------- +linkgit:git-rev-parse[1] + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-reset.adoc b/Documentation/git-reset.adoc index 50e8a0ba6f..3b9ba9aee9 100644 --- a/Documentation/git-reset.adoc +++ b/Documentation/git-reset.adoc @@ -90,7 +90,8 @@ but carries forward unmerged index entries. If a file that is different between _<commit>_ and `HEAD` has local changes, reset is aborted. -`--[no-]recurse-submodules`:: +`--recurse-submodules`:: +`--no-recurse-submodules`:: When the working tree is updated, using `--recurse-submodules` will also recursively reset the working tree of all active submodules according to the commit recorded in the superproject, also setting diff --git a/Documentation/git-send-email.adoc b/Documentation/git-send-email.adoc index 5335502d68..263b977353 100644 --- a/Documentation/git-send-email.adoc +++ b/Documentation/git-send-email.adoc @@ -115,7 +115,8 @@ illustration below where `[PATCH v2 0/3]` is in reply to `[PATCH 0/2]`: Only necessary if `--compose` is also set. If `--compose` is not set, this will be prompted for. ---[no-]outlook-id-fix:: +--outlook-id-fix:: +--no-outlook-id-fix:: Microsoft Outlook SMTP servers discard the Message-ID sent via email and assign a new random Message-ID, thus breaking threads. + @@ -299,6 +300,32 @@ must be used for each option. commands and replies will be printed. Useful to debug TLS connection and authentication problems. +--imap-sent-folder=<folder>:: + Some email providers (e.g. iCloud) do not send a copy of the emails sent + using SMTP to the `Sent` folder or similar in your mailbox. Use this option + to use `git imap-send` to send a copy of the emails to the folder specified + using this option. You can run `git imap-send --list` to get a list of + valid folder names, including the correct name of the `Sent` folder in + your mailbox. You can also use this option to send emails to a dedicated + IMAP folder of your choice. ++ +This feature requires setting up `git imap-send`. See linkgit:git-imap-send[1] +for instructions. + +--use-imap-only:: +--no-use-imap-only:: + If this is set, all emails will only be copied to the IMAP folder specified + with `--imap-sent-folder` or `sendemail.imapSentFolder` and will not be sent + to the recipients. Useful if you just want to create a draft of the emails + and use another email client to send them. + If disabled with `--no-use-imap-only`, the emails will be sent like usual. + Disabled by default, but the `sendemail.useImapOnly` configuration + variable can be used to enable it. + ++ +This feature requires setting up `git imap-send`. See linkgit:git-imap-send[1] +for instructions. + --batch-size=<num>:: Some email servers (e.g. 'smtp.163.com') limit the number of emails to be sent per session (connection) and this will lead to a failure when @@ -350,7 +377,8 @@ Automating --no-header-cmd:: Disable any header command in use. ---[no-]chain-reply-to:: +--chain-reply-to:: +--no-chain-reply-to:: If this is set, each email will be sent as a reply to the previous email sent. If disabled with `--no-chain-reply-to`, all emails after the first will be sent as replies to the first email sent. When using @@ -364,19 +392,22 @@ Automating values in the `sendemail` section. The default identity is the value of `sendemail.identity`. ---[no-]signed-off-by-cc:: +--signed-off-by-cc:: +--no-signed-off-by-cc:: If this is set, add emails found in the `Signed-off-by` trailer or `Cc:` lines to the cc list. Default is the value of `sendemail.signedOffByCc` configuration value; if that is unspecified, default to `--signed-off-by-cc`. ---[no-]cc-cover:: +--cc-cover:: +--no-cc-cover:: If this is set, emails found in `Cc:` headers in the first patch of the series (typically the cover letter) are added to the cc list for each email set. Default is the value of `sendemail.ccCover` configuration value; if that is unspecified, default to `--no-cc-cover`. ---[no-]to-cover:: +--to-cover:: +--no-to-cover:: If this is set, emails found in `To:` headers in the first patch of the series (typically the cover letter) are added to the to list for each email set. Default is the value of `sendemail.toCover` @@ -407,12 +438,14 @@ Default is the value of `sendemail.suppressCc` configuration value; if that is unspecified, default to `self` if `--suppress-from` is specified, as well as `body` if `--no-signed-off-cc` is specified. ---[no-]suppress-from:: +--suppress-from:: +--no-suppress-from:: If this is set, do not add the `From:` address to the `Cc:` list. Default is the value of `sendemail.suppressFrom` configuration value; if that is unspecified, default to `--no-suppress-from`. ---[no-]thread:: +--thread:: +--no-thread:: If this is set, the `In-Reply-To` and `References` headers will be added to each email sent. Whether each mail refers to the previous email (`deep` threading per `git format-patch` @@ -430,7 +463,8 @@ exists when `git send-email` is asked to add it (especially note that Failure to do so may not produce the expected result in the recipient's MUA. ---[no-]mailmap:: +--mailmap:: +--no-mailmap:: Use the mailmap file (see linkgit:gitmailmap[5]) to map all addresses to their canonical real name and email address. Additional mailmap data specific to `git send-email` may be provided using the @@ -459,7 +493,8 @@ have been specified, in which case default to `compose`. --dry-run:: Do everything except actually send the emails. ---[no-]format-patch:: +--format-patch:: +--no-format-patch:: When an argument may be understood either as a reference or as a file name, choose to understand it as a format-patch argument (`--format-patch`) or as a file name (`--no-format-patch`). By default, when such a conflict @@ -469,7 +504,8 @@ have been specified, in which case default to `compose`. Make `git send-email` less verbose. One line per email should be all that is output. ---[no-]validate:: +--validate:: +--no-validate:: Perform sanity checks on patches. Currently, validation means the following: + @@ -521,10 +557,10 @@ edit `~/.gitconfig` to specify your account settings: ---- [sendemail] - smtpEncryption = tls + smtpEncryption = ssl smtpServer = smtp.gmail.com smtpUser = yourname@gmail.com - smtpServerPort = 587 + smtpServerPort = 465 ---- Gmail does not allow using your regular password for `git send-email`. @@ -542,10 +578,10 @@ if you want to use `OAUTHBEARER`, edit your `~/.gitconfig` file and add ---- [sendemail] - smtpEncryption = tls + smtpEncryption = ssl smtpServer = smtp.gmail.com smtpUser = yourname@gmail.com - smtpServerPort = 587 + smtpServerPort = 465 smtpAuth = OAUTHBEARER ---- diff --git a/Documentation/git-send-pack.adoc b/Documentation/git-send-pack.adoc index b9e73f2e77..811193f16c 100644 --- a/Documentation/git-send-pack.adoc +++ b/Documentation/git-send-pack.adoc @@ -71,7 +71,8 @@ be in a separate packet, and the list must end with a flush packet. fails to update then the entire push will fail without changing any refs. ---[no-]signed:: +--signed:: +--no-signed:: --signed=(true|false|if-asked):: GPG-sign the push request to update refs on the receiving side, to allow it to be checked by the hooks and/or be diff --git a/Documentation/git-submodule.adoc b/Documentation/git-submodule.adoc index 503c84a200..95beaee561 100644 --- a/Documentation/git-submodule.adoc +++ b/Documentation/git-submodule.adoc @@ -442,7 +442,8 @@ options carefully. clone with a history truncated to the specified number of revisions. See linkgit:git-clone[1] ---[no-]recommend-shallow:: +--recommend-shallow:: +--no-recommend-shallow:: This option is only valid for the update command. The initial clone of a submodule will use the recommended `submodule.<name>.shallow` as provided by the `.gitmodules` file @@ -454,7 +455,8 @@ options carefully. Clone new submodules in parallel with as many jobs. Defaults to the `submodule.fetchJobs` option. ---[no-]single-branch:: +--single-branch:: +--no-single-branch:: This option is only valid for the update command. Clone only one branch during update: HEAD or one specified by --branch. diff --git a/Documentation/git-svn.adoc b/Documentation/git-svn.adoc index bcf7d84a87..c26c12bab3 100644 --- a/Documentation/git-svn.adoc +++ b/Documentation/git-svn.adoc @@ -1012,9 +1012,11 @@ branch. If you do merge, note the following rule: 'git svn dcommit' will attempt to commit on top of the SVN commit named in + ------------------------------------------------------------------------ git log --grep=^git-svn-id: --first-parent -1 ------------------------------------------------------------------------ + You 'must' therefore ensure that the most recent commit of the branch you want to dcommit to is the 'first' parent of the merge. Chaos will ensue otherwise, especially if the first parent is an older commit on diff --git a/Documentation/git-update-index.adoc b/Documentation/git-update-index.adoc index 7128aed540..9bea9fab9a 100644 --- a/Documentation/git-update-index.adoc +++ b/Documentation/git-update-index.adoc @@ -86,7 +86,8 @@ OPTIONS --chmod=(+|-)x:: Set the execute permissions on the updated files. ---[no-]assume-unchanged:: +--assume-unchanged:: +--no-assume-unchanged:: When this flag is specified, the object names recorded for the paths are not updated. Instead, this option sets/unsets the "assume unchanged" bit for the @@ -108,18 +109,21 @@ you will need to handle the situation manually. Like `--refresh`, but checks stat information unconditionally, without regard to the "assume unchanged" setting. ---[no-]skip-worktree:: +--skip-worktree:: +--no-skip-worktree:: When one of these flags is specified, the object names recorded for the paths are not updated. Instead, these options set and unset the "skip-worktree" bit for the paths. See section "Skip-worktree bit" below for more information. ---[no-]ignore-skip-worktree-entries:: +--ignore-skip-worktree-entries:: +--no-ignore-skip-worktree-entries:: Do not remove skip-worktree (AKA "index-only") entries even when the `--remove` option was specified. ---[no-]fsmonitor-valid:: +--fsmonitor-valid:: +--no-fsmonitor-valid:: When one of these flags is specified, the object names recorded for the paths are not updated. Instead, these options set and unset the "fsmonitor valid" bit for the paths. See diff --git a/Documentation/git-upload-pack.adoc b/Documentation/git-upload-pack.adoc index 516d1639d9..9167a321d0 100644 --- a/Documentation/git-upload-pack.adoc +++ b/Documentation/git-upload-pack.adoc @@ -25,7 +25,8 @@ repository. For push operations, see 'git send-pack'. OPTIONS ------- ---[no-]strict:: +--strict:: +--no-strict:: Do not try <directory>/.git/ if <directory> is not a Git directory. --timeout=<n>:: diff --git a/Documentation/git-worktree.adoc b/Documentation/git-worktree.adoc index 8340b7f028..389e669ac0 100644 --- a/Documentation/git-worktree.adoc +++ b/Documentation/git-worktree.adoc @@ -200,13 +200,15 @@ To remove a locked worktree, specify `--force` twice. With `add`, detach `HEAD` in the new worktree. See "DETACHED HEAD" in linkgit:git-checkout[1]. ---[no-]checkout:: +--checkout:: +--no-checkout:: By default, `add` checks out `<commit-ish>`, however, `--no-checkout` can be used to suppress checkout in order to make customizations, such as configuring sparse-checkout. See "Sparse checkout" in linkgit:git-read-tree[1]. ---[no-]guess-remote:: +--guess-remote:: +--no-guess-remote:: With `worktree add <path>`, without `<commit-ish>`, instead of creating a new branch from `HEAD`, if there exists a tracking branch in exactly one remote matching the basename of `<path>`, @@ -216,7 +218,8 @@ To remove a locked worktree, specify `--force` twice. This can also be set up as the default behaviour by using the `worktree.guessRemote` config option. ---[no-]relative-paths:: +--relative-paths:: +--no-relative-paths:: Link worktrees using relative paths or absolute paths (default). Overrides the `worktree.useRelativePaths` config option, see linkgit:git-config[1]. @@ -224,7 +227,8 @@ This can also be set up as the default behaviour by using the With `repair`, the linking files will be updated if there's an absolute/relative mismatch, even if the links are correct. ---[no-]track:: +--track:: +--no-track:: When creating a new branch, if `<commit-ish>` is a branch, mark it as "upstream" from the new branch. This is the default if `<commit-ish>` is a remote-tracking branch. See diff --git a/Documentation/git.adoc b/Documentation/git.adoc index 743b7b00e4..03e9e69d25 100644 --- a/Documentation/git.adoc +++ b/Documentation/git.adoc @@ -684,7 +684,7 @@ other `GIT_PROGRESS_DELAY`:: A number controlling how many seconds to delay before showing - optional progress indicators. Defaults to 2. + optional progress indicators. Defaults to 1. `GIT_EDITOR`:: This environment variable overrides `$EDITOR` and `$VISUAL`. diff --git a/Documentation/gitk.adoc b/Documentation/gitk.adoc index 58ce40ddb1..5b34dcd077 100644 --- a/Documentation/gitk.adoc +++ b/Documentation/gitk.adoc @@ -163,16 +163,16 @@ used by default. If '$XDG_CONFIG_HOME' is not set it defaults to History ------- -Gitk was the first graphical repository browser. It's written in -tcl/tk. +Gitk was the first graphical repository browser, written by +Paul Mackerras in Tcl/Tk. 'gitk' is actually maintained as an independent project, but stable versions are distributed as part of the Git suite for the convenience of end users. -gitk-git/ comes from Paul Mackerras's gitk project: +`gitk-git/` comes from Johannes Sixt's gitk project: - git://ozlabs.org/~paulus/gitk + https://github.com/j6t/gitk SEE ALSO -------- diff --git a/Documentation/gitprotocol-http.adoc b/Documentation/gitprotocol-http.adoc index ec40a550cc..d024010414 100644 --- a/Documentation/gitprotocol-http.adoc +++ b/Documentation/gitprotocol-http.adoc @@ -318,7 +318,7 @@ Extra Parameter. Smart Service git-upload-pack ------------------------------- +----------------------------- This service reads from the repository pointed to by `$GIT_URL`. Clients MUST first perform ref discovery with diff --git a/Documentation/gitprotocol-v2.adoc b/Documentation/gitprotocol-v2.adoc index 9a57005d77..c7db103299 100644 --- a/Documentation/gitprotocol-v2.adoc +++ b/Documentation/gitprotocol-v2.adoc @@ -785,33 +785,64 @@ retrieving the header from a bundle at the indicated URI, and thus save themselves and the server(s) the request(s) needed to inspect the headers of that bundle or bundles. -promisor-remote=<pr-infos> -~~~~~~~~~~~~~~~~~~~~~~~~~~ +promisor-remote=<pr-info> +~~~~~~~~~~~~~~~~~~~~~~~~~ The server may advertise some promisor remotes it is using or knows about to a client which may want to use them as its promisor remotes, -instead of this repository. In this case <pr-infos> should be of the +instead of this repository. In this case <pr-info> should be of the form: - pr-infos = pr-info | pr-infos ";" pr-info + pr-info = pr-fields | pr-info ";" pr-fields - pr-info = "name=" pr-name | "name=" pr-name "," "url=" pr-url + pr-fields = pr-field | pr-fields "," pr-field -where `pr-name` is the urlencoded name of a promisor remote, and -`pr-url` the urlencoded URL of that promisor remote. + pr-field = field-name "=" field-value -In this case, if the client decides to use one or more promisor -remotes the server advertised, it can reply with -"promisor-remote=<pr-names>" where <pr-names> should be of the form: +where all the `field-name` and `field-value` in a given `pr-fields` +are field names and values related to a single promisor remote. A +given `field-name` MUST NOT appear more than once in given +`pr-fields`. + +The server MUST advertise at least the "name" and "url" field names +along with the associated field values, which are the name of a valid +remote and its URL, in each `pr-fields`. The "name" and "url" fields +MUST appear first in each pr-fields, in that order. + +After these mandatory fields, the server MAY advertise the following +optional fields in any order: + +`partialCloneFilter`:: The filter specification used by the remote. +Clients can use this to determine if the remote's filtering strategy +is compatible with their needs (e.g., checking if both use "blob:none"). +It corresponds to the "remote.<name>.partialCloneFilter" config setting. + +`token`:: An authentication token that clients can use when +connecting to the remote. It corresponds to the "remote.<name>.token" +config setting. + +No other fields are defined by the protocol at this time. Field names +are case-sensitive and MUST be transmitted exactly as specified +above. Clients MUST ignore fields they don't recognize to allow for +future protocol extensions. + +For now, the client can only use information transmitted through these +fields to decide if it accepts the advertised promisor remote. In the +future that information might be used for other purposes though. + +Field values MUST be urlencoded. + +If the client decides to use one or more promisor remotes the server +advertised, it can reply with "promisor-remote=<pr-names>" where +<pr-names> should be of the form: pr-names = pr-name | pr-names ";" pr-name where `pr-name` is the urlencoded name of a promisor remote the server advertised and the client accepts. -Note that, everywhere in this document, `pr-name` MUST be a valid -remote name, and the ';' and ',' characters MUST be encoded if they -appear in `pr-name` or `pr-url`. +Note that, everywhere in this document, the ';' and ',' characters +MUST be encoded if they appear in `pr-name` or `field-value`. If the server doesn't know any promisor remote that could be good for a client to use, or prefers a client not to use any promisor remote it @@ -822,9 +853,10 @@ In this case, or if the client doesn't want to use any promisor remote the server advertised, the client shouldn't advertise the "promisor-remote" capability at all in its reply. -The "promisor.advertise" and "promisor.acceptFromServer" configuration -options can be used on the server and client side to control what they -advertise or accept respectively. See the documentation of these +On the server side, the "promisor.advertise" and "promisor.sendFields" +configuration options can be used to control what it advertises. On +the client side, the "promisor.acceptFromServer" configuration option +can be used to control what it accepts. See the documentation of these configuration options for more information. Note that in the future it would be nice if the "promisor-remote" diff --git a/Documentation/gitsubmodules.adoc b/Documentation/gitsubmodules.adoc index f7b5a25a0c..2082296199 100644 --- a/Documentation/gitsubmodules.adoc +++ b/Documentation/gitsubmodules.adoc @@ -8,6 +8,7 @@ gitsubmodules - Mounting one repository inside another SYNOPSIS -------- .gitmodules, $GIT_DIR/config + ------------------ git submodule git <command> --recurse-submodules @@ -240,7 +241,7 @@ Workflow for a third party library Workflow for an artificially split repo --------------------------------------- +--------------------------------------- # Enable recursion for relevant commands, such that # regular commands recurse into submodules by default diff --git a/Documentation/gitweb.conf.adoc b/Documentation/gitweb.conf.adoc index 1348e9b125..64bebb811c 100644 --- a/Documentation/gitweb.conf.adoc +++ b/Documentation/gitweb.conf.adoc @@ -178,7 +178,7 @@ $export_ok:: Show repository only if this file exists (in repository). Only effective if this variable evaluates to true. Can be set when building gitweb by setting `GITWEB_EXPORT_OK`. This path is - relative to `GIT_DIR`. git-daemon[1] uses 'git-daemon-export-ok', + relative to `GIT_DIR`. linkgit:git-daemon[1] uses 'git-daemon-export-ok', unless started with `--export-all`. By default this variable is not set, which means that this feature is turned off. diff --git a/Documentation/lint-delimited-sections.perl b/Documentation/lint-delimited-sections.perl new file mode 100755 index 0000000000..140b852e5d --- /dev/null +++ b/Documentation/lint-delimited-sections.perl @@ -0,0 +1,48 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +my $exit_code = 0; +sub report { + my ($msg) = @_; + print STDERR "$ARGV:$.: $msg\n"; + $exit_code = 1; +} + +my $line_length = 0; +my $in_section = 0; +my $section_header = ""; + + +while (my $line = <>) { + if (($line =~ /^\+?$/) || + ($line =~ /^\[.*\]$/) || + ($line =~ /^ifdef::/)) { + $line_length = 0; + } elsif ($line =~ /^[^-.]/) { + $line_length = length($line); + } elsif (($line =~ /^-{3,}$/) || ($line =~ /^\.{3,}$/)) { + if ($in_section) { + if ($line eq $section_header) { + $in_section = 0; + } + next; + } + if ($line_length == 0) { + $in_section = 1; + $section_header = $line; + next; + } + if (($line_length != 0) && (length($line) != $line_length)) { + report("section delimiter not preceded by an empty line"); + } + $line_length = 0; + } +} + +if ($in_section) { + report("section not finished"); +} + +exit $exit_code; diff --git a/Documentation/lint-documentation-style.perl b/Documentation/lint-documentation-style.perl new file mode 100755 index 0000000000..d7ab732293 --- /dev/null +++ b/Documentation/lint-documentation-style.perl @@ -0,0 +1,33 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +my $exit_code = 0; +sub report { + my ($line, $msg) = @_; + chomp $line; + print STDERR "$ARGV:$.: '$line' $msg\n"; + $exit_code = 1; +} + +my $synopsis_style = 0; + +while (my $line = <>) { + if ($line =~ /^[ \t]*`?[-a-z0-9.]+`?(, `?[-a-z0-9.]+`?)+(::|;;)$/) { + + report($line, "multiple parameters in a definition list item"); + } + if ($line =~ /^`?--\[no-\][a-z0-9-]+.*(::|;;)$/) { + report($line, "definition list item with a `--[no-]` parameter"); + } + if ($line =~ /^\[synopsis\]$/) { + $synopsis_style = 1; + } + if (($line =~ /^(-[-a-z].*|<[-a-z0-9]+>(\.{3})?)(::|;;)$/) && ($synopsis_style)) { + report($line, "synopsis style and definition list item not backquoted"); + } +} + + +exit $exit_code; diff --git a/Documentation/lint-gitlink.perl b/Documentation/lint-gitlink.perl index aea564dad7..f183a18df2 100755 --- a/Documentation/lint-gitlink.perl +++ b/Documentation/lint-gitlink.perl @@ -41,6 +41,13 @@ die "BUG: No list of valid linkgit:* files given" unless @ARGV; @ARGV = $to_check; while (<>) { my $line = $_; + while ($line =~ m/(.{,8})((git[-a-z]+|scalar)\[(\d)*\])/g) { + my $pos = pos $line; + my ($macro, $target, $page, $section) = ($1, $2, $3, $4); + if ( $macro ne "linkgit:" && $macro !~ "ifn?def::" && $macro ne "endif::" ) { + report($pos, $line, $target, "linkgit: macro expected"); + } + } while ($line =~ m/linkgit:((.*?)\[(\d)\])/g) { my $pos = pos $line; my ($target, $page, $section) = ($1, $2, $3); diff --git a/Documentation/merge-options.adoc b/Documentation/merge-options.adoc index 95ef491be1..9d433265b2 100644 --- a/Documentation/merge-options.adoc +++ b/Documentation/merge-options.adoc @@ -135,7 +135,8 @@ ifdef::git-pull[] Only useful when merging. endif::git-pull[] -`--[no-]verify`:: +`--verify`:: +`--no-verify`:: By default, the pre-merge and commit-msg hooks are run. When `--no-verify` is given, these are bypassed. See also linkgit:githooks[5]. diff --git a/Documentation/mergetools/vimdiff.adoc b/Documentation/mergetools/vimdiff.adoc index abfd426f74..b4ab83a510 100644 --- a/Documentation/mergetools/vimdiff.adoc +++ b/Documentation/mergetools/vimdiff.adoc @@ -3,6 +3,7 @@ Description When specifying `--tool=vimdiff` in `git mergetool` Git will open Vim with a 4 windows layout distributed in the following way: + .... ------------------------------------------ | | | | @@ -56,6 +57,7 @@ needed in this case. The next layout definition is equivalent: + -- If, for some reason, we are not interested in the `BASE` buffer. + .... ------------------------------------------ | | | | @@ -72,6 +74,7 @@ If, for some reason, we are not interested in the `BASE` buffer. Only the `MERGED` buffer will be shown. Note, however, that all the other ones are still loaded in vim, and you can access them with the "buffers" command. + .... ------------------------------------------ | | @@ -88,6 +91,7 @@ command. When `MERGED` is not present in the layout, you must "mark" one of the buffers with an arobase (`@`). That will become the buffer you need to edit and save after resolving the conflicts. + .... ------------------------------------------ | | | @@ -106,6 +110,7 @@ save after resolving the conflicts. Three tabs will open: the first one is a copy of the default layout, while the other two only show the differences between (`BASE` and `LOCAL`) and (`BASE` and `REMOTE`) respectively. + .... ------------------------------------------ | <TAB #1> | TAB #2 | TAB #3 | | @@ -119,6 +124,7 @@ the other two only show the differences between (`BASE` and `LOCAL`) and | | ------------------------------------------ .... + .... ------------------------------------------ | TAB #1 | <TAB #2> | TAB #3 | | @@ -132,6 +138,7 @@ the other two only show the differences between (`BASE` and `LOCAL`) and | | | ------------------------------------------ .... + .... ------------------------------------------ | TAB #1 | TAB #2 | <TAB #3> | | @@ -151,6 +158,7 @@ the other two only show the differences between (`BASE` and `LOCAL`) and -- Same as the previous example, but adds a fourth tab with the same information as the first tab, with a different layout. + .... --------------------------------------------- | TAB #1 | TAB #2 | TAB #3 | <TAB #4> | diff --git a/Documentation/meson.build b/Documentation/meson.build index 4404c623f0..e34965c5b0 100644 --- a/Documentation/meson.build +++ b/Documentation/meson.build @@ -74,6 +74,7 @@ manpages = { 'git-init.adoc' : 1, 'git-instaweb.adoc' : 1, 'git-interpret-trailers.adoc' : 1, + 'git-last-modified.adoc' : 1, 'git-log.adoc' : 1, 'git-ls-files.adoc' : 1, 'git-ls-remote.adoc' : 1, @@ -116,6 +117,7 @@ manpages = { 'git-repack.adoc' : 1, 'git-replace.adoc' : 1, 'git-replay.adoc' : 1, + 'git-repo.adoc' : 1, 'git-request-pull.adoc' : 1, 'git-rerere.adoc' : 1, 'git-reset.adoc' : 1, diff --git a/Documentation/pretty-formats.adoc b/Documentation/pretty-formats.adoc index 9ed0417fc8..618ddc4a0c 100644 --- a/Documentation/pretty-formats.adoc +++ b/Documentation/pretty-formats.adoc @@ -233,11 +233,11 @@ colon and zero or more comma-separated options. Option values may contain literal formatting codes. These must be used for commas (`%x2C`) and closing parentheses (`%x29`), due to their role in the option syntax. + -** `prefix=<value>`: Shown before the list of ref names. Defaults to "{nbsp}+(+". +** `prefix=<value>`: Shown before the list of ref names. Defaults to "{nbsp}++(++". ** `suffix=<value>`: Shown after the list of ref names. Defaults to "+)+". ** `separator=<value>`: Shown between ref names. Defaults to "+,+{nbsp}". ** `pointer=<value>`: Shown between HEAD and the branch it points to, if any. - Defaults to "{nbsp}+->+{nbsp}". + Defaults to "{nbsp}++->++{nbsp}". ** `tag=<value>`: Shown before tag names. Defaults to "`tag:`{nbsp}". + diff --git a/Documentation/scalar.adoc b/Documentation/scalar.adoc index 4bd5b150e8..f81b2832f8 100644 --- a/Documentation/scalar.adoc +++ b/Documentation/scalar.adoc @@ -71,7 +71,8 @@ HEAD[:<directory>]`. Instead of checking out the branch pointed to by the cloned repository's HEAD, check out the `<name>` branch instead. ---[no-]single-branch:: +--single-branch:: +--no-single-branch:: Clone only the history leading to the tip of a single branch, either specified by the `--branch` option or the primary branch remote's `HEAD` points at. @@ -81,23 +82,27 @@ remote-tracking branch for the branch this option was used for the initial cloning. If the HEAD at the remote did not point at any branch when `--single-branch` clone was made, no remote-tracking branch is created. ---[no-]src:: +--src:: +--no-src:: By default, `scalar clone` places the cloned repository within a `<entlistment>/src` directory. Use `--no-src` to place the cloned repository directly in the `<enlistment>` directory. ---[no-]tags:: +--tags:: +--no-tags:: By default, `scalar clone` will fetch the tag objects advertised by the remote and future `git fetch` commands will do the same. Use `--no-tags` to avoid fetching tags in `scalar clone` and to configure the repository to avoid fetching tags in the future. To fetch tags after cloning with `--no-tags`, run `git fetch --tags`. ---[no-]full-clone:: +--full-clone:: +--no-full-clone:: A sparse-checkout is initialized by default. This behavior can be turned off via `--full-clone`. ---[no-]maintenance:: +--maintenance:: +--no-maintenance:: By default, `scalar clone` configures the enlistment to use Git's background maintenance feature. Use the `--no-maintenance` to skip this configuration. @@ -122,7 +127,8 @@ Note: when this subcommand is called in a worktree that is called `src/`, its parent directory is considered to be the Scalar enlistment. If the worktree is _not_ called `src/`, it itself will be considered to be the Scalar enlistment. ---[no-]maintenance:: +--maintenance:: +--no-maintenance:: By default, `scalar register` configures the enlistment to use Git's background maintenance feature. Use the `--no-maintenance` to skip this configuration. This does not disable any maintenance that may diff --git a/Documentation/technical/api-path-walk.adoc b/Documentation/technical/api-path-walk.adoc index 34c905eb9c..a67de1b143 100644 --- a/Documentation/technical/api-path-walk.adoc +++ b/Documentation/technical/api-path-walk.adoc @@ -39,7 +39,10 @@ It is also important that you do not specify the `--objects` flag for the the objects will be walked in a separate way based on those starting commits. -`commits`, `blobs`, `trees`, `tags`:: +`commits`:: +`blobs`:: +`trees`:: +`tags`:: By default, these members are enabled and signal that the path-walk API should call the `path_fn` on objects of these types. Specialized applications could disable some options to make it simpler to walk diff --git a/Documentation/technical/long-running-process-protocol.adoc b/Documentation/technical/long-running-process-protocol.adoc index 6f33654b42..39bd89d467 100644 --- a/Documentation/technical/long-running-process-protocol.adoc +++ b/Documentation/technical/long-running-process-protocol.adoc @@ -24,6 +24,7 @@ After the version negotiation Git sends a list of all capabilities that it supports and a flush packet. Git expects to read a list of desired capabilities, which must be a subset of the supported capabilities list, and a flush packet as response: + ------------------------ packet: git> git-filter-client packet: git> version=2 diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 64cbc58335..b16db85e77 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,6 +1,6 @@ #!/bin/sh -DEF_VER=v2.51.0 +DEF_VER=v2.51.GIT LF=' ' @@ -1265,6 +1265,7 @@ BUILTIN_OBJS += builtin/hook.o BUILTIN_OBJS += builtin/index-pack.o BUILTIN_OBJS += builtin/init-db.o BUILTIN_OBJS += builtin/interpret-trailers.o +BUILTIN_OBJS += builtin/last-modified.o BUILTIN_OBJS += builtin/log.o BUILTIN_OBJS += builtin/ls-files.o BUILTIN_OBJS += builtin/ls-remote.o @@ -1306,6 +1307,7 @@ BUILTIN_OBJS += builtin/remote.o BUILTIN_OBJS += builtin/repack.o BUILTIN_OBJS += builtin/replace.o BUILTIN_OBJS += builtin/replay.o +BUILTIN_OBJS += builtin/repo.o BUILTIN_OBJS += builtin/rerere.o BUILTIN_OBJS += builtin/reset.o BUILTIN_OBJS += builtin/rev-list.o @@ -1354,6 +1356,7 @@ THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/% THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/% CLAR_TEST_SUITES += u-ctype +CLAR_TEST_SUITES += u-dir CLAR_TEST_SUITES += u-example-decorate CLAR_TEST_SUITES += u-hash CLAR_TEST_SUITES += u-hashmap @@ -3943,13 +3946,12 @@ unit-tests: $(UNIT_TEST_PROGS) $(CLAR_TEST_PROG) t/helper/test-tool$X $(MAKE) -C t/ unit-tests .PHONY: libgit-sys libgit-rs -libgit-sys libgit-rs: - $(QUIET)(\ - cd contrib/$@ && \ - cargo build \ - ) +libgit-sys: + $(QUIET)cargo build --manifest-path contrib/libgit-sys/Cargo.toml +libgit-rs: libgit-sys + $(QUIET)cargo build --manifest-path contrib/libgit-rs/Cargo.toml ifdef INCLUDE_LIBGIT_RS -all:: libgit-sys libgit-rs +all:: libgit-rs endif LIBGIT_PUB_OBJS += contrib/libgit-sys/public_symbol_export.o @@ -1 +1 @@ -Documentation/RelNotes/2.51.0.adoc
\ No newline at end of file +Documentation/RelNotes/2.52.0.adoc
\ No newline at end of file diff --git a/add-interactive.c b/add-interactive.c index 3e692b47ec..4604c69140 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -20,14 +20,14 @@ #include "prompt.h" #include "tree.h" -static void init_color(struct repository *r, struct add_i_state *s, +static void init_color(struct repository *r, int use_color, const char *section_and_slot, char *dst, const char *default_color) { char *key = xstrfmt("color.%s", section_and_slot); const char *value; - if (!s->use_color) + if (!use_color) dst[0] = '\0'; else if (repo_config_get_value(r, key, &value) || color_parse(value, dst)) @@ -36,42 +36,63 @@ static void init_color(struct repository *r, struct add_i_state *s, free(key); } -void init_add_i_state(struct add_i_state *s, struct repository *r, - struct add_p_opt *add_p_opt) +static int check_color_config(struct repository *r, const char *var) { const char *value; + int ret; + + if (repo_config_get_value(r, var, &value)) + ret = -1; + else + ret = git_config_colorbool(var, value); + + /* + * Do not rely on want_color() to fall back to color.ui for us. It uses + * the value parsed by git_color_config(), which may not have been + * called by the main command. + */ + if (ret < 0 && !repo_config_get_value(r, "color.ui", &value)) + ret = git_config_colorbool("color.ui", value); + return want_color(ret); +} + +void init_add_i_state(struct add_i_state *s, struct repository *r, + struct add_p_opt *add_p_opt) +{ s->r = r; s->context = -1; s->interhunkcontext = -1; - if (repo_config_get_value(r, "color.interactive", &value)) - s->use_color = -1; - else - s->use_color = - git_config_colorbool("color.interactive", value); - s->use_color = want_color(s->use_color); - - init_color(r, s, "interactive.header", s->header_color, GIT_COLOR_BOLD); - init_color(r, s, "interactive.help", s->help_color, GIT_COLOR_BOLD_RED); - init_color(r, s, "interactive.prompt", s->prompt_color, - GIT_COLOR_BOLD_BLUE); - init_color(r, s, "interactive.error", s->error_color, - GIT_COLOR_BOLD_RED); - - init_color(r, s, "diff.frag", s->fraginfo_color, - diff_get_color(s->use_color, DIFF_FRAGINFO)); - init_color(r, s, "diff.context", s->context_color, "fall back"); + s->use_color_interactive = check_color_config(r, "color.interactive"); + + init_color(r, s->use_color_interactive, "interactive.header", + s->header_color, GIT_COLOR_BOLD); + init_color(r, s->use_color_interactive, "interactive.help", + s->help_color, GIT_COLOR_BOLD_RED); + init_color(r, s->use_color_interactive, "interactive.prompt", + s->prompt_color, GIT_COLOR_BOLD_BLUE); + init_color(r, s->use_color_interactive, "interactive.error", + s->error_color, GIT_COLOR_BOLD_RED); + strlcpy(s->reset_color_interactive, + s->use_color_interactive ? GIT_COLOR_RESET : "", COLOR_MAXLEN); + + s->use_color_diff = check_color_config(r, "color.diff"); + + init_color(r, s->use_color_diff, "diff.frag", s->fraginfo_color, + diff_get_color(s->use_color_diff, DIFF_FRAGINFO)); + init_color(r, s->use_color_diff, "diff.context", s->context_color, + "fall back"); if (!strcmp(s->context_color, "fall back")) - init_color(r, s, "diff.plain", s->context_color, - diff_get_color(s->use_color, DIFF_CONTEXT)); - init_color(r, s, "diff.old", s->file_old_color, - diff_get_color(s->use_color, DIFF_FILE_OLD)); - init_color(r, s, "diff.new", s->file_new_color, - diff_get_color(s->use_color, DIFF_FILE_NEW)); - - strlcpy(s->reset_color, - s->use_color ? GIT_COLOR_RESET : "", COLOR_MAXLEN); + init_color(r, s->use_color_diff, "diff.plain", + s->context_color, + diff_get_color(s->use_color_diff, DIFF_CONTEXT)); + init_color(r, s->use_color_diff, "diff.old", s->file_old_color, + diff_get_color(s->use_color_diff, DIFF_FILE_OLD)); + init_color(r, s->use_color_diff, "diff.new", s->file_new_color, + diff_get_color(s->use_color_diff, DIFF_FILE_NEW)); + strlcpy(s->reset_color_diff, + s->use_color_diff ? GIT_COLOR_RESET : "", COLOR_MAXLEN); FREE_AND_NULL(s->interactive_diff_filter); repo_config_get_string(r, "interactive.difffilter", @@ -109,7 +130,8 @@ void clear_add_i_state(struct add_i_state *s) FREE_AND_NULL(s->interactive_diff_filter); FREE_AND_NULL(s->interactive_diff_algorithm); memset(s, 0, sizeof(*s)); - s->use_color = -1; + s->use_color_interactive = -1; + s->use_color_diff = -1; } /* @@ -1188,9 +1210,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps, * When color was asked for, use the prompt color for * highlighting, otherwise use square brackets. */ - if (s.use_color) { + if (s.use_color_interactive) { data.color = s.prompt_color; - data.reset = s.reset_color; + data.reset = s.reset_color_interactive; } print_file_item_data.color = data.color; print_file_item_data.reset = data.reset; diff --git a/add-interactive.h b/add-interactive.h index 4213dcd67b..ceadfa6bb6 100644 --- a/add-interactive.h +++ b/add-interactive.h @@ -12,16 +12,19 @@ struct add_p_opt { struct add_i_state { struct repository *r; - int use_color; + int use_color_interactive; + int use_color_diff; char header_color[COLOR_MAXLEN]; char help_color[COLOR_MAXLEN]; char prompt_color[COLOR_MAXLEN]; char error_color[COLOR_MAXLEN]; - char reset_color[COLOR_MAXLEN]; + char reset_color_interactive[COLOR_MAXLEN]; + char fraginfo_color[COLOR_MAXLEN]; char context_color[COLOR_MAXLEN]; char file_old_color[COLOR_MAXLEN]; char file_new_color[COLOR_MAXLEN]; + char reset_color_diff[COLOR_MAXLEN]; int use_single_key; char *interactive_diff_filter, *interactive_diff_algorithm; diff --git a/add-patch.c b/add-patch.c index 302e6ba7d9..b0389c5d5b 100644 --- a/add-patch.c +++ b/add-patch.c @@ -300,7 +300,7 @@ static void err(struct add_p_state *s, const char *fmt, ...) va_start(args, fmt); fputs(s->s.error_color, stdout); vprintf(fmt, args); - puts(s->s.reset_color); + puts(s->s.reset_color_interactive); va_end(args); } @@ -457,7 +457,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) } strbuf_complete_line(plain); - if (want_color_fd(1, -1)) { + if (want_color_fd(1, s->s.use_color_diff)) { struct child_process colored_cp = CHILD_PROCESS_INIT; const char *diff_filter = s->s.interactive_diff_filter; @@ -714,7 +714,7 @@ static void render_hunk(struct add_p_state *s, struct hunk *hunk, if (len) strbuf_add(out, p, len); else if (colored) - strbuf_addf(out, "%s\n", s->s.reset_color); + strbuf_addf(out, "%s\n", s->s.reset_color_diff); else strbuf_addch(out, '\n'); } @@ -1107,7 +1107,7 @@ static void recolor_hunk(struct add_p_state *s, struct hunk *hunk) s->s.file_new_color : s->s.context_color); strbuf_add(&s->colored, plain + current, eol - current); - strbuf_addstr(&s->colored, s->s.reset_color); + strbuf_addstr(&s->colored, s->s.reset_color_diff); if (next > eol) strbuf_add(&s->colored, plain + eol, next - eol); current = next; @@ -1528,8 +1528,8 @@ static int patch_update_file(struct add_p_state *s, : 1)); printf(_(s->mode->prompt_mode[prompt_mode_type]), s->buf.buf); - if (*s->s.reset_color) - fputs(s->s.reset_color, stdout); + if (*s->s.reset_color_interactive) + fputs(s->s.reset_color_interactive, stdout); fflush(stdout); if (read_single_character(s) == EOF) break; @@ -36,19 +36,25 @@ struct alloc_state { int slab_nr, slab_alloc; }; -struct alloc_state *allocate_alloc_state(void) +struct alloc_state *alloc_state_alloc(void) { return xcalloc(1, sizeof(struct alloc_state)); } -void clear_alloc_state(struct alloc_state *s) +void alloc_state_free_and_null(struct alloc_state **s_) { + struct alloc_state *s = *s_; + + if (!s) + return; + while (s->slab_nr > 0) { s->slab_nr--; free(s->slabs[s->slab_nr]); } FREE_AND_NULL(s->slabs); + FREE_AND_NULL(*s_); } static inline void *alloc_node(struct alloc_state *s, size_t node_size) @@ -14,7 +14,7 @@ void *alloc_commit_node(struct repository *r); void *alloc_tag_node(struct repository *r); void *alloc_object_node(struct repository *r); -struct alloc_state *allocate_alloc_state(void); -void clear_alloc_state(struct alloc_state *s); +struct alloc_state *alloc_state_alloc(void); +void alloc_state_free_and_null(struct alloc_state **s_); #endif @@ -176,6 +176,7 @@ int cmd_hook(int argc, const char **argv, const char *prefix, struct repository int cmd_index_pack(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_init_db(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_interpret_trailers(int argc, const char **argv, const char *prefix, struct repository *repo); +int cmd_last_modified(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_log_reflog(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_log(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_ls_files(int argc, const char **argv, const char *prefix, struct repository *repo); @@ -216,6 +217,7 @@ int cmd_remote_ext(int argc, const char **argv, const char *prefix, struct repos int cmd_remote_fd(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_repack(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_replay(int argc, const char **argv, const char *prefix, struct repository *repo); +int cmd_repo(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_rerere(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_reset(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_restore(int argc, const char **argv, const char *prefix, struct repository *repo); diff --git a/builtin/add.c b/builtin/add.c index 0235854f80..740c7c4581 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -389,6 +389,7 @@ int cmd_add(int argc, char *seen = NULL; char *ps_matched = NULL; struct lock_file lock_file = LOCK_INIT; + struct odb_transaction *transaction; repo_config(repo, add_config, NULL); @@ -574,7 +575,7 @@ int cmd_add(int argc, string_list_clear(&only_match_skip_worktree, 0); } - begin_odb_transaction(); + transaction = begin_odb_transaction(repo->objects); ps_matched = xcalloc(pathspec.nr, 1); if (add_renormalize) @@ -593,7 +594,7 @@ int cmd_add(int argc, if (chmod_arg && pathspec.nr) exit_status |= chmod_pathspec(repo, &pathspec, chmod_arg[0], show_only); - end_odb_transaction(); + end_odb_transaction(transaction); finish: if (write_locked_index(repo->index, &lock_file, diff --git a/builtin/blame.c b/builtin/blame.c index 5b10e84b66..2703820258 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -413,7 +413,7 @@ static void parse_color_fields(const char *s) colorfield_nr = 0; /* Ideally this would be stripped and split at the same time? */ - string_list_split(&l, s, ',', -1); + string_list_split(&l, s, ",", -1); ALLOC_GROW(colorfield, colorfield_nr + 1, colorfield_alloc); for_each_string_list_item(item, &l) { diff --git a/builtin/clean.c b/builtin/clean.c index a1977b92dc..38b67923a6 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -478,43 +478,39 @@ static int find_unique(const char *choice, struct menu_stuff *menu_stuff) */ static int parse_choice(struct menu_stuff *menu_stuff, int is_single, - struct strbuf input, + char *input, int **chosen) { - struct strbuf **choice_list, **ptr; + struct string_list choice = STRING_LIST_INIT_NODUP; + struct string_list_item *item; int nr = 0; int i; - if (is_single) { - choice_list = strbuf_split_max(&input, '\n', 0); - } else { - char *p = input.buf; - do { - if (*p == ',') - *p = ' '; - } while (*p++); - choice_list = strbuf_split_max(&input, ' ', 0); - } + string_list_split_in_place_f(&choice, input, + is_single ? "\n" : ", ", -1, + STRING_LIST_SPLIT_TRIM); - for (ptr = choice_list; *ptr; ptr++) { - char *p; - int choose = 1; + for_each_string_list_item(item, &choice) { + const char *string; + int choose; int bottom = 0, top = 0; int is_range, is_number; - strbuf_trim(*ptr); - if (!(*ptr)->len) + string = item->string; + if (!*string) continue; /* Input that begins with '-'; unchoose */ - if (*(*ptr)->buf == '-') { + if (string[0] == '-') { choose = 0; - strbuf_remove((*ptr), 0, 1); + string++; + } else { + choose = 1; } is_range = 0; is_number = 1; - for (p = (*ptr)->buf; *p; p++) { + for (const char *p = string; *p; p++) { if ('-' == *p) { if (!is_range) { is_range = 1; @@ -532,27 +528,27 @@ static int parse_choice(struct menu_stuff *menu_stuff, } if (is_number) { - bottom = atoi((*ptr)->buf); + bottom = atoi(string); top = bottom; } else if (is_range) { - bottom = atoi((*ptr)->buf); + bottom = atoi(string); /* a range can be specified like 5-7 or 5- */ - if (!*(strchr((*ptr)->buf, '-') + 1)) + if (!*(strchr(string, '-') + 1)) top = menu_stuff->nr; else - top = atoi(strchr((*ptr)->buf, '-') + 1); - } else if (!strcmp((*ptr)->buf, "*")) { + top = atoi(strchr(string, '-') + 1); + } else if (!strcmp(string, "*")) { bottom = 1; top = menu_stuff->nr; } else { - bottom = find_unique((*ptr)->buf, menu_stuff); + bottom = find_unique(string, menu_stuff); top = bottom; } if (top <= 0 || bottom <= 0 || top > menu_stuff->nr || bottom > top || (is_single && bottom != top)) { clean_print_color(CLEAN_COLOR_ERROR); - printf(_("Huh (%s)?\n"), (*ptr)->buf); + printf(_("Huh (%s)?\n"), string); clean_print_color(CLEAN_COLOR_RESET); continue; } @@ -561,7 +557,7 @@ static int parse_choice(struct menu_stuff *menu_stuff, (*chosen)[i-1] = choose; } - strbuf_list_free(choice_list); + string_list_clear(&choice, 0); for (i = 0; i < menu_stuff->nr; i++) nr += (*chosen)[i]; @@ -631,7 +627,7 @@ static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff) nr = parse_choice(stuff, opts->flags & MENU_OPTS_SINGLETON, - choice, + choice.buf, &chosen); if (opts->flags & MENU_OPTS_SINGLETON) { @@ -679,12 +675,13 @@ static int filter_by_patterns_cmd(void) { struct dir_struct dir = DIR_INIT; struct strbuf confirm = STRBUF_INIT; - struct strbuf **ignore_list; - struct string_list_item *item; struct pattern_list *pl; int changed = -1, i; for (;;) { + struct string_list ignore_list = STRING_LIST_INIT_NODUP; + struct string_list_item *item; + if (!del_list.nr) break; @@ -702,14 +699,15 @@ static int filter_by_patterns_cmd(void) break; pl = add_pattern_list(&dir, EXC_CMDL, "manual exclude"); - ignore_list = strbuf_split_max(&confirm, ' ', 0); - for (i = 0; ignore_list[i]; i++) { - strbuf_trim(ignore_list[i]); - if (!ignore_list[i]->len) - continue; + string_list_split_in_place_f(&ignore_list, confirm.buf, " ", -1, + STRING_LIST_SPLIT_TRIM); - add_pattern(ignore_list[i]->buf, "", 0, pl, -(i+1)); + for (i = 0; i < ignore_list.nr; i++) { + item = &ignore_list.items[i]; + if (!*item->string) + continue; + add_pattern(item->string, "", 0, pl, -(i+1)); } changed = 0; @@ -730,7 +728,7 @@ static int filter_by_patterns_cmd(void) clean_print_color(CLEAN_COLOR_RESET); } - strbuf_list_free(ignore_list); + string_list_clear(&ignore_list, 0); dir_clear(&dir); } diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c index 4992ac146e..fe3ebaadad 100644 --- a/builtin/commit-graph.c +++ b/builtin/commit-graph.c @@ -102,14 +102,15 @@ static int graph_verify(int argc, const char **argv, const char *prefix, if (opts.progress) flags |= COMMIT_GRAPH_WRITE_PROGRESS; - source = odb_find_source(the_repository->objects, opts.obj_dir); + source = odb_find_source_or_die(the_repository->objects, opts.obj_dir); graph_name = get_commit_graph_filename(source); chain_name = get_commit_graph_chain_filename(source); if (open_commit_graph(graph_name, &fd, &st)) opened = OPENED_GRAPH; else if (errno != ENOENT) die_errno(_("Could not open commit-graph '%s'"), graph_name); - else if (open_commit_graph_chain(chain_name, &fd, &st)) + else if (open_commit_graph_chain(chain_name, &fd, &st, + the_repository->hash_algo)) opened = OPENED_CHAIN; else if (errno != ENOENT) die_errno(_("could not open commit-graph chain '%s'"), chain_name); @@ -121,15 +122,15 @@ static int graph_verify(int argc, const char **argv, const char *prefix, if (opened == OPENED_NONE) return 0; else if (opened == OPENED_GRAPH) - graph = load_commit_graph_one_fd_st(the_repository, fd, &st, source); + graph = load_commit_graph_one_fd_st(source, fd, &st); else - graph = load_commit_graph_chain_fd_st(the_repository, fd, &st, + graph = load_commit_graph_chain_fd_st(the_repository->objects, fd, &st, &incomplete_chain); if (!graph) return 1; - ret = verify_commit_graph(the_repository, graph, flags); + ret = verify_commit_graph(graph, flags); free_commit_graph(graph); if (incomplete_chain) { @@ -290,7 +291,7 @@ static int graph_write(int argc, const char **argv, const char *prefix, git_env_bool(GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS, 0)) flags |= COMMIT_GRAPH_WRITE_BLOOM_FILTERS; - source = odb_find_source(the_repository->objects, opts.obj_dir); + source = odb_find_source_or_die(the_repository->objects, opts.obj_dir); if (opts.reachable) { if (write_commit_graph_reachable(source, flags, &write_opts)) diff --git a/builtin/commit.c b/builtin/commit.c index b5b9608813..384d0b4e93 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -695,6 +695,7 @@ static int author_date_is_interesting(void) return author_message || force_date; } +#ifndef WITH_BREAKING_CHANGES static void adjust_comment_line_char(const struct strbuf *sb) { char candidates[] = "#;@!$%^&|:"; @@ -732,6 +733,7 @@ static void adjust_comment_line_char(const struct strbuf *sb) free(comment_line_str_to_free); comment_line_str = comment_line_str_to_free = xstrfmt("%c", *p); } +#endif /* !WITH_BREAKING_CHANGES */ static void prepare_amend_commit(struct commit *commit, struct strbuf *sb, struct pretty_print_context *ctx) @@ -928,8 +930,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len) die_errno(_("could not write commit template")); +#ifndef WITH_BREAKING_CHANGES if (auto_comment_line_char) adjust_comment_line_char(&sb); +#endif /* !WITH_BREAKING_CHANGES */ strbuf_release(&sb); /* This checks if committer ident is explicitly given */ @@ -1793,6 +1797,9 @@ int cmd_commit(int argc, show_usage_with_options_if_asked(argc, argv, builtin_commit_usage, builtin_commit_options); +#ifndef WITH_BREAKING_CHANGES + warn_on_auto_comment_char = true; +#endif /* !WITH_BREAKING_CHANGES */ prepare_repo_settings(the_repository); the_repository->settings.command_requires_full_index = 0; @@ -1947,7 +1954,7 @@ int cmd_commit(int argc, "new index file. Check that disk is not full and quota is\n" "not exceeded, and then \"git restore --staged :/\" to recover.")); - git_test_write_commit_graph_or_die(); + git_test_write_commit_graph_or_die(the_repository->objects->sources); repo_rerere(the_repository, 0); run_auto_maintenance(quiet); diff --git a/builtin/describe.c b/builtin/describe.c index d7dd8139de..9f4e26d7ff 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -23,6 +23,8 @@ #include "list-objects.h" #include "commit-slab.h" #include "wildmatch.h" +#include "prio-queue.h" +#include "oidset.h" #define MAX_TAGS (FLAG_BITS - 1) #define DEFAULT_CANDIDATES 10 @@ -249,36 +251,83 @@ static int compare_pt(const void *a_, const void *b_) return 0; } -static unsigned long finish_depth_computation( - struct commit_list **list, - struct possible_tag *best) +struct lazy_queue { + struct prio_queue queue; + bool get_pending; +}; + +#define LAZY_QUEUE_INIT { { compare_commits_by_commit_date }, false } + +static void *lazy_queue_get(struct lazy_queue *queue) +{ + if (queue->get_pending) + prio_queue_get(&queue->queue); + else + queue->get_pending = true; + return prio_queue_peek(&queue->queue); +} + +static void lazy_queue_put(struct lazy_queue *queue, void *thing) +{ + if (queue->get_pending) + prio_queue_replace(&queue->queue, thing); + else + prio_queue_put(&queue->queue, thing); + queue->get_pending = false; +} + +static bool lazy_queue_empty(const struct lazy_queue *queue) +{ + return queue->queue.nr == (queue->get_pending ? 1 : 0); +} + +static void lazy_queue_clear(struct lazy_queue *queue) +{ + clear_prio_queue(&queue->queue); + queue->get_pending = false; +} + +static unsigned long finish_depth_computation(struct lazy_queue *queue, + struct possible_tag *best) { unsigned long seen_commits = 0; - while (*list) { - struct commit *c = pop_commit(list); + struct oidset unflagged = OIDSET_INIT; + + for (size_t i = queue->get_pending ? 1 : 0; i < queue->queue.nr; i++) { + struct commit *commit = queue->queue.array[i].data; + if (!(commit->object.flags & best->flag_within)) + oidset_insert(&unflagged, &commit->object.oid); + } + + while (!lazy_queue_empty(queue)) { + struct commit *c = lazy_queue_get(queue); struct commit_list *parents = c->parents; seen_commits++; if (c->object.flags & best->flag_within) { - struct commit_list *a = *list; - while (a) { - struct commit *i = a->item; - if (!(i->object.flags & best->flag_within)) - break; - a = a->next; - } - if (!a) + if (!oidset_size(&unflagged)) break; - } else + } else { + oidset_remove(&unflagged, &c->object.oid); best->depth++; + } while (parents) { + unsigned seen, flag_before, flag_after; struct commit *p = parents->item; repo_parse_commit(the_repository, p); - if (!(p->object.flags & SEEN)) - commit_list_insert_by_date(p, list); + seen = p->object.flags & SEEN; + if (!seen) + lazy_queue_put(queue, p); + flag_before = p->object.flags & best->flag_within; p->object.flags |= c->object.flags; + flag_after = p->object.flags & best->flag_within; + if (!seen && !flag_after) + oidset_insert(&unflagged, &p->object.oid); + if (seen && !flag_before && flag_after) + oidset_remove(&unflagged, &p->object.oid); parents = parents->next; } } + oidset_clear(&unflagged); return seen_commits; } @@ -313,18 +362,16 @@ static void append_suffix(int depth, const struct object_id *oid, struct strbuf repo_find_unique_abbrev(the_repository, oid, abbrev)); } -static void describe_commit(struct object_id *oid, struct strbuf *dst) +static void describe_commit(struct commit *cmit, struct strbuf *dst) { - struct commit *cmit, *gave_up_on = NULL; - struct commit_list *list; + struct commit *gave_up_on = NULL; + struct lazy_queue queue = LAZY_QUEUE_INIT; struct commit_name *n; struct possible_tag all_matches[MAX_TAGS]; unsigned int match_cnt = 0, annotated_cnt = 0, cur_match; unsigned long seen_commits = 0; unsigned int unannotated_cnt = 0; - cmit = lookup_commit_reference(the_repository, oid); - n = find_commit_name(&cmit->object.oid); if (n && (tags || all || n->prio == 2)) { /* @@ -332,7 +379,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) */ append_name(n, dst); if (n->misnamed || longformat) - append_suffix(0, n->tag ? get_tagged_oid(n->tag) : oid, dst); + append_suffix(0, n->tag ? get_tagged_oid(n->tag) : &cmit->object.oid, dst); if (suffix) strbuf_addstr(dst, suffix); return; @@ -359,11 +406,10 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) have_util = 1; } - list = NULL; cmit->object.flags = SEEN; - commit_list_insert(cmit, &list); - while (list) { - struct commit *c = pop_commit(&list); + lazy_queue_put(&queue, cmit); + while (!lazy_queue_empty(&queue)) { + struct commit *c = lazy_queue_get(&queue); struct commit_list *parents = c->parents; struct commit_name **slot; @@ -397,7 +443,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) t->depth++; } /* Stop if last remaining path already covered by best candidate(s) */ - if (annotated_cnt && !list) { + if (annotated_cnt && lazy_queue_empty(&queue)) { int best_depth = INT_MAX; unsigned best_within = 0; for (cur_match = 0; cur_match < match_cnt; cur_match++) { @@ -420,7 +466,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) struct commit *p = parents->item; repo_parse_commit(the_repository, p); if (!(p->object.flags & SEEN)) - commit_list_insert_by_date(p, &list); + lazy_queue_put(&queue, p); p->object.flags |= c->object.flags; parents = parents->next; @@ -435,6 +481,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) strbuf_add_unique_abbrev(dst, cmit_oid, abbrev); if (suffix) strbuf_addstr(dst, suffix); + lazy_queue_clear(&queue); return; } if (unannotated_cnt) @@ -450,11 +497,11 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) QSORT(all_matches, match_cnt, compare_pt); if (gave_up_on) { - commit_list_insert_by_date(gave_up_on, &list); + lazy_queue_put(&queue, gave_up_on); seen_commits--; } - seen_commits += finish_depth_computation(&list, &all_matches[0]); - free_commit_list(list); + seen_commits += finish_depth_computation(&queue, &all_matches[0]); + lazy_queue_clear(&queue); if (debug) { static int label_width = -1; @@ -489,8 +536,8 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) } struct process_commit_data { - struct object_id current_commit; - struct object_id looking_for; + struct commit *current_commit; + const struct object_id *looking_for; struct strbuf *dst; struct rev_info *revs; }; @@ -498,30 +545,38 @@ struct process_commit_data { static void process_commit(struct commit *commit, void *data) { struct process_commit_data *pcd = data; - pcd->current_commit = commit->object.oid; + pcd->current_commit = commit; } static void process_object(struct object *obj, const char *path, void *data) { struct process_commit_data *pcd = data; - if (oideq(&pcd->looking_for, &obj->oid) && !pcd->dst->len) { + if (oideq(pcd->looking_for, &obj->oid) && !pcd->dst->len) { reset_revision_walk(); - describe_commit(&pcd->current_commit, pcd->dst); - strbuf_addf(pcd->dst, ":%s", path); + if (pcd->current_commit) { + describe_commit(pcd->current_commit, pcd->dst); + strbuf_addf(pcd->dst, ":%s", path); + } free_commit_list(pcd->revs->commits); pcd->revs->commits = NULL; } } -static void describe_blob(struct object_id oid, struct strbuf *dst) +static void describe_blob(const struct object_id *oid, struct strbuf *dst) { struct rev_info revs; struct strvec args = STRVEC_INIT; - struct process_commit_data pcd = { *null_oid(the_hash_algo), oid, dst, &revs}; + struct object_id head_oid; + struct process_commit_data pcd = { NULL, oid, dst, &revs}; + + if (repo_get_oid(the_repository, "HEAD", &head_oid)) + die(_("cannot search for blob '%s' on an unborn branch"), + oid_to_hex(oid)); strvec_pushl(&args, "internal: The first arg is not parsed", - "--objects", "--in-commit-order", "--reverse", "HEAD", + "--objects", "--in-commit-order", "--reverse", + oid_to_hex(&head_oid), NULL); repo_init_revisions(the_repository, &revs, NULL); @@ -535,6 +590,9 @@ static void describe_blob(struct object_id oid, struct strbuf *dst) reset_revision_walk(); release_revisions(&revs); strvec_clear(&args); + + if (!dst->len) + die(_("blob '%s' not reachable from HEAD"), oid_to_hex(oid)); } static void describe(const char *arg, int last_one) @@ -551,10 +609,10 @@ static void describe(const char *arg, int last_one) cmit = lookup_commit_reference_gently(the_repository, &oid, 1); if (cmit) - describe_commit(&oid, &sb); + describe_commit(cmit, &sb); else if (odb_read_object_info(the_repository->objects, &oid, NULL) == OBJ_BLOB) - describe_blob(oid, &sb); + describe_blob(&oid, &sb); else die(_("%s is neither a commit nor blob"), arg); diff --git a/builtin/diff.c b/builtin/diff.c index 9a89e25a98..0b23c41456 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -487,6 +487,21 @@ int cmd_diff(int argc, init_diff_ui_defaults(); repo_config(the_repository, git_diff_ui_config, NULL); + + /* + * If we are ignoring the fact that our current directory may + * be part of a working tree controlled by a Git repository to + * pretend to be a "better GNU diff", we should undo the + * effect of the setup code that did a chdir() to the top of + * the working tree. Where we came from is recorded in the + * prefix. + */ + if (no_index && prefix) { + if (chdir(prefix)) + die(_("cannot come back to cwd")); + prefix = NULL; + } + prefix = precompose_argv_prefix(argc, argv, prefix); repo_init_revisions(the_repository, &rev, prefix); diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index edb93c0b3a..cf4273a52c 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #include "builtin.h" #include "config.h" #include "fmt-merge-msg.h" @@ -13,12 +12,13 @@ static const char * const fmt_merge_msg_usage[] = { int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix, - struct repository *repo UNUSED) + struct repository *repo) { char *inpath = NULL; const char *message = NULL; char *into_name = NULL; int shortlog_len = -1; + int merge_log_config = -1; struct option options[] = { { .type = OPTION_INTEGER, @@ -53,7 +53,7 @@ int cmd_fmt_merge_msg(int argc, int ret; struct fmt_merge_msg_opts opts; - repo_config(the_repository, fmt_merge_msg_config, NULL); + repo_config(repo, fmt_merge_msg_config, &merge_log_config); argc = parse_options(argc, argv, prefix, options, fmt_merge_msg_usage, 0); if (argc > 0) diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 222637a2c0..4a2fc421db 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -2,6 +2,7 @@ #include "commit.h" #include "config.h" #include "environment.h" +#include "for-each-ref.h" #include "gettext.h" #include "object.h" #include "parse-options.h" @@ -9,19 +10,7 @@ #include "strbuf.h" #include "strvec.h" -static char const * const for_each_ref_usage[] = { - N_("git for-each-ref [<options>] [<pattern>]"), - N_("git for-each-ref [--points-at <object>]"), - N_("git for-each-ref [--merged [<commit>]] [--no-merged [<commit>]]"), - N_("git for-each-ref [--contains [<commit>]] [--no-contains [<commit>]]"), - N_("git for-each-ref [--start-after <marker>]"), - NULL -}; - -int cmd_for_each_ref(int argc, - const char **argv, - const char *prefix, - struct repository *repo) +int for_each_ref_core(int argc, const char **argv, const char *prefix, struct repository *repo, const char *const *usage) { struct ref_sorting *sorting; struct string_list sorting_options = STRING_LIST_INIT_DUP; @@ -70,17 +59,17 @@ int cmd_for_each_ref(int argc, /* Set default (refname) sorting */ string_list_append(&sorting_options, "refname"); - parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0); + parse_options(argc, argv, prefix, opts, usage, 0); if (format.array_opts.max_count < 0) { error("invalid --count argument: `%d'", format.array_opts.max_count); - usage_with_options(for_each_ref_usage, opts); + usage_with_options(usage, opts); } if (HAS_MULTI_BITS(format.quote_style)) { error("more than one quoting style?"); - usage_with_options(for_each_ref_usage, opts); + usage_with_options(usage, opts); } if (verify_ref_format(&format)) - usage_with_options(for_each_ref_usage, opts); + usage_with_options(usage, opts); if (filter.start_after && sorting_options.nr > 1) die(_("cannot use --start-after with custom sort options")); @@ -120,3 +109,16 @@ int cmd_for_each_ref(int argc, strvec_clear(&vec); return 0; } + +int cmd_for_each_ref(int argc, + const char **argv, + const char *prefix, + struct repository *repo) +{ + static char const * const for_each_ref_usage[] = { + N_("git for-each-ref " COMMON_USAGE_FOR_EACH_REF), + NULL + }; + + return for_each_ref_core(argc, argv, prefix, repo, for_each_ref_usage); +} diff --git a/builtin/fsck.c b/builtin/fsck.c index 543a2cdb5c..d2eb9d4fbe 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -503,13 +503,12 @@ static void fsck_handle_reflog_oid(const char *refname, struct object_id *oid, } } -static int fsck_handle_reflog_ent(struct object_id *ooid, struct object_id *noid, +static int fsck_handle_reflog_ent(const char *refname, + struct object_id *ooid, struct object_id *noid, const char *email UNUSED, timestamp_t timestamp, int tz UNUSED, - const char *message UNUSED, void *cb_data) + const char *message UNUSED, void *cb_data UNUSED) { - const char *refname = cb_data; - if (verbose) fprintf_ln(stderr, _("Checking reflog %s->%s"), oid_to_hex(ooid), oid_to_hex(noid)); @@ -526,7 +525,7 @@ static int fsck_handle_reflog(const char *logname, void *cb_data) strbuf_worktree_ref(cb_data, &refname, logname); refs_for_each_reflog_ent(get_main_ref_store(the_repository), refname.buf, fsck_handle_reflog_ent, - refname.buf); + NULL); strbuf_release(&refname); return 0; } diff --git a/builtin/gc.c b/builtin/gc.c index 0edd94a76f..03ae4926b2 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -312,7 +312,8 @@ struct count_reflog_entries_data { size_t limit; }; -static int count_reflog_entries(struct object_id *old_oid, struct object_id *new_oid, +static int count_reflog_entries(const char *refname UNUSED, + struct object_id *old_oid, struct object_id *new_oid, const char *committer, timestamp_t timestamp, int tz, const char *msg, void *cb_data) { diff --git a/builtin/last-modified.c b/builtin/last-modified.c new file mode 100644 index 0000000000..886ba12cb5 --- /dev/null +++ b/builtin/last-modified.c @@ -0,0 +1,326 @@ +#include "git-compat-util.h" +#include "bloom.h" +#include "builtin.h" +#include "commit-graph.h" +#include "commit.h" +#include "config.h" +#include "environment.h" +#include "diff.h" +#include "diffcore.h" +#include "environment.h" +#include "hashmap.h" +#include "hex.h" +#include "log-tree.h" +#include "object-name.h" +#include "object.h" +#include "parse-options.h" +#include "quote.h" +#include "repository.h" +#include "revision.h" + +struct last_modified_entry { + struct hashmap_entry hashent; + struct object_id oid; + struct bloom_key key; + const char path[FLEX_ARRAY]; +}; + +static int last_modified_entry_hashcmp(const void *unused UNUSED, + const struct hashmap_entry *hent1, + const struct hashmap_entry *hent2, + const void *path) +{ + const struct last_modified_entry *ent1 = + container_of(hent1, const struct last_modified_entry, hashent); + const struct last_modified_entry *ent2 = + container_of(hent2, const struct last_modified_entry, hashent); + return strcmp(ent1->path, path ? path : ent2->path); +} + +struct last_modified { + struct hashmap paths; + struct rev_info rev; + bool recursive; + bool show_trees; +}; + +static void last_modified_release(struct last_modified *lm) +{ + struct hashmap_iter iter; + struct last_modified_entry *ent; + + hashmap_for_each_entry(&lm->paths, &iter, ent, hashent) + bloom_key_clear(&ent->key); + + hashmap_clear_and_free(&lm->paths, struct last_modified_entry, hashent); + release_revisions(&lm->rev); +} + +struct last_modified_callback_data { + struct last_modified *lm; + struct commit *commit; +}; + +static void add_path_from_diff(struct diff_queue_struct *q, + struct diff_options *opt UNUSED, void *data) +{ + struct last_modified *lm = data; + + for (int i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + struct last_modified_entry *ent; + const char *path = p->two->path; + + FLEX_ALLOC_STR(ent, path, path); + oidcpy(&ent->oid, &p->two->oid); + if (lm->rev.bloom_filter_settings) + bloom_key_fill(&ent->key, path, strlen(path), + lm->rev.bloom_filter_settings); + hashmap_entry_init(&ent->hashent, strhash(ent->path)); + hashmap_add(&lm->paths, &ent->hashent); + } +} + +static int populate_paths_from_revs(struct last_modified *lm) +{ + int num_interesting = 0; + struct diff_options diffopt; + + /* + * Create a copy of `struct diff_options`. In this copy a callback is + * set that when called adds entries to `paths` in `struct last_modified`. + * This copy is used to diff the tree of the target revision against an + * empty tree. This results in all paths in the target revision being + * listed. After `paths` is populated, we don't need this copy no more. + */ + memcpy(&diffopt, &lm->rev.diffopt, sizeof(diffopt)); + copy_pathspec(&diffopt.pathspec, &lm->rev.diffopt.pathspec); + diffopt.output_format = DIFF_FORMAT_CALLBACK; + diffopt.format_callback = add_path_from_diff; + diffopt.format_callback_data = lm; + + for (size_t i = 0; i < lm->rev.pending.nr; i++) { + struct object_array_entry *obj = lm->rev.pending.objects + i; + + if (obj->item->flags & UNINTERESTING) + continue; + + if (num_interesting++) + return error(_("last-modified can only operate on one tree at a time")); + + diff_tree_oid(lm->rev.repo->hash_algo->empty_tree, + &obj->item->oid, "", &diffopt); + diff_flush(&diffopt); + } + clear_pathspec(&diffopt.pathspec); + + return 0; +} + +static void last_modified_emit(struct last_modified *lm, + const char *path, const struct commit *commit) + +{ + if (commit->object.flags & BOUNDARY) + putchar('^'); + printf("%s\t", oid_to_hex(&commit->object.oid)); + + if (lm->rev.diffopt.line_termination) + write_name_quoted(path, stdout, '\n'); + else + printf("%s%c", path, '\0'); +} + +static void mark_path(const char *path, const struct object_id *oid, + struct last_modified_callback_data *data) +{ + struct last_modified_entry *ent; + + /* Is it even a path that we are interested in? */ + ent = hashmap_get_entry_from_hash(&data->lm->paths, strhash(path), path, + struct last_modified_entry, hashent); + if (!ent) + return; + + /* + * Is it arriving at a version of interest, or is it from a side branch + * which did not contribute to the final state? + */ + if (!oideq(oid, &ent->oid)) + return; + + last_modified_emit(data->lm, path, data->commit); + + hashmap_remove(&data->lm->paths, &ent->hashent, path); + bloom_key_clear(&ent->key); + free(ent); +} + +static void last_modified_diff(struct diff_queue_struct *q, + struct diff_options *opt UNUSED, void *cbdata) +{ + struct last_modified_callback_data *data = cbdata; + + for (int i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + switch (p->status) { + case DIFF_STATUS_DELETED: + /* + * There's no point in feeding a deletion, as it could + * not have resulted in our current state, which + * actually has the file. + */ + break; + + default: + /* + * Otherwise, we care only that we somehow arrived at + * a final oid state. Note that this covers some + * potentially controversial areas, including: + * + * 1. A rename or copy will be found, as it is the + * first time the content has arrived at the given + * path. + * + * 2. Even a non-content modification like a mode or + * type change will trigger it. + * + * We take the inclusive approach for now, and find + * anything which impacts the path. Options to tweak + * the behavior (e.g., to "--follow" the content across + * renames) can come later. + */ + mark_path(p->two->path, &p->two->oid, data); + break; + } + } +} + +static bool maybe_changed_path(struct last_modified *lm, struct commit *origin) +{ + struct bloom_filter *filter; + struct last_modified_entry *ent; + struct hashmap_iter iter; + + if (!lm->rev.bloom_filter_settings) + return true; + + if (commit_graph_generation(origin) == GENERATION_NUMBER_INFINITY) + return true; + + filter = get_bloom_filter(lm->rev.repo, origin); + if (!filter) + return true; + + hashmap_for_each_entry(&lm->paths, &iter, ent, hashent) { + if (bloom_filter_contains(filter, &ent->key, + lm->rev.bloom_filter_settings)) + return true; + } + return false; +} + +static int last_modified_run(struct last_modified *lm) +{ + struct last_modified_callback_data data = { .lm = lm }; + + lm->rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; + lm->rev.diffopt.format_callback = last_modified_diff; + lm->rev.diffopt.format_callback_data = &data; + + prepare_revision_walk(&lm->rev); + + while (hashmap_get_size(&lm->paths)) { + data.commit = get_revision(&lm->rev); + if (!data.commit) + BUG("paths remaining beyond boundary in last-modified"); + + if (data.commit->object.flags & BOUNDARY) { + diff_tree_oid(lm->rev.repo->hash_algo->empty_tree, + &data.commit->object.oid, "", + &lm->rev.diffopt); + diff_flush(&lm->rev.diffopt); + + break; + } + + if (!maybe_changed_path(lm, data.commit)) + continue; + + log_tree_commit(&lm->rev, data.commit); + } + + return 0; +} + +static int last_modified_init(struct last_modified *lm, struct repository *r, + const char *prefix, int argc, const char **argv) +{ + hashmap_init(&lm->paths, last_modified_entry_hashcmp, NULL, 0); + + repo_init_revisions(r, &lm->rev, prefix); + lm->rev.def = "HEAD"; + lm->rev.combine_merges = 1; + lm->rev.show_root_diff = 1; + lm->rev.boundary = 1; + lm->rev.no_commit_id = 1; + lm->rev.diff = 1; + lm->rev.diffopt.flags.recursive = lm->recursive; + lm->rev.diffopt.flags.tree_in_recursive = lm->show_trees; + + argc = setup_revisions(argc, argv, &lm->rev, NULL); + if (argc > 1) { + error(_("unknown last-modified argument: %s"), argv[1]); + return argc; + } + + lm->rev.bloom_filter_settings = get_bloom_filter_settings(lm->rev.repo); + + if (populate_paths_from_revs(lm) < 0) + return error(_("unable to setup last-modified")); + + return 0; +} + +int cmd_last_modified(int argc, const char **argv, const char *prefix, + struct repository *repo) +{ + int ret; + struct last_modified lm = { 0 }; + + const char * const last_modified_usage[] = { + N_("git last-modified [--recursive] [--show-trees] " + "[<revision-range>] [[--] <path>...]"), + NULL + }; + + struct option last_modified_options[] = { + OPT_BOOL('r', "recursive", &lm.recursive, + N_("recurse into subtrees")), + OPT_BOOL('t', "show-trees", &lm.show_trees, + N_("show tree entries when recursing into subtrees")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, last_modified_options, + last_modified_usage, + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT); + + repo_config(repo, git_default_config, NULL); + + ret = last_modified_init(&lm, repo, prefix, argc, argv); + if (ret > 0) + usage_with_options(last_modified_usage, + last_modified_options); + if (ret) + goto out; + + ret = last_modified_run(&lm); + if (ret) + goto out; + +out: + last_modified_release(&lm); + + return ret; +} diff --git a/builtin/log.c b/builtin/log.c index c2f8bbf863..5f552d14c0 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -1404,6 +1404,7 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file, struct range_diff_options range_diff_opts = { .creation_factor = rev->creation_factor, .dual_color = 1, + .max_memory = RANGE_DIFF_MAX_MEMORY_DEFAULT, .diffopt = &opts, .other_arg = &other_arg }; diff --git a/builtin/ls-files.c b/builtin/ls-files.c index c06a6f33e4..b148607f7a 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -414,14 +414,21 @@ static void show_files(struct repository *repo, struct dir_struct *dir) if (!(show_cached || show_stage || show_deleted || show_modified)) return; - if (!show_sparse_dirs) - ensure_full_index(repo->index); - for (i = 0; i < repo->index->cache_nr; i++) { const struct cache_entry *ce = repo->index->cache[i]; struct stat st; int stat_err; + if (S_ISSPARSEDIR(ce->ce_mode) && !show_sparse_dirs) { + /* + * This is the first time we've hit a sparse dir, + * so expansion will leave the first 'i' entries + * alone. + */ + ensure_full_index(repo->index); + ce = repo->index->cache[i]; + } + construct_fullname(&fullname, repo, ce); if ((dir->flags & DIR_SHOW_IGNORED) && diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 5d55731ca3..ec6940fc7c 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -373,7 +373,6 @@ int cmd_ls_tree(int argc, OPT_END() }; struct ls_tree_cmdmode_to_fmt *m2f = ls_tree_cmdmode_format; - struct object_context obj_context = {0}; int ret; repo_config(the_repository, git_default_config, NULL); @@ -405,9 +404,8 @@ int cmd_ls_tree(int argc, ls_tree_usage, ls_tree_options); if (argc < 1) usage_with_options(ls_tree_usage, ls_tree_options); - if (get_oid_with_context(the_repository, argv[0], - GET_OID_HASH_ANY, &oid, - &obj_context)) + if (repo_get_oid_with_flags(the_repository, argv[0], &oid, + GET_OID_HASH_ANY)) die("Not a valid object name %s", argv[0]); /* @@ -447,6 +445,5 @@ int cmd_ls_tree(int argc, ret = !!read_tree(the_repository, tree, &options.pathspec, fn, &options); clear_pathspec(&options.pathspec); - object_context_release(&obj_context); return ret; } diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c index 03b5100cfa..17aa4db37a 100644 --- a/builtin/merge-recursive.c +++ b/builtin/merge-recursive.c @@ -38,7 +38,8 @@ int cmd_merge_recursive(int argc, if (argv[0] && ends_with(argv[0], "-subtree")) o.subtree_shift = ""; - if (argc == 2 && !strcmp(argv[1], "-h")) { + if (argc == 2 && (!strcmp(argv[1], "-h") || + !strcmp(argv[1], "--help-all"))) { struct strbuf msg = STRBUF_INIT; strbuf_addf(&msg, builtin_merge_recursive_usage, argv[0]); show_usage_if_asked(argc, argv, msg.buf); diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index 203f0e6456..1c063d9a41 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -619,32 +619,34 @@ int cmd_merge_tree(int argc, "--merge-base", "--stdin"); line_termination = '\0'; while (strbuf_getline_lf(&buf, stdin) != EOF) { - struct strbuf **split; + struct string_list split = STRING_LIST_INIT_NODUP; const char *input_merge_base = NULL; - split = strbuf_split(&buf, ' '); - if (!split[0] || !split[1]) + string_list_split_in_place_f(&split, buf.buf, " ", -1, + STRING_LIST_SPLIT_TRIM); + + if (split.nr < 2) die(_("malformed input line: '%s'."), buf.buf); - strbuf_rtrim(split[0]); - strbuf_rtrim(split[1]); /* parse the merge-base */ - if (!strcmp(split[1]->buf, "--")) { - input_merge_base = split[0]->buf; + if (!strcmp(split.items[1].string, "--")) { + input_merge_base = split.items[0].string; } - if (input_merge_base && split[2] && split[3] && !split[4]) { - strbuf_rtrim(split[2]); - strbuf_rtrim(split[3]); - real_merge(&o, input_merge_base, split[2]->buf, split[3]->buf, prefix); - } else if (!input_merge_base && !split[2]) { - real_merge(&o, NULL, split[0]->buf, split[1]->buf, prefix); + if (input_merge_base && split.nr == 4) { + real_merge(&o, input_merge_base, + split.items[2].string, split.items[3].string, + prefix); + } else if (!input_merge_base && split.nr == 2) { + real_merge(&o, NULL, + split.items[0].string, split.items[1].string, + prefix); } else { die(_("malformed input line: '%s'."), buf.buf); } maybe_flush_or_die(stdout, "stdout"); - strbuf_list_free(split); + string_list_clear(&split, 0); } strbuf_release(&buf); diff --git a/builtin/merge.c b/builtin/merge.c index ce880e6ccb..c421a11b0b 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -875,7 +875,7 @@ static void add_strategies(const char *string, unsigned attr) if (string) { struct string_list list = STRING_LIST_INIT_DUP; struct string_list_item *item; - string_list_split(&list, string, ' ', -1); + string_list_split(&list, string, " ", -1); for_each_string_list_item(item, &list) append_strategy(get_strategy(item->string)); string_list_clear(&list, 0); @@ -1374,10 +1374,14 @@ int cmd_merge(int argc, struct commit_list *remoteheads = NULL, *p; void *branch_to_free; int orig_argc = argc; + int merge_log_config = -1; show_usage_with_options_if_asked(argc, argv, builtin_merge_usage, builtin_merge_options); +#ifndef WITH_BREAKING_CHANGES + warn_on_auto_comment_char = true; +#endif /* !WITH_BREAKING_CHANGES */ prepare_repo_settings(the_repository); the_repository->settings.command_requires_full_index = 0; @@ -1392,7 +1396,7 @@ int cmd_merge(int argc, skip_prefix(branch, "refs/heads/", &branch); init_diff_ui_defaults(); - repo_config(the_repository, git_merge_config, NULL); + repo_config(the_repository, git_merge_config, &merge_log_config); if (!branch || is_null_oid(&head_oid)) head_commit = NULL; @@ -1862,7 +1866,7 @@ int cmd_merge(int argc, if (squash) { finish(head_commit, remoteheads, NULL, NULL); - git_test_write_commit_graph_or_die(); + git_test_write_commit_graph_or_die(the_repository->objects->sources); } else write_merge_state(remoteheads); diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c index d3b9e98be3..5f364aa816 100644 --- a/builtin/multi-pack-index.c +++ b/builtin/multi-pack-index.c @@ -65,12 +65,20 @@ static int parse_object_dir(const struct option *opt, const char *arg, char **value = opt->value; free(*value); if (unset) - *value = xstrdup(repo_get_object_directory(the_repository)); + *value = xstrdup(the_repository->objects->sources->path); else *value = real_pathdup(arg, 1); return 0; } +static struct odb_source *handle_object_dir_option(struct repository *repo) +{ + struct odb_source *source = odb_find_source(repo->objects, opts.object_dir); + if (!source) + source = odb_add_to_alternates_memory(repo->objects, opts.object_dir); + return source; +} + static struct option common_opts[] = { OPT_CALLBACK(0, "object-dir", &opts.object_dir, N_("directory"), @@ -140,6 +148,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv, N_("refs snapshot for selecting bitmap commits")), OPT_END(), }; + struct odb_source *source; int ret; opts.flags |= MIDX_WRITE_BITMAP_HASH_CACHE; @@ -158,6 +167,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv, if (argc) usage_with_options(builtin_multi_pack_index_write_usage, options); + source = handle_object_dir_option(repo); FREE_AND_NULL(options); @@ -166,7 +176,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv, read_packs_from_stdin(&packs); - ret = write_midx_file_only(repo, opts.object_dir, &packs, + ret = write_midx_file_only(source, &packs, opts.preferred_pack, opts.refs_snapshot, opts.flags); @@ -177,7 +187,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv, } - ret = write_midx_file(repo, opts.object_dir, opts.preferred_pack, + ret = write_midx_file(source, opts.preferred_pack, opts.refs_snapshot, opts.flags); free(opts.refs_snapshot); @@ -194,6 +204,8 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv, N_("force progress reporting"), MIDX_PROGRESS), OPT_END(), }; + struct odb_source *source; + options = add_common_options(builtin_multi_pack_index_verify_options); trace2_cmd_mode(argv[0]); @@ -206,10 +218,11 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv, if (argc) usage_with_options(builtin_multi_pack_index_verify_usage, options); + source = handle_object_dir_option(the_repository); FREE_AND_NULL(options); - return verify_midx_file(the_repository, opts.object_dir, opts.flags); + return verify_midx_file(source, opts.flags); } static int cmd_multi_pack_index_expire(int argc, const char **argv, @@ -222,6 +235,8 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv, N_("force progress reporting"), MIDX_PROGRESS), OPT_END(), }; + struct odb_source *source; + options = add_common_options(builtin_multi_pack_index_expire_options); trace2_cmd_mode(argv[0]); @@ -234,10 +249,11 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv, if (argc) usage_with_options(builtin_multi_pack_index_expire_usage, options); + source = handle_object_dir_option(the_repository); FREE_AND_NULL(options); - return expire_midx_packs(the_repository, opts.object_dir, opts.flags); + return expire_midx_packs(source, opts.flags); } static int cmd_multi_pack_index_repack(int argc, const char **argv, @@ -252,6 +268,7 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv, N_("force progress reporting"), MIDX_PROGRESS), OPT_END(), }; + struct odb_source *source; options = add_common_options(builtin_multi_pack_index_repack_options); @@ -266,11 +283,11 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv, if (argc) usage_with_options(builtin_multi_pack_index_repack_usage, options); + source = handle_object_dir_option(the_repository); FREE_AND_NULL(options); - return midx_repack(the_repository, opts.object_dir, - (size_t)opts.batch_size, opts.flags); + return midx_repack(source, (size_t)opts.batch_size, opts.flags); } int cmd_multi_pack_index(int argc, diff --git a/builtin/notes.c b/builtin/notes.c index 6fb4144da3..9af602bdd7 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -376,18 +376,19 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd) while (strbuf_getline_lf(&buf, stdin) != EOF) { struct object_id from_obj, to_obj; - struct strbuf **split; + struct string_list split = STRING_LIST_INIT_NODUP; int err; - split = strbuf_split(&buf, ' '); - if (!split[0] || !split[1]) + string_list_split_in_place_f(&split, buf.buf, " ", -1, + STRING_LIST_SPLIT_TRIM); + if (split.nr < 2) die(_("malformed input line: '%s'."), buf.buf); - strbuf_rtrim(split[0]); - strbuf_rtrim(split[1]); - if (repo_get_oid(the_repository, split[0]->buf, &from_obj)) - die(_("failed to resolve '%s' as a valid ref."), split[0]->buf); - if (repo_get_oid(the_repository, split[1]->buf, &to_obj)) - die(_("failed to resolve '%s' as a valid ref."), split[1]->buf); + if (repo_get_oid(the_repository, split.items[0].string, &from_obj)) + die(_("failed to resolve '%s' as a valid ref."), + split.items[0].string); + if (repo_get_oid(the_repository, split.items[1].string, &to_obj)) + die(_("failed to resolve '%s' as a valid ref."), + split.items[1].string); if (rewrite_cmd) err = copy_note_for_rewrite(c, &from_obj, &to_obj); @@ -397,11 +398,11 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd) if (err) { error(_("failed to copy notes from '%s' to '%s'"), - split[0]->buf, split[1]->buf); + split.items[0].string, split.items[1].string); ret = 1; } - strbuf_list_free(split); + string_list_clear(&split, 0); } if (!rewrite_cmd) { diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 53a2256250..1494afcf3d 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -1741,7 +1741,7 @@ static int want_object_in_pack_mtime(const struct object_id *oid, struct multi_pack_index *m = get_multi_pack_index(source); struct pack_entry e; - if (m && fill_midx_entry(the_repository, oid, &e, m)) { + if (m && fill_midx_entry(m, oid, &e)) { want = want_object_in_pack_one(e.p, oid, exclude, found_pack, found_offset, found_mtime); if (want != -1) return want; diff --git a/builtin/range-diff.c b/builtin/range-diff.c index a563abff5f..aafcc99b96 100644 --- a/builtin/range-diff.c +++ b/builtin/range-diff.c @@ -6,6 +6,7 @@ #include "parse-options.h" #include "range-diff.h" #include "config.h" +#include "parse.h" static const char * const builtin_range_diff_usage[] = { @@ -15,6 +16,21 @@ N_("git range-diff [<options>] <base> <old-tip> <new-tip>"), NULL }; +static int parse_max_memory(const struct option *opt, const char *arg, int unset) +{ + size_t *max_memory = opt->value; + uintmax_t val; + + if (unset) + return 0; + + if (!git_parse_unsigned(arg, &val, SIZE_MAX)) + return error(_("invalid max-memory value: %s"), arg); + + *max_memory = (size_t)val; + return 0; +} + int cmd_range_diff(int argc, const char **argv, const char *prefix, @@ -25,6 +41,7 @@ int cmd_range_diff(int argc, struct strvec diff_merges_arg = STRVEC_INIT; struct range_diff_options range_diff_opts = { .creation_factor = RANGE_DIFF_CREATION_FACTOR_DEFAULT, + .max_memory = RANGE_DIFF_MAX_MEMORY_DEFAULT, .diffopt = &diffopt, .other_arg = &other_arg }; @@ -40,6 +57,10 @@ int cmd_range_diff(int argc, PARSE_OPT_OPTARG), OPT_PASSTHRU_ARGV(0, "diff-merges", &diff_merges_arg, N_("style"), N_("passed to 'git log'"), 0), + OPT_CALLBACK(0, "max-memory", &range_diff_opts.max_memory, + N_("size"), + N_("maximum memory for cost matrix (default 4G)"), + parse_max_memory), OPT_PASSTHRU_ARGV(0, "remerge-diff", &diff_merges_arg, NULL, N_("passed to 'git log'"), PARSE_OPT_NOARG), OPT_BOOL(0, "left-only", &left_only, diff --git a/builtin/rebase.c b/builtin/rebase.c index 3c85768d29..67c0352bf8 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1242,6 +1242,9 @@ int cmd_rebase(int argc, builtin_rebase_usage, builtin_rebase_options); +#ifndef WITH_BREAKING_CHANGES + warn_on_auto_comment_char = true; +#endif /* !WITH_BREAKING_CHANGES */ prepare_repo_settings(the_repository); the_repository->settings.command_requires_full_index = 0; diff --git a/builtin/reflog.c b/builtin/reflog.c index 1db26aa65f..c8f6b93d60 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -3,6 +3,8 @@ #include "builtin.h" #include "config.h" #include "gettext.h" +#include "hex.h" +#include "odb.h" #include "revision.h" #include "reachable.h" #include "wildmatch.h" @@ -17,21 +19,24 @@ #define BUILTIN_REFLOG_LIST_USAGE \ N_("git reflog list") -#define BUILTIN_REFLOG_EXPIRE_USAGE \ - N_("git reflog expire [--expire=<time>] [--expire-unreachable=<time>]\n" \ - " [--rewrite] [--updateref] [--stale-fix]\n" \ - " [--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...]") +#define BUILTIN_REFLOG_EXISTS_USAGE \ + N_("git reflog exists <ref>") + +#define BUILTIN_REFLOG_WRITE_USAGE \ + N_("git reflog write <ref> <old-oid> <new-oid> <message>") #define BUILTIN_REFLOG_DELETE_USAGE \ N_("git reflog delete [--rewrite] [--updateref]\n" \ " [--dry-run | -n] [--verbose] <ref>@{<specifier>}...") -#define BUILTIN_REFLOG_EXISTS_USAGE \ - N_("git reflog exists <ref>") - #define BUILTIN_REFLOG_DROP_USAGE \ N_("git reflog drop [--all [--single-worktree] | <refs>...]") +#define BUILTIN_REFLOG_EXPIRE_USAGE \ + N_("git reflog expire [--expire=<time>] [--expire-unreachable=<time>]\n" \ + " [--rewrite] [--updateref] [--stale-fix]\n" \ + " [--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...]") + static const char *const reflog_show_usage[] = { BUILTIN_REFLOG_SHOW_USAGE, NULL, @@ -42,9 +47,14 @@ static const char *const reflog_list_usage[] = { NULL, }; -static const char *const reflog_expire_usage[] = { - BUILTIN_REFLOG_EXPIRE_USAGE, - NULL +static const char *const reflog_exists_usage[] = { + BUILTIN_REFLOG_EXISTS_USAGE, + NULL, +}; + +static const char *const reflog_write_usage[] = { + BUILTIN_REFLOG_WRITE_USAGE, + NULL, }; static const char *const reflog_delete_usage[] = { @@ -52,23 +62,24 @@ static const char *const reflog_delete_usage[] = { NULL }; -static const char *const reflog_exists_usage[] = { - BUILTIN_REFLOG_EXISTS_USAGE, - NULL, -}; - static const char *const reflog_drop_usage[] = { BUILTIN_REFLOG_DROP_USAGE, NULL, }; +static const char *const reflog_expire_usage[] = { + BUILTIN_REFLOG_EXPIRE_USAGE, + NULL +}; + static const char *const reflog_usage[] = { BUILTIN_REFLOG_SHOW_USAGE, BUILTIN_REFLOG_LIST_USAGE, - BUILTIN_REFLOG_EXPIRE_USAGE, + BUILTIN_REFLOG_EXISTS_USAGE, + BUILTIN_REFLOG_WRITE_USAGE, BUILTIN_REFLOG_DELETE_USAGE, BUILTIN_REFLOG_DROP_USAGE, - BUILTIN_REFLOG_EXISTS_USAGE, + BUILTIN_REFLOG_EXPIRE_USAGE, NULL }; @@ -395,6 +406,59 @@ static int cmd_reflog_drop(int argc, const char **argv, const char *prefix, return ret; } +static int cmd_reflog_write(int argc, const char **argv, const char *prefix, + struct repository *repo) +{ + const struct option options[] = { + OPT_END() + }; + struct object_id old_oid, new_oid; + struct strbuf err = STRBUF_INIT; + struct ref_transaction *tx; + const char *ref, *message; + int ret; + + argc = parse_options(argc, argv, prefix, options, reflog_write_usage, 0); + if (argc != 4) + usage_with_options(reflog_write_usage, options); + + ref = argv[0]; + if (!is_root_ref(ref) && check_refname_format(ref, 0)) + die(_("invalid reference name: %s"), ref); + + ret = get_oid_hex_algop(argv[1], &old_oid, repo->hash_algo); + if (ret) + die(_("invalid old object ID: '%s'"), argv[1]); + if (!is_null_oid(&old_oid) && !odb_has_object(repo->objects, &old_oid, 0)) + die(_("old object '%s' does not exist"), argv[1]); + + ret = get_oid_hex_algop(argv[2], &new_oid, repo->hash_algo); + if (ret) + die(_("invalid new object ID: '%s'"), argv[2]); + if (!is_null_oid(&new_oid) && !odb_has_object(repo->objects, &new_oid, 0)) + die(_("new object '%s' does not exist"), argv[2]); + + message = argv[3]; + + tx = ref_store_transaction_begin(get_main_ref_store(repo), 0, &err); + if (!tx) + die(_("cannot start transaction: %s"), err.buf); + + ret = ref_transaction_update_reflog(tx, ref, &new_oid, &old_oid, + git_committer_info(0), + message, 0, &err); + if (ret) + die(_("cannot queue reflog update: %s"), err.buf); + + ret = ref_transaction_commit(tx, &err); + if (ret) + die(_("cannot commit reflog update: %s"), err.buf); + + ref_transaction_free(tx); + strbuf_release(&err); + return 0; +} + /* * main "reflog" */ @@ -407,10 +471,11 @@ int cmd_reflog(int argc, struct option options[] = { OPT_SUBCOMMAND("show", &fn, cmd_reflog_show), OPT_SUBCOMMAND("list", &fn, cmd_reflog_list), - OPT_SUBCOMMAND("expire", &fn, cmd_reflog_expire), - OPT_SUBCOMMAND("delete", &fn, cmd_reflog_delete), OPT_SUBCOMMAND("exists", &fn, cmd_reflog_exists), + OPT_SUBCOMMAND("write", &fn, cmd_reflog_write), + OPT_SUBCOMMAND("delete", &fn, cmd_reflog_delete), OPT_SUBCOMMAND("drop", &fn, cmd_reflog_drop), + OPT_SUBCOMMAND("expire", &fn, cmd_reflog_expire), OPT_END() }; diff --git a/builtin/refs.c b/builtin/refs.c index c7ad0a2963..91548783b7 100644 --- a/builtin/refs.c +++ b/builtin/refs.c @@ -6,6 +6,8 @@ #include "refs.h" #include "strbuf.h" #include "worktree.h" +#include "for-each-ref.h" +#include "refs/refs-internal.h" #define REFS_MIGRATE_USAGE \ N_("git refs migrate --ref-format=<format> [--no-reflog] [--dry-run]") @@ -13,6 +15,9 @@ #define REFS_VERIFY_USAGE \ N_("git refs verify [--strict] [--verbose]") +#define REFS_EXISTS_USAGE \ + N_("git refs exists <ref>") + static int cmd_refs_migrate(int argc, const char **argv, const char *prefix, struct repository *repo UNUSED) { @@ -101,6 +106,59 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix, return ret; } +static int cmd_refs_list(int argc, const char **argv, const char *prefix, + struct repository *repo) +{ + static char const * const refs_list_usage[] = { + N_("git refs list " COMMON_USAGE_FOR_EACH_REF), + NULL + }; + + return for_each_ref_core(argc, argv, prefix, repo, refs_list_usage); +} + +static int cmd_refs_exists(int argc, const char **argv, const char *prefix, + struct repository *repo UNUSED) +{ + struct strbuf unused_referent = STRBUF_INIT; + struct object_id unused_oid; + unsigned int unused_type; + int failure_errno = 0; + const char *ref; + int ret = 0; + const char * const exists_usage[] = { + REFS_EXISTS_USAGE, + NULL, + }; + struct option options[] = { + OPT_END(), + }; + + argc = parse_options(argc, argv, prefix, options, exists_usage, 0); + if (argc != 1) + die(_("'git refs exists' requires a reference")); + + ref = *argv++; + if (refs_read_raw_ref(get_main_ref_store(the_repository), ref, + &unused_oid, &unused_referent, &unused_type, + &failure_errno)) { + if (failure_errno == ENOENT || failure_errno == EISDIR) { + error(_("reference does not exist")); + ret = 2; + } else { + errno = failure_errno; + error_errno(_("failed to look up reference")); + ret = 1; + } + + goto out; + } + +out: + strbuf_release(&unused_referent); + return ret; +} + int cmd_refs(int argc, const char **argv, const char *prefix, @@ -109,12 +167,16 @@ int cmd_refs(int argc, const char * const refs_usage[] = { REFS_MIGRATE_USAGE, REFS_VERIFY_USAGE, + "git refs list " COMMON_USAGE_FOR_EACH_REF, + REFS_EXISTS_USAGE, NULL, }; parse_opt_subcommand_fn *fn = NULL; struct option opts[] = { OPT_SUBCOMMAND("migrate", &fn, cmd_refs_migrate), OPT_SUBCOMMAND("verify", &fn, cmd_refs_verify), + OPT_SUBCOMMAND("list", &fn, cmd_refs_list), + OPT_SUBCOMMAND("exists", &fn, cmd_refs_exists), OPT_END(), }; diff --git a/builtin/remote.c b/builtin/remote.c index 8961ae6a89..8a7ed4299a 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -1,9 +1,11 @@ #define USE_THE_REPOSITORY_VARIABLE -#define DISABLE_SIGN_COMPARE_WARNINGS #include "builtin.h" +#include "advice.h" #include "config.h" +#include "date.h" #include "gettext.h" +#include "ident.h" #include "parse-options.h" #include "path.h" #include "transport.h" @@ -182,7 +184,6 @@ static int add(int argc, const char **argv, const char *prefix, struct remote *remote; struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT; const char *name, *url; - int i; int result = 0; struct option options[] = { @@ -233,7 +234,7 @@ static int add(int argc, const char **argv, const char *prefix, strbuf_addf(&buf, "remote.%s.fetch", name); if (track.nr == 0) string_list_append(&track, "*"); - for (i = 0; i < track.nr; i++) { + for (size_t i = 0; i < track.nr; i++) { add_branch(buf.buf, track.items[i].string, name, mirror, &buf2); } @@ -612,53 +613,169 @@ static int add_branch_for_removal(const char *refname, struct rename_info { const char *old_name; const char *new_name; - struct string_list *remote_branches; - uint32_t symrefs_nr; + struct ref_transaction *transaction; + struct progress *progress; + struct strbuf *err; + uint32_t progress_nr; + uint64_t index; }; -static int read_remote_branches(const char *refname, const char *referent UNUSED, - const struct object_id *oid UNUSED, - int flags UNUSED, void *cb_data) +static void compute_renamed_ref(struct rename_info *rename, + const char *refname, + struct strbuf *out) +{ + strbuf_reset(out); + strbuf_addstr(out, refname); + strbuf_splice(out, strlen("refs/remotes/"), strlen(rename->old_name), + rename->new_name, strlen(rename->new_name)); +} + +static int rename_one_reflog_entry(const char *old_refname, + struct object_id *old_oid, + struct object_id *new_oid, + const char *committer, + timestamp_t timestamp, int tz, + const char *msg, void *cb_data) { struct rename_info *rename = cb_data; - struct strbuf buf = STRBUF_INIT; - struct string_list_item *item; - int flag; - const char *symref; - - strbuf_addf(&buf, "refs/remotes/%s/", rename->old_name); - if (starts_with(refname, buf.buf)) { - item = string_list_append(rename->remote_branches, refname); - symref = refs_resolve_ref_unsafe(get_main_ref_store(the_repository), - refname, RESOLVE_REF_READING, - NULL, &flag); - if (symref && (flag & REF_ISSYMREF)) { - item->util = xstrdup(symref); - rename->symrefs_nr++; - } else { - item->util = NULL; - } + struct strbuf new_refname = STRBUF_INIT; + struct strbuf identity = STRBUF_INIT; + struct strbuf name = STRBUF_INIT; + struct strbuf mail = STRBUF_INIT; + struct ident_split ident; + const char *date; + int error; + + compute_renamed_ref(rename, old_refname, &new_refname); + + if (split_ident_line(&ident, committer, strlen(committer)) < 0) { + error = -1; + goto out; } - strbuf_release(&buf); - return 0; + strbuf_add(&name, ident.name_begin, ident.name_end - ident.name_begin); + strbuf_add(&mail, ident.mail_begin, ident.mail_end - ident.mail_begin); + + date = show_date(timestamp, tz, DATE_MODE(NORMAL)); + strbuf_addstr(&identity, fmt_ident(name.buf, mail.buf, + WANT_BLANK_IDENT, date, 0)); + + error = ref_transaction_update_reflog(rename->transaction, new_refname.buf, + new_oid, old_oid, identity.buf, msg, + rename->index++, rename->err); + +out: + strbuf_release(&new_refname); + strbuf_release(&identity); + strbuf_release(&name); + strbuf_release(&mail); + return error; +} + +static int rename_one_reflog(const char *old_refname, + const struct object_id *old_oid, + struct rename_info *rename) +{ + struct strbuf new_refname = STRBUF_INIT; + struct strbuf message = STRBUF_INIT; + int error; + + if (!refs_reflog_exists(get_main_ref_store(the_repository), old_refname)) + return 0; + + error = refs_for_each_reflog_ent(get_main_ref_store(the_repository), + old_refname, rename_one_reflog_entry, rename); + if (error < 0) + goto out; + + compute_renamed_ref(rename, old_refname, &new_refname); + + /* + * Manually write the reflog entry for the now-renamed ref. We cannot + * rely on `rename_one_ref()` to do this for us as that would screw + * over order in which reflog entries are being written. + * + * Furthermore, we only append the entry in case the reference + * resolves. Missing references shouldn't have reflogs anyway. + */ + strbuf_addf(&message, "remote: renamed %s to %s", old_refname, + new_refname.buf); + + error = ref_transaction_update_reflog(rename->transaction, new_refname.buf, + old_oid, old_oid, git_committer_info(0), + message.buf, rename->index++, rename->err); + if (error < 0) + return error; + +out: + strbuf_release(&new_refname); + strbuf_release(&message); + return error; +} + +static int rename_one_ref(const char *old_refname, const char *referent, + const struct object_id *oid, + int flags, void *cb_data) +{ + struct strbuf new_referent = STRBUF_INIT; + struct strbuf new_refname = STRBUF_INIT; + struct rename_info *rename = cb_data; + int error; + + compute_renamed_ref(rename, old_refname, &new_refname); + + if (flags & REF_ISSYMREF) { + /* + * Stupidly enough `referent` is not pointing to the immediate + * target of a symref, but it's the recursively resolved value. + * So symrefs pointing to symrefs would be misresolved, and + * unborn symrefs don't have any value for the `referent` at all. + */ + referent = refs_resolve_ref_unsafe(get_main_ref_store(the_repository), + old_refname, RESOLVE_REF_NO_RECURSE, + NULL, NULL); + compute_renamed_ref(rename, referent, &new_referent); + oid = NULL; + } + + error = ref_transaction_delete(rename->transaction, old_refname, + oid, referent, REF_NO_DEREF, NULL, rename->err); + if (error < 0) + goto out; + + error = ref_transaction_update(rename->transaction, new_refname.buf, oid, null_oid(the_hash_algo), + (flags & REF_ISSYMREF) ? new_referent.buf : NULL, NULL, + REF_SKIP_CREATE_REFLOG | REF_NO_DEREF | REF_SKIP_OID_VERIFICATION, + NULL, rename->err); + if (error < 0) + goto out; + + error = rename_one_reflog(old_refname, oid, rename); + if (error < 0) + goto out; + + display_progress(rename->progress, ++rename->progress_nr); + +out: + strbuf_release(&new_referent); + strbuf_release(&new_refname); + return error; } static int migrate_file(struct remote *remote) { struct strbuf buf = STRBUF_INIT; - int i; strbuf_addf(&buf, "remote.%s.url", remote->name); - for (i = 0; i < remote->url.nr; i++) + for (size_t i = 0; i < remote->url.nr; i++) repo_config_set_multivar(the_repository, buf.buf, remote->url.v[i], "^$", 0); strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s.push", remote->name); - for (i = 0; i < remote->push.nr; i++) + for (int i = 0; i < remote->push.nr; i++) repo_config_set_multivar(the_repository, buf.buf, remote->push.items[i].raw, "^$", 0); strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s.fetch", remote->name); - for (i = 0; i < remote->fetch.nr; i++) + for (int i = 0; i < remote->fetch.nr; i++) repo_config_set_multivar(the_repository, buf.buf, remote->fetch.items[i].raw, "^$", 0); #ifndef WITH_BREAKING_CHANGES if (remote->origin == REMOTE_REMOTES) @@ -730,6 +847,14 @@ static void handle_push_default(const char* old_name, const char* new_name) strbuf_release(&push_default.origin); } +static const char conflicting_remote_refs_advice[] = N_( + "The remote you are trying to rename has conflicting references in the\n" + "new target refspec. This is most likely caused by you trying to nest\n" + "a remote into itself, e.g. by renaming 'parent' into 'parent/child'\n" + "or by unnesting a remote, e.g. the other way round.\n" + "\n" + "If that is the case, you can address this by first renaming the\n" + "remote to a different name.\n"); static int mv(int argc, const char **argv, const char *prefix, struct repository *repo UNUSED) @@ -741,11 +866,11 @@ static int mv(int argc, const char **argv, const char *prefix, }; struct remote *oldremote, *newremote; struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT, - old_remote_context = STRBUF_INIT; - struct string_list remote_branches = STRING_LIST_INIT_DUP; - struct rename_info rename; - int i, refs_renamed_nr = 0, refspec_updated = 0; - struct progress *progress = NULL; + old_remote_context = STRBUF_INIT, err = STRBUF_INIT; + struct rename_info rename = { + .err = &err, + }; + int refspecs_need_update = 0; int result = 0; argc = parse_options(argc, argv, prefix, options, @@ -756,8 +881,6 @@ static int mv(int argc, const char **argv, const char *prefix, rename.old_name = argv[0]; rename.new_name = argv[1]; - rename.remote_branches = &remote_branches; - rename.symrefs_nr = 0; oldremote = remote_get(rename.old_name); if (!remote_is_configured(oldremote, 1)) { @@ -785,19 +908,50 @@ static int mv(int argc, const char **argv, const char *prefix, goto out; } + strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old_name); + + for (int i = 0; i < oldremote->fetch.nr && !refspecs_need_update; i++) + refspecs_need_update = !!strstr(oldremote->fetch.items[i].raw, + old_remote_context.buf); + + if (refspecs_need_update) { + rename.transaction = ref_store_transaction_begin(get_main_ref_store(the_repository), + 0, &err); + if (!rename.transaction) + goto out; + + if (show_progress) + rename.progress = start_delayed_progress(the_repository, + _("Renaming remote references"), 0); + + strbuf_reset(&buf); + strbuf_addf(&buf, "refs/remotes/%s/", rename.old_name); + + result = refs_for_each_rawref_in(get_main_ref_store(the_repository), buf.buf, + rename_one_ref, &rename); + if (result < 0) + die(_("queueing remote ref renames failed: %s"), rename.err->buf); + + result = ref_transaction_prepare(rename.transaction, &err); + if (result < 0) { + error("renaming remote references failed: %s", err.buf); + if (result == REF_TRANSACTION_ERROR_NAME_CONFLICT) + advise(conflicting_remote_refs_advice); + die(NULL); + } + } + if (oldremote->fetch.nr) { strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s.fetch", rename.new_name); repo_config_set_multivar(the_repository, buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE); - strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old_name); - for (i = 0; i < oldremote->fetch.nr; i++) { + for (int i = 0; i < oldremote->fetch.nr; i++) { char *ptr; strbuf_reset(&buf2); strbuf_addstr(&buf2, oldremote->fetch.items[i].raw); ptr = strstr(buf2.buf, old_remote_context.buf); if (ptr) { - refspec_updated = 1; strbuf_splice(&buf2, ptr-buf2.buf + strlen(":refs/remotes/"), strlen(rename.old_name), rename.new_name, @@ -813,7 +967,7 @@ static int mv(int argc, const char **argv, const char *prefix, } read_branches(); - for (i = 0; i < branch_list.nr; i++) { + for (size_t i = 0; i < branch_list.nr; i++) { struct string_list_item *item = branch_list.items + i; struct branch_info *info = item->util; if (info->remote_name && !strcmp(info->remote_name, rename.old_name)) { @@ -828,83 +982,23 @@ static int mv(int argc, const char **argv, const char *prefix, } } - if (!refspec_updated) - goto out; - - /* - * First remove symrefs, then rename the rest, finally create - * the new symrefs. - */ - refs_for_each_ref(get_main_ref_store(the_repository), - read_remote_branches, &rename); - if (show_progress) { - /* - * Count symrefs twice, since "renaming" them is done by - * deleting and recreating them in two separate passes. - */ - progress = start_progress(the_repository, - _("Renaming remote references"), - rename.remote_branches->nr + rename.symrefs_nr); - } - for (i = 0; i < remote_branches.nr; i++) { - struct string_list_item *item = remote_branches.items + i; - struct strbuf referent = STRBUF_INIT; - - if (refs_read_symbolic_ref(get_main_ref_store(the_repository), item->string, - &referent)) - continue; - if (refs_delete_ref(get_main_ref_store(the_repository), NULL, item->string, NULL, REF_NO_DEREF)) - die(_("deleting '%s' failed"), item->string); - - strbuf_release(&referent); - display_progress(progress, ++refs_renamed_nr); - } - for (i = 0; i < remote_branches.nr; i++) { - struct string_list_item *item = remote_branches.items + i; + if (refspecs_need_update) { + result = ref_transaction_commit(rename.transaction, &err); + if (result < 0) + die(_("renaming remote refs failed: %s"), rename.err->buf); - if (item->util) - continue; - strbuf_reset(&buf); - strbuf_addstr(&buf, item->string); - strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old_name), - rename.new_name, strlen(rename.new_name)); - strbuf_reset(&buf2); - strbuf_addf(&buf2, "remote: renamed %s to %s", - item->string, buf.buf); - if (refs_rename_ref(get_main_ref_store(the_repository), item->string, buf.buf, buf2.buf)) - die(_("renaming '%s' failed"), item->string); - display_progress(progress, ++refs_renamed_nr); - } - for (i = 0; i < remote_branches.nr; i++) { - struct string_list_item *item = remote_branches.items + i; + stop_progress(&rename.progress); - if (!item->util) - continue; - strbuf_reset(&buf); - strbuf_addstr(&buf, item->string); - strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old_name), - rename.new_name, strlen(rename.new_name)); - strbuf_reset(&buf2); - strbuf_addstr(&buf2, item->util); - strbuf_splice(&buf2, strlen("refs/remotes/"), strlen(rename.old_name), - rename.new_name, strlen(rename.new_name)); - strbuf_reset(&buf3); - strbuf_addf(&buf3, "remote: renamed %s to %s", - item->string, buf.buf); - if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf)) - die(_("creating '%s' failed"), buf.buf); - display_progress(progress, ++refs_renamed_nr); + handle_push_default(rename.old_name, rename.new_name); } - stop_progress(&progress); - - handle_push_default(rename.old_name, rename.new_name); out: - string_list_clear(&remote_branches, 1); + ref_transaction_free(rename.transaction); strbuf_release(&old_remote_context); strbuf_release(&buf); strbuf_release(&buf2); strbuf_release(&buf3); + strbuf_release(&err); return result; } @@ -920,7 +1014,7 @@ static int rm(int argc, const char **argv, const char *prefix, struct string_list branches = STRING_LIST_INIT_DUP; struct string_list skipped = STRING_LIST_INIT_DUP; struct branches_for_remote cb_data; - int i, result; + int result; memset(&cb_data, 0, sizeof(cb_data)); cb_data.branches = &branches; @@ -942,7 +1036,7 @@ static int rm(int argc, const char **argv, const char *prefix, for_each_remote(add_known_remote, &known_remotes); read_branches(); - for (i = 0; i < branch_list.nr; i++) { + for (size_t i = 0; i < branch_list.nr; i++) { struct string_list_item *item = branch_list.items + i; struct branch_info *info = item->util; if (info->remote_name && !strcmp(info->remote_name, remote->name)) { @@ -988,7 +1082,7 @@ static int rm(int argc, const char **argv, const char *prefix, "Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n" "to delete them, use:", skipped.nr)); - for (i = 0; i < skipped.nr; i++) + for (size_t i = 0; i < skipped.nr; i++) fprintf(stderr, " git branch -d %s\n", skipped.items[i].string); } @@ -1166,7 +1260,6 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data) struct branch_info *branch_info = item->util; struct string_list *merge = &branch_info->merge; int width = show_info->width + 4; - int i; if (branch_info->rebase >= REBASE_TRUE && branch_info->merge.nr > 1) { error(_("invalid branch.%s.merge; cannot rebase onto > 1 branch"), @@ -1192,7 +1285,7 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data) } else { printf_ln(_("merges with remote %s"), merge->items[0].string); } - for (i = 1; i < merge->nr; i++) + for (size_t i = 1; i < merge->nr; i++) printf(_("%-*s and with remote %s\n"), width, "", merge->items[i].string); @@ -1277,7 +1370,6 @@ static int get_one_entry(struct remote *remote, void *priv) struct string_list *list = priv; struct strbuf remote_info_buf = STRBUF_INIT; struct strvec *url; - int i; if (remote->url.nr > 0) { struct strbuf promisor_config = STRBUF_INIT; @@ -1294,8 +1386,7 @@ static int get_one_entry(struct remote *remote, void *priv) } else string_list_append(list, remote->name)->util = NULL; url = push_url_of_remote(remote); - for (i = 0; i < url->nr; i++) - { + for (size_t i = 0; i < url->nr; i++) { strbuf_addf(&remote_info_buf, "%s (push)", url->v[i]); string_list_append(list, remote->name)->util = strbuf_detach(&remote_info_buf, NULL); @@ -1312,10 +1403,8 @@ static int show_all(void) result = for_each_remote(get_one_entry, &list); if (!result) { - int i; - string_list_sort(&list); - for (i = 0; i < list.nr; i++) { + for (size_t i = 0; i < list.nr; i++) { struct string_list_item *item = list.items + i; if (verbose) printf("%s\t%s\n", item->string, @@ -1352,7 +1441,7 @@ static int show(int argc, const char **argv, const char *prefix, query_flag = (GET_REF_STATES | GET_HEAD_NAMES | GET_PUSH_REF_STATES); for (; argc; argc--, argv++) { - int i; + size_t i; struct strvec *url; get_remote_ref_states(*argv, &info.states, query_flag); @@ -1458,7 +1547,7 @@ static void report_set_head_auto(const char *remote, const char *head_name, static int set_head(int argc, const char **argv, const char *prefix, struct repository *repo UNUSED) { - int i, opt_a = 0, opt_d = 0, result = 0, was_detached; + int opt_a = 0, opt_d = 0, result = 0, was_detached; struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT, b_local_head = STRBUF_INIT; char *head_name = NULL; @@ -1492,7 +1581,7 @@ static int set_head(int argc, const char **argv, const char *prefix, else if (states.heads.nr > 1) { result |= error(_("Multiple remote HEAD branches. " "Please choose one explicitly with:")); - for (i = 0; i < states.heads.nr; i++) + for (size_t i = 0; i < states.heads.nr; i++) fprintf(stderr, " git remote set-head %s %s\n", argv[0], states.heads.items[i].string); } else @@ -1717,7 +1806,7 @@ static int set_branches(int argc, const char **argv, const char *prefix, static int get_url(int argc, const char **argv, const char *prefix, struct repository *repo UNUSED) { - int i, push_mode = 0, all_mode = 0; + int push_mode = 0, all_mode = 0; const char *remotename = NULL; struct remote *remote; struct strvec *url; @@ -1745,7 +1834,7 @@ static int get_url(int argc, const char **argv, const char *prefix, url = push_mode ? push_url_of_remote(remote) : &remote->url; if (all_mode) { - for (i = 0; i < url->nr; i++) + for (size_t i = 0; i < url->nr; i++) printf_ln("%s", url->v[i]); } else { printf_ln("%s", url->v[0]); @@ -1757,7 +1846,7 @@ static int get_url(int argc, const char **argv, const char *prefix, static int set_url(int argc, const char **argv, const char *prefix, struct repository *repo UNUSED) { - int i, push_mode = 0, add_mode = 0, delete_mode = 0; + int push_mode = 0, add_mode = 0, delete_mode = 0; int matches = 0, negative_matches = 0; const char *remotename = NULL; const char *newurl = NULL; @@ -1821,7 +1910,7 @@ static int set_url(int argc, const char **argv, const char *prefix, if (regcomp(&old_regex, oldurl, REG_EXTENDED)) die(_("Invalid old URL pattern: %s"), oldurl); - for (i = 0; i < urlset->nr; i++) + for (size_t i = 0; i < urlset->nr; i++) if (!regexec(&old_regex, urlset->v[i], 0, NULL, 0)) matches++; else diff --git a/builtin/repack.c b/builtin/repack.c index a4def39197..c490a51e91 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -223,9 +223,10 @@ static void mark_packs_for_deletion(struct existing_packs *existing, static void remove_redundant_pack(const char *dir_name, const char *base_name) { struct strbuf buf = STRBUF_INIT; - struct multi_pack_index *m = get_multi_pack_index(the_repository->objects->sources); + struct odb_source *source = the_repository->objects->sources; + struct multi_pack_index *m = get_multi_pack_index(source); strbuf_addf(&buf, "%s.pack", base_name); - if (m && m->local && midx_contains_pack(m, buf.buf)) + if (m && source->local && midx_contains_pack(m, buf.buf)) clear_midx_file(the_repository); strbuf_insertf(&buf, 0, "%s/", dir_name); unlink_pack_path(buf.buf, 1); @@ -1711,7 +1712,7 @@ int cmd_repack(int argc, unsigned flags = 0; if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL, 0)) flags |= MIDX_WRITE_INCREMENTAL; - write_midx_file(the_repository, repo_get_object_directory(the_repository), + write_midx_file(the_repository->objects->sources, NULL, NULL, flags); } diff --git a/builtin/repo.c b/builtin/repo.c new file mode 100644 index 0000000000..bbb0966f2d --- /dev/null +++ b/builtin/repo.c @@ -0,0 +1,171 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "builtin.h" +#include "environment.h" +#include "parse-options.h" +#include "quote.h" +#include "refs.h" +#include "strbuf.h" +#include "shallow.h" + +static const char *const repo_usage[] = { + "git repo info [--format=(keyvalue|nul)] [-z] [<key>...]", + NULL +}; + +typedef int get_value_fn(struct repository *repo, struct strbuf *buf); + +enum output_format { + FORMAT_KEYVALUE, + FORMAT_NUL_TERMINATED, +}; + +struct field { + const char *key; + get_value_fn *get_value; +}; + +static int get_layout_bare(struct repository *repo UNUSED, struct strbuf *buf) +{ + strbuf_addstr(buf, is_bare_repository() ? "true" : "false"); + return 0; +} + +static int get_layout_shallow(struct repository *repo, struct strbuf *buf) +{ + strbuf_addstr(buf, + is_repository_shallow(repo) ? "true" : "false"); + return 0; +} + +static int get_object_format(struct repository *repo, struct strbuf *buf) +{ + strbuf_addstr(buf, repo->hash_algo->name); + return 0; +} + +static int get_references_format(struct repository *repo, struct strbuf *buf) +{ + strbuf_addstr(buf, + ref_storage_format_to_name(repo->ref_storage_format)); + return 0; +} + +/* repo_info_fields keys must be in lexicographical order */ +static const struct field repo_info_fields[] = { + { "layout.bare", get_layout_bare }, + { "layout.shallow", get_layout_shallow }, + { "object.format", get_object_format }, + { "references.format", get_references_format }, +}; + +static int repo_info_fields_cmp(const void *va, const void *vb) +{ + const struct field *a = va; + const struct field *b = vb; + + return strcmp(a->key, b->key); +} + +static get_value_fn *get_value_fn_for_key(const char *key) +{ + const struct field search_key = { key, NULL }; + const struct field *found = bsearch(&search_key, repo_info_fields, + ARRAY_SIZE(repo_info_fields), + sizeof(*found), + repo_info_fields_cmp); + return found ? found->get_value : NULL; +} + +static int print_fields(int argc, const char **argv, + struct repository *repo, + enum output_format format) +{ + int ret = 0; + struct strbuf valbuf = STRBUF_INIT; + struct strbuf quotbuf = STRBUF_INIT; + + for (int i = 0; i < argc; i++) { + get_value_fn *get_value; + const char *key = argv[i]; + + get_value = get_value_fn_for_key(key); + + if (!get_value) { + ret = error(_("key '%s' not found"), key); + continue; + } + + strbuf_reset(&valbuf); + strbuf_reset("buf); + + get_value(repo, &valbuf); + + switch (format) { + case FORMAT_KEYVALUE: + quote_c_style(valbuf.buf, "buf, NULL, 0); + printf("%s=%s\n", key, quotbuf.buf); + break; + case FORMAT_NUL_TERMINATED: + printf("%s\n%s%c", key, valbuf.buf, '\0'); + break; + default: + BUG("not a valid output format: %d", format); + } + } + + strbuf_release(&valbuf); + strbuf_release("buf); + return ret; +} + +static int parse_format_cb(const struct option *opt, + const char *arg, int unset UNUSED) +{ + enum output_format *format = opt->value; + + if (opt->short_name == 'z') + *format = FORMAT_NUL_TERMINATED; + else if (!strcmp(arg, "nul")) + *format = FORMAT_NUL_TERMINATED; + else if (!strcmp(arg, "keyvalue")) + *format = FORMAT_KEYVALUE; + else + die(_("invalid format '%s'"), arg); + + return 0; +} + +static int repo_info(int argc, const char **argv, const char *prefix, + struct repository *repo) +{ + enum output_format format = FORMAT_KEYVALUE; + struct option options[] = { + OPT_CALLBACK_F(0, "format", &format, N_("format"), + N_("output format"), + PARSE_OPT_NONEG, parse_format_cb), + OPT_CALLBACK_F('z', NULL, &format, NULL, + N_("synonym for --format=nul"), + PARSE_OPT_NONEG | PARSE_OPT_NOARG, + parse_format_cb), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, repo_usage, 0); + + return print_fields(argc, argv, repo, format); +} + +int cmd_repo(int argc, const char **argv, const char *prefix, + struct repository *repo) +{ + parse_opt_subcommand_fn *fn = NULL; + struct option options[] = { + OPT_SUBCOMMAND("info", &fn, repo_info), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, repo_usage, 0); + + return fn(argc, argv, prefix, repo); +} diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 44ff1b8342..9da92b990d 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -708,7 +708,6 @@ int cmd_rev_parse(int argc, struct object_id oid; unsigned int flags = 0; const char *name = NULL; - struct object_context unused; struct strbuf buf = STRBUF_INIT; int seen_end_of_options = 0; enum format_type format = FORMAT_DEFAULT; @@ -1141,9 +1140,8 @@ int cmd_rev_parse(int argc, name++; type = REVERSED; } - if (!get_oid_with_context(the_repository, name, - flags, &oid, &unused)) { - object_context_release(&unused); + if (!repo_get_oid_with_flags(the_repository, name, &oid, + flags)) { if (output_algo) repo_oid_to_algop(the_repository, &oid, output_algo, &oid); @@ -1153,7 +1151,6 @@ int cmd_rev_parse(int argc, show_rev(type, &oid, name); continue; } - object_context_release(&unused); if (verify) die_no_single_rev(quiet); if (has_dashdash) diff --git a/builtin/revert.c b/builtin/revert.c index c3f92b585d..bedc40f368 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -4,6 +4,7 @@ #include "builtin.h" #include "parse-options.h" #include "diff.h" +#include "environment.h" #include "gettext.h" #include "revision.h" #include "rerere.h" @@ -285,6 +286,9 @@ int cmd_revert(int argc, struct replay_opts opts = REPLAY_OPTS_INIT; int res; +#ifndef WITH_BREAKING_CHANGES + warn_on_auto_comment_char = true; +#endif /* !WITH_BREAKING_CHANGES */ opts.action = REPLAY_REVERT; sequencer_init_config(&opts); res = run_sequencer(argc, argv, prefix, &opts); @@ -302,6 +306,9 @@ struct repository *repo UNUSED) struct replay_opts opts = REPLAY_OPTS_INIT; int res; +#ifndef WITH_BREAKING_CHANGES + warn_on_auto_comment_char = true; +#endif /* !WITH_BREAKING_CHANGES */ opts.action = REPLAY_PICK; sequencer_init_config(&opts); res = run_sequencer(argc, argv, prefix, &opts); diff --git a/builtin/stash.c b/builtin/stash.c index 1977e50df2..b7db7c8364 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -377,7 +377,7 @@ static int diff_tree_binary(struct strbuf *out, struct object_id *w_commit) * however it should be done together with apply_cached. */ cp.git_cmd = 1; - strvec_pushl(&cp.args, "diff-tree", "--binary", NULL); + strvec_pushl(&cp.args, "diff-tree", "--binary", "--no-color", NULL); strvec_pushf(&cp.args, "%s^2^..%s^2", w_commit_hex, w_commit_hex); return pipe_command(&cp, NULL, 0, out, 0, NULL, 0); @@ -738,7 +738,8 @@ cleanup: return ret; } -static int reject_reflog_ent(struct object_id *ooid UNUSED, +static int reject_reflog_ent(const char *refname UNUSED, + struct object_id *ooid UNUSED, struct object_id *noid UNUSED, const char *email UNUSED, timestamp_t timestamp UNUSED, @@ -1088,7 +1089,6 @@ static int store_stash(int argc, const char **argv, const char *prefix, int quiet = 0; const char *stash_msg = NULL; struct object_id obj; - struct object_context dummy = {0}; struct option options[] = { OPT__QUIET(&quiet, N_("be quiet")), OPT_STRING('m', "message", &stash_msg, "message", @@ -1108,9 +1108,8 @@ static int store_stash(int argc, const char **argv, const char *prefix, return -1; } - if (get_oid_with_context(the_repository, - argv[0], quiet ? GET_OID_QUIETLY : 0, &obj, - &dummy)) { + if (repo_get_oid_with_flags(the_repository, argv[0], &obj, + quiet ? GET_OID_QUIETLY : 0)) { if (!quiet) fprintf_ln(stderr, _("Cannot update %s with %s"), ref_stash, argv[0]); @@ -1121,7 +1120,6 @@ static int store_stash(int argc, const char **argv, const char *prefix, ret = do_store_stash(&obj, stash_msg, quiet); out: - object_context_release(&dummy); return ret; } @@ -1283,6 +1281,7 @@ static int stash_staged(struct stash_info *info, struct strbuf *out_patch, cp_diff_tree.git_cmd = 1; strvec_pushl(&cp_diff_tree.args, "diff-tree", "-p", "--binary", + "--no-color", "-U1", "HEAD", oid_to_hex(&info->w_tree), "--", NULL); if (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) { ret = -1; @@ -1345,6 +1344,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps, cp_diff_tree.git_cmd = 1; strvec_pushl(&cp_diff_tree.args, "diff-tree", "-p", "-U1", "HEAD", + "--no-color", oid_to_hex(&info->w_tree), "--", NULL); if (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) { ret = -1; @@ -1719,6 +1719,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q cp_diff.git_cmd = 1; strvec_pushl(&cp_diff.args, "diff-index", "-p", + "--no-color", "--cached", "--binary", "HEAD", "--", NULL); add_pathspecs(&cp_diff.args, ps); @@ -2207,7 +2208,8 @@ struct stash_entry_data { size_t count; }; -static int collect_stash_entries(struct object_id *old_oid UNUSED, +static int collect_stash_entries(const char *refname UNUSED, + struct object_id *old_oid UNUSED, struct object_id *new_oid, const char *committer UNUSED, timestamp_t timestamp UNUSED, @@ -2233,7 +2235,6 @@ static int do_export_stash(struct repository *r, const char **argv) { struct object_id base; - struct object_context unused; struct commit *prev; struct commit_list *items = NULL, **iter = &items, *cur; int res = 0; @@ -2267,9 +2268,9 @@ static int do_export_stash(struct repository *r, struct commit *stash; if (parse_stash_revision(&revision, argv[i], 1) || - get_oid_with_context(r, revision.buf, - GET_OID_QUIETLY | GET_OID_GENTLY, - &oid, &unused)) { + repo_get_oid_with_flags(r, revision.buf, &oid, + GET_OID_QUIETLY | + GET_OID_GENTLY)) { res = error(_("unable to find stash entry %s"), argv[i]); goto out; } diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index 7ae7c82b6c..28124b324d 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -584,6 +584,7 @@ static void unpack_all(void) { int i; unsigned char *hdr = fill(sizeof(struct pack_header)); + struct odb_transaction *transaction; if (get_be32(hdr) != PACK_SIGNATURE) die("bad pack file"); @@ -599,12 +600,12 @@ static void unpack_all(void) progress = start_progress(the_repository, _("Unpacking objects"), nr_objects); CALLOC_ARRAY(obj_list, nr_objects); - begin_odb_transaction(); + transaction = begin_odb_transaction(the_repository->objects); for (i = 0; i < nr_objects; i++) { unpack_one(i); display_progress(progress, i + 1); } - end_odb_transaction(); + end_odb_transaction(transaction); stop_progress(&progress); if (delta_list) diff --git a/builtin/update-index.c b/builtin/update-index.c index 2380f3ccd6..2ba2d29c95 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -77,7 +77,7 @@ static void report(const char *fmt, ...) * objects invisible while a transaction is active, so flush the * transaction here before reporting a change made by update-index. */ - flush_odb_transaction(); + flush_odb_transaction(the_repository->objects->transaction); va_start(vp, fmt); vprintf(fmt, vp); putchar('\n'); @@ -940,6 +940,7 @@ int cmd_update_index(int argc, strbuf_getline_fn getline_fn; int parseopt_state = PARSE_OPT_UNKNOWN; struct repository *r = the_repository; + struct odb_transaction *transaction; struct option options[] = { OPT_BIT('q', NULL, &refresh_args.flags, N_("continue refresh even when index needs update"), @@ -1130,7 +1131,7 @@ int cmd_update_index(int argc, * Allow the object layer to optimize adding multiple objects in * a batch. */ - begin_odb_transaction(); + transaction = begin_odb_transaction(the_repository->objects); while (ctx.argc) { if (parseopt_state != PARSE_OPT_DONE) parseopt_state = parse_options_step(&ctx, options, @@ -1213,7 +1214,7 @@ int cmd_update_index(int argc, /* * By now we have added all of the new objects */ - end_odb_transaction(); + end_odb_transaction(transaction); if (split_index > 0) { if (repo_config_get_split_index(the_repository) == 0) diff --git a/builtin/var.c b/builtin/var.c index a2d790d453..cc3a43cde2 100644 --- a/builtin/var.c +++ b/builtin/var.c @@ -182,7 +182,7 @@ static void list_vars(void) if (ptr->multivalued && *val) { struct string_list list = STRING_LIST_INIT_DUP; - string_list_split(&list, val, '\n', -1); + string_list_split(&list, val, "\n", -1); for (size_t i = 0; i < list.nr; i++) printf("%s=%s\n", ptr->name, list.items[i].string); string_list_clear(&list, 0); diff --git a/bulk-checkin.c b/bulk-checkin.c index b2809ab039..124c493067 100644 --- a/bulk-checkin.c +++ b/bulk-checkin.c @@ -19,11 +19,7 @@ #include "object-file.h" #include "odb.h" -static int odb_transaction_nesting; - -static struct tmp_objdir *bulk_fsync_objdir; - -static struct bulk_checkin_packfile { +struct bulk_checkin_packfile { char *pack_tmp_name; struct hashfile *f; off_t offset; @@ -32,27 +28,36 @@ static struct bulk_checkin_packfile { struct pack_idx_entry **written; uint32_t alloc_written; uint32_t nr_written; -} bulk_checkin_packfile; +}; + +struct odb_transaction { + struct object_database *odb; + + int nesting; + struct tmp_objdir *objdir; + struct bulk_checkin_packfile packfile; +}; -static void finish_tmp_packfile(struct strbuf *basename, - const char *pack_tmp_name, - struct pack_idx_entry **written_list, - uint32_t nr_written, - struct pack_idx_option *pack_idx_opts, +static void finish_tmp_packfile(struct odb_transaction *transaction, + struct strbuf *basename, unsigned char hash[]) { + struct bulk_checkin_packfile *state = &transaction->packfile; + struct repository *repo = transaction->odb->repo; char *idx_tmp_name = NULL; - stage_tmp_packfiles(the_repository, basename, pack_tmp_name, - written_list, nr_written, NULL, pack_idx_opts, hash, - &idx_tmp_name); - rename_tmp_packfile_idx(the_repository, basename, &idx_tmp_name); + stage_tmp_packfiles(repo, basename, state->pack_tmp_name, + state->written, state->nr_written, NULL, + &state->pack_idx_opts, hash, &idx_tmp_name); + rename_tmp_packfile_idx(repo, basename, &idx_tmp_name); free(idx_tmp_name); } -static void flush_bulk_checkin_packfile(struct bulk_checkin_packfile *state) +static void flush_bulk_checkin_packfile(struct odb_transaction *transaction) { + struct bulk_checkin_packfile *state = &transaction->packfile; + struct repository *repo = transaction->odb->repo; unsigned char hash[GIT_MAX_RAWSZ]; struct strbuf packname = STRBUF_INIT; @@ -69,17 +74,17 @@ static void flush_bulk_checkin_packfile(struct bulk_checkin_packfile *state) CSUM_HASH_IN_STREAM | CSUM_FSYNC | CSUM_CLOSE); } else { int fd = finalize_hashfile(state->f, hash, FSYNC_COMPONENT_PACK, 0); - fixup_pack_header_footer(the_hash_algo, fd, hash, state->pack_tmp_name, + fixup_pack_header_footer(repo->hash_algo, fd, hash, state->pack_tmp_name, state->nr_written, hash, state->offset); close(fd); } - strbuf_addf(&packname, "%s/pack/pack-%s.", repo_get_object_directory(the_repository), - hash_to_hex(hash)); - finish_tmp_packfile(&packname, state->pack_tmp_name, - state->written, state->nr_written, - &state->pack_idx_opts, hash); + strbuf_addf(&packname, "%s/pack/pack-%s.", + repo_get_object_directory(transaction->odb->repo), + hash_to_hex_algop(hash, repo->hash_algo)); + + finish_tmp_packfile(transaction, &packname, hash); for (uint32_t i = 0; i < state->nr_written; i++) free(state->written[i]); @@ -90,18 +95,18 @@ clear_exit: strbuf_release(&packname); /* Make objects we just wrote available to ourselves */ - reprepare_packed_git(the_repository); + reprepare_packed_git(repo); } /* * Cleanup after batch-mode fsync_object_files. */ -static void flush_batch_fsync(void) +static void flush_batch_fsync(struct odb_transaction *transaction) { struct strbuf temp_path = STRBUF_INIT; struct tempfile *temp; - if (!bulk_fsync_objdir) + if (!transaction->objdir) return; /* @@ -113,7 +118,8 @@ static void flush_batch_fsync(void) * to ensure that the data in each new object file is durable before * the final name is visible. */ - strbuf_addf(&temp_path, "%s/bulk_fsync_XXXXXX", repo_get_object_directory(the_repository)); + strbuf_addf(&temp_path, "%s/bulk_fsync_XXXXXX", + repo_get_object_directory(transaction->odb->repo)); temp = xmks_tempfile(temp_path.buf); fsync_or_die(get_tempfile_fd(temp), get_tempfile_path(temp)); delete_tempfile(&temp); @@ -123,20 +129,21 @@ static void flush_batch_fsync(void) * Make the object files visible in the primary ODB after their data is * fully durable. */ - tmp_objdir_migrate(bulk_fsync_objdir); - bulk_fsync_objdir = NULL; + tmp_objdir_migrate(transaction->objdir); + transaction->objdir = NULL; } -static int already_written(struct bulk_checkin_packfile *state, struct object_id *oid) +static int already_written(struct odb_transaction *transaction, + struct object_id *oid) { /* The object may already exist in the repository */ - if (odb_has_object(the_repository->objects, oid, + if (odb_has_object(transaction->odb, oid, HAS_OBJECT_RECHECK_PACKED | HAS_OBJECT_FETCH_PROMISOR)) return 1; /* Might want to keep the list sorted */ - for (uint32_t i = 0; i < state->nr_written; i++) - if (oideq(&state->written[i]->oid, oid)) + for (uint32_t i = 0; i < transaction->packfile.nr_written; i++) + if (oideq(&transaction->packfile.written[i]->oid, oid)) return 1; /* This is a new object we need to keep */ @@ -235,13 +242,15 @@ static int stream_blob_to_pack(struct bulk_checkin_packfile *state, } /* Lazily create backing packfile for the state */ -static void prepare_to_stream(struct bulk_checkin_packfile *state, +static void prepare_to_stream(struct odb_transaction *transaction, unsigned flags) { + struct bulk_checkin_packfile *state = &transaction->packfile; if (!(flags & INDEX_WRITE_OBJECT) || state->f) return; - state->f = create_tmp_packfile(the_repository, &state->pack_tmp_name); + state->f = create_tmp_packfile(transaction->odb->repo, + &state->pack_tmp_name); reset_pack_idx_option(&state->pack_idx_opts); /* Pretend we are going to write only one object */ @@ -250,11 +259,11 @@ static void prepare_to_stream(struct bulk_checkin_packfile *state, die_errno("unable to write pack header"); } -static int deflate_blob_to_pack(struct bulk_checkin_packfile *state, - struct object_id *result_oid, - int fd, size_t size, - const char *path, unsigned flags) +int index_blob_bulk_checkin(struct odb_transaction *transaction, + struct object_id *result_oid, int fd, size_t size, + const char *path, unsigned flags) { + struct bulk_checkin_packfile *state = &transaction->packfile; off_t seekback, already_hashed_to; struct git_hash_ctx ctx; unsigned char obuf[16384]; @@ -268,21 +277,21 @@ static int deflate_blob_to_pack(struct bulk_checkin_packfile *state, header_len = format_object_header((char *)obuf, sizeof(obuf), OBJ_BLOB, size); - the_hash_algo->init_fn(&ctx); + transaction->odb->repo->hash_algo->init_fn(&ctx); git_hash_update(&ctx, obuf, header_len); /* Note: idx is non-NULL when we are writing */ if ((flags & INDEX_WRITE_OBJECT) != 0) { CALLOC_ARRAY(idx, 1); - prepare_to_stream(state, flags); + prepare_to_stream(transaction, flags); hashfile_checkpoint_init(state->f, &checkpoint); } already_hashed_to = 0; while (1) { - prepare_to_stream(state, flags); + prepare_to_stream(transaction, flags); if (idx) { hashfile_checkpoint(state->f, &checkpoint); idx->offset = state->offset; @@ -300,7 +309,7 @@ static int deflate_blob_to_pack(struct bulk_checkin_packfile *state, BUG("should not happen"); hashfile_truncate(state->f, &checkpoint); state->offset = checkpoint.offset; - flush_bulk_checkin_packfile(state); + flush_bulk_checkin_packfile(transaction); if (lseek(fd, seekback, SEEK_SET) == (off_t) -1) return error("cannot seek back"); } @@ -309,7 +318,7 @@ static int deflate_blob_to_pack(struct bulk_checkin_packfile *state, return 0; idx->crc32 = crc32_end(state->f); - if (already_written(state, result_oid)) { + if (already_written(transaction, result_oid)) { hashfile_truncate(state->f, &checkpoint); state->offset = checkpoint.offset; free(idx); @@ -323,7 +332,7 @@ static int deflate_blob_to_pack(struct bulk_checkin_packfile *state, return 0; } -void prepare_loose_object_bulk_checkin(void) +void prepare_loose_object_bulk_checkin(struct odb_transaction *transaction) { /* * We lazily create the temporary object directory @@ -331,15 +340,16 @@ void prepare_loose_object_bulk_checkin(void) * callers may not know whether any objects will be * added at the time they call begin_odb_transaction. */ - if (!odb_transaction_nesting || bulk_fsync_objdir) + if (!transaction || transaction->objdir) return; - bulk_fsync_objdir = tmp_objdir_create(the_repository, "bulk-fsync"); - if (bulk_fsync_objdir) - tmp_objdir_replace_primary_odb(bulk_fsync_objdir, 0); + transaction->objdir = tmp_objdir_create(transaction->odb->repo, "bulk-fsync"); + if (transaction->objdir) + tmp_objdir_replace_primary_odb(transaction->objdir, 0); } -void fsync_loose_object_bulk_checkin(int fd, const char *filename) +void fsync_loose_object_bulk_checkin(struct odb_transaction *transaction, + int fd, const char *filename) { /* * If we have an active ODB transaction, we issue a call that @@ -348,7 +358,7 @@ void fsync_loose_object_bulk_checkin(int fd, const char *filename) * before renaming the objects to their final names as part of * flush_batch_fsync. */ - if (!bulk_fsync_objdir || + if (!transaction || !transaction->objdir || git_fsync(fd, FSYNC_WRITEOUT_ONLY) < 0) { if (errno == ENOSYS) warning(_("core.fsyncMethod = batch is unsupported on this platform")); @@ -356,36 +366,38 @@ void fsync_loose_object_bulk_checkin(int fd, const char *filename) } } -int index_blob_bulk_checkin(struct object_id *oid, - int fd, size_t size, - const char *path, unsigned flags) +struct odb_transaction *begin_odb_transaction(struct object_database *odb) { - int status = deflate_blob_to_pack(&bulk_checkin_packfile, oid, fd, size, - path, flags); - if (!odb_transaction_nesting) - flush_bulk_checkin_packfile(&bulk_checkin_packfile); - return status; -} + if (!odb->transaction) { + CALLOC_ARRAY(odb->transaction, 1); + odb->transaction->odb = odb; + } -void begin_odb_transaction(void) -{ - odb_transaction_nesting += 1; + odb->transaction->nesting += 1; + + return odb->transaction; } -void flush_odb_transaction(void) +void flush_odb_transaction(struct odb_transaction *transaction) { - flush_batch_fsync(); - flush_bulk_checkin_packfile(&bulk_checkin_packfile); + if (!transaction) + return; + + flush_batch_fsync(transaction); + flush_bulk_checkin_packfile(transaction); } -void end_odb_transaction(void) +void end_odb_transaction(struct odb_transaction *transaction) { - odb_transaction_nesting -= 1; - if (odb_transaction_nesting < 0) + if (!transaction || transaction->nesting == 0) BUG("Unbalanced ODB transaction nesting"); - if (odb_transaction_nesting) + transaction->nesting -= 1; + + if (transaction->nesting) return; - flush_odb_transaction(); + flush_odb_transaction(transaction); + transaction->odb->transaction = NULL; + free(transaction); } diff --git a/bulk-checkin.h b/bulk-checkin.h index 7246ea58dc..ac8887f476 100644 --- a/bulk-checkin.h +++ b/bulk-checkin.h @@ -5,13 +5,20 @@ #define BULK_CHECKIN_H #include "object.h" +#include "odb.h" -void prepare_loose_object_bulk_checkin(void); -void fsync_loose_object_bulk_checkin(int fd, const char *filename); +struct odb_transaction; + +void prepare_loose_object_bulk_checkin(struct odb_transaction *transaction); +void fsync_loose_object_bulk_checkin(struct odb_transaction *transaction, + int fd, const char *filename); /* - * This creates one packfile per large blob unless bulk-checkin - * machinery is "plugged". + * This writes the specified object to a packfile. Objects written here + * during the same transaction are written to the same packfile. The + * packfile is not flushed until the transaction is flushed. The caller + * is expected to ensure a valid transaction is setup for objects to be + * recorded to. * * This also bypasses the usual "convert-to-git" dance, and that is on * purpose. We could write a streaming version of the converting @@ -24,8 +31,8 @@ void fsync_loose_object_bulk_checkin(int fd, const char *filename); * binary blobs, they generally do not want to get any conversion, and * callers should avoid this code path when filters are requested. */ -int index_blob_bulk_checkin(struct object_id *oid, - int fd, size_t size, +int index_blob_bulk_checkin(struct odb_transaction *transaction, + struct object_id *oid, int fd, size_t size, const char *path, unsigned flags); /* @@ -35,20 +42,20 @@ int index_blob_bulk_checkin(struct object_id *oid, * and objects are only visible after the outermost transaction * is complete or the transaction is flushed. */ -void begin_odb_transaction(void); +struct odb_transaction *begin_odb_transaction(struct object_database *odb); /* * Make any objects that are currently part of a pending object * database transaction visible. It is valid to call this function * even if no transaction is active. */ -void flush_odb_transaction(void); +void flush_odb_transaction(struct odb_transaction *transaction); /* * Tell the object database to make any objects from the * current transaction visible if this is the final nested * transaction. */ -void end_odb_transaction(void); +void end_odb_transaction(struct odb_transaction *transaction); #endif diff --git a/cache-tree.c b/cache-tree.c index 66ef2becbe..d225554eed 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -474,6 +474,7 @@ static int update_one(struct cache_tree *it, int cache_tree_update(struct index_state *istate, int flags) { + struct odb_transaction *transaction; int skip, i; i = verify_cache(istate, flags); @@ -489,10 +490,10 @@ int cache_tree_update(struct index_state *istate, int flags) trace_performance_enter(); trace2_region_enter("cache_tree", "update", the_repository); - begin_odb_transaction(); + transaction = begin_odb_transaction(the_repository->objects); i = update_one(istate->cache_tree, istate->cache, istate->cache_nr, "", 0, &skip, flags); - end_odb_transaction(); + end_odb_transaction(transaction); trace2_region_leave("cache_tree", "update", the_repository); trace_performance_leave("cache_tree_update"); if (i < 0) diff --git a/combine-diff.c b/combine-diff.c index 4ea2dc93c4..3878faabe7 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -1315,7 +1315,7 @@ static struct diff_filepair *combined_pair(struct combine_diff_path *p, struct diff_filepair *pair; struct diff_filespec *pool; - pair = xmalloc(sizeof(*pair)); + CALLOC_ARRAY(pair, 1); CALLOC_ARRAY(pool, st_add(num_parent, 1)); pair->one = pool + 1; pair->two = pool; diff --git a/command-list.txt b/command-list.txt index b7ade3ab9f..accd3d0c4b 100644 --- a/command-list.txt +++ b/command-list.txt @@ -124,6 +124,7 @@ git-index-pack plumbingmanipulators git-init mainporcelain init git-instaweb ancillaryinterrogators complete git-interpret-trailers purehelpers +git-last-modified plumbinginterrogators git-log mainporcelain info git-ls-files plumbinginterrogators git-ls-remote plumbinginterrogators @@ -164,6 +165,7 @@ git-remote ancillarymanipulators complete git-repack ancillarymanipulators complete git-replace ancillarymanipulators complete git-replay plumbingmanipulators +git-repo plumbinginterrogators git-request-pull foreignscminterface complete git-rerere ancillaryinterrogators git-reset mainporcelain history diff --git a/commit-graph.c b/commit-graph.c index e0d92b816f..2f20f66cfd 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #define DISABLE_SIGN_COMPARE_WARNINGS #include "git-compat-util.h" @@ -29,7 +28,7 @@ #include "tree.h" #include "chunk-format.h" -void git_test_write_commit_graph_or_die(void) +void git_test_write_commit_graph_or_die(struct odb_source *source) { int flags = 0; if (!git_env_bool(GIT_TEST_COMMIT_GRAPH, 0)) @@ -38,8 +37,7 @@ void git_test_write_commit_graph_or_die(void) if (git_env_bool(GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS, 0)) flags = COMMIT_GRAPH_WRITE_BLOOM_FILTERS; - if (write_commit_graph_reachable(the_repository->objects->sources, - flags, NULL)) + if (write_commit_graph_reachable(source, flags, NULL)) die("failed to write commit-graph under GIT_TEST_COMMIT_GRAPH"); } @@ -54,8 +52,6 @@ void git_test_write_commit_graph_or_die(void) #define GRAPH_CHUNKID_BLOOMDATA 0x42444154 /* "BDAT" */ #define GRAPH_CHUNKID_BASE 0x42415345 /* "BASE" */ -#define GRAPH_DATA_WIDTH (the_hash_algo->rawsz + 16) - #define GRAPH_VERSION_1 0x1 #define GRAPH_VERSION GRAPH_VERSION_1 @@ -67,8 +63,6 @@ void git_test_write_commit_graph_or_die(void) #define GRAPH_HEADER_SIZE 8 #define GRAPH_FANOUT_SIZE (4 * 256) -#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) @@ -81,6 +75,16 @@ define_commit_slab(topo_level_slab, uint32_t); define_commit_slab(commit_pos, int); static struct commit_pos commit_pos = COMMIT_SLAB_INIT(1, commit_pos); +static size_t graph_data_width(const struct git_hash_algo *algop) +{ + return algop->rawsz + 16; +} + +static size_t graph_min_size(const struct git_hash_algo *algop) +{ + return GRAPH_HEADER_SIZE + 4 * CHUNK_TOC_ENTRY_SIZE + GRAPH_FANOUT_SIZE + algop->rawsz; +} + static void set_commit_pos(struct repository *r, const struct object_id *oid) { static int32_t max_pos; @@ -249,9 +253,8 @@ int open_commit_graph(const char *graph_file, int *fd, struct stat *st) return 1; } -struct commit_graph *load_commit_graph_one_fd_st(struct repository *r, - int fd, struct stat *st, - struct odb_source *source) +struct commit_graph *load_commit_graph_one_fd_st(struct odb_source *source, + int fd, struct stat *st) { void *graph_map; size_t graph_size; @@ -259,16 +262,15 @@ struct commit_graph *load_commit_graph_one_fd_st(struct repository *r, graph_size = xsize_t(st->st_size); - if (graph_size < GRAPH_MIN_SIZE) { + if (graph_size < graph_min_size(source->odb->repo->hash_algo)) { close(fd); error(_("commit-graph file is too small")); return NULL; } graph_map = xmmap(NULL, graph_size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); - prepare_repo_settings(r); - ret = parse_commit_graph(&r->settings, graph_map, graph_size); + ret = parse_commit_graph(source->odb->repo, graph_map, graph_size); if (ret) ret->odb_source = source; else @@ -306,7 +308,7 @@ static int graph_read_oid_lookup(const unsigned char *chunk_start, { struct commit_graph *g = data; g->chunk_oid_lookup = chunk_start; - if (chunk_size / g->hash_len != g->num_commits) + if (chunk_size / g->hash_algo->rawsz != g->num_commits) return error(_("commit-graph OID lookup chunk is the wrong size")); return 0; } @@ -315,7 +317,7 @@ static int graph_read_commit_data(const unsigned char *chunk_start, size_t chunk_size, void *data) { struct commit_graph *g = data; - if (chunk_size / GRAPH_DATA_WIDTH != g->num_commits) + if (chunk_size / graph_data_width(g->hash_algo) != g->num_commits) return error(_("commit-graph commit data chunk is wrong size")); g->chunk_commit_data = chunk_start; return 0; @@ -368,7 +370,7 @@ static int graph_read_bloom_data(const unsigned char *chunk_start, return 0; } -struct commit_graph *parse_commit_graph(struct repo_settings *s, +struct commit_graph *parse_commit_graph(struct repository *r, void *graph_map, size_t graph_size) { const unsigned char *data; @@ -380,7 +382,7 @@ struct commit_graph *parse_commit_graph(struct repo_settings *s, if (!graph_map) return NULL; - if (graph_size < GRAPH_MIN_SIZE) + if (graph_size < graph_min_size(r->hash_algo)) return NULL; data = (const unsigned char *)graph_map; @@ -400,22 +402,22 @@ struct commit_graph *parse_commit_graph(struct repo_settings *s, } hash_version = *(unsigned char*)(data + 5); - if (hash_version != oid_version(the_hash_algo)) { + if (hash_version != oid_version(r->hash_algo)) { error(_("commit-graph hash version %X does not match version %X"), - hash_version, oid_version(the_hash_algo)); + hash_version, oid_version(r->hash_algo)); return NULL; } graph = alloc_commit_graph(); - graph->hash_len = the_hash_algo->rawsz; + graph->hash_algo = r->hash_algo; graph->num_chunks = *(unsigned char*)(data + 6); graph->data = graph_map; graph->data_len = graph_size; if (graph_size < GRAPH_HEADER_SIZE + (graph->num_chunks + 1) * CHUNK_TOC_ENTRY_SIZE + - GRAPH_FANOUT_SIZE + the_hash_algo->rawsz) { + GRAPH_FANOUT_SIZE + r->hash_algo->rawsz) { error(_("commit-graph file is too small to hold %u chunks"), graph->num_chunks); free(graph); @@ -446,7 +448,9 @@ struct commit_graph *parse_commit_graph(struct repo_settings *s, pair_chunk(cf, GRAPH_CHUNKID_BASE, &graph->chunk_base_graphs, &graph->chunk_base_graphs_size); - if (s->commit_graph_generation_version >= 2) { + prepare_repo_settings(r); + + if (r->settings.commit_graph_generation_version >= 2) { read_chunk(cf, GRAPH_CHUNKID_GENERATION_DATA, graph_read_generation_data, graph); pair_chunk(cf, GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW, @@ -457,7 +461,7 @@ struct commit_graph *parse_commit_graph(struct repo_settings *s, graph->read_generation_data = 1; } - if (s->commit_graph_changed_paths_version) { + if (r->settings.commit_graph_changed_paths_version) { read_chunk(cf, GRAPH_CHUNKID_BLOOMINDEXES, graph_read_bloom_index, graph); read_chunk(cf, GRAPH_CHUNKID_BLOOMDATA, @@ -473,8 +477,8 @@ struct commit_graph *parse_commit_graph(struct repo_settings *s, FREE_AND_NULL(graph->bloom_filter_settings); } - oidread(&graph->oid, graph->data + graph->data_len - graph->hash_len, - the_repository->hash_algo); + oidread(&graph->oid, graph->data + graph->data_len - graph->hash_algo->rawsz, + r->hash_algo); free_chunkfile(cf); return graph; @@ -486,11 +490,9 @@ free_and_return: return NULL; } -static struct commit_graph *load_commit_graph_one(struct repository *r, - const char *graph_file, - struct odb_source *source) +static struct commit_graph *load_commit_graph_one(struct odb_source *source, + const char *graph_file) { - struct stat st; int fd; struct commit_graph *g; @@ -499,19 +501,17 @@ static struct commit_graph *load_commit_graph_one(struct repository *r, if (!open_ok) return NULL; - g = load_commit_graph_one_fd_st(r, fd, &st, source); - + g = load_commit_graph_one_fd_st(source, fd, &st); if (g) g->filename = xstrdup(graph_file); return g; } -static struct commit_graph *load_commit_graph_v1(struct repository *r, - struct odb_source *source) +static struct commit_graph *load_commit_graph_v1(struct odb_source *source) { char *graph_name = get_commit_graph_filename(source); - struct commit_graph *g = load_commit_graph_one(r, graph_name, source); + struct commit_graph *g = load_commit_graph_one(source, graph_name); free(graph_name); return g; @@ -579,7 +579,7 @@ static int add_graph_to_chain(struct commit_graph *g, return 0; } - if (g->chunk_base_graphs_size / g->hash_len < n) { + if (g->chunk_base_graphs_size / g->hash_algo->rawsz < n) { warning(_("commit-graph base graphs chunk is too small")); return 0; } @@ -589,8 +589,8 @@ static int add_graph_to_chain(struct commit_graph *g, if (!cur_g || !oideq(&oids[n], &cur_g->oid) || - !hasheq(oids[n].hash, g->chunk_base_graphs + st_mult(g->hash_len, n), - the_repository->hash_algo)) { + !hasheq(oids[n].hash, g->chunk_base_graphs + st_mult(g->hash_algo->rawsz, n), + g->hash_algo)) { warning(_("commit-graph chain does not match")); return 0; } @@ -614,7 +614,8 @@ static int add_graph_to_chain(struct commit_graph *g, } int open_commit_graph_chain(const char *chain_file, - int *fd, struct stat *st) + int *fd, struct stat *st, + const struct git_hash_algo *hash_algo) { *fd = git_open(chain_file); if (*fd < 0) @@ -623,7 +624,7 @@ int open_commit_graph_chain(const char *chain_file, close(*fd); return 0; } - if (st->st_size < the_hash_algo->hexsz) { + if (st->st_size < hash_algo->hexsz) { close(*fd); if (!st->st_size) { /* treat empty files the same as missing */ @@ -637,7 +638,7 @@ int open_commit_graph_chain(const char *chain_file, return 1; } -struct commit_graph *load_commit_graph_chain_fd_st(struct repository *r, +struct commit_graph *load_commit_graph_chain_fd_st(struct object_database *odb, int fd, struct stat *st, int *incomplete_chain) { @@ -647,10 +648,10 @@ struct commit_graph *load_commit_graph_chain_fd_st(struct repository *r, int i = 0, valid = 1, count; FILE *fp = xfdopen(fd, "r"); - count = st->st_size / (the_hash_algo->hexsz + 1); + count = st->st_size / (odb->repo->hash_algo->hexsz + 1); CALLOC_ARRAY(oids, count); - odb_prepare_alternates(r->objects); + odb_prepare_alternates(odb); for (i = 0; i < count; i++) { struct odb_source *source; @@ -658,7 +659,7 @@ struct commit_graph *load_commit_graph_chain_fd_st(struct repository *r, if (strbuf_getline_lf(&line, fp) == EOF) break; - if (get_oid_hex(line.buf, &oids[i])) { + if (get_oid_hex_algop(line.buf, &oids[i], odb->repo->hash_algo)) { warning(_("invalid commit-graph chain: line '%s' not a hash"), line.buf); valid = 0; @@ -666,9 +667,9 @@ struct commit_graph *load_commit_graph_chain_fd_st(struct repository *r, } valid = 0; - for (source = r->objects->sources; source; source = source->next) { + for (source = odb->sources; source; source = source->next) { char *graph_name = get_split_graph_filename(source, line.buf); - struct commit_graph *g = load_commit_graph_one(r, graph_name, source); + struct commit_graph *g = load_commit_graph_one(source, graph_name); free(graph_name); @@ -701,50 +702,38 @@ struct commit_graph *load_commit_graph_chain_fd_st(struct repository *r, return graph_chain; } -static struct commit_graph *load_commit_graph_chain(struct repository *r, - struct odb_source *source) +static struct commit_graph *load_commit_graph_chain(struct odb_source *source) { char *chain_file = get_commit_graph_chain_filename(source); struct stat st; int fd; struct commit_graph *g = NULL; - if (open_commit_graph_chain(chain_file, &fd, &st)) { + if (open_commit_graph_chain(chain_file, &fd, &st, source->odb->repo->hash_algo)) { int incomplete; /* ownership of fd is taken over by load function */ - g = load_commit_graph_chain_fd_st(r, fd, &st, &incomplete); + g = load_commit_graph_chain_fd_st(source->odb, fd, &st, &incomplete); } free(chain_file); return g; } -struct commit_graph *read_commit_graph_one(struct repository *r, - struct odb_source *source) +struct commit_graph *read_commit_graph_one(struct odb_source *source) { - struct commit_graph *g = load_commit_graph_v1(r, source); + struct commit_graph *g = load_commit_graph_v1(source); if (!g) - g = load_commit_graph_chain(r, source); + g = load_commit_graph_chain(source); return g; } -static void prepare_commit_graph_one(struct repository *r, - struct odb_source *source) -{ - - if (r->objects->commit_graph) - return; - - r->objects->commit_graph = read_commit_graph_one(r, source); -} - /* * Return 1 if commit_graph is non-NULL, and 0 otherwise. * * On the first invocation, this function attempts to load the commit - * graph if the_repository is configured to have one. + * graph if the repository is configured to have one. */ static int prepare_commit_graph(struct repository *r) { @@ -780,10 +769,12 @@ static int prepare_commit_graph(struct repository *r) return 0; odb_prepare_alternates(r->objects); - for (source = r->objects->sources; - !r->objects->commit_graph && source; - source = source->next) - prepare_commit_graph_one(r, source); + for (source = r->objects->sources; source; source = source->next) { + r->objects->commit_graph = read_commit_graph_one(source); + if (r->objects->commit_graph) + break; + } + return !!r->objects->commit_graph; } @@ -800,7 +791,7 @@ int generation_numbers_enabled(struct repository *r) return 0; first_generation = get_be32(g->chunk_commit_data + - g->hash_len + 8) >> 2; + g->hash_algo->rawsz + 8) >> 2; return !!first_generation; } @@ -821,7 +812,12 @@ int corrected_commit_dates_enabled(struct repository *r) struct bloom_filter_settings *get_bloom_filter_settings(struct repository *r) { - struct commit_graph *g = r->objects->commit_graph; + struct commit_graph *g; + + if (!prepare_commit_graph(r)) + return NULL; + + g = r->objects->commit_graph; while (g) { if (g->bloom_filter_settings) return g->bloom_filter_settings; @@ -844,7 +840,7 @@ void close_commit_graph(struct object_database *o) static int bsearch_graph(struct commit_graph *g, const struct object_id *oid, uint32_t *pos) { return bsearch_hash(oid->hash, g->chunk_oid_fanout, - g->chunk_oid_lookup, g->hash_len, pos); + g->chunk_oid_lookup, g->hash_algo->rawsz, pos); } static void load_oid_from_graph(struct commit_graph *g, @@ -864,12 +860,11 @@ static void load_oid_from_graph(struct commit_graph *g, lex_index = pos - g->num_commits_in_base; - oidread(oid, g->chunk_oid_lookup + st_mult(g->hash_len, lex_index), - the_repository->hash_algo); + oidread(oid, g->chunk_oid_lookup + st_mult(g->hash_algo->rawsz, lex_index), + g->hash_algo); } -static struct commit_list **insert_parent_or_die(struct repository *r, - struct commit_graph *g, +static struct commit_list **insert_parent_or_die(struct commit_graph *g, uint32_t pos, struct commit_list **pptr) { @@ -880,7 +875,7 @@ static struct commit_list **insert_parent_or_die(struct repository *r, die("invalid parent position %"PRIu32, pos); load_oid_from_graph(g, pos, &oid); - c = lookup_commit(r, &oid); + c = lookup_commit(g->odb_source->odb->repo, &oid); if (!c) die(_("could not find commit %s"), oid_to_hex(&oid)); commit_graph_data_at(c)->graph_pos = pos; @@ -901,13 +896,13 @@ static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, die(_("invalid commit position. commit-graph is likely corrupt")); lex_index = pos - g->num_commits_in_base; - commit_data = g->chunk_commit_data + st_mult(GRAPH_DATA_WIDTH, lex_index); + commit_data = g->chunk_commit_data + st_mult(graph_data_width(g->hash_algo), lex_index); graph_data = commit_graph_data_at(item); graph_data->graph_pos = pos; - date_high = get_be32(commit_data + g->hash_len + 8) & 0x3; - date_low = get_be32(commit_data + g->hash_len + 12); + date_high = get_be32(commit_data + g->hash_algo->rawsz + 8) & 0x3; + date_low = get_be32(commit_data + g->hash_algo->rawsz + 12); item->date = (timestamp_t)((date_high << 32) | date_low); if (g->read_generation_data) { @@ -925,10 +920,10 @@ static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, } else graph_data->generation = item->date + offset; } else - graph_data->generation = get_be32(commit_data + g->hash_len + 8) >> 2; + graph_data->generation = get_be32(commit_data + g->hash_algo->rawsz + 8) >> 2; if (g->topo_levels) - *topo_level_slab_at(g->topo_levels, item) = get_be32(commit_data + g->hash_len + 8) >> 2; + *topo_level_slab_at(g->topo_levels, item) = get_be32(commit_data + g->hash_algo->rawsz + 8) >> 2; } static inline void set_commit_tree(struct commit *c, struct tree *t) @@ -936,8 +931,7 @@ static inline void set_commit_tree(struct commit *c, struct tree *t) c->maybe_tree = t; } -static int fill_commit_in_graph(struct repository *r, - struct commit *item, +static int fill_commit_in_graph(struct commit *item, struct commit_graph *g, uint32_t pos) { uint32_t edge_value; @@ -952,7 +946,7 @@ static int fill_commit_in_graph(struct repository *r, fill_commit_graph_info(item, g, pos); lex_index = pos - g->num_commits_in_base; - commit_data = g->chunk_commit_data + st_mult(g->hash_len + 16, lex_index); + commit_data = g->chunk_commit_data + st_mult(g->hash_algo->rawsz + 16, lex_index); item->object.parsed = 1; @@ -960,16 +954,16 @@ static int fill_commit_in_graph(struct repository *r, pptr = &item->parents; - edge_value = get_be32(commit_data + g->hash_len); + edge_value = get_be32(commit_data + g->hash_algo->rawsz); if (edge_value == GRAPH_PARENT_NONE) return 1; - pptr = insert_parent_or_die(r, g, edge_value, pptr); + pptr = insert_parent_or_die(g, edge_value, pptr); - edge_value = get_be32(commit_data + g->hash_len + 4); + edge_value = get_be32(commit_data + g->hash_algo->rawsz + 4); if (edge_value == GRAPH_PARENT_NONE) return 1; if (!(edge_value & GRAPH_EXTRA_EDGES_NEEDED)) { - pptr = insert_parent_or_die(r, g, edge_value, pptr); + pptr = insert_parent_or_die(g, edge_value, pptr); return 1; } @@ -984,7 +978,7 @@ static int fill_commit_in_graph(struct repository *r, } edge_value = get_be32(g->chunk_extra_edges + sizeof(uint32_t) * parent_data_pos); - pptr = insert_parent_or_die(r, g, + pptr = insert_parent_or_die(g, edge_value & GRAPH_EDGE_LAST_MASK, pptr); parent_data_pos++; @@ -1050,14 +1044,13 @@ struct commit *lookup_commit_in_graph(struct repository *repo, const struct obje if (commit->object.parsed) return commit; - if (!fill_commit_in_graph(repo, commit, repo->objects->commit_graph, pos)) + if (!fill_commit_in_graph(commit, repo->objects->commit_graph, pos)) return NULL; return commit; } -static int parse_commit_in_graph_one(struct repository *r, - struct commit_graph *g, +static int parse_commit_in_graph_one(struct commit_graph *g, struct commit *item) { uint32_t pos; @@ -1066,7 +1059,7 @@ static int parse_commit_in_graph_one(struct repository *r, return 1; if (find_commit_pos_in_graph(item, g, &pos)) - return fill_commit_in_graph(r, item, g, pos); + return fill_commit_in_graph(item, g, pos); return 0; } @@ -1083,7 +1076,7 @@ int parse_commit_in_graph(struct repository *r, struct commit *item) if (!prepare_commit_graph(r)) return 0; - return parse_commit_in_graph_one(r, r->objects->commit_graph, item); + return parse_commit_in_graph_one(r->objects->commit_graph, item); } void load_commit_graph_info(struct repository *r, struct commit *item) @@ -1093,8 +1086,7 @@ void load_commit_graph_info(struct repository *r, struct commit *item) fill_commit_graph_info(item, r->objects->commit_graph, pos); } -static struct tree *load_tree_for_commit(struct repository *r, - struct commit_graph *g, +static struct tree *load_tree_for_commit(struct commit_graph *g, struct commit *c) { struct object_id oid; @@ -1105,16 +1097,16 @@ static struct tree *load_tree_for_commit(struct repository *r, g = g->base_graph; commit_data = g->chunk_commit_data + - st_mult(GRAPH_DATA_WIDTH, graph_pos - g->num_commits_in_base); + st_mult(graph_data_width(g->hash_algo), + graph_pos - g->num_commits_in_base); - oidread(&oid, commit_data, the_repository->hash_algo); - set_commit_tree(c, lookup_tree(r, &oid)); + oidread(&oid, commit_data, g->hash_algo); + set_commit_tree(c, lookup_tree(g->odb_source->odb->repo, &oid)); return c->maybe_tree; } -static struct tree *get_commit_tree_in_graph_one(struct repository *r, - struct commit_graph *g, +static struct tree *get_commit_tree_in_graph_one(struct commit_graph *g, const struct commit *c) { if (c->maybe_tree) @@ -1122,12 +1114,12 @@ static struct tree *get_commit_tree_in_graph_one(struct repository *r, if (commit_graph_position(c) == COMMIT_NOT_FROM_GRAPH) BUG("get_commit_tree_in_graph_one called from non-commit-graph commit"); - return load_tree_for_commit(r, g, (struct commit *)c); + return load_tree_for_commit(g, (struct commit *)c); } struct tree *get_commit_tree_in_graph(struct repository *r, const struct commit *c) { - return get_commit_tree_in_graph_one(r, r->objects->commit_graph, c); + return get_commit_tree_in_graph_one(r->objects->commit_graph, c); } struct packed_commit_list { @@ -1213,7 +1205,7 @@ static int write_graph_chunk_oids(struct hashfile *f, int count; for (count = 0; count < ctx->commits.nr; count++, list++) { display_progress(ctx->progress, ++ctx->progress_cnt); - hashwrite(f, (*list)->object.oid.hash, the_hash_algo->rawsz); + hashwrite(f, (*list)->object.oid.hash, f->algop->rawsz); } return 0; @@ -1244,7 +1236,7 @@ static int write_graph_chunk_data(struct hashfile *f, die(_("unable to parse commit %s"), oid_to_hex(&(*list)->object.oid)); tree = get_commit_tree_oid(*list); - hashwrite(f, tree->hash, the_hash_algo->rawsz); + hashwrite(f, tree->hash, ctx->r->hash_algo->rawsz); parent = (*list)->parents; @@ -1534,7 +1526,7 @@ static void close_reachable(struct write_commit_graph_context *ctx) if (ctx->report_progress) ctx->progress = start_delayed_progress( - the_repository, + ctx->r, _("Loading known commits in commit graph"), ctx->oids.nr); for (i = 0; i < ctx->oids.nr; i++) { @@ -1552,7 +1544,7 @@ static void close_reachable(struct write_commit_graph_context *ctx) */ if (ctx->report_progress) ctx->progress = start_delayed_progress( - the_repository, + ctx->r, _("Expanding reachable commits in commit graph"), 0); for (i = 0; i < ctx->oids.nr; i++) { @@ -1573,7 +1565,7 @@ static void close_reachable(struct write_commit_graph_context *ctx) if (ctx->report_progress) ctx->progress = start_delayed_progress( - the_repository, + ctx->r, _("Clearing commit marks in commit graph"), ctx->oids.nr); for (i = 0; i < ctx->oids.nr; i++) { @@ -1691,7 +1683,7 @@ static void compute_topological_levels(struct write_commit_graph_context *ctx) if (ctx->report_progress) info.progress = ctx->progress = start_delayed_progress( - the_repository, + ctx->r, _("Computing commit graph topological levels"), ctx->commits.nr); @@ -1726,7 +1718,7 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx) if (ctx->report_progress) info.progress = ctx->progress = start_delayed_progress( - the_repository, + ctx->r, _("Computing commit graph generation numbers"), ctx->commits.nr); @@ -1803,7 +1795,7 @@ static void compute_bloom_filters(struct write_commit_graph_context *ctx) if (ctx->report_progress) progress = start_delayed_progress( - the_repository, + ctx->r, _("Computing commit changed paths Bloom filters"), ctx->commits.nr); @@ -1849,6 +1841,7 @@ static void compute_bloom_filters(struct write_commit_graph_context *ctx) } struct refs_cb_data { + struct repository *repo; struct oidset *commits; struct progress *progress; }; @@ -1861,9 +1854,9 @@ static int add_ref_to_set(const char *refname UNUSED, struct object_id peeled; struct refs_cb_data *data = (struct refs_cb_data *)cb_data; - if (!peel_iterated_oid(the_repository, oid, &peeled)) + if (!peel_iterated_oid(data->repo, oid, &peeled)) oid = &peeled; - if (odb_read_object_info(the_repository->objects, oid, NULL) == OBJ_COMMIT) + if (odb_read_object_info(data->repo->objects, oid, NULL) == OBJ_COMMIT) oidset_insert(data->commits, oid); display_progress(data->progress, oidset_size(data->commits)); @@ -1880,13 +1873,15 @@ int write_commit_graph_reachable(struct odb_source *source, int result; memset(&data, 0, sizeof(data)); + data.repo = source->odb->repo; data.commits = &commits; + if (flags & COMMIT_GRAPH_WRITE_PROGRESS) data.progress = start_delayed_progress( - the_repository, + source->odb->repo, _("Collecting referenced commits"), 0); - refs_for_each_ref(get_main_ref_store(the_repository), add_ref_to_set, + refs_for_each_ref(get_main_ref_store(source->odb->repo), add_ref_to_set, &data); stop_progress(&data.progress); @@ -1915,7 +1910,7 @@ static int fill_oids_from_packs(struct write_commit_graph_context *ctx, "Finding commits for commit graph in %"PRIuMAX" packs", pack_indexes->nr), (uintmax_t)pack_indexes->nr); - ctx->progress = start_delayed_progress(the_repository, + ctx->progress = start_delayed_progress(ctx->r, progress_title.buf, 0); ctx->progress_done = 0; } @@ -1969,7 +1964,7 @@ static void fill_oids_from_all_packs(struct write_commit_graph_context *ctx) { if (ctx->report_progress) ctx->progress = start_delayed_progress( - the_repository, + ctx->r, _("Finding commits for commit graph among packed objects"), ctx->approx_nr_objects); for_each_packed_object(ctx->r, add_packed_commits, ctx, @@ -1988,7 +1983,7 @@ static void copy_oids_to_commits(struct write_commit_graph_context *ctx) ctx->num_extra_edges = 0; if (ctx->report_progress) ctx->progress = start_delayed_progress( - the_repository, + ctx->r, _("Finding extra edges in commit graph"), ctx->oids.nr); oid_array_sort(&ctx->oids); @@ -2027,7 +2022,7 @@ static int write_graph_chunk_base_1(struct hashfile *f, return 0; num = write_graph_chunk_base_1(f, g->base_graph); - hashwrite(f, g->oid.hash, the_hash_algo->rawsz); + hashwrite(f, g->oid.hash, g->hash_algo->rawsz); return num + 1; } @@ -2051,7 +2046,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx) struct hashfile *f; struct tempfile *graph_layer; /* when ctx->split is non-zero */ struct lock_file lk = LOCK_INIT; - const unsigned hashsz = the_hash_algo->rawsz; + const unsigned hashsz = ctx->r->hash_algo->rawsz; struct strbuf progress_title = STRBUF_INIT; struct chunkfile *cf; unsigned char file_hash[GIT_MAX_RAWSZ]; @@ -2067,7 +2062,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx) ctx->graph_name = get_commit_graph_filename(ctx->odb_source); } - if (safe_create_leading_directories(the_repository, ctx->graph_name)) { + if (safe_create_leading_directories(ctx->r, ctx->graph_name)) { error(_("unable to create leading directories of %s"), ctx->graph_name); return -1; @@ -2086,18 +2081,18 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx) return -1; } - if (adjust_shared_perm(the_repository, get_tempfile_path(graph_layer))) { + if (adjust_shared_perm(ctx->r, get_tempfile_path(graph_layer))) { error(_("unable to adjust shared permissions for '%s'"), get_tempfile_path(graph_layer)); return -1; } - f = hashfd(the_repository->hash_algo, + f = hashfd(ctx->r->hash_algo, get_tempfile_fd(graph_layer), get_tempfile_path(graph_layer)); } else { hold_lock_file_for_update_mode(&lk, ctx->graph_name, LOCK_DIE_ON_ERROR, 0444); - f = hashfd(the_repository->hash_algo, + f = hashfd(ctx->r->hash_algo, get_lock_file_fd(&lk), get_lock_file_path(&lk)); } @@ -2139,7 +2134,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx) hashwrite_be32(f, GRAPH_SIGNATURE); hashwrite_u8(f, GRAPH_VERSION); - hashwrite_u8(f, oid_version(the_hash_algo)); + hashwrite_u8(f, oid_version(ctx->r->hash_algo)); hashwrite_u8(f, get_num_chunks(cf)); hashwrite_u8(f, ctx->num_commit_graphs_after - 1); @@ -2150,7 +2145,7 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx) get_num_chunks(cf)), get_num_chunks(cf)); ctx->progress = start_delayed_progress( - the_repository, + ctx->r, progress_title.buf, st_mult(get_num_chunks(cf), ctx->commits.nr)); } @@ -2208,7 +2203,8 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx) } free(ctx->commit_graph_hash_after[ctx->num_commit_graphs_after - 1]); - ctx->commit_graph_hash_after[ctx->num_commit_graphs_after - 1] = xstrdup(hash_to_hex(file_hash)); + ctx->commit_graph_hash_after[ctx->num_commit_graphs_after - 1] = + xstrdup(hash_to_hex_algop(file_hash, ctx->r->hash_algo)); final_graph_name = get_split_graph_filename(ctx->odb_source, ctx->commit_graph_hash_after[ctx->num_commit_graphs_after - 1]); free(ctx->commit_graph_filenames_after[ctx->num_commit_graphs_after - 1]); @@ -2363,7 +2359,7 @@ static void sort_and_scan_merged_commits(struct write_commit_graph_context *ctx) if (ctx->report_progress) ctx->progress = start_delayed_progress( - the_repository, + ctx->r, _("Scanning merged commits"), ctx->commits.nr); @@ -2408,7 +2404,7 @@ static void merge_commit_graphs(struct write_commit_graph_context *ctx) current_graph_number--; if (ctx->report_progress) - ctx->progress = start_delayed_progress(the_repository, + ctx->progress = start_delayed_progress(ctx->r, _("Merging commit-graph"), 0); merge_commit_graph(ctx, g); @@ -2511,7 +2507,7 @@ int write_commit_graph(struct odb_source *source, enum commit_graph_write_flags flags, const struct commit_graph_opts *opts) { - struct repository *r = the_repository; + struct repository *r = source->odb->repo; struct write_commit_graph_context ctx = { .r = r, .odb_source = source, @@ -2611,14 +2607,14 @@ int write_commit_graph(struct odb_source *source, replace = ctx.opts->split_flags & COMMIT_GRAPH_SPLIT_REPLACE; } - ctx.approx_nr_objects = repo_approximate_object_count(the_repository); + ctx.approx_nr_objects = repo_approximate_object_count(r); if (ctx.append && ctx.r->objects->commit_graph) { struct commit_graph *g = ctx.r->objects->commit_graph; for (i = 0; i < g->num_commits; i++) { struct object_id oid; - oidread(&oid, g->chunk_oid_lookup + st_mult(g->hash_len, i), - the_repository->hash_algo); + oidread(&oid, g->chunk_oid_lookup + st_mult(g->hash_algo->rawsz, i), + r->hash_algo); oid_array_append(&ctx.oids, &oid); } } @@ -2726,15 +2722,15 @@ static void graph_report(const char *fmt, ...) static int commit_graph_checksum_valid(struct commit_graph *g) { - return hashfile_checksum_valid(the_repository->hash_algo, + return hashfile_checksum_valid(g->hash_algo, g->data, g->data_len); } -static int verify_one_commit_graph(struct repository *r, - struct commit_graph *g, +static int verify_one_commit_graph(struct commit_graph *g, struct progress *progress, uint64_t *seen) { + struct repository *r = g->odb_source->odb->repo; uint32_t i, cur_fanout_pos = 0; struct object_id prev_oid, cur_oid; struct commit *seen_gen_zero = NULL; @@ -2748,8 +2744,8 @@ static int verify_one_commit_graph(struct repository *r, for (i = 0; i < g->num_commits; i++) { struct commit *graph_commit; - oidread(&cur_oid, g->chunk_oid_lookup + st_mult(g->hash_len, i), - the_repository->hash_algo); + oidread(&cur_oid, g->chunk_oid_lookup + st_mult(g->hash_algo->rawsz, i), + g->hash_algo); if (i && oidcmp(&prev_oid, &cur_oid) >= 0) graph_report(_("commit-graph has incorrect OID order: %s then %s"), @@ -2768,7 +2764,7 @@ static int verify_one_commit_graph(struct repository *r, } graph_commit = lookup_commit(r, &cur_oid); - if (!parse_commit_in_graph_one(r, g, graph_commit)) + if (!parse_commit_in_graph_one(g, graph_commit)) graph_report(_("failed to parse commit %s from commit-graph"), oid_to_hex(&cur_oid)); } @@ -2793,8 +2789,8 @@ static int verify_one_commit_graph(struct repository *r, timestamp_t generation; display_progress(progress, ++(*seen)); - oidread(&cur_oid, g->chunk_oid_lookup + st_mult(g->hash_len, i), - the_repository->hash_algo); + oidread(&cur_oid, g->chunk_oid_lookup + st_mult(g->hash_algo->rawsz, i), + g->hash_algo); graph_commit = lookup_commit(r, &cur_oid); odb_commit = (struct commit *)create_object(r, &cur_oid, alloc_commit_node(r)); @@ -2804,7 +2800,7 @@ static int verify_one_commit_graph(struct repository *r, continue; } - if (!oideq(&get_commit_tree_in_graph_one(r, g, graph_commit)->object.oid, + if (!oideq(&get_commit_tree_in_graph_one(g, graph_commit)->object.oid, get_commit_tree_oid(odb_commit))) graph_report(_("root tree OID for commit %s in commit-graph is %s != %s"), oid_to_hex(&cur_oid), @@ -2822,7 +2818,7 @@ static int verify_one_commit_graph(struct repository *r, } /* parse parent in case it is in a base graph */ - parse_commit_in_graph_one(r, g, graph_parents->item); + parse_commit_in_graph_one(g, graph_parents->item); if (!oideq(&graph_parents->item->object.oid, &odb_parents->item->object.oid)) graph_report(_("commit-graph parent for %s is %s != %s"), @@ -2882,7 +2878,7 @@ static int verify_one_commit_graph(struct repository *r, return verify_commit_graph_error; } -int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags) +int verify_commit_graph(struct commit_graph *g, int flags) { struct progress *progress = NULL; int local_error = 0; @@ -2898,13 +2894,13 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags) if (!(flags & COMMIT_GRAPH_VERIFY_SHALLOW)) total += g->num_commits_in_base; - progress = start_progress(the_repository, + progress = start_progress(g->odb_source->odb->repo, _("Verifying commits in commit graph"), total); } for (; g; g = g->base_graph) { - local_error |= verify_one_commit_graph(r, g, progress, &seen); + local_error |= verify_one_commit_graph(g, progress, &seen); if (flags & COMMIT_GRAPH_VERIFY_SHALLOW) break; } diff --git a/commit-graph.h b/commit-graph.h index 78ab7b875b..4899b54ef8 100644 --- a/commit-graph.h +++ b/commit-graph.h @@ -21,7 +21,7 @@ * call this method oustide of a builtin, and only if you know what * you are doing! */ -void git_test_write_commit_graph_or_die(void); +void git_test_write_commit_graph_or_die(struct odb_source *source); struct commit; struct bloom_filter_settings; @@ -32,7 +32,8 @@ struct string_list; char *get_commit_graph_filename(struct odb_source *source); char *get_commit_graph_chain_filename(struct odb_source *source); int open_commit_graph(const char *graph_file, int *fd, struct stat *st); -int open_commit_graph_chain(const char *chain_file, int *fd, struct stat *st); +int open_commit_graph_chain(const char *chain_file, int *fd, struct stat *st, + const struct git_hash_algo *hash_algo); /* * Given a commit struct, try to fill the commit struct info, including: @@ -84,7 +85,7 @@ struct commit_graph { const unsigned char *data; size_t data_len; - unsigned char hash_len; + const struct git_hash_algo *hash_algo; unsigned char num_chunks; uint32_t num_commits; struct object_id oid; @@ -113,14 +114,12 @@ struct commit_graph { struct bloom_filter_settings *bloom_filter_settings; }; -struct commit_graph *load_commit_graph_one_fd_st(struct repository *r, - int fd, struct stat *st, - struct odb_source *source); -struct commit_graph *load_commit_graph_chain_fd_st(struct repository *r, +struct commit_graph *load_commit_graph_one_fd_st(struct odb_source *source, + int fd, struct stat *st); +struct commit_graph *load_commit_graph_chain_fd_st(struct object_database *odb, int fd, struct stat *st, int *incomplete_chain); -struct commit_graph *read_commit_graph_one(struct repository *r, - struct odb_source *source); +struct commit_graph *read_commit_graph_one(struct odb_source *source); struct repo_settings; @@ -128,7 +127,7 @@ struct repo_settings; * Callers should initialize the repo_settings with prepare_repo_settings() * prior to calling parse_commit_graph(). */ -struct commit_graph *parse_commit_graph(struct repo_settings *s, +struct commit_graph *parse_commit_graph(struct repository *r, void *graph_map, size_t graph_size); /* @@ -184,7 +183,7 @@ int write_commit_graph(struct odb_source *source, #define COMMIT_GRAPH_VERIFY_SHALLOW (1 << 0) -int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags); +int verify_commit_graph(struct commit_graph *g, int flags); void close_commit_graph(struct object_database *); void free_commit_graph(struct commit_graph *); @@ -1039,7 +1039,8 @@ static void add_one_commit(struct object_id *oid, struct rev_collect *revs) commit->object.flags |= TMP_MARK; } -static int collect_one_reflog_ent(struct object_id *ooid, struct object_id *noid, +static int collect_one_reflog_ent(const char *refname UNUSED, + struct object_id *ooid, struct object_id *noid, const char *ident UNUSED, timestamp_t timestamp UNUSED, int tz UNUSED, const char *message UNUSED, void *cbdata) @@ -8,9 +8,11 @@ #include "git-compat-util.h" #include "abspath.h" +#include "advice.h" #include "date.h" #include "branch.h" #include "config.h" +#include "dir.h" #include "parse.h" #include "convert.h" #include "environment.h" @@ -629,31 +631,28 @@ int git_config_parse_parameter(const char *text, config_fn_t fn, void *data) { const char *value; - struct strbuf **pair; + struct string_list pair = STRING_LIST_INIT_DUP; int ret; struct key_value_info kvi = KVI_INIT; kvi_from_param(&kvi); - pair = strbuf_split_str(text, '=', 2); - if (!pair[0]) + string_list_split(&pair, text, "=", 1); + if (!pair.nr) return error(_("bogus config parameter: %s"), text); - if (pair[0]->len && pair[0]->buf[pair[0]->len - 1] == '=') { - strbuf_setlen(pair[0], pair[0]->len - 1); - value = pair[1] ? pair[1]->buf : ""; - } else { + if (pair.nr == 1) value = NULL; - } + else + value = pair.items[1].string; - strbuf_trim(pair[0]); - if (!pair[0]->len) { - strbuf_list_free(pair); + if (!*pair.items[0].string) { + string_list_clear(&pair, 0); return error(_("bogus config parameter: %s"), text); } - ret = config_parse_pair(pair[0]->buf, value, &kvi, fn, data); - strbuf_list_free(pair); + ret = config_parse_pair(pair.items[0].string, value, &kvi, fn, data); + string_list_clear(&pair, 0); return ret; } @@ -1951,10 +1950,290 @@ int git_configset_get_pathname(struct config_set *set, const char *key, char **d return 1; } +struct comment_char_config { + unsigned last_key_id; + bool auto_set; + bool auto_set_in_file; + struct strintmap key_flags; + size_t alloc, nr; + struct comment_char_config_item { + unsigned key_id; + char *path; + enum config_scope scope; + } *item; +}; + +#define COMMENT_CHAR_CFG_INIT { \ + .key_flags = STRINTMAP_INIT, \ + } + +static void comment_char_config_release(struct comment_char_config *config) +{ + strintmap_clear(&config->key_flags); + for (size_t i = 0; i < config->nr; i++) + free(config->item[i].path); + free(config->item); +} + +/* Used to track whether the key occurs more than once in a given file */ +#define KEY_SEEN_ONCE 1u +#define KEY_SEEN_TWICE 2u +#define COMMENT_KEY_SHIFT(id) (2 * (id)) +#define COMMENT_KEY_MASK(id) (3u << COMMENT_KEY_SHIFT(id)) + +static void set_comment_key_flags(struct comment_char_config *config, + const char *path, unsigned id, unsigned value) +{ + unsigned old = strintmap_get(&config->key_flags, path); + unsigned new = (old & ~COMMENT_KEY_MASK(id)) | + value << COMMENT_KEY_SHIFT(id); + + strintmap_set(&config->key_flags, path, new); +} + +static unsigned get_comment_key_flags(struct comment_char_config *config, + const char *path, unsigned id) +{ + unsigned value = strintmap_get(&config->key_flags, path); + + return (value & COMMENT_KEY_MASK(id)) >> COMMENT_KEY_SHIFT(id); +} + +static const char *comment_key_name(unsigned id) +{ + static const char *name[] = { + "core.commentChar", + "core.commentString", + }; + + if (id >= ARRAY_SIZE(name)) + BUG("invalid comment key id"); + + return name[id]; +} + +static void comment_char_callback(const char *key, const char *value, + const struct config_context *ctx, void *data) +{ + struct comment_char_config *config = data; + const struct key_value_info *kvi = ctx->kvi; + unsigned key_id; + + if (!strcmp(key, "core.commentchar")) + key_id = 0; + else if (!strcmp(key, "core.commentstring")) + key_id = 1; + else + return; + + config->last_key_id = key_id; + config->auto_set = value && !strcmp(value, "auto"); + if (kvi->origin_type != CONFIG_ORIGIN_FILE) { + return; + } else if (get_comment_key_flags(config, kvi->filename, key_id)) { + set_comment_key_flags(config, kvi->filename, key_id, + KEY_SEEN_TWICE); + } else { + struct comment_char_config_item *item; + + ALLOC_GROW_BY(config->item, config->nr, 1, config->alloc); + item = &config->item[config->nr - 1]; + item->key_id = key_id; + item->scope = kvi->scope; + item->path = xstrdup(kvi->filename); + set_comment_key_flags(config, kvi->filename, key_id, + KEY_SEEN_ONCE); + } + config->auto_set_in_file = config->auto_set; +} + +static void add_config_scope_arg(struct repository *repo, struct strbuf *buf, + struct comment_char_config_item *item) +{ + char *global_config = git_global_config(); + char *system_config = git_system_config(); + + if (item->scope == CONFIG_SCOPE_SYSTEM && access(item->path, W_OK)) { + /* + * If the user cannot write to the system config recommend + * setting the global config instead. + */ + strbuf_addstr(buf, "--global "); + } else if (fspatheq(item->path, system_config)) { + strbuf_addstr(buf, "--system "); + } else if (fspatheq(item->path, global_config)) { + strbuf_addstr(buf, "--global "); + } else if (fspatheq(item->path, + mkpath("%s/config", + repo_get_git_dir(repo)))) { + ; /* --local is the default */ + } else if (fspatheq(item->path, + mkpath("%s/config.worktree", + repo_get_common_dir(repo)))) { + strbuf_addstr(buf, "--worktree "); + } else { + const char *path = item->path; + const char *home = getenv("HOME"); + + strbuf_addstr(buf, "--file "); + if (home && !fspathncmp(path, home, strlen(home))) { + path += strlen(home); + if (!fspathncmp(path, "/", 1)) + path++; + strbuf_addstr(buf, "~/"); + } + sq_quote_buf_pretty(buf, path); + strbuf_addch(buf, ' '); + } + + free(global_config); + free(system_config); +} + +static bool can_unset_comment_char_config(struct comment_char_config *config) +{ + for (size_t i = 0; i < config->nr; i++) { + struct comment_char_config_item *item = &config->item[i]; + + if (item->scope == CONFIG_SCOPE_SYSTEM && + access(item->path, W_OK)) + return false; + } + + return true; +} + +static void add_unset_auto_comment_char_advice(struct repository *repo, + struct comment_char_config *config) +{ + struct strbuf buf = STRBUF_INIT; + + if (!can_unset_comment_char_config(config)) + return; + + for (size_t i = 0; i < config->nr; i++) { + struct comment_char_config_item *item = &config->item[i]; + + strbuf_addstr(&buf, " git config unset "); + add_config_scope_arg(repo, &buf, item); + if (get_comment_key_flags(config, item->path, item->key_id) == KEY_SEEN_TWICE) + strbuf_addstr(&buf, "--all "); + strbuf_addf(&buf, "%s\n", comment_key_name(item->key_id)); + } + advise(_("\nTo use the default comment string (#) please run\n\n%s"), + buf.buf); + strbuf_release(&buf); +} + +static void add_comment_char_advice(struct repository *repo, + struct comment_char_config *config) +{ + struct strbuf buf = STRBUF_INIT; + struct comment_char_config_item *item; + /* TRANSLATORS this is a place holder for the value of core.commentString */ + const char *placeholder = _("<comment string>"); + + /* + * If auto is set in the last file that we saw advise the user how to + * update their config. + */ + if (!config->auto_set_in_file) + return; + + add_unset_auto_comment_char_advice(repo, config); + item = &config->item[config->nr - 1]; + strbuf_reset(&buf); + strbuf_addstr(&buf, " git config set "); + add_config_scope_arg(repo, &buf, item); + strbuf_addf(&buf, "%s %s\n", comment_key_name(item->key_id), + placeholder); + advise(_("\nTo set a custom comment string please run\n\n" + "%s\nwhere '%s' is the string you wish to use.\n"), + buf.buf, placeholder); + strbuf_release(&buf); +} + +#undef KEY_SEEN_ONCE +#undef KEY_SEEN_TWICE +#undef COMMENT_KEY_SHIFT +#undef COMMENT_KEY_MASK + +struct repo_config { + struct repository *repo; + struct comment_char_config comment_char_config; +}; + +#define REPO_CONFIG_INIT(repo_) { \ + .comment_char_config = COMMENT_CHAR_CFG_INIT, \ + .repo = repo_, \ + }; + +static void repo_config_release(struct repo_config *config) +{ + comment_char_config_release(&config->comment_char_config); +} + +#ifdef WITH_BREAKING_CHANGES +static void check_auto_comment_char_config(struct repository *repo, + struct comment_char_config *config) +{ + if (!config->auto_set) + return; + + die_message(_("Support for '%s=auto' has been removed in Git 3.0"), + comment_key_name(config->last_key_id)); + add_comment_char_advice(repo, config); + die(NULL); +} +#else +static void check_auto_comment_char_config(struct repository *repo, + struct comment_char_config *config) +{ + extern bool warn_on_auto_comment_char; + const char *DEPRECATED_CONFIG_ENV = + "GIT_AUTO_COMMENT_CHAR_CONFIG_WARNING_GIVEN"; + + if (!config->auto_set || !warn_on_auto_comment_char) + return; + + /* + * Use an environment variable to ensure that subprocesses do not repeat + * the warning. + */ + if (git_env_bool(DEPRECATED_CONFIG_ENV, false)) + return; + + setenv(DEPRECATED_CONFIG_ENV, "true", true); + + warning(_("Support for '%s=auto' is deprecated and will be removed in " + "Git 3.0"), comment_key_name(config->last_key_id)); + add_comment_char_advice(repo, config); +} +#endif /* WITH_BREAKING_CHANGES */ + +static void check_deprecated_config(struct repo_config *config) +{ + if (!config->repo->check_deprecated_config) + return; + + check_auto_comment_char_config(config->repo, + &config->comment_char_config); +} + +static int repo_config_callback(const char *key, const char *value, + const struct config_context *ctx, void *data) +{ + struct repo_config *config = data; + + comment_char_callback(key, value, ctx, &config->comment_char_config); + return config_set_callback(key, value, ctx, config->repo->config); +} + /* Functions use to read configuration from a repository */ static void repo_read_config(struct repository *repo) { struct config_options opts = { 0 }; + struct repo_config config = REPO_CONFIG_INIT(repo); opts.respect_includes = 1; opts.commondir = repo->commondir; @@ -1966,8 +2245,8 @@ static void repo_read_config(struct repository *repo) git_configset_clear(repo->config); git_configset_init(repo->config); - if (config_with_options(config_set_callback, repo->config, NULL, - repo, &opts) < 0) + if (config_with_options(repo_config_callback, &config, NULL, repo, + &opts) < 0) /* * config_with_options() normally returns only * zero, as most errors are fatal, and @@ -1980,6 +2259,8 @@ static void repo_read_config(struct repository *repo) * immediately. */ die(_("unknown error occurred while reading the configuration files")); + check_deprecated_config(&config); + repo_config_release(&config); } static void git_config_check_init(struct repository *repo) @@ -2667,6 +2948,14 @@ int repo_config_set_multivar_in_file_gently(struct repository *r, char *contents = NULL; size_t contents_sz; struct config_store_data store = CONFIG_STORE_INIT; + bool saved_check_deprecated_config = r->check_deprecated_config; + + /* + * Do not warn or die if there are deprecated config settings as + * we want the user to be able to change those settings by running + * "git config". + */ + r->check_deprecated_config = false; validate_comment_string(comment); @@ -2898,6 +3187,7 @@ out_free: if (in_fd >= 0) close(in_fd); config_store_data_clear(&store); + r->check_deprecated_config = saved_check_deprecated_config; return ret; write_err_out: @@ -407,7 +407,7 @@ static int process_ref_v2(struct packet_reader *reader, struct ref ***list, * name. Subsequent fields (symref-target and peeled) are optional and * don't have a particular order. */ - if (string_list_split(&line_sections, line, ' ', -1) < 2) { + if (string_list_split(&line_sections, line, " ", -1) < 2) { ret = 0; goto out; } diff --git a/contrib/diff-highlight/README b/contrib/diff-highlight/README index d4c2343175..1db4440e68 100644 --- a/contrib/diff-highlight/README +++ b/contrib/diff-highlight/README @@ -58,6 +58,14 @@ following in your git configuration: diff = diff-highlight | less --------------------------------------------- +If you use the interactive patch mode of `git add -p`, `git checkout +-p`, etc, you may also want to configure it to be used there: + +--------------------------------------------- +[interactive] + diffFilter = diff-highlight +--------------------------------------------- + Color Config ------------ diff --git a/contrib/git-jump/git-jump b/contrib/git-jump/git-jump index 3f69675961..8d1d5d79a6 100755 --- a/contrib/git-jump/git-jump +++ b/contrib/git-jump/git-jump @@ -44,7 +44,7 @@ open_editor() { mode_diff() { git diff --no-prefix --relative "$@" | perl -ne ' - if (m{^\+\+\+ (.*)}) { $file = $1 eq "/dev/null" ? undef : $1; next } + if (m{^\+\+\+ (.*?)\t?$}) { $file = $1 eq "/dev/null" ? undef : $1; next } defined($file) or next; if (m/^@@ .*?\+(\d+)/) { $line = $1; next } defined($line) or next; diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh index 3fddba797c..17106d1a72 100755 --- a/contrib/subtree/git-subtree.sh +++ b/contrib/subtree/git-subtree.sh @@ -785,20 +785,40 @@ ensure_valid_ref_format () { die "fatal: '$1' does not look like a ref" } -# Usage: check if a commit from another subtree should be +# Usage: should_ignore_subtree_split_commit REV +# +# Check if REV is a commit from another subtree and should be # ignored from processing for splits should_ignore_subtree_split_commit () { assert test $# = 1 - local rev="$1" - if test -n "$(git log -1 --grep="git-subtree-dir:" $rev)" + + git show \ + --no-patch \ + --no-show-signature \ + --format='%(trailers:key=git-subtree-dir,key=git-subtree-mainline)' \ + "$1" | + ( + have_mainline= + subtree_dir= + + while read -r trailer val + do + case "$trailer" in + git-subtree-dir:) + subtree_dir="${val%/}" ;; + git-subtree-mainline:) + have_mainline=y ;; + esac + done + + if test -n "${subtree_dir}" && + test -z "${have_mainline}" && + test "${subtree_dir}" != "$arg_prefix" then - if test -z "$(git log -1 --grep="git-subtree-mainline:" $rev)" && - test -z "$(git log -1 --grep="git-subtree-dir: $arg_prefix$" $rev)" - then - return 0 - fi + return 0 fi return 1 + ) } # Usage: process_split_commit REV PARENTS diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh index 3edbb33af4..316dc5269e 100755 --- a/contrib/subtree/t/t7900-subtree.sh +++ b/contrib/subtree/t/t7900-subtree.sh @@ -9,6 +9,9 @@ This test verifies the basic operation of the add, merge, split, pull, and push subcommands of git subtree. ' +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + TEST_DIRECTORY=$(pwd)/../../../t . "$TEST_DIRECTORY"/test-lib.sh . "$TEST_DIRECTORY"/lib-gpg.sh @@ -68,6 +71,33 @@ test_create_pre2_32_repo () { git -C "$1-clone" replace HEAD^2 $new_commit } +# test_create_subtree_add REPO ORPHAN PREFIX FILENAME ... +# +# Create a simple subtree on a new branch named ORPHAN in REPO. +# The subtree is then merged into the current branch of REPO, +# under PREFIX. The generated subtree has has one commit +# with subject and tag FILENAME with a single file "FILENAME.t" +# +# When this method returns: +# - the current branch of REPO will have file PREFIX/FILENAME.t +# - REPO will have a branch named ORPHAN with subtree history +# +# additional arguments are forwarded to "subtree add" +test_create_subtree_add () { + ( + cd "$1" && + orphan="$2" && + prefix="$3" && + filename="$4" && + shift 4 && + last="$(git branch --show-current)" && + git switch --orphan "$orphan" && + test_commit "$filename" && + git checkout "$last" && + git subtree add --prefix="$prefix" "$@" "$orphan" + ) +} + test_expect_success 'shows short help text for -h' ' test_expect_code 129 git subtree -h >out 2>err && test_must_be_empty err && @@ -426,6 +456,47 @@ test_expect_success 'split with multiple subtrees' ' --squash --rejoin -d -m "Sub B Split 1" 2>&1 | grep -w "\[1\]")" = "" ' +# When subtree split-ing a directory that has other subtree +# *merges* underneath it, the split must include those subtrees. +# This test creates a nested subtree, `subA/subB`, and tests +# that the tree is correct after a subtree split of `subA/`. +# The test covers: +# - An initial `subtree add`; and +# - A follow-up `subtree merge` +# both with and without `--squashed`. +for is_squashed in '' 'y' +do + test_expect_success "split keeps nested ${is_squashed:+--squash }subtrees that are part of the split" ' + subtree_test_create_repo "$test_count" && + ( + cd "$test_count" && + mkdir subA && + test_commit subA/file1 && + test_create_subtree_add \ + . mksubtree subA/subB file2 ${is_squashed:+--squash} && + test_path_is_file subA/file1.t && + test_path_is_file subA/subB/file2.t && + git subtree split --prefix=subA --branch=bsplit && + git checkout bsplit && + test_path_is_file file1.t && + test_path_is_file subB/file2.t && + git checkout mksubtree && + git branch -D bsplit && + test_commit file3 && + git checkout main && + git subtree merge \ + ${is_squashed:+--squash} \ + --prefix=subA/subB mksubtree && + test_path_is_file subA/subB/file3.t && + git subtree split --prefix=subA --branch=bsplit && + git checkout bsplit && + test_path_is_file file1.t && + test_path_is_file subB/file2.t && + test_path_is_file subB/file3.t + ) + ' +done + test_expect_success 'split sub dir/ with --rejoin from scratch' ' subtree_test_create_repo "$test_count" && test_create_commit "$test_count" main1 && diff --git a/diff-lib.c b/diff-lib.c index 244468dd1a..b8f8f3bc31 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -115,6 +115,9 @@ void run_diff_files(struct rev_info *revs, unsigned int option) uint64_t start = getnanotime(); struct index_state *istate = revs->diffopt.repo->index; + if (revs->diffopt.max_depth_valid) + die(_("max-depth is not supported for worktree diffs")); + diff_set_mnemonic_prefix(&revs->diffopt, "i/", "w/"); refresh_fsmonitor(istate); @@ -560,6 +563,8 @@ static int diff_cache(struct rev_info *revs, opts.dst_index = NULL; opts.pathspec = &revs->diffopt.pathspec; opts.pathspec->recursive = 1; + if (revs->diffopt.max_depth_valid) + die(_("max-depth is not supported for index diffs")); init_tree_desc(&t, &tree->object.oid, tree->buffer, tree->size); return unpack_trees(1, &t, &opts); @@ -327,29 +327,23 @@ static unsigned parse_color_moved_ws(const char *arg) struct string_list l = STRING_LIST_INIT_DUP; struct string_list_item *i; - string_list_split(&l, arg, ',', -1); + string_list_split_f(&l, arg, ",", -1, STRING_LIST_SPLIT_TRIM); for_each_string_list_item(i, &l) { - struct strbuf sb = STRBUF_INIT; - strbuf_addstr(&sb, i->string); - strbuf_trim(&sb); - - if (!strcmp(sb.buf, "no")) + if (!strcmp(i->string, "no")) ret = 0; - else if (!strcmp(sb.buf, "ignore-space-change")) + else if (!strcmp(i->string, "ignore-space-change")) ret |= XDF_IGNORE_WHITESPACE_CHANGE; - else if (!strcmp(sb.buf, "ignore-space-at-eol")) + else if (!strcmp(i->string, "ignore-space-at-eol")) ret |= XDF_IGNORE_WHITESPACE_AT_EOL; - else if (!strcmp(sb.buf, "ignore-all-space")) + else if (!strcmp(i->string, "ignore-all-space")) ret |= XDF_IGNORE_WHITESPACE; - else if (!strcmp(sb.buf, "allow-indentation-change")) + else if (!strcmp(i->string, "allow-indentation-change")) ret |= COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE; else { ret |= COLOR_MOVED_WS_ERROR; - error(_("unknown color-moved-ws mode '%s', possible values are 'ignore-space-change', 'ignore-space-at-eol', 'ignore-all-space', 'allow-indentation-change'"), sb.buf); + error(_("unknown color-moved-ws mode '%s', possible values are 'ignore-space-change', 'ignore-space-at-eol', 'ignore-all-space', 'allow-indentation-change'"), i->string); } - - strbuf_release(&sb); } if ((ret & COLOR_MOVED_WS_ALLOW_INDENTATION_CHANGE) && @@ -2444,6 +2438,15 @@ static int fn_out_consume(void *priv, char *line, unsigned long len) return 0; } +static int quick_consume(void *priv, char *line UNUSED, unsigned long len UNUSED) +{ + struct emit_callback *ecbdata = priv; + struct diff_options *o = ecbdata->opt; + + o->found_changes = 1; + return 1; +} + static void pprint_rename(struct strbuf *name, const char *a, const char *b) { const char *old_name = a; @@ -3759,8 +3762,21 @@ static void builtin_diff(const char *name_a, if (o->word_diff) init_diff_words_data(&ecbdata, o, one, two); - if (xdi_diff_outf(&mf1, &mf2, NULL, fn_out_consume, - &ecbdata, &xpp, &xecfg)) + if (o->dry_run) { + /* + * Unlike the !dry_run case, we need to ignore the + * return value from xdi_diff_outf() here, because + * xdi_diff_outf() takes non-zero return from its + * callback function as a sign of error and returns + * early (which is why we return non-zero from our + * callback, quick_consume()). Unfortunately, + * xdi_diff_outf() signals an error by returning + * non-zero. + */ + xdi_diff_outf(&mf1, &mf2, NULL, quick_consume, + &ecbdata, &xpp, &xecfg); + } else if (xdi_diff_outf(&mf1, &mf2, NULL, fn_out_consume, + &ecbdata, &xpp, &xecfg)) die("unable to generate diff for %s", one->path); if (o->word_diff) free_diff_words_data(&ecbdata); @@ -4988,6 +5004,9 @@ void diff_setup_done(struct diff_options *options) options->filter = ~filter_bit[DIFF_STATUS_FILTER_AON]; options->filter &= ~options->filter_not; } + + if (options->pathspec.has_wildcard && options->max_depth_valid) + die("max-depth cannot be used with wildcard pathspecs"); } int parse_long_opt(const char *opt, const char **argv, @@ -5622,6 +5641,23 @@ static int diff_opt_rotate_to(const struct option *opt, const char *arg, int uns return 0; } +static int diff_opt_max_depth(const struct option *opt, + const char *arg, int unset) +{ + struct diff_options *options = opt->value; + + BUG_ON_OPT_NEG(unset); + + if (!git_parse_int(arg, &options->max_depth)) + return error(_("invalid value for '%s': '%s'"), + "--max-depth", arg); + + options->flags.recursive = 1; + options->max_depth_valid = options->max_depth >= 0; + + return 0; +} + /* * Consider adding new flags to __git_diff_common_options * in contrib/completion/git-completion.bash @@ -5894,6 +5930,10 @@ struct option *add_diff_options(const struct option *opts, OPT_CALLBACK_F(0, "diff-filter", options, N_("[(A|C|D|M|R|T|U|X|B)...[*]]"), N_("select files by diff type"), PARSE_OPT_NONEG, diff_opt_diff_filter), + OPT_CALLBACK_F(0, "max-depth", options, N_("<depth>"), + N_("maximum tree depth to recurse"), + PARSE_OPT_NONEG, diff_opt_max_depth), + { .type = OPTION_CALLBACK, .long_name = "output", @@ -6150,6 +6190,22 @@ static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o) run_diff(p, o); } +/* return 1 if any change is found; otherwise, return 0 */ +static int diff_flush_patch_quietly(struct diff_filepair *p, struct diff_options *o) +{ + int saved_dry_run = o->dry_run; + int saved_found_changes = o->found_changes; + int ret; + + o->dry_run = 1; + o->found_changes = 0; + diff_flush_patch(p, o); + ret = o->found_changes; + o->dry_run = saved_dry_run; + o->found_changes |= saved_found_changes; + return ret; +} + static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o, struct diffstat_t *diffstat) { @@ -6778,8 +6834,15 @@ void diff_flush(struct diff_options *options) DIFF_FORMAT_CHECKDIFF)) { for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; - if (check_pair_status(p)) - flush_one_pair(p, options); + + if (!check_pair_status(p)) + continue; + + if (options->flags.diff_from_contents && + !diff_flush_patch_quietly(p, options)) + continue; + + flush_one_pair(p, options); } separator++; } @@ -6831,19 +6894,10 @@ void diff_flush(struct diff_options *options) if (output_format & DIFF_FORMAT_NO_OUTPUT && options->flags.exit_with_status && options->flags.diff_from_contents) { - /* - * run diff_flush_patch for the exit status. setting - * options->file to /dev/null should be safe, because we - * aren't supposed to produce any output anyway. - */ - diff_free_file(options); - options->file = xfopen("/dev/null", "w"); - options->close_file = 1; - options->color_moved = 0; for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; if (check_pair_status(p)) - diff_flush_patch(p, options); + diff_flush_patch_quietly(p, options); if (options->found_changes) break; } @@ -400,10 +400,20 @@ struct diff_options { #define COLOR_MOVED_WS_ERROR (1<<0) unsigned color_moved_ws_handling; + bool dry_run; + struct repository *repo; struct strmap *additional_path_headers; int no_free; + + /* + * The value '0' is a valid max-depth (for no recursion), and value '-1' + * also (for unlimited recursion), so the extra "valid" flag is used to + * determined whether the user specified option --max-depth. + */ + int max_depth; + int max_depth_valid; }; unsigned diff_filter_bit(char status); @@ -277,7 +277,7 @@ int within_depth(const char *name, int namelen, if (depth > max_depth) return 0; } - return 1; + return depth <= max_depth; } /* diff --git a/environment.c b/environment.c index ae1427bb9e..a770b5921d 100644 --- a/environment.c +++ b/environment.c @@ -78,7 +78,6 @@ int grafts_keep_true_parents; int core_apply_sparse_checkout; int core_sparse_checkout_cone; int sparse_expect_files_outside_of_patterns; -int merge_log_config = -1; int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */ unsigned long pack_size_limit_cfg; int max_allowed_tree_depth = @@ -122,7 +121,10 @@ int protect_ntfs = PROTECT_NTFS_DEFAULT; */ const char *comment_line_str = "#"; char *comment_line_str_to_free; +#ifndef WITH_BREAKING_CHANGES int auto_comment_line_char; +bool warn_on_auto_comment_char; +#endif /* !WITH_BREAKING_CHANGES */ /* This is set by setup_git_directory_gently() and/or git_default_config() */ char *git_work_tree_cfg; @@ -175,10 +177,10 @@ int have_git_dir(void) const char *get_git_namespace(void) { static const char *namespace; - struct strbuf buf = STRBUF_INIT; - struct strbuf **components, **c; const char *raw_namespace; + struct string_list components = STRING_LIST_INIT_DUP; + struct string_list_item *item; if (namespace) return namespace; @@ -190,12 +192,17 @@ const char *get_git_namespace(void) } strbuf_addstr(&buf, raw_namespace); - components = strbuf_split(&buf, '/'); + + string_list_split(&components, buf.buf, "/", -1); strbuf_reset(&buf); - for (c = components; *c; c++) - if (strcmp((*c)->buf, "/") != 0) - strbuf_addf(&buf, "refs/namespaces/%s", (*c)->buf); - strbuf_list_free(components); + + for_each_string_list_item(item, &components) { + if (item->string[0]) + strbuf_addf(&buf, "refs/namespaces/%s/", item->string); + } + string_list_clear(&components, 0); + + strbuf_trim_trailing_dir_sep(&buf); if (check_refname_format(buf.buf, 0)) die(_("bad git namespace path \"%s\""), raw_namespace); strbuf_addch(&buf, '/'); @@ -459,16 +466,22 @@ static int git_default_core_config(const char *var, const char *value, if (!strcmp(var, "core.commentchar") || !strcmp(var, "core.commentstring")) { - if (!value) + if (!value) { return config_error_nonbool(var); - else if (!strcasecmp(value, "auto")) +#ifndef WITH_BREAKING_CHANGES + } else if (!strcasecmp(value, "auto")) { auto_comment_line_char = 1; - else if (value[0]) { + FREE_AND_NULL(comment_line_str_to_free); + comment_line_str = "#"; +#endif /* !WITH_BREAKING_CHANGES */ + } else if (value[0]) { if (strchr(value, '\n')) return error(_("%s cannot contain newline"), var); comment_line_str = value; FREE_AND_NULL(comment_line_str_to_free); +#ifndef WITH_BREAKING_CHANGES auto_comment_line_char = 0; +#endif /* !WITH_BREAKING_CHANGES */ } else return error(_("%s must have at least one character"), var); return 0; diff --git a/environment.h b/environment.h index 8cfce41015..51898c99cd 100644 --- a/environment.h +++ b/environment.h @@ -208,7 +208,10 @@ extern char *excludes_file; */ extern const char *comment_line_str; extern char *comment_line_str_to_free; +#ifndef WITH_BREAKING_CHANGES extern int auto_comment_line_char; +extern bool warn_on_auto_comment_char; +#endif /* !WITH_BREAKING_CHANGES */ # endif /* USE_THE_REPOSITORY_VARIABLE */ #endif /* ENVIRONMENT_H */ diff --git a/fetch-pack.c b/fetch-pack.c index 46c39f85c4..6ed5662951 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -143,7 +143,8 @@ static struct commit *deref_without_lazy_fetch(const struct object_id *oid, commit = lookup_commit_in_graph(the_repository, oid); if (commit) { if (mark_tags_complete_and_check_obj_db) { - if (!odb_has_object(the_repository->objects, oid, 0)) + if (!odb_has_object(the_repository->objects, oid, + HAS_OBJECT_RECHECK_PACKED)) die_in_commit_graph_only(oid); } return commit; @@ -1914,7 +1915,7 @@ static void fetch_pack_config(void) char *str; if (!repo_config_get_string(the_repository, "fetch.uriprotocols", &str) && str) { - string_list_split(&uri_protocols, str, ',', -1); + string_list_split(&uri_protocols, str, ",", -1); free(str); } } diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c index 40174efa3d..c9085edc40 100644 --- a/fmt-merge-msg.c +++ b/fmt-merge-msg.c @@ -26,13 +26,15 @@ static struct string_list suppress_dest_patterns = STRING_LIST_INIT_DUP; int fmt_merge_msg_config(const char *key, const char *value, const struct config_context *ctx, void *cb) { + int *merge_log_config = cb; + if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) { int is_bool; - merge_log_config = git_config_bool_or_int(key, value, ctx->kvi, &is_bool); - if (!is_bool && merge_log_config < 0) + *merge_log_config = git_config_bool_or_int(key, value, ctx->kvi, &is_bool); + if (!is_bool && *merge_log_config < 0) return error("%s: negative length %s", key, value); - if (is_bool && merge_log_config) - merge_log_config = DEFAULT_MERGE_LOG_LEN; + if (is_bool && *merge_log_config) + *merge_log_config = DEFAULT_MERGE_LOG_LEN; } else if (!strcmp(key, "merge.branchdesc")) { use_branch_desc = git_config_bool(key, value); } else if (!strcmp(key, "merge.suppressdest")) { diff --git a/fmt-merge-msg.h b/fmt-merge-msg.h index 73ca3e4465..c066d83761 100644 --- a/fmt-merge-msg.h +++ b/fmt-merge-msg.h @@ -12,7 +12,6 @@ struct fmt_merge_msg_opts { const char *into_name; }; -extern int merge_log_config; int fmt_merge_msg_config(const char *key, const char *value, const struct config_context *ctx, void *cb); int fmt_merge_msg(struct strbuf *in, struct strbuf *out, diff --git a/for-each-ref.h b/for-each-ref.h new file mode 100644 index 0000000000..c8d0219179 --- /dev/null +++ b/for-each-ref.h @@ -0,0 +1,26 @@ +#ifndef FOR_EACH_REF_H +#define FOR_EACH_REF_H + +struct repository; + +/* + * Shared usage string for options common to git-for-each-ref(1) + * and git-refs-list(1). The command-specific part (e.g., "git refs list ") + * must be prepended by the caller. + */ +#define COMMON_USAGE_FOR_EACH_REF \ + "[--count=<count>] [--shell|--perl|--python|--tcl]\n" \ + " [(--sort=<key>)...] [--format=<format>]\n" \ + " [--include-root-refs] [--points-at=<object>]\n" \ + " [--merged[=<object>]] [--no-merged[=<object>]]\n" \ + " [--contains[=<object>]] [--no-contains[=<object>]]\n" \ + " [(--exclude=<pattern>)...] [--start-after=<marker>]\n" \ + " [ --stdin | (<pattern>...)]" + +/* + * The core logic for for-each-ref and its clones. + */ +int for_each_ref_core(int argc, const char **argv, const char *prefix, + struct repository *repo, const char *const *usage); + +#endif /* FOR_EACH_REF_H */ diff --git a/git-curl-compat.h b/git-curl-compat.h index aa8eed7ed2..659e5a3875 100644 --- a/git-curl-compat.h +++ b/git-curl-compat.h @@ -46,6 +46,13 @@ #endif /** + * curl_global_trace() was added in 8.3.0, released September 2023. + */ +#if LIBCURL_VERSION_NUM >= 0x080300 +#define GIT_CURL_HAVE_GLOBAL_TRACE 1 +#endif + +/** * CURLOPT_TCP_KEEPCNT was added in 8.9.0, released in July, 2024. */ #if LIBCURL_VERSION_NUM >= 0x080900 diff --git a/git-gui/Makefile b/git-gui/Makefile index 27bbe051de..69b0b84435 100644 --- a/git-gui/Makefile +++ b/git-gui/Makefile @@ -186,6 +186,7 @@ install: all $(QUIET)$(INSTALL_D0)'$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL_D1) $(QUIET)$(INSTALL_X0)git-gui $(INSTALL_X1) '$(DESTDIR_SQ)$(gitexecdir_SQ)' $(QUIET)$(INSTALL_X0)git-gui--askpass $(INSTALL_X1) '$(DESTDIR_SQ)$(gitexecdir_SQ)' + $(QUIET)$(INSTALL_X0)git-gui--askyesno $(INSTALL_X1) '$(DESTDIR_SQ)$(gitexecdir_SQ)' $(QUIET)$(foreach p,$(GITGUI_BUILT_INS), $(INSTALL_L0)'$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' $(INSTALL_L1)'$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' $(INSTALL_L2)'$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' $(INSTALL_L3) &&) true ifdef GITGUI_WINDOWS_WRAPPER $(QUIET)$(INSTALL_R0)git-gui.tcl $(INSTALL_R1) '$(DESTDIR_SQ)$(gitexecdir_SQ)' @@ -200,6 +201,7 @@ uninstall: $(QUIET)$(CLEAN_DST) '$(DESTDIR_SQ)$(gitexecdir_SQ)' $(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/git-gui $(REMOVE_F1) $(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/git-gui--askpass $(REMOVE_F1) + $(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/git-gui--askyesno $(REMOVE_F1) $(QUIET)$(foreach p,$(GITGUI_BUILT_INS), $(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/$p $(REMOVE_F1) &&) true ifdef GITGUI_WINDOWS_WRAPPER $(QUIET)$(REMOVE_F0)'$(DESTDIR_SQ)$(gitexecdir_SQ)'/git-gui.tcl $(REMOVE_F1) diff --git a/git-gui/git-gui--askyesno b/git-gui/git-gui--askyesno new file mode 100755 index 0000000000..142d1bc3de --- /dev/null +++ b/git-gui/git-gui--askyesno @@ -0,0 +1,63 @@ +#!/bin/sh +# Tcl ignores the next line -*- tcl -*- \ +exec wish "$0" -- "$@" + +# This is an implementation of a simple yes no dialog +# which is injected into the git commandline by git gui +# in case a yesno question needs to be answered. +# +# The window title, which defaults to "Question?", can be +# overridden via the optional `--title` command-line +# option. + +set NS {} +set use_ttk [package vsatisfies [package provide Tk] 8.5] +if {$use_ttk} { + set NS ttk +} + +set title "Question?" +if {$argc < 1} { + puts stderr "Usage: $argv0 <question>" + exit 1 +} else { + if {$argc > 2 && [lindex $argv 0] == "--title"} { + set title [lindex $argv 1] + set argv [lreplace $argv 0 1] + } + set prompt [join $argv " "] +} + +${NS}::frame .t +${NS}::label .t.m -text $prompt -justify center -width 40 +.t.m configure -wraplength 400 +pack .t.m -side top -fill x -padx 20 -pady 20 -expand 1 +pack .t -side top -fill x -ipadx 20 -ipady 20 -expand 1 + +${NS}::frame .b +${NS}::frame .b.left -width 200 +${NS}::button .b.yes -text Yes -command {exit 0} +${NS}::button .b.no -text No -command {exit 1} + +pack .b.left -side left -expand 1 -fill x +pack .b.yes -side left -expand 1 +pack .b.no -side right -expand 1 -ipadx 5 +pack .b -side bottom -fill x -ipadx 20 -ipady 15 + +bind . <Key-Return> {exit 0} +bind . <Key-Escape> {exit 1} + +if {$::tcl_platform(platform) eq {windows}} { + set icopath [file dirname [file normalize $argv0]] + if {[file tail $icopath] eq {git-core}} { + set icopath [file dirname $icopath] + } + set icopath [file dirname $icopath] + set icopath [file join $icopath share git git-for-windows.ico] + if {[file exists $icopath]} { + wm iconbitmap . -default $icopath + } +} + +wm title . $title +tk::PlaceWindow . diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index a931d7f7c9..d3d3aa14a9 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -103,7 +103,6 @@ if {[is_Windows]} { set _path_sep {:} } -set _search_path {} set _path_seen [dict create] foreach p [split $env(PATH) $_path_sep] { # Keep only absolute paths, getting rid of ., empty, etc. @@ -112,12 +111,9 @@ foreach p [split $env(PATH) $_path_sep] { } # Keep only the first occurence of any duplicates. set norm_p [file normalize $p] - if {[dict exists $_path_seen $norm_p]} { - continue - } dict set _path_seen $norm_p 1 - lappend _search_path $norm_p } +set _search_path [dict keys $_path_seen] unset _path_seen set env(PATH) [join $_search_path $_path_sep] @@ -583,21 +579,6 @@ proc open_cmd_pipe {cmd path} { return [open |$run r] } -proc _lappend_nice {cmd_var} { - global _nice - upvar $cmd_var cmd - - if {![info exists _nice]} { - set _nice [_which nice] - if {[catch {safe_exec [list $_nice git version]}]} { - set _nice {} - } - } - if {$_nice ne {}} { - lappend cmd $_nice - } -} - proc git {args} { git_redir $args {} } @@ -631,15 +612,14 @@ proc git_read {cmd {redir {}}} { return [safe_open_command $cmdp $redir] } -proc git_read_nice {cmd} { - global _git - set opt [list] - - _lappend_nice opt - - set cmdp [concat [list $_git] $cmd] +set _nice [list [_which nice]] +if {[catch {safe_exec [list {*}$_nice git version]}]} { + set _nice {} +} - return [safe_open_command [concat $opt $cmdp]] +proc git_read_nice {cmd} { + set cmdp [list {*}$::_nice $::_git {*}$cmd] + return [safe_open_command $cmdp] } proc git_write {cmd} { @@ -1130,6 +1110,12 @@ set argv0dir [file dirname [file normalize $::argv0]] if {![info exists env(SSH_ASKPASS)]} { set env(SSH_ASKPASS) [file join $argv0dir git-gui--askpass] } +if {![info exists env(GIT_ASKPASS)]} { + set env(GIT_ASKPASS) [file join $argv0dir git-gui--askpass] +} +if {![info exists env(GIT_ASK_YESNO)]} { + set env(GIT_ASK_YESNO) [file join $argv0dir git-gui--askyesno] +} unset argv0dir ###################################################################### diff --git a/git-gui/lib/index.tcl b/git-gui/lib/index.tcl index 7aa09c7728..e1d38e54be 100644 --- a/git-gui/lib/index.tcl +++ b/git-gui/lib/index.tcl @@ -425,6 +425,11 @@ proc revert_helper {txt paths} { if {![lock_index begin-update]} return + # Workaround for Tcl < 9.0: chord namespaces are not obeyed and + # operated in the global namespace. This clears an error that could + # have been left over from a previous operation. + set ::err {} + # Common "after" functionality that waits until multiple asynchronous # operations are complete (by waiting for them to activate their notes # on the chord). @@ -432,7 +437,7 @@ proc revert_helper {txt paths} { # The asynchronous operations are each indicated below by a comment # before the code block that starts the async operation. set after_chord [SimpleChord::new { - if {[string trim $err] != ""} { + if {[info exists err] && [string trim $err] ne ""} { rescan_on_error $err } else { unlock_index diff --git a/git-send-email.perl b/git-send-email.perl index 437f8ac46a..96504e7be1 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -62,7 +62,7 @@ git send-email --translate-aliases --smtp-user <str> * Username for SMTP-AUTH. --smtp-pass <str> * Password for SMTP-AUTH; not necessary. --smtp-encryption <str> * tls or ssl; anything else disables. - --smtp-ssl * Deprecated. Use '--smtp-encryption ssl'. + --smtp-ssl * Deprecated. Use `--smtp-encryption ssl`. --smtp-ssl-cert-path <str> * Path to ca-certificates (either directory or file). Pass an empty string to disable certificate verification. @@ -73,6 +73,10 @@ git send-email --translate-aliases --no-smtp-auth * Disable SMTP authentication. Shorthand for `--smtp-auth=none` --smtp-debug <0|1> * Disable, enable Net::SMTP debug. + --imap-sent-folder <str> * IMAP folder where a copy of the emails should be sent. + Make sure `git imap-send` is set up to use this feature. + --[no-]use-imap-only * Only copy emails to the IMAP folder specified by + `--imap-sent-folder` instead of actually sending them. --batch-size <int> * send max <int> message per connection. --relogin-delay <int> * delay <int> seconds between two successive login. @@ -200,7 +204,7 @@ my $re_encoded_word = qr/=\?($re_token)\?($re_token)\?($re_encoded_text)\?=/; # Variables we fill in automatically, or via prompting: my (@to,@cc,@xh,$envelope_sender, - $initial_in_reply_to,$reply_to,$initial_subject,@files, + $initial_in_reply_to,$reply_to,$initial_subject,@files,@imap_copy, $author,$sender,$smtp_authpass,$annotate,$compose,$time); # Things we either get from config, *or* are overridden on the # command-line. @@ -277,6 +281,7 @@ my ($smtp_server, $smtp_server_port, @smtp_server_options); my ($smtp_authuser, $smtp_encryption, $smtp_ssl_cert_path); my ($batch_size, $relogin_delay); my ($identity, $aliasfiletype, @alias_files, $smtp_domain, $smtp_auth); +my ($imap_sent_folder); my ($confirm); my (@suppress_cc); my ($auto_8bit_encoding); @@ -293,6 +298,7 @@ my $mailmap = 0; my $target_xfer_encoding = 'auto'; my $forbid_sendmail_variables = 1; my $outlook_id_fix = 'auto'; +my $use_imap_only = 0; my %config_bool_settings = ( "thread" => \$thread, @@ -309,6 +315,7 @@ my %config_bool_settings = ( "forbidsendmailvariables" => \$forbid_sendmail_variables, "mailmap" => \$mailmap, "outlookidfix" => \$outlook_id_fix, + "useimaponly" => \$use_imap_only, ); my %config_settings = ( @@ -322,6 +329,7 @@ my %config_settings = ( "smtpauth" => \$smtp_auth, "smtpbatchsize" => \$batch_size, "smtprelogindelay" => \$relogin_delay, + "imapsentfolder" => \$imap_sent_folder, "to" => \@config_to, "tocmd" => \$to_cmd, "cc" => \@config_cc, @@ -527,6 +535,8 @@ my %options = ( "smtp-domain:s" => \$smtp_domain, "smtp-auth=s" => \$smtp_auth, "no-smtp-auth" => sub {$smtp_auth = 'none'}, + "imap-sent-folder=s" => \$imap_sent_folder, + "use-imap-only!" => \$use_imap_only, "annotate!" => \$annotate, "compose" => \$compose, "quiet" => \$quiet, @@ -1678,6 +1688,8 @@ EOF if ($dry_run) { # We don't want to send the email. + } elsif ($use_imap_only) { + die __("The destination IMAP folder is not properly defined.") if !defined $imap_sent_folder; } elsif (defined $sendmail_cmd || file_name_is_absolute($smtp_server)) { my $pid = open my $sm, '|-'; defined $pid or die $!; @@ -1829,6 +1841,17 @@ EOF print "\n"; } + if ($imap_sent_folder && !$dry_run) { + my $imap_header = $header; + if (@initial_bcc) { + # Bcc is not a part of $header, so we add it here. + # This is only for the IMAP copy, not for the actual email + # sent to the recipients. + $imap_header .= "Bcc: " . join(", ", @initial_bcc) . "\n"; + } + push @imap_copy, "From git-send-email\n$imap_header\n$message"; + } + return 1; } @@ -2223,6 +2246,19 @@ sub cleanup_compose_files { $smtp->quit if $smtp; +if ($imap_sent_folder && @imap_copy && !$dry_run) { + my $imap_input = join("\n", @imap_copy); + eval { + print "\nStarting git imap-send...\n"; + my ($fh, $ctx) = Git::command_input_pipe(['imap-send', '-f', $imap_sent_folder]); + print $fh $imap_input; + Git::command_close_pipe($fh, $ctx); + 1; + } or do { + warn "Warning: failed to send messages to IMAP folder $imap_sent_folder: $@"; + }; +} + sub apply_transfer_encoding { my $message = shift; my $from = shift; @@ -445,7 +445,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv, struct const char *prefix; int run_setup = (p->option & (RUN_SETUP | RUN_SETUP_GENTLY)); - help = argc == 2 && !strcmp(argv[1], "-h"); + help = argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help-all")); if (help && (run_setup & RUN_SETUP)) /* demote to GENTLY to allow 'git cmd -h' outside repo */ run_setup = RUN_SETUP_GENTLY; @@ -565,6 +565,7 @@ static struct cmd_struct commands[] = { { "init", cmd_init_db }, { "init-db", cmd_init_db }, { "interpret-trailers", cmd_interpret_trailers, RUN_SETUP_GENTLY }, + { "last-modified", cmd_last_modified, RUN_SETUP }, { "log", cmd_log, RUN_SETUP }, { "ls-files", cmd_ls_files, RUN_SETUP }, { "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY }, @@ -611,6 +612,7 @@ static struct cmd_struct commands[] = { { "repack", cmd_repack, RUN_SETUP }, { "replace", cmd_replace, RUN_SETUP }, { "replay", cmd_replay, RUN_SETUP }, + { "repo", cmd_repo, RUN_SETUP }, { "rerere", cmd_rerere, RUN_SETUP }, { "reset", cmd_reset, RUN_SETUP }, { "restore", cmd_restore, RUN_SETUP | NEED_WORK_TREE }, diff --git a/gitk-git/README.md b/gitk-git/README.md new file mode 100644 index 0000000000..2e307463c6 --- /dev/null +++ b/gitk-git/README.md @@ -0,0 +1,93 @@ +Gitk - The Git Repository Browser +================================= + +Gitk is a graphical Git repository browser. It displays the commit +history of a Git repository as a graph, showing the relationships +between commits, branches, and tags. + +Usage +===== + +To view the history of the current repository: +```bash +gitk +``` + +To view the history of specific files or directories: +```bash +gitk path/to/file +gitk path/to/directory +``` + +To view a specific branch or range of commits: +```bash +gitk branch-name +gitk v1.0..v2.0 +``` + +For more usage examples and options, see the [gitk manual](https://git-scm.com/docs/gitk). + +Building +======== + +Gitk is a Tcl/Tk application. It requires Tcl/Tk to be installed on +your system. + +Running directly +---------------- + +Gitk can be run from the source directory without installation: + +```bash +./gitk +``` + +This allows for quick testing of changes. + +Installation +------------ + +To install system-wide, you can use either `make` or `meson`: + +```bash +# Install to default location ($HOME/bin) +make install + +# Install to system-wide location +sudo make install prefix=/usr/local + +# Install to custom location +make install prefix=/opt/gitk + +# Using Meson +meson setup builddir +meson compile -C builddir +meson install -C builddir +``` + +Both build systems will handle setting the correct Tcl/Tk interpreter +path and installing translation files. + +Contributing +============ + +Contributions are welcome! The preferred method for submitting patches +is via email to the Git mailing list, as this allows for more thorough +review and broader community feedback. However, GitHub pull requests +are also accepted. + +All commits must be signed off (use `git commit --signoff`) and should +have commit messages prefixed with `gitk:`. + +Email Patches +------------- + +Send patches to git@vger.kernel.org and CC j6t@kdbg.org. See the Git +project's [patch submission guidelines](https://git-scm.com/docs/SubmittingPatches) +for detailed instructions on creating and sending patches. + +License +======= + +Gitk is distributed under the GNU General Public License, either +version 2, or (at your option) any later version. diff --git a/gitk-git/gitk b/gitk-git/gitk index 3b6acfc592..6e4d71d585 100755 --- a/gitk-git/gitk +++ b/gitk-git/gitk @@ -2301,6 +2301,11 @@ proc scrollval {D {koff 0}} { return [expr int(-($D / $scroll_D0) * max(1, $kscroll-$koff))] } +proc precisescrollval {D {koff 0}} { + global kscroll + return [expr (-($D / 10.0) * max(1, $kscroll-$koff))] +} + proc bind_mousewheel {} { global canv cflist ctext bindall <MouseWheel> {allcanvs yview scroll [scrollval %D] units} @@ -2319,6 +2324,25 @@ proc bind_mousewheel {} { bind $cflist <Alt-MouseWheel> {$cflist yview scroll [scrollval 5*%D 2] units} bind $cflist <Alt-Shift-MouseWheel> break bind $canv <Alt-Shift-MouseWheel> {$canv xview scroll [scrollval 5*%D] units} + + bindall <TouchpadScroll> { + lassign [tk::PreciseScrollDeltas %D] deltaX deltaY + allcanvs yview scroll [precisescrollval $deltaY] units + } + bind $ctext <TouchpadScroll> { + lassign [tk::PreciseScrollDeltas %D] deltaX deltaY + $ctext yview scroll [precisescrollval $deltaY 2] units + $ctext xview scroll [precisescrollval $deltaX 2] units + } + bind $cflist <TouchpadScroll> { + lassign [tk::PreciseScrollDeltas %D] deltaX deltaY + $cflist yview scroll [precisescrollval $deltaY 2] units + } + bind $canv <TouchpadScroll> { + lassign [tk::PreciseScrollDeltas %D] deltaX deltaY + $canv xview scroll [precisescrollval $deltaX] units + allcanvs yview scroll [precisescrollval $deltaY] units + } } } @@ -12596,7 +12620,7 @@ set foundbgcolor yellow set currentsearchhitbgcolor orange # button for popping up context menus -if {[tk windowingsystem] eq "aqua"} { +if {[tk windowingsystem] eq "aqua" && [package vcompare $::tcl_version 8.7] < 0} { set ctxbut <Button-2> } else { set ctxbut <Button-3> @@ -1348,6 +1348,14 @@ void http_init(struct remote *remote, const char *url, int proactive_auth) if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) die("curl_global_init failed"); +#ifdef GIT_CURL_HAVE_GLOBAL_TRACE + { + const char *comp = getenv("GIT_TRACE_CURL_COMPONENTS"); + if (comp) + curl_global_trace(comp); + } +#endif + if (proactive_auth && http_proactive_auth == PROACTIVE_AUTH_NONE) http_proactive_auth = PROACTIVE_AUTH_IF_CREDENTIALS; @@ -272,7 +272,7 @@ static void strbuf_addstr_without_crud(struct strbuf *sb, const char *src) * can still be NULL if the input line only has the name/email part * (e.g. reading from a reflog entry). */ -int split_ident_line(struct ident_split *split, const char *line, int len) +int split_ident_line(struct ident_split *split, const char *line, size_t len) { const char *cp; size_t span; @@ -35,7 +35,7 @@ void reset_ident_date(void); * Signals an success with 0, but time part of the result may be NULL * if the input lacks timestamp and zone */ -int split_ident_line(struct ident_split *, const char *, int); +int split_ident_line(struct ident_split *, const char *, size_t); /* * Given a commit or tag object buffer and the commit or tag headers, replaces diff --git a/imap-send.c b/imap-send.c index 254ec83ab7..4bd5b8aa0d 100644 --- a/imap-send.c +++ b/imap-send.c @@ -1442,14 +1442,24 @@ static int count_messages(struct strbuf *all_msgs) while (1) { if (starts_with(p, "From ")) { - p = strstr(p+5, "\nFrom: "); - if (!p) break; - p = strstr(p+7, "\nDate: "); - if (!p) break; - p = strstr(p+7, "\nSubject: "); - if (!p) break; - p += 10; - count++; + if (starts_with(p, "From git-send-email")) { + p = strstr(p+5, "\nFrom: "); + if (!p) break; + p += 7; + p = strstr(p, "\nTo: "); + if (!p) break; + p += 5; + count++; + } else { + p = strstr(p+5, "\nFrom: "); + if (!p) break; + p = strstr(p+7, "\nDate: "); + if (!p) break; + p = strstr(p+7, "\nSubject: "); + if (!p) break; + p += 10; + count++; + } } p = strstr(p+5, "\nFrom "); if (!p) diff --git a/line-log.c b/line-log.c index 07f2154e84..8bd422148d 100644 --- a/line-log.c +++ b/line-log.c @@ -201,7 +201,7 @@ static void range_set_difference(struct range_set *out, * b: ------| */ j++; - if (j >= b->nr || end < b->ranges[j].start) { + if (j >= b->nr || end <= b->ranges[j].start) { /* * b exhausted, or * a: ----| @@ -408,7 +408,7 @@ static void diff_ranges_filter_touched(struct diff_ranges *out, assert(out->target.nr == 0); for (i = 0; i < diff->target.nr; i++) { - while (diff->target.ranges[i].start > rs->ranges[j].end) { + while (diff->target.ranges[i].start >= rs->ranges[j].end) { j++; if (j == rs->nr) return; @@ -939,9 +939,18 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang long t_cur = t_start; unsigned int j_last; + /* + * If a diff range touches multiple line ranges, then all + * those line ranges should be shown, so take a step back if + * the current line range is still in the previous diff range + * (even if only partially). + */ + if (j > 0 && diff->target.ranges[j-1].end > t_start) + j--; + while (j < diff->target.nr && diff->target.ranges[j].end < t_start) j++; - if (j == diff->target.nr || diff->target.ranges[j].start > t_end) + if (j == diff->target.nr || diff->target.ranges[j].start >= t_end) continue; /* Scan ahead to determine the last diff that falls in this range */ @@ -1087,13 +1096,6 @@ static struct diff_filepair *diff_filepair_dup(struct diff_filepair *pair) return new_filepair; } -static void free_diffqueues(int n, struct diff_queue_struct *dq) -{ - for (int i = 0; i < n; i++) - diff_queue_clear(&dq[i]); - free(dq); -} - static int process_all_files(struct line_log_data **range_out, struct rev_info *rev, struct diff_queue_struct *queue, @@ -1189,7 +1191,7 @@ static int process_ranges_ordinary_commit(struct rev_info *rev, struct commit *c struct line_log_data *range) { struct commit *parent = NULL; - struct diff_queue_struct queue; + struct diff_queue_struct queue = DIFF_QUEUE_INIT; struct line_log_data *parent_range; int changed; @@ -1209,9 +1211,7 @@ static int process_ranges_ordinary_commit(struct rev_info *rev, struct commit *c static int process_ranges_merge_commit(struct rev_info *rev, struct commit *commit, struct line_log_data *range) { - struct diff_queue_struct *diffqueues; struct line_log_data **cand; - struct commit **parents; struct commit_list *p; int i; int nparents = commit_list_count(commit->parents); @@ -1220,28 +1220,27 @@ static int process_ranges_merge_commit(struct rev_info *rev, struct commit *comm if (nparents > 1 && rev->first_parent_only) nparents = 1; - ALLOC_ARRAY(diffqueues, nparents); CALLOC_ARRAY(cand, nparents); - ALLOC_ARRAY(parents, nparents); - p = commit->parents; - for (i = 0; i < nparents; i++) { - parents[i] = p->item; - p = p->next; - queue_diffs(range, &rev->diffopt, &diffqueues[i], commit, parents[i]); - } - - for (i = 0; i < nparents; i++) { + for (p = commit->parents, i = 0; + p && i < nparents; + p = p->next, i++) { + struct commit *parent = p->item; + struct diff_queue_struct diffqueue = DIFF_QUEUE_INIT; int changed; - changed = process_all_files(&cand[i], rev, &diffqueues[i], range); + + queue_diffs(range, &rev->diffopt, &diffqueue, commit, parent); + + changed = process_all_files(&cand[i], rev, &diffqueue, range); + diff_queue_clear(&diffqueue); if (!changed) { /* * This parent can take all the blame, so we * don't follow any other path in history */ - add_line_range(rev, parents[i], cand[i]); + add_line_range(rev, parent, cand[i]); free_commit_list(commit->parents); - commit_list_append(parents[i], &commit->parents); + commit_list_append(parent, &commit->parents); ret = 0; goto out; @@ -1252,14 +1251,15 @@ static int process_ranges_merge_commit(struct rev_info *rev, struct commit *comm * No single parent took the blame. We add the candidates * from the above loop to the parents. */ - for (i = 0; i < nparents; i++) - add_line_range(rev, parents[i], cand[i]); + for (p = commit->parents, i = 0; + p && i < nparents; + p = p->next, i++) + add_line_range(rev, p->item, cand[i]); ret = 1; out: clear_commit_line_range(rev, commit); - free(parents); for (i = 0; i < nparents; i++) { if (!cand[i]) continue; @@ -1267,7 +1267,6 @@ out: free(cand[i]); } free(cand); - free_diffqueues(nparents, diffqueues); return ret; /* NEEDSWORK evil merge detection stuff */ @@ -1283,10 +1282,10 @@ int line_log_process_ranges_arbitrary_commit(struct rev_info *rev, struct commit struct line_log_data *prange = line_log_data_copy(range); add_line_range(rev, commit->parents->item, prange); clear_commit_line_range(rev, commit); - } else if (!commit->parents || !commit->parents->next) - changed = process_ranges_ordinary_commit(rev, commit, range); - else + } else if (commit->parents && commit->parents->next) changed = process_ranges_merge_commit(rev, commit, range); + else + changed = process_ranges_ordinary_commit(rev, commit, range); } if (!changed) diff --git a/list-objects-filter.c b/list-objects-filter.c index 7ecd4d9ef5..acd65ebb73 100644 --- a/list-objects-filter.c +++ b/list-objects-filter.c @@ -524,12 +524,11 @@ static void filter_sparse_oid__init( struct filter *filter) { struct filter_sparse_data *d = xcalloc(1, sizeof(*d)); - struct object_context oc; struct object_id sparse_oid; - if (get_oid_with_context(the_repository, - filter_options->sparse_oid_name, - GET_OID_BLOB, &sparse_oid, &oc)) + if (repo_get_oid_with_flags(the_repository, + filter_options->sparse_oid_name, + &sparse_oid, GET_OID_BLOB)) die(_("unable to access sparse blob in '%s'"), filter_options->sparse_oid_name); if (add_patterns_from_blob_to_list(&sparse_oid, "", 0, &d->pl) < 0) @@ -544,8 +543,6 @@ static void filter_sparse_oid__init( filter->filter_data = d; filter->filter_object_fn = filter_sparse; filter->free_fn = filter_sparse_free; - - object_context_release(&oc); } /* diff --git a/log-tree.c b/log-tree.c index 233bf9f227..73d21f7176 100644 --- a/log-tree.c +++ b/log-tree.c @@ -717,6 +717,7 @@ static void show_diff_of_diff(struct rev_info *opt) struct range_diff_options range_diff_opts = { .creation_factor = opt->creation_factor, .dual_color = 1, + .max_memory = RANGE_DIFF_MAX_MEMORY_DEFAULT, .diffopt = &opts }; diff --git a/merge-ort.c b/merge-ort.c index cd0cb4d1f0..29858074f9 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -316,9 +316,14 @@ struct merge_options_internal { * (e.g. "drivers/firmware/raspberrypi.c"). * * store all relevant paths in the repo, both directories and * files (e.g. drivers, drivers/firmware would also be included) - * * these keys serve to intern all the path strings, which allows - * us to do pointer comparison on directory names instead of - * strcmp; we just have to be careful to use the interned strings. + * * these keys serve to intern *all* path strings, which allows us + * to do pointer comparisons on file & directory names instead of + * using strcmp; however, for this pointer-comparison optimization + * to work, any code path that independently computes a path needs + * to check for it existing in this strmap, and if so, point to + * the path in this strmap instead of their computed copy. See + * the "reuse known pointer" comment in + * apply_directory_rename_modifications() for an example. * * The values of paths: * * either a pointer to a merged_info, or a conflict_info struct @@ -2163,7 +2168,7 @@ static int handle_content_merge(struct merge_options *opt, /* * FIXME: If opt->priv->call_depth && !clean, then we really * should not make result->mode match either a->mode or - * b->mode; that causes t6036 "check conflicting mode for + * b->mode; that causes t6416 "check conflicting mode for * regular file" to fail. It would be best to use some other * mode, but we'll confuse all kinds of stuff if we use one * where S_ISREG(result->mode) isn't true, and if we use @@ -2313,14 +2318,20 @@ static char *apply_dir_rename(struct strmap_entry *rename_info, return strbuf_detach(&new_path, NULL); } -static int path_in_way(struct strmap *paths, const char *path, unsigned side_mask) +static int path_in_way(struct strmap *paths, + const char *path, + unsigned side_mask, + struct diff_filepair *p) { struct merged_info *mi = strmap_get(paths, path); struct conflict_info *ci; if (!mi) return 0; INITIALIZE_CI(ci, mi); - return mi->clean || (side_mask & (ci->filemask | ci->dirmask)); + return mi->clean || (side_mask & (ci->filemask | ci->dirmask)) + /* See testcases 12[npq] of t6423 for this next condition */ + || ((ci->filemask & 0x01) && + strcmp(p->one->path, path)); } /* @@ -2332,6 +2343,7 @@ static int path_in_way(struct strmap *paths, const char *path, unsigned side_mas static char *handle_path_level_conflicts(struct merge_options *opt, const char *path, unsigned side_index, + struct diff_filepair *p, struct strmap_entry *rename_info, struct strmap *collisions) { @@ -2366,7 +2378,7 @@ static char *handle_path_level_conflicts(struct merge_options *opt, */ if (c_info->reported_already) { clean = 0; - } else if (path_in_way(&opt->priv->paths, new_path, 1 << side_index)) { + } else if (path_in_way(&opt->priv->paths, new_path, 1 << side_index, p)) { c_info->reported_already = 1; strbuf_add_separated_string_list(&collision_paths, ", ", &c_info->source_files); @@ -2520,7 +2532,7 @@ static void compute_collisions(struct strmap *collisions, * happening, and fall back to no-directory-rename detection * behavior for those paths. * - * See testcases 9e and all of section 5 from t6043 for examples. + * See testcases 9e and all of section 5 from t6423 for examples. */ for (i = 0; i < pairs->nr; ++i) { struct strmap_entry *rename_info; @@ -2573,6 +2585,7 @@ static void free_collisions(struct strmap *collisions) static char *check_for_directory_rename(struct merge_options *opt, const char *path, unsigned side_index, + struct diff_filepair *p, struct strmap *dir_renames, struct strmap *dir_rename_exclusions, struct strmap *collisions, @@ -2580,7 +2593,6 @@ static char *check_for_directory_rename(struct merge_options *opt, { char *new_path; struct strmap_entry *rename_info; - struct strmap_entry *otherinfo; const char *new_dir; int other_side = 3 - side_index; @@ -2615,14 +2627,13 @@ static char *check_for_directory_rename(struct merge_options *opt, * to not let Side1 do the rename to dumbdir, since we know that is * the source of one of our directory renames. * - * That's why otherinfo and dir_rename_exclusions is here. + * That's why dir_rename_exclusions is here. * * As it turns out, this also prevents N-way transient rename - * confusion; See testcases 9c and 9d of t6043. + * confusion; See testcases 9c and 9d of t6423. */ new_dir = rename_info->value; /* old_dir = rename_info->key; */ - otherinfo = strmap_get_entry(dir_rename_exclusions, new_dir); - if (otherinfo) { + if (strmap_contains(dir_rename_exclusions, new_dir)) { path_msg(opt, INFO_DIR_RENAME_SKIPPED_DUE_TO_RERENAME, 1, rename_info->key, path, new_dir, NULL, _("WARNING: Avoiding applying %s -> %s rename " @@ -2631,7 +2642,7 @@ static char *check_for_directory_rename(struct merge_options *opt, return NULL; } - new_path = handle_path_level_conflicts(opt, path, side_index, + new_path = handle_path_level_conflicts(opt, path, side_index, p, rename_info, &collisions[side_index]); *clean_merge &= (new_path != NULL); @@ -2876,6 +2887,20 @@ static int process_renames(struct merge_options *opt, } /* + * Directory renames can result in rename-to-self; the code + * below assumes we have A->B with different A & B, and tries + * to move all entries to path B. If A & B are the same path, + * the logic can get confused, so skip further processing when + * A & B are already the same path. + * + * As a reminder, we can avoid strcmp here because all paths + * are interned in opt->priv->paths; see the comment above + * "paths" in struct merge_options_internal. + */ + if (oldpath == newpath) + continue; + + /* * If pair->one->path isn't in opt->priv->paths, that means * that either directory rename detection removed that * path, or a parent directory of oldpath was resolved and @@ -3419,7 +3444,7 @@ static int collect_renames(struct merge_options *opt, } new_path = check_for_directory_rename(opt, p->two->path, - side_index, + side_index, p, dir_renames_for_side, rename_exclusions, collisions, diff --git a/meson.build b/meson.build index 5dd299b496..b3dfcc0497 100644 --- a/meson.build +++ b/meson.build @@ -607,6 +607,7 @@ builtin_sources = [ 'builtin/index-pack.c', 'builtin/init-db.c', 'builtin/interpret-trailers.c', + 'builtin/last-modified.c', 'builtin/log.c', 'builtin/ls-files.c', 'builtin/ls-remote.c', @@ -645,6 +646,7 @@ builtin_sources = [ 'builtin/repack.c', 'builtin/replace.c', 'builtin/replay.c', + 'builtin/repo.c', 'builtin/rerere.c', 'builtin/reset.c', 'builtin/rev-list.c', diff --git a/midx-write.c b/midx-write.c index a0aceab5e0..c73010df6d 100644 --- a/midx-write.c +++ b/midx-write.c @@ -1,5 +1,3 @@ -#define DISABLE_SIGN_COMPARE_WARNINGS - #include "git-compat-util.h" #include "abspath.h" #include "config.h" @@ -24,11 +22,12 @@ #define BITMAP_POS_UNKNOWN (~((uint32_t)0)) #define MIDX_CHUNK_FANOUT_SIZE (sizeof(uint32_t) * 256) #define MIDX_CHUNK_LARGE_OFFSET_WIDTH (sizeof(uint64_t)) +#define NO_PREFERRED_PACK (~((uint32_t)0)) extern int midx_checksum_valid(struct multi_pack_index *m); -extern void clear_midx_files_ext(const char *object_dir, const char *ext, +extern void clear_midx_files_ext(struct odb_source *source, const char *ext, const char *keep_hash); -extern void clear_incremental_midx_files_ext(const char *object_dir, +extern void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext, const char **keep_hashes, uint32_t hashes_nr); @@ -104,7 +103,7 @@ struct write_midx_context { unsigned large_offsets_needed:1; uint32_t num_large_offsets; - int preferred_pack_idx; + uint32_t preferred_pack_idx; int incremental; uint32_t num_multi_pack_indexes_before; @@ -112,6 +111,7 @@ struct write_midx_context { struct string_list *to_include; struct repository *repo; + struct odb_source *source; }; static int should_include_pack(const struct write_midx_context *ctx, @@ -260,7 +260,7 @@ static void midx_fanout_sort(struct midx_fanout *fanout) static void midx_fanout_add_midx_fanout(struct midx_fanout *fanout, struct multi_pack_index *m, uint32_t cur_fanout, - int preferred_pack) + uint32_t preferred_pack) { uint32_t start = m->num_objects_in_base, end; uint32_t cur_object; @@ -274,7 +274,7 @@ static void midx_fanout_add_midx_fanout(struct midx_fanout *fanout, end = m->num_objects_in_base + ntohl(m->chunk_oid_fanout[cur_fanout]); for (cur_object = start; cur_object < end; cur_object++) { - if ((preferred_pack > -1) && + if ((preferred_pack != NO_PREFERRED_PACK) && (preferred_pack == nth_midxed_pack_int_id(m, cur_object))) { /* * Objects from preferred packs are added @@ -364,7 +364,8 @@ static void compute_sorted_entries(struct write_midx_context *ctx, preferred, cur_fanout); } - if (-1 < ctx->preferred_pack_idx && ctx->preferred_pack_idx < start_pack) + if (ctx->preferred_pack_idx != NO_PREFERRED_PACK && + ctx->preferred_pack_idx < start_pack) midx_fanout_add_pack_fanout(&fanout, ctx->info, ctx->preferred_pack_idx, 1, cur_fanout); @@ -648,7 +649,6 @@ static uint32_t *midx_pack_order(struct write_midx_context *ctx) } static void write_midx_reverse_index(struct write_midx_context *ctx, - const char *object_dir, unsigned char *midx_hash) { struct strbuf buf = STRBUF_INIT; @@ -657,11 +657,10 @@ static void write_midx_reverse_index(struct write_midx_context *ctx, trace2_region_enter("midx", "write_midx_reverse_index", ctx->repo); if (ctx->incremental) - get_split_midx_filename_ext(ctx->repo->hash_algo, &buf, - object_dir, midx_hash, - MIDX_EXT_REV); + get_split_midx_filename_ext(ctx->source, &buf, + midx_hash, MIDX_EXT_REV); else - get_midx_filename_ext(ctx->repo->hash_algo, &buf, object_dir, + get_midx_filename_ext(ctx->source, &buf, midx_hash, MIDX_EXT_REV); tmp_file = write_rev_file_order(ctx->repo, NULL, ctx->pack_order, @@ -836,14 +835,13 @@ static struct commit **find_commits_for_midx_bitmap(uint32_t *indexed_commits_nr } static int write_midx_bitmap(struct write_midx_context *ctx, - const char *object_dir, const unsigned char *midx_hash, struct packing_data *pdata, struct commit **commits, uint32_t commits_nr, unsigned flags) { - int ret, i; + int ret; uint16_t options = 0; struct bitmap_writer writer; struct pack_idx_entry **index; @@ -852,12 +850,11 @@ static int write_midx_bitmap(struct write_midx_context *ctx, trace2_region_enter("midx", "write_midx_bitmap", ctx->repo); if (ctx->incremental) - get_split_midx_filename_ext(ctx->repo->hash_algo, &bitmap_name, - object_dir, midx_hash, - MIDX_EXT_BITMAP); + get_split_midx_filename_ext(ctx->source, &bitmap_name, + midx_hash, MIDX_EXT_BITMAP); else - get_midx_filename_ext(ctx->repo->hash_algo, &bitmap_name, - object_dir, midx_hash, MIDX_EXT_BITMAP); + get_midx_filename_ext(ctx->source, &bitmap_name, + midx_hash, MIDX_EXT_BITMAP); if (flags & MIDX_WRITE_BITMAP_HASH_CACHE) options |= BITMAP_OPT_HASH_CACHE; @@ -871,7 +868,7 @@ static int write_midx_bitmap(struct write_midx_context *ctx, * this order). */ ALLOC_ARRAY(index, pdata->nr_objects); - for (i = 0; i < pdata->nr_objects; i++) + for (uint32_t i = 0; i < pdata->nr_objects; i++) index[i] = &pdata->objects[i].idx; bitmap_writer_init(&writer, ctx->repo, pdata, @@ -892,7 +889,7 @@ static int write_midx_bitmap(struct write_midx_context *ctx, * happens between bitmap_writer_build_type_index() and * bitmap_writer_finish(). */ - for (i = 0; i < pdata->nr_objects; i++) + for (uint32_t i = 0; i < pdata->nr_objects; i++) index[ctx->pack_order[i]] = &pdata->objects[i].idx; bitmap_writer_select_commits(&writer, commits, commits_nr); @@ -913,15 +910,7 @@ cleanup: return ret; } -static struct multi_pack_index *lookup_multi_pack_index(struct repository *r, - const char *object_dir) -{ - struct odb_source *source = odb_find_source(r->objects, object_dir); - return get_multi_pack_index(source); -} - -static int fill_packs_from_midx(struct write_midx_context *ctx, - const char *preferred_pack_name, uint32_t flags) +static int fill_packs_from_midx(struct write_midx_context *ctx) { struct multi_pack_index *m; @@ -929,30 +918,10 @@ static int fill_packs_from_midx(struct write_midx_context *ctx, uint32_t i; for (i = 0; i < m->num_packs; i++) { - ALLOC_GROW(ctx->info, ctx->nr + 1, ctx->alloc); - - /* - * If generating a reverse index, need to have - * packed_git's loaded to compare their - * mtimes and object count. - * - * If a preferred pack is specified, need to - * have packed_git's loaded to ensure the chosen - * preferred pack has a non-zero object count. - */ - if (flags & MIDX_WRITE_REV_INDEX || - preferred_pack_name) { - if (prepare_midx_pack(ctx->repo, m, - m->num_packs_in_base + i)) { - error(_("could not load pack")); - return 1; - } - - if (open_pack_index(m->packs[i])) - die(_("could not open index for %s"), - m->packs[i]->pack_name); - } + if (prepare_midx_pack(m, m->num_packs_in_base + i)) + return error(_("could not load pack")); + ALLOC_GROW(ctx->info, ctx->nr + 1, ctx->alloc); fill_pack_info(&ctx->info[ctx->nr++], m->packs[i], m->pack_names[i], m->num_packs_in_base + i); @@ -989,10 +958,9 @@ static int link_midx_to_chain(struct multi_pack_index *m) for (i = 0; i < ARRAY_SIZE(midx_exts); i++) { const unsigned char *hash = get_midx_checksum(m); - get_midx_filename_ext(m->repo->hash_algo, &from, m->object_dir, + get_midx_filename_ext(m->source, &from, hash, midx_exts[i].non_split); - get_split_midx_filename_ext(m->repo->hash_algo, &to, - m->object_dir, hash, + get_split_midx_filename_ext(m->source, &to, hash, midx_exts[i].split); if (link(from.buf, to.buf) < 0 && errno != ENOENT) { @@ -1011,7 +979,7 @@ done: return ret; } -static void clear_midx_files(struct repository *r, const char *object_dir, +static void clear_midx_files(struct odb_source *source, const char **hashes, uint32_t hashes_nr, unsigned incremental) { @@ -1030,16 +998,16 @@ static void clear_midx_files(struct repository *r, const char *object_dir, uint32_t i, j; for (i = 0; i < ARRAY_SIZE(exts); i++) { - clear_incremental_midx_files_ext(object_dir, exts[i], + clear_incremental_midx_files_ext(source, exts[i], hashes, hashes_nr); for (j = 0; j < hashes_nr; j++) - clear_midx_files_ext(object_dir, exts[i], hashes[j]); + clear_midx_files_ext(source, exts[i], hashes[j]); } if (incremental) - get_midx_filename(r->hash_algo, &buf, object_dir); + get_midx_filename(source, &buf); else - get_midx_chain_filename(&buf, object_dir); + get_midx_chain_filename(source, &buf); if (unlink(buf.buf) && errno != ENOENT) die_errno(_("failed to clear multi-pack-index at %s"), buf.buf); @@ -1047,45 +1015,49 @@ static void clear_midx_files(struct repository *r, const char *object_dir, strbuf_release(&buf); } -static int write_midx_internal(struct repository *r, const char *object_dir, +static int write_midx_internal(struct odb_source *source, struct string_list *packs_to_include, struct string_list *packs_to_drop, const char *preferred_pack_name, const char *refs_snapshot, unsigned flags) { + struct repository *r = source->odb->repo; struct strbuf midx_name = STRBUF_INIT; unsigned char midx_hash[GIT_MAX_RAWSZ]; - uint32_t i, start_pack; + uint32_t start_pack; struct hashfile *f = NULL; struct lock_file lk; struct tempfile *incr; - struct write_midx_context ctx = { 0 }; + struct write_midx_context ctx = { + .preferred_pack_idx = NO_PREFERRED_PACK, + }; int bitmapped_packs_concat_len = 0; int pack_name_concat_len = 0; int dropped_packs = 0; - int result = 0; + int result = -1; const char **keep_hashes = NULL; struct chunkfile *cf; trace2_region_enter("midx", "write_midx_internal", r); ctx.repo = r; + ctx.source = source; ctx.incremental = !!(flags & MIDX_WRITE_INCREMENTAL); if (ctx.incremental) strbuf_addf(&midx_name, "%s/pack/multi-pack-index.d/tmp_midx_XXXXXX", - object_dir); + source->path); else - get_midx_filename(r->hash_algo, &midx_name, object_dir); + get_midx_filename(source, &midx_name); if (safe_create_leading_directories(r, midx_name.buf)) die_errno(_("unable to create leading directories of %s"), midx_name.buf); if (!packs_to_include || ctx.incremental) { - struct multi_pack_index *m = lookup_multi_pack_index(r, object_dir); + struct multi_pack_index *m = get_multi_pack_index(source); if (m && !midx_checksum_valid(m)) { warning(_("ignoring existing multi-pack-index; checksum mismatch")); m = NULL; @@ -1116,15 +1088,13 @@ static int write_midx_internal(struct repository *r, const char *object_dir, if (flags & MIDX_WRITE_BITMAP && load_midx_revindex(m)) { error(_("could not load reverse index for MIDX %s"), hash_to_hex_algop(get_midx_checksum(m), - m->repo->hash_algo)); - result = 1; + m->source->odb->repo->hash_algo)); goto cleanup; } ctx.num_multi_pack_indexes_before++; m = m->base_midx; } - } else if (ctx.m && fill_packs_from_midx(&ctx, preferred_pack_name, - flags) < 0) { + } else if (ctx.m && fill_packs_from_midx(&ctx)) { goto cleanup; } @@ -1139,7 +1109,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir, ctx.to_include = packs_to_include; - for_each_file_in_pack_dir(object_dir, add_pack_to_midx, &ctx); + for_each_file_in_pack_dir(source->path, add_pack_to_midx, &ctx); stop_progress(&ctx.progress); if ((ctx.m && ctx.nr == ctx.m->num_packs + ctx.m->num_packs_in_base) && @@ -1159,18 +1129,21 @@ static int write_midx_internal(struct repository *r, const char *object_dir, * corresponding bitmap (or one wasn't requested). */ if (!want_bitmap) - clear_midx_files_ext(object_dir, "bitmap", NULL); + clear_midx_files_ext(source, "bitmap", NULL); + result = 0; goto cleanup; } } - if (ctx.incremental && !ctx.nr) + if (ctx.incremental && !ctx.nr) { + result = 0; goto cleanup; /* nothing to do */ + } if (preferred_pack_name) { - ctx.preferred_pack_idx = -1; + ctx.preferred_pack_idx = NO_PREFERRED_PACK; - for (i = 0; i < ctx.nr; i++) { + for (size_t i = 0; i < ctx.nr; i++) { if (!cmp_idx_or_pack_name(preferred_pack_name, ctx.info[i].pack_name)) { ctx.preferred_pack_idx = i; @@ -1178,14 +1151,21 @@ static int write_midx_internal(struct repository *r, const char *object_dir, } } - if (ctx.preferred_pack_idx == -1) + if (ctx.preferred_pack_idx == NO_PREFERRED_PACK) warning(_("unknown preferred pack: '%s'"), preferred_pack_name); } else if (ctx.nr && (flags & (MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP))) { - struct packed_git *oldest = ctx.info[ctx.preferred_pack_idx].p; + struct packed_git *oldest = ctx.info[0].p; ctx.preferred_pack_idx = 0; + /* + * Attempt opening the pack index to populate num_objects. + * Ignore failiures as they can be expected and are not + * fatal during this selection time. + */ + open_pack_index(oldest); + if (packs_to_drop && packs_to_drop->nr) BUG("cannot write a MIDX bitmap during expiration"); @@ -1195,11 +1175,12 @@ static int write_midx_internal(struct repository *r, const char *object_dir, * pack-order has all of its objects selected from that pack * (and not another pack containing a duplicate) */ - for (i = 1; i < ctx.nr; i++) { + for (size_t i = 1; i < ctx.nr; i++) { struct packed_git *p = ctx.info[i].p; if (!oldest->num_objects || p->mtime < oldest->mtime) { oldest = p; + open_pack_index(oldest); ctx.preferred_pack_idx = i; } } @@ -1211,22 +1192,26 @@ static int write_midx_internal(struct repository *r, const char *object_dir, * objects to resolve, so the preferred value doesn't * matter. */ - ctx.preferred_pack_idx = -1; + ctx.preferred_pack_idx = NO_PREFERRED_PACK; } } else { /* * otherwise don't mark any pack as preferred to avoid * interfering with expiration logic below */ - ctx.preferred_pack_idx = -1; + ctx.preferred_pack_idx = NO_PREFERRED_PACK; } - if (ctx.preferred_pack_idx > -1) { + if (ctx.preferred_pack_idx != NO_PREFERRED_PACK) { struct packed_git *preferred = ctx.info[ctx.preferred_pack_idx].p; + + if (open_pack_index(preferred)) + die(_("failed to open preferred pack %s"), + ctx.info[ctx.preferred_pack_idx].pack_name); + if (!preferred->num_objects) { error(_("cannot select preferred pack %s with no objects"), preferred->pack_name); - result = 1; goto cleanup; } } @@ -1234,7 +1219,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir, compute_sorted_entries(&ctx, start_pack); ctx.large_offsets_needed = 0; - for (i = 0; i < ctx.entries_nr; i++) { + for (size_t i = 0; i < ctx.entries_nr; i++) { if (ctx.entries[i].offset > 0x7fffffff) ctx.num_large_offsets++; if (ctx.entries[i].offset > 0xffffffff) @@ -1244,10 +1229,10 @@ static int write_midx_internal(struct repository *r, const char *object_dir, QSORT(ctx.info, ctx.nr, pack_info_compare); if (packs_to_drop && packs_to_drop->nr) { - int drop_index = 0; + size_t drop_index = 0; int missing_drops = 0; - for (i = 0; i < ctx.nr && drop_index < packs_to_drop->nr; i++) { + for (size_t 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); @@ -1265,10 +1250,8 @@ static int write_midx_internal(struct repository *r, const char *object_dir, } } - if (missing_drops) { - result = 1; + if (missing_drops) goto cleanup; - } } /* @@ -1278,7 +1261,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir, * pack_perm[old_id] = new_id */ ALLOC_ARRAY(ctx.pack_perm, ctx.nr); - for (i = 0; i < ctx.nr; i++) { + for (size_t i = 0; i < ctx.nr; i++) { if (ctx.info[i].expired) { dropped_packs++; ctx.pack_perm[ctx.info[i].orig_pack_int_id] = PACK_EXPIRED; @@ -1287,7 +1270,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir, } } - for (i = 0; i < ctx.nr; i++) { + for (size_t i = 0; i < ctx.nr; i++) { if (ctx.info[i].expired) continue; pack_name_concat_len += strlen(ctx.info[i].pack_name) + 1; @@ -1314,7 +1297,6 @@ static int write_midx_internal(struct repository *r, const char *object_dir, if (ctx.nr - dropped_packs == 0) { error(_("no pack files to index.")); - result = 1; goto cleanup; } @@ -1327,20 +1309,20 @@ static int write_midx_internal(struct repository *r, const char *object_dir, if (ctx.incremental) { struct strbuf lock_name = STRBUF_INIT; - get_midx_chain_filename(&lock_name, object_dir); + get_midx_chain_filename(source, &lock_name); hold_lock_file_for_update(&lk, lock_name.buf, LOCK_DIE_ON_ERROR); strbuf_release(&lock_name); incr = mks_tempfile_m(midx_name.buf, 0444); if (!incr) { error(_("unable to create temporary MIDX layer")); - return -1; + goto cleanup; } if (adjust_shared_perm(r, get_tempfile_path(incr))) { error(_("unable to adjust shared permissions for '%s'"), get_tempfile_path(incr)); - return -1; + goto cleanup; } f = hashfd(r->hash_algo, get_tempfile_fd(incr), @@ -1390,7 +1372,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir, if (flags & MIDX_WRITE_REV_INDEX && git_env_bool("GIT_TEST_MIDX_WRITE_REV", 0)) - write_midx_reverse_index(&ctx, object_dir, midx_hash); + write_midx_reverse_index(&ctx, midx_hash); if (flags & MIDX_WRITE_BITMAP) { struct packing_data pdata; @@ -1413,11 +1395,10 @@ static int write_midx_internal(struct repository *r, const char *object_dir, FREE_AND_NULL(ctx.entries); ctx.entries_nr = 0; - if (write_midx_bitmap(&ctx, object_dir, + if (write_midx_bitmap(&ctx, midx_hash, &pdata, commits, commits_nr, flags) < 0) { error(_("could not write multi-pack bitmap")); - result = 1; clear_packing_data(&pdata); free(commits); goto cleanup; @@ -1431,6 +1412,9 @@ static int write_midx_internal(struct repository *r, const char *object_dir, * have been freed in the previous if block. */ + if (ctx.num_multi_pack_indexes_before == UINT32_MAX) + die(_("too many multi-pack-indexes")); + CALLOC_ARRAY(keep_hashes, ctx.num_multi_pack_indexes_before + 1); if (ctx.incremental) { @@ -1440,18 +1424,18 @@ static int write_midx_internal(struct repository *r, const char *object_dir, if (!chainf) { error_errno(_("unable to open multi-pack-index chain file")); - return -1; + goto cleanup; } if (link_midx_to_chain(ctx.base_midx) < 0) - return -1; + goto cleanup; - get_split_midx_filename_ext(r->hash_algo, &final_midx_name, - object_dir, midx_hash, MIDX_EXT_MIDX); + get_split_midx_filename_ext(source, &final_midx_name, + midx_hash, MIDX_EXT_MIDX); if (rename_tempfile(&incr, final_midx_name.buf) < 0) { error_errno(_("unable to rename new multi-pack-index layer")); - return -1; + goto cleanup; } strbuf_release(&final_midx_name); @@ -1459,7 +1443,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir, keep_hashes[ctx.num_multi_pack_indexes_before] = xstrdup(hash_to_hex_algop(midx_hash, r->hash_algo)); - for (i = 0; i < ctx.num_multi_pack_indexes_before; i++) { + for (uint32_t i = 0; i < ctx.num_multi_pack_indexes_before; i++) { uint32_t j = ctx.num_multi_pack_indexes_before - i - 1; keep_hashes[j] = xstrdup(hash_to_hex_algop(get_midx_checksum(m), @@ -1467,7 +1451,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir, m = m->base_midx; } - for (i = 0; i < ctx.num_multi_pack_indexes_before + 1; i++) + for (uint32_t i = 0; i <= ctx.num_multi_pack_indexes_before; i++) fprintf(get_lock_file_fp(&lk), "%s\n", keep_hashes[i]); } else { keep_hashes[ctx.num_multi_pack_indexes_before] = @@ -1480,12 +1464,13 @@ static int write_midx_internal(struct repository *r, const char *object_dir, if (commit_lock_file(&lk) < 0) die_errno(_("could not write multi-pack-index")); - clear_midx_files(r, object_dir, keep_hashes, + clear_midx_files(source, keep_hashes, ctx.num_multi_pack_indexes_before + 1, ctx.incremental); + result = 0; cleanup: - for (i = 0; i < ctx.nr; i++) { + for (size_t i = 0; i < ctx.nr; i++) { if (ctx.info[i].p) { close_pack(ctx.info[i].p); free(ctx.info[i].p); @@ -1498,7 +1483,7 @@ cleanup: free(ctx.pack_perm); free(ctx.pack_order); if (keep_hashes) { - for (i = 0; i < ctx.num_multi_pack_indexes_before + 1; i++) + for (uint32_t i = 0; i <= ctx.num_multi_pack_indexes_before; i++) free((char *)keep_hashes[i]); free(keep_hashes); } @@ -1509,29 +1494,29 @@ cleanup: return result; } -int write_midx_file(struct repository *r, const char *object_dir, +int write_midx_file(struct odb_source *source, const char *preferred_pack_name, const char *refs_snapshot, unsigned flags) { - return write_midx_internal(r, object_dir, NULL, NULL, + return write_midx_internal(source, NULL, NULL, preferred_pack_name, refs_snapshot, flags); } -int write_midx_file_only(struct repository *r, const char *object_dir, +int write_midx_file_only(struct odb_source *source, struct string_list *packs_to_include, const char *preferred_pack_name, const char *refs_snapshot, unsigned flags) { - return write_midx_internal(r, object_dir, packs_to_include, NULL, + return write_midx_internal(source, packs_to_include, NULL, preferred_pack_name, refs_snapshot, flags); } -int expire_midx_packs(struct repository *r, const char *object_dir, unsigned flags) +int expire_midx_packs(struct odb_source *source, unsigned flags) { uint32_t i, *count, result = 0; struct string_list packs_to_drop = STRING_LIST_INIT_DUP; - struct multi_pack_index *m = lookup_multi_pack_index(r, object_dir); + struct multi_pack_index *m = get_multi_pack_index(source); struct progress *progress = NULL; if (!m) @@ -1544,7 +1529,7 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned fla if (flags & MIDX_PROGRESS) progress = start_delayed_progress( - r, + source->odb->repo, _("Counting referenced objects"), m->num_objects); for (i = 0; i < m->num_objects; i++) { @@ -1556,7 +1541,7 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned fla if (flags & MIDX_PROGRESS) progress = start_delayed_progress( - r, + source->odb->repo, _("Finding and deleting unreferenced packfiles"), m->num_packs); for (i = 0; i < m->num_packs; i++) { @@ -1566,7 +1551,7 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned fla if (count[i]) continue; - if (prepare_midx_pack(r, m, i)) + if (prepare_midx_pack(m, i)) continue; if (m->packs[i]->pack_keep || m->packs[i]->is_cruft) @@ -1584,7 +1569,7 @@ int expire_midx_packs(struct repository *r, const char *object_dir, unsigned fla free(count); if (packs_to_drop.nr) - result = write_midx_internal(r, object_dir, NULL, + result = write_midx_internal(source, NULL, &packs_to_drop, NULL, NULL, flags); string_list_clear(&packs_to_drop, 0); @@ -1612,13 +1597,12 @@ static int compare_by_mtime(const void *a_, const void *b_) return 0; } -static int want_included_pack(struct repository *r, - struct multi_pack_index *m, +static int want_included_pack(struct multi_pack_index *m, int pack_kept_objects, uint32_t pack_int_id) { struct packed_git *p; - if (prepare_midx_pack(r, m, pack_int_id)) + if (prepare_midx_pack(m, pack_int_id)) return 0; p = m->packs[pack_int_id]; if (!pack_kept_objects && p->pack_keep) @@ -1640,7 +1624,7 @@ static void fill_included_packs_all(struct repository *r, repo_config_get_bool(r, "repack.packkeptobjects", &pack_kept_objects); for (i = 0; i < m->num_packs; i++) { - if (!want_included_pack(r, m, pack_kept_objects, i)) + if (!want_included_pack(m, pack_kept_objects, i)) continue; include_pack[i] = 1; @@ -1664,7 +1648,7 @@ static void fill_included_packs_batch(struct repository *r, for (i = 0; i < m->num_packs; i++) { pack_info[i].pack_int_id = i; - if (prepare_midx_pack(r, m, i)) + if (prepare_midx_pack(m, i)) continue; pack_info[i].mtime = m->packs[i]->mtime; @@ -1683,7 +1667,7 @@ static void fill_included_packs_batch(struct repository *r, struct packed_git *p = m->packs[pack_int_id]; uint64_t expected_size; - if (!want_included_pack(r, m, pack_kept_objects, pack_int_id)) + if (!want_included_pack(m, pack_kept_objects, pack_int_id)) continue; /* @@ -1710,14 +1694,15 @@ static void fill_included_packs_batch(struct repository *r, free(pack_info); } -int midx_repack(struct repository *r, const char *object_dir, size_t batch_size, unsigned flags) +int midx_repack(struct odb_source *source, size_t batch_size, unsigned flags) { + struct repository *r = source->odb->repo; int result = 0; uint32_t i, packs_to_repack = 0; unsigned char *include_pack; struct child_process cmd = CHILD_PROCESS_INIT; FILE *cmd_in; - struct multi_pack_index *m = lookup_multi_pack_index(r, object_dir); + struct multi_pack_index *m = get_multi_pack_index(source); /* * When updating the default for these configuration @@ -1751,7 +1736,7 @@ int midx_repack(struct repository *r, const char *object_dir, size_t batch_size, strvec_push(&cmd.args, "pack-objects"); - strvec_pushf(&cmd.args, "%s/pack/pack", object_dir); + strvec_pushf(&cmd.args, "%s/pack/pack", source->path); if (delta_base_offset) strvec_push(&cmd.args, "--delta-base-offset"); @@ -1792,7 +1777,7 @@ int midx_repack(struct repository *r, const char *object_dir, size_t batch_size, goto cleanup; } - result = write_midx_internal(r, object_dir, NULL, NULL, NULL, NULL, + result = write_midx_internal(source, NULL, NULL, NULL, NULL, flags); cleanup: @@ -16,9 +16,9 @@ #define MIDX_PACK_ERROR ((void *)(intptr_t)-1) int midx_checksum_valid(struct multi_pack_index *m); -void clear_midx_files_ext(const char *object_dir, const char *ext, +void clear_midx_files_ext(struct odb_source *source, const char *ext, const char *keep_hash); -void clear_incremental_midx_files_ext(const char *object_dir, const char *ext, +void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext, char **keep_hashes, uint32_t hashes_nr); int cmp_idx_or_pack_name(const char *idx_or_pack_name, @@ -26,22 +26,20 @@ int cmp_idx_or_pack_name(const char *idx_or_pack_name, const unsigned char *get_midx_checksum(struct multi_pack_index *m) { - return m->data + m->data_len - m->repo->hash_algo->rawsz; + return m->data + m->data_len - m->source->odb->repo->hash_algo->rawsz; } -void get_midx_filename(const struct git_hash_algo *hash_algo, - struct strbuf *out, const char *object_dir) +void get_midx_filename(struct odb_source *source, struct strbuf *out) { - get_midx_filename_ext(hash_algo, out, object_dir, NULL, NULL); + get_midx_filename_ext(source, out, NULL, NULL); } -void get_midx_filename_ext(const struct git_hash_algo *hash_algo, - struct strbuf *out, const char *object_dir, +void get_midx_filename_ext(struct odb_source *source, struct strbuf *out, const unsigned char *hash, const char *ext) { - strbuf_addf(out, "%s/pack/multi-pack-index", object_dir); + strbuf_addf(out, "%s/pack/multi-pack-index", source->path); if (ext) - strbuf_addf(out, "-%s.%s", hash_to_hex_algop(hash, hash_algo), ext); + strbuf_addf(out, "-%s.%s", hash_to_hex_algop(hash, source->odb->repo->hash_algo), ext); } static int midx_read_oid_fanout(const unsigned char *chunk_start, @@ -95,11 +93,10 @@ static int midx_read_object_offsets(const unsigned char *chunk_start, return 0; } -static struct multi_pack_index *load_multi_pack_index_one(struct repository *r, - const char *object_dir, - const char *midx_name, - int local) +static struct multi_pack_index *load_multi_pack_index_one(struct odb_source *source, + const char *midx_name) { + struct repository *r = source->odb->repo; struct multi_pack_index *m = NULL; int fd; struct stat st; @@ -129,11 +126,10 @@ static struct multi_pack_index *load_multi_pack_index_one(struct repository *r, midx_map = xmmap(NULL, midx_size, PROT_READ, MAP_PRIVATE, fd, 0); close(fd); - FLEX_ALLOC_STR(m, object_dir, object_dir); + CALLOC_ARRAY(m, 1); m->data = midx_map; m->data_len = midx_size; - m->local = local; - m->repo = r; + m->source = source; m->signature = get_be32(m->data); if (m->signature != MIDX_SIGNATURE) @@ -224,24 +220,23 @@ cleanup_fail: return NULL; } -void get_midx_chain_dirname(struct strbuf *buf, const char *object_dir) +void get_midx_chain_dirname(struct odb_source *source, struct strbuf *buf) { - strbuf_addf(buf, "%s/pack/multi-pack-index.d", object_dir); + strbuf_addf(buf, "%s/pack/multi-pack-index.d", source->path); } -void get_midx_chain_filename(struct strbuf *buf, const char *object_dir) +void get_midx_chain_filename(struct odb_source *source, struct strbuf *buf) { - get_midx_chain_dirname(buf, object_dir); + get_midx_chain_dirname(source, buf); strbuf_addstr(buf, "/multi-pack-index-chain"); } -void get_split_midx_filename_ext(const struct git_hash_algo *hash_algo, - struct strbuf *buf, const char *object_dir, +void get_split_midx_filename_ext(struct odb_source *source, struct strbuf *buf, const unsigned char *hash, const char *ext) { - get_midx_chain_dirname(buf, object_dir); + get_midx_chain_dirname(source, buf); strbuf_addf(buf, "/multi-pack-index-%s.%s", - hash_to_hex_algop(hash, hash_algo), ext); + hash_to_hex_algop(hash, source->odb->repo->hash_algo), ext); } static int open_multi_pack_index_chain(const struct git_hash_algo *hash_algo, @@ -297,19 +292,18 @@ static int add_midx_to_chain(struct multi_pack_index *midx, return 1; } -static struct multi_pack_index *load_midx_chain_fd_st(struct repository *r, - const char *object_dir, - int local, +static struct multi_pack_index *load_midx_chain_fd_st(struct odb_source *source, int fd, struct stat *st, int *incomplete_chain) { + const struct git_hash_algo *hash_algo = source->odb->repo->hash_algo; struct multi_pack_index *midx_chain = NULL; struct strbuf buf = STRBUF_INIT; int valid = 1; uint32_t i, count; FILE *fp = xfdopen(fd, "r"); - count = st->st_size / (r->hash_algo->hexsz + 1); + count = st->st_size / (hash_algo->hexsz + 1); for (i = 0; i < count; i++) { struct multi_pack_index *m; @@ -318,7 +312,7 @@ static struct multi_pack_index *load_midx_chain_fd_st(struct repository *r, if (strbuf_getline_lf(&buf, fp) == EOF) break; - if (get_oid_hex_algop(buf.buf, &layer, r->hash_algo)) { + if (get_oid_hex_algop(buf.buf, &layer, hash_algo)) { warning(_("invalid multi-pack-index chain: line '%s' " "not a hash"), buf.buf); @@ -329,9 +323,9 @@ static struct multi_pack_index *load_midx_chain_fd_st(struct repository *r, valid = 0; strbuf_reset(&buf); - get_split_midx_filename_ext(r->hash_algo, &buf, object_dir, + get_split_midx_filename_ext(source, &buf, layer.hash, MIDX_EXT_MIDX); - m = load_multi_pack_index_one(r, object_dir, buf.buf, local); + m = load_multi_pack_index_one(source, buf.buf); if (m) { if (add_midx_to_chain(m, midx_chain)) { @@ -354,40 +348,34 @@ static struct multi_pack_index *load_midx_chain_fd_st(struct repository *r, return midx_chain; } -static struct multi_pack_index *load_multi_pack_index_chain(struct repository *r, - const char *object_dir, - int local) +static struct multi_pack_index *load_multi_pack_index_chain(struct odb_source *source) { struct strbuf chain_file = STRBUF_INIT; struct stat st; int fd; struct multi_pack_index *m = NULL; - get_midx_chain_filename(&chain_file, object_dir); - if (open_multi_pack_index_chain(r->hash_algo, chain_file.buf, &fd, &st)) { + get_midx_chain_filename(source, &chain_file); + if (open_multi_pack_index_chain(source->odb->repo->hash_algo, chain_file.buf, &fd, &st)) { int incomplete; /* ownership of fd is taken over by load function */ - m = load_midx_chain_fd_st(r, object_dir, local, fd, &st, - &incomplete); + m = load_midx_chain_fd_st(source, fd, &st, &incomplete); } strbuf_release(&chain_file); return m; } -struct multi_pack_index *load_multi_pack_index(struct repository *r, - const char *object_dir, - int local) +struct multi_pack_index *load_multi_pack_index(struct odb_source *source) { struct strbuf midx_name = STRBUF_INIT; struct multi_pack_index *m; - get_midx_filename(r->hash_algo, &midx_name, object_dir); + get_midx_filename(source, &midx_name); - m = load_multi_pack_index_one(r, object_dir, - midx_name.buf, local); + m = load_multi_pack_index_one(source, midx_name.buf); if (!m) - m = load_multi_pack_index_chain(r, object_dir, local); + m = load_multi_pack_index_chain(source); strbuf_release(&midx_name); @@ -450,9 +438,10 @@ static uint32_t midx_for_pack(struct multi_pack_index **_m, return pack_int_id - m->num_packs_in_base; } -int prepare_midx_pack(struct repository *r, struct multi_pack_index *m, +int prepare_midx_pack(struct multi_pack_index *m, uint32_t pack_int_id) { + struct repository *r = m->source->odb->repo; struct strbuf pack_name = STRBUF_INIT; struct strbuf key = STRBUF_INIT; struct packed_git *p; @@ -464,7 +453,7 @@ int prepare_midx_pack(struct repository *r, struct multi_pack_index *m, if (m->packs[pack_int_id]) return 0; - strbuf_addf(&pack_name, "%s/pack/%s", m->object_dir, + strbuf_addf(&pack_name, "%s/pack/%s", m->source->path, m->pack_names[pack_int_id]); /* pack_map holds the ".pack" name, but we have the .idx */ @@ -475,7 +464,8 @@ int prepare_midx_pack(struct repository *r, struct multi_pack_index *m, strhash(key.buf), key.buf, struct packed_git, packmap_ent); if (!p) { - p = add_packed_git(r, pack_name.buf, pack_name.len, m->local); + p = add_packed_git(r, pack_name.buf, pack_name.len, + m->source->local); if (p) { install_packed_git(r, p); list_add_tail(&p->mru, &r->objects->packed_git_mru); @@ -507,7 +497,7 @@ struct packed_git *nth_midxed_pack(struct multi_pack_index *m, #define MIDX_CHUNK_BITMAPPED_PACKS_WIDTH (2 * sizeof(uint32_t)) -int nth_bitmapped_pack(struct repository *r, struct multi_pack_index *m, +int nth_bitmapped_pack(struct multi_pack_index *m, struct bitmapped_pack *bp, uint32_t pack_int_id) { uint32_t local_pack_int_id = midx_for_pack(&m, pack_int_id); @@ -515,7 +505,7 @@ int nth_bitmapped_pack(struct repository *r, struct multi_pack_index *m, if (!m->chunk_bitmapped_packs) return error(_("MIDX does not contain the BTMP chunk")); - if (prepare_midx_pack(r, m, pack_int_id)) + if (prepare_midx_pack(m, pack_int_id)) return error(_("could not load bitmapped pack %"PRIu32), pack_int_id); bp->p = m->packs[local_pack_int_id]; @@ -534,7 +524,8 @@ int bsearch_one_midx(const struct object_id *oid, struct multi_pack_index *m, uint32_t *result) { int ret = bsearch_hash(oid->hash, m->chunk_oid_fanout, - m->chunk_oid_lookup, m->repo->hash_algo->rawsz, + m->chunk_oid_lookup, + m->source->odb->repo->hash_algo->rawsz, result); if (result) *result += m->num_objects_in_base; @@ -565,7 +556,7 @@ struct object_id *nth_midxed_object_oid(struct object_id *oid, n = midx_for_object(&m, n); oidread(oid, m->chunk_oid_lookup + st_mult(m->hash_len, n), - m->repo->hash_algo); + m->source->odb->repo->hash_algo); return oid; } @@ -600,10 +591,9 @@ uint32_t nth_midxed_pack_int_id(struct multi_pack_index *m, uint32_t pos) (off_t)pos * MIDX_CHUNK_OFFSET_WIDTH); } -int fill_midx_entry(struct repository *r, +int fill_midx_entry(struct multi_pack_index *m, const struct object_id *oid, - struct pack_entry *e, - struct multi_pack_index *m) + struct pack_entry *e) { uint32_t pos; uint32_t pack_int_id; @@ -615,7 +605,7 @@ int fill_midx_entry(struct repository *r, midx_for_object(&m, pos); pack_int_id = nth_midxed_pack_int_id(m, pos); - if (prepare_midx_pack(r, m, pack_int_id)) + if (prepare_midx_pack(m, pack_int_id)) return 0; p = m->packs[pack_int_id - m->num_packs_in_base]; @@ -723,7 +713,7 @@ int midx_preferred_pack(struct multi_pack_index *m, uint32_t *pack_int_id) return 0; } -int prepare_multi_pack_index_one(struct odb_source *source, int local) +int prepare_multi_pack_index_one(struct odb_source *source) { struct repository *r = source->odb->repo; @@ -734,14 +724,14 @@ int prepare_multi_pack_index_one(struct odb_source *source, int local) if (source->midx) return 1; - source->midx = load_multi_pack_index(r, source->path, local); + source->midx = load_multi_pack_index(source); return !!source->midx; } int midx_checksum_valid(struct multi_pack_index *m) { - return hashfile_checksum_valid(m->repo->hash_algo, + return hashfile_checksum_valid(m->source->odb->repo->hash_algo, m->data, m->data_len); } @@ -768,7 +758,7 @@ static void clear_midx_file_ext(const char *full_path, size_t full_path_len UNUS die_errno(_("failed to remove %s"), full_path); } -void clear_midx_files_ext(const char *object_dir, const char *ext, +void clear_midx_files_ext(struct odb_source *source, const char *ext, const char *keep_hash) { struct clear_midx_data data; @@ -782,7 +772,7 @@ void clear_midx_files_ext(const char *object_dir, const char *ext, } data.ext = ext; - for_each_file_in_pack_dir(object_dir, + for_each_file_in_pack_dir(source->path, clear_midx_file_ext, &data); @@ -791,7 +781,7 @@ void clear_midx_files_ext(const char *object_dir, const char *ext, free(data.keep); } -void clear_incremental_midx_files_ext(const char *object_dir, const char *ext, +void clear_incremental_midx_files_ext(struct odb_source *source, const char *ext, char **keep_hashes, uint32_t hashes_nr) { @@ -807,7 +797,7 @@ void clear_incremental_midx_files_ext(const char *object_dir, const char *ext, data.keep_nr = hashes_nr; data.ext = ext; - for_each_file_in_pack_subdir(object_dir, "multi-pack-index.d", + for_each_file_in_pack_subdir(source->path, "multi-pack-index.d", clear_midx_file_ext, &data); for (i = 0; i < hashes_nr; i++) @@ -819,7 +809,7 @@ void clear_midx_file(struct repository *r) { struct strbuf midx = STRBUF_INIT; - get_midx_filename(r->hash_algo, &midx, r->objects->sources->path); + get_midx_filename(r->objects->sources, &midx); if (r->objects) { struct odb_source *source; @@ -834,8 +824,8 @@ void clear_midx_file(struct repository *r) if (remove_path(midx.buf)) die(_("failed to clear multi-pack-index at %s"), midx.buf); - clear_midx_files_ext(r->objects->sources->path, MIDX_EXT_BITMAP, NULL); - clear_midx_files_ext(r->objects->sources->path, MIDX_EXT_REV, NULL); + clear_midx_files_ext(r->objects->sources, MIDX_EXT_BITMAP, NULL); + clear_midx_files_ext(r->objects->sources, MIDX_EXT_REV, NULL); strbuf_release(&midx); } @@ -879,12 +869,13 @@ static int compare_pair_pos_vs_id(const void *_a, const void *_b) display_progress(progress, _n); \ } while (0) -int verify_midx_file(struct repository *r, const char *object_dir, unsigned flags) +int verify_midx_file(struct odb_source *source, unsigned flags) { + struct repository *r = source->odb->repo; struct pair_pos_vs_id *pairs = NULL; uint32_t i; struct progress *progress = NULL; - struct multi_pack_index *m = load_multi_pack_index(r, object_dir, 1); + struct multi_pack_index *m = load_multi_pack_index(source); struct multi_pack_index *curr; verify_midx_error = 0; @@ -893,7 +884,7 @@ int verify_midx_file(struct repository *r, const char *object_dir, unsigned flag struct stat sb; struct strbuf filename = STRBUF_INIT; - get_midx_filename(r->hash_algo, &filename, object_dir); + get_midx_filename(source, &filename); if (!stat(filename.buf, &sb)) { error(_("multi-pack-index file exists, but failed to parse")); @@ -911,7 +902,7 @@ int verify_midx_file(struct repository *r, const char *object_dir, unsigned flag _("Looking for referenced packfiles"), m->num_packs + m->num_packs_in_base); for (i = 0; i < m->num_packs + m->num_packs_in_base; i++) { - if (prepare_midx_pack(r, m, i)) + if (prepare_midx_pack(m, i)) midx_report("failed to load pack in position %d", i); display_progress(progress, i + 1); @@ -988,7 +979,7 @@ int verify_midx_file(struct repository *r, const char *object_dir, unsigned flag nth_midxed_object_oid(&oid, m, pairs[i].pos); - if (!fill_midx_entry(r, &oid, &e, m)) { + if (!fill_midx_entry(m, &oid, &e)) { midx_report(_("failed to load pack entry for oid[%d] = %s"), pairs[i].pos, oid_to_hex(&oid)); continue; @@ -35,6 +35,8 @@ struct odb_source; "GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL" struct multi_pack_index { + struct odb_source *source; + const unsigned char *data; size_t data_len; @@ -50,7 +52,6 @@ struct multi_pack_index { uint32_t num_objects; int preferred_pack_idx; - int local; int has_chain; const unsigned char *chunk_pack_names; @@ -71,10 +72,6 @@ struct multi_pack_index { const char **pack_names; struct packed_git **packs; - - struct repository *repo; - - char object_dir[FLEX_ARRAY]; }; #define MIDX_PROGRESS (1 << 0) @@ -89,24 +86,19 @@ struct multi_pack_index { #define MIDX_EXT_MIDX "midx" const unsigned char *get_midx_checksum(struct multi_pack_index *m); -void get_midx_filename(const struct git_hash_algo *hash_algo, - struct strbuf *out, const char *object_dir); -void get_midx_filename_ext(const struct git_hash_algo *hash_algo, - struct strbuf *out, const char *object_dir, +void get_midx_filename(struct odb_source *source, struct strbuf *out); +void get_midx_filename_ext(struct odb_source *source, struct strbuf *out, const unsigned char *hash, const char *ext); -void get_midx_chain_dirname(struct strbuf *buf, const char *object_dir); -void get_midx_chain_filename(struct strbuf *buf, const char *object_dir); -void get_split_midx_filename_ext(const struct git_hash_algo *hash_algo, - struct strbuf *buf, const char *object_dir, +void get_midx_chain_dirname(struct odb_source *source, struct strbuf *out); +void get_midx_chain_filename(struct odb_source *source, struct strbuf *out); +void get_split_midx_filename_ext(struct odb_source *source, struct strbuf *buf, const unsigned char *hash, const char *ext); -struct multi_pack_index *load_multi_pack_index(struct repository *r, - const char *object_dir, - int local); -int prepare_midx_pack(struct repository *r, struct multi_pack_index *m, uint32_t pack_int_id); +struct multi_pack_index *load_multi_pack_index(struct odb_source *source); +int prepare_midx_pack(struct multi_pack_index *m, uint32_t pack_int_id); struct packed_git *nth_midxed_pack(struct multi_pack_index *m, uint32_t pack_int_id); -int nth_bitmapped_pack(struct repository *r, struct multi_pack_index *m, +int nth_bitmapped_pack(struct multi_pack_index *m, struct bitmapped_pack *bp, uint32_t pack_int_id); int bsearch_one_midx(const struct object_id *oid, struct multi_pack_index *m, uint32_t *result); @@ -118,27 +110,27 @@ uint32_t nth_midxed_pack_int_id(struct multi_pack_index *m, uint32_t pos); struct object_id *nth_midxed_object_oid(struct object_id *oid, struct multi_pack_index *m, uint32_t n); -int fill_midx_entry(struct repository *r, const struct object_id *oid, struct pack_entry *e, struct multi_pack_index *m); +int fill_midx_entry(struct multi_pack_index *m, const struct object_id *oid, struct pack_entry *e); int midx_contains_pack(struct multi_pack_index *m, const char *idx_or_pack_name); int midx_preferred_pack(struct multi_pack_index *m, uint32_t *pack_int_id); -int prepare_multi_pack_index_one(struct odb_source *source, int local); +int prepare_multi_pack_index_one(struct odb_source *source); /* * Variant of write_midx_file which writes a MIDX containing only the packs * specified in packs_to_include. */ -int write_midx_file(struct repository *r, const char *object_dir, +int write_midx_file(struct odb_source *source, const char *preferred_pack_name, const char *refs_snapshot, unsigned flags); -int write_midx_file_only(struct repository *r, const char *object_dir, +int write_midx_file_only(struct odb_source *source, struct string_list *packs_to_include, const char *preferred_pack_name, const char *refs_snapshot, unsigned flags); void clear_midx_file(struct repository *r); -int verify_midx_file(struct repository *r, const char *object_dir, unsigned flags); -int expire_midx_packs(struct repository *r, const char *object_dir, unsigned flags); -int midx_repack(struct repository *r, const char *object_dir, size_t batch_size, unsigned flags); +int verify_midx_file(struct odb_source *source, unsigned flags); +int expire_midx_packs(struct odb_source *source, unsigned flags); +int midx_repack(struct odb_source *source, size_t batch_size, unsigned flags); void close_midx(struct multi_pack_index *m); @@ -894,7 +894,7 @@ static int string_list_add_note_lines(struct string_list *list, * later, along with any empty strings that came from empty * lines within the file. */ - string_list_split(list, data, '\n', -1); + string_list_split(list, data, "\n", -1); free(data); return 0; } @@ -973,8 +973,8 @@ void string_list_add_refs_from_colon_sep(struct string_list *list, char *globs_copy = xstrdup(globs); int i; - string_list_split_in_place(&split, globs_copy, ":", -1); - string_list_remove_empty_items(&split, 0); + string_list_split_in_place_f(&split, globs_copy, ":", -1, + STRING_LIST_SPLIT_NONEMPTY); for (i = 0; i < split.nr; i++) string_list_add_refs_by_glob(list, split.items[i].string); diff --git a/object-file.c b/object-file.c index 2bc36ab3ee..bc15af4245 100644 --- a/object-file.c +++ b/object-file.c @@ -674,7 +674,7 @@ static void close_loose_object(struct odb_source *source, goto out; if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT)) - fsync_loose_object_bulk_checkin(fd, filename); + fsync_loose_object_bulk_checkin(source->odb->transaction, fd, filename); else if (fsync_object_files > 0) fsync_or_die(fd, filename); else @@ -852,7 +852,7 @@ static int write_loose_object(struct odb_source *source, static struct strbuf filename = STRBUF_INIT; if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT)) - prepare_loose_object_bulk_checkin(); + prepare_loose_object_bulk_checkin(source->odb->transaction); odb_loose_path(source, &filename, oid); @@ -941,7 +941,7 @@ int stream_loose_object(struct odb_source *source, int hdrlen; if (batch_fsync_enabled(FSYNC_COMPONENT_LOOSE_OBJECT)) - prepare_loose_object_bulk_checkin(); + prepare_loose_object_bulk_checkin(source->odb->transaction); /* Since oid is not determined, save tmp file to odb path. */ strbuf_addf(&filename, "%s/", source->path); @@ -1253,18 +1253,26 @@ int index_fd(struct index_state *istate, struct object_id *oid, * Call xsize_t() only when needed to avoid potentially unnecessary * die() for large files. */ - if (type == OBJ_BLOB && path && would_convert_to_git_filter_fd(istate, path)) + if (type == OBJ_BLOB && path && would_convert_to_git_filter_fd(istate, path)) { ret = index_stream_convert_blob(istate, oid, fd, path, flags); - else if (!S_ISREG(st->st_mode)) + } else if (!S_ISREG(st->st_mode)) { ret = index_pipe(istate, oid, fd, type, path, flags); - else if ((st->st_size >= 0 && (size_t) st->st_size <= repo_settings_get_big_file_threshold(istate->repo)) || - type != OBJ_BLOB || - (path && would_convert_to_git(istate, path))) + } else if ((st->st_size >= 0 && + (size_t)st->st_size <= repo_settings_get_big_file_threshold(istate->repo)) || + type != OBJ_BLOB || + (path && would_convert_to_git(istate, path))) { ret = index_core(istate, oid, fd, xsize_t(st->st_size), type, path, flags); - else - ret = index_blob_bulk_checkin(oid, fd, xsize_t(st->st_size), path, - flags); + } else { + struct odb_transaction *transaction; + + transaction = begin_odb_transaction(the_repository->objects); + ret = index_blob_bulk_checkin(transaction, + oid, fd, xsize_t(st->st_size), + path, flags); + end_odb_transaction(transaction); + } + close(fd); return ret; } diff --git a/object-name.c b/object-name.c index 11aa0e6afc..7774991d28 100644 --- a/object-name.c +++ b/object-name.c @@ -696,15 +696,14 @@ static inline char get_hex_char_from_oid(const struct object_id *oid, return hex[oid->hash[pos >> 1] & 0xf]; } -static int extend_abbrev_len(const struct object_id *oid, void *cb_data) +static int extend_abbrev_len(const struct object_id *oid, + struct min_abbrev_data *mad) { - struct min_abbrev_data *mad = cb_data; - unsigned int i = mad->init_len; while (mad->hex[i] && mad->hex[i] == get_hex_char_from_oid(oid, i)) i++; - if (i < GIT_MAX_RAWSZ && i >= mad->cur_len) + if (mad->hex[i] && i >= mad->cur_len) mad->cur_len = i + 1; return 0; @@ -1525,7 +1524,8 @@ struct grab_nth_branch_switch_cbdata { struct strbuf *sb; }; -static int grab_nth_branch_switch(struct object_id *ooid UNUSED, +static int grab_nth_branch_switch(const char *refname UNUSED, + struct object_id *ooid UNUSED, struct object_id *noid UNUSED, const char *email UNUSED, timestamp_t timestamp UNUSED, @@ -1857,55 +1857,35 @@ int repo_get_oid_committish(struct repository *r, const char *name, struct object_id *oid) { - struct object_context unused; - int ret = get_oid_with_context(r, name, GET_OID_COMMITTISH, - oid, &unused); - object_context_release(&unused); - return ret; + return repo_get_oid_with_flags(r, name, oid, GET_OID_COMMITTISH); } int repo_get_oid_treeish(struct repository *r, const char *name, struct object_id *oid) { - struct object_context unused; - int ret = get_oid_with_context(r, name, GET_OID_TREEISH, - oid, &unused); - object_context_release(&unused); - return ret; + return repo_get_oid_with_flags(r, name, oid, GET_OID_TREEISH); } int repo_get_oid_commit(struct repository *r, const char *name, struct object_id *oid) { - struct object_context unused; - int ret = get_oid_with_context(r, name, GET_OID_COMMIT, - oid, &unused); - object_context_release(&unused); - return ret; + return repo_get_oid_with_flags(r, name, oid, GET_OID_COMMIT); } int repo_get_oid_tree(struct repository *r, const char *name, struct object_id *oid) { - struct object_context unused; - int ret = get_oid_with_context(r, name, GET_OID_TREE, - oid, &unused); - object_context_release(&unused); - return ret; + return repo_get_oid_with_flags(r, name, oid, GET_OID_TREE); } int repo_get_oid_blob(struct repository *r, const char *name, struct object_id *oid) { - struct object_context unused; - int ret = get_oid_with_context(r, name, GET_OID_BLOB, - oid, &unused); - object_context_release(&unused); - return ret; + return repo_get_oid_with_flags(r, name, oid, GET_OID_BLOB); } /* Must be called only when object_name:filename doesn't exist. */ @@ -517,12 +517,11 @@ struct parsed_object_pool *parsed_object_pool_new(struct repository *repo) memset(o, 0, sizeof(*o)); o->repo = repo; - o->blob_state = allocate_alloc_state(); - o->tree_state = allocate_alloc_state(); - o->commit_state = allocate_alloc_state(); - o->tag_state = allocate_alloc_state(); - o->object_state = allocate_alloc_state(); - + o->blob_state = alloc_state_alloc(); + o->tree_state = alloc_state_alloc(); + o->commit_state = alloc_state_alloc(); + o->tag_state = alloc_state_alloc(); + o->object_state = alloc_state_alloc(); o->is_shallow = -1; CALLOC_ARRAY(o->shallow_stat, 1); @@ -573,16 +572,11 @@ void parsed_object_pool_clear(struct parsed_object_pool *o) o->buffer_slab = NULL; parsed_object_pool_reset_commit_grafts(o); - clear_alloc_state(o->blob_state); - clear_alloc_state(o->tree_state); - clear_alloc_state(o->commit_state); - clear_alloc_state(o->tag_state); - clear_alloc_state(o->object_state); + alloc_state_free_and_null(&o->blob_state); + alloc_state_free_and_null(&o->tree_state); + alloc_state_free_and_null(&o->commit_state); + alloc_state_free_and_null(&o->tag_state); + alloc_state_free_and_null(&o->object_state); stat_validity_clear(o->shallow_stat); - FREE_AND_NULL(o->blob_state); - FREE_AND_NULL(o->tree_state); - FREE_AND_NULL(o->commit_state); - FREE_AND_NULL(o->tag_state); - FREE_AND_NULL(o->object_state); FREE_AND_NULL(o->shallow_stat); } @@ -139,23 +139,21 @@ static void read_info_alternates(struct object_database *odb, const char *relative_base, int depth); -static int link_alt_odb_entry(struct object_database *odb, - const struct strbuf *entry, - const char *relative_base, - int depth, - const char *normalized_objdir) +static struct odb_source *link_alt_odb_entry(struct object_database *odb, + const char *dir, + const char *relative_base, + int depth) { - struct odb_source *alternate; + struct odb_source *alternate = NULL; struct strbuf pathbuf = STRBUF_INIT; struct strbuf tmp = STRBUF_INIT; khiter_t pos; - int ret = -1; - if (!is_absolute_path(entry->buf) && relative_base) { + if (!is_absolute_path(dir) && relative_base) { strbuf_realpath(&pathbuf, relative_base, 1); strbuf_addch(&pathbuf, '/'); } - strbuf_addbuf(&pathbuf, entry); + strbuf_addstr(&pathbuf, dir); if (!strbuf_realpath(&tmp, pathbuf.buf, 0)) { error(_("unable to normalize alternate object path: %s"), @@ -171,11 +169,15 @@ static int link_alt_odb_entry(struct object_database *odb, while (pathbuf.len && pathbuf.buf[pathbuf.len - 1] == '/') strbuf_setlen(&pathbuf, pathbuf.len - 1); - if (!alt_odb_usable(odb, &pathbuf, normalized_objdir, &pos)) + strbuf_reset(&tmp); + strbuf_realpath(&tmp, odb->sources->path, 1); + + if (!alt_odb_usable(odb, &pathbuf, tmp.buf, &pos)) goto error; CALLOC_ARRAY(alternate, 1); alternate->odb = odb; + alternate->local = false; /* pathbuf.buf is already in r->objects->source_by_path */ alternate->path = strbuf_detach(&pathbuf, NULL); @@ -188,11 +190,11 @@ static int link_alt_odb_entry(struct object_database *odb, /* recursively add alternates */ read_info_alternates(odb, alternate->path, depth + 1); - ret = 0; + error: strbuf_release(&tmp); strbuf_release(&pathbuf); - return ret; + return alternate; } static const char *parse_alt_odb_entry(const char *string, @@ -227,8 +229,7 @@ static const char *parse_alt_odb_entry(const char *string, static void link_alt_odb_entries(struct object_database *odb, const char *alt, int sep, const char *relative_base, int depth) { - struct strbuf objdirbuf = STRBUF_INIT; - struct strbuf entry = STRBUF_INIT; + struct strbuf dir = STRBUF_INIT; if (!alt || !*alt) return; @@ -239,17 +240,13 @@ static void link_alt_odb_entries(struct object_database *odb, const char *alt, return; } - strbuf_realpath(&objdirbuf, odb->sources->path, 1); - while (*alt) { - alt = parse_alt_odb_entry(alt, sep, &entry); - if (!entry.len) + alt = parse_alt_odb_entry(alt, sep, &dir); + if (!dir.len) continue; - link_alt_odb_entry(odb, &entry, - relative_base, depth, objdirbuf.buf); + link_alt_odb_entry(odb, dir.buf, relative_base, depth); } - strbuf_release(&entry); - strbuf_release(&objdirbuf); + strbuf_release(&dir); } static void read_info_alternates(struct object_database *odb, @@ -272,7 +269,7 @@ static void read_info_alternates(struct object_database *odb, } void odb_add_to_alternates_file(struct object_database *odb, - const char *reference) + const char *dir) { struct lock_file lock = LOCK_INIT; char *alts = repo_git_path(odb->repo, "objects/info/alternates"); @@ -289,7 +286,7 @@ void odb_add_to_alternates_file(struct object_database *odb, struct strbuf line = STRBUF_INIT; while (strbuf_getline(&line, in) != EOF) { - if (!strcmp(reference, line.buf)) { + if (!strcmp(dir, line.buf)) { found = 1; break; } @@ -305,27 +302,24 @@ void odb_add_to_alternates_file(struct object_database *odb, if (found) { rollback_lock_file(&lock); } else { - fprintf_or_die(out, "%s\n", reference); + fprintf_or_die(out, "%s\n", dir); if (commit_lock_file(&lock)) die_errno(_("unable to move new alternates file into place")); if (odb->loaded_alternates) - link_alt_odb_entries(odb, reference, - '\n', NULL, 0); + link_alt_odb_entries(odb, dir, '\n', NULL, 0); } free(alts); } -void odb_add_to_alternates_memory(struct object_database *odb, - const char *reference) +struct odb_source *odb_add_to_alternates_memory(struct object_database *odb, + const char *dir) { /* * Make sure alternates are initialized, or else our entry may be * overwritten when they are. */ odb_prepare_alternates(odb); - - link_alt_odb_entries(odb, reference, - '\n', NULL, 0); + return link_alt_odb_entry(odb, dir, NULL, 0); } struct odb_source *odb_set_temporary_primary_source(struct object_database *odb, @@ -463,6 +457,12 @@ struct odb_source *odb_find_source(struct object_database *odb, const char *obj_ free(obj_dir_real); strbuf_release(&odb_path_real); + return source; +} + +struct odb_source *odb_find_source_or_die(struct object_database *odb, const char *obj_dir) +{ + struct odb_source *source = odb_find_source(odb, obj_dir); if (!source) die(_("could not find object directory matching %s"), obj_dir); return source; @@ -64,6 +64,14 @@ struct odb_source { struct multi_pack_index *midx; /* + * Figure out whether this is the local source of the owning + * repository, which would typically be its ".git/objects" directory. + * This local object directory is usually where objects would be + * written to. + */ + bool local; + + /* * This is a temporary object store created by the tmp_objdir * facility. Disable ref updates since the objects in the store * might be discarded on rollback. @@ -84,6 +92,7 @@ struct odb_source { struct packed_git; struct cached_object_entry; +struct odb_transaction; /* * The object database encapsulates access to objects in a repository. It @@ -95,6 +104,13 @@ struct object_database { struct repository *repo; /* + * State of current current object database transaction. Only one + * transaction may be pending at a time. Is NULL when no transaction is + * configured. + */ + struct odb_transaction *transaction; + + /* * Set of all object directories; the main directory is first (and * cannot be NULL after initialization). Subsequent directories are * alternates. @@ -178,11 +194,14 @@ struct object_database *odb_new(struct repository *repo); void odb_clear(struct object_database *o); /* - * Find source by its object directory path. Dies in case the source couldn't - * be found. + * Find source by its object directory path. Returns a `NULL` pointer in case + * the source could not be found. */ struct odb_source *odb_find_source(struct object_database *odb, const char *obj_dir); +/* Same as `odb_find_source()`, but dies in case the source doesn't exist. */ +struct odb_source *odb_find_source_or_die(struct object_database *odb, const char *obj_dir); + /* * Replace the current writable object directory with the specified temporary * object directory; returns the former primary source. @@ -257,8 +276,8 @@ void odb_add_to_alternates_file(struct object_database *odb, * recursive alternates it points to), but do not modify the on-disk alternates * file. */ -void odb_add_to_alternates_memory(struct object_database *odb, - const char *dir); +struct odb_source *odb_add_to_alternates_memory(struct object_database *odb, + const char *dir); /* * Read an object from the database. Returns the object data and assigns object diff --git a/oss-fuzz/fuzz-commit-graph.c b/oss-fuzz/fuzz-commit-graph.c index fbb77fec19..fb8b8787a4 100644 --- a/oss-fuzz/fuzz-commit-graph.c +++ b/oss-fuzz/fuzz-commit-graph.c @@ -4,9 +4,6 @@ #include "commit-graph.h" #include "repository.h" -struct commit_graph *parse_commit_graph(struct repo_settings *s, - void *graph_map, size_t graph_size); - int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size); int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) @@ -22,9 +19,10 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) * possible. */ repo_set_hash_algo(the_repository, GIT_HASH_SHA1); + the_repository->settings.initialized = 1; the_repository->settings.commit_graph_generation_version = 2; the_repository->settings.commit_graph_changed_paths_version = 1; - g = parse_commit_graph(&the_repository->settings, (void *)data, size); + g = parse_commit_graph(the_repository, (void *)data, size); repo_clear(the_repository); free_commit_graph(g); diff --git a/pack-bitmap.c b/pack-bitmap.c index d14421ee20..058bdb5d7d 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -216,7 +216,7 @@ static uint32_t bitmap_num_objects(struct bitmap_index *index) static struct repository *bitmap_repo(struct bitmap_index *bitmap_git) { if (bitmap_is_midx(bitmap_git)) - return bitmap_git->midx->repo; + return bitmap_git->midx->source->odb->repo; return bitmap_git->pack->repo; } @@ -418,13 +418,12 @@ char *midx_bitmap_filename(struct multi_pack_index *midx) { struct strbuf buf = STRBUF_INIT; if (midx->has_chain) - get_split_midx_filename_ext(midx->repo->hash_algo, &buf, - midx->object_dir, + get_split_midx_filename_ext(midx->source, &buf, get_midx_checksum(midx), MIDX_EXT_BITMAP); else - get_midx_filename_ext(midx->repo->hash_algo, &buf, - midx->object_dir, get_midx_checksum(midx), + get_midx_filename_ext(midx->source, &buf, + get_midx_checksum(midx), MIDX_EXT_BITMAP); return strbuf_detach(&buf, NULL); @@ -463,7 +462,7 @@ static int open_midx_bitmap_1(struct bitmap_index *bitmap_git, if (bitmap_git->pack || bitmap_git->midx) { struct strbuf buf = STRBUF_INIT; - get_midx_filename(midx->repo->hash_algo, &buf, midx->object_dir); + get_midx_filename(midx->source, &buf); trace2_data_string("bitmap", bitmap_repo(bitmap_git), "ignoring extra midx bitmap file", buf.buf); close(fd); @@ -493,7 +492,7 @@ static int open_midx_bitmap_1(struct bitmap_index *bitmap_git, } for (i = 0; i < bitmap_git->midx->num_packs + bitmap_git->midx->num_packs_in_base; i++) { - if (prepare_midx_pack(bitmap_repo(bitmap_git), bitmap_git->midx, i)) { + if (prepare_midx_pack(bitmap_git->midx, i)) { warning(_("could not open pack %s"), bitmap_git->midx->pack_names[i]); goto cleanup; @@ -2466,7 +2465,7 @@ void reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git, struct multi_pack_index *m = bitmap_git->midx; for (i = 0; i < m->num_packs + m->num_packs_in_base; i++) { struct bitmapped_pack pack; - if (nth_bitmapped_pack(r, bitmap_git->midx, &pack, i) < 0) { + if (nth_bitmapped_pack(bitmap_git->midx, &pack, i) < 0) { warning(_("unable to load pack: '%s', disabling pack-reuse"), bitmap_git->midx->pack_names[i]); free(packs); diff --git a/pack-revindex.c b/pack-revindex.c index 0cc422a1e6..d0791cc493 100644 --- a/pack-revindex.c +++ b/pack-revindex.c @@ -379,25 +379,25 @@ int load_midx_revindex(struct multi_pack_index *m) * not want to accidentally call munmap() in the middle of the * MIDX. */ - trace2_data_string("load_midx_revindex", m->repo, + trace2_data_string("load_midx_revindex", m->source->odb->repo, "source", "midx"); m->revindex_data = (const uint32_t *)m->chunk_revindex; return 0; } - trace2_data_string("load_midx_revindex", m->repo, + trace2_data_string("load_midx_revindex", m->source->odb->repo, "source", "rev"); if (m->has_chain) - get_split_midx_filename_ext(m->repo->hash_algo, &revindex_name, - m->object_dir, get_midx_checksum(m), + get_split_midx_filename_ext(m->source, &revindex_name, + get_midx_checksum(m), MIDX_EXT_REV); else - get_midx_filename_ext(m->repo->hash_algo, &revindex_name, - m->object_dir, get_midx_checksum(m), + get_midx_filename_ext(m->source, &revindex_name, + get_midx_checksum(m), MIDX_EXT_REV); - ret = load_revindex_from_disk(m->repo->hash_algo, + ret = load_revindex_from_disk(m->source->odb->repo->hash_algo, revindex_name.buf, m->num_objects, &m->revindex_map, diff --git a/packfile.c b/packfile.c index 5d73932f50..acb680966d 100644 --- a/packfile.c +++ b/packfile.c @@ -935,14 +935,14 @@ static void prepare_pack(const char *full_name, size_t full_name_len, report_garbage(PACKDIR_FILE_GARBAGE, full_name); } -static void prepare_packed_git_one(struct odb_source *source, int local) +static void prepare_packed_git_one(struct odb_source *source) { struct string_list garbage = STRING_LIST_INIT_DUP; struct prepare_pack_data data = { .m = source->midx, .r = source->odb->repo, .garbage = &garbage, - .local = local, + .local = source->local, }; for_each_file_in_pack_dir(source->path, prepare_pack, &data); @@ -1037,9 +1037,8 @@ static void prepare_packed_git(struct repository *r) odb_prepare_alternates(r->objects); for (source = r->objects->sources; source; source = source->next) { - int local = (source == r->objects->sources); - prepare_multi_pack_index_one(source, local); - prepare_packed_git_one(source, local); + prepare_multi_pack_index_one(source); + prepare_packed_git_one(source); } rearrange_packed_git(r); @@ -1092,7 +1091,7 @@ struct packed_git *get_all_packs(struct repository *r) if (!m) continue; for (uint32_t i = 0; i < m->num_packs + m->num_packs_in_base; i++) - prepare_midx_pack(r, m, i); + prepare_midx_pack(m, i); } return r->objects->packed_git; @@ -2078,7 +2077,7 @@ int find_pack_entry(struct repository *r, const struct object_id *oid, struct pa prepare_packed_git(r); for (struct odb_source *source = r->objects->sources; source; source = source->next) - if (source->midx && fill_midx_entry(r, oid, e, source->midx)) + if (source->midx && fill_midx_entry(source->midx, oid, e)) return 1; if (!r->objects->packed_git) diff --git a/parse-options.c b/parse-options.c index 5224203ffe..992ec9631f 100644 --- a/parse-options.c +++ b/parse-options.c @@ -953,10 +953,16 @@ static void free_preprocessed_options(struct option *options) free(options); } +#define USAGE_NORMAL 0 +#define USAGE_FULL 1 +#define USAGE_TO_STDOUT 0 +#define USAGE_TO_STDERR 1 + static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t *, const char * const *, const struct option *, - int, int); + int full_usage, + int usage_to_stderr); enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx, const struct option *options, @@ -1088,7 +1094,8 @@ enum parse_opt_result parse_options_step(struct parse_opt_ctx_t *ctx, } if (internal_help && !strcmp(arg + 2, "help-all")) - return usage_with_options_internal(ctx, usagestr, options, 1, 0); + return usage_with_options_internal(ctx, usagestr, options, + USAGE_FULL, USAGE_TO_STDOUT); if (internal_help && !strcmp(arg + 2, "help")) goto show_usage; switch (parse_long_opt(ctx, arg + 2, options)) { @@ -1129,7 +1136,8 @@ unknown: return PARSE_OPT_DONE; show_usage: - return usage_with_options_internal(ctx, usagestr, options, 0, 0); + return usage_with_options_internal(ctx, usagestr, options, + USAGE_NORMAL, USAGE_TO_STDOUT); } int parse_options_end(struct parse_opt_ctx_t *ctx) @@ -1338,7 +1346,7 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t if (!saw_empty_line && !*str) saw_empty_line = 1; - string_list_split(&list, str, '\n', -1); + string_list_split(&list, str, "\n", -1); for (j = 0; j < list.nr; j++) { const char *line = list.items[j].string; @@ -1444,7 +1452,8 @@ static enum parse_opt_result usage_with_options_internal(struct parse_opt_ctx_t void NORETURN usage_with_options(const char * const *usagestr, const struct option *opts) { - usage_with_options_internal(NULL, usagestr, opts, 0, 1); + usage_with_options_internal(NULL, usagestr, opts, + USAGE_NORMAL, USAGE_TO_STDERR); exit(129); } @@ -1452,9 +1461,16 @@ void show_usage_with_options_if_asked(int ac, const char **av, const char * const *usagestr, const struct option *opts) { - if (ac == 2 && !strcmp(av[1], "-h")) { - usage_with_options_internal(NULL, usagestr, opts, 0, 0); - exit(129); + if (ac == 2) { + if (!strcmp(av[1], "-h")) { + usage_with_options_internal(NULL, usagestr, opts, + USAGE_NORMAL, USAGE_TO_STDOUT); + exit(129); + } else if (!strcmp(av[1], "--help-all")) { + usage_with_options_internal(NULL, usagestr, opts, + USAGE_FULL, USAGE_TO_STDOUT); + exit(129); + } } } diff --git a/path-walk.c b/path-walk.c index 2d4ddbadd5..f1ceed99e9 100644 --- a/path-walk.c +++ b/path-walk.c @@ -105,6 +105,24 @@ static void push_to_stack(struct path_walk_context *ctx, prio_queue_put(&ctx->path_stack, xstrdup(path)); } +static void add_path_to_list(struct path_walk_context *ctx, + const char *path, + enum object_type type, + struct object_id *oid, + int interesting) +{ + struct type_and_oid_list *list = strmap_get(&ctx->paths_to_lists, path); + + if (!list) { + CALLOC_ARRAY(list, 1); + list->type = type; + strmap_put(&ctx->paths_to_lists, path, list); + } + + list->maybe_interesting |= interesting; + oid_array_append(&list->oids, oid); +} + static int add_tree_entries(struct path_walk_context *ctx, const char *base_path, struct object_id *oid) @@ -129,7 +147,6 @@ static int add_tree_entries(struct path_walk_context *ctx, init_tree_desc(&desc, &tree->object.oid, tree->buffer, tree->size); while (tree_entry(&desc, &entry)) { - struct type_and_oid_list *list; struct object *o; /* Not actually true, but we will ignore submodules later. */ enum object_type type = S_ISDIR(entry.mode) ? OBJ_TREE : OBJ_BLOB; @@ -190,17 +207,10 @@ static int add_tree_entries(struct path_walk_context *ctx, continue; } - if (!(list = strmap_get(&ctx->paths_to_lists, path.buf))) { - CALLOC_ARRAY(list, 1); - list->type = type; - strmap_put(&ctx->paths_to_lists, path.buf, list); - } - push_to_stack(ctx, path.buf); - - if (!(o->flags & UNINTERESTING)) - list->maybe_interesting = 1; + add_path_to_list(ctx, path.buf, type, &entry.oid, + !(o->flags & UNINTERESTING)); - oid_array_append(&list->oids, &entry.oid); + push_to_stack(ctx, path.buf); } free_tree_buffer(tree); @@ -377,15 +387,9 @@ static int setup_pending_objects(struct path_walk_info *info, if (!info->trees) continue; if (pending->path) { - struct type_and_oid_list *list; char *path = *pending->path ? xstrfmt("%s/", pending->path) : xstrdup(""); - if (!(list = strmap_get(&ctx->paths_to_lists, path))) { - CALLOC_ARRAY(list, 1); - list->type = OBJ_TREE; - strmap_put(&ctx->paths_to_lists, path, list); - } - oid_array_append(&list->oids, &obj->oid); + add_path_to_list(ctx, path, OBJ_TREE, &obj->oid, 1); free(path); } else { /* assume a root tree, such as a lightweight tag. */ @@ -396,19 +400,10 @@ static int setup_pending_objects(struct path_walk_info *info, case OBJ_BLOB: if (!info->blobs) continue; - if (pending->path) { - struct type_and_oid_list *list; - char *path = pending->path; - if (!(list = strmap_get(&ctx->paths_to_lists, path))) { - CALLOC_ARRAY(list, 1); - list->type = OBJ_BLOB; - strmap_put(&ctx->paths_to_lists, path, list); - } - oid_array_append(&list->oids, &obj->oid); - } else { - /* assume a root tree, such as a lightweight tag. */ + if (pending->path) + add_path_to_list(ctx, pending->path, OBJ_BLOB, &obj->oid, 1); + else oid_array_append(&tagged_blobs->oids, &obj->oid); - } break; case OBJ_COMMIT: diff --git a/pathspec.c b/pathspec.c index a3ddd701c7..5993c4afa0 100644 --- a/pathspec.c +++ b/pathspec.c @@ -201,8 +201,7 @@ static void parse_pathspec_attr_match(struct pathspec_item *item, const char *va if (!value || !*value) die(_("attr spec must not be empty")); - string_list_split(&list, value, ' ', -1); - string_list_remove_empty_items(&list, 0); + string_list_split_f(&list, value, " ", -1, STRING_LIST_SPLIT_NONEMPTY); item->attr_check = attr_check_alloc(); CALLOC_ARRAY(item->attr_match, list.nr); diff --git a/progress.c b/progress.c index 8d5ae70f3a..8315bdc3d4 100644 --- a/progress.c +++ b/progress.c @@ -114,16 +114,19 @@ static void display(struct progress *progress, uint64_t n, const char *done) const char *tp; struct strbuf *counters_sb = &progress->counters_sb; int show_update = 0; + int update = !!progress_update; int last_count_len = counters_sb->len; - if (progress->delay && (!progress_update || --progress->delay)) + progress_update = 0; + + if (progress->delay && (!update || --progress->delay)) return; progress->last_value = n; tp = (progress->throughput) ? progress->throughput->display.buf : ""; if (progress->total) { unsigned percent = n * 100 / progress->total; - if (percent != progress->last_percent || progress_update) { + if (percent != progress->last_percent || update) { progress->last_percent = percent; strbuf_reset(counters_sb); @@ -133,7 +136,7 @@ static void display(struct progress *progress, uint64_t n, const char *done) tp); show_update = 1; } - } else if (progress_update) { + } else if (update) { strbuf_reset(counters_sb); strbuf_addf(counters_sb, "%"PRIuMAX"%s", (uintmax_t)n, tp); show_update = 1; @@ -166,7 +169,6 @@ static void display(struct progress *progress, uint64_t n, const char *done) } fflush(stderr); } - progress_update = 0; } } @@ -281,7 +283,7 @@ static int get_default_delay(void) static int delay_in_secs = -1; if (delay_in_secs < 0) - delay_in_secs = git_env_ulong("GIT_PROGRESS_DELAY", 2); + delay_in_secs = git_env_ulong("GIT_PROGRESS_DELAY", 1); return delay_in_secs; } diff --git a/promisor-remote.c b/promisor-remote.c index 08b0da8962..77ebf537e2 100644 --- a/promisor-remote.c +++ b/promisor-remote.c @@ -314,9 +314,162 @@ static int allow_unsanitized(char ch) return ch > 32 && ch < 127; } -static void promisor_info_vecs(struct repository *repo, - struct strvec *names, - struct strvec *urls) +/* + * All the fields used in "promisor-remote" protocol capability, + * including the mandatory "name" and "url" ones. + */ +static const char promisor_field_name[] = "name"; +static const char promisor_field_url[] = "url"; +static const char promisor_field_filter[] = "partialCloneFilter"; +static const char promisor_field_token[] = "token"; + +/* + * List of optional field names that can be used in the + * "promisor-remote" protocol capability (others must be + * ignored). Each field should correspond to a configurable property + * of a remote that can be relevant for the client. + */ +static const char *known_fields[] = { + promisor_field_filter, /* Filter used for partial clone */ + promisor_field_token, /* Authentication token for the remote */ + NULL +}; + +/* + * Check if 'field' is in the list of the known field names for the + * "promisor-remote" protocol capability. + */ +static int is_known_field(const char *field) +{ + const char **p; + + for (p = known_fields; *p; p++) + if (!strcasecmp(*p, field)) + return 1; + return 0; +} + +static int is_valid_field(struct string_list_item *item, void *cb_data) +{ + const char *field = item->string; + const char *config_key = (const char *)cb_data; + + if (!is_known_field(field)) { + warning(_("unsupported field '%s' in '%s' config"), field, config_key); + return 0; + } + return 1; +} + +static char *fields_from_config(struct string_list *fields_list, const char *config_key) +{ + char *fields = NULL; + + if (!repo_config_get_string(the_repository, config_key, &fields) && *fields) { + string_list_split_in_place_f(fields_list, fields, ",", -1, + STRING_LIST_SPLIT_TRIM | + STRING_LIST_SPLIT_NONEMPTY); + filter_string_list(fields_list, 0, is_valid_field, (void *)config_key); + } + + return fields; +} + +static struct string_list *fields_sent(void) +{ + static struct string_list fields_list = STRING_LIST_INIT_NODUP; + static int initialized; + + if (!initialized) { + fields_list.cmp = strcasecmp; + fields_from_config(&fields_list, "promisor.sendFields"); + initialized = 1; + } + + return &fields_list; +} + +static struct string_list *fields_checked(void) +{ + static struct string_list fields_list = STRING_LIST_INIT_NODUP; + static int initialized; + + if (!initialized) { + fields_list.cmp = strcasecmp; + fields_from_config(&fields_list, "promisor.checkFields"); + initialized = 1; + } + + return &fields_list; +} + +/* + * Struct for promisor remotes involved in the "promisor-remote" + * protocol capability. + * + * Except for "name", each <member> in this struct and its <value> + * should correspond (either on the client side or on the server side) + * to a "remote.<name>.<member>" config variable set to <value> where + * "<name>" is a promisor remote name. + */ +struct promisor_info { + const char *name; + const char *url; + const char *filter; + const char *token; +}; + +static void promisor_info_free(struct promisor_info *p) +{ + free((char *)p->name); + free((char *)p->url); + free((char *)p->filter); + free((char *)p->token); + free(p); +} + +static void promisor_info_list_clear(struct string_list *list) +{ + for (size_t i = 0; i < list->nr; i++) + promisor_info_free(list->items[i].util); + string_list_clear(list, 0); +} + +static void set_one_field(struct promisor_info *p, + const char *field, const char *value) +{ + if (!strcasecmp(field, promisor_field_filter)) + p->filter = xstrdup(value); + else if (!strcasecmp(field, promisor_field_token)) + p->token = xstrdup(value); + else + BUG("invalid field '%s'", field); +} + +static void set_fields(struct promisor_info *p, + struct string_list *field_names) +{ + struct string_list_item *item; + + for_each_string_list_item(item, field_names) { + char *key = xstrfmt("remote.%s.%s", p->name, item->string); + const char *val; + if (!repo_config_get_string_tmp(the_repository, key, &val) && *val) + set_one_field(p, item->string, val); + free(key); + } +} + +/* + * Populate 'list' with promisor remote information from the config. + * The 'util' pointer of each list item will hold a 'struct + * promisor_info'. Except "name" and "url", only members of that + * struct specified by the 'field_names' list are set (using values + * from the configuration). + */ +static void promisor_config_info_list(struct repository *repo, + struct string_list *list, + struct string_list *field_names) { struct promisor_remote *r; @@ -328,8 +481,17 @@ static void promisor_info_vecs(struct repository *repo, /* Only add remotes with a non empty URL */ if (!repo_config_get_string_tmp(the_repository, url_key, &url) && *url) { - strvec_push(names, r->name); - strvec_push(urls, url); + struct promisor_info *new_info = xcalloc(1, sizeof(*new_info)); + struct string_list_item *item; + + new_info->name = xstrdup(r->name); + new_info->url = xstrdup(url); + + if (field_names) + set_fields(new_info, field_names); + + item = string_list_append(list, new_info->name); + item->util = new_info; } free(url_key); @@ -340,47 +502,45 @@ char *promisor_remote_info(struct repository *repo) { struct strbuf sb = STRBUF_INIT; int advertise_promisors = 0; - struct strvec names = STRVEC_INIT; - struct strvec urls = STRVEC_INIT; + struct string_list config_info = STRING_LIST_INIT_NODUP; + struct string_list_item *item; repo_config_get_bool(the_repository, "promisor.advertise", &advertise_promisors); if (!advertise_promisors) return NULL; - promisor_info_vecs(repo, &names, &urls); + promisor_config_info_list(repo, &config_info, fields_sent()); - if (!names.nr) + if (!config_info.nr) return NULL; - for (size_t i = 0; i < names.nr; i++) { - if (i) + for_each_string_list_item(item, &config_info) { + struct promisor_info *p = item->util; + + if (item != config_info.items) strbuf_addch(&sb, ';'); - strbuf_addstr(&sb, "name="); - strbuf_addstr_urlencode(&sb, names.v[i], allow_unsanitized); - strbuf_addstr(&sb, ",url="); - strbuf_addstr_urlencode(&sb, urls.v[i], allow_unsanitized); + + strbuf_addf(&sb, "%s=", promisor_field_name); + strbuf_addstr_urlencode(&sb, p->name, allow_unsanitized); + strbuf_addf(&sb, ",%s=", promisor_field_url); + strbuf_addstr_urlencode(&sb, p->url, allow_unsanitized); + + if (p->filter) { + strbuf_addf(&sb, ",%s=", promisor_field_filter); + strbuf_addstr_urlencode(&sb, p->filter, allow_unsanitized); + } + if (p->token) { + strbuf_addf(&sb, ",%s=", promisor_field_token); + strbuf_addstr_urlencode(&sb, p->token, allow_unsanitized); + } } - strvec_clear(&names); - strvec_clear(&urls); + promisor_info_list_clear(&config_info); return strbuf_detach(&sb, NULL); } -/* - * Find first index of 'nicks' where there is 'nick'. 'nick' is - * compared case sensitively to the strings in 'nicks'. If not found - * 'nicks->nr' is returned. - */ -static size_t remote_nick_find(struct strvec *nicks, const char *nick) -{ - for (size_t i = 0; i < nicks->nr; i++) - if (!strcmp(nicks->v[i], nick)) - return i; - return nicks->nr; -} - enum accept_promisor { ACCEPT_NONE = 0, ACCEPT_KNOWN_URL, @@ -388,23 +548,84 @@ enum accept_promisor { ACCEPT_ALL }; +static int match_field_against_config(const char *field, const char *value, + struct promisor_info *config_info) +{ + if (config_info->filter && !strcasecmp(field, promisor_field_filter)) + return !strcmp(config_info->filter, value); + else if (config_info->token && !strcasecmp(field, promisor_field_token)) + return !strcmp(config_info->token, value); + + return 0; +} + +static int all_fields_match(struct promisor_info *advertised, + struct string_list *config_info, + int in_list) +{ + struct string_list *fields = fields_checked(); + struct string_list_item *item_checked; + + for_each_string_list_item(item_checked, fields) { + int match = 0; + const char *field = item_checked->string; + const char *value = NULL; + struct string_list_item *item; + + if (!strcasecmp(field, promisor_field_filter)) + value = advertised->filter; + else if (!strcasecmp(field, promisor_field_token)) + value = advertised->token; + + if (!value) + return 0; + + if (in_list) { + for_each_string_list_item(item, config_info) { + struct promisor_info *p = item->util; + if (match_field_against_config(field, value, p)) { + match = 1; + break; + } + } + } else { + item = string_list_lookup(config_info, advertised->name); + if (item) { + struct promisor_info *p = item->util; + match = match_field_against_config(field, value, p); + } + } + + if (!match) + return 0; + } + + return 1; +} + static int should_accept_remote(enum accept_promisor accept, - const char *remote_name, const char *remote_url, - struct strvec *names, struct strvec *urls) + struct promisor_info *advertised, + struct string_list *config_info) { - size_t i; + struct promisor_info *p; + struct string_list_item *item; + const char *remote_name = advertised->name; + const char *remote_url = advertised->url; if (accept == ACCEPT_ALL) - return 1; + return all_fields_match(advertised, config_info, 1); - i = remote_nick_find(names, remote_name); + /* Get config info for that promisor remote */ + item = string_list_lookup(config_info, remote_name); - if (i >= names->nr) + if (!item) /* We don't know about that remote */ return 0; + p = item->util; + if (accept == ACCEPT_KNOWN_NAME) - return 1; + return all_fields_match(advertised, config_info, 0); if (accept != ACCEPT_KNOWN_URL) BUG("Unhandled 'enum accept_promisor' value '%d'", accept); @@ -414,24 +635,72 @@ static int should_accept_remote(enum accept_promisor accept, return 0; } - if (!strcmp(urls->v[i], remote_url)) - return 1; + if (!strcmp(p->url, remote_url)) + return all_fields_match(advertised, config_info, 0); warning(_("known remote named '%s' but with URL '%s' instead of '%s'"), - remote_name, urls->v[i], remote_url); + remote_name, p->url, remote_url); return 0; } +static int skip_field_name_prefix(const char *elem, const char *field_name, const char **value) +{ + const char *p; + if (!skip_prefix(elem, field_name, &p) || *p != '=') + return 0; + *value = p + 1; + return 1; +} + +static struct promisor_info *parse_one_advertised_remote(const char *remote_info) +{ + struct promisor_info *info = xcalloc(1, sizeof(*info)); + struct string_list elem_list = STRING_LIST_INIT_DUP; + struct string_list_item *item; + + string_list_split(&elem_list, remote_info, ",", -1); + + for_each_string_list_item(item, &elem_list) { + const char *elem = item->string; + const char *p = strchr(elem, '='); + + if (!p) { + warning(_("invalid element '%s' from remote info"), elem); + continue; + } + + if (skip_field_name_prefix(elem, promisor_field_name, &p)) + info->name = url_percent_decode(p); + else if (skip_field_name_prefix(elem, promisor_field_url, &p)) + info->url = url_percent_decode(p); + else if (skip_field_name_prefix(elem, promisor_field_filter, &p)) + info->filter = url_percent_decode(p); + else if (skip_field_name_prefix(elem, promisor_field_token, &p)) + info->token = url_percent_decode(p); + } + + string_list_clear(&elem_list, 0); + + if (!info->name || !info->url) { + warning(_("server advertised a promisor remote without a name or URL: %s"), + remote_info); + promisor_info_free(info); + return NULL; + } + + return info; +} + static void filter_promisor_remote(struct repository *repo, struct strvec *accepted, const char *info) { - struct strbuf **remotes; const char *accept_str; enum accept_promisor accept = ACCEPT_NONE; - struct strvec names = STRVEC_INIT; - struct strvec urls = STRVEC_INIT; + struct string_list config_info = STRING_LIST_INIT_NODUP; + struct string_list remote_info = STRING_LIST_INIT_DUP; + struct string_list_item *item; if (!repo_config_get_string_tmp(the_repository, "promisor.acceptfromserver", &accept_str)) { if (!*accept_str || !strcasecmp("None", accept_str)) @@ -450,49 +719,31 @@ static void filter_promisor_remote(struct repository *repo, if (accept == ACCEPT_NONE) return; - if (accept != ACCEPT_ALL) - promisor_info_vecs(repo, &names, &urls); - /* Parse remote info received */ - remotes = strbuf_split_str(info, ';', 0); - - for (size_t i = 0; remotes[i]; i++) { - struct strbuf **elems; - const char *remote_name = NULL; - const char *remote_url = NULL; - char *decoded_name = NULL; - char *decoded_url = NULL; - - strbuf_strip_suffix(remotes[i], ";"); - elems = strbuf_split(remotes[i], ','); - - for (size_t j = 0; elems[j]; j++) { - int res; - strbuf_strip_suffix(elems[j], ","); - res = skip_prefix(elems[j]->buf, "name=", &remote_name) || - skip_prefix(elems[j]->buf, "url=", &remote_url); - if (!res) - warning(_("unknown element '%s' from remote info"), - elems[j]->buf); - } + string_list_split(&remote_info, info, ";", -1); + + for_each_string_list_item(item, &remote_info) { + struct promisor_info *advertised; - if (remote_name) - decoded_name = url_percent_decode(remote_name); - if (remote_url) - decoded_url = url_percent_decode(remote_url); + advertised = parse_one_advertised_remote(item->string); + + if (!advertised) + continue; + + if (!config_info.nr) { + promisor_config_info_list(repo, &config_info, fields_checked()); + string_list_sort(&config_info); + } - if (decoded_name && should_accept_remote(accept, decoded_name, decoded_url, &names, &urls)) - strvec_push(accepted, decoded_name); + if (should_accept_remote(accept, advertised, &config_info)) + strvec_push(accepted, advertised->name); - strbuf_list_free(elems); - free(decoded_name); - free(decoded_url); + promisor_info_free(advertised); } - strvec_clear(&names); - strvec_clear(&urls); - strbuf_list_free(remotes); + promisor_info_list_clear(&config_info); + string_list_clear(&remote_info, 0); } char *promisor_remote_reply(const char *info) @@ -518,16 +769,15 @@ char *promisor_remote_reply(const char *info) void mark_promisor_remotes_as_accepted(struct repository *r, const char *remotes) { - struct strbuf **accepted_remotes = strbuf_split_str(remotes, ';', 0); + struct string_list accepted_remotes = STRING_LIST_INIT_DUP; + struct string_list_item *item; - for (size_t i = 0; accepted_remotes[i]; i++) { - struct promisor_remote *p; - char *decoded_remote; + string_list_split(&accepted_remotes, remotes, ";", -1); - strbuf_strip_suffix(accepted_remotes[i], ";"); - decoded_remote = url_percent_decode(accepted_remotes[i]->buf); + for_each_string_list_item(item, &accepted_remotes) { + char *decoded_remote = url_percent_decode(item->string); + struct promisor_remote *p = repo_promisor_remote_find(r, decoded_remote); - p = repo_promisor_remote_find(r, decoded_remote); if (p) p->accepted = 1; else @@ -537,5 +787,5 @@ void mark_promisor_remotes_as_accepted(struct repository *r, const char *remotes free(decoded_remote); } - strbuf_list_free(accepted_remotes); + string_list_clear(&accepted_remotes, 0); } diff --git a/protocol.c b/protocol.c index 65f6621702..a3e26a8dd3 100644 --- a/protocol.c +++ b/protocol.c @@ -61,7 +61,7 @@ enum protocol_version determine_protocol_version_server(void) if (git_protocol) { struct string_list list = STRING_LIST_INIT_DUP; const struct string_list_item *item; - string_list_split(&list, git_protocol, ':', -1); + string_list_split(&list, git_protocol, ":", -1); for_each_string_list_item(item, &list) { const char *value; diff --git a/range-diff.c b/range-diff.c index 8a2dcbee32..ca449a0769 100644 --- a/range-diff.c +++ b/range-diff.c @@ -325,13 +325,24 @@ static int diffsize(const char *a, const char *b) } static void get_correspondences(struct string_list *a, struct string_list *b, - int creation_factor) + int creation_factor, size_t max_memory) { int n = a->nr + b->nr; int *cost, c, *a2b, *b2a; int i, j; - - ALLOC_ARRAY(cost, st_mult(n, n)); + size_t cost_size = st_mult(n, n); + size_t cost_bytes = st_mult(sizeof(int), cost_size); + if (cost_bytes >= max_memory) { + struct strbuf cost_str = STRBUF_INIT; + struct strbuf max_str = STRBUF_INIT; + strbuf_humanise_bytes(&cost_str, cost_bytes); + strbuf_humanise_bytes(&max_str, max_memory); + die(_("range-diff: unable to compute the range-diff, since it " + "exceeds the maximum memory for the cost matrix: %s " + "(%"PRIuMAX" bytes) needed, limited to %s (%"PRIuMAX" bytes)"), + cost_str.buf, (uintmax_t)cost_bytes, max_str.buf, (uintmax_t)max_memory); + } + ALLOC_ARRAY(cost, cost_size); ALLOC_ARRAY(a2b, n); ALLOC_ARRAY(b2a, n); @@ -591,7 +602,8 @@ int show_range_diff(const char *range1, const char *range2, if (!res) { find_exact_matches(&branch1, &branch2); get_correspondences(&branch1, &branch2, - range_diff_opts->creation_factor); + range_diff_opts->creation_factor, + range_diff_opts->max_memory); output(&branch1, &branch2, range_diff_opts); } diff --git a/range-diff.h b/range-diff.h index cd85000b5a..9d39818e34 100644 --- a/range-diff.h +++ b/range-diff.h @@ -5,6 +5,10 @@ #include "strvec.h" #define RANGE_DIFF_CREATION_FACTOR_DEFAULT 60 +#define RANGE_DIFF_MAX_MEMORY_DEFAULT \ + (sizeof(void*) >= 8 ? \ + ((size_t)(1024L * 1024L) * (size_t)(4L * 1024L)) : /* 4GB on 64-bit */ \ + ((size_t)(1024L * 1024L) * (size_t)(2L * 1024L))) /* 2GB on 32-bit */ /* * A much higher value than the default, when we KNOW we are comparing @@ -17,6 +21,7 @@ struct range_diff_options { unsigned dual_color:1; unsigned left_only:1, right_only:1; unsigned include_merges:1; + size_t max_memory; const struct diff_options *diffopt; /* may be NULL */ const struct strvec *other_arg; /* may be NULL */ }; diff --git a/read-cache.c b/read-cache.c index 06ad74db22..229b8ef11c 100644 --- a/read-cache.c +++ b/read-cache.c @@ -3947,6 +3947,7 @@ int add_files_to_cache(struct repository *repo, const char *prefix, const struct pathspec *pathspec, char *ps_matched, int include_sparse, int flags) { + struct odb_transaction *transaction; struct update_callback_data data; struct rev_info rev; @@ -3972,9 +3973,9 @@ int add_files_to_cache(struct repository *repo, const char *prefix, * This function is invoked from commands other than 'add', which * may not have their own transaction active. */ - begin_odb_transaction(); + transaction = begin_odb_transaction(repo->objects); run_diff_files(&rev, DIFF_RACY_IS_MODIFIED); - end_odb_transaction(); + end_odb_transaction(transaction); release_revisions(&rev); return !!data.add_errors; diff --git a/ref-filter.c b/ref-filter.c index 4edf0df4cc..520d2539c9 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -435,7 +435,7 @@ static int remote_ref_atom_parser(struct ref_format *format UNUSED, } atom->u.remote_ref.nobracket = 0; - string_list_split(¶ms, arg, ',', -1); + string_list_split(¶ms, arg, ",", -1); for (i = 0; i < params.nr; i++) { const char *s = params.items[i].string; @@ -831,7 +831,7 @@ static int align_atom_parser(struct ref_format *format UNUSED, align->position = ALIGN_LEFT; - string_list_split(¶ms, arg, ',', -1); + string_list_split(¶ms, arg, ",", -1); for (i = 0; i < params.nr; i++) { const char *s = params.items[i].string; int position; diff --git a/reflog-walk.c b/reflog-walk.c index c7070b13b0..4f1ce04749 100644 --- a/reflog-walk.c +++ b/reflog-walk.c @@ -22,9 +22,10 @@ struct complete_reflogs { int nr, alloc; }; -static int read_one_reflog(struct object_id *ooid, struct object_id *noid, - const char *email, timestamp_t timestamp, int tz, - const char *message, void *cb_data) +static int read_one_reflog(const char *refname UNUSED, + struct object_id *ooid, struct object_id *noid, + const char *email, timestamp_t timestamp, int tz, + const char *message, void *cb_data) { struct complete_reflogs *array = cb_data; struct reflog_info *item; @@ -507,7 +507,8 @@ void reflog_expiry_cleanup(void *cb_data) free_commit_list(cb->mark_list); } -int count_reflog_ent(struct object_id *ooid UNUSED, +int count_reflog_ent(const char *refname UNUSED, + struct object_id *ooid UNUSED, struct object_id *noid UNUSED, const char *email UNUSED, timestamp_t timestamp, int tz UNUSED, @@ -65,7 +65,8 @@ void reflog_expiry_prepare(const char *refname, const struct object_id *oid, int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid, const char *email, timestamp_t timestamp, int tz, const char *message, void *cb_data); -int count_reflog_ent(struct object_id *ooid, struct object_id *noid, +int count_reflog_ent(const char *refname, + struct object_id *ooid, struct object_id *noid, const char *email, timestamp_t timestamp, int tz, const char *message, void *cb_data); int should_expire_reflog_ent_verbose(struct object_id *ooid, @@ -1022,7 +1022,6 @@ int is_branch(const char *refname) } struct read_ref_at_cb { - const char *refname; timestamp_t at_time; int cnt; int reccnt; @@ -1052,7 +1051,8 @@ static void set_read_ref_cutoffs(struct read_ref_at_cb *cb, *cb->cutoff_cnt = cb->reccnt; } -static int read_ref_at_ent(struct object_id *ooid, struct object_id *noid, +static int read_ref_at_ent(const char *refname, + struct object_id *ooid, struct object_id *noid, const char *email UNUSED, timestamp_t timestamp, int tz, const char *message, void *cb_data) @@ -1072,14 +1072,13 @@ static int read_ref_at_ent(struct object_id *ooid, struct object_id *noid, oidcpy(cb->oid, noid); if (!oideq(&cb->ooid, noid)) warning(_("log for ref %s has gap after %s"), - cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822))); + refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822))); } else if (cb->date == cb->at_time) oidcpy(cb->oid, noid); else if (!oideq(noid, cb->oid)) warning(_("log for ref %s unexpectedly ended on %s"), - cb->refname, show_date(cb->date, cb->tz, - DATE_MODE(RFC2822))); + refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822))); cb->reccnt++; oidcpy(&cb->ooid, ooid); oidcpy(&cb->noid, noid); @@ -1094,7 +1093,8 @@ static int read_ref_at_ent(struct object_id *ooid, struct object_id *noid, return 0; } -static int read_ref_at_ent_oldest(struct object_id *ooid, struct object_id *noid, +static int read_ref_at_ent_oldest(const char *refname UNUSED, + struct object_id *ooid, struct object_id *noid, const char *email UNUSED, timestamp_t timestamp, int tz, const char *message, void *cb_data) @@ -1117,7 +1117,6 @@ int read_ref_at(struct ref_store *refs, const char *refname, struct read_ref_at_cb cb; memset(&cb, 0, sizeof(cb)); - cb.refname = refname; cb.at_time = at_time; cb.cnt = cnt; cb.msg = msg; @@ -1362,27 +1361,22 @@ int ref_transaction_update(struct ref_transaction *transaction, return 0; } -/* - * Similar to`ref_transaction_update`, but this function is only for adding - * a reflog update. Supports providing custom committer information. The index - * field can be utiltized to order updates as desired. When not used, the - * updates default to being ordered by refname. - */ -static int ref_transaction_update_reflog(struct ref_transaction *transaction, - const char *refname, - const struct object_id *new_oid, - const struct object_id *old_oid, - const char *committer_info, - unsigned int flags, - const char *msg, - uint64_t index, - struct strbuf *err) +int ref_transaction_update_reflog(struct ref_transaction *transaction, + const char *refname, + const struct object_id *new_oid, + const struct object_id *old_oid, + const char *committer_info, + const char *msg, + uint64_t index, + struct strbuf *err) { struct ref_update *update; + unsigned int flags; assert(err); - flags |= REF_LOG_ONLY | REF_FORCE_CREATE_REFLOG | REF_NO_DEREF; + flags = REF_HAVE_OLD | REF_HAVE_NEW | REF_LOG_ONLY | REF_FORCE_CREATE_REFLOG | REF_NO_DEREF | + REF_LOG_USE_PROVIDED_OIDS; if (!transaction_refname_valid(refname, new_oid, flags, err)) return -1; @@ -1390,11 +1384,6 @@ static int ref_transaction_update_reflog(struct ref_transaction *transaction, update = ref_transaction_add_update(transaction, refname, flags, new_oid, old_oid, NULL, NULL, committer_info, msg); - /* - * While we do set the old_oid value, we unset the flag to skip - * old_oid verification which only makes sense for refs. - */ - update->flags &= ~REF_HAVE_OLD; update->index = index; /* @@ -1850,7 +1839,13 @@ int refs_for_each_namespaced_ref(struct ref_store *refs, int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data) { - return do_for_each_ref(refs, "", NULL, fn, 0, + return refs_for_each_rawref_in(refs, "", fn, cb_data); +} + +int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix, + each_ref_fn fn, void *cb_data) +{ + return do_for_each_ref(refs, prefix, NULL, fn, 0, DO_FOR_EACH_INCLUDE_BROKEN, cb_data); } @@ -2951,7 +2946,8 @@ struct migration_data { struct ref_store *old_refs; struct ref_transaction *transaction; struct strbuf *errbuf; - struct strbuf sb; + struct strbuf sb, name, mail; + uint64_t index; }; static int migrate_one_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid, @@ -2984,50 +2980,41 @@ done: return ret; } -struct reflog_migration_data { - uint64_t index; - const char *refname; - struct ref_store *old_refs; - struct ref_transaction *transaction; - struct strbuf *errbuf; - struct strbuf *sb; -}; - -static int migrate_one_reflog_entry(struct object_id *old_oid, +static int migrate_one_reflog_entry(const char *refname, + struct object_id *old_oid, struct object_id *new_oid, const char *committer, timestamp_t timestamp, int tz, const char *msg, void *cb_data) { - struct reflog_migration_data *data = cb_data; + struct migration_data *data = cb_data; + struct ident_split ident; const char *date; int ret; + if (split_ident_line(&ident, committer, strlen(committer)) < 0) + return -1; + + strbuf_reset(&data->name); + strbuf_add(&data->name, ident.name_begin, ident.name_end - ident.name_begin); + strbuf_reset(&data->mail); + strbuf_add(&data->mail, ident.mail_begin, ident.mail_end - ident.mail_begin); + date = show_date(timestamp, tz, DATE_MODE(NORMAL)); - strbuf_reset(data->sb); - /* committer contains name and email */ - strbuf_addstr(data->sb, fmt_ident("", committer, WANT_BLANK_IDENT, date, 0)); - - ret = ref_transaction_update_reflog(data->transaction, data->refname, - new_oid, old_oid, data->sb->buf, - REF_HAVE_NEW | REF_HAVE_OLD, msg, - data->index++, data->errbuf); + strbuf_reset(&data->sb); + strbuf_addstr(&data->sb, fmt_ident(data->name.buf, data->mail.buf, WANT_BLANK_IDENT, date, 0)); + + ret = ref_transaction_update_reflog(data->transaction, refname, + new_oid, old_oid, data->sb.buf, + msg, data->index++, data->errbuf); return ret; } static int migrate_one_reflog(const char *refname, void *cb_data) { struct migration_data *migration_data = cb_data; - struct reflog_migration_data data = { - .refname = refname, - .old_refs = migration_data->old_refs, - .transaction = migration_data->transaction, - .errbuf = migration_data->errbuf, - .sb = &migration_data->sb, - }; - return refs_for_each_reflog_ent(migration_data->old_refs, refname, - migrate_one_reflog_entry, &data); + migrate_one_reflog_entry, migration_data); } static int move_files(const char *from_path, const char *to_path, struct strbuf *errbuf) @@ -3122,6 +3109,8 @@ int repo_migrate_ref_storage_format(struct repository *repo, struct strbuf new_gitdir = STRBUF_INIT; struct migration_data data = { .sb = STRBUF_INIT, + .name = STRBUF_INIT, + .mail = STRBUF_INIT, }; int did_migrate_refs = 0; int ret; @@ -3297,11 +3286,16 @@ done: ref_transaction_free(transaction); strbuf_release(&new_gitdir); strbuf_release(&data.sb); + strbuf_release(&data.name); + strbuf_release(&data.mail); return ret; } int ref_update_expects_existing_old_ref(struct ref_update *update) { + if (update->flags & REF_LOG_ONLY) + return 0; + return (update->flags & REF_HAVE_OLD) && (!is_null_oid(&update->old_oid) || update->old_target); } @@ -428,6 +428,8 @@ int refs_for_each_namespaced_ref(struct ref_store *refs, /* can be used to learn about broken ref and symref */ int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data); +int refs_for_each_rawref_in(struct ref_store *refs, const char *prefix, + each_ref_fn fn, void *cb_data); /* * Iterates over all refs including root refs, i.e. pseudorefs and HEAD. @@ -558,10 +560,13 @@ int refs_delete_reflog(struct ref_store *refs, const char *refname); * The cb_data is a caller-supplied pointer given to the iterator * functions. */ -typedef int each_reflog_ent_fn( - struct object_id *old_oid, struct object_id *new_oid, - const char *committer, timestamp_t timestamp, - int tz, const char *msg, void *cb_data); +typedef int each_reflog_ent_fn(const char *refname, + struct object_id *old_oid, + struct object_id *new_oid, + const char *committer, + timestamp_t timestamp, + int tz, const char *msg, + void *cb_data); /* Iterate over reflog entries in the log for `refname`. */ @@ -760,12 +765,19 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, #define REF_SKIP_CREATE_REFLOG (1 << 12) /* + * When writing a REF_LOG_ONLY record, use the old and new object IDs provided + * in the update instead of resolving the old object ID. The caller must also + * set both REF_HAVE_OLD and REF_HAVE_NEW. + */ +#define REF_LOG_USE_PROVIDED_OIDS (1 << 13) + +/* * Bitmask of all of the flags that are allowed to be passed in to * ref_transaction_update() and friends: */ #define REF_TRANSACTION_UPDATE_ALLOWED_FLAGS \ (REF_NO_DEREF | REF_FORCE_CREATE_REFLOG | REF_SKIP_OID_VERIFICATION | \ - REF_SKIP_REFNAME_VERIFICATION | REF_SKIP_CREATE_REFLOG) + REF_SKIP_REFNAME_VERIFICATION | REF_SKIP_CREATE_REFLOG | REF_LOG_USE_PROVIDED_OIDS) /* * Add a reference update to transaction. `new_oid` is the value that @@ -795,6 +807,21 @@ int ref_transaction_update(struct ref_transaction *transaction, struct strbuf *err); /* + * Similar to `ref_transaction_update`, but this function is only for adding + * a reflog update. Supports providing custom committer information. The index + * field can be utiltized to order updates as desired. When set to zero, the + * updates default to being ordered by refname. + */ +int ref_transaction_update_reflog(struct ref_transaction *transaction, + const char *refname, + const struct object_id *new_oid, + const struct object_id *old_oid, + const char *committer_info, + const char *msg, + uint64_t index, + struct strbuf *err); + +/* * Add a reference creation to transaction. new_oid is the value that * the reference should have after the update; it must not be * null_oid. It is verified that the reference does not exist diff --git a/refs/debug.c b/refs/debug.c index da300efaf3..1cb955961e 100644 --- a/refs/debug.c +++ b/refs/debug.c @@ -277,7 +277,8 @@ struct debug_reflog { void *cb_data; }; -static int debug_print_reflog_ent(struct object_id *old_oid, +static int debug_print_reflog_ent(const char *refname, + struct object_id *old_oid, struct object_id *new_oid, const char *committer, timestamp_t timestamp, int tz, const char *msg, void *cb_data) @@ -292,7 +293,7 @@ static int debug_print_reflog_ent(struct object_id *old_oid, if (new_oid) oid_to_hex_r(n, new_oid); - ret = dbg->fn(old_oid, new_oid, committer, timestamp, tz, msg, + ret = dbg->fn(refname, old_oid, new_oid, committer, timestamp, tz, msg, dbg->cb_data); trace_printf_key(&trace_refs, "reflog_ent %s (ret %d): %s -> %s, %s %ld \"%.*s\"\n", diff --git a/refs/files-backend.c b/refs/files-backend.c index 088b52c740..1b3bf26add 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -68,6 +68,12 @@ */ #define REF_DELETED_RMDIR (1 << 9) +/* + * Used to indicate that the reflog-only update has been created via + * `split_head_update()`. + */ +#define REF_LOG_VIA_SPLIT (1 << 14) + struct ref_lock { char *ref_name; struct lock_file lk; @@ -2109,7 +2115,9 @@ static int files_delete_reflog(struct ref_store *ref_store, return ret; } -static int show_one_reflog_ent(struct files_ref_store *refs, struct strbuf *sb, +static int show_one_reflog_ent(struct files_ref_store *refs, + const char *refname, + struct strbuf *sb, each_reflog_ent_fn fn, void *cb_data) { struct object_id ooid, noid; @@ -2136,7 +2144,7 @@ static int show_one_reflog_ent(struct files_ref_store *refs, struct strbuf *sb, message += 6; else message += 7; - return fn(&ooid, &noid, p, timestamp, tz, message, cb_data); + return fn(refname, &ooid, &noid, p, timestamp, tz, message, cb_data); } static char *find_beginning_of_line(char *bob, char *scan) @@ -2220,7 +2228,7 @@ static int files_for_each_reflog_ent_reverse(struct ref_store *ref_store, strbuf_splice(&sb, 0, 0, bp + 1, endp - (bp + 1)); scanp = bp; endp = bp + 1; - ret = show_one_reflog_ent(refs, &sb, fn, cb_data); + ret = show_one_reflog_ent(refs, refname, &sb, fn, cb_data); strbuf_reset(&sb); if (ret) break; @@ -2232,7 +2240,7 @@ static int files_for_each_reflog_ent_reverse(struct ref_store *ref_store, * Process it, and we can end the loop. */ strbuf_splice(&sb, 0, 0, buf, endp - buf); - ret = show_one_reflog_ent(refs, &sb, fn, cb_data); + ret = show_one_reflog_ent(refs, refname, &sb, fn, cb_data); strbuf_reset(&sb); break; } @@ -2282,7 +2290,7 @@ static int files_for_each_reflog_ent(struct ref_store *ref_store, return -1; while (!ret && !strbuf_getwholeline(&sb, logfp, '\n')) - ret = show_one_reflog_ent(refs, &sb, fn, cb_data); + ret = show_one_reflog_ent(refs, refname, &sb, fn, cb_data); fclose(logfp); strbuf_release(&sb); return ret; @@ -2421,9 +2429,10 @@ static enum ref_transaction_error split_head_update(struct ref_update *update, new_update = ref_transaction_add_update( transaction, "HEAD", - update->flags | REF_LOG_ONLY | REF_NO_DEREF, + update->flags | REF_LOG_ONLY | REF_NO_DEREF | REF_LOG_VIA_SPLIT, &update->new_oid, &update->old_oid, NULL, NULL, update->committer_info, update->msg); + new_update->parent_update = update; /* * Add "HEAD". This insertion is O(N) in the transaction @@ -2494,7 +2503,6 @@ static enum ref_transaction_error split_symref_update(struct ref_update *update, * done when new_update is processed. */ update->flags |= REF_LOG_ONLY | REF_NO_DEREF; - update->flags &= ~REF_HAVE_OLD; return 0; } @@ -2507,11 +2515,36 @@ static enum ref_transaction_error split_symref_update(struct ref_update *update, */ static enum ref_transaction_error check_old_oid(struct ref_update *update, struct object_id *oid, + struct strbuf *referent, struct strbuf *err) { - if (!(update->flags & REF_HAVE_OLD) || - oideq(oid, &update->old_oid)) + if (update->flags & REF_LOG_ONLY || + !(update->flags & REF_HAVE_OLD)) + return 0; + + if (oideq(oid, &update->old_oid)) { + /* + * Normally matching the expected old oid is enough. Either we + * found the ref at the expected state, or we are creating and + * expect the null oid (and likewise found nothing). + * + * But there is one exception for the null oid: if we found a + * symref pointing to nothing we'll also get the null oid. In + * regular recursive mode, that's good (we'll write to what the + * symref points to, which doesn't exist). But in no-deref + * mode, it means we'll clobber the symref, even though the + * caller asked for this to be a creation event. So flag + * that case to preserve the dangling symref. + */ + if ((update->flags & REF_NO_DEREF) && referent->len && + is_null_oid(oid)) { + strbuf_addf(err, "cannot lock ref '%s': " + "dangling symref already exists", + ref_update_original_update_refname(update)); + return REF_TRANSACTION_ERROR_CREATE_EXISTS; + } return 0; + } if (is_null_oid(&update->old_oid)) { strbuf_addf(err, "cannot lock ref '%s': " @@ -2601,7 +2634,36 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re update->backend_data = lock; - if (update->type & REF_ISSYMREF) { + if (update->flags & REF_LOG_VIA_SPLIT) { + struct ref_lock *parent_lock; + + if (!update->parent_update) + BUG("split update without a parent"); + + parent_lock = update->parent_update->backend_data; + + /* + * Check that "HEAD" didn't racily change since we have looked + * it up. If it did we must refuse to write the reflog entry. + * + * Note that this does not catch all races: if "HEAD" was + * racily changed to point to one of the refs part of the + * transaction then we would miss writing the split reflog + * entry for "HEAD". + */ + if (!(update->type & REF_ISSYMREF) || + strcmp(update->parent_update->refname, referent.buf)) { + strbuf_addstr(err, "HEAD has been racily updated"); + ret = REF_TRANSACTION_ERROR_GENERIC; + goto out; + } + + if (update->flags & REF_HAVE_OLD) { + oidcpy(&lock->old_oid, &update->old_oid); + } else { + oidcpy(&lock->old_oid, &parent_lock->old_oid); + } + } else if (update->type & REF_ISSYMREF) { if (update->flags & REF_NO_DEREF) { /* * We won't be reading the referent as part of @@ -2623,7 +2685,8 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re if (update->old_target) ret = ref_update_check_old_target(referent.buf, update, err); else - ret = check_old_oid(update, &lock->old_oid, err); + ret = check_old_oid(update, &lock->old_oid, + &referent, err); if (ret) goto out; } else { @@ -2655,7 +2718,8 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re ret = REF_TRANSACTION_ERROR_EXPECTED_SYMREF; goto out; } else { - ret = check_old_oid(update, &lock->old_oid, err); + ret = check_old_oid(update, &lock->old_oid, + &referent, err); if (ret) { goto out; } @@ -2977,6 +3041,20 @@ static int parse_and_write_reflog(struct files_ref_store *refs, struct ref_lock *lock, struct strbuf *err) { + struct object_id *old_oid = &lock->old_oid; + + if (update->flags & REF_LOG_USE_PROVIDED_OIDS) { + if (!(update->flags & REF_HAVE_OLD) || + !(update->flags & REF_HAVE_NEW) || + !(update->flags & REF_LOG_ONLY)) { + strbuf_addf(err, _("trying to write reflog for '%s'" + "with incomplete values"), update->refname); + return REF_TRANSACTION_ERROR_GENERIC; + } + + old_oid = &update->old_oid; + } + if (update->new_target) { /* * We want to get the resolved OID for the target, to ensure @@ -2994,7 +3072,7 @@ static int parse_and_write_reflog(struct files_ref_store *refs, } } - if (files_log_ref_write(refs, lock->ref_name, &lock->old_oid, + if (files_log_ref_write(refs, lock->ref_name, old_oid, &update->new_oid, update->committer_info, update->msg, update->flags, err)) { char *old_msg = strbuf_detach(err, NULL); @@ -3062,7 +3140,8 @@ static int files_transaction_finish_initial(struct files_ref_store *refs, for (i = 0; i < transaction->nr; i++) { struct ref_update *update = transaction->updates[i]; - if ((update->flags & REF_HAVE_OLD) && + if (!(update->flags & REF_LOG_ONLY) && + (update->flags & REF_HAVE_OLD) && !is_null_oid(&update->old_oid)) BUG("initial ref transaction with old_sha1 set"); @@ -3309,7 +3388,8 @@ struct expire_reflog_cb { dry_run:1; }; -static int expire_reflog_ent(struct object_id *ooid, struct object_id *noid, +static int expire_reflog_ent(const char *refname UNUSED, + struct object_id *ooid, struct object_id *noid, const char *email, timestamp_t timestamp, int tz, const char *message, void *cb_data) { diff --git a/refs/refs-internal.h b/refs/refs-internal.h index 40c1c0f93d..54c2079c12 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -662,7 +662,8 @@ enum ref_transaction_error ref_update_check_old_target(const char *referent, /* * Check if the ref must exist, this means that the old_oid or - * old_target is non NULL. + * old_target is non NULL. Log-only updates never require the old state to + * match. */ int ref_update_expects_existing_old_ref(struct ref_update *update); diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index 8dae1e1112..9e889da2ff 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -1012,10 +1012,6 @@ static int prepare_transaction_update(struct write_transaction_table_arg **out, if (!arg) { struct reftable_addition *addition; - ret = reftable_stack_reload(be->stack); - if (ret) - return ret; - ret = reftable_stack_new_addition(&addition, be->stack, REFTABLE_STACK_NEW_ADDITION_RELOAD); if (ret) { @@ -1102,6 +1098,20 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor if (ret) return REF_TRANSACTION_ERROR_GENERIC; + if (u->flags & REF_LOG_USE_PROVIDED_OIDS) { + if (!(u->flags & REF_HAVE_OLD) || + !(u->flags & REF_HAVE_NEW) || + !(u->flags & REF_LOG_ONLY)) { + strbuf_addf(err, _("trying to write reflog for '%s'" + "with incomplete values"), u->refname); + return REF_TRANSACTION_ERROR_GENERIC; + } + + if (queue_transaction_update(refs, tx_data, u, &u->old_oid, err)) + return REF_TRANSACTION_ERROR_GENERIC; + return 0; + } + /* Verify that the new object ID is valid. */ if ((u->flags & REF_HAVE_NEW) && !is_null_oid(&u->new_oid) && !(u->flags & REF_SKIP_OID_VERIFICATION) && @@ -1186,8 +1196,6 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor if (ret > 0) { /* The reference does not exist, but we expected it to. */ strbuf_addf(err, _("cannot lock ref '%s': " - - "unable to resolve reference '%s'"), ref_update_original_update_refname(u), u->refname); return REF_TRANSACTION_ERROR_NONEXISTENT_REF; @@ -1241,13 +1249,8 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor new_update->parent_update = u; - /* - * Change the symbolic ref update to log only. Also, it - * doesn't need to check its old OID value, as that will be - * done when new_update is processed. - */ + /* Change the symbolic ref update to log only. */ u->flags |= REF_LOG_ONLY | REF_NO_DEREF; - u->flags &= ~REF_HAVE_OLD; } } @@ -1271,8 +1274,33 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor ret = ref_update_check_old_target(referent->buf, u, err); if (ret) return ret; - } else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) { - if (is_null_oid(&u->old_oid)) { + } else if ((u->flags & (REF_LOG_ONLY | REF_HAVE_OLD)) == REF_HAVE_OLD) { + if (oideq(¤t_oid, &u->old_oid)) { + /* + * Normally matching the expected old oid is enough. Either we + * found the ref at the expected state, or we are creating and + * expect the null oid (and likewise found nothing). + * + * But there is one exception for the null oid: if we found a + * symref pointing to nothing we'll also get the null oid. In + * regular recursive mode, that's good (we'll write to what the + * symref points to, which doesn't exist). But in no-deref + * mode, it means we'll clobber the symref, even though the + * caller asked for this to be a creation event. So flag + * that case to preserve the dangling symref. + * + * Everything else is OK and we can fall through to the + * end of the conditional chain. + */ + if ((u->flags & REF_NO_DEREF) && + referent->len && + is_null_oid(&u->old_oid)) { + strbuf_addf(err, _("cannot lock ref '%s': " + "dangling symref already exists"), + ref_update_original_update_refname(u)); + return REF_TRANSACTION_ERROR_CREATE_EXISTS; + } + } else if (is_null_oid(&u->old_oid)) { strbuf_addf(err, _("cannot lock ref '%s': " "reference already exists"), ref_update_original_update_refname(u)); @@ -1966,7 +1994,8 @@ static int reftable_be_rename_ref(struct ref_store *ref_store, ret = backend_for(&arg.be, refs, newrefname, &newrefname, 1); if (ret) goto done; - ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg); + ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg, + REFTABLE_STACK_NEW_ADDITION_RELOAD); done: assert(ret != REFTABLE_API_ERROR); @@ -1995,7 +2024,8 @@ static int reftable_be_copy_ref(struct ref_store *ref_store, ret = backend_for(&arg.be, refs, newrefname, &newrefname, 1); if (ret) goto done; - ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg); + ret = reftable_stack_add(arg.be->stack, &write_copy_table, &arg, + REFTABLE_STACK_NEW_ADDITION_RELOAD); done: assert(ret != REFTABLE_API_ERROR); @@ -2147,7 +2177,7 @@ static int yield_log_record(struct reftable_ref_store *refs, full_committer = fmt_ident(log->value.update.name, log->value.update.email, WANT_COMMITTER_IDENT, NULL, IDENT_NO_DATE); - return fn(&old_oid, &new_oid, full_committer, + return fn(log->refname, &old_oid, &new_oid, full_committer, log->value.update.time, log->value.update.tz_offset, log->value.update.message, cb_data); } @@ -2367,7 +2397,8 @@ static int reftable_be_create_reflog(struct ref_store *ref_store, goto done; arg.stack = be->stack; - ret = reftable_stack_add(be->stack, &write_reflog_existence_table, &arg); + ret = reftable_stack_add(be->stack, &write_reflog_existence_table, &arg, + REFTABLE_STACK_NEW_ADDITION_RELOAD); done: return ret; @@ -2438,7 +2469,8 @@ static int reftable_be_delete_reflog(struct ref_store *ref_store, return ret; arg.stack = be->stack; - ret = reftable_stack_add(be->stack, &write_reflog_delete_table, &arg); + ret = reftable_stack_add(be->stack, &write_reflog_delete_table, &arg, + REFTABLE_STACK_NEW_ADDITION_RELOAD); assert(ret != REFTABLE_API_ERROR); return ret; @@ -2559,15 +2591,16 @@ static int reftable_be_reflog_expire(struct ref_store *ref_store, if (ret < 0) goto done; - ret = reftable_stack_init_log_iterator(be->stack, &it); + ret = reftable_stack_new_addition(&add, be->stack, + REFTABLE_STACK_NEW_ADDITION_RELOAD); if (ret < 0) goto done; - ret = reftable_iterator_seek_log(&it, refname); + ret = reftable_stack_init_log_iterator(be->stack, &it); if (ret < 0) goto done; - ret = reftable_stack_new_addition(&add, be->stack, 0); + ret = reftable_iterator_seek_log(&it, refname); if (ret < 0) goto done; diff --git a/reftable/reftable-stack.h b/reftable/reftable-stack.h index 910ec6ef3a..d70fcb705d 100644 --- a/reftable/reftable-stack.h +++ b/reftable/reftable-stack.h @@ -68,12 +68,15 @@ int reftable_addition_commit(struct reftable_addition *add); * transaction. Releases the lock if held. */ void reftable_addition_destroy(struct reftable_addition *add); -/* add a new table to the stack. The write_table function must call - * reftable_writer_set_limits, add refs and return an error value. */ +/* + * Add a new table to the stack. The write_table function must call + * reftable_writer_set_limits, add refs and return an error value. + * The flags are passed through to `reftable_stack_new_addition()`. + */ int reftable_stack_add(struct reftable_stack *st, int (*write_table)(struct reftable_writer *wr, void *write_arg), - void *write_arg); + void *write_arg, unsigned flags); struct reftable_iterator; diff --git a/reftable/reftable-writer.h b/reftable/reftable-writer.h index 0fbeff17f4..1e7003cd69 100644 --- a/reftable/reftable-writer.h +++ b/reftable/reftable-writer.h @@ -156,7 +156,7 @@ int reftable_writer_add_ref(struct reftable_writer *w, the records before adding them, reordering the records array passed in. */ int reftable_writer_add_refs(struct reftable_writer *w, - struct reftable_ref_record *refs, int n); + struct reftable_ref_record *refs, size_t n); /* adds reftable_log_records. Log records are keyed by (refname, decreasing @@ -171,7 +171,7 @@ int reftable_writer_add_log(struct reftable_writer *w, the records before adding them, reordering records array passed in. */ int reftable_writer_add_logs(struct reftable_writer *w, - struct reftable_log_record *logs, int n); + struct reftable_log_record *logs, size_t n); /* reftable_writer_close finalizes the reftable. The writer is retained so * statistics can be inspected. */ diff --git a/reftable/stack.c b/reftable/stack.c index 4caf96aa1d..f91ce50bcd 100644 --- a/reftable/stack.c +++ b/reftable/stack.c @@ -17,18 +17,6 @@ #include "table.h" #include "writer.h" -static int stack_try_add(struct reftable_stack *st, - int (*write_table)(struct reftable_writer *wr, - void *arg), - void *arg); -static int stack_write_compact(struct reftable_stack *st, - struct reftable_writer *wr, - size_t first, size_t last, - struct reftable_log_expiry_config *config); -static void reftable_addition_close(struct reftable_addition *add); -static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st, - int reuse_open); - static int stack_filename(struct reftable_buf *dest, struct reftable_stack *st, const char *name) { @@ -84,54 +72,6 @@ static int fd_writer_flush(void *arg) return stack_fsync(writer->opts, writer->fd); } -int reftable_new_stack(struct reftable_stack **dest, const char *dir, - const struct reftable_write_options *_opts) -{ - struct reftable_buf list_file_name = REFTABLE_BUF_INIT; - struct reftable_write_options opts = { 0 }; - struct reftable_stack *p; - int err; - - p = reftable_calloc(1, sizeof(*p)); - if (!p) { - err = REFTABLE_OUT_OF_MEMORY_ERROR; - goto out; - } - - if (_opts) - opts = *_opts; - if (opts.hash_id == 0) - opts.hash_id = REFTABLE_HASH_SHA1; - - *dest = NULL; - - reftable_buf_reset(&list_file_name); - if ((err = reftable_buf_addstr(&list_file_name, dir)) < 0 || - (err = reftable_buf_addstr(&list_file_name, "/tables.list")) < 0) - goto out; - - p->list_file = reftable_buf_detach(&list_file_name); - p->list_fd = -1; - p->opts = opts; - p->reftable_dir = reftable_strdup(dir); - if (!p->reftable_dir) { - err = REFTABLE_OUT_OF_MEMORY_ERROR; - goto out; - } - - err = reftable_stack_reload_maybe_reuse(p, 1); - if (err < 0) - goto out; - - *dest = p; - err = 0; - -out: - if (err < 0) - reftable_stack_destroy(p); - return err; -} - static int fd_read_lines(int fd, char ***namesp) { char *buf = NULL; @@ -591,9 +531,59 @@ out: return err; } -/* -1 = error - 0 = up to date - 1 = changed. */ +int reftable_new_stack(struct reftable_stack **dest, const char *dir, + const struct reftable_write_options *_opts) +{ + struct reftable_buf list_file_name = REFTABLE_BUF_INIT; + struct reftable_write_options opts = { 0 }; + struct reftable_stack *p; + int err; + + p = reftable_calloc(1, sizeof(*p)); + if (!p) { + err = REFTABLE_OUT_OF_MEMORY_ERROR; + goto out; + } + + if (_opts) + opts = *_opts; + if (opts.hash_id == 0) + opts.hash_id = REFTABLE_HASH_SHA1; + + *dest = NULL; + + reftable_buf_reset(&list_file_name); + if ((err = reftable_buf_addstr(&list_file_name, dir)) < 0 || + (err = reftable_buf_addstr(&list_file_name, "/tables.list")) < 0) + goto out; + + p->list_file = reftable_buf_detach(&list_file_name); + p->list_fd = -1; + p->opts = opts; + p->reftable_dir = reftable_strdup(dir); + if (!p->reftable_dir) { + err = REFTABLE_OUT_OF_MEMORY_ERROR; + goto out; + } + + err = reftable_stack_reload_maybe_reuse(p, 1); + if (err < 0) + goto out; + + *dest = p; + err = 0; + +out: + if (err < 0) + reftable_stack_destroy(p); + return err; +} + +/* + * Check whether the given stack is up-to-date with what we have in memory. + * Returns 0 if so, 1 if the stack is out-of-date or a negative error code + * otherwise. + */ static int stack_uptodate(struct reftable_stack *st) { char **names = NULL; @@ -667,34 +657,6 @@ int reftable_stack_reload(struct reftable_stack *st) return err; } -int reftable_stack_add(struct reftable_stack *st, - int (*write)(struct reftable_writer *wr, void *arg), - void *arg) -{ - int err = stack_try_add(st, write, arg); - if (err < 0) { - if (err == REFTABLE_OUTDATED_ERROR) { - /* Ignore error return, we want to propagate - REFTABLE_OUTDATED_ERROR. - */ - reftable_stack_reload(st); - } - return err; - } - - return 0; -} - -static int format_name(struct reftable_buf *dest, uint64_t min, uint64_t max) -{ - char buf[100]; - uint32_t rnd = reftable_rand(); - snprintf(buf, sizeof(buf), "0x%012" PRIx64 "-0x%012" PRIx64 "-%08x", - min, max, rnd); - reftable_buf_reset(dest); - return reftable_buf_addstr(dest, buf); -} - struct reftable_addition { struct reftable_flock tables_list_lock; struct reftable_stack *stack; @@ -704,7 +666,25 @@ struct reftable_addition { uint64_t next_update_index; }; -#define REFTABLE_ADDITION_INIT {0} +static void reftable_addition_close(struct reftable_addition *add) +{ + struct reftable_buf nm = REFTABLE_BUF_INIT; + size_t i; + + for (i = 0; i < add->new_tables_len; i++) { + if (!stack_filename(&nm, add->stack, add->new_tables[i])) + unlink(nm.buf); + reftable_free(add->new_tables[i]); + add->new_tables[i] = NULL; + } + reftable_free(add->new_tables); + add->new_tables = NULL; + add->new_tables_len = 0; + add->new_tables_cap = 0; + + flock_release(&add->tables_list_lock); + reftable_buf_release(&nm); +} static int reftable_stack_init_addition(struct reftable_addition *add, struct reftable_stack *st, @@ -713,18 +693,14 @@ static int reftable_stack_init_addition(struct reftable_addition *add, struct reftable_buf lock_file_name = REFTABLE_BUF_INIT; int err; + memset(add, 0, sizeof(*add)); add->stack = st; err = flock_acquire(&add->tables_list_lock, st->list_file, st->opts.lock_timeout_ms); - if (err < 0) { - if (errno == EEXIST) { - err = REFTABLE_LOCK_ERROR; - } else { - err = REFTABLE_IO_ERROR; - } + if (err < 0) goto done; - } + if (st->opts.default_permissions) { if (chmod(add->tables_list_lock.path, st->opts.default_permissions) < 0) { @@ -754,24 +730,54 @@ done: return err; } -static void reftable_addition_close(struct reftable_addition *add) +static int stack_try_add(struct reftable_stack *st, + int (*write_table)(struct reftable_writer *wr, + void *arg), + void *arg, unsigned flags) { - struct reftable_buf nm = REFTABLE_BUF_INIT; - size_t i; + struct reftable_addition add; + int err; - for (i = 0; i < add->new_tables_len; i++) { - if (!stack_filename(&nm, add->stack, add->new_tables[i])) - unlink(nm.buf); - reftable_free(add->new_tables[i]); - add->new_tables[i] = NULL; + err = reftable_stack_init_addition(&add, st, flags); + if (err < 0) + goto done; + + err = reftable_addition_add(&add, write_table, arg); + if (err < 0) + goto done; + + err = reftable_addition_commit(&add); +done: + reftable_addition_close(&add); + return err; +} + +int reftable_stack_add(struct reftable_stack *st, + int (*write)(struct reftable_writer *wr, void *arg), + void *arg, unsigned flags) +{ + int err = stack_try_add(st, write, arg, flags); + if (err < 0) { + if (err == REFTABLE_OUTDATED_ERROR) { + /* Ignore error return, we want to propagate + REFTABLE_OUTDATED_ERROR. + */ + reftable_stack_reload(st); + } + return err; } - reftable_free(add->new_tables); - add->new_tables = NULL; - add->new_tables_len = 0; - add->new_tables_cap = 0; - flock_release(&add->tables_list_lock); - reftable_buf_release(&nm); + return 0; +} + +static int format_name(struct reftable_buf *dest, uint64_t min, uint64_t max) +{ + char buf[100]; + uint32_t rnd = reftable_rand(); + snprintf(buf, sizeof(buf), "0x%012" PRIx64 "-0x%012" PRIx64 "-%08x", + min, max, rnd); + reftable_buf_reset(dest); + return reftable_buf_addstr(dest, buf); } void reftable_addition_destroy(struct reftable_addition *add) @@ -841,10 +847,13 @@ int reftable_addition_commit(struct reftable_addition *add) * control. It is possible that a concurrent writer is already * trying to compact parts of the stack, which would lead to a * `REFTABLE_LOCK_ERROR` because parts of the stack are locked - * already. This is a benign error though, so we ignore it. + * already. Similarly, the stack may have been rewritten by a + * concurrent writer, which causes `REFTABLE_OUTDATED_ERROR`. + * Both of these errors are benign, so we simply ignore them. */ err = reftable_stack_auto_compact(add->stack); - if (err < 0 && err != REFTABLE_LOCK_ERROR) + if (err < 0 && err != REFTABLE_LOCK_ERROR && + err != REFTABLE_OUTDATED_ERROR) goto done; err = 0; } @@ -858,39 +867,18 @@ int reftable_stack_new_addition(struct reftable_addition **dest, struct reftable_stack *st, unsigned int flags) { - int err = 0; - struct reftable_addition empty = REFTABLE_ADDITION_INIT; + int err; REFTABLE_CALLOC_ARRAY(*dest, 1); if (!*dest) return REFTABLE_OUT_OF_MEMORY_ERROR; - **dest = empty; err = reftable_stack_init_addition(*dest, st, flags); if (err) { reftable_free(*dest); *dest = NULL; } - return err; -} -static int stack_try_add(struct reftable_stack *st, - int (*write_table)(struct reftable_writer *wr, - void *arg), - void *arg) -{ - struct reftable_addition add = REFTABLE_ADDITION_INIT; - int err = reftable_stack_init_addition(&add, st, 0); - if (err < 0) - goto done; - - err = reftable_addition_add(&add, write_table, arg); - if (err < 0) - goto done; - - err = reftable_addition_commit(&add); -done: - reftable_addition_close(&add); return err; } @@ -1007,72 +995,6 @@ uint64_t reftable_stack_next_update_index(struct reftable_stack *st) return 1; } -static int stack_compact_locked(struct reftable_stack *st, - size_t first, size_t last, - struct reftable_log_expiry_config *config, - struct reftable_tmpfile *tab_file_out) -{ - struct reftable_buf next_name = REFTABLE_BUF_INIT; - struct reftable_buf tab_file_path = REFTABLE_BUF_INIT; - struct reftable_writer *wr = NULL; - struct fd_writer writer= { - .opts = &st->opts, - }; - struct reftable_tmpfile tab_file = REFTABLE_TMPFILE_INIT; - int err = 0; - - err = format_name(&next_name, reftable_table_min_update_index(st->tables[first]), - reftable_table_max_update_index(st->tables[last])); - if (err < 0) - goto done; - - err = stack_filename(&tab_file_path, st, next_name.buf); - if (err < 0) - goto done; - - err = reftable_buf_addstr(&tab_file_path, ".temp.XXXXXX"); - if (err < 0) - goto done; - - err = tmpfile_from_pattern(&tab_file, tab_file_path.buf); - if (err < 0) - goto done; - - if (st->opts.default_permissions && - chmod(tab_file.path, st->opts.default_permissions) < 0) { - err = REFTABLE_IO_ERROR; - goto done; - } - - writer.fd = tab_file.fd; - err = reftable_writer_new(&wr, fd_writer_write, fd_writer_flush, - &writer, &st->opts); - if (err < 0) - goto done; - - err = stack_write_compact(st, wr, first, last, config); - if (err < 0) - goto done; - - err = reftable_writer_close(wr); - if (err < 0) - goto done; - - err = tmpfile_close(&tab_file); - if (err < 0) - goto done; - - *tab_file_out = tab_file; - tab_file = REFTABLE_TMPFILE_INIT; - -done: - tmpfile_delete(&tab_file); - reftable_writer_free(wr); - reftable_buf_release(&next_name); - reftable_buf_release(&tab_file_path); - return err; -} - static int stack_write_compact(struct reftable_stack *st, struct reftable_writer *wr, size_t first, size_t last, @@ -1172,6 +1094,72 @@ done: return err; } +static int stack_compact_locked(struct reftable_stack *st, + size_t first, size_t last, + struct reftable_log_expiry_config *config, + struct reftable_tmpfile *tab_file_out) +{ + struct reftable_buf next_name = REFTABLE_BUF_INIT; + struct reftable_buf tab_file_path = REFTABLE_BUF_INIT; + struct reftable_writer *wr = NULL; + struct fd_writer writer= { + .opts = &st->opts, + }; + struct reftable_tmpfile tab_file = REFTABLE_TMPFILE_INIT; + int err = 0; + + err = format_name(&next_name, reftable_table_min_update_index(st->tables[first]), + reftable_table_max_update_index(st->tables[last])); + if (err < 0) + goto done; + + err = stack_filename(&tab_file_path, st, next_name.buf); + if (err < 0) + goto done; + + err = reftable_buf_addstr(&tab_file_path, ".temp.XXXXXX"); + if (err < 0) + goto done; + + err = tmpfile_from_pattern(&tab_file, tab_file_path.buf); + if (err < 0) + goto done; + + if (st->opts.default_permissions && + chmod(tab_file.path, st->opts.default_permissions) < 0) { + err = REFTABLE_IO_ERROR; + goto done; + } + + writer.fd = tab_file.fd; + err = reftable_writer_new(&wr, fd_writer_write, fd_writer_flush, + &writer, &st->opts); + if (err < 0) + goto done; + + err = stack_write_compact(st, wr, first, last, config); + if (err < 0) + goto done; + + err = reftable_writer_close(wr); + if (err < 0) + goto done; + + err = tmpfile_close(&tab_file); + if (err < 0) + goto done; + + *tab_file_out = tab_file; + tab_file = REFTABLE_TMPFILE_INIT; + +done: + tmpfile_delete(&tab_file); + reftable_writer_free(wr); + reftable_buf_release(&next_name); + reftable_buf_release(&tab_file_path); + return err; +} + enum stack_compact_range_flags { /* * Perform a best-effort compaction. That is, even if we cannot lock @@ -1219,17 +1207,27 @@ static int stack_compact_range(struct reftable_stack *st, * which are part of the user-specified range. */ err = flock_acquire(&tables_list_lock, st->list_file, st->opts.lock_timeout_ms); - if (err < 0) { - if (errno == EEXIST) - err = REFTABLE_LOCK_ERROR; - else - err = REFTABLE_IO_ERROR; + if (err < 0) goto done; - } + /* + * Check whether the stack is up-to-date. We unfortunately cannot + * handle the situation gracefully in case it's _not_ up-to-date + * because the range of tables that the user has requested us to + * compact may have been changed. So instead we abort. + * + * We could in theory improve the situation by having the caller not + * pass in a range, but instead the list of tables to compact. If so, + * we could check that relevant tables still exist. But for now it's + * good enough to just abort. + */ err = stack_uptodate(st); - if (err) + if (err < 0) + goto done; + if (err > 0) { + err = REFTABLE_OUTDATED_ERROR; goto done; + } /* * Lock all tables in the user-provided range. This is the slice of our @@ -1264,7 +1262,7 @@ static int stack_compact_range(struct reftable_stack *st, * tables, otherwise there would be nothing to compact. * In that case, we return a lock error to our caller. */ - if (errno == EEXIST && last - (i - 1) >= 2 && + if (err == REFTABLE_LOCK_ERROR && last - (i - 1) >= 2 && flags & STACK_COMPACT_RANGE_BEST_EFFORT) { err = 0; /* @@ -1276,13 +1274,9 @@ static int stack_compact_range(struct reftable_stack *st, */ first = (i - 1) + 1; break; - } else if (errno == EEXIST) { - err = REFTABLE_LOCK_ERROR; - goto done; - } else { - err = REFTABLE_IO_ERROR; - goto done; } + + goto done; } /* @@ -1291,10 +1285,8 @@ static int stack_compact_range(struct reftable_stack *st, * of tables. */ err = flock_close(&table_locks[nlocks++]); - if (err < 0) { - err = REFTABLE_IO_ERROR; + if (err < 0) goto done; - } } /* @@ -1326,13 +1318,8 @@ static int stack_compact_range(struct reftable_stack *st, * the new table. */ err = flock_acquire(&tables_list_lock, st->list_file, st->opts.lock_timeout_ms); - if (err < 0) { - if (errno == EEXIST) - err = REFTABLE_LOCK_ERROR; - else - err = REFTABLE_IO_ERROR; + if (err < 0) goto done; - } if (st->opts.default_permissions) { if (chmod(tables_list_lock.path, diff --git a/reftable/system.c b/reftable/system.c index 1ee268b125..725a25844e 100644 --- a/reftable/system.c +++ b/reftable/system.c @@ -72,7 +72,7 @@ int flock_acquire(struct reftable_flock *l, const char *target_path, reftable_free(lockfile); if (errno == EEXIST) return REFTABLE_LOCK_ERROR; - return -1; + return REFTABLE_IO_ERROR; } l->fd = get_lock_file_fd(lockfile); diff --git a/reftable/system.h b/reftable/system.h index beb9d2431f..c54ed4cad6 100644 --- a/reftable/system.h +++ b/reftable/system.h @@ -81,7 +81,9 @@ struct reftable_flock { * to acquire the lock. If `timeout_ms` is 0 we don't wait, if it is negative * we block indefinitely. * - * Retrun 0 on success, a reftable error code on error. + * Retrun 0 on success, a reftable error code on error. Specifically, + * `REFTABLE_LOCK_ERROR` should be returned in case the target path is already + * locked. */ int flock_acquire(struct reftable_flock *l, const char *target_path, long timeout_ms); diff --git a/reftable/writer.c b/reftable/writer.c index 3b4ebdd6dc..0133b64975 100644 --- a/reftable/writer.c +++ b/reftable/writer.c @@ -395,14 +395,16 @@ out: } int reftable_writer_add_refs(struct reftable_writer *w, - struct reftable_ref_record *refs, int n) + struct reftable_ref_record *refs, size_t n) { int err = 0; - int i = 0; - QSORT(refs, n, reftable_ref_record_compare_name); - for (i = 0; err == 0 && i < n; i++) { + + if (n) + qsort(refs, n, sizeof(*refs), reftable_ref_record_compare_name); + + for (size_t i = 0; err == 0 && i < n; i++) err = reftable_writer_add_ref(w, &refs[i]); - } + return err; } @@ -486,15 +488,16 @@ done: } int reftable_writer_add_logs(struct reftable_writer *w, - struct reftable_log_record *logs, int n) + struct reftable_log_record *logs, size_t n) { int err = 0; - int i = 0; - QSORT(logs, n, reftable_log_record_compare_key); - for (i = 0; err == 0 && i < n; i++) { + if (n) + qsort(logs, n, sizeof(*logs), reftable_log_record_compare_key); + + for (size_t i = 0; err == 0 && i < n; i++) err = reftable_writer_add_log(w, &logs[i]); - } + return err; } @@ -1171,7 +1171,6 @@ static void show_push_unqualified_ref_name_error(const char *dst_value, const char *matched_src_name) { struct object_id oid; - enum object_type type; /* * TRANSLATORS: "matches '%s'%" is the <dst> part of "git push @@ -1196,30 +1195,37 @@ static void show_push_unqualified_ref_name_error(const char *dst_value, BUG("'%s' is not a valid object, " "match_explicit_lhs() should catch this!", matched_src_name); - type = odb_read_object_info(the_repository->objects, &oid, NULL); - if (type == OBJ_COMMIT) { + + switch (odb_read_object_info(the_repository->objects, &oid, NULL)) { + case OBJ_COMMIT: advise(_("The <src> part of the refspec is a commit object.\n" "Did you mean to create a new branch by pushing to\n" "'%s:refs/heads/%s'?"), matched_src_name, dst_value); - } else if (type == OBJ_TAG) { + break; + case OBJ_TAG: advise(_("The <src> part of the refspec is a tag object.\n" "Did you mean to create a new tag by pushing to\n" "'%s:refs/tags/%s'?"), matched_src_name, dst_value); - } else if (type == OBJ_TREE) { + break; + case OBJ_TREE: advise(_("The <src> part of the refspec is a tree object.\n" "Did you mean to tag a new tree by pushing to\n" "'%s:refs/tags/%s'?"), matched_src_name, dst_value); - } else if (type == OBJ_BLOB) { + break; + case OBJ_BLOB: advise(_("The <src> part of the refspec is a blob object.\n" "Did you mean to tag a new blob by pushing to\n" "'%s:refs/tags/%s'?"), matched_src_name, dst_value); - } else { - BUG("'%s' should be commit/tag/tree/blob, is '%d'", - matched_src_name, type); + break; + default: + advise(_("The <src> part of the refspec ('%s') " + "is an object ID that doesn't exist.\n"), + matched_src_name); + break; } } @@ -2578,7 +2584,8 @@ struct check_and_collect_until_cb_data { }; /* Get the timestamp of the latest entry. */ -static int peek_reflog(struct object_id *o_oid UNUSED, +static int peek_reflog(const char *refname UNUSED, + struct object_id *o_oid UNUSED, struct object_id *n_oid UNUSED, const char *ident UNUSED, timestamp_t timestamp, int tz UNUSED, @@ -2589,7 +2596,8 @@ static int peek_reflog(struct object_id *o_oid UNUSED, return 1; } -static int check_and_collect_until(struct object_id *o_oid UNUSED, +static int check_and_collect_until(const char *refname UNUSED, + struct object_id *o_oid UNUSED, struct object_id *n_oid, const char *ident UNUSED, timestamp_t timestamp, int tz UNUSED, diff --git a/repository.c b/repository.c index ecd691181f..6faf5c7398 100644 --- a/repository.c +++ b/repository.c @@ -57,6 +57,7 @@ void initialize_repository(struct repository *repo) repo->parsed_objects = parsed_object_pool_new(repo); ALLOC_ARRAY(repo->index, 1); index_state_init(repo->index, repo); + repo->check_deprecated_config = true; /* * When a command runs inside a repository, it learns what @@ -168,6 +169,7 @@ void repo_set_gitdir(struct repository *repo, if (!repo->objects->sources) { CALLOC_ARRAY(repo->objects->sources, 1); repo->objects->sources->odb = repo->objects; + repo->objects->sources->local = true; repo->objects->sources_tail = &repo->objects->sources->next; } expand_base_dir(&repo->objects->sources->path, o->object_dir, diff --git a/repository.h b/repository.h index 042dc93f0f..5808a5d610 100644 --- a/repository.h +++ b/repository.h @@ -161,6 +161,9 @@ struct repository { /* Indicate if a repository has a different 'commondir' from 'gitdir' */ unsigned different_commondir:1; + + /* Should repo_config() check for deprecated settings */ + bool check_deprecated_config; }; #ifdef USE_THE_REPOSITORY_VARIABLE diff --git a/revision.c b/revision.c index 18f300d455..6ba8f67054 100644 --- a/revision.c +++ b/revision.c @@ -671,12 +671,17 @@ static void trace2_bloom_filter_statistics_atexit(void) static int forbid_bloom_filters(struct pathspec *spec) { - if (spec->has_wildcard) - return 1; - if (spec->magic & ~PATHSPEC_LITERAL) + unsigned int allowed_magic = + PATHSPEC_FROMTOP | + PATHSPEC_MAXDEPTH | + PATHSPEC_LITERAL | + PATHSPEC_GLOB | + PATHSPEC_ATTR; + + if (spec->magic & ~allowed_magic) return 1; for (size_t nr = 0; nr < spec->nr; nr++) - if (spec->items[nr].magic & ~PATHSPEC_LITERAL) + if (spec->items[nr].magic & ~allowed_magic) return 1; return 0; @@ -691,23 +696,34 @@ static int convert_pathspec_to_bloom_keyvec(struct bloom_keyvec **out, char *path_alloc = NULL; const char *path; size_t len; - int res = 0; + int res = -1; + len = pi->nowildcard_len; + if (len != pi->len) { + /* + * for path like "dir/file*", nowildcard part would be + * "dir/file", but only "dir" should be used for the + * bloom filter. + */ + while (len > 0 && pi->match[len - 1] != '/') + len--; + } /* remove single trailing slash from path, if needed */ - if (pi->len > 0 && pi->match[pi->len - 1] == '/') { - path_alloc = xmemdupz(pi->match, pi->len - 1); + if (len > 0 && pi->match[len - 1] == '/') + len--; + + if (!len) + goto cleanup; + + if (len != pi->len) { + path_alloc = xmemdupz(pi->match, len); path = path_alloc; } else path = pi->match; - len = strlen(path); - if (!len) { - res = -1; - goto cleanup; - } - *out = bloom_keyvec_new(path, len, settings); + res = 0; cleanup: free(path_alloc); return res; @@ -1689,7 +1705,8 @@ static void handle_one_reflog_commit(struct object_id *oid, void *cb_data) } } -static int handle_one_reflog_ent(struct object_id *ooid, struct object_id *noid, +static int handle_one_reflog_ent(const char *refname UNUSED, + struct object_id *ooid, struct object_id *noid, const char *email UNUSED, timestamp_t timestamp UNUSED, int tz UNUSED, diff --git a/sequencer.c b/sequencer.c index aaf2e4df64..9ae40a91b2 100644 --- a/sequencer.c +++ b/sequencer.c @@ -2721,6 +2721,7 @@ static int check_merge_commit_insn(enum todo_command command) return error(_("cannot squash merge commit into another commit")); case TODO_MERGE: + case TODO_DROP: return 0; default: @@ -1460,8 +1460,9 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir, if (env_ceiling_dirs) { int empty_entry_found = 0; + static const char path_sep[] = { PATH_SEP, '\0' }; - string_list_split(&ceiling_dirs, env_ceiling_dirs, PATH_SEP, -1); + string_list_split(&ceiling_dirs, env_ceiling_dirs, path_sep, -1); filter_string_list(&ceiling_dirs, 0, canonicalize_ceiling_entry, &empty_entry_found); ceil_offset = longest_ancestor_length(dir->buf, &ceiling_dirs); diff --git a/shared.mak b/shared.mak index 1a99848a95..5c7bc94785 100644 --- a/shared.mak +++ b/shared.mak @@ -88,6 +88,8 @@ ifndef V QUIET_LINT_GITLINK = @echo ' ' LINT GITLINK $<; QUIET_LINT_MANSEC = @echo ' ' LINT MAN SEC $<; + QUIET_LINT_DELIMSEC = @echo ' ' LINT DEL SEC $<; + QUIET_LINT_DOCSTYLE = @echo ' ' LINT DOCSTYLE $<; QUIET_LINT_MANEND = @echo ' ' LINT MAN END $<; export V diff --git a/string-list.c b/string-list.c index 53faaa8420..343cf1ca90 100644 --- a/string-list.c +++ b/string-list.c @@ -276,55 +276,99 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_ list->nr--; } -int string_list_split(struct string_list *list, const char *string, - int delim, int maxsplit) +/* + * append a substring [p..end] to list; return number of things it + * appended to the list. + */ +static int append_one(struct string_list *list, + const char *p, const char *end, + int in_place, unsigned flags) +{ + if (!end) + end = p + strlen(p); + + if ((flags & STRING_LIST_SPLIT_TRIM)) { + /* rtrim */ + for (; p < end; end--) + if (!isspace(end[-1])) + break; + } + + if ((flags & STRING_LIST_SPLIT_NONEMPTY) && (end <= p)) + return 0; + + if (in_place) { + *((char *)end) = '\0'; + string_list_append(list, p); + } else { + string_list_append_nodup(list, xmemdupz(p, end - p)); + } + return 1; +} + +/* + * Unfortunately this cannot become a public interface, as _in_place() + * wants to have "const char *string" while the other variant wants to + * have "char *string" for type safety. + * + * This accepts "const char *string" to allow both wrappers to use it; + * it internally casts away the constness when in_place is true by + * taking advantage of strpbrk() that takes a "const char *" arg and + * returns "char *" pointer into that const string. Yucky but works ;-). + */ +static int split_string(struct string_list *list, const char *string, const char *delim, + int maxsplit, int in_place, unsigned flags) { int count = 0; - const char *p = string, *end; + const char *p = string; + + if (in_place && list->strdup_strings) + BUG("string_list_split_in_place() called with strdup_strings"); + else if (!in_place && !list->strdup_strings) + BUG("string_list_split() called without strdup_strings"); - if (!list->strdup_strings) - die("internal error in string_list_split(): " - "list->strdup_strings must be set"); for (;;) { - count++; - if (maxsplit >= 0 && count > maxsplit) { - string_list_append(list, p); - return count; + char *end; + + if (flags & STRING_LIST_SPLIT_TRIM) { + /* ltrim */ + while (*p && isspace(*p)) + p++; } - end = strchr(p, delim); - if (end) { - string_list_append_nodup(list, xmemdupz(p, end - p)); - p = end + 1; - } else { - string_list_append(list, p); + + if (0 <= maxsplit && maxsplit <= count) + end = NULL; + else + end = strpbrk(p, delim); + + count += append_one(list, p, end, in_place, flags); + + if (!end) return count; - } + p = end + 1; } } +int string_list_split(struct string_list *list, const char *string, + const char *delim, int maxsplit) +{ + return split_string(list, string, delim, maxsplit, 0, 0); +} + int string_list_split_in_place(struct string_list *list, char *string, const char *delim, int maxsplit) { - int count = 0; - char *p = string, *end; + return split_string(list, string, delim, maxsplit, 1, 0); +} - if (list->strdup_strings) - die("internal error in string_list_split_in_place(): " - "list->strdup_strings must not be set"); - for (;;) { - count++; - if (maxsplit >= 0 && count > maxsplit) { - string_list_append(list, p); - return count; - } - end = strpbrk(p, delim); - if (end) { - *end = '\0'; - string_list_append(list, p); - p = end + 1; - } else { - string_list_append(list, p); - return count; - } - } +int string_list_split_f(struct string_list *list, const char *string, + const char *delim, int maxsplit, unsigned flags) +{ + return split_string(list, string, delim, maxsplit, 0, flags); +} + +int string_list_split_in_place_f(struct string_list *list, char *string, + const char *delim, int maxsplit, unsigned flags) +{ + return split_string(list, string, delim, maxsplit, 1, flags); } diff --git a/string-list.h b/string-list.h index 122b318641..2b438c7733 100644 --- a/string-list.h +++ b/string-list.h @@ -254,7 +254,7 @@ struct string_list_item *unsorted_string_list_lookup(struct string_list *list, void unsorted_string_list_delete_item(struct string_list *list, int i, int free_util); /** - * Split string into substrings on character `delim` and append the + * Split string into substrings on characters in `delim` and append the * substrings to `list`. The input string is not modified. * list->strdup_strings must be set, as new memory needs to be * allocated to hold the substrings. If maxsplit is non-negative, @@ -262,15 +262,15 @@ void unsorted_string_list_delete_item(struct string_list *list, int i, int free_ * appended to list. * * Examples: - * string_list_split(l, "foo:bar:baz", ':', -1) -> ["foo", "bar", "baz"] - * string_list_split(l, "foo:bar:baz", ':', 0) -> ["foo:bar:baz"] - * string_list_split(l, "foo:bar:baz", ':', 1) -> ["foo", "bar:baz"] - * string_list_split(l, "foo:bar:", ':', -1) -> ["foo", "bar", ""] - * string_list_split(l, "", ':', -1) -> [""] - * string_list_split(l, ":", ':', -1) -> ["", ""] + * string_list_split(l, "foo:bar:baz", ":", -1) -> ["foo", "bar", "baz"] + * string_list_split(l, "foo:bar:baz", ":", 0) -> ["foo:bar:baz"] + * string_list_split(l, "foo:bar:baz", ":", 1) -> ["foo", "bar:baz"] + * string_list_split(l, "foo:bar:", ":", -1) -> ["foo", "bar", ""] + * string_list_split(l, "", ":", -1) -> [""] + * string_list_split(l, ":", ":", -1) -> ["", ""] */ int string_list_split(struct string_list *list, const char *string, - int delim, int maxsplit); + const char *delim, int maxsplit); /* * Like string_list_split(), except that string is split in-place: the @@ -281,4 +281,21 @@ int string_list_split(struct string_list *list, const char *string, */ int string_list_split_in_place(struct string_list *list, char *string, const char *delim, int maxsplit); + +/* Flag bits for split_f and split_in_place_f functions */ +enum { + /* + * trim whitespaces around resulting string piece before adding + * it to the list + */ + STRING_LIST_SPLIT_TRIM = (1 << 0), + /* omit adding empty string piece to the resulting list */ + STRING_LIST_SPLIT_NONEMPTY = (1 << 1), +}; + +int string_list_split_f(struct string_list *, const char *string, + const char *delim, int maxsplit, unsigned flags); + +int string_list_split_in_place_f(struct string_list *, char *string, + const char *delim, int maxsplit, unsigned flags); #endif /* STRING_LIST_H */ diff --git a/sub-process.c b/sub-process.c index 1daf5a9752..83bf0a0e82 100644 --- a/sub-process.c +++ b/sub-process.c @@ -30,23 +30,20 @@ struct subprocess_entry *subprocess_find_entry(struct hashmap *hashmap, const ch int subprocess_read_status(int fd, struct strbuf *status) { - struct strbuf **pair; - char *line; int len; for (;;) { + char *line; + const char *value; + len = packet_read_line_gently(fd, NULL, &line); if ((len < 0) || !line) break; - pair = strbuf_split_str(line, '=', 2); - if (pair[0] && pair[0]->len && pair[1]) { + if (skip_prefix(line, "status=", &value)) { /* the last "status=<foo>" line wins */ - if (!strcmp(pair[0]->buf, "status=")) { - strbuf_reset(status); - strbuf_addbuf(status, pair[1]); - } + strbuf_reset(status); + strbuf_addstr(status, value); } - strbuf_list_free(pair); } return (len < 0) ? len : 0; diff --git a/t/Makefile b/t/Makefile index 757674e727..ab8a5b54aa 100644 --- a/t/Makefile +++ b/t/Makefile @@ -189,15 +189,9 @@ perf: .PHONY: libgit-sys-test libgit-rs-test libgit-sys-test: - $(QUIET)(\ - cd ../contrib/libgit-sys && \ - cargo test \ - ) -libgit-rs-test: - $(QUIET)(\ - cd ../contrib/libgit-rs && \ - cargo test \ - ) + $(QUIET)cargo test --manifest-path ../contrib/libgit-sys/Cargo.toml +libgit-rs-test: libgit-sys-test + $(QUIET)cargo test --manifest-path ../contrib/libgit-rs/Cargo.toml ifdef INCLUDE_LIBGIT_RS -all:: libgit-sys-test libgit-rs-test +all:: libgit-rs-test endif diff --git a/t/for-each-ref-tests.sh b/t/for-each-ref-tests.sh new file mode 100644 index 0000000000..e3ad19298a --- /dev/null +++ b/t/for-each-ref-tests.sh @@ -0,0 +1,2141 @@ +git_for_each_ref=${git_for_each_ref:-git for-each-ref} +GNUPGHOME_NOT_USED=$GNUPGHOME +. "$TEST_DIRECTORY"/lib-gpg.sh +. "$TEST_DIRECTORY"/lib-terminal.sh + +# Mon Jul 3 23:18:43 2006 +0000 +datestamp=1151968723 +setdate_and_increment () { + GIT_COMMITTER_DATE="$datestamp +0200" + datestamp=$(expr "$datestamp" + 1) + GIT_AUTHOR_DATE="$datestamp +0200" + datestamp=$(expr "$datestamp" + 1) + export GIT_COMMITTER_DATE GIT_AUTHOR_DATE +} + +test_object_file_size () { + oid=$(git rev-parse "$1") + path=".git/objects/$(test_oid_to_path $oid)" + test_file_size "$path" +} + +test_expect_success setup ' + # setup .mailmap + cat >.mailmap <<-EOF && + A Thor <athor@example.com> A U Thor <author@example.com> + C Mitter <cmitter@example.com> C O Mitter <committer@example.com> + EOF + + setdate_and_increment && + echo "Using $datestamp" > one && + git add one && + git commit -m "Initial" && + git branch -M main && + setdate_and_increment && + git tag -a -m "Tagging at $datestamp" testtag && + git update-ref refs/remotes/origin/main main && + git remote add origin nowhere && + git config branch.main.remote origin && + git config branch.main.merge refs/heads/main && + git remote add myfork elsewhere && + git config remote.pushdefault myfork && + git config push.default current +' + +test_atom () { + case "$1" in + head) ref=refs/heads/main ;; + tag) ref=refs/tags/testtag ;; + sym) ref=refs/heads/sym ;; + *) ref=$1 ;; + esac + format=$2 + test_do=test_expect_${4:-success} + + printf '%s\n' "$3" >expected + $test_do $PREREQ "basic atom: $ref $format" ' + ${git_for_each_ref} --format="%($format)" "$ref" >actual && + sanitize_pgp <actual >actual.clean && + test_cmp expected actual.clean + ' + + # Automatically test "contents:size" atom after testing "contents" + if test "$format" = "contents" + then + # for commit leg, $3 is changed there + expect=$(printf '%s' "$3" | wc -c) + $test_do $PREREQ "basic atom: $ref contents:size" ' + type=$(git cat-file -t "$ref") && + case $type in + tag) + # We cannot use $3 as it expects sanitize_pgp to run + git cat-file tag $ref >out && + expect=$(tail -n +6 out | wc -c) && + rm -f out ;; + tree | blob) + expect="" ;; + commit) + : "use the calculated expect" ;; + *) + BUG "unknown object type" ;; + esac && + # Leave $expect unquoted to lose possible leading whitespaces + echo $expect >expected && + ${git_for_each_ref} --format="%(contents:size)" "$ref" >actual && + test_cmp expected actual + ' + fi +} + +hexlen=$(test_oid hexsz) + +test_atom head refname refs/heads/main +test_atom head refname: refs/heads/main +test_atom head refname:short main +test_atom head refname:lstrip=1 heads/main +test_atom head refname:lstrip=2 main +test_atom head refname:lstrip=-1 main +test_atom head refname:lstrip=-2 heads/main +test_atom head refname:rstrip=1 refs/heads +test_atom head refname:rstrip=2 refs +test_atom head refname:rstrip=-1 refs +test_atom head refname:rstrip=-2 refs/heads +test_atom head refname:strip=1 heads/main +test_atom head refname:strip=2 main +test_atom head refname:strip=-1 main +test_atom head refname:strip=-2 heads/main +test_atom head upstream refs/remotes/origin/main +test_atom head upstream:short origin/main +test_atom head upstream:lstrip=2 origin/main +test_atom head upstream:lstrip=-2 origin/main +test_atom head upstream:rstrip=2 refs/remotes +test_atom head upstream:rstrip=-2 refs/remotes +test_atom head upstream:strip=2 origin/main +test_atom head upstream:strip=-2 origin/main +test_atom head push refs/remotes/myfork/main +test_atom head push:short myfork/main +test_atom head push:lstrip=1 remotes/myfork/main +test_atom head push:lstrip=-1 main +test_atom head push:rstrip=1 refs/remotes/myfork +test_atom head push:rstrip=-1 refs +test_atom head push:strip=1 remotes/myfork/main +test_atom head push:strip=-1 main +test_atom head objecttype commit +test_atom head objectsize $((131 + hexlen)) +test_atom head objectsize:disk $(test_object_file_size refs/heads/main) +test_atom head deltabase $ZERO_OID +test_atom head objectname $(git rev-parse refs/heads/main) +test_atom head objectname:short $(git rev-parse --short refs/heads/main) +test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/main) +test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/main) +test_atom head tree $(git rev-parse refs/heads/main^{tree}) +test_atom head tree:short $(git rev-parse --short refs/heads/main^{tree}) +test_atom head tree:short=1 $(git rev-parse --short=1 refs/heads/main^{tree}) +test_atom head tree:short=10 $(git rev-parse --short=10 refs/heads/main^{tree}) +test_atom head parent '' +test_atom head parent:short '' +test_atom head parent:short=1 '' +test_atom head parent:short=10 '' +test_atom head numparent 0 +test_atom head object '' +test_atom head type '' +test_atom head raw "$(git cat-file commit refs/heads/main) +" +test_atom head '*objectname' '' +test_atom head '*objecttype' '' +test_atom head author 'A U Thor <author@example.com> 1151968724 +0200' +test_atom head authorname 'A U Thor' +test_atom head authorname:mailmap 'A Thor' +test_atom head authoremail '<author@example.com>' +test_atom head authoremail:trim 'author@example.com' +test_atom head authoremail:localpart 'author' +test_atom head authoremail:trim,localpart 'author' +test_atom head authoremail:mailmap '<athor@example.com>' +test_atom head authoremail:mailmap,trim 'athor@example.com' +test_atom head authoremail:trim,mailmap 'athor@example.com' +test_atom head authoremail:mailmap,localpart 'athor' +test_atom head authoremail:localpart,mailmap 'athor' +test_atom head authoremail:mailmap,trim,localpart,mailmap,trim 'athor' +test_atom head authordate 'Tue Jul 4 01:18:44 2006 +0200' +test_atom head committer 'C O Mitter <committer@example.com> 1151968723 +0200' +test_atom head committername 'C O Mitter' +test_atom head committername:mailmap 'C Mitter' +test_atom head committeremail '<committer@example.com>' +test_atom head committeremail:trim 'committer@example.com' +test_atom head committeremail:localpart 'committer' +test_atom head committeremail:localpart,trim 'committer' +test_atom head committeremail:mailmap '<cmitter@example.com>' +test_atom head committeremail:mailmap,trim 'cmitter@example.com' +test_atom head committeremail:trim,mailmap 'cmitter@example.com' +test_atom head committeremail:mailmap,localpart 'cmitter' +test_atom head committeremail:localpart,mailmap 'cmitter' +test_atom head committeremail:trim,mailmap,trim,trim,localpart 'cmitter' +test_atom head committerdate 'Tue Jul 4 01:18:43 2006 +0200' +test_atom head tag '' +test_atom head tagger '' +test_atom head taggername '' +test_atom head taggeremail '' +test_atom head taggeremail:trim '' +test_atom head taggeremail:localpart '' +test_atom head taggerdate '' +test_atom head creator 'C O Mitter <committer@example.com> 1151968723 +0200' +test_atom head creatordate 'Tue Jul 4 01:18:43 2006 +0200' +test_atom head subject 'Initial' +test_atom head subject:sanitize 'Initial' +test_atom head contents:subject 'Initial' +test_atom head body '' +test_atom head contents:body '' +test_atom head contents:signature '' +test_atom head contents 'Initial +' +test_atom head HEAD '*' + +test_atom tag refname refs/tags/testtag +test_atom tag refname:short testtag +test_atom tag upstream '' +test_atom tag push '' +test_atom tag objecttype tag +test_atom tag objectsize $((114 + hexlen)) +test_atom tag objectsize:disk $(test_object_file_size refs/tags/testtag) +test_atom tag '*objectsize:disk' $(test_object_file_size refs/heads/main) +test_atom tag deltabase $ZERO_OID +test_atom tag '*deltabase' $ZERO_OID +test_atom tag objectname $(git rev-parse refs/tags/testtag) +test_atom tag objectname:short $(git rev-parse --short refs/tags/testtag) +test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/main) +test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/main) +test_atom tag tree '' +test_atom tag tree:short '' +test_atom tag tree:short=1 '' +test_atom tag tree:short=10 '' +test_atom tag parent '' +test_atom tag parent:short '' +test_atom tag parent:short=1 '' +test_atom tag parent:short=10 '' +test_atom tag numparent '' +test_atom tag object $(git rev-parse refs/tags/testtag^0) +test_atom tag type 'commit' +test_atom tag '*objectname' $(git rev-parse refs/tags/testtag^{}) +test_atom tag '*objecttype' 'commit' +test_atom tag author '' +test_atom tag authorname '' +test_atom tag authorname:mailmap '' +test_atom tag authoremail '' +test_atom tag authoremail:trim '' +test_atom tag authoremail:localpart '' +test_atom tag authoremail:trim,localpart '' +test_atom tag authoremail:mailmap '' +test_atom tag authoremail:mailmap,trim '' +test_atom tag authoremail:trim,mailmap '' +test_atom tag authoremail:mailmap,localpart '' +test_atom tag authoremail:localpart,mailmap '' +test_atom tag authoremail:mailmap,trim,localpart,mailmap,trim '' +test_atom tag authordate '' +test_atom tag committer '' +test_atom tag committername '' +test_atom tag committername:mailmap '' +test_atom tag committeremail '' +test_atom tag committeremail:trim '' +test_atom tag committeremail:localpart '' +test_atom tag committeremail:localpart,trim '' +test_atom tag committeremail:mailmap '' +test_atom tag committeremail:mailmap,trim '' +test_atom tag committeremail:trim,mailmap '' +test_atom tag committeremail:mailmap,localpart '' +test_atom tag committeremail:localpart,mailmap '' +test_atom tag committeremail:trim,mailmap,trim,trim,localpart '' +test_atom tag committerdate '' +test_atom tag tag 'testtag' +test_atom tag tagger 'C O Mitter <committer@example.com> 1151968725 +0200' +test_atom tag taggername 'C O Mitter' +test_atom tag taggername:mailmap 'C Mitter' +test_atom tag taggeremail '<committer@example.com>' +test_atom tag taggeremail:trim 'committer@example.com' +test_atom tag taggeremail:localpart 'committer' +test_atom tag taggeremail:trim,localpart 'committer' +test_atom tag taggeremail:mailmap '<cmitter@example.com>' +test_atom tag taggeremail:mailmap,trim 'cmitter@example.com' +test_atom tag taggeremail:trim,mailmap 'cmitter@example.com' +test_atom tag taggeremail:mailmap,localpart 'cmitter' +test_atom tag taggeremail:localpart,mailmap 'cmitter' +test_atom tag taggeremail:trim,mailmap,trim,localpart,localpart 'cmitter' +test_atom tag taggerdate 'Tue Jul 4 01:18:45 2006 +0200' +test_atom tag creator 'C O Mitter <committer@example.com> 1151968725 +0200' +test_atom tag creatordate 'Tue Jul 4 01:18:45 2006 +0200' +test_atom tag subject 'Tagging at 1151968727' +test_atom tag subject:sanitize 'Tagging-at-1151968727' +test_atom tag contents:subject 'Tagging at 1151968727' +test_atom tag body '' +test_atom tag contents:body '' +test_atom tag contents:signature '' +test_atom tag contents 'Tagging at 1151968727 +' +test_atom tag HEAD ' ' + +test_expect_success 'basic atom: refs/tags/testtag *raw' ' + git cat-file commit refs/tags/testtag^{} >expected && + ${git_for_each_ref} --format="%(*raw)" refs/tags/testtag >actual && + sanitize_pgp <expected >expected.clean && + echo >>expected.clean && + sanitize_pgp <actual >actual.clean && + test_cmp expected.clean actual.clean +' + +test_expect_success 'Check invalid atoms names are errors' ' + test_must_fail ${git_for_each_ref} --format="%(INVALID)" refs/heads +' + +test_expect_success 'Check format specifiers are ignored in naming date atoms' ' + ${git_for_each_ref} --format="%(authordate)" refs/heads && + ${git_for_each_ref} --format="%(authordate:default) %(authordate)" refs/heads && + ${git_for_each_ref} --format="%(authordate) %(authordate:default)" refs/heads && + ${git_for_each_ref} --format="%(authordate:default) %(authordate:default)" refs/heads +' + +test_expect_success 'Check valid format specifiers for date fields' ' + ${git_for_each_ref} --format="%(authordate:default)" refs/heads && + ${git_for_each_ref} --format="%(authordate:relative)" refs/heads && + ${git_for_each_ref} --format="%(authordate:short)" refs/heads && + ${git_for_each_ref} --format="%(authordate:local)" refs/heads && + ${git_for_each_ref} --format="%(authordate:iso8601)" refs/heads && + ${git_for_each_ref} --format="%(authordate:rfc2822)" refs/heads +' + +test_expect_success 'Check invalid format specifiers are errors' ' + test_must_fail ${git_for_each_ref} --format="%(authordate:INVALID)" refs/heads +' + +test_expect_success 'arguments to %(objectname:short=) must be positive integers' ' + test_must_fail ${git_for_each_ref} --format="%(objectname:short=0)" && + test_must_fail ${git_for_each_ref} --format="%(objectname:short=-1)" && + test_must_fail ${git_for_each_ref} --format="%(objectname:short=foo)" +' + +test_bad_atom () { + case "$1" in + head) ref=refs/heads/main ;; + tag) ref=refs/tags/testtag ;; + sym) ref=refs/heads/sym ;; + *) ref=$1 ;; + esac + format=$2 + test_do=test_expect_${4:-success} + + printf '%s\n' "$3" >expect + $test_do $PREREQ "err basic atom: $ref $format" ' + test_must_fail ${git_for_each_ref} \ + --format="%($format)" "$ref" 2>error && + test_cmp expect error + ' +} + +test_bad_atom head 'authoremail:foo' \ + 'fatal: unrecognized %(authoremail) argument: foo' + +test_bad_atom head 'authoremail:mailmap,trim,bar' \ + 'fatal: unrecognized %(authoremail) argument: bar' + +test_bad_atom head 'authoremail:trim,' \ + 'fatal: unrecognized %(authoremail) argument: ' + +test_bad_atom head 'authoremail:mailmaptrim' \ + 'fatal: unrecognized %(authoremail) argument: trim' + +test_bad_atom head 'committeremail: ' \ + 'fatal: unrecognized %(committeremail) argument: ' + +test_bad_atom head 'committeremail: trim,foo' \ + 'fatal: unrecognized %(committeremail) argument: trim,foo' + +test_bad_atom head 'committeremail:mailmap,localpart ' \ + 'fatal: unrecognized %(committeremail) argument: ' + +test_bad_atom head 'committeremail:trim_localpart' \ + 'fatal: unrecognized %(committeremail) argument: _localpart' + +test_bad_atom head 'committeremail:localpart,,,trim' \ + 'fatal: unrecognized %(committeremail) argument: ,,trim' + +test_bad_atom tag 'taggeremail:mailmap,trim, foo ' \ + 'fatal: unrecognized %(taggeremail) argument: foo ' + +test_bad_atom tag 'taggeremail:trim,localpart,' \ + 'fatal: unrecognized %(taggeremail) argument: ' + +test_bad_atom tag 'taggeremail:mailmap;localpart trim' \ + 'fatal: unrecognized %(taggeremail) argument: ;localpart trim' + +test_bad_atom tag 'taggeremail:localpart trim' \ + 'fatal: unrecognized %(taggeremail) argument: trim' + +test_bad_atom tag 'taggeremail:mailmap,mailmap,trim,qux,localpart,trim' \ + 'fatal: unrecognized %(taggeremail) argument: qux,localpart,trim' + +test_date () { + f=$1 && + committer_date=$2 && + author_date=$3 && + tagger_date=$4 && + cat >expected <<-EOF && + 'refs/heads/main' '$committer_date' '$author_date' + 'refs/tags/testtag' '$tagger_date' + EOF + ( + ${git_for_each_ref} --shell \ + --format="%(refname) %(committerdate${f:+:$f}) %(authordate${f:+:$f})" \ + refs/heads && + ${git_for_each_ref} --shell \ + --format="%(refname) %(taggerdate${f:+:$f})" \ + refs/tags + ) >actual && + test_cmp expected actual +} + +test_expect_success 'Check unformatted date fields output' ' + test_date "" \ + "Tue Jul 4 01:18:43 2006 +0200" \ + "Tue Jul 4 01:18:44 2006 +0200" \ + "Tue Jul 4 01:18:45 2006 +0200" +' + +test_expect_success 'Check format "default" formatted date fields output' ' + test_date default \ + "Tue Jul 4 01:18:43 2006 +0200" \ + "Tue Jul 4 01:18:44 2006 +0200" \ + "Tue Jul 4 01:18:45 2006 +0200" +' + +test_expect_success 'Check format "default-local" date fields output' ' + test_date default-local "Mon Jul 3 23:18:43 2006" "Mon Jul 3 23:18:44 2006" "Mon Jul 3 23:18:45 2006" +' + +# Don't know how to do relative check because I can't know when this script +# is going to be run and can't fake the current time to git, and hence can't +# provide expected output. Instead, I'll just make sure that "relative" +# doesn't exit in error +test_expect_success 'Check format "relative" date fields output' ' + f=relative && + (${git_for_each_ref} --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && + ${git_for_each_ref} --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual +' + +# We just check that this is the same as "relative" for now. +test_expect_success 'Check format "relative-local" date fields output' ' + test_date relative-local \ + "$(${git_for_each_ref} --format="%(committerdate:relative)" refs/heads)" \ + "$(${git_for_each_ref} --format="%(authordate:relative)" refs/heads)" \ + "$(${git_for_each_ref} --format="%(taggerdate:relative)" refs/tags)" +' + +test_expect_success 'Check format "short" date fields output' ' + test_date short 2006-07-04 2006-07-04 2006-07-04 +' + +test_expect_success 'Check format "short-local" date fields output' ' + test_date short-local 2006-07-03 2006-07-03 2006-07-03 +' + +test_expect_success 'Check format "local" date fields output' ' + test_date local \ + "Mon Jul 3 23:18:43 2006" \ + "Mon Jul 3 23:18:44 2006" \ + "Mon Jul 3 23:18:45 2006" +' + +test_expect_success 'Check format "iso8601" date fields output' ' + test_date iso8601 \ + "2006-07-04 01:18:43 +0200" \ + "2006-07-04 01:18:44 +0200" \ + "2006-07-04 01:18:45 +0200" +' + +test_expect_success 'Check format "iso8601-local" date fields output' ' + test_date iso8601-local "2006-07-03 23:18:43 +0000" "2006-07-03 23:18:44 +0000" "2006-07-03 23:18:45 +0000" +' + +test_expect_success 'Check format "rfc2822" date fields output' ' + test_date rfc2822 \ + "Tue, 4 Jul 2006 01:18:43 +0200" \ + "Tue, 4 Jul 2006 01:18:44 +0200" \ + "Tue, 4 Jul 2006 01:18:45 +0200" +' + +test_expect_success 'Check format "rfc2822-local" date fields output' ' + test_date rfc2822-local "Mon, 3 Jul 2006 23:18:43 +0000" "Mon, 3 Jul 2006 23:18:44 +0000" "Mon, 3 Jul 2006 23:18:45 +0000" +' + +test_expect_success 'Check format "raw" date fields output' ' + test_date raw "1151968723 +0200" "1151968724 +0200" "1151968725 +0200" +' + +test_expect_success 'Check format "raw-local" date fields output' ' + test_date raw-local "1151968723 +0000" "1151968724 +0000" "1151968725 +0000" +' + +test_expect_success 'Check format of strftime date fields' ' + echo "my date is 2006-07-04" >expected && + ${git_for_each_ref} \ + --format="%(authordate:format:my date is %Y-%m-%d)" \ + refs/heads >actual && + test_cmp expected actual +' + +test_expect_success 'Check format of strftime-local date fields' ' + echo "my date is 2006-07-03" >expected && + ${git_for_each_ref} \ + --format="%(authordate:format-local:my date is %Y-%m-%d)" \ + refs/heads >actual && + test_cmp expected actual +' + +test_expect_success 'exercise strftime with odd fields' ' + echo >expected && + ${git_for_each_ref} --format="%(authordate:format:)" refs/heads >actual && + test_cmp expected actual && + long="long format -- $ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID" && + echo $long >expected && + ${git_for_each_ref} --format="%(authordate:format:$long)" refs/heads >actual && + test_cmp expected actual +' + +cat >expected <<\EOF +refs/heads/main +refs/remotes/origin/main +refs/tags/testtag +EOF + +test_expect_success 'Verify ascending sort' ' + ${git_for_each_ref} --format="%(refname)" --sort=refname >actual && + test_cmp expected actual +' + + +cat >expected <<\EOF +refs/tags/testtag +refs/remotes/origin/main +refs/heads/main +EOF + +test_expect_success 'Verify descending sort' ' + ${git_for_each_ref} --format="%(refname)" --sort=-refname >actual && + test_cmp expected actual +' + +test_expect_success 'Give help even with invalid sort atoms' ' + test_expect_code 129 ${git_for_each_ref} --sort=bogus -h >actual 2>&1 && + grep "^usage: ${git_for_each_ref}" actual +' + +cat >expected <<\EOF +refs/tags/testtag +refs/tags/testtag-2 +EOF + +test_expect_success 'exercise patterns with prefixes' ' + git tag testtag-2 && + test_when_finished "git tag -d testtag-2" && + ${git_for_each_ref} --format="%(refname)" \ + refs/tags/testtag refs/tags/testtag-2 >actual && + test_cmp expected actual +' + +cat >expected <<\EOF +refs/tags/testtag +refs/tags/testtag-2 +EOF + +test_expect_success 'exercise glob patterns with prefixes' ' + git tag testtag-2 && + test_when_finished "git tag -d testtag-2" && + ${git_for_each_ref} --format="%(refname)" \ + refs/tags/testtag "refs/tags/testtag-*" >actual && + test_cmp expected actual +' + +cat >expected <<\EOF +refs/tags/bar +refs/tags/baz +refs/tags/testtag +EOF + +test_expect_success 'exercise patterns with prefix exclusions' ' + for tag in foo/one foo/two foo/three bar baz + do + git tag "$tag" || return 1 + done && + test_when_finished "git tag -d foo/one foo/two foo/three bar baz" && + ${git_for_each_ref} --format="%(refname)" \ + refs/tags/ --exclude=refs/tags/foo >actual && + test_cmp expected actual +' + +cat >expected <<\EOF +refs/tags/bar +refs/tags/baz +refs/tags/foo/one +refs/tags/testtag +EOF + +test_expect_success 'exercise patterns with pattern exclusions' ' + for tag in foo/one foo/two foo/three bar baz + do + git tag "$tag" || return 1 + done && + test_when_finished "git tag -d foo/one foo/two foo/three bar baz" && + ${git_for_each_ref} --format="%(refname)" \ + refs/tags/ --exclude="refs/tags/foo/t*" >actual && + test_cmp expected actual +' + +cat >expected <<\EOF +'refs/heads/main' +'refs/remotes/origin/main' +'refs/tags/testtag' +EOF + +test_expect_success 'Quoting style: shell' ' + ${git_for_each_ref} --shell --format="%(refname)" >actual && + test_cmp expected actual +' + +test_expect_success 'Quoting style: perl' ' + ${git_for_each_ref} --perl --format="%(refname)" >actual && + test_cmp expected actual +' + +test_expect_success 'Quoting style: python' ' + ${git_for_each_ref} --python --format="%(refname)" >actual && + test_cmp expected actual +' + +cat >expected <<\EOF +"refs/heads/main" +"refs/remotes/origin/main" +"refs/tags/testtag" +EOF + +test_expect_success 'Quoting style: tcl' ' + ${git_for_each_ref} --tcl --format="%(refname)" >actual && + test_cmp expected actual +' + +for i in "--perl --shell" "-s --python" "--python --tcl" "--tcl --perl"; do + test_expect_success "more than one quoting style: $i" " + test_must_fail ${git_for_each_ref} $i 2>err && + grep '^error: more than one quoting style' err + " +done + +test_expect_success 'setup for upstream:track[short]' ' + test_commit two +' + +test_atom head upstream:track '[ahead 1]' +test_atom head upstream:trackshort '>' +test_atom head upstream:track,nobracket 'ahead 1' +test_atom head upstream:nobracket,track 'ahead 1' + +test_expect_success 'setup for push:track[short]' ' + test_commit third && + git update-ref refs/remotes/myfork/main main && + git reset main~1 +' + +test_atom head push:track '[behind 1]' +test_atom head push:trackshort '<' + +test_expect_success 'Check that :track[short] cannot be used with other atoms' ' + test_must_fail ${git_for_each_ref} --format="%(refname:track)" 2>/dev/null && + test_must_fail ${git_for_each_ref} --format="%(refname:trackshort)" 2>/dev/null +' + +test_expect_success 'Check that :track[short] works when upstream is invalid' ' + cat >expected <<-\EOF && + [gone] + + EOF + test_when_finished "git config branch.main.merge refs/heads/main" && + git config branch.main.merge refs/heads/does-not-exist && + ${git_for_each_ref} \ + --format="%(upstream:track)$LF%(upstream:trackshort)" \ + refs/heads >actual && + test_cmp expected actual +' + +test_expect_success 'Check for invalid refname format' ' + test_must_fail ${git_for_each_ref} --format="%(refname:INVALID)" +' + +test_expect_success 'set up color tests' ' + cat >expected.color <<-EOF && + $(git rev-parse --short refs/heads/main) <GREEN>main<RESET> + $(git rev-parse --short refs/remotes/myfork/main) <GREEN>myfork/main<RESET> + $(git rev-parse --short refs/remotes/origin/main) <GREEN>origin/main<RESET> + $(git rev-parse --short refs/tags/testtag) <GREEN>testtag<RESET> + $(git rev-parse --short refs/tags/third) <GREEN>third<RESET> + $(git rev-parse --short refs/tags/two) <GREEN>two<RESET> + EOF + sed "s/<[^>]*>//g" <expected.color >expected.bare && + color_format="%(objectname:short) %(color:green)%(refname:short)" +' + +test_expect_success TTY '%(color) shows color with a tty' ' + test_terminal ${git_for_each_ref} --format="$color_format" >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expected.color actual +' + +test_expect_success '%(color) does not show color without tty' ' + TERM=vt100 ${git_for_each_ref} --format="$color_format" >actual && + test_cmp expected.bare actual +' + +test_expect_success '--color can override tty check' ' + ${git_for_each_ref} --color --format="$color_format" >actual.raw && + test_decode_color <actual.raw >actual && + test_cmp expected.color actual +' + +test_expect_success 'color.ui=always does not override tty check' ' + git -c color.ui=always ${git_for_each_ref#git} --format="$color_format" >actual && + test_cmp expected.bare actual +' + +test_expect_success 'setup for describe atom tests' ' + git init -b master describe-repo && + ( + cd describe-repo && + + test_commit --no-tag one && + git tag tagone && + + test_commit --no-tag two && + git tag -a -m "tag two" tagtwo + ) +' + +test_expect_success 'describe atom vs git describe' ' + ( + cd describe-repo && + + ${git_for_each_ref} --format="%(objectname)" \ + refs/tags/ >obj && + while read hash + do + if desc=$(git describe $hash) + then + : >expect-contains-good + else + : >expect-contains-bad + fi && + echo "$hash $desc" || return 1 + done <obj >expect && + test_path_exists expect-contains-good && + test_path_exists expect-contains-bad && + + ${git_for_each_ref} --format="%(objectname) %(describe)" \ + refs/tags/ >actual 2>err && + test_cmp expect actual && + test_must_be_empty err + ) +' + +test_expect_success 'describe:tags vs describe --tags' ' + ( + cd describe-repo && + git describe --tags >expect && + ${git_for_each_ref} --format="%(describe:tags)" \ + refs/heads/master >actual && + test_cmp expect actual + ) +' + +test_expect_success 'describe:abbrev=... vs describe --abbrev=...' ' + ( + cd describe-repo && + + # Case 1: We have commits between HEAD and the most + # recent tag reachable from it + test_commit --no-tag file && + git describe --abbrev=14 >expect && + ${git_for_each_ref} --format="%(describe:abbrev=14)" \ + refs/heads/master >actual && + test_cmp expect actual && + + # Make sure the hash used is at least 14 digits long + sed -e "s/^.*-g\([0-9a-f]*\)$/\1/" <actual >hexpart && + test 15 -le $(wc -c <hexpart) && + + # Case 2: We have a tag at HEAD, describe directly gives + # the name of the tag + git tag -a -m tagged tagname && + git describe --abbrev=14 >expect && + ${git_for_each_ref} --format="%(describe:abbrev=14)" \ + refs/heads/master >actual && + test_cmp expect actual && + test tagname = $(cat actual) + ) +' + +test_expect_success 'describe:match=... vs describe --match ...' ' + ( + cd describe-repo && + git tag -a -m "tag foo" tag-foo && + git describe --match "*-foo" >expect && + ${git_for_each_ref} --format="%(describe:match="*-foo")" \ + refs/heads/master >actual && + test_cmp expect actual + ) +' + +test_expect_success 'describe:exclude:... vs describe --exclude ...' ' + ( + cd describe-repo && + git tag -a -m "tag bar" tag-bar && + git describe --exclude "*-bar" >expect && + ${git_for_each_ref} --format="%(describe:exclude="*-bar")" \ + refs/heads/master >actual && + test_cmp expect actual + ) +' + +test_expect_success 'deref with describe atom' ' + ( + cd describe-repo && + cat >expect <<-\EOF && + + tagname + tagname + tagname + + tagtwo + EOF + ${git_for_each_ref} --format="%(*describe)" >actual && + test_cmp expect actual + ) +' + +test_expect_success 'err on bad describe atom arg' ' + ( + cd describe-repo && + + # The bad arg is the only arg passed to describe atom + cat >expect <<-\EOF && + fatal: unrecognized %(describe) argument: baz + EOF + test_must_fail ${git_for_each_ref} --format="%(describe:baz)" \ + refs/heads/master 2>actual && + test_cmp expect actual && + + # The bad arg is in the middle of the option string + # passed to the describe atom + cat >expect <<-\EOF && + fatal: unrecognized %(describe) argument: qux=1,abbrev=14 + EOF + test_must_fail ${git_for_each_ref} \ + --format="%(describe:tags,qux=1,abbrev=14)" \ + ref/heads/master 2>actual && + test_cmp expect actual + ) +' + +cat >expected <<\EOF +heads/main +tags/main +EOF + +test_expect_success 'Check ambiguous head and tag refs (strict)' ' + git config --bool core.warnambiguousrefs true && + git checkout -b newtag && + echo "Using $datestamp" > one && + git add one && + git commit -m "Branch" && + setdate_and_increment && + git tag -m "Tagging at $datestamp" main && + ${git_for_each_ref} --format "%(refname:short)" refs/heads/main refs/tags/main >actual && + test_cmp expected actual +' + +cat >expected <<\EOF +heads/main +main +EOF + +test_expect_success 'Check ambiguous head and tag refs (loose)' ' + git config --bool core.warnambiguousrefs false && + ${git_for_each_ref} --format "%(refname:short)" refs/heads/main refs/tags/main >actual && + test_cmp expected actual +' + +cat >expected <<\EOF +heads/ambiguous +ambiguous +EOF + +test_expect_success 'Check ambiguous head and tag refs II (loose)' ' + git checkout main && + git tag ambiguous testtag^0 && + git branch ambiguous testtag^0 && + ${git_for_each_ref} --format "%(refname:short)" refs/heads/ambiguous refs/tags/ambiguous >actual && + test_cmp expected actual +' + +test_expect_success 'create tag without tagger' ' + git tag -a -m "Broken tag" taggerless && + git tag -f taggerless $(git cat-file tag taggerless | + sed -e "/^tagger /d" | + git hash-object --literally --stdin -w -t tag) +' + +test_atom refs/tags/taggerless type 'commit' +test_atom refs/tags/taggerless tag 'taggerless' +test_atom refs/tags/taggerless tagger '' +test_atom refs/tags/taggerless taggername '' +test_atom refs/tags/taggerless taggeremail '' +test_atom refs/tags/taggerless taggeremail:trim '' +test_atom refs/tags/taggerless taggeremail:localpart '' +test_atom refs/tags/taggerless taggerdate '' +test_atom refs/tags/taggerless committer '' +test_atom refs/tags/taggerless committername '' +test_atom refs/tags/taggerless committeremail '' +test_atom refs/tags/taggerless committeremail:trim '' +test_atom refs/tags/taggerless committeremail:localpart '' +test_atom refs/tags/taggerless committerdate '' +test_atom refs/tags/taggerless subject 'Broken tag' + +test_expect_success 'an unusual tag with an incomplete line' ' + + git tag -m "bogo" bogo && + bogo=$(git cat-file tag bogo) && + bogo=$(printf "%s" "$bogo" | git mktag) && + git tag -f bogo "$bogo" && + ${git_for_each_ref} --format "%(body)" refs/tags/bogo + +' + +test_expect_success 'create tag with subject and body content' ' + cat >>msg <<-\EOF && + the subject line + + first body line + second body line + EOF + git tag -F msg subject-body +' +test_atom refs/tags/subject-body subject 'the subject line' +test_atom refs/tags/subject-body subject:sanitize 'the-subject-line' +test_atom refs/tags/subject-body body 'first body line +second body line +' +test_atom refs/tags/subject-body contents 'the subject line + +first body line +second body line +' + +test_expect_success 'create tag with multiline subject' ' + cat >msg <<-\EOF && + first subject line + second subject line + + first body line + second body line + EOF + git tag -F msg multiline +' +test_atom refs/tags/multiline subject 'first subject line second subject line' +test_atom refs/tags/multiline subject:sanitize 'first-subject-line-second-subject-line' +test_atom refs/tags/multiline contents:subject 'first subject line second subject line' +test_atom refs/tags/multiline body 'first body line +second body line +' +test_atom refs/tags/multiline contents:body 'first body line +second body line +' +test_atom refs/tags/multiline contents:signature '' +test_atom refs/tags/multiline contents 'first subject line +second subject line + +first body line +second body line +' + +test_expect_success GPG 'create signed tags' ' + git tag -s -m "" signed-empty && + git tag -s -m "subject line" signed-short && + cat >msg <<-\EOF && + subject line + + body contents + EOF + git tag -s -F msg signed-long +' + +sig='-----BEGIN PGP SIGNATURE----- +-----END PGP SIGNATURE----- +' + +PREREQ=GPG +test_atom refs/tags/signed-empty subject '' +test_atom refs/tags/signed-empty subject:sanitize '' +test_atom refs/tags/signed-empty contents:subject '' +test_atom refs/tags/signed-empty body "$sig" +test_atom refs/tags/signed-empty contents:body '' +test_atom refs/tags/signed-empty contents:signature "$sig" +test_atom refs/tags/signed-empty contents "$sig" + +test_expect_success GPG 'basic atom: refs/tags/signed-empty raw' ' + git cat-file tag refs/tags/signed-empty >expected && + ${git_for_each_ref} --format="%(raw)" refs/tags/signed-empty >actual && + sanitize_pgp <expected >expected.clean && + echo >>expected.clean && + sanitize_pgp <actual >actual.clean && + test_cmp expected.clean actual.clean +' + +test_atom refs/tags/signed-short subject 'subject line' +test_atom refs/tags/signed-short subject:sanitize 'subject-line' +test_atom refs/tags/signed-short contents:subject 'subject line' +test_atom refs/tags/signed-short body "$sig" +test_atom refs/tags/signed-short contents:body '' +test_atom refs/tags/signed-short contents:signature "$sig" +test_atom refs/tags/signed-short contents "subject line +$sig" + +test_expect_success GPG 'basic atom: refs/tags/signed-short raw' ' + git cat-file tag refs/tags/signed-short >expected && + ${git_for_each_ref} --format="%(raw)" refs/tags/signed-short >actual && + sanitize_pgp <expected >expected.clean && + echo >>expected.clean && + sanitize_pgp <actual >actual.clean && + test_cmp expected.clean actual.clean +' + +test_atom refs/tags/signed-long subject 'subject line' +test_atom refs/tags/signed-long subject:sanitize 'subject-line' +test_atom refs/tags/signed-long contents:subject 'subject line' +test_atom refs/tags/signed-long body "body contents +$sig" +test_atom refs/tags/signed-long contents:body 'body contents +' +test_atom refs/tags/signed-long contents:signature "$sig" +test_atom refs/tags/signed-long contents "subject line + +body contents +$sig" + +test_expect_success GPG 'basic atom: refs/tags/signed-long raw' ' + git cat-file tag refs/tags/signed-long >expected && + ${git_for_each_ref} --format="%(raw)" refs/tags/signed-long >actual && + sanitize_pgp <expected >expected.clean && + echo >>expected.clean && + sanitize_pgp <actual >actual.clean && + test_cmp expected.clean actual.clean +' + +test_expect_success 'set up refs pointing to tree and blob' ' + git update-ref refs/mytrees/first refs/heads/main^{tree} && + git update-ref refs/myblobs/first refs/heads/main:one +' + +test_atom refs/mytrees/first subject "" +test_atom refs/mytrees/first contents:subject "" +test_atom refs/mytrees/first body "" +test_atom refs/mytrees/first contents:body "" +test_atom refs/mytrees/first contents:signature "" +test_atom refs/mytrees/first contents "" + +test_expect_success 'basic atom: refs/mytrees/first raw' ' + git cat-file tree refs/mytrees/first >expected && + echo >>expected && + ${git_for_each_ref} --format="%(raw)" refs/mytrees/first >actual && + test_cmp expected actual && + git cat-file -s refs/mytrees/first >expected && + ${git_for_each_ref} --format="%(raw:size)" refs/mytrees/first >actual && + test_cmp expected actual +' + +test_atom refs/myblobs/first subject "" +test_atom refs/myblobs/first contents:subject "" +test_atom refs/myblobs/first body "" +test_atom refs/myblobs/first contents:body "" +test_atom refs/myblobs/first contents:signature "" +test_atom refs/myblobs/first contents "" + +test_expect_success 'basic atom: refs/myblobs/first raw' ' + git cat-file blob refs/myblobs/first >expected && + echo >>expected && + ${git_for_each_ref} --format="%(raw)" refs/myblobs/first >actual && + test_cmp expected actual && + git cat-file -s refs/myblobs/first >expected && + ${git_for_each_ref} --format="%(raw:size)" refs/myblobs/first >actual && + test_cmp expected actual +' + +test_expect_success 'set up refs pointing to binary blob' ' + printf "a\0b\0c" >blob1 && + printf "a\0c\0b" >blob2 && + printf "\0a\0b\0c" >blob3 && + printf "abc" >blob4 && + printf "\0 \0 \0 " >blob5 && + printf "\0 \0a\0 " >blob6 && + printf " " >blob7 && + >blob8 && + obj=$(git hash-object -w blob1) && + git update-ref refs/myblobs/blob1 "$obj" && + obj=$(git hash-object -w blob2) && + git update-ref refs/myblobs/blob2 "$obj" && + obj=$(git hash-object -w blob3) && + git update-ref refs/myblobs/blob3 "$obj" && + obj=$(git hash-object -w blob4) && + git update-ref refs/myblobs/blob4 "$obj" && + obj=$(git hash-object -w blob5) && + git update-ref refs/myblobs/blob5 "$obj" && + obj=$(git hash-object -w blob6) && + git update-ref refs/myblobs/blob6 "$obj" && + obj=$(git hash-object -w blob7) && + git update-ref refs/myblobs/blob7 "$obj" && + obj=$(git hash-object -w blob8) && + git update-ref refs/myblobs/blob8 "$obj" +' + +test_expect_success 'Verify sorts with raw' ' + cat >expected <<-EOF && + refs/myblobs/blob8 + refs/myblobs/blob5 + refs/myblobs/blob6 + refs/myblobs/blob3 + refs/myblobs/blob7 + refs/mytrees/first + refs/myblobs/first + refs/myblobs/blob1 + refs/myblobs/blob2 + refs/myblobs/blob4 + refs/heads/main + EOF + ${git_for_each_ref} --format="%(refname)" --sort=raw \ + refs/heads/main refs/myblobs/ refs/mytrees/first >actual && + test_cmp expected actual +' + +test_expect_success 'Verify sorts with raw:size' ' + cat >expected <<-EOF && + refs/myblobs/blob8 + refs/myblobs/blob7 + refs/myblobs/blob4 + refs/myblobs/blob1 + refs/myblobs/blob2 + refs/myblobs/blob3 + refs/myblobs/blob5 + refs/myblobs/blob6 + refs/myblobs/first + refs/mytrees/first + refs/heads/main + EOF + ${git_for_each_ref} --format="%(refname)" --sort=raw:size \ + refs/heads/main refs/myblobs/ refs/mytrees/first >actual && + test_cmp expected actual +' + +test_expect_success 'validate raw atom with %(if:equals)' ' + cat >expected <<-EOF && + not equals + not equals + not equals + not equals + not equals + not equals + refs/myblobs/blob4 + not equals + not equals + not equals + not equals + not equals + EOF + ${git_for_each_ref} --format="%(if:equals=abc)%(raw)%(then)%(refname)%(else)not equals%(end)" \ + refs/myblobs/ refs/heads/ >actual && + test_cmp expected actual +' + +test_expect_success 'validate raw atom with %(if:notequals)' ' + cat >expected <<-EOF && + refs/heads/ambiguous + refs/heads/main + refs/heads/newtag + refs/myblobs/blob1 + refs/myblobs/blob2 + refs/myblobs/blob3 + equals + refs/myblobs/blob5 + refs/myblobs/blob6 + refs/myblobs/blob7 + refs/myblobs/blob8 + refs/myblobs/first + EOF + ${git_for_each_ref} --format="%(if:notequals=abc)%(raw)%(then)%(refname)%(else)equals%(end)" \ + refs/myblobs/ refs/heads/ >actual && + test_cmp expected actual +' + +test_expect_success 'empty raw refs with %(if)' ' + cat >expected <<-EOF && + refs/myblobs/blob1 not empty + refs/myblobs/blob2 not empty + refs/myblobs/blob3 not empty + refs/myblobs/blob4 not empty + refs/myblobs/blob5 not empty + refs/myblobs/blob6 not empty + refs/myblobs/blob7 empty + refs/myblobs/blob8 empty + refs/myblobs/first not empty + EOF + ${git_for_each_ref} --format="%(refname) %(if)%(raw)%(then)not empty%(else)empty%(end)" \ + refs/myblobs/ >actual && + test_cmp expected actual +' + +test_expect_success '%(raw) with --python must fail' ' + test_must_fail ${git_for_each_ref} --format="%(raw)" --python +' + +test_expect_success '%(raw) with --tcl must fail' ' + test_must_fail ${git_for_each_ref} --format="%(raw)" --tcl +' + +test_expect_success PERL_TEST_HELPERS '%(raw) with --perl' ' + ${git_for_each_ref} --format="\$name= %(raw); +print \"\$name\"" refs/myblobs/blob1 --perl | perl >actual && + cmp blob1 actual && + ${git_for_each_ref} --format="\$name= %(raw); +print \"\$name\"" refs/myblobs/blob3 --perl | perl >actual && + cmp blob3 actual && + ${git_for_each_ref} --format="\$name= %(raw); +print \"\$name\"" refs/myblobs/blob8 --perl | perl >actual && + cmp blob8 actual && + ${git_for_each_ref} --format="\$name= %(raw); +print \"\$name\"" refs/myblobs/first --perl | perl >actual && + cmp one actual && + git cat-file tree refs/mytrees/first > expected && + ${git_for_each_ref} --format="\$name= %(raw); +print \"\$name\"" refs/mytrees/first --perl | perl >actual && + cmp expected actual +' + +test_expect_success '%(raw) with --shell must fail' ' + test_must_fail ${git_for_each_ref} --format="%(raw)" --shell +' + +test_expect_success '%(raw) with --shell and --sort=raw must fail' ' + test_must_fail ${git_for_each_ref} --format="%(raw)" --sort=raw --shell +' + +test_expect_success '%(raw:size) with --shell' ' + ${git_for_each_ref} --format="%(raw:size)" | sed "s/^/$SQ/;s/$/$SQ/" >expect && + ${git_for_each_ref} --format="%(raw:size)" --shell >actual && + test_cmp expect actual +' + +test_expect_success "${git_for_each_ref} --format compare with cat-file --batch" ' + git rev-parse refs/mytrees/first | git cat-file --batch >expected && + ${git_for_each_ref} --format="%(objectname) %(objecttype) %(objectsize) +%(raw)" refs/mytrees/first >actual && + test_cmp expected actual +' + +test_expect_success 'verify sorts with contents:size' ' + cat >expect <<-\EOF && + refs/heads/main + refs/heads/newtag + refs/heads/ambiguous + EOF + ${git_for_each_ref} --format="%(refname)" \ + --sort=contents:size refs/heads/ >actual && + test_cmp expect actual +' + +test_expect_success 'set up multiple-sort tags' ' + for when in 100000 200000 + do + for email in user1 user2 + do + for ref in ref1 ref2 + do + GIT_COMMITTER_DATE="@$when +0000" \ + GIT_COMMITTER_EMAIL="$email@example.com" \ + git tag -m "tag $ref-$when-$email" \ + multi-$ref-$when-$email || return 1 + done + done + done +' + +test_expect_success 'Verify sort with multiple keys' ' + cat >expected <<-\EOF && + 100000 <user1@example.com> refs/tags/multi-ref2-100000-user1 + 100000 <user1@example.com> refs/tags/multi-ref1-100000-user1 + 100000 <user2@example.com> refs/tags/multi-ref2-100000-user2 + 100000 <user2@example.com> refs/tags/multi-ref1-100000-user2 + 200000 <user1@example.com> refs/tags/multi-ref2-200000-user1 + 200000 <user1@example.com> refs/tags/multi-ref1-200000-user1 + 200000 <user2@example.com> refs/tags/multi-ref2-200000-user2 + 200000 <user2@example.com> refs/tags/multi-ref1-200000-user2 + EOF + ${git_for_each_ref} \ + --format="%(taggerdate:unix) %(taggeremail) %(refname)" \ + --sort=-refname \ + --sort=taggeremail \ + --sort=taggerdate \ + "refs/tags/multi-*" >actual && + test_cmp expected actual +' + +test_expect_success 'equivalent sorts fall back on refname' ' + cat >expected <<-\EOF && + 100000 <user1@example.com> refs/tags/multi-ref1-100000-user1 + 100000 <user2@example.com> refs/tags/multi-ref1-100000-user2 + 100000 <user1@example.com> refs/tags/multi-ref2-100000-user1 + 100000 <user2@example.com> refs/tags/multi-ref2-100000-user2 + 200000 <user1@example.com> refs/tags/multi-ref1-200000-user1 + 200000 <user2@example.com> refs/tags/multi-ref1-200000-user2 + 200000 <user1@example.com> refs/tags/multi-ref2-200000-user1 + 200000 <user2@example.com> refs/tags/multi-ref2-200000-user2 + EOF + ${git_for_each_ref} \ + --format="%(taggerdate:unix) %(taggeremail) %(refname)" \ + --sort=taggerdate \ + "refs/tags/multi-*" >actual && + test_cmp expected actual +' + +test_expect_success '--no-sort cancels the previous sort keys' ' + cat >expected <<-\EOF && + 100000 <user1@example.com> refs/tags/multi-ref1-100000-user1 + 100000 <user2@example.com> refs/tags/multi-ref1-100000-user2 + 100000 <user1@example.com> refs/tags/multi-ref2-100000-user1 + 100000 <user2@example.com> refs/tags/multi-ref2-100000-user2 + 200000 <user1@example.com> refs/tags/multi-ref1-200000-user1 + 200000 <user2@example.com> refs/tags/multi-ref1-200000-user2 + 200000 <user1@example.com> refs/tags/multi-ref2-200000-user1 + 200000 <user2@example.com> refs/tags/multi-ref2-200000-user2 + EOF + ${git_for_each_ref} \ + --format="%(taggerdate:unix) %(taggeremail) %(refname)" \ + --sort=-refname \ + --sort=taggeremail \ + --no-sort \ + --sort=taggerdate \ + "refs/tags/multi-*" >actual && + test_cmp expected actual +' + +test_expect_success '--no-sort without subsequent --sort prints expected refs' ' + cat >expected <<-\EOF && + refs/tags/multi-ref1-100000-user1 + refs/tags/multi-ref1-100000-user2 + refs/tags/multi-ref1-200000-user1 + refs/tags/multi-ref1-200000-user2 + refs/tags/multi-ref2-100000-user1 + refs/tags/multi-ref2-100000-user2 + refs/tags/multi-ref2-200000-user1 + refs/tags/multi-ref2-200000-user2 + EOF + + # Sort the results with `sort` for a consistent comparison against + # expected + ${git_for_each_ref} \ + --format="%(refname)" \ + --no-sort \ + "refs/tags/multi-*" | sort >actual && + test_cmp expected actual +' + +test_expect_success 'set up custom date sorting' ' + # Dates: + # - Wed Feb 07 2024 21:34:20 +0000 + # - Tue Dec 14 1999 00:05:22 +0000 + # - Fri Jun 04 2021 11:26:51 +0000 + # - Mon Jan 22 2007 16:44:01 GMT+0000 + i=1 && + for when in 1707341660 945129922 1622806011 1169484241 + do + GIT_COMMITTER_DATE="@$when +0000" \ + GIT_COMMITTER_EMAIL="user@example.com" \ + git tag -m "tag $when" custom-dates-$i && + i=$(($i+1)) || return 1 + done +' + +test_expect_success 'sort by date defaults to full timestamp' ' + cat >expected <<-\EOF && + 945129922 refs/tags/custom-dates-2 + 1169484241 refs/tags/custom-dates-4 + 1622806011 refs/tags/custom-dates-3 + 1707341660 refs/tags/custom-dates-1 + EOF + + ${git_for_each_ref} \ + --format="%(creatordate:unix) %(refname)" \ + --sort=creatordate \ + "refs/tags/custom-dates-*" >actual && + test_cmp expected actual +' + +test_expect_success 'sort by custom date format' ' + cat >expected <<-\EOF && + 00:05:22 refs/tags/custom-dates-2 + 11:26:51 refs/tags/custom-dates-3 + 16:44:01 refs/tags/custom-dates-4 + 21:34:20 refs/tags/custom-dates-1 + EOF + + ${git_for_each_ref} \ + --format="%(creatordate:format:%H:%M:%S) %(refname)" \ + --sort="creatordate:format:%H:%M:%S" \ + "refs/tags/custom-dates-*" >actual && + test_cmp expected actual +' + +test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' ' + test_when_finished "git checkout main" && + ${git_for_each_ref} --format="%(HEAD) %(refname:short)" refs/heads/ >actual && + sed -e "s/^\* / /" actual >expect && + git checkout --orphan orphaned-branch && + ${git_for_each_ref} --format="%(HEAD) %(refname:short)" refs/heads/ >actual && + test_cmp expect actual +' + +cat >trailers <<EOF +Reviewed-by: A U Thor <author@example.com> +Signed-off-by: A U Thor <author@example.com> +[ v2 updated patch description ] +Acked-by: A U Thor + <author@example.com> +EOF + +unfold () { + perl -0pe 's/\n\s+/ /g' +} + +test_expect_success 'set up trailers for next test' ' + echo "Some contents" > two && + git add two && + git commit -F - <<-EOF + trailers: this commit message has trailers + + Some message contents + + $(cat trailers) + EOF +' + +test_trailer_option () { + if test "$#" -eq 3 + then + prereq="$1" + shift + fi && + title=$1 option=$2 + cat >expect + test_expect_success $prereq "$title" ' + ${git_for_each_ref} --format="%($option)" refs/heads/main >actual && + test_cmp expect actual && + ${git_for_each_ref} --format="%(contents:$option)" refs/heads/main >actual && + test_cmp expect actual + ' +} + +test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) unfolds trailers' \ + 'trailers:unfold' <<-EOF + $(unfold <trailers) + + EOF + +test_trailer_option '%(trailers:only) shows only "key: value" trailers' \ + 'trailers:only' <<-EOF + $(grep -v patch.description <trailers) + + EOF + +test_trailer_option '%(trailers:only=no,only=true) shows only "key: value" trailers' \ + 'trailers:only=no,only=true' <<-EOF + $(grep -v patch.description <trailers) + + EOF + +test_trailer_option '%(trailers:only=yes) shows only "key: value" trailers' \ + 'trailers:only=yes' <<-EOF + $(grep -v patch.description <trailers) + + EOF + +test_trailer_option '%(trailers:only=no) shows all trailers' \ + 'trailers:only=no' <<-EOF + $(cat trailers) + + EOF + +test_trailer_option PERL_TEST_HELPERS '%(trailers:only) and %(trailers:unfold) work together' \ + 'trailers:only,unfold' <<-EOF + $(grep -v patch.description <trailers | unfold) + + EOF + +test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) and %(trailers:only) work together' \ + 'trailers:unfold,only' <<-EOF + $(grep -v patch.description <trailers | unfold) + + EOF + +test_trailer_option '%(trailers:key=foo) shows that trailer' \ + 'trailers:key=Signed-off-by' <<-EOF + Signed-off-by: A U Thor <author@example.com> + + EOF + +test_trailer_option '%(trailers:key=foo) is case insensitive' \ + 'trailers:key=SiGned-oFf-bY' <<-EOF + Signed-off-by: A U Thor <author@example.com> + + EOF + +test_trailer_option '%(trailers:key=foo:) trailing colon also works' \ + 'trailers:key=Signed-off-by:' <<-EOF + Signed-off-by: A U Thor <author@example.com> + + EOF + +test_trailer_option '%(trailers:key=foo) multiple keys' \ + 'trailers:key=Reviewed-by:,key=Signed-off-by' <<-EOF + Reviewed-by: A U Thor <author@example.com> + Signed-off-by: A U Thor <author@example.com> + + EOF + +test_trailer_option '%(trailers:key=nonexistent) becomes empty' \ + 'trailers:key=Shined-off-by:' <<-EOF + + EOF + +test_trailer_option '%(trailers:key=foo) handles multiple lines even if folded' \ + 'trailers:key=Acked-by' <<-EOF + $(grep -v patch.description <trailers | grep -v Signed-off-by | grep -v Reviewed-by) + + EOF + +test_trailer_option '%(trailers:key=foo,unfold) properly unfolds' \ + 'trailers:key=Signed-Off-by,unfold' <<-EOF + $(unfold <trailers | grep Signed-off-by) + + EOF + +test_trailer_option '%(trailers:key=foo,only=no) also includes nontrailer lines' \ + 'trailers:key=Signed-off-by,only=no' <<-EOF + Signed-off-by: A U Thor <author@example.com> + $(grep patch.description <trailers) + + EOF + +test_trailer_option '%(trailers:key=foo,valueonly) shows only value' \ + 'trailers:key=Signed-off-by,valueonly' <<-EOF + A U Thor <author@example.com> + + EOF + +test_trailer_option '%(trailers:separator) changes separator' \ + 'trailers:separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF + Reviewed-by: A U Thor <author@example.com>,Signed-off-by: A U Thor <author@example.com> + EOF + +test_trailer_option '%(trailers:key_value_separator) changes key-value separator' \ + 'trailers:key_value_separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF + Reviewed-by,A U Thor <author@example.com> + Signed-off-by,A U Thor <author@example.com> + + EOF + +test_trailer_option '%(trailers:separator,key_value_separator) changes both separators' \ + 'trailers:separator=%x2C,key_value_separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF + Reviewed-by,A U Thor <author@example.com>,Signed-off-by,A U Thor <author@example.com> + EOF + +test_expect_success 'multiple %(trailers) use their own options' ' + git tag -F - tag-with-trailers <<-\EOF && + body + + one: foo + one: bar + two: baz + two: qux + EOF + t1="%(trailers:key=one,key_value_separator=W,separator=X)" && + t2="%(trailers:key=two,key_value_separator=Y,separator=Z)" && + ${git_for_each_ref} --format="$t1%0a$t2" refs/tags/tag-with-trailers >actual && + cat >expect <<-\EOF && + oneWfooXoneWbar + twoYbazZtwoYqux + EOF + test_cmp expect actual +' + +test_failing_trailer_option () { + title=$1 option=$2 + cat >expect + test_expect_success "$title" ' + # error message cannot be checked under i18n + test_must_fail ${git_for_each_ref} --format="%($option)" refs/heads/main 2>actual && + test_cmp expect actual && + test_must_fail ${git_for_each_ref} --format="%(contents:$option)" refs/heads/main 2>actual && + test_cmp expect actual + ' +} + +test_failing_trailer_option '%(trailers) rejects unknown trailers arguments' \ + 'trailers:unsupported' <<-\EOF + fatal: unknown %(trailers) argument: unsupported + EOF + +test_failing_trailer_option '%(trailers:key) without value is error' \ + 'trailers:key' <<-\EOF + fatal: expected %(trailers:key=<value>) + EOF + +test_expect_success 'if arguments, %(contents:trailers) shows error if colon is missing' ' + cat >expect <<-EOF && + fatal: unrecognized %(contents) argument: trailersonly + EOF + test_must_fail ${git_for_each_ref} --format="%(contents:trailersonly)" 2>actual && + test_cmp expect actual +' + +test_expect_success 'basic atom: head contents:trailers' ' + ${git_for_each_ref} --format="%(contents:trailers)" refs/heads/main >actual && + sanitize_pgp <actual >actual.clean && + # ${git_for_each_ref} ends with a blank line + cat >expect <<-EOF && + $(cat trailers) + + EOF + test_cmp expect actual.clean +' + +test_expect_success 'basic atom: rest must fail' ' + test_must_fail ${git_for_each_ref} --format="%(rest)" refs/heads/main +' + +test_expect_success 'HEAD atom does not take arguments' ' + test_must_fail ${git_for_each_ref} --format="%(HEAD:foo)" 2>err && + echo "fatal: %(HEAD) does not take arguments" >expect && + test_cmp expect err +' + +test_expect_success 'subject atom rejects unknown arguments' ' + test_must_fail ${git_for_each_ref} --format="%(subject:foo)" 2>err && + echo "fatal: unrecognized %(subject) argument: foo" >expect && + test_cmp expect err +' + +test_expect_success 'refname atom rejects unknown arguments' ' + test_must_fail ${git_for_each_ref} --format="%(refname:foo)" 2>err && + echo "fatal: unrecognized %(refname) argument: foo" >expect && + test_cmp expect err +' + +test_expect_success 'trailer parsing not fooled by --- line' ' + git commit --allow-empty -F - <<-\EOF && + this is the subject + + This is the body. The message has a "---" line which would confuse a + message+patch parser. But here we know we have only a commit message, + so we get it right. + + trailer: wrong + --- + This is more body. + + trailer: right + EOF + + { + echo "trailer: right" && + echo + } >expect && + ${git_for_each_ref} --format="%(trailers)" refs/heads/main >actual && + test_cmp expect actual +' + +test_expect_success 'Add symbolic ref for the following tests' ' + git symbolic-ref refs/heads/sym refs/heads/main +' + +cat >expected <<EOF +refs/heads/main +EOF + +test_expect_success 'Verify usage of %(symref) atom' ' + ${git_for_each_ref} --format="%(symref)" refs/heads/sym >actual && + test_cmp expected actual +' + +cat >expected <<EOF +heads/main +EOF + +test_expect_success 'Verify usage of %(symref:short) atom' ' + ${git_for_each_ref} --format="%(symref:short)" refs/heads/sym >actual && + test_cmp expected actual +' + +cat >expected <<EOF +main +heads/main +EOF + +test_expect_success 'Verify usage of %(symref:lstrip) atom' ' + ${git_for_each_ref} --format="%(symref:lstrip=2)" refs/heads/sym > actual && + ${git_for_each_ref} --format="%(symref:lstrip=-2)" refs/heads/sym >> actual && + test_cmp expected actual && + + ${git_for_each_ref} --format="%(symref:strip=2)" refs/heads/sym > actual && + ${git_for_each_ref} --format="%(symref:strip=-2)" refs/heads/sym >> actual && + test_cmp expected actual +' + +cat >expected <<EOF +refs +refs/heads +EOF + +test_expect_success 'Verify usage of %(symref:rstrip) atom' ' + ${git_for_each_ref} --format="%(symref:rstrip=2)" refs/heads/sym > actual && + ${git_for_each_ref} --format="%(symref:rstrip=-2)" refs/heads/sym >> actual && + test_cmp expected actual +' + +test_expect_success ':remotename and :remoteref' ' + git init remote-tests && + ( + cd remote-tests && + test_commit initial && + git branch -M main && + git remote add from fifth.coffee:blub && + git config branch.main.remote from && + git config branch.main.merge refs/heads/stable && + git remote add to southridge.audio:repo && + git config remote.to.push "refs/heads/*:refs/heads/pushed/*" && + git config branch.main.pushRemote to && + for pair in "%(upstream)=refs/remotes/from/stable" \ + "%(upstream:remotename)=from" \ + "%(upstream:remoteref)=refs/heads/stable" \ + "%(push)=refs/remotes/to/pushed/main" \ + "%(push:remotename)=to" \ + "%(push:remoteref)=refs/heads/pushed/main" + do + echo "${pair#*=}" >expect && + ${git_for_each_ref} --format="${pair%=*}" \ + refs/heads/main >actual && + test_cmp expect actual || exit 1 + done && + git branch push-simple && + git config branch.push-simple.pushRemote from && + actual="$(${git_for_each_ref} \ + --format="%(push:remotename),%(push:remoteref)" \ + refs/heads/push-simple)" && + test from, = "$actual" + ) +' + +test_expect_success "${git_for_each_ref} --ignore-case ignores case" ' + ${git_for_each_ref} --format="%(refname)" refs/heads/MAIN >actual && + test_must_be_empty actual && + + echo refs/heads/main >expect && + ${git_for_each_ref} --format="%(refname)" --ignore-case \ + refs/heads/MAIN >actual && + test_cmp expect actual +' + +test_expect_success "${git_for_each_ref} --omit-empty works" ' + ${git_for_each_ref} --format="%(refname)" >actual && + test_line_count -gt 1 actual && + ${git_for_each_ref} --format="%(if:equals=refs/heads/main)%(refname)%(then)%(refname)%(end)" --omit-empty >actual && + echo refs/heads/main >expect && + test_cmp expect actual +' + +test_expect_success "${git_for_each_ref} --ignore-case works on multiple sort keys" ' + # name refs numerically to avoid case-insensitive filesystem conflicts + nr=0 && + for email in a A b B + do + for subject in a A b B + do + GIT_COMMITTER_EMAIL="$email@example.com" \ + git tag -m "tag $subject" icase-$(printf %02d $nr) && + nr=$((nr+1))|| + return 1 + done + done && + ${git_for_each_ref} --ignore-case \ + --format="%(taggeremail) %(subject) %(refname)" \ + --sort=refname \ + --sort=subject \ + --sort=taggeremail \ + refs/tags/icase-* >actual && + cat >expect <<-\EOF && + <a@example.com> tag a refs/tags/icase-00 + <a@example.com> tag A refs/tags/icase-01 + <A@example.com> tag a refs/tags/icase-04 + <A@example.com> tag A refs/tags/icase-05 + <a@example.com> tag b refs/tags/icase-02 + <a@example.com> tag B refs/tags/icase-03 + <A@example.com> tag b refs/tags/icase-06 + <A@example.com> tag B refs/tags/icase-07 + <b@example.com> tag a refs/tags/icase-08 + <b@example.com> tag A refs/tags/icase-09 + <B@example.com> tag a refs/tags/icase-12 + <B@example.com> tag A refs/tags/icase-13 + <b@example.com> tag b refs/tags/icase-10 + <b@example.com> tag B refs/tags/icase-11 + <B@example.com> tag b refs/tags/icase-14 + <B@example.com> tag B refs/tags/icase-15 + EOF + test_cmp expect actual +' + +test_expect_success "${git_for_each_ref} reports broken tags" ' + git tag -m "good tag" broken-tag-good HEAD && + git cat-file tag broken-tag-good >good && + sed s/commit/blob/ <good >bad && + bad=$(git hash-object -w -t tag bad) && + git update-ref refs/tags/broken-tag-bad $bad && + test_must_fail ${git_for_each_ref} --format="%(*objectname)" \ + refs/tags/broken-tag-* +' + +test_expect_success 'set up tag with signature and no blank lines' ' + git tag -F - fake-sig-no-blanks <<-\EOF + this is the subject + -----BEGIN PGP SIGNATURE----- + not a real signature, but we just care about the + subject/body parsing. It is important here that + there are no blank lines in the signature. + -----END PGP SIGNATURE----- + EOF +' + +test_atom refs/tags/fake-sig-no-blanks contents:subject 'this is the subject' +test_atom refs/tags/fake-sig-no-blanks contents:body '' +test_atom refs/tags/fake-sig-no-blanks contents:signature "$sig" + +test_expect_success 'set up tag with CRLF signature' ' + append_cr <<-\EOF | + this is the subject + -----BEGIN PGP SIGNATURE----- + + not a real signature, but we just care about + the subject/body parsing. It is important here + that there is a blank line separating this + from the signature header. + -----END PGP SIGNATURE----- + EOF + git tag -F - --cleanup=verbatim fake-sig-crlf +' + +test_atom refs/tags/fake-sig-crlf contents:subject 'this is the subject' +test_atom refs/tags/fake-sig-crlf contents:body '' + +# CRLF is retained in the signature, so we have to pass our expected value +# through append_cr. But test_atom requires a shell string, which means command +# substitution, and the shell will strip trailing newlines from the output of +# the substitution. Hack around it by adding and then removing a dummy line. +sig_crlf="$(printf "%s" "$sig" | append_cr; echo dummy)" +sig_crlf=${sig_crlf%dummy} +test_atom refs/tags/fake-sig-crlf contents:signature "$sig_crlf" + +test_expect_success 'set up tag with signature and trailers' ' + git tag -F - fake-sig-trailer <<-\EOF + this is the subject + + this is the body + + My-Trailer: foo + -----BEGIN PGP SIGNATURE----- + + not a real signature, but we just care about the + subject/body/trailer parsing. + -----END PGP SIGNATURE----- + EOF +' + +# use "separator=" here to suppress the terminating newline +test_atom refs/tags/fake-sig-trailer trailers:separator= 'My-Trailer: foo' + +test_expect_success "${git_for_each_ref} --stdin: empty" ' + >in && + ${git_for_each_ref} --format="%(refname)" --stdin <in >actual && + ${git_for_each_ref} --format="%(refname)" >expect && + test_cmp expect actual +' + +test_expect_success "${git_for_each_ref} --stdin: fails if extra args" ' + >in && + test_must_fail ${git_for_each_ref} --format="%(refname)" \ + --stdin refs/heads/extra <in 2>err && + grep "unknown arguments supplied with --stdin" err +' + +test_expect_success "${git_for_each_ref} --stdin: matches" ' + cat >in <<-EOF && + refs/tags/multi* + refs/heads/amb* + EOF + + cat >expect <<-EOF && + refs/heads/ambiguous + refs/tags/multi-ref1-100000-user1 + refs/tags/multi-ref1-100000-user2 + refs/tags/multi-ref1-200000-user1 + refs/tags/multi-ref1-200000-user2 + refs/tags/multi-ref2-100000-user1 + refs/tags/multi-ref2-100000-user2 + refs/tags/multi-ref2-200000-user1 + refs/tags/multi-ref2-200000-user2 + refs/tags/multiline + EOF + + ${git_for_each_ref} --format="%(refname)" --stdin <in >actual && + test_cmp expect actual +' + +test_expect_success "${git_for_each_ref} with non-existing refs" ' + cat >in <<-EOF && + refs/heads/this-ref-does-not-exist + refs/tags/bogus + EOF + + ${git_for_each_ref} --format="%(refname)" --stdin <in >actual && + test_must_be_empty actual && + + xargs ${git_for_each_ref} --format="%(refname)" <in >actual && + test_must_be_empty actual +' + +test_expect_success "${git_for_each_ref} with nested tags" ' + git tag -am "Normal tag" nested/base HEAD && + git tag -am "Nested tag" nested/nest1 refs/tags/nested/base && + git tag -am "Double nested tag" nested/nest2 refs/tags/nested/nest1 && + + head_oid="$(git rev-parse HEAD)" && + base_tag_oid="$(git rev-parse refs/tags/nested/base)" && + nest1_tag_oid="$(git rev-parse refs/tags/nested/nest1)" && + nest2_tag_oid="$(git rev-parse refs/tags/nested/nest2)" && + + cat >expect <<-EOF && + refs/tags/nested/base $base_tag_oid tag $head_oid commit + refs/tags/nested/nest1 $nest1_tag_oid tag $head_oid commit + refs/tags/nested/nest2 $nest2_tag_oid tag $head_oid commit + EOF + + ${git_for_each_ref} \ + --format="%(refname) %(objectname) %(objecttype) %(*objectname) %(*objecttype)" \ + refs/tags/nested/ >actual && + test_cmp expect actual +' + +test_expect_success 'is-base atom with non-commits' ' + ${git_for_each_ref} --format="%(is-base:HEAD) %(refname)" >out 2>err && + grep "(HEAD) refs/heads/main" out && + + test_line_count = 2 err && + grep "error: object .* is a commit, not a blob" err && + grep "error: bad tag pointer to" err +' + +GRADE_FORMAT="%(signature:grade)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)" +TRUSTLEVEL_FORMAT="%(signature:trustlevel)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)" + +test_expect_success GPG 'setup for signature atom using gpg' ' + git checkout -b signed && + + test_when_finished "test_unconfig commit.gpgSign" && + + echo "1" >file && + git add file && + test_tick && + git commit -S -m "file: 1" && + git tag first-signed && + + echo "2" >file && + test_tick && + git commit -a -m "file: 2" && + git tag second-unsigned && + + git config commit.gpgSign 1 && + echo "3" >file && + test_tick && + git commit -a --no-gpg-sign -m "file: 3" && + git tag third-unsigned && + + test_tick && + git rebase -f HEAD^^ && git tag second-signed HEAD^ && + git tag third-signed && + + echo "4" >file && + test_tick && + git commit -a -SB7227189 -m "file: 4" && + git tag fourth-signed && + + echo "5" >file && + test_tick && + git commit -a --no-gpg-sign -m "file: 5" && + git tag fifth-unsigned && + + echo "6" >file && + test_tick && + git commit -a --no-gpg-sign -m "file: 6" && + + test_tick && + git rebase -f HEAD^^ && + git tag fifth-signed HEAD^ && + git tag sixth-signed && + + echo "7" >file && + test_tick && + git commit -a --no-gpg-sign -m "file: 7" && + git tag seventh-unsigned +' + +test_expect_success GPGSSH 'setup for signature atom using ssh' ' + test_when_finished "test_unconfig gpg.format user.signingkey" && + + test_config gpg.format ssh && + test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && + echo "8" >file && + test_tick && + git add file && + git commit -S -m "file: 8" && + git tag eighth-signed-ssh +' + +test_expect_success GPG2 'bare signature atom' ' + git verify-commit first-signed 2>expect && + echo >>expect && + ${git_for_each_ref} refs/tags/first-signed \ + --format="%(signature)" >actual && + test_cmp expect actual +' + +test_expect_success GPG 'show good signature with custom format' ' + git verify-commit first-signed && + cat >expect <<-\EOF && + G + 13B6F51ECDDE430D + C O Mitter <committer@example.com> + 73D758744BE721698EC54E8713B6F51ECDDE430D + 73D758744BE721698EC54E8713B6F51ECDDE430D + EOF + ${git_for_each_ref} refs/tags/first-signed \ + --format="$GRADE_FORMAT" >actual && + test_cmp expect actual +' +test_expect_success GPGSSH 'show good signature with custom format with ssh' ' + test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && + FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") && + cat >expect.tmpl <<-\EOF && + G + FINGERPRINT + principal with number 1 + FINGERPRINT + + EOF + sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect && + ${git_for_each_ref} refs/tags/eighth-signed-ssh \ + --format="$GRADE_FORMAT" >actual && + test_cmp expect actual +' + +test_expect_success GPG 'signature atom with grade option and bad signature' ' + git cat-file commit third-signed >raw && + sed -e "s/^file: 3/file: 3 forged/" raw >forged1 && + FORGED1=$(git hash-object -w -t commit forged1) && + git update-ref refs/tags/third-signed "$FORGED1" && + test_must_fail git verify-commit "$FORGED1" && + + cat >expect <<-\EOF && + B + 13B6F51ECDDE430D + C O Mitter <committer@example.com> + + + EOF + ${git_for_each_ref} refs/tags/third-signed \ + --format="$GRADE_FORMAT" >actual && + test_cmp expect actual +' + +test_expect_success GPG 'show untrusted signature with custom format' ' + cat >expect <<-\EOF && + U + 65A0EEA02E30CAD7 + Eris Discordia <discord@example.net> + F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7 + D4BE22311AD3131E5EDA29A461092E85B7227189 + EOF + ${git_for_each_ref} refs/tags/fourth-signed \ + --format="$GRADE_FORMAT" >actual && + test_cmp expect actual +' + +test_expect_success GPG 'show untrusted signature with undefined trust level' ' + cat >expect <<-\EOF && + undefined + 65A0EEA02E30CAD7 + Eris Discordia <discord@example.net> + F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7 + D4BE22311AD3131E5EDA29A461092E85B7227189 + EOF + ${git_for_each_ref} refs/tags/fourth-signed \ + --format="$TRUSTLEVEL_FORMAT" >actual && + test_cmp expect actual +' + +test_expect_success GPG 'show untrusted signature with ultimate trust level' ' + cat >expect <<-\EOF && + ultimate + 13B6F51ECDDE430D + C O Mitter <committer@example.com> + 73D758744BE721698EC54E8713B6F51ECDDE430D + 73D758744BE721698EC54E8713B6F51ECDDE430D + EOF + ${git_for_each_ref} refs/tags/sixth-signed \ + --format="$TRUSTLEVEL_FORMAT" >actual && + test_cmp expect actual +' + +test_expect_success GPG 'show unknown signature with custom format' ' + cat >expect <<-\EOF && + E + 13B6F51ECDDE430D + + + + EOF + GNUPGHOME="$GNUPGHOME_NOT_USED" ${git_for_each_ref} \ + refs/tags/sixth-signed --format="$GRADE_FORMAT" >actual && + test_cmp expect actual +' + +test_expect_success GPG 'show lack of signature with custom format' ' + cat >expect <<-\EOF && + N + + + + + EOF + ${git_for_each_ref} refs/tags/seventh-unsigned \ + --format="$GRADE_FORMAT" >actual && + test_cmp expect actual +' + +test_done diff --git a/t/helper/test-hashmap.c b/t/helper/test-hashmap.c index 16a3145c3a..56d223a283 100644 --- a/t/helper/test-hashmap.c +++ b/t/helper/test-hashmap.c @@ -154,8 +154,8 @@ int cmd__hashmap(int argc UNUSED, const char **argv UNUSED) /* break line into command and up to two parameters */ string_list_setlen(&parts, 0); - string_list_split_in_place(&parts, line.buf, DELIM, 2); - string_list_remove_empty_items(&parts, 0); + string_list_split_in_place_f(&parts, line.buf, DELIM, 2, + STRING_LIST_SPLIT_NONEMPTY); /* ignore empty lines */ if (!parts.nr) diff --git a/t/helper/test-json-writer.c b/t/helper/test-json-writer.c index a288069b04..f8316a7d29 100644 --- a/t/helper/test-json-writer.c +++ b/t/helper/test-json-writer.c @@ -492,8 +492,8 @@ static int scripted(void) /* break line into command and zero or more tokens */ string_list_setlen(&parts, 0); - string_list_split_in_place(&parts, line, " ", -1); - string_list_remove_empty_items(&parts, 0); + string_list_split_in_place_f(&parts, line, " ", -1, + STRING_LIST_SPLIT_NONEMPTY); /* ignore empty lines */ if (!parts.nr || !*parts.items[0].string) diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c index 086238c826..f5f33751da 100644 --- a/t/helper/test-path-utils.c +++ b/t/helper/test-path-utils.c @@ -348,6 +348,7 @@ int cmd__path_utils(int argc, const char **argv) if (argc == 4 && !strcmp(argv[1], "longest_ancestor_length")) { int len; struct string_list ceiling_dirs = STRING_LIST_INIT_DUP; + const char path_sep[] = { PATH_SEP, '\0' }; char *path = xstrdup(argv[2]); /* @@ -362,7 +363,7 @@ int cmd__path_utils(int argc, const char **argv) */ if (normalize_path_copy(path, path)) die("Path \"%s\" could not be normalized", argv[2]); - string_list_split(&ceiling_dirs, argv[3], PATH_SEP, -1); + string_list_split(&ceiling_dirs, argv[3], path_sep, -1); filter_string_list(&ceiling_dirs, 0, normalize_ceiling_entry, NULL); len = longest_ancestor_length(path, &ceiling_dirs); diff --git a/t/helper/test-read-graph.c b/t/helper/test-read-graph.c index ef5339bbee..6a5f64e473 100644 --- a/t/helper/test-read-graph.c +++ b/t/helper/test-read-graph.c @@ -81,7 +81,7 @@ int cmd__read_graph(int argc, const char **argv) prepare_repo_settings(the_repository); - graph = read_commit_graph_one(the_repository, source); + graph = read_commit_graph_one(source); if (!graph) { ret = 1; goto done; diff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c index da2aa036b5..6de5d1665a 100644 --- a/t/helper/test-read-midx.c +++ b/t/helper/test-read-midx.c @@ -11,14 +11,24 @@ #include "gettext.h" #include "pack-revindex.h" +static struct multi_pack_index *setup_midx(const char *object_dir) +{ + struct odb_source *source; + setup_git_directory(); + source = odb_find_source(the_repository->objects, object_dir); + if (!source) + source = odb_add_to_alternates_memory(the_repository->objects, + object_dir); + return load_multi_pack_index(source); +} + static int read_midx_file(const char *object_dir, const char *checksum, int show_objects) { uint32_t i; struct multi_pack_index *m; - setup_git_directory(); - m = load_multi_pack_index(the_repository, object_dir, 1); + m = setup_midx(object_dir); if (!m) return 1; @@ -56,7 +66,7 @@ static int read_midx_file(const char *object_dir, const char *checksum, for (i = 0; i < m->num_packs; i++) printf("%s\n", m->pack_names[i]); - printf("object-dir: %s\n", m->object_dir); + printf("object-dir: %s\n", m->source->path); if (show_objects) { struct object_id oid; @@ -65,7 +75,7 @@ static int read_midx_file(const char *object_dir, const char *checksum, for (i = 0; i < m->num_objects; i++) { nth_midxed_object_oid(&oid, m, i + m->num_objects_in_base); - fill_midx_entry(the_repository, &oid, &e, m); + fill_midx_entry(m, &oid, &e); printf("%s %"PRIu64"\t%s\n", oid_to_hex(&oid), e.offset, e.p->pack_name); @@ -81,8 +91,7 @@ static int read_midx_checksum(const char *object_dir) { struct multi_pack_index *m; - setup_git_directory(); - m = load_multi_pack_index(the_repository, object_dir, 1); + m = setup_midx(object_dir); if (!m) return 1; printf("%s\n", hash_to_hex(get_midx_checksum(m))); @@ -96,9 +105,7 @@ static int read_midx_preferred_pack(const char *object_dir) struct multi_pack_index *midx = NULL; uint32_t preferred_pack; - setup_git_directory(); - - midx = load_multi_pack_index(the_repository, object_dir, 1); + midx = setup_midx(object_dir); if (!midx) return 1; @@ -119,14 +126,12 @@ static int read_midx_bitmapped_packs(const char *object_dir) struct bitmapped_pack pack; uint32_t i; - setup_git_directory(); - - midx = load_multi_pack_index(the_repository, object_dir, 1); + midx = setup_midx(object_dir); if (!midx) return 1; for (i = 0; i < midx->num_packs + midx->num_packs_in_base; i++) { - if (nth_bitmapped_pack(the_repository, midx, &pack, i) < 0) { + if (nth_bitmapped_pack(midx, &pack, i) < 0) { close_midx(midx); return 1; } diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c index 8d9a271845..83b06d39a3 100644 --- a/t/helper/test-ref-store.c +++ b/t/helper/test-ref-store.c @@ -29,7 +29,7 @@ static unsigned int parse_flags(const char *str, struct flag_definition *defs) if (!strcmp(str, "0")) return 0; - string_list_split(&masks, str, ',', 64); + string_list_split(&masks, str, ",", 64); for (size_t i = 0; i < masks.nr; i++) { const char *name = masks.items[i].string; struct flag_definition *def = defs; @@ -215,7 +215,8 @@ static int cmd_for_each_reflog(struct ref_store *refs, return refs_for_each_reflog(refs, each_reflog, NULL); } -static int each_reflog_ent(struct object_id *old_oid, struct object_id *new_oid, +static int each_reflog_ent(const char *refname UNUSED, + struct object_id *old_oid, struct object_id *new_oid, const char *committer, timestamp_t timestamp, int tz, const char *msg, void *cb_data UNUSED) { diff --git a/t/meson.build b/t/meson.build index bbeba1a8d5..7974795fe4 100644 --- a/t/meson.build +++ b/t/meson.build @@ -1,5 +1,6 @@ clar_test_suites = [ 'unit-tests/u-ctype.c', + 'unit-tests/u-dir.c', 'unit-tests/u-example-decorate.c', 'unit-tests/u-hash.c', 'unit-tests/u-hashmap.c', @@ -204,10 +205,14 @@ integration_tests = [ 't1418-reflog-exists.sh', 't1419-exclude-refs.sh', 't1420-lost-found.sh', + 't1421-reflog-write.sh', + 't1422-show-ref-exists.sh', 't1430-bad-ref-name.sh', 't1450-fsck.sh', 't1451-fsck-buffer.sh', 't1460-refs-migrate.sh', + 't1461-refs-list.sh', + 't1462-refs-exists.sh', 't1500-rev-parse.sh', 't1501-work-tree.sh', 't1502-rev-parse-parseopt.sh', @@ -230,6 +235,7 @@ integration_tests = [ 't1700-split-index.sh', 't1701-racy-split-index.sh', 't1800-hook.sh', + 't1900-repo.sh', 't2000-conflict-when-checking-files-out.sh', 't2002-checkout-cache-u.sh', 't2003-checkout-cache-mkdir.sh', @@ -486,6 +492,7 @@ integration_tests = [ 't4069-remerge-diff.sh', 't4070-diff-pairs.sh', 't4071-diff-minimal.sh', + 't4072-diff-max-depth.sh', 't4100-apply-stat.sh', 't4101-apply-nonl.sh', 't4102-apply-rename.sh', @@ -946,6 +953,7 @@ integration_tests = [ 't8012-blame-colors.sh', 't8013-blame-ignore-revs.sh', 't8014-blame-ignore-fuzzy.sh', + 't8020-last-modified.sh', 't9001-send-email.sh', 't9002-column.sh', 't9003-help-autocorrect.sh', @@ -1139,6 +1147,7 @@ benchmarks = [ 'perf/p7820-grep-engines.sh', 'perf/p7821-grep-engines-fixed.sh', 'perf/p7822-grep-perl-character.sh', + 'perf/p8020-last-modified.sh', 'perf/p9210-scalar.sh', 'perf/p9300-fast-import-export.sh', ] @@ -1214,4 +1223,4 @@ if perl.found() and time.found() timeout: 0, ) endforeach -endif
\ No newline at end of file +endif diff --git a/t/perf/p8020-last-modified.sh b/t/perf/p8020-last-modified.sh new file mode 100755 index 0000000000..cb1f98d3db --- /dev/null +++ b/t/perf/p8020-last-modified.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +test_description='last-modified perf tests' +. ./perf-lib.sh + +test_perf_default_repo + +test_perf 'top-level last-modified' ' + git last-modified HEAD +' + +test_perf 'top-level recursive last-modified' ' + git last-modified -r HEAD +' + +test_perf 'subdir last-modified' ' + git ls-tree -d HEAD >subtrees && + path="$(head -n 1 subtrees | cut -f2)" && + git last-modified -r HEAD -- "$path" +' + +test_done diff --git a/t/show-ref-exists-tests.sh b/t/show-ref-exists-tests.sh new file mode 100644 index 0000000000..36e8e9df33 --- /dev/null +++ b/t/show-ref-exists-tests.sh @@ -0,0 +1,77 @@ +git_show_ref_exists=${git_show_ref_exists:-git show-ref --exists} + +test_expect_success setup ' + test_commit --annotate A && + git checkout -b side && + test_commit --annotate B && + git checkout main && + test_commit C && + git branch B A^0 +' + +test_expect_success '--exists with existing reference' ' + ${git_show_ref_exists} refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +' + +test_expect_success '--exists with missing reference' ' + test_expect_code 2 ${git_show_ref_exists} refs/heads/does-not-exist +' + +test_expect_success '--exists does not use DWIM' ' + test_expect_code 2 ${git_show_ref_exists} $GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME 2>err && + grep "reference does not exist" err +' + +test_expect_success '--exists with HEAD' ' + ${git_show_ref_exists} HEAD +' + +test_expect_success '--exists with bad reference name' ' + test_when_finished "git update-ref -d refs/heads/bad...name" && + new_oid=$(git rev-parse HEAD) && + test-tool ref-store main update-ref msg refs/heads/bad...name $new_oid $ZERO_OID REF_SKIP_REFNAME_VERIFICATION && + ${git_show_ref_exists} refs/heads/bad...name +' + +test_expect_success '--exists with arbitrary symref' ' + test_when_finished "git symbolic-ref -d refs/symref" && + git symbolic-ref refs/symref refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME && + ${git_show_ref_exists} refs/symref +' + +test_expect_success '--exists with dangling symref' ' + test_when_finished "git symbolic-ref -d refs/heads/dangling" && + git symbolic-ref refs/heads/dangling refs/heads/does-not-exist && + ${git_show_ref_exists} refs/heads/dangling +' + +test_expect_success '--exists with nonexistent object ID' ' + test-tool ref-store main update-ref msg refs/heads/missing-oid $(test_oid 001) $ZERO_OID REF_SKIP_OID_VERIFICATION && + ${git_show_ref_exists} refs/heads/missing-oid +' + +test_expect_success '--exists with non-commit object' ' + tree_oid=$(git rev-parse HEAD^{tree}) && + test-tool ref-store main update-ref msg refs/heads/tree ${tree_oid} $ZERO_OID REF_SKIP_OID_VERIFICATION && + ${git_show_ref_exists} refs/heads/tree +' + +test_expect_success '--exists with directory fails with generic error' ' + cat >expect <<-EOF && + error: reference does not exist + EOF + test_expect_code 2 ${git_show_ref_exists} refs/heads 2>err && + test_cmp expect err +' + +test_expect_success '--exists with non-existent special ref' ' + test_expect_code 2 ${git_show_ref_exists} FETCH_HEAD +' + +test_expect_success '--exists with existing special ref' ' + test_when_finished "rm .git/FETCH_HEAD" && + git rev-parse HEAD >.git/FETCH_HEAD && + ${git_show_ref_exists} FETCH_HEAD +' + +test_done diff --git a/t/t0450-txt-doc-vs-help.sh b/t/t0450-txt-doc-vs-help.sh index 2f7504ae7e..e12e18f97f 100755 --- a/t/t0450-txt-doc-vs-help.sh +++ b/t/t0450-txt-doc-vs-help.sh @@ -41,7 +41,7 @@ help_to_synopsis () { } builtin_to_adoc () { - echo "$GIT_BUILD_DIR/Documentation/git-$1.adoc" + echo "$GIT_SOURCE_DIR/Documentation/git-$1.adoc" } adoc_to_synopsis () { @@ -112,10 +112,19 @@ do adoc="$(builtin_to_adoc "$builtin")" && preq="$(echo BUILTIN_ADOC_$builtin | tr '[:lower:]-' '[:upper:]_')" && - if test -f "$adoc" + # If and only if *.adoc is missing, builtin shall be listed in t0450/adoc-missing. + if grep -q "^$builtin$" "$TEST_DIRECTORY"/t0450/adoc-missing then + test_expect_success "$builtin appropriately marked as not having .adoc" ' + ! test -f "$adoc" + ' + else test_set_prereq "$preq" - fi && + + test_expect_success "$builtin appropriately marked as having .adoc" ' + test -f "$adoc" + ' + fi # *.adoc output assertions test_expect_success "$preq" "$builtin *.adoc SYNOPSIS has dashed labels" ' diff --git a/t/t0450/adoc-help-mismatches b/t/t0450/adoc-help-mismatches index 06b469bdee..2c6ecd5fc8 100644 --- a/t/t0450/adoc-help-mismatches +++ b/t/t0450/adoc-help-mismatches @@ -17,7 +17,6 @@ fast-export fast-import fetch-pack fmt-merge-msg -for-each-ref format-patch fsck-objects fsmonitor--daemon diff --git a/t/t0450/adoc-missing b/t/t0450/adoc-missing new file mode 100644 index 0000000000..1ec9f8dcf3 --- /dev/null +++ b/t/t0450/adoc-missing @@ -0,0 +1,9 @@ +checkout--worker +merge-ours +merge-recursive +merge-recursive-ours +merge-recursive-theirs +merge-subtree +pickaxe +submodule--helper +upload-archive--writer diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh index d8101139b4..b0f691c151 100755 --- a/t/t1092-sparse-checkout-compatibility.sh +++ b/t/t1092-sparse-checkout-compatibility.sh @@ -1506,6 +1506,8 @@ test_expect_success 'sparse-index is not expanded' ' ensure_not_expanded reset --hard && ensure_not_expanded restore -s rename-out-to-out -- deep/deeper1 && + ensure_not_expanded ls-files deep/deeper1 && + echo >>sparse-index/README.md && ensure_not_expanded add -A && echo >>sparse-index/extra.txt && @@ -1607,6 +1609,17 @@ test_expect_success 'describe tested on all' ' test_all_match git describe --dirty ' +test_expect_success 'ls-files filtering and expansion' ' + init_repos && + + # This filtering will hit a sparse directory midway + # through the iteration. + test_all_match git ls-files deep && + + # This pathspec will filter the index to only a sparse + # directory. + test_all_match git ls-files folder1 +' test_expect_success 'sparse-index is not expanded: describe' ' init_repos && diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index 96648a6e5d..b7415ec9d5 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -2368,4 +2368,25 @@ test_expect_success REFFILES 'empty directories are pruned when not committing' test_path_is_missing .git/refs/heads/nested ' +test_expect_success 'dangling symref not overwritten by creation' ' + test_when_finished "git update-ref -d refs/heads/dangling" && + git symbolic-ref refs/heads/dangling refs/heads/does-not-exist && + test_must_fail git update-ref --no-deref --stdin 2>err <<-\EOF && + create refs/heads/dangling HEAD + EOF + test_grep "cannot lock.*dangling symref already exists" err && + test_must_fail git rev-parse --verify refs/heads/dangling && + test_must_fail git rev-parse --verify refs/heads/does-not-exist +' + +test_expect_success 'dangling symref overwritten without old oid' ' + test_when_finished "git update-ref -d refs/heads/dangling" && + git symbolic-ref refs/heads/dangling refs/heads/does-not-exist && + git update-ref --no-deref --stdin <<-\EOF && + update refs/heads/dangling HEAD + EOF + git rev-parse --verify refs/heads/dangling && + test_must_fail git rev-parse --verify refs/heads/does-not-exist +' + test_done diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh index 9da3650e91..36c903ca19 100755 --- a/t/t1403-show-ref.sh +++ b/t/t1403-show-ref.sh @@ -228,69 +228,4 @@ test_expect_success 'show-ref sub-modes are mutually exclusive' ' grep "cannot be used together" err ' -test_expect_success '--exists with existing reference' ' - git show-ref --exists refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME -' - -test_expect_success '--exists with missing reference' ' - test_expect_code 2 git show-ref --exists refs/heads/does-not-exist -' - -test_expect_success '--exists does not use DWIM' ' - test_expect_code 2 git show-ref --exists $GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME 2>err && - grep "reference does not exist" err -' - -test_expect_success '--exists with HEAD' ' - git show-ref --exists HEAD -' - -test_expect_success '--exists with bad reference name' ' - test_when_finished "git update-ref -d refs/heads/bad...name" && - new_oid=$(git rev-parse HEAD) && - test-tool ref-store main update-ref msg refs/heads/bad...name $new_oid $ZERO_OID REF_SKIP_REFNAME_VERIFICATION && - git show-ref --exists refs/heads/bad...name -' - -test_expect_success '--exists with arbitrary symref' ' - test_when_finished "git symbolic-ref -d refs/symref" && - git symbolic-ref refs/symref refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME && - git show-ref --exists refs/symref -' - -test_expect_success '--exists with dangling symref' ' - test_when_finished "git symbolic-ref -d refs/heads/dangling" && - git symbolic-ref refs/heads/dangling refs/heads/does-not-exist && - git show-ref --exists refs/heads/dangling -' - -test_expect_success '--exists with nonexistent object ID' ' - test-tool ref-store main update-ref msg refs/heads/missing-oid $(test_oid 001) $ZERO_OID REF_SKIP_OID_VERIFICATION && - git show-ref --exists refs/heads/missing-oid -' - -test_expect_success '--exists with non-commit object' ' - tree_oid=$(git rev-parse HEAD^{tree}) && - test-tool ref-store main update-ref msg refs/heads/tree ${tree_oid} $ZERO_OID REF_SKIP_OID_VERIFICATION && - git show-ref --exists refs/heads/tree -' - -test_expect_success '--exists with directory fails with generic error' ' - cat >expect <<-EOF && - error: reference does not exist - EOF - test_expect_code 2 git show-ref --exists refs/heads 2>err && - test_cmp expect err -' - -test_expect_success '--exists with non-existent special ref' ' - test_expect_code 2 git show-ref --exists FETCH_HEAD -' - -test_expect_success '--exists with existing special ref' ' - test_when_finished "rm .git/FETCH_HEAD" && - git rev-parse HEAD >.git/FETCH_HEAD && - git show-ref --exists FETCH_HEAD -' - test_done diff --git a/t/t1421-reflog-write.sh b/t/t1421-reflog-write.sh new file mode 100755 index 0000000000..46df64c176 --- /dev/null +++ b/t/t1421-reflog-write.sh @@ -0,0 +1,126 @@ +#!/bin/sh + +test_description='Manually write reflog entries' + +. ./test-lib.sh + +SIGNATURE="C O Mitter <committer@example.com> 1112911993 -0700" + +test_reflog_matches () { + repo="$1" && + refname="$2" && + cat >actual && + test-tool -C "$repo" ref-store main for-each-reflog-ent "$refname" >expected && + test_cmp expected actual +} + +test_expect_success 'invalid number of arguments' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + for args in "" "1" "1 2" "1 2 3" "1 2 3 4 5" + do + test_must_fail git reflog write $args 2>err && + test_grep "usage: git reflog write" err || return 1 + done + ) +' + +test_expect_success 'invalid refname' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_must_fail git reflog write "refs/heads/ invalid" $ZERO_OID $ZERO_OID first 2>err && + test_grep "invalid reference name: " err + ) +' + +test_expect_success 'unqualified refname is rejected' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_must_fail git reflog write unqualified $ZERO_OID $ZERO_OID first 2>err && + test_grep "invalid reference name: " err + ) +' + +test_expect_success 'nonexistent object IDs' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_must_fail git reflog write refs/heads/something $(test_oid deadbeef) $ZERO_OID old-object-id 2>err && + test_grep "old object .* does not exist" err && + test_must_fail git reflog write refs/heads/something $ZERO_OID $(test_oid deadbeef) new-object-id 2>err && + test_grep "new object .* does not exist" err + ) +' + +test_expect_success 'abbreviated object IDs' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + abbreviated_oid=$(git rev-parse HEAD | test_copy_bytes 8) && + test_must_fail git reflog write refs/heads/something $abbreviated_oid $ZERO_OID old-object-id 2>err && + test_grep "invalid old object ID" err && + test_must_fail git reflog write refs/heads/something $ZERO_OID $abbreviated_oid new-object-id 2>err && + test_grep "invalid new object ID" err + ) +' + +test_expect_success 'reflog message gets normalized' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + COMMIT_OID=$(git rev-parse HEAD) && + git reflog write HEAD $COMMIT_OID $COMMIT_OID "$(printf "message\nwith\nnewlines")" && + git reflog show -1 --format=%gs HEAD >actual && + echo "message with newlines" >expected && + test_cmp expected actual + ) +' + +test_expect_success 'simple writes' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + COMMIT_OID=$(git rev-parse HEAD) && + + git reflog write refs/heads/something $ZERO_OID $COMMIT_OID first && + test_reflog_matches . refs/heads/something <<-EOF && + $ZERO_OID $COMMIT_OID $SIGNATURE first + EOF + + git reflog write refs/heads/something $COMMIT_OID $COMMIT_OID second && + test_reflog_matches . refs/heads/something <<-EOF + $ZERO_OID $COMMIT_OID $SIGNATURE first + $COMMIT_OID $COMMIT_OID $SIGNATURE second + EOF + ) +' + +test_expect_success 'can write to root ref' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + COMMIT_OID=$(git rev-parse HEAD) && + + git reflog write ROOT_REF_HEAD $ZERO_OID $COMMIT_OID first && + test_reflog_matches . ROOT_REF_HEAD <<-EOF + $ZERO_OID $COMMIT_OID $SIGNATURE first + EOF + ) +' + +test_done diff --git a/t/t1422-show-ref-exists.sh b/t/t1422-show-ref-exists.sh new file mode 100755 index 0000000000..fdca3f16c8 --- /dev/null +++ b/t/t1422-show-ref-exists.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +test_description='show-ref --exists' +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + +. ./test-lib.sh + +. "$TEST_DIRECTORY"/show-ref-exists-tests.sh diff --git a/t/t1460-refs-migrate.sh b/t/t1460-refs-migrate.sh index 2ab97e1b7d..0e1116a319 100755 --- a/t/t1460-refs-migrate.sh +++ b/t/t1460-refs-migrate.sh @@ -7,6 +7,17 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh +print_all_reflog_entries () { + repo=$1 && + test-tool -C "$repo" ref-store main for-each-reflog >reflogs && + while read reflog + do + echo "REFLOG: $reflog" && + test-tool -C "$repo" ref-store main for-each-reflog-ent "$reflog" || + return 1 + done <reflogs +} + # Migrate the provided repository from one format to the other and # verify that the references and logs are migrated over correctly. # Usage: test_migration <repo> <format> [<skip_reflog_verify> [<options...>]] @@ -28,8 +39,7 @@ test_migration () { --format='%(refname) %(objectname) %(symref)' >expect && if ! $skip_reflog_verify then - git -C "$repo" reflog --all >expect_logs && - git -C "$repo" reflog list >expect_log_list + print_all_reflog_entries "$repo" >expect_logs fi && git -C "$repo" refs migrate --ref-format="$format" "$@" && @@ -39,10 +49,8 @@ test_migration () { test_cmp expect actual && if ! $skip_reflog_verify then - git -C "$repo" reflog --all >actual_logs && - git -C "$repo" reflog list >actual_log_list && - test_cmp expect_logs actual_logs && - test_cmp expect_log_list actual_log_list + print_all_reflog_entries "$repo" >actual_logs && + test_cmp expect_logs actual_logs fi && git -C "$repo" rev-parse --show-ref-format >actual && @@ -273,7 +281,7 @@ test_expect_success 'multiple reftable blocks with multiple entries' ' test_commit -C repo second && printf "update refs/heads/ref-%d HEAD\n" $(test_seq 3000) >stdin && git -C repo update-ref --stdin <stdin && - test_migration repo reftable + test_migration repo reftable true ' test_expect_success 'migrating from files format deletes backend files' ' diff --git a/t/t1461-refs-list.sh b/t/t1461-refs-list.sh new file mode 100755 index 0000000000..36e3d81e59 --- /dev/null +++ b/t/t1461-refs-list.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +test_description='git refs list tests' + +. ./test-lib.sh + +git_for_each_ref='git refs list' +. "$TEST_DIRECTORY"/for-each-ref-tests.sh diff --git a/t/t1462-refs-exists.sh b/t/t1462-refs-exists.sh new file mode 100755 index 0000000000..349453c4ca --- /dev/null +++ b/t/t1462-refs-exists.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +test_description='refs exists' +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + +. ./test-lib.sh + +git_show_ref_exists='git refs exists' +. "$TEST_DIRECTORY"/show-ref-exists-tests.sh diff --git a/t/t1517-outside-repo.sh b/t/t1517-outside-repo.sh index 8f59b867f2..c824c1a25c 100755 --- a/t/t1517-outside-repo.sh +++ b/t/t1517-outside-repo.sh @@ -107,18 +107,46 @@ test_expect_success LIBCURL 'remote-http outside repository' ' test_grep "^error: remote-curl" actual ' -test_expect_success 'update-server-info does not crash with -h' ' - test_expect_code 129 git update-server-info -h >usage && - test_grep "[Uu]sage: git update-server-info " usage && - test_expect_code 129 nongit git update-server-info -h >usage && - test_grep "[Uu]sage: git update-server-info " usage -' - -test_expect_success 'prune does not crash with -h' ' - test_expect_code 129 git prune -h >usage && - test_grep "[Uu]sage: git prune " usage && - test_expect_code 129 nongit git prune -h >usage && - test_grep "[Uu]sage: git prune " usage +for cmd in $(git --list-cmds=main) +do + cmd=${cmd%.*} # strip .sh, .perl, etc. + case "$cmd" in + archimport | citool | credential-netrc | credential-libsecret | \ + credential-osxkeychain | cvsexportcommit | cvsimport | cvsserver | \ + daemon | \ + difftool--helper | filter-branch | fsck-objects | get-tar-commit-id | \ + gui | gui--askpass | \ + http-backend | http-fetch | http-push | init-db | \ + merge-octopus | merge-one-file | merge-resolve | mergetool | \ + mktag | p4 | p4.py | pickaxe | remote-ftp | remote-ftps | \ + remote-http | remote-https | replay | send-email | \ + sh-i18n--envsubst | shell | show | stage | submodule | svn | \ + upload-archive--writer | upload-pack | web--browse | whatchanged) + expect_outcome=expect_failure ;; + *) + expect_outcome=expect_success ;; + esac + case "$cmd" in + instaweb) + prereq=PERL ;; + *) + prereq= ;; + esac + test_$expect_outcome $prereq "'git $cmd -h' outside a repository" ' + test_expect_code 129 nongit git $cmd -h >usage && + test_grep "[Uu]sage: git $cmd " usage + ' + test_$expect_outcome $prereq "'git $cmd --help-all' outside a repository" ' + test_expect_code 129 nongit git $cmd --help-all >usage && + test_grep "[Uu]sage: git $cmd " usage + ' +done + +test_expect_success 'fmt-merge-msg does not crash with -h' ' + test_expect_code 129 git fmt-merge-msg -h >usage && + test_grep "[Uu]sage: git fmt-merge-msg " usage && + test_expect_code 129 nongit git fmt-merge-msg -h >usage && + test_grep "[Uu]sage: git fmt-merge-msg " usage ' test_done diff --git a/t/t1900-repo.sh b/t/t1900-repo.sh new file mode 100755 index 0000000000..2beba67889 --- /dev/null +++ b/t/t1900-repo.sh @@ -0,0 +1,113 @@ +#!/bin/sh + +test_description='test git repo-info' + +. ./test-lib.sh + +# Test whether a key-value pair is correctly returned +# +# Usage: test_repo_info <label> <init command> <repo_name> <key> <expected value> +# +# Arguments: +# label: the label of the test +# init_command: a command which creates a repository +# repo_name: the name of the repository that will be created in init_command +# key: the key of the field that is being tested +# expected_value: the value that the field should contain +test_repo_info () { + label=$1 + init_command=$2 + repo_name=$3 + key=$4 + expected_value=$5 + + test_expect_success "setup: $label" ' + eval "$init_command $repo_name" + ' + + test_expect_success "keyvalue: $label" ' + echo "$key=$expected_value" > expect && + git -C "$repo_name" repo info "$key" >actual && + test_cmp expect actual + ' + + test_expect_success "nul: $label" ' + printf "%s\n%s\0" "$key" "$expected_value" >expect && + git -C "$repo_name" repo info --format=nul "$key" >actual && + test_cmp_bin expect actual + ' +} + +test_repo_info 'ref format files is retrieved correctly' \ + 'git init --ref-format=files' 'format-files' 'references.format' 'files' + +test_repo_info 'ref format reftable is retrieved correctly' \ + 'git init --ref-format=reftable' 'format-reftable' 'references.format' 'reftable' + +test_repo_info 'bare repository = false is retrieved correctly' \ + 'git init' 'nonbare' 'layout.bare' 'false' + +test_repo_info 'bare repository = true is retrieved correctly' \ + 'git init --bare' 'bare' 'layout.bare' 'true' + +test_repo_info 'shallow repository = false is retrieved correctly' \ + 'git init' 'nonshallow' 'layout.shallow' 'false' + +test_expect_success 'setup remote' ' + git init remote && + echo x >remote/x && + git -C remote add x && + git -C remote commit -m x +' + +test_repo_info 'shallow repository = true is retrieved correctly' \ + 'git clone --depth 1 "file://$PWD/remote"' 'shallow' 'layout.shallow' 'true' + +test_repo_info 'object.format = sha1 is retrieved correctly' \ + 'git init --object-format=sha1' 'sha1' 'object.format' 'sha1' + +test_repo_info 'object.format = sha256 is retrieved correctly' \ + 'git init --object-format=sha256' 'sha256' 'object.format' 'sha256' + +test_expect_success 'values returned in order requested' ' + cat >expect <<-\EOF && + layout.bare=false + references.format=files + layout.bare=false + EOF + git init --ref-format=files ordered && + git -C ordered repo info layout.bare references.format layout.bare >actual && + test_cmp expect actual +' + +test_expect_success 'git-repo-info fails if an invalid key is requested' ' + echo "error: key ${SQ}foo${SQ} not found" >expect && + test_must_fail git repo info foo 2>actual && + test_cmp expect actual +' + +test_expect_success 'git-repo-info outputs data even if there is an invalid field' ' + echo "references.format=$(test_detect_ref_format)" >expect && + test_must_fail git repo info foo references.format bar >actual && + test_cmp expect actual +' + +test_expect_success 'git-repo-info aborts when requesting an invalid format' ' + echo "fatal: invalid format ${SQ}foo${SQ}" >expect && + test_must_fail git repo info --format=foo 2>actual && + test_cmp expect actual +' + +test_expect_success '-z uses nul-terminated format' ' + printf "layout.bare\nfalse\0layout.shallow\nfalse\0" >expected && + git repo info -z layout.bare layout.shallow >actual && + test_cmp expected actual +' + +test_expect_success 'git repo info uses the last requested format' ' + echo "layout.bare=false" >expected && + git repo info --format=nul -z --format=keyvalue layout.bare >actual && + test_cmp expected actual +' + +test_done diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 6bac217ed3..e778dd8ae4 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -1176,7 +1176,7 @@ test_expect_success 'rebase -i respects core.commentchar' ' test B = $(git cat-file commit HEAD^ | sed -ne \$p) ' -test_expect_success 'rebase -i respects core.commentchar=auto' ' +test_expect_success !WITH_BREAKING_CHANGES 'rebase -i respects core.commentchar=auto' ' test_config core.commentchar auto && write_script copy-edit-script.sh <<-\EOF && cp "$1" edit-script @@ -1184,8 +1184,23 @@ test_expect_success 'rebase -i respects core.commentchar=auto' ' test_when_finished "git rebase --abort || :" && ( test_set_editor "$(pwd)/copy-edit-script.sh" && - git rebase -i HEAD^ + git rebase -i HEAD^ 2>err ) && + sed -n "s/^hint: *\$//p; s/^hint: //p; s/^warning: //p" err >actual && + cat >expect <<-EOF && + Support for ${SQ}core.commentChar=auto${SQ} is deprecated and will be removed in Git 3.0 + + To use the default comment string (#) please run + + git config unset core.commentChar + + To set a custom comment string please run + + git config set core.commentChar <comment string> + + where ${SQ}<comment string>${SQ} is the string you wish to use. + EOF + test_cmp expect actual && test -z "$(grep -ve "^#" -e "^\$" -e "^pick" edit-script)" ' @@ -2263,6 +2278,7 @@ test_expect_success 'non-merge commands reject merge commits' ' edit $oid fixup $oid squash $oid + drop $oid # acceptable, no advice EOF ( set_replace_editor todo && diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh index b8a8dd77e7..f9b8999db5 100755 --- a/t/t3418-rebase-continue.sh +++ b/t/t3418-rebase-continue.sh @@ -328,7 +328,7 @@ test_expect_success 'there is no --no-reschedule-failed-exec in an ongoing rebas test_expect_code 129 git rebase --edit-todo --no-reschedule-failed-exec ' -test_expect_success 'no change in comment character due to conflicts markers with core.commentChar=auto' ' +test_expect_success !WITH_BREAKING_CHANGES 'no change in comment character due to conflicts markers with core.commentChar=auto' ' git checkout -b branch-a && test_commit A F1 && git checkout -b branch-b HEAD^ && diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 04d2a19835..d9fe289a7a 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -866,6 +866,44 @@ test_expect_success 'colorized diffs respect diff.wsErrorHighlight' ' test_grep "old<" output ' +test_expect_success 'diff color respects color.diff' ' + git reset --hard && + + echo old >test && + git add test && + echo new >test && + + printf n >n && + force_color git \ + -c color.interactive=auto \ + -c color.interactive.prompt=blue \ + -c color.diff=false \ + -c color.diff.old=red \ + add -p >output.raw 2>&1 <n && + test_decode_color <output.raw >output && + test_grep "BLUE.*Stage this hunk" output && + test_grep ! "RED" output +' + +test_expect_success 're-coloring diff without color.interactive' ' + git reset --hard && + + test_write_lines 1 2 3 >test && + git add test && + test_write_lines one 2 three >test && + + test_write_lines s n n | + force_color git \ + -c color.interactive=false \ + -c color.interactive.prompt=blue \ + -c color.diff=true \ + -c color.diff.frag="bold magenta" \ + add -p >output.raw 2>&1 && + test_decode_color <output.raw >output && + test_grep "<BOLD;MAGENTA>@@" output && + test_grep ! "BLUE" output +' + test_expect_success 'diffFilter filters diff' ' git reset --hard && @@ -1283,6 +1321,12 @@ test_expect_success 'stash accepts -U and --inter-hunk-context' ' test_grep "@@ -2,20 +2,20 @@" actual ' +test_expect_success 'set up base for -p color tests' ' + echo commit >file && + git commit -am "commit state" && + git tag patch-base +' + for cmd in add checkout commit reset restore "stash save" "stash push" do test_expect_success "$cmd rejects invalid context options" ' @@ -1299,6 +1343,15 @@ do test_must_fail git $cmd --inter-hunk-context 2 2>actual && test_grep -E ".--inter-hunk-context. requires .(--interactive/)?--patch." actual ' + + test_expect_success "$cmd falls back to color.ui" ' + git reset --hard patch-base && + echo working-tree >file && + test_write_lines y | + force_color git -c color.ui=false $cmd -p >output.raw 2>&1 && + test_decode_color <output.raw >output && + test_cmp output.raw output + ' done test_done diff --git a/t/t3904-stash-patch.sh b/t/t3904-stash-patch.sh index ae313e3c70..90a4ff2c10 100755 --- a/t/t3904-stash-patch.sh +++ b/t/t3904-stash-patch.sh @@ -107,4 +107,23 @@ test_expect_success 'stash -p with split hunk' ' ! grep "added line 2" test ' +test_expect_success 'stash -p not confused by GIT_PAGER_IN_USE' ' + echo to-stash >test && + # Set both GIT_PAGER_IN_USE and TERM. Our goal is to entice any + # diff subprocesses into thinking that they could output + # color, even though their stdout is not going into a tty. + echo y | + GIT_PAGER_IN_USE=1 TERM=vt100 git stash -p && + git diff --exit-code +' + +test_expect_success 'index push not confused by GIT_PAGER_IN_USE' ' + echo index >test && + git add test && + echo working-tree >test && + # As above, we try to entice the child diff into using color. + GIT_PAGER_IN_USE=1 TERM=vt100 git stash push test && + git diff --exit-code +' + test_done diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index 8ebd170451..cfeec239e0 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -648,6 +648,19 @@ test_expect_success 'diff -I<regex>: detect malformed regex' ' test_grep "invalid regex given to -I: " error ' +test_expect_success 'diff -I<regex>: ignore matching file' ' + test_when_finished "git rm -f file1" && + test_seq 50 >file1 && + git add file1 && + test_seq 50 | sed -e "s/13/ten and three/" -e "s/^[124-9].*/& /" >file1 && + + : >actual && + git diff --raw --ignore-blank-lines -I"ten.*e" -I"^[124-9]" >>actual && + git diff --name-only --ignore-blank-lines -I"ten.*e" -I"^[124-9]" >>actual && + git diff --name-status --ignore-blank-lines -I"ten.*e" -I"^[124-9]" >>actual && + test_grep ! "file1" actual +' + # check_prefix <patch> <src> <dst> # check only lines with paths to avoid dependency on exact oid/contents check_prefix () { diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 52e3e476ff..9de7f73f42 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -11,12 +11,8 @@ test_description='Test special whitespace in diff engine. . "$TEST_DIRECTORY"/lib-diff.sh for opt_res in --patch --quiet -s --stat --shortstat --dirstat=lines \ - --raw! --name-only! --name-status! + --raw --name-only --name-status do - opts=${opt_res%!} expect_failure= - test "$opts" = "$opt_res" || - expect_failure="test_expect_code 1" - test_expect_success "status with $opts (different)" ' echo foo >x && git add x && @@ -43,7 +39,7 @@ do echo foo >x && git add x && echo " foo" >x && - $expect_failure git diff -w $opts --exit-code x + git diff -w $opts --exit-code x ' done diff --git a/t/t4053-diff-no-index.sh b/t/t4053-diff-no-index.sh index 01db9243ab..44b4b13f5d 100755 --- a/t/t4053-diff-no-index.sh +++ b/t/t4053-diff-no-index.sh @@ -26,6 +26,23 @@ test_expect_success 'git diff --no-index directories' ' test_line_count = 14 cnt ' +test_expect_success 'git diff --no-index with -' ' + cat >expect <<-\EOF && + diff --git a/- b/- + new file mode 100644 + --- /dev/null + +++ b/- + @@ -0,0 +1 @@ + +frotz + EOF + ( + cd a && + echo frotz | + test_expect_code 1 git diff --no-index /dev/null - >../actual + ) && + test_cmp expect actual +' + test_expect_success 'git diff --no-index relative path outside repo' ' ( cd repo && diff --git a/t/t4072-diff-max-depth.sh b/t/t4072-diff-max-depth.sh new file mode 100755 index 0000000000..0fbf1321f7 --- /dev/null +++ b/t/t4072-diff-max-depth.sh @@ -0,0 +1,116 @@ +#!/bin/sh + +test_description='check that diff --max-depth will limit recursion' +. ./test-lib.sh + +make_dir() { + mkdir -p "$1" && + echo "$2" >"$1/file" +} + +make_files() { + echo "$1" >file && + make_dir one "$1" && + make_dir one/two "$1" && + make_dir one/two/three "$1" +} + +test_expect_success 'setup' ' + git commit --allow-empty -m empty && + git tag empty && + make_files added && + git add . && + git commit -m added && + make_files modified && + git add . && + git commit -m modified && + make_files index && + git add . && + make_files worktree +' + +test_expect_success '--max-depth is disallowed with wildcard pathspecs' ' + test_must_fail git diff-tree --max-depth=0 HEAD^ HEAD -- "f*" +' + +check_one() { + type=$1; shift + args=$1; shift + path=$1; shift + depth=$1; shift + test_expect_${expect:-success} "diff-$type $args, path=$path, depth=$depth" " + for i in $*; do echo \$i; done >expect && + git diff-$type --max-depth=$depth --name-only $args -- $path >actual && + test_cmp expect actual + " +} + +# For tree comparisons, we expect to see subtrees at the boundary +# get their own entry. +check_trees() { + check_one tree "$*" '' 0 file one + check_one tree "$*" '' 1 file one/file one/two + check_one tree "$*" '' 2 file one/file one/two/file one/two/three + check_one tree "$*" '' 3 file one/file one/two/file one/two/three/file + check_one tree "$*" '' -1 file one/file one/two/file one/two/three/file + check_one tree "$*" one 0 one + check_one tree "$*" one 1 one/file one/two + check_one tree "$*" one 2 one/file one/two/file one/two/three + check_one tree "$*" one 3 one/file one/two/file one/two/three/file + check_one tree "$*" one/two 0 one/two + check_one tree "$*" one/two 1 one/two/file one/two/three + check_one tree "$*" one/two 2 one/two/file one/two/three/file + check_one tree "$*" one/two 2 one/two/file one/two/three/file + check_one tree "$*" one/two/three 0 one/two/three + check_one tree "$*" one/two/three 1 one/two/three/file +} + +# But for index comparisons, we do not store subtrees at all, so we do not +# expect them. +check_index() { + check_one "$@" '' 0 file + check_one "$@" '' 1 file one/file + check_one "$@" '' 2 file one/file one/two/file + check_one "$@" '' 3 file one/file one/two/file one/two/three/file + check_one "$@" one 0 + check_one "$@" one 1 one/file + check_one "$@" one 2 one/file one/two/file + check_one "$@" one 3 one/file one/two/file one/two/three/file + check_one "$@" one/two 0 + check_one "$@" one/two 1 one/two/file + check_one "$@" one/two 2 one/two/file one/two/three/file + check_one "$@" one/two/three 0 + check_one "$@" one/two/three 1 one/two/three/file + + # Value '-1' for '--max-depth is the same as recursion without limit, + # and thus should always succeed. + local expect= + check_one "$@" '' -1 file one/file one/two/file one/two/three/file +} + +# Check as a modification... +check_trees HEAD^ HEAD +# ...and as an addition... +check_trees empty HEAD +# ...and as a deletion. +check_trees HEAD empty + +# We currently only implement max-depth for trees. +expect=failure +# Check index against a tree +check_index index "--cached HEAD" +# and index against the worktree +check_index files "" +expect= + +test_expect_success 'find shortest path within embedded pathspecs' ' + cat >expect <<-\EOF && + one/file + one/two/file + one/two/three/file + EOF + git diff-tree --max-depth=2 --name-only HEAD^ HEAD -- one one/two >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh index 950451cf6a..0a7c3ca42f 100755 --- a/t/t4211-line-log.sh +++ b/t/t4211-line-log.sh @@ -78,6 +78,8 @@ canned_test "-L :main:a.c -L 4,18:a.c simple" multiple-overlapping canned_test "-L 4:a.c -L 8,12:a.c simple" multiple-superset canned_test "-L 8,12:a.c -L 4:a.c simple" multiple-superset +canned_test "-L 10,16:b.c -L 18,26:b.c main" no-assertion-error + test_bad_opts "-L" "switch.*requires a value" test_bad_opts "-L b.c" "argument not .start,end:file" test_bad_opts "-L 1:" "argument not .start,end:file" diff --git a/t/t4211/sha1/expect.multiple b/t/t4211/sha1/expect.multiple index 76ad5b598c..1eee8a7801 100644 --- a/t/t4211/sha1/expect.multiple +++ b/t/t4211/sha1/expect.multiple @@ -102,3 +102,9 @@ diff --git a/a.c b/a.c + s++; + } +} +@@ -0,0 +16,5 @@ ++int main () ++{ ++ printf("%d\n", f(15)); ++ return 0; ++} diff --git a/t/t4211/sha1/expect.no-assertion-error b/t/t4211/sha1/expect.no-assertion-error new file mode 100644 index 0000000000..994c37db1e --- /dev/null +++ b/t/t4211/sha1/expect.no-assertion-error @@ -0,0 +1,90 @@ +commit 0d8dcfc6b968e06a27d5215bad1fdde3de9d6235 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:50:24 2013 +0100 + + move within the file + +diff --git a/b.c b/b.c +--- a/b.c ++++ b/b.c +@@ -25,0 +18,9 @@ ++long f(long x) ++{ ++ int s = 0; ++ while (x) { ++ x /= 2; ++ s++; ++ } ++ return s; ++} + +commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:48:43 2013 +0100 + + change back to complete line + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -18,5 +18,7 @@ + int main () + { + printf("%ld\n", f(15)); + return 0; +-} +\ No newline at end of file ++} ++ ++/* incomplete lines are bad! */ + +commit 100b61a6f2f720f812620a9d10afb3a960ccb73c +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:48:10 2013 +0100 + + change to an incomplete line at end + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -18,5 +18,5 @@ + int main () + { + printf("%ld\n", f(15)); + return 0; +-} ++} +\ No newline at end of file + +commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:45:16 2013 +0100 + + touch both functions + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -17,5 +17,5 @@ + int main () + { +- printf("%d\n", f(15)); ++ printf("%ld\n", f(15)); + return 0; + } + +commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:48 2013 +0100 + + initial + +diff --git a/a.c b/a.c +--- /dev/null ++++ b/a.c +@@ -0,0 +16,5 @@ ++int main () ++{ ++ printf("%d\n", f(15)); ++ return 0; ++} diff --git a/t/t4211/sha1/expect.two-ranges b/t/t4211/sha1/expect.two-ranges index 6109aa0dce..c5164f3be3 100644 --- a/t/t4211/sha1/expect.two-ranges +++ b/t/t4211/sha1/expect.two-ranges @@ -100,3 +100,9 @@ diff --git a/a.c b/a.c + s++; + } +} +@@ -0,0 +16,5 @@ ++int main () ++{ ++ printf("%d\n", f(15)); ++ return 0; ++} diff --git a/t/t4211/sha256/expect.multiple b/t/t4211/sha256/expect.multiple index ca00409b9a..dbd987b74a 100644 --- a/t/t4211/sha256/expect.multiple +++ b/t/t4211/sha256/expect.multiple @@ -102,3 +102,9 @@ diff --git a/a.c b/a.c + s++; + } +} +@@ -0,0 +16,5 @@ ++int main () ++{ ++ printf("%d\n", f(15)); ++ return 0; ++} diff --git a/t/t4211/sha256/expect.no-assertion-error b/t/t4211/sha256/expect.no-assertion-error new file mode 100644 index 0000000000..36ed12aa9c --- /dev/null +++ b/t/t4211/sha256/expect.no-assertion-error @@ -0,0 +1,90 @@ +commit eb871b8aa9aff323e484723039c9a92ab0266e060bc0ef2afb08fadda25c5ace +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:50:24 2013 +0100 + + move within the file + +diff --git a/b.c b/b.c +--- a/b.c ++++ b/b.c +@@ -25,0 +18,9 @@ ++long f(long x) ++{ ++ int s = 0; ++ while (x) { ++ x /= 2; ++ s++; ++ } ++ return s; ++} + +commit 5526ed05c2476b56af8b7be499e8f78bd50f490740733a9ca7e1f55878fa90a9 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:48:43 2013 +0100 + + change back to complete line + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -18,5 +18,7 @@ + int main () + { + printf("%ld\n", f(15)); + return 0; +-} +\ No newline at end of file ++} ++ ++/* incomplete lines are bad! */ + +commit 29f32ac3141c48b22803e5c4127b719917b67d0f8ca8c5248bebfa2a19f7da10 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:48:10 2013 +0100 + + change to an incomplete line at end + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -18,5 +18,5 @@ + int main () + { + printf("%ld\n", f(15)); + return 0; +-} ++} +\ No newline at end of file + +commit ccf97b9878189c40a981da50b15713bb80a35755326320ec80900caf22ced46f +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:45:16 2013 +0100 + + touch both functions + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -17,5 +17,5 @@ + int main () + { +- printf("%d\n", f(15)); ++ printf("%ld\n", f(15)); + return 0; + } + +commit 1dd7e9b2b1699324b53b341e728653b913bc192a14dfea168c5b51f2b3d03592 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:48 2013 +0100 + + initial + +diff --git a/a.c b/a.c +--- /dev/null ++++ b/a.c +@@ -0,0 +16,5 @@ ++int main () ++{ ++ printf("%d\n", f(15)); ++ return 0; ++} diff --git a/t/t4211/sha256/expect.two-ranges b/t/t4211/sha256/expect.two-ranges index af57c8b997..6a94d3b9cb 100644 --- a/t/t4211/sha256/expect.two-ranges +++ b/t/t4211/sha256/expect.two-ranges @@ -100,3 +100,9 @@ diff --git a/a.c b/a.c + s++; + } +} +@@ -0,0 +16,5 @@ ++int main () ++{ ++ printf("%d\n", f(15)); ++ return 0; ++} diff --git a/t/t4216-log-bloom.sh b/t/t4216-log-bloom.sh index 639868ac56..1064990de3 100755 --- a/t/t4216-log-bloom.sh +++ b/t/t4216-log-bloom.sh @@ -154,11 +154,34 @@ test_expect_success 'git log with multiple literal paths uses Bloom filter' ' test_bloom_filters_used "-- file*" ' -test_expect_success 'git log with path contains a wildcard does not use Bloom filter' ' +test_expect_success 'git log with paths all contain non-wildcard part uses Bloom filter' ' + test_bloom_filters_used "-- A/\* file4" && + test_bloom_filters_used "-- A/file\*" && + test_bloom_filters_used "-- * A/\*" +' + +test_expect_success 'git log with path only contains wildcard part does not use Bloom filter' ' test_bloom_filters_not_used "-- file\*" && - test_bloom_filters_not_used "-- A/\* file4" && - test_bloom_filters_not_used "-- file4 A/\*" && - test_bloom_filters_not_used "-- * A/\*" + test_bloom_filters_not_used "-- file\* A/\*" && + test_bloom_filters_not_used "-- file\* *" && + test_bloom_filters_not_used "-- \*" +' + +test_expect_success 'git log with path contains various magic signatures' ' + cd A && + test_bloom_filters_used "-- \:\(top\)B" && + cd .. && + + test_bloom_filters_used "-- \:\(glob\)A/\*\*/C" && + test_bloom_filters_not_used "-- \:\(icase\)FILE4" && + test_bloom_filters_not_used "-- \:\(exclude\)A/B/C" && + + test_when_finished "rm -f .gitattributes" && + cat >.gitattributes <<-EOF && + A/file1 text + A/B/file2 -text + EOF + test_bloom_filters_used "-- \:\(attr\:text\)A" ' test_expect_success 'setup - add commit-graph to the chain without Bloom filters' ' diff --git a/t/t5200-update-server-info.sh b/t/t5200-update-server-info.sh index 8365907055..a551e955b5 100755 --- a/t/t5200-update-server-info.sh +++ b/t/t5200-update-server-info.sh @@ -46,4 +46,9 @@ test_expect_success 'midx does not create duplicate pack entries' ' test_must_be_empty dups ' +test_expect_success 'update-server-info does not crash with -h' ' + test_expect_code 129 git update-server-info -h >usage && + test_grep "[Uu]sage: git update-server-info " usage +' + test_done diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh index 1f1f664871..2be7cd30de 100755 --- a/t/t5304-prune.sh +++ b/t/t5304-prune.sh @@ -364,4 +364,9 @@ test_expect_success 'gc.recentObjectsHook' ' git cat-file -p $BLOB ' +test_expect_success 'prune does not crash with -h' ' + test_expect_code 129 git prune -h >usage && + test_grep "[Uu]sage: git prune " usage +' + test_done diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index bd75dea950..93f319a4b2 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -28,11 +28,11 @@ midx_read_expect () { EOF if test $NUM_PACKS -ge 1 then - ls $OBJECT_DIR/pack/ | grep idx | sort + ls "$OBJECT_DIR"/pack/ | grep idx | sort fi && printf "object-dir: $OBJECT_DIR\n" } >expect && - test-tool read-midx $OBJECT_DIR >actual && + test-tool read-midx "$OBJECT_DIR" >actual && test_cmp expect actual } @@ -305,7 +305,7 @@ test_expect_success 'midx picks objects from preferred pack' ' ofs=$(git show-index <objects/pack/test-BC-$bc.idx | grep $b | cut -d" " -f1) && - printf "%s %s\tobjects/pack/test-BC-%s.pack\n" \ + printf "%s %s\t./objects/pack/test-BC-%s.pack\n" \ "$b" "$ofs" "$bc" >expect && grep ^$b out >actual && @@ -639,7 +639,7 @@ test_expect_success 'force some 64-bit offsets with pack-objects' ' ( cd ../objects64 && pwd ) >.git/objects/info/alternates && midx64=$(git multi-pack-index --object-dir=../objects64 write) ) && - midx_read_expect 1 63 5 objects64 " large-offsets" + midx_read_expect 1 63 5 "$(pwd)/objects64" " large-offsets" ' test_expect_success 'verify multi-pack-index with 64-bit offsets' ' @@ -989,6 +989,23 @@ test_expect_success 'repack --batch-size=0 repacks everything' ' ) ' +test_expect_success EXPENSIVE 'repack/expire with many packs' ' + cp -r dup many && + ( + cd many && + + for i in $(test_seq 1 100) + do + test_commit extra$i && + git maintenance run --task=loose-objects || return 1 + done && + + git multi-pack-index write && + git multi-pack-index repack && + git multi-pack-index expire + ) +' + test_expect_success 'repack --batch-size=<large> repacks everything' ' ( cd dup2 && @@ -1083,7 +1100,10 @@ test_expect_success 'load reverse index when missing .idx, .pack' ' mv $idx.bak $idx && mv $pack $pack.bak && - git cat-file --batch-check="%(objectsize:disk)" <tip + git cat-file --batch-check="%(objectsize:disk)" <tip && + + test_must_fail git multi-pack-index write 2>err && + test_grep "could not load pack" err ) ' diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 2701eef85e..e592c0bcde 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -1658,4 +1658,77 @@ test_expect_success 'forbid adding superset of existing remote' ' test_grep ".outer. is a superset of existing remote .outer/inner." err ' +test_expect_success 'rename handles unborn HEAD' ' + test_when_finished "git remote remove unborn-renamed" && + git remote add unborn url && + git symbolic-ref refs/remotes/unborn/HEAD refs/remotes/unborn/nonexistent && + git remote rename unborn unborn-renamed && + git symbolic-ref refs/remotes/unborn-renamed/HEAD >actual && + echo refs/remotes/unborn-renamed/nonexistent >expected && + test_cmp expected actual +' + +test_expect_success 'rename can nest a remote into itself' ' + test_commit parent-commit && + COMMIT_ID=$(git rev-parse HEAD) && + test_when_finished "git remote remove parent || true" && + git remote add parent url && + git update-ref refs/remotes/parent/branch $COMMIT_ID && + test_when_finished "git remote remove parent/child" && + git remote rename parent parent/child && + git for-each-ref refs/remotes/ >actual && + printf "$COMMIT_ID commit\trefs/remotes/parent/child/branch\n" >expected && + test_cmp expected actual +' + +test_expect_success 'rename can nest a remote into itself with a conflicting branch name' ' + test_commit parent-conflict && + COMMIT_ID=$(git rev-parse HEAD) && + test_when_finished "git remote remove parent || true" && + git remote add parent url && + git update-ref refs/remotes/parent/child $COMMIT_ID && + test_when_finished "git remote remove parent/child" && + test_must_fail git remote rename parent parent/child 2>err && + test_grep "renaming remote references failed" err && + test_grep "The remote you are trying to rename has conflicting references" err && + git for-each-ref refs/remotes/ >actual && + printf "$COMMIT_ID commit\trefs/remotes/parent/child\n" >expected && + test_cmp expected actual +' + +test_expect_success 'rename can unnest a remote' ' + test_commit parent-child-commit && + COMMIT_ID=$(git rev-parse HEAD) && + test_when_finished "git remote remove parent/child || true" && + git remote add parent/child url && + git update-ref refs/remotes/parent/child/branch $COMMIT_ID && + git remote rename parent/child parent && + git for-each-ref refs/remotes/ >actual && + printf "$COMMIT_ID commit\trefs/remotes/parent/branch\n" >expected && + test_cmp expected actual +' + +test_expect_success 'rename moves around the reflog' ' + test_commit reflog-old && + COMMIT_ID=$(git rev-parse HEAD) && + test_config core.logAllRefUpdates true && + test_when_finished "git remote remove reflog-old || true" && + git remote add reflog-old url && + git update-ref refs/remotes/reflog-old/branch $COMMIT_ID && + test-tool ref-store main for-each-reflog >actual && + test_grep refs/remotes/reflog-old/branch actual && + test-tool ref-store main for-each-reflog-ent refs/remotes/reflog-old/branch >reflog-entries-old && + test_line_count = 1 reflog-entries-old && + git remote rename reflog-old reflog-new && + test-tool ref-store main for-each-reflog >actual && + test_grep ! refs/remotes/reflog-old actual && + test_grep refs/remotes/reflog-new/branch actual && + test-tool ref-store main for-each-reflog-ent refs/remotes/reflog-new/branch >reflog-entries-new && + cat >expect <<-EOF && + $(cat reflog-entries-old) + $COMMIT_ID $COMMIT_ID $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1112912173 -0700 remote: renamed refs/remotes/reflog-old/branch to refs/remotes/reflog-new/branch + EOF + test_cmp expect reflog-entries-new +' + test_done diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index ebc696546b..83d1aadf9f 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -14,8 +14,6 @@ then test_done fi -D=$(pwd) - test_expect_success setup ' echo >file original && git add file && @@ -51,46 +49,50 @@ test_expect_success "clone and setup child repos" ' ' test_expect_success "fetch test" ' - cd "$D" && echo >file updated by origin && git commit -a -m "updated by origin" && - cd two && - git fetch && - git rev-parse --verify refs/heads/one && - mine=$(git rev-parse refs/heads/one) && - his=$(cd ../one && git rev-parse refs/heads/main) && - test "z$mine" = "z$his" + ( + cd two && + git fetch && + git rev-parse --verify refs/heads/one && + mine=$(git rev-parse refs/heads/one) && + his=$(cd ../one && git rev-parse refs/heads/main) && + test "z$mine" = "z$his" + ) ' test_expect_success "fetch test for-merge" ' - cd "$D" && - cd three && - git fetch && - git rev-parse --verify refs/heads/two && - git rev-parse --verify refs/heads/one && - main_in_two=$(cd ../two && git rev-parse main) && - one_in_two=$(cd ../two && git rev-parse one) && - { - echo "$one_in_two " && - echo "$main_in_two not-for-merge" - } >expected && - cut -f -2 .git/FETCH_HEAD >actual && - test_cmp expected actual' + ( + cd three && + git fetch && + git rev-parse --verify refs/heads/two && + git rev-parse --verify refs/heads/one && + main_in_two=$(cd ../two && git rev-parse main) && + one_in_two=$(cd ../two && git rev-parse one) && + { + echo "$one_in_two " && + echo "$main_in_two not-for-merge" + } >expected && + cut -f -2 .git/FETCH_HEAD >actual && + test_cmp expected actual + ) +' test_expect_success "fetch test remote HEAD" ' - cd "$D" && - cd two && - git fetch && - git rev-parse --verify refs/remotes/origin/HEAD && - git rev-parse --verify refs/remotes/origin/main && - head=$(git rev-parse refs/remotes/origin/HEAD) && - branch=$(git rev-parse refs/remotes/origin/main) && - test "z$head" = "z$branch"' + ( + cd two && + git fetch && + git rev-parse --verify refs/remotes/origin/HEAD && + git rev-parse --verify refs/remotes/origin/main && + head=$(git rev-parse refs/remotes/origin/HEAD) && + branch=$(git rev-parse refs/remotes/origin/main) && + test "z$head" = "z$branch" + ) +' test_expect_success "fetch test remote HEAD in bare repository" ' test_when_finished rm -rf barerepo && ( - cd "$D" && git init --bare barerepo && cd barerepo && git remote add upstream ../two && @@ -105,262 +107,235 @@ test_expect_success "fetch test remote HEAD in bare repository" ' test_expect_success "fetch test remote HEAD change" ' - cd "$D" && - cd two && - git switch -c other && - git push -u origin other && - git rev-parse --verify refs/remotes/origin/HEAD && - git rev-parse --verify refs/remotes/origin/main && - git rev-parse --verify refs/remotes/origin/other && - git remote set-head origin other && - git fetch && - head=$(git rev-parse refs/remotes/origin/HEAD) && - branch=$(git rev-parse refs/remotes/origin/other) && - test "z$head" = "z$branch"' - -test_expect_success "fetch test followRemoteHEAD never" ' - test_when_finished "git config unset remote.origin.followRemoteHEAD" && - ( - cd "$D" && - cd two && - git update-ref --no-deref -d refs/remotes/origin/HEAD && - git config set remote.origin.followRemoteHEAD "never" && - GIT_TRACE_PACKET=$PWD/trace.out git fetch && - # Confirm that we do not even ask for HEAD when we are - # not going to act on it. - test_grep ! "ref-prefix HEAD" trace.out && - test_must_fail git rev-parse --verify refs/remotes/origin/HEAD - ) -' - -test_expect_success "fetch test followRemoteHEAD warn no change" ' - test_when_finished "git config unset remote.origin.followRemoteHEAD" && ( - cd "$D" && cd two && - git rev-parse --verify refs/remotes/origin/other && - git remote set-head origin other && + git switch -c other && + git push -u origin other && git rev-parse --verify refs/remotes/origin/HEAD && git rev-parse --verify refs/remotes/origin/main && - git config set remote.origin.followRemoteHEAD "warn" && - git fetch >output && - echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \ - "but we have ${SQ}other${SQ} locally." >expect && - test_cmp expect output && + git rev-parse --verify refs/remotes/origin/other && + git remote set-head origin other && + git fetch && head=$(git rev-parse refs/remotes/origin/HEAD) && branch=$(git rev-parse refs/remotes/origin/other) && test "z$head" = "z$branch" ) ' +test_expect_success "fetch test followRemoteHEAD never" ' + git -C two update-ref --no-deref -d refs/remotes/origin/HEAD && + test_config -C two remote.origin.followRemoteHEAD "never" && + GIT_TRACE_PACKET=$PWD/trace.out git -C two fetch && + # Confirm that we do not even ask for HEAD when we are + # not going to act on it. + test_grep ! "ref-prefix HEAD" trace.out && + test_must_fail git -C two rev-parse --verify refs/remotes/origin/HEAD +' + +test_expect_success "fetch test followRemoteHEAD warn no change" ' + git -C two rev-parse --verify refs/remotes/origin/other && + git -C two remote set-head origin other && + git -C two rev-parse --verify refs/remotes/origin/HEAD && + git -C two rev-parse --verify refs/remotes/origin/main && + test_config -C two remote.origin.followRemoteHEAD "warn" && + git -C two fetch >output && + echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \ + "but we have ${SQ}other${SQ} locally." >expect && + test_cmp expect output && + head=$(git -C two rev-parse refs/remotes/origin/HEAD) && + branch=$(git -C two rev-parse refs/remotes/origin/other) && + test "z$head" = "z$branch" +' + test_expect_success "fetch test followRemoteHEAD warn create" ' - test_when_finished "git config unset remote.origin.followRemoteHEAD" && - ( - cd "$D" && - cd two && - git update-ref --no-deref -d refs/remotes/origin/HEAD && - git config set remote.origin.followRemoteHEAD "warn" && - git rev-parse --verify refs/remotes/origin/main && - output=$(git fetch) && - test "z" = "z$output" && - head=$(git rev-parse refs/remotes/origin/HEAD) && - branch=$(git rev-parse refs/remotes/origin/main) && - test "z$head" = "z$branch" - ) + git -C two update-ref --no-deref -d refs/remotes/origin/HEAD && + test_config -C two remote.origin.followRemoteHEAD "warn" && + git -C two rev-parse --verify refs/remotes/origin/main && + output=$(git -C two fetch) && + test "z" = "z$output" && + head=$(git -C two rev-parse refs/remotes/origin/HEAD) && + branch=$(git -C two rev-parse refs/remotes/origin/main) && + test "z$head" = "z$branch" ' test_expect_success "fetch test followRemoteHEAD warn detached" ' - test_when_finished "git config unset remote.origin.followRemoteHEAD" && - ( - cd "$D" && - cd two && - git update-ref --no-deref -d refs/remotes/origin/HEAD && - git update-ref refs/remotes/origin/HEAD HEAD && - HEAD=$(git log --pretty="%H") && - git config set remote.origin.followRemoteHEAD "warn" && - git fetch >output && - echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \ - "but we have a detached HEAD pointing to" \ - "${SQ}${HEAD}${SQ} locally." >expect && - test_cmp expect output - ) + git -C two update-ref --no-deref -d refs/remotes/origin/HEAD && + git -C two update-ref refs/remotes/origin/HEAD HEAD && + HEAD=$(git -C two log --pretty="%H") && + test_config -C two remote.origin.followRemoteHEAD "warn" && + git -C two fetch >output && + echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \ + "but we have a detached HEAD pointing to" \ + "${SQ}${HEAD}${SQ} locally." >expect && + test_cmp expect output ' test_expect_success "fetch test followRemoteHEAD warn quiet" ' - test_when_finished "git config unset remote.origin.followRemoteHEAD" && - ( - cd "$D" && - cd two && - git rev-parse --verify refs/remotes/origin/other && - git remote set-head origin other && - git rev-parse --verify refs/remotes/origin/HEAD && - git rev-parse --verify refs/remotes/origin/main && - git config set remote.origin.followRemoteHEAD "warn" && - output=$(git fetch --quiet) && - test "z" = "z$output" && - head=$(git rev-parse refs/remotes/origin/HEAD) && - branch=$(git rev-parse refs/remotes/origin/other) && - test "z$head" = "z$branch" - ) + git -C two rev-parse --verify refs/remotes/origin/other && + git -C two remote set-head origin other && + git -C two rev-parse --verify refs/remotes/origin/HEAD && + git -C two rev-parse --verify refs/remotes/origin/main && + test_config -C two remote.origin.followRemoteHEAD "warn" && + output=$(git -C two fetch --quiet) && + test "z" = "z$output" && + head=$(git -C two rev-parse refs/remotes/origin/HEAD) && + branch=$(git -C two rev-parse refs/remotes/origin/other) && + test "z$head" = "z$branch" ' test_expect_success "fetch test followRemoteHEAD warn-if-not-branch branch is same" ' - test_when_finished "git config unset remote.origin.followRemoteHEAD" && - ( - cd "$D" && - cd two && - git rev-parse --verify refs/remotes/origin/other && - git remote set-head origin other && - git rev-parse --verify refs/remotes/origin/HEAD && - git rev-parse --verify refs/remotes/origin/main && - git config set remote.origin.followRemoteHEAD "warn-if-not-main" && - actual=$(git fetch) && - test "z" = "z$actual" && - head=$(git rev-parse refs/remotes/origin/HEAD) && - branch=$(git rev-parse refs/remotes/origin/other) && - test "z$head" = "z$branch" - ) + git -C two rev-parse --verify refs/remotes/origin/other && + git -C two remote set-head origin other && + git -C two rev-parse --verify refs/remotes/origin/HEAD && + git -C two rev-parse --verify refs/remotes/origin/main && + test_config -C two remote.origin.followRemoteHEAD "warn-if-not-main" && + actual=$(git -C two fetch) && + test "z" = "z$actual" && + head=$(git -C two rev-parse refs/remotes/origin/HEAD) && + branch=$(git -C two rev-parse refs/remotes/origin/other) && + test "z$head" = "z$branch" ' test_expect_success "fetch test followRemoteHEAD warn-if-not-branch branch is different" ' - test_when_finished "git config unset remote.origin.followRemoteHEAD" && - ( - cd "$D" && - cd two && - git rev-parse --verify refs/remotes/origin/other && - git remote set-head origin other && - git rev-parse --verify refs/remotes/origin/HEAD && - git rev-parse --verify refs/remotes/origin/main && - git config set remote.origin.followRemoteHEAD "warn-if-not-some/different-branch" && - git fetch >actual && - echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \ - "but we have ${SQ}other${SQ} locally." >expect && - test_cmp expect actual && - head=$(git rev-parse refs/remotes/origin/HEAD) && - branch=$(git rev-parse refs/remotes/origin/other) && - test "z$head" = "z$branch" - ) + git -C two rev-parse --verify refs/remotes/origin/other && + git -C two remote set-head origin other && + git -C two rev-parse --verify refs/remotes/origin/HEAD && + git -C two rev-parse --verify refs/remotes/origin/main && + test_config -C two remote.origin.followRemoteHEAD "warn-if-not-some/different-branch" && + git -C two fetch >actual && + echo "${SQ}HEAD${SQ} at ${SQ}origin${SQ} is ${SQ}main${SQ}," \ + "but we have ${SQ}other${SQ} locally." >expect && + test_cmp expect actual && + head=$(git -C two rev-parse refs/remotes/origin/HEAD) && + branch=$(git -C two rev-parse refs/remotes/origin/other) && + test "z$head" = "z$branch" ' test_expect_success "fetch test followRemoteHEAD always" ' - test_when_finished "git config unset remote.origin.followRemoteHEAD" && - ( - cd "$D" && - cd two && - git rev-parse --verify refs/remotes/origin/other && - git remote set-head origin other && - git rev-parse --verify refs/remotes/origin/HEAD && - git rev-parse --verify refs/remotes/origin/main && - git config set remote.origin.followRemoteHEAD "always" && - git fetch && - head=$(git rev-parse refs/remotes/origin/HEAD) && - branch=$(git rev-parse refs/remotes/origin/main) && - test "z$head" = "z$branch" - ) + git -C two rev-parse --verify refs/remotes/origin/other && + git -C two remote set-head origin other && + git -C two rev-parse --verify refs/remotes/origin/HEAD && + git -C two rev-parse --verify refs/remotes/origin/main && + test_config -C two remote.origin.followRemoteHEAD "always" && + git -C two fetch && + head=$(git -C two rev-parse refs/remotes/origin/HEAD) && + branch=$(git -C two rev-parse refs/remotes/origin/main) && + test "z$head" = "z$branch" ' test_expect_success 'followRemoteHEAD does not kick in with refspecs' ' - test_when_finished "git config unset remote.origin.followRemoteHEAD" && - ( - cd "$D" && - cd two && - git remote set-head origin other && - git config set remote.origin.followRemoteHEAD always && - git fetch origin refs/heads/main:refs/remotes/origin/main && - echo refs/remotes/origin/other >expect && - git symbolic-ref refs/remotes/origin/HEAD >actual && - test_cmp expect actual - ) + git -C two remote set-head origin other && + test_config -C two remote.origin.followRemoteHEAD always && + git -C two fetch origin refs/heads/main:refs/remotes/origin/main && + echo refs/remotes/origin/other >expect && + git -C two symbolic-ref refs/remotes/origin/HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'followRemoteHEAD create does not overwrite dangling symref' ' + git -C two remote add -m does-not-exist custom-head ../one && + test_config -C two remote.custom-head.followRemoteHEAD create && + git -C two fetch custom-head && + echo refs/remotes/custom-head/does-not-exist >expect && + git -C two symbolic-ref refs/remotes/custom-head/HEAD >actual && + test_cmp expect actual ' test_expect_success 'fetch --prune on its own works as expected' ' - cd "$D" && git clone . prune && - cd prune && - git update-ref refs/remotes/origin/extrabranch main && + ( + cd prune && + git update-ref refs/remotes/origin/extrabranch main && - git fetch --prune origin && - test_must_fail git rev-parse origin/extrabranch + git fetch --prune origin && + test_must_fail git rev-parse origin/extrabranch + ) ' test_expect_success 'fetch --prune with a branch name keeps branches' ' - cd "$D" && git clone . prune-branch && - cd prune-branch && - git update-ref refs/remotes/origin/extrabranch main && + ( + cd prune-branch && + git update-ref refs/remotes/origin/extrabranch main && - git fetch --prune origin main && - git rev-parse origin/extrabranch + git fetch --prune origin main && + git rev-parse origin/extrabranch + ) ' test_expect_success 'fetch --prune with a namespace keeps other namespaces' ' - cd "$D" && git clone . prune-namespace && - cd prune-namespace && + ( + cd prune-namespace && - git fetch --prune origin refs/heads/a/*:refs/remotes/origin/a/* && - git rev-parse origin/main + git fetch --prune origin refs/heads/a/*:refs/remotes/origin/a/* && + git rev-parse origin/main + ) ' test_expect_success 'fetch --prune handles overlapping refspecs' ' - cd "$D" && git update-ref refs/pull/42/head main && git clone . prune-overlapping && - cd prune-overlapping && - git config --add remote.origin.fetch refs/pull/*/head:refs/remotes/origin/pr/* && + ( + cd prune-overlapping && + git config --add remote.origin.fetch refs/pull/*/head:refs/remotes/origin/pr/* && - git fetch --prune origin && - git rev-parse origin/main && - git rev-parse origin/pr/42 && + git fetch --prune origin && + git rev-parse origin/main && + git rev-parse origin/pr/42 && - git config --unset-all remote.origin.fetch && - git config remote.origin.fetch refs/pull/*/head:refs/remotes/origin/pr/* && - git config --add remote.origin.fetch refs/heads/*:refs/remotes/origin/* && + git config --unset-all remote.origin.fetch && + git config remote.origin.fetch refs/pull/*/head:refs/remotes/origin/pr/* && + git config --add remote.origin.fetch refs/heads/*:refs/remotes/origin/* && - git fetch --prune origin && - git rev-parse origin/main && - git rev-parse origin/pr/42 + git fetch --prune origin && + git rev-parse origin/main && + git rev-parse origin/pr/42 + ) ' test_expect_success 'fetch --prune --tags prunes branches but not tags' ' - cd "$D" && git clone . prune-tags && - cd prune-tags && - git tag sometag main && - # Create what looks like a remote-tracking branch from an earlier - # fetch that has since been deleted from the remote: - git update-ref refs/remotes/origin/fake-remote main && - - git fetch --prune --tags origin && - git rev-parse origin/main && - test_must_fail git rev-parse origin/fake-remote && - git rev-parse sometag + ( + cd prune-tags && + git tag sometag main && + # Create what looks like a remote-tracking branch from an earlier + # fetch that has since been deleted from the remote: + git update-ref refs/remotes/origin/fake-remote main && + + git fetch --prune --tags origin && + git rev-parse origin/main && + test_must_fail git rev-parse origin/fake-remote && + git rev-parse sometag + ) ' test_expect_success 'fetch --prune --tags with branch does not prune other things' ' - cd "$D" && git clone . prune-tags-branch && - cd prune-tags-branch && - git tag sometag main && - git update-ref refs/remotes/origin/extrabranch main && + ( + cd prune-tags-branch && + git tag sometag main && + git update-ref refs/remotes/origin/extrabranch main && - git fetch --prune --tags origin main && - git rev-parse origin/extrabranch && - git rev-parse sometag + git fetch --prune --tags origin main && + git rev-parse origin/extrabranch && + git rev-parse sometag + ) ' test_expect_success 'fetch --prune --tags with refspec prunes based on refspec' ' - cd "$D" && git clone . prune-tags-refspec && - cd prune-tags-refspec && - git tag sometag main && - git update-ref refs/remotes/origin/foo/otherbranch main && - git update-ref refs/remotes/origin/extrabranch main && - - git fetch --prune --tags origin refs/heads/foo/*:refs/remotes/origin/foo/* && - test_must_fail git rev-parse refs/remotes/origin/foo/otherbranch && - git rev-parse origin/extrabranch && - git rev-parse sometag + ( + cd prune-tags-refspec && + git tag sometag main && + git update-ref refs/remotes/origin/foo/otherbranch main && + git update-ref refs/remotes/origin/extrabranch main && + + git fetch --prune --tags origin refs/heads/foo/*:refs/remotes/origin/foo/* && + test_must_fail git rev-parse refs/remotes/origin/foo/otherbranch && + git rev-parse origin/extrabranch && + git rev-parse sometag + ) ' test_expect_success 'fetch --tags gets tags even without a configured remote' ' @@ -381,21 +356,21 @@ test_expect_success 'fetch --tags gets tags even without a configured remote' ' ' test_expect_success REFFILES 'fetch --prune fails to delete branches' ' - cd "$D" && git clone . prune-fail && - cd prune-fail && - git update-ref refs/remotes/origin/extrabranch main && - git pack-refs --all && - : this will prevent --prune from locking packed-refs for deleting refs, but adding loose refs still succeeds && - >.git/packed-refs.new && + ( + cd prune-fail && + git update-ref refs/remotes/origin/extrabranch main && + git pack-refs --all && + : this will prevent --prune from locking packed-refs for deleting refs, but adding loose refs still succeeds && + >.git/packed-refs.new && - test_must_fail git fetch --prune origin + test_must_fail git fetch --prune origin + ) ' test_expect_success 'fetch --atomic works with a single branch' ' - test_when_finished "rm -rf \"$D\"/atomic" && + test_when_finished "rm -rf atomic" && - cd "$D" && git clone . atomic && git branch atomic-branch && oid=$(git rev-parse atomic-branch) && @@ -408,9 +383,8 @@ test_expect_success 'fetch --atomic works with a single branch' ' ' test_expect_success 'fetch --atomic works with multiple branches' ' - test_when_finished "rm -rf \"$D\"/atomic" && + test_when_finished "rm -rf atomic" && - cd "$D" && git clone . atomic && git branch atomic-branch-1 && git branch atomic-branch-2 && @@ -423,9 +397,8 @@ test_expect_success 'fetch --atomic works with multiple branches' ' ' test_expect_success 'fetch --atomic works with mixed branches and tags' ' - test_when_finished "rm -rf \"$D\"/atomic" && + test_when_finished "rm -rf atomic" && - cd "$D" && git clone . atomic && git branch atomic-mixed-branch && git tag atomic-mixed-tag && @@ -437,9 +410,8 @@ test_expect_success 'fetch --atomic works with mixed branches and tags' ' ' test_expect_success 'fetch --atomic prunes references' ' - test_when_finished "rm -rf \"$D\"/atomic" && + test_when_finished "rm -rf atomic" && - cd "$D" && git branch atomic-prune-delete && git clone . atomic && git branch --delete atomic-prune-delete && @@ -453,9 +425,8 @@ test_expect_success 'fetch --atomic prunes references' ' ' test_expect_success 'fetch --atomic aborts with non-fast-forward update' ' - test_when_finished "rm -rf \"$D\"/atomic" && + test_when_finished "rm -rf atomic" && - cd "$D" && git branch atomic-non-ff && git clone . atomic && git rev-parse HEAD >actual && @@ -472,9 +443,8 @@ test_expect_success 'fetch --atomic aborts with non-fast-forward update' ' ' test_expect_success 'fetch --atomic executes a single reference transaction only' ' - test_when_finished "rm -rf \"$D\"/atomic" && + test_when_finished "rm -rf atomic" && - cd "$D" && git clone . atomic && git branch atomic-hooks-1 && git branch atomic-hooks-2 && @@ -499,9 +469,8 @@ test_expect_success 'fetch --atomic executes a single reference transaction only ' test_expect_success 'fetch --atomic aborts all reference updates if hook aborts' ' - test_when_finished "rm -rf \"$D\"/atomic" && + test_when_finished "rm -rf atomic" && - cd "$D" && git clone . atomic && git branch atomic-hooks-abort-1 && git branch atomic-hooks-abort-2 && @@ -536,9 +505,8 @@ test_expect_success 'fetch --atomic aborts all reference updates if hook aborts' ' test_expect_success 'fetch --atomic --append appends to FETCH_HEAD' ' - test_when_finished "rm -rf \"$D\"/atomic" && + test_when_finished "rm -rf atomic" && - cd "$D" && git clone . atomic && oid=$(git rev-parse HEAD) && @@ -574,8 +542,7 @@ test_expect_success REFFILES 'fetch --atomic fails transaction if reference lock ' test_expect_success '--refmap="" ignores configured refspec' ' - cd "$TRASH_DIRECTORY" && - git clone "$D" remote-refs && + git clone . remote-refs && git -C remote-refs rev-parse remotes/origin/main >old && git -C remote-refs update-ref refs/remotes/origin/main main~1 && git -C remote-refs rev-parse remotes/origin/main >new && @@ -599,34 +566,26 @@ test_expect_success '--refmap="" and --prune' ' test_expect_success 'fetch tags when there is no tags' ' - cd "$D" && - - mkdir notags && - cd notags && - git init && - - git fetch -t .. + git init notags && + git -C notags fetch -t .. ' test_expect_success 'fetch following tags' ' - cd "$D" && git tag -a -m "annotated" anno HEAD && git tag light HEAD && - mkdir four && - cd four && - git init && - - git fetch .. :track && - git show-ref --verify refs/tags/anno && - git show-ref --verify refs/tags/light - + git init four && + ( + cd four && + git fetch .. :track && + git show-ref --verify refs/tags/anno && + git show-ref --verify refs/tags/light + ) ' test_expect_success 'fetch uses remote ref names to describe new refs' ' - cd "$D" && git init descriptive && ( cd descriptive && @@ -654,30 +613,20 @@ test_expect_success 'fetch uses remote ref names to describe new refs' ' test_expect_success 'fetch must not resolve short tag name' ' - cd "$D" && - - mkdir five && - cd five && - git init && - - test_must_fail git fetch .. anno:five + git init five && + test_must_fail git -C five fetch .. anno:five ' test_expect_success 'fetch can now resolve short remote name' ' - cd "$D" && git update-ref refs/remotes/six/HEAD HEAD && - mkdir six && - cd six && - git init && - - git fetch .. six:six + git init six && + git -C six fetch .. six:six ' test_expect_success 'create bundle 1' ' - cd "$D" && echo >file updated again by origin && git commit -a -m "tip" && git bundle create --version=3 bundle1 main^..main @@ -691,35 +640,36 @@ test_expect_success 'header of bundle looks right' ' OID refs/heads/main EOF - sed -e "s/$OID_REGEX/OID/g" -e "5q" "$D"/bundle1 >actual && + sed -e "s/$OID_REGEX/OID/g" -e "5q" bundle1 >actual && test_cmp expect actual ' test_expect_success 'create bundle 2' ' - cd "$D" && git bundle create bundle2 main~2..main ' test_expect_success 'unbundle 1' ' - cd "$D/bundle" && - git checkout -b some-branch && - test_must_fail git fetch "$D/bundle1" main:main + ( + cd bundle && + git checkout -b some-branch && + test_must_fail git fetch bundle1 main:main + ) ' test_expect_success 'bundle 1 has only 3 files ' ' - cd "$D" && test_bundle_object_count bundle1 3 ' test_expect_success 'unbundle 2' ' - cd "$D/bundle" && - git fetch ../bundle2 main:main && - test "tip" = "$(git log -1 --pretty=oneline main | cut -d" " -f2)" + ( + cd bundle && + git fetch ../bundle2 main:main && + test "tip" = "$(git log -1 --pretty=oneline main | cut -d" " -f2)" + ) ' test_expect_success 'bundle does not prerequisite objects' ' - cd "$D" && touch file2 && git add file2 && git commit -m add.file2 file2 && @@ -729,7 +679,6 @@ test_expect_success 'bundle does not prerequisite objects' ' test_expect_success 'bundle should be able to create a full history' ' - cd "$D" && git tag -a -m "1.0" v1.0 main && git bundle create bundle4 v1.0 @@ -783,7 +732,6 @@ test_expect_success 'quoting of a strangely named repo' ' test_expect_success 'bundle should record HEAD correctly' ' - cd "$D" && git bundle create bundle5 HEAD main && git bundle list-heads bundle5 >actual && for h in HEAD refs/heads/main @@ -803,7 +751,6 @@ test_expect_success 'mark initial state of origin/main' ' test_expect_success 'explicit fetch should update tracking' ' - cd "$D" && git branch -f side && ( cd three && @@ -818,7 +765,6 @@ test_expect_success 'explicit fetch should update tracking' ' test_expect_success 'explicit pull should update tracking' ' - cd "$D" && git branch -f side && ( cd three && @@ -832,7 +778,6 @@ test_expect_success 'explicit pull should update tracking' ' ' test_expect_success 'explicit --refmap is allowed only with command-line refspec' ' - cd "$D" && ( cd three && test_must_fail git fetch --refmap="*:refs/remotes/none/*" @@ -840,7 +785,6 @@ test_expect_success 'explicit --refmap is allowed only with command-line refspec ' test_expect_success 'explicit --refmap option overrides remote.*.fetch' ' - cd "$D" && git branch -f side && ( cd three && @@ -855,7 +799,6 @@ test_expect_success 'explicit --refmap option overrides remote.*.fetch' ' ' test_expect_success 'explicitly empty --refmap option disables remote.*.fetch' ' - cd "$D" && git branch -f side && ( cd three && @@ -870,7 +813,6 @@ test_expect_success 'explicitly empty --refmap option disables remote.*.fetch' ' test_expect_success 'configured fetch updates tracking' ' - cd "$D" && git branch -f side && ( cd three && @@ -884,7 +826,6 @@ test_expect_success 'configured fetch updates tracking' ' ' test_expect_success 'non-matching refspecs do not confuse tracking update' ' - cd "$D" && git update-ref refs/odd/location HEAD && ( cd three && @@ -901,14 +842,12 @@ test_expect_success 'non-matching refspecs do not confuse tracking update' ' test_expect_success 'pushing nonexistent branch by mistake should not segv' ' - cd "$D" && test_must_fail git push seven no:no ' test_expect_success 'auto tag following fetches minimum' ' - cd "$D" && git clone .git follow && git checkout HEAD^0 && ( @@ -1307,7 +1246,7 @@ test_expect_success 'fetch --prune prints the remotes url' ' cd only-prunes && git fetch --prune origin 2>&1 | head -n1 >../actual ) && - echo "From ${D}/." >expect && + echo "From $(pwd)/." >expect && test_cmp expect actual ' @@ -1357,14 +1296,14 @@ test_expect_success 'fetching with auto-gc does not lock up' ' echo "$*" && false EOF - git clone "file://$D" auto-gc && + git clone "file://$PWD" auto-gc && test_commit test2 && ( cd auto-gc && git config fetch.unpackLimit 1 && git config gc.autoPackLimit 1 && git config gc.autoDetach false && - GIT_ASK_YESNO="$D/askyesno" git fetch --verbose >fetch.out 2>&1 && + GIT_ASK_YESNO="$TRASH_DIRECTORY/askyesno" git fetch --verbose >fetch.out 2>&1 && test_grep "Auto packing the repository" fetch.out && ! grep "Should I try again" fetch.out ) diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 4e9c27b0f2..46926e7bbd 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -105,7 +105,6 @@ check_push_result () { } test_expect_success setup ' - >path1 && git add path1 && test_tick && @@ -117,7 +116,6 @@ test_expect_success setup ' test_tick && git commit -a -m second && the_commit=$(git show-ref -s --verify refs/heads/main) - ' for cmd in push fetch @@ -322,104 +320,82 @@ test_expect_success 'push with pushInsteadOf and explicit pushurl (pushInsteadOf ' test_expect_success 'push with matching heads' ' - mk_test testrepo heads/main && git push testrepo : && check_push_result testrepo $the_commit heads/main - ' test_expect_success 'push with matching heads on the command line' ' - mk_test testrepo heads/main && git push testrepo : && check_push_result testrepo $the_commit heads/main - ' test_expect_success 'failed (non-fast-forward) push with matching heads' ' - mk_test testrepo heads/main && git push testrepo : && git commit --amend -massaged && test_must_fail git push testrepo && check_push_result testrepo $the_commit heads/main && git reset --hard $the_commit - ' test_expect_success 'push --force with matching heads' ' - mk_test testrepo heads/main && git push testrepo : && git commit --amend -massaged && git push --force testrepo : && ! check_push_result testrepo $the_commit heads/main && git reset --hard $the_commit - ' test_expect_success 'push with matching heads and forced update' ' - mk_test testrepo heads/main && git push testrepo : && git commit --amend -massaged && git push testrepo +: && ! check_push_result testrepo $the_commit heads/main && git reset --hard $the_commit - ' test_expect_success 'push with no ambiguity (1)' ' - mk_test testrepo heads/main && git push testrepo main:main && check_push_result testrepo $the_commit heads/main - ' test_expect_success 'push with no ambiguity (2)' ' - mk_test testrepo remotes/origin/main && git push testrepo main:origin/main && check_push_result testrepo $the_commit remotes/origin/main - ' test_expect_success 'push with colon-less refspec, no ambiguity' ' - mk_test testrepo heads/main heads/t/main && git branch -f t/main main && git push testrepo main && check_push_result testrepo $the_commit heads/main && check_push_result testrepo $the_first_commit heads/t/main - ' test_expect_success 'push with weak ambiguity (1)' ' - mk_test testrepo heads/main remotes/origin/main && git push testrepo main:main && check_push_result testrepo $the_commit heads/main && check_push_result testrepo $the_first_commit remotes/origin/main - ' test_expect_success 'push with weak ambiguity (2)' ' - mk_test testrepo heads/main remotes/origin/main remotes/another/main && git push testrepo main:main && check_push_result testrepo $the_commit heads/main && check_push_result testrepo $the_first_commit remotes/origin/main remotes/another/main - ' test_expect_success 'push with ambiguity' ' - mk_test testrepo heads/frotz tags/frotz && test_must_fail git push testrepo main:frotz && check_push_result testrepo $the_first_commit heads/frotz tags/frotz - ' test_expect_success 'push with onelevel ref' ' @@ -428,17 +404,14 @@ test_expect_success 'push with onelevel ref' ' ' test_expect_success 'push with colon-less refspec (1)' ' - mk_test testrepo heads/frotz tags/frotz && git branch -f frotz main && git push testrepo frotz && check_push_result testrepo $the_commit heads/frotz && check_push_result testrepo $the_first_commit tags/frotz - ' test_expect_success 'push with colon-less refspec (2)' ' - mk_test testrepo heads/frotz tags/frotz && if git show-ref --verify -q refs/heads/frotz then @@ -448,7 +421,6 @@ test_expect_success 'push with colon-less refspec (2)' ' git push -f testrepo frotz && check_push_result testrepo $the_commit tags/frotz && check_push_result testrepo $the_first_commit heads/frotz - ' test_expect_success 'push with colon-less refspec (3)' ' @@ -465,7 +437,6 @@ test_expect_success 'push with colon-less refspec (3)' ' ' test_expect_success 'push with colon-less refspec (4)' ' - mk_test testrepo && if git show-ref --verify -q refs/heads/frotz then @@ -475,38 +446,34 @@ test_expect_success 'push with colon-less refspec (4)' ' git push testrepo frotz && check_push_result testrepo $the_commit tags/frotz && test 1 = $( cd testrepo && git show-ref | wc -l ) - ' test_expect_success 'push head with non-existent, incomplete dest' ' - mk_test testrepo && git push testrepo main:branch && check_push_result testrepo $the_commit heads/branch - ' test_expect_success 'push tag with non-existent, incomplete dest' ' - mk_test testrepo && git tag -f v1.0 && git push testrepo v1.0:tag && check_push_result testrepo $the_commit tags/tag - ' test_expect_success 'push oid with non-existent, incomplete dest' ' - mk_test testrepo && test_must_fail git push testrepo $(git rev-parse main):foo - ' test_expect_success 'push ref expression with non-existent, incomplete dest' ' - mk_test testrepo && test_must_fail git push testrepo main^:branch +' +test_expect_success 'push ref expression with non-existent oid src' ' + mk_test testrepo && + test_must_fail git push testrepo $(test_oid 001):branch ' for head in HEAD @ @@ -550,7 +517,6 @@ do git checkout main && git push testrepo $head:branch && check_push_result testrepo $the_commit heads/branch - ' test_expect_success "push with config remote.*.push = $head" ' @@ -596,7 +562,6 @@ test_expect_success 'push with remote.pushdefault' ' ' test_expect_success 'push with config remote.*.pushurl' ' - mk_test testrepo heads/main && git checkout main && test_config remote.there.url test2repo && @@ -655,7 +620,6 @@ test_expect_success 'push ignores "branch." config without subsection' ' ' test_expect_success 'push with dry-run' ' - mk_test testrepo heads/main && old_commit=$(git -C testrepo show-ref -s --verify refs/heads/main) && git push --dry-run testrepo : && @@ -663,7 +627,6 @@ test_expect_success 'push with dry-run' ' ' test_expect_success 'push updates local refs' ' - mk_test testrepo heads/main && mk_child testrepo child && ( @@ -673,11 +636,9 @@ test_expect_success 'push updates local refs' ' test $(git rev-parse main) = \ $(git rev-parse remotes/origin/main) ) - ' test_expect_success 'push updates up-to-date local refs' ' - mk_test testrepo heads/main && mk_child testrepo child1 && mk_child testrepo child2 && @@ -689,11 +650,9 @@ test_expect_success 'push updates up-to-date local refs' ' test $(git rev-parse main) = \ $(git rev-parse remotes/origin/main) ) - ' test_expect_success 'push preserves up-to-date packed refs' ' - mk_test testrepo heads/main && mk_child testrepo child && ( @@ -701,11 +660,9 @@ test_expect_success 'push preserves up-to-date packed refs' ' git push && ! test -f .git/refs/remotes/origin/main ) - ' test_expect_success 'push does not update local refs on failure' ' - mk_test testrepo heads/main && mk_child testrepo child && echo "#!/no/frobnication/today" >testrepo/.git/hooks/pre-receive && @@ -717,16 +674,13 @@ test_expect_success 'push does not update local refs on failure' ' test $(git rev-parse main) != \ $(git rev-parse remotes/origin/main) ) - ' test_expect_success 'allow deleting an invalid remote ref' ' - mk_test testrepo heads/branch && rm -f testrepo/.git/objects/??/* && git push testrepo :refs/heads/branch && (cd testrepo && test_must_fail git rev-parse --verify refs/heads/branch) - ' test_expect_success 'pushing valid refs triggers post-receive and post-update hooks' ' diff --git a/t/t5530-upload-pack-error.sh b/t/t5530-upload-pack-error.sh index 558eedf25a..d40292cfb7 100755 --- a/t/t5530-upload-pack-error.sh +++ b/t/t5530-upload-pack-error.sh @@ -4,8 +4,6 @@ test_description='errors in upload-pack' . ./test-lib.sh -D=$(pwd) - corrupt_repo () { object_sha1=$(git rev-parse "$1") && ob=$(expr "$object_sha1" : "\(..\)") && @@ -21,11 +19,7 @@ test_expect_success 'setup and corrupt repository' ' test_tick && echo changed >file && git commit -a -m changed && - corrupt_repo HEAD:file - -' - -test_expect_success 'fsck fails' ' + corrupt_repo HEAD:file && test_must_fail git fsck ' @@ -40,17 +34,12 @@ test_expect_success 'upload-pack fails due to error in pack-objects packing' ' ' test_expect_success 'corrupt repo differently' ' - git hash-object -w file && - corrupt_repo HEAD^^{tree} - -' - -test_expect_success 'fsck fails' ' + corrupt_repo HEAD^^{tree} && test_must_fail git fsck ' -test_expect_success 'upload-pack fails due to error in rev-list' ' +test_expect_success 'upload-pack fails due to error in rev-list' ' printf "%04xwant %s\n%04xshallow %s00000009done\n0000" \ $(($hexsz + 10)) $(git rev-parse HEAD) \ $(($hexsz + 12)) $(git rev-parse HEAD^) >input && @@ -59,7 +48,6 @@ test_expect_success 'upload-pack fails due to error in rev-list' ' ' test_expect_success 'upload-pack fails due to bad want (no object)' ' - printf "%04xwant %s multi_ack_detailed\n00000009done\n0000" \ $(($hexsz + 29)) $(test_oid deadbeef) >input && test_must_fail git upload-pack . <input >output 2>output.err && @@ -69,7 +57,6 @@ test_expect_success 'upload-pack fails due to bad want (no object)' ' ' test_expect_success 'upload-pack fails due to bad want (not tip)' ' - oid=$(echo an object we have | git hash-object -w --stdin) && printf "%04xwant %s multi_ack_detailed\n00000009done\n0000" \ $(($hexsz + 29)) "$oid" >input && @@ -80,7 +67,6 @@ test_expect_success 'upload-pack fails due to bad want (not tip)' ' ' test_expect_success 'upload-pack fails due to error in pack-objects enumeration' ' - printf "%04xwant %s\n00000009done\n0000" \ $((hexsz + 10)) $(git rev-parse HEAD) >input && test_must_fail git upload-pack . <input >/dev/null 2>output.err && @@ -105,18 +91,48 @@ test_expect_success 'upload-pack tolerates EOF just after stateless client wants test_cmp expect actual ' -test_expect_success 'create empty repository' ' - - mkdir foo && - cd foo && - git init - -' - test_expect_success 'fetch fails' ' + git init foo && + test_must_fail git -C foo fetch .. main +' - test_must_fail git fetch .. main +test_expect_success 'upload-pack ACKs repeated non-commit objects repeatedly (protocol v0)' ' + commit_id=$(git rev-parse HEAD) && + tree_id=$(git rev-parse HEAD^{tree}) && + test-tool pkt-line pack >request <<-EOF && + want $commit_id + 0000 + have $tree_id + have $tree_id + 0000 + EOF + git upload-pack --stateless-rpc . <request >actual && + depacketize <actual >actual.raw && + grep ^ACK actual.raw >actual.acks && + cat >expect <<-EOF && + ACK $tree_id + ACK $tree_id + EOF + test_cmp expect actual.acks +' +test_expect_success 'upload-pack ACKs repeated non-commit objects once only (protocol v2)' ' + commit_id=$(git rev-parse HEAD) && + tree_id=$(git rev-parse HEAD^{tree}) && + test-tool pkt-line pack >request <<-EOF && + command=fetch + object-format=$(test_oid algo) + 0001 + want $commit_id + have $tree_id + have $tree_id + 0000 + EOF + GIT_PROTOCOL=version=2 git upload-pack . <request >actual && + depacketize <actual >actual.raw && + grep ^ACK actual.raw >actual.acks && + echo "ACK $tree_id" >expect && + test_cmp expect actual.acks ' test_done diff --git a/t/t5564-http-proxy.sh b/t/t5564-http-proxy.sh index b27e481f95..c3903faf2d 100755 --- a/t/t5564-http-proxy.sh +++ b/t/t5564-http-proxy.sh @@ -72,7 +72,9 @@ test_expect_success SOCKS_PROXY 'clone via Unix socket' ' test_when_finished "rm -rf clone" && test_config_global http.proxy "socks4://localhost$PWD/%2530.sock" && { { - GIT_TRACE_CURL=$PWD/trace git clone "$HTTPD_URL/smart/repo.git" clone 2>err && + GIT_TRACE_CURL=$PWD/trace \ + GIT_TRACE_CURL_COMPONENTS=socks \ + git clone "$HTTPD_URL/smart/repo.git" clone 2>err && grep -i "SOCKS4 request granted" trace } || old_libcurl_error err diff --git a/t/t5710-promisor-remote-capability.sh b/t/t5710-promisor-remote-capability.sh index cb061b1f35..023735d6a8 100755 --- a/t/t5710-promisor-remote-capability.sh +++ b/t/t5710-promisor-remote-capability.sh @@ -295,6 +295,71 @@ test_expect_success "clone with 'KnownUrl' and empty url, so not advertised" ' check_missing_objects server 1 "$oid" ' +test_expect_success "clone with promisor.sendFields" ' + git -C server config promisor.advertise true && + test_when_finished "rm -rf client" && + + git -C server remote add otherLop "https://invalid.invalid" && + git -C server config remote.otherLop.token "fooBar" && + git -C server config remote.otherLop.stuff "baz" && + git -C server config remote.otherLop.partialCloneFilter "blob:limit=10k" && + test_when_finished "git -C server remote remove otherLop" && + test_config -C server promisor.sendFields "partialCloneFilter, token" && + test_when_finished "rm trace" && + + # Clone from server to create a client + GIT_TRACE_PACKET="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \ + -c remote.lop.promisor=true \ + -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \ + -c remote.lop.url="file://$(pwd)/lop" \ + -c promisor.acceptfromserver=All \ + --no-local --filter="blob:limit=5k" server client && + + # Check that fields are properly transmitted + ENCODED_URL=$(echo "file://$(pwd)/lop" | sed -e "s/ /%20/g") && + PR1="name=lop,url=$ENCODED_URL,partialCloneFilter=blob:none" && + PR2="name=otherLop,url=https://invalid.invalid,partialCloneFilter=blob:limit=10k,token=fooBar" && + test_grep "clone< promisor-remote=$PR1;$PR2" trace && + test_grep "clone> promisor-remote=lop;otherLop" trace && + + # Check that the largest object is still missing on the server + check_missing_objects server 1 "$oid" +' + +test_expect_success "clone with promisor.checkFields" ' + git -C server config promisor.advertise true && + test_when_finished "rm -rf client" && + + git -C server remote add otherLop "https://invalid.invalid" && + git -C server config remote.otherLop.token "fooBar" && + git -C server config remote.otherLop.stuff "baz" && + git -C server config remote.otherLop.partialCloneFilter "blob:limit=10k" && + test_when_finished "git -C server remote remove otherLop" && + test_config -C server promisor.sendFields "partialCloneFilter, token" && + test_when_finished "rm trace" && + + # Clone from server to create a client + GIT_TRACE_PACKET="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \ + -c remote.lop.promisor=true \ + -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \ + -c remote.lop.url="file://$(pwd)/lop" \ + -c remote.lop.partialCloneFilter="blob:none" \ + -c promisor.acceptfromserver=All \ + -c promisor.checkFields=partialcloneFilter \ + --no-local --filter="blob:limit=5k" server client && + + # Check that fields are properly transmitted + ENCODED_URL=$(echo "file://$(pwd)/lop" | sed -e "s/ /%20/g") && + PR1="name=lop,url=$ENCODED_URL,partialCloneFilter=blob:none" && + PR2="name=otherLop,url=https://invalid.invalid,partialCloneFilter=blob:limit=10k,token=fooBar" && + test_grep "clone< promisor-remote=$PR1;$PR2" trace && + test_grep "clone> promisor-remote=lop" trace && + test_grep ! "clone> promisor-remote=lop;otherLop" trace && + + # Check that the largest object is still missing on the server + check_missing_objects server 1 "$oid" +' + test_expect_success "clone with promisor.advertise set to 'true' but don't delete the client" ' git -C server config promisor.advertise true && diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index 256ccaefb7..2c70cc561a 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -409,6 +409,36 @@ test_expect_success 'describe tag object' ' test_grep "fatal: test-blob-1 is neither a commit nor blob" actual ' +test_expect_success 'describe an unreachable blob' ' + blob=$(echo not-found-anywhere | git hash-object -w --stdin) && + test_must_fail git describe $blob 2>actual && + test_grep "blob .$blob. not reachable from HEAD" actual +' + +test_expect_success 'describe blob on an unborn branch' ' + oldbranch=$(git symbolic-ref HEAD) && + test_when_finished "git symbolic-ref HEAD $oldbranch" && + git symbolic-ref HEAD refs/heads/does-not-exist && + test_must_fail git describe test-blob 2>actual && + test_grep "cannot search .* on an unborn branch" actual +' + +# This test creates a repository state that we generally try to disallow: HEAD +# is pointing to an object that is not a commit. The ref update code forbids +# non-commit writes directly to HEAD or to any branch in refs/heads/. But we +# can use the loophole of pointing HEAD to another non-branch ref (something we +# should forbid, but don't for historical reasons). +# +# Do not take this test as an endorsement of the loophole! If we ever tighten +# it, it is reasonable to just drop this test entirely. +test_expect_success 'describe blob on a non-commit HEAD' ' + oldbranch=$(git symbolic-ref HEAD) && + test_when_finished "git symbolic-ref HEAD $oldbranch" && + git symbolic-ref HEAD refs/tags/test-blob && + test_must_fail git describe test-blob 2>actual && + test_grep "blob .* not reachable from HEAD" actual +' + test_expect_success ULIMIT_STACK_SIZE 'name-rev works in a deep repo' ' i=1 && while test $i -lt 8000 diff --git a/t/t6137-pathspec-wildcards-literal.sh b/t/t6137-pathspec-wildcards-literal.sh index 20abad5667..17a03085ef 100755 --- a/t/t6137-pathspec-wildcards-literal.sh +++ b/t/t6137-pathspec-wildcards-literal.sh @@ -3,8 +3,8 @@ test_description='test wildcards and literals with git add/commit (subshell styl . ./test-lib.sh -test_have_prereq FUNNYNAMES || { - skip_all='skipping: needs FUNNYNAMES (non-Windows only)' +test_have_prereq BSLASHPSPEC || { + skip_all='skipping: needs BSLASHPSPEC (backslashes in pathspecs)' test_done } @@ -184,7 +184,7 @@ test_expect_success 'add wildcard f?z' ' ) ' -test_expect_success 'add literal \? literal' ' +test_expect_success 'add literal \?' ' git init test-q-lit && ( cd test-q-lit && @@ -241,7 +241,7 @@ test_expect_success 'add literal hello\?world' ' ) ' -test_expect_success 'add literal [abc]' ' +test_expect_success 'add literal \[abc\]' ' git init test-brackets-lit && ( cd test-brackets-lit && @@ -280,7 +280,7 @@ test_expect_success 'commit: wildcard *' ' ) ' -test_expect_success 'commit: literal *' ' +test_expect_success 'commit: literal \*' ' git init test-c-asterisk-lit && ( cd test-c-asterisk-lit && @@ -328,7 +328,7 @@ test_expect_success 'commit: literal f\*' ' ) ' -test_expect_success 'commit: wildcard pathspec limits commit' ' +test_expect_success 'commit: wildcard f**' ' git init test-c-pathlimit && ( cd test-c-pathlimit && diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index ce9af79ab1..1d9809114d 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -6,2150 +6,14 @@ test_description='for-each-ref test' . ./test-lib.sh -GNUPGHOME_NOT_USED=$GNUPGHOME -. "$TEST_DIRECTORY"/lib-gpg.sh -. "$TEST_DIRECTORY"/lib-terminal.sh -# Mon Jul 3 23:18:43 2006 +0000 -datestamp=1151968723 -setdate_and_increment () { - GIT_COMMITTER_DATE="$datestamp +0200" - datestamp=$(expr "$datestamp" + 1) - GIT_AUTHOR_DATE="$datestamp +0200" - datestamp=$(expr "$datestamp" + 1) - export GIT_COMMITTER_DATE GIT_AUTHOR_DATE -} - -test_object_file_size () { - oid=$(git rev-parse "$1") - path=".git/objects/$(test_oid_to_path $oid)" - test_file_size "$path" -} - -test_expect_success setup ' - # setup .mailmap - cat >.mailmap <<-EOF && - A Thor <athor@example.com> A U Thor <author@example.com> - C Mitter <cmitter@example.com> C O Mitter <committer@example.com> - EOF - - setdate_and_increment && - echo "Using $datestamp" > one && - git add one && - git commit -m "Initial" && - git branch -M main && - setdate_and_increment && - git tag -a -m "Tagging at $datestamp" testtag && - git update-ref refs/remotes/origin/main main && - git remote add origin nowhere && - git config branch.main.remote origin && - git config branch.main.merge refs/heads/main && - git remote add myfork elsewhere && - git config remote.pushdefault myfork && - git config push.default current -' - -test_atom () { - case "$1" in - head) ref=refs/heads/main ;; - tag) ref=refs/tags/testtag ;; - sym) ref=refs/heads/sym ;; - *) ref=$1 ;; - esac - format=$2 - test_do=test_expect_${4:-success} - - printf '%s\n' "$3" >expected - $test_do $PREREQ "basic atom: $ref $format" ' - git for-each-ref --format="%($format)" "$ref" >actual && - sanitize_pgp <actual >actual.clean && - test_cmp expected actual.clean - ' - - # Automatically test "contents:size" atom after testing "contents" - if test "$format" = "contents" - then - # for commit leg, $3 is changed there - expect=$(printf '%s' "$3" | wc -c) - $test_do $PREREQ "basic atom: $ref contents:size" ' - type=$(git cat-file -t "$ref") && - case $type in - tag) - # We cannot use $3 as it expects sanitize_pgp to run - git cat-file tag $ref >out && - expect=$(tail -n +6 out | wc -c) && - rm -f out ;; - tree | blob) - expect="" ;; - commit) - : "use the calculated expect" ;; - *) - BUG "unknown object type" ;; - esac && - # Leave $expect unquoted to lose possible leading whitespaces - echo $expect >expected && - git for-each-ref --format="%(contents:size)" "$ref" >actual && - test_cmp expected actual - ' - fi -} - -hexlen=$(test_oid hexsz) - -test_atom head refname refs/heads/main -test_atom head refname: refs/heads/main -test_atom head refname:short main -test_atom head refname:lstrip=1 heads/main -test_atom head refname:lstrip=2 main -test_atom head refname:lstrip=-1 main -test_atom head refname:lstrip=-2 heads/main -test_atom head refname:rstrip=1 refs/heads -test_atom head refname:rstrip=2 refs -test_atom head refname:rstrip=-1 refs -test_atom head refname:rstrip=-2 refs/heads -test_atom head refname:strip=1 heads/main -test_atom head refname:strip=2 main -test_atom head refname:strip=-1 main -test_atom head refname:strip=-2 heads/main -test_atom head upstream refs/remotes/origin/main -test_atom head upstream:short origin/main -test_atom head upstream:lstrip=2 origin/main -test_atom head upstream:lstrip=-2 origin/main -test_atom head upstream:rstrip=2 refs/remotes -test_atom head upstream:rstrip=-2 refs/remotes -test_atom head upstream:strip=2 origin/main -test_atom head upstream:strip=-2 origin/main -test_atom head push refs/remotes/myfork/main -test_atom head push:short myfork/main -test_atom head push:lstrip=1 remotes/myfork/main -test_atom head push:lstrip=-1 main -test_atom head push:rstrip=1 refs/remotes/myfork -test_atom head push:rstrip=-1 refs -test_atom head push:strip=1 remotes/myfork/main -test_atom head push:strip=-1 main -test_atom head objecttype commit -test_atom head objectsize $((131 + hexlen)) -test_atom head objectsize:disk $(test_object_file_size refs/heads/main) -test_atom head deltabase $ZERO_OID -test_atom head objectname $(git rev-parse refs/heads/main) -test_atom head objectname:short $(git rev-parse --short refs/heads/main) -test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/main) -test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/main) -test_atom head tree $(git rev-parse refs/heads/main^{tree}) -test_atom head tree:short $(git rev-parse --short refs/heads/main^{tree}) -test_atom head tree:short=1 $(git rev-parse --short=1 refs/heads/main^{tree}) -test_atom head tree:short=10 $(git rev-parse --short=10 refs/heads/main^{tree}) -test_atom head parent '' -test_atom head parent:short '' -test_atom head parent:short=1 '' -test_atom head parent:short=10 '' -test_atom head numparent 0 -test_atom head object '' -test_atom head type '' -test_atom head raw "$(git cat-file commit refs/heads/main) -" -test_atom head '*objectname' '' -test_atom head '*objecttype' '' -test_atom head author 'A U Thor <author@example.com> 1151968724 +0200' -test_atom head authorname 'A U Thor' -test_atom head authorname:mailmap 'A Thor' -test_atom head authoremail '<author@example.com>' -test_atom head authoremail:trim 'author@example.com' -test_atom head authoremail:localpart 'author' -test_atom head authoremail:trim,localpart 'author' -test_atom head authoremail:mailmap '<athor@example.com>' -test_atom head authoremail:mailmap,trim 'athor@example.com' -test_atom head authoremail:trim,mailmap 'athor@example.com' -test_atom head authoremail:mailmap,localpart 'athor' -test_atom head authoremail:localpart,mailmap 'athor' -test_atom head authoremail:mailmap,trim,localpart,mailmap,trim 'athor' -test_atom head authordate 'Tue Jul 4 01:18:44 2006 +0200' -test_atom head committer 'C O Mitter <committer@example.com> 1151968723 +0200' -test_atom head committername 'C O Mitter' -test_atom head committername:mailmap 'C Mitter' -test_atom head committeremail '<committer@example.com>' -test_atom head committeremail:trim 'committer@example.com' -test_atom head committeremail:localpart 'committer' -test_atom head committeremail:localpart,trim 'committer' -test_atom head committeremail:mailmap '<cmitter@example.com>' -test_atom head committeremail:mailmap,trim 'cmitter@example.com' -test_atom head committeremail:trim,mailmap 'cmitter@example.com' -test_atom head committeremail:mailmap,localpart 'cmitter' -test_atom head committeremail:localpart,mailmap 'cmitter' -test_atom head committeremail:trim,mailmap,trim,trim,localpart 'cmitter' -test_atom head committerdate 'Tue Jul 4 01:18:43 2006 +0200' -test_atom head tag '' -test_atom head tagger '' -test_atom head taggername '' -test_atom head taggeremail '' -test_atom head taggeremail:trim '' -test_atom head taggeremail:localpart '' -test_atom head taggerdate '' -test_atom head creator 'C O Mitter <committer@example.com> 1151968723 +0200' -test_atom head creatordate 'Tue Jul 4 01:18:43 2006 +0200' -test_atom head subject 'Initial' -test_atom head subject:sanitize 'Initial' -test_atom head contents:subject 'Initial' -test_atom head body '' -test_atom head contents:body '' -test_atom head contents:signature '' -test_atom head contents 'Initial -' -test_atom head HEAD '*' - -test_atom tag refname refs/tags/testtag -test_atom tag refname:short testtag -test_atom tag upstream '' -test_atom tag push '' -test_atom tag objecttype tag -test_atom tag objectsize $((114 + hexlen)) -test_atom tag objectsize:disk $(test_object_file_size refs/tags/testtag) -test_atom tag '*objectsize:disk' $(test_object_file_size refs/heads/main) -test_atom tag deltabase $ZERO_OID -test_atom tag '*deltabase' $ZERO_OID -test_atom tag objectname $(git rev-parse refs/tags/testtag) -test_atom tag objectname:short $(git rev-parse --short refs/tags/testtag) -test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/main) -test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/main) -test_atom tag tree '' -test_atom tag tree:short '' -test_atom tag tree:short=1 '' -test_atom tag tree:short=10 '' -test_atom tag parent '' -test_atom tag parent:short '' -test_atom tag parent:short=1 '' -test_atom tag parent:short=10 '' -test_atom tag numparent '' -test_atom tag object $(git rev-parse refs/tags/testtag^0) -test_atom tag type 'commit' -test_atom tag '*objectname' $(git rev-parse refs/tags/testtag^{}) -test_atom tag '*objecttype' 'commit' -test_atom tag author '' -test_atom tag authorname '' -test_atom tag authorname:mailmap '' -test_atom tag authoremail '' -test_atom tag authoremail:trim '' -test_atom tag authoremail:localpart '' -test_atom tag authoremail:trim,localpart '' -test_atom tag authoremail:mailmap '' -test_atom tag authoremail:mailmap,trim '' -test_atom tag authoremail:trim,mailmap '' -test_atom tag authoremail:mailmap,localpart '' -test_atom tag authoremail:localpart,mailmap '' -test_atom tag authoremail:mailmap,trim,localpart,mailmap,trim '' -test_atom tag authordate '' -test_atom tag committer '' -test_atom tag committername '' -test_atom tag committername:mailmap '' -test_atom tag committeremail '' -test_atom tag committeremail:trim '' -test_atom tag committeremail:localpart '' -test_atom tag committeremail:localpart,trim '' -test_atom tag committeremail:mailmap '' -test_atom tag committeremail:mailmap,trim '' -test_atom tag committeremail:trim,mailmap '' -test_atom tag committeremail:mailmap,localpart '' -test_atom tag committeremail:localpart,mailmap '' -test_atom tag committeremail:trim,mailmap,trim,trim,localpart '' -test_atom tag committerdate '' -test_atom tag tag 'testtag' -test_atom tag tagger 'C O Mitter <committer@example.com> 1151968725 +0200' -test_atom tag taggername 'C O Mitter' -test_atom tag taggername:mailmap 'C Mitter' -test_atom tag taggeremail '<committer@example.com>' -test_atom tag taggeremail:trim 'committer@example.com' -test_atom tag taggeremail:localpart 'committer' -test_atom tag taggeremail:trim,localpart 'committer' -test_atom tag taggeremail:mailmap '<cmitter@example.com>' -test_atom tag taggeremail:mailmap,trim 'cmitter@example.com' -test_atom tag taggeremail:trim,mailmap 'cmitter@example.com' -test_atom tag taggeremail:mailmap,localpart 'cmitter' -test_atom tag taggeremail:localpart,mailmap 'cmitter' -test_atom tag taggeremail:trim,mailmap,trim,localpart,localpart 'cmitter' -test_atom tag taggerdate 'Tue Jul 4 01:18:45 2006 +0200' -test_atom tag creator 'C O Mitter <committer@example.com> 1151968725 +0200' -test_atom tag creatordate 'Tue Jul 4 01:18:45 2006 +0200' -test_atom tag subject 'Tagging at 1151968727' -test_atom tag subject:sanitize 'Tagging-at-1151968727' -test_atom tag contents:subject 'Tagging at 1151968727' -test_atom tag body '' -test_atom tag contents:body '' -test_atom tag contents:signature '' -test_atom tag contents 'Tagging at 1151968727 -' -test_atom tag HEAD ' ' - -test_expect_success 'basic atom: refs/tags/testtag *raw' ' - git cat-file commit refs/tags/testtag^{} >expected && - git for-each-ref --format="%(*raw)" refs/tags/testtag >actual && - sanitize_pgp <expected >expected.clean && - echo >>expected.clean && - sanitize_pgp <actual >actual.clean && - test_cmp expected.clean actual.clean -' - -test_expect_success 'Check invalid atoms names are errors' ' - test_must_fail git for-each-ref --format="%(INVALID)" refs/heads -' - -test_expect_success 'for-each-ref does not crash with -h' ' +test_expect_success "for-each-ref does not crash with -h" ' test_expect_code 129 git for-each-ref -h >usage && test_grep "[Uu]sage: git for-each-ref " usage && test_expect_code 129 nongit git for-each-ref -h >usage && test_grep "[Uu]sage: git for-each-ref " usage ' -test_expect_success 'Check format specifiers are ignored in naming date atoms' ' - git for-each-ref --format="%(authordate)" refs/heads && - git for-each-ref --format="%(authordate:default) %(authordate)" refs/heads && - git for-each-ref --format="%(authordate) %(authordate:default)" refs/heads && - git for-each-ref --format="%(authordate:default) %(authordate:default)" refs/heads -' - -test_expect_success 'Check valid format specifiers for date fields' ' - git for-each-ref --format="%(authordate:default)" refs/heads && - git for-each-ref --format="%(authordate:relative)" refs/heads && - git for-each-ref --format="%(authordate:short)" refs/heads && - git for-each-ref --format="%(authordate:local)" refs/heads && - git for-each-ref --format="%(authordate:iso8601)" refs/heads && - git for-each-ref --format="%(authordate:rfc2822)" refs/heads -' - -test_expect_success 'Check invalid format specifiers are errors' ' - test_must_fail git for-each-ref --format="%(authordate:INVALID)" refs/heads -' - -test_expect_success 'arguments to %(objectname:short=) must be positive integers' ' - test_must_fail git for-each-ref --format="%(objectname:short=0)" && - test_must_fail git for-each-ref --format="%(objectname:short=-1)" && - test_must_fail git for-each-ref --format="%(objectname:short=foo)" -' - -test_bad_atom () { - case "$1" in - head) ref=refs/heads/main ;; - tag) ref=refs/tags/testtag ;; - sym) ref=refs/heads/sym ;; - *) ref=$1 ;; - esac - format=$2 - test_do=test_expect_${4:-success} - - printf '%s\n' "$3" >expect - $test_do $PREREQ "err basic atom: $ref $format" ' - test_must_fail git for-each-ref \ - --format="%($format)" "$ref" 2>error && - test_cmp expect error - ' -} - -test_bad_atom head 'authoremail:foo' \ - 'fatal: unrecognized %(authoremail) argument: foo' - -test_bad_atom head 'authoremail:mailmap,trim,bar' \ - 'fatal: unrecognized %(authoremail) argument: bar' - -test_bad_atom head 'authoremail:trim,' \ - 'fatal: unrecognized %(authoremail) argument: ' - -test_bad_atom head 'authoremail:mailmaptrim' \ - 'fatal: unrecognized %(authoremail) argument: trim' - -test_bad_atom head 'committeremail: ' \ - 'fatal: unrecognized %(committeremail) argument: ' - -test_bad_atom head 'committeremail: trim,foo' \ - 'fatal: unrecognized %(committeremail) argument: trim,foo' - -test_bad_atom head 'committeremail:mailmap,localpart ' \ - 'fatal: unrecognized %(committeremail) argument: ' - -test_bad_atom head 'committeremail:trim_localpart' \ - 'fatal: unrecognized %(committeremail) argument: _localpart' - -test_bad_atom head 'committeremail:localpart,,,trim' \ - 'fatal: unrecognized %(committeremail) argument: ,,trim' - -test_bad_atom tag 'taggeremail:mailmap,trim, foo ' \ - 'fatal: unrecognized %(taggeremail) argument: foo ' - -test_bad_atom tag 'taggeremail:trim,localpart,' \ - 'fatal: unrecognized %(taggeremail) argument: ' - -test_bad_atom tag 'taggeremail:mailmap;localpart trim' \ - 'fatal: unrecognized %(taggeremail) argument: ;localpart trim' - -test_bad_atom tag 'taggeremail:localpart trim' \ - 'fatal: unrecognized %(taggeremail) argument: trim' - -test_bad_atom tag 'taggeremail:mailmap,mailmap,trim,qux,localpart,trim' \ - 'fatal: unrecognized %(taggeremail) argument: qux,localpart,trim' - -test_date () { - f=$1 && - committer_date=$2 && - author_date=$3 && - tagger_date=$4 && - cat >expected <<-EOF && - 'refs/heads/main' '$committer_date' '$author_date' - 'refs/tags/testtag' '$tagger_date' - EOF - ( - git for-each-ref --shell \ - --format="%(refname) %(committerdate${f:+:$f}) %(authordate${f:+:$f})" \ - refs/heads && - git for-each-ref --shell \ - --format="%(refname) %(taggerdate${f:+:$f})" \ - refs/tags - ) >actual && - test_cmp expected actual -} - -test_expect_success 'Check unformatted date fields output' ' - test_date "" \ - "Tue Jul 4 01:18:43 2006 +0200" \ - "Tue Jul 4 01:18:44 2006 +0200" \ - "Tue Jul 4 01:18:45 2006 +0200" -' - -test_expect_success 'Check format "default" formatted date fields output' ' - test_date default \ - "Tue Jul 4 01:18:43 2006 +0200" \ - "Tue Jul 4 01:18:44 2006 +0200" \ - "Tue Jul 4 01:18:45 2006 +0200" -' - -test_expect_success 'Check format "default-local" date fields output' ' - test_date default-local "Mon Jul 3 23:18:43 2006" "Mon Jul 3 23:18:44 2006" "Mon Jul 3 23:18:45 2006" -' - -# Don't know how to do relative check because I can't know when this script -# is going to be run and can't fake the current time to git, and hence can't -# provide expected output. Instead, I'll just make sure that "relative" -# doesn't exit in error -test_expect_success 'Check format "relative" date fields output' ' - f=relative && - (git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads && - git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual -' - -# We just check that this is the same as "relative" for now. -test_expect_success 'Check format "relative-local" date fields output' ' - test_date relative-local \ - "$(git for-each-ref --format="%(committerdate:relative)" refs/heads)" \ - "$(git for-each-ref --format="%(authordate:relative)" refs/heads)" \ - "$(git for-each-ref --format="%(taggerdate:relative)" refs/tags)" -' - -test_expect_success 'Check format "short" date fields output' ' - test_date short 2006-07-04 2006-07-04 2006-07-04 -' - -test_expect_success 'Check format "short-local" date fields output' ' - test_date short-local 2006-07-03 2006-07-03 2006-07-03 -' - -test_expect_success 'Check format "local" date fields output' ' - test_date local \ - "Mon Jul 3 23:18:43 2006" \ - "Mon Jul 3 23:18:44 2006" \ - "Mon Jul 3 23:18:45 2006" -' - -test_expect_success 'Check format "iso8601" date fields output' ' - test_date iso8601 \ - "2006-07-04 01:18:43 +0200" \ - "2006-07-04 01:18:44 +0200" \ - "2006-07-04 01:18:45 +0200" -' - -test_expect_success 'Check format "iso8601-local" date fields output' ' - test_date iso8601-local "2006-07-03 23:18:43 +0000" "2006-07-03 23:18:44 +0000" "2006-07-03 23:18:45 +0000" -' - -test_expect_success 'Check format "rfc2822" date fields output' ' - test_date rfc2822 \ - "Tue, 4 Jul 2006 01:18:43 +0200" \ - "Tue, 4 Jul 2006 01:18:44 +0200" \ - "Tue, 4 Jul 2006 01:18:45 +0200" -' - -test_expect_success 'Check format "rfc2822-local" date fields output' ' - test_date rfc2822-local "Mon, 3 Jul 2006 23:18:43 +0000" "Mon, 3 Jul 2006 23:18:44 +0000" "Mon, 3 Jul 2006 23:18:45 +0000" -' - -test_expect_success 'Check format "raw" date fields output' ' - test_date raw "1151968723 +0200" "1151968724 +0200" "1151968725 +0200" -' - -test_expect_success 'Check format "raw-local" date fields output' ' - test_date raw-local "1151968723 +0000" "1151968724 +0000" "1151968725 +0000" -' - -test_expect_success 'Check format of strftime date fields' ' - echo "my date is 2006-07-04" >expected && - git for-each-ref \ - --format="%(authordate:format:my date is %Y-%m-%d)" \ - refs/heads >actual && - test_cmp expected actual -' - -test_expect_success 'Check format of strftime-local date fields' ' - echo "my date is 2006-07-03" >expected && - git for-each-ref \ - --format="%(authordate:format-local:my date is %Y-%m-%d)" \ - refs/heads >actual && - test_cmp expected actual -' - -test_expect_success 'exercise strftime with odd fields' ' - echo >expected && - git for-each-ref --format="%(authordate:format:)" refs/heads >actual && - test_cmp expected actual && - long="long format -- $ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID" && - echo $long >expected && - git for-each-ref --format="%(authordate:format:$long)" refs/heads >actual && - test_cmp expected actual -' - -cat >expected <<\EOF -refs/heads/main -refs/remotes/origin/main -refs/tags/testtag -EOF - -test_expect_success 'Verify ascending sort' ' - git for-each-ref --format="%(refname)" --sort=refname >actual && - test_cmp expected actual -' - - -cat >expected <<\EOF -refs/tags/testtag -refs/remotes/origin/main -refs/heads/main -EOF - -test_expect_success 'Verify descending sort' ' - git for-each-ref --format="%(refname)" --sort=-refname >actual && - test_cmp expected actual -' - -test_expect_success 'Give help even with invalid sort atoms' ' - test_expect_code 129 git for-each-ref --sort=bogus -h >actual 2>&1 && - grep "^usage: git for-each-ref" actual -' - -cat >expected <<\EOF -refs/tags/testtag -refs/tags/testtag-2 -EOF - -test_expect_success 'exercise patterns with prefixes' ' - git tag testtag-2 && - test_when_finished "git tag -d testtag-2" && - git for-each-ref --format="%(refname)" \ - refs/tags/testtag refs/tags/testtag-2 >actual && - test_cmp expected actual -' - -cat >expected <<\EOF -refs/tags/testtag -refs/tags/testtag-2 -EOF - -test_expect_success 'exercise glob patterns with prefixes' ' - git tag testtag-2 && - test_when_finished "git tag -d testtag-2" && - git for-each-ref --format="%(refname)" \ - refs/tags/testtag "refs/tags/testtag-*" >actual && - test_cmp expected actual -' - -cat >expected <<\EOF -refs/tags/bar -refs/tags/baz -refs/tags/testtag -EOF - -test_expect_success 'exercise patterns with prefix exclusions' ' - for tag in foo/one foo/two foo/three bar baz - do - git tag "$tag" || return 1 - done && - test_when_finished "git tag -d foo/one foo/two foo/three bar baz" && - git for-each-ref --format="%(refname)" \ - refs/tags/ --exclude=refs/tags/foo >actual && - test_cmp expected actual -' - -cat >expected <<\EOF -refs/tags/bar -refs/tags/baz -refs/tags/foo/one -refs/tags/testtag -EOF - -test_expect_success 'exercise patterns with pattern exclusions' ' - for tag in foo/one foo/two foo/three bar baz - do - git tag "$tag" || return 1 - done && - test_when_finished "git tag -d foo/one foo/two foo/three bar baz" && - git for-each-ref --format="%(refname)" \ - refs/tags/ --exclude="refs/tags/foo/t*" >actual && - test_cmp expected actual -' - -cat >expected <<\EOF -'refs/heads/main' -'refs/remotes/origin/main' -'refs/tags/testtag' -EOF - -test_expect_success 'Quoting style: shell' ' - git for-each-ref --shell --format="%(refname)" >actual && - test_cmp expected actual -' - -test_expect_success 'Quoting style: perl' ' - git for-each-ref --perl --format="%(refname)" >actual && - test_cmp expected actual -' - -test_expect_success 'Quoting style: python' ' - git for-each-ref --python --format="%(refname)" >actual && - test_cmp expected actual -' - -cat >expected <<\EOF -"refs/heads/main" -"refs/remotes/origin/main" -"refs/tags/testtag" -EOF - -test_expect_success 'Quoting style: tcl' ' - git for-each-ref --tcl --format="%(refname)" >actual && - test_cmp expected actual -' - -for i in "--perl --shell" "-s --python" "--python --tcl" "--tcl --perl"; do - test_expect_success "more than one quoting style: $i" " - test_must_fail git for-each-ref $i 2>err && - grep '^error: more than one quoting style' err - " -done - -test_expect_success 'setup for upstream:track[short]' ' - test_commit two -' - -test_atom head upstream:track '[ahead 1]' -test_atom head upstream:trackshort '>' -test_atom head upstream:track,nobracket 'ahead 1' -test_atom head upstream:nobracket,track 'ahead 1' - -test_expect_success 'setup for push:track[short]' ' - test_commit third && - git update-ref refs/remotes/myfork/main main && - git reset main~1 -' - -test_atom head push:track '[behind 1]' -test_atom head push:trackshort '<' - -test_expect_success 'Check that :track[short] cannot be used with other atoms' ' - test_must_fail git for-each-ref --format="%(refname:track)" 2>/dev/null && - test_must_fail git for-each-ref --format="%(refname:trackshort)" 2>/dev/null -' - -test_expect_success 'Check that :track[short] works when upstream is invalid' ' - cat >expected <<-\EOF && - [gone] - - EOF - test_when_finished "git config branch.main.merge refs/heads/main" && - git config branch.main.merge refs/heads/does-not-exist && - git for-each-ref \ - --format="%(upstream:track)$LF%(upstream:trackshort)" \ - refs/heads >actual && - test_cmp expected actual -' - -test_expect_success 'Check for invalid refname format' ' - test_must_fail git for-each-ref --format="%(refname:INVALID)" -' - -test_expect_success 'set up color tests' ' - cat >expected.color <<-EOF && - $(git rev-parse --short refs/heads/main) <GREEN>main<RESET> - $(git rev-parse --short refs/remotes/myfork/main) <GREEN>myfork/main<RESET> - $(git rev-parse --short refs/remotes/origin/main) <GREEN>origin/main<RESET> - $(git rev-parse --short refs/tags/testtag) <GREEN>testtag<RESET> - $(git rev-parse --short refs/tags/third) <GREEN>third<RESET> - $(git rev-parse --short refs/tags/two) <GREEN>two<RESET> - EOF - sed "s/<[^>]*>//g" <expected.color >expected.bare && - color_format="%(objectname:short) %(color:green)%(refname:short)" -' - -test_expect_success TTY '%(color) shows color with a tty' ' - test_terminal git for-each-ref --format="$color_format" >actual.raw && - test_decode_color <actual.raw >actual && - test_cmp expected.color actual -' - -test_expect_success '%(color) does not show color without tty' ' - TERM=vt100 git for-each-ref --format="$color_format" >actual && - test_cmp expected.bare actual -' - -test_expect_success '--color can override tty check' ' - git for-each-ref --color --format="$color_format" >actual.raw && - test_decode_color <actual.raw >actual && - test_cmp expected.color actual -' - -test_expect_success 'color.ui=always does not override tty check' ' - git -c color.ui=always for-each-ref --format="$color_format" >actual && - test_cmp expected.bare actual -' - -test_expect_success 'setup for describe atom tests' ' - git init -b master describe-repo && - ( - cd describe-repo && - - test_commit --no-tag one && - git tag tagone && - - test_commit --no-tag two && - git tag -a -m "tag two" tagtwo - ) -' - -test_expect_success 'describe atom vs git describe' ' - ( - cd describe-repo && - - git for-each-ref --format="%(objectname)" \ - refs/tags/ >obj && - while read hash - do - if desc=$(git describe $hash) - then - : >expect-contains-good - else - : >expect-contains-bad - fi && - echo "$hash $desc" || return 1 - done <obj >expect && - test_path_exists expect-contains-good && - test_path_exists expect-contains-bad && - - git for-each-ref --format="%(objectname) %(describe)" \ - refs/tags/ >actual 2>err && - test_cmp expect actual && - test_must_be_empty err - ) -' - -test_expect_success 'describe:tags vs describe --tags' ' - ( - cd describe-repo && - git describe --tags >expect && - git for-each-ref --format="%(describe:tags)" \ - refs/heads/master >actual && - test_cmp expect actual - ) -' - -test_expect_success 'describe:abbrev=... vs describe --abbrev=...' ' - ( - cd describe-repo && - - # Case 1: We have commits between HEAD and the most - # recent tag reachable from it - test_commit --no-tag file && - git describe --abbrev=14 >expect && - git for-each-ref --format="%(describe:abbrev=14)" \ - refs/heads/master >actual && - test_cmp expect actual && - - # Make sure the hash used is at least 14 digits long - sed -e "s/^.*-g\([0-9a-f]*\)$/\1/" <actual >hexpart && - test 15 -le $(wc -c <hexpart) && - - # Case 2: We have a tag at HEAD, describe directly gives - # the name of the tag - git tag -a -m tagged tagname && - git describe --abbrev=14 >expect && - git for-each-ref --format="%(describe:abbrev=14)" \ - refs/heads/master >actual && - test_cmp expect actual && - test tagname = $(cat actual) - ) -' - -test_expect_success 'describe:match=... vs describe --match ...' ' - ( - cd describe-repo && - git tag -a -m "tag foo" tag-foo && - git describe --match "*-foo" >expect && - git for-each-ref --format="%(describe:match="*-foo")" \ - refs/heads/master >actual && - test_cmp expect actual - ) -' - -test_expect_success 'describe:exclude:... vs describe --exclude ...' ' - ( - cd describe-repo && - git tag -a -m "tag bar" tag-bar && - git describe --exclude "*-bar" >expect && - git for-each-ref --format="%(describe:exclude="*-bar")" \ - refs/heads/master >actual && - test_cmp expect actual - ) -' - -test_expect_success 'deref with describe atom' ' - ( - cd describe-repo && - cat >expect <<-\EOF && - - tagname - tagname - tagname - - tagtwo - EOF - git for-each-ref --format="%(*describe)" >actual && - test_cmp expect actual - ) -' - -test_expect_success 'err on bad describe atom arg' ' - ( - cd describe-repo && - - # The bad arg is the only arg passed to describe atom - cat >expect <<-\EOF && - fatal: unrecognized %(describe) argument: baz - EOF - test_must_fail git for-each-ref --format="%(describe:baz)" \ - refs/heads/master 2>actual && - test_cmp expect actual && - - # The bad arg is in the middle of the option string - # passed to the describe atom - cat >expect <<-\EOF && - fatal: unrecognized %(describe) argument: qux=1,abbrev=14 - EOF - test_must_fail git for-each-ref \ - --format="%(describe:tags,qux=1,abbrev=14)" \ - ref/heads/master 2>actual && - test_cmp expect actual - ) -' - -cat >expected <<\EOF -heads/main -tags/main -EOF - -test_expect_success 'Check ambiguous head and tag refs (strict)' ' - git config --bool core.warnambiguousrefs true && - git checkout -b newtag && - echo "Using $datestamp" > one && - git add one && - git commit -m "Branch" && - setdate_and_increment && - git tag -m "Tagging at $datestamp" main && - git for-each-ref --format "%(refname:short)" refs/heads/main refs/tags/main >actual && - test_cmp expected actual -' - -cat >expected <<\EOF -heads/main -main -EOF - -test_expect_success 'Check ambiguous head and tag refs (loose)' ' - git config --bool core.warnambiguousrefs false && - git for-each-ref --format "%(refname:short)" refs/heads/main refs/tags/main >actual && - test_cmp expected actual -' - -cat >expected <<\EOF -heads/ambiguous -ambiguous -EOF - -test_expect_success 'Check ambiguous head and tag refs II (loose)' ' - git checkout main && - git tag ambiguous testtag^0 && - git branch ambiguous testtag^0 && - git for-each-ref --format "%(refname:short)" refs/heads/ambiguous refs/tags/ambiguous >actual && - test_cmp expected actual -' - -test_expect_success 'create tag without tagger' ' - git tag -a -m "Broken tag" taggerless && - git tag -f taggerless $(git cat-file tag taggerless | - sed -e "/^tagger /d" | - git hash-object --literally --stdin -w -t tag) -' - -test_atom refs/tags/taggerless type 'commit' -test_atom refs/tags/taggerless tag 'taggerless' -test_atom refs/tags/taggerless tagger '' -test_atom refs/tags/taggerless taggername '' -test_atom refs/tags/taggerless taggeremail '' -test_atom refs/tags/taggerless taggeremail:trim '' -test_atom refs/tags/taggerless taggeremail:localpart '' -test_atom refs/tags/taggerless taggerdate '' -test_atom refs/tags/taggerless committer '' -test_atom refs/tags/taggerless committername '' -test_atom refs/tags/taggerless committeremail '' -test_atom refs/tags/taggerless committeremail:trim '' -test_atom refs/tags/taggerless committeremail:localpart '' -test_atom refs/tags/taggerless committerdate '' -test_atom refs/tags/taggerless subject 'Broken tag' - -test_expect_success 'an unusual tag with an incomplete line' ' - - git tag -m "bogo" bogo && - bogo=$(git cat-file tag bogo) && - bogo=$(printf "%s" "$bogo" | git mktag) && - git tag -f bogo "$bogo" && - git for-each-ref --format "%(body)" refs/tags/bogo - -' - -test_expect_success 'create tag with subject and body content' ' - cat >>msg <<-\EOF && - the subject line - - first body line - second body line - EOF - git tag -F msg subject-body -' -test_atom refs/tags/subject-body subject 'the subject line' -test_atom refs/tags/subject-body subject:sanitize 'the-subject-line' -test_atom refs/tags/subject-body body 'first body line -second body line -' -test_atom refs/tags/subject-body contents 'the subject line - -first body line -second body line -' - -test_expect_success 'create tag with multiline subject' ' - cat >msg <<-\EOF && - first subject line - second subject line - - first body line - second body line - EOF - git tag -F msg multiline -' -test_atom refs/tags/multiline subject 'first subject line second subject line' -test_atom refs/tags/multiline subject:sanitize 'first-subject-line-second-subject-line' -test_atom refs/tags/multiline contents:subject 'first subject line second subject line' -test_atom refs/tags/multiline body 'first body line -second body line -' -test_atom refs/tags/multiline contents:body 'first body line -second body line -' -test_atom refs/tags/multiline contents:signature '' -test_atom refs/tags/multiline contents 'first subject line -second subject line - -first body line -second body line -' - -test_expect_success GPG 'create signed tags' ' - git tag -s -m "" signed-empty && - git tag -s -m "subject line" signed-short && - cat >msg <<-\EOF && - subject line - - body contents - EOF - git tag -s -F msg signed-long -' - -sig='-----BEGIN PGP SIGNATURE----- ------END PGP SIGNATURE----- -' - -PREREQ=GPG -test_atom refs/tags/signed-empty subject '' -test_atom refs/tags/signed-empty subject:sanitize '' -test_atom refs/tags/signed-empty contents:subject '' -test_atom refs/tags/signed-empty body "$sig" -test_atom refs/tags/signed-empty contents:body '' -test_atom refs/tags/signed-empty contents:signature "$sig" -test_atom refs/tags/signed-empty contents "$sig" - -test_expect_success GPG 'basic atom: refs/tags/signed-empty raw' ' - git cat-file tag refs/tags/signed-empty >expected && - git for-each-ref --format="%(raw)" refs/tags/signed-empty >actual && - sanitize_pgp <expected >expected.clean && - echo >>expected.clean && - sanitize_pgp <actual >actual.clean && - test_cmp expected.clean actual.clean -' - -test_atom refs/tags/signed-short subject 'subject line' -test_atom refs/tags/signed-short subject:sanitize 'subject-line' -test_atom refs/tags/signed-short contents:subject 'subject line' -test_atom refs/tags/signed-short body "$sig" -test_atom refs/tags/signed-short contents:body '' -test_atom refs/tags/signed-short contents:signature "$sig" -test_atom refs/tags/signed-short contents "subject line -$sig" - -test_expect_success GPG 'basic atom: refs/tags/signed-short raw' ' - git cat-file tag refs/tags/signed-short >expected && - git for-each-ref --format="%(raw)" refs/tags/signed-short >actual && - sanitize_pgp <expected >expected.clean && - echo >>expected.clean && - sanitize_pgp <actual >actual.clean && - test_cmp expected.clean actual.clean -' - -test_atom refs/tags/signed-long subject 'subject line' -test_atom refs/tags/signed-long subject:sanitize 'subject-line' -test_atom refs/tags/signed-long contents:subject 'subject line' -test_atom refs/tags/signed-long body "body contents -$sig" -test_atom refs/tags/signed-long contents:body 'body contents -' -test_atom refs/tags/signed-long contents:signature "$sig" -test_atom refs/tags/signed-long contents "subject line - -body contents -$sig" - -test_expect_success GPG 'basic atom: refs/tags/signed-long raw' ' - git cat-file tag refs/tags/signed-long >expected && - git for-each-ref --format="%(raw)" refs/tags/signed-long >actual && - sanitize_pgp <expected >expected.clean && - echo >>expected.clean && - sanitize_pgp <actual >actual.clean && - test_cmp expected.clean actual.clean -' - -test_expect_success 'set up refs pointing to tree and blob' ' - git update-ref refs/mytrees/first refs/heads/main^{tree} && - git update-ref refs/myblobs/first refs/heads/main:one -' - -test_atom refs/mytrees/first subject "" -test_atom refs/mytrees/first contents:subject "" -test_atom refs/mytrees/first body "" -test_atom refs/mytrees/first contents:body "" -test_atom refs/mytrees/first contents:signature "" -test_atom refs/mytrees/first contents "" - -test_expect_success 'basic atom: refs/mytrees/first raw' ' - git cat-file tree refs/mytrees/first >expected && - echo >>expected && - git for-each-ref --format="%(raw)" refs/mytrees/first >actual && - test_cmp expected actual && - git cat-file -s refs/mytrees/first >expected && - git for-each-ref --format="%(raw:size)" refs/mytrees/first >actual && - test_cmp expected actual -' - -test_atom refs/myblobs/first subject "" -test_atom refs/myblobs/first contents:subject "" -test_atom refs/myblobs/first body "" -test_atom refs/myblobs/first contents:body "" -test_atom refs/myblobs/first contents:signature "" -test_atom refs/myblobs/first contents "" - -test_expect_success 'basic atom: refs/myblobs/first raw' ' - git cat-file blob refs/myblobs/first >expected && - echo >>expected && - git for-each-ref --format="%(raw)" refs/myblobs/first >actual && - test_cmp expected actual && - git cat-file -s refs/myblobs/first >expected && - git for-each-ref --format="%(raw:size)" refs/myblobs/first >actual && - test_cmp expected actual -' - -test_expect_success 'set up refs pointing to binary blob' ' - printf "a\0b\0c" >blob1 && - printf "a\0c\0b" >blob2 && - printf "\0a\0b\0c" >blob3 && - printf "abc" >blob4 && - printf "\0 \0 \0 " >blob5 && - printf "\0 \0a\0 " >blob6 && - printf " " >blob7 && - >blob8 && - obj=$(git hash-object -w blob1) && - git update-ref refs/myblobs/blob1 "$obj" && - obj=$(git hash-object -w blob2) && - git update-ref refs/myblobs/blob2 "$obj" && - obj=$(git hash-object -w blob3) && - git update-ref refs/myblobs/blob3 "$obj" && - obj=$(git hash-object -w blob4) && - git update-ref refs/myblobs/blob4 "$obj" && - obj=$(git hash-object -w blob5) && - git update-ref refs/myblobs/blob5 "$obj" && - obj=$(git hash-object -w blob6) && - git update-ref refs/myblobs/blob6 "$obj" && - obj=$(git hash-object -w blob7) && - git update-ref refs/myblobs/blob7 "$obj" && - obj=$(git hash-object -w blob8) && - git update-ref refs/myblobs/blob8 "$obj" -' - -test_expect_success 'Verify sorts with raw' ' - cat >expected <<-EOF && - refs/myblobs/blob8 - refs/myblobs/blob5 - refs/myblobs/blob6 - refs/myblobs/blob3 - refs/myblobs/blob7 - refs/mytrees/first - refs/myblobs/first - refs/myblobs/blob1 - refs/myblobs/blob2 - refs/myblobs/blob4 - refs/heads/main - EOF - git for-each-ref --format="%(refname)" --sort=raw \ - refs/heads/main refs/myblobs/ refs/mytrees/first >actual && - test_cmp expected actual -' - -test_expect_success 'Verify sorts with raw:size' ' - cat >expected <<-EOF && - refs/myblobs/blob8 - refs/myblobs/blob7 - refs/myblobs/blob4 - refs/myblobs/blob1 - refs/myblobs/blob2 - refs/myblobs/blob3 - refs/myblobs/blob5 - refs/myblobs/blob6 - refs/myblobs/first - refs/mytrees/first - refs/heads/main - EOF - git for-each-ref --format="%(refname)" --sort=raw:size \ - refs/heads/main refs/myblobs/ refs/mytrees/first >actual && - test_cmp expected actual -' - -test_expect_success 'validate raw atom with %(if:equals)' ' - cat >expected <<-EOF && - not equals - not equals - not equals - not equals - not equals - not equals - refs/myblobs/blob4 - not equals - not equals - not equals - not equals - not equals - EOF - git for-each-ref --format="%(if:equals=abc)%(raw)%(then)%(refname)%(else)not equals%(end)" \ - refs/myblobs/ refs/heads/ >actual && - test_cmp expected actual -' - -test_expect_success 'validate raw atom with %(if:notequals)' ' - cat >expected <<-EOF && - refs/heads/ambiguous - refs/heads/main - refs/heads/newtag - refs/myblobs/blob1 - refs/myblobs/blob2 - refs/myblobs/blob3 - equals - refs/myblobs/blob5 - refs/myblobs/blob6 - refs/myblobs/blob7 - refs/myblobs/blob8 - refs/myblobs/first - EOF - git for-each-ref --format="%(if:notequals=abc)%(raw)%(then)%(refname)%(else)equals%(end)" \ - refs/myblobs/ refs/heads/ >actual && - test_cmp expected actual -' - -test_expect_success 'empty raw refs with %(if)' ' - cat >expected <<-EOF && - refs/myblobs/blob1 not empty - refs/myblobs/blob2 not empty - refs/myblobs/blob3 not empty - refs/myblobs/blob4 not empty - refs/myblobs/blob5 not empty - refs/myblobs/blob6 not empty - refs/myblobs/blob7 empty - refs/myblobs/blob8 empty - refs/myblobs/first not empty - EOF - git for-each-ref --format="%(refname) %(if)%(raw)%(then)not empty%(else)empty%(end)" \ - refs/myblobs/ >actual && - test_cmp expected actual -' - -test_expect_success '%(raw) with --python must fail' ' - test_must_fail git for-each-ref --format="%(raw)" --python -' - -test_expect_success '%(raw) with --tcl must fail' ' - test_must_fail git for-each-ref --format="%(raw)" --tcl -' - -test_expect_success PERL_TEST_HELPERS '%(raw) with --perl' ' - git for-each-ref --format="\$name= %(raw); -print \"\$name\"" refs/myblobs/blob1 --perl | perl >actual && - cmp blob1 actual && - git for-each-ref --format="\$name= %(raw); -print \"\$name\"" refs/myblobs/blob3 --perl | perl >actual && - cmp blob3 actual && - git for-each-ref --format="\$name= %(raw); -print \"\$name\"" refs/myblobs/blob8 --perl | perl >actual && - cmp blob8 actual && - git for-each-ref --format="\$name= %(raw); -print \"\$name\"" refs/myblobs/first --perl | perl >actual && - cmp one actual && - git cat-file tree refs/mytrees/first > expected && - git for-each-ref --format="\$name= %(raw); -print \"\$name\"" refs/mytrees/first --perl | perl >actual && - cmp expected actual -' - -test_expect_success '%(raw) with --shell must fail' ' - test_must_fail git for-each-ref --format="%(raw)" --shell -' - -test_expect_success '%(raw) with --shell and --sort=raw must fail' ' - test_must_fail git for-each-ref --format="%(raw)" --sort=raw --shell -' - -test_expect_success '%(raw:size) with --shell' ' - git for-each-ref --format="%(raw:size)" | sed "s/^/$SQ/;s/$/$SQ/" >expect && - git for-each-ref --format="%(raw:size)" --shell >actual && - test_cmp expect actual -' - -test_expect_success 'for-each-ref --format compare with cat-file --batch' ' - git rev-parse refs/mytrees/first | git cat-file --batch >expected && - git for-each-ref --format="%(objectname) %(objecttype) %(objectsize) -%(raw)" refs/mytrees/first >actual && - test_cmp expected actual -' - -test_expect_success 'verify sorts with contents:size' ' - cat >expect <<-\EOF && - refs/heads/main - refs/heads/newtag - refs/heads/ambiguous - EOF - git for-each-ref --format="%(refname)" \ - --sort=contents:size refs/heads/ >actual && - test_cmp expect actual -' - -test_expect_success 'set up multiple-sort tags' ' - for when in 100000 200000 - do - for email in user1 user2 - do - for ref in ref1 ref2 - do - GIT_COMMITTER_DATE="@$when +0000" \ - GIT_COMMITTER_EMAIL="$email@example.com" \ - git tag -m "tag $ref-$when-$email" \ - multi-$ref-$when-$email || return 1 - done - done - done -' - -test_expect_success 'Verify sort with multiple keys' ' - cat >expected <<-\EOF && - 100000 <user1@example.com> refs/tags/multi-ref2-100000-user1 - 100000 <user1@example.com> refs/tags/multi-ref1-100000-user1 - 100000 <user2@example.com> refs/tags/multi-ref2-100000-user2 - 100000 <user2@example.com> refs/tags/multi-ref1-100000-user2 - 200000 <user1@example.com> refs/tags/multi-ref2-200000-user1 - 200000 <user1@example.com> refs/tags/multi-ref1-200000-user1 - 200000 <user2@example.com> refs/tags/multi-ref2-200000-user2 - 200000 <user2@example.com> refs/tags/multi-ref1-200000-user2 - EOF - git for-each-ref \ - --format="%(taggerdate:unix) %(taggeremail) %(refname)" \ - --sort=-refname \ - --sort=taggeremail \ - --sort=taggerdate \ - "refs/tags/multi-*" >actual && - test_cmp expected actual -' - -test_expect_success 'equivalent sorts fall back on refname' ' - cat >expected <<-\EOF && - 100000 <user1@example.com> refs/tags/multi-ref1-100000-user1 - 100000 <user2@example.com> refs/tags/multi-ref1-100000-user2 - 100000 <user1@example.com> refs/tags/multi-ref2-100000-user1 - 100000 <user2@example.com> refs/tags/multi-ref2-100000-user2 - 200000 <user1@example.com> refs/tags/multi-ref1-200000-user1 - 200000 <user2@example.com> refs/tags/multi-ref1-200000-user2 - 200000 <user1@example.com> refs/tags/multi-ref2-200000-user1 - 200000 <user2@example.com> refs/tags/multi-ref2-200000-user2 - EOF - git for-each-ref \ - --format="%(taggerdate:unix) %(taggeremail) %(refname)" \ - --sort=taggerdate \ - "refs/tags/multi-*" >actual && - test_cmp expected actual -' - -test_expect_success '--no-sort cancels the previous sort keys' ' - cat >expected <<-\EOF && - 100000 <user1@example.com> refs/tags/multi-ref1-100000-user1 - 100000 <user2@example.com> refs/tags/multi-ref1-100000-user2 - 100000 <user1@example.com> refs/tags/multi-ref2-100000-user1 - 100000 <user2@example.com> refs/tags/multi-ref2-100000-user2 - 200000 <user1@example.com> refs/tags/multi-ref1-200000-user1 - 200000 <user2@example.com> refs/tags/multi-ref1-200000-user2 - 200000 <user1@example.com> refs/tags/multi-ref2-200000-user1 - 200000 <user2@example.com> refs/tags/multi-ref2-200000-user2 - EOF - git for-each-ref \ - --format="%(taggerdate:unix) %(taggeremail) %(refname)" \ - --sort=-refname \ - --sort=taggeremail \ - --no-sort \ - --sort=taggerdate \ - "refs/tags/multi-*" >actual && - test_cmp expected actual -' - -test_expect_success '--no-sort without subsequent --sort prints expected refs' ' - cat >expected <<-\EOF && - refs/tags/multi-ref1-100000-user1 - refs/tags/multi-ref1-100000-user2 - refs/tags/multi-ref1-200000-user1 - refs/tags/multi-ref1-200000-user2 - refs/tags/multi-ref2-100000-user1 - refs/tags/multi-ref2-100000-user2 - refs/tags/multi-ref2-200000-user1 - refs/tags/multi-ref2-200000-user2 - EOF - - # Sort the results with `sort` for a consistent comparison against - # expected - git for-each-ref \ - --format="%(refname)" \ - --no-sort \ - "refs/tags/multi-*" | sort >actual && - test_cmp expected actual -' - -test_expect_success 'set up custom date sorting' ' - # Dates: - # - Wed Feb 07 2024 21:34:20 +0000 - # - Tue Dec 14 1999 00:05:22 +0000 - # - Fri Jun 04 2021 11:26:51 +0000 - # - Mon Jan 22 2007 16:44:01 GMT+0000 - i=1 && - for when in 1707341660 945129922 1622806011 1169484241 - do - GIT_COMMITTER_DATE="@$when +0000" \ - GIT_COMMITTER_EMAIL="user@example.com" \ - git tag -m "tag $when" custom-dates-$i && - i=$(($i+1)) || return 1 - done -' - -test_expect_success 'sort by date defaults to full timestamp' ' - cat >expected <<-\EOF && - 945129922 refs/tags/custom-dates-2 - 1169484241 refs/tags/custom-dates-4 - 1622806011 refs/tags/custom-dates-3 - 1707341660 refs/tags/custom-dates-1 - EOF - - git for-each-ref \ - --format="%(creatordate:unix) %(refname)" \ - --sort=creatordate \ - "refs/tags/custom-dates-*" >actual && - test_cmp expected actual -' - -test_expect_success 'sort by custom date format' ' - cat >expected <<-\EOF && - 00:05:22 refs/tags/custom-dates-2 - 11:26:51 refs/tags/custom-dates-3 - 16:44:01 refs/tags/custom-dates-4 - 21:34:20 refs/tags/custom-dates-1 - EOF - - git for-each-ref \ - --format="%(creatordate:format:%H:%M:%S) %(refname)" \ - --sort="creatordate:format:%H:%M:%S" \ - "refs/tags/custom-dates-*" >actual && - test_cmp expected actual -' - -test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' ' - test_when_finished "git checkout main" && - git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual && - sed -e "s/^\* / /" actual >expect && - git checkout --orphan orphaned-branch && - git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual && - test_cmp expect actual -' - -cat >trailers <<EOF -Reviewed-by: A U Thor <author@example.com> -Signed-off-by: A U Thor <author@example.com> -[ v2 updated patch description ] -Acked-by: A U Thor - <author@example.com> -EOF - -unfold () { - perl -0pe 's/\n\s+/ /g' -} - -test_expect_success 'set up trailers for next test' ' - echo "Some contents" > two && - git add two && - git commit -F - <<-EOF - trailers: this commit message has trailers - - Some message contents - - $(cat trailers) - EOF -' - -test_trailer_option () { - if test "$#" -eq 3 - then - prereq="$1" - shift - fi && - title=$1 option=$2 - cat >expect - test_expect_success $prereq "$title" ' - git for-each-ref --format="%($option)" refs/heads/main >actual && - test_cmp expect actual && - git for-each-ref --format="%(contents:$option)" refs/heads/main >actual && - test_cmp expect actual - ' -} - -test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) unfolds trailers' \ - 'trailers:unfold' <<-EOF - $(unfold <trailers) - - EOF - -test_trailer_option '%(trailers:only) shows only "key: value" trailers' \ - 'trailers:only' <<-EOF - $(grep -v patch.description <trailers) - - EOF - -test_trailer_option '%(trailers:only=no,only=true) shows only "key: value" trailers' \ - 'trailers:only=no,only=true' <<-EOF - $(grep -v patch.description <trailers) - - EOF - -test_trailer_option '%(trailers:only=yes) shows only "key: value" trailers' \ - 'trailers:only=yes' <<-EOF - $(grep -v patch.description <trailers) - - EOF - -test_trailer_option '%(trailers:only=no) shows all trailers' \ - 'trailers:only=no' <<-EOF - $(cat trailers) - - EOF - -test_trailer_option PERL_TEST_HELPERS '%(trailers:only) and %(trailers:unfold) work together' \ - 'trailers:only,unfold' <<-EOF - $(grep -v patch.description <trailers | unfold) - - EOF - -test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) and %(trailers:only) work together' \ - 'trailers:unfold,only' <<-EOF - $(grep -v patch.description <trailers | unfold) - - EOF - -test_trailer_option '%(trailers:key=foo) shows that trailer' \ - 'trailers:key=Signed-off-by' <<-EOF - Signed-off-by: A U Thor <author@example.com> - - EOF - -test_trailer_option '%(trailers:key=foo) is case insensitive' \ - 'trailers:key=SiGned-oFf-bY' <<-EOF - Signed-off-by: A U Thor <author@example.com> - - EOF - -test_trailer_option '%(trailers:key=foo:) trailing colon also works' \ - 'trailers:key=Signed-off-by:' <<-EOF - Signed-off-by: A U Thor <author@example.com> - - EOF - -test_trailer_option '%(trailers:key=foo) multiple keys' \ - 'trailers:key=Reviewed-by:,key=Signed-off-by' <<-EOF - Reviewed-by: A U Thor <author@example.com> - Signed-off-by: A U Thor <author@example.com> - - EOF - -test_trailer_option '%(trailers:key=nonexistent) becomes empty' \ - 'trailers:key=Shined-off-by:' <<-EOF - - EOF - -test_trailer_option '%(trailers:key=foo) handles multiple lines even if folded' \ - 'trailers:key=Acked-by' <<-EOF - $(grep -v patch.description <trailers | grep -v Signed-off-by | grep -v Reviewed-by) - - EOF - -test_trailer_option '%(trailers:key=foo,unfold) properly unfolds' \ - 'trailers:key=Signed-Off-by,unfold' <<-EOF - $(unfold <trailers | grep Signed-off-by) - - EOF - -test_trailer_option '%(trailers:key=foo,only=no) also includes nontrailer lines' \ - 'trailers:key=Signed-off-by,only=no' <<-EOF - Signed-off-by: A U Thor <author@example.com> - $(grep patch.description <trailers) - - EOF - -test_trailer_option '%(trailers:key=foo,valueonly) shows only value' \ - 'trailers:key=Signed-off-by,valueonly' <<-EOF - A U Thor <author@example.com> - - EOF - -test_trailer_option '%(trailers:separator) changes separator' \ - 'trailers:separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF - Reviewed-by: A U Thor <author@example.com>,Signed-off-by: A U Thor <author@example.com> - EOF - -test_trailer_option '%(trailers:key_value_separator) changes key-value separator' \ - 'trailers:key_value_separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF - Reviewed-by,A U Thor <author@example.com> - Signed-off-by,A U Thor <author@example.com> - - EOF - -test_trailer_option '%(trailers:separator,key_value_separator) changes both separators' \ - 'trailers:separator=%x2C,key_value_separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF - Reviewed-by,A U Thor <author@example.com>,Signed-off-by,A U Thor <author@example.com> - EOF - -test_expect_success 'multiple %(trailers) use their own options' ' - git tag -F - tag-with-trailers <<-\EOF && - body - - one: foo - one: bar - two: baz - two: qux - EOF - t1="%(trailers:key=one,key_value_separator=W,separator=X)" && - t2="%(trailers:key=two,key_value_separator=Y,separator=Z)" && - git for-each-ref --format="$t1%0a$t2" refs/tags/tag-with-trailers >actual && - cat >expect <<-\EOF && - oneWfooXoneWbar - twoYbazZtwoYqux - EOF - test_cmp expect actual -' - -test_failing_trailer_option () { - title=$1 option=$2 - cat >expect - test_expect_success "$title" ' - # error message cannot be checked under i18n - test_must_fail git for-each-ref --format="%($option)" refs/heads/main 2>actual && - test_cmp expect actual && - test_must_fail git for-each-ref --format="%(contents:$option)" refs/heads/main 2>actual && - test_cmp expect actual - ' -} - -test_failing_trailer_option '%(trailers) rejects unknown trailers arguments' \ - 'trailers:unsupported' <<-\EOF - fatal: unknown %(trailers) argument: unsupported - EOF - -test_failing_trailer_option '%(trailers:key) without value is error' \ - 'trailers:key' <<-\EOF - fatal: expected %(trailers:key=<value>) - EOF - -test_expect_success 'if arguments, %(contents:trailers) shows error if colon is missing' ' - cat >expect <<-EOF && - fatal: unrecognized %(contents) argument: trailersonly - EOF - test_must_fail git for-each-ref --format="%(contents:trailersonly)" 2>actual && - test_cmp expect actual -' - -test_expect_success 'basic atom: head contents:trailers' ' - git for-each-ref --format="%(contents:trailers)" refs/heads/main >actual && - sanitize_pgp <actual >actual.clean && - # git for-each-ref ends with a blank line - cat >expect <<-EOF && - $(cat trailers) - - EOF - test_cmp expect actual.clean -' - -test_expect_success 'basic atom: rest must fail' ' - test_must_fail git for-each-ref --format="%(rest)" refs/heads/main -' - -test_expect_success 'HEAD atom does not take arguments' ' - test_must_fail git for-each-ref --format="%(HEAD:foo)" 2>err && - echo "fatal: %(HEAD) does not take arguments" >expect && - test_cmp expect err -' - -test_expect_success 'subject atom rejects unknown arguments' ' - test_must_fail git for-each-ref --format="%(subject:foo)" 2>err && - echo "fatal: unrecognized %(subject) argument: foo" >expect && - test_cmp expect err -' - -test_expect_success 'refname atom rejects unknown arguments' ' - test_must_fail git for-each-ref --format="%(refname:foo)" 2>err && - echo "fatal: unrecognized %(refname) argument: foo" >expect && - test_cmp expect err -' - -test_expect_success 'trailer parsing not fooled by --- line' ' - git commit --allow-empty -F - <<-\EOF && - this is the subject - - This is the body. The message has a "---" line which would confuse a - message+patch parser. But here we know we have only a commit message, - so we get it right. - - trailer: wrong - --- - This is more body. - - trailer: right - EOF - - { - echo "trailer: right" && - echo - } >expect && - git for-each-ref --format="%(trailers)" refs/heads/main >actual && - test_cmp expect actual -' - -test_expect_success 'Add symbolic ref for the following tests' ' - git symbolic-ref refs/heads/sym refs/heads/main -' - -cat >expected <<EOF -refs/heads/main -EOF - -test_expect_success 'Verify usage of %(symref) atom' ' - git for-each-ref --format="%(symref)" refs/heads/sym >actual && - test_cmp expected actual -' - -cat >expected <<EOF -heads/main -EOF - -test_expect_success 'Verify usage of %(symref:short) atom' ' - git for-each-ref --format="%(symref:short)" refs/heads/sym >actual && - test_cmp expected actual -' - -cat >expected <<EOF -main -heads/main -EOF - -test_expect_success 'Verify usage of %(symref:lstrip) atom' ' - git for-each-ref --format="%(symref:lstrip=2)" refs/heads/sym > actual && - git for-each-ref --format="%(symref:lstrip=-2)" refs/heads/sym >> actual && - test_cmp expected actual && - - git for-each-ref --format="%(symref:strip=2)" refs/heads/sym > actual && - git for-each-ref --format="%(symref:strip=-2)" refs/heads/sym >> actual && - test_cmp expected actual -' - -cat >expected <<EOF -refs -refs/heads -EOF - -test_expect_success 'Verify usage of %(symref:rstrip) atom' ' - git for-each-ref --format="%(symref:rstrip=2)" refs/heads/sym > actual && - git for-each-ref --format="%(symref:rstrip=-2)" refs/heads/sym >> actual && - test_cmp expected actual -' - -test_expect_success ':remotename and :remoteref' ' - git init remote-tests && - ( - cd remote-tests && - test_commit initial && - git branch -M main && - git remote add from fifth.coffee:blub && - git config branch.main.remote from && - git config branch.main.merge refs/heads/stable && - git remote add to southridge.audio:repo && - git config remote.to.push "refs/heads/*:refs/heads/pushed/*" && - git config branch.main.pushRemote to && - for pair in "%(upstream)=refs/remotes/from/stable" \ - "%(upstream:remotename)=from" \ - "%(upstream:remoteref)=refs/heads/stable" \ - "%(push)=refs/remotes/to/pushed/main" \ - "%(push:remotename)=to" \ - "%(push:remoteref)=refs/heads/pushed/main" - do - echo "${pair#*=}" >expect && - git for-each-ref --format="${pair%=*}" \ - refs/heads/main >actual && - test_cmp expect actual || exit 1 - done && - git branch push-simple && - git config branch.push-simple.pushRemote from && - actual="$(git for-each-ref \ - --format="%(push:remotename),%(push:remoteref)" \ - refs/heads/push-simple)" && - test from, = "$actual" - ) -' - -test_expect_success 'for-each-ref --ignore-case ignores case' ' - git for-each-ref --format="%(refname)" refs/heads/MAIN >actual && - test_must_be_empty actual && - - echo refs/heads/main >expect && - git for-each-ref --format="%(refname)" --ignore-case \ - refs/heads/MAIN >actual && - test_cmp expect actual -' - -test_expect_success 'for-each-ref --omit-empty works' ' - git for-each-ref --format="%(refname)" >actual && - test_line_count -gt 1 actual && - git for-each-ref --format="%(if:equals=refs/heads/main)%(refname)%(then)%(refname)%(end)" --omit-empty >actual && - echo refs/heads/main >expect && - test_cmp expect actual -' - -test_expect_success 'for-each-ref --ignore-case works on multiple sort keys' ' - # name refs numerically to avoid case-insensitive filesystem conflicts - nr=0 && - for email in a A b B - do - for subject in a A b B - do - GIT_COMMITTER_EMAIL="$email@example.com" \ - git tag -m "tag $subject" icase-$(printf %02d $nr) && - nr=$((nr+1))|| - return 1 - done - done && - git for-each-ref --ignore-case \ - --format="%(taggeremail) %(subject) %(refname)" \ - --sort=refname \ - --sort=subject \ - --sort=taggeremail \ - refs/tags/icase-* >actual && - cat >expect <<-\EOF && - <a@example.com> tag a refs/tags/icase-00 - <a@example.com> tag A refs/tags/icase-01 - <A@example.com> tag a refs/tags/icase-04 - <A@example.com> tag A refs/tags/icase-05 - <a@example.com> tag b refs/tags/icase-02 - <a@example.com> tag B refs/tags/icase-03 - <A@example.com> tag b refs/tags/icase-06 - <A@example.com> tag B refs/tags/icase-07 - <b@example.com> tag a refs/tags/icase-08 - <b@example.com> tag A refs/tags/icase-09 - <B@example.com> tag a refs/tags/icase-12 - <B@example.com> tag A refs/tags/icase-13 - <b@example.com> tag b refs/tags/icase-10 - <b@example.com> tag B refs/tags/icase-11 - <B@example.com> tag b refs/tags/icase-14 - <B@example.com> tag B refs/tags/icase-15 - EOF - test_cmp expect actual -' - -test_expect_success 'for-each-ref reports broken tags' ' - git tag -m "good tag" broken-tag-good HEAD && - git cat-file tag broken-tag-good >good && - sed s/commit/blob/ <good >bad && - bad=$(git hash-object -w -t tag bad) && - git update-ref refs/tags/broken-tag-bad $bad && - test_must_fail git for-each-ref --format="%(*objectname)" \ - refs/tags/broken-tag-* -' - -test_expect_success 'set up tag with signature and no blank lines' ' - git tag -F - fake-sig-no-blanks <<-\EOF - this is the subject - -----BEGIN PGP SIGNATURE----- - not a real signature, but we just care about the - subject/body parsing. It is important here that - there are no blank lines in the signature. - -----END PGP SIGNATURE----- - EOF -' - -test_atom refs/tags/fake-sig-no-blanks contents:subject 'this is the subject' -test_atom refs/tags/fake-sig-no-blanks contents:body '' -test_atom refs/tags/fake-sig-no-blanks contents:signature "$sig" - -test_expect_success 'set up tag with CRLF signature' ' - append_cr <<-\EOF | - this is the subject - -----BEGIN PGP SIGNATURE----- - - not a real signature, but we just care about - the subject/body parsing. It is important here - that there is a blank line separating this - from the signature header. - -----END PGP SIGNATURE----- - EOF - git tag -F - --cleanup=verbatim fake-sig-crlf -' - -test_atom refs/tags/fake-sig-crlf contents:subject 'this is the subject' -test_atom refs/tags/fake-sig-crlf contents:body '' - -# CRLF is retained in the signature, so we have to pass our expected value -# through append_cr. But test_atom requires a shell string, which means command -# substitution, and the shell will strip trailing newlines from the output of -# the substitution. Hack around it by adding and then removing a dummy line. -sig_crlf="$(printf "%s" "$sig" | append_cr; echo dummy)" -sig_crlf=${sig_crlf%dummy} -test_atom refs/tags/fake-sig-crlf contents:signature "$sig_crlf" - -test_expect_success 'set up tag with signature and trailers' ' - git tag -F - fake-sig-trailer <<-\EOF - this is the subject - - this is the body - - My-Trailer: foo - -----BEGIN PGP SIGNATURE----- - - not a real signature, but we just care about the - subject/body/trailer parsing. - -----END PGP SIGNATURE----- - EOF -' - -# use "separator=" here to suppress the terminating newline -test_atom refs/tags/fake-sig-trailer trailers:separator= 'My-Trailer: foo' - -test_expect_success 'git for-each-ref --stdin: empty' ' - >in && - git for-each-ref --format="%(refname)" --stdin <in >actual && - git for-each-ref --format="%(refname)" >expect && - test_cmp expect actual -' - -test_expect_success 'git for-each-ref --stdin: fails if extra args' ' - >in && - test_must_fail git for-each-ref --format="%(refname)" \ - --stdin refs/heads/extra <in 2>err && - grep "unknown arguments supplied with --stdin" err -' - -test_expect_success 'git for-each-ref --stdin: matches' ' - cat >in <<-EOF && - refs/tags/multi* - refs/heads/amb* - EOF - - cat >expect <<-EOF && - refs/heads/ambiguous - refs/tags/multi-ref1-100000-user1 - refs/tags/multi-ref1-100000-user2 - refs/tags/multi-ref1-200000-user1 - refs/tags/multi-ref1-200000-user2 - refs/tags/multi-ref2-100000-user1 - refs/tags/multi-ref2-100000-user2 - refs/tags/multi-ref2-200000-user1 - refs/tags/multi-ref2-200000-user2 - refs/tags/multiline - EOF - - git for-each-ref --format="%(refname)" --stdin <in >actual && - test_cmp expect actual -' - -test_expect_success 'git for-each-ref with non-existing refs' ' - cat >in <<-EOF && - refs/heads/this-ref-does-not-exist - refs/tags/bogus - EOF - - git for-each-ref --format="%(refname)" --stdin <in >actual && - test_must_be_empty actual && - - xargs git for-each-ref --format="%(refname)" <in >actual && - test_must_be_empty actual -' - -test_expect_success 'git for-each-ref with nested tags' ' - git tag -am "Normal tag" nested/base HEAD && - git tag -am "Nested tag" nested/nest1 refs/tags/nested/base && - git tag -am "Double nested tag" nested/nest2 refs/tags/nested/nest1 && - - head_oid="$(git rev-parse HEAD)" && - base_tag_oid="$(git rev-parse refs/tags/nested/base)" && - nest1_tag_oid="$(git rev-parse refs/tags/nested/nest1)" && - nest2_tag_oid="$(git rev-parse refs/tags/nested/nest2)" && - - cat >expect <<-EOF && - refs/tags/nested/base $base_tag_oid tag $head_oid commit - refs/tags/nested/nest1 $nest1_tag_oid tag $head_oid commit - refs/tags/nested/nest2 $nest2_tag_oid tag $head_oid commit - EOF - - git for-each-ref \ - --format="%(refname) %(objectname) %(objecttype) %(*objectname) %(*objecttype)" \ - refs/tags/nested/ >actual && - test_cmp expect actual -' - -test_expect_success 'is-base atom with non-commits' ' - git for-each-ref --format="%(is-base:HEAD) %(refname)" >out 2>err && - grep "(HEAD) refs/heads/main" out && - - test_line_count = 2 err && - grep "error: object .* is a commit, not a blob" err && - grep "error: bad tag pointer to" err -' - -GRADE_FORMAT="%(signature:grade)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)" -TRUSTLEVEL_FORMAT="%(signature:trustlevel)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)" - -test_expect_success GPG 'setup for signature atom using gpg' ' - git checkout -b signed && - - test_when_finished "test_unconfig commit.gpgSign" && - - echo "1" >file && - git add file && - test_tick && - git commit -S -m "file: 1" && - git tag first-signed && - - echo "2" >file && - test_tick && - git commit -a -m "file: 2" && - git tag second-unsigned && - - git config commit.gpgSign 1 && - echo "3" >file && - test_tick && - git commit -a --no-gpg-sign -m "file: 3" && - git tag third-unsigned && - - test_tick && - git rebase -f HEAD^^ && git tag second-signed HEAD^ && - git tag third-signed && - - echo "4" >file && - test_tick && - git commit -a -SB7227189 -m "file: 4" && - git tag fourth-signed && - - echo "5" >file && - test_tick && - git commit -a --no-gpg-sign -m "file: 5" && - git tag fifth-unsigned && - - echo "6" >file && - test_tick && - git commit -a --no-gpg-sign -m "file: 6" && - - test_tick && - git rebase -f HEAD^^ && - git tag fifth-signed HEAD^ && - git tag sixth-signed && - - echo "7" >file && - test_tick && - git commit -a --no-gpg-sign -m "file: 7" && - git tag seventh-unsigned -' - -test_expect_success GPGSSH 'setup for signature atom using ssh' ' - test_when_finished "test_unconfig gpg.format user.signingkey" && - - test_config gpg.format ssh && - test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" && - echo "8" >file && - test_tick && - git add file && - git commit -S -m "file: 8" && - git tag eighth-signed-ssh -' - -test_expect_success GPG2 'bare signature atom' ' - git verify-commit first-signed 2>expect && - echo >>expect && - git for-each-ref refs/tags/first-signed \ - --format="%(signature)" >actual && - test_cmp expect actual -' - -test_expect_success GPG 'show good signature with custom format' ' - git verify-commit first-signed && - cat >expect <<-\EOF && - G - 13B6F51ECDDE430D - C O Mitter <committer@example.com> - 73D758744BE721698EC54E8713B6F51ECDDE430D - 73D758744BE721698EC54E8713B6F51ECDDE430D - EOF - git for-each-ref refs/tags/first-signed \ - --format="$GRADE_FORMAT" >actual && - test_cmp expect actual -' -test_expect_success GPGSSH 'show good signature with custom format with ssh' ' - test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" && - FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") && - cat >expect.tmpl <<-\EOF && - G - FINGERPRINT - principal with number 1 - FINGERPRINT - - EOF - sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect && - git for-each-ref refs/tags/eighth-signed-ssh \ - --format="$GRADE_FORMAT" >actual && - test_cmp expect actual -' - -test_expect_success GPG 'signature atom with grade option and bad signature' ' - git cat-file commit third-signed >raw && - sed -e "s/^file: 3/file: 3 forged/" raw >forged1 && - FORGED1=$(git hash-object -w -t commit forged1) && - git update-ref refs/tags/third-signed "$FORGED1" && - test_must_fail git verify-commit "$FORGED1" && - - cat >expect <<-\EOF && - B - 13B6F51ECDDE430D - C O Mitter <committer@example.com> - - - EOF - git for-each-ref refs/tags/third-signed \ - --format="$GRADE_FORMAT" >actual && - test_cmp expect actual -' - -test_expect_success GPG 'show untrusted signature with custom format' ' - cat >expect <<-\EOF && - U - 65A0EEA02E30CAD7 - Eris Discordia <discord@example.net> - F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7 - D4BE22311AD3131E5EDA29A461092E85B7227189 - EOF - git for-each-ref refs/tags/fourth-signed \ - --format="$GRADE_FORMAT" >actual && - test_cmp expect actual -' - -test_expect_success GPG 'show untrusted signature with undefined trust level' ' - cat >expect <<-\EOF && - undefined - 65A0EEA02E30CAD7 - Eris Discordia <discord@example.net> - F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7 - D4BE22311AD3131E5EDA29A461092E85B7227189 - EOF - git for-each-ref refs/tags/fourth-signed \ - --format="$TRUSTLEVEL_FORMAT" >actual && - test_cmp expect actual -' - -test_expect_success GPG 'show untrusted signature with ultimate trust level' ' - cat >expect <<-\EOF && - ultimate - 13B6F51ECDDE430D - C O Mitter <committer@example.com> - 73D758744BE721698EC54E8713B6F51ECDDE430D - 73D758744BE721698EC54E8713B6F51ECDDE430D - EOF - git for-each-ref refs/tags/sixth-signed \ - --format="$TRUSTLEVEL_FORMAT" >actual && - test_cmp expect actual -' - -test_expect_success GPG 'show unknown signature with custom format' ' - cat >expect <<-\EOF && - E - 13B6F51ECDDE430D - - - - EOF - GNUPGHOME="$GNUPGHOME_NOT_USED" git for-each-ref \ - refs/tags/sixth-signed --format="$GRADE_FORMAT" >actual && - test_cmp expect actual -' - -test_expect_success GPG 'show lack of signature with custom format' ' - cat >expect <<-\EOF && - N - - - - - EOF - git for-each-ref refs/tags/seventh-unsigned \ - --format="$GRADE_FORMAT" >actual && - test_cmp expect actual -' +. "$TEST_DIRECTORY"/for-each-ref-tests.sh test_done diff --git a/t/t6423-merge-rename-directories.sh b/t/t6423-merge-rename-directories.sh index f48ed6d035..533ac85dc8 100755 --- a/t/t6423-merge-rename-directories.sh +++ b/t/t6423-merge-rename-directories.sh @@ -4731,7 +4731,7 @@ test_setup_12i () { mkdir -p source/subdir && echo foo >source/subdir/foo && - echo bar >source/bar && + printf "%d\n" 1 2 3 4 5 6 7 >source/bar && echo baz >source/baz && git add source && git commit -m orig && @@ -4747,6 +4747,7 @@ test_setup_12i () { git switch B && git mv source/bar source/subdir/bar && echo more baz >>source/baz && + git add source/baz && git commit -m B ) } @@ -4758,6 +4759,88 @@ test_expect_success '12i: Directory rename causes rename-to-self' ' git checkout A^0 && + # NOTE: A potentially better resolution would be for + # source/bar -> source/subdir/bar + # to use the directory rename to become + # source/bar -> source/bar + # (a rename to self), and thus we end up with bar with + # a path conflict (given merge.directoryRenames=conflict). + # However, since the relevant renames optimization + # prevents us from noticing + # source/bar -> source/subdir/bar + # as a rename and looking at it just as + # delete source/bar + # add source/subdir/bar + # the directory rename of source/subdir/bar -> source/bar does + # not look like a rename-to-self situation but a + # rename-on-top-of-other-file situation. We do not want + # stage 1 entries from an unrelated file, so we expect an + # error about there being a file in the way. + + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 >out && + + grep "CONFLICT (implicit dir rename).*source/bar in the way" out && + test_path_is_missing source/bar && + test_path_is_file source/subdir/bar && + test_path_is_file source/baz && + + git ls-files >actual && + uniq <actual >tracked && + test_line_count = 3 tracked && + + git status --porcelain -uno >actual && + cat >expect <<-\EOF && + M source/baz + R source/bar -> source/subdir/bar + EOF + test_cmp expect actual + ) +' + +# Testcase 12i2, Identical to 12i except that source/subdir/bar modified on unrenamed side +# Commit O: source/{subdir/foo, bar, baz_1} +# Commit A: source/{foo, bar_2, baz_1} +# Commit B: source/{subdir/{foo, bar}, baz_2} +# Expected: source/{foo, bar, baz_2}, with conflicts on +# source/bar vs. source/subdir/bar + +test_setup_12i2 () { + git init 12i2 && + ( + cd 12i2 && + + mkdir -p source/subdir && + echo foo >source/subdir/foo && + printf "%d\n" 1 2 3 4 5 6 7 >source/bar && + echo baz >source/baz && + git add source && + git commit -m orig && + + git branch O && + git branch A && + git branch B && + + git switch A && + git mv source/subdir/foo source/foo && + echo 8 >> source/bar && + git add source/bar && + git commit -m A && + + git switch B && + git mv source/bar source/subdir/bar && + echo more baz >>source/baz && + git add source/baz && + git commit -m B + ) +} + +test_expect_success '12i2: Directory rename causes rename-to-self' ' + test_setup_12i2 && + ( + cd 12i2 && + + git checkout A^0 && + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 && test_path_is_missing source/subdir && @@ -4771,7 +4854,7 @@ test_expect_success '12i: Directory rename causes rename-to-self' ' git status --porcelain -uno >actual && cat >expect <<-\EOF && UU source/bar - M source/baz + M source/baz EOF test_cmp expect actual ) @@ -4806,6 +4889,7 @@ test_setup_12j () { git switch B && git mv bar subdir/bar && echo more baz >>baz && + git add baz && git commit -m B ) } @@ -4817,10 +4901,29 @@ test_expect_success '12j: Directory rename to root causes rename-to-self' ' git checkout A^0 && - test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 && - - test_path_is_missing subdir && - test_path_is_file bar && + # NOTE: A potentially better resolution would be for + # bar -> subdir/bar + # to use the directory rename to become + # bar -> bar + # (a rename to self), and thus we end up with bar with + # a path conflict (given merge.directoryRenames=conflict). + # However, since the relevant renames optimization + # prevents us from noticing + # bar -> subdir/bar + # as a rename and looking at it just as + # delete bar + # add subdir/bar + # the directory rename of subdir/bar -> bar does not look + # like a rename-to-self situation but a + # rename-on-top-of-other-file situation. We do not want + # stage 1 entries from an unrelated file, so we expect an + # error about there being a file in the way. + + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 >out && + grep "CONFLICT (implicit dir rename).*bar in the way" out && + + test_path_is_missing bar && + test_path_is_file subdir/bar && test_path_is_file baz && git ls-files >actual && @@ -4829,8 +4932,8 @@ test_expect_success '12j: Directory rename to root causes rename-to-self' ' git status --porcelain -uno >actual && cat >expect <<-\EOF && - UU bar - M baz + M baz + R bar -> subdir/bar EOF test_cmp expect actual ) @@ -4865,6 +4968,7 @@ test_setup_12k () { git switch B && git mv dirA/bar dirB/bar && echo more baz >>dirA/baz && + git add dirA/baz && git commit -m B ) } @@ -4876,10 +4980,29 @@ test_expect_success '12k: Directory rename with sibling causes rename-to-self' ' git checkout A^0 && - test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 && - - test_path_is_missing dirB && - test_path_is_file dirA/bar && + # NOTE: A potentially better resolution would be for + # dirA/bar -> dirB/bar + # to use the directory rename (dirB/ -> dirA/) to become + # dirA/bar -> dirA/bar + # (a rename to self), and thus we end up with bar with + # a path conflict (given merge.directoryRenames=conflict). + # However, since the relevant renames optimization + # prevents us from noticing + # dirA/bar -> dirB/bar + # as a rename and looking at it just as + # delete dirA/bar + # add dirB/bar + # the directory rename of dirA/bar -> dirB/bar does + # not look like a rename-to-self situation but a + # rename-on-top-of-other-file situation. We do not want + # stage 1 entries from an unrelated file, so we expect an + # error about there being a file in the way. + + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 >out && + grep "CONFLICT (implicit dir rename).*dirA/bar in the way" out && + + test_path_is_missing dirA/bar && + test_path_is_file dirB/bar && test_path_is_file dirA/baz && git ls-files >actual && @@ -4888,8 +5011,8 @@ test_expect_success '12k: Directory rename with sibling causes rename-to-self' ' git status --porcelain -uno >actual && cat >expect <<-\EOF && - UU dirA/bar - M dirA/baz + M dirA/baz + R dirA/bar -> dirB/bar EOF test_cmp expect actual ) @@ -5056,6 +5179,25 @@ test_expect_success '12m: Change parent of renamed-dir to symlink on other side' ) ' +# Testcase 12n, Directory rename transitively makes rename back to self +# +# (Since this is a cherry-pick instead of merge, the labels are a bit weird. +# O, the original commit, is A~1 rather than what branch O points to.) +# +# Commit O: tools/hello +# world +# Commit A: tools/hello +# tools/world +# Commit B: hello +# In words: +# A: world -> tools/world +# B: tools/ -> /, i.e. rename all of tools to toplevel directory +# delete world +# +# Expected: +# CONFLICT (file location): tools/world vs. world +# + test_setup_12n () { git init 12n && ( @@ -5092,10 +5234,357 @@ test_expect_success '12n: Directory rename transitively makes rename back to sel git checkout -q B^0 && test_must_fail git cherry-pick A^0 >out && - grep "CONFLICT (file location).*should perhaps be moved" out + test_grep "CONFLICT (file location).*should perhaps be moved" out && + + # Should have 1 entry for hello, and 2 for world + test_stdout_line_count = 3 git ls-files -s && + test_stdout_line_count = 1 git ls-files -s hello && + test_stdout_line_count = 2 git ls-files -s world + ) +' + +# Testcase 12n2, Directory rename transitively makes rename back to self +# +# Commit O: tools/hello +# world +# Commit A: tools/hello +# tools/world +# Commit B: hello +# In words: +# A: world -> tools/world +# B: tools/ -> /, i.e. rename all of tools to toplevel directory +# delete world +# +# Expected: +# CONFLICT (file location): tools/world vs. world +# + +test_setup_12n2 () { + git init 12n2 && + ( + cd 12n2 && + + mkdir tools && + echo hello >tools/hello && + git add tools/hello && + echo world >world && + git add world && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git switch A && + git mv world tools/world && + git commit -m "Move world into tools/" && + + git switch B && + git mv tools/hello hello && + git rm world && + git commit -m "Move hello from tools/ to toplevel" + ) +} + +test_expect_success '12n2: Directory rename transitively makes rename back to self' ' + test_setup_12n2 && + ( + cd 12n2 && + + git checkout -q B^0 && + + test_might_fail git -c merge.directoryRenames=true merge A^0 >out && + + # Should have 1 entry for hello, and either 0 or 2 for world + # + # NOTE: Since merge.directoryRenames=true, there is no path + # conflict for world vs. tools/world; it should end up at + # world. The fact that world was unmodified on side A, means + # there was no content conflict; we should just take the + # content from side B -- i.e. delete the file. So merging + # could just delete world. + # + # However, rename-to-self-via-directory-rename is a bit more + # challenging. Relax this test to allow world to be treated + # as a modify/delete conflict as well, meaning it will have + # two higher order stages, that just so happen to match. + # + test_stdout_line_count = 1 git ls-files -s hello && + test_stdout_line_count = 2 git ls-files -s world && + test_grep "CONFLICT (modify/delete).*world deleted in HEAD" out ) ' +# Testcase 12o, Directory rename hits other rename source; file still in way on same side +# Commit O: A/file1_1 +# A/stuff +# B/file1_2 +# B/stuff +# C/other +# Commit A: A/file1_1 +# A/stuff +# B/stuff +# C/file1_2 +# C/other +# Commit B: D/file2_1 +# A/stuff +# B/file1_2 +# B/stuff +# A/other +# In words: +# A: rename B/file1_2 -> C/file1_2 +# B: rename C/ -> A/ +# rename A/file1_1 -> D/file2_1 +# Rationale: +# A/stuff is unmodified, it shows up in final output +# B/stuff is unmodified, it shows up in final output +# C/other touched on one side (rename to A), so A/other shows up in output +# A/file1 is renamed to D/file2 +# B/file1 -> C/file1 and even though C/ -> A/, A/file1 is +# "in the way" so we don't do the directory rename +# Expected: A/stuff +# B/stuff +# A/other +# D/file2 +# C/file1 +# + CONFLICT (implicit dir rename): A/file1 in way of C/file1 +# + +test_setup_12o () { + git init 12o && + ( + cd 12o && + + mkdir -p A B C && + echo 1 >A/file1 && + echo 2 >B/file1 && + echo other >C/other && + echo Astuff >A/stuff && + echo Bstuff >B/stuff && + git add . && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git switch A && + git mv B/file1 C/ && + git add . && + git commit -m "A" && + + git switch B && + mkdir -p D && + git mv A/file1 D/file2 && + git mv C/other A/other && + git add . && + git commit -m "B" + ) +} + +test_expect_success '12o: Directory rename hits other rename source; file still in way on same side' ' + test_setup_12o && + ( + cd 12o && + + git checkout -q A^0 && + + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 >out && + + test_stdout_line_count = 5 git ls-files -s && + test_stdout_line_count = 1 git ls-files -s A/other && + test_stdout_line_count = 1 git ls-files -s A/stuff && + test_stdout_line_count = 1 git ls-files -s B/stuff && + test_stdout_line_count = 1 git ls-files -s D/file2 && + + grep "CONFLICT (implicit dir rename).*Existing file/dir at A/file1 in the way" out && + test_stdout_line_count = 1 git ls-files -s C/file1 + ) +' + +# Testcase 12p, Directory rename hits other rename source; file still in way on other side +# Commit O: A/file1_1 +# A/stuff +# B/file1_2 +# B/stuff +# C/other +# Commit A: D/file2_1 +# A/stuff +# B/stuff +# C/file1_2 +# C/other +# Commit B: A/file1_1 +# A/stuff +# B/file1_2 +# B/stuff +# A/other +# Short version: +# A: rename A/file1_1 -> D/file2_1 +# rename B/file1_2 -> C/file1_2 +# B: Rename C/ -> A/ +# Rationale: +# A/stuff is unmodified, it shows up in final output +# B/stuff is unmodified, it shows up in final output +# C/other touched on one side (rename to A), so A/other shows up in output +# A/file1 is renamed to D/file2 +# B/file1 -> C/file1 and even though C/ -> A/, A/file1 is +# "in the way" so we don't do the directory rename +# Expected: A/stuff +# B/stuff +# A/other +# D/file2 +# C/file1 +# + CONFLICT (implicit dir rename): A/file1 in way of C/file1 +# + +test_setup_12p () { + git init 12p && + ( + cd 12p && + + mkdir -p A B C && + echo 1 >A/file1 && + echo 2 >B/file1 && + echo other >C/other && + echo Astuff >A/stuff && + echo Bstuff >B/stuff && + git add . && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git switch A && + mkdir -p D && + git mv A/file1 D/file2 && + git mv B/file1 C/ && + git add . && + git commit -m "A" && + + git switch B && + git mv C/other A/other && + git add . && + git commit -m "B" + ) +} + +test_expect_success '12p: Directory rename hits other rename source; file still in way on other side' ' + test_setup_12p && + ( + cd 12p && + + git checkout -q A^0 && + + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 >out && + + test_stdout_line_count = 5 git ls-files -s && + test_stdout_line_count = 1 git ls-files -s A/other && + test_stdout_line_count = 1 git ls-files -s A/stuff && + test_stdout_line_count = 1 git ls-files -s B/stuff && + test_stdout_line_count = 1 git ls-files -s D/file2 && + + grep "CONFLICT (implicit dir rename).*Existing file/dir at A/file1 in the way" out && + test_stdout_line_count = 1 git ls-files -s C/file1 + ) +' + +# Testcase 12q, Directory rename hits other rename source; file removed though +# Commit O: A/file1_1 +# A/stuff +# B/file1_2 +# B/stuff +# C/other +# Commit A: A/stuff +# B/stuff +# C/file1_2 +# C/other +# Commit B: D/file2_1 +# A/stuff +# B/file1_2 +# B/stuff +# A/other +# In words: +# A: delete A/file1_1, rename B/file1_2 -> C/file1_2 +# B: Rename C/ -> A/, rename A/file1_1 -> D/file2_1 +# Rationale: +# A/stuff is unmodified, it shows up in final output +# B/stuff is unmodified, it shows up in final output +# C/other touched on one side (rename to A), so A/other shows up in output +# A/file1 is rename/delete to D/file2, so two stages for D/file2 +# B/file1 -> C/file1 and even though C/ -> A/, A/file1 as a source was +# "in the way" (ish) so we don't do the directory rename +# Expected: A/stuff +# B/stuff +# A/other +# D/file2 (two stages) +# C/file1 +# + CONFLICT (implicit dir rename): A/file1 in way of C/file1 +# + CONFLICT (rename/delete): D/file2 +# + +test_setup_12q () { + git init 12q && + ( + cd 12q && + + mkdir -p A B C && + echo 1 >A/file1 && + echo 2 >B/file1 && + echo other >C/other && + echo Astuff >A/stuff && + echo Bstuff >B/stuff && + git add . && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git switch A && + git rm A/file1 && + git mv B/file1 C/ && + git add . && + git commit -m "A" && + + git switch B && + mkdir -p D && + git mv A/file1 D/file2 && + git mv C/other A/other && + git add . && + git commit -m "B" + ) +} + +test_expect_success '12q: Directory rename hits other rename source; file removed though' ' + test_setup_12q && + ( + cd 12q && + + git checkout -q A^0 && + + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 >out && + + grep "CONFLICT (rename/delete).*A/file1.*D/file2" out && + grep "CONFLICT (implicit dir rename).*Existing file/dir at A/file1 in the way" out && + + test_stdout_line_count = 6 git ls-files -s && + test_stdout_line_count = 1 git ls-files -s A/other && + test_stdout_line_count = 1 git ls-files -s A/stuff && + test_stdout_line_count = 1 git ls-files -s B/stuff && + test_stdout_line_count = 2 git ls-files -s D/file2 && + + # This is a slightly suboptimal resolution; allowing the + # rename of C/ -> A/ to affect C/file1 and further rename + # it to A/file1 would probably be preferable, but since + # A/file1 existed as the source of another rename, allowing + # the dir rename of C/file1 -> A/file1 would mean modifying + # the code so that renames do not adjust both their source + # and target paths in all cases. + ! grep "CONFLICT (file location)" out && + test_stdout_line_count = 1 git ls-files -s C/file1 + ) +' ########################################################################### # SECTION 13: Checking informational and conflict messages diff --git a/t/t7005-editor.sh b/t/t7005-editor.sh index 5fcf281dfb..c490e5707a 100755 --- a/t/t7005-editor.sh +++ b/t/t7005-editor.sh @@ -7,125 +7,96 @@ test_description='GIT_EDITOR, core.editor, and stuff' unset EDITOR VISUAL GIT_EDITOR test_expect_success 'determine default editor' ' - vi=$(TERM=vt100 git var GIT_EDITOR) && test -n "$vi" - ' -if ! expr "$vi" : '[a-z]*$' >/dev/null -then - vi= -fi - -for i in GIT_EDITOR core_editor EDITOR VISUAL $vi -do - cat >e-$i.sh <<-EOF - #!$SHELL_PATH - echo "Edited by $i" >"\$1" - EOF - chmod +x e-$i.sh -done +test_expect_success setup ' + if ! expr "$vi" : "[a-z]*$" >/dev/null + then + vi= + fi && -if ! test -z "$vi" -then - mv e-$vi.sh $vi -fi + for i in GIT_EDITOR core_editor EDITOR VISUAL $vi + do + write_script e-$i.sh <<-EOF || return 1 + echo "Edited by $i" >"\$1" + EOF + done && -test_expect_success setup ' + if ! test -z "$vi" + then + mv e-$vi.sh $vi + fi && msg="Hand-edited" && test_commit "$msg" && - echo "$msg" >expect && - git show -s --format=%s > actual && - test_cmp expect actual - + test_commit_message HEAD -m "$msg" ' -TERM=dumb -export TERM test_expect_success 'dumb should error out when falling back on vi' ' - - if git commit --amend - then - echo "Oops?" - false - else - : happy - fi + test_must_fail env TERM=dumb git commit --amend ' test_expect_success 'dumb should prefer EDITOR to VISUAL' ' - - EDITOR=./e-EDITOR.sh && - VISUAL=./e-VISUAL.sh && - export EDITOR VISUAL && - git commit --amend && - test "$(git show -s --format=%s)" = "Edited by EDITOR" - + TERM=dumb EDITOR=./e-EDITOR.sh VISUAL=./e-VISUAL.sh \ + git commit --amend && + test_commit_message HEAD -m "Edited by EDITOR" ' -TERM=vt100 -export TERM for i in $vi EDITOR VISUAL core_editor GIT_EDITOR do - echo "Edited by $i" >expect - unset EDITOR VISUAL GIT_EDITOR - git config --unset-all core.editor - case "$i" in - core_editor) - git config core.editor ./e-core_editor.sh - ;; - [A-Z]*) - eval "$i=./e-$i.sh" - export $i - ;; - esac test_expect_success "Using $i" ' - git --exec-path=. commit --amend && - git show -s --pretty=oneline | - sed -e "s/^[0-9a-f]* //" >actual && - test_cmp expect actual + if test "$i" = core_editor + then + test_config core.editor ./e-core_editor.sh + fi && + ( + case "$i" in + [A-Z]*) + eval "$i=./e-$i.sh" && + export $i + ;; + esac && + PATH="$PWD:$PATH" TERM=vt100 git commit --amend + ) && + test_commit_message HEAD -m "Edited by $i" ' done -unset EDITOR VISUAL GIT_EDITOR -git config --unset-all core.editor -for i in $vi EDITOR VISUAL core_editor GIT_EDITOR -do - echo "Edited by $i" >expect - case "$i" in - core_editor) - git config core.editor ./e-core_editor.sh - ;; - [A-Z]*) - eval "$i=./e-$i.sh" - export $i - ;; - esac - test_expect_success "Using $i (override)" ' - git --exec-path=. commit --amend && - git show -s --pretty=oneline | - sed -e "s/^[0-9a-f]* //" >actual && - test_cmp expect actual - ' -done +test_expect_success 'Using editors with overrides' ' + ( + TERM=vt100 && + export TERM && + for i in $vi EDITOR VISUAL core_editor GIT_EDITOR + do + echo "Edited by $i" >expect && + case "$i" in + core_editor) + git config core.editor ./e-core_editor.sh + ;; + [A-Z]*) + eval "$i=./e-$i.sh" && + export $i + ;; + esac && + PATH="$PWD:$PATH" git commit --amend && + test_commit_message HEAD expect || exit 1 + done + ) +' test_expect_success 'editor with a space' ' echo "echo space >\"\$1\"" >"e space.sh" && chmod a+x "e space.sh" && GIT_EDITOR="./e\ space.sh" git commit --amend && - test space = "$(git show -s --pretty=format:%s)" - + test_commit_message HEAD -m space ' -unset GIT_EDITOR test_expect_success 'core.editor with a space' ' - - git config core.editor \"./e\ space.sh\" && + test_config core.editor \"./e\ space.sh\" && git commit --amend && - test space = "$(git show -s --pretty=format:%s)" - + test_commit_message HEAD -m space ' test_done diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh index b37e2018a7..05f6da4ad9 100755 --- a/t/t7502-commit-porcelain.sh +++ b/t/t7502-commit-porcelain.sh @@ -956,13 +956,39 @@ test_expect_success 'commit --status with custom comment character' ' test_grep "^; Changes to be committed:" .git/COMMIT_EDITMSG ' -test_expect_success 'switch core.commentchar' ' +test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar' ' test_commit "#foo" foo && - GIT_EDITOR=.git/FAKE_EDITOR git -c core.commentChar=auto commit --amend && + cat >config-include <<-\EOF && + [core] + commentString=: + commentString=% + commentChar=auto + EOF + test_when_finished "rm config-include" && + test_config include.path "$(pwd)/config-include" && + test_config core.commentChar ! && + GIT_EDITOR=.git/FAKE_EDITOR git commit --amend 2>err && + sed -n "s/^hint: *\$//p; s/^hint: //p; s/^warning: //p" err >actual && + cat >expect <<-EOF && + Support for ${SQ}core.commentChar=auto${SQ} is deprecated and will be removed in Git 3.0 + + To use the default comment string (#) please run + + git config unset core.commentChar + git config unset --file ~/config-include --all core.commentString + git config unset --file ~/config-include core.commentChar + + To set a custom comment string please run + + git config set --file ~/config-include core.commentChar <comment string> + + where ${SQ}<comment string>${SQ} is the string you wish to use. + EOF + test_cmp expect actual && test_grep "^; Changes to be committed:" .git/COMMIT_EDITMSG ' -test_expect_success 'switch core.commentchar but out of options' ' +test_expect_success !WITH_BREAKING_CHANGES 'switch core.commentchar but out of options' ' cat >text <<\EOF && # 1 ; 2 @@ -982,4 +1008,24 @@ EOF ) ' +test_expect_success WITH_BREAKING_CHANGES 'core.commentChar=auto is rejected' ' + test_config core.commentChar auto && + test_must_fail git rev-parse --git-dir 2>err && + sed -n "s/^hint: *\$//p; s/^hint: //p; s/^fatal: //p" err >actual && + cat >expect <<-EOF && + Support for ${SQ}core.commentChar=auto${SQ} has been removed in Git 3.0 + + To use the default comment string (#) please run + + git config unset core.commentChar + + To set a custom comment string please run + + git config set core.commentChar <comment string> + + where ${SQ}<comment string>${SQ} is the string you wish to use. + EOF + test_cmp expect actual +' + test_done diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh index 611755cc13..73b78bdd88 100755 --- a/t/t7700-repack.sh +++ b/t/t7700-repack.sh @@ -838,4 +838,67 @@ test_expect_success '-n overrides repack.updateServerInfo=true' ' test_server_info_missing ' +test_expect_success 'pending objects are repacked appropriately' ' + test_when_finished rm -rf pending && + git init pending && + + ( + cd pending && + + # Commit file, a/b/c and never change them. + mkdir -p a/b && + echo singleton >file && + echo stuff >a/b/c && + echo more >a/d && + git add file a && + git commit -m "single blobs" && + + # Files a/d and a/e will not be singletons. + echo d >a/d && + echo e >a/e && + git add a && + git commit -m "more blobs" && + + # This use of a sparse index helps to force + # test that the cache-tree is walked, too. + git sparse-checkout set --sparse-index a x && + + # Create staged changes: + # * a/e now has multiple versions. + # * a/i now has only one version. + echo f >a/d && + echo h >a/e && + echo i >a/i && + git add a && + + # Stage and unstage a change to make use of + # resolve-undo cache and how that impacts fsck. + mkdir x && + echo y >x/y && + git add x && + xy=$(git rev-parse :x/y) && + git rm --cached x/y && + + # The blob for x/y must persist through repacks, + # but fsck currently ignores the REUC extension + # for finding links to the blob. + cat >expect <<-EOF && + dangling blob $xy + EOF + + # Bring the loose objects into a packfile to avoid + # leftovers in next test. Without this, the loose + # objects persist and the test succeeds for other + # reasons. + git repack -adf && + git fsck >out && + test_cmp expect out && + + # Test path walk version with pack.useSparse. + git -c pack.useSparse=true repack -adf --path-walk && + git fsck >out && + test_cmp expect out + ) +' + test_done diff --git a/t/t8020-last-modified.sh b/t/t8020-last-modified.sh new file mode 100755 index 0000000000..5eb4cef035 --- /dev/null +++ b/t/t8020-last-modified.sh @@ -0,0 +1,210 @@ +#!/bin/sh + +test_description='last-modified tests' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit 1 file && + mkdir a && + test_commit 2 a/file && + mkdir a/b && + test_commit 3 a/b/file +' + +test_expect_success 'cannot run last-modified on two trees' ' + test_must_fail git last-modified HEAD HEAD~1 +' + +check_last_modified() { + local indir= && + while test $# != 0 + do + case "$1" in + -C) + indir="$2" + shift + ;; + *) + break + ;; + esac && + shift + done && + + cat >expect && + test_when_finished "rm -f tmp.*" && + git ${indir:+-C "$indir"} last-modified "$@" >tmp.1 && + git name-rev --annotate-stdin --name-only --tags \ + <tmp.1 >tmp.2 && + tr '\t' ' ' <tmp.2 >actual && + test_cmp expect actual +} + +test_expect_success 'last-modified non-recursive' ' + check_last_modified <<-\EOF + 3 a + 1 file + EOF +' + +test_expect_success 'last-modified recursive' ' + check_last_modified -r <<-\EOF + 3 a/b/file + 2 a/file + 1 file + EOF +' + +test_expect_success 'last-modified recursive with show-trees' ' + check_last_modified -r -t <<-\EOF + 3 a + 3 a/b + 3 a/b/file + 2 a/file + 1 file + EOF +' + +test_expect_success 'last-modified non-recursive with show-trees' ' + check_last_modified -t <<-\EOF + 3 a + 1 file + EOF +' + +test_expect_success 'last-modified subdir' ' + check_last_modified a <<-\EOF + 3 a + EOF +' + +test_expect_success 'last-modified subdir recursive' ' + check_last_modified -r a <<-\EOF + 3 a/b/file + 2 a/file + EOF +' + +test_expect_success 'last-modified from non-HEAD commit' ' + check_last_modified HEAD^ <<-\EOF + 2 a + 1 file + EOF +' + +test_expect_success 'last-modified from subdir defaults to root' ' + check_last_modified -C a <<-\EOF + 3 a + 1 file + EOF +' + +test_expect_success 'last-modified from subdir uses relative pathspecs' ' + check_last_modified -C a -r b <<-\EOF + 3 a/b/file + EOF +' + +test_expect_success 'limit last-modified traversal by count' ' + check_last_modified -1 <<-\EOF + 3 a + ^2 file + EOF +' + +test_expect_success 'limit last-modified traversal by commit' ' + check_last_modified HEAD~2..HEAD <<-\EOF + 3 a + ^1 file + EOF +' + +test_expect_success 'only last-modified files in the current tree' ' + git rm -rf a && + git commit -m "remove a" && + check_last_modified <<-\EOF + 1 file + EOF +' + +test_expect_success 'cross merge boundaries in blaming' ' + git checkout HEAD^0 && + git rm -rf . && + test_commit m1 && + git checkout HEAD^ && + git rm -rf . && + test_commit m2 && + git merge m1 && + check_last_modified <<-\EOF + m2 m2.t + m1 m1.t + EOF +' + +test_expect_success 'last-modified merge for resolved conflicts' ' + git checkout HEAD^0 && + git rm -rf . && + test_commit c1 conflict && + git checkout HEAD^ && + git rm -rf . && + test_commit c2 conflict && + test_must_fail git merge c1 && + test_commit resolved conflict && + check_last_modified conflict <<-\EOF + resolved conflict + EOF +' + + +# Consider `file` with this content through history: +# +# A---B---B-------B---B +# \ / +# C---D +test_expect_success 'last-modified merge ignores content from branch' ' + git checkout HEAD^0 && + git rm -rf . && + test_commit a1 file A && + test_commit a2 file B && + test_commit a3 file C && + test_commit a4 file D && + git checkout a2 && + git merge --no-commit --no-ff a4 && + git checkout a2 -- file && + git merge --continue && + check_last_modified <<-\EOF + a2 file + EOF +' + +# Consider `file` with this content through history: +# +# A---B---B---C---D---B---B +# \ / +# B-------B +test_expect_success 'last-modified merge undoes changes' ' + git checkout HEAD^0 && + git rm -rf . && + test_commit b1 file A && + test_commit b2 file B && + test_commit b3 file C && + test_commit b4 file D && + git checkout b2 && + test_commit b5 file2 2 && + git checkout b4 && + git merge --no-commit --no-ff b5 && + git checkout b2 -- file && + git merge --continue && + check_last_modified <<-\EOF + b5 file2 + b2 file + EOF +' + +test_expect_success 'last-modified complains about unknown arguments' ' + test_must_fail git last-modified --foo 2>err && + grep "unknown last-modified argument: --foo" err +' + +test_done diff --git a/t/unit-tests/u-dir.c b/t/unit-tests/u-dir.c new file mode 100644 index 0000000000..2d0adaa39e --- /dev/null +++ b/t/unit-tests/u-dir.c @@ -0,0 +1,47 @@ +#include "unit-test.h" +#include "dir.h" + +#define TEST_WITHIN_DEPTH(path, depth, max_depth, expect) do { \ + int actual = within_depth(path, strlen(path), \ + depth, max_depth); \ + if (actual != expect) \ + cl_failf("path '%s' with depth '%d' and max-depth '%d': expected %d, got %d", \ + path, depth, max_depth, expect, actual); \ + } while (0) + +void test_dir__within_depth(void) +{ + /* depth = 0; max_depth = 0 */ + TEST_WITHIN_DEPTH("", 0, 0, 1); + TEST_WITHIN_DEPTH("file", 0, 0, 1); + TEST_WITHIN_DEPTH("a", 0, 0, 1); + TEST_WITHIN_DEPTH("a/file", 0, 0, 0); + TEST_WITHIN_DEPTH("a/b", 0, 0, 0); + TEST_WITHIN_DEPTH("a/b/file", 0, 0, 0); + + /* depth = 0; max_depth = 1 */ + TEST_WITHIN_DEPTH("", 0, 1, 1); + TEST_WITHIN_DEPTH("file", 0, 1, 1); + TEST_WITHIN_DEPTH("a", 0, 1, 1); + TEST_WITHIN_DEPTH("a/file", 0, 1, 1); + TEST_WITHIN_DEPTH("a/b", 0, 1, 1); + TEST_WITHIN_DEPTH("a/b/file", 0, 1, 0); + + /* depth = 1; max_depth = 1 */ + TEST_WITHIN_DEPTH("", 1, 1, 1); + TEST_WITHIN_DEPTH("file", 1, 1, 1); + TEST_WITHIN_DEPTH("a", 1, 1, 1); + TEST_WITHIN_DEPTH("a/file", 1, 1, 0); + TEST_WITHIN_DEPTH("a/b", 1, 1, 0); + TEST_WITHIN_DEPTH("a/b/file", 1, 1, 0); + + /* depth = 1; max_depth = 0 */ + TEST_WITHIN_DEPTH("", 1, 0, 0); + TEST_WITHIN_DEPTH("file", 1, 0, 0); + TEST_WITHIN_DEPTH("a", 1, 0, 0); + TEST_WITHIN_DEPTH("a/file", 1, 0, 0); + TEST_WITHIN_DEPTH("a/b", 1, 0, 0); + TEST_WITHIN_DEPTH("a/b/file", 1, 0, 0); + + +} diff --git a/t/unit-tests/u-reftable-stack.c b/t/unit-tests/u-reftable-stack.c index e4ea57138e..a8b91812e8 100644 --- a/t/unit-tests/u-reftable-stack.c +++ b/t/unit-tests/u-reftable-stack.c @@ -128,7 +128,7 @@ static void write_n_ref_tables(struct reftable_stack *st, cl_reftable_set_hash(ref.value.val1, i, REFTABLE_HASH_SHA1); cl_assert_equal_i(reftable_stack_add(st, - &write_test_ref, &ref), 0); + &write_test_ref, &ref, 0), 0); } st->opts.disable_auto_compact = disable_auto_compact; @@ -171,7 +171,7 @@ void test_reftable_stack__add_one(void) err = reftable_new_stack(&st, dir, &opts); cl_assert(!err); - err = reftable_stack_add(st, write_test_ref, &ref); + err = reftable_stack_add(st, write_test_ref, &ref, 0); cl_assert(!err); err = reftable_stack_read_ref(st, ref.refname, &dest); @@ -235,12 +235,12 @@ void test_reftable_stack__uptodate(void) cl_assert_equal_i(reftable_new_stack(&st1, dir, &opts), 0); cl_assert_equal_i(reftable_new_stack(&st2, dir, &opts), 0); cl_assert_equal_i(reftable_stack_add(st1, write_test_ref, - &ref1), 0); + &ref1, 0), 0); cl_assert_equal_i(reftable_stack_add(st2, write_test_ref, - &ref2), REFTABLE_OUTDATED_ERROR); + &ref2, 0), REFTABLE_OUTDATED_ERROR); cl_assert_equal_i(reftable_stack_reload(st2), 0); cl_assert_equal_i(reftable_stack_add(st2, write_test_ref, - &ref2), 0); + &ref2, 0), 0); reftable_stack_destroy(st1); reftable_stack_destroy(st2); clear_dir(dir); @@ -406,7 +406,7 @@ void test_reftable_stack__auto_compaction_fails_gracefully(void) cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &ref), 0); + &ref, 0), 0); cl_assert_equal_i(st->merged->tables_len, 1); cl_assert_equal_i(st->stats.attempts, 0); cl_assert_equal_i(st->stats.failures, 0); @@ -424,7 +424,7 @@ void test_reftable_stack__auto_compaction_fails_gracefully(void) write_file_buf(table_path.buf, "", 0); ref.update_index = 2; - err = reftable_stack_add(st, write_test_ref, &ref); + err = reftable_stack_add(st, write_test_ref, &ref, 0); cl_assert(!err); cl_assert_equal_i(st->merged->tables_len, 2); cl_assert_equal_i(st->stats.attempts, 1); @@ -460,9 +460,9 @@ void test_reftable_stack__update_index_check(void) cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &ref1), 0); + &ref1, 0), 0); cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &ref2), REFTABLE_API_ERROR); + &ref2, 0), REFTABLE_API_ERROR); reftable_stack_destroy(st); clear_dir(dir); } @@ -477,7 +477,7 @@ void test_reftable_stack__lock_failure(void) cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); for (i = -1; i != REFTABLE_EMPTY_TABLE_ERROR; i--) cl_assert_equal_i(reftable_stack_add(st, write_error, - &i), i); + &i, 0), i); reftable_stack_destroy(st); clear_dir(dir); @@ -521,7 +521,7 @@ void test_reftable_stack__add(void) for (i = 0; i < N; i++) cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &refs[i]), 0); + &refs[i], 0), 0); for (i = 0; i < N; i++) { struct write_log_arg arg = { @@ -529,7 +529,7 @@ void test_reftable_stack__add(void) .update_index = reftable_stack_next_update_index(st), }; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg), 0); + &arg, 0), 0); } cl_assert_equal_i(reftable_stack_compact_all(st, NULL), 0); @@ -612,8 +612,8 @@ void test_reftable_stack__iterator(void) } for (i = 0; i < N; i++) - cl_assert_equal_i(reftable_stack_add(st, - write_test_ref, &refs[i]), 0); + cl_assert_equal_i(reftable_stack_add(st, write_test_ref, + &refs[i], 0), 0); for (i = 0; i < N; i++) { struct write_log_arg arg = { @@ -621,8 +621,8 @@ void test_reftable_stack__iterator(void) .update_index = reftable_stack_next_update_index(st), }; - cl_assert_equal_i(reftable_stack_add(st, - write_test_log, &arg), 0); + cl_assert_equal_i(reftable_stack_add(st, write_test_log, + &arg, 0), 0); } reftable_stack_init_ref_iterator(st, &it); @@ -697,11 +697,11 @@ void test_reftable_stack__log_normalize(void) input.value.update.message = (char *) "one\ntwo"; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg), REFTABLE_API_ERROR); + &arg, 0), REFTABLE_API_ERROR); input.value.update.message = (char *) "one"; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg), 0); + &arg, 0), 0); cl_assert_equal_i(reftable_stack_read_log(st, input.refname, &dest), 0); cl_assert_equal_s(dest.value.update.message, "one\n"); @@ -709,7 +709,7 @@ void test_reftable_stack__log_normalize(void) input.value.update.message = (char *) "two\n"; arg.update_index = 2; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg), 0); + &arg, 0), 0); cl_assert_equal_i(reftable_stack_read_log(st, input.refname, &dest), 0); cl_assert_equal_s(dest.value.update.message, "two\n"); @@ -759,15 +759,16 @@ void test_reftable_stack__tombstone(void) } } for (i = 0; i < N; i++) - cl_assert_equal_i(reftable_stack_add(st, write_test_ref, &refs[i]), 0); + cl_assert_equal_i(reftable_stack_add(st, write_test_ref, + &refs[i], 0), 0); for (i = 0; i < N; i++) { struct write_log_arg arg = { .log = &logs[i], .update_index = reftable_stack_next_update_index(st), }; - cl_assert_equal_i(reftable_stack_add(st, - write_test_log, &arg), 0); + cl_assert_equal_i(reftable_stack_add(st, write_test_log, + &arg, 0), 0); } cl_assert_equal_i(reftable_stack_read_ref(st, "branch", @@ -815,7 +816,7 @@ void test_reftable_stack__hash_id(void) cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); cl_assert_equal_i(reftable_stack_add(st, write_test_ref, - &ref), 0); + &ref, 0), 0); /* can't read it with the wrong hash ID. */ cl_assert_equal_i(reftable_new_stack(&st32, dir, @@ -884,7 +885,7 @@ void test_reftable_stack__reflog_expire(void) .update_index = reftable_stack_next_update_index(st), }; cl_assert_equal_i(reftable_stack_add(st, write_test_log, - &arg), 0); + &arg, 0), 0); } cl_assert_equal_i(reftable_stack_compact_all(st, NULL), 0); @@ -924,7 +925,7 @@ void test_reftable_stack__empty_add(void) cl_assert_equal_i(reftable_new_stack(&st, dir, &opts), 0); cl_assert_equal_i(reftable_stack_add(st, write_nothing, - NULL), 0); + NULL, 0), 0); cl_assert_equal_i(reftable_new_stack(&st2, dir, &opts), 0); clear_dir(dir); reftable_stack_destroy(st); @@ -963,7 +964,7 @@ void test_reftable_stack__auto_compaction(void) }; snprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i); - err = reftable_stack_add(st, write_test_ref, &ref); + err = reftable_stack_add(st, write_test_ref, &ref, 0); cl_assert(!err); err = reftable_stack_auto_compact(st); @@ -999,7 +1000,7 @@ void test_reftable_stack__auto_compaction_factor(void) }; xsnprintf(name, sizeof(name), "branch%04"PRIuMAX, (uintmax_t)i); - err = reftable_stack_add(st, &write_test_ref, &ref); + err = reftable_stack_add(st, &write_test_ref, &ref, 0); cl_assert(!err); cl_assert(i < 5 || st->merged->tables_len < 5 * fastlogN(i, 5)); @@ -1078,8 +1079,8 @@ void test_reftable_stack__add_performs_auto_compaction(void) snprintf(buf, sizeof(buf), "branch-%04"PRIuMAX, (uintmax_t)i); ref.refname = buf; - cl_assert_equal_i(reftable_stack_add(st, - write_test_ref, &ref), 0); + cl_assert_equal_i(reftable_stack_add(st, write_test_ref, + &ref, 0), 0); /* * The stack length should grow continuously for all runs where diff --git a/t/unit-tests/u-string-list.c b/t/unit-tests/u-string-list.c index d4ba5f9fa5..a2457d7b1e 100644 --- a/t/unit-tests/u-string-list.c +++ b/t/unit-tests/u-string-list.c @@ -43,7 +43,7 @@ static void t_string_list_equal(struct string_list *list, expected_strings->items[i].string); } -static void t_string_list_split(const char *data, int delim, int maxsplit, ...) +static void t_string_list_split(const char *data, const char *delim, int maxsplit, ...) { struct string_list expected_strings = STRING_LIST_INIT_DUP; struct string_list list = STRING_LIST_INIT_DUP; @@ -63,15 +63,94 @@ static void t_string_list_split(const char *data, int delim, int maxsplit, ...) string_list_clear(&list, 0); } +static void t_string_list_split_f(const char *data, const char *delim, + int maxsplit, unsigned flags, ...) +{ + struct string_list expected_strings = STRING_LIST_INIT_DUP; + struct string_list list = STRING_LIST_INIT_DUP; + va_list ap; + int len; + + va_start(ap, flags); + t_vcreate_string_list_dup(&expected_strings, 0, ap); + va_end(ap); + + string_list_clear(&list, 0); + len = string_list_split_f(&list, data, delim, maxsplit, flags); + cl_assert_equal_i(len, expected_strings.nr); + t_string_list_equal(&list, &expected_strings); + + string_list_clear(&expected_strings, 0); + string_list_clear(&list, 0); +} + +void test_string_list__split_f(void) +{ + t_string_list_split_f("::foo:bar:baz:", ":", -1, 0, + "", "", "foo", "bar", "baz", "", NULL); + t_string_list_split_f(" foo:bar : baz", ":", -1, STRING_LIST_SPLIT_TRIM, + "foo", "bar", "baz", NULL); + t_string_list_split_f(" a b c ", " ", 1, STRING_LIST_SPLIT_TRIM, + "a", "b c", NULL); + t_string_list_split_f("::foo::bar:baz:", ":", -1, STRING_LIST_SPLIT_NONEMPTY, + "foo", "bar", "baz", NULL); + t_string_list_split_f("foo:baz", ":", -1, STRING_LIST_SPLIT_NONEMPTY, + "foo", "baz", NULL); + t_string_list_split_f("foo :: : baz", ":", -1, + STRING_LIST_SPLIT_NONEMPTY | STRING_LIST_SPLIT_TRIM, + "foo", "baz", NULL); +} + +static void t_string_list_split_in_place_f(const char *data_, const char *delim, + int maxsplit, unsigned flags, ...) +{ + struct string_list expected_strings = STRING_LIST_INIT_DUP; + struct string_list list = STRING_LIST_INIT_NODUP; + char *data = xstrdup(data_); + va_list ap; + int len; + + va_start(ap, flags); + t_vcreate_string_list_dup(&expected_strings, 0, ap); + va_end(ap); + + string_list_clear(&list, 0); + len = string_list_split_in_place_f(&list, data, delim, maxsplit, flags); + cl_assert_equal_i(len, expected_strings.nr); + t_string_list_equal(&list, &expected_strings); + + free(data); + string_list_clear(&expected_strings, 0); + string_list_clear(&list, 0); +} + +void test_string_list__split_in_place_f(void) +{ + t_string_list_split_in_place_f("::foo:bar:baz:", ":", -1, 0, + "", "", "foo", "bar", "baz", "", NULL); + t_string_list_split_in_place_f(" foo:bar : baz", ":", -1, STRING_LIST_SPLIT_TRIM, + "foo", "bar", "baz", NULL); + t_string_list_split_in_place_f(" a b c ", " ", 1, STRING_LIST_SPLIT_TRIM, + "a", "b c", NULL); + t_string_list_split_in_place_f("::foo::bar:baz:", ":", -1, + STRING_LIST_SPLIT_NONEMPTY, + "foo", "bar", "baz", NULL); + t_string_list_split_in_place_f("foo:baz", ":", -1, STRING_LIST_SPLIT_NONEMPTY, + "foo", "baz", NULL); + t_string_list_split_in_place_f("foo :: : baz", ":", -1, + STRING_LIST_SPLIT_NONEMPTY | STRING_LIST_SPLIT_TRIM, + "foo", "baz", NULL); +} + void test_string_list__split(void) { - t_string_list_split("foo:bar:baz", ':', -1, "foo", "bar", "baz", NULL); - t_string_list_split("foo:bar:baz", ':', 0, "foo:bar:baz", NULL); - t_string_list_split("foo:bar:baz", ':', 1, "foo", "bar:baz", NULL); - t_string_list_split("foo:bar:baz", ':', 2, "foo", "bar", "baz", NULL); - t_string_list_split("foo:bar:", ':', -1, "foo", "bar", "", NULL); - t_string_list_split("", ':', -1, "", NULL); - t_string_list_split(":", ':', -1, "", "", NULL); + t_string_list_split("foo:bar:baz", ":", -1, "foo", "bar", "baz", NULL); + t_string_list_split("foo:bar:baz", ":", 0, "foo:bar:baz", NULL); + t_string_list_split("foo:bar:baz", ":", 1, "foo", "bar:baz", NULL); + t_string_list_split("foo:bar:baz", ":", 2, "foo", "bar", "baz", NULL); + t_string_list_split("foo:bar:", ":", -1, "foo", "bar", "", NULL); + t_string_list_split("", ":", -1, "", NULL); + t_string_list_split(":", ":", -1, "", "", NULL); } static void t_string_list_split_in_place(const char *data, const char *delim, diff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c index 22a99a0682..bbcfeda60a 100644 --- a/trace2/tr2_cfg.c +++ b/trace2/tr2_cfg.c @@ -8,89 +8,65 @@ #include "trace2/tr2_sysenv.h" #include "wildmatch.h" -static struct strbuf **tr2_cfg_patterns; -static int tr2_cfg_count_patterns; +static struct string_list tr2_cfg_patterns = STRING_LIST_INIT_DUP; static int tr2_cfg_loaded; -static struct strbuf **tr2_cfg_env_vars; -static int tr2_cfg_env_vars_count; +static struct string_list tr2_cfg_env_vars = STRING_LIST_INIT_DUP; static int tr2_cfg_env_vars_loaded; /* * Parse a string containing a comma-delimited list of config keys - * or wildcard patterns into a list of strbufs. + * or wildcard patterns into a string list. */ -static int tr2_cfg_load_patterns(void) +static size_t tr2_cfg_load_patterns(void) { - struct strbuf **s; const char *envvar; if (tr2_cfg_loaded) - return tr2_cfg_count_patterns; + return tr2_cfg_patterns.nr; tr2_cfg_loaded = 1; envvar = tr2_sysenv_get(TR2_SYSENV_CFG_PARAM); if (!envvar || !*envvar) - return tr2_cfg_count_patterns; + return tr2_cfg_patterns.nr; - tr2_cfg_patterns = strbuf_split_buf(envvar, strlen(envvar), ',', -1); - for (s = tr2_cfg_patterns; *s; s++) { - struct strbuf *buf = *s; - - if (buf->len && buf->buf[buf->len - 1] == ',') - strbuf_setlen(buf, buf->len - 1); - strbuf_trim_trailing_newline(*s); - strbuf_trim(*s); - } - - tr2_cfg_count_patterns = s - tr2_cfg_patterns; - return tr2_cfg_count_patterns; + string_list_split_f(&tr2_cfg_patterns, envvar, ",", -1, + STRING_LIST_SPLIT_TRIM); + return tr2_cfg_patterns.nr; } void tr2_cfg_free_patterns(void) { - if (tr2_cfg_patterns) - strbuf_list_free(tr2_cfg_patterns); - tr2_cfg_count_patterns = 0; + if (tr2_cfg_patterns.nr) + string_list_clear(&tr2_cfg_patterns, 0); tr2_cfg_loaded = 0; } /* * Parse a string containing a comma-delimited list of environment variable - * names into a list of strbufs. + * names into a string list. */ -static int tr2_load_env_vars(void) +static size_t tr2_load_env_vars(void) { - struct strbuf **s; const char *varlist; if (tr2_cfg_env_vars_loaded) - return tr2_cfg_env_vars_count; + return tr2_cfg_env_vars.nr; tr2_cfg_env_vars_loaded = 1; varlist = tr2_sysenv_get(TR2_SYSENV_ENV_VARS); if (!varlist || !*varlist) - return tr2_cfg_env_vars_count; - - tr2_cfg_env_vars = strbuf_split_buf(varlist, strlen(varlist), ',', -1); - for (s = tr2_cfg_env_vars; *s; s++) { - struct strbuf *buf = *s; - - if (buf->len && buf->buf[buf->len - 1] == ',') - strbuf_setlen(buf, buf->len - 1); - strbuf_trim_trailing_newline(*s); - strbuf_trim(*s); - } + return tr2_cfg_env_vars.nr; - tr2_cfg_env_vars_count = s - tr2_cfg_env_vars; - return tr2_cfg_env_vars_count; + string_list_split_f(&tr2_cfg_env_vars, varlist, ",", -1, + STRING_LIST_SPLIT_TRIM); + return tr2_cfg_env_vars.nr; } void tr2_cfg_free_env_vars(void) { - if (tr2_cfg_env_vars) - strbuf_list_free(tr2_cfg_env_vars); - tr2_cfg_env_vars_count = 0; + if (tr2_cfg_env_vars.nr) + string_list_clear(&tr2_cfg_env_vars, 0); tr2_cfg_env_vars_loaded = 0; } @@ -105,12 +81,11 @@ struct tr2_cfg_data { static int tr2_cfg_cb(const char *key, const char *value, const struct config_context *ctx, void *d) { - struct strbuf **s; + struct string_list_item *item; struct tr2_cfg_data *data = (struct tr2_cfg_data *)d; - for (s = tr2_cfg_patterns; *s; s++) { - struct strbuf *buf = *s; - int wm = wildmatch(buf->buf, key, WM_CASEFOLD); + for_each_string_list_item(item, &tr2_cfg_patterns) { + int wm = wildmatch(item->string, key, WM_CASEFOLD); if (wm == WM_MATCH) { trace2_def_param_fl(data->file, data->line, key, value, ctx->kvi); @@ -132,17 +107,16 @@ void tr2_cfg_list_config_fl(const char *file, int line) void tr2_list_env_vars_fl(const char *file, int line) { struct key_value_info kvi = KVI_INIT; - struct strbuf **s; + struct string_list_item *item; kvi_from_param(&kvi); if (tr2_load_env_vars() <= 0) return; - for (s = tr2_cfg_env_vars; *s; s++) { - struct strbuf *buf = *s; - const char *val = getenv(buf->buf); + for_each_string_list_item(item, &tr2_cfg_env_vars) { + const char *val = getenv(item->string); if (val && *val) - trace2_def_param_fl(file, line, buf->buf, val, &kvi); + trace2_def_param_fl(file, line, item->string, val, &kvi); } } diff --git a/transport.c b/transport.c index e305d6bd22..6ac8aa402b 100644 --- a/transport.c +++ b/transport.c @@ -1042,7 +1042,7 @@ static const struct string_list *protocol_allow_list(void) if (enabled < 0) { const char *v = getenv("GIT_ALLOW_PROTOCOL"); if (v) { - string_list_split(&allowed, v, ':', -1); + string_list_split(&allowed, v, ":", -1); string_list_sort(&allowed); enabled = 1; } else { diff --git a/tree-diff.c b/tree-diff.c index e00fc2f450..5988148b60 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -13,6 +13,7 @@ #include "tree-walk.h" #include "environment.h" #include "repository.h" +#include "dir.h" /* * Some mode bits are also used internally for computations. @@ -48,6 +49,73 @@ free((x)); \ } while(0) +/* Returns true if and only if "dir" is a leading directory of "path" */ +static int is_dir_prefix(const char *path, const char *dir, int dirlen) +{ + return !strncmp(path, dir, dirlen) && + (!path[dirlen] || path[dirlen] == '/'); +} + +static int check_recursion_depth(const struct strbuf *name, + const struct pathspec *ps, + int max_depth) +{ + int i; + + if (!ps->nr) + return within_depth(name->buf, name->len, 1, max_depth); + + /* + * We look through the pathspecs in reverse-sorted order, because we + * want to find the longest match first (e.g., "a/b" is better for + * checking depth than "a/b/c"). + */ + for (i = ps->nr - 1; i >= 0; i--) { + const struct pathspec_item *item = ps->items+i; + + /* + * If the name to match is longer than the pathspec, then we + * are only interested if the pathspec matches and we are + * within the allowed depth. + */ + if (name->len >= item->len) { + if (!is_dir_prefix(name->buf, item->match, item->len)) + continue; + return within_depth(name->buf + item->len, + name->len - item->len, + 1, max_depth); + } + + /* + * Otherwise, our name is shorter than the pathspec. We need to + * check if it is a prefix of the pathspec; if so, we must + * always recurse in order to process further (the resulting + * paths we find might or might not match our pathspec, but we + * cannot know until we recurse). + */ + if (is_dir_prefix(item->match, name->buf, name->len)) + return 1; + } + return 0; +} + +static int should_recurse(const struct strbuf *name, struct diff_options *opt) +{ + if (!opt->flags.recursive) + return 0; + if (!opt->max_depth_valid) + return 1; + + /* + * We catch this during diff_setup_done, but let's double-check + * against any internal munging. + */ + if (opt->pathspec.has_wildcard) + BUG("wildcard pathspecs are incompatible with max-depth"); + + return check_recursion_depth(name, &opt->pathspec, opt->max_depth); +} + static void ll_diff_tree_paths( struct combine_diff_path ***tail, const struct object_id *oid, const struct object_id **parents_oid, int nparent, @@ -170,9 +238,13 @@ static void emit_path(struct combine_diff_path ***tail, mode = 0; } - if (opt->flags.recursive && isdir) { - recurse = 1; - emitthis = opt->flags.tree_in_recursive; + if (isdir) { + strbuf_add(base, path, pathlen); + if (should_recurse(base, opt)) { + recurse = 1; + emitthis = opt->flags.tree_in_recursive; + } + strbuf_setlen(base, old_baselen); } if (emitthis) { diff --git a/upload-pack.c b/upload-pack.c index 4f26f6afc7..f78fabc1e1 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -476,20 +476,17 @@ static void create_pack_file(struct upload_pack_data *pack_data, static int do_got_oid(struct upload_pack_data *data, const struct object_id *oid) { - int we_knew_they_have = 0; struct object *o = parse_object_with_flags(the_repository, oid, PARSE_OBJECT_SKIP_HASH_CHECK | PARSE_OBJECT_DISCARD_TREE); if (!o) die("oops (%s)", oid_to_hex(oid)); + if (o->type == OBJ_COMMIT) { struct commit_list *parents; struct commit *commit = (struct commit *)o; - if (o->flags & THEY_HAVE) - we_knew_they_have = 1; - else - o->flags |= THEY_HAVE; + if (!data->oldest_have || (commit->date < data->oldest_have)) data->oldest_have = commit->date; for (parents = commit->parents; @@ -497,11 +494,13 @@ static int do_got_oid(struct upload_pack_data *data, const struct object_id *oid parents = parents->next) parents->item->object.flags |= THEY_HAVE; } - if (!we_knew_they_have) { - add_object_array(o, NULL, &data->have_obj); - return 1; - } - return 0; + + if (o->flags & THEY_HAVE) + return 0; + o->flags |= THEY_HAVE; + + add_object_array(o, NULL, &data->have_obj); + return 1; } static int got_oid(struct upload_pack_data *data, @@ -1685,7 +1684,7 @@ static void process_args(struct packet_reader *request, if (data->uri_protocols.nr) send_err_and_die(data, "multiple packfile-uris lines forbidden"); - string_list_split(&data->uri_protocols, p, ',', -1); + string_list_split(&data->uri_protocols, p, ",", -1); continue; } @@ -192,7 +192,8 @@ static void show_usage_if_asked_helper(const char *err, ...) void show_usage_if_asked(int ac, const char **av, const char *err) { - if (ac == 2 && !strcmp(av[1], "-h")) + if (ac == 2 && (!strcmp(av[1], "-h") || + !strcmp(av[1], "--help-all"))) show_usage_if_asked_helper(err); } diff --git a/wt-status.c b/wt-status.c index 454601afa1..21dabab77d 100644 --- a/wt-status.c +++ b/wt-status.c @@ -972,7 +972,8 @@ static void wt_longstatus_print_changed(struct wt_status *s) wt_longstatus_print_trailer(s); } -static int stash_count_refs(struct object_id *ooid UNUSED, +static int stash_count_refs(const char *refname UNUSED, + struct object_id *ooid UNUSED, struct object_id *noid UNUSED, const char *email UNUSED, timestamp_t timestamp UNUSED, int tz UNUSED, @@ -1351,8 +1352,8 @@ static int split_commit_in_progress(struct wt_status *s) */ static void abbrev_oid_in_line(struct strbuf *line) { - struct strbuf **split; - int i; + struct string_list split = STRING_LIST_INIT_DUP; + struct object_id oid; if (starts_with(line->buf, "exec ") || starts_with(line->buf, "x ") || @@ -1360,26 +1361,15 @@ static void abbrev_oid_in_line(struct strbuf *line) starts_with(line->buf, "l ")) return; - split = strbuf_split_max(line, ' ', 3); - if (split[0] && split[1]) { - struct object_id oid; - - /* - * strbuf_split_max left a space. Trim it and re-add - * it after abbreviation. - */ - strbuf_trim(split[1]); - if (!repo_get_oid(the_repository, split[1]->buf, &oid)) { - strbuf_reset(split[1]); - strbuf_add_unique_abbrev(split[1], &oid, - DEFAULT_ABBREV); - strbuf_addch(split[1], ' '); - strbuf_reset(line); - for (i = 0; split[i]; i++) - strbuf_addbuf(line, split[i]); - } + if ((2 <= string_list_split(&split, line->buf, " ", 2)) && + !repo_get_oid(the_repository, split.items[1].string, &oid)) { + strbuf_reset(line); + strbuf_addf(line, "%s ", split.items[0].string); + strbuf_add_unique_abbrev(line, &oid, DEFAULT_ABBREV); + for (size_t i = 2; i < split.nr; i++) + strbuf_addf(line, " %s", split.items[i].string); } - strbuf_list_free(split); + string_list_clear(&split, 0); } static int read_rebase_todolist(const char *fname, struct string_list *lines) @@ -1664,7 +1654,8 @@ struct grab_1st_switch_cbdata { struct object_id noid; }; -static int grab_1st_switch(struct object_id *ooid UNUSED, +static int grab_1st_switch(const char *refname UNUSED, + struct object_id *ooid UNUSED, struct object_id *noid, const char *email UNUSED, timestamp_t timestamp UNUSED, int tz UNUSED, diff --git a/xdiff-interface.h b/xdiff-interface.h index 1ed430b622..dfc55daddf 100644 --- a/xdiff-interface.h +++ b/xdiff-interface.h @@ -28,9 +28,9 @@ * from an error internal to xdiff, xdiff itself will see that * non-zero return and translate it to -1. * - * See "diff_grep" in diffcore-pickaxe.c for a trick to work around - * this, i.e. using the "consume_callback_data" to note the desired - * early return. + * See "diff_grep" in diffcore-pickaxe.c and "quick_consume" in diff.c + * for a trick to work around this, i.e. using the "consume_callback_data" + * to note the desired early return. */ typedef int (*xdiff_emit_line_fn)(void *, char *, unsigned long); typedef void (*xdiff_emit_hunk_fn)(void *data, diff --git a/xdiff/xutils.c b/xdiff/xutils.c index 444a108f87..78d1cf74b1 100644 --- a/xdiff/xutils.c +++ b/xdiff/xutils.c @@ -249,7 +249,7 @@ int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags) return 1; } -static unsigned long xdl_hash_record_with_whitespace(char const **data, +unsigned long xdl_hash_record_with_whitespace(char const **data, char const *top, long flags) { unsigned long ha = 5381; char const *ptr = *data; @@ -294,19 +294,67 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data, return ha; } -unsigned long xdl_hash_record(char const **data, char const *top, long flags) { - unsigned long ha = 5381; +/* + * Compiler reassociation barrier: pretend to modify X and Y to disallow + * changing evaluation order with respect to following uses of X and Y. + */ +#ifdef __GNUC__ +#define REASSOC_FENCE(x, y) __asm__("" : "+r"(x), "+r"(y)) +#else +#define REASSOC_FENCE(x, y) +#endif + +unsigned long xdl_hash_record_verbatim(char const **data, char const *top) { + unsigned long ha = 5381, c0, c1; char const *ptr = *data; - - if (flags & XDF_WHITESPACE_FLAGS) - return xdl_hash_record_with_whitespace(data, top, flags); - +#if 0 + /* + * The baseline form of the optimized loop below. This is the djb2 + * hash (the above function uses a variant with XOR instead of ADD). + */ for (; ptr < top && *ptr != '\n'; ptr++) { ha += (ha << 5); - ha ^= (unsigned long) *ptr; + ha += (unsigned long) *ptr; } *data = ptr < top ? ptr + 1: ptr; - +#else + /* Process two characters per iteration. */ + if (top - ptr >= 2) do { + if ((c0 = ptr[0]) == '\n') { + *data = ptr + 1; + return ha; + } + if ((c1 = ptr[1]) == '\n') { + *data = ptr + 2; + c0 += ha; + REASSOC_FENCE(c0, ha); + ha = ha * 32 + c0; + return ha; + } + /* + * Combine characters C0 and C1 into the hash HA. We have + * HA = (HA * 33 + C0) * 33 + C1, and we want to ensure + * that dependency chain over HA is just one multiplication + * and one addition, i.e. we want to evaluate this as + * HA = HA * 33 * 33 + (C0 * 33 + C1), and likewise prefer + * (C0 * 32 + (C0 + C1)) for the expression in parenthesis. + */ + ha *= 33 * 33; + c1 += c0; + REASSOC_FENCE(c1, c0); + c1 += c0 * 32; + REASSOC_FENCE(c1, ha); + ha += c1; + + ptr += 2; + } while (ptr < top - 1); + *data = top; + if (ptr < top && (c0 = ptr[0]) != '\n') { + c0 += ha; + REASSOC_FENCE(c0, ha); + ha = ha * 32 + c0; + } +#endif return ha; } diff --git a/xdiff/xutils.h b/xdiff/xutils.h index fd0bba94e8..13f6831047 100644 --- a/xdiff/xutils.h +++ b/xdiff/xutils.h @@ -34,7 +34,15 @@ void *xdl_cha_alloc(chastore_t *cha); long xdl_guess_lines(mmfile_t *mf, long sample); int xdl_blankline(const char *line, long size, long flags); int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags); -unsigned long xdl_hash_record(char const **data, char const *top, long flags); +unsigned long xdl_hash_record_verbatim(char const **data, char const *top); +unsigned long xdl_hash_record_with_whitespace(char const **data, char const *top, long flags); +static inline unsigned long xdl_hash_record(char const **data, char const *top, long flags) +{ + if (flags & XDF_WHITESPACE_FLAGS) + return xdl_hash_record_with_whitespace(data, top, flags); + else + return xdl_hash_record_verbatim(data, top); +} unsigned int xdl_hashbits(unsigned int size); int xdl_num_out(char *out, long val); int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2, |
