diff options
232 files changed, 6519 insertions, 2225 deletions
diff --git a/.gitignore b/.gitignore index 054249b20a..f817c509ec 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,7 @@ /git-grep /git-hash-object /git-help +/git-hook /git-http-backend /git-http-fetch /git-http-push diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines index 0e27b5395d..c37c43186e 100644 --- a/Documentation/CodingGuidelines +++ b/Documentation/CodingGuidelines @@ -26,6 +26,13 @@ code. For Git in general, a few rough rules are: go and fix it up." Cf. http://lkml.iu.edu/hypermail/linux/kernel/1001.3/01069.html + - Log messages to explain your changes are as important as the + changes themselves. Clearly written code and in-code comments + explain how the code works and what is assumed from the surrounding + context. The log messages explain what the changes wanted to + achieve and why the changes were necessary (more on this in the + accompanying SubmittingPatches document). + Make your code readable and sensible, and don't try to be clever. As for more concrete guidelines, just imitate the existing code diff --git a/Documentation/RelNotes/2.36.0.txt b/Documentation/RelNotes/2.36.0.txt new file mode 100644 index 0000000000..4e8309701b --- /dev/null +++ b/Documentation/RelNotes/2.36.0.txt @@ -0,0 +1,217 @@ +Git 2.36 Release Notes +====================== + +Updates since Git 2.35 +---------------------- + +Backward compatibility warts + + * "git name-rev --stdin" has been deprecated and issues a warning + when used; use "git name-rev --annotate-stdin" instead. + + +Note to those who build from the source + + * + + +UI, Workflows & Features + + * Assorted updates to "git cat-file", especially "-h". + + * The command line completion (in contrib/) learns to complete + arguments to give to "git sparse-checkout" command. + + * "git log --remerge-diff" shows the difference from mechanical merge + result and the result that is actually recorded in a merge commit. + + * "git log" and friends learned an option --exclude-first-parent-only + to propagate UNINTERESTING bit down only along the first-parent + chain, just like --first-parent option shows commits that lack the + UNINTERESTING bit only along the first-parent chain. + + * The command line completion script (in contrib/) learned to + complete all Git subcommands, including the ones that are normally + hidden, when GIT_COMPLETION_SHOW_ALL_COMMANDS is used. + + * "git branch" learned the "--recurse-submodules" option. + + +Performance, Internal Implementation, Development Support etc. + + * "git apply" (ab)used the util pointer of the string-list to keep + track of how each symbolic link needs to be handled, which has been + simplified by using strset. + + * Fix a hand-rolled alloca() imitation that may have violated + alignment requirement of data being sorted in compatibility + implementation of qsort_s() and stable qsort(). + + * Use the parse-options API in "git reflog" command. + + * The conditional inclusion mechanism of configuration files using + "[includeIf <condition>]" learns to base its decision on the + URL of the remote repository the repository interacts with. + (merge 399b198489 jt/conditional-config-on-remote-url later to maint). + + * "git name-rev --stdin" does not behave like usual "--stdin" at + all. Start the process of renaming it to "--annotate-stdin". + (merge a2585719b3 jc/name-rev-stdin later to maint). + + * "git update-index", "git checkout-index", and "git clean" are + taught to work better with the sparse checkout feature. + + * Use an internal call to reset_head() helper function instead of + spawning "git checkout" in "rebase", and update code paths that are + involved in the change. + + +Fixes since v2.35 +----------------- + + * "rebase" and "stash" in secondary worktrees are broken in + Git 2.35.0, which has been corrected. + + * "git pull --rebase" ignored the rebase.autostash configuration + variable when the remote history is a descendant of our history, + which has been corrected. + (merge 3013d98d7a pb/pull-rebase-autostash-fix later to maint). + + * "git update-index --refresh" has been taught to deal better with + racy timestamps (just like "git status" already does). + (merge 2ede073fd2 ms/update-index-racy later to maint). + + * Avoid tests that are run under GIT_TRACE2 set from failing + unnecessarily. + (merge 944d808e42 js/test-unset-trace2-parents later to maint). + + * The merge-ort misbehaved when merge.renameLimit configuration is + set too low and failed to find all renames. + (merge 9ae39fef7f en/merge-ort-restart-optim-fix later to maint). + + * We explain that revs come first before the pathspec among command + line arguments, but did not spell out that dashed options come + before other args, which has been corrected. + (merge c11f95010c tl/doc-cli-options-first later to maint). + + * "git add -p" rewritten in C regressed hunk splitting in some cases, + which has been corrected. + (merge 7008ddc645 pw/add-p-hunk-split-fix later to maint). + + * "git fetch --negotiate-only" is an internal command used by "git + push" to figure out which part of our history is missing from the + other side. It should never recurse into submodules even when + fetch.recursesubmodules configuration variable is set, nor it + should trigger "gc". The code has been tightened up to ensure it + only does common ancestry discovery and nothing else. + (merge de4eaae63a gc/fetch-negotiate-only-early-return later to maint). + + * The code path that verifies signatures made with ssh were made to + work better on a system with CRLF line endings. + (merge caeef01ea7 fs/ssh-signing-crlf later to maint). + + * "git sparse-checkout init" failed to write into $GIT_DIR/info + directory when the repository was created without one, which has + been corrected to auto-create it. + (merge 7f44842ac1 jt/sparse-checkout-leading-dir-fix later to maint). + + * Cloning from a repository that does not yet have any branches or + tags but has other refs resulted in a "remote transport reported + error", which has been corrected. + (merge dccea605b6 jt/clone-not-quite-empty later to maint). + + * Mark in various places in the code that the sparse index and the + split index features are mutually incompatible. + (merge 451b66c533 js/sparse-vs-split-index later to maint). + + * Update the logic to compute alignment requirement for our mem-pool. + (merge e38bcc66d8 jc/mem-pool-alignment later to maint). + + * Pick a better random number generator and use it when we prepare + temporary filenames. + (merge 47efda967c bc/csprng-mktemps later to maint). + + * Update the contributor-facing documents on proposed log messages. + (merge cdba0295b0 jc/doc-log-messages later to maint). + + * When "git fetch --prune" failed to prune the refs it wanted to + prune, the command issued error messages but exited with exit + status 0, which has been corrected. + (merge c9e04d905e tg/fetch-prune-exit-code-fix later to maint). + + * Problems identified by Coverity in the reftable code have been + corrected. + (merge 01033de49f hn/reftable-coverity-fixes later to maint). + + * A bug that made multi-pack bitmap and the object order out-of-sync, + making the .midx data corrupt, has been fixed. + (merge f8b60cf99b tb/midx-bitmap-corruption-fix later to maint). + + * The build procedure has been taught to notice older version of zlib + and enable our replacement uncompress2() automatically. + (merge 07564773c2 ab/auto-detect-zlib-compress2 later to maint). + + * Interaction between fetch.negotiationAlgorithm and + feature.experimental configuration variables has been corrected. + (merge 714edc620c en/fetch-negotiation-default-fix later to maint). + + * "git diff --diff-filter=aR" is now parsed correctly. + (merge 75408ca949 js/diff-filter-negation-fix later to maint). + + * When "git subtree" wants to create a merge, it used "git merge" and + let it be affected by end-user's "merge.ff" configuration, which + has been corrected. + (merge 9158a3564a tk/subtree-merge-not-ff-only later to maint). + + * Unlike "git apply", "git patch-id" did not handle patches with + hunks that has only 1 line in either preimage or postimage, which + has been corrected. + (merge 757e75c81e jz/patch-id-hunk-header-parsing-fix later to maint). + + * "receive-pack" checks if it will do any ref updates (various + conditions could reject a push) before received objects are taken + out of the temporary directory used for quarantine purposes, so + that a push that is known-to-fail will not leave crufts that a + future "gc" needs to clean up. + (merge 5407764069 cb/clear-quarantine-early-on-all-ref-update-errors later to maint). + + * Because a deletion of ref would need to remove it from both the + loose ref store and the packed ref store, a delete-ref operation + that logically removes one ref may end up invoking ref-transaction + hook twice, which has been corrected. + (merge 2ed1b64ebd ps/avoid-unnecessary-hook-invocation-with-packed-refs later to maint). + + * When there is no object to write .bitmap file for, "git + multi-pack-index" triggered an error, instead of just skipping, + which has been corrected. + (merge eb57277ba3 tb/midx-no-bitmap-for-no-objects later to maint). + + * "git cmd -h" outside a repository should error out cleanly for many + commands, but instead it hit a BUG(), which has been corrected. + (merge 87ad07d735 js/short-help-outside-repo-fix later to maint). + + * "working tree" and "per-worktree ref" were in glossary, but + "worktree" itself wasn't, which has been corrected. + (merge 2df5387ed0 jc/glossary-worktree later to maint). + + * Other code cleanup, docfix, build fix, etc. + (merge cfc5cf428b jc/find-header later to maint). + (merge 40e7cfdd46 jh/p4-fix-use-of-process-error-exception later to maint). + (merge 727e6ea350 jh/p4-spawning-external-commands-cleanup later to maint). + (merge 0a6adc26e2 rs/grep-expr-cleanup later to maint). + (merge 4ed7dfa713 po/readme-mention-contributor-hints later to maint). + (merge 6046f7a91c en/plug-leaks-in-merge later to maint). + (merge 8c591dbfce bc/clarify-eol-attr later to maint). + (merge 518e15db74 rs/parse-options-lithelp-help later to maint). + (merge cbac0076ef gh/doc-typos later to maint). + (merge ce14de03db ab/no-errno-from-resolve-ref-unsafe later to maint). + (merge 2826ffad8c rc/negotiate-only-typofix later to maint). + (merge 0f03f04c5c en/sparse-checkout-leakfix later to maint). + (merge 74f3390dde sy/diff-usage-typofix later to maint). + (merge 45d0212a71 ll/doc-mktree-typofix later to maint). + (merge e9b272e4c1 js/no-more-legacy-stash later to maint). + (merge 6798b08e84 ab/do-not-hide-failures-in-git-dot-pm later to maint). + (merge 9325285df4 po/doc-check-ignore-markup-fix later to maint). + (merge cd26cd6c7c sy/modernize-t-lib-read-tree-m-3way later to maint). + (merge d17294a05e ab/hash-object-leakfix later to maint). + (merge b8403129d3 jd/t0015-modernize later to maint). diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 92b80d94d4..a6121d1d42 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -110,6 +110,35 @@ run `git diff --check` on your changes before you commit. [[describe-changes]] === Describe your changes well. +The log message that explains your changes is just as important as the +changes themselves. Your code may be clearly written with in-code +comment to sufficiently explain how it works with the surrounding +code, but those who need to fix or enhance your code in the future +will need to know _why_ your code does what it does, for a few +reasons: + +. Your code may be doing something differently from what you wanted it + to do. Writing down what you actually wanted to achieve will help + them fix your code and make it do what it should have been doing + (also, you often discover your own bugs yourself, while writing the + log message to summarize the thought behind it). + +. Your code may be doing things that were only necessary for your + immediate needs (e.g. "do X to directories" without implementing or + even designing what is to be done on files). Writing down why you + excluded what the code does not do will help guide future developers. + Writing down "we do X to directories, because directories have + characteristic Y" would help them infer "oh, files also have the same + characteristic Y, so perhaps doing X to them would also make sense?". + Saying "we don't do the same X to files, because ..." will help them + decide if the reasoning is sound (in which case they do not waste + time extending your code to cover files), or reason differently (in + which case, they can explain why they extend your code to cover + files, too). + +The goal of your log message is to convey the _why_ behind your +change to help future developers. + The first line of the commit message should be a short description (50 characters is the soft limit, see DISCUSSION in linkgit:git-commit[1]), and should skip the full stop. It is also conventional in most cases to @@ -142,6 +171,13 @@ The body should provide a meaningful commit message, which: . alternate solutions considered but discarded, if any. +[[present-tense]] +The problem statement that describes the status quo is written in the +present tense. Write "The code does X when it is given input Y", +instead of "The code used to do Y when given input X". You do not +have to say "Currently"---the status quo in the problem statement is +about the code _without_ your change, by project convention. + [[imperative-mood]] Describe your changes in imperative mood, e.g. "make xyzzy do frotz" instead of "[This patch] makes xyzzy do frotz" or "[I] changed xyzzy diff --git a/Documentation/config.txt b/Documentation/config.txt index b168f02dc3..bf3e512921 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -159,6 +159,33 @@ 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 + 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 + the config files will be scanned for remote URLs (without + applying any values). If there exists at least one remote URL + that matches this pattern, the include condition is met. ++ +Files included by this option (directly or indirectly) are not allowed +to contain remote URLs. ++ +Note that unlike other includeIf conditions, resolving this condition +relies on information that is not yet known at the point of reading the +condition. A typical use case is this option being present as a +system-level or global-level config, and the remote URL being in a +local-level config; hence the need to scan ahead when resolving this +condition. In order to avoid the chicken-and-egg problem in which +potentially-included files can affect whether such files are potentially +included, Git breaks the cycle by prohibiting these files from affecting +the resolution of these conditions (thus, prohibiting them from +declaring remote URLs). ++ +As for the naming of this keyword, it is for forwards compatibiliy with +a naming scheme that supports more variable-based include conditions, +but currently Git only supports the exact keyword described above. + A few more notes on matching via `gitdir` and `gitdir/i`: * Symlinks in `$GIT_DIR` are not resolved before matching. @@ -226,6 +253,14 @@ Example ; currently checked out [includeIf "onbranch:foo-branch"] path = foo.inc + +; include only if a remote with the given URL exists (note +; that such a URL may be provided later in a file or in a +; file read after this file is read, as seen in this example) +[includeIf "hasconfig:remote.*.url:https://example.com/**"] + path = foo.inc +[remote "origin"] + url = https://example.com/git ---- Values diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt index 063eec2511..adee26fbbb 100644 --- a/Documentation/config/advice.txt +++ b/Documentation/config/advice.txt @@ -116,6 +116,9 @@ advice.*:: submoduleAlternateErrorStrategyDie:: Advice shown when a submodule.alternateErrorStrategy option configured to "die" causes a fatal error. + submodulesNotUpdated:: + Advice shown when a user runs a submodule command that fails + because `git submodule update --init` was not run. addIgnoredFile:: Advice shown if a user attempts to add an ignored file to the index. diff --git a/Documentation/config/fetch.txt b/Documentation/config/fetch.txt index 63748c02b7..cd65d236b4 100644 --- a/Documentation/config/fetch.txt +++ b/Documentation/config/fetch.txt @@ -56,18 +56,19 @@ fetch.output:: OUTPUT in linkgit:git-fetch[1] for detail. fetch.negotiationAlgorithm:: - Control how information about the commits in the local repository is - sent when negotiating the contents of the packfile to be sent by the - server. Set to "skipping" to use an algorithm that skips commits in an - effort to converge faster, but may result in a larger-than-necessary - packfile; or set to "noop" to not send any information at all, which - will almost certainly result in a larger-than-necessary packfile, but - will skip the negotiation step. - The default is "default" which instructs Git to use the default algorithm - that never skips commits (unless the server has acknowledged it or one - of its descendants). If `feature.experimental` is enabled, then this - setting defaults to "skipping". - Unknown values will cause 'git fetch' to error out. + Control how information about the commits in the local repository + is sent when negotiating the contents of the packfile to be sent by + the server. Set to "consecutive" to use an algorithm that walks + over consecutive commits checking each one. Set to "skipping" to + use an algorithm that skips commits in an effort to converge + faster, but may result in a larger-than-necessary packfile; or set + to "noop" to not send any information at all, which will almost + certainly result in a larger-than-necessary packfile, but will skip + the negotiation step. Set to "default" to override settings made + previously and use the default behaviour. The default is normally + "consecutive", but if `feature.experimental` is true, then the + default is "skipping". Unknown values will cause 'git fetch' to + error out. + See also the `--negotiate-only` and `--negotiation-tip` options to linkgit:git-fetch[1]. diff --git a/Documentation/config/gpg.txt b/Documentation/config/gpg.txt index 0cb189a077..86892ada77 100644 --- a/Documentation/config/gpg.txt +++ b/Documentation/config/gpg.txt @@ -37,7 +37,7 @@ gpg.minTrustLevel:: gpg.ssh.defaultKeyCommand:: This command that will be run when user.signingkey is not set and a ssh signature is requested. On successful exit a valid ssh public key is - expected in the first line of its output. To automatically use the first + expected in the first line of its output. To automatically use the first available key from your ssh-agent set this to "ssh-add -L". gpg.ssh.allowedSignersFile:: @@ -66,7 +66,7 @@ This way only committers with an already valid key can add or change keys in the + Since OpensSSH 8.8 this file allows specifying a key lifetime using valid-after & valid-before options. Git will mark signatures as valid if the signing key was -valid at the time of the signatures creation. This allows users to change a +valid at the time of the signature's creation. This allows users to change a signing key without invalidating all previously made signatures. + Using a SSH CA key with the cert-authority option diff --git a/Documentation/config/stash.txt b/Documentation/config/stash.txt index 9ed775281f..b9f609ed76 100644 --- a/Documentation/config/stash.txt +++ b/Documentation/config/stash.txt @@ -1,10 +1,3 @@ -stash.useBuiltin:: - Unused configuration variable. Used in Git versions 2.22 to - 2.26 as an escape hatch to enable the legacy shellscript - implementation of stash. Now the built-in rewrite of it in C - is always used. Setting this will emit a warning, to alert any - remaining users that setting this now does nothing. - stash.showIncludeUntracked:: If this is set to true, the `git stash show` command will show the untracked files of a stash entry. Defaults to false. See diff --git a/Documentation/config/submodule.txt b/Documentation/config/submodule.txt index ee454f8126..6490527b45 100644 --- a/Documentation/config/submodule.txt +++ b/Documentation/config/submodule.txt @@ -59,18 +59,33 @@ submodule.active:: submodule.recurse:: A boolean indicating if commands should enable the `--recurse-submodules` - option by default. - Applies to all commands that support this option - (`checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`, `reset`, - `restore` and `switch`) except `clone` and `ls-files`. + option by default. Defaults to false. ++ +When set to true, it can be deactivated via the +`--no-recurse-submodules` option. Note that some Git commands +lacking this option may call some of the above commands affected by +`submodule.recurse`; for instance `git remote update` will call +`git fetch` but does not have a `--no-recurse-submodules` option. +For these commands a workaround is to temporarily change the +configuration value by using `git -c submodule.recurse=0`. ++ +The following list shows the commands that accept +`--recurse-submodules` and whether they are supported by this +setting. + +* `checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`, +`reset`, `restore` and `switch` are always supported. +* `clone` and `ls-files` are not supported. +* `branch` is supported only if `submodule.propagateBranches` is +enabled + +submodule.propagateBranches:: + [EXPERIMENTAL] A boolean that enables branching support when + using `--recurse-submodules` or `submodule.recurse=true`. + Enabling this will allow certain commands to accept + `--recurse-submodules` and certain commands that already accept + `--recurse-submodules` will now consider branches. Defaults to false. - When set to true, it can be deactivated via the - `--no-recurse-submodules` option. Note that some Git commands - lacking this option may call some of the above commands affected by - `submodule.recurse`; for instance `git remote update` will call - `git fetch` but does not have a `--no-recurse-submodules` option. - For these commands a workaround is to temporarily change the - configuration value by using `git -c submodule.recurse=0`. submodule.fetchJobs:: Specifies how many submodules are fetched/cloned at the same time. diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index c89d530d3d..3674ac48e9 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -34,7 +34,7 @@ endif::git-diff[] endif::git-format-patch[] ifdef::git-log[] ---diff-merges=(off|none|on|first-parent|1|separate|m|combined|c|dense-combined|cc):: +--diff-merges=(off|none|on|first-parent|1|separate|m|combined|c|dense-combined|cc|remerge|r):: --no-diff-merges:: Specify diff format to be used for merge commits. Default is {diff-merges-default} unless `--first-parent` is in use, in which case @@ -64,6 +64,18 @@ ifdef::git-log[] each of the parents. Separate log entry and diff is generated for each parent. + +--diff-merges=remerge::: +--diff-merges=r::: +--remerge-diff::: + With this option, two-parent merge commits are remerged to + create a temporary tree object -- potentially containing files + with conflict markers and such. A diff is then shown between + that temporary tree and the actual merge commit. ++ +The output emitted when this option is used is subject to change, and +so is its interaction with other options (unless explicitly +documented). ++ --diff-merges=combined::: --diff-merges=c::: -c::: @@ -616,11 +628,8 @@ ifndef::git-format-patch[] Also, these upper-case letters can be downcased to exclude. E.g. `--diff-filter=ad` excludes added and deleted paths. + -Note that not all diffs can feature all types. For instance, diffs -from the index to the working tree can never have Added entries -(because the set of paths included in the diff is limited by what is in -the index). Similarly, copied and renamed entries cannot appear if -detection for those types is disabled. +Note that not all diffs can feature all types. For instance, copied and +renamed entries cannot appear if detection for those types is disabled. -S<string>:: Look for differences that change the number of occurrences of diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt index e967ff1874..f903683189 100644 --- a/Documentation/fetch-options.txt +++ b/Documentation/fetch-options.txt @@ -71,6 +71,7 @@ configuration variables documented in linkgit:git-config[1], and the ancestors of the provided `--negotiation-tip=*` arguments, which we have in common with the server. + +This is incompatible with `--recurse-submodules=[yes|on-demand]`. Internally this is used to implement the `push.negotiate` option, see linkgit:git-config[1]. diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index 731e340cbc..c8b4f9ce3c 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -16,7 +16,8 @@ SYNOPSIS [--points-at <object>] [--format=<format>] [(-r | --remotes) | (-a | --all)] [--list] [<pattern>...] -'git branch' [--track[=(direct|inherit)] | --no-track] [-f] <branchname> [<start-point>] +'git branch' [--track[=(direct|inherit)] | --no-track] [-f] + [--recurse-submodules] <branchname> [<start-point>] 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>] 'git branch' --unset-upstream [<branchname>] 'git branch' (-m | -M) [<oldbranch>] <newbranch> @@ -235,6 +236,22 @@ how the `branch.<name>.remote` and `branch.<name>.merge` options are used. Do not set up "upstream" configuration, even if the branch.autoSetupMerge configuration variable is set. +--recurse-submodules:: + THIS OPTION IS EXPERIMENTAL! Causes the current command to + recurse into submodules if `submodule.propagateBranches` is + enabled. See `submodule.propagateBranches` in + linkgit:git-config[1]. Currently, only branch creation is + supported. ++ +When used in branch creation, a new branch <branchname> will be created +in the superproject and all of the submodules in the superproject's +<start-point>. In submodules, the branch will point to the submodule +commit in the superproject's <start-point> but the branch's tracking +information will be set up based on the submodule's branches and remotes +e.g. `git branch --recurse-submodules topic origin/main` will create the +submodule branch "topic" that points to the submodule commit in the +superproject's "origin/main", but tracks the submodule's "origin/main". + --set-upstream:: As this option had confusing syntax, it is no longer supported. Please use `--track` or `--set-upstream-to` instead. diff --git a/Documentation/git-cat-file.txt b/Documentation/git-cat-file.txt index 27b27e2b30..bef76f4dd0 100644 --- a/Documentation/git-cat-file.txt +++ b/Documentation/git-cat-file.txt @@ -9,8 +9,14 @@ git-cat-file - Provide content or type and size information for repository objec SYNOPSIS -------- [verse] -'git cat-file' (-t [--allow-unknown-type]| -s [--allow-unknown-type]| -e | -p | <type> | --textconv | --filters ) [--path=<path>] <object> -'git cat-file' (--batch[=<format>] | --batch-check[=<format>]) [ --textconv | --filters ] [--follow-symlinks] +'git cat-file' <type> <object> +'git cat-file' (-e | -p) <object> +'git cat-file' (-t | -s) [--allow-unknown-type] <object> +'git cat-file' (--batch | --batch-check) [--batch-all-objects] + [--buffer] [--follow-symlinks] [--unordered] + [--textconv | --filters] +'git cat-file' (--textconv | --filters) + [<rev>:<path|tree-ish> | --path=<path|tree-ish> <rev>] DESCRIPTION ----------- diff --git a/Documentation/git-check-ignore.txt b/Documentation/git-check-ignore.txt index 0c3924a63d..2892799e32 100644 --- a/Documentation/git-check-ignore.txt +++ b/Documentation/git-check-ignore.txt @@ -33,7 +33,7 @@ OPTIONS 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 - means the path is excluded, but if the pattern begins with '!' + means the path is excluded, but if the pattern begins with "`!`" then it is a negated pattern and matching it means the path is NOT excluded.) + @@ -77,7 +77,7 @@ If `--verbose` is specified, the output is a series of lines of the form: <pathname> is the path of a file being queried, <pattern> is the matching pattern, <source> is the pattern's source file, and <linenum> is the line number of the pattern within that source. If the pattern -contained a `!` prefix or `/` suffix, it will be preserved in the +contained a "`!`" prefix or "`/`" suffix, it will be preserved in the output. <source> will be an absolute path when referring to the file configured by `core.excludesFile`, or relative to the repository root when referring to `.git/info/exclude` or a per-directory exclude file. diff --git a/Documentation/git-checkout-index.txt b/Documentation/git-checkout-index.txt index 4d33e7be0f..01dbd5cbf5 100644 --- a/Documentation/git-checkout-index.txt +++ b/Documentation/git-checkout-index.txt @@ -12,6 +12,7 @@ SYNOPSIS 'git checkout-index' [-u] [-q] [-a] [-f] [-n] [--prefix=<string>] [--stage=<number>|all] [--temp] + [--ignore-skip-worktree-bits] [-z] [--stdin] [--] [<file>...] @@ -37,8 +38,9 @@ OPTIONS -a:: --all:: - checks out all files in the index. Cannot be used - together with explicit filenames. + checks out all files in the index except for those with the + skip-worktree bit set (see `--ignore-skip-worktree-bits`). + Cannot be used together with explicit filenames. -n:: --no-create:: @@ -59,6 +61,10 @@ OPTIONS write the content to temporary files. The temporary name associations will be written to stdout. +--ignore-skip-worktree-bits:: + Check out all files, including those with the skip-worktree bit + set. + --stdin:: Instead of taking list of paths from the command line, read list of paths from the standard input. Paths are diff --git a/Documentation/git-hook.txt b/Documentation/git-hook.txt new file mode 100644 index 0000000000..77c3a8ad90 --- /dev/null +++ b/Documentation/git-hook.txt @@ -0,0 +1,45 @@ +git-hook(1) +=========== + +NAME +---- +git-hook - Run git hooks + +SYNOPSIS +-------- +[verse] +'git hook' run [--ignore-missing] <hook-name> [-- <hook-args>] + +DESCRIPTION +----------- + +A command interface to running git hooks (see linkgit:githooks[5]), +for use by other scripted git commands. + +SUBCOMMANDS +----------- + +run:: + Run the `<hook-name>` hook. See linkgit:githooks[5] for + supported hook names. ++ + +Any positional arguments to the hook should be passed after a +mandatory `--` (or `--end-of-options`, see linkgit:gitcli[7]). See +linkgit:githooks[5] for arguments hooks might expect (if any). + +OPTIONS +------- + +--ignore-missing:: + Ignore any missing hook by quietly returning zero. Used for + tools that want to do a blind one-shot run of a hook that may + or may not be present. + +SEE ALSO +-------- +linkgit:githooks[5] + +GIT +--- +Part of the linkgit:git[1] suite diff --git a/Documentation/git-mktree.txt b/Documentation/git-mktree.txt index 27fe2b32e1..76b44f4da1 100644 --- a/Documentation/git-mktree.txt +++ b/Documentation/git-mktree.txt @@ -31,7 +31,7 @@ OPTIONS --batch:: Allow building of more than one tree object before exiting. Each - tree is separated by as single blank line. The final new-line is + tree is separated by a single blank line. The final new-line is optional. Note - if the `-z` option is used, lines are terminated with NUL. diff --git a/Documentation/git-name-rev.txt b/Documentation/git-name-rev.txt index 5cb0eb0855..ec8a27ce8b 100644 --- a/Documentation/git-name-rev.txt +++ b/Documentation/git-name-rev.txt @@ -42,11 +42,37 @@ OPTIONS --all:: List all commits reachable from all refs ---stdin:: +--annotate-stdin:: Transform stdin by substituting all the 40-character SHA-1 hexes (say $hex) with "$hex ($rev_name)". When used with --name-only, substitute with "$rev_name", omitting $hex - altogether. Intended for the scripter's use. + altogether. ++ +For example: ++ +----------- +$ cat sample.txt + +An abbreviated revision 2ae0a9cb82 will not be substituted. +The full name after substitution is 2ae0a9cb8298185a94e5998086f380a355dd8907, +while its tree object is 70d105cc79e63b81cfdcb08a15297c23e60b07ad + +$ git name-rev --annotate-stdin <sample.txt + +An abbreviated revision 2ae0a9cb82 will not be substituted. +The full name after substitution is 2ae0a9cb8298185a94e5998086f380a355dd8907 (master), +while its tree object is 70d105cc79e63b81cfdcb08a15297c23e60b07ad + +$ git name-rev --name-only --annotate-stdin <sample.txt + +An abbreviated revision 2ae0a9cb82 will not be substituted. +The full name after substitution is master, +while its tree object is 70d105cc79e63b81cfdcb08a15297c23e60b07ad +----------- + +--stdin:: + This option is deprecated in favor of 'git name-rev --annotate-stdin'. + They are functionally equivalent. --name-only:: Instead of printing both the SHA-1 and the name, print only diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 83fd4e19a4..60984a4682 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -160,11 +160,12 @@ unspecified. ^^^^^ This attribute sets a specific line-ending style to be used in the -working directory. It enables end-of-line conversion without any -content checks, effectively setting the `text` attribute. Note that -setting this attribute on paths which are in the index with CRLF line -endings may make the paths to be considered dirty. Adding the path to -the index again will normalize the line endings in the index. +working directory. This attribute has effect only if the `text` +attribute is set or unspecified, or if it is set to `auto` and the file +is detected as text. Note that setting this attribute on paths which +are in the index with CRLF line endings may make the paths to be +considered dirty. Adding the path to the index again will normalize the +line endings in the index. Set to string value "crlf":: diff --git a/Documentation/gitcli.txt b/Documentation/gitcli.txt index 92e4ba6a2f..1819a5a185 100644 --- a/Documentation/gitcli.txt +++ b/Documentation/gitcli.txt @@ -19,6 +19,15 @@ Many commands take revisions (most often "commits", but sometimes "tree-ish", depending on the context and command) and paths as their arguments. Here are the rules: + * Options come first and then args. + A subcommand may take dashed options (which may take their own + arguments, e.g. "--max-parents 2") and arguments. You SHOULD + give dashed options first and then arguments. Some commands may + accept dashed options after you have already gave non-option + arguments (which may make the command ambiguous), but you should + not rely on it (because eventually we may find a way to fix + these ambiguity by enforcing the "options then args" rule). + * Revisions come first and then paths. E.g. in `git diff v1.0 v2.0 arch/x86 include/asm-x86`, `v1.0` and `v2.0` are revisions and `arch/x86` and `include/asm-x86` @@ -72,24 +81,24 @@ you will. Here are the rules regarding the "flags" that you should follow when you are scripting Git: - * it's preferred to use the non-dashed form of Git commands, which means that + * It's preferred to use the non-dashed form of Git commands, which means that you should prefer `git foo` to `git-foo`. - * splitting short options to separate words (prefer `git foo -a -b` + * Splitting short options to separate words (prefer `git foo -a -b` to `git foo -ab`, the latter may not even work). - * when a command-line option takes an argument, use the 'stuck' form. In + * When a command-line option takes an argument, use the 'stuck' form. In other words, write `git foo -oArg` instead of `git foo -o Arg` for short options, and `git foo --long-opt=Arg` instead of `git foo --long-opt Arg` for long options. An option that takes optional option-argument must be written in the 'stuck' form. - * when you give a revision parameter to a command, make sure the parameter is + * When you give a revision parameter to a command, make sure the parameter is not ambiguous with a name of a file in the work tree. E.g. do not write `git log -1 HEAD` but write `git log -1 HEAD --`; the former will not work if you happen to have a file called `HEAD` in the work tree. - * many commands allow a long option `--option` to be abbreviated + * Many commands allow a long option `--option` to be abbreviated only to their unique prefix (e.g. if there is no other option whose name begins with `opt`, you may be able to spell `--opt` to invoke the `--option` flag), but you should fully spell them out diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt index b51959ff94..a16e62bc8c 100644 --- a/Documentation/githooks.txt +++ b/Documentation/githooks.txt @@ -698,6 +698,10 @@ and "0" meaning they were not. Only one parameter should be set to "1" when the hook runs. The hook running passing "1", "1" should not be possible. +SEE ALSO +-------- +linkgit:git-hook[1] + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt index c077971335..aa2f41f5e7 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -312,7 +312,7 @@ Pathspecs are used on the command line of "git ls-files", "git ls-tree", "git add", "git grep", "git diff", "git checkout", and many other commands to limit the scope of operations to some subset of the tree or -worktree. See the documentation of each command for whether +working tree. See the documentation of each command for whether paths are relative to the current directory or toplevel. The pathspec syntax is as follows: + @@ -446,7 +446,7 @@ exclude;; interface than the <<def_plumbing,plumbing>>. [[def_per_worktree_ref]]per-worktree ref:: - Refs that are per-<<def_working_tree,worktree>>, rather than + Refs that are per-<<def_worktree,worktree>>, rather than global. This is presently only <<def_HEAD,HEAD>> and any refs that start with `refs/bisect/`, but might later include other unusual refs. @@ -669,3 +669,12 @@ The most notable example is `HEAD`. The tree of actual checked out files. The working tree normally contains the contents of the <<def_HEAD,HEAD>> commit's tree, plus any local changes that you have made but not yet committed. + +[[def_worktree]]worktree:: + A repository can have zero (i.e. bare repository) or one or + more worktrees attached to it. One "worktree" consists of a + "working tree" and repository metadata, most of which are + shared among other worktrees of a single repository, and + some of which are maintained separately per worktree + (e.g. the index, HEAD and pseudorefs like MERGE_HEAD, + per-worktree refs and per-worktree configuration file). diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 43a86fa562..fd4f4e26c9 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -122,19 +122,27 @@ again. Equivalent forms are `--min-parents=0` (any commit has 0 or more parents) and `--max-parents=-1` (negative numbers denote no upper limit). --first-parent:: - Follow only the first parent commit upon seeing a merge - commit. This option can give a better overview when - viewing the evolution of a particular topic branch, - because merges into a topic branch tend to be only about - adjusting to updated upstream from time to time, and - this option allows you to ignore the individual commits - brought in to your history by such a merge. + When finding commits to include, follow only the first + parent commit upon seeing a merge commit. This option + can give a better overview when viewing the evolution of + a particular topic branch, because merges into a topic + branch tend to be only about adjusting to updated upstream + from time to time, and this option allows you to ignore + the individual commits brought in to your history by such + a merge. ifdef::git-log[] + This option also changes default diff format for merge commits to `first-parent`, see `--diff-merges=first-parent` for details. endif::git-log[] +--exclude-first-parent-only:: + When finding commits to exclude (with a '{caret}'), follow only + the first parent commit upon seeing a merge commit. + This can be used to find the set of changes in a topic branch + from the point where it diverged from the remote branch, given + that arbitrary merges can be valid topic branch changes. + --not:: Reverses the meaning of the '{caret}' prefix (or lack thereof) for all following revision specifiers, up to the next `--not`. diff --git a/Documentation/technical/multi-pack-index.txt b/Documentation/technical/multi-pack-index.txt index b39c69da8c..f2221d2b44 100644 --- a/Documentation/technical/multi-pack-index.txt +++ b/Documentation/technical/multi-pack-index.txt @@ -24,6 +24,7 @@ and their offsets into multiple packfiles. It contains: ** An offset within the jth packfile for the object. * If large offsets are required, we use another list of large offsets similar to version 2 pack-indexes. +- An optional list of objects in pseudo-pack order (used with MIDX bitmaps). Thus, we can provide O(log N) lookup time for any number of packfiles. diff --git a/Documentation/technical/pack-format.txt b/Documentation/technical/pack-format.txt index 8d2f42f29e..6d3efb7d16 100644 --- a/Documentation/technical/pack-format.txt +++ b/Documentation/technical/pack-format.txt @@ -376,6 +376,11 @@ CHUNK DATA: [Optional] Object Large Offsets (ID: {'L', 'O', 'F', 'F'}) 8-byte offsets into large packfiles. + [Optional] Bitmap pack order (ID: {'R', 'I', 'D', 'X'}) + A list of MIDX positions (one per object in the MIDX, num_objects in + total, each a 4-byte unsigned integer in network byte order), sorted + according to their relative bitmap/pseudo-pack positions. + TRAILER: Index checksum of the above contents. @@ -456,9 +461,5 @@ In short, a MIDX's pseudo-pack is the de-duplicated concatenation of objects in packs stored by the MIDX, laid out in pack order, and the packs arranged in MIDX order (with the preferred pack coming first). -Finally, note that the MIDX's reverse index is not stored as a chunk in -the multi-pack-index itself. This is done because the reverse index -includes the checksum of the pack or MIDX to which it belongs, which -makes it impossible to write in the MIDX. To avoid races when rewriting -the MIDX, a MIDX reverse index includes the MIDX's checksum in its -filename (e.g., `multi-pack-index-xyz.rev`). +The MIDX's reverse index is stored in the optional 'RIDX' chunk within +the MIDX itself. diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index b3cf1ff939..f964f9ba7d 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.35.1 +DEF_VER=v2.35.GIT LF=' ' @@ -234,6 +234,14 @@ all:: # Define NO_TRUSTABLE_FILEMODE if your filesystem may claim to support # the executable mode bit, but doesn't really do so. # +# Define CSPRNG_METHOD to "arc4random" if your system has arc4random and +# arc4random_buf, "libbsd" if your system has those functions from libbsd, +# "getrandom" if your system has getrandom, "getentropy" if your system has +# getentropy, "rtlgenrandom" for RtlGenRandom (Windows only), or "openssl" if +# you'd want to use the OpenSSL CSPRNG. You may set multiple options with +# spaces, in which case a suitable option will be chosen. If unset or set to +# anything else, defaults to using "/dev/urandom". +# # Define NEEDS_MODE_TRANSLATION if your OS strays from the typical file type # bits in mode values (e.g. z/OS defines I_SFMT to 0xFF000000 as opposed to the # usual 0xF000). @@ -256,8 +264,6 @@ all:: # # Define NO_DEFLATE_BOUND if your zlib does not have deflateBound. # -# Define NO_UNCOMPRESS2 if your zlib does not have uncompress2. -# # Define NO_NORETURN if using buggy versions of gcc 4.6+ and profile feedback, # as the compiler can crash (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=49299) # @@ -693,6 +699,7 @@ TEST_BUILTINS_OBJS += test-bloom.o TEST_BUILTINS_OBJS += test-chmtime.o TEST_BUILTINS_OBJS += test-config.o TEST_BUILTINS_OBJS += test-crontab.o +TEST_BUILTINS_OBJS += test-csprng.o TEST_BUILTINS_OBJS += test-ctype.o TEST_BUILTINS_OBJS += test-date.o TEST_BUILTINS_OBJS += test-delta.o @@ -862,6 +869,7 @@ LIB_OBJS += commit-reach.o LIB_OBJS += commit.o LIB_OBJS += compat/obstack.o LIB_OBJS += compat/terminal.o +LIB_OBJS += compat/zlib-uncompress2.o LIB_OBJS += config.o LIB_OBJS += connect.o LIB_OBJS += connected.o @@ -1109,6 +1117,7 @@ BUILTIN_OBJS += builtin/get-tar-commit-id.o BUILTIN_OBJS += builtin/grep.o BUILTIN_OBJS += builtin/hash-object.o BUILTIN_OBJS += builtin/help.o +BUILTIN_OBJS += builtin/hook.o BUILTIN_OBJS += builtin/index-pack.o BUILTIN_OBJS += builtin/init-db.o BUILTIN_OBJS += builtin/interpret-trailers.o @@ -1194,7 +1203,8 @@ THIRD_PARTY_SOURCES += compat/regex/% THIRD_PARTY_SOURCES += sha1collisiondetection/% THIRD_PARTY_SOURCES += sha1dc/% -GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) +# xdiff and reftable libs may in turn depend on what is in libgit.a +GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(LIB_FILE) EXTLIBS = GIT_USER_AGENT = git/$(GIT_VERSION) @@ -1726,11 +1736,6 @@ ifdef NO_DEFLATE_BOUND BASIC_CFLAGS += -DNO_DEFLATE_BOUND endif -ifdef NO_UNCOMPRESS2 - BASIC_CFLAGS += -DNO_UNCOMPRESS2 - REFTABLE_OBJS += compat/zlib-uncompress2.o -endif - ifdef NO_POSIX_GOODIES BASIC_CFLAGS += -DNO_POSIX_GOODIES endif @@ -1908,6 +1913,31 @@ ifdef HAVE_GETDELIM BASIC_CFLAGS += -DHAVE_GETDELIM endif +ifneq ($(findstring arc4random,$(CSPRNG_METHOD)),) + BASIC_CFLAGS += -DHAVE_ARC4RANDOM +endif + +ifneq ($(findstring libbsd,$(CSPRNG_METHOD)),) + BASIC_CFLAGS += -DHAVE_ARC4RANDOM_LIBBSD + EXTLIBS += -lbsd +endif + +ifneq ($(findstring getrandom,$(CSPRNG_METHOD)),) + BASIC_CFLAGS += -DHAVE_GETRANDOM +endif + +ifneq ($(findstring getentropy,$(CSPRNG_METHOD)),) + BASIC_CFLAGS += -DHAVE_GETENTROPY +endif + +ifneq ($(findstring rtlgenrandom,$(CSPRNG_METHOD)),) + BASIC_CFLAGS += -DHAVE_RTLGENRANDOM +endif + +ifneq ($(findstring openssl,$(CSPRNG_METHOD)),) + BASIC_CFLAGS += -DHAVE_OPENSSL_CSPRNG +endif + ifneq ($(PROCFS_EXECUTABLE_PATH),) procfs_executable_path_SQ = $(subst ','\'',$(PROCFS_EXECUTABLE_PATH)) BASIC_CFLAGS += '-DPROCFS_EXECUTABLE_PATH="$(procfs_executable_path_SQ)"' @@ -32,10 +32,16 @@ installed). The user discussion and development of Git take place on the Git mailing list -- everyone is welcome to post bug reports, feature requests, comments and patches to git@vger.kernel.org (read -[Documentation/SubmittingPatches][] for instructions on patch submission). +[Documentation/SubmittingPatches][] for instructions on patch submission +and [Documentation/CodingGuidelines][]). + +Those wishing to help with error message, usage and informational message +string translations (localization l10) should see [po/README.md][] +(a `po` file is a Portable Object file that holds the translations). + To subscribe to the list, send an email with just "subscribe git" in -the body to majordomo@vger.kernel.org. The mailing list archives are -available at <https://lore.kernel.org/git/>, +the body to majordomo@vger.kernel.org (not the Git list). The mailing +list archives are available at <https://lore.kernel.org/git/>, <http://marc.info/?l=git> and other archival sites. Issues which are security relevant should be disclosed privately to @@ -64,3 +70,5 @@ and the name as (depending on your mood): [Documentation/giteveryday.txt]: Documentation/giteveryday.txt [Documentation/gitcvs-migration.txt]: Documentation/gitcvs-migration.txt [Documentation/SubmittingPatches]: Documentation/SubmittingPatches +[Documentation/CodingGuidelines]: Documentation/CodingGuidelines +[po/README.md]: po/README.md @@ -1 +1 @@ -Documentation/RelNotes/2.35.1.txt
\ No newline at end of file +Documentation/RelNotes/2.36.0.txt
\ No newline at end of file diff --git a/add-patch.c b/add-patch.c index 573eef0cc4..55d719f784 100644 --- a/add-patch.c +++ b/add-patch.c @@ -383,6 +383,17 @@ static int is_octal(const char *p, size_t len) return 1; } +static void complete_file(char marker, struct hunk *hunk) +{ + if (marker == '-' || marker == '+') + /* + * Last hunk ended in non-context line (i.e. it + * appended lines to the file, so there are no + * trailing context lines). + */ + hunk->splittable_into++; +} + static int parse_diff(struct add_p_state *s, const struct pathspec *ps) { struct strvec args = STRVEC_INIT; @@ -472,6 +483,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) eol = pend; if (starts_with(p, "diff ")) { + complete_file(marker, hunk); ALLOC_GROW_BY(s->file_diff, s->file_diff_nr, 1, file_diff_alloc); file_diff = s->file_diff + s->file_diff_nr - 1; @@ -598,13 +610,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) file_diff->hunk->colored_end = hunk->colored_end; } } - - if (marker == '-' || marker == '+') - /* - * Last hunk ended in non-context line (i.e. it appended lines - * to the file, so there are no trailing context lines). - */ - hunk->splittable_into++; + complete_file(marker, hunk); /* non-colored shorter than colored? */ if (colored_p != colored_pend) { @@ -70,6 +70,7 @@ static struct { [ADVICE_STATUS_HINTS] = { "statusHints", 1 }, [ADVICE_STATUS_U_OPTION] = { "statusUoption", 1 }, [ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { "submoduleAlternateErrorStrategyDie", 1 }, + [ADVICE_SUBMODULES_NOT_UPDATED] = { "submodulesNotUpdated", 1 }, [ADVICE_UPDATE_SPARSE_PATH] = { "updateSparsePath", 1 }, [ADVICE_WAITING_FOR_EDITOR] = { "waitingForEditor", 1 }, }; @@ -44,6 +44,7 @@ struct string_list; ADVICE_STATUS_HINTS, ADVICE_STATUS_U_OPTION, ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE, + ADVICE_SUBMODULES_NOT_UPDATED, ADVICE_UPDATE_SPARSE_PATH, ADVICE_WAITING_FOR_EDITOR, ADVICE_SKIPPED_CHERRY_PICKS, @@ -103,7 +103,8 @@ int init_apply_state(struct apply_state *state, state->linenr = 1; string_list_init_nodup(&state->fn_table); string_list_init_nodup(&state->limit_by_name); - string_list_init_nodup(&state->symlink_changes); + strset_init(&state->removed_symlinks); + strset_init(&state->kept_symlinks); strbuf_init(&state->root, 0); git_apply_config(); @@ -117,7 +118,8 @@ int init_apply_state(struct apply_state *state, void clear_apply_state(struct apply_state *state) { string_list_clear(&state->limit_by_name, 0); - string_list_clear(&state->symlink_changes, 0); + strset_clear(&state->removed_symlinks); + strset_clear(&state->kept_symlinks); strbuf_release(&state->root); /* &state->fn_table is cleared at the end of apply_patch() */ @@ -3492,7 +3494,7 @@ static int three_way_merge(struct apply_state *state, { mmfile_t base_file, our_file, their_file; mmbuffer_t result = { NULL }; - int status; + enum ll_merge_result status; /* resolve trivial cases first */ if (oideq(base, ours)) @@ -3509,6 +3511,9 @@ static int three_way_merge(struct apply_state *state, &their_file, "theirs", state->repo->index, NULL); + if (status == LL_MERGE_BINARY_CONFLICT) + warning("Cannot merge binary files: %s (%s vs. %s)", + path, "ours", "theirs"); free(base_file.ptr); free(our_file.ptr); free(their_file.ptr); @@ -3814,59 +3819,31 @@ static int check_to_create(struct apply_state *state, return 0; } -static uintptr_t register_symlink_changes(struct apply_state *state, - const char *path, - uintptr_t what) -{ - struct string_list_item *ent; - - ent = string_list_lookup(&state->symlink_changes, path); - if (!ent) { - ent = string_list_insert(&state->symlink_changes, path); - ent->util = (void *)0; - } - ent->util = (void *)(what | ((uintptr_t)ent->util)); - return (uintptr_t)ent->util; -} - -static uintptr_t check_symlink_changes(struct apply_state *state, const char *path) -{ - struct string_list_item *ent; - - ent = string_list_lookup(&state->symlink_changes, path); - if (!ent) - return 0; - return (uintptr_t)ent->util; -} - static void prepare_symlink_changes(struct apply_state *state, struct patch *patch) { for ( ; patch; patch = patch->next) { if ((patch->old_name && S_ISLNK(patch->old_mode)) && (patch->is_rename || patch->is_delete)) /* the symlink at patch->old_name is removed */ - register_symlink_changes(state, patch->old_name, APPLY_SYMLINK_GOES_AWAY); + strset_add(&state->removed_symlinks, patch->old_name); if (patch->new_name && S_ISLNK(patch->new_mode)) /* the symlink at patch->new_name is created or remains */ - register_symlink_changes(state, patch->new_name, APPLY_SYMLINK_IN_RESULT); + strset_add(&state->kept_symlinks, patch->new_name); } } static int path_is_beyond_symlink_1(struct apply_state *state, struct strbuf *name) { do { - unsigned int change; - while (--name->len && name->buf[name->len] != '/') ; /* scan backwards */ if (!name->len) break; name->buf[name->len] = '\0'; - change = check_symlink_changes(state, name->buf); - if (change & APPLY_SYMLINK_IN_RESULT) + if (strset_contains(&state->kept_symlinks, name->buf)) return 1; - if (change & APPLY_SYMLINK_GOES_AWAY) + if (strset_contains(&state->removed_symlinks, name->buf)) /* * This cannot be "return 0", because we may * see a new one created at a higher level. @@ -4,6 +4,7 @@ #include "hash.h" #include "lockfile.h" #include "string-list.h" +#include "strmap.h" struct repository; @@ -25,20 +26,6 @@ enum apply_verbosity { verbosity_verbose = 1 }; -/* - * We need to keep track of how symlinks in the preimage are - * manipulated by the patches. A patch to add a/b/c where a/b - * is a symlink should not be allowed to affect the directory - * the symlink points at, but if the same patch removes a/b, - * it is perfectly fine, as the patch removes a/b to make room - * to create a directory a/b so that a/b/c can be created. - * - * See also "struct string_list symlink_changes" in "struct - * apply_state". - */ -#define APPLY_SYMLINK_GOES_AWAY 01 -#define APPLY_SYMLINK_IN_RESULT 02 - struct apply_state { const char *prefix; @@ -86,7 +73,16 @@ struct apply_state { /* Various "current state" */ int linenr; /* current line number */ - struct string_list symlink_changes; /* we have to track symlinks */ + /* + * We need to keep track of how symlinks in the preimage are + * manipulated by the patches. A patch to add a/b/c where a/b + * is a symlink should not be allowed to affect the directory + * the symlink points at, but if the same patch removes a/b, + * it is perfectly fine, as the patch removes a/b to make room + * to create a directory a/b so that a/b/c can be created. + */ + struct strset removed_symlinks; + struct strset kept_symlinks; /* * For "diff-stat" like behaviour, we keep track of the biggest change @@ -2615,7 +2615,7 @@ void assign_blame(struct blame_scoreboard *sb, int opt) else { commit->object.flags |= UNINTERESTING; if (commit->object.parsed) - mark_parents_uninteresting(commit); + mark_parents_uninteresting(sb->revs, commit); } /* treat root commit as boundary */ if (!commit->parents && !sb->show_root) @@ -8,6 +8,8 @@ #include "sequencer.h" #include "commit.h" #include "worktree.h" +#include "submodule-config.h" +#include "run-command.h" struct tracking { struct refspec_item spec; @@ -218,9 +220,11 @@ static int inherit_tracking(struct tracking *tracking, const char *orig_ref) } /* - * This is called when new_ref is branched off of orig_ref, and tries - * to infer the settings for branch.<new_ref>.{remote,merge} from the - * config. + * Used internally to set the branch.<new_ref>.{remote,merge} config + * settings so that branch 'new_ref' tracks 'orig_ref'. Unlike + * dwim_and_setup_tracking(), this does not do DWIM, i.e. "origin/main" + * will not be expanded to "refs/remotes/origin/main", so it is not safe + * for 'orig_ref' to be raw user input. */ static void setup_tracking(const char *new_ref, const char *orig_ref, enum branch_track track, int quiet) @@ -235,7 +239,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref, if (track != BRANCH_TRACK_INHERIT) for_each_remote(find_tracked_branch, &tracking); else if (inherit_tracking(&tracking, orig_ref)) - return; + goto cleanup; if (!tracking.matches) switch (track) { @@ -245,7 +249,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref, case BRANCH_TRACK_INHERIT: break; default: - return; + goto cleanup; } if (tracking.matches > 1) @@ -258,7 +262,8 @@ static void setup_tracking(const char *new_ref, const char *orig_ref, tracking.remote, tracking.srcs) < 0) exit(-1); - string_list_clear(tracking.srcs, 0); +cleanup: + string_list_clear(&tracking_srcs, 0); } int read_branch_desc(struct strbuf *buf, const char *branch_name) @@ -346,31 +351,37 @@ N_("\n" "will track its remote counterpart, you may want to use\n" "\"git push -u\" to set the upstream config as you push."); -void create_branch(struct repository *r, - const char *name, const char *start_name, - int force, int clobber_head_ok, int reflog, - int quiet, enum branch_track track) +/** + * DWIMs a user-provided ref to determine the starting point for a + * branch and validates it, where: + * + * - r is the repository to validate the branch for + * + * - start_name is the ref that we would like to test. This is + * expanded with DWIM and assigned to out_real_ref. + * + * - track is the tracking mode of the new branch. If tracking is + * explicitly requested, start_name must be a branch (because + * otherwise start_name cannot be tracked) + * + * - out_oid is an out parameter containing the object_id of start_name + * + * - out_real_ref is an out parameter containing the full, 'real' form + * of start_name e.g. refs/heads/main instead of main + * + */ +static void dwim_branch_start(struct repository *r, const char *start_name, + enum branch_track track, char **out_real_ref, + struct object_id *out_oid) { struct commit *commit; struct object_id oid; char *real_ref; - struct strbuf ref = STRBUF_INIT; - int forcing = 0; - int dont_change_ref = 0; int explicit_tracking = 0; if (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE) explicit_tracking = 1; - if ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok) - ? validate_branchname(name, &ref) - : validate_new_branchname(name, &ref, force)) { - if (!force) - dont_change_ref = 1; - else - forcing = 1; - } - real_ref = NULL; if (get_oid_mb(start_name, &oid)) { if (explicit_tracking) { @@ -407,40 +418,218 @@ void create_branch(struct repository *r, if ((commit = lookup_commit_reference(r, &oid)) == NULL) die(_("not a valid branch point: '%s'"), start_name); - oidcpy(&oid, &commit->object.oid); + if (out_real_ref) { + *out_real_ref = real_ref; + real_ref = NULL; + } + if (out_oid) + oidcpy(out_oid, &commit->object.oid); + + FREE_AND_NULL(real_ref); +} + +void create_branch(struct repository *r, + const char *name, const char *start_name, + int force, int clobber_head_ok, int reflog, + int quiet, enum branch_track track, int dry_run) +{ + struct object_id oid; + char *real_ref; + struct strbuf ref = STRBUF_INIT; + int forcing = 0; + struct ref_transaction *transaction; + struct strbuf err = STRBUF_INIT; + char *msg; + + if (track == BRANCH_TRACK_OVERRIDE) + BUG("'track' cannot be BRANCH_TRACK_OVERRIDE. Did you mean to call dwim_and_setup_tracking()?"); + if (clobber_head_ok && !force) + BUG("'clobber_head_ok' can only be used with 'force'"); + + if (clobber_head_ok ? + validate_branchname(name, &ref) : + validate_new_branchname(name, &ref, force)) { + forcing = 1; + } + + dwim_branch_start(r, start_name, track, &real_ref, &oid); + if (dry_run) + goto cleanup; if (reflog) log_all_ref_updates = LOG_REFS_NORMAL; - if (!dont_change_ref) { - struct ref_transaction *transaction; - struct strbuf err = STRBUF_INIT; - char *msg; - - if (forcing) - msg = xstrfmt("branch: Reset to %s", start_name); - else - msg = xstrfmt("branch: Created from %s", start_name); - - transaction = ref_transaction_begin(&err); - if (!transaction || - ref_transaction_update(transaction, ref.buf, - &oid, forcing ? NULL : null_oid(), - 0, msg, &err) || - ref_transaction_commit(transaction, &err)) - die("%s", err.buf); - ref_transaction_free(transaction); - strbuf_release(&err); - free(msg); - } + if (forcing) + msg = xstrfmt("branch: Reset to %s", start_name); + else + msg = xstrfmt("branch: Created from %s", start_name); + transaction = ref_transaction_begin(&err); + if (!transaction || + ref_transaction_update(transaction, ref.buf, + &oid, forcing ? NULL : null_oid(), + 0, msg, &err) || + ref_transaction_commit(transaction, &err)) + die("%s", err.buf); + ref_transaction_free(transaction); + strbuf_release(&err); + free(msg); if (real_ref && track) setup_tracking(ref.buf + 11, real_ref, track, quiet); +cleanup: strbuf_release(&ref); free(real_ref); } +void dwim_and_setup_tracking(struct repository *r, const char *new_ref, + const char *orig_ref, enum branch_track track, + int quiet) +{ + char *real_orig_ref; + dwim_branch_start(r, orig_ref, track, &real_orig_ref, NULL); + setup_tracking(new_ref, real_orig_ref, track, quiet); +} + +/** + * Creates a branch in a submodule by calling + * create_branches_recursively() in a child process. The child process + * is necessary because install_branch_config_multiple_remotes() (which + * is called by setup_tracking()) does not support writing configs to + * submodules. + */ +static int submodule_create_branch(struct repository *r, + const struct submodule *submodule, + const char *name, const char *start_oid, + const char *tracking_name, int force, + int reflog, int quiet, + enum branch_track track, int dry_run) +{ + int ret = 0; + struct child_process child = CHILD_PROCESS_INIT; + struct strbuf child_err = STRBUF_INIT; + struct strbuf out_buf = STRBUF_INIT; + char *out_prefix = xstrfmt("submodule '%s': ", submodule->name); + child.git_cmd = 1; + child.err = -1; + child.stdout_to_stderr = 1; + + prepare_other_repo_env(&child.env_array, r->gitdir); + /* + * submodule_create_branch() is indirectly invoked by "git + * branch", but we cannot invoke "git branch" in the child + * process. "git branch" accepts a branch name and start point, + * where the start point is assumed to provide both the OID + * (start_oid) and the branch to use for tracking + * (tracking_name). But when recursing through submodules, + * start_oid and tracking name need to be specified separately + * (see create_branches_recursively()). + */ + strvec_pushl(&child.args, "submodule--helper", "create-branch", NULL); + if (dry_run) + strvec_push(&child.args, "--dry-run"); + if (force) + strvec_push(&child.args, "--force"); + if (quiet) + strvec_push(&child.args, "--quiet"); + if (reflog) + strvec_push(&child.args, "--create-reflog"); + if (track == BRANCH_TRACK_ALWAYS || track == BRANCH_TRACK_EXPLICIT) + strvec_push(&child.args, "--track"); + + strvec_pushl(&child.args, name, start_oid, tracking_name, NULL); + + if ((ret = start_command(&child))) + return ret; + ret = finish_command(&child); + strbuf_read(&child_err, child.err, 0); + strbuf_add_lines(&out_buf, out_prefix, child_err.buf, child_err.len); + + if (ret) + fprintf(stderr, "%s", out_buf.buf); + else + printf("%s", out_buf.buf); + + strbuf_release(&child_err); + strbuf_release(&out_buf); + return ret; +} + +void create_branches_recursively(struct repository *r, const char *name, + const char *start_commitish, + const char *tracking_name, int force, + int reflog, int quiet, enum branch_track track, + int dry_run) +{ + int i = 0; + char *branch_point = NULL; + struct object_id super_oid; + struct submodule_entry_list submodule_entry_list; + + /* Perform dwim on start_commitish to get super_oid and branch_point. */ + dwim_branch_start(r, start_commitish, BRANCH_TRACK_NEVER, + &branch_point, &super_oid); + + /* + * If we were not given an explicit name to track, then assume we are at + * the top level and, just like the non-recursive case, the tracking + * name is the branch point. + */ + if (!tracking_name) + tracking_name = branch_point; + + submodules_of_tree(r, &super_oid, &submodule_entry_list); + /* + * Before creating any branches, first check that the branch can + * be created in every submodule. + */ + for (i = 0; i < submodule_entry_list.entry_nr; i++) { + if (submodule_entry_list.entries[i].repo == NULL) { + if (advice_enabled(ADVICE_SUBMODULES_NOT_UPDATED)) + advise(_("You may try updating the submodules using 'git checkout %s && git submodule update --init'"), + start_commitish); + die(_("submodule '%s': unable to find submodule"), + submodule_entry_list.entries[i].submodule->name); + } + + if (submodule_create_branch( + submodule_entry_list.entries[i].repo, + submodule_entry_list.entries[i].submodule, name, + oid_to_hex(&submodule_entry_list.entries[i] + .name_entry->oid), + tracking_name, force, reflog, quiet, track, 1)) + die(_("submodule '%s': cannot create branch '%s'"), + submodule_entry_list.entries[i].submodule->name, + name); + } + + create_branch(the_repository, name, start_commitish, force, 0, reflog, quiet, + BRANCH_TRACK_NEVER, dry_run); + if (dry_run) + return; + /* + * NEEDSWORK If tracking was set up in the superproject but not the + * submodule, users might expect "git branch --recurse-submodules" to + * fail or give a warning, but this is not yet implemented because it is + * tedious to determine whether or not tracking was set up in the + * superproject. + */ + setup_tracking(name, tracking_name, track, quiet); + + for (i = 0; i < submodule_entry_list.entry_nr; i++) { + if (submodule_create_branch( + submodule_entry_list.entries[i].repo, + submodule_entry_list.entries[i].submodule, name, + oid_to_hex(&submodule_entry_list.entries[i] + .name_entry->oid), + tracking_name, force, reflog, quiet, track, 0)) + die(_("submodule '%s': cannot create branch '%s'"), + submodule_entry_list.entries[i].submodule->name, + name); + repo_clear(submodule_entry_list.entries[i].repo); + } +} + void remove_merge_branch_state(struct repository *r) { unlink(git_path_merge_head(r)); @@ -18,6 +18,28 @@ extern enum branch_track git_branch_track; /* Functions for acting on the information about branches. */ +/** + * Sets branch.<new_ref>.{remote,merge} config settings such that + * new_ref tracks orig_ref according to the specified tracking mode. + * + * - new_ref is the name of the branch that we are setting tracking + * for. + * + * - orig_ref is the name of the ref that is 'upstream' of new_ref. + * orig_ref will be expanded with DWIM so that the config settings + * are in the correct format e.g. "refs/remotes/origin/main" instead + * of "origin/main". + * + * - track is the tracking mode e.g. BRANCH_TRACK_REMOTE causes + * new_ref to track orig_ref directly, whereas BRANCH_TRACK_INHERIT + * causes new_ref to track whatever orig_ref tracks. + * + * - quiet suppresses tracking information. + */ +void dwim_and_setup_tracking(struct repository *r, const char *new_ref, + const char *orig_ref, enum branch_track track, + int quiet); + /* * Creates a new branch, where: * @@ -30,8 +52,8 @@ extern enum branch_track git_branch_track; * * - force enables overwriting an existing (non-head) branch * - * - clobber_head_ok allows the currently checked out (hence existing) - * branch to be overwritten; without 'force', it has no effect. + * - clobber_head_ok, when enabled with 'force', allows the currently + * checked out (head) branch to be overwritten * * - reflog creates a reflog for the branch * @@ -40,13 +62,45 @@ extern enum branch_track git_branch_track; * - track causes the new branch to be configured to merge the remote branch * that start_name is a tracking branch for (if any). * + * - dry_run causes the branch to be validated but not created. + * */ void create_branch(struct repository *r, const char *name, const char *start_name, int force, int clobber_head_ok, - int reflog, int quiet, enum branch_track track); + int reflog, int quiet, enum branch_track track, + int dry_run); /* + * Creates a new branch in a repository and its submodules (and its + * submodules, recursively). The parameters are mostly analogous to + * those of create_branch() except for start_name, which is represented + * by two different parameters: + * + * - start_commitish is the commit-ish, in repository r, that determines + * which commits the branches will point to. The superproject branch + * will point to the commit of start_commitish and the submodule + * branches will point to the gitlink commit oids in start_commitish's + * tree. + * + * - tracking_name is the name of the ref, in repository r, that will be + * used to set up tracking information. This value is propagated to + * all submodules, which will evaluate the ref using their own ref + * stores. If NULL, this defaults to start_commitish. + * + * When this function is called on the superproject, start_commitish + * can be any user-provided ref and tracking_name can be NULL (similar + * to create_branches()). But when recursing through submodules, + * start_commitish is the plain gitlink commit oid. Since the oid cannot + * be used for tracking information, tracking_name is propagated and + * used for tracking instead. + */ +void create_branches_recursively(struct repository *r, const char *name, + const char *start_commitish, + const char *tracking_name, int force, + int reflog, int quiet, enum branch_track track, + int dry_run); +/* * Check if 'name' can be a valid name for a branch; die otherwise. * Return 1 if the named branch already exists; return 0 otherwise. * Fill ref with the full refname for the branch. @@ -164,6 +164,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix); int cmd_grep(int argc, const char **argv, const char *prefix); int cmd_hash_object(int argc, const char **argv, const char *prefix); int cmd_help(int argc, const char **argv, const char *prefix); +int cmd_hook(int argc, const char **argv, const char *prefix); int cmd_index_pack(int argc, const char **argv, const char *prefix); int cmd_init_db(int argc, const char **argv, const char *prefix); int cmd_interpret_trailers(int argc, const char **argv, const char *prefix); diff --git a/builtin/add.c b/builtin/add.c index 84dff3e796..3ffb86a433 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -32,7 +32,6 @@ static int add_renormalize; static int pathspec_file_nul; static int include_sparse; static const char *pathspec_from_file; -static int legacy_stash_p; /* support for the scripted `git stash` */ struct update_callback_data { int flags; @@ -388,8 +387,6 @@ static struct option builtin_add_options[] = { N_("override the executable bit of the listed files")), OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo, N_("warn when adding an embedded repository")), - OPT_HIDDEN_BOOL(0, "legacy-stash-p", &legacy_stash_p, - N_("backend for `git stash -p`")), OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), OPT_END(), @@ -512,17 +509,6 @@ int cmd_add(int argc, const char **argv, const char *prefix) die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--interactive/--patch"); exit(interactive_add(argv + 1, prefix, patch_interactive)); } - if (legacy_stash_p) { - struct pathspec pathspec; - - parse_pathspec(&pathspec, 0, - PATHSPEC_PREFER_FULL | - PATHSPEC_SYMLINK_LEADING_PATH | - PATHSPEC_PREFIX_ORIGIN, - prefix, argv); - - return run_add_interactive(NULL, "--patch=stash", &pathspec); - } if (edit_interactive) { if (pathspec_from_file) diff --git a/builtin/am.c b/builtin/am.c index b6be1f1cb1..7de2c89ef2 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -474,7 +474,7 @@ static int run_applypatch_msg_hook(struct am_state *state) int ret; assert(state->msg); - ret = run_hook_le(NULL, "applypatch-msg", am_path(state, "final-commit"), NULL); + ret = run_hooks_l("applypatch-msg", am_path(state, "final-commit"), NULL); if (!ret) { FREE_AND_NULL(state->msg); @@ -1636,7 +1636,7 @@ static void do_commit(const struct am_state *state) const char *reflog_msg, *author, *committer = NULL; struct strbuf sb = STRBUF_INIT; - if (run_hook_le(NULL, "pre-applypatch", NULL)) + if (run_hooks("pre-applypatch")) exit(1); if (write_cache_as_tree(&tree, 0, NULL)) @@ -1688,7 +1688,7 @@ static void do_commit(const struct am_state *state) fclose(fp); } - run_hook_le(NULL, "post-applypatch", NULL); + run_hooks("post-applypatch"); strbuf_release(&sb); } diff --git a/builtin/branch.c b/builtin/branch.c index 4ce2a24754..5d00d0b8d3 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -27,7 +27,8 @@ static const char * const builtin_branch_usage[] = { N_("git branch [<options>] [-r | -a] [--merged] [--no-merged]"), - N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"), + N_("git branch [<options>] [-f] [--recurse-submodules] <branch-name> [<start-point>]"), + N_("git branch [<options>] [-l] [<pattern>...]"), N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."), N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"), N_("git branch [<options>] (-c | -C) [<old-branch>] <new-branch>"), @@ -38,6 +39,8 @@ static const char * const builtin_branch_usage[] = { static const char *head; static struct object_id head_oid; +static int recurse_submodules = 0; +static int submodule_propagate_branches = 0; static int branch_use_color = -1; static char branch_colors[][COLOR_MAXLEN] = { @@ -99,6 +102,15 @@ static int git_branch_config(const char *var, const char *value, void *cb) return config_error_nonbool(var); return color_parse(value, branch_colors[slot]); } + if (!strcmp(var, "submodule.recurse")) { + recurse_submodules = git_config_bool(var, value); + return 0; + } + if (!strcasecmp(var, "submodule.propagateBranches")) { + submodule_propagate_branches = git_config_bool(var, value); + return 0; + } + return git_color_default_config(var, value, cb); } @@ -621,14 +633,16 @@ static int edit_branch_description(const char *branch_name) int cmd_branch(int argc, const char **argv, const char *prefix) { - int delete = 0, rename = 0, copy = 0, force = 0, list = 0; - int show_current = 0; - int reflog = 0, edit_description = 0; - int quiet = 0, unset_upstream = 0; + /* possible actions */ + int delete = 0, rename = 0, copy = 0, list = 0, + unset_upstream = 0, show_current = 0, edit_description = 0; const char *new_upstream = NULL; + int noncreate_actions = 0; + /* possible options */ + int reflog = 0, quiet = 0, icase = 0, force = 0, + recurse_submodules_explicit = 0; enum branch_track track; struct ref_filter filter; - int icase = 0; static struct ref_sorting *sorting; struct string_list sorting_options = STRING_LIST_INIT_DUP; struct ref_format format = REF_FORMAT_INIT; @@ -677,6 +691,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_CALLBACK(0, "points-at", &filter.points_at, N_("object"), N_("print only branches of the object"), parse_opt_object_name), OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")), + OPT_BOOL(0, "recurse-submodules", &recurse_submodules_explicit, N_("recurse through submodules")), OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")), OPT_END(), }; @@ -713,10 +728,23 @@ int cmd_branch(int argc, const char **argv, const char *prefix) filter.reachable_from || filter.unreachable_from || filter.points_at.nr) list = 1; - if (!!delete + !!rename + !!copy + !!new_upstream + !!show_current + - list + edit_description + unset_upstream > 1) + noncreate_actions = !!delete + !!rename + !!copy + !!new_upstream + + !!show_current + !!list + !!edit_description + + !!unset_upstream; + if (noncreate_actions > 1) usage_with_options(builtin_branch_usage, options); + if (recurse_submodules_explicit) { + if (!submodule_propagate_branches) + die(_("branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled")); + if (noncreate_actions) + die(_("--recurse-submodules can only be used to create branches")); + } + + recurse_submodules = + (recurse_submodules || recurse_submodules_explicit) && + submodule_propagate_branches; + if (filter.abbrev == -1) filter.abbrev = DEFAULT_ABBREV; filter.ignore_case = icase; @@ -828,12 +856,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (!ref_exists(branch->refname)) die(_("branch '%s' does not exist"), branch->name); - /* - * create_branch takes care of setting up the tracking - * info and making sure new_upstream is correct - */ - create_branch(the_repository, branch->name, new_upstream, - 0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE); + dwim_and_setup_tracking(the_repository, branch->name, + new_upstream, BRANCH_TRACK_OVERRIDE, + quiet); } else if (unset_upstream) { struct branch *branch = branch_get(argv[0]); struct strbuf buf = STRBUF_INIT; @@ -857,7 +882,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix) strbuf_addf(&buf, "branch.%s.merge", branch->name); git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE); strbuf_release(&buf); - } else if (argc > 0 && argc <= 2) { + } else if (!noncreate_actions && argc > 0 && argc <= 2) { + const char *branch_name = argv[0]; + const char *start_name = argc == 2 ? argv[1] : head; + if (filter.kind != FILTER_REFS_BRANCHES) die(_("The -a, and -r, options to 'git branch' do not take a branch name.\n" "Did you mean to use: -a|-r --list <pattern>?")); @@ -865,10 +893,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (track == BRANCH_TRACK_OVERRIDE) die(_("the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead.")); - create_branch(the_repository, - argv[0], (argc == 2) ? argv[1] : head, - force, 0, reflog, quiet, track); - + if (recurse_submodules) { + create_branches_recursively(the_repository, branch_name, + start_name, NULL, force, + reflog, quiet, track, 0); + return 0; + } + create_branch(the_repository, branch_name, start_name, force, 0, + reflog, quiet, track, 0); } else usage_with_options(builtin_branch_usage, options); diff --git a/builtin/cat-file.c b/builtin/cat-file.c index d94050e6c1..7b3f42950e 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -73,14 +73,17 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, struct object_info oi = OBJECT_INFO_INIT; struct strbuf sb = STRBUF_INIT; unsigned flags = OBJECT_INFO_LOOKUP_REPLACE; + unsigned get_oid_flags = GET_OID_RECORD_PATH | GET_OID_ONLY_TO_DIE; const char *path = force_path; + const int opt_cw = (opt == 'c' || opt == 'w'); + if (!path && opt_cw) + get_oid_flags |= GET_OID_REQUIRE_PATH; if (unknown_type) flags |= OBJECT_INFO_ALLOW_UNKNOWN_TYPE; - if (get_oid_with_context(the_repository, obj_name, - GET_OID_RECORD_PATH, - &oid, &obj_context)) + if (get_oid_with_context(the_repository, obj_name, get_oid_flags, &oid, + &obj_context)) die("Not a valid object name %s", obj_name); if (!path) @@ -112,9 +115,6 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, return !has_object_file(&oid); case 'w': - if (!path) - die("git cat-file --filters %s: <object> must be " - "<sha1:path>", obj_name); if (filter_object(path, obj_context.mode, &oid, &buf, &size)) @@ -122,10 +122,6 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, break; case 'c': - if (!path) - die("git cat-file --textconv %s: <object> must be <sha1:path>", - obj_name); - if (textconv_object(the_repository, path, obj_context.mode, &oid, 1, &buf, &size)) break; @@ -618,12 +614,6 @@ static int batch_objects(struct batch_options *opt) return retval; } -static const char * const cat_file_usage[] = { - N_("git cat-file (-t [--allow-unknown-type] | -s [--allow-unknown-type] | -e | -p | <type> | --textconv | --filters) [--path=<path>] <object>"), - N_("git cat-file (--batch[=<format>] | --batch-check[=<format>]) [--follow-symlinks] [--textconv | --filters]"), - NULL -}; - static int git_cat_file_config(const char *var, const char *value, void *cb) { if (userdiff_config(var, value) < 0) @@ -654,90 +644,138 @@ static int batch_option_callback(const struct option *opt, int cmd_cat_file(int argc, const char **argv, const char *prefix) { int opt = 0; + int opt_cw = 0; + int opt_epts = 0; const char *exp_type = NULL, *obj_name = NULL; struct batch_options batch = {0}; int unknown_type = 0; + const char * const usage[] = { + N_("git cat-file <type> <object>"), + N_("git cat-file (-e | -p) <object>"), + N_("git cat-file (-t | -s) [--allow-unknown-type] <object>"), + N_("git cat-file (--batch | --batch-check) [--batch-all-objects]\n" + " [--buffer] [--follow-symlinks] [--unordered]\n" + " [--textconv | --filters]"), + N_("git cat-file (--textconv | --filters)\n" + " [<rev>:<path|tree-ish> | --path=<path|tree-ish> <rev>]"), + NULL + }; const struct option options[] = { - OPT_GROUP(N_("<type> can be one of: blob, tree, commit, tag")), - OPT_CMDMODE('t', NULL, &opt, N_("show object type"), 't'), - OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'), + /* Simple queries */ + OPT_GROUP(N_("Check object existence or emit object contents")), OPT_CMDMODE('e', NULL, &opt, - N_("exit with zero when there's no error"), 'e'), - OPT_CMDMODE('p', NULL, &opt, N_("pretty-print object's content"), 'p'), - OPT_CMDMODE(0, "textconv", &opt, - N_("for blob objects, run textconv on object's content"), 'c'), - OPT_CMDMODE(0, "filters", &opt, - N_("for blob objects, run filters on object's content"), 'w'), - OPT_STRING(0, "path", &force_path, N_("blob"), - N_("use a specific path for --textconv/--filters")), + N_("check if <object> exists"), 'e'), + OPT_CMDMODE('p', NULL, &opt, N_("pretty-print <object> content"), 'p'), + + OPT_GROUP(N_("Emit [broken] object attributes")), + OPT_CMDMODE('t', NULL, &opt, N_("show object type (one of 'blob', 'tree', 'commit', 'tag', ...)"), 't'), + OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'), OPT_BOOL(0, "allow-unknown-type", &unknown_type, N_("allow -s and -t to work with broken/corrupt objects")), - OPT_BOOL(0, "buffer", &batch.buffer_output, N_("buffer --batch output")), - OPT_CALLBACK_F(0, "batch", &batch, "format", - N_("show info and content of objects fed from the standard input"), + /* Batch mode */ + OPT_GROUP(N_("Batch objects requested on stdin (or --batch-all-objects)")), + OPT_CALLBACK_F(0, "batch", &batch, N_("format"), + N_("show full <object> or <rev> contents"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, batch_option_callback), - OPT_CALLBACK_F(0, "batch-check", &batch, "format", - N_("show info about objects fed from the standard input"), + OPT_CALLBACK_F(0, "batch-check", &batch, N_("format"), + N_("like --batch, but don't emit <contents>"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, batch_option_callback), + OPT_CMDMODE(0, "batch-all-objects", &opt, + N_("with --batch[-check]: ignores stdin, batches all known objects"), 'b'), + /* Batch-specific options */ + OPT_GROUP(N_("Change or optimize batch output")), + OPT_BOOL(0, "buffer", &batch.buffer_output, N_("buffer --batch output")), OPT_BOOL(0, "follow-symlinks", &batch.follow_symlinks, - N_("follow in-tree symlinks (used with --batch or --batch-check)")), - OPT_BOOL(0, "batch-all-objects", &batch.all_objects, - N_("show all objects with --batch or --batch-check")), + N_("follow in-tree symlinks")), OPT_BOOL(0, "unordered", &batch.unordered, - N_("do not order --batch-all-objects output")), + N_("do not order objects before emitting them")), + /* Textconv options, stand-ole*/ + OPT_GROUP(N_("Emit object (blob or tree) with conversion or filter (stand-alone, or with batch)")), + OPT_CMDMODE(0, "textconv", &opt, + N_("run textconv on object's content"), 'c'), + OPT_CMDMODE(0, "filters", &opt, + N_("run filters on object's content"), 'w'), + OPT_STRING(0, "path", &force_path, N_("blob|tree"), + N_("use a <path> for (--textconv | --filters); Not with 'batch'")), OPT_END() }; git_config(git_cat_file_config, NULL); batch.buffer_output = -1; - argc = parse_options(argc, argv, prefix, options, cat_file_usage, 0); - - if (opt) { - if (batch.enabled && (opt == 'c' || opt == 'w')) - batch.cmdmode = opt; - else if (argc == 1) - obj_name = argv[0]; - else - usage_with_options(cat_file_usage, options); - } - if (!opt && !batch.enabled) { - if (argc == 2) { - exp_type = argv[0]; - obj_name = argv[1]; - } else - usage_with_options(cat_file_usage, options); - } - if (batch.enabled) { - if (batch.cmdmode != opt || argc) - usage_with_options(cat_file_usage, options); - if (batch.cmdmode && batch.all_objects) - die("--batch-all-objects cannot be combined with " - "--textconv nor with --filters"); - } - if ((batch.follow_symlinks || batch.all_objects) && !batch.enabled) { - usage_with_options(cat_file_usage, options); - } + argc = parse_options(argc, argv, prefix, options, usage, 0); + opt_cw = (opt == 'c' || opt == 'w'); + opt_epts = (opt == 'e' || opt == 'p' || opt == 't' || opt == 's'); - if (force_path && opt != 'c' && opt != 'w') { - error("--path=<path> needs --textconv or --filters"); - usage_with_options(cat_file_usage, options); - } + /* --batch-all-objects? */ + if (opt == 'b') + batch.all_objects = 1; - if (force_path && batch.enabled) { - error("options '--path=<path>' and '--batch' cannot be used together"); - usage_with_options(cat_file_usage, options); - } + /* Option compatibility */ + if (force_path && !opt_cw) + usage_msg_optf(_("'%s=<%s>' needs '%s' or '%s'"), + usage, options, + "--path", _("path|tree-ish"), "--filters", + "--textconv"); + /* Option compatibility with batch mode */ + if (batch.enabled) + ; + else if (batch.follow_symlinks) + usage_msg_optf(_("'%s' requires a batch mode"), usage, options, + "--follow-symlinks"); + else if (batch.buffer_output >= 0) + usage_msg_optf(_("'%s' requires a batch mode"), usage, options, + "--buffer"); + else if (batch.all_objects) + usage_msg_optf(_("'%s' requires a batch mode"), usage, options, + "--batch-all-objects"); + + /* Batch defaults */ if (batch.buffer_output < 0) batch.buffer_output = batch.all_objects; - if (batch.enabled) + /* Return early if we're in batch mode? */ + if (batch.enabled) { + if (opt_cw) + batch.cmdmode = opt; + else if (opt && opt != 'b') + usage_msg_optf(_("'-%c' is incompatible with batch mode"), + usage, options, opt); + else if (argc) + usage_msg_opt(_("batch modes take no arguments"), usage, + options); + return batch_objects(&batch); + } + + if (opt) { + if (!argc && opt == 'c') + usage_msg_optf(_("<rev> required with '%s'"), + usage, options, "--textconv"); + else if (!argc && opt == 'w') + usage_msg_optf(_("<rev> required with '%s'"), + usage, options, "--filters"); + else if (!argc && opt_epts) + usage_msg_optf(_("<object> required with '-%c'"), + usage, options, opt); + else if (argc == 1) + obj_name = argv[0]; + else + usage_msg_opt(_("too many arguments"), usage, options); + } else if (!argc) { + usage_with_options(usage, options); + } else if (argc != 2) { + usage_msg_optf(_("only two arguments allowed in <type> <object> mode, not %d"), + usage, options, argc); + } else if (argc) { + exp_type = argv[0]; + obj_name = argv[1]; + } if (unknown_type && opt != 't' && opt != 's') die("git cat-file --allow-unknown-type: use with -s or -t"); diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index e21620d964..97e06e8c52 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -7,6 +7,7 @@ #define USE_THE_INDEX_COMPATIBILITY_MACROS #include "builtin.h" #include "config.h" +#include "dir.h" #include "lockfile.h" #include "quote.h" #include "cache-tree.h" @@ -17,6 +18,7 @@ #define CHECKOUT_ALL 4 static int nul_term_line; static int checkout_stage; /* default to checkout stage0 */ +static int ignore_skip_worktree; /* default to 0 */ static int to_tempfile; static char topath[4][TEMPORARY_FILENAME_LENGTH + 1]; @@ -65,6 +67,8 @@ static int checkout_file(const char *name, const char *prefix) int namelen = strlen(name); int pos = cache_name_pos(name, namelen); int has_same_name = 0; + int is_file = 0; + int is_skipped = 1; int did_checkout = 0; int errs = 0; @@ -78,6 +82,12 @@ static int checkout_file(const char *name, const char *prefix) break; has_same_name = 1; pos++; + if (S_ISSPARSEDIR(ce->ce_mode)) + break; + is_file = 1; + if (!ignore_skip_worktree && ce_skip_worktree(ce)) + break; + is_skipped = 0; if (ce_stage(ce) != checkout_stage && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce))) continue; @@ -106,6 +116,11 @@ static int checkout_file(const char *name, const char *prefix) fprintf(stderr, "git checkout-index: %s ", name); if (!has_same_name) fprintf(stderr, "is not in the cache"); + else if (!is_file) + fprintf(stderr, "is a sparse directory"); + else if (is_skipped) + fprintf(stderr, "has skip-worktree enabled; " + "use '--ignore-skip-worktree-bits' to checkout"); else if (checkout_stage) fprintf(stderr, "does not exist at stage %d", checkout_stage); @@ -121,10 +136,27 @@ static int checkout_all(const char *prefix, int prefix_length) int i, errs = 0; struct cache_entry *last_ce = NULL; - /* TODO: audit for interaction with sparse-index. */ - ensure_full_index(&the_index); for (i = 0; i < active_nr ; i++) { struct cache_entry *ce = active_cache[i]; + + if (S_ISSPARSEDIR(ce->ce_mode)) { + if (!ce_skip_worktree(ce)) + BUG("sparse directory '%s' does not have skip-worktree set", ce->name); + + /* + * If the current entry is a sparse directory and skip-worktree + * entries are being checked out, expand the index and continue + * the loop on the current index position (now pointing to the + * first entry inside the expanded sparse directory). + */ + if (ignore_skip_worktree) { + ensure_full_index(&the_index); + ce = active_cache[i]; + } + } + + if (!ignore_skip_worktree && ce_skip_worktree(ce)) + continue; if (ce_stage(ce) != checkout_stage && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce))) continue; @@ -185,6 +217,8 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) struct option builtin_checkout_index_options[] = { OPT_BOOL('a', "all", &all, N_("check out all files in the index")), + OPT_BOOL(0, "ignore-skip-worktree-bits", &ignore_skip_worktree, + N_("do not skip files with skip-worktree set")), OPT__FORCE(&force, N_("force overwrite of existing files"), 0), OPT__QUIET(&quiet, N_("no warning for existing files and files not in index")), @@ -212,6 +246,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); prefix_length = prefix ? strlen(prefix) : 0; + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + if (read_cache() < 0) { die("invalid cache"); } diff --git a/builtin/checkout.c b/builtin/checkout.c index cc804ba8e1..d9b31bbb6d 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -9,6 +9,7 @@ #include "config.h" #include "diff.h" #include "dir.h" +#include "hook.h" #include "ll-merge.h" #include "lockfile.h" #include "merge-recursive.h" @@ -114,7 +115,7 @@ static void branch_info_release(struct branch_info *info) static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit, int changed) { - return run_hook_le(NULL, "post-checkout", + return run_hooks_l("post-checkout", oid_to_hex(old_commit ? &old_commit->object.oid : null_oid()), oid_to_hex(new_commit ? &new_commit->object.oid : null_oid()), changed ? "1" : "0", NULL); @@ -245,6 +246,7 @@ static int checkout_merged(int pos, const struct checkout *state, struct cache_entry *ce = active_cache[pos]; const char *path = ce->name; mmfile_t ancestor, ours, theirs; + enum ll_merge_result merge_status; int status; struct object_id oid; mmbuffer_t result_buf; @@ -275,13 +277,16 @@ static int checkout_merged(int pos, const struct checkout *state, memset(&ll_opts, 0, sizeof(ll_opts)); git_config_get_bool("merge.renormalize", &renormalize); ll_opts.renormalize = renormalize; - status = ll_merge(&result_buf, path, &ancestor, "base", - &ours, "ours", &theirs, "theirs", - state->istate, &ll_opts); + merge_status = ll_merge(&result_buf, path, &ancestor, "base", + &ours, "ours", &theirs, "theirs", + state->istate, &ll_opts); free(ancestor.ptr); free(ours.ptr); free(theirs.ptr); - if (status < 0 || !result_buf.ptr) { + if (merge_status == LL_MERGE_BINARY_CONFLICT) + warning("Cannot merge binary files: %s (%s vs. %s)", + path, "ours", "theirs"); + if (merge_status < 0 || !result_buf.ptr) { free(result_buf.ptr); return error(_("path '%s': cannot merge"), path); } @@ -904,7 +909,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts, opts->new_branch_force ? 1 : 0, opts->new_branch_log, opts->quiet, - opts->track); + opts->track, + 0); free(new_branch_info->name); free(new_branch_info->refname); new_branch_info->name = xstrdup(opts->new_branch); @@ -1602,9 +1608,10 @@ static int checkout_main(int argc, const char **argv, const char *prefix, opts->show_progress = -1; git_config(git_checkout_config, opts); - - prepare_repo_settings(the_repository); - the_repository->settings.command_requires_full_index = 0; + if (the_repository->gitdir) { + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + } opts->track = BRANCH_TRACK_UNSPECIFIED; diff --git a/builtin/clean.c b/builtin/clean.c index 3ff02bbbff..5466636e66 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -1009,6 +1009,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix) dir.flags |= DIR_KEEP_UNTRACKED_CONTENTS; } + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + if (read_cache() < 0) die(_("index file corrupt")); diff --git a/builtin/clone.c b/builtin/clone.c index 727e16e0ae..0d80b135c9 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -32,6 +32,7 @@ #include "connected.h" #include "packfile.h" #include "list-objects-filter-options.h" +#include "hook.h" /* * Overall FIXMEs: @@ -705,7 +706,7 @@ static int checkout(int submodule_progress) if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); - err |= run_hook_le(NULL, "post-checkout", oid_to_hex(null_oid()), + err |= run_hooks_l("post-checkout", oid_to_hex(null_oid()), oid_to_hex(&oid), "1", NULL); if (!err && (option_recurse_submodules.nr > 0)) { @@ -862,7 +863,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) const struct ref *refs, *remote_head; struct ref *remote_head_points_at = NULL; const struct ref *our_head_points_at; - struct ref *mapped_refs; + struct ref *mapped_refs = NULL; const struct ref *ref; struct strbuf key = STRBUF_INIT; struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT; @@ -1184,7 +1185,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) refs = transport_get_remote_refs(transport, &transport_ls_refs_options); - if (refs) { + if (refs) + mapped_refs = wanted_peer_refs(refs, &remote->fetch); + + if (mapped_refs) { int hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport)); /* @@ -1193,8 +1197,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) */ initialize_repository_version(hash_algo, 1); repo_set_hash_algo(the_repository, hash_algo); - - mapped_refs = wanted_peer_refs(refs, &remote->fetch); /* * transport_get_remote_refs() may return refs with null sha-1 * in mapped_refs (see struct transport->get_refs_list @@ -1233,14 +1235,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } else { const char *branch; - char *ref; + const char *ref; + char *ref_free = NULL; if (option_branch) die(_("Remote branch %s not found in upstream %s"), option_branch, remote_name); warning(_("You appear to have cloned an empty repository.")); - mapped_refs = NULL; our_head_points_at = NULL; remote_head_points_at = NULL; remote_head = NULL; @@ -1250,17 +1252,16 @@ int cmd_clone(int argc, const char **argv, const char *prefix) skip_prefix(transport_ls_refs_options.unborn_head_target, "refs/heads/", &branch)) { ref = transport_ls_refs_options.unborn_head_target; - transport_ls_refs_options.unborn_head_target = NULL; create_symref("HEAD", ref, reflog_msg.buf); } else { branch = git_default_branch_name(0); - ref = xstrfmt("refs/heads/%s", branch); + ref_free = xstrfmt("refs/heads/%s", branch); + ref = ref_free; } if (!option_bare) install_branch_config(0, branch, remote_name, ref); - - free(ref); + free(ref_free); } write_refspec_config(src_ref_prefix, our_head_points_at, @@ -1271,7 +1272,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (is_local) clone_local(path, git_dir); - else if (refs && complete_refs_before_fetch) { + else if (mapped_refs && complete_refs_before_fetch) { if (transport_fetch_refs(transport, mapped_refs)) die(_("remote transport reported error")); } @@ -1312,7 +1313,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) UNLEAK(repo); junk_mode = JUNK_LEAVE_ALL; - strvec_clear(&transport_ls_refs_options.ref_prefixes); - free(transport_ls_refs_options.unborn_head_target); + transport_ls_refs_options_release(&transport_ls_refs_options); return err; } diff --git a/builtin/diff.c b/builtin/diff.c index fa4683377e..bb7fafca61 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -28,9 +28,9 @@ static const char builtin_diff_usage[] = "git diff [<options>] [<commit>] [--] [<path>...]\n" " or: git diff [<options>] --cached [--merge-base] [<commit>] [--] [<path>...]\n" " or: git diff [<options>] [--merge-base] <commit> [<commit>...] <commit> [--] [<path>...]\n" -" or: git diff [<options>] <commit>...<commit>] [--] [<path>...]\n" -" or: git diff [<options>] <blob> <blob>]\n" -" or: git diff [<options>] --no-index [--] <path> <path>]\n" +" or: git diff [<options>] <commit>...<commit> [--] [<path>...]\n" +" or: git diff [<options>] <blob> <blob>\n" +" or: git diff [<options>] --no-index [--] <path> <path>\n" COMMON_DIFF_OPTIONS_HELP; static const char *blob_path(struct object_array_entry *entry) diff --git a/builtin/fetch.c b/builtin/fetch.c index 5f06b21f8e..5583f71ef3 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -76,6 +76,7 @@ static struct transport *gtransport; static struct transport *gsecondary; static const char *submodule_prefix = ""; static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT; +static int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT; static int recurse_submodules_default = RECURSE_SUBMODULES_ON_DEMAND; static int shown_url = 0; static struct refspec refmap = REFSPEC_INIT_FETCH; @@ -167,7 +168,7 @@ static struct option builtin_fetch_options[] = { N_("prune remote-tracking branches no longer on remote")), OPT_BOOL('P', "prune-tags", &prune_tags, N_("prune local tags no longer on remote and clobber changed tags")), - OPT_CALLBACK_F(0, "recurse-submodules", &recurse_submodules, N_("on-demand"), + OPT_CALLBACK_F(0, "recurse-submodules", &recurse_submodules_cli, N_("on-demand"), N_("control recursive fetching of submodules"), PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules), OPT_BOOL(0, "dry-run", &dry_run, @@ -1593,7 +1594,7 @@ static int do_fetch(struct transport *transport, } else remote_refs = NULL; - strvec_clear(&transport_ls_refs_options.ref_prefixes); + transport_ls_refs_options_release(&transport_ls_refs_options); ref_map = get_ref_map(transport->remote, remote_refs, rs, tags, &autotags); @@ -1609,12 +1610,14 @@ static int do_fetch(struct transport *transport, * don't care whether --tags was specified. */ if (rs->nr) { - prune_refs(rs, ref_map, transport->url); + retcode = prune_refs(rs, ref_map, transport->url); } else { - prune_refs(&transport->remote->fetch, - ref_map, - transport->url); + retcode = prune_refs(&transport->remote->fetch, + ref_map, + transport->url); } + if (retcode != 0) + retcode = 1; } if (fetch_and_consume_refs(transport, ref_map, worktrees)) { free_refs(ref_map); @@ -2014,11 +2017,35 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) } git_config(git_fetch_config, NULL); - prepare_repo_settings(the_repository); - the_repository->settings.command_requires_full_index = 0; + if (the_repository->gitdir) { + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + } argc = parse_options(argc, argv, prefix, builtin_fetch_options, builtin_fetch_usage, 0); + + if (recurse_submodules_cli != RECURSE_SUBMODULES_DEFAULT) + recurse_submodules = recurse_submodules_cli; + + if (negotiate_only) { + switch (recurse_submodules_cli) { + case RECURSE_SUBMODULES_OFF: + case RECURSE_SUBMODULES_DEFAULT: + /* + * --negotiate-only should never recurse into + * submodules. Skip it by setting recurse_submodules to + * RECURSE_SUBMODULES_OFF. + */ + recurse_submodules = RECURSE_SUBMODULES_OFF; + break; + + default: + die(_("options '%s' and '%s' cannot be used together"), + "--negotiate-only", "--recurse-submodules"); + } + } + if (recurse_submodules != RECURSE_SUBMODULES_OFF) { int *sfjc = submodule_fetch_jobs_config == -1 ? &submodule_fetch_jobs_config : NULL; @@ -2029,7 +2056,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) } if (negotiate_only && !negotiation_tip.nr) - die(_("--negotiate-only needs one or more --negotiate-tip=*")); + die(_("--negotiate-only needs one or more --negotiation-tip=*")); if (deepen_relative) { if (deepen_relative < 0) @@ -2100,7 +2127,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) gtransport->smart_options->acked_commits = &acked_commits; } else { warning(_("protocol does not support --negotiate-only, exiting")); - return 1; + result = 1; + goto cleanup; } if (server_options.nr) gtransport->server_options = &server_options; @@ -2156,7 +2184,16 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) strvec_clear(&options); } - string_list_clear(&list, 0); + /* + * Skip irrelevant tasks because we know objects were not + * fetched. + * + * NEEDSWORK: as a future optimization, we can return early + * whenever objects were not fetched e.g. if we already have all + * of them. + */ + if (negotiate_only) + goto cleanup; prepare_repo_settings(the_repository); if (fetch_write_commit_graph > 0 || @@ -2175,5 +2212,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (enable_auto_gc) run_auto_maintenance(verbosity < 0); + cleanup: + string_list_clear(&list, 0); return result; } diff --git a/builtin/gc.c b/builtin/gc.c index 8e60ef1eab..ffaf0daf5d 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -32,6 +32,7 @@ #include "remote.h" #include "object-store.h" #include "exec-cmd.h" +#include "hook.h" #define FAILED_RUN "failed to run %s" @@ -394,7 +395,7 @@ static int need_to_gc(void) else return 0; - if (run_hook_le(NULL, "pre-auto-gc", NULL)) + if (run_hooks("pre-auto-gc")) return 0; return 1; } diff --git a/builtin/hash-object.c b/builtin/hash-object.c index c7b3ad74c6..db9b253527 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -92,6 +92,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) int nongit = 0; unsigned flags = HASH_FORMAT_CHECK; const char *vpath = NULL; + char *vpath_free = NULL; const struct option hash_object_options[] = { OPT_STRING('t', NULL, &type, N_("type"), N_("object type")), OPT_BIT('w', NULL, &flags, N_("write the object into the object database"), @@ -114,8 +115,10 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) else prefix = setup_git_directory_gently(&nongit); - if (vpath && prefix) - vpath = prefix_filename(prefix, vpath); + if (vpath && prefix) { + vpath_free = prefix_filename(prefix, vpath); + vpath = vpath_free; + } git_config(git_default_config, NULL); @@ -156,5 +159,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) if (stdin_paths) hash_stdin_paths(type, no_filters, flags, literally); + free(vpath_free); + return 0; } diff --git a/builtin/hook.c b/builtin/hook.c new file mode 100644 index 0000000000..54e5c6ec93 --- /dev/null +++ b/builtin/hook.c @@ -0,0 +1,84 @@ +#include "cache.h" +#include "builtin.h" +#include "config.h" +#include "hook.h" +#include "parse-options.h" +#include "strbuf.h" +#include "strvec.h" + +#define BUILTIN_HOOK_RUN_USAGE \ + N_("git hook run [--ignore-missing] <hook-name> [-- <hook-args>]") + +static const char * const builtin_hook_usage[] = { + BUILTIN_HOOK_RUN_USAGE, + NULL +}; + +static const char * const builtin_hook_run_usage[] = { + BUILTIN_HOOK_RUN_USAGE, + NULL +}; + +static int run(int argc, const char **argv, const char *prefix) +{ + int i; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + int ignore_missing = 0; + const char *hook_name; + struct option run_options[] = { + OPT_BOOL(0, "ignore-missing", &ignore_missing, + N_("silently ignore missing requested <hook-name>")), + OPT_END(), + }; + int ret; + + argc = parse_options(argc, argv, prefix, run_options, + builtin_hook_run_usage, + PARSE_OPT_KEEP_DASHDASH); + + if (!argc) + goto usage; + + /* + * Having a -- for "run" when providing <hook-args> is + * mandatory. + */ + if (argc > 1 && strcmp(argv[1], "--") && + strcmp(argv[1], "--end-of-options")) + goto usage; + + /* Add our arguments, start after -- */ + for (i = 2 ; i < argc; i++) + strvec_push(&opt.args, argv[i]); + + /* Need to take into account core.hooksPath */ + git_config(git_default_config, NULL); + + hook_name = argv[0]; + if (!ignore_missing) + opt.error_if_missing = 1; + ret = run_hooks_opt(hook_name, &opt); + if (ret < 0) /* error() return */ + ret = 1; + return ret; +usage: + usage_with_options(builtin_hook_run_usage, run_options); +} + +int cmd_hook(int argc, const char **argv, const char *prefix) +{ + struct option builtin_hook_options[] = { + OPT_END(), + }; + + argc = parse_options(argc, argv, NULL, builtin_hook_options, + builtin_hook_usage, PARSE_OPT_STOP_AT_NON_OPTION); + if (!argc) + goto usage; + + if (!strcmp(argv[0], "run")) + return run(argc, argv, prefix); + +usage: + usage_with_options(builtin_hook_usage, builtin_hook_options); +} diff --git a/builtin/log.c b/builtin/log.c index 4b493408cc..093d0d2655 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -35,6 +35,7 @@ #include "repository.h" #include "commit-reach.h" #include "range-diff.h" +#include "tmp-objdir.h" #define MAIL_DEFAULT_WRAP 72 #define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100 @@ -422,6 +423,13 @@ static int cmd_log_walk(struct rev_info *rev) int saved_nrl = 0; int saved_dcctc = 0; + if (rev->remerge_diff) { + rev->remerge_objdir = tmp_objdir_create("remerge-diff"); + if (!rev->remerge_objdir) + die(_("unable to create temporary object directory")); + tmp_objdir_replace_primary_odb(rev->remerge_objdir, 1); + } + if (rev->early_output) setup_early_output(); @@ -464,6 +472,11 @@ static int cmd_log_walk(struct rev_info *rev) rev->diffopt.no_free = 0; diff_free(&rev->diffopt); + if (rev->remerge_diff) { + tmp_objdir_destroy(rev->remerge_objdir); + rev->remerge_objdir = NULL; + } + if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF && rev->diffopt.flags.check_failed) { return 02; @@ -1958,6 +1971,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) die(_("--name-status does not make sense")); if (rev.diffopt.output_format & DIFF_FORMAT_CHECKDIFF) die(_("--check does not make sense")); + if (rev.remerge_diff) + die(_("--remerge-diff does not make sense")); if (!use_patch_format && (!rev.diffopt.output_format || diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 44448fa61d..d856085e94 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -155,6 +155,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) ref_array_clear(&ref_array); if (transport_disconnect(transport)) - return 1; + status = 1; + transport_ls_refs_options_release(&transport_options); return status; } diff --git a/builtin/merge.c b/builtin/merge.c index 74e53cf20a..a94a03384a 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -490,7 +490,7 @@ static void finish(struct commit *head_commit, } /* Run a post-merge hook */ - run_hook_le(NULL, "post-merge", squash ? "1" : "0", NULL); + run_hooks_l("post-merge", squash ? "1" : "0", NULL); apply_autostash(git_path_merge_autostash(the_repository)); strbuf_release(&reflog_message); @@ -1273,7 +1273,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0; struct commit_list *common = NULL; const char *best_strategy = NULL, *wt_strategy = NULL; - struct commit_list *remoteheads, *p; + struct commit_list *remoteheads = NULL, *p; void *branch_to_free; int orig_argc = argc; @@ -1568,8 +1568,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (autostash) create_autostash(the_repository, - git_path_merge_autostash(the_repository), - "merge"); + git_path_merge_autostash(the_repository)); if (checkout_fast_forward(the_repository, &head_commit->object.oid, &commit->object.oid, @@ -1640,8 +1639,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (autostash) create_autostash(the_repository, - git_path_merge_autostash(the_repository), - "merge"); + git_path_merge_autostash(the_repository)); /* We are going to make a new commit. */ git_committer_info(IDENT_STRICT); @@ -1752,6 +1750,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix) ret = suggest_conflicts(); done: + if (!automerge_was_ok) { + free_commit_list(common); + free_commit_list(remoteheads); + } strbuf_release(&buf); free(branch_to_free); return ret; diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 27f60153a6..138e3c30a2 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -527,7 +527,7 @@ static void name_rev_line(char *p, struct name_ref_data *data) int cmd_name_rev(int argc, const char **argv, const char *prefix) { struct object_array revs = OBJECT_ARRAY_INIT; - int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0; + int all = 0, annotate_stdin = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0; struct name_ref_data data = { 0, 0, STRING_LIST_INIT_NODUP, STRING_LIST_INIT_NODUP }; struct option opts[] = { OPT_BOOL(0, "name-only", &data.name_only, N_("print only ref-based names (no object names)")), @@ -538,7 +538,8 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) N_("ignore refs matching <pattern>")), OPT_GROUP(""), OPT_BOOL(0, "all", &all, N_("list all commits reachable from all refs")), - OPT_BOOL(0, "stdin", &transform_stdin, N_("read from stdin")), + OPT_BOOL(0, "stdin", &transform_stdin, N_("deprecated: use annotate-stdin instead")), + OPT_BOOL(0, "annotate-stdin", &annotate_stdin, N_("annotate text from stdin")), OPT_BOOL(0, "undefined", &allow_undefined, N_("allow to print `undefined` names (default)")), OPT_BOOL(0, "always", &always, N_("show abbreviated commit object as fallback")), @@ -554,11 +555,19 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) init_commit_rev_name(&rev_names); git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, opts, name_rev_usage, 0); - if (all + transform_stdin + !!argc > 1) { + + if (transform_stdin) { + warning("--stdin is deprecated. Please use --annotate-stdin instead, " + "which is functionally equivalent.\n" + "This option will be removed in a future release."); + annotate_stdin = 1; + } + + if (all + annotate_stdin + !!argc > 1) { error("Specify either a list, or --all, not both!"); usage_with_options(name_rev_usage, opts); } - if (all || transform_stdin) + if (all || annotate_stdin) cutoff = 0; for (; argc; argc--, argv++) { @@ -613,15 +622,14 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) for_each_ref(name_ref, &data); name_tips(); - if (transform_stdin) { - char buffer[2048]; + if (annotate_stdin) { + struct strbuf sb = STRBUF_INIT; - while (!feof(stdin)) { - char *p = fgets(buffer, sizeof(buffer), stdin); - if (!p) - break; - name_rev_line(p, &data); + while (strbuf_getline(&sb, stdin) != EOF) { + strbuf_addch(&sb, '\n'); + name_rev_line(sb.buf, &data); } + strbuf_release(&sb); } else if (all) { int i, max; diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index ba2006f221..87cb7b45c3 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -3976,9 +3976,11 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) read_replace_refs = 0; sparse = git_env_bool("GIT_TEST_PACK_SPARSE", -1); - prepare_repo_settings(the_repository); - if (sparse < 0) - sparse = the_repository->settings.pack_use_sparse; + if (the_repository->gitdir) { + prepare_repo_settings(the_repository); + if (sparse < 0) + sparse = the_repository->settings.pack_use_sparse; + } reset_pack_idx_option(&pack_idx_opts); git_config(git_pack_config, NULL); diff --git a/builtin/patch-id.c b/builtin/patch-id.c index 822ffff51f..881fcf3273 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c @@ -32,8 +32,12 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after) n = strspn(q, digits); if (q[n] == ',') { q += n + 1; + *p_before = atoi(q); n = strspn(q, digits); + } else { + *p_before = 1; } + if (n == 0 || q[n] != ' ' || q[n+1] != '+') return 0; @@ -41,13 +45,14 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after) n = strspn(r, digits); if (r[n] == ',') { r += n + 1; + *p_after = atoi(r); n = strspn(r, digits); + } else { + *p_after = 1; } if (n == 0) return 0; - *p_before = atoi(q); - *p_after = atoi(r); return 1; } diff --git a/builtin/pull.c b/builtin/pull.c index 100cbf9fb8..3768552e68 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -994,8 +994,10 @@ int cmd_pull(int argc, const char **argv, const char *prefix) set_reflog_message(argc, argv); git_config(git_pull_config, NULL); - prepare_repo_settings(the_repository); - the_repository->settings.command_requires_full_index = 0; + if (the_repository->gitdir) { + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + } argc = parse_options(argc, argv, prefix, pull_options, pull_usage, 0); @@ -1038,14 +1040,13 @@ int cmd_pull(int argc, const char **argv, const char *prefix) oidclr(&orig_head); if (opt_rebase) { - int autostash = config_autostash; - if (opt_autostash != -1) - autostash = opt_autostash; + if (opt_autostash == -1) + opt_autostash = config_autostash; if (is_null_oid(&orig_head) && !is_cache_unborn()) die(_("Updating an unborn branch with changes added to the index.")); - if (!autostash) + if (!opt_autostash) require_clean_work_tree(the_repository, N_("pull with rebase"), _("please commit or stash them."), 1, 0); diff --git a/builtin/rebase.c b/builtin/rebase.c index 36490d06c8..d858add3fe 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -28,6 +28,7 @@ #include "sequencer.h" #include "rebase-interactive.h" #include "reset.h" +#include "hook.h" #define DEFAULT_REFLOG_ACTION "rebase" @@ -570,7 +571,8 @@ static int finish_rebase(struct rebase_options *opts) static int move_to_original_branch(struct rebase_options *opts) { - struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT; + struct strbuf branch_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT; + struct reset_head_opts ropts = { 0 }; int ret; if (!opts->head_name) @@ -579,16 +581,17 @@ static int move_to_original_branch(struct rebase_options *opts) if (!opts->onto) BUG("move_to_original_branch without onto"); - strbuf_addf(&orig_head_reflog, "rebase finished: %s onto %s", + strbuf_addf(&branch_reflog, "rebase finished: %s onto %s", opts->head_name, oid_to_hex(&opts->onto->object.oid)); strbuf_addf(&head_reflog, "rebase finished: returning to %s", opts->head_name); - ret = reset_head(the_repository, NULL, "", opts->head_name, - RESET_HEAD_REFS_ONLY, - orig_head_reflog.buf, head_reflog.buf, - DEFAULT_REFLOG_ACTION); + ropts.branch = opts->head_name; + ropts.flags = RESET_HEAD_REFS_ONLY; + ropts.branch_msg = branch_reflog.buf; + ropts.head_msg = head_reflog.buf; + ret = reset_head(the_repository, &ropts); - strbuf_release(&orig_head_reflog); + strbuf_release(&branch_reflog); strbuf_release(&head_reflog); return ret; } @@ -670,13 +673,15 @@ static int run_am(struct rebase_options *opts) status = run_command(&format_patch); if (status) { + struct reset_head_opts ropts = { 0 }; unlink(rebased_patches); free(rebased_patches); strvec_clear(&am.args); - reset_head(the_repository, &opts->orig_head, "checkout", - opts->head_name, 0, - "HEAD", NULL, DEFAULT_REFLOG_ACTION); + ropts.oid = &opts->orig_head; + ropts.branch = opts->head_name; + ropts.default_reflog_action = DEFAULT_REFLOG_ACTION; + reset_head(the_repository, &ropts); error(_("\ngit encountered an error while preparing the " "patches to replay\n" "these revisions:\n" @@ -812,6 +817,26 @@ static int rebase_config(const char *var, const char *value, void *data) return git_default_config(var, value, data); } +static int checkout_up_to_date(struct rebase_options *options) +{ + struct strbuf buf = STRBUF_INIT; + struct reset_head_opts ropts = { 0 }; + int ret = 0; + + strbuf_addf(&buf, "%s: checkout %s", + getenv(GIT_REFLOG_ACTION_ENVIRONMENT), + options->switch_to); + ropts.oid = &options->orig_head; + ropts.branch = options->head_name; + ropts.flags = RESET_HEAD_RUN_POST_CHECKOUT_HOOK; + ropts.head_msg = buf.buf; + if (reset_head(the_repository, &ropts) < 0) + ret = error(_("could not switch to %s"), options->switch_to); + strbuf_release(&buf); + + return ret; +} + /* * Determines whether the commits in from..to are linear, i.e. contain * no merge commits. This function *expects* `from` to be an ancestor of @@ -1017,6 +1042,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) int reschedule_failed_exec = -1; int allow_preemptive_ff = 1; int preserve_merges_selected = 0; + struct reset_head_opts ropts = { 0 }; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -1254,9 +1280,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) rerere_clear(the_repository, &merge_rr); string_list_clear(&merge_rr, 1); - - if (reset_head(the_repository, NULL, "reset", NULL, RESET_HEAD_HARD, - NULL, NULL, DEFAULT_REFLOG_ACTION) < 0) + ropts.flags = RESET_HEAD_HARD; + if (reset_head(the_repository, &ropts) < 0) die(_("could not discard worktree changes")); remove_branch_state(the_repository, 0); if (read_basic_state(&options)) @@ -1273,9 +1298,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (read_basic_state(&options)) exit(1); - if (reset_head(the_repository, &options.orig_head, "reset", - options.head_name, RESET_HEAD_HARD, - NULL, NULL, DEFAULT_REFLOG_ACTION) < 0) + ropts.oid = &options.orig_head; + ropts.branch = options.head_name; + ropts.flags = RESET_HEAD_HARD; + ropts.default_reflog_action = DEFAULT_REFLOG_ACTION; + if (reset_head(the_repository, &ropts) < 0) die(_("could not move back to %s"), oid_to_hex(&options.orig_head)); remove_branch_state(the_repository, 0); @@ -1641,10 +1668,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (repo_read_index(the_repository) < 0) die(_("could not read index")); - if (options.autostash) { - create_autostash(the_repository, state_dir_path("autostash", &options), - DEFAULT_REFLOG_ACTION); - } + if (options.autostash) + create_autostash(the_repository, + state_dir_path("autostash", &options)); + if (require_clean_work_tree(the_repository, "rebase", _("Please commit or stash them."), 1, 1)) { @@ -1673,21 +1700,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (!(options.flags & REBASE_FORCE)) { /* Lazily switch to the target branch if needed... */ if (options.switch_to) { - strbuf_reset(&buf); - strbuf_addf(&buf, "%s: checkout %s", - getenv(GIT_REFLOG_ACTION_ENVIRONMENT), - options.switch_to); - if (reset_head(the_repository, - &options.orig_head, "checkout", - options.head_name, - RESET_HEAD_RUN_POST_CHECKOUT_HOOK, - NULL, buf.buf, - DEFAULT_REFLOG_ACTION) < 0) { - ret = error(_("could not switch to " - "%s"), - options.switch_to); + ret = checkout_up_to_date(&options); + if (ret) goto cleanup; - } } if (!(options.flags & REBASE_NO_QUIET)) @@ -1712,7 +1727,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) /* If a hook exists, give it a chance to interrupt*/ if (!ok_to_skip_pre_rebase && - run_hook_le(NULL, "pre-rebase", options.upstream_arg, + run_hooks_l("pre-rebase", options.upstream_arg, argc ? argv[0] : NULL, NULL)) die(_("The pre-rebase hook refused to rebase.")); @@ -1754,10 +1769,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&msg, "%s: checkout %s", getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name); - if (reset_head(the_repository, &options.onto->object.oid, "checkout", NULL, - RESET_HEAD_DETACH | RESET_ORIG_HEAD | - RESET_HEAD_RUN_POST_CHECKOUT_HOOK, - NULL, msg.buf, DEFAULT_REFLOG_ACTION)) + ropts.oid = &options.onto->object.oid; + ropts.orig_head = &options.orig_head, + ropts.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD | + RESET_HEAD_RUN_POST_CHECKOUT_HOOK; + ropts.head_msg = msg.buf; + ropts.default_reflog_action = DEFAULT_REFLOG_ACTION; + if (reset_head(the_repository, &ropts)) die(_("Could not detach HEAD")); strbuf_release(&msg); @@ -1772,9 +1790,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&msg, "rebase finished: %s onto %s", options.head_name ? options.head_name : "detached HEAD", oid_to_hex(&options.onto->object.oid)); - reset_head(the_repository, NULL, "Fast-forwarded", options.head_name, - RESET_HEAD_REFS_ONLY, "HEAD", msg.buf, - DEFAULT_REFLOG_ACTION); + memset(&ropts, 0, sizeof(ropts)); + ropts.branch = options.head_name; + ropts.flags = RESET_HEAD_REFS_ONLY; + ropts.head_msg = msg.buf; + reset_head(the_repository, &ropts); strbuf_release(&msg); ret = finish_rebase(&options); goto cleanup; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 9f4a0b816c..d10aeb7e78 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -581,32 +581,19 @@ static char *prepare_push_cert_nonce(const char *path, timestamp_t stamp) return strbuf_detach(&buf, NULL); } -/* - * NEEDSWORK: reuse find_commit_header() from jk/commit-author-parsing - * after dropping "_commit" from its name and possibly moving it out - * of commit.c - */ static char *find_header(const char *msg, size_t len, const char *key, const char **next_line) { - int key_len = strlen(key); - const char *line = msg; - - while (line && line < msg + len) { - const char *eol = strchrnul(line, '\n'); - - if ((msg + len <= eol) || line == eol) - return NULL; - if (line + key_len < eol && - !memcmp(line, key, key_len) && line[key_len] == ' ') { - int offset = key_len + 1; - if (next_line) - *next_line = *eol ? eol + 1 : eol; - return xmemdupz(line + offset, (eol - line) - offset); - } - line = *eol ? eol + 1 : NULL; - } - return NULL; + size_t out_len; + const char *val = find_header_mem(msg, len, key, &out_len); + + if (!val) + return NULL; + + if (next_line) + *next_line = val + out_len + 1; + + return xmemdupz(val, out_len); } /* @@ -1424,9 +1411,12 @@ static const char *push_to_checkout(unsigned char *hash, struct strvec *env, const char *work_tree) { + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + strvec_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree)); - if (run_hook_le(env->v, push_to_checkout_hook, - hash_to_hex(hash), NULL)) + strvec_pushv(&opt.env, env->v); + strvec_push(&opt.args, hash_to_hex(hash)); + if (run_hooks_opt(push_to_checkout_hook, &opt)) return "push-to-checkout hook declined"; else return NULL; @@ -1972,6 +1962,15 @@ static void execute_commands(struct command *commands, } /* + * If there is no command ready to run, should return directly to destroy + * temporary data in the quarantine area. + */ + for (cmd = commands; cmd && cmd->error_string; cmd = cmd->next) + ; /* nothing */ + if (!cmd) + return; + + /* * Now we'll start writing out refs, which means the objects need * to be in their final positions so that other processes can see them. */ diff --git a/builtin/reflog.c b/builtin/reflog.c index a4b1dd27e1..85b838720c 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -12,15 +12,6 @@ #include "reachable.h" #include "worktree.h" -/* NEEDSWORK: switch to using parse_options */ -static const char reflog_expire_usage[] = -N_("git reflog expire [--expire=<time>] " - "[--expire-unreachable=<time>] " - "[--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] " - "[--verbose] [--all] <refs>..."); -static const char reflog_delete_usage[] = -N_("git reflog delete [--rewrite] [--updateref] " - "[--dry-run | -n] [--verbose] <refs>..."); static const char reflog_exists_usage[] = N_("git reflog exists <ref>"); @@ -29,6 +20,7 @@ static timestamp_t default_reflog_expire_unreachable; struct cmd_reflog_expire_cb { int stalefix; + int explicit_expiry; timestamp_t expire_total; timestamp_t expire_unreachable; int recno; @@ -520,18 +512,18 @@ static int reflog_expire_config(const char *var, const char *value, void *cb) return 0; } -static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, const char *ref) +static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, const char *ref) { struct reflog_expire_cfg *ent; - if (slot == (EXPIRE_TOTAL|EXPIRE_UNREACH)) + if (cb->explicit_expiry == (EXPIRE_TOTAL|EXPIRE_UNREACH)) return; /* both given explicitly -- nothing to tweak */ for (ent = reflog_expire_cfg; ent; ent = ent->next) { if (!wildmatch(ent->pattern, ref, 0)) { - if (!(slot & EXPIRE_TOTAL)) + if (!(cb->explicit_expiry & EXPIRE_TOTAL)) cb->expire_total = ent->expire_total; - if (!(slot & EXPIRE_UNREACH)) + if (!(cb->explicit_expiry & EXPIRE_UNREACH)) cb->expire_unreachable = ent->expire_unreachable; return; } @@ -541,29 +533,89 @@ static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, c * If unconfigured, make stash never expire */ if (!strcmp(ref, "refs/stash")) { - if (!(slot & EXPIRE_TOTAL)) + if (!(cb->explicit_expiry & EXPIRE_TOTAL)) cb->expire_total = 0; - if (!(slot & EXPIRE_UNREACH)) + if (!(cb->explicit_expiry & EXPIRE_UNREACH)) cb->expire_unreachable = 0; return; } /* Nothing matched -- use the default value */ - if (!(slot & EXPIRE_TOTAL)) + if (!(cb->explicit_expiry & EXPIRE_TOTAL)) cb->expire_total = default_reflog_expire; - if (!(slot & EXPIRE_UNREACH)) + if (!(cb->explicit_expiry & EXPIRE_UNREACH)) cb->expire_unreachable = default_reflog_expire_unreachable; } +static const char * reflog_expire_usage[] = { + N_("git reflog expire [--expire=<time>] " + "[--expire-unreachable=<time>] " + "[--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] " + "[--verbose] [--all] <refs>..."), + NULL +}; + +static int expire_unreachable_callback(const struct option *opt, + const char *arg, + int unset) +{ + struct cmd_reflog_expire_cb *cmd = opt->value; + + if (parse_expiry_date(arg, &cmd->expire_unreachable)) + die(_("invalid timestamp '%s' given to '--%s'"), + arg, opt->long_name); + + cmd->explicit_expiry |= EXPIRE_UNREACH; + return 0; +} + +static int expire_total_callback(const struct option *opt, + const char *arg, + int unset) +{ + struct cmd_reflog_expire_cb *cmd = opt->value; + + if (parse_expiry_date(arg, &cmd->expire_total)) + die(_("invalid timestamp '%s' given to '--%s'"), + arg, opt->long_name); + + cmd->explicit_expiry |= EXPIRE_TOTAL; + return 0; +} + static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) { struct cmd_reflog_expire_cb cmd = { 0 }; timestamp_t now = time(NULL); int i, status, do_all, all_worktrees = 1; - int explicit_expiry = 0; unsigned int flags = 0; int verbose = 0; reflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent; + const struct option options[] = { + OPT_BIT(0, "dry-run", &flags, N_("do not actually prune any entries"), + EXPIRE_REFLOGS_DRY_RUN), + OPT_BIT(0, "rewrite", &flags, + N_("rewrite the old SHA1 with the new SHA1 of the entry that now precedes it"), + EXPIRE_REFLOGS_REWRITE), + OPT_BIT(0, "updateref", &flags, + N_("update the reference to the value of the top reflog entry"), + EXPIRE_REFLOGS_UPDATE_REF), + OPT_BOOL(0, "verbose", &verbose, N_("print extra information on screen.")), + OPT_CALLBACK_F(0, "expire", &cmd, N_("timestamp"), + N_("prune entries older than the specified time"), + PARSE_OPT_NONEG, + expire_total_callback), + OPT_CALLBACK_F(0, "expire-unreachable", &cmd, N_("timestamp"), + N_("prune entries older than <time> that are not reachable from the current tip of the branch"), + PARSE_OPT_NONEG, + expire_unreachable_callback), + OPT_BOOL(0, "stale-fix", &cmd.stalefix, + N_("prune any reflog entries that point to broken commits")), + OPT_BOOL(0, "all", &do_all, N_("process the reflogs of all references")), + OPT_BOOL(1, "single-worktree", &all_worktrees, + N_("limits processing to reflogs from the current worktree only.")), + OPT_END() + }; default_reflog_expire_unreachable = now - 30 * 24 * 3600; default_reflog_expire = now - 90 * 24 * 3600; @@ -572,45 +624,11 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) save_commit_buffer = 0; do_all = status = 0; + cmd.explicit_expiry = 0; cmd.expire_total = default_reflog_expire; cmd.expire_unreachable = default_reflog_expire_unreachable; - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - - if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n")) - flags |= EXPIRE_REFLOGS_DRY_RUN; - else if (skip_prefix(arg, "--expire=", &arg)) { - if (parse_expiry_date(arg, &cmd.expire_total)) - die(_("'%s' is not a valid timestamp"), arg); - explicit_expiry |= EXPIRE_TOTAL; - } - else if (skip_prefix(arg, "--expire-unreachable=", &arg)) { - if (parse_expiry_date(arg, &cmd.expire_unreachable)) - die(_("'%s' is not a valid timestamp"), arg); - explicit_expiry |= EXPIRE_UNREACH; - } - else if (!strcmp(arg, "--stale-fix")) - cmd.stalefix = 1; - else if (!strcmp(arg, "--rewrite")) - flags |= EXPIRE_REFLOGS_REWRITE; - else if (!strcmp(arg, "--updateref")) - flags |= EXPIRE_REFLOGS_UPDATE_REF; - else if (!strcmp(arg, "--all")) - do_all = 1; - else if (!strcmp(arg, "--single-worktree")) - all_worktrees = 0; - else if (!strcmp(arg, "--verbose")) - verbose = 1; - else if (!strcmp(arg, "--")) { - i++; - break; - } - else if (arg[0] == '-') - usage(_(reflog_expire_usage)); - else - break; - } + argc = parse_options(argc, argv, prefix, options, reflog_expire_usage, 0); if (verbose) should_prune_fn = should_expire_reflog_ent_verbose; @@ -657,7 +675,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) .dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN), }; - set_reflog_expiry_param(&cb.cmd, explicit_expiry, item->string); + set_reflog_expiry_param(&cb.cmd, item->string); status |= reflog_expire(item->string, flags, reflog_expiry_prepare, should_prune_fn, @@ -667,7 +685,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) string_list_clear(&collected.reflogs, 0); } - for (; i < argc; i++) { + for (i = 0; i < argc; i++) { char *ref; struct expire_reflog_policy_cb cb = { .cmd = cmd }; @@ -675,7 +693,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) status |= error(_("%s points nowhere!"), argv[i]); continue; } - set_reflog_expiry_param(&cb.cmd, explicit_expiry, ref); + set_reflog_expiry_param(&cb.cmd, ref); status |= reflog_expire(ref, flags, reflog_expiry_prepare, should_prune_fn, @@ -696,6 +714,12 @@ static int count_reflog_ent(struct object_id *ooid, struct object_id *noid, return 0; } +static const char * reflog_delete_usage[] = { + N_("git reflog delete [--rewrite] [--updateref] " + "[--dry-run | -n] [--verbose] <refs>..."), + NULL +}; + static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) { struct cmd_reflog_expire_cb cmd = { 0 }; @@ -703,34 +727,28 @@ static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) unsigned int flags = 0; int verbose = 0; reflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent; - - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n")) - flags |= EXPIRE_REFLOGS_DRY_RUN; - else if (!strcmp(arg, "--rewrite")) - flags |= EXPIRE_REFLOGS_REWRITE; - else if (!strcmp(arg, "--updateref")) - flags |= EXPIRE_REFLOGS_UPDATE_REF; - else if (!strcmp(arg, "--verbose")) - verbose = 1; - else if (!strcmp(arg, "--")) { - i++; - break; - } - else if (arg[0] == '-') - usage(_(reflog_delete_usage)); - else - break; - } + const struct option options[] = { + OPT_BIT(0, "dry-run", &flags, N_("do not actually prune any entries"), + EXPIRE_REFLOGS_DRY_RUN), + OPT_BIT(0, "rewrite", &flags, + N_("rewrite the old SHA1 with the new SHA1 of the entry that now precedes it"), + EXPIRE_REFLOGS_REWRITE), + OPT_BIT(0, "updateref", &flags, + N_("update the reference to the value of the top reflog entry"), + EXPIRE_REFLOGS_UPDATE_REF), + OPT_BOOL(0, "verbose", &verbose, N_("print extra information on screen.")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, reflog_delete_usage, 0); if (verbose) should_prune_fn = should_expire_reflog_ent_verbose; - if (argc - i < 1) + if (argc < 1) return error(_("no reflog specified to delete")); - for ( ; i < argc; i++) { + for (i = 0; i < argc; i++) { const char *spec = strstr(argv[i], "@{"); char *ep, *ref; int recno; diff --git a/builtin/reset.c b/builtin/reset.c index b97745ee94..75b8d86481 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -204,10 +204,16 @@ static int pathspec_needs_expanded_index(const struct pathspec *pathspec) /* * Special case: if the pattern is a path inside the cone * followed by only wildcards, the pattern cannot match - * partial sparse directories, so we don't expand the index. + * partial sparse directories, so we know we don't need to + * expand the index. + * + * Examples: + * - in-cone/foo***: doesn't need expanded index + * - not-in-cone/bar*: may need expanded index + * - **.c: may need expanded index */ - if (path_in_cone_mode_sparse_checkout(item.original, &the_index) && - strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len) + if (strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len && + path_in_cone_mode_sparse_checkout(item.original, &the_index)) continue; for (pos = 0; pos < active_nr; pos++) { diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index 679c107036..a311483a7d 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -185,6 +185,8 @@ static void clean_tracked_sparse_directories(struct repository *r) item->string); } + strvec_clear(&s); + clear_pathspec(&p); dir_clear(&dir); } @@ -471,6 +473,9 @@ static int sparse_checkout_init(int argc, const char **argv) FILE *fp; /* assume we are in a fresh repo, but update the sparse-checkout file */ + if (safe_create_leading_directories(sparse_filename)) + die(_("unable to create leading directories of %s"), + sparse_filename); fp = xfopen(sparse_filename, "w"); if (!fp) die(_("failed to open '%s'"), sparse_filename); diff --git a/builtin/stash.c b/builtin/stash.c index 86cd0b456e..5897febfbe 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -788,7 +788,6 @@ static int list_stash(int argc, const char **argv, const char *prefix) static int show_stat = 1; static int show_patch; static int show_include_untracked; -static int use_legacy_stash; static int git_stash_config(const char *var, const char *value, void *cb) { @@ -804,10 +803,6 @@ static int git_stash_config(const char *var, const char *value, void *cb) show_include_untracked = git_config_bool(var, value); return 0; } - if (!strcmp(var, "stash.usebuiltin")) { - use_legacy_stash = !git_config_bool(var, value); - return 0; - } return git_diff_basic_config(var, value, cb); } @@ -1782,11 +1777,6 @@ int cmd_stash(int argc, const char **argv, const char *prefix) git_config(git_stash_config, NULL); - if (use_legacy_stash || - !git_env_bool("GIT_TEST_STASH_USE_BUILTIN", -1)) - warning(_("the stash.useBuiltin support has been removed!\n" - "See its entry in 'git help config' for details.")); - argc = parse_options(argc, argv, prefix, options, git_stash_usage, PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); @@ -1819,8 +1809,8 @@ int cmd_stash(int argc, const char **argv, const char *prefix) else if (!strcmp(argv[0], "save")) return !!save_stash(argc, argv, prefix); else if (*argv[0] != '-') - usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]), - git_stash_usage, options); + usage_msg_optf(_("unknown subcommand: %s"), + git_stash_usage, options, argv[0]); /* Assume 'stash push' */ strvec_push(&args, "push"); diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index c5d3fc3817..33c82c3ab9 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -20,6 +20,7 @@ #include "diff.h" #include "object-store.h" #include "advice.h" +#include "branch.h" #define OPT_QUIET (1 << 0) #define OPT_CACHED (1 << 1) @@ -2984,6 +2985,42 @@ static int module_set_branch(int argc, const char **argv, const char *prefix) return !!ret; } +static int module_create_branch(int argc, const char **argv, const char *prefix) +{ + enum branch_track track; + int quiet = 0, force = 0, reflog = 0, dry_run = 0; + + struct option options[] = { + OPT__QUIET(&quiet, N_("print only error messages")), + OPT__FORCE(&force, N_("force creation"), 0), + OPT_BOOL(0, "create-reflog", &reflog, + N_("create the branch's reflog")), + OPT_SET_INT('t', "track", &track, + N_("set up tracking mode (see git-pull(1))"), + BRANCH_TRACK_EXPLICIT), + OPT__DRY_RUN(&dry_run, + N_("show whether the branch would be created")), + OPT_END() + }; + const char *const usage[] = { + N_("git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] [-n|--dry-run] <name> <start_oid> <start_name>"), + NULL + }; + + git_config(git_default_config, NULL); + track = git_branch_track; + argc = parse_options(argc, argv, prefix, options, usage, 0); + + if (argc != 3) + usage_with_options(usage, options); + + if (!quiet && !dry_run) + printf_ln(_("creating branch '%s'"), argv[0]); + + create_branches_recursively(the_repository, argv[0], argv[1], argv[2], + force, reflog, quiet, track, dry_run); + return 0; +} struct add_data { const char *prefix; const char *branch; @@ -3390,6 +3427,7 @@ static struct cmd_struct commands[] = { {"config", module_config, 0}, {"set-url", module_set_url, 0}, {"set-branch", module_set_branch, 0}, + {"create-branch", module_create_branch, 0}, }; int cmd_submodule__helper(int argc, const char **argv, const char *prefix) diff --git a/builtin/update-index.c b/builtin/update-index.c index 187203e8bb..aafe7eeac2 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -606,7 +606,7 @@ static struct cache_entry *read_one_ent(const char *which, error("%s: not in %s branch.", path, which); return NULL; } - if (mode == S_IFDIR) { + if (!the_index.sparse_index && mode == S_IFDIR) { if (which) error("%s: not a blob in %s branch.", path, which); return NULL; @@ -743,8 +743,6 @@ static int do_reupdate(int ac, const char **av, */ has_head = 0; redo: - /* TODO: audit for interaction with sparse-index. */ - ensure_full_index(&the_index); for (pos = 0; pos < active_nr; pos++) { const struct cache_entry *ce = active_cache[pos]; struct cache_entry *old = NULL; @@ -761,6 +759,16 @@ static int do_reupdate(int ac, const char **av, discard_cache_entry(old); continue; /* unchanged */ } + + /* At this point, we know the contents of the sparse directory are + * modified with respect to HEAD, so we expand the index and restart + * to process each path individually + */ + if (S_ISSPARSEDIR(ce->ce_mode)) { + ensure_full_index(&the_index); + goto redo; + } + /* Be careful. The working tree may not have the * path anymore, in which case, under 'allow_remove', * or worse yet 'allow_replace', active_nr may decrease. @@ -787,6 +795,17 @@ static int refresh(struct refresh_params *o, unsigned int flag) setup_work_tree(); read_cache(); *o->has_errors |= refresh_cache(o->flags | flag); + if (has_racy_timestamp(&the_index)) { + /* + * Even if nothing else has changed, updating the file + * increases the chance that racy timestamps become + * non-racy, helping future run-time performance. + * We do that even in case of "errors" returned by + * refresh_cache() as these are no actual errors. + * cmd_status() does the same. + */ + active_cache_changed |= SOMETHING_CHANGED; + } return 0; } @@ -1077,6 +1096,9 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); + prepare_repo_settings(r); + the_repository->settings.command_requires_full_index = 0; + /* we will diagnose later if it turns out that we need to update it */ newfd = hold_locked_index(&lock_file, 0); if (newfd < 0) diff --git a/builtin/worktree.c b/builtin/worktree.c index 2838254f7f..0d0809276f 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -382,21 +382,17 @@ done: * is_junk is cleared, but do return appropriate code when hook fails. */ if (!ret && opts->checkout) { - const char *hook = find_hook("post-checkout"); - if (hook) { - const char *env[] = { "GIT_DIR", "GIT_WORK_TREE", NULL }; - struct child_process cp = CHILD_PROCESS_INIT; - cp.no_stdin = 1; - cp.stdout_to_stderr = 1; - cp.dir = path; - strvec_pushv(&cp.env_array, env); - cp.trace2_hook_name = "post-checkout"; - strvec_pushl(&cp.args, absolute_path(hook), - oid_to_hex(null_oid()), - oid_to_hex(&commit->object.oid), - "1", NULL); - ret = run_command(&cp); - } + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + + strvec_pushl(&opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL); + strvec_pushl(&opt.args, + oid_to_hex(null_oid()), + oid_to_hex(&commit->object.oid), + "1", + NULL); + opt.dir = path; + + ret = run_hooks_opt("post-checkout", &opt); } strvec_clear(&child_env); @@ -18,7 +18,6 @@ #include "repository.h" #include "mem-pool.h" -#include <zlib.h> typedef struct git_zstream { z_stream z; unsigned long avail_in; @@ -889,6 +888,7 @@ void *read_blob_data_from_index(struct index_state *, const char *, unsigned lon #define CE_MATCH_IGNORE_FSMONITOR 0X20 int is_racy_timestamp(const struct index_state *istate, const struct cache_entry *ce); +int has_racy_timestamp(struct index_state *istate); int ie_match_stat(struct index_state *, const struct cache_entry *, struct stat *, unsigned int); int ie_modified(struct index_state *, const struct cache_entry *, struct stat *, unsigned int); @@ -1375,6 +1375,7 @@ struct object_context { #define GET_OID_FOLLOW_SYMLINKS 0100 #define GET_OID_RECORD_PATH 0200 #define GET_OID_ONLY_TO_DIE 04000 +#define GET_OID_REQUIRE_PATH 010000 #define GET_OID_DISAMBIGUATORS \ (GET_OID_COMMIT | GET_OID_COMMITTISH | \ @@ -197,7 +197,6 @@ esac case "$jobname" in linux32) CC=gcc - MAKEFLAGS="$MAKEFLAGS NO_UNCOMPRESS2=1" ;; linux-musl) CC=gcc diff --git a/command-list.txt b/command-list.txt index 675c28f0bd..9bd6f3c48f 100644 --- a/command-list.txt +++ b/command-list.txt @@ -103,6 +103,7 @@ git-grep mainporcelain info git-gui mainporcelain git-hash-object plumbingmanipulators git-help ancillaryinterrogators complete +git-hook purehelpers git-http-backend synchingrepositories git-http-fetch synchelpers git-http-push synchelpers @@ -21,6 +21,7 @@ #include "commit-reach.h" #include "run-command.h" #include "shallow.h" +#include "hook.h" static struct commit_extra_header *read_commit_extra_header_lines(const char *buf, size_t len, const char **); @@ -1631,12 +1632,20 @@ struct commit_list **commit_list_append(struct commit *commit, return &new_commit->next; } -const char *find_commit_header(const char *msg, const char *key, size_t *out_len) +const char *find_header_mem(const char *msg, size_t len, + const char *key, size_t *out_len) { int key_len = strlen(key); const char *line = msg; - while (line) { + /* + * NEEDSWORK: It's possible for strchrnul() to scan beyond the range + * given by len. However, current callers are safe because they compute + * len by scanning a NUL-terminated block of memory starting at msg. + * Nonetheless, it would be better to ensure the function does not look + * at msg beyond the len provided by the caller. + */ + while (line && line < msg + len) { const char *eol = strchrnul(line, '\n'); if (line == eol) @@ -1653,6 +1662,10 @@ const char *find_commit_header(const char *msg, const char *key, size_t *out_len return NULL; } +const char *find_commit_header(const char *msg, const char *key, size_t *out_len) +{ + return find_header_mem(msg, strlen(msg), key, out_len); +} /* * Inspect the given string and determine the true "end" of the log message, in * order to find where to put a new Signed-off-by trailer. Ignored are @@ -1702,22 +1715,22 @@ size_t ignore_non_trailer(const char *buf, size_t len) int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...) { - struct strvec hook_env = STRVEC_INIT; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; va_list args; - int ret; + const char *arg; - strvec_pushf(&hook_env, "GIT_INDEX_FILE=%s", index_file); + strvec_pushf(&opt.env, "GIT_INDEX_FILE=%s", index_file); /* * Let the hook know that no editor will be launched. */ if (!editor_is_used) - strvec_push(&hook_env, "GIT_EDITOR=:"); + strvec_push(&opt.env, "GIT_EDITOR=:"); va_start(args, name); - ret = run_hook_ve(hook_env.v, name, args); + while ((arg = va_arg(args, const char *))) + strvec_push(&opt.args, arg); va_end(args); - strvec_clear(&hook_env); - return ret; + return run_hooks_opt(name, &opt); } @@ -290,12 +290,17 @@ void free_commit_extra_headers(struct commit_extra_header *extra); /* * Search the commit object contents given by "msg" for the header "key". + * Reads up to "len" bytes of "msg". * Returns a pointer to the start of the header contents, or NULL. The length * of the header, up to the first newline, is returned via out_len. * * Note that some headers (like mergetag) may be multi-line. It is the caller's * responsibility to parse further in this case! */ +const char *find_header_mem(const char *msg, size_t len, + const char *key, + size_t *out_len); + const char *find_commit_header(const char *msg, const char *key, size_t *out_len); diff --git a/compat/qsort_s.c b/compat/qsort_s.c index 52d1f0a73d..0f7ff30f5f 100644 --- a/compat/qsort_s.c +++ b/compat/qsort_s.c @@ -49,21 +49,15 @@ int git_qsort_s(void *b, size_t n, size_t s, int (*cmp)(const void *, const void *, void *), void *ctx) { const size_t size = st_mult(n, s); - char buf[1024]; + char *tmp; if (!n) return 0; if (!b || !cmp) return -1; - if (size < sizeof(buf)) { - /* The temporary array fits on the small on-stack buffer. */ - msort_with_tmp(b, n, s, cmp, buf, ctx); - } else { - /* It's somewhat large, so malloc it. */ - char *tmp = xmalloc(size); - msort_with_tmp(b, n, s, cmp, tmp, ctx); - free(tmp); - } + tmp = xmalloc(size); + msort_with_tmp(b, n, s, cmp, tmp, ctx); + free(tmp); return 0; } diff --git a/compat/winansi.c b/compat/winansi.c index 4fceecf14c..936a80a5f0 100644 --- a/compat/winansi.c +++ b/compat/winansi.c @@ -3,6 +3,12 @@ */ #undef NOGDI + +/* + * Including the appropriate header file for RtlGenRandom causes MSVC to see a + * redefinition of types in an incompatible way when including headers below. + */ +#undef HAVE_RTLGENRANDOM #include "../git-compat-util.h" #include <wingdi.h> #include <winreg.h> diff --git a/compat/zlib-uncompress2.c b/compat/zlib-uncompress2.c index 722610b971..77a1b08048 100644 --- a/compat/zlib-uncompress2.c +++ b/compat/zlib-uncompress2.c @@ -1,3 +1,6 @@ +#include "git-compat-util.h" + +#if ZLIB_VERNUM < 0x1290 /* taken from zlib's uncompr.c commit cacf7f1d4e3d44d871b605da3b647f07d718623f @@ -8,16 +11,11 @@ */ -#include "../reftable/system.h" -#define z_const - /* * Copyright (C) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler * For conditions of distribution and use, see copyright notice in zlib.h */ -#include <zlib.h> - /* clang-format off */ /* =========================================================================== @@ -93,3 +91,6 @@ int ZEXPORT uncompress2 ( err == Z_BUF_ERROR && left + stream.avail_out ? Z_DATA_ERROR : err; } +#else +static void *dummy_variable = &dummy_variable; +#endif @@ -120,6 +120,22 @@ static long config_buf_ftell(struct config_source *conf) return conf->u.buf.pos; } +struct config_include_data { + int depth; + config_fn_t fn; + void *data; + const struct config_options *opts; + struct git_config_source *config_source; + + /* + * All remote URLs discovered when reading all config files. + */ + struct string_list *remote_urls; +}; +#define CONFIG_INCLUDE_INIT { 0 } + +static int git_config_include(const char *var, const char *value, void *data); + #define MAX_INCLUDE_DEPTH 10 static const char include_depth_advice[] = N_( "exceeded maximum include depth (%d) while including\n" @@ -294,9 +310,92 @@ static int include_by_branch(const char *cond, size_t cond_len) return ret; } -static int include_condition_is_true(const struct config_options *opts, +static int add_remote_url(const char *var, const char *value, void *data) +{ + struct string_list *remote_urls = data; + const char *remote_name; + size_t remote_name_len; + const char *key; + + if (!parse_config_key(var, "remote", &remote_name, &remote_name_len, + &key) && + remote_name && + !strcmp(key, "url")) + string_list_append(remote_urls, value); + return 0; +} + +static void populate_remote_urls(struct config_include_data *inc) +{ + struct config_options opts; + + struct config_source *store_cf = cf; + struct key_value_info *store_kvi = current_config_kvi; + enum config_scope store_scope = current_parsing_scope; + + opts = *inc->opts; + opts.unconditional_remote_url = 1; + + cf = NULL; + current_config_kvi = NULL; + current_parsing_scope = 0; + + inc->remote_urls = xmalloc(sizeof(*inc->remote_urls)); + string_list_init_dup(inc->remote_urls); + config_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts); + + cf = store_cf; + current_config_kvi = store_kvi; + current_parsing_scope = store_scope; +} + +static int forbid_remote_url(const char *var, const char *value, void *data) +{ + const char *remote_name; + size_t remote_name_len; + const char *key; + + if (!parse_config_key(var, "remote", &remote_name, &remote_name_len, + &key) && + remote_name && + !strcmp(key, "url")) + die(_("remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote.*.url")); + return 0; +} + +static int at_least_one_url_matches_glob(const char *glob, int glob_len, + struct string_list *remote_urls) +{ + struct strbuf pattern = STRBUF_INIT; + struct string_list_item *url_item; + int found = 0; + + strbuf_add(&pattern, glob, glob_len); + for_each_string_list_item(url_item, remote_urls) { + if (!wildmatch(pattern.buf, url_item->string, WM_PATHNAME)) { + found = 1; + break; + } + } + strbuf_release(&pattern); + return found; +} + +static int include_by_remote_url(struct config_include_data *inc, + const char *cond, size_t cond_len) +{ + if (inc->opts->unconditional_remote_url) + return 1; + if (!inc->remote_urls) + populate_remote_urls(inc); + return at_least_one_url_matches_glob(cond, cond_len, + inc->remote_urls); +} + +static int include_condition_is_true(struct config_include_data *inc, const char *cond, size_t cond_len) { + const struct config_options *opts = inc->opts; if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len)) return include_by_gitdir(opts, cond, cond_len, 0); @@ -304,12 +403,15 @@ static int include_condition_is_true(const struct config_options *opts, return include_by_gitdir(opts, cond, cond_len, 1); else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len)) return include_by_branch(cond, cond_len); + else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond, + &cond_len)) + return include_by_remote_url(inc, cond, cond_len); /* unknown conditionals are always false */ return 0; } -int git_config_include(const char *var, const char *value, void *data) +static int git_config_include(const char *var, const char *value, void *data) { struct config_include_data *inc = data; const char *cond, *key; @@ -328,9 +430,15 @@ int git_config_include(const char *var, const char *value, void *data) ret = handle_path_include(value, inc); if (!parse_config_key(var, "includeif", &cond, &cond_len, &key) && - (cond && include_condition_is_true(inc->opts, cond, cond_len)) && - !strcmp(key, "path")) + cond && include_condition_is_true(inc, cond, cond_len) && + !strcmp(key, "path")) { + config_fn_t old_fn = inc->fn; + + if (inc->opts->unconditional_remote_url) + inc->fn = forbid_remote_url; ret = handle_path_include(value, inc); + inc->fn = old_fn; + } return ret; } @@ -1929,11 +2037,13 @@ int config_with_options(config_fn_t fn, void *data, const struct config_options *opts) { struct config_include_data inc = CONFIG_INCLUDE_INIT; + int ret; if (opts->respect_includes) { inc.fn = fn; inc.data = data; inc.opts = opts; + inc.config_source = config_source; fn = git_config_include; data = &inc; } @@ -1946,17 +2056,23 @@ int config_with_options(config_fn_t fn, void *data, * regular lookup sequence. */ if (config_source && config_source->use_stdin) { - return git_config_from_stdin(fn, data); + ret = git_config_from_stdin(fn, data); } else if (config_source && config_source->file) { - return git_config_from_file(fn, config_source->file, data); + ret = git_config_from_file(fn, config_source->file, data); } else if (config_source && config_source->blob) { struct repository *repo = config_source->repo ? config_source->repo : the_repository; - return git_config_from_blob_ref(fn, repo, config_source->blob, + ret = git_config_from_blob_ref(fn, repo, config_source->blob, data); + } else { + ret = do_git_config_sequence(opts, fn, data); } - return do_git_config_sequence(opts, fn, data); + if (inc.remote_urls) { + string_list_clear(inc.remote_urls, 0); + FREE_AND_NULL(inc.remote_urls); + } + return ret; } static void configset_iter(struct config_set *cs, config_fn_t fn, void *data) @@ -89,6 +89,15 @@ struct config_options { unsigned int ignore_worktree : 1; unsigned int ignore_cmdline : 1; unsigned int system_gently : 1; + + /* + * For internal use. Include all includeif.hasremoteurl paths without + * checking if the repo has that remote URL, and when doing so, verify + * that files included in this way do not configure any remote URLs + * themselves. + */ + unsigned int unconditional_remote_url : 1; + const char *commondir; const char *git_dir; config_parser_event_fn_t event_fn; @@ -126,6 +135,8 @@ int git_default_config(const char *, const char *, void *); /** * Read a specific file in git-config format. * This function takes the same callback and data parameters as `git_config`. + * + * Unlike git_config(), this function does not respect includes. */ int git_config_from_file(config_fn_t fn, const char *, void *); @@ -158,6 +169,8 @@ void read_very_early_config(config_fn_t cb, void *data); * will first feed the user-wide one to the callback, and then the * repo-specific one; by overwriting, the higher-priority repo-specific * value is left at the end). + * + * Unlike git_config_from_file(), this function respects includes. */ void git_config(config_fn_t fn, void *); @@ -338,39 +351,6 @@ const char *current_config_origin_type(void); const char *current_config_name(void); int current_config_line(void); -/** - * Include Directives - * ------------------ - * - * By default, the config parser does not respect include directives. - * However, a caller can use the special `git_config_include` wrapper - * callback to support them. To do so, you simply wrap your "real" callback - * function and data pointer in a `struct config_include_data`, and pass - * the wrapper to the regular config-reading functions. For example: - * - * ------------------------------------------- - * int read_file_with_include(const char *file, config_fn_t fn, void *data) - * { - * struct config_include_data inc = CONFIG_INCLUDE_INIT; - * inc.fn = fn; - * inc.data = data; - * return git_config_from_file(git_config_include, file, &inc); - * } - * ------------------------------------------- - * - * `git_config` respects includes automatically. The lower-level - * `git_config_from_file` does not. - * - */ -struct config_include_data { - int depth; - config_fn_t fn; - void *data; - const struct config_options *opts; -}; -#define CONFIG_INCLUDE_INIT { 0 } -int git_config_include(const char *name, const char *value, void *data); - /* * Match and parse a config key of the form: * diff --git a/config.mak.uname b/config.mak.uname index c48db45106..4352ea39e9 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -66,7 +66,6 @@ ifeq ($(uname_S),Linux) # centos7/rhel7 provides gcc 4.8.5 and zlib 1.2.7. ifneq ($(findstring .el7.,$(uname_R)),) BASIC_CFLAGS += -std=c99 - NO_UNCOMPRESS2 = YesPlease endif endif ifeq ($(uname_S),GNU/kFreeBSD) @@ -146,6 +145,7 @@ ifeq ($(uname_S),Darwin) HAVE_BSD_SYSCTL = YesPlease FREAD_READS_DIRECTORIES = UnfortunatelyYes HAVE_NS_GET_EXECUTABLE_PATH = YesPlease + CSPRNG_METHOD = arc4random # Workaround for `gettext` being keg-only and not even being linked via # `brew link --force gettext`, should be obsolete as of @@ -261,15 +261,12 @@ ifeq ($(uname_S),FreeBSD) HAVE_PATHS_H = YesPlease HAVE_BSD_SYSCTL = YesPlease HAVE_BSD_KERN_PROC_SYSCTL = YesPlease + CSPRNG_METHOD = arc4random PAGER_ENV = LESS=FRX LV=-c MORE=FRX FREAD_READS_DIRECTORIES = UnfortunatelyYes FILENO_IS_A_MACRO = UnfortunatelyYes endif ifeq ($(uname_S),OpenBSD) - # Versions < 7.0 need compatibility layer - ifeq ($(shell expr "$(uname_R)" : "[1-6]\."),2) - NO_UNCOMPRESS2 = UnfortunatelyYes - endif NO_STRCASESTR = YesPlease NO_MEMMEM = YesPlease USE_ST_TIMESPEC = YesPlease @@ -279,6 +276,7 @@ ifeq ($(uname_S),OpenBSD) HAVE_PATHS_H = YesPlease HAVE_BSD_SYSCTL = YesPlease HAVE_BSD_KERN_PROC_SYSCTL = YesPlease + CSPRNG_METHOD = arc4random PROCFS_EXECUTABLE_PATH = /proc/curproc/file FREAD_READS_DIRECTORIES = UnfortunatelyYes FILENO_IS_A_MACRO = UnfortunatelyYes @@ -290,6 +288,7 @@ ifeq ($(uname_S),MirBSD) NEEDS_LIBICONV = YesPlease HAVE_PATHS_H = YesPlease HAVE_BSD_SYSCTL = YesPlease + CSPRNG_METHOD = arc4random endif ifeq ($(uname_S),NetBSD) ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2) @@ -301,6 +300,7 @@ ifeq ($(uname_S),NetBSD) HAVE_PATHS_H = YesPlease HAVE_BSD_SYSCTL = YesPlease HAVE_BSD_KERN_PROC_SYSCTL = YesPlease + CSPRNG_METHOD = arc4random PROCFS_EXECUTABLE_PATH = /proc/curproc/exe endif ifeq ($(uname_S),AIX) @@ -430,6 +430,7 @@ ifeq ($(uname_S),Windows) NO_STRTOUMAX = YesPlease NO_MKDTEMP = YesPlease NO_INTTYPES_H = YesPlease + CSPRNG_METHOD = rtlgenrandom # VS2015 with UCRT claims that snprintf and friends are C99 compliant, # so we don't need this: # @@ -525,7 +526,6 @@ ifeq ($(uname_S),Interix) endif endif ifeq ($(uname_S),Minix) - NO_UNCOMPRESS2 = YesPlease NO_IPV6 = YesPlease NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease NO_NSEC = YesPlease @@ -581,7 +581,6 @@ ifeq ($(uname_S),NONSTOP_KERNEL) NO_SETENV = YesPlease NO_UNSETENV = YesPlease NO_MKDTEMP = YesPlease - NO_UNCOMPRESS2 = YesPlease # Currently libiconv-1.9.1. OLD_ICONV = UnfortunatelyYes NO_REGEX = NeedsStartEnd @@ -599,6 +598,7 @@ ifeq ($(uname_S),NONSTOP_KERNEL) NO_MMAP = YesPlease NO_POLL = YesPlease NO_INTPTR_T = UnfortunatelyYes + CSPRNG_METHOD = openssl SANE_TOOL_PATH = /usr/coreutils/bin:/usr/local/bin SHELL_PATH = /usr/coreutils/bin/bash endif @@ -634,6 +634,7 @@ ifeq ($(uname_S),MINGW) NO_POSIX_GOODIES = UnfortunatelyYes DEFAULT_HELP_FORMAT = html HAVE_PLATFORM_PROCINFO = YesPlease + CSPRNG_METHOD = rtlgenrandom BASIC_LDFLAGS += -municode COMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32 COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\" diff --git a/configure.ac b/configure.ac index d60d494ee4..5ee25ec95c 100644 --- a/configure.ac +++ b/configure.ac @@ -664,22 +664,9 @@ AC_LINK_IFELSE([ZLIBTEST_SRC], NO_DEFLATE_BOUND=yes]) LIBS="$old_LIBS" -AC_DEFUN([ZLIBTEST_UNCOMPRESS2_SRC], [ -AC_LANG_PROGRAM([#include <zlib.h>], - [uncompress2(NULL,NULL,NULL,NULL);])]) -AC_MSG_CHECKING([for uncompress2 in -lz]) -old_LIBS="$LIBS" -LIBS="$LIBS -lz" -AC_LINK_IFELSE([ZLIBTEST_UNCOMPRESS2_SRC], - [AC_MSG_RESULT([yes])], - [AC_MSG_RESULT([no]) - NO_UNCOMPRESS2=yes]) -LIBS="$old_LIBS" - GIT_UNSTASH_FLAGS($ZLIB_PATH) GIT_CONF_SUBST([NO_DEFLATE_BOUND]) -GIT_CONF_SUBST([NO_UNCOMPRESS2]) # # Define NEEDS_SOCKET if linking with libc is not enough (SunOS, @@ -379,7 +379,7 @@ struct ref **get_remote_heads(struct packet_reader *reader, /* Returns 1 when a valid ref has been added to `list`, 0 otherwise */ static int process_ref_v2(struct packet_reader *reader, struct ref ***list, - char **unborn_head_target) + const char **unborn_head_target) { int ret = 1; int i = 0; @@ -483,7 +483,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader, const char *hash_name; struct strvec *ref_prefixes = transport_options ? &transport_options->ref_prefixes : NULL; - char **unborn_head_target = transport_options ? + const char **unborn_head_target = transport_options ? &transport_options->unborn_head_target : NULL; *list = NULL; diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt index 5100f56bb3..e44232f85d 100644 --- a/contrib/buildsystems/CMakeLists.txt +++ b/contrib/buildsystems/CMakeLists.txt @@ -260,7 +260,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows") _CONSOLE DETECT_MSYS_TTY STRIP_EXTENSION=".exe" NO_SYMLINK_HEAD UNRELIABLE_FSTAT NOGDI OBJECT_CREATION_MODE=1 __USE_MINGW_ANSI_STDIO=0 USE_NED_ALLOCATOR OVERRIDE_STRDUP MMAP_PREVENTS_DELETE USE_WIN32_MMAP - UNICODE _UNICODE HAVE_WPGMPTR ENSURE_MSYSTEM_IS_SET) + UNICODE _UNICODE HAVE_WPGMPTR ENSURE_MSYSTEM_IS_SET HAVE_RTLGENRANDOM) list(APPEND compat_SOURCES compat/mingw.c compat/winansi.c compat/win32/path-utils.c compat/win32/pthread.c compat/win32mmap.c compat/win32/syslog.c compat/win32/trace2_win32_process_info.c compat/win32/dirent.c diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 377d6c5494..49a328aa8a 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -49,6 +49,11 @@ # and git-switch completion (e.g., completing "foo" when "origin/foo" # exists). # +# GIT_COMPLETION_SHOW_ALL_COMMANDS +# +# When set to "1" suggest all commands, including plumbing commands +# which are hidden by default (e.g. "cat-file" on "git ca<TAB>"). +# # GIT_COMPLETION_SHOW_ALL # # When set to "1" suggest all options, including options which are @@ -2986,9 +2991,37 @@ _git_show_branch () __git_complete_revlist } +__gitcomp_directories () +{ + local _tmp_dir _tmp_completions _found=0 + + # Get the directory of the current token; this differs from dirname + # in that it keeps up to the final trailing slash. If no slash found + # that's fine too. + [[ "$cur" =~ .*/ ]] + _tmp_dir=$BASH_REMATCH + + # Find possible directory completions, adding trailing '/' characters, + # de-quoting, and handling unusual characters. + while IFS= read -r -d $'\0' c ; do + # If there are directory completions, find ones that start + # with "$cur", the current token, and put those in COMPREPLY + if [[ $c == "$cur"* ]]; then + COMPREPLY+=("$c/") + _found=1 + fi + done < <(git ls-tree -z -d --name-only HEAD $_tmp_dir) + + if [[ $_found == 0 ]] && [[ "$cur" =~ /$ ]]; then + # No possible further completions any deeper, so assume we're at + # a leaf directory and just consider it complete + __gitcomp_direct_append "$cur " + fi +} + _git_sparse_checkout () { - local subcommands="list init set disable" + local subcommands="list init set disable add reapply" local subcommand="$(__git_find_on_cmdline "$subcommands")" if [ -z "$subcommand" ]; then __gitcomp "$subcommands" @@ -2996,14 +3029,14 @@ _git_sparse_checkout () fi case "$subcommand,$cur" in - init,--*) - __gitcomp "--cone" - ;; - set,--*) - __gitcomp "--stdin" - ;; - *) + *,--*) + __gitcomp_builtin sparse-checkout_$subcommand "" "--" ;; + set,*|add,*) + if [ "$(__git config core.sparseCheckoutCone)" == "true" ] || + [ -n "$(__git_find_on_cmdline --cone)" ]; then + __gitcomp_directories + fi esac } @@ -3455,7 +3488,13 @@ __git_main () then __gitcomp "$GIT_TESTING_PORCELAIN_COMMAND_LIST" else - __gitcomp "$(__git --list-cmds=list-mainporcelain,others,nohelpers,alias,list-complete,config)" + local list_cmds=list-mainporcelain,others,nohelpers,alias,list-complete,config + + if test "${GIT_COMPLETION_SHOW_ALL_COMMANDS-}" = "1" + then + list_cmds=builtins,$list_cmds + fi + __gitcomp "$(__git --list-cmds=$list_cmds)" fi ;; esac diff --git a/contrib/scalar/scalar.c b/contrib/scalar/scalar.c index 1ce9c2b00e..7db2a97416 100644 --- a/contrib/scalar/scalar.c +++ b/contrib/scalar/scalar.c @@ -808,6 +808,25 @@ int cmd_main(int argc, const char **argv) struct strbuf scalar_usage = STRBUF_INIT; int i; + while (argc > 1 && *argv[1] == '-') { + if (!strcmp(argv[1], "-C")) { + if (argc < 3) + die(_("-C requires a <directory>")); + if (chdir(argv[2]) < 0) + die_errno(_("could not change to '%s'"), + argv[2]); + argc -= 2; + argv += 2; + } else if (!strcmp(argv[1], "-c")) { + if (argc < 3) + die(_("-c requires a <key>=<value> argument")); + git_config_push_parameter(argv[2]); + argc -= 2; + argv += 2; + } else + break; + } + if (argc > 1) { argv++; argc--; @@ -818,7 +837,8 @@ int cmd_main(int argc, const char **argv) } strbuf_addstr(&scalar_usage, - N_("scalar <command> [<options>]\n\nCommands:\n")); + N_("scalar [-C <directory>] [-c <key>=<value>] " + "<command> [<options>]\n\nCommands:\n")); for (i = 0; builtins[i].name; i++) strbuf_addf(&scalar_usage, "\t%s\n", builtins[i].name); diff --git a/contrib/scalar/scalar.txt b/contrib/scalar/scalar.txt index f416d63728..cf4e5b889c 100644 --- a/contrib/scalar/scalar.txt +++ b/contrib/scalar/scalar.txt @@ -36,6 +36,16 @@ The `scalar` command implements various subcommands, and different options depending on the subcommand. With the exception of `clone`, `list` and `reconfigure --all`, all subcommands expect to be run in an enlistment. +The following options can be specified _before_ the subcommand: + +-C <directory>:: + Before running the subcommand, change the working directory. This + option imitates the same option of linkgit:git[1]. + +-c <key>=<value>:: + For the duration of running the specified subcommand, configure this + setting. This option imitates the same option of linkgit:git[1]. + COMMANDS -------- diff --git a/contrib/scalar/t/t9099-scalar.sh b/contrib/scalar/t/t9099-scalar.sh index 2e1502ad45..89781568f4 100755 --- a/contrib/scalar/t/t9099-scalar.sh +++ b/contrib/scalar/t/t9099-scalar.sh @@ -85,4 +85,12 @@ test_expect_success 'scalar delete with enlistment' ' test_path_is_missing cloned ' +test_expect_success 'scalar supports -c/-C' ' + test_when_finished "scalar delete sub" && + git init sub && + scalar -C sub -c status.aheadBehind=bogus register && + test -z "$(git -C sub config --local status.aheadBehind)" && + test true = "$(git -C sub config core.preloadIndex)" +' + test_done diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh index 71f1fd94bd..1af1d9653e 100755 --- a/contrib/subtree/git-subtree.sh +++ b/contrib/subtree/git-subtree.sh @@ -975,10 +975,10 @@ cmd_merge () { if test -n "$arg_addmerge_message" then - git merge -Xsubtree="$arg_prefix" \ + git merge --no-ff -Xsubtree="$arg_prefix" \ --message="$arg_addmerge_message" "$rev" else - git merge -Xsubtree="$arg_prefix" $rev + git merge --no-ff -Xsubtree="$arg_prefix" $rev fi } diff --git a/diff-merges.c b/diff-merges.c index 5060ccd890..a833fd747a 100644 --- a/diff-merges.c +++ b/diff-merges.c @@ -17,12 +17,14 @@ static void suppress(struct rev_info *revs) revs->combined_all_paths = 0; revs->merges_imply_patch = 0; revs->merges_need_diff = 0; + revs->remerge_diff = 0; } static void set_separate(struct rev_info *revs) { suppress(revs); revs->separate_merges = 1; + revs->simplify_history = 0; } static void set_first_parent(struct rev_info *revs) @@ -45,6 +47,13 @@ static void set_dense_combined(struct rev_info *revs) revs->dense_combined_merges = 1; } +static void set_remerge_diff(struct rev_info *revs) +{ + suppress(revs); + revs->remerge_diff = 1; + revs->simplify_history = 0; +} + static diff_merges_setup_func_t func_by_opt(const char *optarg) { if (!strcmp(optarg, "off") || !strcmp(optarg, "none")) @@ -57,6 +66,8 @@ static diff_merges_setup_func_t func_by_opt(const char *optarg) return set_combined; else if (!strcmp(optarg, "cc") || !strcmp(optarg, "dense-combined")) return set_dense_combined; + else if (!strcmp(optarg, "r") || !strcmp(optarg, "remerge")) + return set_remerge_diff; else if (!strcmp(optarg, "m") || !strcmp(optarg, "on")) return set_to_default; return NULL; @@ -110,6 +121,9 @@ int diff_merges_parse_opts(struct rev_info *revs, const char **argv) } else if (!strcmp(arg, "--cc")) { set_dense_combined(revs); revs->merges_imply_patch = 1; + } else if (!strcmp(arg, "--remerge-diff")) { + set_remerge_diff(revs); + revs->merges_imply_patch = 1; } else if (!strcmp(arg, "--no-diff-merges")) { suppress(revs); } else if (!strcmp(arg, "--combined-all-paths")) { @@ -28,6 +28,7 @@ #include "help.h" #include "promisor-remote.h" #include "dir.h" +#include "strmap.h" #ifdef NO_FAST_WORKING_DIRECTORY #define FAST_WORKING_DIRECTORY 0 @@ -3353,6 +3354,31 @@ struct userdiff_driver *get_textconv(struct repository *r, return userdiff_get_textconv(r, one->driver); } +static struct strbuf *additional_headers(struct diff_options *o, + const char *path) +{ + if (!o->additional_path_headers) + return NULL; + return strmap_get(o->additional_path_headers, path); +} + +static void add_formatted_headers(struct strbuf *msg, + struct strbuf *more_headers, + const char *line_prefix, + const char *meta, + const char *reset) +{ + char *next, *newline; + + for (next = more_headers->buf; *next; next = newline) { + newline = strchrnul(next, '\n'); + strbuf_addf(msg, "%s%s%.*s%s\n", line_prefix, meta, + (int)(newline - next), next, reset); + if (*newline) + newline++; + } +} + static void builtin_diff(const char *name_a, const char *name_b, struct diff_filespec *one, @@ -3411,6 +3437,17 @@ static void builtin_diff(const char *name_a, b_two = quote_two(b_prefix, name_b + (*name_b == '/')); lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null"; lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null"; + if (!DIFF_FILE_VALID(one) && !DIFF_FILE_VALID(two)) { + /* + * We should only reach this point for pairs from + * create_filepairs_for_header_only_notifications(). For + * these, we should avoid the "/dev/null" special casing + * above, meaning we avoid showing such pairs as either + * "new file" or "deleted file" below. + */ + lbl[0] = a_one; + lbl[1] = b_two; + } strbuf_addf(&header, "%s%sdiff --git %s %s%s\n", line_prefix, meta, a_one, b_two, reset); if (lbl[0][0] == '/') { /* /dev/null */ @@ -4275,6 +4312,7 @@ static void fill_metainfo(struct strbuf *msg, const char *set = diff_get_color(use_color, DIFF_METAINFO); const char *reset = diff_get_color(use_color, DIFF_RESET); const char *line_prefix = diff_line_prefix(o); + struct strbuf *more_headers = NULL; *must_show_header = 1; strbuf_init(msg, PATH_MAX * 2 + 300); @@ -4311,6 +4349,11 @@ static void fill_metainfo(struct strbuf *msg, default: *must_show_header = 0; } + if ((more_headers = additional_headers(o, name))) { + add_formatted_headers(msg, more_headers, + line_prefix, set, reset); + *must_show_header = 1; + } if (one && two && !oideq(&one->oid, &two->oid)) { const unsigned hexsz = the_hash_algo->hexsz; int abbrev = o->abbrev ? o->abbrev : DEFAULT_ABBREV; @@ -4570,6 +4613,43 @@ void repo_diff_setup(struct repository *r, struct diff_options *options) prep_parse_options(options); } +static const char diff_status_letters[] = { + DIFF_STATUS_ADDED, + DIFF_STATUS_COPIED, + DIFF_STATUS_DELETED, + DIFF_STATUS_MODIFIED, + DIFF_STATUS_RENAMED, + DIFF_STATUS_TYPE_CHANGED, + DIFF_STATUS_UNKNOWN, + DIFF_STATUS_UNMERGED, + DIFF_STATUS_FILTER_AON, + DIFF_STATUS_FILTER_BROKEN, + '\0', +}; + +static unsigned int filter_bit['Z' + 1]; + +static void prepare_filter_bits(void) +{ + int i; + + if (!filter_bit[DIFF_STATUS_ADDED]) { + for (i = 0; diff_status_letters[i]; i++) + filter_bit[(int) diff_status_letters[i]] = (1 << i); + } +} + +static unsigned filter_bit_tst(char status, const struct diff_options *opt) +{ + return opt->filter & filter_bit[(int) status]; +} + +unsigned diff_filter_bit(char status) +{ + prepare_filter_bits(); + return filter_bit[(int) status]; +} + void diff_setup_done(struct diff_options *options) { unsigned check_mask = DIFF_FORMAT_NAME | @@ -4683,6 +4763,12 @@ void diff_setup_done(struct diff_options *options) if (!options->use_color || external_diff()) options->color_moved = 0; + if (options->filter_not) { + if (!options->filter) + options->filter = ~filter_bit[DIFF_STATUS_FILTER_AON]; + options->filter &= ~options->filter_not; + } + FREE_AND_NULL(options->parseopts); } @@ -4774,43 +4860,6 @@ static int parse_dirstat_opt(struct diff_options *options, const char *params) return 1; } -static const char diff_status_letters[] = { - DIFF_STATUS_ADDED, - DIFF_STATUS_COPIED, - DIFF_STATUS_DELETED, - DIFF_STATUS_MODIFIED, - DIFF_STATUS_RENAMED, - DIFF_STATUS_TYPE_CHANGED, - DIFF_STATUS_UNKNOWN, - DIFF_STATUS_UNMERGED, - DIFF_STATUS_FILTER_AON, - DIFF_STATUS_FILTER_BROKEN, - '\0', -}; - -static unsigned int filter_bit['Z' + 1]; - -static void prepare_filter_bits(void) -{ - int i; - - if (!filter_bit[DIFF_STATUS_ADDED]) { - for (i = 0; diff_status_letters[i]; i++) - filter_bit[(int) diff_status_letters[i]] = (1 << i); - } -} - -static unsigned filter_bit_tst(char status, const struct diff_options *opt) -{ - return opt->filter & filter_bit[(int) status]; -} - -unsigned diff_filter_bit(char status) -{ - prepare_filter_bits(); - return filter_bit[(int) status]; -} - static int diff_opt_diff_filter(const struct option *option, const char *optarg, int unset) { @@ -4820,21 +4869,6 @@ static int diff_opt_diff_filter(const struct option *option, BUG_ON_OPT_NEG(unset); prepare_filter_bits(); - /* - * If there is a negation e.g. 'd' in the input, and we haven't - * initialized the filter field with another --diff-filter, start - * from full set of bits, except for AON. - */ - if (!opt->filter) { - for (i = 0; (optch = optarg[i]) != '\0'; i++) { - if (optch < 'a' || 'z' < optch) - continue; - opt->filter = (1 << (ARRAY_SIZE(diff_status_letters) - 1)) - 1; - opt->filter &= ~filter_bit[DIFF_STATUS_FILTER_AON]; - break; - } - } - for (i = 0; (optch = optarg[i]) != '\0'; i++) { unsigned int bit; int negate; @@ -4851,7 +4885,7 @@ static int diff_opt_diff_filter(const struct option *option, return error(_("unknown change class '%c' in --diff-filter=%s"), optarg[i], optarg); if (negate) - opt->filter &= ~bit; + opt->filter_not |= bit; else opt->filter |= bit; } @@ -5803,12 +5837,27 @@ int diff_unmodified_pair(struct diff_filepair *p) static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o) { - if (diff_unmodified_pair(p)) + int include_conflict_headers = + (additional_headers(o, p->one->path) && + (!o->filter || filter_bit_tst(DIFF_STATUS_UNMERGED, o))); + + /* + * Check if we can return early without showing a diff. Note that + * diff_filepair only stores {oid, path, mode, is_valid} + * information for each path, and thus diff_unmodified_pair() only + * considers those bits of info. However, we do not want pairs + * created by create_filepairs_for_header_only_notifications() + * (which always look like unmodified pairs) to be ignored, so + * return early if both p is unmodified AND we don't want to + * include_conflict_headers. + */ + if (diff_unmodified_pair(p) && !include_conflict_headers) return; + /* Actually, we can also return early to avoid showing tree diffs */ if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) || (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode))) - return; /* no tree diffs in patch format */ + return; run_diff(p, o); } @@ -5839,10 +5888,17 @@ static void diff_flush_checkdiff(struct diff_filepair *p, run_checkdiff(p, o); } -int diff_queue_is_empty(void) +int diff_queue_is_empty(struct diff_options *o) { struct diff_queue_struct *q = &diff_queued_diff; int i; + int include_conflict_headers = + (o->additional_path_headers && + (!o->filter || filter_bit_tst(DIFF_STATUS_UNMERGED, o))); + + if (include_conflict_headers) + return 0; + for (i = 0; i < q->nr; i++) if (!diff_unmodified_pair(q->queue[i])) return 0; @@ -6276,6 +6332,54 @@ void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc) warning(_(rename_limit_advice), varname, needed); } +static void create_filepairs_for_header_only_notifications(struct diff_options *o) +{ + struct strset present; + struct diff_queue_struct *q = &diff_queued_diff; + struct hashmap_iter iter; + struct strmap_entry *e; + int i; + + strset_init_with_options(&present, /*pool*/ NULL, /*strdup*/ 0); + + /* + * Find out which paths exist in diff_queued_diff, preferring + * one->path for any pair that has multiple paths. + */ + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + char *path = p->one->path ? p->one->path : p->two->path; + + if (strmap_contains(o->additional_path_headers, path)) + strset_add(&present, path); + } + + /* + * Loop over paths in additional_path_headers; for each NOT already + * in diff_queued_diff, create a synthetic filepair and insert that + * into diff_queued_diff. + */ + strmap_for_each_entry(o->additional_path_headers, &iter, e) { + if (!strset_contains(&present, e->key)) { + struct diff_filespec *one, *two; + struct diff_filepair *p; + + one = alloc_filespec(e->key); + two = alloc_filespec(e->key); + fill_filespec(one, null_oid(), 0, 0); + fill_filespec(two, null_oid(), 0, 0); + p = diff_queue(q, one, two); + p->status = DIFF_STATUS_MODIFIED; + } + } + + /* Re-sort the filepairs */ + diffcore_fix_diff_index(); + + /* Cleanup */ + strset_clear(&present); +} + static void diff_flush_patch_all_file_pairs(struct diff_options *o) { int i; @@ -6288,6 +6392,9 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o) if (o->color_moved) o->emitted_symbols = &esm; + if (o->additional_path_headers) + create_filepairs_for_header_only_notifications(o); + for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; if (check_pair_status(p)) @@ -6358,7 +6465,7 @@ void diff_flush(struct diff_options *options) * Order: raw, stat, summary, patch * or: name/name-status/checkdiff (other bits clear) */ - if (!q->nr) + if (!q->nr && !options->additional_path_headers) goto free_queue; if (output_format & (DIFF_FORMAT_RAW | @@ -283,7 +283,7 @@ struct diff_options { struct diff_flags flags; /* diff-filter bits */ - unsigned int filter; + unsigned int filter, filter_not; int use_color; @@ -395,6 +395,7 @@ struct diff_options { struct repository *repo; struct option *parseopts; + struct strmap *additional_path_headers; int no_free; }; @@ -593,7 +594,7 @@ void diffcore_fix_diff_index(void); " show all files diff when -S is used and hit is found.\n" \ " -a --text treat all files as text.\n" -int diff_queue_is_empty(void); +int diff_queue_is_empty(struct diff_options *o); void diff_flush(struct diff_options*); void diff_free(struct diff_options*); void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc); diff --git a/fetch-negotiator.c b/fetch-negotiator.c index 273390229f..874797d767 100644 --- a/fetch-negotiator.c +++ b/fetch-negotiator.c @@ -18,7 +18,7 @@ void fetch_negotiator_init(struct repository *r, noop_negotiator_init(negotiator); return; - case FETCH_NEGOTIATION_DEFAULT: + case FETCH_NEGOTIATION_CONSECUTIVE: default_negotiator_init(negotiator); return; } diff --git a/fetch-pack.c b/fetch-pack.c index dd6ec449f2..dbcaaf4dbb 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -1415,9 +1415,17 @@ static int process_ack(struct fetch_negotiator *negotiator, * otherwise. */ if (*received_ready && reader->status != PACKET_READ_DELIM) - die(_("expected packfile to be sent after 'ready'")); + /* + * TRANSLATORS: The parameter will be 'ready', a protocol + * keyword. + */ + die(_("expected packfile to be sent after '%s'"), "ready"); if (!*received_ready && reader->status != PACKET_READ_FLUSH) - die(_("expected no other sections to be sent after no 'ready'")); + /* + * TRANSLATORS: The parameter will be 'ready', a protocol + * keyword. + */ + die(_("expected no other sections to be sent after no '%s'"), "ready"); return 0; } diff --git a/git-compat-util.h b/git-compat-util.h index 1229c8296b..876907b9df 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -197,6 +197,12 @@ #endif #include <windows.h> #define GIT_WINDOWS_NATIVE +#ifdef HAVE_RTLGENRANDOM +/* This is required to get access to RtlGenRandom. */ +#define SystemFunction036 NTAPI SystemFunction036 +#include <NTSecAPI.h> +#undef SystemFunction036 +#endif #endif #include <unistd.h> @@ -267,6 +273,12 @@ #else #include <stdint.h> #endif +#ifdef HAVE_ARC4RANDOM_LIBBSD +#include <bsd/stdlib.h> +#endif +#ifdef HAVE_GETRANDOM +#include <sys/random.h> +#endif #ifdef NO_INTPTR_T /* * On I16LP32, ILP32 and LP64 "long" is the safe bet, however @@ -1386,6 +1398,18 @@ void unleak_memory(const void *ptr, size_t len); #define UNLEAK(var) do {} while (0) #endif +#define z_const +#include <zlib.h> + +#if ZLIB_VERNUM < 0x1290 +/* + * This is uncompress2, which is only available in zlib >= 1.2.9 + * (released as of early 2017). See compat/zlib-uncompress2.c. + */ +int uncompress2(Bytef *dest, uLongf *destLen, const Bytef *source, + uLong *sourceLen); +#endif + /* * This include must come after system headers, since it introduces macros that * replace system names. @@ -1432,4 +1456,11 @@ static inline void *container_of_or_null_offset(void *ptr, size_t offset) void sleep_millisec(int millisec); +/* + * Generate len bytes from the system cryptographically secure PRNG. + * Returns 0 on success and -1 on error, setting errno. The inability to + * satisfy the full request is an error. + */ +int csprng_bytes(void *buf, size_t len); + #endif @@ -108,10 +108,7 @@ def p4_build_cmd(cmd): # Provide a way to not pass this option by setting git-p4.retries to 0 real_cmd += ["-r", str(retries)] - if not isinstance(cmd, list): - real_cmd = ' '.join(real_cmd) + ' ' + cmd - else: - real_cmd += cmd + real_cmd += cmd # now check that we can actually talk to the server global p4_access_checked @@ -223,153 +220,93 @@ def decode_path(path): def run_git_hook(cmd, param=[]): """Execute a hook if the hook exists.""" + args = ['git', 'hook', 'run', '--ignore-missing', cmd] + if param: + args.append("--") + for p in param: + args.append(p) + return subprocess.call(args) == 0 + +def write_pipe(c, stdin, *k, **kw): if verbose: - sys.stderr.write("Looking for hook: %s\n" % cmd) - sys.stderr.flush() - - hooks_path = gitConfig("core.hooksPath") - if len(hooks_path) <= 0: - hooks_path = os.path.join(os.environ["GIT_DIR"], "hooks") - - if not isinstance(param, list): - param=[param] - - # resolve hook file name, OS depdenent - hook_file = os.path.join(hooks_path, cmd) - if platform.system() == 'Windows': - if not os.path.isfile(hook_file): - # look for the file with an extension - files = glob.glob(hook_file + ".*") - if not files: - return True - files.sort() - hook_file = files.pop() - while hook_file.upper().endswith(".SAMPLE"): - # The file is a sample hook. We don't want it - if len(files) > 0: - hook_file = files.pop() - else: - return True - - if not os.path.isfile(hook_file) or not os.access(hook_file, os.X_OK): - return True - - return run_hook_command(hook_file, param) == 0 - -def run_hook_command(cmd, param): - """Executes a git hook command - cmd = the command line file to be executed. This can be - a file that is run by OS association. - - param = a list of parameters to pass to the cmd command - - On windows, the extension is checked to see if it should - be run with the Git for Windows Bash shell. If there - is no file extension, the file is deemed a bash shell - and will be handed off to sh.exe. Otherwise, Windows - will be called with the shell to handle the file assocation. - - For non Windows operating systems, the file is called - as an executable. - """ - cli = [cmd] + param - use_shell = False - if platform.system() == 'Windows': - (root,ext) = os.path.splitext(cmd) - if ext == "": - exe_path = os.environ.get("EXEPATH") - if exe_path is None: - exe_path = "" - else: - exe_path = os.path.join(exe_path, "bin") - cli = [os.path.join(exe_path, "SH.EXE")] + cli - else: - use_shell = True - return subprocess.call(cli, shell=use_shell) - - -def write_pipe(c, stdin): - if verbose: - sys.stderr.write('Writing pipe: %s\n' % str(c)) + sys.stderr.write('Writing pipe: {}\n'.format(' '.join(c))) - expand = not isinstance(c, list) - p = subprocess.Popen(c, stdin=subprocess.PIPE, shell=expand) + p = subprocess.Popen(c, stdin=subprocess.PIPE, *k, **kw) pipe = p.stdin val = pipe.write(stdin) pipe.close() if p.wait(): - die('Command failed: %s' % str(c)) + die('Command failed: {}'.format(' '.join(c))) return val -def p4_write_pipe(c, stdin): +def p4_write_pipe(c, stdin, *k, **kw): real_cmd = p4_build_cmd(c) if bytes is not str and isinstance(stdin, str): stdin = encode_text_stream(stdin) - return write_pipe(real_cmd, stdin) + return write_pipe(real_cmd, stdin, *k, **kw) -def read_pipe_full(c): +def read_pipe_full(c, *k, **kw): """ Read output from command. Returns a tuple of the return status, stdout text and stderr text. """ if verbose: - sys.stderr.write('Reading pipe: %s\n' % str(c)) + sys.stderr.write('Reading pipe: {}\n'.format(' '.join(c))) - expand = not isinstance(c, list) - p = subprocess.Popen(c, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=expand) + p = subprocess.Popen( + c, stdout=subprocess.PIPE, stderr=subprocess.PIPE, *k, **kw) (out, err) = p.communicate() return (p.returncode, out, decode_text_stream(err)) -def read_pipe(c, ignore_error=False, raw=False): +def read_pipe(c, ignore_error=False, raw=False, *k, **kw): """ Read output from command. Returns the output text on success. On failure, terminates execution, unless ignore_error is True, when it returns an empty string. If raw is True, do not attempt to decode output text. """ - (retcode, out, err) = read_pipe_full(c) + (retcode, out, err) = read_pipe_full(c, *k, **kw) if retcode != 0: if ignore_error: out = "" else: - die('Command failed: %s\nError: %s' % (str(c), err)) + die('Command failed: {}\nError: {}'.format(' '.join(c), err)) if not raw: out = decode_text_stream(out) return out -def read_pipe_text(c): +def read_pipe_text(c, *k, **kw): """ Read output from a command with trailing whitespace stripped. On error, returns None. """ - (retcode, out, err) = read_pipe_full(c) + (retcode, out, err) = read_pipe_full(c, *k, **kw) if retcode != 0: return None else: return decode_text_stream(out).rstrip() -def p4_read_pipe(c, ignore_error=False, raw=False): +def p4_read_pipe(c, ignore_error=False, raw=False, *k, **kw): real_cmd = p4_build_cmd(c) - return read_pipe(real_cmd, ignore_error, raw=raw) + return read_pipe(real_cmd, ignore_error, raw=raw, *k, **kw) -def read_pipe_lines(c, raw=False): +def read_pipe_lines(c, raw=False, *k, **kw): if verbose: - sys.stderr.write('Reading pipe: %s\n' % str(c)) + sys.stderr.write('Reading pipe: {}\n'.format(' '.join(c))) - expand = not isinstance(c, list) - p = subprocess.Popen(c, stdout=subprocess.PIPE, shell=expand) + p = subprocess.Popen(c, stdout=subprocess.PIPE, *k, **kw) pipe = p.stdout lines = pipe.readlines() if not raw: lines = [decode_text_stream(line) for line in lines] if pipe.close() or p.wait(): - die('Command failed: %s' % str(c)) + die('Command failed: {}'.format(' '.join(c))) return lines -def p4_read_pipe_lines(c): +def p4_read_pipe_lines(c, *k, **kw): """Specifically invoke p4 on the command supplied. """ real_cmd = p4_build_cmd(c) - return read_pipe_lines(real_cmd) + return read_pipe_lines(real_cmd, *k, **kw) def p4_has_command(cmd): """Ask p4 for help on this command. If it returns an error, the @@ -400,23 +337,22 @@ def p4_has_move_command(): # assume it failed because @... was invalid changelist return True -def system(cmd, ignore_error=False): - expand = not isinstance(cmd, list) +def system(cmd, ignore_error=False, *k, **kw): if verbose: - sys.stderr.write("executing %s\n" % str(cmd)) - retcode = subprocess.call(cmd, shell=expand) + sys.stderr.write("executing {}\n".format( + ' '.join(cmd) if isinstance(cmd, list) else cmd)) + retcode = subprocess.call(cmd, *k, **kw) if retcode and not ignore_error: - raise CalledProcessError(retcode, cmd) + raise subprocess.CalledProcessError(retcode, cmd) return retcode -def p4_system(cmd): +def p4_system(cmd, *k, **kw): """Specifically invoke p4 as the system command. """ real_cmd = p4_build_cmd(cmd) - expand = not isinstance(real_cmd, list) - retcode = subprocess.call(real_cmd, shell=expand) + retcode = subprocess.call(real_cmd, *k, **kw) if retcode: - raise CalledProcessError(retcode, real_cmd) + raise subprocess.CalledProcessError(retcode, real_cmd) def die_bad_access(s): die("failure accessing depot: {0}".format(s.rstrip())) @@ -735,18 +671,11 @@ def isModeExecChanged(src_mode, dst_mode): return isModeExec(src_mode) != isModeExec(dst_mode) def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False, - errors_as_exceptions=False): - - if not isinstance(cmd, list): - cmd = "-G " + cmd - expand = True - else: - cmd = ["-G"] + cmd - expand = False + errors_as_exceptions=False, *k, **kw): - cmd = p4_build_cmd(cmd) + cmd = p4_build_cmd(["-G"] + cmd) if verbose: - sys.stderr.write("Opening pipe: %s\n" % str(cmd)) + sys.stderr.write("Opening pipe: {}\n".format(' '.join(cmd))) # Use a temporary file to avoid deadlocks without # subprocess.communicate(), which would put another copy @@ -763,10 +692,8 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False, stdin_file.flush() stdin_file.seek(0) - p4 = subprocess.Popen(cmd, - shell=expand, - stdin=stdin_file, - stdout=subprocess.PIPE) + p4 = subprocess.Popen( + cmd, stdin=stdin_file, stdout=subprocess.PIPE, *k, **kw) result = [] try: @@ -819,8 +746,8 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False, return result -def p4Cmd(cmd): - list = p4CmdList(cmd) +def p4Cmd(cmd, *k, **kw): + list = p4CmdList(cmd, *k, **kw) result = {} for entry in list: result.update(entry) @@ -869,7 +796,7 @@ def isValidGitDir(path): return git_dir(path) != None def parseRevision(ref): - return read_pipe("git rev-parse %s" % ref).strip() + return read_pipe(["git", "rev-parse", ref]).strip() def branchExists(ref): rev = read_pipe(["git", "rev-parse", "-q", "--verify", ref], @@ -975,11 +902,11 @@ def p4BranchesInGit(branchesAreInRemotes=True): branches = {} - cmdline = "git rev-parse --symbolic " + cmdline = ["git", "rev-parse", "--symbolic"] if branchesAreInRemotes: - cmdline += "--remotes" + cmdline.append("--remotes") else: - cmdline += "--branches" + cmdline.append("--branches") for line in read_pipe_lines(cmdline): line = line.strip() @@ -1044,7 +971,7 @@ def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent originPrefix = "origin/p4/" - for line in read_pipe_lines("git rev-parse --symbolic --remotes"): + for line in read_pipe_lines(["git", "rev-parse", "--symbolic", "--remotes"]): line = line.strip() if (not line.startswith(originPrefix)) or line.endswith("HEAD"): continue @@ -1082,7 +1009,7 @@ def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent remoteHead, ','.join(settings['depot-paths']))) if update: - system("git update-ref %s %s" % (remoteHead, originHead)) + system(["git", "update-ref", remoteHead, originHead]) def originP4BranchesExist(): return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master") @@ -1196,7 +1123,7 @@ def getClientSpec(): """Look at the p4 client spec, create a View() object that contains all the mappings, and return it.""" - specList = p4CmdList("client -o") + specList = p4CmdList(["client", "-o"]) if len(specList) != 1: die('Output from "client -o" is %d lines, expecting 1' % len(specList)) @@ -1225,7 +1152,7 @@ def getClientSpec(): def getClientRoot(): """Grab the client directory.""" - output = p4CmdList("client -o") + output = p4CmdList(["client", "-o"]) if len(output) != 1: die('Output from "client -o" is %d lines, expecting 1' % len(output)) @@ -1480,7 +1407,7 @@ class P4UserMap: if self.myP4UserId: return self.myP4UserId - results = p4CmdList("user -o") + results = p4CmdList(["user", "-o"]) for r in results: if 'User' in r: self.myP4UserId = r['User'] @@ -1505,7 +1432,7 @@ class P4UserMap: self.users = {} self.emails = {} - for output in p4CmdList("users"): + for output in p4CmdList(["users"]): if "User" not in output: continue self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">" @@ -1629,7 +1556,7 @@ class P4Submit(Command, P4UserMap): die("Large file system not supported for git-p4 submit command. Please remove it from config.") def check(self): - if len(p4CmdList("opened ...")) > 0: + if len(p4CmdList(["opened", "..."])) > 0: die("You have files opened with perforce! Close them before starting the sync.") def separate_jobs_from_description(self, message): @@ -1733,7 +1660,7 @@ class P4Submit(Command, P4UserMap): # then gets used to patch up the username in the change. If the same # client spec is being used by multiple processes then this might go # wrong. - results = p4CmdList("client -o") # find the current client + results = p4CmdList(["client", "-o"]) # find the current client client = None for r in results: if 'Client' in r: @@ -1749,7 +1676,7 @@ class P4Submit(Command, P4UserMap): def modifyChangelistUser(self, changelist, newUser): # fixup the user field of a changelist after it has been submitted. - changes = p4CmdList("change -o %s" % changelist) + changes = p4CmdList(["change", "-o", changelist]) if len(changes) != 1: die("Bad output from p4 change modifying %s to user %s" % (changelist, newUser)) @@ -1760,7 +1687,7 @@ class P4Submit(Command, P4UserMap): # p4 does not understand format version 3 and above input = marshal.dumps(c, 2) - result = p4CmdList("change -f -i", stdin=input) + result = p4CmdList(["change", "-f", "-i"], stdin=input) for r in result: if 'code' in r: if r['code'] == 'error': @@ -1866,7 +1793,7 @@ class P4Submit(Command, P4UserMap): if "P4EDITOR" in os.environ and (os.environ.get("P4EDITOR") != ""): editor = os.environ.get("P4EDITOR") else: - editor = read_pipe("git var GIT_EDITOR").strip() + editor = read_pipe(["git", "var", "GIT_EDITOR"]).strip() system(["sh", "-c", ('%s "$@"' % editor), editor, template_file]) # If the file was not saved, prompt to see if this patch should @@ -1924,7 +1851,8 @@ class P4Submit(Command, P4UserMap): (p4User, gitEmail) = self.p4UserForCommit(id) - diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (self.diffOpts, id, id)) + diff = read_pipe_lines( + ["git", "diff-tree", "-r"] + self.diffOpts + ["{}^".format(id), id]) filesToAdd = set() filesToChangeType = set() filesToDelete = set() @@ -2060,7 +1988,7 @@ class P4Submit(Command, P4UserMap): # # Apply the patch for real, and do add/delete/+x handling. # - system(applyPatchCmd) + system(applyPatchCmd, shell=True) for f in filesToChangeType: p4_edit(f, "-t", "auto") @@ -2410,17 +2338,17 @@ class P4Submit(Command, P4UserMap): # if self.detectRenames: # command-line -M arg - self.diffOpts = "-M" + self.diffOpts = ["-M"] else: # If not explicitly set check the config variable detectRenames = gitConfig("git-p4.detectRenames") if detectRenames.lower() == "false" or detectRenames == "": - self.diffOpts = "" + self.diffOpts = [] elif detectRenames.lower() == "true": - self.diffOpts = "-M" + self.diffOpts = ["-M"] else: - self.diffOpts = "-M%s" % detectRenames + self.diffOpts = ["-M{}".format(detectRenames)] # no command-line arg for -C or --find-copies-harder, just # config variables @@ -2428,12 +2356,12 @@ class P4Submit(Command, P4UserMap): if detectCopies.lower() == "false" or detectCopies == "": pass elif detectCopies.lower() == "true": - self.diffOpts += " -C" + self.diffOpts.append("-C") else: - self.diffOpts += " -C%s" % detectCopies + self.diffOpts.append("-C{}".format(detectCopies)) if gitConfigBool("git-p4.detectCopiesHarder"): - self.diffOpts += " --find-copies-harder" + self.diffOpts.append("--find-copies-harder") num_shelves = len(self.update_shelve) if num_shelves > 0 and num_shelves != len(commits): @@ -3381,12 +3309,9 @@ class P4Sync(Command, P4UserMap): lostAndFoundBranches = set() user = gitConfig("git-p4.branchUser") - if len(user) > 0: - command = "branches -u %s" % user - else: - command = "branches" - for info in p4CmdList(command): + for info in p4CmdList( + ["branches"] + (["-u", user] if len(user) > 0 else [])): details = p4Cmd(["branch", "-o", info["branch"]]) viewIdx = 0 while "View%s" % viewIdx in details: @@ -3477,7 +3402,8 @@ class P4Sync(Command, P4UserMap): while True: if self.verbose: print("trying: earliest %s latest %s" % (earliestCommit, latestCommit)) - next = read_pipe("git rev-list --bisect %s %s" % (latestCommit, earliestCommit)).strip() + next = read_pipe(["git", "rev-list", "--bisect", + latestCommit, earliestCommit]).strip() if len(next) == 0: if self.verbose: print("argh") @@ -3633,7 +3559,7 @@ class P4Sync(Command, P4UserMap): if self.hasOrigin: if not self.silent: print('Syncing with origin first, using "git fetch origin"') - system("git fetch origin") + system(["git", "fetch", "origin"]) def importHeadRevision(self, revision): print("Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)) @@ -3800,8 +3726,8 @@ class P4Sync(Command, P4UserMap): if len(self.branch) == 0: self.branch = self.refPrefix + "master" if gitBranchExists("refs/heads/p4") and self.importIntoRemotes: - system("git update-ref %s refs/heads/p4" % self.branch) - system("git branch -D p4") + system(["git", "update-ref", self.branch, "refs/heads/p4"]) + system(["git", "branch", "-D", "p4"]) # accept either the command-line option, or the configuration variable if self.useClientSpec: @@ -4004,7 +3930,7 @@ class P4Sync(Command, P4UserMap): # Cleanup temporary branches created during import if self.tempBranches != []: for branch in self.tempBranches: - read_pipe("git update-ref -d %s" % branch) + read_pipe(["git", "update-ref", "-d", branch]) os.rmdir(os.path.join(os.environ.get("GIT_DIR", ".git"), self.tempBranchLocation)) # Create a symbolic ref p4/HEAD pointing to p4/<branch> to allow @@ -4036,7 +3962,7 @@ class P4Rebase(Command): def rebase(self): if os.system("git update-index --refresh") != 0: die("Some files in your working directory are modified and different than what is in your index. You can use git update-index <filename> to bring the index up to date or stash away all your changes with git stash."); - if len(read_pipe("git diff-index HEAD --")) > 0: + if len(read_pipe(["git", "diff-index", "HEAD", "--"])) > 0: die("You have uncommitted changes. Please commit them before rebasing or stash them away with git stash."); [upstream, settings] = findUpstreamBranchPoint() @@ -4047,9 +3973,10 @@ class P4Rebase(Command): upstream = re.sub("~[0-9]+$", "", upstream) print("Rebasing the current branch onto %s" % upstream) - oldHead = read_pipe("git rev-parse HEAD").strip() - system("git rebase %s" % upstream) - system("git diff-tree --stat --summary -M %s HEAD --" % oldHead) + oldHead = read_pipe(["git", "rev-parse", "HEAD"]).strip() + system(["git", "rebase", upstream]) + system(["git", "diff-tree", "--stat", "--summary", "-M", oldHead, + "HEAD", "--"]) return True class P4Clone(P4Sync): @@ -4110,7 +4037,7 @@ class P4Clone(P4Sync): init_cmd.append("--bare") retcode = subprocess.call(init_cmd) if retcode: - raise CalledProcessError(retcode, init_cmd) + raise subprocess.CalledProcessError(retcode, init_cmd) if not P4Sync.run(self, depotPaths): return False @@ -4126,7 +4053,7 @@ class P4Clone(P4Sync): # auto-set this variable if invoked with --use-client-spec if self.useClientSpec_from_options: - system("git config --bool git-p4.useclientspec true") + system(["git", "config", "--bool", "git-p4.useclientspec", "true"]) return True @@ -4257,10 +4184,7 @@ class P4Branches(Command): if originP4BranchesExist(): createOrUpdateBranchesFromOrigin() - cmdline = "git rev-parse --symbolic " - cmdline += " --remotes" - - for line in read_pipe_lines(cmdline): + for line in read_pipe_lines(["git", "rev-parse", "--symbolic", "--remotes"]): line = line.strip() if not line.startswith('p4/') or line == "p4/HEAD": @@ -4343,9 +4267,9 @@ def main(): cmd.gitdir = os.path.abspath(".git") if not isValidGitDir(cmd.gitdir): # "rev-parse --git-dir" without arguments will try $PWD/.git - cmd.gitdir = read_pipe("git rev-parse --git-dir").strip() + cmd.gitdir = read_pipe(["git", "rev-parse", "--git-dir"]).strip() if os.path.exists(cmd.gitdir): - cdup = read_pipe("git rev-parse --show-cdup").strip() + cdup = read_pipe(["git", "rev-parse", "--show-cdup"]).strip() if len(cdup) > 0: chdir(cdup); diff --git a/git-send-email.perl b/git-send-email.perl index 04087221aa..a98460bdb9 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -225,13 +225,13 @@ my $multiedit; my $editor; sub system_or_msg { - my ($args, $msg) = @_; + my ($args, $msg, $cmd_name) = @_; system(@$args); my $signalled = $? & 127; my $exit_code = $? >> 8; return unless $signalled or $exit_code; - my @sprintf_args = ($args->[0], $exit_code); + my @sprintf_args = ($cmd_name ? $cmd_name : $args->[0], $exit_code); if (defined $msg) { # Quiet the 'redundant' warning category, except we # need to support down to Perl 5.8, so we can't do a @@ -2075,10 +2075,10 @@ sub validate_patch { my ($fn, $xfer_encoding) = @_; if ($repo) { + my $hook_name = 'sendemail-validate'; my $hooks_path = $repo->command_oneline('rev-parse', '--git-path', 'hooks'); require File::Spec; - my $validate_hook = File::Spec->catfile($hooks_path, - 'sendemail-validate'); + my $validate_hook = File::Spec->catfile($hooks_path, $hook_name); my $hook_error; if (-x $validate_hook) { require Cwd; @@ -2088,13 +2088,19 @@ sub validate_patch { chdir($repo->wc_path() or $repo->repo_path()) or die("chdir: $!"); local $ENV{"GIT_DIR"} = $repo->repo_path(); - $hook_error = system_or_msg([$validate_hook, $target]); + my @cmd = ("git", "hook", "run", "--ignore-missing", + $hook_name, "--"); + my @cmd_msg = (@cmd, "<patch>"); + my @cmd_run = (@cmd, $target); + $hook_error = system_or_msg(\@cmd_run, undef, "@cmd_msg"); chdir($cwd_save) or die("chdir: $!"); } if ($hook_error) { - die sprintf(__("fatal: %s: rejected by sendemail-validate hook\n" . - "%s\n" . - "warning: no patches were sent\n"), $fn, $hook_error); + $hook_error = sprintf(__("fatal: %s: rejected by %s hook\n" . + $hook_error . "\n" . + "warning: no patches were sent\n"), + $fn, $hook_name); + die $hook_error; } } diff --git a/git-sh-setup.sh b/git-sh-setup.sh index b93f39288c..d92df37e99 100644 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -101,7 +101,6 @@ $LONG_USAGE")" case "$1" in -h) echo "$LONG_USAGE" - case "$0" in *git-legacy-stash) exit 129;; esac exit esac fi @@ -541,6 +541,7 @@ static struct cmd_struct commands[] = { { "grep", cmd_grep, RUN_SETUP_GENTLY }, { "hash-object", cmd_hash_object }, { "help", cmd_help }, + { "hook", cmd_hook, RUN_SETUP }, { "index-pack", cmd_index_pack, RUN_SETUP_GENTLY | NO_PARSEOPT }, { "init", cmd_init_db }, { "init-db", cmd_init_db }, diff --git a/gpg-interface.c b/gpg-interface.c index b52eb0e2e0..17b1e44baa 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -433,7 +433,6 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc, struct tempfile *buffer_file; int ret = -1; const char *line; - size_t trust_size; char *principal; struct strbuf ssh_principals_out = STRBUF_INIT; struct strbuf ssh_principals_err = STRBUF_INIT; @@ -502,15 +501,30 @@ static int verify_ssh_signed_buffer(struct signature_check *sigc, ret = -1; } else { /* Check every principal we found (one per line) */ - for (line = ssh_principals_out.buf; *line; - line = strchrnul(line + 1, '\n')) { - while (*line == '\n') - line++; - if (!*line) - break; - - trust_size = strcspn(line, "\n"); - principal = xmemdupz(line, trust_size); + const char *next; + for (line = ssh_principals_out.buf; + *line; + line = next) { + const char *end_of_text; + + next = end_of_text = strchrnul(line, '\n'); + + /* Did we find a LF, and did we have CR before it? */ + if (*end_of_text && + line < end_of_text && + end_of_text[-1] == '\r') + end_of_text--; + + /* Unless we hit NUL, skip over the LF we found */ + if (*next) + next++; + + /* Not all lines are data. Skip empty ones */ + if (line == end_of_text) + continue; + + /* We now know we have an non-empty line. Process it */ + principal = xmemdupz(line, end_of_text - line); child_process_init(&ssh_keygen); strbuf_release(&ssh_keygen_out); @@ -595,6 +595,35 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) } } +static struct grep_expr *grep_not_expr(struct grep_expr *expr) +{ + struct grep_expr *z = xcalloc(1, sizeof(*z)); + z->node = GREP_NODE_NOT; + z->u.unary = expr; + return z; +} + +static struct grep_expr *grep_binexp(enum grep_expr_node kind, + struct grep_expr *left, + struct grep_expr *right) +{ + struct grep_expr *z = xcalloc(1, sizeof(*z)); + z->node = kind; + z->u.binary.left = left; + z->u.binary.right = right; + return z; +} + +static struct grep_expr *grep_or_expr(struct grep_expr *left, struct grep_expr *right) +{ + return grep_binexp(GREP_NODE_OR, left, right); +} + +static struct grep_expr *grep_and_expr(struct grep_expr *left, struct grep_expr *right) +{ + return grep_binexp(GREP_NODE_AND, left, right); +} + static struct grep_expr *compile_pattern_or(struct grep_pat **); static struct grep_expr *compile_pattern_atom(struct grep_pat **list) { @@ -638,12 +667,10 @@ static struct grep_expr *compile_pattern_not(struct grep_pat **list) if (!p->next) die("--not not followed by pattern expression"); *list = p->next; - CALLOC_ARRAY(x, 1); - x->node = GREP_NODE_NOT; - x->u.unary = compile_pattern_not(list); - if (!x->u.unary) + x = compile_pattern_not(list); + if (!x) die("--not followed by non pattern expression"); - return x; + return grep_not_expr(x); default: return compile_pattern_atom(list); } @@ -652,7 +679,7 @@ static struct grep_expr *compile_pattern_not(struct grep_pat **list) static struct grep_expr *compile_pattern_and(struct grep_pat **list) { struct grep_pat *p; - struct grep_expr *x, *y, *z; + struct grep_expr *x, *y; x = compile_pattern_not(list); p = *list; @@ -665,11 +692,7 @@ static struct grep_expr *compile_pattern_and(struct grep_pat **list) y = compile_pattern_and(list); if (!y) die("--and not followed by pattern expression"); - CALLOC_ARRAY(z, 1); - z->node = GREP_NODE_AND; - z->u.binary.left = x; - z->u.binary.right = y; - return z; + return grep_and_expr(x, y); } return x; } @@ -677,7 +700,7 @@ static struct grep_expr *compile_pattern_and(struct grep_pat **list) static struct grep_expr *compile_pattern_or(struct grep_pat **list) { struct grep_pat *p; - struct grep_expr *x, *y, *z; + struct grep_expr *x, *y; x = compile_pattern_and(list); p = *list; @@ -685,11 +708,7 @@ static struct grep_expr *compile_pattern_or(struct grep_pat **list) y = compile_pattern_or(list); if (!y) die("not a pattern expression %s", p->pattern); - CALLOC_ARRAY(z, 1); - z->node = GREP_NODE_OR; - z->u.binary.left = x; - z->u.binary.right = y; - return z; + return grep_or_expr(x, y); } return x; } @@ -699,14 +718,6 @@ static struct grep_expr *compile_pattern_expr(struct grep_pat **list) return compile_pattern_or(list); } -static struct grep_expr *grep_not_expr(struct grep_expr *expr) -{ - struct grep_expr *z = xcalloc(1, sizeof(*z)); - z->node = GREP_NODE_NOT; - z->u.unary = expr; - return z; -} - static struct grep_expr *grep_true_expr(void) { struct grep_expr *z = xcalloc(1, sizeof(*z)); @@ -714,15 +725,6 @@ static struct grep_expr *grep_true_expr(void) return z; } -static struct grep_expr *grep_or_expr(struct grep_expr *left, struct grep_expr *right) -{ - struct grep_expr *z = xcalloc(1, sizeof(*z)); - z->node = GREP_NODE_OR; - z->u.binary.left = left; - z->u.binary.right = right; - return z; -} - static struct grep_expr *prep_header_patterns(struct grep_opt *opt) { struct grep_pat *p; @@ -1,6 +1,7 @@ #include "cache.h" #include "hook.h" #include "run-command.h" +#include "config.h" const char *find_hook(const char *name) { @@ -40,3 +41,133 @@ int hook_exists(const char *name) { return !!find_hook(name); } + +static int pick_next_hook(struct child_process *cp, + struct strbuf *out, + void *pp_cb, + void **pp_task_cb) +{ + struct hook_cb_data *hook_cb = pp_cb; + const char *hook_path = hook_cb->hook_path; + + if (!hook_path) + return 0; + + cp->no_stdin = 1; + strvec_pushv(&cp->env_array, hook_cb->options->env.v); + cp->stdout_to_stderr = 1; + cp->trace2_hook_name = hook_cb->hook_name; + cp->dir = hook_cb->options->dir; + + strvec_push(&cp->args, hook_path); + strvec_pushv(&cp->args, hook_cb->options->args.v); + + /* Provide context for errors if necessary */ + *pp_task_cb = (char *)hook_path; + + /* + * This pick_next_hook() will be called again, we're only + * running one hook, so indicate that no more work will be + * done. + */ + hook_cb->hook_path = NULL; + + return 1; +} + +static int notify_start_failure(struct strbuf *out, + void *pp_cb, + void *pp_task_cp) +{ + struct hook_cb_data *hook_cb = pp_cb; + const char *hook_path = pp_task_cp; + + hook_cb->rc |= 1; + + strbuf_addf(out, _("Couldn't start hook '%s'\n"), + hook_path); + + return 1; +} + +static int notify_hook_finished(int result, + struct strbuf *out, + void *pp_cb, + void *pp_task_cb) +{ + struct hook_cb_data *hook_cb = pp_cb; + + hook_cb->rc |= result; + + return 0; +} + +static void run_hooks_opt_clear(struct run_hooks_opt *options) +{ + strvec_clear(&options->env); + strvec_clear(&options->args); +} + +int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options) +{ + struct strbuf abs_path = STRBUF_INIT; + struct hook_cb_data cb_data = { + .rc = 0, + .hook_name = hook_name, + .options = options, + }; + const char *const hook_path = find_hook(hook_name); + int jobs = 1; + int ret = 0; + + if (!options) + BUG("a struct run_hooks_opt must be provided to run_hooks"); + + if (!hook_path && !options->error_if_missing) + goto cleanup; + + if (!hook_path) { + ret = error("cannot find a hook named %s", hook_name); + goto cleanup; + } + + cb_data.hook_path = hook_path; + if (options->dir) { + strbuf_add_absolute_path(&abs_path, hook_path); + cb_data.hook_path = abs_path.buf; + } + + run_processes_parallel_tr2(jobs, + pick_next_hook, + notify_start_failure, + notify_hook_finished, + &cb_data, + "hook", + hook_name); + ret = cb_data.rc; +cleanup: + strbuf_release(&abs_path); + run_hooks_opt_clear(options); + return ret; +} + +int run_hooks(const char *hook_name) +{ + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + + return run_hooks_opt(hook_name, &opt); +} + +int run_hooks_l(const char *hook_name, ...) +{ + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + va_list ap; + const char *arg; + + va_start(ap, hook_name); + while ((arg = va_arg(ap, const char *))) + strvec_push(&opt.args, arg); + va_end(ap); + + return run_hooks_opt(hook_name, &opt); +} @@ -1,5 +1,37 @@ #ifndef HOOK_H #define HOOK_H +#include "strvec.h" + +struct run_hooks_opt +{ + /* Environment vars to be set for each hook */ + struct strvec env; + + /* Args to be passed to each hook */ + struct strvec args; + + /* Emit an error if the hook is missing */ + unsigned int error_if_missing:1; + + /** + * An optional initial working directory for the hook, + * translates to "struct child_process"'s "dir" member. + */ + const char *dir; +}; + +#define RUN_HOOKS_OPT_INIT { \ + .env = STRVEC_INIT, \ + .args = STRVEC_INIT, \ +} + +struct hook_cb_data { + /* rc reflects the cumulative failure state */ + int rc; + const char *hook_name; + const char *hook_path; + struct run_hooks_opt *options; +}; /* * Returns the path to the hook file, or NULL if the hook is missing @@ -13,4 +45,29 @@ const char *find_hook(const char *name); */ int hook_exists(const char *hookname); +/** + * Takes a `hook_name`, resolves it to a path with find_hook(), and + * runs the hook for you with the options specified in "struct + * run_hooks opt". Will free memory associated with the "struct run_hooks_opt". + * + * Returns the status code of the run hook, or a negative value on + * error(). + */ +int run_hooks_opt(const char *hook_name, struct run_hooks_opt *options); + +/** + * A wrapper for run_hooks_opt() which provides a dummy "struct + * run_hooks_opt" initialized with "RUN_HOOKS_OPT_INIT". + */ +int run_hooks(const char *hook_name); + +/** + * Like run_hooks(), a wrapper for run_hooks_opt(). + * + * In addition to the wrapping behavior provided by run_hooks(), this + * wrapper takes a list of strings terminated by a NULL + * argument. These things will be used as positional arguments to the + * hook. This function behaves like the old run_hook_le() API. + */ +int run_hooks_l(const char *hook_name, ...); #endif diff --git a/ll-merge.c b/ll-merge.c index 261657578c..a937cec59a 100644 --- a/ll-merge.c +++ b/ll-merge.c @@ -14,7 +14,7 @@ struct ll_merge_driver; -typedef int (*ll_merge_fn)(const struct ll_merge_driver *, +typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *, mmbuffer_t *result, const char *path, mmfile_t *orig, const char *orig_name, @@ -49,7 +49,7 @@ void reset_merge_attributes(void) /* * Built-in low-levels */ -static int ll_binary_merge(const struct ll_merge_driver *drv_unused, +static enum ll_merge_result ll_binary_merge(const struct ll_merge_driver *drv_unused, mmbuffer_t *result, const char *path, mmfile_t *orig, const char *orig_name, @@ -58,6 +58,7 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused, const struct ll_merge_options *opts, int marker_size) { + enum ll_merge_result ret; mmfile_t *stolen; assert(opts); @@ -68,16 +69,19 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused, */ if (opts->virtual_ancestor) { stolen = orig; + ret = LL_MERGE_OK; } else { switch (opts->variant) { default: - warning("Cannot merge binary files: %s (%s vs. %s)", - path, name1, name2); - /* fallthru */ + ret = LL_MERGE_BINARY_CONFLICT; + stolen = src1; + break; case XDL_MERGE_FAVOR_OURS: + ret = LL_MERGE_OK; stolen = src1; break; case XDL_MERGE_FAVOR_THEIRS: + ret = LL_MERGE_OK; stolen = src2; break; } @@ -87,16 +91,10 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused, result->size = stolen->size; stolen->ptr = NULL; - /* - * With -Xtheirs or -Xours, we have cleanly merged; - * otherwise we got a conflict. - */ - return opts->variant == XDL_MERGE_FAVOR_OURS || - opts->variant == XDL_MERGE_FAVOR_THEIRS ? - 0 : 1; + return ret; } -static int ll_xdl_merge(const struct ll_merge_driver *drv_unused, +static enum ll_merge_result ll_xdl_merge(const struct ll_merge_driver *drv_unused, mmbuffer_t *result, const char *path, mmfile_t *orig, const char *orig_name, @@ -105,7 +103,9 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused, const struct ll_merge_options *opts, int marker_size) { + enum ll_merge_result ret; xmparam_t xmp; + int status; assert(opts); if (orig->size > MAX_XDIFF_SIZE || @@ -133,10 +133,12 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused, xmp.ancestor = orig_name; xmp.file1 = name1; xmp.file2 = name2; - return xdl_merge(orig, src1, src2, &xmp, result); + status = xdl_merge(orig, src1, src2, &xmp, result); + ret = (status > 0) ? LL_MERGE_CONFLICT : status; + return ret; } -static int ll_union_merge(const struct ll_merge_driver *drv_unused, +static enum ll_merge_result ll_union_merge(const struct ll_merge_driver *drv_unused, mmbuffer_t *result, const char *path, mmfile_t *orig, const char *orig_name, @@ -178,7 +180,7 @@ static void create_temp(mmfile_t *src, char *path, size_t len) /* * User defined low-level merge driver support. */ -static int ll_ext_merge(const struct ll_merge_driver *fn, +static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn, mmbuffer_t *result, const char *path, mmfile_t *orig, const char *orig_name, @@ -194,6 +196,7 @@ static int ll_ext_merge(const struct ll_merge_driver *fn, const char *args[] = { NULL, NULL }; int status, fd, i; struct stat st; + enum ll_merge_result ret; assert(opts); sq_quote_buf(&path_sq, path); @@ -236,7 +239,8 @@ static int ll_ext_merge(const struct ll_merge_driver *fn, unlink_or_warn(temp[i]); strbuf_release(&cmd); strbuf_release(&path_sq); - return status; + ret = (status > 0) ? LL_MERGE_CONFLICT : status; + return ret; } /* @@ -362,7 +366,7 @@ static void normalize_file(mmfile_t *mm, const char *path, struct index_state *i } } -int ll_merge(mmbuffer_t *result_buf, +enum ll_merge_result ll_merge(mmbuffer_t *result_buf, const char *path, mmfile_t *ancestor, const char *ancestor_label, mmfile_t *ours, const char *our_label, diff --git a/ll-merge.h b/ll-merge.h index aceb1b2413..e4a20e81a3 100644 --- a/ll-merge.h +++ b/ll-merge.h @@ -82,13 +82,20 @@ struct ll_merge_options { long xdl_opts; }; +enum ll_merge_result { + LL_MERGE_ERROR = -1, + LL_MERGE_OK = 0, + LL_MERGE_CONFLICT, + LL_MERGE_BINARY_CONFLICT, +}; + /** * Perform a three-way single-file merge in core. This is a thin wrapper * around `xdl_merge` that takes the path and any merge backend specified in * `.gitattributes` or `.git/info/attributes` into account. * Returns 0 for a clean merge. */ -int ll_merge(mmbuffer_t *result_buf, +enum ll_merge_result ll_merge(mmbuffer_t *result_buf, const char *path, mmfile_t *ancestor, const char *ancestor_label, mmfile_t *ours, const char *our_label, diff --git a/log-tree.c b/log-tree.c index d3e7a40b64..25165e2a91 100644 --- a/log-tree.c +++ b/log-tree.c @@ -1,12 +1,15 @@ #include "cache.h" +#include "commit-reach.h" #include "config.h" #include "diff.h" #include "object-store.h" #include "repository.h" +#include "tmp-objdir.h" #include "commit.h" #include "tag.h" #include "graph.h" #include "log-tree.h" +#include "merge-ort.h" #include "reflog-walk.h" #include "refs.h" #include "string-list.h" @@ -16,6 +19,7 @@ #include "line-log.h" #include "help.h" #include "range-diff.h" +#include "strmap.h" static struct decoration name_decoration = { "object names" }; static int decoration_loaded; @@ -849,7 +853,7 @@ int log_tree_diff_flush(struct rev_info *opt) opt->shown_dashes = 0; diffcore_std(&opt->diffopt); - if (diff_queue_is_empty()) { + if (diff_queue_is_empty(&opt->diffopt)) { int saved_fmt = opt->diffopt.output_format; opt->diffopt.output_format = DIFF_FORMAT_NO_OUTPUT; diff_flush(&opt->diffopt); @@ -904,6 +908,106 @@ static int do_diff_combined(struct rev_info *opt, struct commit *commit) return !opt->loginfo; } +static void setup_additional_headers(struct diff_options *o, + struct strmap *all_headers) +{ + struct hashmap_iter iter; + struct strmap_entry *entry; + + /* + * Make o->additional_path_headers contain the subset of all_headers + * that match o->pathspec. If there aren't any that match o->pathspec, + * then make o->additional_path_headers be NULL. + */ + + if (!o->pathspec.nr) { + o->additional_path_headers = all_headers; + return; + } + + o->additional_path_headers = xmalloc(sizeof(struct strmap)); + strmap_init_with_options(o->additional_path_headers, NULL, 0); + strmap_for_each_entry(all_headers, &iter, entry) { + if (match_pathspec(the_repository->index, &o->pathspec, + entry->key, strlen(entry->key), + 0 /* prefix */, NULL /* seen */, + 0 /* is_dir */)) + strmap_put(o->additional_path_headers, + entry->key, entry->value); + } + if (!strmap_get_size(o->additional_path_headers)) { + strmap_clear(o->additional_path_headers, 0); + FREE_AND_NULL(o->additional_path_headers); + } +} + +static void cleanup_additional_headers(struct diff_options *o) +{ + if (!o->pathspec.nr) { + o->additional_path_headers = NULL; + return; + } + if (!o->additional_path_headers) + return; + + strmap_clear(o->additional_path_headers, 0); + FREE_AND_NULL(o->additional_path_headers); +} + +static int do_remerge_diff(struct rev_info *opt, + struct commit_list *parents, + struct object_id *oid, + struct commit *commit) +{ + struct merge_options o; + struct commit_list *bases; + struct merge_result res = {0}; + struct pretty_print_context ctx = {0}; + struct commit *parent1 = parents->item; + struct commit *parent2 = parents->next->item; + struct strbuf parent1_desc = STRBUF_INIT; + struct strbuf parent2_desc = STRBUF_INIT; + + /* Setup merge options */ + init_merge_options(&o, the_repository); + o.show_rename_progress = 0; + o.record_conflict_msgs_as_headers = 1; + o.msg_header_prefix = "remerge"; + + ctx.abbrev = DEFAULT_ABBREV; + format_commit_message(parent1, "%h (%s)", &parent1_desc, &ctx); + format_commit_message(parent2, "%h (%s)", &parent2_desc, &ctx); + o.branch1 = parent1_desc.buf; + o.branch2 = parent2_desc.buf; + + /* Parse the relevant commits and get the merge bases */ + parse_commit_or_die(parent1); + parse_commit_or_die(parent2); + bases = get_merge_bases(parent1, parent2); + + /* Re-merge the parents */ + merge_incore_recursive(&o, bases, parent1, parent2, &res); + + /* Show the diff */ + setup_additional_headers(&opt->diffopt, res.path_messages); + diff_tree_oid(&res.tree->object.oid, oid, "", &opt->diffopt); + log_tree_diff_flush(opt); + + /* Cleanup */ + cleanup_additional_headers(&opt->diffopt); + strbuf_release(&parent1_desc); + strbuf_release(&parent2_desc); + merge_finalize(&o, &res); + + /* Clean up the contents of the temporary object directory */ + if (opt->remerge_objdir) + tmp_objdir_discard_objects(opt->remerge_objdir); + else + BUG("did a remerge diff without remerge_objdir?!?"); + + return !opt->loginfo; +} + /* * Show the diff of a commit. * @@ -938,6 +1042,18 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log } if (is_merge) { + int octopus = (parents->next->next != NULL); + + if (opt->remerge_diff) { + if (octopus) { + show_log(opt); + fprintf(opt->diffopt.file, + "diff: warning: Skipping remerge-diff " + "for octopus merges.\n"); + return 1; + } + return do_remerge_diff(opt, parents, oid, commit); + } if (opt->combine_merges) return do_diff_combined(opt, commit); if (opt->separate_merges) { diff --git a/mem-pool.c b/mem-pool.c index ccdcad2e3d..599d8e895f 100644 --- a/mem-pool.c +++ b/mem-pool.c @@ -8,6 +8,26 @@ #define BLOCK_GROWTH_SIZE (1024 * 1024 - sizeof(struct mp_block)) /* + * The inner union is an approximation for C11's max_align_t, and the + * struct + offsetof computes _Alignof. This can all just be replaced + * with _Alignof(max_align_t) if/when C11 is part of the baseline. + * Note that _Alignof(X) need not be the same as sizeof(X); it's only + * required to be a (possibly trivial) factor. They are the same for + * most architectures, but m68k for example has only 2-byte alignment + * for its 4-byte and 8-byte types, so using sizeof would waste space. + * + * Add more types to the union if the current set is insufficient. + */ +struct git_max_alignment { + char unalign; + union { + uintmax_t max_align_uintmax; + void *max_align_pointer; + } aligned; +}; +#define GIT_MAX_ALIGNMENT offsetof(struct git_max_alignment, aligned) + +/* * Allocate a new mp_block and insert it after the block specified in * `insert_after`. If `insert_after` is NULL, then insert block at the * head of the linked list. @@ -69,9 +89,9 @@ void *mem_pool_alloc(struct mem_pool *pool, size_t len) struct mp_block *p = NULL; void *r; - /* round up to a 'uintmax_t' alignment */ - if (len & (sizeof(uintmax_t) - 1)) - len += sizeof(uintmax_t) - (len & (sizeof(uintmax_t) - 1)); + /* round up to a 'GIT_MAX_ALIGNMENT' alignment */ + if (len & (GIT_MAX_ALIGNMENT - 1)) + len += GIT_MAX_ALIGNMENT - (len & (GIT_MAX_ALIGNMENT - 1)); if (pool->mp_block && pool->mp_block->end - pool->mp_block->next_free >= len) diff --git a/merge-blobs.c b/merge-blobs.c index ee0a0e90c9..8138090f81 100644 --- a/merge-blobs.c +++ b/merge-blobs.c @@ -36,7 +36,7 @@ static void *three_way_filemerge(struct index_state *istate, mmfile_t *their, unsigned long *size) { - int merge_status; + enum ll_merge_result merge_status; mmbuffer_t res; /* @@ -50,6 +50,9 @@ static void *three_way_filemerge(struct index_state *istate, istate, NULL); if (merge_status < 0) return NULL; + if (merge_status == LL_MERGE_BINARY_CONFLICT) + warning("Cannot merge binary files: %s (%s vs. %s)", + path, ".our", ".their"); *size = res.size; return res.ptr; diff --git a/merge-ort.c b/merge-ort.c index c319797021..d85b1cd99e 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -634,17 +634,51 @@ static void path_msg(struct merge_options *opt, const char *fmt, ...) { va_list ap; - struct strbuf *sb = strmap_get(&opt->priv->output, path); + struct strbuf *sb, *dest; + struct strbuf tmp = STRBUF_INIT; + + if (opt->record_conflict_msgs_as_headers && omittable_hint) + return; /* Do not record mere hints in headers */ + if (opt->record_conflict_msgs_as_headers && opt->priv->call_depth) + return; /* Do not record inner merge issues in headers */ + sb = strmap_get(&opt->priv->output, path); if (!sb) { sb = xmalloc(sizeof(*sb)); strbuf_init(sb, 0); strmap_put(&opt->priv->output, path, sb); } + dest = (opt->record_conflict_msgs_as_headers ? &tmp : sb); + va_start(ap, fmt); - strbuf_vaddf(sb, fmt, ap); + strbuf_vaddf(dest, fmt, ap); va_end(ap); + if (opt->record_conflict_msgs_as_headers) { + int i_sb = 0, i_tmp = 0; + + /* Start with the specified prefix */ + if (opt->msg_header_prefix) + strbuf_addf(sb, "%s ", opt->msg_header_prefix); + + /* Copy tmp to sb, adding spaces after newlines */ + strbuf_grow(sb, sb->len + 2*tmp.len); /* more than sufficient */ + for (; i_tmp < tmp.len; i_tmp++, i_sb++) { + /* Copy next character from tmp to sb */ + sb->buf[sb->len + i_sb] = tmp.buf[i_tmp]; + + /* If we copied a newline, add a space */ + if (tmp.buf[i_tmp] == '\n') + sb->buf[++i_sb] = ' '; + } + /* Update length and ensure it's NUL-terminated */ + sb->len += i_sb; + sb->buf[sb->len] = '\0'; + + strbuf_release(&tmp); + } + + /* Add final newline character to sb */ strbuf_addch(sb, '\n'); } @@ -1743,7 +1777,7 @@ static int merge_3way(struct merge_options *opt, mmfile_t orig, src1, src2; struct ll_merge_options ll_opts = {0}; char *base, *name1, *name2; - int merge_status; + enum ll_merge_result merge_status; if (!opt->priv->attr_index.initialized) initialize_attr_index(opt); @@ -1787,6 +1821,10 @@ static int merge_3way(struct merge_options *opt, merge_status = ll_merge(result_buf, path, &orig, base, &src1, name1, &src2, name2, &opt->priv->attr_index, &ll_opts); + if (merge_status == LL_MERGE_BINARY_CONFLICT) + path_msg(opt, path, 0, + "warning: Cannot merge binary files: %s (%s vs. %s)", + path, name1, name2); free(base); free(name1); @@ -2416,7 +2454,7 @@ static void apply_directory_rename_modifications(struct merge_options *opt, */ ci->path_conflict = 1; if (pair->status == 'A') - path_msg(opt, new_path, 0, + path_msg(opt, new_path, 1, _("CONFLICT (file location): %s added in %s " "inside a directory that was renamed in %s, " "suggesting it should perhaps be moved to " @@ -2424,7 +2462,7 @@ static void apply_directory_rename_modifications(struct merge_options *opt, old_path, branch_with_new_path, branch_with_dir_rename, new_path); else - path_msg(opt, new_path, 0, + path_msg(opt, new_path, 1, _("CONFLICT (file location): %s renamed to %s " "in %s, inside a directory that was renamed " "in %s, suggesting it should perhaps be " @@ -3060,6 +3098,10 @@ static int detect_and_process_renames(struct merge_options *opt, trace2_region_enter("merge", "regular renames", opt->repo); detection_run |= detect_regular_renames(opt, MERGE_SIDE1); detection_run |= detect_regular_renames(opt, MERGE_SIDE2); + if (renames->needed_limit) { + renames->cached_pairs_valid_side = 0; + renames->redo_after_renames = 0; + } if (renames->redo_after_renames && detection_run) { int i, side; struct diff_filepair *p; @@ -4255,6 +4297,9 @@ void merge_switch_to_result(struct merge_options *opt, struct string_list olist = STRING_LIST_INIT_NODUP; int i; + if (opt->record_conflict_msgs_as_headers) + BUG("Either display conflict messages or record them as headers, not both"); + trace2_region_enter("merge", "display messages", opt->repo); /* Hack to pre-allocate olist to the desired size */ @@ -4356,6 +4401,9 @@ static void merge_start(struct merge_options *opt, struct merge_result *result) assert(opt->recursive_variant >= MERGE_VARIANT_NORMAL && opt->recursive_variant <= MERGE_VARIANT_THEIRS); + if (opt->msg_header_prefix) + assert(opt->record_conflict_msgs_as_headers); + /* * detect_renames, verbosity, buffer_output, and obuf are ignored * fields that were used by "recursive" rather than "ort" -- but @@ -4556,6 +4604,7 @@ redo: trace2_region_leave("merge", "process_entries", opt->repo); /* Set return values */ + result->path_messages = &opt->priv->output; result->tree = parse_tree_indirect(&working_tree_oid); /* existence of conflicted entries implies unclean */ result->clean &= strmap_empty(&opt->priv->conflicted); @@ -4575,7 +4624,7 @@ static void merge_ort_internal(struct merge_options *opt, struct commit *h2, struct merge_result *result) { - struct commit_list *iter; + struct commit *next; struct commit *merged_merge_bases; const char *ancestor_name; struct strbuf merge_base_abbrev = STRBUF_INIT; @@ -4604,7 +4653,8 @@ static void merge_ort_internal(struct merge_options *opt, ancestor_name = merge_base_abbrev.buf; } - for (iter = merge_bases; iter; iter = iter->next) { + for (next = pop_commit(&merge_bases); next; + next = pop_commit(&merge_bases)) { const char *saved_b1, *saved_b2; struct commit *prev = merged_merge_bases; @@ -4621,7 +4671,7 @@ static void merge_ort_internal(struct merge_options *opt, saved_b2 = opt->branch2; opt->branch1 = "Temporary merge branch 1"; opt->branch2 = "Temporary merge branch 2"; - merge_ort_internal(opt, NULL, prev, iter->item, result); + merge_ort_internal(opt, NULL, prev, next, result); if (result->clean < 0) return; opt->branch1 = saved_b1; @@ -4632,8 +4682,7 @@ static void merge_ort_internal(struct merge_options *opt, result->tree, "merged tree"); commit_list_insert(prev, &merged_merge_bases->parents); - commit_list_insert(iter->item, - &merged_merge_bases->parents->next); + commit_list_insert(next, &merged_merge_bases->parents->next); clear_or_reinit_internal_opts(opt->priv, 1); } diff --git a/merge-ort.h b/merge-ort.h index c011864ffe..fe599b8786 100644 --- a/merge-ort.h +++ b/merge-ort.h @@ -5,6 +5,7 @@ struct commit; struct tree; +struct strmap; struct merge_result { /* @@ -24,6 +25,15 @@ struct merge_result { struct tree *tree; /* + * Special messages and conflict notices for various paths + * + * This is a map of pathnames to strbufs. It contains various + * warning/conflict/notice messages (possibly multiple per path) + * that callers may want to use. + */ + struct strmap *path_messages; + + /* * Additional metadata used by merge_switch_to_result() or future calls * to merge_incore_*(). Includes data needed to update the index (if * !clean) and to print "CONFLICT" messages. Not for external use. diff --git a/merge-recursive.c b/merge-recursive.c index d9457797db..9ec1e6d043 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1044,7 +1044,7 @@ static int merge_3way(struct merge_options *opt, mmfile_t orig, src1, src2; struct ll_merge_options ll_opts = {0}; char *base, *name1, *name2; - int merge_status; + enum ll_merge_result merge_status; ll_opts.renormalize = opt->renormalize; ll_opts.extra_marker_size = extra_marker_size; @@ -1090,6 +1090,9 @@ static int merge_3way(struct merge_options *opt, merge_status = ll_merge(result_buf, a->path, &orig, base, &src1, name1, &src2, name2, opt->repo->index, &ll_opts); + if (merge_status == LL_MERGE_BINARY_CONFLICT) + warning("Cannot merge binary files: %s (%s vs. %s)", + a->path, name1, name2); free(base); free(name1); @@ -3711,6 +3714,10 @@ static int merge_start(struct merge_options *opt, struct tree *head) assert(opt->priv == NULL); + /* Not supported; option specific to merge-ort */ + assert(!opt->record_conflict_msgs_as_headers); + assert(!opt->msg_header_prefix); + /* Sanity check on repo state; index must match head */ if (repo_index_has_changes(opt->repo, head, &sb)) { err(opt, _("Your local changes to the following files would be overwritten by merge:\n %s"), diff --git a/merge-recursive.h b/merge-recursive.h index 0795a1d3ec..b88000e3c2 100644 --- a/merge-recursive.h +++ b/merge-recursive.h @@ -46,6 +46,8 @@ struct merge_options { /* miscellaneous control options */ const char *subtree_shift; unsigned renormalize : 1; + unsigned record_conflict_msgs_as_headers : 1; + const char *msg_header_prefix; /* internal fields used by the implementation */ struct merge_options_internal *priv; @@ -33,6 +33,7 @@ #define MIDX_CHUNKID_OIDLOOKUP 0x4f49444c /* "OIDL" */ #define MIDX_CHUNKID_OBJECTOFFSETS 0x4f4f4646 /* "OOFF" */ #define MIDX_CHUNKID_LARGEOFFSETS 0x4c4f4646 /* "LOFF" */ +#define MIDX_CHUNKID_REVINDEX 0x52494458 /* "RIDX" */ #define MIDX_CHUNK_FANOUT_SIZE (sizeof(uint32_t) * 256) #define MIDX_CHUNK_OFFSET_WIDTH (2 * sizeof(uint32_t)) #define MIDX_CHUNK_LARGE_OFFSET_WIDTH (sizeof(uint64_t)) @@ -161,6 +162,9 @@ struct multi_pack_index *load_multi_pack_index(const char *object_dir, int local pair_chunk(cf, MIDX_CHUNKID_LARGEOFFSETS, &m->chunk_large_offsets); + if (git_env_bool("GIT_TEST_MIDX_READ_RIDX", 1)) + pair_chunk(cf, MIDX_CHUNKID_REVINDEX, &m->chunk_revindex); + m->num_objects = ntohl(m->chunk_oid_fanout[255]); CALLOC_ARRAY(m->pack_names, m->num_packs); @@ -833,6 +837,18 @@ static int write_midx_large_offsets(struct hashfile *f, return 0; } +static int write_midx_revindex(struct hashfile *f, + void *data) +{ + struct write_midx_context *ctx = data; + uint32_t i; + + for (i = 0; i < ctx->entries_nr; i++) + hashwrite_be32(f, ctx->pack_order[i]); + + return 0; +} + struct midx_pack_order_data { uint32_t nr; uint32_t pack; @@ -1061,6 +1077,9 @@ static int write_midx_bitmap(char *midx_name, unsigned char *midx_hash, char *bitmap_name = xstrfmt("%s-%s.bitmap", midx_name, hash_to_hex(midx_hash)); int ret; + if (!ctx->entries_nr) + BUG("cannot write a bitmap without any objects"); + if (flags & MIDX_WRITE_BITMAP_HASH_CACHE) options |= BITMAP_OPT_HASH_CACHE; @@ -1385,6 +1404,12 @@ static int write_midx_internal(const char *object_dir, goto cleanup; } + if (!ctx.entries_nr) { + if (flags & MIDX_WRITE_BITMAP) + warning(_("refusing to write multi-pack .bitmap without any objects")); + flags &= ~(MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP); + } + cf = init_chunkfile(f); add_chunk(cf, MIDX_CHUNKID_PACKNAMES, pack_name_concat_len, @@ -1403,16 +1428,21 @@ static int write_midx_internal(const char *object_dir, (size_t)ctx.num_large_offsets * MIDX_CHUNK_LARGE_OFFSET_WIDTH, write_midx_large_offsets); + if (flags & (MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP)) { + ctx.pack_order = midx_pack_order(&ctx); + add_chunk(cf, MIDX_CHUNKID_REVINDEX, + ctx.entries_nr * sizeof(uint32_t), + write_midx_revindex); + } + write_midx_header(f, get_num_chunks(cf), ctx.nr - dropped_packs); write_chunkfile(cf, &ctx); finalize_hashfile(f, midx_hash, CSUM_FSYNC | CSUM_HASH_IN_STREAM); free_chunkfile(cf); - if (flags & (MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP)) - ctx.pack_order = midx_pack_order(&ctx); - - if (flags & MIDX_WRITE_REV_INDEX) + if (flags & MIDX_WRITE_REV_INDEX && + git_env_bool("GIT_TEST_MIDX_WRITE_REV", 0)) write_midx_reverse_index(midx_name.buf, midx_hash, &ctx); if (flags & MIDX_WRITE_BITMAP) { if (write_midx_bitmap(midx_name.buf, midx_hash, &ctx, @@ -36,6 +36,7 @@ struct multi_pack_index { const unsigned char *chunk_oid_lookup; const unsigned char *chunk_object_offsets; const unsigned char *chunk_large_offsets; + const unsigned char *chunk_revindex; const char **pack_names; struct packed_git **packs; diff --git a/notes-merge.c b/notes-merge.c index b4a3a903e8..01d596920e 100644 --- a/notes-merge.c +++ b/notes-merge.c @@ -344,7 +344,7 @@ static int ll_merge_in_worktree(struct notes_merge_options *o, { mmbuffer_t result_buf; mmfile_t base, local, remote; - int status; + enum ll_merge_result status; read_mmblob(&base, &p->base); read_mmblob(&local, &p->local); @@ -358,6 +358,9 @@ static int ll_merge_in_worktree(struct notes_merge_options *o, free(local.ptr); free(remote.ptr); + if (status == LL_MERGE_BINARY_CONFLICT) + warning("Cannot merge binary files: %s (%s vs. %s)", + oid_to_hex(&p->obj), o->local_ref, o->remote_ref); if ((status < 0) || !result_buf.ptr) die("Failed to execute internal merge"); diff --git a/object-name.c b/object-name.c index fdff4601b2..92862eeb1a 100644 --- a/object-name.c +++ b/object-name.c @@ -1795,13 +1795,13 @@ static enum get_oid_result get_oid_with_context_1(struct repository *repo, const char *cp; int only_to_die = flags & GET_OID_ONLY_TO_DIE; - if (only_to_die) - flags |= GET_OID_QUIETLY; - memset(oc, 0, sizeof(*oc)); oc->mode = S_IFINVALID; strbuf_init(&oc->symlink_path, 0); ret = get_oid_1(repo, name, namelen, oid, flags); + if (!ret && flags & GET_OID_REQUIRE_PATH) + die(_("<object>:<path> required, only <object> '%s' given"), + name); if (!ret) return ret; /* @@ -1932,7 +1932,7 @@ void maybe_die_on_misspelt_object_name(struct repository *r, { struct object_context oc; struct object_id oid; - get_oid_with_context_1(r, name, GET_OID_ONLY_TO_DIE, + get_oid_with_context_1(r, name, GET_OID_ONLY_TO_DIE | GET_OID_QUIETLY, prefix, &oid, &oc); } diff --git a/pack-bitmap.c b/pack-bitmap.c index f772d3cb7f..9c666cdb8b 100644 --- a/pack-bitmap.c +++ b/pack-bitmap.c @@ -358,7 +358,9 @@ static int open_midx_bitmap_1(struct bitmap_index *bitmap_git, cleanup: munmap(bitmap_git->map, bitmap_git->map_size); bitmap_git->map_size = 0; + bitmap_git->map_pos = 0; bitmap_git->map = NULL; + bitmap_git->midx = NULL; return -1; } @@ -405,6 +407,8 @@ static int open_pack_bitmap_1(struct bitmap_index *bitmap_git, struct packed_git munmap(bitmap_git->map, bitmap_git->map_size); bitmap_git->map = NULL; bitmap_git->map_size = 0; + bitmap_git->map_pos = 0; + bitmap_git->pack = NULL; return -1; } diff --git a/pack-revindex.c b/pack-revindex.c index 70d0fbafcb..08dc160167 100644 --- a/pack-revindex.c +++ b/pack-revindex.c @@ -298,9 +298,29 @@ int load_midx_revindex(struct multi_pack_index *m) { struct strbuf revindex_name = STRBUF_INIT; int ret; + if (m->revindex_data) return 0; + if (m->chunk_revindex) { + /* + * If the MIDX `m` has a `RIDX` chunk, then use its contents for + * the reverse index instead of trying to load a separate `.rev` + * file. + * + * Note that we do *not* set `m->revindex_map` here, since we do + * not want to accidentally call munmap() in the middle of the + * MIDX. + */ + trace2_data_string("load_midx_revindex", the_repository, + "source", "midx"); + m->revindex_data = (const uint32_t *)m->chunk_revindex; + return 0; + } + + trace2_data_string("load_midx_revindex", the_repository, + "source", "rev"); + get_midx_rev_filename(&revindex_name, m); ret = load_revindex_from_disk(revindex_name.buf, diff --git a/parse-options.c b/parse-options.c index a8283037be..2437ad3bcd 100644 --- a/parse-options.c +++ b/parse-options.c @@ -1079,3 +1079,16 @@ void NORETURN usage_msg_opt(const char *msg, die_message("%s\n", msg); /* The extra \n is intentional */ usage_with_options(usagestr, options); } + +void NORETURN usage_msg_optf(const char * const fmt, + const char * const *usagestr, + const struct option *options, ...) +{ + struct strbuf msg = STRBUF_INIT; + va_list ap; + va_start(ap, options); + strbuf_vaddf(&msg, fmt, ap); + va_end(ap); + + usage_msg_opt(msg.buf, usagestr, options); +} diff --git a/parse-options.h b/parse-options.h index e22846d3b7..659a4c28b2 100644 --- a/parse-options.h +++ b/parse-options.h @@ -85,6 +85,11 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx, * token to explain the kind of argument this option wants. Does not * begin in capital letter, and does not end with a full stop. * Should be wrapped by N_() for translation. + * Is automatically enclosed in brackets when printed, unless it + * contains any of the following characters: ()<>[]| + * E.g. "name" is shown as "<name>" to indicate that a name value + * needs to be supplied, not the literal string "name", but + * "<start>,<end>" and "(this|that)" are printed verbatim. * * `help`:: * the short help associated to what the option does. @@ -225,6 +230,16 @@ NORETURN void usage_msg_opt(const char *msg, const char * const *usagestr, const struct option *options); +/** + * usage_msg_optf() is like usage_msg_opt() except that the first + * argument is a format string, and optional format arguments follow + * after the 3rd option. + */ +__attribute__((format (printf,1,4))) +void NORETURN usage_msg_optf(const char *fmt, + const char * const *usagestr, + const struct option *options, ...); + /* * Use these assertions for callbacks that expect to be called with NONEG and * NOARG respectively, and do not otherwise handle the "unset" and "arg" diff --git a/perl/Git.pm b/perl/Git.pm index 090a7df63f..080cdc2a21 100644 --- a/perl/Git.pm +++ b/perl/Git.pm @@ -1686,6 +1686,16 @@ sub _setup_git_cmd_env { # by searching for it at proper places. sub _execv_git_cmd { exec('git', @_); } +sub _is_sig { + my ($v, $n) = @_; + + # We are avoiding a "use POSIX qw(SIGPIPE SIGABRT)" in the hot + # Git.pm codepath. + require POSIX; + no strict 'refs'; + $v == *{"POSIX::$n"}->(); +} + # Close pipe to a subprocess. sub _cmd_close { my $ctx = shift @_; @@ -1698,9 +1708,16 @@ sub _cmd_close { } elsif ($? >> 8) { # The caller should pepper this. throw Git::Error::Command($ctx, $? >> 8); + } elsif ($? & 127 && _is_sig($? & 127, "SIGPIPE")) { + # we might e.g. closed a live stream; the command + # dying of SIGPIPE would drive us here. + } elsif ($? & 127 && _is_sig($? & 127, "SIGABRT")) { + die sprintf('BUG?: got SIGABRT ($? = %d, $? & 127 = %d) when closing pipe', + $?, $? & 127); + } elsif ($? & 127) { + die sprintf('got signal ($? = %d, $? & 127 = %d) when closing pipe', + $?, $? & 127); } - # else we might e.g. closed a live stream; the command - # dying of SIGPIPE would drive us here. } } diff --git a/read-cache.c b/read-cache.c index cbe73f14e5..79b9b99ebf 100644 --- a/read-cache.c +++ b/read-cache.c @@ -28,6 +28,7 @@ #include "sparse-index.h" #include "csum-file.h" #include "promisor-remote.h" +#include "hook.h" /* Mask for the name length in ce_flags in the on-disk index */ @@ -1339,9 +1340,6 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK; int new_only = option & ADD_CACHE_NEW_ONLY; - if (!(option & ADD_CACHE_KEEP_CACHE_TREE)) - cache_tree_invalidate_path(istate, ce->name); - /* * If this entry's path sorts after the last entry in the index, * we can avoid searching for it. @@ -1352,6 +1350,13 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e else pos = index_name_stage_pos(istate, ce->name, ce_namelen(ce), ce_stage(ce), EXPAND_SPARSE); + /* + * Cache tree path should be invalidated only after index_name_stage_pos, + * in case it expands a sparse index. + */ + if (!(option & ADD_CACHE_KEEP_CACHE_TREE)) + cache_tree_invalidate_path(istate, ce->name); + /* existing match? Just replace it. */ if (pos >= 0) { if (!new_only) @@ -2775,7 +2780,7 @@ static int repo_verify_index(struct repository *repo) return verify_index_from(repo->index, repo->index_file); } -static int has_racy_timestamp(struct index_state *istate) +int has_racy_timestamp(struct index_state *istate) { int entries = istate->cache_nr; int i; @@ -3009,6 +3014,9 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile, !is_null_oid(&istate->split_index->base_oid)) { struct strbuf sb = STRBUF_INIT; + if (istate->sparse_index) + die(_("cannot write split index for a sparse index")); + err = write_link_extension(&sb, istate) < 0 || write_index_ext_header(f, eoie_c, CACHE_EXT_LINK, sb.len) < 0; @@ -3150,7 +3158,7 @@ static int do_write_locked_index(struct index_state *istate, struct lock_file *l else ret = close_lock_file_gently(lock); - run_hook_le(NULL, "post-index-change", + run_hooks_l("post-index-change", istate->updated_workdir ? "1" : "0", istate->updated_skipworktree ? "1" : "0", NULL); istate->updated_workdir = 0; @@ -269,10 +269,9 @@ char *refs_resolve_refdup(struct ref_store *refs, struct object_id *oid, int *flags) { const char *result; - int ignore_errno; result = refs_resolve_ref_unsafe(refs, refname, resolve_flags, - oid, flags, &ignore_errno); + oid, flags); return xstrdup_or_null(result); } @@ -294,11 +293,10 @@ struct ref_filter { int read_ref_full(const char *refname, int resolve_flags, struct object_id *oid, int *flags) { - int ignore_errno; struct ref_store *refs = get_main_ref_store(the_repository); if (refs_resolve_ref_unsafe(refs, refname, resolve_flags, - oid, flags, &ignore_errno)) + oid, flags)) return 0; return -1; } @@ -310,9 +308,8 @@ int read_ref(const char *refname, struct object_id *oid) int refs_ref_exists(struct ref_store *refs, const char *refname) { - int ignore_errno; return !!refs_resolve_ref_unsafe(refs, refname, RESOLVE_REF_READING, - NULL, NULL, &ignore_errno); + NULL, NULL); } int ref_exists(const char *refname) @@ -656,15 +653,13 @@ int expand_ref(struct repository *repo, const char *str, int len, struct object_id *this_result; int flag; struct ref_store *refs = get_main_ref_store(repo); - int ignore_errno; this_result = refs_found ? &oid_from_ref : oid; strbuf_reset(&fullref); strbuf_addf(&fullref, *p, len, str); r = refs_resolve_ref_unsafe(refs, fullref.buf, RESOLVE_REF_READING, - this_result, &flag, - &ignore_errno); + this_result, &flag); if (r) { if (!refs_found++) *ref = xstrdup(r); @@ -693,14 +688,12 @@ int repo_dwim_log(struct repository *r, const char *str, int len, for (p = ref_rev_parse_rules; *p; p++) { struct object_id hash; const char *ref, *it; - int ignore_errno; strbuf_reset(&path); strbuf_addf(&path, *p, len, str); ref = refs_resolve_ref_unsafe(refs, path.buf, RESOLVE_REF_READING, - oid ? &hash : NULL, NULL, - &ignore_errno); + oid ? &hash : NULL, NULL); if (!ref) continue; if (refs_reflog_exists(refs, path.buf)) @@ -800,7 +793,7 @@ int refs_delete_ref(struct ref_store *refs, const char *msg, struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; - transaction = ref_store_transaction_begin(refs, &err); + transaction = ref_store_transaction_begin(refs, 0, &err); if (!transaction || ref_transaction_delete(transaction, refname, old_oid, flags, msg, &err) || @@ -1005,6 +998,7 @@ int read_ref_at(struct ref_store *refs, const char *refname, } struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, + unsigned int flags, struct strbuf *err) { struct ref_transaction *tr; @@ -1012,12 +1006,13 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, CALLOC_ARRAY(tr, 1); tr->ref_store = refs; + tr->flags = flags; return tr; } struct ref_transaction *ref_transaction_begin(struct strbuf *err) { - return ref_store_transaction_begin(get_main_ref_store(the_repository), err); + return ref_store_transaction_begin(get_main_ref_store(the_repository), 0, err); } void ref_transaction_free(struct ref_transaction *transaction) @@ -1156,7 +1151,7 @@ int refs_update_ref(struct ref_store *refs, const char *msg, struct strbuf err = STRBUF_INIT; int ret = 0; - t = ref_store_transaction_begin(refs, &err); + t = ref_store_transaction_begin(refs, 0, &err); if (!t || ref_transaction_update(t, refname, new_oid, old_oid, flags, msg, &err) || @@ -1390,10 +1385,9 @@ int refs_head_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data) { struct object_id oid; int flag; - int ignore_errno; if (refs_resolve_ref_unsafe(refs, "HEAD", RESOLVE_REF_READING, - &oid, &flag, &ignore_errno)) + &oid, &flag)) return fn("HEAD", &oid, flag, cb_data); return 0; @@ -1682,15 +1676,13 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs, const char *refname, int resolve_flags, struct object_id *oid, - int *flags, int *failure_errno) + int *flags) { static struct strbuf sb_refname = STRBUF_INIT; struct object_id unused_oid; int unused_flags; int symref_count; - assert(failure_errno); - if (!oid) oid = &unused_oid; if (!flags) @@ -1700,10 +1692,8 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs, if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) || - !refname_is_safe(refname)) { - *failure_errno = EINVAL; + !refname_is_safe(refname)) return NULL; - } /* * dwim_ref() uses REF_ISBROKEN to distinguish between @@ -1718,9 +1708,10 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs, for (symref_count = 0; symref_count < SYMREF_MAXDEPTH; symref_count++) { unsigned int read_flags = 0; + int failure_errno; if (refs_read_raw_ref(refs, refname, oid, &sb_refname, - &read_flags, failure_errno)) { + &read_flags, &failure_errno)) { *flags |= read_flags; /* In reading mode, refs must eventually resolve */ @@ -1732,9 +1723,9 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs, * may show errors besides ENOENT if there are * similarly-named refs. */ - if (*failure_errno != ENOENT && - *failure_errno != EISDIR && - *failure_errno != ENOTDIR) + if (failure_errno != ENOENT && + failure_errno != EISDIR && + failure_errno != ENOTDIR) return NULL; oidclr(oid); @@ -1760,16 +1751,13 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs, } if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) || - !refname_is_safe(refname)) { - *failure_errno = EINVAL; + !refname_is_safe(refname)) return NULL; - } *flags |= REF_ISBROKEN | REF_BAD_NAME; } } - *failure_errno = ELOOP; return NULL; } @@ -1784,10 +1772,8 @@ int refs_init_db(struct strbuf *err) const char *resolve_ref_unsafe(const char *refname, int resolve_flags, struct object_id *oid, int *flags) { - int ignore_errno; - return refs_resolve_ref_unsafe(get_main_ref_store(the_repository), refname, - resolve_flags, oid, flags, &ignore_errno); + resolve_flags, oid, flags); } int resolve_gitlink_ref(const char *submodule, const char *refname, @@ -1795,15 +1781,14 @@ int resolve_gitlink_ref(const char *submodule, const char *refname, { struct ref_store *refs; int flags; - int ignore_errno; refs = get_submodule_ref_store(submodule); if (!refs) return -1; - if (!refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags, - &ignore_errno) || is_null_oid(oid)) + if (!refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags) || + is_null_oid(oid)) return -1; return 0; } @@ -2082,6 +2067,9 @@ static int run_transaction_hook(struct ref_transaction *transaction, const char *hook; int ret = 0, i; + if (transaction->flags & REF_TRANSACTION_SKIP_HOOK) + return 0; + hook = find_hook("reference-transaction"); if (!hook) return ret; @@ -58,11 +58,6 @@ struct worktree; * resolved. The function returns NULL for such ref names. * Caps and underscores refers to the special refs, such as HEAD, * FETCH_HEAD and friends, that all live outside of the refs/ directory. - * - * Callers should not inspect "errno" on failure, but rather pass in a - * "failure_errno" parameter, on failure the "errno" will indicate the - * type of failure encountered, but not necessarily one that came from - * a syscall. We might have faked it up. */ #define RESOLVE_REF_READING 0x01 #define RESOLVE_REF_NO_RECURSE 0x02 @@ -72,7 +67,7 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs, const char *refname, int resolve_flags, struct object_id *oid, - int *flags, int *failure_errno); + int *flags); const char *resolve_ref_unsafe(const char *refname, int resolve_flags, struct object_id *oid, int *flags); @@ -231,7 +226,7 @@ char *repo_default_branch_name(struct repository *r, int quiet); * struct strbuf err = STRBUF_INIT; * int ret = 0; * - * transaction = ref_store_transaction_begin(refs, &err); + * transaction = ref_store_transaction_begin(refs, 0, &err); * if (!transaction || * ref_transaction_update(...) || * ref_transaction_create(...) || @@ -569,10 +564,16 @@ enum action_on_err { }; /* + * Skip executing the reference-transaction hook. + */ +#define REF_TRANSACTION_SKIP_HOOK (1 << 0) + +/* * Begin a reference transaction. The reference transaction must * be freed by calling ref_transaction_free(). */ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, + unsigned int flags, struct strbuf *err); struct ref_transaction *ref_transaction_begin(struct strbuf *err); diff --git a/refs/files-backend.c b/refs/files-backend.c index 43a3b882d7..f59589d6cc 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -277,11 +277,10 @@ static void loose_fill_ref_dir(struct ref_store *ref_store, create_dir_entry(dir->cache, refname.buf, refname.len)); } else { - int ignore_errno; if (!refs_resolve_ref_unsafe(&refs->base, refname.buf, RESOLVE_REF_READING, - &oid, &flag, &ignore_errno)) { + &oid, &flag)) { oidclr(&oid); flag |= REF_ISBROKEN; } else if (is_null_oid(&oid)) { @@ -1006,7 +1005,6 @@ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs, { struct strbuf ref_file = STRBUF_INIT; struct ref_lock *lock; - int ignore_errno; files_assert_main_repository(refs, "lock_ref_oid_basic"); assert(err); @@ -1034,7 +1032,7 @@ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs, } if (!refs_resolve_ref_unsafe(&refs->base, lock->ref_name, 0, - &lock->old_oid, NULL, &ignore_errno)) + &lock->old_oid, NULL)) oidclr(&lock->old_oid); goto out; @@ -1116,7 +1114,8 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r) if (check_refname_format(r->name, 0)) return; - transaction = ref_store_transaction_begin(&refs->base, &err); + transaction = ref_store_transaction_begin(&refs->base, + REF_TRANSACTION_SKIP_HOOK, &err); if (!transaction) goto cleanup; ref_transaction_add_update( @@ -1187,7 +1186,8 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags) struct strbuf err = STRBUF_INIT; struct ref_transaction *transaction; - transaction = ref_store_transaction_begin(refs->packed_ref_store, &err); + transaction = ref_store_transaction_begin(refs->packed_ref_store, + REF_TRANSACTION_SKIP_HOOK, &err); if (!transaction) return -1; @@ -1244,6 +1244,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, { struct files_ref_store *refs = files_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); + struct ref_transaction *transaction = NULL; struct strbuf err = STRBUF_INIT; int i, result = 0; @@ -1253,10 +1254,15 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, if (packed_refs_lock(refs->packed_ref_store, 0, &err)) goto error; - if (refs_delete_refs(refs->packed_ref_store, msg, refnames, flags)) { - packed_refs_unlock(refs->packed_ref_store); + transaction = ref_store_transaction_begin(refs->packed_ref_store, + REF_TRANSACTION_SKIP_HOOK, &err); + if (!transaction) + goto error; + + result = packed_refs_delete_refs(refs->packed_ref_store, + transaction, msg, refnames, flags); + if (result) goto error; - } packed_refs_unlock(refs->packed_ref_store); @@ -1267,6 +1273,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg, result |= error(_("could not remove reference %s"), refname); } + ref_transaction_free(transaction); strbuf_release(&err); return result; @@ -1283,6 +1290,7 @@ error: else error(_("could not delete references: %s"), err.buf); + ref_transaction_free(transaction); strbuf_release(&err); return -1; } @@ -1399,7 +1407,6 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store, struct strbuf tmp_renamed_log = STRBUF_INIT; int log, ret; struct strbuf err = STRBUF_INIT; - int ignore_errno; files_reflog_path(refs, &sb_oldref, oldrefname); files_reflog_path(refs, &sb_newref, newrefname); @@ -1413,7 +1420,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store, if (!refs_resolve_ref_unsafe(&refs->base, oldrefname, RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE, - &orig_oid, &flag, &ignore_errno)) { + &orig_oid, &flag)) { ret = error("refname %s not found", oldrefname); goto out; } @@ -1459,7 +1466,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store, */ if (!copy && refs_resolve_ref_unsafe(&refs->base, newrefname, RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE, - NULL, NULL, &ignore_errno) && + NULL, NULL) && refs_delete_ref(&refs->base, NULL, newrefname, NULL, REF_NO_DEREF)) { if (errno == EISDIR) { @@ -1828,12 +1835,10 @@ static int commit_ref_update(struct files_ref_store *refs, */ int head_flag; const char *head_ref; - int ignore_errno; head_ref = refs_resolve_ref_unsafe(&refs->base, "HEAD", RESOLVE_REF_READING, - NULL, &head_flag, - &ignore_errno); + NULL, &head_flag); if (head_ref && (head_flag & REF_ISSYMREF) && !strcmp(head_ref, lock->ref_name)) { struct strbuf log_err = STRBUF_INIT; @@ -1877,12 +1882,10 @@ static void update_symref_reflog(struct files_ref_store *refs, { struct strbuf err = STRBUF_INIT; struct object_id new_oid; - int ignore_errno; if (logmsg && refs_resolve_ref_unsafe(&refs->base, target, - RESOLVE_REF_READING, &new_oid, NULL, - &ignore_errno) && + RESOLVE_REF_READING, &new_oid, NULL) && files_log_ref_write(refs, refname, &lock->old_oid, &new_oid, logmsg, 0, &err)) { error("%s", err.buf); @@ -2156,7 +2159,6 @@ static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator) (struct files_reflog_iterator *)ref_iterator; struct dir_iterator *diter = iter->dir_iterator; int ok; - int ignore_errno; while ((ok = dir_iterator_advance(diter)) == ITER_OK) { int flags; @@ -2170,8 +2172,7 @@ static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator) if (!refs_resolve_ref_unsafe(iter->ref_store, diter->relative_path, 0, - &iter->oid, &flags, - &ignore_errno)) { + &iter->oid, &flags)) { error("bad ref for %s", diter->path.buf); continue; } @@ -2515,11 +2516,9 @@ static int lock_ref_for_update(struct files_ref_store *refs, * the transaction, so we have to read it here * to record and possibly check old_oid: */ - int ignore_errno; if (!refs_resolve_ref_unsafe(&refs->base, referent.buf, 0, - &lock->old_oid, NULL, - &ignore_errno)) { + &lock->old_oid, NULL)) { if (update->flags & REF_HAVE_OLD) { strbuf_addf(err, "cannot lock ref '%s': " "error reading reference", @@ -2762,7 +2761,8 @@ static int files_transaction_prepare(struct ref_store *ref_store, */ if (!packed_transaction) { packed_transaction = ref_store_transaction_begin( - refs->packed_ref_store, err); + refs->packed_ref_store, + REF_TRANSACTION_SKIP_HOOK, err); if (!packed_transaction) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; @@ -3033,7 +3033,8 @@ static int files_initial_transaction_commit(struct ref_store *ref_store, &affected_refnames)) BUG("initial ref transaction called with existing refs"); - packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, err); + packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, + REF_TRANSACTION_SKIP_HOOK, err); if (!packed_transaction) { ret = TRANSACTION_GENERIC_ERROR; goto cleanup; @@ -3208,14 +3209,12 @@ static int files_reflog_expire(struct ref_store *ref_store, if ((expire_flags & EXPIRE_REFLOGS_UPDATE_REF) && !is_null_oid(&cb.last_kept_oid)) { - int ignore_errno; int type; const char *ref; ref = refs_resolve_ref_unsafe(&refs->base, refname, RESOLVE_REF_NO_RECURSE, - NULL, &type, - &ignore_errno); + NULL, &type); update = !!(ref && !(type & REF_ISSYMREF)); } diff --git a/refs/packed-backend.c b/refs/packed-backend.c index d91a2018f6..27dd8c3922 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -1521,15 +1521,10 @@ static int packed_initial_transaction_commit(struct ref_store *ref_store, static int packed_delete_refs(struct ref_store *ref_store, const char *msg, struct string_list *refnames, unsigned int flags) { - struct packed_ref_store *refs = - packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); struct strbuf err = STRBUF_INIT; struct ref_transaction *transaction; - struct string_list_item *item; int ret; - (void)refs; /* We need the check above, but don't use the variable */ - if (!refnames->nr) return 0; @@ -1539,10 +1534,30 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, * updates into a single transaction. */ - transaction = ref_store_transaction_begin(ref_store, &err); + transaction = ref_store_transaction_begin(ref_store, 0, &err); if (!transaction) return -1; + ret = packed_refs_delete_refs(ref_store, transaction, + msg, refnames, flags); + + ref_transaction_free(transaction); + return ret; +} + +int packed_refs_delete_refs(struct ref_store *ref_store, + struct ref_transaction *transaction, + const char *msg, + struct string_list *refnames, + unsigned int flags) +{ + struct strbuf err = STRBUF_INIT; + struct string_list_item *item; + int ret; + + /* Assert that the ref store refers to a packed backend. */ + packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); + for_each_string_list_item(item, refnames) { if (ref_transaction_delete(transaction, item->string, NULL, flags, msg, &err)) { @@ -1562,7 +1577,6 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg, error(_("could not delete references: %s"), err.buf); } - ref_transaction_free(transaction); strbuf_release(&err); return ret; } diff --git a/refs/packed-backend.h b/refs/packed-backend.h index 9dd8a344c3..52e0490753 100644 --- a/refs/packed-backend.h +++ b/refs/packed-backend.h @@ -3,6 +3,7 @@ struct repository; struct ref_transaction; +struct string_list; /* * Support for storing references in a `packed-refs` file. @@ -27,6 +28,12 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err) void packed_refs_unlock(struct ref_store *ref_store); int packed_refs_is_locked(struct ref_store *ref_store); +int packed_refs_delete_refs(struct ref_store *ref_store, + struct ref_transaction *transaction, + const char *msg, + struct string_list *refnames, + unsigned int flags); + /* * Return true if `transaction` really needs to be carried out against * the specified packed_ref_store, or false if it can be skipped diff --git a/refs/refs-internal.h b/refs/refs-internal.h index 7ff6fba4f0..6e15db3ca4 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -213,6 +213,7 @@ struct ref_transaction { size_t nr; enum ref_transaction_state state; void *backend_data; + unsigned int flags; }; /* diff --git a/reftable/block.c b/reftable/block.c index 855e3f5c94..2170748c5e 100644 --- a/reftable/block.c +++ b/reftable/block.c @@ -188,13 +188,16 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block, uint32_t full_block_size = table_block_size; uint8_t typ = block->data[header_off]; uint32_t sz = get_be24(block->data + header_off + 1); - + int err = 0; uint16_t restart_count = 0; uint32_t restart_start = 0; uint8_t *restart_bytes = NULL; + uint8_t *uncompressed = NULL; - if (!reftable_is_block_type(typ)) - return REFTABLE_FORMAT_ERROR; + if (!reftable_is_block_type(typ)) { + err = REFTABLE_FORMAT_ERROR; + goto done; + } if (typ == BLOCK_TYPE_LOG) { int block_header_skip = 4 + header_off; @@ -203,7 +206,7 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block, uLongf src_len = block->len - block_header_skip; /* Log blocks specify the *uncompressed* size in their header. */ - uint8_t *uncompressed = reftable_malloc(sz); + uncompressed = reftable_malloc(sz); /* Copy over the block header verbatim. It's not compressed. */ memcpy(uncompressed, block->data, block_header_skip); @@ -212,16 +215,19 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block, if (Z_OK != uncompress2(uncompressed + block_header_skip, &dst_len, block->data + block_header_skip, &src_len)) { - reftable_free(uncompressed); - return REFTABLE_ZLIB_ERROR; + err = REFTABLE_ZLIB_ERROR; + goto done; } - if (dst_len + block_header_skip != sz) - return REFTABLE_FORMAT_ERROR; + if (dst_len + block_header_skip != sz) { + err = REFTABLE_FORMAT_ERROR; + goto done; + } /* We're done with the input data. */ reftable_block_done(block); block->data = uncompressed; + uncompressed = NULL; block->len = sz; block->source = malloc_block_source(); full_block_size = src_len + block_header_skip; @@ -251,7 +257,9 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block, br->restart_count = restart_count; br->restart_bytes = restart_bytes; - return 0; +done: + reftable_free(uncompressed); + return err; } static uint32_t block_reader_restart_offset(struct block_reader *br, int i) @@ -413,7 +421,7 @@ int block_reader_seek(struct block_reader *br, struct block_iter *it, done: strbuf_release(&key); strbuf_release(&next.last_key); - reftable_record_destroy(&rec); + reftable_record_release(&rec); return err; } diff --git a/reftable/block_test.c b/reftable/block_test.c index 4b3ea262dc..fa2ee092ec 100644 --- a/reftable/block_test.c +++ b/reftable/block_test.c @@ -26,8 +26,9 @@ static void test_block_read_write(void) struct block_writer bw = { .last_key = STRBUF_INIT, }; - struct reftable_ref_record ref = { NULL }; - struct reftable_record rec = { NULL }; + struct reftable_record rec = { + .type = BLOCK_TYPE_REF, + }; int i = 0; int n; struct block_reader br = { 0 }; @@ -40,7 +41,6 @@ static void test_block_read_write(void) block.source = malloc_block_source(); block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size, header_off, hash_size(GIT_SHA1_FORMAT_ID)); - reftable_record_from_ref(&rec, &ref); for (i = 0; i < N; i++) { char name[100]; @@ -48,14 +48,14 @@ static void test_block_read_write(void) snprintf(name, sizeof(name), "branch%02d", i); memset(hash, i, sizeof(hash)); - ref.refname = name; - ref.value_type = REFTABLE_REF_VAL1; - ref.value.val1 = hash; + rec.u.ref.refname = name; + rec.u.ref.value_type = REFTABLE_REF_VAL1; + rec.u.ref.value.val1 = hash; names[i] = xstrdup(name); n = block_writer_add(&bw, &rec); - ref.refname = NULL; - ref.value_type = REFTABLE_REF_DELETION; + rec.u.ref.refname = NULL; + rec.u.ref.value_type = REFTABLE_REF_DELETION; EXPECT(n == 0); } @@ -74,7 +74,7 @@ static void test_block_read_write(void) if (r > 0) { break; } - EXPECT_STREQ(names[j], ref.refname); + EXPECT_STREQ(names[j], rec.u.ref.refname); j++; } @@ -92,7 +92,7 @@ static void test_block_read_write(void) n = block_iter_next(&it, &rec); EXPECT(n == 0); - EXPECT_STREQ(names[i], ref.refname); + EXPECT_STREQ(names[i], rec.u.ref.refname); want.len--; n = block_reader_seek(&br, &it, &want); @@ -100,7 +100,7 @@ static void test_block_read_write(void) n = block_iter_next(&it, &rec); EXPECT(n == 0); - EXPECT_STREQ(names[10 * (i / 10)], ref.refname); + EXPECT_STREQ(names[10 * (i / 10)], rec.u.ref.refname); block_iter_close(&it); } diff --git a/reftable/blocksource.c b/reftable/blocksource.c index 0044eecd9a..2605371c28 100644 --- a/reftable/blocksource.c +++ b/reftable/blocksource.c @@ -134,8 +134,10 @@ int reftable_block_source_from_file(struct reftable_block_source *bs, } err = fstat(fd, &st); - if (err < 0) - return -1; + if (err < 0) { + close(fd); + return REFTABLE_IO_ERROR; + } p = reftable_calloc(sizeof(struct file_block_source)); p->size = st.st_size; diff --git a/reftable/generic.c b/reftable/generic.c index 7a8a738d86..b27d152e89 100644 --- a/reftable/generic.c +++ b/reftable/generic.c @@ -7,6 +7,7 @@ https://developers.google.com/open-source/licenses/bsd */ #include "basics.h" +#include "constants.h" #include "record.h" #include "generic.h" #include "reftable-iterator.h" @@ -15,23 +16,21 @@ https://developers.google.com/open-source/licenses/bsd int reftable_table_seek_ref(struct reftable_table *tab, struct reftable_iterator *it, const char *name) { - struct reftable_ref_record ref = { - .refname = (char *)name, - }; - struct reftable_record rec = { NULL }; - reftable_record_from_ref(&rec, &ref); + struct reftable_record rec = { .type = BLOCK_TYPE_REF, + .u.ref = { + .refname = (char *)name, + } }; return tab->ops->seek_record(tab->table_arg, it, &rec); } int reftable_table_seek_log(struct reftable_table *tab, struct reftable_iterator *it, const char *name) { - struct reftable_log_record log = { - .refname = (char *)name, - .update_index = ~((uint64_t)0), - }; - struct reftable_record rec = { NULL }; - reftable_record_from_log(&rec, &log); + struct reftable_record rec = { .type = BLOCK_TYPE_LOG, + .u.log = { + .refname = (char *)name, + .update_index = ~((uint64_t)0), + } }; return tab->ops->seek_record(tab->table_arg, it, &rec); } @@ -129,17 +128,25 @@ void reftable_iterator_destroy(struct reftable_iterator *it) int reftable_iterator_next_ref(struct reftable_iterator *it, struct reftable_ref_record *ref) { - struct reftable_record rec = { NULL }; - reftable_record_from_ref(&rec, ref); - return iterator_next(it, &rec); + struct reftable_record rec = { + .type = BLOCK_TYPE_REF, + .u.ref = *ref, + }; + int err = iterator_next(it, &rec); + *ref = rec.u.ref; + return err; } int reftable_iterator_next_log(struct reftable_iterator *it, struct reftable_log_record *log) { - struct reftable_record rec = { NULL }; - reftable_record_from_log(&rec, log); - return iterator_next(it, &rec); + struct reftable_record rec = { + .type = BLOCK_TYPE_LOG, + .u.log = *log, + }; + int err = iterator_next(it, &rec); + *log = rec.u.log; + return err; } int iterator_next(struct reftable_iterator *it, struct reftable_record *rec) diff --git a/reftable/iter.c b/reftable/iter.c index 93d04f735b..a8d174c040 100644 --- a/reftable/iter.c +++ b/reftable/iter.c @@ -32,7 +32,7 @@ static int filtering_ref_iterator_next(void *iter_arg, struct reftable_record *rec) { struct filtering_ref_iterator *fri = iter_arg; - struct reftable_ref_record *ref = rec->data; + struct reftable_ref_record *ref = &rec->u.ref; int err = 0; while (1) { err = reftable_iterator_next_ref(&fri->it, ref); @@ -127,7 +127,7 @@ static int indexed_table_ref_iter_next_block(struct indexed_table_ref_iter *it) static int indexed_table_ref_iter_next(void *p, struct reftable_record *rec) { struct indexed_table_ref_iter *it = p; - struct reftable_ref_record *ref = rec->data; + struct reftable_ref_record *ref = &rec->u.ref; while (1) { int err = block_iter_next(&it->cur, rec); diff --git a/reftable/merged.c b/reftable/merged.c index e5b53da6db..2a6efa110d 100644 --- a/reftable/merged.c +++ b/reftable/merged.c @@ -30,7 +30,7 @@ static int merged_iter_init(struct merged_iter *mi) if (err > 0) { reftable_iterator_destroy(&mi->stack[i]); - reftable_record_destroy(&rec); + reftable_record_release(&rec); } else { struct pq_entry e = { .rec = rec, @@ -57,18 +57,17 @@ static void merged_iter_close(void *p) static int merged_iter_advance_nonnull_subiter(struct merged_iter *mi, size_t idx) { - struct reftable_record rec = reftable_new_record(mi->typ); struct pq_entry e = { - .rec = rec, + .rec = reftable_new_record(mi->typ), .index = idx, }; - int err = iterator_next(&mi->stack[idx], &rec); + int err = iterator_next(&mi->stack[idx], &e.rec); if (err < 0) return err; if (err > 0) { reftable_iterator_destroy(&mi->stack[idx]); - reftable_record_destroy(&rec); + reftable_record_release(&e.rec); return 0; } @@ -126,11 +125,11 @@ static int merged_iter_next_entry(struct merged_iter *mi, if (err < 0) { return err; } - reftable_record_destroy(&top.rec); + reftable_record_release(&top.rec); } reftable_record_copy_from(rec, &entry.rec, hash_size(mi->hash_id)); - reftable_record_destroy(&entry.rec); + reftable_record_release(&entry.rec); strbuf_release(&entry_key); return 0; } @@ -290,11 +289,12 @@ int reftable_merged_table_seek_ref(struct reftable_merged_table *mt, struct reftable_iterator *it, const char *name) { - struct reftable_ref_record ref = { - .refname = (char *)name, + struct reftable_record rec = { + .type = BLOCK_TYPE_REF, + .u.ref = { + .refname = (char *)name, + }, }; - struct reftable_record rec = { NULL }; - reftable_record_from_ref(&rec, &ref); return merged_table_seek_record(mt, it, &rec); } @@ -302,12 +302,11 @@ int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt, struct reftable_iterator *it, const char *name, uint64_t update_index) { - struct reftable_log_record log = { - .refname = (char *)name, - .update_index = update_index, - }; - struct reftable_record rec = { NULL }; - reftable_record_from_log(&rec, &log); + struct reftable_record rec = { .type = BLOCK_TYPE_LOG, + .u.log = { + .refname = (char *)name, + .update_index = update_index, + } }; return merged_table_seek_record(mt, it, &rec); } diff --git a/reftable/pq.c b/reftable/pq.c index efc474017a..96ca6dd37b 100644 --- a/reftable/pq.c +++ b/reftable/pq.c @@ -74,6 +74,7 @@ struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq) void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e) { int i = 0; + if (pq->len == pq->cap) { pq->cap = 2 * pq->cap + 1; pq->heap = reftable_realloc(pq->heap, @@ -98,7 +99,7 @@ void merged_iter_pqueue_release(struct merged_iter_pqueue *pq) { int i = 0; for (i = 0; i < pq->len; i++) { - reftable_record_destroy(&pq->heap[i].rec); + reftable_record_release(&pq->heap[i].rec); } FREE_AND_NULL(pq->heap); pq->len = pq->cap = 0; diff --git a/reftable/pq_test.c b/reftable/pq_test.c index c9bb05e37b..7de5e886f3 100644 --- a/reftable/pq_test.c +++ b/reftable/pq_test.c @@ -31,7 +31,7 @@ static void test_pq(void) int N = ARRAY_SIZE(names) - 1; struct merged_iter_pqueue pq = { NULL }; - const char *last = NULL; + char *last = NULL; int i = 0; for (i = 0; i < N; i++) { @@ -42,12 +42,10 @@ static void test_pq(void) i = 1; do { - struct reftable_record rec = - reftable_new_record(BLOCK_TYPE_REF); - struct pq_entry e = { 0 }; - - reftable_record_as_ref(&rec)->refname = names[i]; - e.rec = rec; + struct pq_entry e = { .rec = { .type = BLOCK_TYPE_REF, + .u.ref = { + .refname = names[i], + } } }; merged_iter_pqueue_add(&pq, e); merged_iter_pqueue_check(pq); i = (i * 7) % N; @@ -55,19 +53,18 @@ static void test_pq(void) while (!merged_iter_pqueue_is_empty(pq)) { struct pq_entry e = merged_iter_pqueue_remove(&pq); - struct reftable_ref_record *ref = - reftable_record_as_ref(&e.rec); - + struct reftable_record *rec = &e.rec; merged_iter_pqueue_check(pq); + EXPECT(reftable_record_type(rec) == BLOCK_TYPE_REF); if (last) { - EXPECT(strcmp(last, ref->refname) < 0); + EXPECT(strcmp(last, rec->u.ref.refname) < 0); } - last = ref->refname; - ref->refname = NULL; - reftable_free(ref); + // this is names[i], so don't dealloc. + last = rec->u.ref.refname; + rec->u.ref.refname = NULL; + reftable_record_release(rec); } - for (i = 0; i < N; i++) { reftable_free(names[i]); } diff --git a/reftable/reader.c b/reftable/reader.c index 006709a645..00906e7a2d 100644 --- a/reftable/reader.c +++ b/reftable/reader.c @@ -239,8 +239,7 @@ static int table_iter_next_in_block(struct table_iter *ti, { int res = block_iter_next(&ti->bi, rec); if (res == 0 && reftable_record_type(rec) == BLOCK_TYPE_REF) { - ((struct reftable_ref_record *)rec->data)->update_index += - ti->r->min_update_index; + rec->u.ref.update_index += ti->r->min_update_index; } return res; @@ -290,28 +289,33 @@ int reader_init_block_reader(struct reftable_reader *r, struct block_reader *br, err = reader_get_block(r, &block, next_off, guess_block_size); if (err < 0) - return err; + goto done; block_size = extract_block_size(block.data, &block_typ, next_off, r->version); - if (block_size < 0) - return block_size; - + if (block_size < 0) { + err = block_size; + goto done; + } if (want_typ != BLOCK_TYPE_ANY && block_typ != want_typ) { - reftable_block_done(&block); - return 1; + err = 1; + goto done; } if (block_size > guess_block_size) { reftable_block_done(&block); err = reader_get_block(r, &block, next_off, block_size); if (err < 0) { - return err; + goto done; } } - return block_reader_init(br, &block, header_off, r->block_size, - hash_size(r->hash_id)); + err = block_reader_init(br, &block, header_off, r->block_size, + hash_size(r->hash_id)); +done: + reftable_block_done(&block); + + return err; } static int table_iter_next_block(struct table_iter *dest, @@ -475,7 +479,7 @@ static int reader_seek_linear(struct reftable_reader *r, struct table_iter *ti, done: block_iter_close(&next.bi); - reftable_record_destroy(&rec); + reftable_record_release(&rec); strbuf_release(&want_key); strbuf_release(&got_key); return err; @@ -485,34 +489,35 @@ static int reader_seek_indexed(struct reftable_reader *r, struct reftable_iterator *it, struct reftable_record *rec) { - struct reftable_index_record want_index = { .last_key = STRBUF_INIT }; - struct reftable_record want_index_rec = { NULL }; - struct reftable_index_record index_result = { .last_key = STRBUF_INIT }; - struct reftable_record index_result_rec = { NULL }; + struct reftable_record want_index = { + .type = BLOCK_TYPE_INDEX, .u.idx = { .last_key = STRBUF_INIT } + }; + struct reftable_record index_result = { + .type = BLOCK_TYPE_INDEX, + .u.idx = { .last_key = STRBUF_INIT }, + }; struct table_iter index_iter = TABLE_ITER_INIT; struct table_iter next = TABLE_ITER_INIT; int err = 0; - reftable_record_key(rec, &want_index.last_key); - reftable_record_from_index(&want_index_rec, &want_index); - reftable_record_from_index(&index_result_rec, &index_result); - + reftable_record_key(rec, &want_index.u.idx.last_key); err = reader_start(r, &index_iter, reftable_record_type(rec), 1); if (err < 0) goto done; - err = reader_seek_linear(r, &index_iter, &want_index_rec); + err = reader_seek_linear(r, &index_iter, &want_index); while (1) { - err = table_iter_next(&index_iter, &index_result_rec); + err = table_iter_next(&index_iter, &index_result); table_iter_block_done(&index_iter); if (err != 0) goto done; - err = reader_table_iter_at(r, &next, index_result.offset, 0); + err = reader_table_iter_at(r, &next, index_result.u.idx.offset, + 0); if (err != 0) goto done; - err = block_iter_seek(&next.bi, &want_index.last_key); + err = block_iter_seek(&next.bi, &want_index.u.idx.last_key); if (err < 0) goto done; @@ -540,8 +545,8 @@ static int reader_seek_indexed(struct reftable_reader *r, done: block_iter_close(&next.bi); table_iter_close(&index_iter); - reftable_record_release(&want_index_rec); - reftable_record_release(&index_result_rec); + reftable_record_release(&want_index); + reftable_record_release(&index_result); return err; } @@ -590,11 +595,12 @@ static int reader_seek(struct reftable_reader *r, struct reftable_iterator *it, int reftable_reader_seek_ref(struct reftable_reader *r, struct reftable_iterator *it, const char *name) { - struct reftable_ref_record ref = { - .refname = (char *)name, + struct reftable_record rec = { + .type = BLOCK_TYPE_REF, + .u.ref = { + .refname = (char *)name, + }, }; - struct reftable_record rec = { NULL }; - reftable_record_from_ref(&rec, &ref); return reader_seek(r, it, &rec); } @@ -602,12 +608,11 @@ int reftable_reader_seek_log_at(struct reftable_reader *r, struct reftable_iterator *it, const char *name, uint64_t update_index) { - struct reftable_log_record log = { - .refname = (char *)name, - .update_index = update_index, - }; - struct reftable_record rec = { NULL }; - reftable_record_from_log(&rec, &log); + struct reftable_record rec = { .type = BLOCK_TYPE_LOG, + .u.log = { + .refname = (char *)name, + .update_index = update_index, + } }; return reader_seek(r, it, &rec); } @@ -641,6 +646,8 @@ int reftable_new_reader(struct reftable_reader **p, void reftable_reader_free(struct reftable_reader *r) { + if (!r) + return; reader_close(r); reftable_free(r); } @@ -649,31 +656,33 @@ static int reftable_reader_refs_for_indexed(struct reftable_reader *r, struct reftable_iterator *it, uint8_t *oid) { - struct reftable_obj_record want = { - .hash_prefix = oid, - .hash_prefix_len = r->object_id_len, + struct reftable_record want = { + .type = BLOCK_TYPE_OBJ, + .u.obj = { + .hash_prefix = oid, + .hash_prefix_len = r->object_id_len, + }, }; - struct reftable_record want_rec = { NULL }; struct reftable_iterator oit = { NULL }; - struct reftable_obj_record got = { NULL }; - struct reftable_record got_rec = { NULL }; + struct reftable_record got = { + .type = BLOCK_TYPE_OBJ, + .u.obj = { 0 }, + }; int err = 0; struct indexed_table_ref_iter *itr = NULL; /* Look through the reverse index. */ - reftable_record_from_obj(&want_rec, &want); - err = reader_seek(r, &oit, &want_rec); + err = reader_seek(r, &oit, &want); if (err != 0) goto done; /* read out the reftable_obj_record */ - reftable_record_from_obj(&got_rec, &got); - err = iterator_next(&oit, &got_rec); + err = iterator_next(&oit, &got); if (err < 0) goto done; - if (err > 0 || - memcmp(want.hash_prefix, got.hash_prefix, r->object_id_len)) { + if (err > 0 || memcmp(want.u.obj.hash_prefix, got.u.obj.hash_prefix, + r->object_id_len)) { /* didn't find it; return empty iterator */ iterator_set_empty(it); err = 0; @@ -681,15 +690,16 @@ static int reftable_reader_refs_for_indexed(struct reftable_reader *r, } err = new_indexed_table_ref_iter(&itr, r, oid, hash_size(r->hash_id), - got.offsets, got.offset_len); + got.u.obj.offsets, + got.u.obj.offset_len); if (err < 0) goto done; - got.offsets = NULL; + got.u.obj.offsets = NULL; iterator_from_indexed_table_ref_iter(it, itr); done: reftable_iterator_destroy(&oit); - reftable_record_release(&got_rec); + reftable_record_release(&got); return err; } diff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c index 70c7aedba2..605ba0f9fd 100644 --- a/reftable/readwrite_test.c +++ b/reftable/readwrite_test.c @@ -288,6 +288,71 @@ static void test_log_write_read(void) reader_close(&rd); } +static void test_log_zlib_corruption(void) +{ + struct reftable_write_options opts = { + .block_size = 256, + }; + struct reftable_iterator it = { 0 }; + struct reftable_reader rd = { 0 }; + struct reftable_block_source source = { 0 }; + struct strbuf buf = STRBUF_INIT; + struct reftable_writer *w = + reftable_new_writer(&strbuf_add_void, &buf, &opts); + const struct reftable_stats *stats = NULL; + uint8_t hash1[GIT_SHA1_RAWSZ] = { 1 }; + uint8_t hash2[GIT_SHA1_RAWSZ] = { 2 }; + char message[100] = { 0 }; + int err, i, n; + + struct reftable_log_record log = { + .refname = "refname", + .value_type = REFTABLE_LOG_UPDATE, + .value = { + .update = { + .new_hash = hash1, + .old_hash = hash2, + .name = "My Name", + .email = "myname@invalid", + .message = message, + }, + }, + }; + + for (i = 0; i < sizeof(message) - 1; i++) + message[i] = (uint8_t)(rand() % 64 + ' '); + + reftable_writer_set_limits(w, 1, 1); + + err = reftable_writer_add_log(w, &log); + EXPECT_ERR(err); + + n = reftable_writer_close(w); + EXPECT(n == 0); + + stats = writer_stats(w); + EXPECT(stats->log_stats.blocks > 0); + reftable_writer_free(w); + w = NULL; + + /* corrupt the data. */ + buf.buf[50] ^= 0x99; + + block_source_from_strbuf(&source, &buf); + + err = init_reader(&rd, &source, "file.log"); + EXPECT_ERR(err); + + err = reftable_reader_seek_log(&rd, &it, "refname"); + EXPECT(err == REFTABLE_ZLIB_ERROR); + + reftable_iterator_destroy(&it); + + /* cleanup. */ + strbuf_release(&buf); + reader_close(&rd); +} + static void test_table_read_write_sequential(void) { char **names; @@ -631,7 +696,6 @@ static void test_write_key_order(void) err = reftable_writer_add_ref(w, &refs[0]); EXPECT_ERR(err); err = reftable_writer_add_ref(w, &refs[1]); - printf("%d\n", err); EXPECT(err == REFTABLE_API_ERROR); reftable_writer_close(w); reftable_writer_free(w); @@ -667,6 +731,7 @@ static void test_corrupt_table(void) int readwrite_test_main(int argc, const char *argv[]) { + RUN_TEST(test_log_zlib_corruption); RUN_TEST(test_corrupt_table); RUN_TEST(test_corrupt_table_empty); RUN_TEST(test_log_write_read); diff --git a/reftable/record.c b/reftable/record.c index 6a5dac32dc..fbaa1fbef5 100644 --- a/reftable/record.c +++ b/reftable/record.c @@ -15,6 +15,10 @@ https://developers.google.com/open-source/licenses/bsd #include "reftable-error.h" #include "basics.h" +static struct reftable_record_vtable * +reftable_record_vtable(struct reftable_record *rec); +static void *reftable_record_data(struct reftable_record *rec); + int get_var_int(uint64_t *dest, struct string_view *in) { int ptr = 0; @@ -72,7 +76,7 @@ int reftable_is_block_type(uint8_t typ) return 0; } -uint8_t *reftable_ref_record_val1(struct reftable_ref_record *rec) +uint8_t *reftable_ref_record_val1(const struct reftable_ref_record *rec) { switch (rec->value_type) { case REFTABLE_REF_VAL1: @@ -84,7 +88,7 @@ uint8_t *reftable_ref_record_val1(struct reftable_ref_record *rec) } } -uint8_t *reftable_ref_record_val2(struct reftable_ref_record *rec) +uint8_t *reftable_ref_record_val2(const struct reftable_ref_record *rec) { switch (rec->value_type) { case REFTABLE_REF_VAL2: @@ -251,24 +255,24 @@ static void hex_format(char *dest, uint8_t *src, int hash_size) } } -void reftable_ref_record_print(struct reftable_ref_record *ref, - uint32_t hash_id) +static void reftable_ref_record_print_sz(const struct reftable_ref_record *ref, + int hash_size) { - char hex[2 * GIT_SHA256_RAWSZ + 1] = { 0 }; /* BUG */ + char hex[GIT_MAX_HEXSZ + 1] = { 0 }; /* BUG */ printf("ref{%s(%" PRIu64 ") ", ref->refname, ref->update_index); switch (ref->value_type) { case REFTABLE_REF_SYMREF: printf("=> %s", ref->value.symref); break; case REFTABLE_REF_VAL2: - hex_format(hex, ref->value.val2.value, hash_size(hash_id)); + hex_format(hex, ref->value.val2.value, hash_size); printf("val 2 %s", hex); hex_format(hex, ref->value.val2.target_value, - hash_size(hash_id)); + hash_size); printf("(T %s)", hex); break; case REFTABLE_REF_VAL1: - hex_format(hex, ref->value.val1, hash_size(hash_id)); + hex_format(hex, ref->value.val1, hash_size); printf("val 1 %s", hex); break; case REFTABLE_REF_DELETION: @@ -278,6 +282,11 @@ void reftable_ref_record_print(struct reftable_ref_record *ref, printf("}\n"); } +void reftable_ref_record_print(const struct reftable_ref_record *ref, + uint32_t hash_id) { + reftable_ref_record_print_sz(ref, hash_size(hash_id)); +} + static void reftable_ref_record_release_void(void *rec) { reftable_ref_record_release(rec); @@ -430,6 +439,21 @@ static int reftable_ref_record_is_deletion_void(const void *p) (const struct reftable_ref_record *)p); } + +static int reftable_ref_record_equal_void(const void *a, + const void *b, int hash_size) +{ + struct reftable_ref_record *ra = (struct reftable_ref_record *) a; + struct reftable_ref_record *rb = (struct reftable_ref_record *) b; + return reftable_ref_record_equal(ra, rb, hash_size); +} + +static void reftable_ref_record_print_void(const void *rec, + int hash_size) +{ + reftable_ref_record_print_sz((struct reftable_ref_record *) rec, hash_size); +} + static struct reftable_record_vtable reftable_ref_record_vtable = { .key = &reftable_ref_record_key, .type = BLOCK_TYPE_REF, @@ -439,6 +463,8 @@ static struct reftable_record_vtable reftable_ref_record_vtable = { .decode = &reftable_ref_record_decode, .release = &reftable_ref_record_release_void, .is_deletion = &reftable_ref_record_is_deletion_void, + .equal = &reftable_ref_record_equal_void, + .print = &reftable_ref_record_print_void, }; static void reftable_obj_record_key(const void *r, struct strbuf *dest) @@ -457,6 +483,21 @@ static void reftable_obj_record_release(void *rec) memset(obj, 0, sizeof(struct reftable_obj_record)); } +static void reftable_obj_record_print(const void *rec, int hash_size) +{ + const struct reftable_obj_record *obj = rec; + char hex[GIT_MAX_HEXSZ + 1] = { 0 }; + struct strbuf offset_str = STRBUF_INIT; + int i; + + for (i = 0; i < obj->offset_len; i++) + strbuf_addf(&offset_str, "%" PRIu64 " ", obj->offsets[i]); + hex_format(hex, obj->hash_prefix, obj->hash_prefix_len); + printf("prefix %s (len %d), offsets [%s]\n", + hex, obj->hash_prefix_len, offset_str.buf); + strbuf_release(&offset_str); +} + static void reftable_obj_record_copy_from(void *rec, const void *src_rec, int hash_size) { @@ -465,12 +506,14 @@ static void reftable_obj_record_copy_from(void *rec, const void *src_rec, (const struct reftable_obj_record *)src_rec; reftable_obj_record_release(obj); - *obj = *src; - obj->hash_prefix = reftable_malloc(obj->hash_prefix_len); - memcpy(obj->hash_prefix, src->hash_prefix, obj->hash_prefix_len); + obj->hash_prefix = reftable_malloc(src->hash_prefix_len); + obj->hash_prefix_len = src->hash_prefix_len; + if (src->hash_prefix_len) + memcpy(obj->hash_prefix, src->hash_prefix, obj->hash_prefix_len); - obj->offsets = reftable_malloc(obj->offset_len * sizeof(uint64_t)); - COPY_ARRAY(obj->offsets, src->offsets, obj->offset_len); + obj->offsets = reftable_malloc(src->offset_len * sizeof(uint64_t)); + obj->offset_len = src->offset_len; + COPY_ARRAY(obj->offsets, src->offsets, src->offset_len); } static uint8_t reftable_obj_record_val_type(const void *rec) @@ -572,6 +615,25 @@ static int not_a_deletion(const void *p) return 0; } +static int reftable_obj_record_equal_void(const void *a, const void *b, int hash_size) +{ + struct reftable_obj_record *ra = (struct reftable_obj_record *) a; + struct reftable_obj_record *rb = (struct reftable_obj_record *) b; + + if (ra->hash_prefix_len != rb->hash_prefix_len + || ra->offset_len != rb->offset_len) + return 0; + + if (ra->hash_prefix_len && + memcmp(ra->hash_prefix, rb->hash_prefix, ra->hash_prefix_len)) + return 0; + if (ra->offset_len && + memcmp(ra->offsets, rb->offsets, ra->offset_len * sizeof(uint64_t))) + return 0; + + return 1; +} + static struct reftable_record_vtable reftable_obj_record_vtable = { .key = &reftable_obj_record_key, .type = BLOCK_TYPE_OBJ, @@ -580,32 +642,43 @@ static struct reftable_record_vtable reftable_obj_record_vtable = { .encode = &reftable_obj_record_encode, .decode = &reftable_obj_record_decode, .release = &reftable_obj_record_release, - .is_deletion = not_a_deletion, + .is_deletion = ¬_a_deletion, + .equal = &reftable_obj_record_equal_void, + .print = &reftable_obj_record_print, }; -void reftable_log_record_print(struct reftable_log_record *log, - uint32_t hash_id) +static void reftable_log_record_print_sz(struct reftable_log_record *log, + int hash_size) { - char hex[GIT_SHA256_RAWSZ + 1] = { 0 }; + char hex[GIT_MAX_HEXSZ + 1] = { 0 }; switch (log->value_type) { case REFTABLE_LOG_DELETION: - printf("log{%s(%" PRIu64 ") delete", log->refname, + printf("log{%s(%" PRIu64 ") delete\n", log->refname, log->update_index); break; case REFTABLE_LOG_UPDATE: printf("log{%s(%" PRIu64 ") %s <%s> %" PRIu64 " %04d\n", - log->refname, log->update_index, log->value.update.name, - log->value.update.email, log->value.update.time, + log->refname, log->update_index, + log->value.update.name ? log->value.update.name : "", + log->value.update.email ? log->value.update.email : "", + log->value.update.time, log->value.update.tz_offset); - hex_format(hex, log->value.update.old_hash, hash_size(hash_id)); + hex_format(hex, log->value.update.old_hash, hash_size); printf("%s => ", hex); - hex_format(hex, log->value.update.new_hash, hash_size(hash_id)); - printf("%s\n\n%s\n}\n", hex, log->value.update.message); + hex_format(hex, log->value.update.new_hash, hash_size); + printf("%s\n\n%s\n}\n", hex, + log->value.update.message ? log->value.update.message : ""); break; } } +void reftable_log_record_print(struct reftable_log_record *log, + uint32_t hash_id) +{ + reftable_log_record_print_sz(log, hash_size(hash_id)); +} + static void reftable_log_record_key(const void *r, struct strbuf *dest) { const struct reftable_log_record *rec = @@ -881,8 +954,16 @@ static int zero_hash_eq(uint8_t *a, uint8_t *b, int sz) return !memcmp(a, b, sz); } -int reftable_log_record_equal(struct reftable_log_record *a, - struct reftable_log_record *b, int hash_size) +static int reftable_log_record_equal_void(const void *a, + const void *b, int hash_size) +{ + return reftable_log_record_equal((struct reftable_log_record *) a, + (struct reftable_log_record *) b, + hash_size); +} + +int reftable_log_record_equal(const struct reftable_log_record *a, + const struct reftable_log_record *b, int hash_size) { if (!(null_streq(a->refname, b->refname) && a->update_index == b->update_index && @@ -915,6 +996,11 @@ static int reftable_log_record_is_deletion_void(const void *p) (const struct reftable_log_record *)p); } +static void reftable_log_record_print_void(const void *rec, int hash_size) +{ + reftable_log_record_print_sz((struct reftable_log_record*)rec, hash_size); +} + static struct reftable_record_vtable reftable_log_record_vtable = { .key = &reftable_log_record_key, .type = BLOCK_TYPE_LOG, @@ -924,60 +1010,10 @@ static struct reftable_record_vtable reftable_log_record_vtable = { .decode = &reftable_log_record_decode, .release = &reftable_log_record_release_void, .is_deletion = &reftable_log_record_is_deletion_void, + .equal = &reftable_log_record_equal_void, + .print = &reftable_log_record_print_void, }; -struct reftable_record reftable_new_record(uint8_t typ) -{ - struct reftable_record rec = { NULL }; - switch (typ) { - case BLOCK_TYPE_REF: { - struct reftable_ref_record *r = - reftable_calloc(sizeof(struct reftable_ref_record)); - reftable_record_from_ref(&rec, r); - return rec; - } - - case BLOCK_TYPE_OBJ: { - struct reftable_obj_record *r = - reftable_calloc(sizeof(struct reftable_obj_record)); - reftable_record_from_obj(&rec, r); - return rec; - } - case BLOCK_TYPE_LOG: { - struct reftable_log_record *r = - reftable_calloc(sizeof(struct reftable_log_record)); - reftable_record_from_log(&rec, r); - return rec; - } - case BLOCK_TYPE_INDEX: { - struct reftable_index_record empty = { .last_key = - STRBUF_INIT }; - struct reftable_index_record *r = - reftable_calloc(sizeof(struct reftable_index_record)); - *r = empty; - reftable_record_from_index(&rec, r); - return rec; - } - } - abort(); - return rec; -} - -/* clear out the record, yielding the reftable_record data that was - * encapsulated. */ -static void *reftable_record_yield(struct reftable_record *rec) -{ - void *p = rec->data; - rec->data = NULL; - return p; -} - -void reftable_record_destroy(struct reftable_record *rec) -{ - reftable_record_release(rec); - reftable_free(reftable_record_yield(rec)); -} - static void reftable_index_record_key(const void *r, struct strbuf *dest) { const struct reftable_index_record *rec = r; @@ -1042,6 +1078,21 @@ static int reftable_index_record_decode(void *rec, struct strbuf key, return start.len - in.len; } +static int reftable_index_record_equal(const void *a, const void *b, int hash_size) +{ + struct reftable_index_record *ia = (struct reftable_index_record *) a; + struct reftable_index_record *ib = (struct reftable_index_record *) b; + + return ia->offset == ib->offset && !strbuf_cmp(&ia->last_key, &ib->last_key); +} + +static void reftable_index_record_print(const void *rec, int hash_size) +{ + const struct reftable_index_record *idx = rec; + /* TODO: escape null chars? */ + printf("\"%s\" %" PRIu64 "\n", idx->last_key.buf, idx->offset); +} + static struct reftable_record_vtable reftable_index_record_vtable = { .key = &reftable_index_record_key, .type = BLOCK_TYPE_INDEX, @@ -1051,95 +1102,66 @@ static struct reftable_record_vtable reftable_index_record_vtable = { .decode = &reftable_index_record_decode, .release = &reftable_index_record_release, .is_deletion = ¬_a_deletion, + .equal = &reftable_index_record_equal, + .print = &reftable_index_record_print, }; void reftable_record_key(struct reftable_record *rec, struct strbuf *dest) { - rec->ops->key(rec->data, dest); + reftable_record_vtable(rec)->key(reftable_record_data(rec), dest); } uint8_t reftable_record_type(struct reftable_record *rec) { - return rec->ops->type; + return rec->type; } int reftable_record_encode(struct reftable_record *rec, struct string_view dest, int hash_size) { - return rec->ops->encode(rec->data, dest, hash_size); + return reftable_record_vtable(rec)->encode(reftable_record_data(rec), + dest, hash_size); } void reftable_record_copy_from(struct reftable_record *rec, struct reftable_record *src, int hash_size) { - assert(src->ops->type == rec->ops->type); + assert(src->type == rec->type); - rec->ops->copy_from(rec->data, src->data, hash_size); + reftable_record_vtable(rec)->copy_from(reftable_record_data(rec), + reftable_record_data(src), + hash_size); } uint8_t reftable_record_val_type(struct reftable_record *rec) { - return rec->ops->val_type(rec->data); + return reftable_record_vtable(rec)->val_type(reftable_record_data(rec)); } int reftable_record_decode(struct reftable_record *rec, struct strbuf key, uint8_t extra, struct string_view src, int hash_size) { - return rec->ops->decode(rec->data, key, extra, src, hash_size); + return reftable_record_vtable(rec)->decode(reftable_record_data(rec), + key, extra, src, hash_size); } void reftable_record_release(struct reftable_record *rec) { - rec->ops->release(rec->data); + reftable_record_vtable(rec)->release(reftable_record_data(rec)); } int reftable_record_is_deletion(struct reftable_record *rec) { - return rec->ops->is_deletion(rec->data); + return reftable_record_vtable(rec)->is_deletion( + reftable_record_data(rec)); } -void reftable_record_from_ref(struct reftable_record *rec, - struct reftable_ref_record *ref_rec) +int reftable_record_equal(struct reftable_record *a, struct reftable_record *b, int hash_size) { - assert(!rec->ops); - rec->data = ref_rec; - rec->ops = &reftable_ref_record_vtable; -} - -void reftable_record_from_obj(struct reftable_record *rec, - struct reftable_obj_record *obj_rec) -{ - assert(!rec->ops); - rec->data = obj_rec; - rec->ops = &reftable_obj_record_vtable; -} - -void reftable_record_from_index(struct reftable_record *rec, - struct reftable_index_record *index_rec) -{ - assert(!rec->ops); - rec->data = index_rec; - rec->ops = &reftable_index_record_vtable; -} - -void reftable_record_from_log(struct reftable_record *rec, - struct reftable_log_record *log_rec) -{ - assert(!rec->ops); - rec->data = log_rec; - rec->ops = &reftable_log_record_vtable; -} - -struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *rec) -{ - assert(reftable_record_type(rec) == BLOCK_TYPE_REF); - return rec->data; -} - -struct reftable_log_record *reftable_record_as_log(struct reftable_record *rec) -{ - assert(reftable_record_type(rec) == BLOCK_TYPE_LOG); - return rec->data; + if (a->type != b->type) + return 0; + return reftable_record_vtable(a)->equal( + reftable_record_data(a), reftable_record_data(b), hash_size); } static int hash_equal(uint8_t *a, uint8_t *b, int hash_size) @@ -1150,13 +1172,15 @@ static int hash_equal(uint8_t *a, uint8_t *b, int hash_size) return a == b; } -int reftable_ref_record_equal(struct reftable_ref_record *a, - struct reftable_ref_record *b, int hash_size) +int reftable_ref_record_equal(const struct reftable_ref_record *a, + const struct reftable_ref_record *b, int hash_size) { assert(hash_size > 0); - if (!(0 == strcmp(a->refname, b->refname) && - a->update_index == b->update_index && - a->value_type == b->value_type)) + if (!null_streq(a->refname, b->refname)) + return 0; + + if (a->update_index != b->update_index || + a->value_type != b->value_type) return 0; switch (a->value_type) { @@ -1210,3 +1234,81 @@ void string_view_consume(struct string_view *s, int n) s->buf += n; s->len -= n; } + +static void *reftable_record_data(struct reftable_record *rec) +{ + switch (rec->type) { + case BLOCK_TYPE_REF: + return &rec->u.ref; + case BLOCK_TYPE_LOG: + return &rec->u.log; + case BLOCK_TYPE_INDEX: + return &rec->u.idx; + case BLOCK_TYPE_OBJ: + return &rec->u.obj; + } + abort(); +} + +static struct reftable_record_vtable * +reftable_record_vtable(struct reftable_record *rec) +{ + switch (rec->type) { + case BLOCK_TYPE_REF: + return &reftable_ref_record_vtable; + case BLOCK_TYPE_LOG: + return &reftable_log_record_vtable; + case BLOCK_TYPE_INDEX: + return &reftable_index_record_vtable; + case BLOCK_TYPE_OBJ: + return &reftable_obj_record_vtable; + } + abort(); +} + +struct reftable_record reftable_new_record(uint8_t typ) +{ + struct reftable_record clean = { + .type = typ, + }; + + /* the following is involved, but the naive solution (just return + * `clean` as is, except for BLOCK_TYPE_INDEX), returns a garbage + * clean.u.obj.offsets pointer on Windows VS CI. Go figure. + */ + switch (typ) { + case BLOCK_TYPE_OBJ: + { + struct reftable_obj_record obj = { 0 }; + clean.u.obj = obj; + break; + } + case BLOCK_TYPE_INDEX: + { + struct reftable_index_record idx = { + .last_key = STRBUF_INIT, + }; + clean.u.idx = idx; + break; + } + case BLOCK_TYPE_REF: + { + struct reftable_ref_record ref = { 0 }; + clean.u.ref = ref; + break; + } + case BLOCK_TYPE_LOG: + { + struct reftable_log_record log = { 0 }; + clean.u.log = log; + break; + } + } + return clean; +} + +void reftable_record_print(struct reftable_record *rec, int hash_size) +{ + printf("'%c': ", rec->type); + reftable_record_vtable(rec)->print(reftable_record_data(rec), hash_size); +} diff --git a/reftable/record.h b/reftable/record.h index 498e8c50bf..fd80cd451d 100644 --- a/reftable/record.h +++ b/reftable/record.h @@ -58,18 +58,18 @@ struct reftable_record_vtable { /* is this a tombstone? */ int (*is_deletion)(const void *rec); -}; -/* record is a generic wrapper for different types of records. */ -struct reftable_record { - void *data; - struct reftable_record_vtable *ops; + /* Are two records equal? This assumes they have the same type. Returns 0 for non-equal. */ + int (*equal)(const void *a, const void *b, int hash_size); + + /* Print on stdout, for debugging. */ + void (*print)(const void *rec, int hash_size); }; /* returns true for recognized block types. Block start with the block type. */ int reftable_is_block_type(uint8_t typ); -/* creates a malloced record of the given type. Dispose with record_destroy */ +/* return an initialized record for the given type */ struct reftable_record reftable_new_record(uint8_t typ); /* Encode `key` into `dest`. Sets `is_restart` to indicate a restart. Returns @@ -97,8 +97,25 @@ struct reftable_obj_record { int offset_len; }; -/* see struct record_vtable */ +/* record is a generic wrapper for different types of records. It is normally + * created on the stack, or embedded within another struct. If the type is + * known, a fresh instance can be initialized explicitly. Otherwise, use + * reftable_new_record() to initialize generically (as the index_record is not + * valid as 0-initialized structure) + */ +struct reftable_record { + uint8_t type; + union { + struct reftable_ref_record ref; + struct reftable_log_record log; + struct reftable_obj_record obj; + struct reftable_index_record idx; + } u; +}; +/* see struct record_vtable */ +int reftable_record_equal(struct reftable_record *a, struct reftable_record *b, int hash_size); +void reftable_record_print(struct reftable_record *rec, int hash_size); void reftable_record_key(struct reftable_record *rec, struct strbuf *dest); uint8_t reftable_record_type(struct reftable_record *rec); void reftable_record_copy_from(struct reftable_record *rec, @@ -111,25 +128,9 @@ int reftable_record_decode(struct reftable_record *rec, struct strbuf key, int hash_size); int reftable_record_is_deletion(struct reftable_record *rec); -/* zeroes out the embedded record */ +/* frees and zeroes out the embedded record */ void reftable_record_release(struct reftable_record *rec); -/* clear and deallocate embedded record, and zero `rec`. */ -void reftable_record_destroy(struct reftable_record *rec); - -/* initialize generic records from concrete records. The generic record should - * be zeroed out. */ -void reftable_record_from_obj(struct reftable_record *rec, - struct reftable_obj_record *objrec); -void reftable_record_from_index(struct reftable_record *rec, - struct reftable_index_record *idxrec); -void reftable_record_from_ref(struct reftable_record *rec, - struct reftable_ref_record *refrec); -void reftable_record_from_log(struct reftable_record *rec, - struct reftable_log_record *logrec); -struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *ref); -struct reftable_log_record *reftable_record_as_log(struct reftable_record *ref); - /* for qsort. */ int reftable_ref_record_compare_name(const void *a, const void *b); diff --git a/reftable/record_test.c b/reftable/record_test.c index f4ad7cace4..f91ea5e883 100644 --- a/reftable/record_test.c +++ b/reftable/record_test.c @@ -16,24 +16,20 @@ static void test_copy(struct reftable_record *rec) { - struct reftable_record copy = - reftable_new_record(reftable_record_type(rec)); + struct reftable_record copy = { 0 }; + uint8_t typ; + + typ = reftable_record_type(rec); + copy = reftable_new_record(typ); reftable_record_copy_from(©, rec, GIT_SHA1_RAWSZ); /* do it twice to catch memory leaks */ reftable_record_copy_from(©, rec, GIT_SHA1_RAWSZ); - switch (reftable_record_type(©)) { - case BLOCK_TYPE_REF: - EXPECT(reftable_ref_record_equal(reftable_record_as_ref(©), - reftable_record_as_ref(rec), - GIT_SHA1_RAWSZ)); - break; - case BLOCK_TYPE_LOG: - EXPECT(reftable_log_record_equal(reftable_record_as_log(©), - reftable_record_as_log(rec), - GIT_SHA1_RAWSZ)); - break; - } - reftable_record_destroy(©); + EXPECT(reftable_record_equal(rec, ©, GIT_SHA1_RAWSZ)); + + puts("testing print coverage:\n"); + reftable_record_print(©, GIT_SHA1_RAWSZ); + + reftable_record_release(©); } static void test_varint_roundtrip(void) @@ -106,61 +102,58 @@ static void test_reftable_ref_record_roundtrip(void) int i = 0; for (i = REFTABLE_REF_DELETION; i < REFTABLE_NR_REF_VALUETYPES; i++) { - struct reftable_ref_record in = { NULL }; - struct reftable_ref_record out = { NULL }; - struct reftable_record rec_out = { NULL }; + struct reftable_record in = { + .type = BLOCK_TYPE_REF, + }; + struct reftable_record out = { .type = BLOCK_TYPE_REF }; struct strbuf key = STRBUF_INIT; - struct reftable_record rec = { NULL }; uint8_t buffer[1024] = { 0 }; struct string_view dest = { .buf = buffer, .len = sizeof(buffer), }; - int n, m; - in.value_type = i; + in.u.ref.value_type = i; switch (i) { case REFTABLE_REF_DELETION: break; case REFTABLE_REF_VAL1: - in.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ); - set_hash(in.value.val1, 1); + in.u.ref.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ); + set_hash(in.u.ref.value.val1, 1); break; case REFTABLE_REF_VAL2: - in.value.val2.value = reftable_malloc(GIT_SHA1_RAWSZ); - set_hash(in.value.val2.value, 1); - in.value.val2.target_value = + in.u.ref.value.val2.value = reftable_malloc(GIT_SHA1_RAWSZ); - set_hash(in.value.val2.target_value, 2); + set_hash(in.u.ref.value.val2.value, 1); + in.u.ref.value.val2.target_value = + reftable_malloc(GIT_SHA1_RAWSZ); + set_hash(in.u.ref.value.val2.target_value, 2); break; case REFTABLE_REF_SYMREF: - in.value.symref = xstrdup("target"); + in.u.ref.value.symref = xstrdup("target"); break; } - in.refname = xstrdup("refs/heads/master"); + in.u.ref.refname = xstrdup("refs/heads/master"); - reftable_record_from_ref(&rec, &in); - test_copy(&rec); + test_copy(&in); - EXPECT(reftable_record_val_type(&rec) == i); + EXPECT(reftable_record_val_type(&in) == i); - reftable_record_key(&rec, &key); - n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ); + reftable_record_key(&in, &key); + n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ); EXPECT(n > 0); /* decode into a non-zero reftable_record to test for leaks. */ - - reftable_record_from_ref(&rec_out, &out); - m = reftable_record_decode(&rec_out, key, i, dest, - GIT_SHA1_RAWSZ); + m = reftable_record_decode(&out, key, i, dest, GIT_SHA1_RAWSZ); EXPECT(n == m); - EXPECT(reftable_ref_record_equal(&in, &out, GIT_SHA1_RAWSZ)); - reftable_record_release(&rec_out); + EXPECT(reftable_ref_record_equal(&in.u.ref, &out.u.ref, + GIT_SHA1_RAWSZ)); + reftable_record_release(&in); strbuf_release(&key); - reftable_ref_record_release(&in); + reftable_record_release(&out); } } @@ -187,7 +180,8 @@ static void test_reftable_log_record_equal(void) static void test_reftable_log_record_roundtrip(void) { int i; - struct reftable_log_record in[2] = { + + struct reftable_log_record in[] = { { .refname = xstrdup("refs/heads/master"), .update_index = 42, @@ -208,12 +202,26 @@ static void test_reftable_log_record_roundtrip(void) .refname = xstrdup("refs/heads/master"), .update_index = 22, .value_type = REFTABLE_LOG_DELETION, + }, + { + .refname = xstrdup("branch"), + .update_index = 33, + .value_type = REFTABLE_LOG_UPDATE, + .value = { + .update = { + .old_hash = reftable_malloc(GIT_SHA1_RAWSZ), + .new_hash = reftable_malloc(GIT_SHA1_RAWSZ), + /* rest of fields left empty. */ + }, + }, } }; set_test_hash(in[0].value.update.new_hash, 1); set_test_hash(in[0].value.update.old_hash, 2); + set_test_hash(in[2].value.update.new_hash, 3); + set_test_hash(in[2].value.update.old_hash, 4); for (i = 0; i < ARRAY_SIZE(in); i++) { - struct reftable_record rec = { NULL }; + struct reftable_record rec = { .type = BLOCK_TYPE_LOG }; struct strbuf key = STRBUF_INIT; uint8_t buffer[1024] = { 0 }; struct string_view dest = { @@ -221,23 +229,25 @@ static void test_reftable_log_record_roundtrip(void) .len = sizeof(buffer), }; /* populate out, to check for leaks. */ - struct reftable_log_record out = { - .refname = xstrdup("old name"), - .value_type = REFTABLE_LOG_UPDATE, - .value = { - .update = { - .new_hash = reftable_calloc(GIT_SHA1_RAWSZ), - .old_hash = reftable_calloc(GIT_SHA1_RAWSZ), - .name = xstrdup("old name"), - .email = xstrdup("old@email"), - .message = xstrdup("old message"), + struct reftable_record out = { + .type = BLOCK_TYPE_LOG, + .u.log = { + .refname = xstrdup("old name"), + .value_type = REFTABLE_LOG_UPDATE, + .value = { + .update = { + .new_hash = reftable_calloc(GIT_SHA1_RAWSZ), + .old_hash = reftable_calloc(GIT_SHA1_RAWSZ), + .name = xstrdup("old name"), + .email = xstrdup("old@email"), + .message = xstrdup("old message"), + }, }, }, }; - struct reftable_record rec_out = { NULL }; int n, m, valtype; - reftable_record_from_log(&rec, &in[i]); + rec.u.log = in[i]; test_copy(&rec); @@ -245,16 +255,16 @@ static void test_reftable_log_record_roundtrip(void) n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ); EXPECT(n >= 0); - reftable_record_from_log(&rec_out, &out); valtype = reftable_record_val_type(&rec); - m = reftable_record_decode(&rec_out, key, valtype, dest, + m = reftable_record_decode(&out, key, valtype, dest, GIT_SHA1_RAWSZ); EXPECT(n == m); - EXPECT(reftable_log_record_equal(&in[i], &out, GIT_SHA1_RAWSZ)); + EXPECT(reftable_log_record_equal(&in[i], &out.u.log, + GIT_SHA1_RAWSZ)); reftable_log_record_release(&in[i]); strbuf_release(&key); - reftable_record_release(&rec_out); + reftable_record_release(&out); } } @@ -322,47 +332,43 @@ static void test_reftable_obj_record_roundtrip(void) } }; int i = 0; for (i = 0; i < ARRAY_SIZE(recs); i++) { - struct reftable_obj_record in = recs[i]; uint8_t buffer[1024] = { 0 }; struct string_view dest = { .buf = buffer, .len = sizeof(buffer), }; - struct reftable_record rec = { NULL }; + struct reftable_record in = { + .type = BLOCK_TYPE_OBJ, + .u.obj = recs[i], + }; struct strbuf key = STRBUF_INIT; - struct reftable_obj_record out = { NULL }; - struct reftable_record rec_out = { NULL }; + struct reftable_record out = { .type = BLOCK_TYPE_OBJ }; int n, m; uint8_t extra; - reftable_record_from_obj(&rec, &in); - test_copy(&rec); - reftable_record_key(&rec, &key); - n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ); + test_copy(&in); + reftable_record_key(&in, &key); + n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ); EXPECT(n > 0); - extra = reftable_record_val_type(&rec); - reftable_record_from_obj(&rec_out, &out); - m = reftable_record_decode(&rec_out, key, extra, dest, + extra = reftable_record_val_type(&in); + m = reftable_record_decode(&out, key, extra, dest, GIT_SHA1_RAWSZ); EXPECT(n == m); - EXPECT(in.hash_prefix_len == out.hash_prefix_len); - EXPECT(in.offset_len == out.offset_len); - - EXPECT(!memcmp(in.hash_prefix, out.hash_prefix, - in.hash_prefix_len)); - EXPECT(0 == memcmp(in.offsets, out.offsets, - sizeof(uint64_t) * in.offset_len)); + EXPECT(reftable_record_equal(&in, &out, GIT_SHA1_RAWSZ)); strbuf_release(&key); - reftable_record_release(&rec_out); + reftable_record_release(&out); } } static void test_reftable_index_record_roundtrip(void) { - struct reftable_index_record in = { - .offset = 42, - .last_key = STRBUF_INIT, + struct reftable_record in = { + .type = BLOCK_TYPE_INDEX, + .u.idx = { + .offset = 42, + .last_key = STRBUF_INIT, + }, }; uint8_t buffer[1024] = { 0 }; struct string_view dest = { @@ -370,31 +376,30 @@ static void test_reftable_index_record_roundtrip(void) .len = sizeof(buffer), }; struct strbuf key = STRBUF_INIT; - struct reftable_record rec = { NULL }; - struct reftable_index_record out = { .last_key = STRBUF_INIT }; - struct reftable_record out_rec = { NULL }; + struct reftable_record out = { + .type = BLOCK_TYPE_INDEX, + .u.idx = { .last_key = STRBUF_INIT }, + }; int n, m; uint8_t extra; - strbuf_addstr(&in.last_key, "refs/heads/master"); - reftable_record_from_index(&rec, &in); - reftable_record_key(&rec, &key); - test_copy(&rec); + strbuf_addstr(&in.u.idx.last_key, "refs/heads/master"); + reftable_record_key(&in, &key); + test_copy(&in); - EXPECT(0 == strbuf_cmp(&key, &in.last_key)); - n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ); + EXPECT(0 == strbuf_cmp(&key, &in.u.idx.last_key)); + n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ); EXPECT(n > 0); - extra = reftable_record_val_type(&rec); - reftable_record_from_index(&out_rec, &out); - m = reftable_record_decode(&out_rec, key, extra, dest, GIT_SHA1_RAWSZ); + extra = reftable_record_val_type(&in); + m = reftable_record_decode(&out, key, extra, dest, GIT_SHA1_RAWSZ); EXPECT(m == n); - EXPECT(in.offset == out.offset); + EXPECT(reftable_record_equal(&in, &out, GIT_SHA1_RAWSZ)); - reftable_record_release(&out_rec); + reftable_record_release(&out); strbuf_release(&key); - strbuf_release(&in.last_key); + strbuf_release(&in.u.idx.last_key); } int record_test_main(int argc, const char *argv[]) diff --git a/reftable/reftable-record.h b/reftable/reftable-record.h index 5370d2288c..67104f8fbf 100644 --- a/reftable/reftable-record.h +++ b/reftable/reftable-record.h @@ -49,25 +49,25 @@ struct reftable_ref_record { /* Returns the first hash, or NULL if `rec` is not of type * REFTABLE_REF_VAL1 or REFTABLE_REF_VAL2. */ -uint8_t *reftable_ref_record_val1(struct reftable_ref_record *rec); +uint8_t *reftable_ref_record_val1(const struct reftable_ref_record *rec); /* Returns the second hash, or NULL if `rec` is not of type * REFTABLE_REF_VAL2. */ -uint8_t *reftable_ref_record_val2(struct reftable_ref_record *rec); +uint8_t *reftable_ref_record_val2(const struct reftable_ref_record *rec); /* returns whether 'ref' represents a deletion */ int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref); /* prints a reftable_ref_record onto stdout. Useful for debugging. */ -void reftable_ref_record_print(struct reftable_ref_record *ref, +void reftable_ref_record_print(const struct reftable_ref_record *ref, uint32_t hash_id); /* frees and nulls all pointer values inside `ref`. */ void reftable_ref_record_release(struct reftable_ref_record *ref); /* returns whether two reftable_ref_records are the same. Useful for testing. */ -int reftable_ref_record_equal(struct reftable_ref_record *a, - struct reftable_ref_record *b, int hash_size); +int reftable_ref_record_equal(const struct reftable_ref_record *a, + const struct reftable_ref_record *b, int hash_size); /* reftable_log_record holds a reflog entry */ struct reftable_log_record { @@ -104,8 +104,8 @@ int reftable_log_record_is_deletion(const struct reftable_log_record *log); void reftable_log_record_release(struct reftable_log_record *log); /* returns whether two records are equal. Useful for testing. */ -int reftable_log_record_equal(struct reftable_log_record *a, - struct reftable_log_record *b, int hash_size); +int reftable_log_record_equal(const struct reftable_log_record *a, + const struct reftable_log_record *b, int hash_size); /* dumps a reftable_log_record on stdout, for debugging/testing. */ void reftable_log_record_print(struct reftable_log_record *log, diff --git a/reftable/reftable.c b/reftable/reftable.c deleted file mode 100644 index 0e4607a7cd..0000000000 --- a/reftable/reftable.c +++ /dev/null @@ -1,115 +0,0 @@ -/* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ - -#include "basics.h" -#include "record.h" -#include "generic.h" -#include "reftable-iterator.h" -#include "reftable-generic.h" - -int reftable_table_seek_ref(struct reftable_table *tab, - struct reftable_iterator *it, const char *name) -{ - struct reftable_ref_record ref = { - .refname = (char *)name, - }; - struct reftable_record rec = { NULL }; - reftable_record_from_ref(&rec, &ref); - return tab->ops->seek_record(tab->table_arg, it, &rec); -} - -int reftable_table_read_ref(struct reftable_table *tab, const char *name, - struct reftable_ref_record *ref) -{ - struct reftable_iterator it = { NULL }; - int err = reftable_table_seek_ref(tab, &it, name); - if (err) - goto done; - - err = reftable_iterator_next_ref(&it, ref); - if (err) - goto done; - - if (strcmp(ref->refname, name) || - reftable_ref_record_is_deletion(ref)) { - reftable_ref_record_release(ref); - err = 1; - goto done; - } - -done: - reftable_iterator_destroy(&it); - return err; -} - -uint64_t reftable_table_max_update_index(struct reftable_table *tab) -{ - return tab->ops->max_update_index(tab->table_arg); -} - -uint64_t reftable_table_min_update_index(struct reftable_table *tab) -{ - return tab->ops->min_update_index(tab->table_arg); -} - -uint32_t reftable_table_hash_id(struct reftable_table *tab) -{ - return tab->ops->hash_id(tab->table_arg); -} - -void reftable_iterator_destroy(struct reftable_iterator *it) -{ - if (!it->ops) { - return; - } - it->ops->close(it->iter_arg); - it->ops = NULL; - FREE_AND_NULL(it->iter_arg); -} - -int reftable_iterator_next_ref(struct reftable_iterator *it, - struct reftable_ref_record *ref) -{ - struct reftable_record rec = { NULL }; - reftable_record_from_ref(&rec, ref); - return iterator_next(it, &rec); -} - -int reftable_iterator_next_log(struct reftable_iterator *it, - struct reftable_log_record *log) -{ - struct reftable_record rec = { NULL }; - reftable_record_from_log(&rec, log); - return iterator_next(it, &rec); -} - -int iterator_next(struct reftable_iterator *it, struct reftable_record *rec) -{ - return it->ops->next(it->iter_arg, rec); -} - -static int empty_iterator_next(void *arg, struct reftable_record *rec) -{ - return 1; -} - -static void empty_iterator_close(void *arg) -{ -} - -static struct reftable_iterator_vtable empty_vtable = { - .next = &empty_iterator_next, - .close = &empty_iterator_close, -}; - -void iterator_set_empty(struct reftable_iterator *it) -{ - assert(!it->ops); - it->iter_arg = NULL; - it->ops = &empty_vtable; -} diff --git a/reftable/stack.c b/reftable/stack.c index 56bf5f2d84..ddbdf1b9c8 100644 --- a/reftable/stack.c +++ b/reftable/stack.c @@ -889,7 +889,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last, struct strbuf new_table_path = STRBUF_INIT; int err = 0; int have_lock = 0; - int lock_file_fd = 0; + int lock_file_fd = -1; int compact_count = last - first + 1; char **listp = NULL; char **delete_on_success = @@ -923,7 +923,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last, } /* Don't want to write to the lock for now. */ close(lock_file_fd); - lock_file_fd = 0; + lock_file_fd = -1; have_lock = 1; err = stack_uptodate(st); @@ -1031,7 +1031,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last, goto done; } err = close(lock_file_fd); - lock_file_fd = 0; + lock_file_fd = -1; if (err < 0) { err = REFTABLE_IO_ERROR; unlink(new_table_path.buf); @@ -1068,9 +1068,9 @@ done: listp++; } free_names(subtable_locks); - if (lock_file_fd > 0) { + if (lock_file_fd >= 0) { close(lock_file_fd); - lock_file_fd = 0; + lock_file_fd = -1; } if (have_lock) { unlink(lock_file_name.buf); diff --git a/reftable/stack_test.c b/reftable/stack_test.c index f4c743db80..19fe4e2008 100644 --- a/reftable/stack_test.c +++ b/reftable/stack_test.c @@ -90,7 +90,7 @@ static void test_read_file(void) EXPECT(0 == strcmp(want[i], names[i])); } free_names(names); - remove(fn); + (void) remove(fn); } static void test_parse_names(void) @@ -839,6 +839,7 @@ static void test_reftable_stack_auto_compaction(void) EXPECT_ERR(err); err = reftable_stack_auto_compact(st); + EXPECT_ERR(err); EXPECT(i < 3 || st->merged->stack_len < 2 * fastlog2(i)); } diff --git a/reftable/system.h b/reftable/system.h index 4907306c0c..18f9207dfe 100644 --- a/reftable/system.h +++ b/reftable/system.h @@ -16,17 +16,6 @@ https://developers.google.com/open-source/licenses/bsd #include "hash.h" /* hash ID, sizes.*/ #include "dir.h" /* remove_dir_recursively, for tests.*/ -#include <zlib.h> - -#ifdef NO_UNCOMPRESS2 -/* - * This is uncompress2, which is only available in zlib >= 1.2.9 - * (released as of early 2017) - */ -int uncompress2(Bytef *dest, uLongf *destLen, const Bytef *source, - uLong *sourceLen); -#endif - int hash_size(uint32_t id); #endif diff --git a/reftable/writer.c b/reftable/writer.c index 35c8649c9b..944c2329ab 100644 --- a/reftable/writer.c +++ b/reftable/writer.c @@ -150,6 +150,8 @@ void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min, void reftable_writer_free(struct reftable_writer *w) { + if (!w) + return; reftable_free(w->block); reftable_free(w); } @@ -254,8 +256,10 @@ done: int reftable_writer_add_ref(struct reftable_writer *w, struct reftable_ref_record *ref) { - struct reftable_record rec = { NULL }; - struct reftable_ref_record copy = *ref; + struct reftable_record rec = { + .type = BLOCK_TYPE_REF, + .u.ref = *ref, + }; int err = 0; if (ref->refname == NULL) @@ -264,8 +268,7 @@ int reftable_writer_add_ref(struct reftable_writer *w, ref->update_index > w->max_update_index) return REFTABLE_API_ERROR; - reftable_record_from_ref(&rec, ©); - copy.update_index -= w->min_update_index; + rec.u.ref.update_index -= w->min_update_index; err = writer_add_record(w, &rec); if (err < 0) @@ -304,7 +307,10 @@ int reftable_writer_add_refs(struct reftable_writer *w, static int reftable_writer_add_log_verbatim(struct reftable_writer *w, struct reftable_log_record *log) { - struct reftable_record rec = { NULL }; + struct reftable_record rec = { + .type = BLOCK_TYPE_LOG, + .u.log = *log, + }; if (w->block_writer && block_writer_type(w->block_writer) == BLOCK_TYPE_REF) { int err = writer_finish_public_section(w); @@ -314,8 +320,6 @@ static int reftable_writer_add_log_verbatim(struct reftable_writer *w, w->next -= w->pending_padding; w->pending_padding = 0; - - reftable_record_from_log(&rec, log); return writer_add_record(w, &rec); } @@ -396,8 +400,10 @@ static int writer_finish_section(struct reftable_writer *w) w->index_len = 0; w->index_cap = 0; for (i = 0; i < idx_len; i++) { - struct reftable_record rec = { NULL }; - reftable_record_from_index(&rec, idx + i); + struct reftable_record rec = { + .type = BLOCK_TYPE_INDEX, + .u.idx = idx[i], + }; if (block_writer_add(w->block_writer, &rec) == 0) { continue; } @@ -465,17 +471,17 @@ static void write_object_record(void *void_arg, void *key) { struct write_record_arg *arg = void_arg; struct obj_index_tree_node *entry = key; - struct reftable_obj_record obj_rec = { - .hash_prefix = (uint8_t *)entry->hash.buf, - .hash_prefix_len = arg->w->stats.object_id_len, - .offsets = entry->offsets, - .offset_len = entry->offset_len, - }; - struct reftable_record rec = { NULL }; + struct reftable_record + rec = { .type = BLOCK_TYPE_OBJ, + .u.obj = { + .hash_prefix = (uint8_t *)entry->hash.buf, + .hash_prefix_len = arg->w->stats.object_id_len, + .offsets = entry->offsets, + .offset_len = entry->offset_len, + } }; if (arg->err < 0) goto done; - reftable_record_from_obj(&rec, &obj_rec); arg->err = block_writer_add(arg->w->block_writer, &rec); if (arg->err == 0) goto done; @@ -488,7 +494,8 @@ static void write_object_record(void *void_arg, void *key) arg->err = block_writer_add(arg->w->block_writer, &rec); if (arg->err == 0) goto done; - obj_rec.offset_len = 0; + + rec.u.obj.offset_len = 0; arg->err = block_writer_add(arg->w->block_writer, &rec); /* Should be able to write into a fresh block. */ @@ -508,9 +508,8 @@ static void read_config(struct repository *repo) repo->remote_state->current_branch = NULL; if (startup_info->have_repository) { - int ignore_errno; const char *head_ref = refs_resolve_ref_unsafe( - get_main_ref_store(repo), "HEAD", 0, NULL, &flag, &ignore_errno); + get_main_ref_store(repo), "HEAD", 0, NULL, &flag); if (head_ref && (flag & REF_ISSYMREF) && skip_prefix(head_ref, "refs/heads/", &head_ref)) { repo->remote_state->current_branch = make_branch( diff --git a/repo-settings.c b/repo-settings.c index 00ca5571a1..b4fbd16cdc 100644 --- a/repo-settings.c +++ b/repo-settings.c @@ -26,7 +26,7 @@ void prepare_repo_settings(struct repository *r) /* Defaults */ r->settings.index_version = -1; r->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP; - r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_DEFAULT; + r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_CONSECUTIVE; /* Booleans config or default, cascades to other settings */ repo_cfg_bool(r, "feature.manyfiles", &manyfiles, 0); @@ -81,10 +81,17 @@ void prepare_repo_settings(struct repository *r) } if (!repo_config_get_string(r, "fetch.negotiationalgorithm", &strval)) { + int fetch_default = r->settings.fetch_negotiation_algorithm; if (!strcasecmp(strval, "skipping")) r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_SKIPPING; else if (!strcasecmp(strval, "noop")) r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_NOOP; + else if (!strcasecmp(strval, "consecutive")) + r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_CONSECUTIVE; + else if (!strcasecmp(strval, "default")) + r->settings.fetch_negotiation_algorithm = fetch_default; + else + die("unknown fetch negotiation algorithm '%s'", strval); } /* diff --git a/repository.h b/repository.h index 2b5cf97f31..ca837cb9e9 100644 --- a/repository.h +++ b/repository.h @@ -20,7 +20,7 @@ enum untracked_cache_setting { }; enum fetch_negotiation_setting { - FETCH_NEGOTIATION_DEFAULT, + FETCH_NEGOTIATION_CONSECUTIVE, FETCH_NEGOTIATION_SKIPPING, FETCH_NEGOTIATION_NOOP, }; @@ -609,19 +609,20 @@ static int try_merge(struct index_state *istate, const struct rerere_id *id, const char *path, mmfile_t *cur, mmbuffer_t *result) { - int ret; + enum ll_merge_result ret; mmfile_t base = {NULL, 0}, other = {NULL, 0}; if (read_mmfile(&base, rerere_path(id, "preimage")) || - read_mmfile(&other, rerere_path(id, "postimage"))) - ret = 1; - else + read_mmfile(&other, rerere_path(id, "postimage"))) { + ret = LL_MERGE_CONFLICT; + } else { /* * A three-way merge. Note that this honors user-customizable * low-level merge driver settings. */ ret = ll_merge(result, path, &base, NULL, cur, "", &other, "", istate, NULL); + } free(base.ptr); free(other.ptr); @@ -7,38 +7,108 @@ #include "tree-walk.h" #include "tree.h" #include "unpack-trees.h" +#include "hook.h" -int reset_head(struct repository *r, struct object_id *oid, const char *action, - const char *switch_to_branch, unsigned flags, - const char *reflog_orig_head, const char *reflog_head, - const char *default_reflog_action) +static int update_refs(const struct reset_head_opts *opts, + const struct object_id *oid, + const struct object_id *head) { - unsigned detach_head = flags & RESET_HEAD_DETACH; - unsigned reset_hard = flags & RESET_HEAD_HARD; - unsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK; - unsigned refs_only = flags & RESET_HEAD_REFS_ONLY; - unsigned update_orig_head = flags & RESET_ORIG_HEAD; - struct object_id head_oid; + unsigned detach_head = opts->flags & RESET_HEAD_DETACH; + unsigned run_hook = opts->flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK; + unsigned update_orig_head = opts->flags & RESET_ORIG_HEAD; + const struct object_id *orig_head = opts->orig_head; + const char *switch_to_branch = opts->branch; + const char *reflog_branch = opts->branch_msg; + const char *reflog_head = opts->head_msg; + const char *reflog_orig_head = opts->orig_head_msg; + const char *default_reflog_action = opts->default_reflog_action; + struct object_id *old_orig = NULL, oid_old_orig; + struct strbuf msg = STRBUF_INIT; + const char *reflog_action; + size_t prefix_len; + int ret; + + if ((update_orig_head && !reflog_orig_head) || !reflog_head) { + if (!default_reflog_action) + BUG("default_reflog_action must be given when reflog messages are omitted"); + reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT); + strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : + default_reflog_action); + } + prefix_len = msg.len; + + if (update_orig_head) { + if (!get_oid("ORIG_HEAD", &oid_old_orig)) + old_orig = &oid_old_orig; + if (head) { + if (!reflog_orig_head) { + strbuf_addstr(&msg, "updating ORIG_HEAD"); + reflog_orig_head = msg.buf; + } + update_ref(reflog_orig_head, "ORIG_HEAD", + orig_head ? orig_head : head, + old_orig, 0, UPDATE_REFS_MSG_ON_ERR); + } else if (old_orig) + delete_ref(NULL, "ORIG_HEAD", old_orig, 0); + } + + if (!reflog_head) { + strbuf_setlen(&msg, prefix_len); + strbuf_addstr(&msg, "updating HEAD"); + reflog_head = msg.buf; + } + if (!switch_to_branch) + ret = update_ref(reflog_head, "HEAD", oid, head, + detach_head ? REF_NO_DEREF : 0, + UPDATE_REFS_MSG_ON_ERR); + else { + ret = update_ref(reflog_branch ? reflog_branch : reflog_head, + switch_to_branch, oid, NULL, 0, + UPDATE_REFS_MSG_ON_ERR); + if (!ret) + ret = create_symref("HEAD", switch_to_branch, + reflog_head); + } + if (!ret && run_hook) + run_hooks_l("post-checkout", + oid_to_hex(head ? head : null_oid()), + oid_to_hex(oid), "1", NULL); + strbuf_release(&msg); + return ret; +} + +int reset_head(struct repository *r, const struct reset_head_opts *opts) +{ + const struct object_id *oid = opts->oid; + const char *switch_to_branch = opts->branch; + unsigned reset_hard = opts->flags & RESET_HEAD_HARD; + unsigned refs_only = opts->flags & RESET_HEAD_REFS_ONLY; + unsigned update_orig_head = opts->flags & RESET_ORIG_HEAD; + struct object_id *head = NULL, head_oid; struct tree_desc desc[2] = { { NULL }, { NULL } }; struct lock_file lock = LOCK_INIT; struct unpack_trees_options unpack_tree_opts = { 0 }; struct tree *tree; - const char *reflog_action; - struct strbuf msg = STRBUF_INIT; - size_t prefix_len; - struct object_id *orig = NULL, oid_orig, - *old_orig = NULL, oid_old_orig; + const char *action; int ret = 0, nr = 0; if (switch_to_branch && !starts_with(switch_to_branch, "refs/")) BUG("Not a fully qualified branch: '%s'", switch_to_branch); + if (opts->orig_head_msg && !update_orig_head) + BUG("ORIG_HEAD reflog message given without updating ORIG_HEAD"); + + if (opts->branch_msg && !opts->branch) + BUG("branch reflog message given without a branch"); + if (!refs_only && repo_hold_locked_index(r, &lock, LOCK_REPORT_ON_ERROR) < 0) { ret = -1; goto leave_reset_head; } - if ((!oid || !reset_hard) && get_oid("HEAD", &head_oid)) { + if (!get_oid("HEAD", &head_oid)) { + head = &head_oid; + } else if (!oid || !reset_hard) { ret = error(_("could not determine HEAD revision")); goto leave_reset_head; } @@ -47,8 +117,9 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action, oid = &head_oid; if (refs_only) - goto reset_head_refs; + return update_refs(opts, oid, head); + action = reset_hard ? "reset" : "checkout"; setup_unpack_trees_porcelain(&unpack_tree_opts, action); unpack_tree_opts.head_idx = 1; unpack_tree_opts.src_index = r->index; @@ -58,7 +129,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action, unpack_tree_opts.merge = 1; unpack_tree_opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */ init_checkout_metadata(&unpack_tree_opts.meta, switch_to_branch, oid, NULL); - if (!detach_head) + if (reset_hard) unpack_tree_opts.reset = UNPACK_RESET_PROTECT_UNTRACKED; if (repo_read_index_unmerged(r) < 0) { @@ -90,49 +161,10 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action, goto leave_reset_head; } -reset_head_refs: - reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT); - strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : default_reflog_action); - prefix_len = msg.len; - - if (update_orig_head) { - if (!get_oid("ORIG_HEAD", &oid_old_orig)) - old_orig = &oid_old_orig; - if (!get_oid("HEAD", &oid_orig)) { - orig = &oid_orig; - if (!reflog_orig_head) { - strbuf_addstr(&msg, "updating ORIG_HEAD"); - reflog_orig_head = msg.buf; - } - update_ref(reflog_orig_head, "ORIG_HEAD", orig, - old_orig, 0, UPDATE_REFS_MSG_ON_ERR); - } else if (old_orig) - delete_ref(NULL, "ORIG_HEAD", old_orig, 0); - } - - if (!reflog_head) { - strbuf_setlen(&msg, prefix_len); - strbuf_addstr(&msg, "updating HEAD"); - reflog_head = msg.buf; - } - if (!switch_to_branch) - ret = update_ref(reflog_head, "HEAD", oid, orig, - detach_head ? REF_NO_DEREF : 0, - UPDATE_REFS_MSG_ON_ERR); - else { - ret = update_ref(reflog_head, switch_to_branch, oid, - NULL, 0, UPDATE_REFS_MSG_ON_ERR); - if (!ret) - ret = create_symref("HEAD", switch_to_branch, - reflog_head); - } - if (run_hook) - run_hook_le(NULL, "post-checkout", - oid_to_hex(orig ? orig : null_oid()), - oid_to_hex(oid), "1", NULL); + if (oid != &head_oid || update_orig_head || switch_to_branch) + ret = update_refs(opts, oid, head); leave_reset_head: - strbuf_release(&msg); rollback_lock_file(&lock); clear_unpack_trees_porcelain(&unpack_tree_opts); while (nr) @@ -6,15 +6,55 @@ #define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION" +/* Request a detached checkout */ #define RESET_HEAD_DETACH (1<<0) +/* Request a reset rather than a checkout */ #define RESET_HEAD_HARD (1<<1) +/* Run the post-checkout hook */ #define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2) +/* Only update refs, do not touch the worktree */ #define RESET_HEAD_REFS_ONLY (1<<3) +/* Update ORIG_HEAD as well as HEAD */ #define RESET_ORIG_HEAD (1<<4) -int reset_head(struct repository *r, struct object_id *oid, const char *action, - const char *switch_to_branch, unsigned flags, - const char *reflog_orig_head, const char *reflog_head, - const char *default_reflog_action); +struct reset_head_opts { + /* + * The commit to checkout/reset to. Defaults to HEAD. + */ + const struct object_id *oid; + /* + * Optional value to set ORIG_HEAD. Defaults to HEAD. + */ + const struct object_id *orig_head; + /* + * Optional branch to switch to. + */ + const char *branch; + /* + * Flags defined above. + */ + unsigned flags; + /* + * Optional reflog message for branch, defaults to head_msg. + */ + const char *branch_msg; + /* + * Optional reflog message for HEAD, if this omitted but oid or branch + * are given then default_reflog_action must be given. + */ + const char *head_msg; + /* + * Optional reflog message for ORIG_HEAD, if this omitted and flags + * contains RESET_ORIG_HEAD then default_reflog_action must be given. + */ + const char *orig_head_msg; + /* + * Action to use in default reflog messages, only required if a ref is + * being updated and the reflog messages above are omitted. + */ + const char *default_reflog_action; +}; + +int reset_head(struct repository *r, const struct reset_head_opts *opts); #endif diff --git a/revision.c b/revision.c index ad4286fbdd..d8d326d6b0 100644 --- a/revision.c +++ b/revision.c @@ -273,7 +273,7 @@ static void commit_stack_clear(struct commit_stack *stack) stack->nr = stack->alloc = 0; } -static void mark_one_parent_uninteresting(struct commit *commit, +static void mark_one_parent_uninteresting(struct rev_info *revs, struct commit *commit, struct commit_stack *pending) { struct commit_list *l; @@ -290,20 +290,26 @@ static void mark_one_parent_uninteresting(struct commit *commit, * wasn't uninteresting), in which case we need * to mark its parents recursively too.. */ - for (l = commit->parents; l; l = l->next) + for (l = commit->parents; l; l = l->next) { commit_stack_push(pending, l->item); + if (revs && revs->exclude_first_parent_only) + break; + } } -void mark_parents_uninteresting(struct commit *commit) +void mark_parents_uninteresting(struct rev_info *revs, struct commit *commit) { struct commit_stack pending = COMMIT_STACK_INIT; struct commit_list *l; - for (l = commit->parents; l; l = l->next) - mark_one_parent_uninteresting(l->item, &pending); + for (l = commit->parents; l; l = l->next) { + mark_one_parent_uninteresting(revs, l->item, &pending); + if (revs && revs->exclude_first_parent_only) + break; + } while (pending.nr > 0) - mark_one_parent_uninteresting(commit_stack_pop(&pending), + mark_one_parent_uninteresting(revs, commit_stack_pop(&pending), &pending); commit_stack_clear(&pending); @@ -441,7 +447,7 @@ static struct commit *handle_commit(struct rev_info *revs, if (repo_parse_commit(revs->repo, commit) < 0) die("unable to parse commit %s", name); if (flags & UNINTERESTING) { - mark_parents_uninteresting(commit); + mark_parents_uninteresting(revs, commit); if (!revs->topo_order || !generation_numbers_enabled(the_repository)) revs->limited = 1; @@ -1124,7 +1130,7 @@ static int process_parents(struct rev_info *revs, struct commit *commit, if (repo_parse_commit_gently(revs->repo, p, 1) < 0) continue; if (p->parents) - mark_parents_uninteresting(p); + mark_parents_uninteresting(revs, p); if (p->object.flags & SEEN) continue; p->object.flags |= (SEEN | NOT_USER_GIVEN); @@ -1132,6 +1138,8 @@ static int process_parents(struct rev_info *revs, struct commit *commit, commit_list_insert_by_date(p, list); if (queue) prio_queue_put(queue, p); + if (revs->exclude_first_parent_only) + break; } return 0; } @@ -1422,7 +1430,7 @@ static int limit_list(struct rev_info *revs) if (process_parents(revs, commit, &original_list, NULL) < 0) return -1; if (obj->flags & UNINTERESTING) { - mark_parents_uninteresting(commit); + mark_parents_uninteresting(revs, commit); slop = still_interesting(original_list, date, slop, &interesting_cache); if (slop) continue; @@ -2223,6 +2231,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg return argcount; } else if (!strcmp(arg, "--first-parent")) { revs->first_parent_only = 1; + } else if (!strcmp(arg, "--exclude-first-parent-only")) { + revs->exclude_first_parent_only = 1; } else if (!strcmp(arg, "--ancestry-path")) { revs->ancestry_path = 1; revs->simplify_history = 0; @@ -3345,7 +3355,7 @@ static void explore_walk_step(struct rev_info *revs) return; if (c->object.flags & UNINTERESTING) - mark_parents_uninteresting(c); + mark_parents_uninteresting(revs, c); for (p = c->parents; p; p = p->next) test_flag_and_insert(&info->explore_queue, p->item, TOPO_WALK_EXPLORED); diff --git a/revision.h b/revision.h index 3f66147bfd..e16d06f29d 100644 --- a/revision.h +++ b/revision.h @@ -158,6 +158,7 @@ struct rev_info { bisect:1, ancestry_path:1, first_parent_only:1, + exclude_first_parent_only:1, line_level_traverse:1, tree_blobs_in_commit_order:1, @@ -195,7 +196,8 @@ struct rev_info { combine_merges:1, combined_all_paths:1, dense_combined_merges:1, - first_parent_merges:1; + first_parent_merges:1, + remerge_diff:1; /* Format info */ int show_notes; @@ -315,6 +317,9 @@ struct rev_info { /* misc. flags related to '--no-kept-objects' */ unsigned keep_pack_cache_flags; + + /* Location where temporary objects for remerge-diff are written. */ + struct tmp_objdir *remerge_objdir; }; int ref_excluded(struct string_list *, const char *path); @@ -398,7 +403,7 @@ const char *get_revision_mark(const struct rev_info *revs, void put_revision_mark(const struct rev_info *revs, const struct commit *commit); -void mark_parents_uninteresting(struct commit *commit); +void mark_parents_uninteresting(struct rev_info *revs, struct commit *commit); void mark_tree_uninteresting(struct repository *r, struct tree *tree); void mark_trees_uninteresting_sparse(struct repository *r, struct oidset *trees); diff --git a/run-command.c b/run-command.c index 69dde42f1e..a8501e38ce 100644 --- a/run-command.c +++ b/run-command.c @@ -1307,39 +1307,6 @@ int async_with_fork(void) #endif } -int run_hook_ve(const char *const *env, const char *name, va_list args) -{ - struct child_process hook = CHILD_PROCESS_INIT; - const char *p; - - p = find_hook(name); - if (!p) - return 0; - - strvec_push(&hook.args, p); - while ((p = va_arg(args, const char *))) - strvec_push(&hook.args, p); - if (env) - strvec_pushv(&hook.env_array, (const char **)env); - hook.no_stdin = 1; - hook.stdout_to_stderr = 1; - hook.trace2_hook_name = name; - - return run_command(&hook); -} - -int run_hook_le(const char *const *env, const char *name, ...) -{ - va_list args; - int ret; - - va_start(args, name); - ret = run_hook_ve(env, name, args); - va_end(args); - - return ret; -} - struct io_pump { /* initialized by caller */ int fd; diff --git a/run-command.h b/run-command.h index 2be5f5d642..07bed6c31b 100644 --- a/run-command.h +++ b/run-command.h @@ -220,23 +220,6 @@ int finish_command_in_signal(struct child_process *); */ int run_command(struct child_process *); -/** - * Run a hook. - * The first argument is a pathname to an index file, or NULL - * if the hook uses the default index file or no index is needed. - * The second argument is the name of the hook. - * The further arguments correspond to the hook arguments. - * The last argument has to be NULL to terminate the arguments list. - * If the hook does not exist or is not executable, the return - * value will be zero. - * If it is executable, the hook will be executed and the exit - * status of the hook is returned. - * On execution, .stdout_to_stderr and .no_stdin will be set. - */ -LAST_ARG_MUST_BE_NULL -int run_hook_le(const char *const *env, const char *name, ...); -int run_hook_ve(const char *const *env, const char *name, va_list args); - /* * Trigger an auto-gc */ diff --git a/sequencer.c b/sequencer.c index 5213d16e97..9b697158c5 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1281,7 +1281,6 @@ void print_commit_summary(struct repository *r, struct strbuf author_ident = STRBUF_INIT; struct strbuf committer_ident = STRBUF_INIT; struct ref_store *refs; - int resolve_errno; commit = lookup_commit(r, oid); if (!commit) @@ -1332,12 +1331,9 @@ void print_commit_summary(struct repository *r, diff_setup_done(&rev.diffopt); refs = get_main_ref_store(the_repository); - head = refs_resolve_ref_unsafe(refs, "HEAD", 0, NULL, NULL, - &resolve_errno); - if (!head) { - errno = resolve_errno; - die_errno(_("unable to resolve HEAD after creating commit")); - } + head = refs_resolve_ref_unsafe(refs, "HEAD", 0, NULL, NULL); + if (!head) + die(_("unable to resolve HEAD after creating commit")); if (!strcmp(head, "HEAD")) head = _("detached HEAD"); else @@ -3588,7 +3584,7 @@ static int do_label(struct repository *r, const char *name, int len) strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name); strbuf_addf(&msg, "rebase (label) '%.*s'", len, name); - transaction = ref_store_transaction_begin(refs, &err); + transaction = ref_store_transaction_begin(refs, 0, &err); if (!transaction) { error("%s", err.buf); ret = -1; @@ -4089,8 +4085,7 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset) return -1; } -void create_autostash(struct repository *r, const char *path, - const char *default_reflog_action) +void create_autostash(struct repository *r, const char *path) { struct strbuf buf = STRBUF_INIT; struct lock_file lock_file = LOCK_INIT; @@ -4105,6 +4100,7 @@ void create_autostash(struct repository *r, const char *path, if (has_unstaged_changes(r, 1) || has_uncommitted_changes(r, 1)) { struct child_process stash = CHILD_PROCESS_INIT; + struct reset_head_opts ropts = { .flags = RESET_HEAD_HARD }; struct object_id oid; strvec_pushl(&stash.args, @@ -4126,11 +4122,8 @@ void create_autostash(struct repository *r, const char *path, path); write_file(path, "%s", oid_to_hex(&oid)); printf(_("Created autostash: %s\n"), buf.buf); - if (reset_head(r, NULL, "reset --hard", - NULL, RESET_HEAD_HARD, NULL, NULL, - default_reflog_action) < 0) + if (reset_head(r, &ropts) < 0) die(_("could not reset --hard")); - if (discard_index(r->index) < 0 || repo_read_index(r) < 0) die(_("could not read index")); @@ -4215,47 +4208,26 @@ int apply_autostash_oid(const char *stash_oid) return apply_save_autostash_oid(stash_oid, 1); } -static int run_git_checkout(struct repository *r, struct replay_opts *opts, - const char *commit, const char *action) -{ - struct child_process cmd = CHILD_PROCESS_INIT; - int ret; - - cmd.git_cmd = 1; - - if (startup_info->original_cwd) { - cmd.dir = startup_info->original_cwd; - strvec_pushf(&cmd.env_array, "%s=%s", - GIT_WORK_TREE_ENVIRONMENT, r->worktree); - } - strvec_push(&cmd.args, "checkout"); - strvec_push(&cmd.args, commit); - strvec_pushf(&cmd.env_array, GIT_REFLOG_ACTION "=%s", action); - - if (opts->verbose) - ret = run_command(&cmd); - else - ret = run_command_silent_on_success(&cmd); - - if (!ret) - discard_index(r->index); - - return ret; -} - static int checkout_onto(struct repository *r, struct replay_opts *opts, const char *onto_name, const struct object_id *onto, const struct object_id *orig_head) { - const char *action = reflog_message(opts, "start", "checkout %s", onto_name); - - if (run_git_checkout(r, opts, oid_to_hex(onto), action)) { + struct reset_head_opts ropts = { + .oid = onto, + .orig_head = orig_head, + .flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD | + RESET_HEAD_RUN_POST_CHECKOUT_HOOK, + .head_msg = reflog_message(opts, "start", "checkout %s", + onto_name), + .default_reflog_action = "rebase" + }; + if (reset_head(r, &ropts)) { apply_autostash(rebase_path_autostash()); sequencer_remove_state(opts); return error(_("could not detach HEAD")); } - return update_ref(NULL, "ORIG_HEAD", orig_head, NULL, 0, UPDATE_REFS_MSG_ON_ERR); + return 0; } static int stopped_at_head(struct repository *r) diff --git a/sequencer.h b/sequencer.h index 05a7d2ba6b..da64473636 100644 --- a/sequencer.h +++ b/sequencer.h @@ -197,8 +197,7 @@ void commit_post_rewrite(struct repository *r, const struct commit *current_head, const struct object_id *new_head); -void create_autostash(struct repository *r, const char *path, - const char *default_reflog_action); +void create_autostash(struct repository *r, const char *path); int save_autostash(const char *path); int apply_autostash(const char *path); int apply_autostash_oid(const char *stash_oid); @@ -603,7 +603,7 @@ static int mark_uninteresting(const char *refname, const struct object_id *oid, if (!commit) return 0; commit->object.flags |= UNINTERESTING; - mark_parents_uninteresting(commit); + mark_parents_uninteresting(NULL, commit); return 0; } diff --git a/sparse-index.c b/sparse-index.c index a1d505d50e..08f54747bb 100644 --- a/sparse-index.c +++ b/sparse-index.c @@ -136,7 +136,7 @@ static int is_sparse_index_allowed(struct index_state *istate, int flags) /* * The sparse index is not (yet) integrated with a split index. */ - if (istate->split_index) + if (istate->split_index || git_env_bool("GIT_TEST_SPLIT_INDEX", 0)) return 0; /* * The GIT_TEST_SPARSE_INDEX environment variable triggers the diff --git a/split-index.c b/split-index.c index 8e52e891c3..9d0ccc30d0 100644 --- a/split-index.c +++ b/split-index.c @@ -5,6 +5,9 @@ struct split_index *init_split_index(struct index_state *istate) { if (!istate->split_index) { + if (istate->sparse_index) + die(_("cannot use split index with a sparse index")); + CALLOC_ARRAY(istate->split_index, 1); istate->split_index->refcount = 1; } diff --git a/stable-qsort.c b/stable-qsort.c index 6cbaf39f7b..7ff12467cd 100644 --- a/stable-qsort.c +++ b/stable-qsort.c @@ -48,15 +48,9 @@ void git_stable_qsort(void *b, size_t n, size_t s, int (*cmp)(const void *, const void *)) { const size_t size = st_mult(n, s); - char buf[1024]; - - if (size < sizeof(buf)) { - /* The temporary array fits on the small on-stack buffer. */ - msort_with_tmp(b, n, s, cmp, buf); - } else { - /* It's somewhat large, so malloc it. */ - char *tmp = xmalloc(size); - msort_with_tmp(b, n, s, cmp, tmp); - free(tmp); - } + char *tmp; + + tmp = xmalloc(size); + msort_with_tmp(b, n, s, cmp, tmp); + free(tmp); } diff --git a/submodule-config.c b/submodule-config.c index f95344028b..c9f54bc72d 100644 --- a/submodule-config.c +++ b/submodule-config.c @@ -7,6 +7,7 @@ #include "strbuf.h" #include "object-store.h" #include "parse-options.h" +#include "tree-walk.h" /* * submodule cache lookup structure @@ -726,6 +727,66 @@ const struct submodule *submodule_from_path(struct repository *r, return config_from(r->submodule_cache, treeish_name, path, lookup_path); } +/** + * Used internally by submodules_of_tree(). Recurses into 'treeish_name' + * and appends submodule entries to 'out'. The submodule_cache expects + * a root-level treeish_name and paths, so keep track of these values + * with 'root_tree' and 'prefix'. + */ +static void traverse_tree_submodules(struct repository *r, + const struct object_id *root_tree, + char *prefix, + const struct object_id *treeish_name, + struct submodule_entry_list *out) +{ + struct tree_desc tree; + struct submodule_tree_entry *st_entry; + struct name_entry *name_entry; + char *tree_path = NULL; + + name_entry = xmalloc(sizeof(*name_entry)); + + fill_tree_descriptor(r, &tree, treeish_name); + while (tree_entry(&tree, name_entry)) { + if (prefix) + tree_path = + mkpathdup("%s/%s", prefix, name_entry->path); + else + tree_path = xstrdup(name_entry->path); + + if (S_ISGITLINK(name_entry->mode) && + is_tree_submodule_active(r, root_tree, tree_path)) { + st_entry = xmalloc(sizeof(*st_entry)); + st_entry->name_entry = xmalloc(sizeof(*st_entry->name_entry)); + *st_entry->name_entry = *name_entry; + st_entry->submodule = + submodule_from_path(r, root_tree, tree_path); + st_entry->repo = xmalloc(sizeof(*st_entry->repo)); + if (repo_submodule_init(st_entry->repo, r, tree_path, + root_tree)) + FREE_AND_NULL(st_entry->repo); + + ALLOC_GROW(out->entries, out->entry_nr + 1, + out->entry_alloc); + out->entries[out->entry_nr++] = *st_entry; + } else if (S_ISDIR(name_entry->mode)) + traverse_tree_submodules(r, root_tree, tree_path, + &name_entry->oid, out); + free(tree_path); + } +} + +void submodules_of_tree(struct repository *r, + const struct object_id *treeish_name, + struct submodule_entry_list *out) +{ + CALLOC_ARRAY(out->entries, 0); + out->entry_nr = 0; + out->entry_alloc = 0; + + traverse_tree_submodules(r, treeish_name, NULL, treeish_name, out); +} + void submodule_free(struct repository *r) { if (r->submodule_cache) diff --git a/submodule-config.h b/submodule-config.h index 65875b94ea..fa229a8b97 100644 --- a/submodule-config.h +++ b/submodule-config.h @@ -6,6 +6,7 @@ #include "hashmap.h" #include "submodule.h" #include "strbuf.h" +#include "tree-walk.h" /** * The submodule config cache API allows to read submodule @@ -101,4 +102,37 @@ int check_submodule_name(const char *name); void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules); void update_clone_config_from_gitmodules(int *max_jobs); +/* + * Submodule entry that contains relevant information about a + * submodule in a tree. + */ +struct submodule_tree_entry { + /* The submodule's tree entry. */ + struct name_entry *name_entry; + /* + * A struct repository corresponding to the submodule. May be + * NULL if the submodule has not been updated. + */ + struct repository *repo; + /* + * A struct submodule containing the submodule config in the + * tree's .gitmodules. + */ + const struct submodule *submodule; +}; + +struct submodule_entry_list { + struct submodule_tree_entry *entries; + int entry_nr; + int entry_alloc; +}; + +/** + * Given a treeish, return all submodules in the tree and its subtrees, + * but excluding nested submodules. Callers that require nested + * submodules are expected to recurse into the submodules themselves. + */ +void submodules_of_tree(struct repository *r, + const struct object_id *treeish_name, + struct submodule_entry_list *ret); #endif /* SUBMODULE_CONFIG_H */ diff --git a/submodule.c b/submodule.c index c689070524..5ace18a7d9 100644 --- a/submodule.c +++ b/submodule.c @@ -267,7 +267,9 @@ int option_parse_recurse_submodules_worktree_updater(const struct option *opt, * ie, the config looks like: "[submodule] active\n". * Since that is an invalid pathspec, we should inform the user. */ -int is_submodule_active(struct repository *repo, const char *path) +int is_tree_submodule_active(struct repository *repo, + const struct object_id *treeish_name, + const char *path) { int ret = 0; char *key = NULL; @@ -275,7 +277,7 @@ int is_submodule_active(struct repository *repo, const char *path) const struct string_list *sl; const struct submodule *module; - module = submodule_from_path(repo, null_oid(), path); + module = submodule_from_path(repo, treeish_name, path); /* early return if there isn't a path->module mapping */ if (!module) @@ -317,6 +319,11 @@ int is_submodule_active(struct repository *repo, const char *path) return ret; } +int is_submodule_active(struct repository *repo, const char *path) +{ + return is_tree_submodule_active(repo, null_oid(), path); +} + int is_submodule_populated_gently(const char *path, int *return_error_code) { int ret = 0; diff --git a/submodule.h b/submodule.h index 6bd2c99fd9..784ceffc0e 100644 --- a/submodule.h +++ b/submodule.h @@ -54,6 +54,9 @@ int git_default_submodule_config(const char *var, const char *value, void *cb); struct option; int option_parse_recurse_submodules_worktree_updater(const struct option *opt, const char *arg, int unset); +int is_tree_submodule_active(struct repository *repo, + const struct object_id *treeish_name, + const char *path); int is_submodule_active(struct repository *repo, const char *path); /* * Determine if a submodule has been populated at a given 'path' by checking if diff --git a/t/helper/test-csprng.c b/t/helper/test-csprng.c new file mode 100644 index 0000000000..65d14973c5 --- /dev/null +++ b/t/helper/test-csprng.c @@ -0,0 +1,29 @@ +#include "test-tool.h" +#include "git-compat-util.h" + + +int cmd__csprng(int argc, const char **argv) +{ + unsigned long count; + unsigned char buf[1024]; + + if (argc > 2) { + fprintf(stderr, "usage: %s [<size>]\n", argv[0]); + return 2; + } + + count = (argc == 2) ? strtoul(argv[1], NULL, 0) : -1L; + + while (count) { + unsigned long chunk = count < sizeof(buf) ? count : sizeof(buf); + if (csprng_bytes(buf, chunk) < 0) { + perror("failed to read"); + return 5; + } + if (fwrite(buf, chunk, 1, stdout) != chunk) + return 1; + count -= chunk; + } + + return 0; +} diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c index 3e4ddaee70..9646d85fc8 100644 --- a/t/helper/test-ref-store.c +++ b/t/helper/test-ref-store.c @@ -180,10 +180,9 @@ static int cmd_resolve_ref(struct ref_store *refs, const char **argv) int resolve_flags = arg_flags(*argv++, "resolve-flags", empty_flags); int flags; const char *ref; - int ignore_errno; ref = refs_resolve_ref_unsafe(refs, refname, resolve_flags, - &oid, &flags, &ignore_errno); + &oid, &flags); printf("%s %s 0x%x\n", oid_to_hex(&oid), ref ? ref : "(null)", flags); return ref ? 0 : 1; } diff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c index 26b03d7b78..1f0a28cbb6 100644 --- a/t/helper/test-reftable.c +++ b/t/helper/test-reftable.c @@ -3,15 +3,16 @@ int cmd__reftable(int argc, const char **argv) { + /* test from simple to complex. */ basics_test_main(argc, argv); + record_test_main(argc, argv); block_test_main(argc, argv); - merged_test_main(argc, argv); + tree_test_main(argc, argv); pq_test_main(argc, argv); - record_test_main(argc, argv); - refname_test_main(argc, argv); readwrite_test_main(argc, argv); + merged_test_main(argc, argv); stack_test_main(argc, argv); - tree_test_main(argc, argv); + refname_test_main(argc, argv); return 0; } diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c index 338a57b104..e6ec69cf32 100644 --- a/t/helper/test-tool.c +++ b/t/helper/test-tool.c @@ -20,6 +20,7 @@ static struct test_cmd cmds[] = { { "chmtime", cmd__chmtime }, { "config", cmd__config }, { "crontab", cmd__crontab }, + { "csprng", cmd__csprng }, { "ctype", cmd__ctype }, { "date", cmd__date }, { "delta", cmd__delta }, diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h index 48cee1f4a2..20756eefdd 100644 --- a/t/helper/test-tool.h +++ b/t/helper/test-tool.h @@ -10,6 +10,7 @@ int cmd__bloom(int argc, const char **argv); int cmd__chmtime(int argc, const char **argv); int cmd__config(int argc, const char **argv); int cmd__crontab(int argc, const char **argv); +int cmd__csprng(int argc, const char **argv); int cmd__ctype(int argc, const char **argv); int cmd__date(int argc, const char **argv); int cmd__delta(int argc, const char **argv); diff --git a/t/lib-bitmap.sh b/t/lib-bitmap.sh index 21d0392dda..a95537e759 100644 --- a/t/lib-bitmap.sh +++ b/t/lib-bitmap.sh @@ -1,6 +1,9 @@ # Helpers for scripts testing bitmap functionality; see t5310 for # example usage. +objdir=.git/objects +midx=$objdir/pack/multi-pack-index + # Compare a file containing rev-list bitmap traversal output to its non-bitmap # counterpart. You can't just use test_cmp for this, because the two produce # subtly different output: @@ -264,3 +267,185 @@ have_delta () { midx_checksum () { test-tool read-midx --checksum "$1" } + +# midx_pack_source <obj> +midx_pack_source () { + test-tool read-midx --show-objects .git/objects | grep "^$1 " | cut -f2 +} + +test_rev_exists () { + commit="$1" + kind="$2" + + test_expect_success "reverse index exists ($kind)" ' + GIT_TRACE2_EVENT=$(pwd)/event.trace \ + git rev-list --test-bitmap "$commit" && + + if test "rev" = "$kind" + then + test_path_is_file $midx-$(midx_checksum $objdir).rev + fi && + grep "\"category\":\"load_midx_revindex\",\"key\":\"source\",\"value\":\"$kind\"" event.trace + ' +} + +midx_bitmap_core () { + rev_kind="${1:-midx}" + + setup_bitmap_history + + test_expect_success 'create single-pack midx with bitmaps' ' + git repack -ad && + git multi-pack-index write --bitmap && + test_path_is_file $midx && + test_path_is_file $midx-$(midx_checksum $objdir).bitmap + ' + + test_rev_exists HEAD "$rev_kind" + + basic_bitmap_tests + + test_expect_success 'create new additional packs' ' + for i in $(test_seq 1 16) + do + test_commit "$i" && + git repack -d || return 1 + done && + + git checkout -b other2 HEAD~8 && + for i in $(test_seq 1 8) + do + test_commit "side-$i" && + git repack -d || return 1 + done && + git checkout second + ' + + test_expect_success 'create multi-pack midx with bitmaps' ' + git multi-pack-index write --bitmap && + + ls $objdir/pack/pack-*.pack >packs && + test_line_count = 25 packs && + + test_path_is_file $midx && + test_path_is_file $midx-$(midx_checksum $objdir).bitmap + ' + + test_rev_exists HEAD "$rev_kind" + + basic_bitmap_tests + + test_expect_success '--no-bitmap is respected when bitmaps exist' ' + git multi-pack-index write --bitmap && + + test_commit respect--no-bitmap && + git repack -d && + + test_path_is_file $midx && + test_path_is_file $midx-$(midx_checksum $objdir).bitmap && + + git multi-pack-index write --no-bitmap && + + test_path_is_file $midx && + test_path_is_missing $midx-$(midx_checksum $objdir).bitmap && + test_path_is_missing $midx-$(midx_checksum $objdir).rev + ' + + test_expect_success 'setup midx with base from later pack' ' + # Write a and b so that "a" is a delta on top of base "b", since Git + # prefers to delete contents out of a base rather than add to a shorter + # object. + test_seq 1 128 >a && + test_seq 1 130 >b && + + git add a b && + git commit -m "initial commit" && + + a=$(git rev-parse HEAD:a) && + b=$(git rev-parse HEAD:b) && + + # In the first pack, "a" is stored as a delta to "b". + p1=$(git pack-objects .git/objects/pack/pack <<-EOF + $a + $b + EOF + ) && + + # In the second pack, "a" is missing, and "b" is not a delta nor base to + # any other object. + p2=$(git pack-objects .git/objects/pack/pack <<-EOF + $b + $(git rev-parse HEAD) + $(git rev-parse HEAD^{tree}) + EOF + ) && + + git prune-packed && + # Use the second pack as the preferred source, so that "b" occurs + # earlier in the MIDX object order, rendering "a" unusable for pack + # reuse. + git multi-pack-index write --bitmap --preferred-pack=pack-$p2.idx && + + have_delta $a $b && + test $(midx_pack_source $a) != $(midx_pack_source $b) + ' + + rev_list_tests 'full bitmap with backwards delta' + + test_expect_success 'clone with bitmaps enabled' ' + git clone --no-local --bare . clone-reverse-delta.git && + test_when_finished "rm -fr clone-reverse-delta.git" && + + git rev-parse HEAD >expect && + git --git-dir=clone-reverse-delta.git rev-parse HEAD >actual && + test_cmp expect actual + ' + + test_expect_success 'changing the preferred pack does not corrupt bitmaps' ' + rm -fr repo && + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + test_commit A && + test_commit B && + + git rev-list --objects --no-object-names HEAD^ >A.objects && + git rev-list --objects --no-object-names HEAD^.. >B.objects && + + A=$(git pack-objects $objdir/pack/pack <A.objects) && + B=$(git pack-objects $objdir/pack/pack <B.objects) && + + cat >indexes <<-EOF && + pack-$A.idx + pack-$B.idx + EOF + + git multi-pack-index write --bitmap --stdin-packs \ + --preferred-pack=pack-$A.pack <indexes && + git rev-list --test-bitmap A && + + git multi-pack-index write --bitmap --stdin-packs \ + --preferred-pack=pack-$B.pack <indexes && + git rev-list --test-bitmap A + ) + ' +} + +midx_bitmap_partial_tests () { + rev_kind="${1:-midx}" + + test_expect_success 'setup partial bitmaps' ' + test_commit packed && + git repack && + test_commit loose && + git multi-pack-index write --bitmap 2>err && + test_path_is_file $midx && + test_path_is_file $midx-$(midx_checksum $objdir).bitmap + ' + + test_rev_exists HEAD~ "$rev_kind" + + basic_bitmap_tests HEAD~ +} diff --git a/t/lib-read-tree-m-3way.sh b/t/lib-read-tree-m-3way.sh index 168329adbc..2da25b3144 100644 --- a/t/lib-read-tree-m-3way.sh +++ b/t/lib-read-tree-m-3way.sh @@ -3,21 +3,21 @@ mkdir Z for a in N D M do - for b in N D M - do - p=$a$b + for b in N D M + do + p=$a$b echo This is $p from the original tree. >$p echo This is Z/$p from the original tree. >Z/$p - test_expect_success \ - "adding test file $p and Z/$p" \ - 'git update-index --add $p && - git update-index --add Z/$p' + test_expect_success "adding test file $p and Z/$p" ' + git update-index --add $p && + git update-index --add Z/$p + ' done done echo This is SS from the original tree. >SS -test_expect_success \ - 'adding test file SS' \ - 'git update-index --add SS' +test_expect_success 'adding test file SS' ' + git update-index --add SS +' cat >TT <<\EOF This is a trivial merge sample text. Branch A is expected to upcase this word, here. @@ -30,12 +30,12 @@ At the very end, here comes another line, that is the word, expected to be upcased by Branch B. This concludes the trivial merge sample file. EOF -test_expect_success \ - 'adding test file TT' \ - 'git update-index --add TT' -test_expect_success \ - 'prepare initial tree' \ - 'tree_O=$(git write-tree)' +test_expect_success 'adding test file TT' ' + git update-index --add TT +' +test_expect_success 'prepare initial tree' ' + tree_O=$(git write-tree) +' ################################################################ # Branch A and B makes the changes according to the above matrix. @@ -45,48 +45,48 @@ test_expect_success \ to_remove=$(echo D? Z/D?) rm -f $to_remove -test_expect_success \ - 'change in branch A (removal)' \ - 'git update-index --remove $to_remove' +test_expect_success 'change in branch A (removal)' ' + git update-index --remove $to_remove +' for p in M? Z/M? do - echo This is modified $p in the branch A. >$p - test_expect_success \ - 'change in branch A (modification)' \ - "git update-index $p" + echo This is modified $p in the branch A. >$p + test_expect_success 'change in branch A (modification)' ' + git update-index $p + ' done for p in AN AA Z/AN Z/AA do - echo This is added $p in the branch A. >$p - test_expect_success \ - 'change in branch A (addition)' \ - "git update-index --add $p" + echo This is added $p in the branch A. >$p + test_expect_success 'change in branch A (addition)' ' + git update-index --add $p + ' done echo This is SS from the modified tree. >SS echo This is LL from the modified tree. >LL -test_expect_success \ - 'change in branch A (addition)' \ - 'git update-index --add LL && - git update-index SS' +test_expect_success 'change in branch A (addition)' ' + git update-index --add LL && + git update-index SS +' mv TT TT- sed -e '/Branch A/s/word/WORD/g' <TT- >TT rm -f TT- -test_expect_success \ - 'change in branch A (edit)' \ - 'git update-index TT' +test_expect_success 'change in branch A (edit)' ' + git update-index TT +' mkdir DF echo Branch A makes a file at DF/DF, creating a directory DF. >DF/DF -test_expect_success \ - 'change in branch A (change file to directory)' \ - 'git update-index --add DF/DF' +test_expect_success 'change in branch A (change file to directory)' ' + git update-index --add DF/DF +' -test_expect_success \ - 'recording branch A tree' \ - 'tree_A=$(git write-tree)' +test_expect_success 'recording branch A tree' ' + tree_A=$(git write-tree) +' ################################################################ # Branch B @@ -94,65 +94,65 @@ test_expect_success \ rm -rf [NDMASLT][NDMASLT] Z DF mkdir Z -test_expect_success \ - 'reading original tree and checking out' \ - 'git read-tree $tree_O && - git checkout-index -a' +test_expect_success 'reading original tree and checking out' ' + git read-tree $tree_O && + git checkout-index -a +' to_remove=$(echo ?D Z/?D) rm -f $to_remove -test_expect_success \ - 'change in branch B (removal)' \ - "git update-index --remove $to_remove" +test_expect_success 'change in branch B (removal)' ' + git update-index --remove $to_remove +' for p in ?M Z/?M do - echo This is modified $p in the branch B. >$p - test_expect_success \ - 'change in branch B (modification)' \ - "git update-index $p" + echo This is modified $p in the branch B. >$p + test_expect_success 'change in branch B (modification)' ' + git update-index $p + ' done for p in NA AA Z/NA Z/AA do - echo This is added $p in the branch B. >$p - test_expect_success \ - 'change in branch B (addition)' \ - "git update-index --add $p" + echo This is added $p in the branch B. >$p + test_expect_success 'change in branch B (addition)' ' + git update-index --add $p + ' done echo This is SS from the modified tree. >SS echo This is LL from the modified tree. >LL -test_expect_success \ - 'change in branch B (addition and modification)' \ - 'git update-index --add LL && - git update-index SS' +test_expect_success 'change in branch B (addition and modification)' ' + git update-index --add LL && + git update-index SS +' mv TT TT- sed -e '/Branch B/s/word/WORD/g' <TT- >TT rm -f TT- -test_expect_success \ - 'change in branch B (modification)' \ - 'git update-index TT' +test_expect_success 'change in branch B (modification)' ' + git update-index TT +' echo Branch B makes a file at DF. >DF -test_expect_success \ - 'change in branch B (addition of a file to conflict with directory)' \ - 'git update-index --add DF' - -test_expect_success \ - 'recording branch B tree' \ - 'tree_B=$(git write-tree)' - -test_expect_success \ - 'keep contents of 3 trees for easy access' \ - 'rm -f .git/index && - git read-tree $tree_O && - mkdir .orig-O && - git checkout-index --prefix=.orig-O/ -f -q -a && - rm -f .git/index && - git read-tree $tree_A && - mkdir .orig-A && - git checkout-index --prefix=.orig-A/ -f -q -a && - rm -f .git/index && - git read-tree $tree_B && - mkdir .orig-B && - git checkout-index --prefix=.orig-B/ -f -q -a' +test_expect_success 'change in branch B (addition of a file to conflict with directory)' ' + git update-index --add DF +' + +test_expect_success 'recording branch B tree' ' + tree_B=$(git write-tree) +' + +test_expect_success 'keep contents of 3 trees for easy access' ' + rm -f .git/index && + git read-tree $tree_O && + mkdir .orig-O && + git checkout-index --prefix=.orig-O/ -f -q -a && + rm -f .git/index && + git read-tree $tree_A && + mkdir .orig-A && + git checkout-index --prefix=.orig-A/ -f -q -a && + rm -f .git/index && + git read-tree $tree_B && + mkdir .orig-B && + git checkout-index --prefix=.orig-B/ -f -q -a +' diff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh index cb777c74a2..2a7106b949 100755 --- a/t/perf/p2000-sparse-operations.sh +++ b/t/perf/p2000-sparse-operations.sh @@ -117,5 +117,7 @@ test_perf_on_all git diff test_perf_on_all git diff --cached test_perf_on_all git blame $SPARSE_CONE/a test_perf_on_all git blame $SPARSE_CONE/f3/a +test_perf_on_all git checkout-index -f --all +test_perf_on_all git update-index --add --remove $SPARSE_CONE/a test_done diff --git a/t/t0012-help.sh b/t/t0012-help.sh index 91b68c74a1..cbd725ccac 100755 --- a/t/t0012-help.sh +++ b/t/t0012-help.sh @@ -139,13 +139,18 @@ test_expect_success 'git help --config-sections-for-completion' ' ' test_expect_success 'generate builtin list' ' + mkdir -p sub && git --list-cmds=builtins >builtins ' while read builtin do test_expect_success "$builtin can handle -h" ' - test_expect_code 129 git $builtin -h >output 2>&1 && + ( + GIT_CEILING_DIRECTORIES=$(pwd) && + export GIT_CEILING_DIRECTORIES && + test_expect_code 129 git -C sub $builtin -h >output 2>&1 + ) && test_i18ngrep usage output ' done <builtins diff --git a/t/t0015-hash.sh b/t/t0015-hash.sh index 291e9061f3..086822fc45 100755 --- a/t/t0015-hash.sh +++ b/t/t0015-hash.sh @@ -15,7 +15,7 @@ test_expect_success 'test basic SHA-1 hash values' ' grep c12252ceda8be8994d5fa0290a47231c1d16aae3 actual && printf "abcdefghijklmnopqrstuvwxyz" | test-tool sha1 >actual && grep 32d10c7b8cf96570ca04ce37f2a19d84240d3a89 actual && - perl -e "$| = 1; print q{aaaaaaaaaa} for 1..100000;" | \ + perl -e "$| = 1; print q{aaaaaaaaaa} for 1..100000;" | test-tool sha1 >actual && grep 34aa973cd4c4daa4f61eeb2bdbad27316534016f actual && printf "blob 0\0" | test-tool sha1 >actual && @@ -38,10 +38,10 @@ test_expect_success 'test basic SHA-256 hash values' ' printf "abcdefghijklmnopqrstuvwxyz" | test-tool sha256 >actual && grep 71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73 actual && # Try to exercise the chunking code by turning autoflush on. - perl -e "$| = 1; print q{aaaaaaaaaa} for 1..100000;" | \ + perl -e "$| = 1; print q{aaaaaaaaaa} for 1..100000;" | test-tool sha256 >actual && grep cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0 actual && - perl -e "$| = 1; print q{abcdefghijklmnopqrstuvwxyz} for 1..100000;" | \ + perl -e "$| = 1; print q{abcdefghijklmnopqrstuvwxyz} for 1..100000;" | test-tool sha256 >actual && grep e406ba321ca712ad35a698bf0af8d61fc4dc40eca6bdcea4697962724ccbde35 actual && printf "blob 0\0" | test-tool sha256 >actual && diff --git a/t/t0027-auto-crlf.sh b/t/t0027-auto-crlf.sh index 4a5c5c602c..c5f7ac63b0 100755 --- a/t/t0027-auto-crlf.sh +++ b/t/t0027-auto-crlf.sh @@ -597,6 +597,12 @@ do # auto: core.autocrlf=false and core.eol unset(or native) uses native eol checkout_files auto "$id" "" false "" $NL CRLF CRLF_mix_LF LF_mix_CR LF_nul checkout_files auto "$id" "" false native $NL CRLF CRLF_mix_LF LF_mix_CR LF_nul + # core.autocrlf false, .gitattributes sets eol + checkout_files "" "$id" "lf" false "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul + checkout_files "" "$id" "crlf" false "" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul + # core.autocrlf true, .gitattributes sets eol + checkout_files "" "$id" "lf" true "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul + checkout_files "" "$id" "crlf" true "" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul done # The rest of the tests are unique; do the usual linting. diff --git a/t/t0051-windows-named-pipe.sh b/t/t0051-windows-named-pipe.sh index 10ac92d225..412f413360 100755 --- a/t/t0051-windows-named-pipe.sh +++ b/t/t0051-windows-named-pipe.sh @@ -3,8 +3,13 @@ test_description='Windows named pipes' . ./test-lib.sh +if ! test_have_prereq MINGW +then + skip_all='skipping Windows-specific tests' + test_done +fi -test_expect_success MINGW 'o_append write to named pipe' ' +test_expect_success 'o_append write to named pipe' ' GIT_TRACE="$(pwd)/expect" git status >/dev/null 2>&1 && { test-tool windows-named-pipe t0051 >actual 2>&1 & } && pid=$! && diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index 39382fa195..145eee11df 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh @@ -4,6 +4,98 @@ test_description='git cat-file' . ./test-lib.sh +test_cmdmode_usage () { + test_expect_code 129 "$@" 2>err && + grep "^error:.*is incompatible with" err +} + +for switches in \ + '-e -p' \ + '-p -t' \ + '-t -s' \ + '-s --textconv' \ + '--textconv --filters' \ + '--batch-all-objects -e' +do + test_expect_success "usage: cmdmode $switches" ' + test_cmdmode_usage git cat-file $switches + ' +done + +test_incompatible_usage () { + test_expect_code 129 "$@" 2>err && + grep -E "^(fatal|error):.*(requires|incompatible with|needs)" err +} + +for opt in --batch --batch-check +do + test_expect_success "usage: incompatible options: --path with $opt" ' + test_incompatible_usage git cat-file --path=foo $opt + ' +done + +test_missing_usage () { + test_expect_code 129 "$@" 2>err && + grep -E "^fatal:.*required" err +} + +short_modes="-e -p -t -s" +cw_modes="--textconv --filters" + +for opt in $cw_modes +do + test_expect_success "usage: $opt requires another option" ' + test_missing_usage git cat-file $opt + ' +done + +for opt in $short_modes +do + test_expect_success "usage: $opt requires another option" ' + test_missing_usage git cat-file $opt + ' + + for opt2 in --batch \ + --batch-check \ + --follow-symlinks \ + "--path=foo HEAD:some-path.txt" + do + test_expect_success "usage: incompatible options: $opt and $opt2" ' + test_incompatible_usage git cat-file $opt $opt2 + ' + done +done + +test_too_many_arguments () { + test_expect_code 129 "$@" 2>err && + grep -E "^fatal: too many arguments$" err +} + +for opt in $short_modes $cw_modes +do + args="one two three" + test_expect_success "usage: too many arguments: $opt $args" ' + test_too_many_arguments git cat-file $opt $args + ' + + for opt2 in --buffer --follow-symlinks + do + test_expect_success "usage: incompatible arguments: $opt with batch option $opt2" ' + test_incompatible_usage git cat-file $opt $opt2 + ' + done +done + +for opt in --buffer \ + --follow-symlinks \ + --batch-all-objects +do + test_expect_success "usage: bad option combination: $opt without batch mode" ' + test_incompatible_usage git cat-file $opt && + test_incompatible_usage git cat-file $opt commit HEAD + ' +done + echo_without_newline () { printf '%s' "$*" } diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh index 64b340f227..ac5ad8c740 100755 --- a/t/t1007-hash-object.sh +++ b/t/t1007-hash-object.sh @@ -2,6 +2,7 @@ test_description="git hash-object" +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh echo_without_newline() { diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh index 42776984fe..3592d12442 100755 --- a/t/t1091-sparse-checkout-builtin.sh +++ b/t/t1091-sparse-checkout-builtin.sh @@ -5,6 +5,9 @@ test_description='sparse checkout builtin tests' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +GIT_TEST_SPLIT_INDEX=false +export GIT_TEST_SPLIT_INDEX + . ./test-lib.sh list_files() { @@ -79,6 +82,12 @@ test_expect_success 'git sparse-checkout init' ' check_files repo a ' +test_expect_success 'git sparse-checkout init in empty repo' ' + test_when_finished rm -rf empty-repo blank-template && + git init --template= empty-repo && + git -C empty-repo sparse-checkout init +' + test_expect_success 'git sparse-checkout list after init' ' git -C repo sparse-checkout list >actual && cat >expect <<-\EOF && @@ -228,36 +237,31 @@ test_expect_success 'sparse-checkout disable' ' ' test_expect_success 'sparse-index enabled and disabled' ' - ( - sane_unset GIT_TEST_SPLIT_INDEX && - git -C repo update-index --no-split-index && - - git -C repo sparse-checkout init --cone --sparse-index && - test_cmp_config -C repo true index.sparse && - git -C repo ls-files --sparse >sparse && - git -C repo sparse-checkout disable && - git -C repo ls-files --sparse >full && - - cat >expect <<-\EOF && - @@ -1,4 +1,7 @@ - a - -deep/ - -folder1/ - -folder2/ - +deep/a - +deep/deeper1/a - +deep/deeper1/deepest/a - +deep/deeper2/a - +folder1/a - +folder2/a - EOF + git -C repo sparse-checkout init --cone --sparse-index && + test_cmp_config -C repo true index.sparse && + git -C repo ls-files --sparse >sparse && + git -C repo sparse-checkout disable && + git -C repo ls-files --sparse >full && - diff -u sparse full | tail -n +3 >actual && - test_cmp expect actual && + cat >expect <<-\EOF && + @@ -1,4 +1,7 @@ + a + -deep/ + -folder1/ + -folder2/ + +deep/a + +deep/deeper1/a + +deep/deeper1/deepest/a + +deep/deeper2/a + +folder1/a + +folder2/a + EOF + + diff -u sparse full | tail -n +3 >actual && + test_cmp expect actual && - git -C repo config --list >config && - ! grep index.sparse config - ) + git -C repo config --list >config && + ! grep index.sparse config ' test_expect_success 'cone mode: init and set' ' diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh index 4ba1617752..f3a059e5af 100755 --- a/t/t1092-sparse-checkout-compatibility.sh +++ b/t/t1092-sparse-checkout-compatibility.sh @@ -593,13 +593,11 @@ test_expect_success 'reset with pathspecs outside sparse definition' ' test_sparse_match git reset update-folder1 -- folder1 && git -C full-checkout reset update-folder1 -- folder1 && - test_sparse_match git status --porcelain=v2 && - test_all_match git rev-parse HEAD:folder1 && + test_all_match git ls-files -s -- folder1 && test_sparse_match git reset update-folder2 -- folder2/a && git -C full-checkout reset update-folder2 -- folder2/a && - test_sparse_match git status --porcelain=v2 && - test_all_match git rev-parse HEAD:folder2/a + test_all_match git ls-files -s -- folder2/a ' test_expect_success 'reset with wildcard pathspec' ' @@ -629,6 +627,173 @@ test_expect_success 'reset with wildcard pathspec' ' test_all_match git ls-files -s -- folder1 ' +test_expect_success 'update-index modify outside sparse definition' ' + init_repos && + + write_script edit-contents <<-\EOF && + echo text >>$1 + EOF + + # Create & modify folder1/a + # Note that this setup is a manual way of reaching the erroneous + # condition in which a `skip-worktree` enabled, outside-of-cone file + # exists on disk. It is used here to ensure `update-index` is stable + # and behaves predictably if such a condition occurs. + run_on_sparse mkdir -p folder1 && + run_on_sparse cp ../initial-repo/folder1/a folder1/a && + run_on_all ../edit-contents folder1/a && + + # If file has skip-worktree enabled, update-index does not modify the + # index entry + test_sparse_match git update-index folder1/a && + test_sparse_match git status --porcelain=v2 && + test_must_be_empty sparse-checkout-out && + + # When skip-worktree is disabled (even on files outside sparse cone), file + # is updated in the index + test_sparse_match git update-index --no-skip-worktree folder1/a && + test_all_match git status --porcelain=v2 && + test_all_match git update-index folder1/a && + test_all_match git status --porcelain=v2 +' + +test_expect_success 'update-index --add outside sparse definition' ' + init_repos && + + write_script edit-contents <<-\EOF && + echo text >>$1 + EOF + + # Create folder1, add new file + run_on_sparse mkdir -p folder1 && + run_on_all ../edit-contents folder1/b && + + # The *untracked* out-of-cone file is added to the index because it does + # not have a `skip-worktree` bit to signal that it should be ignored + # (unlike in `git add`, which will fail due to the file being outside + # the sparse checkout definition). + test_all_match git update-index --add folder1/b && + test_all_match git status --porcelain=v2 +' + +# NEEDSWORK: `--remove`, unlike the rest of `update-index`, does not ignore +# `skip-worktree` entries by default and will remove them from the index. +# The `--ignore-skip-worktree-entries` flag must be used in conjunction with +# `--remove` to ignore the `skip-worktree` entries and prevent their removal +# from the index. +test_expect_success 'update-index --remove outside sparse definition' ' + init_repos && + + # When --ignore-skip-worktree-entries is _not_ specified: + # out-of-cone, not-on-disk files are removed from the index + test_sparse_match git update-index --remove folder1/a && + cat >expect <<-EOF && + D folder1/a + EOF + test_sparse_match git diff --cached --name-status && + test_cmp expect sparse-checkout-out && + + # Reset the state + test_all_match git reset --hard && + + # When --ignore-skip-worktree-entries is specified, out-of-cone + # (skip-worktree) files are ignored + test_sparse_match git update-index --remove --ignore-skip-worktree-entries folder1/a && + test_sparse_match git diff --cached --name-status && + test_must_be_empty sparse-checkout-out && + + # Reset the state + test_all_match git reset --hard && + + # --force-remove supercedes --ignore-skip-worktree-entries, removing + # a skip-worktree file from the index (and disk) when both are specified + # with --remove + test_sparse_match git update-index --force-remove --ignore-skip-worktree-entries folder1/a && + cat >expect <<-EOF && + D folder1/a + EOF + test_sparse_match git diff --cached --name-status && + test_cmp expect sparse-checkout-out +' + +test_expect_success 'update-index with directories' ' + init_repos && + + # update-index will exit silently when provided with a directory name + # containing a trailing slash + test_all_match git update-index deep/ folder1/ && + grep "Ignoring path deep/" sparse-checkout-err && + grep "Ignoring path folder1/" sparse-checkout-err && + + # When update-index is given a directory name WITHOUT a trailing slash, it will + # behave in different ways depending on the status of the directory on disk: + # * if it exists, the command exits with an error ("add individual files instead") + # * if it does NOT exist (e.g., in a sparse-checkout), it is assumed to be a + # file and either triggers an error ("does not exist and --remove not passed") + # or is ignored completely (when using --remove) + test_all_match test_must_fail git update-index deep && + run_on_all test_must_fail git update-index folder1 && + test_must_fail git -C full-checkout update-index --remove folder1 && + test_sparse_match git update-index --remove folder1 && + test_all_match git status --porcelain=v2 +' + +test_expect_success 'update-index --again file outside sparse definition' ' + init_repos && + + test_all_match git checkout -b test-reupdate && + + # Update HEAD without modifying the index to introduce a difference in + # folder1/a + test_sparse_match git reset --soft update-folder1 && + + # Because folder1/a differs in the index vs HEAD, + # `git update-index --no-skip-worktree --again` will effectively perform + # `git update-index --no-skip-worktree folder1/a` and remove the skip-worktree + # flag from folder1/a + test_sparse_match git update-index --no-skip-worktree --again && + test_sparse_match git status --porcelain=v2 && + + cat >expect <<-EOF && + D folder1/a + EOF + test_sparse_match git diff --name-status && + test_cmp expect sparse-checkout-out +' + +test_expect_success 'update-index --cacheinfo' ' + init_repos && + + deep_a_oid=$(git -C full-checkout rev-parse update-deep:deep/a) && + folder2_oid=$(git -C full-checkout rev-parse update-folder2:folder2) && + folder1_a_oid=$(git -C full-checkout rev-parse update-folder1:folder1/a) && + + test_all_match git update-index --cacheinfo 100644 $deep_a_oid deep/a && + test_all_match git status --porcelain=v2 && + + # Cannot add sparse directory, even in sparse index case + test_all_match test_must_fail git update-index --add --cacheinfo 040000 $folder2_oid folder2/ && + + # Sparse match only: the new outside-of-cone entry is added *without* skip-worktree, + # so `git status` reports it as "deleted" in the worktree + test_sparse_match git update-index --add --cacheinfo 100644 $folder1_a_oid folder1/a && + test_sparse_match git status --porcelain=v2 && + cat >expect <<-EOF && + MD folder1/a + EOF + test_sparse_match git status --short -- folder1/a && + test_cmp expect sparse-checkout-out && + + # To return folder1/a to "normal" for a sparse checkout (ignored & + # outside-of-cone), add the skip-worktree flag. + test_sparse_match git update-index --skip-worktree folder1/a && + cat >expect <<-EOF && + S folder1/a + EOF + test_sparse_match git ls-files -t -- folder1/a && + test_cmp expect sparse-checkout-out +' + test_expect_success 'merge, cherry-pick, and rebase' ' init_repos && @@ -754,6 +919,74 @@ test_expect_success 'cherry-pick with conflicts' ' test_all_match test_must_fail git cherry-pick to-cherry-pick ' +test_expect_success 'checkout-index inside sparse definition' ' + init_repos && + + run_on_all rm -f deep/a && + test_all_match git checkout-index -- deep/a && + test_all_match git status --porcelain=v2 && + + echo test >>new-a && + run_on_all cp ../new-a a && + test_all_match test_must_fail git checkout-index -- a && + test_all_match git checkout-index -f -- a && + test_all_match git status --porcelain=v2 +' + +test_expect_success 'checkout-index outside sparse definition' ' + init_repos && + + # Without --ignore-skip-worktree-bits, outside-of-cone files will trigger + # an error + test_sparse_match test_must_fail git checkout-index -- folder1/a && + test_i18ngrep "folder1/a has skip-worktree enabled" sparse-checkout-err && + test_path_is_missing folder1/a && + + # With --ignore-skip-worktree-bits, outside-of-cone files are checked out + test_sparse_match git checkout-index --ignore-skip-worktree-bits -- folder1/a && + test_cmp sparse-checkout/folder1/a sparse-index/folder1/a && + test_cmp sparse-checkout/folder1/a full-checkout/folder1/a && + + run_on_sparse rm -rf folder1 && + echo test >new-a && + run_on_sparse mkdir -p folder1 && + run_on_all cp ../new-a folder1/a && + + test_all_match test_must_fail git checkout-index --ignore-skip-worktree-bits -- folder1/a && + test_all_match git checkout-index -f --ignore-skip-worktree-bits -- folder1/a && + test_cmp sparse-checkout/folder1/a sparse-index/folder1/a && + test_cmp sparse-checkout/folder1/a full-checkout/folder1/a +' + +test_expect_success 'checkout-index with folders' ' + init_repos && + + # Inside checkout definition + test_all_match test_must_fail git checkout-index -f -- deep/ && + + # Outside checkout definition + # Note: although all tests fail (as expected), the messaging differs. For + # non-sparse index checkouts, the error is that the "file" does not appear + # in the index; for sparse checkouts, the error is explicitly that the + # entry is a sparse directory. + run_on_all test_must_fail git checkout-index -f -- folder1/ && + test_cmp full-checkout-err sparse-checkout-err && + ! test_cmp full-checkout-err sparse-index-err && + grep "is a sparse directory" sparse-index-err +' + +test_expect_success 'checkout-index --all' ' + init_repos && + + test_all_match git checkout-index --all && + test_sparse_match test_path_is_missing folder1 && + + # --ignore-skip-worktree-bits will cause `skip-worktree` files to be + # checked out, causing the outside-of-cone `folder1` to exist on-disk + test_all_match git checkout-index --ignore-skip-worktree-bits --all && + test_all_match test_path_exists folder1 +' + test_expect_success 'clean' ' init_repos && @@ -763,23 +996,42 @@ test_expect_success 'clean' ' test_all_match git commit -m "ignore bogus files" && run_on_sparse mkdir folder1 && + run_on_all mkdir -p deep/untracked-deep && run_on_all touch folder1/bogus && + run_on_all touch folder1/untracked && + run_on_all touch deep/untracked-deep/bogus && + run_on_all touch deep/untracked-deep/untracked && test_all_match git status --porcelain=v2 && test_all_match git clean -f && test_all_match git status --porcelain=v2 && test_sparse_match ls && test_sparse_match ls folder1 && + run_on_all test_path_exists folder1/bogus && + run_on_all test_path_is_missing folder1/untracked && + run_on_all test_path_exists deep/untracked-deep/bogus && + run_on_all test_path_exists deep/untracked-deep/untracked && + + test_all_match git clean -fd && + test_all_match git status --porcelain=v2 && + test_sparse_match ls && + test_sparse_match ls folder1 && + run_on_all test_path_exists folder1/bogus && + run_on_all test_path_exists deep/untracked-deep/bogus && + run_on_all test_path_is_missing deep/untracked-deep/untracked && test_all_match git clean -xf && test_all_match git status --porcelain=v2 && test_sparse_match ls && test_sparse_match ls folder1 && + run_on_all test_path_is_missing folder1/bogus && + run_on_all test_path_exists deep/untracked-deep/bogus && test_all_match git clean -xdf && test_all_match git status --porcelain=v2 && test_sparse_match ls && test_sparse_match ls folder1 && + run_on_all test_path_is_missing deep/untracked-deep/bogus && test_sparse_match test_path_is_dir folder1 ' @@ -898,6 +1150,8 @@ test_expect_success 'sparse-index is not expanded' ' echo >>sparse-index/untracked.txt && ensure_not_expanded add . && + ensure_not_expanded checkout-index -f a && + ensure_not_expanded checkout-index -f --all && for ref in update-deep update-folder1 update-folder2 update-deep do echo >>sparse-index/README.md && @@ -926,6 +1180,8 @@ test_expect_success 'sparse-index is not expanded' ' # Wildcard identifies only full sparse directories, no index expansion ensure_not_expanded reset deepest -- folder\* && + ensure_not_expanded clean -fd && + ensure_not_expanded checkout -f update-deep && test_config -C sparse-index pull.twohead ort && ( @@ -1001,6 +1257,24 @@ test_expect_success 'sparse index is not expanded: diff' ' ensure_not_expanded diff --cached ' +test_expect_success 'sparse index is not expanded: update-index' ' + init_repos && + + deep_a_oid=$(git -C full-checkout rev-parse update-deep:deep/a) && + ensure_not_expanded update-index --cacheinfo 100644 $deep_a_oid deep/a && + + echo "test" >sparse-index/README.md && + echo "test2" >sparse-index/a && + rm -f sparse-index/deep/a && + + ensure_not_expanded update-index --add README.md && + ensure_not_expanded update-index a && + ensure_not_expanded update-index --remove deep/a && + + ensure_not_expanded reset --soft update-deep && + ensure_not_expanded update-index --add --remove --again +' + test_expect_success 'sparse index is not expanded: blame' ' init_repos && diff --git a/t/t1300-config.sh b/t/t1300-config.sh index 78359f1f4a..7dd9b325d9 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -2388,4 +2388,122 @@ test_expect_success '--get and --get-all with --fixed-value' ' test_must_fail git config --file=config --get-regexp --fixed-value fixed+ non-existent ' +test_expect_success 'includeIf.hasconfig:remote.*.url' ' + git init hasremoteurlTest && + test_when_finished "rm -rf hasremoteurlTest" && + + cat >include-this <<-\EOF && + [user] + this = this-is-included + EOF + cat >dont-include-that <<-\EOF && + [user] + that = that-is-not-included + EOF + cat >>hasremoteurlTest/.git/config <<-EOF && + [includeIf "hasconfig:remote.*.url:foourl"] + path = "$(pwd)/include-this" + [includeIf "hasconfig:remote.*.url:barurl"] + path = "$(pwd)/dont-include-that" + [remote "foo"] + url = foourl + EOF + + echo this-is-included >expect-this && + git -C hasremoteurlTest config --get user.this >actual-this && + test_cmp expect-this actual-this && + + test_must_fail git -C hasremoteurlTest config --get user.that +' + +test_expect_success 'includeIf.hasconfig:remote.*.url respects last-config-wins' ' + git init hasremoteurlTest && + test_when_finished "rm -rf hasremoteurlTest" && + + cat >include-two-three <<-\EOF && + [user] + two = included-config + three = included-config + EOF + cat >>hasremoteurlTest/.git/config <<-EOF && + [remote "foo"] + url = foourl + [user] + one = main-config + two = main-config + [includeIf "hasconfig:remote.*.url:foourl"] + path = "$(pwd)/include-two-three" + [user] + three = main-config + EOF + + echo main-config >expect-main-config && + echo included-config >expect-included-config && + + git -C hasremoteurlTest config --get user.one >actual && + test_cmp expect-main-config actual && + + git -C hasremoteurlTest config --get user.two >actual && + test_cmp expect-included-config actual && + + git -C hasremoteurlTest config --get user.three >actual && + test_cmp expect-main-config actual +' + +test_expect_success 'includeIf.hasconfig:remote.*.url globs' ' + git init hasremoteurlTest && + test_when_finished "rm -rf hasremoteurlTest" && + + printf "[user]\ndss = yes\n" >double-star-start && + printf "[user]\ndse = yes\n" >double-star-end && + printf "[user]\ndsm = yes\n" >double-star-middle && + printf "[user]\nssm = yes\n" >single-star-middle && + printf "[user]\nno = no\n" >no && + + cat >>hasremoteurlTest/.git/config <<-EOF && + [remote "foo"] + url = https://foo/bar/baz + [includeIf "hasconfig:remote.*.url:**/baz"] + path = "$(pwd)/double-star-start" + [includeIf "hasconfig:remote.*.url:**/nomatch"] + path = "$(pwd)/no" + [includeIf "hasconfig:remote.*.url:https:/**"] + path = "$(pwd)/double-star-end" + [includeIf "hasconfig:remote.*.url:nomatch:/**"] + path = "$(pwd)/no" + [includeIf "hasconfig:remote.*.url:https:/**/baz"] + path = "$(pwd)/double-star-middle" + [includeIf "hasconfig:remote.*.url:https:/**/nomatch"] + path = "$(pwd)/no" + [includeIf "hasconfig:remote.*.url:https://*/bar/baz"] + path = "$(pwd)/single-star-middle" + [includeIf "hasconfig:remote.*.url:https://*/baz"] + path = "$(pwd)/no" + EOF + + git -C hasremoteurlTest config --get user.dss && + git -C hasremoteurlTest config --get user.dse && + git -C hasremoteurlTest config --get user.dsm && + git -C hasremoteurlTest config --get user.ssm && + test_must_fail git -C hasremoteurlTest config --get user.no +' + +test_expect_success 'includeIf.hasconfig:remote.*.url forbids remote url in such included files' ' + git init hasremoteurlTest && + test_when_finished "rm -rf hasremoteurlTest" && + + cat >include-with-url <<-\EOF && + [remote "bar"] + url = barurl + EOF + cat >>hasremoteurlTest/.git/config <<-EOF && + [includeIf "hasconfig:remote.*.url:foourl"] + path = "$(pwd)/include-with-url" + EOF + + # test with any Git command + test_must_fail git -C hasremoteurlTest status 2>err && + grep "fatal: remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote.*.url" err +' + test_done diff --git a/t/t1405-main-ref-store.sh b/t/t1405-main-ref-store.sh index 1a3ee8845d..51f8291628 100755 --- a/t/t1405-main-ref-store.sh +++ b/t/t1405-main-ref-store.sh @@ -40,6 +40,12 @@ test_expect_success 'delete_refs(FOO, refs/tags/new-tag)' ' test_must_fail git rev-parse refs/tags/new-tag -- ' +# In reftable, we keep the reflogs around for deleted refs. +test_expect_success !REFFILES 'delete-reflog(FOO, refs/tags/new-tag)' ' + $RUN delete-reflog FOO && + $RUN delete-reflog refs/tags/new-tag +' + test_expect_success 'rename_refs(main, new-main)' ' git rev-parse main >expected && $RUN rename-ref refs/heads/main refs/heads/new-main && @@ -105,7 +111,7 @@ test_expect_success 'delete_reflog(HEAD)' ' test_must_fail git reflog exists HEAD ' -test_expect_success 'create-reflog(HEAD)' ' +test_expect_success REFFILES 'create-reflog(HEAD)' ' $RUN create-reflog HEAD && git reflog exists HEAD ' diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh index 6c941027a8..4e1e84a91f 100755 --- a/t/t1416-ref-transaction-hooks.sh +++ b/t/t1416-ref-transaction-hooks.sh @@ -136,4 +136,54 @@ test_expect_success 'interleaving hook calls succeed' ' test_cmp expect target-repo.git/actual ' +test_expect_success 'hook does not get called on packing refs' ' + # Pack references first such that we are in a known state. + git pack-refs --all && + + write_script .git/hooks/reference-transaction <<-\EOF && + echo "$@" >>actual + cat >>actual + EOF + rm -f actual && + + git update-ref refs/heads/unpacked-ref $POST_OID && + git pack-refs --all && + + # We only expect a single hook invocation, which is the call to + # git-update-ref(1). + cat >expect <<-EOF && + prepared + $ZERO_OID $POST_OID refs/heads/unpacked-ref + committed + $ZERO_OID $POST_OID refs/heads/unpacked-ref + EOF + + test_cmp expect actual +' + +test_expect_success 'deleting packed ref calls hook once' ' + # Create a reference and pack it. + git update-ref refs/heads/to-be-deleted $POST_OID && + git pack-refs --all && + + write_script .git/hooks/reference-transaction <<-\EOF && + echo "$@" >>actual + cat >>actual + EOF + rm -f actual && + + git update-ref -d refs/heads/to-be-deleted $POST_OID && + + # We only expect a single hook invocation, which is the logical + # deletion. + cat >expect <<-EOF && + prepared + $POST_OID $ZERO_OID refs/heads/to-be-deleted + committed + $POST_OID $ZERO_OID refs/heads/to-be-deleted + EOF + + test_cmp expect actual +' + test_done diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh new file mode 100755 index 0000000000..29718aa991 --- /dev/null +++ b/t/t1800-hook.sh @@ -0,0 +1,134 @@ +#!/bin/sh + +test_description='git-hook command' + +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh + +test_expect_success 'git hook usage' ' + test_expect_code 129 git hook && + test_expect_code 129 git hook run && + test_expect_code 129 git hook run -h && + test_expect_code 129 git hook run --unknown 2>err && + grep "unknown option" err +' + +test_expect_success 'git hook run: nonexistent hook' ' + cat >stderr.expect <<-\EOF && + error: cannot find a hook named test-hook + EOF + test_expect_code 1 git hook run test-hook 2>stderr.actual && + test_cmp stderr.expect stderr.actual +' + +test_expect_success 'git hook run: nonexistent hook with --ignore-missing' ' + git hook run --ignore-missing does-not-exist 2>stderr.actual && + test_must_be_empty stderr.actual +' + +test_expect_success 'git hook run: basic' ' + write_script .git/hooks/test-hook <<-EOF && + echo Test hook + EOF + + cat >expect <<-\EOF && + Test hook + EOF + git hook run test-hook 2>actual && + test_cmp expect actual +' + +test_expect_success 'git hook run: stdout and stderr both write to our stderr' ' + write_script .git/hooks/test-hook <<-EOF && + echo >&1 Will end up on stderr + echo >&2 Will end up on stderr + EOF + + cat >stderr.expect <<-\EOF && + Will end up on stderr + Will end up on stderr + EOF + git hook run test-hook >stdout.actual 2>stderr.actual && + test_cmp stderr.expect stderr.actual && + test_must_be_empty stdout.actual +' + +test_expect_success 'git hook run: exit codes are passed along' ' + write_script .git/hooks/test-hook <<-EOF && + exit 1 + EOF + + test_expect_code 1 git hook run test-hook && + + write_script .git/hooks/test-hook <<-EOF && + exit 2 + EOF + + test_expect_code 2 git hook run test-hook && + + write_script .git/hooks/test-hook <<-EOF && + exit 128 + EOF + + test_expect_code 128 git hook run test-hook && + + write_script .git/hooks/test-hook <<-EOF && + exit 129 + EOF + + test_expect_code 129 git hook run test-hook +' + +test_expect_success 'git hook run arg u ments without -- is not allowed' ' + test_expect_code 129 git hook run test-hook arg u ments +' + +test_expect_success 'git hook run -- pass arguments' ' + write_script .git/hooks/test-hook <<-\EOF && + echo $1 + echo $2 + EOF + + cat >expect <<-EOF && + arg + u ments + EOF + + git hook run test-hook -- arg "u ments" 2>actual && + test_cmp expect actual +' + +test_expect_success 'git hook run -- out-of-repo runs excluded' ' + write_script .git/hooks/test-hook <<-EOF && + echo Test hook + EOF + + nongit test_must_fail git hook run test-hook +' + +test_expect_success 'git -c core.hooksPath=<PATH> hook run' ' + mkdir my-hooks && + write_script my-hooks/test-hook <<-\EOF && + echo Hook ran $1 >>actual + EOF + + cat >expect <<-\EOF && + Test hook + Hook ran one + Hook ran two + Hook ran three + Hook ran four + EOF + + # Test various ways of specifying the path. See also + # t1350-config-hooks-path.sh + >actual && + git hook run test-hook -- ignored 2>>actual && + git -c core.hooksPath=my-hooks hook run test-hook -- one 2>>actual && + git -c core.hooksPath=my-hooks/ hook run test-hook -- two 2>>actual && + git -c core.hooksPath="$PWD/my-hooks" hook run test-hook -- three 2>>actual && + git -c core.hooksPath="$PWD/my-hooks/" hook run test-hook -- four 2>>actual && + test_cmp expect actual +' + +test_done diff --git a/t/t2108-update-index-refresh-racy.sh b/t/t2108-update-index-refresh-racy.sh new file mode 100755 index 0000000000..bc5f2886fa --- /dev/null +++ b/t/t2108-update-index-refresh-racy.sh @@ -0,0 +1,64 @@ +#!/bin/sh + +test_description='update-index refresh tests related to racy timestamps' + +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh + +reset_files () { + echo content >file && + echo content >other && + test_set_magic_mtime file && + test_set_magic_mtime other +} + +update_assert_changed () { + test_set_magic_mtime .git/index && + test_might_fail git update-index "$1" && + ! test_is_magic_mtime .git/index +} + +test_expect_success 'setup' ' + reset_files && + # we are calling reset_files() a couple of times during tests; + # test-tool chmtime does not change the ctime; to not weaken + # or even break our tests, disable ctime-checks entirely + git config core.trustctime false && + git add file other && + git commit -m "initial import" +' + +test_expect_success '--refresh has no racy timestamps to fix' ' + reset_files && + # set the index time far enough to the future; + # it must be at least 3 seconds for VFAT + test_set_magic_mtime .git/index +60 && + git update-index --refresh && + test_is_magic_mtime .git/index +60 +' + +test_expect_success '--refresh should fix racy timestamp' ' + reset_files && + update_assert_changed --refresh +' + +test_expect_success '--really-refresh should fix racy timestamp' ' + reset_files && + update_assert_changed --really-refresh +' + +test_expect_success '--refresh should fix racy timestamp if other file needs update' ' + reset_files && + echo content2 >other && + test_set_magic_mtime other && + update_assert_changed --refresh +' + +test_expect_success '--refresh should fix racy timestamp if racy file needs update' ' + reset_files && + echo content2 >file && + test_set_magic_mtime file && + update_assert_changed --refresh +' + +test_done diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 1bc3795847..7a0ff75ba8 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -42,6 +42,23 @@ test_expect_success 'git branch abc should create a branch' ' git branch abc && test_path_is_file .git/refs/heads/abc ' +test_expect_success 'git branch abc should fail when abc exists' ' + test_must_fail git branch abc +' + +test_expect_success 'git branch --force abc should fail when abc is checked out' ' + test_when_finished git switch main && + git switch abc && + test_must_fail git branch --force abc HEAD~1 +' + +test_expect_success 'git branch --force abc should succeed when abc exists' ' + git rev-parse HEAD~1 >expect && + git branch --force abc HEAD~1 && + git rev-parse abc >actual && + test_cmp expect actual +' + test_expect_success 'git branch a/b/c should create a branch' ' git branch a/b/c && test_path_is_file .git/refs/heads/a/b/c ' diff --git a/t/t3207-branch-submodule.sh b/t/t3207-branch-submodule.sh new file mode 100755 index 0000000000..0d93f7516c --- /dev/null +++ b/t/t3207-branch-submodule.sh @@ -0,0 +1,292 @@ +#!/bin/sh + +test_description='git branch submodule tests' + +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-rebase.sh + +pwd=$(pwd) + +# Creates a clean test environment in "pwd" by copying the repo setup +# from test_dirs. +reset_test () { + rm -fr super && + rm -fr sub-sub-upstream && + rm -fr sub-upstream && + cp -r test_dirs/* . +} + +# Tests that the expected branch does not exist +test_no_branch () { + DIR=$1 && + BRANCH_NAME=$2 && + test_must_fail git -C "$DIR" rev-parse "$BRANCH_NAME" 2>err && + grep "ambiguous argument .$BRANCH_NAME." err +} + +test_expect_success 'setup superproject and submodule' ' + mkdir test_dirs && + ( + cd test_dirs && + git init super && + test_commit -C super foo && + git init sub-sub-upstream && + test_commit -C sub-sub-upstream foo && + git init sub-upstream && + # Submodule in a submodule + git -C sub-upstream submodule add "${pwd}/test_dirs/sub-sub-upstream" sub-sub && + git -C sub-upstream commit -m "add submodule" && + # Regular submodule + git -C super submodule add "${pwd}/test_dirs/sub-upstream" sub && + # Submodule in a subdirectory + git -C super submodule add "${pwd}/test_dirs/sub-sub-upstream" second/sub && + git -C super commit -m "add submodule" && + git -C super config submodule.propagateBranches true && + git -C super/sub submodule update --init + ) && + reset_test +' + +# Test the argument parsing +test_expect_success '--recurse-submodules should create branches' ' + test_when_finished "reset_test" && + ( + cd super && + git branch --recurse-submodules branch-a && + git rev-parse branch-a && + git -C sub rev-parse branch-a && + git -C sub/sub-sub rev-parse branch-a && + git -C second/sub rev-parse branch-a + ) +' + +test_expect_success '--recurse-submodules should die if submodule.propagateBranches is false' ' + test_when_finished "reset_test" && + ( + cd super && + echo "fatal: branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled" >expected && + test_must_fail git -c submodule.propagateBranches=false branch --recurse-submodules branch-a 2>actual && + test_cmp expected actual + ) +' + +test_expect_success '--recurse-submodules should fail when not creating branches' ' + test_when_finished "reset_test" && + ( + cd super && + git branch --recurse-submodules branch-a && + echo "fatal: --recurse-submodules can only be used to create branches" >expected && + test_must_fail git branch --recurse-submodules -D branch-a 2>actual && + test_cmp expected actual && + # Assert that the branches were not deleted + git rev-parse branch-a && + git -C sub rev-parse branch-a + ) +' + +test_expect_success 'should respect submodule.recurse when creating branches' ' + test_when_finished "reset_test" && + ( + cd super && + git -c submodule.recurse=true branch branch-a && + git rev-parse branch-a && + git -C sub rev-parse branch-a + ) +' + +test_expect_success 'should ignore submodule.recurse when not creating branches' ' + test_when_finished "reset_test" && + ( + cd super && + git branch --recurse-submodules branch-a && + git -c submodule.recurse=true branch -D branch-a && + test_no_branch . branch-a && + git -C sub rev-parse branch-a + ) +' + +# Test branch creation behavior +test_expect_success 'should create branches based off commit id in superproject' ' + test_when_finished "reset_test" && + ( + cd super && + git branch --recurse-submodules branch-a && + git checkout --recurse-submodules branch-a && + git -C sub rev-parse HEAD >expected && + # Move the tip of sub:branch-a so that it no longer matches the commit in super:branch-a + git -C sub checkout branch-a && + test_commit -C sub bar && + # Create a new branch-b branch with start-point=branch-a + git branch --recurse-submodules branch-b branch-a && + git rev-parse branch-b && + git -C sub rev-parse branch-b >actual && + # Assert that the commit id of sub:second-branch matches super:branch-a and not sub:branch-a + test_cmp expected actual + ) +' + +test_expect_success 'should not create any branches if branch is not valid for all repos' ' + test_when_finished "reset_test" && + ( + cd super && + git -C sub branch branch-a && + test_must_fail git branch --recurse-submodules branch-a 2>actual && + test_no_branch . branch-a && + grep "submodule .sub.: fatal: a branch named .branch-a. already exists" actual + ) +' + +test_expect_success 'should create branches if branch exists and --force is given' ' + test_when_finished "reset_test" && + ( + cd super && + git -C sub rev-parse HEAD >expected && + test_commit -C sub baz && + # branch-a in sub now points to a newer commit. + git -C sub branch branch-a HEAD && + git -C sub rev-parse branch-a >actual-old-branch-a && + git branch --recurse-submodules --force branch-a && + git rev-parse branch-a && + git -C sub rev-parse branch-a >actual-new-branch-a && + test_cmp expected actual-new-branch-a && + # assert that branch --force actually moved the sub + # branch + ! test_cmp expected actual-old-branch-a + ) +' + +test_expect_success 'should create branch when submodule is not in HEAD:.gitmodules' ' + test_when_finished "reset_test" && + ( + cd super && + git branch branch-a && + git checkout -b branch-b && + git submodule add ../sub-upstream sub2 && + git -C sub2 submodule update --init && + # branch-b now has a committed submodule not in branch-a + git commit -m "add second submodule" && + git checkout branch-a && + git branch --recurse-submodules branch-c branch-b && + git checkout --recurse-submodules branch-c && + git -C sub2 rev-parse branch-c && + git -C sub2/sub-sub rev-parse branch-c + ) +' + +test_expect_success 'should not create branches in inactive submodules' ' + test_when_finished "reset_test" && + test_config -C super submodule.sub.active false && + ( + cd super && + git branch --recurse-submodules branch-a && + git rev-parse branch-a && + test_no_branch sub branch-a + ) +' + +test_expect_success 'should set up tracking of local branches with track=always' ' + test_when_finished "reset_test" && + ( + cd super && + git -c branch.autoSetupMerge=always branch --recurse-submodules branch-a main && + git -C sub rev-parse main && + test_cmp_config -C sub . branch.branch-a.remote && + test_cmp_config -C sub refs/heads/main branch.branch-a.merge + ) +' + +test_expect_success 'should set up tracking of local branches with explicit track' ' + test_when_finished "reset_test" && + ( + cd super && + git branch --track --recurse-submodules branch-a main && + git -C sub rev-parse main && + test_cmp_config -C sub . branch.branch-a.remote && + test_cmp_config -C sub refs/heads/main branch.branch-a.merge + ) +' + +test_expect_success 'should not set up unnecessary tracking of local branches' ' + test_when_finished "reset_test" && + ( + cd super && + git branch --recurse-submodules branch-a main && + git -C sub rev-parse main && + test_cmp_config -C sub "" --default "" branch.branch-a.remote && + test_cmp_config -C sub "" --default "" branch.branch-a.merge + ) +' + +reset_remote_test () { + rm -fr super-clone && + reset_test +} + +test_expect_success 'setup tests with remotes' ' + ( + cd test_dirs && + ( + cd super && + git branch branch-a && + git checkout -b branch-b && + git submodule add ../sub-upstream sub2 && + # branch-b now has a committed submodule not in branch-a + git commit -m "add second submodule" + ) && + git clone --branch main --recurse-submodules super super-clone && + git -C super-clone config submodule.propagateBranches true + ) && + reset_remote_test +' + +test_expect_success 'should get fatal error upon branch creation when submodule is not in .git/modules' ' + test_when_finished "reset_remote_test" && + ( + cd super-clone && + # This should succeed because super-clone has sub in .git/modules + git branch --recurse-submodules branch-a origin/branch-a && + # This should fail because super-clone does not have sub2 .git/modules + test_must_fail git branch --recurse-submodules branch-b origin/branch-b 2>actual && + grep "fatal: submodule .sub2.: unable to find submodule" actual && + test_no_branch . branch-b && + test_no_branch sub branch-b && + # User can fix themselves by initializing the submodule + git checkout origin/branch-b && + git submodule update --init --recursive && + git branch --recurse-submodules branch-b origin/branch-b + ) +' + +test_expect_success 'should set up tracking of remote-tracking branches' ' + test_when_finished "reset_remote_test" && + ( + cd super-clone && + git branch --recurse-submodules branch-a origin/branch-a && + test_cmp_config origin branch.branch-a.remote && + test_cmp_config refs/heads/branch-a branch.branch-a.merge && + # "origin/branch-a" does not exist for "sub", but it matches the refspec + # so tracking should be set up + test_cmp_config -C sub origin branch.branch-a.remote && + test_cmp_config -C sub refs/heads/branch-a branch.branch-a.merge && + test_cmp_config -C sub/sub-sub origin branch.branch-a.remote && + test_cmp_config -C sub/sub-sub refs/heads/branch-a branch.branch-a.merge + ) +' + +test_expect_success 'should not fail when unable to set up tracking in submodule' ' + test_when_finished "reset_remote_test" && + ( + cd super-clone && + git remote rename origin ex-origin && + git branch --recurse-submodules branch-a ex-origin/branch-a && + test_cmp_config ex-origin branch.branch-a.remote && + test_cmp_config refs/heads/branch-a branch.branch-a.merge && + test_cmp_config -C sub "" --default "" branch.branch-a.remote && + test_cmp_config -C sub "" --default "" branch.branch-a.merge + ) +' + +test_done diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh index 77a313f62e..d17b450e81 100755 --- a/t/t3406-rebase-message.sh +++ b/t/t3406-rebase-message.sh @@ -105,6 +105,29 @@ test_expect_success 'GIT_REFLOG_ACTION' ' test_cmp expect actual ' +test_expect_success 'rebase --apply reflog' ' + git checkout -b reflog-apply start && + old_head_reflog="$(git log -g --format=%gs -1 HEAD)" && + + git rebase --apply Y && + + git log -g --format=%gs -4 HEAD >actual && + cat >expect <<-EOF && + rebase finished: returning to refs/heads/reflog-apply + rebase: Z + rebase: checkout Y + $old_head_reflog + EOF + test_cmp expect actual && + + git log -g --format=%gs -2 reflog-apply >actual && + cat >expect <<-EOF && + rebase finished: refs/heads/reflog-apply onto $(git rev-parse Y) + branch: Created from start + EOF + test_cmp expect actual +' + test_expect_success 'rebase -i onto unrelated history' ' git init unrelated && test_commit -C unrelated 1 && diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh index 19c6f4acbf..1e9f7833dd 100755 --- a/t/t3412-rebase-root.sh +++ b/t/t3412-rebase-root.sh @@ -11,7 +11,7 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME log_with_names () { git rev-list --topo-order --parents --pretty="tformat:%s" HEAD | - git name-rev --stdin --name-only --refs=refs/heads/$1 + git name-rev --annotate-stdin --name-only --refs=refs/heads/$1 } diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh index 22eca73aa3..130e2f9b55 100755 --- a/t/t3418-rebase-continue.sh +++ b/t/t3418-rebase-continue.sh @@ -308,4 +308,30 @@ 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_orig_head_helper () { + test_when_finished 'git rebase --abort && + git checkout topic && + git reset --hard commit-new-file-F2-on-topic-branch' && + git update-ref -d ORIG_HEAD && + test_must_fail git rebase "$@" && + test_cmp_rev ORIG_HEAD commit-new-file-F2-on-topic-branch +} + +test_orig_head () { + type=$1 + test_expect_success "rebase $type sets ORIG_HEAD correctly" ' + git checkout topic && + git reset --hard commit-new-file-F2-on-topic-branch && + test_orig_head_helper $type main + ' + + test_expect_success "rebase $type <upstream> <branch> sets ORIG_HEAD correctly" ' + git checkout main && + test_orig_head_helper $type main topic + ' +} + +test_orig_head --apply +test_orig_head --merge + test_done diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 207714655f..94537a6b40 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -326,7 +326,9 @@ test_expect_success 'correct message when there is nothing to do' ' test_expect_success 'setup again' ' git reset --hard && test_chmod +x file && - echo content >>file + echo content >>file && + test_write_lines A B C D>file2 && + git add file2 ' # Write the patch file with a new line at the top and bottom @@ -341,13 +343,27 @@ test_expect_success 'setup patch' ' content +lastline \ No newline at end of file + diff --git a/file2 b/file2 + index 8422d40..35b930a 100644 + --- a/file2 + +++ b/file2 + @@ -1,4 +1,5 @@ + -A + +Z + B + +Y + C + -D + +X EOF ' # Expected output, diff is similar to the patch but w/ diff at the top test_expect_success 'setup expected' ' echo diff --git a/file b/file >expected && - cat patch |sed "/^index/s/ 100644/ 100755/" >>expected && + sed -e "/^index 180b47c/s/ 100644/ 100755/" \ + -e /1,5/s//1,4/ \ + -e /Y/d patch >>expected && cat >expected-output <<-\EOF --- a/file +++ b/file @@ -366,6 +382,28 @@ test_expect_success 'setup expected' ' content +lastline \ No newline at end of file + --- a/file2 + +++ b/file2 + @@ -1,4 +1,5 @@ + -A + +Z + B + +Y + C + -D + +X + @@ -1,2 +1,2 @@ + -A + +Z + B + @@ -2,2 +2,3 @@ + B + +Y + C + @@ -3,2 +4,2 @@ + C + -D + +X EOF ' @@ -373,9 +411,9 @@ test_expect_success 'setup expected' ' test_expect_success 'add first line works' ' git commit -am "clear local changes" && git apply patch && - printf "%s\n" s y y | git add -p file 2>error | - sed -n -e "s/^([1-2]\/[1-2]) Stage this hunk[^@]*\(@@ .*\)/\1/" \ - -e "/^[-+@ \\\\]"/p >output && + test_write_lines s y y s y n y | git add -p 2>error >raw-output && + sed -n -e "s/^([1-9]\/[1-9]) Stage this hunk[^@]*\(@@ .*\)/\1/" \ + -e "/^[-+@ \\\\]"/p raw-output >output && test_must_be_empty error && git diff --cached >diff && diff_cmp expected diff && diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 686747e55a..b149e2af44 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -1272,7 +1272,6 @@ test_expect_success 'stash works when user.name and user.email are not set' ' >2 && git add 2 && test_config user.useconfigonly true && - test_config stash.usebuiltin true && ( sane_unset GIT_AUTHOR_NAME && sane_unset GIT_AUTHOR_EMAIL && @@ -1323,20 +1322,6 @@ test_expect_success 'stash handles skip-worktree entries nicely' ' git rev-parse --verify refs/stash:A.t ' -test_expect_success 'stash -c stash.useBuiltin=false warning ' ' - expected="stash.useBuiltin support has been removed" && - - git -c stash.useBuiltin=false stash 2>err && - test_i18ngrep "$expected" err && - env GIT_TEST_STASH_USE_BUILTIN=false git stash 2>err && - test_i18ngrep "$expected" err && - - git -c stash.useBuiltin=true stash 2>err && - test_must_be_empty err && - env GIT_TEST_STASH_USE_BUILTIN=true git stash 2>err && - test_must_be_empty err -' - test_expect_success 'git stash succeeds despite directory/file change' ' test_create_repo directory_file_switch_v1 && ( diff --git a/t/t4069-remerge-diff.sh b/t/t4069-remerge-diff.sh new file mode 100755 index 0000000000..35f94957fc --- /dev/null +++ b/t/t4069-remerge-diff.sh @@ -0,0 +1,291 @@ +#!/bin/sh + +test_description='remerge-diff handling' + +. ./test-lib.sh + +# This test is ort-specific +if test "${GIT_TEST_MERGE_ALGORITHM}" != ort +then + skip_all="GIT_TEST_MERGE_ALGORITHM != ort" + test_done +fi + +test_expect_success 'setup basic merges' ' + test_write_lines 1 2 3 4 5 6 7 8 9 >numbers && + git add numbers && + git commit -m base && + + git branch feature_a && + git branch feature_b && + git branch feature_c && + + git branch ab_resolution && + git branch bc_resolution && + + git checkout feature_a && + test_write_lines 1 2 three 4 5 6 7 eight 9 >numbers && + git commit -a -m change_a && + + git checkout feature_b && + test_write_lines 1 2 tres 4 5 6 7 8 9 >numbers && + git commit -a -m change_b && + + git checkout feature_c && + test_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers && + git commit -a -m change_c && + + git checkout bc_resolution && + git merge --ff-only feature_b && + # no conflict + git merge feature_c && + + git checkout ab_resolution && + git merge --ff-only feature_a && + # conflicts! + test_must_fail git merge feature_b && + # Resolve conflict...and make another change elsewhere + test_write_lines 1 2 drei 4 5 6 7 acht 9 >numbers && + git add numbers && + git merge --continue +' + +test_expect_success 'remerge-diff on a clean merge' ' + git log -1 --oneline bc_resolution >expect && + git show --oneline --remerge-diff bc_resolution >actual && + test_cmp expect actual +' + +test_expect_success 'remerge-diff with both a resolved conflict and an unrelated change' ' + git log -1 --oneline ab_resolution >tmp && + cat <<-EOF >>tmp && + diff --git a/numbers b/numbers + remerge CONFLICT (content): Merge conflict in numbers + index a1fb731..6875544 100644 + --- a/numbers + +++ b/numbers + @@ -1,13 +1,9 @@ + 1 + 2 + -<<<<<<< b0ed5cb (change_a) + -three + -======= + -tres + ->>>>>>> 6cd3f82 (change_b) + +drei + 4 + 5 + 6 + 7 + -eight + +acht + 9 + EOF + # Hashes above are sha1; rip them out so test works with sha256 + sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect && + + git show --oneline --remerge-diff ab_resolution >tmp && + sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual && + test_cmp expect actual +' + +test_expect_success 'setup non-content conflicts' ' + git switch --orphan base && + + test_write_lines 1 2 3 4 5 6 7 8 9 >numbers && + test_write_lines a b c d e f g h i >letters && + test_write_lines in the way >content && + git add numbers letters content && + git commit -m base && + + git branch side1 && + git branch side2 && + + git checkout side1 && + test_write_lines 1 2 three 4 5 6 7 8 9 >numbers && + git mv letters letters_side1 && + git mv content file_or_directory && + git add numbers && + git commit -m side1 && + + git checkout side2 && + git rm numbers && + git mv letters letters_side2 && + mkdir file_or_directory && + echo hello >file_or_directory/world && + git add file_or_directory/world && + git commit -m side2 && + + git checkout -b resolution side1 && + test_must_fail git merge side2 && + test_write_lines 1 2 three 4 5 6 7 8 9 >numbers && + git add numbers && + git add letters_side1 && + git rm letters && + git rm letters_side2 && + git add file_or_directory~HEAD && + git mv file_or_directory~HEAD wanted_content && + git commit -m resolved +' + +test_expect_success 'remerge-diff with non-content conflicts' ' + git log -1 --oneline resolution >tmp && + cat <<-EOF >>tmp && + diff --git a/file_or_directory~HASH (side1) b/wanted_content + similarity index 100% + rename from file_or_directory~HASH (side1) + rename to wanted_content + remerge CONFLICT (file/directory): directory in the way of file_or_directory from HASH (side1); moving it to file_or_directory~HASH (side1) instead. + diff --git a/letters b/letters + remerge CONFLICT (rename/rename): letters renamed to letters_side1 in HASH (side1) and to letters_side2 in HASH (side2). + diff --git a/letters_side2 b/letters_side2 + deleted file mode 100644 + index b236ae5..0000000 + --- a/letters_side2 + +++ /dev/null + @@ -1,9 +0,0 @@ + -a + -b + -c + -d + -e + -f + -g + -h + -i + diff --git a/numbers b/numbers + remerge CONFLICT (modify/delete): numbers deleted in HASH (side2) and modified in HASH (side1). Version HASH (side1) of numbers left in tree. + EOF + # We still have some sha1 hashes above; rip them out so test works + # with sha256 + sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect && + + git show --oneline --remerge-diff resolution >tmp && + sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual && + test_cmp expect actual +' + +test_expect_success 'remerge-diff w/ diff-filter=U: all conflict headers, no diff content' ' + git log -1 --oneline resolution >tmp && + cat <<-EOF >>tmp && + diff --git a/file_or_directory~HASH (side1) b/file_or_directory~HASH (side1) + remerge CONFLICT (file/directory): directory in the way of file_or_directory from HASH (side1); moving it to file_or_directory~HASH (side1) instead. + diff --git a/letters b/letters + remerge CONFLICT (rename/rename): letters renamed to letters_side1 in HASH (side1) and to letters_side2 in HASH (side2). + diff --git a/numbers b/numbers + remerge CONFLICT (modify/delete): numbers deleted in HASH (side2) and modified in HASH (side1). Version HASH (side1) of numbers left in tree. + EOF + # We still have some sha1 hashes above; rip them out so test works + # with sha256 + sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect && + + git show --oneline --remerge-diff --diff-filter=U resolution >tmp && + sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual && + test_cmp expect actual +' + +test_expect_success 'remerge-diff w/ diff-filter=R: relevant file + conflict header' ' + git log -1 --oneline resolution >tmp && + cat <<-EOF >>tmp && + diff --git a/file_or_directory~HASH (side1) b/wanted_content + similarity index 100% + rename from file_or_directory~HASH (side1) + rename to wanted_content + remerge CONFLICT (file/directory): directory in the way of file_or_directory from HASH (side1); moving it to file_or_directory~HASH (side1) instead. + EOF + # We still have some sha1 hashes above; rip them out so test works + # with sha256 + sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect && + + git show --oneline --remerge-diff --diff-filter=R resolution >tmp && + sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual && + test_cmp expect actual +' + +test_expect_success 'remerge-diff w/ pathspec: limits to relevant file including conflict header' ' + git log -1 --oneline resolution >tmp && + cat <<-EOF >>tmp && + diff --git a/letters b/letters + remerge CONFLICT (rename/rename): letters renamed to letters_side1 in HASH (side1) and to letters_side2 in HASH (side2). + diff --git a/letters_side2 b/letters_side2 + deleted file mode 100644 + index b236ae5..0000000 + --- a/letters_side2 + +++ /dev/null + @@ -1,9 +0,0 @@ + -a + -b + -c + -d + -e + -f + -g + -h + -i + EOF + # We still have some sha1 hashes above; rip them out so test works + # with sha256 + sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect && + + git show --oneline --remerge-diff resolution -- "letters*" >tmp && + sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual && + test_cmp expect actual +' + +test_expect_success 'setup non-content conflicts' ' + git switch --orphan newbase && + + test_write_lines 1 2 3 4 5 6 7 8 9 >numbers && + git add numbers && + git commit -m base && + + git branch newside1 && + git branch newside2 && + + git checkout newside1 && + test_write_lines 1 2 three 4 5 6 7 8 9 >numbers && + git add numbers && + git commit -m side1 && + + git checkout newside2 && + test_write_lines 1 2 drei 4 5 6 7 8 9 >numbers && + git add numbers && + git commit -m side2 && + + git checkout -b newresolution newside1 && + test_must_fail git merge newside2 && + git checkout --theirs numbers && + git add -u numbers && + git commit -m resolved +' + +test_expect_success 'remerge-diff turns off history simplification' ' + git log -1 --oneline newresolution >tmp && + cat <<-EOF >>tmp && + diff --git a/numbers b/numbers + remerge CONFLICT (content): Merge conflict in numbers + index 070e9e7..5335e78 100644 + --- a/numbers + +++ b/numbers + @@ -1,10 +1,6 @@ + 1 + 2 + -<<<<<<< 96f1e45 (side1) + -three + -======= + drei + ->>>>>>> 4fd522f (side2) + 4 + 5 + 6 + EOF + # We still have some sha1 hashes above; rip them out so test works + # with sha256 + sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect && + + git show --oneline --remerge-diff newresolution -- numbers >tmp && + sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 5049559861..a20d578349 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -142,6 +142,19 @@ test_expect_success 'diff-filter=R' ' ' +test_expect_success 'multiple --diff-filter bits' ' + + git log -M --pretty="format:%s" --diff-filter=R HEAD >expect && + git log -M --pretty="format:%s" --diff-filter=Ra HEAD >actual && + test_cmp expect actual && + git log -M --pretty="format:%s" --diff-filter=aR HEAD >actual && + test_cmp expect actual && + git log -M --pretty="format:%s" \ + --diff-filter=a --diff-filter=R HEAD >actual && + test_cmp expect actual + +' + test_expect_success 'diff-filter=C' ' git log -C -C --pretty="format:%s" --diff-filter=C HEAD >actual && @@ -659,7 +672,7 @@ EOF test_expect_success 'log --graph with full output' ' git log --graph --date-order --pretty=short | - git name-rev --name-only --stdin | + git name-rev --name-only --annotate-stdin | sed "s/Merge:.*/Merge: A B/;s/ *\$//" >actual && test_cmp expect actual ' diff --git a/t/t4204-patch-id.sh b/t/t4204-patch-id.sh index 80f4a65b28..a730c0db98 100755 --- a/t/t4204-patch-id.sh +++ b/t/t4204-patch-id.sh @@ -38,7 +38,7 @@ calc_patch_id () { shift git patch-id "$@" >patch-id.output && sed "s/ .*//" patch-id.output >patch-id_"$patch_name" && - test_line_count -gt 0 patch-id_"$patch_name" + test_line_count -eq 1 patch-id_"$patch_name" } get_top_diff () { @@ -166,40 +166,67 @@ test_expect_success 'patch-id respects config from subdir' ' ) ' -cat >nonl <<\EOF -diff --git i/a w/a -index e69de29..2e65efe 100644 ---- i/a -+++ w/a -@@ -0,0 +1 @@ -+a -\ No newline at end of file -diff --git i/b w/b -index e69de29..6178079 100644 ---- i/b -+++ w/b -@@ -0,0 +1 @@ -+b -EOF - -cat >withnl <<\EOF -diff --git i/a w/a -index e69de29..7898192 100644 ---- i/a -+++ w/a -@@ -0,0 +1 @@ -+a -diff --git i/b w/b -index e69de29..6178079 100644 ---- i/b -+++ w/b -@@ -0,0 +1 @@ -+b -EOF - test_expect_success 'patch-id handles no-nl-at-eof markers' ' - cat nonl | calc_patch_id nonl && - cat withnl | calc_patch_id withnl && + cat >nonl <<-\EOF && + diff --git i/a w/a + index e69de29..2e65efe 100644 + --- i/a + +++ w/a + @@ -0,0 +1 @@ + +a + \ No newline at end of file + diff --git i/b w/b + index e69de29..6178079 100644 + --- i/b + +++ w/b + @@ -0,0 +1 @@ + +b + EOF + cat >withnl <<-\EOF && + diff --git i/a w/a + index e69de29..7898192 100644 + --- i/a + +++ w/a + @@ -0,0 +1 @@ + +a + diff --git i/b w/b + index e69de29..6178079 100644 + --- i/b + +++ w/b + @@ -0,0 +1 @@ + +b + EOF + calc_patch_id nonl <nonl && + calc_patch_id withnl <withnl && test_cmp patch-id_nonl patch-id_withnl ' + +test_expect_success 'patch-id handles diffs with one line of before/after' ' + cat >diffu1 <<-\EOF && + diff --git a/bar b/bar + index bdaf90f..31051f6 100644 + --- a/bar + +++ b/bar + @@ -2 +2,2 @@ + b + +c + diff --git a/car b/car + index 00750ed..2ae5e34 100644 + --- a/car + +++ b/car + @@ -1 +1,2 @@ + 3 + +d + diff --git a/foo b/foo + index e439850..7146eb8 100644 + --- a/foo + +++ b/foo + @@ -2 +2,2 @@ + a + +e + EOF + calc_patch_id diffu1 <diffu1 && + test_config patchid.stable true && + calc_patch_id diffu1stable <diffu1 +' test_done diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh index d05ab716f6..f775fc1ce6 100755 --- a/t/t5310-pack-bitmaps.sh +++ b/t/t5310-pack-bitmaps.sh @@ -397,4 +397,32 @@ test_expect_success 'pack.preferBitmapTips' ' ) ' +test_expect_success 'complains about multiple pack bitmaps' ' + rm -fr repo && + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + test_commit base && + + git repack -adb && + bitmap="$(ls .git/objects/pack/pack-*.bitmap)" && + mv "$bitmap" "$bitmap.bak" && + + test_commit other && + git repack -ab && + + mv "$bitmap.bak" "$bitmap" && + + find .git/objects/pack -type f -name "*.pack" >packs && + find .git/objects/pack -type f -name "*.bitmap" >bitmaps && + test_line_count = 2 packs && + test_line_count = 2 bitmaps && + + git rev-list --use-bitmap-index HEAD 2>err && + grep "ignoring extra bitmap file" err + ) +' + test_done diff --git a/t/t5312-prune-corruption.sh b/t/t5312-prune-corruption.sh index ea889c088a..9d8e249ae8 100755 --- a/t/t5312-prune-corruption.sh +++ b/t/t5312-prune-corruption.sh @@ -22,8 +22,8 @@ test_expect_success 'disable reflogs' ' ' create_bogus_ref () { - test_when_finished 'rm -f .git/refs/heads/bogus..name' && - echo $bogus >.git/refs/heads/bogus..name + test-tool ref-store main update-ref msg "refs/heads/bogus..name" $bogus $ZERO_OID REF_SKIP_REFNAME_VERIFICATION && + test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/bogus..name" } test_expect_success 'create history reachable only from a bogus-named ref' ' @@ -113,7 +113,7 @@ test_expect_success 'pack-refs does not silently delete broken loose ref' ' # we do not want to count on running pack-refs to # actually pack it, as it is perfectly reasonable to # skip processing a broken ref -test_expect_success 'create packed-refs file with broken ref' ' +test_expect_success REFFILES 'create packed-refs file with broken ref' ' rm -f .git/refs/heads/main && cat >.git/packed-refs <<-EOF && $missing refs/heads/main @@ -124,13 +124,13 @@ test_expect_success 'create packed-refs file with broken ref' ' test_cmp expect actual ' -test_expect_success 'pack-refs does not silently delete broken packed ref' ' +test_expect_success REFFILES 'pack-refs does not silently delete broken packed ref' ' git pack-refs --all --prune && git rev-parse refs/heads/main >actual && test_cmp expect actual ' -test_expect_success 'pack-refs does not drop broken refs during deletion' ' +test_expect_success REFFILES 'pack-refs does not drop broken refs during deletion' ' git update-ref -d refs/heads/other && git rev-parse refs/heads/main >actual && test_cmp expect actual diff --git a/t/t5326-multi-pack-bitmaps.sh b/t/t5326-multi-pack-bitmaps.sh index e187f90f29..4fe57414c1 100755 --- a/t/t5326-multi-pack-bitmaps.sh +++ b/t/t5326-multi-pack-bitmaps.sh @@ -9,125 +9,13 @@ test_description='exercise basic multi-pack bitmap functionality' GIT_TEST_MULTI_PACK_INDEX=0 GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 -objdir=.git/objects -midx=$objdir/pack/multi-pack-index +# This test exercise multi-pack bitmap functionality where the object order is +# stored and read from a special chunk within the MIDX, so use the default +# behavior here. +sane_unset GIT_TEST_MIDX_WRITE_REV +sane_unset GIT_TEST_MIDX_READ_RIDX -# midx_pack_source <obj> -midx_pack_source () { - test-tool read-midx --show-objects .git/objects | grep "^$1 " | cut -f2 -} - -setup_bitmap_history - -test_expect_success 'enable core.multiPackIndex' ' - git config core.multiPackIndex true -' - -test_expect_success 'create single-pack midx with bitmaps' ' - git repack -ad && - git multi-pack-index write --bitmap && - test_path_is_file $midx && - test_path_is_file $midx-$(midx_checksum $objdir).bitmap && - test_path_is_file $midx-$(midx_checksum $objdir).rev -' - -basic_bitmap_tests - -test_expect_success 'create new additional packs' ' - for i in $(test_seq 1 16) - do - test_commit "$i" && - git repack -d || return 1 - done && - - git checkout -b other2 HEAD~8 && - for i in $(test_seq 1 8) - do - test_commit "side-$i" && - git repack -d || return 1 - done && - git checkout second -' - -test_expect_success 'create multi-pack midx with bitmaps' ' - git multi-pack-index write --bitmap && - - ls $objdir/pack/pack-*.pack >packs && - test_line_count = 25 packs && - - test_path_is_file $midx && - test_path_is_file $midx-$(midx_checksum $objdir).bitmap && - test_path_is_file $midx-$(midx_checksum $objdir).rev -' - -basic_bitmap_tests - -test_expect_success '--no-bitmap is respected when bitmaps exist' ' - git multi-pack-index write --bitmap && - - test_commit respect--no-bitmap && - git repack -d && - - test_path_is_file $midx && - test_path_is_file $midx-$(midx_checksum $objdir).bitmap && - test_path_is_file $midx-$(midx_checksum $objdir).rev && - - git multi-pack-index write --no-bitmap && - - test_path_is_file $midx && - test_path_is_missing $midx-$(midx_checksum $objdir).bitmap && - test_path_is_missing $midx-$(midx_checksum $objdir).rev -' - -test_expect_success 'setup midx with base from later pack' ' - # Write a and b so that "a" is a delta on top of base "b", since Git - # prefers to delete contents out of a base rather than add to a shorter - # object. - test_seq 1 128 >a && - test_seq 1 130 >b && - - git add a b && - git commit -m "initial commit" && - - a=$(git rev-parse HEAD:a) && - b=$(git rev-parse HEAD:b) && - - # In the first pack, "a" is stored as a delta to "b". - p1=$(git pack-objects .git/objects/pack/pack <<-EOF - $a - $b - EOF - ) && - - # In the second pack, "a" is missing, and "b" is not a delta nor base to - # any other object. - p2=$(git pack-objects .git/objects/pack/pack <<-EOF - $b - $(git rev-parse HEAD) - $(git rev-parse HEAD^{tree}) - EOF - ) && - - git prune-packed && - # Use the second pack as the preferred source, so that "b" occurs - # earlier in the MIDX object order, rendering "a" unusable for pack - # reuse. - git multi-pack-index write --bitmap --preferred-pack=pack-$p2.idx && - - have_delta $a $b && - test $(midx_pack_source $a) != $(midx_pack_source $b) -' - -rev_list_tests 'full bitmap with backwards delta' - -test_expect_success 'clone with bitmaps enabled' ' - git clone --no-local --bare . clone-reverse-delta.git && - test_when_finished "rm -fr clone-reverse-delta.git" && - - git rev-parse HEAD >expect && - git --git-dir=clone-reverse-delta.git rev-parse HEAD >actual && - test_cmp expect actual -' +midx_bitmap_core bitmap_reuse_tests() { from=$1 @@ -204,17 +92,7 @@ test_expect_success 'missing object closure fails gracefully' ' ) ' -test_expect_success 'setup partial bitmaps' ' - test_commit packed && - git repack && - test_commit loose && - git multi-pack-index write --bitmap 2>err && - test_path_is_file $midx && - test_path_is_file $midx-$(midx_checksum $objdir).bitmap && - test_path_is_file $midx-$(midx_checksum $objdir).rev -' - -basic_bitmap_tests HEAD~ +midx_bitmap_partial_tests test_expect_success 'removing a MIDX clears stale bitmaps' ' rm -fr repo && @@ -228,7 +106,6 @@ test_expect_success 'removing a MIDX clears stale bitmaps' ' # Write a MIDX and bitmap; remove the MIDX but leave the bitmap. stale_bitmap=$midx-$(midx_checksum $objdir).bitmap && - stale_rev=$midx-$(midx_checksum $objdir).rev && rm $midx && # Then write a new MIDX. @@ -238,9 +115,7 @@ test_expect_success 'removing a MIDX clears stale bitmaps' ' test_path_is_file $midx && test_path_is_file $midx-$(midx_checksum $objdir).bitmap && - test_path_is_file $midx-$(midx_checksum $objdir).rev && - test_path_is_missing $stale_bitmap && - test_path_is_missing $stale_rev + test_path_is_missing $stale_bitmap ) ' @@ -261,7 +136,6 @@ test_expect_success 'pack.preferBitmapTips' ' git multi-pack-index write --bitmap && test_path_is_file $midx && test_path_is_file $midx-$(midx_checksum $objdir).bitmap && - test_path_is_file $midx-$(midx_checksum $objdir).rev && test-tool bitmap list-commits | sort >bitmaps && comm -13 bitmaps commits >before && @@ -271,7 +145,6 @@ test_expect_success 'pack.preferBitmapTips' ' <before | git update-ref --stdin && rm -fr $midx-$(midx_checksum $objdir).bitmap && - rm -fr $midx-$(midx_checksum $objdir).rev && rm -fr $midx && git -c pack.preferBitmapTips=refs/tags/include \ @@ -309,7 +182,6 @@ test_expect_success 'writing a bitmap with --refs-snapshot' ' grep "$(git rev-parse two)" bitmaps && rm -fr $midx-$(midx_checksum $objdir).bitmap && - rm -fr $midx-$(midx_checksum $objdir).rev && rm -fr $midx && # Then again, but with a refs snapshot which only sees @@ -354,7 +226,6 @@ test_expect_success 'write a bitmap with --refs-snapshot (preferred tips)' ' ) >snapshot && rm -fr $midx-$(midx_checksum $objdir).bitmap && - rm -fr $midx-$(midx_checksum $objdir).rev && rm -fr $midx && git multi-pack-index write --bitmap --refs-snapshot=snapshot && @@ -395,4 +266,45 @@ test_expect_success 'hash-cache values are propagated from pack bitmaps' ' ) ' +test_expect_success 'no .bitmap is written without any objects' ' + rm -fr repo && + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + empty="$(git pack-objects $objdir/pack/pack </dev/null)" && + cat >packs <<-EOF && + pack-$empty.idx + EOF + + git multi-pack-index write --bitmap --stdin-packs \ + <packs 2>err && + + grep "bitmap without any objects" err && + + test_path_is_file $midx && + test_path_is_missing $midx-$(midx_checksum $objdir).bitmap + ) +' + +test_expect_success 'graceful fallback when missing reverse index' ' + rm -fr repo && + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + test_commit base && + + # write a pack and MIDX bitmap containing base + git repack -adb && + git multi-pack-index write --bitmap && + + GIT_TEST_MIDX_READ_RIDX=0 \ + git rev-list --use-bitmap-index HEAD 2>err && + ! grep "ignoring extra bitmap file" err + ) +' + test_done diff --git a/t/t5327-multi-pack-bitmaps-rev.sh b/t/t5327-multi-pack-bitmaps-rev.sh new file mode 100755 index 0000000000..d30ba632c8 --- /dev/null +++ b/t/t5327-multi-pack-bitmaps-rev.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +test_description='exercise basic multi-pack bitmap functionality (.rev files)' + +. ./test-lib.sh +. "${TEST_DIRECTORY}/lib-bitmap.sh" + +# We'll be writing our own midx and bitmaps, so avoid getting confused by the +# automatic ones. +GIT_TEST_MULTI_PACK_INDEX=0 +GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 + +# Unlike t5326, this test exercise multi-pack bitmap functionality where the +# object order is stored in a separate .rev file. +GIT_TEST_MIDX_WRITE_REV=1 +GIT_TEST_MIDX_READ_RIDX=0 +export GIT_TEST_MIDX_WRITE_REV +export GIT_TEST_MIDX_READ_RIDX + +midx_bitmap_core rev +midx_bitmap_partial_tests rev + +test_done diff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh index 1ec9e23be7..d118181690 100755 --- a/t/t5403-post-checkout-hook.sh +++ b/t/t5403-post-checkout-hook.sh @@ -49,23 +49,60 @@ test_expect_success 'post-checkout receives the right args when not switching br test $old = $new && test $flag = 0 ' -test_expect_success 'post-checkout is triggered on rebase' ' - test_when_finished "rm -f .git/post-checkout.args" && - git checkout -b rebase-test main && - rm -f .git/post-checkout.args && - git rebase rebase-on-me && - read old new flag <.git/post-checkout.args && - test $old != $new && test $flag = 1 -' +test_rebase () { + args="$*" && + test_expect_success "post-checkout is triggered on rebase $args" ' + test_when_finished "rm -f .git/post-checkout.args" && + git checkout -B rebase-test main && + rm -f .git/post-checkout.args && + git rebase $args rebase-on-me && + read old new flag <.git/post-checkout.args && + test_cmp_rev main $old && + test_cmp_rev rebase-on-me $new && + test $flag = 1 + ' + + test_expect_success "post-checkout is triggered on rebase $args with fast-forward" ' + test_when_finished "rm -f .git/post-checkout.args" && + git checkout -B ff-rebase-test rebase-on-me^ && + rm -f .git/post-checkout.args && + git rebase $args rebase-on-me && + read old new flag <.git/post-checkout.args && + test_cmp_rev rebase-on-me^ $old && + test_cmp_rev rebase-on-me $new && + test $flag = 1 + ' + + test_expect_success "rebase $args fast-forward branch checkout runs post-checkout hook" ' + test_when_finished "test_might_fail git rebase --abort" && + test_when_finished "rm -f .git/post-checkout.args" && + git update-ref refs/heads/rebase-fast-forward three && + git checkout two && + rm -f .git/post-checkout.args && + git rebase $args HEAD rebase-fast-forward && + read old new flag <.git/post-checkout.args && + test_cmp_rev two $old && + test_cmp_rev three $new && + test $flag = 1 + ' + + test_expect_success "rebase $args checkout does not remove untracked files" ' + test_when_finished "test_might_fail git rebase --abort" && + test_when_finished "rm -f .git/post-checkout.args" && + git update-ref refs/heads/rebase-fast-forward three && + git checkout two && + rm -f .git/post-checkout.args && + echo untracked >three.t && + test_when_finished "rm three.t" && + test_must_fail git rebase $args HEAD rebase-fast-forward 2>err && + grep "untracked working tree files would be overwritten by checkout" err && + test_path_is_missing .git/post-checkout.args -test_expect_success 'post-checkout is triggered on rebase with fast-forward' ' - test_when_finished "rm -f .git/post-checkout.args" && - git checkout -b ff-rebase-test rebase-on-me^ && - rm -f .git/post-checkout.args && - git rebase rebase-on-me && - read old new flag <.git/post-checkout.args && - test $old != $new && test $flag = 1 ' +} + +test_rebase --apply && +test_rebase --merge test_expect_success 'post-checkout hook is triggered by clone' ' mkdir -p templates/hooks && diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index f0dc4e6968..ee6d2dde9f 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -927,7 +927,8 @@ test_expect_success 'fetching deepen' ' ) ' -test_expect_success 'use ref advertisement to prune "have" lines sent' ' +test_negotiation_algorithm_default () { + test_when_finished rm -rf clientv0 clientv2 && rm -rf server client && git init server && test_commit -C server both_have_1 && @@ -946,7 +947,7 @@ test_expect_success 'use ref advertisement to prune "have" lines sent' ' rm -f trace && cp -r client clientv0 && GIT_TRACE_PACKET="$(pwd)/trace" git -C clientv0 \ - fetch origin server_has both_have_2 && + "$@" fetch origin server_has both_have_2 && grep "have $(git -C client rev-parse client_has)" trace && grep "have $(git -C client rev-parse both_have_2)" trace && ! grep "have $(git -C client rev-parse both_have_2^)" trace && @@ -954,10 +955,27 @@ test_expect_success 'use ref advertisement to prune "have" lines sent' ' rm -f trace && cp -r client clientv2 && GIT_TRACE_PACKET="$(pwd)/trace" git -C clientv2 -c protocol.version=2 \ - fetch origin server_has both_have_2 && + "$@" fetch origin server_has both_have_2 && grep "have $(git -C client rev-parse client_has)" trace && grep "have $(git -C client rev-parse both_have_2)" trace && ! grep "have $(git -C client rev-parse both_have_2^)" trace +} + +test_expect_success 'use ref advertisement to prune "have" lines sent' ' + test_negotiation_algorithm_default +' + +test_expect_success 'same as last but with config overrides' ' + test_negotiation_algorithm_default \ + -c feature.experimental=true \ + -c fetch.negotiationAlgorithm=consecutive +' + +test_expect_success 'ensure bogus fetch.negotiationAlgorithm yields error' ' + test_when_finished rm -rf clientv0 && + cp -r client clientv0 && + test_must_fail git -C clientv0 --fetch.negotiationAlgorithm=bogus \ + fetch origin server_has both_have_2 ' test_expect_success 'filtering by size' ' diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 20f7110ec1..ef0da0a63b 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -164,6 +164,17 @@ test_expect_success 'fetch --prune --tags with refspec prunes based on refspec' git rev-parse sometag ' +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 && + : 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_expect_success 'fetch --atomic works with a single branch' ' test_when_finished "rm -rf \"$D\"/atomic" && diff --git a/t/t5511-refspec.sh b/t/t5511-refspec.sh index be025b90f9..fc55681a3f 100755 --- a/t/t5511-refspec.sh +++ b/t/t5511-refspec.sh @@ -2,6 +2,7 @@ test_description='refspec parsing' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_refspec () { diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 2f04cf9a1c..3137eb8d4d 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -229,6 +229,18 @@ test_expect_success 'push with negotiation proceeds anyway even if negotiation f test_i18ngrep "push negotiation failed" err ' +test_expect_success 'push with negotiation does not attempt to fetch submodules' ' + mk_empty submodule_upstream && + test_commit -C submodule_upstream submodule_commit && + git submodule add ./submodule_upstream submodule && + mk_empty testrepo && + git push testrepo $the_first_commit:refs/remotes/origin/first_commit && + test_commit -C testrepo unrelated_commit && + git -C testrepo config receive.hideRefs refs/remotes/origin/first_commit && + git -c submodule.recurse=true -c protocol.version=2 -c push.negotiate=1 push testrepo refs/heads/main:refs/remotes/origin/main 2>err && + ! grep "Fetching submodule" err +' + test_expect_success 'push without wildcard' ' mk_empty testrepo && @@ -1809,4 +1821,12 @@ test_expect_success 'refuse fetch to current branch of bare repository worktree' git -C bare.git fetch -u .. HEAD:wt ' +test_expect_success 'refuse to push a hidden ref, and make sure do not pollute the repository' ' + mk_empty testrepo && + git -C testrepo config receive.hiderefs refs/hidden && + git -C testrepo config receive.unpackLimit 1 && + test_must_fail git push testrepo HEAD:refs/hidden/foo && + test_dir_is_empty testrepo/.git/objects/pack +' + test_done diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index 93ecfcdd24..081808009b 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -330,6 +330,19 @@ test_expect_success '--rebase --autostash fast forward' ' test_cmp_rev HEAD to-rebase-ff ' +test_expect_success '--rebase with rebase.autostash succeeds on ff' ' + test_when_finished "rm -fr src dst actual" && + git init src && + test_commit -C src "initial" file "content" && + git clone src dst && + test_commit -C src --printf "more_content" file "more content\ncontent\n" && + echo "dirty" >>dst/file && + test_config -C dst rebase.autostash true && + git -C dst pull --rebase >actual 2>&1 && + grep -q "Fast-forward" actual && + grep -q "Applied autostash." actual +' + test_expect_success '--rebase with conflicts shows advice' ' test_when_finished "git rebase --abort; git checkout -f to-rebase" && git checkout -b seq && diff --git a/t/t5700-protocol-v1.sh b/t/t5700-protocol-v1.sh index 468bd3e13e..6c8d4c6cf1 100755 --- a/t/t5700-protocol-v1.sh +++ b/t/t5700-protocol-v1.sh @@ -149,6 +149,21 @@ test_expect_success 'push with file:// using protocol v1' ' grep "push< version 1" log ' +test_expect_success 'cloning branchless tagless but not refless remote' ' + rm -rf server client && + + git -c init.defaultbranch=main init server && + echo foo >server/foo.txt && + git -C server add foo.txt && + git -C server commit -m "message" && + git -C server update-ref refs/notbranch/alsonottag HEAD && + git -C server checkout --detach && + git -C server branch -D main && + git -C server symbolic-ref HEAD refs/heads/nonexistentbranch && + + git -c protocol.version=1 clone "file://$(pwd)/server" client +' + # Test protocol v1 with 'ssh://' transport # test_expect_success 'setup ssh wrapper' ' diff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh index 710f33e2aa..00ce9aec23 100755 --- a/t/t5702-protocol-v2.sh +++ b/t/t5702-protocol-v2.sh @@ -619,7 +619,7 @@ test_expect_success 'usage: --negotiate-only without --negotiation-tip' ' setup_negotiate_only "$SERVER" "$URI" && cat >err.expect <<-\EOF && - fatal: --negotiate-only needs one or more --negotiate-tip=* + fatal: --negotiate-only needs one or more --negotiation-tip=* EOF test_must_fail git -c protocol.version=2 -C client fetch \ @@ -628,6 +628,18 @@ test_expect_success 'usage: --negotiate-only without --negotiation-tip' ' test_cmp err.expect err.actual ' +test_expect_success 'usage: --negotiate-only with --recurse-submodules' ' + cat >err.expect <<-\EOF && + fatal: options '\''--negotiate-only'\'' and '\''--recurse-submodules'\'' cannot be used together + EOF + + test_must_fail git -c protocol.version=2 -C client fetch \ + --negotiate-only \ + --recurse-submodules \ + origin 2>err.actual && + test_cmp err.expect err.actual +' + test_expect_success 'file:// --negotiate-only' ' SERVER="server" && URI="file://$(pwd)/server" && diff --git a/t/t6007-rev-list-cherry-pick-file.sh b/t/t6007-rev-list-cherry-pick-file.sh index aebe4b69e1..6f3e543977 100755 --- a/t/t6007-rev-list-cherry-pick-file.sh +++ b/t/t6007-rev-list-cherry-pick-file.sh @@ -58,7 +58,7 @@ EOF test_expect_success '--left-right' ' git rev-list --left-right B...C > actual && - git name-rev --stdin --name-only --refs="*tags/*" \ + git name-rev --annotate-stdin --name-only --refs="*tags/*" \ < actual > actual.named && test_cmp expect actual.named ' @@ -78,14 +78,14 @@ EOF test_expect_success '--cherry-pick bar does not come up empty' ' git rev-list --left-right --cherry-pick B...C -- bar > actual && - git name-rev --stdin --name-only --refs="*tags/*" \ + git name-rev --annotate-stdin --name-only --refs="*tags/*" \ < actual > actual.named && test_cmp expect actual.named ' test_expect_success 'bar does not come up empty' ' git rev-list --left-right B...C -- bar > actual && - git name-rev --stdin --name-only --refs="*tags/*" \ + git name-rev --annotate-stdin --name-only --refs="*tags/*" \ < actual > actual.named && test_cmp expect actual.named ' @@ -97,14 +97,14 @@ EOF test_expect_success '--cherry-pick bar does not come up empty (II)' ' git rev-list --left-right --cherry-pick F...E -- bar > actual && - git name-rev --stdin --name-only --refs="*tags/*" \ + git name-rev --annotate-stdin --name-only --refs="*tags/*" \ < actual > actual.named && test_cmp expect actual.named ' test_expect_success 'name-rev multiple --refs combine inclusive' ' git rev-list --left-right --cherry-pick F...E -- bar >actual && - git name-rev --stdin --name-only --refs="*tags/F" --refs="*tags/E" \ + git name-rev --annotate-stdin --name-only --refs="*tags/F" --refs="*tags/E" \ <actual >actual.named && test_cmp expect actual.named ' @@ -116,7 +116,7 @@ EOF test_expect_success 'name-rev --refs excludes non-matched patterns' ' git rev-list --left-right --right-only --cherry-pick F...E -- bar >>expect && git rev-list --left-right --cherry-pick F...E -- bar >actual && - git name-rev --stdin --name-only --refs="*tags/F" \ + git name-rev --annotate-stdin --name-only --refs="*tags/F" \ <actual >actual.named && test_cmp expect actual.named ' @@ -128,14 +128,14 @@ EOF test_expect_success 'name-rev --exclude excludes matched patterns' ' git rev-list --left-right --right-only --cherry-pick F...E -- bar >>expect && git rev-list --left-right --cherry-pick F...E -- bar >actual && - git name-rev --stdin --name-only --refs="*tags/*" --exclude="*E" \ + git name-rev --annotate-stdin --name-only --refs="*tags/*" --exclude="*E" \ <actual >actual.named && test_cmp expect actual.named ' test_expect_success 'name-rev --no-refs clears the refs list' ' git rev-list --left-right --cherry-pick F...E -- bar >expect && - git name-rev --stdin --name-only --refs="*tags/F" --refs="*tags/E" --no-refs --refs="*tags/G" \ + git name-rev --annotate-stdin --name-only --refs="*tags/F" --refs="*tags/E" --no-refs --refs="*tags/G" \ <expect >actual && test_cmp expect actual ' @@ -149,7 +149,7 @@ EOF test_expect_success '--cherry-mark' ' git rev-list --cherry-mark F...E -- bar > actual && - git name-rev --stdin --name-only --refs="*tags/*" \ + git name-rev --annotate-stdin --name-only --refs="*tags/*" \ < actual > actual.named && test_cmp expect actual.named ' @@ -163,7 +163,7 @@ EOF test_expect_success '--cherry-mark --left-right' ' git rev-list --cherry-mark --left-right F...E -- bar > actual && - git name-rev --stdin --name-only --refs="*tags/*" \ + git name-rev --annotate-stdin --name-only --refs="*tags/*" \ < actual > actual.named && test_cmp expect actual.named ' @@ -174,14 +174,14 @@ EOF test_expect_success '--cherry-pick --right-only' ' git rev-list --cherry-pick --right-only F...E -- bar > actual && - git name-rev --stdin --name-only --refs="*tags/*" \ + git name-rev --annotate-stdin --name-only --refs="*tags/*" \ < actual > actual.named && test_cmp expect actual.named ' test_expect_success '--cherry-pick --left-only' ' git rev-list --cherry-pick --left-only E...F -- bar > actual && - git name-rev --stdin --name-only --refs="*tags/*" \ + git name-rev --annotate-stdin --name-only --refs="*tags/*" \ < actual > actual.named && test_cmp expect actual.named ' @@ -193,7 +193,7 @@ EOF test_expect_success '--cherry' ' git rev-list --cherry F...E -- bar > actual && - git name-rev --stdin --name-only --refs="*tags/*" \ + git name-rev --annotate-stdin --name-only --refs="*tags/*" \ < actual > actual.named && test_cmp expect actual.named ' diff --git a/t/t6012-rev-list-simplify.sh b/t/t6012-rev-list-simplify.sh index 4f7fa8b6c0..63fcccec32 100755 --- a/t/t6012-rev-list-simplify.sh +++ b/t/t6012-rev-list-simplify.sh @@ -12,17 +12,16 @@ note () { } unnote () { - git name-rev --tags --stdin | sed -e "s|$OID_REGEX (tags/\([^)]*\)) |\1 |g" + git name-rev --tags --annotate-stdin | sed -e "s|$OID_REGEX (tags/\([^)]*\)) |\1 |g" } # -# Create a test repo with interesting commit graph: +# Create a test repo with an interesting commit graph: # -# A--B----------G--H--I--K--L -# \ \ / / -# \ \ / / -# C------E---F J -# \_/ +# A-----B-----G--H--I--K--L +# \ \ / / +# \ \ / / +# C--D--E--F J # # The commits are laid out from left-to-right starting with # the root commit A and terminating at the tip commit L. @@ -142,6 +141,13 @@ check_result 'I B A' --author-date-order -- file check_result 'H' --first-parent -- another-file check_result 'H' --first-parent --topo-order -- another-file +check_result 'L K I H G B A' --first-parent L +check_result 'F E D C' --exclude-first-parent-only F ^L +check_result '' F ^L +check_result 'L K I H G J' L ^F +check_result 'L K I H G B J' --exclude-first-parent-only L ^F +check_result 'L K I H G B' --exclude-first-parent-only --first-parent L ^F + check_result 'E C B A' --full-history E -- lost test_expect_success 'full history simplification without parent' ' printf "%s\n" E C B A >expect && diff --git a/t/t6111-rev-list-treesame.sh b/t/t6111-rev-list-treesame.sh index e07b6070e0..90ff141640 100755 --- a/t/t6111-rev-list-treesame.sh +++ b/t/t6111-rev-list-treesame.sh @@ -23,7 +23,8 @@ note () { } unnote () { - git name-rev --tags --stdin | sed -e "s|$OID_REGEX (tags/\([^)]*\))\([ ]\)|\1\2|g" + git name-rev --tags --annotate-stdin | \ + sed -e "s|$OID_REGEX (tags/\([^)]*\))\([ ]\)|\1\2|g" } test_expect_success setup ' diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index d8af2bb9d2..9781b92aed 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -270,7 +270,7 @@ test_expect_success 'name-rev --all' ' test_cmp expect actual ' -test_expect_success 'name-rev --stdin' ' +test_expect_success 'name-rev --annotate-stdin' ' >expect.unsorted && for rev in $(git rev-list --all) do @@ -278,11 +278,16 @@ test_expect_success 'name-rev --stdin' ' echo "$rev ($name)" >>expect.unsorted || return 1 done && sort <expect.unsorted >expect && - git rev-list --all | git name-rev --stdin >actual.unsorted && + git rev-list --all | git name-rev --annotate-stdin >actual.unsorted && sort <actual.unsorted >actual && test_cmp expect actual ' +test_expect_success 'name-rev --stdin deprecated' " + git rev-list --all | git name-rev --stdin 2>actual && + grep -E 'warning: --stdin is deprecated' actual +" + test_expect_success 'describe --contains with the exact tags' ' echo "A^0" >expect && tag_object=$(git rev-parse refs/tags/A) && diff --git a/t/t6404-recursive-merge.sh b/t/t6404-recursive-merge.sh index eaf48e941e..b8735c6db4 100755 --- a/t/t6404-recursive-merge.sh +++ b/t/t6404-recursive-merge.sh @@ -108,8 +108,13 @@ test_expect_success 'refuse to merge binary files' ' printf "\0\0" >binary-file && git add binary-file && git commit -m binary2 && - test_must_fail git merge F >merge.out 2>merge.err && - grep "Cannot merge binary files: binary-file (HEAD vs. F)" merge.err + if test "$GIT_TEST_MERGE_ALGORITHM" = ort + then + test_must_fail git merge F >merge_output + else + test_must_fail git merge F 2>merge_output + fi && + grep "Cannot merge binary files: binary-file (HEAD vs. F)" merge_output ' test_expect_success 'mark rename/delete as unmerged' ' diff --git a/t/t6406-merge-attr.sh b/t/t6406-merge-attr.sh index 57e6af5eaa..99abefd44b 100755 --- a/t/t6406-merge-attr.sh +++ b/t/t6406-merge-attr.sh @@ -221,8 +221,13 @@ test_expect_success 'binary files with union attribute' ' printf "two\0" >bin.txt && git commit -am two && - test_must_fail git merge bin-main 2>stderr && - grep -i "warning.*cannot merge.*HEAD vs. bin-main" stderr + if test "$GIT_TEST_MERGE_ALGORITHM" = ort + then + test_must_fail git merge bin-main >output + else + test_must_fail git merge bin-main 2>output + fi && + grep -i "warning.*cannot merge.*HEAD vs. bin-main" output ' test_done diff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh index 035edc40b1..f2bc8a7d2a 100755 --- a/t/t6429-merge-sequence-rename-caching.sh +++ b/t/t6429-merge-sequence-rename-caching.sh @@ -697,4 +697,71 @@ test_expect_success 'caching renames only on upstream side, part 2' ' ) ' +# +# The following testcase just creates two simple renames (slightly modified +# on both sides but without conflicting changes), and a directory full of +# files that are otherwise uninteresting. The setup is as follows: +# +# base: unrelated/<BUNCH OF FILES> +# numbers +# values +# upstream: modify: numbers +# modify: values +# topic: add: unrelated/foo +# modify: numbers +# modify: values +# rename: numbers -> sequence +# rename: values -> progression +# +# This is a trivial rename case, but we're curious what happens with a very +# low renameLimit interacting with the restart optimization trying to notice +# that unrelated/ looks like a trivial merge candidate. +# +test_expect_success 'avoid assuming we detected renames' ' + git init redo-weirdness && + ( + cd redo-weirdness && + + mkdir unrelated && + for i in $(test_seq 1 10) + do + >unrelated/$i + done && + test_seq 2 10 >numbers && + test_seq 12 20 >values && + git add numbers values unrelated/ && + git commit -m orig && + + git branch upstream && + git branch topic && + + git switch upstream && + test_seq 1 10 >numbers && + test_seq 11 20 >values && + git add numbers && + git commit -m "Some tweaks" && + + git switch topic && + + >unrelated/foo && + test_seq 2 12 >numbers && + test_seq 12 22 >values && + git add numbers values unrelated/ && + git mv numbers sequence && + git mv values progression && + git commit -m A && + + # + # Actual testing + # + + git switch --detach topic^0 && + + test_must_fail git -c merge.renameLimit=1 rebase upstream && + + git ls-files -u >actual && + ! test_file_is_empty actual + ) +' + test_done diff --git a/t/t7508-status.sh b/t/t7508-status.sh index 05c6c02435..2b7ef6c41a 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -1647,13 +1647,33 @@ test_expect_success '"Initial commit" should not be noted in commit template' ' ' test_expect_success '--no-optional-locks prevents index update' ' - test-tool chmtime =1234567890 .git/index && + test_set_magic_mtime .git/index && git --no-optional-locks status && - test-tool chmtime --get .git/index >out && - grep ^1234567890 out && + test_is_magic_mtime .git/index && git status && - test-tool chmtime --get .git/index >out && - ! grep ^1234567890 out + ! test_is_magic_mtime .git/index +' + +test_expect_success 'racy timestamps will be fixed for clean worktree' ' + echo content >racy-dirty && + echo content >racy-racy && + git add racy* && + git commit -m "racy test files" && + # let status rewrite the index, if necessary; after that we expect + # no more index writes unless caused by racy timestamps; note that + # timestamps may already be racy now (depending on previous tests) + git status && + test_set_magic_mtime .git/index && + git status && + ! test_is_magic_mtime .git/index +' + +test_expect_success 'racy timestamps will be fixed for dirty worktree' ' + echo content2 >racy-dirty && + git status && + test_set_magic_mtime .git/index && + git status && + ! test_is_magic_mtime .git/index ' test_done diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh index e489869dd9..5922fb5bdd 100755 --- a/t/t7700-repack.sh +++ b/t/t7700-repack.sh @@ -312,16 +312,13 @@ test_expect_success 'cleans up MIDX when appropriate' ' checksum=$(midx_checksum $objdir) && test_path_is_file $midx && test_path_is_file $midx-$checksum.bitmap && - test_path_is_file $midx-$checksum.rev && test_commit repack-3 && GIT_TEST_MULTI_PACK_INDEX=0 git repack -Adb --write-midx && test_path_is_file $midx && test_path_is_missing $midx-$checksum.bitmap && - test_path_is_missing $midx-$checksum.rev && test_path_is_file $midx-$(midx_checksum $objdir).bitmap && - test_path_is_file $midx-$(midx_checksum $objdir).rev && test_commit repack-4 && GIT_TEST_MULTI_PACK_INDEX=0 git repack -Adb && @@ -354,7 +351,6 @@ test_expect_success '--write-midx with preferred bitmap tips' ' test_line_count = 1 before && rm -fr $midx-$(midx_checksum $objdir).bitmap && - rm -fr $midx-$(midx_checksum $objdir).rev && rm -fr $midx && # instead of constructing the snapshot ourselves (c.f., the test diff --git a/t/t8007-cat-file-textconv.sh b/t/t8007-cat-file-textconv.sh index eacd49ade6..b067983ba1 100755 --- a/t/t8007-cat-file-textconv.sh +++ b/t/t8007-cat-file-textconv.sh @@ -19,6 +19,48 @@ test_expect_success 'setup ' ' GIT_AUTHOR_NAME=Number2 git commit -a -m Second --date="2010-01-01 20:00:00" ' +test_expect_success 'usage: <bad rev>' ' + cat >expect <<-\EOF && + fatal: Not a valid object name HEAD2 + EOF + test_must_fail git cat-file --textconv HEAD2 2>actual && + test_cmp expect actual +' + +test_expect_success 'usage: <bad rev>:<bad path>' ' + cat >expect <<-\EOF && + fatal: invalid object name '\''HEAD2'\''. + EOF + test_must_fail git cat-file --textconv HEAD2:two.bin 2>actual && + test_cmp expect actual +' + +test_expect_success 'usage: <rev>:<bad path>' ' + cat >expect <<-\EOF && + fatal: path '\''two.bin'\'' does not exist in '\''HEAD'\'' + EOF + test_must_fail git cat-file --textconv HEAD:two.bin 2>actual && + test_cmp expect actual +' + + +test_expect_success 'usage: <rev> with no <path>' ' + cat >expect <<-\EOF && + fatal: <object>:<path> required, only <object> '\''HEAD'\'' given + EOF + test_must_fail git cat-file --textconv HEAD 2>actual && + test_cmp expect actual +' + + +test_expect_success 'usage: <bad rev>:<good (in HEAD) path>' ' + cat >expect <<-\EOF && + fatal: invalid object name '\''HEAD2'\''. + EOF + test_must_fail git cat-file --textconv HEAD2:one.bin 2>actual && + test_cmp expect actual +' + cat >expected <<EOF bin: test version 2 EOF diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index aa0c20499b..84d0f40d76 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -539,7 +539,7 @@ test_expect_success $PREREQ "--validate respects relative core.hooksPath path" ' test_path_is_file my-hooks.ran && cat >expect <<-EOF && fatal: longline.patch: rejected by sendemail-validate hook - fatal: command '"'"'my-hooks/sendemail-validate'"'"' died with exit code 1 + fatal: command '"'"'git hook run --ignore-missing sendemail-validate -- <patch>'"'"' died with exit code 1 warning: no patches were sent EOF test_cmp expect actual @@ -558,7 +558,7 @@ test_expect_success $PREREQ "--validate respects absolute core.hooksPath path" ' test_path_is_file my-hooks.ran && cat >expect <<-EOF && fatal: longline.patch: rejected by sendemail-validate hook - fatal: command '"'"'$hooks_path/sendemail-validate'"'"' died with exit code 1 + fatal: command '"'"'git hook run --ignore-missing sendemail-validate -- <patch>'"'"' died with exit code 1 warning: no patches were sent EOF test_cmp expect actual diff --git a/t/t9102-git-svn-deep-rmdir.sh b/t/t9102-git-svn-deep-rmdir.sh index 7b2049caa0..946ef85eb9 100755 --- a/t/t9102-git-svn-deep-rmdir.sh +++ b/t/t9102-git-svn-deep-rmdir.sh @@ -1,7 +1,6 @@ #!/bin/sh test_description='git svn rmdir' -TEST_PASSES_SANITIZE_LEAK=true . ./lib-git-svn.sh test_expect_success 'initialize repo' ' diff --git a/t/t9123-git-svn-rebuild-with-rewriteroot.sh b/t/t9123-git-svn-rebuild-with-rewriteroot.sh index 3320b1f39c..ead404589e 100755 --- a/t/t9123-git-svn-rebuild-with-rewriteroot.sh +++ b/t/t9123-git-svn-rebuild-with-rewriteroot.sh @@ -5,7 +5,6 @@ test_description='git svn respects rewriteRoot during rebuild' -TEST_PASSES_SANITIZE_LEAK=true . ./lib-git-svn.sh mkdir import diff --git a/t/t9128-git-svn-cmd-branch.sh b/t/t9128-git-svn-cmd-branch.sh index 9871f5abc9..783e3ba0c5 100755 --- a/t/t9128-git-svn-cmd-branch.sh +++ b/t/t9128-git-svn-cmd-branch.sh @@ -5,7 +5,6 @@ test_description='git svn partial-rebuild tests' -TEST_PASSES_SANITIZE_LEAK=true . ./lib-git-svn.sh test_expect_success 'initialize svnrepo' ' diff --git a/t/t9167-git-svn-cmd-branch-subproject.sh b/t/t9167-git-svn-cmd-branch-subproject.sh index d9fd111c10..d8128430a8 100755 --- a/t/t9167-git-svn-cmd-branch-subproject.sh +++ b/t/t9167-git-svn-cmd-branch-subproject.sh @@ -5,7 +5,6 @@ test_description='git svn branch for subproject clones' -TEST_PASSES_SANITIZE_LEAK=true . ./lib-git-svn.sh test_expect_success 'initialize svnrepo' ' diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index 98c6280632..24117cb901 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -1444,6 +1444,144 @@ test_expect_success 'git checkout - with --detach, complete only references' ' EOF ' +test_expect_success 'setup sparse-checkout tests' ' + # set up sparse-checkout repo + git init sparse-checkout && + ( + cd sparse-checkout && + mkdir -p folder1/0/1 folder2/0 folder3 && + touch folder1/0/1/t.txt && + touch folder2/0/t.txt && + touch folder3/t.txt && + git add . && + git commit -am "Initial commit" + ) +' + +test_expect_success 'sparse-checkout completes subcommands' ' + test_completion "git sparse-checkout " <<-\EOF + list Z + init Z + set Z + add Z + reapply Z + disable Z + EOF +' + +test_expect_success 'cone mode sparse-checkout completes directory names' ' + # initialize sparse-checkout definitions + git -C sparse-checkout sparse-checkout set --cone folder1/0 folder3 && + + # test tab completion + ( + cd sparse-checkout && + test_completion "git sparse-checkout set f" <<-\EOF + folder1/ + folder2/ + folder3/ + EOF + ) && + + ( + cd sparse-checkout && + test_completion "git sparse-checkout set folder1/" <<-\EOF + folder1/0/ + EOF + ) && + + ( + cd sparse-checkout && + test_completion "git sparse-checkout set folder1/0/" <<-\EOF + folder1/0/1/ + EOF + ) && + + ( + cd sparse-checkout/folder1 && + test_completion "git sparse-checkout add 0" <<-\EOF + 0/ + EOF + ) +' + +test_expect_success 'cone mode sparse-checkout completes directory names with spaces and accents' ' + # reset sparse-checkout + git -C sparse-checkout sparse-checkout disable && + ( + cd sparse-checkout && + mkdir "directory with spaces" && + mkdir "directory-with-áccent" && + >"directory with spaces/randomfile" && + >"directory-with-áccent/randomfile" && + git add . && + git commit -m "Add directory with spaces and directory with accent" && + git sparse-checkout set --cone "directory with spaces" \ + "directory-with-áccent" && + test_completion "git sparse-checkout add dir" <<-\EOF && + directory with spaces/ + directory-with-áccent/ + EOF + rm -rf "directory with spaces" && + rm -rf "directory-with-áccent" && + git add . && + git commit -m "Remove directory with spaces and directory with accent" + ) +' + +# use FUNNYNAMES to avoid running on Windows, which doesn't permit backslashes or tabs in paths +test_expect_success FUNNYNAMES 'cone mode sparse-checkout completes directory names with backslashes and tabs' ' + # reset sparse-checkout + git -C sparse-checkout sparse-checkout disable && + ( + cd sparse-checkout && + mkdir "directory\with\backslashes" && + mkdir "$(printf "directory\twith\ttabs")" && + >"directory\with\backslashes/randomfile" && + >"$(printf "directory\twith\ttabs")/randomfile" && + git add . && + git commit -m "Add directory with backslashes and directory with tabs" && + git sparse-checkout set --cone "directory\with\backslashes" \ + "$(printf "directory\twith\ttabs")" && + test_completion "git sparse-checkout add dir" <<-\EOF && + directory\with\backslashes/ + directory with tabs/ + EOF + rm -rf "directory\with\backslashes" && + rm -rf "$(printf "directory\twith\ttabs")" && + git add . && + git commit -m "Remove directory with backslashes and directory with tabs" + ) +' + +test_expect_success 'non-cone mode sparse-checkout uses bash completion' ' + # reset sparse-checkout repo to non-cone mode + git -C sparse-checkout sparse-checkout disable && + git -C sparse-checkout sparse-checkout set --no-cone && + + ( + cd sparse-checkout && + # expected to be empty since we have not configured + # custom completion for non-cone mode + test_completion "git sparse-checkout set f" <<-\EOF + + EOF + ) +' + +test_expect_success 'git sparse-checkout set --cone completes directory names' ' + git -C sparse-checkout sparse-checkout disable && + + ( + cd sparse-checkout && + test_completion "git sparse-checkout set --cone f" <<-\EOF + folder1/ + folder2/ + folder3/ + EOF + ) +' + test_expect_success 'git switch - with -d, complete all references' ' test_completion "git switch -d " <<-\EOF HEAD Z @@ -2396,27 +2534,33 @@ test_expect_success 'options with value' ' ' test_expect_success 'sourcing the completion script clears cached commands' ' - __git_compute_all_commands && - verbose test -n "$__git_all_commands" && - . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" && - verbose test -z "$__git_all_commands" + ( + __git_compute_all_commands && + verbose test -n "$__git_all_commands" && + . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" && + verbose test -z "$__git_all_commands" + ) ' test_expect_success 'sourcing the completion script clears cached merge strategies' ' - __git_compute_merge_strategies && - verbose test -n "$__git_merge_strategies" && - . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" && - verbose test -z "$__git_merge_strategies" + ( + __git_compute_merge_strategies && + verbose test -n "$__git_merge_strategies" && + . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" && + verbose test -z "$__git_merge_strategies" + ) ' test_expect_success 'sourcing the completion script clears cached --options' ' - __gitcomp_builtin checkout && - verbose test -n "$__gitcomp_builtin_checkout" && - __gitcomp_builtin notes_edit && - verbose test -n "$__gitcomp_builtin_notes_edit" && - . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" && - verbose test -z "$__gitcomp_builtin_checkout" && - verbose test -z "$__gitcomp_builtin_notes_edit" + ( + __gitcomp_builtin checkout && + verbose test -n "$__gitcomp_builtin_checkout" && + __gitcomp_builtin notes_edit && + verbose test -n "$__gitcomp_builtin_notes_edit" && + . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" && + verbose test -z "$__gitcomp_builtin_checkout" && + verbose test -z "$__gitcomp_builtin_notes_edit" + ) ' test_expect_success 'option aliases are not shown by default' ' @@ -2424,12 +2568,45 @@ test_expect_success 'option aliases are not shown by default' ' ' test_expect_success 'option aliases are shown with GIT_COMPLETION_SHOW_ALL' ' - . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" && - GIT_COMPLETION_SHOW_ALL=1 && export GIT_COMPLETION_SHOW_ALL && - test_completion "git clone --recurs" <<-\EOF - --recurse-submodules Z - --recursive Z - EOF + ( + . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" && + GIT_COMPLETION_SHOW_ALL=1 && export GIT_COMPLETION_SHOW_ALL && + test_completion "git clone --recurs" <<-\EOF + --recurse-submodules Z + --recursive Z + EOF + ) +' + +test_expect_success 'plumbing commands are excluded without GIT_COMPLETION_SHOW_ALL_COMMANDS' ' + ( + . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" && + sane_unset GIT_TESTING_PORCELAIN_COMMAND_LIST && + + # Just mainporcelain, not plumbing commands + run_completion "git c" && + grep checkout out && + ! grep cat-file out + ) +' + +test_expect_success 'all commands are shown with GIT_COMPLETION_SHOW_ALL_COMMANDS (also main non-builtin)' ' + ( + . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" && + GIT_COMPLETION_SHOW_ALL_COMMANDS=1 && + export GIT_COMPLETION_SHOW_ALL_COMMANDS && + sane_unset GIT_TESTING_PORCELAIN_COMMAND_LIST && + + # Both mainporcelain and plumbing commands + run_completion "git c" && + grep checkout out && + grep cat-file out && + + # Check "gitk", a "main" command, but not a built-in + more plumbing + run_completion "git g" && + grep gitk out && + grep get-tar-commit-id out + ) ' test_expect_success '__git_complete' ' diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index c3d38aaccb..85385d2ede 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -1840,3 +1840,36 @@ test_region () { test_readlink () { perl -le 'print readlink($_) for @ARGV' "$@" } + +# Set mtime to a fixed "magic" timestamp in mid February 2009, before we +# run an operation that may or may not touch the file. If the file was +# touched, its timestamp will not accidentally have such an old timestamp, +# as long as your filesystem clock is reasonably correct. To verify the +# timestamp, follow up with test_is_magic_mtime. +# +# An optional increment to the magic timestamp may be specified as second +# argument. +test_set_magic_mtime () { + local inc=${2:-0} && + local mtime=$((1234567890 + $inc)) && + test-tool chmtime =$mtime "$1" && + test_is_magic_mtime "$1" $inc +} + +# Test whether the given file has the "magic" mtime set. This is meant to +# be used in combination with test_set_magic_mtime. +# +# An optional increment to the magic timestamp may be specified as second +# argument. Usually, this should be the same increment which was used for +# the associated test_set_magic_mtime. +test_is_magic_mtime () { + local inc=${2:-0} && + local mtime=$((1234567890 + $inc)) && + echo $mtime >.git/test-mtime-expect && + test-tool chmtime --get "$1" >.git/test-mtime-actual && + test_cmp .git/test-mtime-expect .git/test-mtime-actual + local ret=$? + rm -f .git/test-mtime-expect + rm -f .git/test-mtime-actual + return $ret +} diff --git a/t/test-lib.sh b/t/test-lib.sh index 0f7a137c7d..e4716b0b86 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -449,6 +449,8 @@ unset VISUAL EMAIL LANGUAGE $("$PERL_PATH" -e ' unset XDG_CACHE_HOME unset XDG_CONFIG_HOME unset GITPERLLIB +unset GIT_TRACE2_PARENT_NAME +unset GIT_TRACE2_PARENT_SID TEST_AUTHOR_LOCALNAME=author TEST_AUTHOR_DOMAIN=example.com GIT_AUTHOR_EMAIL=${TEST_AUTHOR_LOCALNAME}@${TEST_AUTHOR_DOMAIN} diff --git a/tmp-objdir.c b/tmp-objdir.c index 3d38eeab66..adf6033549 100644 --- a/tmp-objdir.c +++ b/tmp-objdir.c @@ -79,6 +79,11 @@ static void remove_tmp_objdir_on_signal(int signo) raise(signo); } +void tmp_objdir_discard_objects(struct tmp_objdir *t) +{ + remove_dir_recursively(&t->path, REMOVE_DIR_KEEP_TOPLEVEL); +} + /* * These env_* functions are for setting up the child environment; the * "replace" variant overrides the value of any existing variable with that diff --git a/tmp-objdir.h b/tmp-objdir.h index cda5ec7677..76efc7edee 100644 --- a/tmp-objdir.h +++ b/tmp-objdir.h @@ -47,6 +47,12 @@ int tmp_objdir_migrate(struct tmp_objdir *); int tmp_objdir_destroy(struct tmp_objdir *); /* + * Remove all objects from the temporary object directory, while leaving it + * around so more objects can be added. + */ +void tmp_objdir_discard_objects(struct tmp_objdir *); + +/* * Add the temporary object directory as an alternate object store in the * current process. */ diff --git a/transport.c b/transport.c index 2a3e324154..253d6671b1 100644 --- a/transport.c +++ b/transport.c @@ -1292,7 +1292,7 @@ int transport_push(struct repository *r, &transport_options); trace2_region_leave("transport_push", "get_refs_list", r); - strvec_clear(&transport_options.ref_prefixes); + transport_ls_refs_options_release(&transport_options); if (flags & TRANSPORT_PUSH_ALL) match_flags |= MATCH_REFS_ALL; @@ -1420,6 +1420,12 @@ const struct ref *transport_get_remote_refs(struct transport *transport, return transport->remote_refs; } +void transport_ls_refs_options_release(struct transport_ls_refs_options *opts) +{ + strvec_clear(&opts->ref_prefixes); + free((char *)opts->unborn_head_target); +} + int transport_fetch_refs(struct transport *transport, struct ref *refs) { int rc; diff --git a/transport.h b/transport.h index 3f16e50c19..a0bc6a1e9e 100644 --- a/transport.h +++ b/transport.h @@ -257,15 +257,19 @@ struct transport_ls_refs_options { /* * If unborn_head_target is not NULL, and the remote reports HEAD as * pointing to an unborn branch, transport_get_remote_refs() stores the - * unborn branch in unborn_head_target. It should be freed by the - * caller. + * unborn branch in unborn_head_target. */ - char *unborn_head_target; + const char *unborn_head_target; }; #define TRANSPORT_LS_REFS_OPTIONS_INIT { \ .ref_prefixes = STRVEC_INIT, \ } +/** + * Release the "struct transport_ls_refs_options". + */ +void transport_ls_refs_options_release(struct transport_ls_refs_options *opts); + /* * Retrieve refs from a remote. */ diff --git a/worktree.c b/worktree.c index 6f598dcfcd..e8f6f6ae6f 100644 --- a/worktree.c +++ b/worktree.c @@ -28,13 +28,11 @@ static void add_head_info(struct worktree *wt) { int flags; const char *target; - int ignore_errno; target = refs_resolve_ref_unsafe(get_worktree_ref_store(wt), "HEAD", 0, - &wt->head_oid, &flags, - &ignore_errno); + &wt->head_oid, &flags); if (!target) return; @@ -416,7 +414,6 @@ const struct worktree *find_shared_symref(struct worktree **worktrees, const char *symref_target; struct ref_store *refs; int flags; - int ignore_errno; if (wt->is_bare) continue; @@ -434,8 +431,7 @@ const struct worktree *find_shared_symref(struct worktree **worktrees, refs = get_worktree_ref_store(wt); symref_target = refs_resolve_ref_unsafe(refs, symref, 0, - NULL, &flags, - &ignore_errno); + NULL, &flags); if ((flags & REF_ISSYMREF) && symref_target && !strcmp(symref_target, target)) { existing = wt; @@ -563,7 +559,6 @@ int other_head_refs(each_ref_fn fn, void *cb_data) struct worktree *wt = *p; struct object_id oid; int flag; - int ignore_errno; if (wt->is_current) continue; @@ -573,7 +568,7 @@ int other_head_refs(each_ref_fn fn, void *cb_data) if (refs_resolve_ref_unsafe(get_main_ref_store(the_repository), refname.buf, RESOLVE_REF_READING, - &oid, &flag, &ignore_errno)) + &oid, &flag)) ret = fn(refname.buf, &oid, flag, cb_data); if (ret) break; @@ -463,8 +463,6 @@ int git_mkstemps_mode(char *pattern, int suffix_len, int mode) static const int num_letters = ARRAY_SIZE(letters) - 1; static const char x_pattern[] = "XXXXXX"; static const int num_x = ARRAY_SIZE(x_pattern) - 1; - uint64_t value; - struct timeval tv; char *filename_template; size_t len; int fd, count; @@ -485,12 +483,13 @@ int git_mkstemps_mode(char *pattern, int suffix_len, int mode) * Replace pattern's XXXXXX characters with randomness. * Try TMP_MAX different filenames. */ - gettimeofday(&tv, NULL); - value = ((uint64_t)tv.tv_usec << 16) ^ tv.tv_sec ^ getpid(); filename_template = &pattern[len - num_x - suffix_len]; for (count = 0; count < TMP_MAX; ++count) { - uint64_t v = value; int i; + uint64_t v; + if (csprng_bytes(&v, sizeof(v)) < 0) + return error_errno("unable to get random bytes for temporary file"); + /* Fill in the random bits. */ for (i = 0; i < num_x; i++) { filename_template[i] = letters[v % num_letters]; @@ -506,12 +505,6 @@ int git_mkstemps_mode(char *pattern, int suffix_len, int mode) */ if (errno != EEXIST) break; - /* - * This is a random value. It is only necessary that - * the next TMP_MAX values generated by adding 7777 to - * VALUE are different with (module 2^32). - */ - value += 7777; } /* We return the null string if we can't find a unique file name. */ pattern[0] = '\0'; @@ -702,3 +695,69 @@ int open_nofollow(const char *path, int flags) return open(path, flags); #endif } + +int csprng_bytes(void *buf, size_t len) +{ +#if defined(HAVE_ARC4RANDOM) || defined(HAVE_ARC4RANDOM_LIBBSD) + /* This function never returns an error. */ + arc4random_buf(buf, len); + return 0; +#elif defined(HAVE_GETRANDOM) + ssize_t res; + char *p = buf; + while (len) { + res = getrandom(p, len, 0); + if (res < 0) + return -1; + len -= res; + p += res; + } + return 0; +#elif defined(HAVE_GETENTROPY) + int res; + char *p = buf; + while (len) { + /* getentropy has a maximum size of 256 bytes. */ + size_t chunk = len < 256 ? len : 256; + res = getentropy(p, chunk); + if (res < 0) + return -1; + len -= chunk; + p += chunk; + } + return 0; +#elif defined(HAVE_RTLGENRANDOM) + if (!RtlGenRandom(buf, len)) + return -1; + return 0; +#elif defined(HAVE_OPENSSL_CSPRNG) + int res = RAND_bytes(buf, len); + if (res == 1) + return 0; + if (res == -1) + errno = ENOTSUP; + else + errno = EIO; + return -1; +#else + ssize_t res; + char *p = buf; + int fd, err; + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) + return -1; + while (len) { + res = xread(fd, p, len); + if (res < 0) { + err = errno; + close(fd); + errno = err; + return -1; + } + len -= res; + p += res; + } + close(fd); + return 0; +#endif +} |