diff options
224 files changed, 5544 insertions, 1873 deletions
@@ -59,8 +59,9 @@ David Reiss <dreiss@facebook.com> <dreiss@dreiss-vmware.(none)> David S. Miller <davem@davemloft.net> David Turner <novalis@novalis.org> <dturner@twopensource.com> David Turner <novalis@novalis.org> <dturner@twosigma.com> -Derrick Stolee <dstolee@microsoft.com> <stolee@gmail.com> -Derrick Stolee <dstolee@microsoft.com> Derrick Stolee via GitGitGadget <gitgitgadget@gmail.com> +Derrick Stolee <derrickstolee@github.com> <stolee@gmail.com> +Derrick Stolee <derrickstolee@github.com> Derrick Stolee via GitGitGadget <gitgitgadget@gmail.com> +Derrick Stolee <derrickstolee@github.com> <dstolee@microsoft.com> Deskin Miller <deskinm@umich.edu> Đoàn Trần Công Danh <congdanhqx@gmail.com> Doan Tran Cong Danh Dirk Süsserott <newsletter@dirk.my1.cc> diff --git a/Documentation/RelNotes/2.36.0.txt b/Documentation/RelNotes/2.36.0.txt index bac3efd715..78e037b865 100644 --- a/Documentation/RelNotes/2.36.0.txt +++ b/Documentation/RelNotes/2.36.0.txt @@ -19,6 +19,23 @@ 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. @@ -41,6 +58,13 @@ Performance, Internal Implementation, Development Support etc. 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 ----------------- @@ -115,6 +139,72 @@ Fixes since v2.35 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). + + * L10n support for a few error messages. + (merge 3d3c23b3a7 bs/forbid-i18n-of-protocol-token-in-fetch-pack later to maint). + + * Test modernization. + (merge d4fe066e4b sy/t0001-use-path-is-helper later to maint). + + * "git log --graph --graph" used to leak a graph structure, and there + was no way to countermand "--graph" that appear earlier on the + command line. A "--no-graph" option has been added and resource + leakage has been plugged. + * 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). @@ -129,3 +219,11 @@ Fixes since v2.35 (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). + (merge 332acc248d ds/mailmap later to maint). 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/clone.txt b/Documentation/config/clone.txt index 7bcfbd18a5..26f4fb137a 100644 --- a/Documentation/config/clone.txt +++ b/Documentation/config/clone.txt @@ -6,3 +6,8 @@ clone.defaultRemoteName:: clone.rejectShallow:: Reject to clone a repository if it is a shallow one, can be overridden by passing option `--reject-shallow` in command line. See linkgit:git-clone[1] + +clone.filterSubmodules:: + If a partial clone filter is provided (see `--filter` in + linkgit:git-rev-list[1]) and `--recurse-submodules` is used, also apply + the filter to submodules. diff --git a/Documentation/config/extensions.txt b/Documentation/config/extensions.txt index 4e23d73cdc..bccaec7a96 100644 --- a/Documentation/config/extensions.txt +++ b/Documentation/config/extensions.txt @@ -6,3 +6,34 @@ extensions.objectFormat:: Note that this setting should only be set by linkgit:git-init[1] or linkgit:git-clone[1]. Trying to change it after initialization will not work and will produce hard-to-diagnose issues. + +extensions.worktreeConfig:: + If enabled, then worktrees will load config settings from the + `$GIT_DIR/config.worktree` file in addition to the + `$GIT_COMMON_DIR/config` file. Note that `$GIT_COMMON_DIR` and + `$GIT_DIR` are the same for the main working tree, while other + working trees have `$GIT_DIR` equal to + `$GIT_COMMON_DIR/worktrees/<id>/`. The settings in the + `config.worktree` file will override settings from any other + config files. ++ +When enabling `extensions.worktreeConfig`, you must be careful to move +certain values from the common config file to the main working tree's +`config.worktree` file, if present: ++ +* `core.worktree` must be moved from `$GIT_COMMON_DIR/config` to + `$GIT_COMMON_DIR/config.worktree`. +* If `core.bare` is true, then it must be moved from `$GIT_COMMON_DIR/config` + to `$GIT_COMMON_DIR/config.worktree`. ++ +It may also be beneficial to adjust the locations of `core.sparseCheckout` +and `core.sparseCheckoutCone` depending on your desire for customizable +sparse-checkout settings for each worktree. By default, the `git +sparse-checkout` builtin enables `extensions.worktreeConfig`, assigns +these config values on a per-worktree basis, and uses the +`$GIT_DIR/info/sparse-checkout` file to specify the sparsity for each +worktree independently. See linkgit:git-sparse-checkout[1] for more +details. ++ +For historical reasons, `extensions.worktreeConfig` is respected +regardless of the `core.repositoryFormatVersion` setting. 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/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/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-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-clone.txt b/Documentation/git-clone.txt index 984d194934..632bd1348e 100644 --- a/Documentation/git-clone.txt +++ b/Documentation/git-clone.txt @@ -16,7 +16,7 @@ SYNOPSIS [--depth <depth>] [--[no-]single-branch] [--no-tags] [--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules] [--[no-]remote-submodules] [--jobs <n>] [--sparse] [--[no-]reject-shallow] - [--filter=<filter>] [--] <repository> + [--filter=<filter> [--also-filter-submodules]] [--] <repository> [<directory>] DESCRIPTION @@ -182,6 +182,11 @@ objects from the source repository into a pack in the cloned repository. at least `<size>`. For more details on filter specifications, see the `--filter` option in linkgit:git-rev-list[1]. +--also-filter-submodules:: + Also apply the partial clone filter to any submodules in the repository. + Requires `--filter` and `--recurse-submodules`. This can be turned on by + default by setting the `clone.filterSubmodules` config option. + --mirror:: Set up a mirror of the source repository. This implies `--bare`. Compared to `--bare`, `--mirror` not only maps local branches of the diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index 2285effb36..bdcfd94b64 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -141,9 +141,13 @@ from all available files. See also <<FILES>>. --worktree:: - Similar to `--local` except that `.git/config.worktree` is + Similar to `--local` except that `$GIT_DIR/config.worktree` is read from or written to if `extensions.worktreeConfig` is - present. If not it's the same as `--local`. + enabled. If not it's the same as `--local`. Note that `$GIT_DIR` + is equal to `$GIT_COMMON_DIR` for the main working tree, but is of + the form `$GIT_DIR/worktrees/<id>/` for other working trees. See + linkgit:git-worktree[1] to learn how to enable + `extensions.worktreeConfig`. -f <config-file>:: --file <config-file>:: 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-sparse-checkout.txt b/Documentation/git-sparse-checkout.txt index b81dbe0654..94dad137b9 100644 --- a/Documentation/git-sparse-checkout.txt +++ b/Documentation/git-sparse-checkout.txt @@ -31,13 +31,21 @@ COMMANDS Describe the patterns in the sparse-checkout file. 'set':: - Enable the necessary config settings - (extensions.worktreeConfig, core.sparseCheckout, - core.sparseCheckoutCone) if they are not already enabled, and - write a set of patterns to the sparse-checkout file from the + Enable the necessary sparse-checkout config settings + (`core.sparseCheckout`, `core.sparseCheckoutCone`, and + `index.sparse`) if they are not already set to the desired values, + and write a set of patterns to the sparse-checkout file from the list of arguments following the 'set' subcommand. Update the working directory to match the new patterns. + +To ensure that adjusting the sparse-checkout settings within a worktree +does not alter the sparse-checkout settings in other worktrees, the 'set' +subcommand will upgrade your repository config to use worktree-specific +config if not already present. The sparsity defined by the arguments to +the 'set' subcommand are stored in the worktree-specific sparse-checkout +file. See linkgit:git-worktree[1] and the documentation of +`extensions.worktreeConfig` in linkgit:git-config[1] for more details. ++ When the `--stdin` option is provided, the patterns are read from standard in as a newline-delimited list instead of from the arguments. + diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt index 7e5f995f77..4d3ab6b9f9 100644 --- a/Documentation/git-submodule.txt +++ b/Documentation/git-submodule.txt @@ -133,7 +133,7 @@ If you really want to remove a submodule from the repository and commit that use linkgit:git-rm[1] instead. See linkgit:gitsubmodules[7] for removal options. -update [--init] [--remote] [-N|--no-fetch] [--[no-]recommend-shallow] [-f|--force] [--checkout|--rebase|--merge] [--reference <repository>] [--depth <depth>] [--recursive] [--jobs <n>] [--[no-]single-branch] [--] [<path>...]:: +update [--init] [--remote] [-N|--no-fetch] [--[no-]recommend-shallow] [-f|--force] [--checkout|--rebase|--merge] [--reference <repository>] [--depth <depth>] [--recursive] [--jobs <n>] [--[no-]single-branch] [--filter <filter spec>] [--] [<path>...]:: + -- Update the registered submodules to match what the superproject @@ -177,6 +177,10 @@ submodule with the `--init` option. If `--recursive` is specified, this command will recurse into the registered submodules, and update any nested submodules within. + +If `--filter <filter spec>` is specified, the given partial clone filter will be +applied to the submodule. See linkgit:git-rev-list[1] for details on filter +specifications. -- set-branch (-b|--branch) <branch> [--] <path>:: set-branch (-d|--default) [--] <path>:: diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt index 9e862fbcf7..b8d53c4830 100644 --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@ -286,8 +286,8 @@ CONFIGURATION FILE ------------------ By default, the repository `config` file is shared across all working trees. If the config variables `core.bare` or `core.worktree` are -already present in the config file, they will be applied to the main -working trees only. +present in the common config file and `extensions.worktreeConfig` is +disabled, then they will be applied to the main working tree only. In order to have configuration specific to working trees, you can turn on the `worktreeConfig` extension, e.g.: @@ -307,11 +307,16 @@ them to the `config.worktree` of the main working tree. You may also take this opportunity to review and move other configuration that you do not want to share to all working trees: - - `core.worktree` and `core.bare` should never be shared + - `core.worktree` should never be shared. + + - `core.bare` should not be shared if the value is `core.bare=true`. - `core.sparseCheckout` is recommended per working tree, unless you are sure you always use sparse checkout for all working trees. +See the documentation of `extensions.worktreeConfig` in +linkgit:git-config[1] for more details. + DETAILS ------- Each linked working tree has a private sub-directory in the repository's diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt index 60984a4682..a71dad2674 100644 --- a/Documentation/gitattributes.txt +++ b/Documentation/gitattributes.txt @@ -161,11 +161,12 @@ unspecified. This attribute sets a specific line-ending style to be used in the 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. +attribute is set or unspecified, or if it is set to `auto`, the file is +detected as text, and it is stored with LF endings in the index. Note +that setting this attribute on paths which are in the index with CRLF +line endings may make the paths to be considered dirty unless +`text=auto` is set. Adding the path to the index again will normalize +the line endings in the index. Set to string value "crlf":: 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. @@ -264,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) # @@ -871,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 @@ -1204,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) @@ -1736,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 @@ -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, @@ -3494,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)) @@ -3511,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); @@ -12,7 +12,7 @@ static char const * const archive_usage[] = { N_("git archive [<options>] <tree-ish> [<path>...]"), - N_("git archive --list"), + "git archive --list", N_("git archive --remote <repo> [--exec <cmd>] [<options>] <tree-ish> [<path>...]"), N_("git archive --remote <repo> [--exec <cmd>] --list"), NULL @@ -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. 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 eb24bc89bb..0f4111bafa 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -200,7 +200,7 @@ static int am_option_parse_empty(const struct option *opt, else if (!strcmp(arg, "keep")) *opt_value = KEEP_EMPTY_COMMIT; else - return error(_("Invalid value for --empty: %s"), arg); + return error(_("invalid value for '%s': '%s'"), "--empty", arg); return 0; } @@ -2240,7 +2240,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int * when you add new options */ else - return error(_("Invalid value for --patch-format: %s"), arg); + return error(_("invalid value for '%s': '%s'"), + "--patch-format", arg); return 0; } @@ -2283,7 +2284,8 @@ static int parse_opt_show_current_patch(const struct option *opt, const char *ar break; } if (new_value >= ARRAY_SIZE(valid_modes)) - return error(_("Invalid value for --show-current-patch: %s"), arg); + return error(_("invalid value for '%s': '%s'"), + "--show-current-patch", arg); } if (resume->mode == RESUME_SHOW_PATCH && new_value != resume->sub_mode) diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index 28a2e6a575..f962dbd430 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -22,15 +22,15 @@ static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN") static const char * const git_bisect_helper_usage[] = { N_("git bisect--helper --bisect-reset [<commit>]"), - N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"), + "git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]", N_("git bisect--helper --bisect-start [--term-{new,bad}=<term> --term-{old,good}=<term>]" " [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<paths>...]"), - N_("git bisect--helper --bisect-next"), + "git bisect--helper --bisect-next", N_("git bisect--helper --bisect-state (bad|new) [<rev>]"), N_("git bisect--helper --bisect-state (good|old) [<rev>...]"), N_("git bisect--helper --bisect-replay <filename>"), N_("git bisect--helper --bisect-skip [(<rev>|<range>)...]"), - N_("git bisect--helper --bisect-visualize"), + "git bisect--helper --bisect-visualize", N_("git bisect--helper --bisect-run <cmd>..."), NULL }; diff --git a/builtin/blame.c b/builtin/blame.c index 7fafeac408..8d15b68afc 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -721,8 +721,8 @@ static int git_blame_config(const char *var, const char *value, void *cb) } if (!strcmp(var, "color.blame.repeatedlines")) { if (color_parse_mem(value, strlen(value), repeated_meta_color)) - warning(_("invalid color '%s' in color.blame.repeatedLines"), - value); + warning(_("invalid value for '%s': '%s'"), + "color.blame.repeatedLines", value); return 0; } if (!strcmp(var, "color.blame.highlightrecent")) { @@ -739,7 +739,8 @@ static int git_blame_config(const char *var, const char *value, void *cb) coloring_mode &= ~(OUTPUT_COLOR_LINE | OUTPUT_SHOW_AGE_WITH_COLOR); } else { - warning(_("invalid value for blame.coloring")); + warning(_("invalid value for '%s': '%s'"), + "blame.coloring", value); return 0; } } @@ -934,6 +935,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) parse_revision_opt(&revs, &ctx, options, blame_opt_usage); } parse_done: + revision_opts_finish(&revs); no_whole_file_rename = !revs.diffopt.flags.follow_renames; xdl_opts |= revs.diffopt.xdl_opts & XDF_INDENT_HEURISTIC; revs.diffopt.flags.follow_renames = 0; 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/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 8f010412a9..d9b31bbb6d 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -246,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; @@ -276,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); } @@ -905,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); @@ -1603,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 9c29093b35..a572cda503 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -72,6 +72,8 @@ static int option_dissociate; static int max_jobs = -1; static struct string_list option_recurse_submodules = STRING_LIST_INIT_NODUP; static struct list_objects_filter_options filter_options; +static int option_filter_submodules = -1; /* unspecified */ +static int config_filter_submodules = -1; /* unspecified */ static struct string_list server_options = STRING_LIST_INIT_NODUP; static int option_remote_submodules; @@ -151,6 +153,8 @@ static struct option builtin_clone_options[] = { OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"), TRANSPORT_FAMILY_IPV6), OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), + OPT_BOOL(0, "also-filter-submodules", &option_filter_submodules, + N_("apply partial clone filters to submodules")), OPT_BOOL(0, "remote-submodules", &option_remote_submodules, N_("any cloned submodules will use their remote-tracking branch")), OPT_BOOL(0, "sparse", &option_sparse_checkout, @@ -651,7 +655,7 @@ static int git_sparse_checkout_init(const char *repo) return result; } -static int checkout(int submodule_progress) +static int checkout(int submodule_progress, int filter_submodules) { struct object_id oid; char *head; @@ -730,6 +734,10 @@ static int checkout(int submodule_progress) strvec_push(&args, "--no-fetch"); } + if (filter_submodules && filter_options.choice) + strvec_pushf(&args, "--filter=%s", + expand_list_objects_filter_spec(&filter_options)); + if (option_single_branch >= 0) strvec_push(&args, option_single_branch ? "--single-branch" : @@ -750,6 +758,8 @@ static int git_clone_config(const char *k, const char *v, void *cb) } if (!strcmp(k, "clone.rejectshallow")) config_reject_shallow = git_config_bool(k, v); + if (!strcmp(k, "clone.filtersubmodules")) + config_filter_submodules = git_config_bool(k, v); return git_default_config(k, v, cb); } @@ -872,6 +882,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) struct remote *remote; int err = 0, complete_refs_before_fetch = 1; int submodule_progress; + int filter_submodules = 0; struct transport_ls_refs_options transport_ls_refs_options = TRANSPORT_LS_REFS_OPTIONS_INIT; @@ -1068,6 +1079,27 @@ int cmd_clone(int argc, const char **argv, const char *prefix) reject_shallow = option_reject_shallow; /* + * If option_filter_submodules is specified from CLI option, + * ignore config_filter_submodules from git_clone_config. + */ + if (config_filter_submodules != -1) + filter_submodules = config_filter_submodules; + if (option_filter_submodules != -1) + filter_submodules = option_filter_submodules; + + /* + * Exit if the user seems to be doing something silly with submodule + * filter flags (but not with filter configs, as those should be + * set-and-forget). + */ + if (option_filter_submodules > 0 && !filter_options.choice) + die(_("the option '%s' requires '%s'"), + "--also-filter-submodules", "--filter"); + if (option_filter_submodules > 0 && !option_recurse_submodules.nr) + die(_("the option '%s' requires '%s'"), + "--also-filter-submodules", "--recurse-submodules"); + + /* * apply the remote name provided by --origin only after this second * call to git_config, to ensure it overrides all config-based values. */ @@ -1235,7 +1267,8 @@ 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"), @@ -1251,17 +1284,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, @@ -1300,7 +1332,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } junk_mode = JUNK_LEAVE_REPO; - err = checkout(submodule_progress); + err = checkout(submodule_progress, filter_submodules); free(remote_name); strbuf_release(&reflog_msg); @@ -1313,7 +1345,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/commit.c b/builtin/commit.c index 6b99ac276d..8b8bdad395 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1243,8 +1243,6 @@ static int parse_and_validate_options(int argc, const char *argv[], struct commit *current_head, struct wt_status *s) { - int f = 0; - argc = parse_options(argc, argv, prefix, options, usage, 0); finalize_deferred_config(s); @@ -1252,7 +1250,7 @@ static int parse_and_validate_options(int argc, const char *argv[], force_author = find_author_by_nickname(force_author); if (force_author && renew_authorship) - die(_("Using both --reset-author and --author does not make sense")); + die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author"); if (logfile || have_option_m || use_message) use_editor = 0; @@ -1269,20 +1267,16 @@ static int parse_and_validate_options(int argc, const char *argv[], die(_("You are in the middle of a rebase -- cannot amend.")); } if (fixup_message && squash_message) - die(_("Options --squash and --fixup cannot be used together")); - if (use_message) - f++; - if (edit_message) - f++; - if (fixup_message) - f++; - if (logfile) - f++; - if (f > 1) - die(_("Only one of -c/-C/-F/--fixup can be used.")); - if (have_option_m && (edit_message || use_message || logfile)) - die((_("Option -m cannot be combined with -c/-C/-F."))); - if (f || have_option_m) + die(_("options '%s' and '%s' cannot be used together"), "--squash", "--fixup"); + die_for_incompatible_opt4(!!use_message, "-C", + !!edit_message, "-c", + !!logfile, "-F", + !!fixup_message, "--fixup"); + die_for_incompatible_opt4(have_option_m, "-m", + !!edit_message, "-c", + !!use_message, "-C", + !!logfile, "-F"); + if (use_message || edit_message || logfile ||fixup_message || have_option_m) template_file = NULL; if (edit_message) use_message = edit_message; @@ -1307,9 +1301,10 @@ static int parse_and_validate_options(int argc, const char *argv[], if (patch_interactive) interactive = 1; - if (also + only + all + interactive > 1) - die(_("Only one of --include/--only/--all/--interactive/--patch can be used.")); - + die_for_incompatible_opt4(also, "-i/--include", + only, "-o/--only", + all, "-a/--all", + interactive, "--interactive/-p/--patch"); if (fixup_message) { /* * We limit --fixup's suboptions to only alpha characters. diff --git a/builtin/count-objects.c b/builtin/count-objects.c index 3fae474f6f..07b9419596 100644 --- a/builtin/count-objects.c +++ b/builtin/count-objects.c @@ -87,7 +87,7 @@ static int print_alternate(struct object_directory *odb, void *data) } static char const * const count_objects_usage[] = { - N_("git count-objects [-v] [-H | --human-readable]"), + "git count-objects [-v] [-H | --human-readable]", NULL }; diff --git a/builtin/difftool.c b/builtin/difftool.c index c79fbbf67e..faa3507369 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -732,8 +732,9 @@ int cmd_difftool(int argc, const char **argv, const char *prefix) } else if (dir_diff) die(_("options '%s' and '%s' cannot be used together"), "--dir-diff", "--no-index"); - if (use_gui_tool + !!difftool_cmd + !!extcmd > 1) - die(_("options '%s', '%s', and '%s' cannot be used together"), "--gui", "--tool", "--extcmd"); + die_for_incompatible_opt3(use_gui_tool, "--gui", + !!difftool_cmd, "--tool", + !!extcmd, "--extcmd"); if (use_gui_tool) setenv("GIT_MERGETOOL_GUI", "true", 1); diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 9f1c730e58..510139e9b5 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -26,7 +26,7 @@ #include "commit-slab.h" static const char *fast_export_usage[] = { - N_("git fast-export [rev-list-opts]"), + N_("git fast-export [<rev-list-opts>]"), NULL }; diff --git a/builtin/fetch.c b/builtin/fetch.c index 6f5e157863..95832ba1df 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -764,8 +764,8 @@ static void prepare_format_display(struct ref *ref_map) else if (!strcasecmp(format, "compact")) compact_format = 1; else - die(_("configuration fetch.output contains invalid value %s"), - format); + die(_("invalid value for '%s': '%s'"), + "fetch.output", format); for (rm = ref_map; rm; rm = rm->next) { if (rm->status == REF_STATUS_REJECT_SHALLOW || @@ -1094,12 +1094,15 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, struct ref *rm; char *url; int want_status; - int summary_width = transport_summary_width(ref_map); + int summary_width = 0; rc = open_fetch_head(&fetch_head); if (rc) return -1; + if (verbosity >= 0) + summary_width = transport_summary_width(ref_map); + if (raw_url) url = transport_anonymize_url(raw_url); else @@ -1345,7 +1348,6 @@ static int prune_refs(struct refspec *rs, struct ref *ref_map, int url_len, i, result = 0; struct ref *ref, *stale_refs = get_stale_heads(rs, ref_map); char *url; - int summary_width = transport_summary_width(stale_refs); const char *dangling_msg = dry_run ? _(" (%s will become dangling)") : _(" (%s has become dangling)"); @@ -1374,6 +1376,8 @@ static int prune_refs(struct refspec *rs, struct ref *ref_map, } if (verbosity >= 0) { + int summary_width = transport_summary_width(stale_refs); + for (ref = stale_refs; ref; ref = ref->next) { struct strbuf sb = STRBUF_INIT; if (!shown_url) { @@ -1594,7 +1598,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); @@ -2017,8 +2021,10 @@ 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); diff --git a/builtin/grep.c b/builtin/grep.c index 9e34a820ad..f1a924eade 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -26,6 +26,8 @@ #include "object-store.h" #include "packfile.h" +static const char *grep_prefix; + static char const * const grep_usage[] = { N_("git grep [<options>] [-e] <pattern> [<rev>...] [[--] <path>...]"), NULL @@ -284,7 +286,7 @@ static int wait_all(void) static int grep_cmd_config(const char *var, const char *value, void *cb) { int st = grep_config(var, value, cb); - if (git_color_default_config(var, value, cb) < 0) + if (git_color_default_config(var, value, NULL) < 0) st = -1; if (!strcmp(var, "grep.threads")) { @@ -315,11 +317,11 @@ static void grep_source_name(struct grep_opt *opt, const char *filename, strbuf_reset(out); if (opt->null_following_name) { - if (opt->relative && opt->prefix_length) { + if (opt->relative && grep_prefix) { struct strbuf rel_buf = STRBUF_INIT; const char *rel_name = relative_path(filename + tree_name_len, - opt->prefix, &rel_buf); + grep_prefix, &rel_buf); if (tree_name_len) strbuf_add(out, filename, tree_name_len); @@ -332,8 +334,8 @@ static void grep_source_name(struct grep_opt *opt, const char *filename, return; } - if (opt->relative && opt->prefix_length) - quote_path(filename + tree_name_len, opt->prefix, out, 0); + if (opt->relative && grep_prefix) + quote_path(filename + tree_name_len, grep_prefix, out, 0); else quote_c_style(filename + tree_name_len, out, NULL, 0); @@ -843,7 +845,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) int i; int dummy; int use_index = 1; - int pattern_type_arg = GREP_PATTERN_TYPE_UNSPECIFIED; int allow_revs; struct option options[] = { @@ -877,16 +878,16 @@ int cmd_grep(int argc, const char **argv, const char *prefix) N_("descend at most <depth> levels"), PARSE_OPT_NONEG, NULL, 1 }, OPT_GROUP(""), - OPT_SET_INT('E', "extended-regexp", &pattern_type_arg, + OPT_SET_INT('E', "extended-regexp", &opt.pattern_type_option, N_("use extended POSIX regular expressions"), GREP_PATTERN_TYPE_ERE), - OPT_SET_INT('G', "basic-regexp", &pattern_type_arg, + OPT_SET_INT('G', "basic-regexp", &opt.pattern_type_option, N_("use basic POSIX regular expressions (default)"), GREP_PATTERN_TYPE_BRE), - OPT_SET_INT('F', "fixed-strings", &pattern_type_arg, + OPT_SET_INT('F', "fixed-strings", &opt.pattern_type_option, N_("interpret patterns as fixed strings"), GREP_PATTERN_TYPE_FIXED), - OPT_SET_INT('P', "perl-regexp", &pattern_type_arg, + OPT_SET_INT('P', "perl-regexp", &opt.pattern_type_option, N_("use Perl-compatible regular expressions"), GREP_PATTERN_TYPE_PCRE), OPT_GROUP(""), @@ -962,9 +963,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix) PARSE_OPT_NOCOMPLETE), OPT_END() }; + grep_prefix = prefix; - git_config(grep_cmd_config, NULL); - grep_init(&opt, the_repository, prefix); + grep_init(&opt, the_repository); + git_config(grep_cmd_config, &opt); /* * If there is no -- then the paths must exist in the working @@ -979,7 +981,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, grep_usage, PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_STOP_AT_NON_OPTION); - grep_commit_pattern_type(pattern_type_arg, &opt); if (use_index && !startup_info->have_repository) { int fallback = 0; @@ -1167,11 +1168,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (!show_in_pager && !opt.status_only) setup_pager(); - if (!use_index && (untracked || cached)) - die(_("--cached or --untracked cannot be used with --no-index")); - - if (untracked && cached) - die(_("--untracked cannot be used with --cached")); + die_for_incompatible_opt3(!use_index, "--no-index", + untracked, "--untracked", + cached, "--cached"); if (!use_index || untracked) { int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude; diff --git a/builtin/hash-object.c b/builtin/hash-object.c index c7b3ad74c6..0837849288 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -81,7 +81,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) { static const char * const hash_object_usage[] = { N_("git hash-object [-t <type>] [-w] [--path=<file> | --no-filters] [--stdin] [--] <file>..."), - N_("git hash-object --stdin-paths"), + "git hash-object --stdin-paths", NULL }; const char *type = blob_type; @@ -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/help.c b/builtin/help.c index d387131dd8..b4f2ad3f94 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -77,8 +77,8 @@ static struct option builtin_help_options[] = { static const char * const builtin_help_usage[] = { N_("git help [-a|--all] [--[no-]verbose]]\n" " [[-i|--info] [-m|--man] [-w|--web]] [<command>]"), - N_("git help [-g|--guides]"), - N_("git help [-c|--config]"), + "git help [-g|--guides]", + "git help [-c|--config]", NULL }; diff --git a/builtin/log.c b/builtin/log.c index 4b493408cc..c211d66d1d 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; @@ -520,8 +533,6 @@ static int git_log_config(const char *var, const char *value, void *cb) return 0; } - if (grep_config(var, value, cb) < 0) - return -1; if (git_gpg_config(var, value, cb) < 0) return -1; return git_diff_ui_config(var, value, cb); @@ -536,6 +547,8 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) git_config(git_log_config, NULL); repo_init_revisions(the_repository, &rev, prefix); + git_config(grep_config, &rev.grep_filter); + rev.diff = 1; rev.simplify_history = 0; memset(&opt, 0, sizeof(opt)); @@ -650,6 +663,8 @@ int cmd_show(int argc, const char **argv, const char *prefix) memset(&match_all, 0, sizeof(match_all)); repo_init_revisions(the_repository, &rev, prefix); + git_config(grep_config, &rev.grep_filter); + rev.diff = 1; rev.always_show_header = 1; rev.no_walk = 1; @@ -733,6 +748,8 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) repo_init_revisions(the_repository, &rev, prefix); init_reflog_walk(&rev.reflog_info); + git_config(grep_config, &rev.grep_filter); + rev.verbose_header = 1; memset(&opt, 0, sizeof(opt)); opt.def = "HEAD"; @@ -766,6 +783,8 @@ int cmd_log(int argc, const char **argv, const char *prefix) git_config(git_log_config, NULL); repo_init_revisions(the_repository, &rev, prefix); + git_config(grep_config, &rev.grep_filter); + rev.always_show_header = 1; memset(&opt, 0, sizeof(opt)); opt.def = "HEAD"; @@ -1848,10 +1867,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) extra_hdr.strdup_strings = 1; extra_to.strdup_strings = 1; extra_cc.strdup_strings = 1; + init_log_defaults(); init_display_notes(¬es_opt); git_config(git_format_config, NULL); repo_init_revisions(the_repository, &rev, prefix); + git_config(grep_config, &rev.grep_filter); + rev.show_notes = show_notes; memcpy(&rev.notes_opt, ¬es_opt, sizeof(notes_opt)); rev.commit_format = CMIT_FMT_EMAIL; @@ -1958,6 +1980,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 || @@ -1978,8 +2002,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (rev.show_notes) load_display_notes(&rev.notes_opt); - if (use_stdout + rev.diffopt.close_file + !!output_directory > 1) - die(_("options '%s', '%s', and '%s' cannot be used together"), "--stdout", "--output", "--output-directory"); + die_for_incompatible_opt3(use_stdout, "--stdout", + rev.diffopt.close_file, "--output", + !!output_directory, "--output-directory"); if (use_stdout) { setup_pager(); 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/ls-tree.c b/builtin/ls-tree.c index 3a442631c7..6cb554cbb0 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -150,7 +150,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); ls_tree_prefix = prefix; - if (prefix && *prefix) + if (prefix) chomp_prefix = strlen(prefix); argc = parse_options(argc, argv, prefix, ls_tree_options, diff --git a/builtin/merge-base.c b/builtin/merge-base.c index 6719ac198d..26b84980db 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -159,12 +159,14 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix) if (argc < 2) usage_with_options(merge_base_usage, options); if (show_all) - die("--is-ancestor cannot be used with --all"); + die(_("options '%s' and '%s' cannot be used together"), + "--is-ancestor", "--all"); return handle_is_ancestor(argc, argv); } if (cmdmode == 'r' && show_all) - die("--independent cannot be used with --all"); + die(_("options '%s' and '%s' cannot be used together"), + "--independent", "--all"); if (cmdmode == 'o') return handle_octopus(argc, argv, show_all); diff --git a/builtin/merge.c b/builtin/merge.c index 133831d42f..a94a03384a 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -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); diff --git a/builtin/mktag.c b/builtin/mktag.c index 3b2dbbb37e..c7b905c614 100644 --- a/builtin/mktag.c +++ b/builtin/mktag.c @@ -7,7 +7,7 @@ #include "config.h" static char const * const builtin_mktag_usage[] = { - N_("git mktag"), + "git mktag", NULL }; static int option_strict = 1; diff --git a/builtin/mktree.c b/builtin/mktree.c index ae78ca1c02..8bdaada922 100644 --- a/builtin/mktree.c +++ b/builtin/mktree.c @@ -63,7 +63,7 @@ static void write_tree(struct object_id *oid) } static const char *mktree_usage[] = { - N_("git mktree [-z] [--missing] [--batch]"), + "git mktree [-z] [--missing] [--batch]", NULL }; diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 138e3c30a2..929591269d 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -473,7 +473,7 @@ static void show_name(const struct object *obj, static char const * const name_rev_usage[] = { N_("git name-rev [<options>] <commit>..."), N_("git name-rev [<options>] --all"), - N_("git name-rev [<options>] --stdin"), + N_("git name-rev [<options>] --annotate-stdin"), NULL }; diff --git a/builtin/notes.c b/builtin/notes.c index 05d60483e8..f99593a185 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -32,8 +32,8 @@ static const char * const git_notes_usage[] = { N_("git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]"), N_("git notes [--ref <notes-ref>] show [<object>]"), N_("git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>"), - N_("git notes merge --commit [-v | -q]"), - N_("git notes merge --abort [-v | -q]"), + "git notes merge --commit [-v | -q]", + "git notes merge --abort [-v | -q]", N_("git notes [--ref <notes-ref>] remove [<object>...]"), N_("git notes [--ref <notes-ref>] prune [-n] [-v]"), N_("git notes [--ref <notes-ref>] get-ref"), @@ -89,7 +89,7 @@ static const char * const git_notes_prune_usage[] = { }; static const char * const git_notes_get_ref_usage[] = { - N_("git notes get-ref"), + "git notes get-ref", NULL }; diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index ba2006f221..178e611f09 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -3504,7 +3504,7 @@ static int option_parse_missing_action(const struct option *opt, return 0; } - die(_("invalid value for --missing")); + die(_("invalid value for '%s': '%s'"), "--missing", arg); return 0; } @@ -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/prune-packed.c b/builtin/prune-packed.c index b7b9281a8c..da3273a268 100644 --- a/builtin/prune-packed.c +++ b/builtin/prune-packed.c @@ -3,7 +3,7 @@ #include "prune-packed.h" static const char * const prune_packed_usage[] = { - N_("git prune-packed [-n | --dry-run] [-q | --quiet]"), + "git prune-packed [-n | --dry-run] [-q | --quiet]", NULL }; diff --git a/builtin/pull.c b/builtin/pull.c index 8f37880a48..4d667abc19 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -42,9 +42,9 @@ static enum rebase_type parse_config_rebase(const char *key, const char *value, return v; if (fatal) - die(_("Invalid value for %s: %s"), key, value); + die(_("invalid value for '%s': '%s'"), key, value); else - error(_("Invalid value for %s: %s"), key, value); + error(_("invalid value for '%s': '%s'"), key, value); return REBASE_INVALID; } @@ -318,7 +318,7 @@ static const char *config_get_ff(void) if (!strcmp(value, "only")) return "--ff-only"; - die(_("Invalid value for pull.ff: %s"), value); + die(_("invalid value for '%s': '%s'"), "pull.ff", value); } /** @@ -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); diff --git a/builtin/push.c b/builtin/push.c index 359db90321..cad997965a 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -486,7 +486,7 @@ static int git_push_config(const char *k, const char *v, void *cb) if (value && !strcasecmp(value, "if-asked")) set_push_cert_flags(flags, SEND_PACK_PUSH_CERT_IF_ASKED); else - return error("Invalid value for '%s'", k); + return error(_("invalid value for '%s'"), k); } } } else if (!strcmp(k, "push.recursesubmodules")) { diff --git a/builtin/rebase.c b/builtin/rebase.c index 2e6d8fa34e..b29ad2b65e 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -37,7 +37,7 @@ static char const * const builtin_rebase_usage[] = { "[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"), N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " "--root [<branch>]"), - N_("git rebase --continue | --abort | --skip | --edit-todo"), + "git rebase --continue | --abort | --skip | --edit-todo", NULL }; @@ -571,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) @@ -580,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; } @@ -671,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" @@ -813,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 @@ -1018,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"), @@ -1255,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)) @@ -1274,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); @@ -1642,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)) { @@ -1674,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)) @@ -1755,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); @@ -1773,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 c427ca09aa..d10aeb7e78 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -1962,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 85b838720c..c8f2b31873 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -818,7 +818,7 @@ static int cmd_reflog_exists(int argc, const char **argv, const char *prefix) */ static const char reflog_usage[] = -N_("git reflog [ show | expire | delete | exists ]"); +"git reflog [ show | expire | delete | exists ]"; int cmd_reflog(int argc, const char **argv, const char *prefix) { diff --git a/builtin/remote.c b/builtin/remote.c index 299c466116..6f27ddc47b 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -14,7 +14,7 @@ #include "commit-reach.h" static const char * const builtin_remote_usage[] = { - N_("git remote [-v | --verbose]"), + "git remote [-v | --verbose]", N_("git remote add [-t <branch>] [-m <master>] [-f] [--tags | --no-tags] [--mirror=<fetch|push>] <name> <url>"), N_("git remote rename <old> <new>"), N_("git remote remove <name>"), diff --git a/builtin/replace.c b/builtin/replace.c index 6ff1734d58..ac92337c0e 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -22,7 +22,7 @@ static const char * const git_replace_usage[] = { N_("git replace [-f] <object> <replacement>"), N_("git replace [-f] --edit <object>"), N_("git replace [-f] --graft <commit> [<parent>...]"), - N_("git replace [-f] --convert-graft-file"), + "git replace [-f] --convert-graft-file", N_("git replace -d <object>..."), N_("git replace [--format=<format>] [-l [<pattern>]]"), NULL 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/rev-list.c b/builtin/rev-list.c index 777558e9b0..38528c7f15 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -20,7 +20,7 @@ #include "packfile.h" static const char rev_list_usage[] = -"git rev-list [OPTION] <commit-id>... [ -- paths... ]\n" +"git rev-list [<options>] <commit-id>... [-- <path>...]\n" " limiting output:\n" " --max-count=<n>\n" " --max-age=<epoch>\n" diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 69c432ef1a..64962be016 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -145,7 +145,7 @@ static int send_pack_config(const char *k, const char *v, void *cb) if (value && !strcasecmp(value, "if-asked")) args.push_cert = SEND_PACK_PUSH_CERT_IF_ASKED; else - return error("Invalid value for '%s'", k); + return error(_("invalid value for '%s'"), k); } } } diff --git a/builtin/shortlog.c b/builtin/shortlog.c index e7f7af5de3..228d782754 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -388,6 +388,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) parse_revision_opt(&rev, &ctx, options, shortlog_usage); } parse_done: + revision_opts_finish(&rev); argc = parse_options_end(&ctx); if (nongit && argc > 1) { diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index a311483a7d..9c338d33ea 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -15,6 +15,7 @@ #include "wt-status.h" #include "quote.h" #include "sparse-index.h" +#include "worktree.h" static const char *empty_base = ""; @@ -43,7 +44,7 @@ static void write_patterns_to_file(FILE *fp, struct pattern_list *pl) } static char const * const builtin_sparse_checkout_list_usage[] = { - N_("git sparse-checkout list"), + "git sparse-checkout list", NULL }; @@ -361,26 +362,23 @@ enum sparse_checkout_mode { static int set_config(enum sparse_checkout_mode mode) { - const char *config_path; - - if (upgrade_repository_format(1) < 0) - die(_("unable to upgrade repository format to enable worktreeConfig")); - if (git_config_set_gently("extensions.worktreeConfig", "true")) { - error(_("failed to set extensions.worktreeConfig setting")); + /* Update to use worktree config, if not already. */ + if (init_worktree_config(the_repository)) { + error(_("failed to initialize worktree config")); return 1; } - config_path = git_path("config.worktree"); - git_config_set_in_file_gently(config_path, - "core.sparseCheckout", - mode ? "true" : NULL); - - git_config_set_in_file_gently(config_path, - "core.sparseCheckoutCone", - mode == MODE_CONE_PATTERNS ? "true" : NULL); + if (repo_config_set_worktree_gently(the_repository, + "core.sparseCheckout", + mode ? "true" : "false") || + repo_config_set_worktree_gently(the_repository, + "core.sparseCheckoutCone", + mode == MODE_CONE_PATTERNS ? + "true" : "false")) + return 1; if (mode == MODE_NO_PATTERNS) - set_sparse_index_config(the_repository, 0); + return set_sparse_index_config(the_repository, 0); return 0; } @@ -421,7 +419,7 @@ static int update_modes(int *cone_mode, int *sparse_index) } static char const * const builtin_sparse_checkout_init_usage[] = { - N_("git sparse-checkout init [--cone] [--[no-]sparse-index]"), + "git sparse-checkout init [--cone] [--[no-]sparse-index]", NULL }; @@ -767,7 +765,7 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix) } static char const * const builtin_sparse_checkout_reapply_usage[] = { - N_("git sparse-checkout reapply [--[no-]cone] [--[no-]sparse-index]"), + "git sparse-checkout reapply [--[no-]cone] [--[no-]sparse-index]", NULL }; @@ -805,7 +803,7 @@ static int sparse_checkout_reapply(int argc, const char **argv) } static char const * const builtin_sparse_checkout_disable_usage[] = { - N_("git sparse-checkout disable"), + "git sparse-checkout disable", NULL }; diff --git a/builtin/stash.c b/builtin/stash.c index 9638c56303..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); diff --git a/builtin/stripspace.c b/builtin/stripspace.c index be33eb83c1..1e34cf2beb 100644 --- a/builtin/stripspace.c +++ b/builtin/stripspace.c @@ -15,8 +15,8 @@ static void comment_lines(struct strbuf *buf) } static const char * const stripspace_usage[] = { - N_("git stripspace [-s | --strip-comments]"), - N_("git stripspace [-c | --comment-lines]"), + "git stripspace [-s | --strip-comments]", + "git stripspace [-c | --comment-lines]", NULL }; diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index c5d3fc3817..eeacefcc38 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -20,6 +20,8 @@ #include "diff.h" #include "object-store.h" #include "advice.h" +#include "branch.h" +#include "list-objects-filter-options.h" #define OPT_QUIET (1 << 0) #define OPT_CACHED (1 << 1) @@ -1630,6 +1632,7 @@ struct module_clone_data { const char *name; const char *url; const char *depth; + struct list_objects_filter_options *filter_options; struct string_list reference; unsigned int quiet: 1; unsigned int progress: 1; @@ -1796,6 +1799,10 @@ static int clone_submodule(struct module_clone_data *clone_data) strvec_push(&cp.args, "--dissociate"); if (sm_gitdir && *sm_gitdir) strvec_pushl(&cp.args, "--separate-git-dir", sm_gitdir, NULL); + if (clone_data->filter_options && clone_data->filter_options->choice) + strvec_pushf(&cp.args, "--filter=%s", + expand_list_objects_filter_spec( + clone_data->filter_options)); if (clone_data->single_branch >= 0) strvec_push(&cp.args, clone_data->single_branch ? "--single-branch" : @@ -1852,6 +1859,7 @@ static int module_clone(int argc, const char **argv, const char *prefix) { int dissociate = 0, quiet = 0, progress = 0, require_init = 0; struct module_clone_data clone_data = MODULE_CLONE_DATA_INIT; + struct list_objects_filter_options filter_options; struct option module_clone_options[] = { OPT_STRING(0, "prefix", &clone_data.prefix, @@ -1881,17 +1889,19 @@ static int module_clone(int argc, const char **argv, const char *prefix) N_("disallow cloning into non-empty directory")), OPT_BOOL(0, "single-branch", &clone_data.single_branch, N_("clone only one branch, HEAD or --branch")), + OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), OPT_END() }; const char *const git_submodule_helper_usage[] = { N_("git submodule--helper clone [--prefix=<path>] [--quiet] " "[--reference <repository>] [--name <name>] [--depth <depth>] " - "[--single-branch] " + "[--single-branch] [--filter <filter-spec>]" "--url <url> --path <path>"), NULL }; + memset(&filter_options, 0, sizeof(filter_options)); argc = parse_options(argc, argv, prefix, module_clone_options, git_submodule_helper_usage, 0); @@ -1899,12 +1909,14 @@ static int module_clone(int argc, const char **argv, const char *prefix) clone_data.quiet = !!quiet; clone_data.progress = !!progress; clone_data.require_init = !!require_init; + clone_data.filter_options = &filter_options; if (argc || !clone_data.url || !clone_data.path || !*(clone_data.path)) usage_with_options(git_submodule_helper_usage, module_clone_options); clone_submodule(&clone_data); + list_objects_filter_release(&filter_options); return 0; } @@ -1994,6 +2006,7 @@ struct submodule_update_clone { const char *recursive_prefix; const char *prefix; int single_branch; + struct list_objects_filter_options *filter_options; /* to be consumed by git-submodule.sh */ struct update_clone_data *update_clone; @@ -2154,6 +2167,9 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, strvec_pushl(&child->args, "--prefix", suc->prefix, NULL); if (suc->recommend_shallow && sub->recommend_shallow == 1) strvec_push(&child->args, "--depth=1"); + if (suc->filter_options && suc->filter_options->choice) + strvec_pushf(&child->args, "--filter=%s", + expand_list_objects_filter_spec(suc->filter_options)); if (suc->require_init) strvec_push(&child->args, "--require-init"); strvec_pushl(&child->args, "--path", sub->path, NULL); @@ -2498,6 +2514,8 @@ static int update_clone(int argc, const char **argv, const char *prefix) const char *update = NULL; struct pathspec pathspec; struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT; + struct list_objects_filter_options filter_options; + int ret; struct option module_update_clone_options[] = { OPT_STRING(0, "prefix", &prefix, @@ -2528,6 +2546,7 @@ static int update_clone(int argc, const char **argv, const char *prefix) N_("disallow cloning into non-empty directory")), OPT_BOOL(0, "single-branch", &suc.single_branch, N_("clone only one branch, HEAD or --branch")), + OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), OPT_END() }; @@ -2540,20 +2559,26 @@ static int update_clone(int argc, const char **argv, const char *prefix) update_clone_config_from_gitmodules(&suc.max_jobs); git_config(git_update_clone_config, &suc.max_jobs); + memset(&filter_options, 0, sizeof(filter_options)); argc = parse_options(argc, argv, prefix, module_update_clone_options, git_submodule_helper_usage, 0); + suc.filter_options = &filter_options; if (update) if (parse_submodule_update_strategy(update, &suc.update) < 0) die(_("bad value for update parameter")); - if (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0) + if (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0) { + list_objects_filter_release(&filter_options); return 1; + } if (pathspec.nr) suc.warn_if_uninitialized = 1; - return update_submodules(&suc); + ret = update_submodules(&suc); + list_objects_filter_release(&filter_options); + return ret; } static int run_update_procedure(int argc, const char **argv, const char *prefix) @@ -2883,7 +2908,7 @@ static int module_config(int argc, const char **argv, const char *prefix) const char *const git_submodule_helper_usage[] = { N_("git submodule--helper config <name> [<value>]"), N_("git submodule--helper config --unset <name>"), - N_("git submodule--helper config --check-writeable"), + "git submodule--helper config --check-writeable", NULL }; @@ -2984,6 +3009,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 +3451,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 7e0a0d9bf8..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. @@ -1088,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/update-server-info.c b/builtin/update-server-info.c index 4321a34456..880fffec58 100644 --- a/builtin/update-server-info.c +++ b/builtin/update-server-info.c @@ -4,7 +4,7 @@ #include "parse-options.h" static const char * const update_server_info_usage[] = { - N_("git update-server-info [--force]"), + "git update-server-info [--force]", NULL }; diff --git a/builtin/worktree.c b/builtin/worktree.c index 0d0809276f..e23e6343d0 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -335,6 +335,69 @@ static int add_worktree(const char *path, const char *refname, strbuf_addf(&sb, "%s/commondir", sb_repo.buf); write_file(sb.buf, "../.."); + /* + * If the current worktree has sparse-checkout enabled, then copy + * the sparse-checkout patterns from the current worktree. + */ + if (core_apply_sparse_checkout) { + char *from_file = git_pathdup("info/sparse-checkout"); + char *to_file = xstrfmt("%s/info/sparse-checkout", + sb_repo.buf); + + if (file_exists(from_file)) { + if (safe_create_leading_directories(to_file) || + copy_file(to_file, from_file, 0666)) + error(_("failed to copy '%s' to '%s'; sparse-checkout may not work correctly"), + from_file, to_file); + } + + free(from_file); + free(to_file); + } + + /* + * If we are using worktree config, then copy all current config + * values from the current worktree into the new one, that way the + * new worktree behaves the same as this one. + */ + if (repository_format_worktree_config) { + char *from_file = git_pathdup("config.worktree"); + char *to_file = xstrfmt("%s/config.worktree", + sb_repo.buf); + + if (file_exists(from_file)) { + struct config_set cs = { { 0 } }; + const char *core_worktree; + int bare; + + if (safe_create_leading_directories(to_file) || + copy_file(to_file, from_file, 0666)) { + error(_("failed to copy worktree config from '%s' to '%s'"), + from_file, to_file); + goto worktree_copy_cleanup; + } + + git_configset_init(&cs); + git_configset_add_file(&cs, from_file); + + if (!git_configset_get_bool(&cs, "core.bare", &bare) && + bare && + git_config_set_multivar_in_file_gently( + to_file, "core.bare", NULL, "true", 0)) + error(_("failed to unset 'core.bare' in '%s'"), to_file); + if (!git_configset_get_value(&cs, "core.worktree", &core_worktree) && + git_config_set_in_file_gently(to_file, + "core.worktree", NULL)) + error(_("failed to unset 'core.worktree' in '%s'"), to_file); + + git_configset_clear(&cs); + } + +worktree_copy_cleanup: + free(from_file); + free(to_file); + } + strvec_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf); strvec_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path); cp.git_cmd = 1; @@ -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; @@ -197,7 +197,6 @@ esac case "$jobname" in linux32) CC=gcc - MAKEFLAGS="$MAKEFLAGS NO_UNCOMPRESS2=1" ;; linux-musl) CC=gcc 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 @@ -22,6 +22,7 @@ #include "dir.h" #include "color.h" #include "refs.h" +#include "worktree.h" struct config_source { struct config_source *prev; @@ -2295,8 +2296,8 @@ int git_configset_get_string(struct config_set *cs, const char *key, char **dest return 1; } -int git_configset_get_string_tmp(struct config_set *cs, const char *key, - const char **dest) +static int git_configset_get_string_tmp(struct config_set *cs, const char *key, + const char **dest) { const char *value; if (!git_configset_get_value(cs, key, &value)) { @@ -3001,6 +3002,20 @@ int git_config_set_gently(const char *key, const char *value) return git_config_set_multivar_gently(key, value, NULL, 0); } +int repo_config_set_worktree_gently(struct repository *r, + const char *key, const char *value) +{ + /* Only use worktree-specific config if it is is already enabled. */ + if (repository_format_worktree_config) { + char *file = repo_git_path(r, "config.worktree"); + int ret = git_config_set_multivar_in_file_gently( + file, key, value, NULL, 0); + free(file); + return ret; + } + return repo_config_set_multivar_gently(r, key, value, NULL, 0); +} + void git_config_set(const char *key, const char *value) { git_config_set_multivar(key, value, NULL, 0); @@ -3298,14 +3313,28 @@ void git_config_set_multivar_in_file(const char *config_filename, int git_config_set_multivar_gently(const char *key, const char *value, const char *value_pattern, unsigned flags) { - return git_config_set_multivar_in_file_gently(NULL, key, value, value_pattern, - flags); + return repo_config_set_multivar_gently(the_repository, key, value, + value_pattern, flags); +} + +int repo_config_set_multivar_gently(struct repository *r, const char *key, + const char *value, + const char *value_pattern, unsigned flags) +{ + char *file = repo_git_path(r, "config"); + int res = git_config_set_multivar_in_file_gently(file, + key, value, + value_pattern, + flags); + free(file); + return res; } void git_config_set_multivar(const char *key, const char *value, const char *value_pattern, unsigned flags) { - git_config_set_multivar_in_file(NULL, key, value, value_pattern, + git_config_set_multivar_in_file(git_path("config"), + key, value, value_pattern, flags); } @@ -267,6 +267,13 @@ void git_config_set_in_file(const char *, const char *, const char *); int git_config_set_gently(const char *, const char *); /** + * Write a config value that should apply to the current worktree. If + * extensions.worktreeConfig is enabled, then the write will happen in the + * current worktree's config. Otherwise, write to the common config file. + */ +int repo_config_set_worktree_gently(struct repository *, const char *, const char *); + +/** * write config values to `.git/config`, takes a key/value pair as parameter. */ void git_config_set(const char *, const char *); @@ -294,6 +301,7 @@ int git_config_parse_key(const char *, char **, size_t *); int git_config_set_multivar_gently(const char *, const char *, const char *, unsigned); void git_config_set_multivar(const char *, const char *, const char *, unsigned); +int repo_config_set_multivar_gently(struct repository *, const char *, const char *, const char *, unsigned); int git_config_set_multivar_in_file_gently(const char *, const char *, const char *, const char *, unsigned); /** @@ -466,7 +474,6 @@ void git_configset_clear(struct config_set *cs); int git_configset_get_value(struct config_set *cs, const char *key, const char **dest); int git_configset_get_string(struct config_set *cs, const char *key, char **dest); -int git_configset_get_string_tmp(struct config_set *cs, const char *key, const char **dest); int git_configset_get_int(struct config_set *cs, const char *key, int *dest); int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest); int git_configset_get_bool(struct config_set *cs, const char *key, int *dest); diff --git a/config.mak.uname b/config.mak.uname index ff6485a0a4..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) @@ -268,10 +267,6 @@ ifeq ($(uname_S),FreeBSD) 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 @@ -531,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 @@ -587,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 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/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..7f64156b8b 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; @@ -67,7 +78,7 @@ static void set_diff_merges(struct rev_info *revs, const char *optarg) diff_merges_setup_func_t func = func_by_opt(optarg); if (!func) - die(_("unknown value for --diff-merges: %s"), optarg); + die(_("invalid value for '%s': '%s'"), "--diff-merges", optarg); func(revs); @@ -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..87657907e7 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -696,26 +696,30 @@ static void mark_complete_and_common_ref(struct fetch_negotiator *negotiator, trace2_region_enter("fetch-pack", "parse_remote_refs_and_find_cutoff", NULL); for (ref = *refs; ref; ref = ref->next) { - struct object *o; + struct commit *commit; + + commit = lookup_commit_in_graph(the_repository, &ref->old_oid); + if (!commit) { + struct object *o; - if (!has_object_file_with_flags(&ref->old_oid, + if (!has_object_file_with_flags(&ref->old_oid, OBJECT_INFO_QUICK | - OBJECT_INFO_SKIP_FETCH_OBJECT)) - continue; - o = parse_object(the_repository, &ref->old_oid); - if (!o) - continue; + OBJECT_INFO_SKIP_FETCH_OBJECT)) + continue; + o = parse_object(the_repository, &ref->old_oid); + if (!o || o->type != OBJ_COMMIT) + continue; + + commit = (struct commit *)o; + } /* * We already have it -- which may mean that we were * in sync with the other side at some time after * that (it is OK if we guess wrong here). */ - if (o->type == OBJ_COMMIT) { - struct commit *commit = (struct commit *)o; - if (!cutoff || cutoff < commit->date) - cutoff = commit->date; - } + if (!cutoff || cutoff < commit->date) + cutoff = commit->date; } trace2_region_leave("fetch-pack", "parse_remote_refs_and_find_cutoff", NULL); @@ -1415,9 +1419,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 350b122221..876907b9df 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -1398,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. 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 diff --git a/git-submodule.sh b/git-submodule.sh index 652861aa66..87772ac891 100755 --- a/git-submodule.sh +++ b/git-submodule.sh @@ -10,7 +10,7 @@ USAGE="[--quiet] [--cached] or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...] or: $dashless [--quiet] init [--] [<path>...] or: $dashless [--quiet] deinit [-f|--force] (--all| [--] <path>...) - or: $dashless [--quiet] update [--init] [--remote] [-N|--no-fetch] [-f|--force] [--checkout|--merge|--rebase] [--[no-]recommend-shallow] [--reference <repository>] [--recursive] [--[no-]single-branch] [--] [<path>...] + or: $dashless [--quiet] update [--init [--filter=<filter-spec>]] [--remote] [-N|--no-fetch] [-f|--force] [--checkout|--merge|--rebase] [--[no-]recommend-shallow] [--reference <repository>] [--recursive] [--[no-]single-branch] [--] [<path>...] or: $dashless [--quiet] set-branch (--default|--branch <branch>) [--] <path> or: $dashless [--quiet] set-url [--] <path> <newurl> or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...] @@ -49,6 +49,7 @@ dissociate= single_branch= jobs= recommend_shallow= +filter= die_if_unmatched () { @@ -347,6 +348,14 @@ cmd_update() --no-single-branch) single_branch="--no-single-branch" ;; + --filter) + case "$2" in '') usage ;; esac + filter="--filter=$2" + shift + ;; + --filter=*) + filter="$1" + ;; --) shift break @@ -361,6 +370,11 @@ cmd_update() shift done + if test -n "$filter" && test "$init" != "1" + then + usage + fi + if test -n "$init" then cmd_init "--" "$@" || return @@ -379,6 +393,7 @@ cmd_update() $single_branch \ $recommend_shallow \ $jobs \ + $filter \ -- \ "$@" || echo "#unmatched" $? } | { @@ -436,6 +436,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv) } else { prefix = NULL; } + assert(!prefix || *prefix); precompose_argv_prefix(argc, argv, NULL); if (use_pager == -1 && run_setup && !(p->option & DELAY_PAGER_CONFIG)) diff --git a/gpg-interface.c b/gpg-interface.c index 17b1e44baa..aa50224e67 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -716,7 +716,7 @@ int git_gpg_config(const char *var, const char *value, void *cb) return config_error_nonbool(var); fmt = get_format_by_name(value); if (!fmt) - return error("unsupported value for %s: %s", + return error(_("invalid value for '%s': '%s'"), var, value); use_format = fmt; return 0; @@ -731,8 +731,8 @@ int git_gpg_config(const char *var, const char *value, void *cb) free(trust); if (ret) - return error("unsupported value for %s: %s", var, - value); + return error(_("invalid value for '%s': '%s'"), + var, value); return 0; } @@ -401,6 +401,18 @@ struct git_graph *graph_init(struct rev_info *opt) return graph; } +void graph_clear(struct git_graph *graph) +{ + if (!graph) + return; + + free(graph->columns); + free(graph->new_columns); + free(graph->mapping); + free(graph->old_mapping); + free(graph); +} + static void graph_update_state(struct git_graph *graph, enum graph_state s) { graph->prev_state = graph->state; @@ -140,6 +140,11 @@ void graph_set_column_colors(const char **colors, unsigned short colors_max); struct git_graph *graph_init(struct rev_info *opt); /* + * Free a struct git_graph. + */ +void graph_clear(struct git_graph *graph); + +/* * Update a git_graph with a new commit. * This will cause the graph to begin outputting lines for the new commit * the next time graph_next_line() is called. @@ -19,27 +19,6 @@ static void std_output(struct grep_opt *opt, const void *buf, size_t size) fwrite(buf, size, 1, stdout); } -static struct grep_opt grep_defaults = { - .relative = 1, - .pathname = 1, - .max_depth = -1, - .pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED, - .colors = { - [GREP_COLOR_CONTEXT] = "", - [GREP_COLOR_FILENAME] = GIT_COLOR_MAGENTA, - [GREP_COLOR_FUNCTION] = "", - [GREP_COLOR_LINENO] = GIT_COLOR_GREEN, - [GREP_COLOR_COLUMNNO] = GIT_COLOR_GREEN, - [GREP_COLOR_MATCH_CONTEXT] = GIT_COLOR_BOLD_RED, - [GREP_COLOR_MATCH_SELECTED] = GIT_COLOR_BOLD_RED, - [GREP_COLOR_SELECTED] = "", - [GREP_COLOR_SEP] = GIT_COLOR_CYAN, - }, - .only_matching = 0, - .color = -1, - .output = std_output, -}; - static const char *color_grep_slots[] = { [GREP_COLOR_CONTEXT] = "context", [GREP_COLOR_FILENAME] = "filename", @@ -75,20 +54,12 @@ define_list_config_array_extra(color_grep_slots, {"match"}); */ int grep_config(const char *var, const char *value, void *cb) { - struct grep_opt *opt = &grep_defaults; + struct grep_opt *opt = cb; const char *slot; if (userdiff_config(var, value) < 0) return -1; - /* - * The instance of grep_opt that we set up here is copied by - * grep_init() to be used by each individual invocation. - * When populating a new field of this structure here, be - * sure to think about ownership -- e.g., you might need to - * override the shallow copy in grep_init() with a deep copy. - */ - if (!strcmp(var, "grep.extendedregexp")) { opt->extended_regexp_option = git_config_bool(var, value); return 0; @@ -134,78 +105,16 @@ int grep_config(const char *var, const char *value, void *cb) return 0; } -/* - * Initialize one instance of grep_opt and copy the - * default values from the template we read the configuration - * information in an earlier call to git_config(grep_config). - */ -void grep_init(struct grep_opt *opt, struct repository *repo, const char *prefix) +void grep_init(struct grep_opt *opt, struct repository *repo) { - *opt = grep_defaults; + struct grep_opt blank = GREP_OPT_INIT; + memcpy(opt, &blank, sizeof(*opt)); opt->repo = repo; - opt->prefix = prefix; - opt->prefix_length = (prefix && *prefix) ? strlen(prefix) : 0; opt->pattern_tail = &opt->pattern_list; opt->header_tail = &opt->header_list; } -static void grep_set_pattern_type_option(enum grep_pattern_type pattern_type, struct grep_opt *opt) -{ - /* - * When committing to the pattern type by setting the relevant - * fields in grep_opt it's generally not necessary to zero out - * the fields we're not choosing, since they won't have been - * set by anything. The extended_regexp_option field is the - * only exception to this. - * - * This is because in the process of parsing grep.patternType - * & grep.extendedRegexp we set opt->pattern_type_option and - * opt->extended_regexp_option, respectively. We then - * internally use opt->extended_regexp_option to see if we're - * compiling an ERE. It must be unset if that's not actually - * the case. - */ - if (pattern_type != GREP_PATTERN_TYPE_ERE && - opt->extended_regexp_option) - opt->extended_regexp_option = 0; - - switch (pattern_type) { - case GREP_PATTERN_TYPE_UNSPECIFIED: - /* fall through */ - - case GREP_PATTERN_TYPE_BRE: - break; - - case GREP_PATTERN_TYPE_ERE: - opt->extended_regexp_option = 1; - break; - - case GREP_PATTERN_TYPE_FIXED: - opt->fixed = 1; - break; - - case GREP_PATTERN_TYPE_PCRE: - opt->pcre2 = 1; - break; - } -} - -void grep_commit_pattern_type(enum grep_pattern_type pattern_type, struct grep_opt *opt) -{ - if (pattern_type != GREP_PATTERN_TYPE_UNSPECIFIED) - grep_set_pattern_type_option(pattern_type, opt); - else if (opt->pattern_type_option != GREP_PATTERN_TYPE_UNSPECIFIED) - grep_set_pattern_type_option(opt->pattern_type_option, opt); - else if (opt->extended_regexp_option) - /* - * This branch *must* happen after setting from the - * opt->pattern_type_option above, we don't want - * grep.extendedRegexp to override grep.patternType! - */ - grep_set_pattern_type_option(GREP_PATTERN_TYPE_ERE, opt); -} - static struct grep_pat *create_grep_pat(const char *pat, size_t patlen, const char *origin, int no, enum grep_pat_token t, @@ -523,11 +432,17 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) int err; int regflags = REG_NEWLINE; + if (opt->pattern_type_option == GREP_PATTERN_TYPE_UNSPECIFIED) + opt->pattern_type_option = (opt->extended_regexp_option + ? GREP_PATTERN_TYPE_ERE + : GREP_PATTERN_TYPE_BRE); + p->word_regexp = opt->word_regexp; p->ignore_case = opt->ignore_case; - p->fixed = opt->fixed; + p->fixed = opt->pattern_type_option == GREP_PATTERN_TYPE_FIXED; - if (memchr(p->pattern, 0, p->patternlen) && !opt->pcre2) + if (opt->pattern_type_option != GREP_PATTERN_TYPE_PCRE && + memchr(p->pattern, 0, p->patternlen)) die(_("given pattern contains NULL byte (via -f <file>). This is only supported with -P under PCRE v2")); p->is_fixed = is_fixed(p->pattern, p->patternlen); @@ -578,14 +493,14 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) return; } - if (opt->pcre2) { + if (opt->pattern_type_option == GREP_PATTERN_TYPE_PCRE) { compile_pcre2_pattern(p, opt); return; } if (p->ignore_case) regflags |= REG_ICASE; - if (opt->extended_regexp_option) + if (opt->pattern_type_option == GREP_PATTERN_TYPE_ERE) regflags |= REG_EXTENDED; err = regcomp(&p->regexp, p->pattern, regflags); if (err) { @@ -134,9 +134,6 @@ struct grep_opt { */ struct repository *repo; - const char *prefix; - int prefix_length; - regex_t regexp; int linenum; int columnnum; int invert; @@ -146,7 +143,6 @@ struct grep_opt { int unmatch_name_only; int count; int word_regexp; - int fixed; int all_match; int no_body_match; int body_hit; @@ -157,7 +153,6 @@ struct grep_opt { int allow_textconv; int extended; int use_reflog_filter; - int pcre2; int relative; int pathname; int null_following_name; @@ -167,7 +162,7 @@ struct grep_opt { int funcname; int funcbody; int extended_regexp_option; - int pattern_type_option; + enum grep_pattern_type pattern_type_option; int ignore_locale; char colors[NR_GREP_COLORS][COLOR_MAXLEN]; unsigned pre_context; @@ -182,9 +177,29 @@ struct grep_opt { void *output_priv; }; +#define GREP_OPT_INIT { \ + .relative = 1, \ + .pathname = 1, \ + .max_depth = -1, \ + .pattern_type_option = GREP_PATTERN_TYPE_UNSPECIFIED, \ + .colors = { \ + [GREP_COLOR_CONTEXT] = "", \ + [GREP_COLOR_FILENAME] = GIT_COLOR_MAGENTA, \ + [GREP_COLOR_FUNCTION] = "", \ + [GREP_COLOR_LINENO] = GIT_COLOR_GREEN, \ + [GREP_COLOR_COLUMNNO] = GIT_COLOR_GREEN, \ + [GREP_COLOR_MATCH_CONTEXT] = GIT_COLOR_BOLD_RED, \ + [GREP_COLOR_MATCH_SELECTED] = GIT_COLOR_BOLD_RED, \ + [GREP_COLOR_SELECTED] = "", \ + [GREP_COLOR_SEP] = GIT_COLOR_CYAN, \ + }, \ + .only_matching = 0, \ + .color = -1, \ + .output = std_output, \ +} + int grep_config(const char *var, const char *value, void *); -void grep_init(struct grep_opt *, struct repository *repo, const char *prefix); -void grep_commit_pattern_type(enum grep_pattern_type, struct grep_opt *opt); +void grep_init(struct grep_opt *, struct repository *repo); void append_grep_pat(struct grep_opt *opt, const char *pat, size_t patlen, const char *origin, int no, enum grep_pat_token t); void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t); 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) { @@ -34,7 +34,8 @@ static void ensure_config_read(void) } else if (!strcmp(str, "ignore")) { /* do nothing */ } else { - die(_("invalid value '%s' for lsrefs.unborn"), str); + die(_("invalid value for '%s': '%s'"), + "lsrefs.unborn", str); } } config_read = 1; 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 d455fca7d2..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 " @@ -4259,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 */ @@ -4360,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 @@ -4560,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); 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 060d892a97..f0e327f91f 100644 --- a/object-name.c +++ b/object-name.c @@ -352,35 +352,118 @@ static int init_object_disambiguation(struct repository *r, return 0; } +struct ambiguous_output { + const struct disambiguate_state *ds; + struct strbuf advice; + struct strbuf sb; +}; + static int show_ambiguous_object(const struct object_id *oid, void *data) { - const struct disambiguate_state *ds = data; - struct strbuf desc = STRBUF_INIT; + struct ambiguous_output *state = data; + const struct disambiguate_state *ds = state->ds; + struct strbuf *advice = &state->advice; + struct strbuf *sb = &state->sb; int type; + const char *hash; if (ds->fn && !ds->fn(ds->repo, oid, ds->cb_data)) return 0; + hash = repo_find_unique_abbrev(ds->repo, oid, DEFAULT_ABBREV); type = oid_object_info(ds->repo, oid, NULL); + + if (type < 0) { + /* + * TRANSLATORS: This is a line of ambiguous object + * output shown when we cannot look up or parse the + * object in question. E.g. "deadbeef [bad object]". + */ + strbuf_addf(sb, _("%s [bad object]"), hash); + goto out; + } + + assert(type == OBJ_TREE || type == OBJ_COMMIT || + type == OBJ_BLOB || type == OBJ_TAG); + if (type == OBJ_COMMIT) { + struct strbuf date = STRBUF_INIT; + struct strbuf msg = STRBUF_INIT; struct commit *commit = lookup_commit(ds->repo, oid); + if (commit) { struct pretty_print_context pp = {0}; pp.date_mode.type = DATE_SHORT; - format_commit_message(commit, " %ad - %s", &desc, &pp); + format_commit_message(commit, "%ad", &date, &pp); + format_commit_message(commit, "%s", &msg, &pp); } + + /* + * TRANSLATORS: This is a line of ambiguous commit + * object output. E.g.: + * + * "deadbeef commit 2021-01-01 - Some Commit Message" + */ + strbuf_addf(sb, _("%s commit %s - %s"), hash, date.buf, + msg.buf); + + strbuf_release(&date); + strbuf_release(&msg); } else if (type == OBJ_TAG) { struct tag *tag = lookup_tag(ds->repo, oid); - if (!parse_tag(tag) && tag->tag) - strbuf_addf(&desc, " %s", tag->tag); + + if (!parse_tag(tag) && tag->tag) { + /* + * TRANSLATORS: This is a line of ambiguous + * tag object output. E.g.: + * + * "deadbeef tag 2022-01-01 - Some Tag Message" + * + * The second argument is the YYYY-MM-DD found + * in the tag. + * + * The third argument is the "tag" string + * from object.c. + */ + strbuf_addf(sb, _("%s tag %s - %s"), hash, + show_date(tag->date, 0, DATE_MODE(SHORT)), + tag->tag); + } else { + /* + * TRANSLATORS: This is a line of ambiguous + * tag object output where we couldn't parse + * the tag itself. E.g.: + * + * "deadbeef [bad tag, could not parse it]" + */ + strbuf_addf(sb, _("%s [bad tag, could not parse it]"), + hash); + } + } else if (type == OBJ_TREE) { + /* + * TRANSLATORS: This is a line of ambiguous <type> + * object output. E.g. "deadbeef tree". + */ + strbuf_addf(sb, _("%s tree"), hash); + } else if (type == OBJ_BLOB) { + /* + * TRANSLATORS: This is a line of ambiguous <type> + * object output. E.g. "deadbeef blob". + */ + strbuf_addf(sb, _("%s blob"), hash); } - advise(" %s %s%s", - repo_find_unique_abbrev(ds->repo, oid, DEFAULT_ABBREV), - type_name(type) ? type_name(type) : "unknown type", - desc.buf); - strbuf_release(&desc); +out: + /* + * TRANSLATORS: This is line item of ambiguous object output + * from describe_ambiguous_object() above. For RTL languages + * you'll probably want to swap the "%s" and leading " " space + * around. + */ + strbuf_addf(advice, _(" %s\n"), sb->buf); + + strbuf_reset(sb); return 0; } @@ -477,6 +560,11 @@ static enum get_oid_result get_short_oid(struct repository *r, if (!quietly && (status == SHORT_NAME_AMBIGUOUS)) { struct oid_array collect = OID_ARRAY_INIT; + struct ambiguous_output out = { + .ds = &ds, + .sb = STRBUF_INIT, + .advice = STRBUF_INIT, + }; error(_("short object ID %s is ambiguous"), ds.hex_pfx); @@ -489,13 +577,22 @@ static enum get_oid_result get_short_oid(struct repository *r, if (!ds.ambiguous) ds.fn = NULL; - advise(_("The candidates are:")); repo_for_each_abbrev(r, ds.hex_pfx, collect_ambiguous, &collect); sort_ambiguous_oid_array(r, &collect); - if (oid_array_for_each(&collect, show_ambiguous_object, &ds)) + if (oid_array_for_each(&collect, show_ambiguous_object, &out)) BUG("show_ambiguous_object shouldn't return non-zero"); + + /* + * TRANSLATORS: The argument is the list of ambiguous + * objects composed in show_ambiguous_object(). See + * its "TRANSLATORS" comments for details. + */ + advise(_("The candidates are:\n%s"), out.advice.buf); + oid_array_clear(&collect); + strbuf_release(&out.advice); + strbuf_release(&out.sb); } return status; diff --git a/pack-bitmap-write.c b/pack-bitmap-write.c index 9c55c1531e..cab3eaa2ac 100644 --- a/pack-bitmap-write.c +++ b/pack-bitmap-write.c @@ -575,15 +575,15 @@ void bitmap_writer_select_commits(struct commit **indexed_commits, QSORT(indexed_commits, indexed_commits_nr, date_compare); - if (writer.show_progress) - writer.progress = start_progress("Selecting bitmap commits", 0); - if (indexed_commits_nr < 100) { for (i = 0; i < indexed_commits_nr; ++i) push_bitmapped_commit(indexed_commits[i]); return; } + if (writer.show_progress) + writer.progress = start_progress("Selecting bitmap commits", 0); + for (;;) { struct commit *chosen = NULL; 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/parallel-checkout.c b/parallel-checkout.c index 8dd7e7bad4..31a3d0ee1b 100644 --- a/parallel-checkout.c +++ b/parallel-checkout.c @@ -39,8 +39,8 @@ void get_parallel_checkout_configs(int *num_workers, int *threshold) if (env_workers && *env_workers) { if (strtol_i(env_workers, 10, num_workers)) { - die("invalid value for GIT_TEST_CHECKOUT_WORKERS: '%s'", - env_workers); + die(_("invalid value for '%s': '%s'"), + "GIT_TEST_CHECKOUT_WORKERS", env_workers); } if (*num_workers < 1) *num_workers = online_cpus(); diff --git a/parse-options.c b/parse-options.c index 2437ad3bcd..6e57744fd2 100644 --- a/parse-options.c +++ b/parse-options.c @@ -1092,3 +1092,37 @@ void NORETURN usage_msg_optf(const char * const fmt, usage_msg_opt(msg.buf, usagestr, options); } + +void die_for_incompatible_opt4(int opt1, const char *opt1_name, + int opt2, const char *opt2_name, + int opt3, const char *opt3_name, + int opt4, const char *opt4_name) +{ + int count = 0; + const char *options[4]; + + if (opt1) + options[count++] = opt1_name; + if (opt2) + options[count++] = opt2_name; + if (opt3) + options[count++] = opt3_name; + if (opt4) + options[count++] = opt4_name; + switch (count) { + case 4: + die(_("options '%s', '%s', '%s', and '%s' cannot be used together"), + opt1_name, opt2_name, opt3_name, opt4_name); + break; + case 3: + die(_("options '%s', '%s', and '%s' cannot be used together"), + options[0], options[1], options[2]); + break; + case 2: + die(_("options '%s' and '%s' cannot be used together"), + options[0], options[1]); + break; + default: + break; + } +} diff --git a/parse-options.h b/parse-options.h index 659a4c28b2..685fccac13 100644 --- a/parse-options.h +++ b/parse-options.h @@ -240,6 +240,22 @@ void NORETURN usage_msg_optf(const char *fmt, const char * const *usagestr, const struct option *options, ...); +void die_for_incompatible_opt4(int opt1, const char *opt1_name, + int opt2, const char *opt2_name, + int opt3, const char *opt3_name, + int opt4, const char *opt4_name); + + +static inline void die_for_incompatible_opt3(int opt1, const char *opt1_name, + int opt2, const char *opt2_name, + int opt3, const char *opt3_name) +{ + die_for_incompatible_opt4(opt1, opt1_name, + opt2, opt2_name, + opt3, opt3_name, + 0, ""); +} + /* * 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/progress.c b/progress.c index 680c6a8bf9..0cdd875d37 100644 --- a/progress.c +++ b/progress.c @@ -311,32 +311,39 @@ struct progress *start_delayed_sparse_progress(const char *title, static void finish_if_sparse(struct progress *progress) { - if (progress && - progress->sparse && + if (progress->sparse && progress->last_value != progress->total) display_progress(progress, progress->total); } -void stop_progress(struct progress **p_progress) +static void force_last_update(struct progress *progress, const char *msg) { - if (!p_progress) - BUG("don't provide NULL to stop_progress"); - - finish_if_sparse(*p_progress); - - if (*p_progress) { - trace2_data_intmax("progress", the_repository, "total_objects", - (*p_progress)->total); + char *buf; + struct throughput *tp = progress->throughput; + + if (tp) { + uint64_t now_ns = progress_getnanotime(progress); + unsigned int misecs, rate; + misecs = ((now_ns - progress->start_ns) * 4398) >> 32; + rate = tp->curr_total / (misecs ? misecs : 1); + throughput_string(&tp->display, tp->curr_total, rate); + } + progress_update = 1; + buf = xstrfmt(", %s.\n", msg); + display(progress, progress->last_value, buf); + free(buf); +} - if ((*p_progress)->throughput) - trace2_data_intmax("progress", the_repository, - "total_bytes", - (*p_progress)->throughput->curr_total); +static void log_trace2(struct progress *progress) +{ + trace2_data_intmax("progress", the_repository, "total_objects", + progress->total); - trace2_region_leave("progress", (*p_progress)->title, the_repository); - } + if (progress->throughput) + trace2_data_intmax("progress", the_repository, "total_bytes", + progress->throughput->curr_total); - stop_progress_msg(p_progress, _("done")); + trace2_region_leave("progress", progress->title, the_repository); } void stop_progress_msg(struct progress **p_progress, const char *msg) @@ -350,23 +357,12 @@ void stop_progress_msg(struct progress **p_progress, const char *msg) if (!progress) return; *p_progress = NULL; - if (progress->last_value != -1) { - /* Force the last update */ - char *buf; - struct throughput *tp = progress->throughput; - - if (tp) { - uint64_t now_ns = progress_getnanotime(progress); - unsigned int misecs, rate; - misecs = ((now_ns - progress->start_ns) * 4398) >> 32; - rate = tp->curr_total / (misecs ? misecs : 1); - throughput_string(&tp->display, tp->curr_total, rate); - } - progress_update = 1; - buf = xstrfmt(", %s.\n", msg); - display(progress, progress->last_value, buf); - free(buf); - } + + finish_if_sparse(progress); + if (progress->last_value != -1) + force_last_update(progress, msg); + log_trace2(progress); + clear_progress_signal(); strbuf_release(&progress->counters_sb); if (progress->throughput) diff --git a/progress.h b/progress.h index f1913acf73..3a945637c8 100644 --- a/progress.h +++ b/progress.h @@ -1,5 +1,6 @@ #ifndef PROGRESS_H #define PROGRESS_H +#include "gettext.h" struct progress; @@ -18,7 +19,9 @@ struct progress *start_sparse_progress(const char *title, uint64_t total); struct progress *start_delayed_progress(const char *title, uint64_t total); struct progress *start_delayed_sparse_progress(const char *title, uint64_t total); -void stop_progress(struct progress **progress); -void stop_progress_msg(struct progress **progress, const char *msg); - +void stop_progress_msg(struct progress **p_progress, const char *msg); +static inline void stop_progress(struct progress **p_progress) +{ + stop_progress_msg(p_progress, _("done")); +} #endif diff --git a/read-cache.c b/read-cache.c index 63a792e578..79b9b99ebf 100644 --- a/read-cache.c +++ b/read-cache.c @@ -1340,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. @@ -1353,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) @@ -794,7 +794,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) || @@ -999,6 +999,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; @@ -1006,12 +1007,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) @@ -1150,7 +1152,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) || @@ -2066,6 +2068,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; @@ -226,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(...) || @@ -564,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 a40267b3ae..f59589d6cc 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -1114,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( @@ -1185,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; @@ -1242,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; @@ -1251,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); @@ -1265,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; @@ -1281,6 +1290,7 @@ error: else error(_("could not delete references: %s"), err.buf); + ref_transaction_free(transaction); strbuf_release(&err); return -1; } @@ -2751,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; @@ -3022,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; 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. */ 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); @@ -9,37 +9,106 @@ #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; } @@ -48,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; @@ -59,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) { @@ -91,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_hooks_l("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..2bb913f2f3 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; @@ -1838,7 +1846,7 @@ void repo_init_revisions(struct repository *r, revs->commit_format = CMIT_FMT_DEFAULT; revs->expand_tabs_in_log_default = 8; - grep_init(&revs->grep_filter, revs->repo, prefix); + grep_init(&revs->grep_filter, revs->repo); revs->grep_filter.status_only = 1; repo_diff_setup(revs->repo, &revs->diffopt); @@ -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; @@ -2424,9 +2434,11 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->pretty_given = 1; revs->abbrev_commit = 1; } else if (!strcmp(arg, "--graph")) { - revs->topo_order = 1; - revs->rewrite_parents = 1; + graph_clear(revs->graph); revs->graph = graph_init(revs); + } else if (!strcmp(arg, "--no-graph")) { + graph_clear(revs->graph); + revs->graph = NULL; } else if (!strcmp(arg, "--encode-email-headers")) { revs->encode_email_headers = 1; } else if (!strcmp(arg, "--no-encode-email-headers")) { @@ -2523,8 +2535,6 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg unkv[(*unkc)++] = arg; return opts; } - if (revs->graph && revs->track_linear) - die(_("options '%s' and '%s' cannot be used together"), "--show-linear-break", "--graph"); return 1; } @@ -2543,6 +2553,17 @@ void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx, ctx->argc -= n; } +void revision_opts_finish(struct rev_info *revs) +{ + if (revs->graph && revs->track_linear) + die(_("options '%s' and '%s' cannot be used together"), "--show-linear-break", "--graph"); + + if (revs->graph) { + revs->topo_order = 1; + revs->rewrite_parents = 1; + } +} + static int for_each_bisect_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data, const char *term) { @@ -2785,6 +2806,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s break; } } + revision_opts_finish(revs); if (prune_data.nr) { /* @@ -2860,8 +2882,6 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s diff_setup_done(&revs->diffopt); - grep_commit_pattern_type(GREP_PATTERN_TYPE_UNSPECIFIED, - &revs->grep_filter); if (!is_encoding_utf8(get_log_output_encoding())) revs->grep_filter.ignore_locale = 1; compile_grep_patterns(&revs->grep_filter); @@ -3345,7 +3365,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..b9c2421687 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); @@ -372,6 +377,7 @@ void parse_revision_opt(struct rev_info *revs, struct parse_opt_ctx_t *ctx, #define REVARG_COMMITTISH 02 int handle_revision_arg(const char *arg, struct rev_info *revs, int flags, unsigned revarg_opt); +void revision_opts_finish(struct rev_info *revs); /** * Reset the flags used by the revision walking api. You can use this to do @@ -398,7 +404,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/sequencer.c b/sequencer.c index fb978a53a0..35006c0cea 100644 --- a/sequencer.c +++ b/sequencer.c @@ -2802,7 +2802,7 @@ static int populate_opts_cb(const char *key, const char *value, void *data) return error(_("invalid key: %s"), key); if (!error_flag) - return error(_("invalid value for %s: %s"), key, value); + return error(_("invalid value for '%s': '%s'"), key, value); return 0; } @@ -3584,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; @@ -4085,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; @@ -4101,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, @@ -4122,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")); @@ -4211,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); @@ -559,7 +559,8 @@ static enum extension_result handle_extension(const char *var, return config_error_nonbool(var); format = hash_algo_by_name(value); if (format == GIT_HASH_UNKNOWN) - return error("invalid value for 'extensions.objectformat'"); + return error(_("invalid value for '%s': '%s'"), + "extensions.objectformat", value); data->hash_algo = format; return EXTENSION_OK; } @@ -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 08f54747bb..fdbe97b976 100644 --- a/sparse-index.c +++ b/sparse-index.c @@ -99,13 +99,9 @@ static int convert_to_sparse_rec(struct index_state *istate, int set_sparse_index_config(struct repository *repo, int enable) { - int res; - char *config_path = repo_git_path(repo, "config.worktree"); - res = git_config_set_in_file_gently(config_path, - "index.sparse", - enable ? "true" : NULL); - free(config_path); - + int res = repo_config_set_worktree_gently(repo, + "index.sparse", + enable ? "true" : "false"); prepare_repo_settings(repo); repo->settings.sparse_index = enable; return res; diff --git a/submodule-config.c b/submodule-config.c index f95344028b..29668b0620 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 @@ -496,7 +497,7 @@ static int parse_config(const char *var, const char *value, void *data) else if (parse_submodule_update_strategy(value, &submodule->update_strategy) < 0 || submodule->update_strategy.type == SM_UPDATE_COMMAND) - die(_("invalid value for %s"), var); + die(_("invalid value for '%s'"), var); } else if (!strcmp(item.buf, "shallow")) { if (!me->overwrite && submodule->recommend_shallow != -1) warn_multiple_config(me->treeish_name, submodule->name, @@ -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-progress.c b/t/helper/test-progress.c index 5d05cbe789..6cc9735b60 100644 --- a/t/helper/test-progress.c +++ b/t/helper/test-progress.c @@ -3,6 +3,9 @@ * * Reads instructions from standard input, one instruction per line: * + * "start <total>[ <title>]" - Call start_progress(title, total), + * Uses the default title of "Working hard" + * if the " <title>" is omitted. * "progress <items>" - Call display_progress() with the given item count * as parameter. * "throughput <bytes> <millis> - Call display_throughput() with the given @@ -10,6 +13,7 @@ * specify the time elapsed since the * start_progress() call. * "update" - Set the 'progress_update' flag. + * "stop" - Call stop_progress(). * * See 't0500-progress-display.sh' for examples. */ @@ -19,34 +23,50 @@ #include "parse-options.h" #include "progress.h" #include "strbuf.h" +#include "string-list.h" int cmd__progress(int argc, const char **argv) { - int total = 0; - const char *title; + const char *const default_title = "Working hard"; + struct string_list titles = STRING_LIST_INIT_DUP; struct strbuf line = STRBUF_INIT; - struct progress *progress; + struct progress *progress = NULL; const char *usage[] = { - "test-tool progress [--total=<n>] <progress-title>", + "test-tool progress <stdin", NULL }; struct option options[] = { - OPT_INTEGER(0, "total", &total, "total number of items"), OPT_END(), }; argc = parse_options(argc, argv, NULL, options, usage, 0); - if (argc != 1) - die("need a title for the progress output"); - title = argv[0]; + if (argc) + usage_with_options(usage, options); progress_testing = 1; - progress = start_progress(title, total); while (strbuf_getline(&line, stdin) != EOF) { char *end; - if (skip_prefix(line.buf, "progress ", (const char **) &end)) { + if (skip_prefix(line.buf, "start ", (const char **) &end)) { + uint64_t total = strtoull(end, &end, 10); + const char *title; + + /* + * We can't use "end + 1" as an argument to + * start_progress(), it doesn't xstrdup() its + * "title" argument. We need to hold onto a + * valid "char *" for it until the end. + */ + if (!*end) + title = default_title; + else if (*end == ' ') + title = string_list_insert(&titles, end + 1)->string; + else + die("invalid input: '%s'\n", line.buf); + + progress = start_progress(title, total); + } else if (skip_prefix(line.buf, "progress ", (const char **) &end)) { uint64_t item_count = strtoull(end, &end, 10); if (*end != '\0') die("invalid input: '%s'\n", line.buf); @@ -63,12 +83,16 @@ int cmd__progress(int argc, const char **argv) die("invalid input: '%s'\n", line.buf); progress_test_ns = test_ms * 1000 * 1000; display_throughput(progress, byte_count); - } else if (!strcmp(line.buf, "update")) + } else if (!strcmp(line.buf, "update")) { progress_test_force_update(); - else + } else if (!strcmp(line.buf, "stop")) { + stop_progress(&progress); + } else { die("invalid input: '%s'\n", line.buf); + } } - stop_progress(&progress); + strbuf_release(&line); + string_list_clear(&titles, 0); return 0; } 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/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/t0001-init.sh b/t/t0001-init.sh index 3235ab4d53..d479303efa 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -6,7 +6,8 @@ TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh check_config () { - if test -d "$1" && test -f "$1/config" && test -d "$1/refs" + if test_path_is_dir "$1" && + test_path_is_file "$1/config" && test_path_is_dir "$1/refs" then : happy else 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/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/t0500-progress-display.sh b/t/t0500-progress-display.sh index 22058b503a..1eb3a8306b 100755 --- a/t/t0500-progress-display.sh +++ b/t/t0500-progress-display.sh @@ -2,6 +2,7 @@ test_description='progress display' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh show_cr () { @@ -17,6 +18,7 @@ test_expect_success 'simple progress display' ' EOF cat >in <<-\EOF && + start 0 update progress 1 update @@ -25,8 +27,9 @@ test_expect_success 'simple progress display' ' progress 4 update progress 5 + stop EOF - test-tool progress "Working hard" <in 2>stderr && + test-tool progress <in 2>stderr && show_cr <stderr >out && test_cmp expect out @@ -41,11 +44,13 @@ test_expect_success 'progress display with total' ' EOF cat >in <<-\EOF && + start 3 progress 1 progress 2 progress 3 + stop EOF - test-tool progress --total=3 "Working hard" <in 2>stderr && + test-tool progress <in 2>stderr && show_cr <stderr >out && test_cmp expect out @@ -62,14 +67,14 @@ Working hard.......2.........3.........4.........5.........6: EOF cat >in <<-\EOF && + start 100000 Working hard.......2.........3.........4.........5.........6 progress 100 progress 1000 progress 10000 progress 100000 + stop EOF - test-tool progress --total=100000 \ - "Working hard.......2.........3.........4.........5.........6" \ - <in 2>stderr && + test-tool progress <in 2>stderr && show_cr <stderr >out && test_cmp expect out @@ -88,16 +93,16 @@ Working hard.......2.........3.........4.........5.........6: EOF cat >in <<-\EOF && + start 100000 Working hard.......2.........3.........4.........5.........6 update progress 1 update progress 2 progress 10000 progress 100000 + stop EOF - test-tool progress --total=100000 \ - "Working hard.......2.........3.........4.........5.........6" \ - <in 2>stderr && + test-tool progress <in 2>stderr && show_cr <stderr >out && test_cmp expect out @@ -116,14 +121,14 @@ Working hard.......2.........3.........4.........5.........6: EOF cat >in <<-\EOF && + start 100000 Working hard.......2.........3.........4.........5.........6 progress 25000 progress 50000 progress 75000 progress 100000 + stop EOF - test-tool progress --total=100000 \ - "Working hard.......2.........3.........4.........5.........6" \ - <in 2>stderr && + test-tool progress <in 2>stderr && show_cr <stderr >out && test_cmp expect out @@ -140,14 +145,14 @@ Working hard.......2.........3.........4.........5.........6.........7.........: EOF cat >in <<-\EOF && + start 100000 Working hard.......2.........3.........4.........5.........6.........7......... progress 25000 progress 50000 progress 75000 progress 100000 + stop EOF - test-tool progress --total=100000 \ - "Working hard.......2.........3.........4.........5.........6.........7........." \ - <in 2>stderr && + test-tool progress <in 2>stderr && show_cr <stderr >out && test_cmp expect out @@ -164,12 +169,14 @@ test_expect_success 'progress shortens - crazy caller' ' EOF cat >in <<-\EOF && + start 1000 progress 100 progress 200 progress 1 progress 1000 + stop EOF - test-tool progress --total=1000 "Working hard" <in 2>stderr && + test-tool progress <in 2>stderr && show_cr <stderr >out && test_cmp expect out @@ -185,6 +192,7 @@ test_expect_success 'progress display with throughput' ' EOF cat >in <<-\EOF && + start 0 throughput 102400 1000 update progress 10 @@ -197,8 +205,9 @@ test_expect_success 'progress display with throughput' ' throughput 409600 4000 update progress 40 + stop EOF - test-tool progress "Working hard" <in 2>stderr && + test-tool progress <in 2>stderr && show_cr <stderr >out && test_cmp expect out @@ -214,6 +223,7 @@ test_expect_success 'progress display with throughput and total' ' EOF cat >in <<-\EOF && + start 40 throughput 102400 1000 progress 10 throughput 204800 2000 @@ -222,8 +232,9 @@ test_expect_success 'progress display with throughput and total' ' progress 30 throughput 409600 4000 progress 40 + stop EOF - test-tool progress --total=40 "Working hard" <in 2>stderr && + test-tool progress <in 2>stderr && show_cr <stderr >out && test_cmp expect out @@ -239,6 +250,7 @@ test_expect_success 'cover up after throughput shortens' ' EOF cat >in <<-\EOF && + start 0 throughput 409600 1000 update progress 1 @@ -251,8 +263,9 @@ test_expect_success 'cover up after throughput shortens' ' throughput 1638400 4000 update progress 4 + stop EOF - test-tool progress "Working hard" <in 2>stderr && + test-tool progress <in 2>stderr && show_cr <stderr >out && test_cmp expect out @@ -267,6 +280,7 @@ test_expect_success 'cover up after throughput shortens a lot' ' EOF cat >in <<-\EOF && + start 0 throughput 1 1000 update progress 1 @@ -276,8 +290,9 @@ test_expect_success 'cover up after throughput shortens a lot' ' throughput 3145728 3000 update progress 3 + stop EOF - test-tool progress "Working hard" <in 2>stderr && + test-tool progress <in 2>stderr && show_cr <stderr >out && test_cmp expect out @@ -285,6 +300,7 @@ test_expect_success 'cover up after throughput shortens a lot' ' test_expect_success 'progress generates traces' ' cat >in <<-\EOF && + start 40 throughput 102400 1000 update progress 10 @@ -297,10 +313,11 @@ test_expect_success 'progress generates traces' ' throughput 409600 4000 update progress 40 + stop EOF - GIT_TRACE2_EVENT="$(pwd)/trace.event" test-tool progress --total=40 \ - "Working hard" <in 2>stderr && + GIT_TRACE2_EVENT="$(pwd)/trace.event" test-tool progress \ + <in 2>stderr && # t0212/parse_events.perl intentionally omits regions and data. test_region progress "Working hard" trace.event && @@ -308,4 +325,54 @@ test_expect_success 'progress generates traces' ' grep "\"key\":\"total_bytes\",\"value\":\"409600\"" trace.event ' +test_expect_success 'progress generates traces: stop / start' ' + cat >in <<-\EOF && + start 0 + stop + EOF + + GIT_TRACE2_EVENT="$PWD/trace-startstop.event" test-tool progress \ + <in 2>stderr && + test_region progress "Working hard" trace-startstop.event +' + +test_expect_success 'progress generates traces: start without stop' ' + cat >in <<-\EOF && + start 0 + EOF + + GIT_TRACE2_EVENT="$PWD/trace-start.event" \ + LSAN_OPTIONS=detect_leaks=0 \ + test-tool progress \ + <in 2>stderr && + grep region_enter.*progress trace-start.event && + ! grep region_leave.*progress trace-start.event +' + +test_expect_success 'progress generates traces: stop without start' ' + cat >in <<-\EOF && + stop + EOF + + GIT_TRACE2_EVENT="$PWD/trace-stop.event" test-tool progress \ + <in 2>stderr && + ! grep region_enter.*progress trace-stop.event && + ! grep region_leave.*progress trace-stop.event +' + +test_expect_success 'progress generates traces: start with active progress bar (no stops)' ' + cat >in <<-\EOF && + start 0 One + start 0 Two + EOF + + GIT_TRACE2_EVENT="$PWD/trace-2start.event" \ + LSAN_OPTIONS=detect_leaks=0 \ + test-tool progress \ + <in 2>stderr && + grep region_enter.*progress.*One trace-2start.event && + grep region_enter.*progress.*Two trace-2start.event && + ! grep region_leave trace-2start.event +' + test_done 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 3592d12442..502d42d183 100755 --- a/t/t1091-sparse-checkout-builtin.sh +++ b/t/t1091-sparse-checkout-builtin.sh @@ -126,7 +126,7 @@ test_expect_success 'switching to cone mode with non-cone mode patterns' ' cd bad-patterns && git sparse-checkout init && git sparse-checkout add dir && - git config core.sparseCheckoutCone true && + git config --worktree core.sparseCheckoutCone true && test_must_fail git sparse-checkout add dir 2>err && grep "existing sparse-checkout patterns do not use cone mode" err ) @@ -155,9 +155,9 @@ test_expect_success 'interaction with clone --no-checkout (unborn index)' ' ' test_expect_success 'set enables config' ' - git init empty-config && + git init worktree-config && ( - cd empty-config && + cd worktree-config && test_commit test file && test_path_is_missing .git/config.worktree && git sparse-checkout set nothing && @@ -210,6 +210,21 @@ test_expect_success 'add to sparse-checkout' ' check_files repo "a folder1 folder2" ' +test_expect_success 'worktree: add copies sparse-checkout patterns' ' + cat repo/.git/info/sparse-checkout >old && + test_when_finished cp old repo/.git/info/sparse-checkout && + test_when_finished git -C repo worktree remove ../worktree && + git -C repo sparse-checkout set --no-cone "/*" && + git -C repo worktree add --quiet ../worktree 2>err && + test_must_be_empty err && + new="$(git -C worktree rev-parse --git-path info/sparse-checkout)" && + test_path_is_file "$new" && + test_cmp repo/.git/info/sparse-checkout "$new" && + git -C worktree sparse-checkout set --cone && + test_cmp_config -C worktree true core.sparseCheckoutCone && + test_must_fail git -C repo core.sparseCheckoutCone +' + test_expect_success 'cone mode: match patterns' ' git -C repo config --worktree core.sparseCheckoutCone true && rm -rf repo/a repo/folder1 repo/folder2 && @@ -261,7 +276,7 @@ test_expect_success 'sparse-index enabled and disabled' ' test_cmp expect actual && git -C repo config --list >config && - ! grep index.sparse config + test_cmp_config -C repo false index.sparse ' test_expect_success 'cone mode: init and set' ' @@ -524,13 +539,13 @@ test_expect_success 'interaction with submodules' ' ' test_expect_success 'different sparse-checkouts with worktrees' ' + git -C repo sparse-checkout set --cone deep folder1 && git -C repo worktree add --detach ../worktree && - check_files worktree "a deep folder1 folder2" && - git -C worktree sparse-checkout init --cone && - git -C repo sparse-checkout set folder1 && - git -C worktree sparse-checkout set deep/deeper1 && - check_files repo a folder1 && - check_files worktree a deep + check_files worktree "a deep folder1" && + git -C repo sparse-checkout set --cone folder1 && + git -C worktree sparse-checkout set --cone deep/deeper1 && + check_files repo "a folder1" && + check_files worktree "a deep" ' test_expect_success 'set using filename keeps file on-disk' ' 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/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/t1410-reflog.sh b/t/t1410-reflog.sh index d7ddf7612d..68f69bb543 100755 --- a/t/t1410-reflog.sh +++ b/t/t1410-reflog.sh @@ -341,7 +341,7 @@ test_expect_success 'stale dirs do not cause d/f conflicts (reflogs off)' ' # Each line is 114 characters, so we need 75 to still have a few before the # last 8K. The 89-character padding on the final entry lines up our # newline exactly. -test_expect_success SHA1 'parsing reverse reflogs at BUFSIZ boundaries' ' +test_expect_success REFFILES,SHA1 'parsing reverse reflogs at BUFSIZ boundaries' ' git checkout -b reflogskip && zf=$(test_oid zero_2) && ident="abc <xyz> 0000000001 +0000" && @@ -418,7 +418,8 @@ test_expect_success 'expire with multiple worktrees' ' test_commit -C link-wt foobar && test_tick && git reflog expire --verbose --all --expire=$test_tick && - test_must_be_empty .git/worktrees/link-wt/logs/HEAD + test-tool ref-store worktree:link-wt for-each-reflog-ent HEAD >actual && + test_must_be_empty actual ) ' 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/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh index b0119bf8bc..98cefe3b70 100755 --- a/t/t1512-rev-parse-disambiguation.sh +++ b/t/t1512-rev-parse-disambiguation.sh @@ -25,6 +25,87 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh +test_cmp_failed_rev_parse () { + dir=$1 + rev=$2 + + cat >expect && + test_must_fail git -C "$dir" rev-parse "$rev" 2>actual.raw && + sed "s/\($rev\)[0-9a-f]*/\1.../" <actual.raw >actual && + test_cmp expect actual +} + +test_expect_success 'ambiguous blob output' ' + git init --bare blob.prefix && + ( + cd blob.prefix && + + # Both start with "dead..", under both SHA-1 and SHA-256 + echo brocdnra | git hash-object -w --stdin && + echo brigddsv | git hash-object -w --stdin && + + # Both start with "beef.." + echo 1agllotbh | git hash-object -w --stdin && + echo 1bbfctrkc | git hash-object -w --stdin + ) && + + test_must_fail git -C blob.prefix rev-parse dead && + test_cmp_failed_rev_parse blob.prefix beef <<-\EOF + error: short object ID beef... is ambiguous + hint: The candidates are: + hint: beef... blob + hint: beef... blob + fatal: ambiguous argument '\''beef...'\'': unknown revision or path not in the working tree. + Use '\''--'\'' to separate paths from revisions, like this: + '\''git <command> [<revision>...] -- [<file>...]'\'' + EOF +' + +test_expect_success 'ambiguous loose bad object parsed as OBJ_BAD' ' + git init --bare blob.bad && + ( + cd blob.bad && + + # Both have the prefix "bad0" + echo xyzfaowcoh | git hash-object -t bad -w --stdin --literally && + echo xyzhjpyvwl | git hash-object -t bad -w --stdin --literally + ) && + + test_cmp_failed_rev_parse blob.bad bad0 <<-\EOF + error: short object ID bad0... is ambiguous + fatal: invalid object type + EOF +' + +test_expect_success POSIXPERM 'ambigous zlib corrupt loose blob' ' + git init --bare blob.corrupt && + ( + cd blob.corrupt && + + # Both have the prefix "cafe" + echo bnkxmdwz | git hash-object -w --stdin && + oid=$(echo bmwsjxzi | git hash-object -w --stdin) && + + oidf=objects/$(test_oid_to_path "$oid") && + chmod 755 $oidf && + echo broken >$oidf + ) && + + test_cmp_failed_rev_parse blob.corrupt cafe <<-\EOF + error: short object ID cafe... is ambiguous + error: inflate: data stream error (incorrect header check) + error: unable to unpack cafe... header + error: inflate: data stream error (incorrect header check) + error: unable to unpack cafe... header + hint: The candidates are: + hint: cafe... [bad object] + hint: cafe... blob + fatal: ambiguous argument '\''cafe...'\'': unknown revision or path not in the working tree. + Use '\''--'\'' to separate paths from revisions, like this: + '\''git <command> [<revision>...] -- [<file>...]'\'' + EOF +' + if ! test_have_prereq SHA1 then skip_all='not using SHA-1 for objects' diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index 37ad79470f..43139af08f 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -165,8 +165,62 @@ test_expect_success '"add" default branch of a bare repo' ' ( git clone --bare . bare2 && cd bare2 && - git worktree add ../there3 main - ) + git worktree add ../there3 main && + cd ../there3 && + # Simple check that a Git command does not + # immediately fail with the current setup + git status + ) && + cat >expect <<-EOF && + init.t + EOF + ls there3 >actual && + test_cmp expect actual +' + +test_expect_success '"add" to bare repo with worktree config' ' + ( + git clone --bare . bare3 && + cd bare3 && + git config extensions.worktreeconfig true && + + # Add config values that are erroneous to have in + # a config.worktree file outside of the main + # working tree, to check that Git filters them out + # when copying config during "git worktree add". + git config --worktree core.bare true && + git config --worktree core.worktree "$(pwd)" && + + # We want to check that bogus.key is copied + git config --worktree bogus.key value && + git config --unset core.bare && + git worktree add ../there4 main && + cd ../there4 && + + # Simple check that a Git command does not + # immediately fail with the current setup + git status && + git worktree add --detach ../there5 && + cd ../there5 && + git status + ) && + + # the worktree has the arbitrary value copied. + test_cmp_config -C there4 value bogus.key && + test_cmp_config -C there5 value bogus.key && + + # however, core.bare and core.worktree were removed. + test_must_fail git -C there4 config core.bare && + test_must_fail git -C there4 config core.worktree && + + cat >expect <<-EOF && + init.t + EOF + + ls there4 >actual && + test_cmp expect actual && + ls there5 >actual && + test_cmp expect actual ' test_expect_success 'checkout with grafts' ' 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/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/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/t4150-am.sh b/t/t4150-am.sh index 6caff0ca39..159fae8d01 100755 --- a/t/t4150-am.sh +++ b/t/t4150-am.sh @@ -1169,7 +1169,7 @@ test_expect_success 'invalid when passing the --empty option alone' ' test_when_finished "git am --abort || :" && git checkout empty-commit^ && test_must_fail git am --empty empty-commit.patch 2>err && - echo "error: Invalid value for --empty: empty-commit.patch" >expected && + echo "error: invalid value for '\''--empty'\'': '\''empty-commit.patch'\''" >expected && test_cmp expected err ' diff --git a/t/t4202-log.sh b/t/t4202-log.sh index dc884107de..55fac64446 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 && @@ -449,6 +462,30 @@ test_expect_success !FAIL_PREREQS 'log with various grep.patternType configurati ) ' +for cmd in show whatchanged reflog format-patch +do + case "$cmd" in + format-patch) myarg="HEAD~.." ;; + *) myarg= ;; + esac + + test_expect_success "$cmd: understands grep.patternType, like 'log'" ' + git init "pattern-type-$cmd" && + ( + cd "pattern-type-$cmd" && + test_commit 1 file A && + test_commit "(1|2)" file B 2 && + + git -c grep.patternType=fixed $cmd --grep="..." $myarg >actual && + test_must_be_empty actual && + + git -c grep.patternType=basic $cmd --grep="..." $myarg >actual && + test_file_not_empty actual + ) + ' +done +test_done + test_expect_success 'log --author' ' cat >expect <<-\EOF && Author: <BOLD;RED>A U<RESET> Thor <author@example.com> @@ -1671,6 +1708,75 @@ test_expect_success 'log --graph with --name-only' ' test_cmp_graph --name-only tangle..reach ' +test_expect_success '--no-graph countermands --graph' ' + git log >expect && + git log --graph --no-graph >actual && + test_cmp expect actual +' + +test_expect_success '--graph countermands --no-graph' ' + git log --graph >expect && + git log --no-graph --graph >actual && + test_cmp expect actual +' + +test_expect_success '--no-graph does not unset --topo-order' ' + git log --topo-order >expect && + git log --topo-order --no-graph >actual && + test_cmp expect actual +' + +test_expect_success '--no-graph does not unset --parents' ' + git log --parents >expect && + git log --parents --no-graph >actual && + test_cmp expect actual +' + +test_expect_success '--reverse and --graph conflict' ' + test_must_fail git log --reverse --graph 2>stderr && + test_i18ngrep "cannot be used together" stderr +' + +test_expect_success '--reverse --graph --no-graph works' ' + git log --reverse >expect && + git log --reverse --graph --no-graph >actual && + test_cmp expect actual +' + +test_expect_success '--show-linear-break and --graph conflict' ' + test_must_fail git log --show-linear-break --graph 2>stderr && + test_i18ngrep "cannot be used together" stderr +' + +test_expect_success '--show-linear-break --graph --no-graph works' ' + git log --show-linear-break >expect && + git log --show-linear-break --graph --no-graph >actual && + test_cmp expect actual +' + +test_expect_success '--no-walk and --graph conflict' ' + test_must_fail git log --no-walk --graph 2>stderr && + test_i18ngrep "cannot be used together" stderr +' + +test_expect_success '--no-walk --graph --no-graph works' ' + git log --no-walk >expect && + git log --no-walk --graph --no-graph >actual && + test_cmp expect actual +' + +test_expect_success '--walk-reflogs and --graph conflict' ' + test_must_fail git log --walk-reflogs --graph 2>stderr && + (test_i18ngrep "cannot combine" stderr || + test_i18ngrep "cannot be used together" stderr) +' + +test_expect_success '--walk-reflogs --graph --no-graph works' ' + git log --walk-reflogs >expect && + git log --walk-reflogs --graph --no-graph >actual && + test_cmp expect actual +' + test_expect_success 'dotdot is a parent directory' ' mkdir -p a/b && ( echo sixth && echo fifth ) >expect && 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/t5316-pack-delta-depth.sh b/t/t5316-pack-delta-depth.sh index df524f7b6d..e9045009a1 100755 --- a/t/t5316-pack-delta-depth.sh +++ b/t/t5316-pack-delta-depth.sh @@ -64,7 +64,11 @@ test_expect_success 'create series of packs' ' echo $cur && echo "$(git rev-parse :file) file" } | git pack-objects --stdout >tmp && - git index-pack --stdin --fix-thin <tmp || return 1 + GIT_TRACE2_EVENT=$PWD/trace \ + git index-pack -v --stdin --fix-thin <tmp || return 1 && + grep -c region_enter.*progress trace >enter && + grep -c region_leave.*progress trace >leave && + test_cmp enter leave && prev=$cur done ' 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/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 87881726ed..3137eb8d4d 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -1821,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/t5617-clone-submodules-remote.sh b/t/t5617-clone-submodules-remote.sh index e2dbb4eaba..ca8f80083a 100755 --- a/t/t5617-clone-submodules-remote.sh +++ b/t/t5617-clone-submodules-remote.sh @@ -28,6 +28,13 @@ test_expect_success 'setup' ' ) ' +# bare clone giving "srv.bare" for use as our server. +test_expect_success 'setup bare clone for server' ' + git clone --bare "file://$(pwd)/." srv.bare && + git -C srv.bare config --local uploadpack.allowfilter 1 && + git -C srv.bare config --local uploadpack.allowanysha1inwant 1 +' + test_expect_success 'clone with --no-remote-submodules' ' test_when_finished "rm -rf super_clone" && git clone --recurse-submodules --no-remote-submodules "file://$pwd/." super_clone && @@ -65,4 +72,38 @@ test_expect_success 'clone with --single-branch' ' ) ' +# do basic partial clone from "srv.bare" +# confirm partial clone was registered in the local config for super and sub. +test_expect_success 'clone with --filter' ' + git clone --recurse-submodules \ + --filter blob:none --also-filter-submodules \ + "file://$pwd/srv.bare" super_clone && + test_cmp_config -C super_clone true remote.origin.promisor && + test_cmp_config -C super_clone blob:none remote.origin.partialclonefilter && + test_cmp_config -C super_clone/sub true remote.origin.promisor && + test_cmp_config -C super_clone/sub blob:none remote.origin.partialclonefilter +' + +# check that clone.filterSubmodules works (--also-filter-submodules can be +# omitted) +test_expect_success 'filters applied with clone.filterSubmodules' ' + test_config_global clone.filterSubmodules true && + git clone --recurse-submodules --filter blob:none \ + "file://$pwd/srv.bare" super_clone2 && + test_cmp_config -C super_clone2 true remote.origin.promisor && + test_cmp_config -C super_clone2 blob:none remote.origin.partialclonefilter && + test_cmp_config -C super_clone2/sub true remote.origin.promisor && + test_cmp_config -C super_clone2/sub blob:none remote.origin.partialclonefilter +' + +test_expect_success '--no-also-filter-submodules overrides clone.filterSubmodules=true' ' + test_config_global clone.filterSubmodules true && + git clone --recurse-submodules --filter blob:none \ + --no-also-filter-submodules \ + "file://$pwd/srv.bare" super_clone3 && + test_cmp_config -C super_clone3 true remote.origin.promisor && + test_cmp_config -C super_clone3 blob:none remote.origin.partialclonefilter && + test_cmp_config -C super_clone3/sub false --default false remote.origin.promisor +' + test_done diff --git a/t/t6012-rev-list-simplify.sh b/t/t6012-rev-list-simplify.sh index 4fedc614fa..63fcccec32 100755 --- a/t/t6012-rev-list-simplify.sh +++ b/t/t6012-rev-list-simplify.sh @@ -16,13 +16,12 @@ unnote () { } # -# 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/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/t7500-commit-template-squash-signoff.sh b/t/t7500-commit-template-squash-signoff.sh index 91964653a0..5fcaa0b4f2 100755 --- a/t/t7500-commit-template-squash-signoff.sh +++ b/t/t7500-commit-template-squash-signoff.sh @@ -442,7 +442,7 @@ test_expect_success '--fixup=reword: give error with pathsec' ' ' test_expect_success '--fixup=reword: -F give error message' ' - echo "fatal: Only one of -c/-C/-F/--fixup can be used." >expect && + echo "fatal: options '\''-F'\'' and '\''--fixup'\'' cannot be used together" >expect && test_must_fail git commit --fixup=reword:HEAD~ -F msg 2>actual && test_cmp expect actual ' 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/t7810-grep.sh b/t/t7810-grep.sh index 424c31c328..6935601171 100755 --- a/t/t7810-grep.sh +++ b/t/t7810-grep.sh @@ -98,6 +98,37 @@ test_expect_success 'grep should not segfault with a bad input' ' test_invalid_grep_expression --and -e A +test_pattern_type () { + H=$1 && + HC=$2 && + L=$3 && + type=$4 && + shift 4 && + + expected_str= && + case "$type" in + BRE) + expected_str="${HC}ab:a+bc" + ;; + ERE) + expected_str="${HC}ab:abc" + ;; + FIX) + expected_str="${HC}ab:a+b*c" + ;; + *) + BUG "unknown pattern type '$type'" + ;; + esac && + config_str="$@" && + + test_expect_success "grep $L with '$config_str' interpreted as $type" ' + echo $expected_str >expected && + git $config_str grep "a+b*c" $H ab >actual && + test_cmp expected actual + ' +} + for H in HEAD '' do case "$H" in @@ -393,35 +424,13 @@ do git grep --no-recursive -n -e vvv $H -- t . >actual && test_cmp expected actual ' - test_expect_success "grep $L with grep.extendedRegexp=false" ' - echo "${HC}ab:a+bc" >expected && - git -c grep.extendedRegexp=false grep "a+b*c" $H ab >actual && - test_cmp expected actual - ' - test_expect_success "grep $L with grep.extendedRegexp=true" ' - echo "${HC}ab:abc" >expected && - git -c grep.extendedRegexp=true grep "a+b*c" $H ab >actual && - test_cmp expected actual - ' - test_expect_success "grep $L with grep.patterntype=basic" ' - echo "${HC}ab:a+bc" >expected && - git -c grep.patterntype=basic grep "a+b*c" $H ab >actual && - test_cmp expected actual - ' - - test_expect_success "grep $L with grep.patterntype=extended" ' - echo "${HC}ab:abc" >expected && - git -c grep.patterntype=extended grep "a+b*c" $H ab >actual && - test_cmp expected actual - ' - - test_expect_success "grep $L with grep.patterntype=fixed" ' - echo "${HC}ab:a+b*c" >expected && - git -c grep.patterntype=fixed grep "a+b*c" $H ab >actual && - test_cmp expected actual - ' + test_pattern_type "$H" "$HC" "$L" BRE -c grep.extendedRegexp=false + test_pattern_type "$H" "$HC" "$L" ERE -c grep.extendedRegexp=true + test_pattern_type "$H" "$HC" "$L" BRE -c grep.patternType=basic + test_pattern_type "$H" "$HC" "$L" ERE -c grep.patternType=extended + test_pattern_type "$H" "$HC" "$L" FIX -c grep.patternType=fixed test_expect_success PCRE "grep $L with grep.patterntype=perl" ' echo "${HC}ab:a+b*c" >expected && @@ -433,59 +442,76 @@ do test_must_fail git -c grep.patterntype=perl grep "foo.*bar" ' - test_expect_success "grep $L with grep.patternType=default and grep.extendedRegexp=true" ' - echo "${HC}ab:abc" >expected && - git \ - -c grep.patternType=default \ - -c grep.extendedRegexp=true \ - grep "a+b*c" $H ab >actual && - test_cmp expected actual - ' - - test_expect_success "grep $L with grep.extendedRegexp=true and grep.patternType=default" ' - echo "${HC}ab:abc" >expected && - git \ - -c grep.extendedRegexp=true \ - -c grep.patternType=default \ - grep "a+b*c" $H ab >actual && - test_cmp expected actual - ' - - test_expect_success "grep $L with grep.patternType=extended and grep.extendedRegexp=false" ' - echo "${HC}ab:abc" >expected && - git \ - -c grep.patternType=extended \ - -c grep.extendedRegexp=false \ - grep "a+b*c" $H ab >actual && - test_cmp expected actual - ' - - test_expect_success "grep $L with grep.patternType=basic and grep.extendedRegexp=true" ' - echo "${HC}ab:a+bc" >expected && - git \ - -c grep.patternType=basic \ - -c grep.extendedRegexp=true \ - grep "a+b*c" $H ab >actual && - test_cmp expected actual - ' - - test_expect_success "grep $L with grep.extendedRegexp=false and grep.patternType=extended" ' - echo "${HC}ab:abc" >expected && - git \ - -c grep.extendedRegexp=false \ - -c grep.patternType=extended \ - grep "a+b*c" $H ab >actual && - test_cmp expected actual - ' - - test_expect_success "grep $L with grep.extendedRegexp=true and grep.patternType=basic" ' - echo "${HC}ab:a+bc" >expected && - git \ - -c grep.extendedRegexp=true \ - -c grep.patternType=basic \ - grep "a+b*c" $H ab >actual && - test_cmp expected actual - ' + test_pattern_type "$H" "$HC" "$L" ERE \ + -c grep.patternType=default \ + -c grep.extendedRegexp=true + test_pattern_type "$H" "$HC" "$L" ERE \ + -c grep.extendedRegexp=true \ + -c grep.patternType=default + test_pattern_type "$H" "$HC" "$L" ERE \ + -c grep.patternType=extended \ + -c grep.extendedRegexp=false + test_pattern_type "$H" "$HC" "$L" BRE \ + -c grep.patternType=basic \ + -c grep.extendedRegexp=true + test_pattern_type "$H" "$HC" "$L" ERE \ + -c grep.extendedRegexp=false \ + -c grep.patternType=extended + test_pattern_type "$H" "$HC" "$L" BRE \ + -c grep.extendedRegexp=true \ + -c grep.patternType=basic + + # grep.extendedRegexp is last-one-wins + test_pattern_type "$H" "$HC" "$L" BRE \ + -c grep.extendedRegexp=true \ + -c grep.extendedRegexp=false + + # grep.patternType=basic pays no attention to grep.extendedRegexp + test_pattern_type "$H" "$HC" "$L" BRE \ + -c grep.extendedRegexp=true \ + -c grep.patternType=basic \ + -c grep.extendedRegexp=false + + # grep.patternType=extended pays no attention to grep.extendedRegexp + test_pattern_type "$H" "$HC" "$L" ERE \ + -c grep.extendedRegexp=true \ + -c grep.patternType=extended \ + -c grep.extendedRegexp=false + + # grep.extendedRegexp is used with a last-one-wins grep.patternType=default + test_pattern_type "$H" "$HC" "$L" ERE \ + -c grep.patternType=fixed \ + -c grep.extendedRegexp=true \ + -c grep.patternType=default + + # grep.extendedRegexp is used with earlier grep.patternType=default + test_pattern_type "$H" "$HC" "$L" ERE \ + -c grep.extendedRegexp=false \ + -c grep.patternType=default \ + -c grep.extendedRegexp=true + + # grep.extendedRegexp is used with a last-one-loses grep.patternType=default + test_pattern_type "$H" "$HC" "$L" ERE \ + -c grep.extendedRegexp=false \ + -c grep.extendedRegexp=true \ + -c grep.patternType=default + + # grep.extendedRegexp and grep.patternType are both last-one-wins independently + test_pattern_type "$H" "$HC" "$L" BRE \ + -c grep.patternType=default \ + -c grep.extendedRegexp=true \ + -c grep.patternType=basic + + # grep.patternType=extended and grep.patternType=default + test_pattern_type "$H" "$HC" "$L" BRE \ + -c grep.patternType=extended \ + -c grep.patternType=default + + # grep.patternType=[extended -> default -> fixed] (BRE)" ' + test_pattern_type "$H" "$HC" "$L" FIX \ + -c grep.patternType=extended \ + -c grep.patternType=default \ + -c grep.patternType=fixed test_expect_success "grep --count $L" ' echo ${HC}ab:3 >expected && diff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh index 058e5d0c96..a4476dc492 100755 --- a/t/t7814-grep-recurse-submodules.sh +++ b/t/t7814-grep-recurse-submodules.sh @@ -544,4 +544,45 @@ test_expect_failure 'grep saves textconv cache in the appropriate repository' ' test_path_is_file "$sub_textconv_cache" ' +test_expect_success 'grep partially-cloned submodule' ' + # Set up clean superproject and submodule for partial cloning. + git init super && + git init super/sub && + ( + cd super && + test_commit --no-tag "Add file in superproject" \ + super-file "Some content for super-file" && + test_commit -C sub --no-tag "Add file in submodule" \ + sub-file "Some content for sub-file" && + git submodule add ./sub && + git commit -m "Add other as submodule sub" && + test_tick && + test_commit -C sub --no-tag --append "Update file in submodule" \ + sub-file "Some more content for sub-file" && + git add sub && + git commit -m "Update submodule" && + test_tick && + git config --local uploadpack.allowfilter 1 && + git config --local uploadpack.allowanysha1inwant 1 && + git -C sub config --local uploadpack.allowfilter 1 && + git -C sub config --local uploadpack.allowanysha1inwant 1 + ) && + # Clone the superproject & submodule, then make sure we can lazy-fetch submodule objects. + git clone --filter=blob:none --also-filter-submodules \ + --recurse-submodules "file://$(pwd)/super" partial && + ( + cd partial && + cat >expect <<-\EOF && + HEAD^:sub/sub-file:Some content for sub-file + HEAD^:super-file:Some content for super-file + EOF + + GIT_TRACE2_EVENT="$(pwd)/trace2.log" git grep -e content \ + --recurse-submodules HEAD^ >actual && + test_cmp expect actual && + # Verify that we actually fetched data from the promisor remote: + grep \"category\":\"promisor\",\"key\":\"fetch_count\",\"value\":\"1\" trace2.log + ) +' + test_done 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/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 e8f6f6ae6f..90fc085f76 100644 --- a/worktree.c +++ b/worktree.c @@ -5,6 +5,7 @@ #include "worktree.h" #include "dir.h" #include "wt-status.h" +#include "config.h" void free_worktrees(struct worktree **worktrees) { @@ -821,3 +822,75 @@ int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath, *wtpath = path; return 0; } + +static int move_config_setting(const char *key, const char *value, + const char *from_file, const char *to_file) +{ + if (git_config_set_in_file_gently(to_file, key, value)) + return error(_("unable to set %s in '%s'"), key, to_file); + if (git_config_set_in_file_gently(from_file, key, NULL)) + return error(_("unable to unset %s in '%s'"), key, from_file); + return 0; +} + +int init_worktree_config(struct repository *r) +{ + int res = 0; + int bare = 0; + struct config_set cs = { { 0 } }; + const char *core_worktree; + char *common_config_file; + char *main_worktree_file; + + /* + * If the extension is already enabled, then we can skip the + * upgrade process. + */ + if (repository_format_worktree_config) + return 0; + if ((res = git_config_set_gently("extensions.worktreeConfig", "true"))) + return error(_("failed to set extensions.worktreeConfig setting")); + + common_config_file = xstrfmt("%s/config", r->commondir); + main_worktree_file = xstrfmt("%s/config.worktree", r->commondir); + + git_configset_init(&cs); + git_configset_add_file(&cs, common_config_file); + + /* + * If core.bare is true in the common config file, then we need to + * move it to the main worktree's config file or it will break all + * worktrees. If it is false, then leave it in place because it + * _could_ be negating a global core.bare=true. + */ + if (!git_configset_get_bool(&cs, "core.bare", &bare) && bare) { + if ((res = move_config_setting("core.bare", "true", + common_config_file, + main_worktree_file))) + goto cleanup; + } + /* + * If core.worktree is set, then the main worktree is located + * somewhere different than the parent of the common Git dir. + * Relocate that value to avoid breaking all worktrees with this + * upgrade to worktree config. + */ + if (!git_configset_get_value(&cs, "core.worktree", &core_worktree)) { + if ((res = move_config_setting("core.worktree", core_worktree, + common_config_file, + main_worktree_file))) + goto cleanup; + } + + /* + * Ensure that we use worktree config for the remaining lifetime + * of the current process. + */ + repository_format_worktree_config = 1; + +cleanup: + git_configset_clear(&cs); + free(common_config_file); + free(main_worktree_file); + return res; +} diff --git a/worktree.h b/worktree.h index 9e06fcbdf3..e9e839926b 100644 --- a/worktree.h +++ b/worktree.h @@ -183,4 +183,25 @@ void strbuf_worktree_ref(const struct worktree *wt, struct strbuf *sb, const char *refname); +/** + * Enable worktree config for the first time. This will make the following + * adjustments: + * + * 1. Add extensions.worktreeConfig=true in the common config file. + * + * 2. If the common config file has a core.worktree value, then that value + * is moved to the main worktree's config.worktree file. + * + * 3. If the common config file has a core.bare enabled, then that value + * is moved to the main worktree's config.worktree file. + * + * If extensions.worktreeConfig is already true, then this method + * terminates early without any of the above steps. The existing config + * arrangement is assumed to be intentional. + * + * Returns 0 on success. Reports an error message and returns non-zero + * if any of these steps fail. + */ +int init_worktree_config(struct repository *r); + #endif |