diff options
109 files changed, 2540 insertions, 706 deletions
diff --git a/.clang-format b/.clang-format index dcfd0aad60..86b4fe33e5 100644 --- a/.clang-format +++ b/.clang-format @@ -149,7 +149,7 @@ SpaceBeforeCaseColon: false # f(); # } # } -SpaceBeforeParens: ControlStatements +SpaceBeforeParens: ControlStatementsExceptControlMacros # Don't insert spaces inside empty '()' SpaceInEmptyParentheses: false diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index af10ebb59a..cf122e706f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -119,6 +119,7 @@ build:mingw64: variables: NO_PERL: 1 before_script: + - Set-MpPreference -DisableRealtimeMonitoring $true - ./ci/install-sdk.ps1 -directory "git-sdk" script: - git-sdk/usr/bin/bash.exe -l -c 'ci/make-test-artifacts.sh artifacts' @@ -135,6 +136,7 @@ test:mingw64: - job: "build:mingw64" artifacts: true before_script: + - Set-MpPreference -DisableRealtimeMonitoring $true - git-sdk/usr/bin/bash.exe -l -c 'tar xf artifacts/artifacts.tar.gz' - New-Item -Path .git/info -ItemType Directory - New-Item .git/info/exclude -ItemType File -Value "/git-sdk" @@ -148,6 +150,7 @@ test:mingw64: tags: - saas-windows-medium-amd64 before_script: + - Set-MpPreference -DisableRealtimeMonitoring $true - choco install -y git meson ninja openssl - Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1 - refreshenv diff --git a/Documentation/BreakingChanges.adoc b/Documentation/BreakingChanges.adoc index f8d2eba061..c4985163c3 100644 --- a/Documentation/BreakingChanges.adoc +++ b/Documentation/BreakingChanges.adoc @@ -235,7 +235,7 @@ These features will be removed. equivalent `git log --raw`. We have nominated the command for removal, have changed the command to refuse to work unless the `--i-still-use-this` option is given, and asked the users to report - when they do so. So far there hasn't been a single complaint. + when they do so. + The command will be removed. diff --git a/Documentation/MyFirstContribution.adoc b/Documentation/MyFirstContribution.adoc index aca7212cfe..bbb7b45bd4 100644 --- a/Documentation/MyFirstContribution.adoc +++ b/Documentation/MyFirstContribution.adoc @@ -908,10 +908,13 @@ Now you should be able to go and check out your newly created branch on GitHub. === Sending a PR to GitGitGadget In order to have your code tested and formatted for review, you need to start by -opening a Pull Request against `gitgitgadget/git`. Head to -https://github.com/gitgitgadget/git and open a PR either with the "New pull -request" button or the convenient "Compare & pull request" button that may -appear with the name of your newly pushed branch. +opening a Pull Request against either `gitgitgadget/git` or `git/git`. Head to +https://github.com/gitgitgadget/git or https://github.com/git/git and open a PR +either with the "New pull request" button or the convenient "Compare & pull +request" button that may appear with the name of your newly pushed branch. + +The differences between using `gitgitgadget/git` and `git/git` as your base can +be found [here](https://gitgitgadget.github.io/#should-i-use-gitgitgadget-on-gitgitgadgets-git-fork-or-on-gits-github-mirror) Review the PR's title and description, as they're used by GitGitGadget respectively as the subject and body of the cover letter for your change. Refer diff --git a/Documentation/RelNotes/2.51.1.adoc b/Documentation/RelNotes/2.51.1.adoc new file mode 100644 index 0000000000..b8bd49c876 --- /dev/null +++ b/Documentation/RelNotes/2.51.1.adoc @@ -0,0 +1,99 @@ +Git 2.51.1 Release Notes +======================== + +There shouldn't be anything exciting to see here. This is primarily +to flush the "do you still use it?" improvements that has landed on +the master front, together with a handful of low-hanging, low-impact +fixes that should be safe. + + +Fixes since Git 2.51.0 +---------------------- + + * The "do you still use it?" message given by a command that is + deeply deprecated and allow us to suggest alternatives has been + updated. + + * The compatObjectFormat extension is used to hide an incomplete + feature that is not yet usable for any purpose other than + developing the feature further. Document it as such to discourage + its use by mere mortals. + + * Manual page for "gitk" is updated with the current maintainer's + name. + + * Update the instructions for using GGG in the MyFirstContribution + document to say that a GitHub PR could be made against `git/git` + instead of `gitgitgadget/git`. + + * Clang-format update to let our control macros be formatted the way we + had them traditionally, e.g., "for_each_string_list_item()" without + space before the parentheses. + + * A few places where a size_t value was cast to curl_off_t without + checking has been updated to use the existing helper function. + + * The start_delayed_progress() function in the progress eye-candy API + did not clear its internal state, making an initial delay value + larger than 1 second ineffective, which has been corrected. + + * Makefile tried to run multiple "cargo build" which would not work + very well; serialize their execution to work around this problem. + + * Adjust to the way newer versions of cURL selectively enable tracing + options, so that our tests can continue to work. + + * During interactive rebase, using 'drop' on a merge commit led to + an error, which has been corrected. + + * "git refs migrate" to migrate the reflog entries from a refs + backend to another had a handful of bugs squashed. + + * "git push" had a code path that led to BUG() but it should have + been a die(), as it is a response to a usual but invalid end-user + action to attempt pushing an object that does not exist. + + * Various bugs about rename handling in "ort" merge strategy have + been fixed. + + * "git diff --no-index" run inside a subdirectory under control of a + Git repository operated at the top of the working tree and stripped + the prefix from the output, and oddballs like "-" (stdin) did not + work correctly because of it. Correct the set-up by undoing what + the set-up sequence did to cwd and prefix. + + * Various options to "git diff" that make comparison ignore certain + aspects of the differences (like "space changes are ignored", + "differences in lines that match these regular expressions are + ignored") did not work well with "--name-only" and friends. + + * Under a race against another process that is repacking the + repository, especially a partially cloned one, "git fetch" may + mistakenly think some objects we do have are missing, which has + been corrected. + + * "git repack --path-walk" lost objects in some corner cases, which + has been corrected. + cf. <CABPp-BHFxxGrqKc0m==TjQNjDGdO=H5Rf6EFsf2nfE1=TuraOQ@mail.gmail.com> + + * Fixes multiple crashes around midx write-out codepaths. + + * A broken or malicious "git fetch" can say that it has the same + object for many many times, and the upload-pack serving it can + exhaust memory storing them redundantly, which has been corrected. + + * A corner case bug in "git log -L..." has been corrected. + + * Some among "git add -p" and friends ignored color.diff and/or + color.ui configuration variables, which is an old regression, which + has been corrected. + + * "git rebase -i" failed to clean-up the commit log message when the + command commits the final one in a chain of "fixup" commands, which + has been corrected. + + * Deal more gracefully with directory / file conflicts when the files + backend is used for ref storage, by failing only the ones that are + involved in the conflict while allowing others. + +Also contains various documentation updates, code cleanups and minor fixups. diff --git a/Documentation/config.adoc b/Documentation/config.adoc index cc769251be..05f1ca7293 100644 --- a/Documentation/config.adoc +++ b/Documentation/config.adoc @@ -114,8 +114,7 @@ whose format and meaning depends on the keyword. Supported keywords are: `gitdir`:: - - The data that follows the keyword `gitdir:` is used as a glob + The data that follows the keyword `gitdir` and a colon is used as a glob pattern. If the location of the .git directory matches the pattern, the include condition is met. + @@ -148,7 +147,7 @@ refer to linkgit:gitignore[5] for details. For convenience: case-insensitively (e.g. on case-insensitive file systems) `onbranch`:: - The data that follows the keyword `onbranch:` is taken to be a + The data that follows the keyword `onbranch` and a colon is taken to be a pattern with standard globbing wildcards and two additional ones, `**/` and `/**`, that can match multiple path components. If we are in a worktree where the name of the branch that is @@ -161,8 +160,8 @@ all branches that begin with `foo/`. This is useful if your branches are organized hierarchically and you would like to apply a configuration to all the branches in that hierarchy. -`hasconfig:remote.*.url:`:: - The data that follows this keyword is taken to +`hasconfig:remote.*.url`:: + The data that follows this keyword and a colon is taken to be a pattern with standard globbing wildcards and two additional ones, `**/` and `/**`, that can match multiple components. The first time this keyword is seen, the rest of diff --git a/Documentation/config/alias.adoc b/Documentation/config/alias.adoc index 2c5db0ad84..80ce17d2de 100644 --- a/Documentation/config/alias.adoc +++ b/Documentation/config/alias.adoc @@ -3,7 +3,8 @@ alias.*:: after defining `alias.last = cat-file commit HEAD`, the invocation `git last` is equivalent to `git cat-file commit HEAD`. To avoid confusion and troubles with script usage, aliases that - hide existing Git commands are ignored. Arguments are split by + hide existing Git commands are ignored except for deprecated + commands. Arguments are split by spaces, the usual shell quoting and escaping are supported. A quote pair or a backslash can be used to quote them. + @@ -38,6 +39,6 @@ it will be treated as a shell command. For example, defining ** A convenient way to deal with this is to write your script operations in an inline function that is then called with any arguments from the command-line. For example `alias.cmd = "!c() { - echo $1 | grep $2 ; }; c" will correctly execute the prior example. + echo $1 | grep $2 ; }; c"` will correctly execute the prior example. ** Setting `GIT_TRACE=1` can help you debug the command being run for your alias. diff --git a/Documentation/config/extensions.adoc b/Documentation/config/extensions.adoc index 9e2f321a6d..532456644b 100644 --- a/Documentation/config/extensions.adoc +++ b/Documentation/config/extensions.adoc @@ -3,8 +3,7 @@ extensions.*:: `core.repositoryFormatVersion` is not `1`. See linkgit:gitrepository-layout[5]. + --- -compatObjectFormat:: +compatObjectFormat::: Specify a compatibility hash algorithm to use. The acceptable values are `sha1` and `sha256`. The value specified must be different from the value of `extensions.objectFormat`. This allows client level @@ -14,19 +13,23 @@ compatObjectFormat:: compatObjectFormat. As well as being able to use oids encoded in compatObjectFormat in addition to oids encoded with objectFormat to locally specify objects. ++ +Note that the functionality enabled by this extension is incomplete and subject +to change. It currently exists only to allow development and testing of +the underlying feature and is not designed to be enabled by end users. -noop:: +noop::: This extension does not change git's behavior at all. It is useful only for testing format-1 compatibility. + For historical reasons, this extension is respected regardless of the `core.repositoryFormatVersion` setting. -noop-v1:: +noop-v1::: This extension does not change git's behavior at all. It is useful only for testing format-1 compatibility. -objectFormat:: +objectFormat::: Specify the hash algorithm to use. The acceptable values are `sha1` and `sha256`. If not specified, `sha1` is assumed. + @@ -34,7 +37,7 @@ 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. -partialClone:: +partialClone::: When enabled, indicates that the repo was created with a partial clone (or later performed a partial fetch) and that the remote may have omitted sending certain unwanted objects. Such a remote is called a @@ -46,30 +49,31 @@ The value of this key is the name of the promisor remote. For historical reasons, this extension is respected regardless of the `core.repositoryFormatVersion` setting. -preciousObjects:: +preciousObjects::: If enabled, indicates that objects in the repository MUST NOT be deleted (e.g., by `git-prune` or `git repack -d`). + For historical reasons, this extension is respected regardless of the `core.repositoryFormatVersion` setting. -refStorage:: +refStorage::: Specify the ref storage format to use. The acceptable values are: + +-- include::../ref-storage-format.adoc[] - +-- + 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. -relativeWorktrees:: +relativeWorktrees::: If enabled, indicates at least one worktree has been linked with relative paths. Automatically set if a worktree has been created or repaired with either the `--relative-paths` option or with the `worktree.useRelativePaths` config set to `true`. -worktreeConfig:: +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 @@ -83,11 +87,12 @@ When enabling this extension, 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 @@ -100,4 +105,3 @@ details. + For historical reasons, this extension is respected regardless of the `core.repositoryFormatVersion` setting. --- diff --git a/Documentation/config/log.adoc b/Documentation/config/log.adoc index 16e00e8d29..f20cc25cd7 100644 --- a/Documentation/config/log.adoc +++ b/Documentation/config/log.adoc @@ -23,14 +23,14 @@ be used. Print out the ref names of any commits that are shown by the log command. Possible values are: + ----- +-- `short`;; the ref name prefixes `refs/heads/`, `refs/tags/` and `refs/remotes/` are not printed. `full`;; the full ref name (including prefix) are printed. `auto`;; if the output is going to a terminal, the ref names are shown as if `short` were given, otherwise no ref names are shown. ----- +-- + This is the same as the `--decorate` option of the `git log`. diff --git a/Documentation/config/mergetool.adoc b/Documentation/config/mergetool.adoc index 6be506145c..7064f5a462 100644 --- a/Documentation/config/mergetool.adoc +++ b/Documentation/config/mergetool.adoc @@ -65,7 +65,7 @@ endif::[] During a merge, Git will automatically resolve as many conflicts as possible and write the `$MERGED` file containing conflict markers around any conflicts that it cannot resolve; `$LOCAL` and `$REMOTE` normally - are the versions of the file from before Git`s conflict + are the versions of the file from before Git's conflict resolution. This flag causes `$LOCAL` and `$REMOTE` to be overwritten so that only the unresolved conflicts are presented to the merge tool. Can be configured per-tool via the `mergetool.<tool>.hideResolved` diff --git a/Documentation/config/worktree.adoc b/Documentation/config/worktree.adoc index 5e35c7d018..9e3f84f748 100644 --- a/Documentation/config/worktree.adoc +++ b/Documentation/config/worktree.adoc @@ -15,5 +15,5 @@ worktree.useRelativePaths:: different locations or environments. Defaults to "false". + Note that setting `worktree.useRelativePaths` to "true" implies enabling the -`extension.relativeWorktrees` config (see linkgit:git-config[1]), +`extensions.relativeWorktrees` config (see linkgit:git-config[1]), thus making it incompatible with older versions of Git. diff --git a/Documentation/fetch-options.adoc b/Documentation/fetch-options.adoc index b01372e4b3..65f7d1e7c7 100644 --- a/Documentation/fetch-options.adoc +++ b/Documentation/fetch-options.adoc @@ -1,7 +1,7 @@ --[no-]all:: Fetch all remotes, except for the ones that has the `remote.<name>.skipFetchAll` configuration variable set. - This overrides the configuration variable fetch.all`. + This overrides the configuration variable `fetch.all`. -a:: --append:: diff --git a/Documentation/git-clone.adoc b/Documentation/git-clone.adoc index 222d558290..d829206d1b 100644 --- a/Documentation/git-clone.adoc +++ b/Documentation/git-clone.adoc @@ -16,7 +16,7 @@ git clone [--template=<template-directory>] [--depth <depth>] [--[no-]single-branch] [--[no-]tags] [--recurse-submodules[=<pathspec>]] [--[no-]shallow-submodules] [--[no-]remote-submodules] [--jobs <n>] [--sparse] [--[no-]reject-shallow] - [--filter=<filter-spec>] [--also-filter-submodules]] [--] <repository> + [--filter=<filter-spec> [--also-filter-submodules]] [--] <repository> [<directory>] DESCRIPTION diff --git a/Documentation/git-count-objects.adoc b/Documentation/git-count-objects.adoc index 97f9f12610..eeee6b9f7f 100644 --- a/Documentation/git-count-objects.adoc +++ b/Documentation/git-count-objects.adoc @@ -28,6 +28,8 @@ size: disk space consumed by loose objects, in KiB (unless -H is specified) + in-pack: the number of in-pack objects + +packs: the number of pack files ++ size-pack: disk space consumed by the packs, in KiB (unless -H is specified) + prune-packable: the number of loose objects that are also present in diff --git a/Documentation/git-fast-import.adoc b/Documentation/git-fast-import.adoc index 6f9763c11b..37d8aa3072 100644 --- a/Documentation/git-fast-import.adoc +++ b/Documentation/git-fast-import.adoc @@ -61,10 +61,10 @@ OPTIONS currently impacts only the `export-marks`, `import-marks`, and `import-marks-if-exists` feature commands. + - Only enable this option if you trust the program generating the - fast-import stream! This option is enabled automatically for - remote-helpers that use the `import` capability, as they are - already trusted to run their own code. +Only enable this option if you trust the program generating the +fast-import stream! This option is enabled automatically for +remote-helpers that use the `import` capability, as they are +already trusted to run their own code. Options for Frontends ~~~~~~~~~~~~~~~~~~~~~ @@ -644,7 +644,7 @@ External data format:: + Here usually `<dataref>` must be either a mark reference (`:<idnum>`) set by a prior `blob` command, or a full 40-byte SHA-1 of an -existing Git blob object. If `<mode>` is `040000`` then +existing Git blob object. If `<mode>` is `040000` then `<dataref>` must be the full 40-byte SHA-1 of an existing Git tree object or a mark reference set with `--import-marks`. diff --git a/Documentation/git-format-patch.adoc b/Documentation/git-format-patch.adoc index a8b53db9a6..a9cf37182e 100644 --- a/Documentation/git-format-patch.adoc +++ b/Documentation/git-format-patch.adoc @@ -587,13 +587,19 @@ an external editor to keep Thunderbird from mangling the patches. Approach #1 (add-on) ^^^^^^^^^^^^^^^^^^^^ -Install the Toggle Word Wrap add-on that is available from -https://addons.mozilla.org/thunderbird/addon/toggle-word-wrap/ -It adds a menu entry "Enable Word Wrap" in the composer's "Options" menu +Install the Toggle Line Wrap add-on that is available from +https://addons.thunderbird.net/thunderbird/addon/toggle-line-wrap +It adds a button "Line Wrap" to the composer's toolbar that you can tick off. Now you can compose the message as you otherwise do (cut + paste, 'git format-patch' | 'git imap-send', etc), but you have to insert line breaks manually in any text that you type. +As a bonus feature, the add-on can detect patch text in the composer +and warns when line wrapping has not yet been turned off. + +The add-on requires a few tweaks of the advanced configuration +(about:config). These are listed on the download page. + Approach #2 (configuration) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Three steps: diff --git a/Documentation/git-interpret-trailers.adoc b/Documentation/git-interpret-trailers.adoc index 82c8780d93..fd335fe772 100644 --- a/Documentation/git-interpret-trailers.adoc +++ b/Documentation/git-interpret-trailers.adoc @@ -142,8 +142,8 @@ OPTIONS provided with '--if-exists' overrides the `trailer.ifExists` and any applicable `trailer.<keyAlias>.ifExists` configuration variables and applies to all '--trailer' options until the next occurrence of - '--if-exists' or '--no-if-exists'. Upon encountering '--no-if-exists, clear the - effect of any previous use of '--if-exists, such that the relevant configuration + '--if-exists' or '--no-if-exists'. Upon encountering '--no-if-exists', clear the + effect of any previous use of '--if-exists', such that the relevant configuration variables are no longer overridden. Possible actions are `addIfDifferent`, `addIfDifferentNeighbor`, `add`, `replace` and `doNothing`. @@ -154,8 +154,8 @@ OPTIONS provided with '--if-missing' overrides the `trailer.ifMissing` and any applicable `trailer.<keyAlias>.ifMissing` configuration variables and applies to all '--trailer' options until the next occurrence of - '--if-missing' or '--no-if-missing'. Upon encountering '--no-if-missing, - clear the effect of any previous use of '--if-missing, such that the relevant + '--if-missing' or '--no-if-missing'. Upon encountering '--no-if-missing', + clear the effect of any previous use of '--if-missing', such that the relevant configuration variables are no longer overridden. Possible actions are `doNothing` or `add`. diff --git a/Documentation/git-merge-tree.adoc b/Documentation/git-merge-tree.adoc index f824eea61f..4e13ed33d6 100644 --- a/Documentation/git-merge-tree.adoc +++ b/Documentation/git-merge-tree.adoc @@ -78,11 +78,17 @@ OPTIONS --merge-base=<tree-ish>:: Instead of finding the merge-bases for <branch1> and <branch2>, - specify a merge-base for the merge, and specifying multiple bases is - currently not supported. This option is incompatible with `--stdin`. + specify a merge-base for the merge. This option is incompatible with + `--stdin`. + -As the merge-base is provided directly, <branch1> and <branch2> do not need -to specify commits; trees are enough. +Specifying multiple bases is currently not supported, which means that when +merging two branches with more than one merge-base, using this option may +cause merge results to differ from what `git merge` would compute. This +can include potentially losing some changes made on one side of the history +in the resulting merge. ++ +With this option, since the merge-base is provided directly, <branch1> and +<branch2> do not need to specify commits; trees are enough. -X<option>:: --strategy-option=<option>:: diff --git a/Documentation/git-multi-pack-index.adoc b/Documentation/git-multi-pack-index.adoc index b6cd0d7f85..85be5b84eb 100644 --- a/Documentation/git-multi-pack-index.adoc +++ b/Documentation/git-multi-pack-index.adoc @@ -28,7 +28,7 @@ OPTIONS --[no-]progress:: Turn progress on/off explicitly. If neither is specified, progress is shown if standard error is connected to a terminal. Supported by - sub-commands `write`, `verify`, `expire`, and `repack. + sub-commands `write`, `verify`, `expire`, and `repack`. The following subcommands are available: diff --git a/Documentation/git-patch-id.adoc b/Documentation/git-patch-id.adoc index 1d15fa45d5..45da0f27ac 100644 --- a/Documentation/git-patch-id.adoc +++ b/Documentation/git-patch-id.adoc @@ -33,27 +33,30 @@ OPTIONS --verbatim:: Calculate the patch-id of the input as it is given, do not strip any whitespace. - - This is the default if patchid.verbatim is true. ++ +This is the default if patchid.verbatim is true. --stable:: Use a "stable" sum of hashes as the patch ID. With this option: - - Reordering file diffs that make up a patch does not affect the ID. - In particular, two patches produced by comparing the same two trees - with two different settings for "-O<orderfile>" result in the same - patch ID signature, thereby allowing the computed result to be used - as a key to index some meta-information about the change between - the two trees; - - - Result is different from the value produced by git 1.9 and older - or produced when an "unstable" hash (see --unstable below) is - configured - even when used on a diff output taken without any use - of "-O<orderfile>", thereby making existing databases storing such - "unstable" or historical patch-ids unusable. - - - All whitespace within the patch is ignored and does not affect the id. - - This is the default if patchid.stable is set to true. ++ +-- +- Reordering file diffs that make up a patch does not affect the ID. + In particular, two patches produced by comparing the same two trees + with two different settings for "-O<orderfile>" result in the same + patch ID signature, thereby allowing the computed result to be used + as a key to index some meta-information about the change between + the two trees; + +- Result is different from the value produced by git 1.9 and older + or produced when an "unstable" hash (see --unstable below) is + configured - even when used on a diff output taken without any use + of "-O<orderfile>", thereby making existing databases storing such + "unstable" or historical patch-ids unusable. + +- All whitespace within the patch is ignored and does not affect the id. +-- ++ +This is the default if patchid.stable is set to true. --unstable:: Use an "unstable" hash as the patch ID. With this option, @@ -61,8 +64,8 @@ OPTIONS by git 1.9 and older and whitespace is ignored. Users with pre-existing databases storing patch-ids produced by git 1.9 and older (who do not deal with reordered patches) may want to use this option. - - This is the default. ++ +This is the default. GIT --- diff --git a/Documentation/git-reflog.adoc b/Documentation/git-reflog.adoc index 412f06b8fe..38af0c977a 100644 --- a/Documentation/git-reflog.adoc +++ b/Documentation/git-reflog.adoc @@ -8,16 +8,17 @@ git-reflog - Manage reflog information SYNOPSIS -------- -[verse] -'git reflog' [show] [<log-options>] [<ref>] -'git reflog list' -'git reflog expire' [--expire=<time>] [--expire-unreachable=<time>] +[synopsis] +git reflog [show] [<log-options>] [<ref>] +git reflog list +git reflog exists <ref> +git reflog write <ref> <old-oid> <new-oid> <message> +git reflog delete [--rewrite] [--updateref] + [--dry-run | -n] [--verbose] <ref>@{<specifier>}... +git reflog drop [--all [--single-worktree] | <refs>...] +git reflog expire [--expire=<time>] [--expire-unreachable=<time>] [--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...] -'git reflog delete' [--rewrite] [--updateref] - [--dry-run | -n] [--verbose] <ref>@{<specifier>}... -'git reflog drop' [--all [--single-worktree] | <refs>...] -'git reflog exists' <ref> DESCRIPTION ----------- @@ -43,11 +44,15 @@ actions, and in addition the `HEAD` reflog records branch switching. The "list" subcommand lists all refs which have a corresponding reflog. -The "expire" subcommand prunes older reflog entries. Entries older -than `expire` time, or entries older than `expire-unreachable` time -and not reachable from the current tip, are removed from the reflog. -This is typically not used directly by end users -- instead, see -linkgit:git-gc[1]. +The "exists" subcommand checks whether a ref has a reflog. It exits +with zero status if the reflog exists, and non-zero status if it does +not. + +The "write" subcommand writes a single entry to the reflog of a given +reference. This new entry is appended to the reflog and will thus become +the most recent entry. The reference name must be fully qualified. Both the old +and new object IDs must not be abbreviated and must point to existing objects. +The reflog message gets normalized. The "delete" subcommand deletes single entries from the reflog, but not the reflog itself. Its argument must be an _exact_ entry (e.g. "`git @@ -58,9 +63,11 @@ The "drop" subcommand completely removes the reflog for the specified references. This is in contrast to "expire" and "delete", both of which can be used to delete reflog entries, but not the reflog itself. -The "exists" subcommand checks whether a ref has a reflog. It exits -with zero status if the reflog exists, and non-zero status if it does -not. +The "expire" subcommand prunes older reflog entries. Entries older +than `expire` time, or entries older than `expire-unreachable` time +and not reachable from the current tip, are removed from the reflog. +This is typically not used directly by end users -- instead, see +linkgit:git-gc[1]. OPTIONS ------- @@ -71,18 +78,37 @@ Options for `show` `git reflog show` accepts any of the options accepted by `git log`. +Options for `delete` +~~~~~~~~~~~~~~~~~~~~ + +`git reflog delete` accepts options `--updateref`, `--rewrite`, `-n`, +`--dry-run`, and `--verbose`, with the same meanings as when they are +used with `expire`. + +Options for `drop` +~~~~~~~~~~~~~~~~~~ + +`--all`:: + Drop the reflogs of all references from all worktrees. + +`--single-worktree`:: + By default when `--all` is specified, reflogs from all working + trees are dropped. This option limits the processing to reflogs + from the current working tree only. + + Options for `expire` ~~~~~~~~~~~~~~~~~~~~ ---all:: +`--all`:: Process the reflogs of all references. ---single-worktree:: +`--single-worktree`:: By default when `--all` is specified, reflogs from all working trees are processed. This option limits the processing to reflogs from the current working tree only. ---expire=<time>:: +`--expire=<time>`:: Prune entries older than the specified time. If this option is not specified, the expiration time is taken from the configuration setting `gc.reflogExpire`, which in turn @@ -90,7 +116,7 @@ Options for `expire` of their age; `--expire=never` turns off pruning of reachable entries (but see `--expire-unreachable`). ---expire-unreachable=<time>:: +`--expire-unreachable=<time>`:: Prune entries older than `<time>` that are not reachable from the current tip of the branch. If this option is not specified, the expiration time is taken from the configuration @@ -100,17 +126,17 @@ Options for `expire` turns off early pruning of unreachable entries (but see `--expire`). ---updateref:: +`--updateref`:: Update the reference to the value of the top reflog entry (i.e. <ref>@\{0\}) if the previous top entry was pruned. (This option is ignored for symbolic references.) ---rewrite:: +`--rewrite`:: If a reflog entry's predecessor is pruned, adjust its "old" SHA-1 to be equal to the "new" SHA-1 field of the entry that now precedes it. ---stale-fix:: +`--stale-fix`:: Prune any reflog entries that point to "broken commits". A broken commit is a commit that is not reachable from any of the reference tips and that refers, directly or indirectly, to @@ -121,33 +147,15 @@ has the same cost as 'git prune'. It is primarily intended to fix corruption caused by garbage collecting using older versions of Git, which didn't protect objects referred to by reflogs. --n:: ---dry-run:: +`-n`:: +`--dry-run`:: Do not actually prune any entries; just show what would have been pruned. ---verbose:: +`--verbose`:: Print extra information on screen. -Options for `delete` -~~~~~~~~~~~~~~~~~~~~ - -`git reflog delete` accepts options `--updateref`, `--rewrite`, `-n`, -`--dry-run`, and `--verbose`, with the same meanings as when they are -used with `expire`. - -Options for `drop` -~~~~~~~~~~~~~~~~~~ - ---all:: - Drop the reflogs of all references from all worktrees. - ---single-worktree:: - By default when `--all` is specified, reflogs from all working - trees are dropped. This option limits the processing to reflogs - from the current working tree only. - GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-send-email.adoc b/Documentation/git-send-email.adoc index 5335502d68..c610909a92 100644 --- a/Documentation/git-send-email.adoc +++ b/Documentation/git-send-email.adoc @@ -521,10 +521,10 @@ edit `~/.gitconfig` to specify your account settings: ---- [sendemail] - smtpEncryption = tls + smtpEncryption = ssl smtpServer = smtp.gmail.com smtpUser = yourname@gmail.com - smtpServerPort = 587 + smtpServerPort = 465 ---- Gmail does not allow using your regular password for `git send-email`. @@ -542,10 +542,10 @@ if you want to use `OAUTHBEARER`, edit your `~/.gitconfig` file and add ---- [sendemail] - smtpEncryption = tls + smtpEncryption = ssl smtpServer = smtp.gmail.com smtpUser = yourname@gmail.com - smtpServerPort = 587 + smtpServerPort = 465 smtpAuth = OAUTHBEARER ---- diff --git a/Documentation/git-whatchanged.adoc b/Documentation/git-whatchanged.adoc index d21484026f..436e219b7d 100644 --- a/Documentation/git-whatchanged.adoc +++ b/Documentation/git-whatchanged.adoc @@ -15,7 +15,7 @@ WARNING ------- `git whatchanged` has been deprecated and is scheduled for removal in a future version of Git, as it is merely `git log` with different -default; `whatchanged` is not even shorter to type than `log --raw`. +defaults. DESCRIPTION ----------- @@ -24,7 +24,11 @@ Shows commit logs and diff output each commit introduces. New users are encouraged to use linkgit:git-log[1] instead. The `whatchanged` command is essentially the same as linkgit:git-log[1] -but defaults to showing the raw format diff output and skipping merges. +but defaults to showing the raw format diff output and skipping merges: + +---- +git log --raw --no-merges +---- The command is primarily kept for historical reasons; fingers of many people who learned Git long before `git log` was invented by diff --git a/Documentation/git.adoc b/Documentation/git.adoc index 743b7b00e4..ce099e78b8 100644 --- a/Documentation/git.adoc +++ b/Documentation/git.adoc @@ -219,7 +219,8 @@ If you just want to run git as if it was started in `<path>` then use List commands by group. This is an internal/experimental option and may change or be removed in the future. Supported groups are: builtins, parseopt (builtin commands that use - parse-options), main (all commands in libexec directory), + parse-options), deprecated (deprecated builtins), + main (all commands in libexec directory), others (all other commands in `$PATH` that have git- prefix), list-<category> (see categories in command-list.txt), nohelpers (exclude helper commands), alias and config @@ -684,7 +685,7 @@ other `GIT_PROGRESS_DELAY`:: A number controlling how many seconds to delay before showing - optional progress indicators. Defaults to 2. + optional progress indicators. Defaults to 1. `GIT_EDITOR`:: This environment variable overrides `$EDITOR` and `$VISUAL`. diff --git a/Documentation/gitcredentials.adoc b/Documentation/gitcredentials.adoc index 3337bb475d..60c2cc4ade 100644 --- a/Documentation/gitcredentials.adoc +++ b/Documentation/gitcredentials.adoc @@ -150,9 +150,8 @@ pattern in the config file. For example, if you have this in your config file: username = foo -------------------------------------- -then we will match: both protocols are the same, both hosts are the same, and -the "pattern" URL does not care about the path component at all. However, this -context would not match: +then we will match: both protocols are the same and both hosts are the same. +However, this context would not match: -------------------------------------- [credential "https://kernel.org"] @@ -166,11 +165,11 @@ match: Git compares the protocols exactly. However, you may use wildcards in the domain name and other pattern matching techniques as with the `http.<URL>.*` options. -If the "pattern" URL does include a path component, then this too must match -exactly: the context `https://example.com/bar/baz.git` will match a config -entry for `https://example.com/bar/baz.git` (in addition to matching the config -entry for `https://example.com`) but will not match a config entry for -`https://example.com/bar`. +If the "pattern" URL does include a path component, then this must match +as a prefix path: the context `https://example.com/bar` will match a config +entry for `https://example.com/bar/baz.git` but will not match a config entry for +`https://example.com/other/repo.git` or `https://example.com/barry/repo.git` +(even though it is a string prefix). CONFIGURATION OPTIONS diff --git a/Documentation/gitk.adoc b/Documentation/gitk.adoc index 58ce40ddb1..5b34dcd077 100644 --- a/Documentation/gitk.adoc +++ b/Documentation/gitk.adoc @@ -163,16 +163,16 @@ used by default. If '$XDG_CONFIG_HOME' is not set it defaults to History ------- -Gitk was the first graphical repository browser. It's written in -tcl/tk. +Gitk was the first graphical repository browser, written by +Paul Mackerras in Tcl/Tk. 'gitk' is actually maintained as an independent project, but stable versions are distributed as part of the Git suite for the convenience of end users. -gitk-git/ comes from Paul Mackerras's gitk project: +`gitk-git/` comes from Johannes Sixt's gitk project: - git://ozlabs.org/~paulus/gitk + https://github.com/j6t/gitk SEE ALSO -------- diff --git a/Documentation/pretty-formats.adoc b/Documentation/pretty-formats.adoc index 9ed0417fc8..2121e8e1df 100644 --- a/Documentation/pretty-formats.adoc +++ b/Documentation/pretty-formats.adoc @@ -232,19 +232,21 @@ ref names with custom decorations. The `decorate` string may be followed by a colon and zero or more comma-separated options. Option values may contain literal formatting codes. These must be used for commas (`%x2C`) and closing parentheses (`%x29`), due to their role in the option syntax. -+ -** `prefix=<value>`: Shown before the list of ref names. Defaults to "{nbsp}+(+". + +** `prefix=<value>`: Shown before the list of ref names. Defaults to "{nbsp}++(++". ** `suffix=<value>`: Shown after the list of ref names. Defaults to "+)+". ** `separator=<value>`: Shown between ref names. Defaults to "+,+{nbsp}". ** `pointer=<value>`: Shown between HEAD and the branch it points to, if any. - Defaults to "{nbsp}+->+{nbsp}". + Defaults to "{nbsp}++->++{nbsp}". ** `tag=<value>`: Shown before tag names. Defaults to "`tag:`{nbsp}". + +-- For example, to produce decorations with no wrapping or tag annotations, and spaces as separators: -+ + ++%(decorate:prefix=,suffix=,tag=,separator= )++ +-- ++%(describe++`[:<option>,...]`++)++:: human-readable name, like linkgit:git-describe[1]; empty string for diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 64cbc58335..13d3557666 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,6 +1,6 @@ #!/bin/sh -DEF_VER=v2.51.0 +DEF_VER=v2.51.1 LF=' ' @@ -883,7 +883,9 @@ BUILT_INS += git-stage$X BUILT_INS += git-status$X BUILT_INS += git-switch$X BUILT_INS += git-version$X +ifndef WITH_BREAKING_CHANGES BUILT_INS += git-whatchanged$X +endif # what 'all' will build but not install in gitexecdir OTHER_PROGRAMS += git$X @@ -3943,13 +3945,12 @@ unit-tests: $(UNIT_TEST_PROGS) $(CLAR_TEST_PROG) t/helper/test-tool$X $(MAKE) -C t/ unit-tests .PHONY: libgit-sys libgit-rs -libgit-sys libgit-rs: - $(QUIET)(\ - cd contrib/$@ && \ - cargo build \ - ) +libgit-sys: + $(QUIET)cargo build --manifest-path contrib/libgit-sys/Cargo.toml +libgit-rs: libgit-sys + $(QUIET)cargo build --manifest-path contrib/libgit-rs/Cargo.toml ifdef INCLUDE_LIBGIT_RS -all:: libgit-sys libgit-rs +all:: libgit-rs endif LIBGIT_PUB_OBJS += contrib/libgit-sys/public_symbol_export.o @@ -1 +1 @@ -Documentation/RelNotes/2.51.0.adoc
\ No newline at end of file +Documentation/RelNotes/2.51.1.adoc
\ No newline at end of file diff --git a/add-interactive.c b/add-interactive.c index 3e692b47ec..4604c69140 100644 --- a/add-interactive.c +++ b/add-interactive.c @@ -20,14 +20,14 @@ #include "prompt.h" #include "tree.h" -static void init_color(struct repository *r, struct add_i_state *s, +static void init_color(struct repository *r, int use_color, const char *section_and_slot, char *dst, const char *default_color) { char *key = xstrfmt("color.%s", section_and_slot); const char *value; - if (!s->use_color) + if (!use_color) dst[0] = '\0'; else if (repo_config_get_value(r, key, &value) || color_parse(value, dst)) @@ -36,42 +36,63 @@ static void init_color(struct repository *r, struct add_i_state *s, free(key); } -void init_add_i_state(struct add_i_state *s, struct repository *r, - struct add_p_opt *add_p_opt) +static int check_color_config(struct repository *r, const char *var) { const char *value; + int ret; + + if (repo_config_get_value(r, var, &value)) + ret = -1; + else + ret = git_config_colorbool(var, value); + + /* + * Do not rely on want_color() to fall back to color.ui for us. It uses + * the value parsed by git_color_config(), which may not have been + * called by the main command. + */ + if (ret < 0 && !repo_config_get_value(r, "color.ui", &value)) + ret = git_config_colorbool("color.ui", value); + return want_color(ret); +} + +void init_add_i_state(struct add_i_state *s, struct repository *r, + struct add_p_opt *add_p_opt) +{ s->r = r; s->context = -1; s->interhunkcontext = -1; - if (repo_config_get_value(r, "color.interactive", &value)) - s->use_color = -1; - else - s->use_color = - git_config_colorbool("color.interactive", value); - s->use_color = want_color(s->use_color); - - init_color(r, s, "interactive.header", s->header_color, GIT_COLOR_BOLD); - init_color(r, s, "interactive.help", s->help_color, GIT_COLOR_BOLD_RED); - init_color(r, s, "interactive.prompt", s->prompt_color, - GIT_COLOR_BOLD_BLUE); - init_color(r, s, "interactive.error", s->error_color, - GIT_COLOR_BOLD_RED); - - init_color(r, s, "diff.frag", s->fraginfo_color, - diff_get_color(s->use_color, DIFF_FRAGINFO)); - init_color(r, s, "diff.context", s->context_color, "fall back"); + s->use_color_interactive = check_color_config(r, "color.interactive"); + + init_color(r, s->use_color_interactive, "interactive.header", + s->header_color, GIT_COLOR_BOLD); + init_color(r, s->use_color_interactive, "interactive.help", + s->help_color, GIT_COLOR_BOLD_RED); + init_color(r, s->use_color_interactive, "interactive.prompt", + s->prompt_color, GIT_COLOR_BOLD_BLUE); + init_color(r, s->use_color_interactive, "interactive.error", + s->error_color, GIT_COLOR_BOLD_RED); + strlcpy(s->reset_color_interactive, + s->use_color_interactive ? GIT_COLOR_RESET : "", COLOR_MAXLEN); + + s->use_color_diff = check_color_config(r, "color.diff"); + + init_color(r, s->use_color_diff, "diff.frag", s->fraginfo_color, + diff_get_color(s->use_color_diff, DIFF_FRAGINFO)); + init_color(r, s->use_color_diff, "diff.context", s->context_color, + "fall back"); if (!strcmp(s->context_color, "fall back")) - init_color(r, s, "diff.plain", s->context_color, - diff_get_color(s->use_color, DIFF_CONTEXT)); - init_color(r, s, "diff.old", s->file_old_color, - diff_get_color(s->use_color, DIFF_FILE_OLD)); - init_color(r, s, "diff.new", s->file_new_color, - diff_get_color(s->use_color, DIFF_FILE_NEW)); - - strlcpy(s->reset_color, - s->use_color ? GIT_COLOR_RESET : "", COLOR_MAXLEN); + init_color(r, s->use_color_diff, "diff.plain", + s->context_color, + diff_get_color(s->use_color_diff, DIFF_CONTEXT)); + init_color(r, s->use_color_diff, "diff.old", s->file_old_color, + diff_get_color(s->use_color_diff, DIFF_FILE_OLD)); + init_color(r, s->use_color_diff, "diff.new", s->file_new_color, + diff_get_color(s->use_color_diff, DIFF_FILE_NEW)); + strlcpy(s->reset_color_diff, + s->use_color_diff ? GIT_COLOR_RESET : "", COLOR_MAXLEN); FREE_AND_NULL(s->interactive_diff_filter); repo_config_get_string(r, "interactive.difffilter", @@ -109,7 +130,8 @@ void clear_add_i_state(struct add_i_state *s) FREE_AND_NULL(s->interactive_diff_filter); FREE_AND_NULL(s->interactive_diff_algorithm); memset(s, 0, sizeof(*s)); - s->use_color = -1; + s->use_color_interactive = -1; + s->use_color_diff = -1; } /* @@ -1188,9 +1210,9 @@ int run_add_i(struct repository *r, const struct pathspec *ps, * When color was asked for, use the prompt color for * highlighting, otherwise use square brackets. */ - if (s.use_color) { + if (s.use_color_interactive) { data.color = s.prompt_color; - data.reset = s.reset_color; + data.reset = s.reset_color_interactive; } print_file_item_data.color = data.color; print_file_item_data.reset = data.reset; diff --git a/add-interactive.h b/add-interactive.h index 4213dcd67b..ceadfa6bb6 100644 --- a/add-interactive.h +++ b/add-interactive.h @@ -12,16 +12,19 @@ struct add_p_opt { struct add_i_state { struct repository *r; - int use_color; + int use_color_interactive; + int use_color_diff; char header_color[COLOR_MAXLEN]; char help_color[COLOR_MAXLEN]; char prompt_color[COLOR_MAXLEN]; char error_color[COLOR_MAXLEN]; - char reset_color[COLOR_MAXLEN]; + char reset_color_interactive[COLOR_MAXLEN]; + char fraginfo_color[COLOR_MAXLEN]; char context_color[COLOR_MAXLEN]; char file_old_color[COLOR_MAXLEN]; char file_new_color[COLOR_MAXLEN]; + char reset_color_diff[COLOR_MAXLEN]; int use_single_key; char *interactive_diff_filter, *interactive_diff_algorithm; diff --git a/add-patch.c b/add-patch.c index 302e6ba7d9..b0389c5d5b 100644 --- a/add-patch.c +++ b/add-patch.c @@ -300,7 +300,7 @@ static void err(struct add_p_state *s, const char *fmt, ...) va_start(args, fmt); fputs(s->s.error_color, stdout); vprintf(fmt, args); - puts(s->s.reset_color); + puts(s->s.reset_color_interactive); va_end(args); } @@ -457,7 +457,7 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) } strbuf_complete_line(plain); - if (want_color_fd(1, -1)) { + if (want_color_fd(1, s->s.use_color_diff)) { struct child_process colored_cp = CHILD_PROCESS_INIT; const char *diff_filter = s->s.interactive_diff_filter; @@ -714,7 +714,7 @@ static void render_hunk(struct add_p_state *s, struct hunk *hunk, if (len) strbuf_add(out, p, len); else if (colored) - strbuf_addf(out, "%s\n", s->s.reset_color); + strbuf_addf(out, "%s\n", s->s.reset_color_diff); else strbuf_addch(out, '\n'); } @@ -1107,7 +1107,7 @@ static void recolor_hunk(struct add_p_state *s, struct hunk *hunk) s->s.file_new_color : s->s.context_color); strbuf_add(&s->colored, plain + current, eol - current); - strbuf_addstr(&s->colored, s->s.reset_color); + strbuf_addstr(&s->colored, s->s.reset_color_diff); if (next > eol) strbuf_add(&s->colored, plain + eol, next - eol); current = next; @@ -1528,8 +1528,8 @@ static int patch_update_file(struct add_p_state *s, : 1)); printf(_(s->mode->prompt_mode[prompt_mode_type]), s->buf.buf); - if (*s->s.reset_color) - fputs(s->s.reset_color, stdout); + if (*s->s.reset_color_interactive) + fputs(s->s.reset_color_interactive, stdout); fflush(stdout); if (read_single_character(s) == EOF) break; diff --git a/builtin/diff.c b/builtin/diff.c index 9a89e25a98..0b23c41456 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -487,6 +487,21 @@ int cmd_diff(int argc, init_diff_ui_defaults(); repo_config(the_repository, git_diff_ui_config, NULL); + + /* + * If we are ignoring the fact that our current directory may + * be part of a working tree controlled by a Git repository to + * pretend to be a "better GNU diff", we should undo the + * effect of the setup code that did a chdir() to the top of + * the working tree. Where we came from is recorded in the + * prefix. + */ + if (no_index && prefix) { + if (chdir(prefix)) + die(_("cannot come back to cwd")); + prefix = NULL; + } + prefix = precompose_argv_prefix(argc, argv, prefix); repo_init_revisions(the_repository, &rev, prefix); diff --git a/builtin/fetch.c b/builtin/fetch.c index 24645c4653..c7ff3480fb 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -1643,7 +1643,8 @@ cleanup: struct ref_rejection_data { int *retcode; - int conflict_msg_shown; + bool conflict_msg_shown; + bool case_sensitive_msg_shown; const char *remote_name; }; @@ -1657,11 +1658,25 @@ static void ref_transaction_rejection_handler(const char *refname, { struct ref_rejection_data *data = cb_data; - if (err == REF_TRANSACTION_ERROR_NAME_CONFLICT && !data->conflict_msg_shown) { + if (err == REF_TRANSACTION_ERROR_CASE_CONFLICT && ignore_case && + !data->case_sensitive_msg_shown) { + error(_("You're on a case-insensitive filesystem, and the remote you are\n" + "trying to fetch from has references that only differ in casing. It\n" + "is impossible to store such references with the 'files' backend. You\n" + "can either accept this as-is, in which case you won't be able to\n" + "store all remote references on disk. Or you can alternatively\n" + "migrate your repository to use the 'reftable' backend with the\n" + "following command:\n\n git refs migrate --ref-format=reftable\n\n" + "Please keep in mind that not all implementations of Git support this\n" + "new format yet. So if you use tools other than Git to access this\n" + "repository it may not be an option to migrate to reftables.\n")); + data->case_sensitive_msg_shown = true; + } else if (err == REF_TRANSACTION_ERROR_NAME_CONFLICT && + !data->conflict_msg_shown) { error(_("some local refs could not be updated; try running\n" " 'git remote prune %s' to remove any old, conflicting " "branches"), data->remote_name); - data->conflict_msg_shown = 1; + data->conflict_msg_shown = true; } else { const char *reason = ref_transaction_error_msg(err); diff --git a/builtin/log.c b/builtin/log.c index c2f8bbf863..1d1e6e9130 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -543,7 +543,13 @@ int cmd_whatchanged(int argc, cmd_log_init(argc, argv, prefix, &rev, &opt, &cfg); if (!cfg.i_still_use_this) - you_still_use_that("git whatchanged"); + you_still_use_that("git whatchanged", + _("\n" + "hint: You can replace 'git whatchanged <opts>' with:\n" + "hint:\tgit log <opts> --raw --no-merges\n" + "hint: Or make an alias:\n" + "hint:\tgit config set --global alias.whatchanged 'log --raw --no-merges'\n" + "\n")); if (!rev.diffopt.output_format) rev.diffopt.output_format = DIFF_FORMAT_RAW; diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 53a2256250..ff6900b654 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -3774,7 +3774,7 @@ static void show_object_pack_hint(struct object *object, const char *name, enum stdin_packs_mode mode = *(enum stdin_packs_mode *)data; if (mode == STDIN_PACKS_MODE_FOLLOW) { if (object->type == OBJ_BLOB && - !has_object(the_repository, &object->oid, 0)) + !odb_has_object(the_repository->objects, &object->oid, 0)) return; add_object_entry(&object->oid, object->type, name, 0); } else { @@ -4591,8 +4591,8 @@ static int add_objects_by_path(const char *path, /* Skip objects that do not exist locally. */ if ((exclude_promisor_objects || arg_missing_action != MA_ERROR) && - oid_object_info_extended(the_repository, oid, &oi, - OBJECT_INFO_FOR_PREFETCH) < 0) + odb_read_object_info_extended(the_repository->objects, oid, &oi, + OBJECT_INFO_FOR_PREFETCH) < 0) continue; exclude = is_oid_uninteresting(the_repository, oid); diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c index fe81c293e3..5d5ae4afa2 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c @@ -626,7 +626,7 @@ int cmd_pack_redundant(int argc, const char **argv, const char *prefix UNUSED, s } if (!i_still_use_this) - you_still_use_that("git pack-redundant"); + you_still_use_that("git pack-redundant", NULL); if (load_all_packs) load_all(); diff --git a/builtin/reflog.c b/builtin/reflog.c index 1db26aa65f..c8f6b93d60 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -3,6 +3,8 @@ #include "builtin.h" #include "config.h" #include "gettext.h" +#include "hex.h" +#include "odb.h" #include "revision.h" #include "reachable.h" #include "wildmatch.h" @@ -17,21 +19,24 @@ #define BUILTIN_REFLOG_LIST_USAGE \ N_("git reflog list") -#define BUILTIN_REFLOG_EXPIRE_USAGE \ - N_("git reflog expire [--expire=<time>] [--expire-unreachable=<time>]\n" \ - " [--rewrite] [--updateref] [--stale-fix]\n" \ - " [--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...]") +#define BUILTIN_REFLOG_EXISTS_USAGE \ + N_("git reflog exists <ref>") + +#define BUILTIN_REFLOG_WRITE_USAGE \ + N_("git reflog write <ref> <old-oid> <new-oid> <message>") #define BUILTIN_REFLOG_DELETE_USAGE \ N_("git reflog delete [--rewrite] [--updateref]\n" \ " [--dry-run | -n] [--verbose] <ref>@{<specifier>}...") -#define BUILTIN_REFLOG_EXISTS_USAGE \ - N_("git reflog exists <ref>") - #define BUILTIN_REFLOG_DROP_USAGE \ N_("git reflog drop [--all [--single-worktree] | <refs>...]") +#define BUILTIN_REFLOG_EXPIRE_USAGE \ + N_("git reflog expire [--expire=<time>] [--expire-unreachable=<time>]\n" \ + " [--rewrite] [--updateref] [--stale-fix]\n" \ + " [--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...]") + static const char *const reflog_show_usage[] = { BUILTIN_REFLOG_SHOW_USAGE, NULL, @@ -42,9 +47,14 @@ static const char *const reflog_list_usage[] = { NULL, }; -static const char *const reflog_expire_usage[] = { - BUILTIN_REFLOG_EXPIRE_USAGE, - NULL +static const char *const reflog_exists_usage[] = { + BUILTIN_REFLOG_EXISTS_USAGE, + NULL, +}; + +static const char *const reflog_write_usage[] = { + BUILTIN_REFLOG_WRITE_USAGE, + NULL, }; static const char *const reflog_delete_usage[] = { @@ -52,23 +62,24 @@ static const char *const reflog_delete_usage[] = { NULL }; -static const char *const reflog_exists_usage[] = { - BUILTIN_REFLOG_EXISTS_USAGE, - NULL, -}; - static const char *const reflog_drop_usage[] = { BUILTIN_REFLOG_DROP_USAGE, NULL, }; +static const char *const reflog_expire_usage[] = { + BUILTIN_REFLOG_EXPIRE_USAGE, + NULL +}; + static const char *const reflog_usage[] = { BUILTIN_REFLOG_SHOW_USAGE, BUILTIN_REFLOG_LIST_USAGE, - BUILTIN_REFLOG_EXPIRE_USAGE, + BUILTIN_REFLOG_EXISTS_USAGE, + BUILTIN_REFLOG_WRITE_USAGE, BUILTIN_REFLOG_DELETE_USAGE, BUILTIN_REFLOG_DROP_USAGE, - BUILTIN_REFLOG_EXISTS_USAGE, + BUILTIN_REFLOG_EXPIRE_USAGE, NULL }; @@ -395,6 +406,59 @@ static int cmd_reflog_drop(int argc, const char **argv, const char *prefix, return ret; } +static int cmd_reflog_write(int argc, const char **argv, const char *prefix, + struct repository *repo) +{ + const struct option options[] = { + OPT_END() + }; + struct object_id old_oid, new_oid; + struct strbuf err = STRBUF_INIT; + struct ref_transaction *tx; + const char *ref, *message; + int ret; + + argc = parse_options(argc, argv, prefix, options, reflog_write_usage, 0); + if (argc != 4) + usage_with_options(reflog_write_usage, options); + + ref = argv[0]; + if (!is_root_ref(ref) && check_refname_format(ref, 0)) + die(_("invalid reference name: %s"), ref); + + ret = get_oid_hex_algop(argv[1], &old_oid, repo->hash_algo); + if (ret) + die(_("invalid old object ID: '%s'"), argv[1]); + if (!is_null_oid(&old_oid) && !odb_has_object(repo->objects, &old_oid, 0)) + die(_("old object '%s' does not exist"), argv[1]); + + ret = get_oid_hex_algop(argv[2], &new_oid, repo->hash_algo); + if (ret) + die(_("invalid new object ID: '%s'"), argv[2]); + if (!is_null_oid(&new_oid) && !odb_has_object(repo->objects, &new_oid, 0)) + die(_("new object '%s' does not exist"), argv[2]); + + message = argv[3]; + + tx = ref_store_transaction_begin(get_main_ref_store(repo), 0, &err); + if (!tx) + die(_("cannot start transaction: %s"), err.buf); + + ret = ref_transaction_update_reflog(tx, ref, &new_oid, &old_oid, + git_committer_info(0), + message, 0, &err); + if (ret) + die(_("cannot queue reflog update: %s"), err.buf); + + ret = ref_transaction_commit(tx, &err); + if (ret) + die(_("cannot commit reflog update: %s"), err.buf); + + ref_transaction_free(tx); + strbuf_release(&err); + return 0; +} + /* * main "reflog" */ @@ -407,10 +471,11 @@ int cmd_reflog(int argc, struct option options[] = { OPT_SUBCOMMAND("show", &fn, cmd_reflog_show), OPT_SUBCOMMAND("list", &fn, cmd_reflog_list), - OPT_SUBCOMMAND("expire", &fn, cmd_reflog_expire), - OPT_SUBCOMMAND("delete", &fn, cmd_reflog_delete), OPT_SUBCOMMAND("exists", &fn, cmd_reflog_exists), + OPT_SUBCOMMAND("write", &fn, cmd_reflog_write), + OPT_SUBCOMMAND("delete", &fn, cmd_reflog_delete), OPT_SUBCOMMAND("drop", &fn, cmd_reflog_drop), + OPT_SUBCOMMAND("expire", &fn, cmd_reflog_expire), OPT_END() }; diff --git a/builtin/stash.c b/builtin/stash.c index 1977e50df2..063208b9b4 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -377,7 +377,7 @@ static int diff_tree_binary(struct strbuf *out, struct object_id *w_commit) * however it should be done together with apply_cached. */ cp.git_cmd = 1; - strvec_pushl(&cp.args, "diff-tree", "--binary", NULL); + strvec_pushl(&cp.args, "diff-tree", "--binary", "--no-color", NULL); strvec_pushf(&cp.args, "%s^2^..%s^2", w_commit_hex, w_commit_hex); return pipe_command(&cp, NULL, 0, out, 0, NULL, 0); @@ -1283,6 +1283,7 @@ static int stash_staged(struct stash_info *info, struct strbuf *out_patch, cp_diff_tree.git_cmd = 1; strvec_pushl(&cp_diff_tree.args, "diff-tree", "-p", "--binary", + "--no-color", "-U1", "HEAD", oid_to_hex(&info->w_tree), "--", NULL); if (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) { ret = -1; @@ -1345,6 +1346,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps, cp_diff_tree.git_cmd = 1; strvec_pushl(&cp_diff_tree.args, "diff-tree", "-p", "-U1", "HEAD", + "--no-color", oid_to_hex(&info->w_tree), "--", NULL); if (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) { ret = -1; @@ -1719,6 +1721,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q cp_diff.git_cmd = 1; strvec_pushl(&cp_diff.args, "diff-index", "-p", + "--no-color", "--cached", "--binary", "HEAD", "--", NULL); add_pathspecs(&cp_diff.args, ps); diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh index d061a47293..6668c4df84 100755 --- a/ci/install-dependencies.sh +++ b/ci/install-dependencies.sh @@ -61,6 +61,15 @@ ubuntu-*|i386/ubuntu-*|debian-*) libsecret-1-dev libpcre2-dev meson ninja-build pkg-config \ ${CC_PACKAGE:-${CC:-gcc}} $PYTHON_PACKAGE + # Starting with Ubuntu 25.10, sudo can now be provided via either + # sudo(1) or sudo-rs(1), with the latter being the default. The problem + # is that it does not support `--preserve-env` though, which we rely on + # in our CI. We thus revert back to the C implementation. + if test -f /etc/alternatives/sudo + then + sudo update-alternatives --set sudo /usr/bin/sudo.ws + fi + case "$distro" in ubuntu-*) mkdir --parents "$CUSTOM_PATH" diff --git a/contrib/diff-highlight/README b/contrib/diff-highlight/README index d4c2343175..1db4440e68 100644 --- a/contrib/diff-highlight/README +++ b/contrib/diff-highlight/README @@ -58,6 +58,14 @@ following in your git configuration: diff = diff-highlight | less --------------------------------------------- +If you use the interactive patch mode of `git add -p`, `git checkout +-p`, etc, you may also want to configure it to be used there: + +--------------------------------------------- +[interactive] + diffFilter = diff-highlight +--------------------------------------------- + Color Config ------------ diff --git a/diff-no-index.c b/diff-no-index.c index 88ae4cee56..f320424f05 100644 --- a/diff-no-index.c +++ b/diff-no-index.c @@ -21,30 +21,21 @@ static int read_directory_contents(const char *path, struct string_list *list, const struct pathspec *pathspec, - int skip) + struct strbuf *match) { - struct strbuf match = STRBUF_INIT; - int len; + int len = match->len; DIR *dir; struct dirent *e; if (!(dir = opendir(path))) return error("Could not open directory %s", path); - if (pathspec) { - strbuf_addstr(&match, path); - strbuf_complete(&match, '/'); - strbuf_remove(&match, 0, skip); - - len = match.len; - } - while ((e = readdir_skip_dot_and_dotdot(dir))) { if (pathspec) { int is_dir = 0; - strbuf_setlen(&match, len); - strbuf_addstr(&match, e->d_name); + strbuf_setlen(match, len); + strbuf_addstr(match, e->d_name); if (NOT_CONSTANT(DTYPE(e)) != DT_UNKNOWN) { is_dir = (DTYPE(e) == DT_DIR); } else { @@ -57,7 +48,7 @@ static int read_directory_contents(const char *path, struct string_list *list, } if (!match_leading_pathspec(NULL, pathspec, - match.buf, match.len, + match->buf, match->len, 0, NULL, is_dir)) continue; } @@ -65,7 +56,7 @@ static int read_directory_contents(const char *path, struct string_list *list, string_list_insert(list, e->d_name); } - strbuf_release(&match); + strbuf_setlen(match, len); closedir(dir); return 0; } @@ -169,7 +160,8 @@ static struct diff_filespec *noindex_filespec(const struct git_hash_algo *algop, static int queue_diff(struct diff_options *o, const struct git_hash_algo *algop, const char *name1, const char *name2, int recursing, - const struct pathspec *ps, int skip1, int skip2) + const struct pathspec *ps, + struct strbuf *ps_match1, struct strbuf *ps_match2) { int mode1 = 0, mode2 = 0; enum special special1 = SPECIAL_NONE, special2 = SPECIAL_NONE; @@ -208,10 +200,12 @@ static int queue_diff(struct diff_options *o, const struct git_hash_algo *algop, struct string_list p2 = STRING_LIST_INIT_DUP; int i1, i2, ret = 0; size_t len1 = 0, len2 = 0; + size_t match1_len = ps_match1->len; + size_t match2_len = ps_match2->len; - if (name1 && read_directory_contents(name1, &p1, ps, skip1)) + if (name1 && read_directory_contents(name1, &p1, ps, ps_match1)) return -1; - if (name2 && read_directory_contents(name2, &p2, ps, skip2)) { + if (name2 && read_directory_contents(name2, &p2, ps, ps_match2)) { string_list_clear(&p1, 0); return -1; } @@ -235,6 +229,11 @@ static int queue_diff(struct diff_options *o, const struct git_hash_algo *algop, strbuf_setlen(&buffer1, len1); strbuf_setlen(&buffer2, len2); + if (ps) { + strbuf_setlen(ps_match1, match1_len); + strbuf_setlen(ps_match2, match2_len); + } + if (i1 == p1.nr) comp = 1; else if (i2 == p2.nr) @@ -245,18 +244,28 @@ static int queue_diff(struct diff_options *o, const struct git_hash_algo *algop, if (comp > 0) n1 = NULL; else { - strbuf_addstr(&buffer1, p1.items[i1++].string); + strbuf_addstr(&buffer1, p1.items[i1].string); + if (ps) { + strbuf_addstr(ps_match1, p1.items[i1].string); + strbuf_complete(ps_match1, '/'); + } n1 = buffer1.buf; + i1++; } if (comp < 0) n2 = NULL; else { - strbuf_addstr(&buffer2, p2.items[i2++].string); + strbuf_addstr(&buffer2, p2.items[i2].string); + if (ps) { + strbuf_addstr(ps_match2, p2.items[i2].string); + strbuf_complete(ps_match2, '/'); + } n2 = buffer2.buf; + i2++; } - ret = queue_diff(o, algop, n1, n2, 1, ps, skip1, skip2); + ret = queue_diff(o, algop, n1, n2, 1, ps, ps_match1, ps_match2); } string_list_clear(&p1, 0); string_list_clear(&p2, 0); @@ -346,7 +355,8 @@ int diff_no_index(struct rev_info *revs, const struct git_hash_algo *algop, int implicit_no_index, int argc, const char **argv) { struct pathspec pathspec, *ps = NULL; - int i, no_index, skip1 = 0, skip2 = 0; + struct strbuf ps_match1 = STRBUF_INIT, ps_match2 = STRBUF_INIT; + int i, no_index; int ret = 1; const char *paths[2]; char *to_free[ARRAY_SIZE(paths)] = { 0 }; @@ -387,11 +397,6 @@ int diff_no_index(struct rev_info *revs, const struct git_hash_algo *algop, NULL, &argv[2]); if (pathspec.nr) ps = &pathspec; - - skip1 = strlen(paths[0]); - skip1 += paths[0][skip1] == '/' ? 0 : 1; - skip2 = strlen(paths[1]); - skip2 += paths[1][skip2] == '/' ? 0 : 1; } else if (argc > 2) { warning(_("Limiting comparison with pathspecs is only " "supported if both paths are directories.")); @@ -415,7 +420,7 @@ int diff_no_index(struct rev_info *revs, const struct git_hash_algo *algop, revs->diffopt.flags.exit_with_status = 1; if (queue_diff(&revs->diffopt, algop, paths[0], paths[1], 0, ps, - skip1, skip2)) + &ps_match1, &ps_match2)) goto out; diff_set_mnemonic_prefix(&revs->diffopt, "1/", "2/"); diffcore_std(&revs->diffopt); @@ -431,6 +436,8 @@ out: for (i = 0; i < ARRAY_SIZE(to_free); i++) free(to_free[i]); strbuf_release(&replacement); + strbuf_release(&ps_match1); + strbuf_release(&ps_match2); if (ps) clear_pathspec(ps); return ret; @@ -2444,6 +2444,15 @@ static int fn_out_consume(void *priv, char *line, unsigned long len) return 0; } +static int quick_consume(void *priv, char *line UNUSED, unsigned long len UNUSED) +{ + struct emit_callback *ecbdata = priv; + struct diff_options *o = ecbdata->opt; + + o->found_changes = 1; + return 1; +} + static void pprint_rename(struct strbuf *name, const char *a, const char *b) { const char *old_name = a; @@ -3759,8 +3768,21 @@ static void builtin_diff(const char *name_a, if (o->word_diff) init_diff_words_data(&ecbdata, o, one, two); - if (xdi_diff_outf(&mf1, &mf2, NULL, fn_out_consume, - &ecbdata, &xpp, &xecfg)) + if (o->dry_run) { + /* + * Unlike the !dry_run case, we need to ignore the + * return value from xdi_diff_outf() here, because + * xdi_diff_outf() takes non-zero return from its + * callback function as a sign of error and returns + * early (which is why we return non-zero from our + * callback, quick_consume()). Unfortunately, + * xdi_diff_outf() signals an error by returning + * non-zero. + */ + xdi_diff_outf(&mf1, &mf2, NULL, quick_consume, + &ecbdata, &xpp, &xecfg); + } else if (xdi_diff_outf(&mf1, &mf2, NULL, fn_out_consume, + &ecbdata, &xpp, &xecfg)) die("unable to generate diff for %s", one->path); if (o->word_diff) free_diff_words_data(&ecbdata); @@ -6150,6 +6172,22 @@ static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o) run_diff(p, o); } +/* return 1 if any change is found; otherwise, return 0 */ +static int diff_flush_patch_quietly(struct diff_filepair *p, struct diff_options *o) +{ + int saved_dry_run = o->dry_run; + int saved_found_changes = o->found_changes; + int ret; + + o->dry_run = 1; + o->found_changes = 0; + diff_flush_patch(p, o); + ret = o->found_changes; + o->dry_run = saved_dry_run; + o->found_changes |= saved_found_changes; + return ret; +} + static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o, struct diffstat_t *diffstat) { @@ -6776,10 +6814,37 @@ void diff_flush(struct diff_options *options) DIFF_FORMAT_NAME | DIFF_FORMAT_NAME_STATUS | DIFF_FORMAT_CHECKDIFF)) { + /* + * make sure diff_Flush_patch_quietly() to be silent. + */ + FILE *dev_null = NULL; + int saved_color_moved = options->color_moved; + + if (options->flags.diff_from_contents) { + dev_null = xfopen("/dev/null", "w"); + options->color_moved = 0; + } for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; - if (check_pair_status(p)) - flush_one_pair(p, options); + + if (!check_pair_status(p)) + continue; + + if (options->flags.diff_from_contents) { + FILE *saved_file = options->file; + int found_changes; + + options->file = dev_null; + found_changes = diff_flush_patch_quietly(p, options); + options->file = saved_file; + if (!found_changes) + continue; + } + flush_one_pair(p, options); + } + if (options->flags.diff_from_contents) { + fclose(dev_null); + options->color_moved = saved_color_moved; } separator++; } @@ -6843,7 +6908,7 @@ void diff_flush(struct diff_options *options) for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; if (check_pair_status(p)) - diff_flush_patch(p, options); + diff_flush_patch_quietly(p, options); if (options->found_changes) break; } @@ -400,6 +400,8 @@ struct diff_options { #define COLOR_MOVED_WS_ERROR (1<<0) unsigned color_moved_ws_handling; + bool dry_run; + struct repository *repo; struct strmap *additional_path_headers; diff --git a/fetch-pack.c b/fetch-pack.c index 46c39f85c4..a9f5e6b510 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -143,7 +143,8 @@ static struct commit *deref_without_lazy_fetch(const struct object_id *oid, commit = lookup_commit_in_graph(the_repository, oid); if (commit) { if (mark_tags_complete_and_check_obj_db) { - if (!odb_has_object(the_repository->objects, oid, 0)) + if (!odb_has_object(the_repository->objects, oid, + HAS_OBJECT_RECHECK_PACKED)) die_in_commit_graph_only(oid); } return commit; diff --git a/git-compat-util.h b/git-compat-util.h index 9408f463e3..398e0fac4f 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -460,7 +460,7 @@ void warning_errno(const char *err, ...) __attribute__((format (printf, 1, 2))); void show_usage_if_asked(int ac, const char **av, const char *err); -NORETURN void you_still_use_that(const char *command_name); +NORETURN void you_still_use_that(const char *command_name, const char *hint); #ifndef NO_OPENSSL #ifdef APPLE_COMMON_CRYPTO diff --git a/git-curl-compat.h b/git-curl-compat.h index aa8eed7ed2..659e5a3875 100644 --- a/git-curl-compat.h +++ b/git-curl-compat.h @@ -46,6 +46,13 @@ #endif /** + * curl_global_trace() was added in 8.3.0, released September 2023. + */ +#if LIBCURL_VERSION_NUM >= 0x080300 +#define GIT_CURL_HAVE_GLOBAL_TRACE 1 +#endif + +/** * CURLOPT_TCP_KEEPCNT was added in 8.9.0, released in July, 2024. */ #if LIBCURL_VERSION_NUM >= 0x080900 @@ -28,6 +28,7 @@ #define NEED_WORK_TREE (1<<3) #define DELAY_PAGER_CONFIG (1<<4) #define NO_PARSEOPT (1<<5) /* parse-options is not used */ +#define DEPRECATED (1<<6) struct cmd_struct { const char *cmd; @@ -51,7 +52,9 @@ const char git_more_info_string[] = static int use_pager = -1; -static void list_builtins(struct string_list *list, unsigned int exclude_option); +static void list_builtins(struct string_list *list, + unsigned int include_option, + unsigned int exclude_option); static void exclude_helpers_from_list(struct string_list *list) { @@ -88,7 +91,7 @@ static int list_cmds(const char *spec) int len = sep - spec; if (match_token(spec, len, "builtins")) - list_builtins(&list, 0); + list_builtins(&list, 0, 0); else if (match_token(spec, len, "main")) list_all_main_cmds(&list); else if (match_token(spec, len, "others")) @@ -99,6 +102,8 @@ static int list_cmds(const char *spec) list_aliases(&list); else if (match_token(spec, len, "config")) list_cmds_by_config(&list); + else if (match_token(spec, len, "deprecated")) + list_builtins(&list, DEPRECATED, 0); else if (len > 5 && !strncmp(spec, "list-", 5)) { struct strbuf sb = STRBUF_INIT; @@ -322,7 +327,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) if (!strcmp(cmd, "parseopt")) { struct string_list list = STRING_LIST_INIT_DUP; - list_builtins(&list, NO_PARSEOPT); + list_builtins(&list, 0, NO_PARSEOPT); for (size_t i = 0; i < list.nr; i++) printf("%s ", list.items[i].string); string_list_clear(&list, 0); @@ -360,7 +365,7 @@ static int handle_options(const char ***argv, int *argc, int *envchanged) return (*argv) - orig_argv; } -static int handle_alias(struct strvec *args) +static int handle_alias(struct strvec *args, struct string_list *expanded_aliases) { int envchanged = 0, ret = 0, saved_errno = errno; int count, option_count; @@ -371,6 +376,8 @@ static int handle_alias(struct strvec *args) alias_command = args->v[0]; alias_string = alias_lookup(alias_command); if (alias_string) { + struct string_list_item *seen; + if (args->nr == 2 && !strcmp(args->v[1], "-h")) fprintf_ln(stderr, _("'%s' is aliased to '%s'"), alias_command, alias_string); @@ -418,6 +425,25 @@ static int handle_alias(struct strvec *args) if (!strcmp(alias_command, new_argv[0])) die(_("recursive alias: %s"), alias_command); + string_list_append(expanded_aliases, alias_command); + seen = unsorted_string_list_lookup(expanded_aliases, + new_argv[0]); + + if (seen) { + struct strbuf sb = STRBUF_INIT; + for (size_t i = 0; i < expanded_aliases->nr; i++) { + struct string_list_item *item = &expanded_aliases->items[i]; + + strbuf_addf(&sb, "\n %s", item->string); + if (item == seen) + strbuf_addstr(&sb, " <=="); + else if (i == expanded_aliases->nr - 1) + strbuf_addstr(&sb, " ==>"); + } + die(_("alias loop detected: expansion of '%s' does" + " not terminate:%s"), expanded_aliases->items[0].string, sb.buf); + } + trace_argv_printf(new_argv, "trace: alias expansion: %s =>", alias_command); @@ -590,7 +616,7 @@ static struct cmd_struct commands[] = { { "notes", cmd_notes, RUN_SETUP }, { "pack-objects", cmd_pack_objects, RUN_SETUP }, #ifndef WITH_BREAKING_CHANGES - { "pack-redundant", cmd_pack_redundant, RUN_SETUP | NO_PARSEOPT }, + { "pack-redundant", cmd_pack_redundant, RUN_SETUP | NO_PARSEOPT | DEPRECATED }, #endif { "pack-refs", cmd_pack_refs, RUN_SETUP }, { "patch-id", cmd_patch_id, RUN_SETUP_GENTLY | NO_PARSEOPT }, @@ -647,7 +673,7 @@ static struct cmd_struct commands[] = { { "verify-tag", cmd_verify_tag, RUN_SETUP }, { "version", cmd_version }, #ifndef WITH_BREAKING_CHANGES - { "whatchanged", cmd_whatchanged, RUN_SETUP }, + { "whatchanged", cmd_whatchanged, RUN_SETUP | DEPRECATED }, #endif { "worktree", cmd_worktree, RUN_SETUP }, { "write-tree", cmd_write_tree, RUN_SETUP }, @@ -668,11 +694,16 @@ int is_builtin(const char *s) return !!get_builtin(s); } -static void list_builtins(struct string_list *out, unsigned int exclude_option) +static void list_builtins(struct string_list *out, + unsigned int include_option, + unsigned int exclude_option) { + if (include_option && exclude_option) + BUG("'include_option' and 'exclude_option' are mutually exclusive"); for (size_t i = 0; i < ARRAY_SIZE(commands); i++) { - if (exclude_option && - (commands[i].option & exclude_option)) + if (include_option && !(commands[i].option & include_option)) + continue; + if (exclude_option && (commands[i].option & exclude_option)) continue; string_list_append(out, commands[i].cmd); } @@ -793,14 +824,30 @@ static void execv_dashed_external(const char **argv) exit(128); } +static int is_deprecated_command(const char *cmd) +{ + struct cmd_struct *builtin = get_builtin(cmd); + return builtin && (builtin->option & DEPRECATED); +} + static int run_argv(struct strvec *args) { int done_alias = 0; - struct string_list cmd_list = STRING_LIST_INIT_DUP; - struct string_list_item *seen; + struct string_list expanded_aliases = STRING_LIST_INIT_DUP; while (1) { /* + * Allow deprecated commands to be overridden by aliases. This + * creates a seamless path forward for people who want to keep + * using the name after it is gone, but want to skip the + * deprecation complaint in the meantime. + */ + if (is_deprecated_command(args->v[0]) && + handle_alias(args, &expanded_aliases)) { + done_alias = 1; + continue; + } + /* * If we tried alias and futzed with our environment, * it no longer is safe to invoke builtins directly in * general. We have to spawn them as dashed externals. @@ -849,35 +896,17 @@ static int run_argv(struct strvec *args) /* .. then try the external ones */ execv_dashed_external(args->v); - seen = unsorted_string_list_lookup(&cmd_list, args->v[0]); - if (seen) { - struct strbuf sb = STRBUF_INIT; - for (size_t i = 0; i < cmd_list.nr; i++) { - struct string_list_item *item = &cmd_list.items[i]; - - strbuf_addf(&sb, "\n %s", item->string); - if (item == seen) - strbuf_addstr(&sb, " <=="); - else if (i == cmd_list.nr - 1) - strbuf_addstr(&sb, " ==>"); - } - die(_("alias loop detected: expansion of '%s' does" - " not terminate:%s"), cmd_list.items[0].string, sb.buf); - } - - string_list_append(&cmd_list, args->v[0]); - /* * It could be an alias -- this works around the insanity * of overriding "git log" with "git show" by having * alias.log = show */ - if (!handle_alias(args)) + if (!handle_alias(args, &expanded_aliases)) break; done_alias = 1; } - string_list_clear(&cmd_list, 0); + string_list_clear(&expanded_aliases, 0); return done_alias; } diff --git a/http-push.c b/http-push.c index 91a5465afb..7a9b96a6d0 100644 --- a/http-push.c +++ b/http-push.c @@ -208,7 +208,8 @@ static void curl_setup_http(CURL *curl, const char *url, curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_INFILE, buffer); - curl_easy_setopt(curl, CURLOPT_INFILESIZE, buffer->buf.len); + curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, + cast_size_t_to_curl_off_t(buffer->buf.len)); curl_easy_setopt(curl, CURLOPT_READFUNCTION, fread_buffer); curl_easy_setopt(curl, CURLOPT_SEEKFUNCTION, seek_buffer); curl_easy_setopt(curl, CURLOPT_SEEKDATA, buffer); @@ -1348,6 +1348,14 @@ void http_init(struct remote *remote, const char *url, int proactive_auth) if (curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) die("curl_global_init failed"); +#ifdef GIT_CURL_HAVE_GLOBAL_TRACE + { + const char *comp = getenv("GIT_TRACE_CURL_COMPONENTS"); + if (comp) + curl_global_trace(comp); + } +#endif + if (proactive_auth && http_proactive_auth == PROACTIVE_AUTH_NONE) http_proactive_auth = PROACTIVE_AUTH_IF_CREDENTIALS; @@ -8,6 +8,7 @@ struct packed_git; #include <curl/curl.h> #include <curl/easy.h> +#include "gettext.h" #include "strbuf.h" #include "remote.h" @@ -95,6 +96,15 @@ static inline int missing__target(int code, int result) #define missing_target(a) missing__target((a)->http_code, (a)->curl_result) +static inline curl_off_t cast_size_t_to_curl_off_t(size_t a) +{ + uintmax_t size = a; + if (size > maximum_signed_value_of_type(curl_off_t)) + die(_("number too large to represent as curl_off_t " + "on this platform: %"PRIuMAX), (uintmax_t)a); + return (curl_off_t)a; +} + /* * Normalize curl results to handle CURL_FAILONERROR (or lack thereof). Failing * http codes have their "result" converted to CURLE_HTTP_RETURNED_ERROR, and @@ -272,7 +272,7 @@ static void strbuf_addstr_without_crud(struct strbuf *sb, const char *src) * can still be NULL if the input line only has the name/email part * (e.g. reading from a reflog entry). */ -int split_ident_line(struct ident_split *split, const char *line, int len) +int split_ident_line(struct ident_split *split, const char *line, size_t len) { const char *cp; size_t span; @@ -35,7 +35,7 @@ void reset_ident_date(void); * Signals an success with 0, but time part of the result may be NULL * if the input lacks timestamp and zone */ -int split_ident_line(struct ident_split *, const char *, int); +int split_ident_line(struct ident_split *, const char *, size_t); /* * Given a commit or tag object buffer and the commit or tag headers, replaces diff --git a/imap-send.c b/imap-send.c index 254ec83ab7..1de4dd2053 100644 --- a/imap-send.c +++ b/imap-send.c @@ -1711,7 +1711,7 @@ static int curl_append_msgs_to_imap(struct imap_server_conf *server, lf_to_crlf(&msgbuf.buf); curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, - (curl_off_t)(msgbuf.buf.len-prev_len)); + cast_size_t_to_curl_off_t(msgbuf.buf.len-prev_len)); res = curl_easy_perform(curl); diff --git a/line-log.c b/line-log.c index 07f2154e84..8ab28cf21b 100644 --- a/line-log.c +++ b/line-log.c @@ -201,7 +201,7 @@ static void range_set_difference(struct range_set *out, * b: ------| */ j++; - if (j >= b->nr || end < b->ranges[j].start) { + if (j >= b->nr || end <= b->ranges[j].start) { /* * b exhausted, or * a: ----| @@ -408,7 +408,7 @@ static void diff_ranges_filter_touched(struct diff_ranges *out, assert(out->target.nr == 0); for (i = 0; i < diff->target.nr; i++) { - while (diff->target.ranges[i].start > rs->ranges[j].end) { + while (diff->target.ranges[i].start >= rs->ranges[j].end) { j++; if (j == rs->nr) return; @@ -939,9 +939,18 @@ static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *rang long t_cur = t_start; unsigned int j_last; + /* + * If a diff range touches multiple line ranges, then all + * those line ranges should be shown, so take a step back if + * the current line range is still in the previous diff range + * (even if only partially). + */ + if (j > 0 && diff->target.ranges[j-1].end > t_start) + j--; + while (j < diff->target.nr && diff->target.ranges[j].end < t_start) j++; - if (j == diff->target.nr || diff->target.ranges[j].start > t_end) + if (j == diff->target.nr || diff->target.ranges[j].start >= t_end) continue; /* Scan ahead to determine the last diff that falls in this range */ diff --git a/merge-ort.c b/merge-ort.c index cd0cb4d1f0..29858074f9 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -316,9 +316,14 @@ struct merge_options_internal { * (e.g. "drivers/firmware/raspberrypi.c"). * * store all relevant paths in the repo, both directories and * files (e.g. drivers, drivers/firmware would also be included) - * * these keys serve to intern all the path strings, which allows - * us to do pointer comparison on directory names instead of - * strcmp; we just have to be careful to use the interned strings. + * * these keys serve to intern *all* path strings, which allows us + * to do pointer comparisons on file & directory names instead of + * using strcmp; however, for this pointer-comparison optimization + * to work, any code path that independently computes a path needs + * to check for it existing in this strmap, and if so, point to + * the path in this strmap instead of their computed copy. See + * the "reuse known pointer" comment in + * apply_directory_rename_modifications() for an example. * * The values of paths: * * either a pointer to a merged_info, or a conflict_info struct @@ -2163,7 +2168,7 @@ static int handle_content_merge(struct merge_options *opt, /* * FIXME: If opt->priv->call_depth && !clean, then we really * should not make result->mode match either a->mode or - * b->mode; that causes t6036 "check conflicting mode for + * b->mode; that causes t6416 "check conflicting mode for * regular file" to fail. It would be best to use some other * mode, but we'll confuse all kinds of stuff if we use one * where S_ISREG(result->mode) isn't true, and if we use @@ -2313,14 +2318,20 @@ static char *apply_dir_rename(struct strmap_entry *rename_info, return strbuf_detach(&new_path, NULL); } -static int path_in_way(struct strmap *paths, const char *path, unsigned side_mask) +static int path_in_way(struct strmap *paths, + const char *path, + unsigned side_mask, + struct diff_filepair *p) { struct merged_info *mi = strmap_get(paths, path); struct conflict_info *ci; if (!mi) return 0; INITIALIZE_CI(ci, mi); - return mi->clean || (side_mask & (ci->filemask | ci->dirmask)); + return mi->clean || (side_mask & (ci->filemask | ci->dirmask)) + /* See testcases 12[npq] of t6423 for this next condition */ + || ((ci->filemask & 0x01) && + strcmp(p->one->path, path)); } /* @@ -2332,6 +2343,7 @@ static int path_in_way(struct strmap *paths, const char *path, unsigned side_mas static char *handle_path_level_conflicts(struct merge_options *opt, const char *path, unsigned side_index, + struct diff_filepair *p, struct strmap_entry *rename_info, struct strmap *collisions) { @@ -2366,7 +2378,7 @@ static char *handle_path_level_conflicts(struct merge_options *opt, */ if (c_info->reported_already) { clean = 0; - } else if (path_in_way(&opt->priv->paths, new_path, 1 << side_index)) { + } else if (path_in_way(&opt->priv->paths, new_path, 1 << side_index, p)) { c_info->reported_already = 1; strbuf_add_separated_string_list(&collision_paths, ", ", &c_info->source_files); @@ -2520,7 +2532,7 @@ static void compute_collisions(struct strmap *collisions, * happening, and fall back to no-directory-rename detection * behavior for those paths. * - * See testcases 9e and all of section 5 from t6043 for examples. + * See testcases 9e and all of section 5 from t6423 for examples. */ for (i = 0; i < pairs->nr; ++i) { struct strmap_entry *rename_info; @@ -2573,6 +2585,7 @@ static void free_collisions(struct strmap *collisions) static char *check_for_directory_rename(struct merge_options *opt, const char *path, unsigned side_index, + struct diff_filepair *p, struct strmap *dir_renames, struct strmap *dir_rename_exclusions, struct strmap *collisions, @@ -2580,7 +2593,6 @@ static char *check_for_directory_rename(struct merge_options *opt, { char *new_path; struct strmap_entry *rename_info; - struct strmap_entry *otherinfo; const char *new_dir; int other_side = 3 - side_index; @@ -2615,14 +2627,13 @@ static char *check_for_directory_rename(struct merge_options *opt, * to not let Side1 do the rename to dumbdir, since we know that is * the source of one of our directory renames. * - * That's why otherinfo and dir_rename_exclusions is here. + * That's why dir_rename_exclusions is here. * * As it turns out, this also prevents N-way transient rename - * confusion; See testcases 9c and 9d of t6043. + * confusion; See testcases 9c and 9d of t6423. */ new_dir = rename_info->value; /* old_dir = rename_info->key; */ - otherinfo = strmap_get_entry(dir_rename_exclusions, new_dir); - if (otherinfo) { + if (strmap_contains(dir_rename_exclusions, new_dir)) { path_msg(opt, INFO_DIR_RENAME_SKIPPED_DUE_TO_RERENAME, 1, rename_info->key, path, new_dir, NULL, _("WARNING: Avoiding applying %s -> %s rename " @@ -2631,7 +2642,7 @@ static char *check_for_directory_rename(struct merge_options *opt, return NULL; } - new_path = handle_path_level_conflicts(opt, path, side_index, + new_path = handle_path_level_conflicts(opt, path, side_index, p, rename_info, &collisions[side_index]); *clean_merge &= (new_path != NULL); @@ -2876,6 +2887,20 @@ static int process_renames(struct merge_options *opt, } /* + * Directory renames can result in rename-to-self; the code + * below assumes we have A->B with different A & B, and tries + * to move all entries to path B. If A & B are the same path, + * the logic can get confused, so skip further processing when + * A & B are already the same path. + * + * As a reminder, we can avoid strcmp here because all paths + * are interned in opt->priv->paths; see the comment above + * "paths" in struct merge_options_internal. + */ + if (oldpath == newpath) + continue; + + /* * If pair->one->path isn't in opt->priv->paths, that means * that either directory rename detection removed that * path, or a parent directory of oldpath was resolved and @@ -3419,7 +3444,7 @@ static int collect_renames(struct merge_options *opt, } new_path = check_for_directory_rename(opt, p->two->path, - side_index, + side_index, p, dir_renames_for_side, rename_exclusions, collisions, diff --git a/midx-write.c b/midx-write.c index a0aceab5e0..72189f74bb 100644 --- a/midx-write.c +++ b/midx-write.c @@ -1,5 +1,3 @@ -#define DISABLE_SIGN_COMPARE_WARNINGS - #include "git-compat-util.h" #include "abspath.h" #include "config.h" @@ -24,6 +22,7 @@ #define BITMAP_POS_UNKNOWN (~((uint32_t)0)) #define MIDX_CHUNK_FANOUT_SIZE (sizeof(uint32_t) * 256) #define MIDX_CHUNK_LARGE_OFFSET_WIDTH (sizeof(uint64_t)) +#define NO_PREFERRED_PACK (~((uint32_t)0)) extern int midx_checksum_valid(struct multi_pack_index *m); extern void clear_midx_files_ext(const char *object_dir, const char *ext, @@ -104,7 +103,7 @@ struct write_midx_context { unsigned large_offsets_needed:1; uint32_t num_large_offsets; - int preferred_pack_idx; + uint32_t preferred_pack_idx; int incremental; uint32_t num_multi_pack_indexes_before; @@ -260,7 +259,7 @@ static void midx_fanout_sort(struct midx_fanout *fanout) static void midx_fanout_add_midx_fanout(struct midx_fanout *fanout, struct multi_pack_index *m, uint32_t cur_fanout, - int preferred_pack) + uint32_t preferred_pack) { uint32_t start = m->num_objects_in_base, end; uint32_t cur_object; @@ -274,7 +273,7 @@ static void midx_fanout_add_midx_fanout(struct midx_fanout *fanout, end = m->num_objects_in_base + ntohl(m->chunk_oid_fanout[cur_fanout]); for (cur_object = start; cur_object < end; cur_object++) { - if ((preferred_pack > -1) && + if ((preferred_pack != NO_PREFERRED_PACK) && (preferred_pack == nth_midxed_pack_int_id(m, cur_object))) { /* * Objects from preferred packs are added @@ -364,7 +363,8 @@ static void compute_sorted_entries(struct write_midx_context *ctx, preferred, cur_fanout); } - if (-1 < ctx->preferred_pack_idx && ctx->preferred_pack_idx < start_pack) + if (ctx->preferred_pack_idx != NO_PREFERRED_PACK && + ctx->preferred_pack_idx < start_pack) midx_fanout_add_pack_fanout(&fanout, ctx->info, ctx->preferred_pack_idx, 1, cur_fanout); @@ -843,7 +843,7 @@ static int write_midx_bitmap(struct write_midx_context *ctx, uint32_t commits_nr, unsigned flags) { - int ret, i; + int ret; uint16_t options = 0; struct bitmap_writer writer; struct pack_idx_entry **index; @@ -871,7 +871,7 @@ static int write_midx_bitmap(struct write_midx_context *ctx, * this order). */ ALLOC_ARRAY(index, pdata->nr_objects); - for (i = 0; i < pdata->nr_objects; i++) + for (uint32_t i = 0; i < pdata->nr_objects; i++) index[i] = &pdata->objects[i].idx; bitmap_writer_init(&writer, ctx->repo, pdata, @@ -892,7 +892,7 @@ static int write_midx_bitmap(struct write_midx_context *ctx, * happens between bitmap_writer_build_type_index() and * bitmap_writer_finish(). */ - for (i = 0; i < pdata->nr_objects; i++) + for (uint32_t i = 0; i < pdata->nr_objects; i++) index[ctx->pack_order[i]] = &pdata->objects[i].idx; bitmap_writer_select_commits(&writer, commits, commits_nr); @@ -920,8 +920,7 @@ static struct multi_pack_index *lookup_multi_pack_index(struct repository *r, return get_multi_pack_index(source); } -static int fill_packs_from_midx(struct write_midx_context *ctx, - const char *preferred_pack_name, uint32_t flags) +static int fill_packs_from_midx(struct write_midx_context *ctx) { struct multi_pack_index *m; @@ -929,30 +928,11 @@ static int fill_packs_from_midx(struct write_midx_context *ctx, uint32_t i; for (i = 0; i < m->num_packs; i++) { - ALLOC_GROW(ctx->info, ctx->nr + 1, ctx->alloc); - - /* - * If generating a reverse index, need to have - * packed_git's loaded to compare their - * mtimes and object count. - * - * If a preferred pack is specified, need to - * have packed_git's loaded to ensure the chosen - * preferred pack has a non-zero object count. - */ - if (flags & MIDX_WRITE_REV_INDEX || - preferred_pack_name) { - if (prepare_midx_pack(ctx->repo, m, - m->num_packs_in_base + i)) { - error(_("could not load pack")); - return 1; - } - - if (open_pack_index(m->packs[i])) - die(_("could not open index for %s"), - m->packs[i]->pack_name); - } + if (prepare_midx_pack(ctx->repo, m, + m->num_packs_in_base + i)) + return error(_("could not load pack")); + ALLOC_GROW(ctx->info, ctx->nr + 1, ctx->alloc); fill_pack_info(&ctx->info[ctx->nr++], m->packs[i], m->pack_names[i], m->num_packs_in_base + i); @@ -1056,15 +1036,17 @@ static int write_midx_internal(struct repository *r, const char *object_dir, { struct strbuf midx_name = STRBUF_INIT; unsigned char midx_hash[GIT_MAX_RAWSZ]; - uint32_t i, start_pack; + uint32_t start_pack; struct hashfile *f = NULL; struct lock_file lk; struct tempfile *incr; - struct write_midx_context ctx = { 0 }; + struct write_midx_context ctx = { + .preferred_pack_idx = NO_PREFERRED_PACK, + }; int bitmapped_packs_concat_len = 0; int pack_name_concat_len = 0; int dropped_packs = 0; - int result = 0; + int result = -1; const char **keep_hashes = NULL; struct chunkfile *cf; @@ -1117,14 +1099,12 @@ static int write_midx_internal(struct repository *r, const char *object_dir, error(_("could not load reverse index for MIDX %s"), hash_to_hex_algop(get_midx_checksum(m), m->repo->hash_algo)); - result = 1; goto cleanup; } ctx.num_multi_pack_indexes_before++; m = m->base_midx; } - } else if (ctx.m && fill_packs_from_midx(&ctx, preferred_pack_name, - flags) < 0) { + } else if (ctx.m && fill_packs_from_midx(&ctx)) { goto cleanup; } @@ -1160,17 +1140,21 @@ static int write_midx_internal(struct repository *r, const char *object_dir, */ if (!want_bitmap) clear_midx_files_ext(object_dir, "bitmap", NULL); + + result = 0; goto cleanup; } } - if (ctx.incremental && !ctx.nr) + if (ctx.incremental && !ctx.nr) { + result = 0; goto cleanup; /* nothing to do */ + } if (preferred_pack_name) { - ctx.preferred_pack_idx = -1; + ctx.preferred_pack_idx = NO_PREFERRED_PACK; - for (i = 0; i < ctx.nr; i++) { + for (size_t i = 0; i < ctx.nr; i++) { if (!cmp_idx_or_pack_name(preferred_pack_name, ctx.info[i].pack_name)) { ctx.preferred_pack_idx = i; @@ -1178,14 +1162,21 @@ static int write_midx_internal(struct repository *r, const char *object_dir, } } - if (ctx.preferred_pack_idx == -1) + if (ctx.preferred_pack_idx == NO_PREFERRED_PACK) warning(_("unknown preferred pack: '%s'"), preferred_pack_name); } else if (ctx.nr && (flags & (MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP))) { - struct packed_git *oldest = ctx.info[ctx.preferred_pack_idx].p; + struct packed_git *oldest = ctx.info[0].p; ctx.preferred_pack_idx = 0; + /* + * Attempt opening the pack index to populate num_objects. + * Ignore failiures as they can be expected and are not + * fatal during this selection time. + */ + open_pack_index(oldest); + if (packs_to_drop && packs_to_drop->nr) BUG("cannot write a MIDX bitmap during expiration"); @@ -1195,11 +1186,12 @@ static int write_midx_internal(struct repository *r, const char *object_dir, * pack-order has all of its objects selected from that pack * (and not another pack containing a duplicate) */ - for (i = 1; i < ctx.nr; i++) { + for (size_t i = 1; i < ctx.nr; i++) { struct packed_git *p = ctx.info[i].p; if (!oldest->num_objects || p->mtime < oldest->mtime) { oldest = p; + open_pack_index(oldest); ctx.preferred_pack_idx = i; } } @@ -1211,22 +1203,26 @@ static int write_midx_internal(struct repository *r, const char *object_dir, * objects to resolve, so the preferred value doesn't * matter. */ - ctx.preferred_pack_idx = -1; + ctx.preferred_pack_idx = NO_PREFERRED_PACK; } } else { /* * otherwise don't mark any pack as preferred to avoid * interfering with expiration logic below */ - ctx.preferred_pack_idx = -1; + ctx.preferred_pack_idx = NO_PREFERRED_PACK; } - if (ctx.preferred_pack_idx > -1) { + if (ctx.preferred_pack_idx != NO_PREFERRED_PACK) { struct packed_git *preferred = ctx.info[ctx.preferred_pack_idx].p; + + if (open_pack_index(preferred)) + die(_("failed to open preferred pack %s"), + ctx.info[ctx.preferred_pack_idx].pack_name); + if (!preferred->num_objects) { error(_("cannot select preferred pack %s with no objects"), preferred->pack_name); - result = 1; goto cleanup; } } @@ -1234,7 +1230,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir, compute_sorted_entries(&ctx, start_pack); ctx.large_offsets_needed = 0; - for (i = 0; i < ctx.entries_nr; i++) { + for (size_t i = 0; i < ctx.entries_nr; i++) { if (ctx.entries[i].offset > 0x7fffffff) ctx.num_large_offsets++; if (ctx.entries[i].offset > 0xffffffff) @@ -1244,10 +1240,10 @@ static int write_midx_internal(struct repository *r, const char *object_dir, QSORT(ctx.info, ctx.nr, pack_info_compare); if (packs_to_drop && packs_to_drop->nr) { - int drop_index = 0; + size_t drop_index = 0; int missing_drops = 0; - for (i = 0; i < ctx.nr && drop_index < packs_to_drop->nr; i++) { + for (size_t i = 0; i < ctx.nr && drop_index < packs_to_drop->nr; i++) { int cmp = strcmp(ctx.info[i].pack_name, packs_to_drop->items[drop_index].string); @@ -1265,10 +1261,8 @@ static int write_midx_internal(struct repository *r, const char *object_dir, } } - if (missing_drops) { - result = 1; + if (missing_drops) goto cleanup; - } } /* @@ -1278,7 +1272,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir, * pack_perm[old_id] = new_id */ ALLOC_ARRAY(ctx.pack_perm, ctx.nr); - for (i = 0; i < ctx.nr; i++) { + for (size_t i = 0; i < ctx.nr; i++) { if (ctx.info[i].expired) { dropped_packs++; ctx.pack_perm[ctx.info[i].orig_pack_int_id] = PACK_EXPIRED; @@ -1287,7 +1281,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir, } } - for (i = 0; i < ctx.nr; i++) { + for (size_t i = 0; i < ctx.nr; i++) { if (ctx.info[i].expired) continue; pack_name_concat_len += strlen(ctx.info[i].pack_name) + 1; @@ -1314,7 +1308,6 @@ static int write_midx_internal(struct repository *r, const char *object_dir, if (ctx.nr - dropped_packs == 0) { error(_("no pack files to index.")); - result = 1; goto cleanup; } @@ -1334,13 +1327,13 @@ static int write_midx_internal(struct repository *r, const char *object_dir, incr = mks_tempfile_m(midx_name.buf, 0444); if (!incr) { error(_("unable to create temporary MIDX layer")); - return -1; + goto cleanup; } if (adjust_shared_perm(r, get_tempfile_path(incr))) { error(_("unable to adjust shared permissions for '%s'"), get_tempfile_path(incr)); - return -1; + goto cleanup; } f = hashfd(r->hash_algo, get_tempfile_fd(incr), @@ -1417,7 +1410,6 @@ static int write_midx_internal(struct repository *r, const char *object_dir, midx_hash, &pdata, commits, commits_nr, flags) < 0) { error(_("could not write multi-pack bitmap")); - result = 1; clear_packing_data(&pdata); free(commits); goto cleanup; @@ -1431,6 +1423,9 @@ static int write_midx_internal(struct repository *r, const char *object_dir, * have been freed in the previous if block. */ + if (ctx.num_multi_pack_indexes_before == UINT32_MAX) + die(_("too many multi-pack-indexes")); + CALLOC_ARRAY(keep_hashes, ctx.num_multi_pack_indexes_before + 1); if (ctx.incremental) { @@ -1440,18 +1435,18 @@ static int write_midx_internal(struct repository *r, const char *object_dir, if (!chainf) { error_errno(_("unable to open multi-pack-index chain file")); - return -1; + goto cleanup; } if (link_midx_to_chain(ctx.base_midx) < 0) - return -1; + goto cleanup; get_split_midx_filename_ext(r->hash_algo, &final_midx_name, object_dir, midx_hash, MIDX_EXT_MIDX); if (rename_tempfile(&incr, final_midx_name.buf) < 0) { error_errno(_("unable to rename new multi-pack-index layer")); - return -1; + goto cleanup; } strbuf_release(&final_midx_name); @@ -1459,7 +1454,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir, keep_hashes[ctx.num_multi_pack_indexes_before] = xstrdup(hash_to_hex_algop(midx_hash, r->hash_algo)); - for (i = 0; i < ctx.num_multi_pack_indexes_before; i++) { + for (uint32_t i = 0; i < ctx.num_multi_pack_indexes_before; i++) { uint32_t j = ctx.num_multi_pack_indexes_before - i - 1; keep_hashes[j] = xstrdup(hash_to_hex_algop(get_midx_checksum(m), @@ -1467,7 +1462,7 @@ static int write_midx_internal(struct repository *r, const char *object_dir, m = m->base_midx; } - for (i = 0; i < ctx.num_multi_pack_indexes_before + 1; i++) + for (uint32_t i = 0; i <= ctx.num_multi_pack_indexes_before; i++) fprintf(get_lock_file_fp(&lk), "%s\n", keep_hashes[i]); } else { keep_hashes[ctx.num_multi_pack_indexes_before] = @@ -1483,9 +1478,10 @@ static int write_midx_internal(struct repository *r, const char *object_dir, clear_midx_files(r, object_dir, keep_hashes, ctx.num_multi_pack_indexes_before + 1, ctx.incremental); + result = 0; cleanup: - for (i = 0; i < ctx.nr; i++) { + for (size_t i = 0; i < ctx.nr; i++) { if (ctx.info[i].p) { close_pack(ctx.info[i].p); free(ctx.info[i].p); @@ -1498,7 +1494,7 @@ cleanup: free(ctx.pack_perm); free(ctx.pack_order); if (keep_hashes) { - for (i = 0; i < ctx.num_multi_pack_indexes_before + 1; i++) + for (uint32_t i = 0; i <= ctx.num_multi_pack_indexes_before; i++) free((char *)keep_hashes[i]); free(keep_hashes); } diff --git a/object-name.c b/object-name.c index 11aa0e6afc..ffd946116d 100644 --- a/object-name.c +++ b/object-name.c @@ -696,10 +696,9 @@ static inline char get_hex_char_from_oid(const struct object_id *oid, return hex[oid->hash[pos >> 1] & 0xf]; } -static int extend_abbrev_len(const struct object_id *oid, void *cb_data) +static int extend_abbrev_len(const struct object_id *oid, + struct min_abbrev_data *mad) { - struct min_abbrev_data *mad = cb_data; - unsigned int i = mad->init_len; while (mad->hex[i] && mad->hex[i] == get_hex_char_from_oid(oid, i)) i++; @@ -475,37 +475,4 @@ static inline int odb_write_object(struct object_database *odb, return odb_write_object_ext(odb, buf, len, type, oid, NULL, 0); } -/* Compatibility wrappers, to be removed once Git 2.51 has been released. */ -#include "repository.h" - -static inline int oid_object_info_extended(struct repository *r, - const struct object_id *oid, - struct object_info *oi, - unsigned flags) -{ - return odb_read_object_info_extended(r->objects, oid, oi, flags); -} - -static inline int oid_object_info(struct repository *r, - const struct object_id *oid, - unsigned long *sizep) -{ - return odb_read_object_info(r->objects, oid, sizep); -} - -static inline void *repo_read_object_file(struct repository *r, - const struct object_id *oid, - enum object_type *type, - unsigned long *size) -{ - return odb_read_object(r->objects, oid, type, size); -} - -static inline int has_object(struct repository *r, - const struct object_id *oid, - unsigned flags) -{ - return odb_has_object(r->objects, oid, flags); -} - #endif /* ODB_H */ diff --git a/pack-objects.c b/pack-objects.c index a9d9855063..7b86ae8998 100644 --- a/pack-objects.c +++ b/pack-objects.c @@ -4,6 +4,7 @@ #include "pack-objects.h" #include "packfile.h" #include "parse.h" +#include "repository.h" static uint32_t locate_object_entry_hash(struct packing_data *pdata, const struct object_id *oid, diff --git a/path-walk.c b/path-walk.c index 2d4ddbadd5..f1ceed99e9 100644 --- a/path-walk.c +++ b/path-walk.c @@ -105,6 +105,24 @@ static void push_to_stack(struct path_walk_context *ctx, prio_queue_put(&ctx->path_stack, xstrdup(path)); } +static void add_path_to_list(struct path_walk_context *ctx, + const char *path, + enum object_type type, + struct object_id *oid, + int interesting) +{ + struct type_and_oid_list *list = strmap_get(&ctx->paths_to_lists, path); + + if (!list) { + CALLOC_ARRAY(list, 1); + list->type = type; + strmap_put(&ctx->paths_to_lists, path, list); + } + + list->maybe_interesting |= interesting; + oid_array_append(&list->oids, oid); +} + static int add_tree_entries(struct path_walk_context *ctx, const char *base_path, struct object_id *oid) @@ -129,7 +147,6 @@ static int add_tree_entries(struct path_walk_context *ctx, init_tree_desc(&desc, &tree->object.oid, tree->buffer, tree->size); while (tree_entry(&desc, &entry)) { - struct type_and_oid_list *list; struct object *o; /* Not actually true, but we will ignore submodules later. */ enum object_type type = S_ISDIR(entry.mode) ? OBJ_TREE : OBJ_BLOB; @@ -190,17 +207,10 @@ static int add_tree_entries(struct path_walk_context *ctx, continue; } - if (!(list = strmap_get(&ctx->paths_to_lists, path.buf))) { - CALLOC_ARRAY(list, 1); - list->type = type; - strmap_put(&ctx->paths_to_lists, path.buf, list); - } - push_to_stack(ctx, path.buf); - - if (!(o->flags & UNINTERESTING)) - list->maybe_interesting = 1; + add_path_to_list(ctx, path.buf, type, &entry.oid, + !(o->flags & UNINTERESTING)); - oid_array_append(&list->oids, &entry.oid); + push_to_stack(ctx, path.buf); } free_tree_buffer(tree); @@ -377,15 +387,9 @@ static int setup_pending_objects(struct path_walk_info *info, if (!info->trees) continue; if (pending->path) { - struct type_and_oid_list *list; char *path = *pending->path ? xstrfmt("%s/", pending->path) : xstrdup(""); - if (!(list = strmap_get(&ctx->paths_to_lists, path))) { - CALLOC_ARRAY(list, 1); - list->type = OBJ_TREE; - strmap_put(&ctx->paths_to_lists, path, list); - } - oid_array_append(&list->oids, &obj->oid); + add_path_to_list(ctx, path, OBJ_TREE, &obj->oid, 1); free(path); } else { /* assume a root tree, such as a lightweight tag. */ @@ -396,19 +400,10 @@ static int setup_pending_objects(struct path_walk_info *info, case OBJ_BLOB: if (!info->blobs) continue; - if (pending->path) { - struct type_and_oid_list *list; - char *path = pending->path; - if (!(list = strmap_get(&ctx->paths_to_lists, path))) { - CALLOC_ARRAY(list, 1); - list->type = OBJ_BLOB; - strmap_put(&ctx->paths_to_lists, path, list); - } - oid_array_append(&list->oids, &obj->oid); - } else { - /* assume a root tree, such as a lightweight tag. */ + if (pending->path) + add_path_to_list(ctx, pending->path, OBJ_BLOB, &obj->oid, 1); + else oid_array_append(&tagged_blobs->oids, &obj->oid); - } break; case OBJ_COMMIT: diff --git a/progress.c b/progress.c index 8d5ae70f3a..8315bdc3d4 100644 --- a/progress.c +++ b/progress.c @@ -114,16 +114,19 @@ static void display(struct progress *progress, uint64_t n, const char *done) const char *tp; struct strbuf *counters_sb = &progress->counters_sb; int show_update = 0; + int update = !!progress_update; int last_count_len = counters_sb->len; - if (progress->delay && (!progress_update || --progress->delay)) + progress_update = 0; + + if (progress->delay && (!update || --progress->delay)) return; progress->last_value = n; tp = (progress->throughput) ? progress->throughput->display.buf : ""; if (progress->total) { unsigned percent = n * 100 / progress->total; - if (percent != progress->last_percent || progress_update) { + if (percent != progress->last_percent || update) { progress->last_percent = percent; strbuf_reset(counters_sb); @@ -133,7 +136,7 @@ static void display(struct progress *progress, uint64_t n, const char *done) tp); show_update = 1; } - } else if (progress_update) { + } else if (update) { strbuf_reset(counters_sb); strbuf_addf(counters_sb, "%"PRIuMAX"%s", (uintmax_t)n, tp); show_update = 1; @@ -166,7 +169,6 @@ static void display(struct progress *progress, uint64_t n, const char *done) } fflush(stderr); } - progress_update = 0; } } @@ -281,7 +283,7 @@ static int get_default_delay(void) static int delay_in_secs = -1; if (delay_in_secs < 0) - delay_in_secs = git_env_ulong("GIT_PROGRESS_DELAY", 2); + delay_in_secs = git_env_ulong("GIT_PROGRESS_DELAY", 1); return delay_in_secs; } @@ -1223,7 +1223,7 @@ int ref_transaction_maybe_set_rejected(struct ref_transaction *transaction, return 0; if (!transaction->rejections) - BUG("transaction not inititalized with failure support"); + BUG("transaction not initialized with failure support"); /* * Don't accept generic errors, since these errors are not user @@ -1232,6 +1232,13 @@ int ref_transaction_maybe_set_rejected(struct ref_transaction *transaction, if (err == REF_TRANSACTION_ERROR_GENERIC) return 0; + /* + * Rejected refnames shouldn't be considered in the availability + * checks, so remove them from the list. + */ + string_list_remove(&transaction->refnames, + transaction->updates[update_idx]->refname, 0); + transaction->updates[update_idx]->rejection_err = err; ALLOC_GROW(transaction->rejections->update_indices, transaction->rejections->nr + 1, @@ -1362,27 +1369,22 @@ int ref_transaction_update(struct ref_transaction *transaction, return 0; } -/* - * Similar to`ref_transaction_update`, but this function is only for adding - * a reflog update. Supports providing custom committer information. The index - * field can be utiltized to order updates as desired. When not used, the - * updates default to being ordered by refname. - */ -static int ref_transaction_update_reflog(struct ref_transaction *transaction, - const char *refname, - const struct object_id *new_oid, - const struct object_id *old_oid, - const char *committer_info, - unsigned int flags, - const char *msg, - uint64_t index, - struct strbuf *err) +int ref_transaction_update_reflog(struct ref_transaction *transaction, + const char *refname, + const struct object_id *new_oid, + const struct object_id *old_oid, + const char *committer_info, + const char *msg, + uint64_t index, + struct strbuf *err) { struct ref_update *update; + unsigned int flags; assert(err); - flags |= REF_LOG_ONLY | REF_FORCE_CREATE_REFLOG | REF_NO_DEREF; + flags = REF_HAVE_OLD | REF_HAVE_NEW | REF_LOG_ONLY | REF_FORCE_CREATE_REFLOG | REF_NO_DEREF | + REF_LOG_USE_PROVIDED_OIDS; if (!transaction_refname_valid(refname, new_oid, flags, err)) return -1; @@ -1390,11 +1392,6 @@ static int ref_transaction_update_reflog(struct ref_transaction *transaction, update = ref_transaction_add_update(transaction, refname, flags, new_oid, old_oid, NULL, NULL, committer_info, msg); - /* - * While we do set the old_oid value, we unset the flag to skip - * old_oid verification which only makes sense for refs. - */ - update->flags &= ~REF_HAVE_OLD; update->index = index; /* @@ -2951,7 +2948,7 @@ struct migration_data { struct ref_store *old_refs; struct ref_transaction *transaction; struct strbuf *errbuf; - struct strbuf sb; + struct strbuf sb, name, mail; }; static int migrate_one_ref(const char *refname, const char *referent UNUSED, const struct object_id *oid, @@ -2990,7 +2987,7 @@ struct reflog_migration_data { struct ref_store *old_refs; struct ref_transaction *transaction; struct strbuf *errbuf; - struct strbuf *sb; + struct strbuf *sb, *name, *mail; }; static int migrate_one_reflog_entry(struct object_id *old_oid, @@ -3000,18 +2997,25 @@ static int migrate_one_reflog_entry(struct object_id *old_oid, const char *msg, void *cb_data) { struct reflog_migration_data *data = cb_data; + struct ident_split ident; const char *date; int ret; + if (split_ident_line(&ident, committer, strlen(committer)) < 0) + return -1; + + strbuf_reset(data->name); + strbuf_add(data->name, ident.name_begin, ident.name_end - ident.name_begin); + strbuf_reset(data->mail); + strbuf_add(data->mail, ident.mail_begin, ident.mail_end - ident.mail_begin); + date = show_date(timestamp, tz, DATE_MODE(NORMAL)); strbuf_reset(data->sb); - /* committer contains name and email */ - strbuf_addstr(data->sb, fmt_ident("", committer, WANT_BLANK_IDENT, date, 0)); + strbuf_addstr(data->sb, fmt_ident(data->name->buf, data->mail->buf, WANT_BLANK_IDENT, date, 0)); ret = ref_transaction_update_reflog(data->transaction, data->refname, new_oid, old_oid, data->sb->buf, - REF_HAVE_NEW | REF_HAVE_OLD, msg, - data->index++, data->errbuf); + msg, data->index++, data->errbuf); return ret; } @@ -3024,6 +3028,8 @@ static int migrate_one_reflog(const char *refname, void *cb_data) .transaction = migration_data->transaction, .errbuf = migration_data->errbuf, .sb = &migration_data->sb, + .name = &migration_data->name, + .mail = &migration_data->mail, }; return refs_for_each_reflog_ent(migration_data->old_refs, refname, @@ -3122,6 +3128,8 @@ int repo_migrate_ref_storage_format(struct repository *repo, struct strbuf new_gitdir = STRBUF_INIT; struct migration_data data = { .sb = STRBUF_INIT, + .name = STRBUF_INIT, + .mail = STRBUF_INIT, }; int did_migrate_refs = 0; int ret; @@ -3297,11 +3305,16 @@ done: ref_transaction_free(transaction); strbuf_release(&new_gitdir); strbuf_release(&data.sb); + strbuf_release(&data.name); + strbuf_release(&data.mail); return ret; } int ref_update_expects_existing_old_ref(struct ref_update *update) { + if (update->flags & REF_LOG_ONLY) + return 0; + return (update->flags & REF_HAVE_OLD) && (!is_null_oid(&update->old_oid) || update->old_target); } @@ -3321,6 +3334,8 @@ const char *ref_transaction_error_msg(enum ref_transaction_error err) return "invalid new value provided"; case REF_TRANSACTION_ERROR_EXPECTED_SYMREF: return "expected symref but found regular ref"; + case REF_TRANSACTION_ERROR_CASE_CONFLICT: + return "reference conflict due to case-insensitive filesystem"; default: return "unknown failure"; } @@ -31,6 +31,8 @@ enum ref_transaction_error { REF_TRANSACTION_ERROR_INVALID_NEW_VALUE = -6, /* Expected ref to be symref, but is a regular ref */ REF_TRANSACTION_ERROR_EXPECTED_SYMREF = -7, + /* Cannot create ref due to case-insensitive filesystem */ + REF_TRANSACTION_ERROR_CASE_CONFLICT = -8, }; /* @@ -760,12 +762,19 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs, #define REF_SKIP_CREATE_REFLOG (1 << 12) /* + * When writing a REF_LOG_ONLY record, use the old and new object IDs provided + * in the update instead of resolving the old object ID. The caller must also + * set both REF_HAVE_OLD and REF_HAVE_NEW. + */ +#define REF_LOG_USE_PROVIDED_OIDS (1 << 13) + +/* * Bitmask of all of the flags that are allowed to be passed in to * ref_transaction_update() and friends: */ #define REF_TRANSACTION_UPDATE_ALLOWED_FLAGS \ (REF_NO_DEREF | REF_FORCE_CREATE_REFLOG | REF_SKIP_OID_VERIFICATION | \ - REF_SKIP_REFNAME_VERIFICATION | REF_SKIP_CREATE_REFLOG) + REF_SKIP_REFNAME_VERIFICATION | REF_SKIP_CREATE_REFLOG | REF_LOG_USE_PROVIDED_OIDS) /* * Add a reference update to transaction. `new_oid` is the value that @@ -795,6 +804,21 @@ int ref_transaction_update(struct ref_transaction *transaction, struct strbuf *err); /* + * Similar to `ref_transaction_update`, but this function is only for adding + * a reflog update. Supports providing custom committer information. The index + * field can be utiltized to order updates as desired. When set to zero, the + * updates default to being ordered by refname. + */ +int ref_transaction_update_reflog(struct ref_transaction *transaction, + const char *refname, + const struct object_id *new_oid, + const struct object_id *old_oid, + const char *committer_info, + const char *msg, + uint64_t index, + struct strbuf *err); + +/* * Add a reference creation to transaction. new_oid is the value that * the reference should have after the update; it must not be * null_oid. It is verified that the reference does not exist diff --git a/refs/files-backend.c b/refs/files-backend.c index 814decf323..b2de84fa69 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -68,6 +68,12 @@ */ #define REF_DELETED_RMDIR (1 << 9) +/* + * Used to indicate that the reflog-only update has been created via + * `split_head_update()`. + */ +#define REF_LOG_VIA_SPLIT (1 << 14) + struct ref_lock { char *ref_name; struct lock_file lk; @@ -648,6 +654,26 @@ static void unlock_ref(struct ref_lock *lock) } /* + * Check if the transaction has another update with a case-insensitive refname + * match. + * + * If the update is part of the transaction, we only check up to that index. + * Further updates are expected to call this function to match previous indices. + */ +static bool transaction_has_case_conflicting_update(struct ref_transaction *transaction, + struct ref_update *update) +{ + for (size_t i = 0; i < transaction->nr; i++) { + if (transaction->updates[i] == update) + break; + + if (!strcasecmp(transaction->updates[i]->refname, update->refname)) + return true; + } + return false; +} + +/* * Lock refname, without following symrefs, and set *lock_p to point * at a newly-allocated lock object. Fill in lock->old_oid, referent, * and type similarly to read_raw_ref(). @@ -677,16 +703,17 @@ static void unlock_ref(struct ref_lock *lock) * - Generate informative error messages in the case of failure */ static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs, - struct ref_update *update, + struct ref_transaction *transaction, size_t update_idx, int mustexist, struct string_list *refnames_to_check, - const struct string_list *extras, struct ref_lock **lock_p, struct strbuf *referent, struct strbuf *err) { enum ref_transaction_error ret = REF_TRANSACTION_ERROR_GENERIC; + struct ref_update *update = transaction->updates[update_idx]; + const struct string_list *extras = &transaction->refnames; const char *refname = update->refname; unsigned int *type = &update->type; struct ref_lock *lock; @@ -776,6 +803,24 @@ retry: goto retry; } else { unable_to_lock_message(ref_file.buf, myerr, err); + if (myerr == EEXIST) { + if (ignore_case && + transaction_has_case_conflicting_update(transaction, update)) { + /* + * In case-insensitive filesystems, ensure that conflicts within a + * given transaction are handled. Pre-existing refs on a + * case-insensitive system will be overridden without any issue. + */ + ret = REF_TRANSACTION_ERROR_CASE_CONFLICT; + } else { + /* + * Pre-existing case-conflicting reference locks should also be + * specially categorized to avoid failing all batched updates. + */ + ret = REF_TRANSACTION_ERROR_CREATE_EXISTS; + } + } + goto error_return; } } @@ -831,6 +876,7 @@ retry: goto error_return; } else if (remove_dir_recursively(&ref_file, REMOVE_DIR_EMPTY_ONLY)) { + ret = REF_TRANSACTION_ERROR_NAME_CONFLICT; if (refs_verify_refname_available( &refs->base, refname, extras, NULL, 0, err)) { @@ -838,14 +884,14 @@ retry: * The error message set by * verify_refname_available() is OK. */ - ret = REF_TRANSACTION_ERROR_NAME_CONFLICT; goto error_return; } else { /* - * We can't delete the directory, - * but we also don't know of any - * references that it should - * contain. + * Directory conflicts can occur if there + * is an existing lock file in the directory + * or if the filesystem is case-insensitive + * and the directory contains a valid reference + * but conflicts with the update. */ strbuf_addf(err, "there is a non-empty directory '%s' " "blocking reference '%s'", @@ -867,8 +913,23 @@ retry: * If the ref did not exist and we are creating it, we have to * make sure there is no existing packed ref that conflicts * with refname. This check is deferred so that we can batch it. + * + * For case-insensitive filesystems, we should also check for F/D + * conflicts between 'foo' and 'Foo/bar'. So let's lowercase + * the refname. */ - item = string_list_append(refnames_to_check, refname); + if (ignore_case) { + struct strbuf lower = STRBUF_INIT; + + strbuf_addstr(&lower, refname); + strbuf_tolower(&lower); + + item = string_list_append_nodup(refnames_to_check, + strbuf_detach(&lower, NULL)); + } else { + item = string_list_append(refnames_to_check, refname); + } + item->util = xmalloc(sizeof(update_idx)); memcpy(item->util, &update_idx, sizeof(update_idx)); } @@ -2421,9 +2482,10 @@ static enum ref_transaction_error split_head_update(struct ref_update *update, new_update = ref_transaction_add_update( transaction, "HEAD", - update->flags | REF_LOG_ONLY | REF_NO_DEREF, + update->flags | REF_LOG_ONLY | REF_NO_DEREF | REF_LOG_VIA_SPLIT, &update->new_oid, &update->old_oid, NULL, NULL, update->committer_info, update->msg); + new_update->parent_update = update; /* * Add "HEAD". This insertion is O(N) in the transaction @@ -2494,7 +2556,6 @@ static enum ref_transaction_error split_symref_update(struct ref_update *update, * done when new_update is processed. */ update->flags |= REF_LOG_ONLY | REF_NO_DEREF; - update->flags &= ~REF_HAVE_OLD; return 0; } @@ -2509,8 +2570,9 @@ static enum ref_transaction_error check_old_oid(struct ref_update *update, struct object_id *oid, struct strbuf *err) { - if (!(update->flags & REF_HAVE_OLD) || - oideq(oid, &update->old_oid)) + if (update->flags & REF_LOG_ONLY || + !(update->flags & REF_HAVE_OLD) || + oideq(oid, &update->old_oid)) return 0; if (is_null_oid(&update->old_oid)) { @@ -2583,9 +2645,8 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re if (lock) { lock->count++; } else { - ret = lock_raw_ref(refs, update, update_idx, mustexist, - refnames_to_check, &transaction->refnames, - &lock, &referent, err); + ret = lock_raw_ref(refs, transaction, update_idx, mustexist, + refnames_to_check, &lock, &referent, err); if (ret) { char *reason; @@ -2601,7 +2662,36 @@ static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *re update->backend_data = lock; - if (update->type & REF_ISSYMREF) { + if (update->flags & REF_LOG_VIA_SPLIT) { + struct ref_lock *parent_lock; + + if (!update->parent_update) + BUG("split update without a parent"); + + parent_lock = update->parent_update->backend_data; + + /* + * Check that "HEAD" didn't racily change since we have looked + * it up. If it did we must refuse to write the reflog entry. + * + * Note that this does not catch all races: if "HEAD" was + * racily changed to point to one of the refs part of the + * transaction then we would miss writing the split reflog + * entry for "HEAD". + */ + if (!(update->type & REF_ISSYMREF) || + strcmp(update->parent_update->refname, referent.buf)) { + strbuf_addstr(err, "HEAD has been racily updated"); + ret = REF_TRANSACTION_ERROR_GENERIC; + goto out; + } + + if (update->flags & REF_HAVE_OLD) { + oidcpy(&lock->old_oid, &update->old_oid); + } else { + oidcpy(&lock->old_oid, &parent_lock->old_oid); + } + } else if (update->type & REF_ISSYMREF) { if (update->flags & REF_NO_DEREF) { /* * We won't be reading the referent as part of @@ -2794,7 +2884,7 @@ static int files_transaction_prepare(struct ref_store *ref_store, "ref_transaction_prepare"); size_t i; int ret = 0; - struct string_list refnames_to_check = STRING_LIST_INIT_NODUP; + struct string_list refnames_to_check = STRING_LIST_INIT_DUP; char *head_ref = NULL; int head_type; struct files_transaction_backend_data *backend_data; @@ -2977,6 +3067,20 @@ static int parse_and_write_reflog(struct files_ref_store *refs, struct ref_lock *lock, struct strbuf *err) { + struct object_id *old_oid = &lock->old_oid; + + if (update->flags & REF_LOG_USE_PROVIDED_OIDS) { + if (!(update->flags & REF_HAVE_OLD) || + !(update->flags & REF_HAVE_NEW) || + !(update->flags & REF_LOG_ONLY)) { + strbuf_addf(err, _("trying to write reflog for '%s'" + "with incomplete values"), update->refname); + return REF_TRANSACTION_ERROR_GENERIC; + } + + old_oid = &update->old_oid; + } + if (update->new_target) { /* * We want to get the resolved OID for the target, to ensure @@ -2994,7 +3098,7 @@ static int parse_and_write_reflog(struct files_ref_store *refs, } } - if (files_log_ref_write(refs, lock->ref_name, &lock->old_oid, + if (files_log_ref_write(refs, lock->ref_name, old_oid, &update->new_oid, update->committer_info, update->msg, update->flags, err)) { char *old_msg = strbuf_detach(err, NULL); @@ -3062,7 +3166,8 @@ static int files_transaction_finish_initial(struct files_ref_store *refs, for (i = 0; i < transaction->nr; i++) { struct ref_update *update = transaction->updates[i]; - if ((update->flags & REF_HAVE_OLD) && + if (!(update->flags & REF_LOG_ONLY) && + (update->flags & REF_HAVE_OLD) && !is_null_oid(&update->old_oid)) BUG("initial ref transaction with old_sha1 set"); diff --git a/refs/refs-internal.h b/refs/refs-internal.h index 40c1c0f93d..54c2079c12 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -662,7 +662,8 @@ enum ref_transaction_error ref_update_check_old_target(const char *referent, /* * Check if the ref must exist, this means that the old_oid or - * old_target is non NULL. + * old_target is non NULL. Log-only updates never require the old state to + * match. */ int ref_update_expects_existing_old_ref(struct ref_update *update); diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c index 8dae1e1112..c0440b4bd0 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -1102,6 +1102,20 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor if (ret) return REF_TRANSACTION_ERROR_GENERIC; + if (u->flags & REF_LOG_USE_PROVIDED_OIDS) { + if (!(u->flags & REF_HAVE_OLD) || + !(u->flags & REF_HAVE_NEW) || + !(u->flags & REF_LOG_ONLY)) { + strbuf_addf(err, _("trying to write reflog for '%s'" + "with incomplete values"), u->refname); + return REF_TRANSACTION_ERROR_GENERIC; + } + + if (queue_transaction_update(refs, tx_data, u, &u->old_oid, err)) + return REF_TRANSACTION_ERROR_GENERIC; + return 0; + } + /* Verify that the new object ID is valid. */ if ((u->flags & REF_HAVE_NEW) && !is_null_oid(&u->new_oid) && !(u->flags & REF_SKIP_OID_VERIFICATION) && @@ -1186,8 +1200,6 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor if (ret > 0) { /* The reference does not exist, but we expected it to. */ strbuf_addf(err, _("cannot lock ref '%s': " - - "unable to resolve reference '%s'"), ref_update_original_update_refname(u), u->refname); return REF_TRANSACTION_ERROR_NONEXISTENT_REF; @@ -1241,13 +1253,8 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor new_update->parent_update = u; - /* - * Change the symbolic ref update to log only. Also, it - * doesn't need to check its old OID value, as that will be - * done when new_update is processed. - */ + /* Change the symbolic ref update to log only. */ u->flags |= REF_LOG_ONLY | REF_NO_DEREF; - u->flags &= ~REF_HAVE_OLD; } } @@ -1271,7 +1278,8 @@ static enum ref_transaction_error prepare_single_update(struct reftable_ref_stor ret = ref_update_check_old_target(referent->buf, u, err); if (ret) return ret; - } else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_oid, &u->old_oid)) { + } else if ((u->flags & (REF_LOG_ONLY | REF_HAVE_OLD)) == REF_HAVE_OLD && + !oideq(¤t_oid, &u->old_oid)) { if (is_null_oid(&u->old_oid)) { strbuf_addf(err, _("cannot lock ref '%s': " "reference already exists"), diff --git a/remote-curl.c b/remote-curl.c index 84f4694780..69f919454a 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -894,14 +894,6 @@ static int probe_rpc(struct rpc_state *rpc, struct slot_results *results) return err; } -static curl_off_t xcurl_off_t(size_t len) -{ - uintmax_t size = len; - if (size > maximum_signed_value_of_type(curl_off_t)) - die(_("cannot handle pushes this big")); - return (curl_off_t)size; -} - /* * If flush_received is true, do not attempt to read any more; just use what's * in rpc->buf. @@ -999,7 +991,7 @@ retry: * and we just need to send it. */ curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, gzip_body); - curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE_LARGE, xcurl_off_t(gzip_size)); + curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE_LARGE, cast_size_t_to_curl_off_t(gzip_size)); } else if (use_gzip && 1024 < rpc->len) { /* The client backend isn't giving us compressed data so @@ -1030,7 +1022,7 @@ retry: headers = curl_slist_append(headers, "Content-Encoding: gzip"); curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, gzip_body); - curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE_LARGE, xcurl_off_t(gzip_size)); + curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE_LARGE, cast_size_t_to_curl_off_t(gzip_size)); if (options.verbosity > 1) { fprintf(stderr, "POST %s (gzip %lu to %lu bytes)\n", @@ -1043,7 +1035,7 @@ retry: * more normal Content-Length approach. */ curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDS, rpc->buf); - curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE_LARGE, xcurl_off_t(rpc->len)); + curl_easy_setopt(slot->curl, CURLOPT_POSTFIELDSIZE_LARGE, cast_size_t_to_curl_off_t(rpc->len)); if (options.verbosity > 1) { fprintf(stderr, "POST %s (%lu bytes)\n", rpc->service_name, (unsigned long)rpc->len); @@ -1171,7 +1171,6 @@ static void show_push_unqualified_ref_name_error(const char *dst_value, const char *matched_src_name) { struct object_id oid; - enum object_type type; /* * TRANSLATORS: "matches '%s'%" is the <dst> part of "git push @@ -1196,30 +1195,37 @@ static void show_push_unqualified_ref_name_error(const char *dst_value, BUG("'%s' is not a valid object, " "match_explicit_lhs() should catch this!", matched_src_name); - type = odb_read_object_info(the_repository->objects, &oid, NULL); - if (type == OBJ_COMMIT) { + + switch (odb_read_object_info(the_repository->objects, &oid, NULL)) { + case OBJ_COMMIT: advise(_("The <src> part of the refspec is a commit object.\n" "Did you mean to create a new branch by pushing to\n" "'%s:refs/heads/%s'?"), matched_src_name, dst_value); - } else if (type == OBJ_TAG) { + break; + case OBJ_TAG: advise(_("The <src> part of the refspec is a tag object.\n" "Did you mean to create a new tag by pushing to\n" "'%s:refs/tags/%s'?"), matched_src_name, dst_value); - } else if (type == OBJ_TREE) { + break; + case OBJ_TREE: advise(_("The <src> part of the refspec is a tree object.\n" "Did you mean to tag a new tree by pushing to\n" "'%s:refs/tags/%s'?"), matched_src_name, dst_value); - } else if (type == OBJ_BLOB) { + break; + case OBJ_BLOB: advise(_("The <src> part of the refspec is a blob object.\n" "Did you mean to tag a new blob by pushing to\n" "'%s:refs/tags/%s'?"), matched_src_name, dst_value); - } else { - BUG("'%s' should be commit/tag/tree/blob, is '%d'", - matched_src_name, type); + break; + default: + advise(_("The <src> part of the refspec ('%s') " + "is an object ID that doesn't exist.\n"), + matched_src_name); + break; } } diff --git a/sequencer.c b/sequencer.c index aaf2e4df64..6d29a938aa 100644 --- a/sequencer.c +++ b/sequencer.c @@ -1087,7 +1087,6 @@ N_("you have staged changes in your working tree\n" #define CLEANUP_MSG (1<<3) #define VERIFY_MSG (1<<4) #define CREATE_ROOT_COMMIT (1<<5) -#define VERBATIM_MSG (1<<6) static int run_command_silent_on_success(struct child_process *cmd) { @@ -1125,9 +1124,6 @@ static int run_git_commit(const char *defmsg, { struct child_process cmd = CHILD_PROCESS_INIT; - if ((flags & CLEANUP_MSG) && (flags & VERBATIM_MSG)) - BUG("CLEANUP_MSG and VERBATIM_MSG are mutually exclusive"); - cmd.git_cmd = 1; if (is_rebase_i(opts) && @@ -1166,8 +1162,6 @@ static int run_git_commit(const char *defmsg, strvec_pushl(&cmd.args, "-C", "HEAD", NULL); if ((flags & CLEANUP_MSG)) strvec_push(&cmd.args, "--cleanup=strip"); - if ((flags & VERBATIM_MSG)) - strvec_push(&cmd.args, "--cleanup=verbatim"); if ((flags & EDIT_MSG)) strvec_push(&cmd.args, "-e"); else if (!(flags & CLEANUP_MSG) && @@ -1540,9 +1534,6 @@ static int try_to_commit(struct repository *r, enum commit_msg_cleanup_mode cleanup; int res = 0; - if ((flags & CLEANUP_MSG) && (flags & VERBATIM_MSG)) - BUG("CLEANUP_MSG and VERBATIM_MSG are mutually exclusive"); - if (parse_head(r, ¤t_head)) return -1; @@ -1618,8 +1609,6 @@ static int try_to_commit(struct repository *r, if (flags & CLEANUP_MSG) cleanup = COMMIT_MSG_CLEANUP_ALL; - else if (flags & VERBATIM_MSG) - cleanup = COMMIT_MSG_CLEANUP_NONE; else if ((opts->signoff || opts->record_origin) && !opts->explicit_cleanup) cleanup = COMMIT_MSG_CLEANUP_SPACE; @@ -2436,7 +2425,6 @@ static int do_pick_commit(struct repository *r, if (!final_fixup) msg_file = rebase_path_squash_msg(); else if (file_exists(rebase_path_fixup_msg())) { - flags |= VERBATIM_MSG; msg_file = rebase_path_fixup_msg(); } else { const char *dest = git_path_squash_msg(r); @@ -2721,6 +2709,7 @@ static int check_merge_commit_insn(enum todo_command command) return error(_("cannot squash merge commit into another commit")); case TODO_MERGE: + case TODO_DROP: return 0; default: diff --git a/t/Makefile b/t/Makefile index 757674e727..ab8a5b54aa 100644 --- a/t/Makefile +++ b/t/Makefile @@ -189,15 +189,9 @@ perf: .PHONY: libgit-sys-test libgit-rs-test libgit-sys-test: - $(QUIET)(\ - cd ../contrib/libgit-sys && \ - cargo test \ - ) -libgit-rs-test: - $(QUIET)(\ - cd ../contrib/libgit-rs && \ - cargo test \ - ) + $(QUIET)cargo test --manifest-path ../contrib/libgit-sys/Cargo.toml +libgit-rs-test: libgit-sys-test + $(QUIET)cargo test --manifest-path ../contrib/libgit-rs/Cargo.toml ifdef INCLUDE_LIBGIT_RS -all:: libgit-sys-test libgit-rs-test +all:: libgit-rs-test endif diff --git a/t/helper/test-pack-deltas.c b/t/helper/test-pack-deltas.c index 4caa024b1e..4981401eaa 100644 --- a/t/helper/test-pack-deltas.c +++ b/t/helper/test-pack-deltas.c @@ -51,16 +51,14 @@ static void write_ref_delta(struct hashfile *f, unsigned long size, base_size, delta_size, compressed_size, hdrlen; enum object_type type; void *base_buf, *delta_buf; - void *buf = repo_read_object_file(the_repository, - oid, &type, - &size); + void *buf = odb_read_object(the_repository->objects, + oid, &type, &size); if (!buf) die("unable to read %s", oid_to_hex(oid)); - base_buf = repo_read_object_file(the_repository, - base, &type, - &base_size); + base_buf = odb_read_object(the_repository->objects, + base, &type, &base_size); if (!base_buf) die("unable to read %s", oid_to_hex(base)); diff --git a/t/meson.build b/t/meson.build index bbeba1a8d5..983245501c 100644 --- a/t/meson.build +++ b/t/meson.build @@ -204,6 +204,7 @@ integration_tests = [ 't1418-reflog-exists.sh', 't1419-exclude-refs.sh', 't1420-lost-found.sh', + 't1421-reflog-write.sh', 't1430-bad-ref-name.sh', 't1450-fsck.sh', 't1451-fsck-buffer.sh', diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh index 854d59ec58..07a53e7366 100755 --- a/t/t0014-alias.sh +++ b/t/t0014-alias.sh @@ -27,6 +27,20 @@ test_expect_success 'looping aliases - internal execution' ' test_grep "^fatal: alias loop detected: expansion of" output ' +test_expect_success 'looping aliases - deprecated builtins' ' + test_config alias.whatchanged pack-redundant && + test_config alias.pack-redundant whatchanged && + cat >expect <<-EOF && + ${SQ}whatchanged${SQ} is aliased to ${SQ}pack-redundant${SQ} + ${SQ}pack-redundant${SQ} is aliased to ${SQ}whatchanged${SQ} + fatal: alias loop detected: expansion of ${SQ}whatchanged${SQ} does not terminate: + whatchanged <== + pack-redundant ==> + EOF + test_must_fail git whatchanged -h 2>actual && + test_cmp expect actual +' + # This test is disabled until external loops are fixed, because would block # the test suite for a full minute. # @@ -55,4 +69,47 @@ test_expect_success 'tracing a shell alias with arguments shows trace of prepare test_cmp expect actual ' +can_alias_deprecated_builtin () { + cmd="$1" && + # some git(1) commands will fail for `-h` (the case for + # git-status as of 2025-09-07) + test_might_fail git status -h >expect && + test_file_not_empty expect && + test_might_fail git -c alias."$cmd"=status "$cmd" -h >actual && + test_cmp expect actual +} + +test_expect_success 'can alias-shadow deprecated builtins' ' + for cmd in $(git --list-cmds=deprecated) + do + can_alias_deprecated_builtin "$cmd" || return 1 + done +' + +test_expect_success 'can alias-shadow via two deprecated builtins' ' + # some git(1) commands will fail... (see above) + test_might_fail git status -h >expect && + test_file_not_empty expect && + test_might_fail git -c alias.whatchanged=pack-redundant \ + -c alias.pack-redundant=status whatchanged -h >actual && + test_cmp expect actual +' + +cannot_alias_regular_builtin () { + cmd="$1" && + # some git(1) commands will fail... (see above) + test_might_fail git "$cmd" -h >expect && + test_file_not_empty expect && + test_might_fail git -c alias."$cmd"=status "$cmd" -h >actual && + test_cmp expect actual +} + +test_expect_success 'cannot alias-shadow a sample of regular builtins' ' + for cmd in grep check-ref-format interpret-trailers \ + checkout-index fast-import diagnose rev-list prune + do + cannot_alias_regular_builtin "$cmd" || return 1 + done +' + test_done diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh index cb3a85c7ff..07aa834d33 100755 --- a/t/t0300-credentials.sh +++ b/t/t0300-credentials.sh @@ -991,18 +991,24 @@ test_expect_success 'url parser not confused by encoded markers' ' test_expect_success 'credential config with partial URLs' ' echo "echo password=yep" | write_script git-credential-yep && - test_write_lines url=https://user@example.com/repo.git >stdin && + test_write_lines url=https://user@example.com/org/repo.git >stdin && for partial in \ example.com \ + example.com/org/repo.git \ user@example.com \ + user@example.com/org/repo.git \ https:// \ https://example.com \ https://example.com/ \ + https://example.com/org \ + https://example.com/org/ \ + https://example.com/org/repo.git \ https://user@example.com \ https://user@example.com/ \ - https://example.com/repo.git \ - https://user@example.com/repo.git \ - /repo.git + https://user@example.com/org \ + https://user@example.com/org/ \ + https://user@example.com/org/repo.git \ + /org/repo.git do git -c credential.$partial.helper=yep \ credential fill <stdin >stdout && @@ -1012,7 +1018,12 @@ test_expect_success 'credential config with partial URLs' ' for partial in \ dont.use.this \ + example.com/o \ + user@example.com/o \ http:// \ + https://example.com/o \ + https://user@example.com/o \ + /o \ /repo do git -c credential.$partial.helper=yep \ diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index 96648a6e5d..08d5df2af7 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -2294,6 +2294,59 @@ do ) ' + test_expect_success CASE_INSENSITIVE_FS,REFFILES "stdin $type batch-updates existing reference" ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + test_commit one && + old_head=$(git rev-parse HEAD) && + test_commit two && + head=$(git rev-parse HEAD) && + + { + format_command $type "create refs/heads/foo" "$head" && + format_command $type "create refs/heads/ref" "$old_head" && + format_command $type "create refs/heads/Foo" "$old_head" + } >stdin && + git update-ref $type --stdin --batch-updates <stdin >stdout && + + echo $head >expect && + git rev-parse refs/heads/foo >actual && + echo $old_head >expect && + git rev-parse refs/heads/ref >actual && + test_cmp expect actual && + test_grep -q "reference conflict due to case-insensitive filesystem" stdout + ) + ' + + test_expect_success CASE_INSENSITIVE_FS "stdin $type batch-updates existing reference" ' + git init --ref-format=reftable repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + test_commit one && + old_head=$(git rev-parse HEAD) && + test_commit two && + head=$(git rev-parse HEAD) && + + { + format_command $type "create refs/heads/foo" "$head" && + format_command $type "create refs/heads/ref" "$old_head" && + format_command $type "create refs/heads/Foo" "$old_head" + } >stdin && + git update-ref $type --stdin --batch-updates <stdin >stdout && + + echo $head >expect && + git rev-parse refs/heads/foo >actual && + echo $old_head >expect && + git rev-parse refs/heads/ref >actual && + test_cmp expect actual && + git rev-parse refs/heads/Foo >actual && + test_cmp expect actual + ) + ' + test_expect_success "stdin $type batch-updates delete incorrect symbolic ref" ' git init repo && test_when_finished "rm -fr repo" && diff --git a/t/t1421-reflog-write.sh b/t/t1421-reflog-write.sh new file mode 100755 index 0000000000..46df64c176 --- /dev/null +++ b/t/t1421-reflog-write.sh @@ -0,0 +1,126 @@ +#!/bin/sh + +test_description='Manually write reflog entries' + +. ./test-lib.sh + +SIGNATURE="C O Mitter <committer@example.com> 1112911993 -0700" + +test_reflog_matches () { + repo="$1" && + refname="$2" && + cat >actual && + test-tool -C "$repo" ref-store main for-each-reflog-ent "$refname" >expected && + test_cmp expected actual +} + +test_expect_success 'invalid number of arguments' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + for args in "" "1" "1 2" "1 2 3" "1 2 3 4 5" + do + test_must_fail git reflog write $args 2>err && + test_grep "usage: git reflog write" err || return 1 + done + ) +' + +test_expect_success 'invalid refname' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_must_fail git reflog write "refs/heads/ invalid" $ZERO_OID $ZERO_OID first 2>err && + test_grep "invalid reference name: " err + ) +' + +test_expect_success 'unqualified refname is rejected' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_must_fail git reflog write unqualified $ZERO_OID $ZERO_OID first 2>err && + test_grep "invalid reference name: " err + ) +' + +test_expect_success 'nonexistent object IDs' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_must_fail git reflog write refs/heads/something $(test_oid deadbeef) $ZERO_OID old-object-id 2>err && + test_grep "old object .* does not exist" err && + test_must_fail git reflog write refs/heads/something $ZERO_OID $(test_oid deadbeef) new-object-id 2>err && + test_grep "new object .* does not exist" err + ) +' + +test_expect_success 'abbreviated object IDs' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + abbreviated_oid=$(git rev-parse HEAD | test_copy_bytes 8) && + test_must_fail git reflog write refs/heads/something $abbreviated_oid $ZERO_OID old-object-id 2>err && + test_grep "invalid old object ID" err && + test_must_fail git reflog write refs/heads/something $ZERO_OID $abbreviated_oid new-object-id 2>err && + test_grep "invalid new object ID" err + ) +' + +test_expect_success 'reflog message gets normalized' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + COMMIT_OID=$(git rev-parse HEAD) && + git reflog write HEAD $COMMIT_OID $COMMIT_OID "$(printf "message\nwith\nnewlines")" && + git reflog show -1 --format=%gs HEAD >actual && + echo "message with newlines" >expected && + test_cmp expected actual + ) +' + +test_expect_success 'simple writes' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + COMMIT_OID=$(git rev-parse HEAD) && + + git reflog write refs/heads/something $ZERO_OID $COMMIT_OID first && + test_reflog_matches . refs/heads/something <<-EOF && + $ZERO_OID $COMMIT_OID $SIGNATURE first + EOF + + git reflog write refs/heads/something $COMMIT_OID $COMMIT_OID second && + test_reflog_matches . refs/heads/something <<-EOF + $ZERO_OID $COMMIT_OID $SIGNATURE first + $COMMIT_OID $COMMIT_OID $SIGNATURE second + EOF + ) +' + +test_expect_success 'can write to root ref' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + COMMIT_OID=$(git rev-parse HEAD) && + + git reflog write ROOT_REF_HEAD $ZERO_OID $COMMIT_OID first && + test_reflog_matches . ROOT_REF_HEAD <<-EOF + $ZERO_OID $COMMIT_OID $SIGNATURE first + EOF + ) +' + +test_done diff --git a/t/t1460-refs-migrate.sh b/t/t1460-refs-migrate.sh index 2ab97e1b7d..0e1116a319 100755 --- a/t/t1460-refs-migrate.sh +++ b/t/t1460-refs-migrate.sh @@ -7,6 +7,17 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh +print_all_reflog_entries () { + repo=$1 && + test-tool -C "$repo" ref-store main for-each-reflog >reflogs && + while read reflog + do + echo "REFLOG: $reflog" && + test-tool -C "$repo" ref-store main for-each-reflog-ent "$reflog" || + return 1 + done <reflogs +} + # Migrate the provided repository from one format to the other and # verify that the references and logs are migrated over correctly. # Usage: test_migration <repo> <format> [<skip_reflog_verify> [<options...>]] @@ -28,8 +39,7 @@ test_migration () { --format='%(refname) %(objectname) %(symref)' >expect && if ! $skip_reflog_verify then - git -C "$repo" reflog --all >expect_logs && - git -C "$repo" reflog list >expect_log_list + print_all_reflog_entries "$repo" >expect_logs fi && git -C "$repo" refs migrate --ref-format="$format" "$@" && @@ -39,10 +49,8 @@ test_migration () { test_cmp expect actual && if ! $skip_reflog_verify then - git -C "$repo" reflog --all >actual_logs && - git -C "$repo" reflog list >actual_log_list && - test_cmp expect_logs actual_logs && - test_cmp expect_log_list actual_log_list + print_all_reflog_entries "$repo" >actual_logs && + test_cmp expect_logs actual_logs fi && git -C "$repo" rev-parse --show-ref-format >actual && @@ -273,7 +281,7 @@ test_expect_success 'multiple reftable blocks with multiple entries' ' test_commit -C repo second && printf "update refs/heads/ref-%d HEAD\n" $(test_seq 3000) >stdin && git -C repo update-ref --stdin <stdin && - test_migration repo reftable + test_migration repo reftable true ' test_expect_success 'migrating from files format deletes backend files' ' diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 6bac217ed3..34d6ad0770 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -2263,6 +2263,7 @@ test_expect_success 'non-merge commands reject merge commits' ' edit $oid fixup $oid squash $oid + drop $oid # acceptable, no advice EOF ( set_replace_editor todo && diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh index 5d093e3a7a..5033411a43 100755 --- a/t/t3415-rebase-autosquash.sh +++ b/t/t3415-rebase-autosquash.sh @@ -486,12 +486,28 @@ test_expect_success 'fixup a fixup' ' test XZWY = $(git show | tr -cd W-Z) ' -test_expect_success 'fixup does not clean up commit message' ' - oneline="#818" && - git commit --allow-empty -m "$oneline" && - git commit --fixup HEAD --allow-empty && - git -c commit.cleanup=strip rebase -ki --autosquash HEAD~2 && - test "$oneline" = "$(git show -s --format=%s)" +test_expect_success 'pick and fixup respect commit.cleanup' ' + git reset --hard base && + test_commit --no-tag "fixup! second commit" file1 fixup && + test_commit something && + write_script .git/hooks/prepare-commit-msg <<-\EOF && + printf "\n# Prepared\n" >> "$1" + EOF + git rebase -i --autosquash HEAD~3 && + test_commit_message HEAD~1 <<-\EOF && + second commit + + # Prepared + EOF + test_commit_message HEAD <<-\EOF && + something + + # Prepared + EOF + git reset --hard something && + git -c commit.cleanup=strip rebase -i --autosquash HEAD~3 && + test_commit_message HEAD~1 -m "second commit" && + test_commit_message HEAD -m "something" ' test_done diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 04d2a19835..d9fe289a7a 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -866,6 +866,44 @@ test_expect_success 'colorized diffs respect diff.wsErrorHighlight' ' test_grep "old<" output ' +test_expect_success 'diff color respects color.diff' ' + git reset --hard && + + echo old >test && + git add test && + echo new >test && + + printf n >n && + force_color git \ + -c color.interactive=auto \ + -c color.interactive.prompt=blue \ + -c color.diff=false \ + -c color.diff.old=red \ + add -p >output.raw 2>&1 <n && + test_decode_color <output.raw >output && + test_grep "BLUE.*Stage this hunk" output && + test_grep ! "RED" output +' + +test_expect_success 're-coloring diff without color.interactive' ' + git reset --hard && + + test_write_lines 1 2 3 >test && + git add test && + test_write_lines one 2 three >test && + + test_write_lines s n n | + force_color git \ + -c color.interactive=false \ + -c color.interactive.prompt=blue \ + -c color.diff=true \ + -c color.diff.frag="bold magenta" \ + add -p >output.raw 2>&1 && + test_decode_color <output.raw >output && + test_grep "<BOLD;MAGENTA>@@" output && + test_grep ! "BLUE" output +' + test_expect_success 'diffFilter filters diff' ' git reset --hard && @@ -1283,6 +1321,12 @@ test_expect_success 'stash accepts -U and --inter-hunk-context' ' test_grep "@@ -2,20 +2,20 @@" actual ' +test_expect_success 'set up base for -p color tests' ' + echo commit >file && + git commit -am "commit state" && + git tag patch-base +' + for cmd in add checkout commit reset restore "stash save" "stash push" do test_expect_success "$cmd rejects invalid context options" ' @@ -1299,6 +1343,15 @@ do test_must_fail git $cmd --inter-hunk-context 2 2>actual && test_grep -E ".--inter-hunk-context. requires .(--interactive/)?--patch." actual ' + + test_expect_success "$cmd falls back to color.ui" ' + git reset --hard patch-base && + echo working-tree >file && + test_write_lines y | + force_color git -c color.ui=false $cmd -p >output.raw 2>&1 && + test_decode_color <output.raw >output && + test_cmp output.raw output + ' done test_done diff --git a/t/t3904-stash-patch.sh b/t/t3904-stash-patch.sh index ae313e3c70..90a4ff2c10 100755 --- a/t/t3904-stash-patch.sh +++ b/t/t3904-stash-patch.sh @@ -107,4 +107,23 @@ test_expect_success 'stash -p with split hunk' ' ! grep "added line 2" test ' +test_expect_success 'stash -p not confused by GIT_PAGER_IN_USE' ' + echo to-stash >test && + # Set both GIT_PAGER_IN_USE and TERM. Our goal is to entice any + # diff subprocesses into thinking that they could output + # color, even though their stdout is not going into a tty. + echo y | + GIT_PAGER_IN_USE=1 TERM=vt100 git stash -p && + git diff --exit-code +' + +test_expect_success 'index push not confused by GIT_PAGER_IN_USE' ' + echo index >test && + git add test && + echo working-tree >test && + # As above, we try to entice the child diff into using color. + GIT_PAGER_IN_USE=1 TERM=vt100 git stash push test && + git diff --exit-code +' + test_done diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index 8ebd170451..cfeec239e0 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -648,6 +648,19 @@ test_expect_success 'diff -I<regex>: detect malformed regex' ' test_grep "invalid regex given to -I: " error ' +test_expect_success 'diff -I<regex>: ignore matching file' ' + test_when_finished "git rm -f file1" && + test_seq 50 >file1 && + git add file1 && + test_seq 50 | sed -e "s/13/ten and three/" -e "s/^[124-9].*/& /" >file1 && + + : >actual && + git diff --raw --ignore-blank-lines -I"ten.*e" -I"^[124-9]" >>actual && + git diff --name-only --ignore-blank-lines -I"ten.*e" -I"^[124-9]" >>actual && + git diff --name-status --ignore-blank-lines -I"ten.*e" -I"^[124-9]" >>actual && + test_grep ! "file1" actual +' + # check_prefix <patch> <src> <dst> # check only lines with paths to avoid dependency on exact oid/contents check_prefix () { diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 52e3e476ff..9de7f73f42 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -11,12 +11,8 @@ test_description='Test special whitespace in diff engine. . "$TEST_DIRECTORY"/lib-diff.sh for opt_res in --patch --quiet -s --stat --shortstat --dirstat=lines \ - --raw! --name-only! --name-status! + --raw --name-only --name-status do - opts=${opt_res%!} expect_failure= - test "$opts" = "$opt_res" || - expect_failure="test_expect_code 1" - test_expect_success "status with $opts (different)" ' echo foo >x && git add x && @@ -43,7 +39,7 @@ do echo foo >x && git add x && echo " foo" >x && - $expect_failure git diff -w $opts --exit-code x + git diff -w $opts --exit-code x ' done diff --git a/t/t4035-diff-quiet.sh b/t/t4035-diff-quiet.sh index 0352bf81a9..35eaf0855f 100755 --- a/t/t4035-diff-quiet.sh +++ b/t/t4035-diff-quiet.sh @@ -50,6 +50,10 @@ test_expect_success 'git diff-tree HEAD HEAD' ' test_expect_code 0 git diff-tree --quiet HEAD HEAD >cnt && test_line_count = 0 cnt ' +test_expect_success 'git diff-tree -w HEAD^ HEAD' ' + test_expect_code 1 git diff-tree --quiet -w HEAD^ HEAD >cnt && + test_line_count = 0 cnt +' test_expect_success 'git diff-files' ' test_expect_code 0 git diff-files --quiet >cnt && test_line_count = 0 cnt diff --git a/t/t4053-diff-no-index.sh b/t/t4053-diff-no-index.sh index 01db9243ab..69599279e9 100755 --- a/t/t4053-diff-no-index.sh +++ b/t/t4053-diff-no-index.sh @@ -26,6 +26,23 @@ test_expect_success 'git diff --no-index directories' ' test_line_count = 14 cnt ' +test_expect_success 'git diff --no-index with -' ' + cat >expect <<-\EOF && + diff --git a/- b/- + new file mode 100644 + --- /dev/null + +++ b/- + @@ -0,0 +1 @@ + +frotz + EOF + ( + cd a && + echo frotz | + test_expect_code 1 git diff --no-index /dev/null - >../actual + ) && + test_cmp expect actual +' + test_expect_success 'git diff --no-index relative path outside repo' ' ( cd repo && @@ -322,6 +339,22 @@ test_expect_success 'diff --no-index with pathspec' ' test_cmp expect actual ' +test_expect_success 'diff --no-index first path ending in slash with pathspec' ' + test_expect_code 1 git diff --name-status --no-index a/ b 1 >actual && + cat >expect <<-EOF && + D a/1 + EOF + test_cmp expect actual +' + +test_expect_success 'diff --no-index second path ending in slash with pathspec' ' + test_expect_code 1 git diff --name-status --no-index a b/ 1 >actual && + cat >expect <<-EOF && + D a/1 + EOF + test_cmp expect actual +' + test_expect_success 'diff --no-index with pathspec no matches' ' test_expect_code 0 git diff --name-status --no-index a b missing ' diff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh index 950451cf6a..0a7c3ca42f 100755 --- a/t/t4211-line-log.sh +++ b/t/t4211-line-log.sh @@ -78,6 +78,8 @@ canned_test "-L :main:a.c -L 4,18:a.c simple" multiple-overlapping canned_test "-L 4:a.c -L 8,12:a.c simple" multiple-superset canned_test "-L 8,12:a.c -L 4:a.c simple" multiple-superset +canned_test "-L 10,16:b.c -L 18,26:b.c main" no-assertion-error + test_bad_opts "-L" "switch.*requires a value" test_bad_opts "-L b.c" "argument not .start,end:file" test_bad_opts "-L 1:" "argument not .start,end:file" diff --git a/t/t4211/sha1/expect.multiple b/t/t4211/sha1/expect.multiple index 76ad5b598c..1eee8a7801 100644 --- a/t/t4211/sha1/expect.multiple +++ b/t/t4211/sha1/expect.multiple @@ -102,3 +102,9 @@ diff --git a/a.c b/a.c + s++; + } +} +@@ -0,0 +16,5 @@ ++int main () ++{ ++ printf("%d\n", f(15)); ++ return 0; ++} diff --git a/t/t4211/sha1/expect.no-assertion-error b/t/t4211/sha1/expect.no-assertion-error new file mode 100644 index 0000000000..994c37db1e --- /dev/null +++ b/t/t4211/sha1/expect.no-assertion-error @@ -0,0 +1,90 @@ +commit 0d8dcfc6b968e06a27d5215bad1fdde3de9d6235 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:50:24 2013 +0100 + + move within the file + +diff --git a/b.c b/b.c +--- a/b.c ++++ b/b.c +@@ -25,0 +18,9 @@ ++long f(long x) ++{ ++ int s = 0; ++ while (x) { ++ x /= 2; ++ s++; ++ } ++ return s; ++} + +commit 4659538844daa2849b1a9e7d6fadb96fcd26fc83 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:48:43 2013 +0100 + + change back to complete line + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -18,5 +18,7 @@ + int main () + { + printf("%ld\n", f(15)); + return 0; +-} +\ No newline at end of file ++} ++ ++/* incomplete lines are bad! */ + +commit 100b61a6f2f720f812620a9d10afb3a960ccb73c +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:48:10 2013 +0100 + + change to an incomplete line at end + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -18,5 +18,5 @@ + int main () + { + printf("%ld\n", f(15)); + return 0; +-} ++} +\ No newline at end of file + +commit a6eb82647d5d67f893da442f8f9375fd89a3b1e2 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:45:16 2013 +0100 + + touch both functions + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -17,5 +17,5 @@ + int main () + { +- printf("%d\n", f(15)); ++ printf("%ld\n", f(15)); + return 0; + } + +commit de4c48ae814792c02a49c4c3c0c757ae69c55f6a +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:48 2013 +0100 + + initial + +diff --git a/a.c b/a.c +--- /dev/null ++++ b/a.c +@@ -0,0 +16,5 @@ ++int main () ++{ ++ printf("%d\n", f(15)); ++ return 0; ++} diff --git a/t/t4211/sha1/expect.two-ranges b/t/t4211/sha1/expect.two-ranges index 6109aa0dce..c5164f3be3 100644 --- a/t/t4211/sha1/expect.two-ranges +++ b/t/t4211/sha1/expect.two-ranges @@ -100,3 +100,9 @@ diff --git a/a.c b/a.c + s++; + } +} +@@ -0,0 +16,5 @@ ++int main () ++{ ++ printf("%d\n", f(15)); ++ return 0; ++} diff --git a/t/t4211/sha256/expect.multiple b/t/t4211/sha256/expect.multiple index ca00409b9a..dbd987b74a 100644 --- a/t/t4211/sha256/expect.multiple +++ b/t/t4211/sha256/expect.multiple @@ -102,3 +102,9 @@ diff --git a/a.c b/a.c + s++; + } +} +@@ -0,0 +16,5 @@ ++int main () ++{ ++ printf("%d\n", f(15)); ++ return 0; ++} diff --git a/t/t4211/sha256/expect.no-assertion-error b/t/t4211/sha256/expect.no-assertion-error new file mode 100644 index 0000000000..36ed12aa9c --- /dev/null +++ b/t/t4211/sha256/expect.no-assertion-error @@ -0,0 +1,90 @@ +commit eb871b8aa9aff323e484723039c9a92ab0266e060bc0ef2afb08fadda25c5ace +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:50:24 2013 +0100 + + move within the file + +diff --git a/b.c b/b.c +--- a/b.c ++++ b/b.c +@@ -25,0 +18,9 @@ ++long f(long x) ++{ ++ int s = 0; ++ while (x) { ++ x /= 2; ++ s++; ++ } ++ return s; ++} + +commit 5526ed05c2476b56af8b7be499e8f78bd50f490740733a9ca7e1f55878fa90a9 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:48:43 2013 +0100 + + change back to complete line + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -18,5 +18,7 @@ + int main () + { + printf("%ld\n", f(15)); + return 0; +-} +\ No newline at end of file ++} ++ ++/* incomplete lines are bad! */ + +commit 29f32ac3141c48b22803e5c4127b719917b67d0f8ca8c5248bebfa2a19f7da10 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:48:10 2013 +0100 + + change to an incomplete line at end + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -18,5 +18,5 @@ + int main () + { + printf("%ld\n", f(15)); + return 0; +-} ++} +\ No newline at end of file + +commit ccf97b9878189c40a981da50b15713bb80a35755326320ec80900caf22ced46f +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:45:16 2013 +0100 + + touch both functions + +diff --git a/a.c b/a.c +--- a/a.c ++++ b/a.c +@@ -17,5 +17,5 @@ + int main () + { +- printf("%d\n", f(15)); ++ printf("%ld\n", f(15)); + return 0; + } + +commit 1dd7e9b2b1699324b53b341e728653b913bc192a14dfea168c5b51f2b3d03592 +Author: Thomas Rast <trast@student.ethz.ch> +Date: Thu Feb 28 10:44:48 2013 +0100 + + initial + +diff --git a/a.c b/a.c +--- /dev/null ++++ b/a.c +@@ -0,0 +16,5 @@ ++int main () ++{ ++ printf("%d\n", f(15)); ++ return 0; ++} diff --git a/t/t4211/sha256/expect.two-ranges b/t/t4211/sha256/expect.two-ranges index af57c8b997..6a94d3b9cb 100644 --- a/t/t4211/sha256/expect.two-ranges +++ b/t/t4211/sha256/expect.two-ranges @@ -100,3 +100,9 @@ diff --git a/a.c b/a.c + s++; + } +} +@@ -0,0 +16,5 @@ ++int main () ++{ ++ printf("%d\n", f(15)); ++ return 0; ++} diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index bd75dea950..2c22fdb931 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -989,6 +989,23 @@ test_expect_success 'repack --batch-size=0 repacks everything' ' ) ' +test_expect_success EXPENSIVE 'repack/expire with many packs' ' + cp -r dup many && + ( + cd many && + + for i in $(test_seq 1 100) + do + test_commit extra$i && + git maintenance run --task=loose-objects || return 1 + done && + + git multi-pack-index write && + git multi-pack-index repack && + git multi-pack-index expire + ) +' + test_expect_success 'repack --batch-size=<large> repacks everything' ' ( cd dup2 && @@ -1083,7 +1100,10 @@ test_expect_success 'load reverse index when missing .idx, .pack' ' mv $idx.bak $idx && mv $pack $pack.bak && - git cat-file --batch-check="%(objectsize:disk)" <tip + git cat-file --batch-check="%(objectsize:disk)" <tip && + + test_must_fail git multi-pack-index write 2>err && + test_grep "could not load pack" err ) ' diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index ebc696546b..6b2739db26 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -47,7 +47,25 @@ test_expect_success "clone and setup child repos" ' git config set branch.main.merge refs/heads/one ) && git clone . bundle && - git clone . seven + git clone . seven && + git clone --ref-format=reftable . case_sensitive && + ( + cd case_sensitive && + git branch branch1 && + git branch bRanch1 + ) && + git clone --ref-format=reftable . case_sensitive_fd && + ( + cd case_sensitive_fd && + git branch foo/bar && + git branch Foo + ) && + git clone --ref-format=reftable . case_sensitive_df && + ( + cd case_sensitive_df && + git branch Foo/bar && + git branch foo + ) ' test_expect_success "fetch test" ' @@ -1526,6 +1544,100 @@ test_expect_success SYMLINKS 'clone does not get confused by a D/F conflict' ' test_path_is_missing whoops ' +test_expect_success CASE_INSENSITIVE_FS,REFFILES 'existing references in a case insensitive filesystem' ' + test_when_finished rm -rf case_insensitive && + ( + git init --bare case_insensitive && + cd case_insensitive && + git remote add origin -- ../case_sensitive && + test_must_fail git fetch -f origin "refs/heads/*:refs/heads/*" 2>err && + test_grep "You${SQ}re on a case-insensitive filesystem" err && + git rev-parse refs/heads/main >expect && + git rev-parse refs/heads/branch1 >actual && + test_cmp expect actual + ) +' + +test_expect_success REFFILES 'existing reference lock in repo' ' + test_when_finished rm -rf base repo && + ( + git init --ref-format=reftable base && + cd base && + echo >file update && + git add . && + git commit -m "updated" && + git branch -M main && + + git update-ref refs/heads/foo @ && + git update-ref refs/heads/branch @ && + cd .. && + + git init --ref-format=files --bare repo && + cd repo && + git remote add origin ../base && + touch refs/heads/foo.lock && + test_must_fail git fetch -f origin "refs/heads/*:refs/heads/*" 2>err && + test_grep "error: fetching ref refs/heads/foo failed: reference already exists" err && + git rev-parse refs/heads/main >expect && + git rev-parse refs/heads/branch >actual && + test_cmp expect actual + ) +' + +test_expect_success CASE_INSENSITIVE_FS,REFFILES 'F/D conflict on case insensitive filesystem' ' + test_when_finished rm -rf case_insensitive && + ( + git init --bare case_insensitive && + cd case_insensitive && + git remote add origin -- ../case_sensitive_fd && + test_must_fail git fetch -f origin "refs/heads/*:refs/heads/*" 2>err && + test_grep "failed: refname conflict" err && + git rev-parse refs/heads/main >expect && + git rev-parse refs/heads/foo/bar >actual && + test_cmp expect actual + ) +' + +test_expect_success CASE_INSENSITIVE_FS,REFFILES 'D/F conflict on case insensitive filesystem' ' + test_when_finished rm -rf case_insensitive && + ( + git init --bare case_insensitive && + cd case_insensitive && + git remote add origin -- ../case_sensitive_df && + test_must_fail git fetch -f origin "refs/heads/*:refs/heads/*" 2>err && + test_grep "failed: refname conflict" err && + git rev-parse refs/heads/main >expect && + git rev-parse refs/heads/Foo/bar >actual && + test_cmp expect actual + ) +' + +test_expect_success REFFILES 'D/F conflict on case sensitive filesystem with lock' ' + ( + git init --ref-format=reftable base && + cd base && + echo >file update && + git add . && + git commit -m "updated" && + git branch -M main && + + git update-ref refs/heads/foo @ && + git update-ref refs/heads/branch @ && + cd .. && + + git init --ref-format=files --bare repo && + cd repo && + git remote add origin ../base && + mkdir refs/heads/foo && + touch refs/heads/foo/random.lock && + test_must_fail git fetch origin "refs/heads/*:refs/heads/*" 2>err && + test_grep "some local refs could not be updated; try running" err && + git rev-parse refs/heads/main >expect && + git rev-parse refs/heads/branch >actual && + test_cmp expect actual + ) +' + . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 4e9c27b0f2..46926e7bbd 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -105,7 +105,6 @@ check_push_result () { } test_expect_success setup ' - >path1 && git add path1 && test_tick && @@ -117,7 +116,6 @@ test_expect_success setup ' test_tick && git commit -a -m second && the_commit=$(git show-ref -s --verify refs/heads/main) - ' for cmd in push fetch @@ -322,104 +320,82 @@ test_expect_success 'push with pushInsteadOf and explicit pushurl (pushInsteadOf ' test_expect_success 'push with matching heads' ' - mk_test testrepo heads/main && git push testrepo : && check_push_result testrepo $the_commit heads/main - ' test_expect_success 'push with matching heads on the command line' ' - mk_test testrepo heads/main && git push testrepo : && check_push_result testrepo $the_commit heads/main - ' test_expect_success 'failed (non-fast-forward) push with matching heads' ' - mk_test testrepo heads/main && git push testrepo : && git commit --amend -massaged && test_must_fail git push testrepo && check_push_result testrepo $the_commit heads/main && git reset --hard $the_commit - ' test_expect_success 'push --force with matching heads' ' - mk_test testrepo heads/main && git push testrepo : && git commit --amend -massaged && git push --force testrepo : && ! check_push_result testrepo $the_commit heads/main && git reset --hard $the_commit - ' test_expect_success 'push with matching heads and forced update' ' - mk_test testrepo heads/main && git push testrepo : && git commit --amend -massaged && git push testrepo +: && ! check_push_result testrepo $the_commit heads/main && git reset --hard $the_commit - ' test_expect_success 'push with no ambiguity (1)' ' - mk_test testrepo heads/main && git push testrepo main:main && check_push_result testrepo $the_commit heads/main - ' test_expect_success 'push with no ambiguity (2)' ' - mk_test testrepo remotes/origin/main && git push testrepo main:origin/main && check_push_result testrepo $the_commit remotes/origin/main - ' test_expect_success 'push with colon-less refspec, no ambiguity' ' - mk_test testrepo heads/main heads/t/main && git branch -f t/main main && git push testrepo main && check_push_result testrepo $the_commit heads/main && check_push_result testrepo $the_first_commit heads/t/main - ' test_expect_success 'push with weak ambiguity (1)' ' - mk_test testrepo heads/main remotes/origin/main && git push testrepo main:main && check_push_result testrepo $the_commit heads/main && check_push_result testrepo $the_first_commit remotes/origin/main - ' test_expect_success 'push with weak ambiguity (2)' ' - mk_test testrepo heads/main remotes/origin/main remotes/another/main && git push testrepo main:main && check_push_result testrepo $the_commit heads/main && check_push_result testrepo $the_first_commit remotes/origin/main remotes/another/main - ' test_expect_success 'push with ambiguity' ' - mk_test testrepo heads/frotz tags/frotz && test_must_fail git push testrepo main:frotz && check_push_result testrepo $the_first_commit heads/frotz tags/frotz - ' test_expect_success 'push with onelevel ref' ' @@ -428,17 +404,14 @@ test_expect_success 'push with onelevel ref' ' ' test_expect_success 'push with colon-less refspec (1)' ' - mk_test testrepo heads/frotz tags/frotz && git branch -f frotz main && git push testrepo frotz && check_push_result testrepo $the_commit heads/frotz && check_push_result testrepo $the_first_commit tags/frotz - ' test_expect_success 'push with colon-less refspec (2)' ' - mk_test testrepo heads/frotz tags/frotz && if git show-ref --verify -q refs/heads/frotz then @@ -448,7 +421,6 @@ test_expect_success 'push with colon-less refspec (2)' ' git push -f testrepo frotz && check_push_result testrepo $the_commit tags/frotz && check_push_result testrepo $the_first_commit heads/frotz - ' test_expect_success 'push with colon-less refspec (3)' ' @@ -465,7 +437,6 @@ test_expect_success 'push with colon-less refspec (3)' ' ' test_expect_success 'push with colon-less refspec (4)' ' - mk_test testrepo && if git show-ref --verify -q refs/heads/frotz then @@ -475,38 +446,34 @@ test_expect_success 'push with colon-less refspec (4)' ' git push testrepo frotz && check_push_result testrepo $the_commit tags/frotz && test 1 = $( cd testrepo && git show-ref | wc -l ) - ' test_expect_success 'push head with non-existent, incomplete dest' ' - mk_test testrepo && git push testrepo main:branch && check_push_result testrepo $the_commit heads/branch - ' test_expect_success 'push tag with non-existent, incomplete dest' ' - mk_test testrepo && git tag -f v1.0 && git push testrepo v1.0:tag && check_push_result testrepo $the_commit tags/tag - ' test_expect_success 'push oid with non-existent, incomplete dest' ' - mk_test testrepo && test_must_fail git push testrepo $(git rev-parse main):foo - ' test_expect_success 'push ref expression with non-existent, incomplete dest' ' - mk_test testrepo && test_must_fail git push testrepo main^:branch +' +test_expect_success 'push ref expression with non-existent oid src' ' + mk_test testrepo && + test_must_fail git push testrepo $(test_oid 001):branch ' for head in HEAD @ @@ -550,7 +517,6 @@ do git checkout main && git push testrepo $head:branch && check_push_result testrepo $the_commit heads/branch - ' test_expect_success "push with config remote.*.push = $head" ' @@ -596,7 +562,6 @@ test_expect_success 'push with remote.pushdefault' ' ' test_expect_success 'push with config remote.*.pushurl' ' - mk_test testrepo heads/main && git checkout main && test_config remote.there.url test2repo && @@ -655,7 +620,6 @@ test_expect_success 'push ignores "branch." config without subsection' ' ' test_expect_success 'push with dry-run' ' - mk_test testrepo heads/main && old_commit=$(git -C testrepo show-ref -s --verify refs/heads/main) && git push --dry-run testrepo : && @@ -663,7 +627,6 @@ test_expect_success 'push with dry-run' ' ' test_expect_success 'push updates local refs' ' - mk_test testrepo heads/main && mk_child testrepo child && ( @@ -673,11 +636,9 @@ test_expect_success 'push updates local refs' ' test $(git rev-parse main) = \ $(git rev-parse remotes/origin/main) ) - ' test_expect_success 'push updates up-to-date local refs' ' - mk_test testrepo heads/main && mk_child testrepo child1 && mk_child testrepo child2 && @@ -689,11 +650,9 @@ test_expect_success 'push updates up-to-date local refs' ' test $(git rev-parse main) = \ $(git rev-parse remotes/origin/main) ) - ' test_expect_success 'push preserves up-to-date packed refs' ' - mk_test testrepo heads/main && mk_child testrepo child && ( @@ -701,11 +660,9 @@ test_expect_success 'push preserves up-to-date packed refs' ' git push && ! test -f .git/refs/remotes/origin/main ) - ' test_expect_success 'push does not update local refs on failure' ' - mk_test testrepo heads/main && mk_child testrepo child && echo "#!/no/frobnication/today" >testrepo/.git/hooks/pre-receive && @@ -717,16 +674,13 @@ test_expect_success 'push does not update local refs on failure' ' test $(git rev-parse main) != \ $(git rev-parse remotes/origin/main) ) - ' test_expect_success 'allow deleting an invalid remote ref' ' - mk_test testrepo heads/branch && rm -f testrepo/.git/objects/??/* && git push testrepo :refs/heads/branch && (cd testrepo && test_must_fail git rev-parse --verify refs/heads/branch) - ' test_expect_success 'pushing valid refs triggers post-receive and post-update hooks' ' diff --git a/t/t5530-upload-pack-error.sh b/t/t5530-upload-pack-error.sh index 558eedf25a..d40292cfb7 100755 --- a/t/t5530-upload-pack-error.sh +++ b/t/t5530-upload-pack-error.sh @@ -4,8 +4,6 @@ test_description='errors in upload-pack' . ./test-lib.sh -D=$(pwd) - corrupt_repo () { object_sha1=$(git rev-parse "$1") && ob=$(expr "$object_sha1" : "\(..\)") && @@ -21,11 +19,7 @@ test_expect_success 'setup and corrupt repository' ' test_tick && echo changed >file && git commit -a -m changed && - corrupt_repo HEAD:file - -' - -test_expect_success 'fsck fails' ' + corrupt_repo HEAD:file && test_must_fail git fsck ' @@ -40,17 +34,12 @@ test_expect_success 'upload-pack fails due to error in pack-objects packing' ' ' test_expect_success 'corrupt repo differently' ' - git hash-object -w file && - corrupt_repo HEAD^^{tree} - -' - -test_expect_success 'fsck fails' ' + corrupt_repo HEAD^^{tree} && test_must_fail git fsck ' -test_expect_success 'upload-pack fails due to error in rev-list' ' +test_expect_success 'upload-pack fails due to error in rev-list' ' printf "%04xwant %s\n%04xshallow %s00000009done\n0000" \ $(($hexsz + 10)) $(git rev-parse HEAD) \ $(($hexsz + 12)) $(git rev-parse HEAD^) >input && @@ -59,7 +48,6 @@ test_expect_success 'upload-pack fails due to error in rev-list' ' ' test_expect_success 'upload-pack fails due to bad want (no object)' ' - printf "%04xwant %s multi_ack_detailed\n00000009done\n0000" \ $(($hexsz + 29)) $(test_oid deadbeef) >input && test_must_fail git upload-pack . <input >output 2>output.err && @@ -69,7 +57,6 @@ test_expect_success 'upload-pack fails due to bad want (no object)' ' ' test_expect_success 'upload-pack fails due to bad want (not tip)' ' - oid=$(echo an object we have | git hash-object -w --stdin) && printf "%04xwant %s multi_ack_detailed\n00000009done\n0000" \ $(($hexsz + 29)) "$oid" >input && @@ -80,7 +67,6 @@ test_expect_success 'upload-pack fails due to bad want (not tip)' ' ' test_expect_success 'upload-pack fails due to error in pack-objects enumeration' ' - printf "%04xwant %s\n00000009done\n0000" \ $((hexsz + 10)) $(git rev-parse HEAD) >input && test_must_fail git upload-pack . <input >/dev/null 2>output.err && @@ -105,18 +91,48 @@ test_expect_success 'upload-pack tolerates EOF just after stateless client wants test_cmp expect actual ' -test_expect_success 'create empty repository' ' - - mkdir foo && - cd foo && - git init - -' - test_expect_success 'fetch fails' ' + git init foo && + test_must_fail git -C foo fetch .. main +' - test_must_fail git fetch .. main +test_expect_success 'upload-pack ACKs repeated non-commit objects repeatedly (protocol v0)' ' + commit_id=$(git rev-parse HEAD) && + tree_id=$(git rev-parse HEAD^{tree}) && + test-tool pkt-line pack >request <<-EOF && + want $commit_id + 0000 + have $tree_id + have $tree_id + 0000 + EOF + git upload-pack --stateless-rpc . <request >actual && + depacketize <actual >actual.raw && + grep ^ACK actual.raw >actual.acks && + cat >expect <<-EOF && + ACK $tree_id + ACK $tree_id + EOF + test_cmp expect actual.acks +' +test_expect_success 'upload-pack ACKs repeated non-commit objects once only (protocol v2)' ' + commit_id=$(git rev-parse HEAD) && + tree_id=$(git rev-parse HEAD^{tree}) && + test-tool pkt-line pack >request <<-EOF && + command=fetch + object-format=$(test_oid algo) + 0001 + want $commit_id + have $tree_id + have $tree_id + 0000 + EOF + GIT_PROTOCOL=version=2 git upload-pack . <request >actual && + depacketize <actual >actual.raw && + grep ^ACK actual.raw >actual.acks && + echo "ACK $tree_id" >expect && + test_cmp expect actual.acks ' test_done diff --git a/t/t5564-http-proxy.sh b/t/t5564-http-proxy.sh index b27e481f95..c3903faf2d 100755 --- a/t/t5564-http-proxy.sh +++ b/t/t5564-http-proxy.sh @@ -72,7 +72,9 @@ test_expect_success SOCKS_PROXY 'clone via Unix socket' ' test_when_finished "rm -rf clone" && test_config_global http.proxy "socks4://localhost$PWD/%2530.sock" && { { - GIT_TRACE_CURL=$PWD/trace git clone "$HTTPD_URL/smart/repo.git" clone 2>err && + GIT_TRACE_CURL=$PWD/trace \ + GIT_TRACE_CURL_COMPONENTS=socks \ + git clone "$HTTPD_URL/smart/repo.git" clone 2>err && grep -i "SOCKS4 request granted" trace } || old_libcurl_error err diff --git a/t/t6137-pathspec-wildcards-literal.sh b/t/t6137-pathspec-wildcards-literal.sh index 20abad5667..17a03085ef 100755 --- a/t/t6137-pathspec-wildcards-literal.sh +++ b/t/t6137-pathspec-wildcards-literal.sh @@ -3,8 +3,8 @@ test_description='test wildcards and literals with git add/commit (subshell styl . ./test-lib.sh -test_have_prereq FUNNYNAMES || { - skip_all='skipping: needs FUNNYNAMES (non-Windows only)' +test_have_prereq BSLASHPSPEC || { + skip_all='skipping: needs BSLASHPSPEC (backslashes in pathspecs)' test_done } @@ -184,7 +184,7 @@ test_expect_success 'add wildcard f?z' ' ) ' -test_expect_success 'add literal \? literal' ' +test_expect_success 'add literal \?' ' git init test-q-lit && ( cd test-q-lit && @@ -241,7 +241,7 @@ test_expect_success 'add literal hello\?world' ' ) ' -test_expect_success 'add literal [abc]' ' +test_expect_success 'add literal \[abc\]' ' git init test-brackets-lit && ( cd test-brackets-lit && @@ -280,7 +280,7 @@ test_expect_success 'commit: wildcard *' ' ) ' -test_expect_success 'commit: literal *' ' +test_expect_success 'commit: literal \*' ' git init test-c-asterisk-lit && ( cd test-c-asterisk-lit && @@ -328,7 +328,7 @@ test_expect_success 'commit: literal f\*' ' ) ' -test_expect_success 'commit: wildcard pathspec limits commit' ' +test_expect_success 'commit: wildcard f**' ' git init test-c-pathlimit && ( cd test-c-pathlimit && diff --git a/t/t6423-merge-rename-directories.sh b/t/t6423-merge-rename-directories.sh index f48ed6d035..533ac85dc8 100755 --- a/t/t6423-merge-rename-directories.sh +++ b/t/t6423-merge-rename-directories.sh @@ -4731,7 +4731,7 @@ test_setup_12i () { mkdir -p source/subdir && echo foo >source/subdir/foo && - echo bar >source/bar && + printf "%d\n" 1 2 3 4 5 6 7 >source/bar && echo baz >source/baz && git add source && git commit -m orig && @@ -4747,6 +4747,7 @@ test_setup_12i () { git switch B && git mv source/bar source/subdir/bar && echo more baz >>source/baz && + git add source/baz && git commit -m B ) } @@ -4758,6 +4759,88 @@ test_expect_success '12i: Directory rename causes rename-to-self' ' git checkout A^0 && + # NOTE: A potentially better resolution would be for + # source/bar -> source/subdir/bar + # to use the directory rename to become + # source/bar -> source/bar + # (a rename to self), and thus we end up with bar with + # a path conflict (given merge.directoryRenames=conflict). + # However, since the relevant renames optimization + # prevents us from noticing + # source/bar -> source/subdir/bar + # as a rename and looking at it just as + # delete source/bar + # add source/subdir/bar + # the directory rename of source/subdir/bar -> source/bar does + # not look like a rename-to-self situation but a + # rename-on-top-of-other-file situation. We do not want + # stage 1 entries from an unrelated file, so we expect an + # error about there being a file in the way. + + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 >out && + + grep "CONFLICT (implicit dir rename).*source/bar in the way" out && + test_path_is_missing source/bar && + test_path_is_file source/subdir/bar && + test_path_is_file source/baz && + + git ls-files >actual && + uniq <actual >tracked && + test_line_count = 3 tracked && + + git status --porcelain -uno >actual && + cat >expect <<-\EOF && + M source/baz + R source/bar -> source/subdir/bar + EOF + test_cmp expect actual + ) +' + +# Testcase 12i2, Identical to 12i except that source/subdir/bar modified on unrenamed side +# Commit O: source/{subdir/foo, bar, baz_1} +# Commit A: source/{foo, bar_2, baz_1} +# Commit B: source/{subdir/{foo, bar}, baz_2} +# Expected: source/{foo, bar, baz_2}, with conflicts on +# source/bar vs. source/subdir/bar + +test_setup_12i2 () { + git init 12i2 && + ( + cd 12i2 && + + mkdir -p source/subdir && + echo foo >source/subdir/foo && + printf "%d\n" 1 2 3 4 5 6 7 >source/bar && + echo baz >source/baz && + git add source && + git commit -m orig && + + git branch O && + git branch A && + git branch B && + + git switch A && + git mv source/subdir/foo source/foo && + echo 8 >> source/bar && + git add source/bar && + git commit -m A && + + git switch B && + git mv source/bar source/subdir/bar && + echo more baz >>source/baz && + git add source/baz && + git commit -m B + ) +} + +test_expect_success '12i2: Directory rename causes rename-to-self' ' + test_setup_12i2 && + ( + cd 12i2 && + + git checkout A^0 && + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 && test_path_is_missing source/subdir && @@ -4771,7 +4854,7 @@ test_expect_success '12i: Directory rename causes rename-to-self' ' git status --porcelain -uno >actual && cat >expect <<-\EOF && UU source/bar - M source/baz + M source/baz EOF test_cmp expect actual ) @@ -4806,6 +4889,7 @@ test_setup_12j () { git switch B && git mv bar subdir/bar && echo more baz >>baz && + git add baz && git commit -m B ) } @@ -4817,10 +4901,29 @@ test_expect_success '12j: Directory rename to root causes rename-to-self' ' git checkout A^0 && - test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 && - - test_path_is_missing subdir && - test_path_is_file bar && + # NOTE: A potentially better resolution would be for + # bar -> subdir/bar + # to use the directory rename to become + # bar -> bar + # (a rename to self), and thus we end up with bar with + # a path conflict (given merge.directoryRenames=conflict). + # However, since the relevant renames optimization + # prevents us from noticing + # bar -> subdir/bar + # as a rename and looking at it just as + # delete bar + # add subdir/bar + # the directory rename of subdir/bar -> bar does not look + # like a rename-to-self situation but a + # rename-on-top-of-other-file situation. We do not want + # stage 1 entries from an unrelated file, so we expect an + # error about there being a file in the way. + + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 >out && + grep "CONFLICT (implicit dir rename).*bar in the way" out && + + test_path_is_missing bar && + test_path_is_file subdir/bar && test_path_is_file baz && git ls-files >actual && @@ -4829,8 +4932,8 @@ test_expect_success '12j: Directory rename to root causes rename-to-self' ' git status --porcelain -uno >actual && cat >expect <<-\EOF && - UU bar - M baz + M baz + R bar -> subdir/bar EOF test_cmp expect actual ) @@ -4865,6 +4968,7 @@ test_setup_12k () { git switch B && git mv dirA/bar dirB/bar && echo more baz >>dirA/baz && + git add dirA/baz && git commit -m B ) } @@ -4876,10 +4980,29 @@ test_expect_success '12k: Directory rename with sibling causes rename-to-self' ' git checkout A^0 && - test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 && - - test_path_is_missing dirB && - test_path_is_file dirA/bar && + # NOTE: A potentially better resolution would be for + # dirA/bar -> dirB/bar + # to use the directory rename (dirB/ -> dirA/) to become + # dirA/bar -> dirA/bar + # (a rename to self), and thus we end up with bar with + # a path conflict (given merge.directoryRenames=conflict). + # However, since the relevant renames optimization + # prevents us from noticing + # dirA/bar -> dirB/bar + # as a rename and looking at it just as + # delete dirA/bar + # add dirB/bar + # the directory rename of dirA/bar -> dirB/bar does + # not look like a rename-to-self situation but a + # rename-on-top-of-other-file situation. We do not want + # stage 1 entries from an unrelated file, so we expect an + # error about there being a file in the way. + + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 >out && + grep "CONFLICT (implicit dir rename).*dirA/bar in the way" out && + + test_path_is_missing dirA/bar && + test_path_is_file dirB/bar && test_path_is_file dirA/baz && git ls-files >actual && @@ -4888,8 +5011,8 @@ test_expect_success '12k: Directory rename with sibling causes rename-to-self' ' git status --porcelain -uno >actual && cat >expect <<-\EOF && - UU dirA/bar - M dirA/baz + M dirA/baz + R dirA/bar -> dirB/bar EOF test_cmp expect actual ) @@ -5056,6 +5179,25 @@ test_expect_success '12m: Change parent of renamed-dir to symlink on other side' ) ' +# Testcase 12n, Directory rename transitively makes rename back to self +# +# (Since this is a cherry-pick instead of merge, the labels are a bit weird. +# O, the original commit, is A~1 rather than what branch O points to.) +# +# Commit O: tools/hello +# world +# Commit A: tools/hello +# tools/world +# Commit B: hello +# In words: +# A: world -> tools/world +# B: tools/ -> /, i.e. rename all of tools to toplevel directory +# delete world +# +# Expected: +# CONFLICT (file location): tools/world vs. world +# + test_setup_12n () { git init 12n && ( @@ -5092,10 +5234,357 @@ test_expect_success '12n: Directory rename transitively makes rename back to sel git checkout -q B^0 && test_must_fail git cherry-pick A^0 >out && - grep "CONFLICT (file location).*should perhaps be moved" out + test_grep "CONFLICT (file location).*should perhaps be moved" out && + + # Should have 1 entry for hello, and 2 for world + test_stdout_line_count = 3 git ls-files -s && + test_stdout_line_count = 1 git ls-files -s hello && + test_stdout_line_count = 2 git ls-files -s world + ) +' + +# Testcase 12n2, Directory rename transitively makes rename back to self +# +# Commit O: tools/hello +# world +# Commit A: tools/hello +# tools/world +# Commit B: hello +# In words: +# A: world -> tools/world +# B: tools/ -> /, i.e. rename all of tools to toplevel directory +# delete world +# +# Expected: +# CONFLICT (file location): tools/world vs. world +# + +test_setup_12n2 () { + git init 12n2 && + ( + cd 12n2 && + + mkdir tools && + echo hello >tools/hello && + git add tools/hello && + echo world >world && + git add world && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git switch A && + git mv world tools/world && + git commit -m "Move world into tools/" && + + git switch B && + git mv tools/hello hello && + git rm world && + git commit -m "Move hello from tools/ to toplevel" + ) +} + +test_expect_success '12n2: Directory rename transitively makes rename back to self' ' + test_setup_12n2 && + ( + cd 12n2 && + + git checkout -q B^0 && + + test_might_fail git -c merge.directoryRenames=true merge A^0 >out && + + # Should have 1 entry for hello, and either 0 or 2 for world + # + # NOTE: Since merge.directoryRenames=true, there is no path + # conflict for world vs. tools/world; it should end up at + # world. The fact that world was unmodified on side A, means + # there was no content conflict; we should just take the + # content from side B -- i.e. delete the file. So merging + # could just delete world. + # + # However, rename-to-self-via-directory-rename is a bit more + # challenging. Relax this test to allow world to be treated + # as a modify/delete conflict as well, meaning it will have + # two higher order stages, that just so happen to match. + # + test_stdout_line_count = 1 git ls-files -s hello && + test_stdout_line_count = 2 git ls-files -s world && + test_grep "CONFLICT (modify/delete).*world deleted in HEAD" out ) ' +# Testcase 12o, Directory rename hits other rename source; file still in way on same side +# Commit O: A/file1_1 +# A/stuff +# B/file1_2 +# B/stuff +# C/other +# Commit A: A/file1_1 +# A/stuff +# B/stuff +# C/file1_2 +# C/other +# Commit B: D/file2_1 +# A/stuff +# B/file1_2 +# B/stuff +# A/other +# In words: +# A: rename B/file1_2 -> C/file1_2 +# B: rename C/ -> A/ +# rename A/file1_1 -> D/file2_1 +# Rationale: +# A/stuff is unmodified, it shows up in final output +# B/stuff is unmodified, it shows up in final output +# C/other touched on one side (rename to A), so A/other shows up in output +# A/file1 is renamed to D/file2 +# B/file1 -> C/file1 and even though C/ -> A/, A/file1 is +# "in the way" so we don't do the directory rename +# Expected: A/stuff +# B/stuff +# A/other +# D/file2 +# C/file1 +# + CONFLICT (implicit dir rename): A/file1 in way of C/file1 +# + +test_setup_12o () { + git init 12o && + ( + cd 12o && + + mkdir -p A B C && + echo 1 >A/file1 && + echo 2 >B/file1 && + echo other >C/other && + echo Astuff >A/stuff && + echo Bstuff >B/stuff && + git add . && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git switch A && + git mv B/file1 C/ && + git add . && + git commit -m "A" && + + git switch B && + mkdir -p D && + git mv A/file1 D/file2 && + git mv C/other A/other && + git add . && + git commit -m "B" + ) +} + +test_expect_success '12o: Directory rename hits other rename source; file still in way on same side' ' + test_setup_12o && + ( + cd 12o && + + git checkout -q A^0 && + + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 >out && + + test_stdout_line_count = 5 git ls-files -s && + test_stdout_line_count = 1 git ls-files -s A/other && + test_stdout_line_count = 1 git ls-files -s A/stuff && + test_stdout_line_count = 1 git ls-files -s B/stuff && + test_stdout_line_count = 1 git ls-files -s D/file2 && + + grep "CONFLICT (implicit dir rename).*Existing file/dir at A/file1 in the way" out && + test_stdout_line_count = 1 git ls-files -s C/file1 + ) +' + +# Testcase 12p, Directory rename hits other rename source; file still in way on other side +# Commit O: A/file1_1 +# A/stuff +# B/file1_2 +# B/stuff +# C/other +# Commit A: D/file2_1 +# A/stuff +# B/stuff +# C/file1_2 +# C/other +# Commit B: A/file1_1 +# A/stuff +# B/file1_2 +# B/stuff +# A/other +# Short version: +# A: rename A/file1_1 -> D/file2_1 +# rename B/file1_2 -> C/file1_2 +# B: Rename C/ -> A/ +# Rationale: +# A/stuff is unmodified, it shows up in final output +# B/stuff is unmodified, it shows up in final output +# C/other touched on one side (rename to A), so A/other shows up in output +# A/file1 is renamed to D/file2 +# B/file1 -> C/file1 and even though C/ -> A/, A/file1 is +# "in the way" so we don't do the directory rename +# Expected: A/stuff +# B/stuff +# A/other +# D/file2 +# C/file1 +# + CONFLICT (implicit dir rename): A/file1 in way of C/file1 +# + +test_setup_12p () { + git init 12p && + ( + cd 12p && + + mkdir -p A B C && + echo 1 >A/file1 && + echo 2 >B/file1 && + echo other >C/other && + echo Astuff >A/stuff && + echo Bstuff >B/stuff && + git add . && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git switch A && + mkdir -p D && + git mv A/file1 D/file2 && + git mv B/file1 C/ && + git add . && + git commit -m "A" && + + git switch B && + git mv C/other A/other && + git add . && + git commit -m "B" + ) +} + +test_expect_success '12p: Directory rename hits other rename source; file still in way on other side' ' + test_setup_12p && + ( + cd 12p && + + git checkout -q A^0 && + + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 >out && + + test_stdout_line_count = 5 git ls-files -s && + test_stdout_line_count = 1 git ls-files -s A/other && + test_stdout_line_count = 1 git ls-files -s A/stuff && + test_stdout_line_count = 1 git ls-files -s B/stuff && + test_stdout_line_count = 1 git ls-files -s D/file2 && + + grep "CONFLICT (implicit dir rename).*Existing file/dir at A/file1 in the way" out && + test_stdout_line_count = 1 git ls-files -s C/file1 + ) +' + +# Testcase 12q, Directory rename hits other rename source; file removed though +# Commit O: A/file1_1 +# A/stuff +# B/file1_2 +# B/stuff +# C/other +# Commit A: A/stuff +# B/stuff +# C/file1_2 +# C/other +# Commit B: D/file2_1 +# A/stuff +# B/file1_2 +# B/stuff +# A/other +# In words: +# A: delete A/file1_1, rename B/file1_2 -> C/file1_2 +# B: Rename C/ -> A/, rename A/file1_1 -> D/file2_1 +# Rationale: +# A/stuff is unmodified, it shows up in final output +# B/stuff is unmodified, it shows up in final output +# C/other touched on one side (rename to A), so A/other shows up in output +# A/file1 is rename/delete to D/file2, so two stages for D/file2 +# B/file1 -> C/file1 and even though C/ -> A/, A/file1 as a source was +# "in the way" (ish) so we don't do the directory rename +# Expected: A/stuff +# B/stuff +# A/other +# D/file2 (two stages) +# C/file1 +# + CONFLICT (implicit dir rename): A/file1 in way of C/file1 +# + CONFLICT (rename/delete): D/file2 +# + +test_setup_12q () { + git init 12q && + ( + cd 12q && + + mkdir -p A B C && + echo 1 >A/file1 && + echo 2 >B/file1 && + echo other >C/other && + echo Astuff >A/stuff && + echo Bstuff >B/stuff && + git add . && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git switch A && + git rm A/file1 && + git mv B/file1 C/ && + git add . && + git commit -m "A" && + + git switch B && + mkdir -p D && + git mv A/file1 D/file2 && + git mv C/other A/other && + git add . && + git commit -m "B" + ) +} + +test_expect_success '12q: Directory rename hits other rename source; file removed though' ' + test_setup_12q && + ( + cd 12q && + + git checkout -q A^0 && + + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 >out && + + grep "CONFLICT (rename/delete).*A/file1.*D/file2" out && + grep "CONFLICT (implicit dir rename).*Existing file/dir at A/file1 in the way" out && + + test_stdout_line_count = 6 git ls-files -s && + test_stdout_line_count = 1 git ls-files -s A/other && + test_stdout_line_count = 1 git ls-files -s A/stuff && + test_stdout_line_count = 1 git ls-files -s B/stuff && + test_stdout_line_count = 2 git ls-files -s D/file2 && + + # This is a slightly suboptimal resolution; allowing the + # rename of C/ -> A/ to affect C/file1 and further rename + # it to A/file1 would probably be preferable, but since + # A/file1 existed as the source of another rename, allowing + # the dir rename of C/file1 -> A/file1 would mean modifying + # the code so that renames do not adjust both their source + # and target paths in all cases. + ! grep "CONFLICT (file location)" out && + test_stdout_line_count = 1 git ls-files -s C/file1 + ) +' ########################################################################### # SECTION 13: Checking informational and conflict messages diff --git a/t/t7508-status.sh b/t/t7508-status.sh index cdc1d6fcc7..abad229e9d 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -717,6 +717,17 @@ test_expect_success TTY 'status -s with color.status' ' ' +test_expect_success TTY 'status -s keeps colors with -z' ' + test_when_finished "rm -f output.*" && + test_terminal git status -s -z >output.raw && + # convert back to newlines to avoid portability issues with + # test_decode_color and test_cmp, and to let us use the same expected + # output as earlier tests + tr "\0" "\n" <output.raw >output.nl && + test_decode_color <output.nl >output && + test_cmp expect output +' + cat >expect <<\EOF ## <YELLOW>main<RESET>...<CYAN>upstream<RESET> [ahead <YELLOW>1<RESET>, behind <CYAN>2<RESET>] <RED>M<RESET> dir1/modified diff --git a/t/t7528-signed-commit-ssh.sh b/t/t7528-signed-commit-ssh.sh index 0f887a3ebe..b50306b9b3 100755 --- a/t/t7528-signed-commit-ssh.sh +++ b/t/t7528-signed-commit-ssh.sh @@ -82,7 +82,7 @@ test_expect_success GPGSSH 'create signed commits' ' test_expect_success GPGSSH 'sign commits using literal public keys with ssh-agent' ' test_when_finished "test_unconfig commit.gpgsign" && test_config gpg.format ssh && - eval $(ssh-agent) && + eval $(ssh-agent -T || ssh-agent) && test_when_finished "kill ${SSH_AGENT_PID}" && test_when_finished "test_unconfig user.signingkey" && mkdir tmpdir && diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh index 611755cc13..73b78bdd88 100755 --- a/t/t7700-repack.sh +++ b/t/t7700-repack.sh @@ -838,4 +838,67 @@ test_expect_success '-n overrides repack.updateServerInfo=true' ' test_server_info_missing ' +test_expect_success 'pending objects are repacked appropriately' ' + test_when_finished rm -rf pending && + git init pending && + + ( + cd pending && + + # Commit file, a/b/c and never change them. + mkdir -p a/b && + echo singleton >file && + echo stuff >a/b/c && + echo more >a/d && + git add file a && + git commit -m "single blobs" && + + # Files a/d and a/e will not be singletons. + echo d >a/d && + echo e >a/e && + git add a && + git commit -m "more blobs" && + + # This use of a sparse index helps to force + # test that the cache-tree is walked, too. + git sparse-checkout set --sparse-index a x && + + # Create staged changes: + # * a/e now has multiple versions. + # * a/i now has only one version. + echo f >a/d && + echo h >a/e && + echo i >a/i && + git add a && + + # Stage and unstage a change to make use of + # resolve-undo cache and how that impacts fsck. + mkdir x && + echo y >x/y && + git add x && + xy=$(git rev-parse :x/y) && + git rm --cached x/y && + + # The blob for x/y must persist through repacks, + # but fsck currently ignores the REUC extension + # for finding links to the blob. + cat >expect <<-EOF && + dangling blob $xy + EOF + + # Bring the loose objects into a packfile to avoid + # leftovers in next test. Without this, the loose + # objects persist and the test succeeds for other + # reasons. + git repack -adf && + git fsck >out && + test_cmp expect out && + + # Test path walk version with pack.useSparse. + git -c pack.useSparse=true repack -adf --path-walk && + git fsck >out && + test_cmp expect out + ) +' + test_done diff --git a/unicode-width.h b/unicode-width.h index 3ffee123a0..b701129515 100644 --- a/unicode-width.h +++ b/unicode-width.h @@ -143,7 +143,8 @@ static const struct interval zero_width[] = { { 0x1A65, 0x1A6C }, { 0x1A73, 0x1A7C }, { 0x1A7F, 0x1A7F }, -{ 0x1AB0, 0x1ACE }, +{ 0x1AB0, 0x1ADD }, +{ 0x1AE0, 0x1AEB }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, { 0x1B36, 0x1B3A }, @@ -229,7 +230,7 @@ static const struct interval zero_width[] = { { 0x10D24, 0x10D27 }, { 0x10D69, 0x10D6D }, { 0x10EAB, 0x10EAC }, -{ 0x10EFC, 0x10EFF }, +{ 0x10EFA, 0x10EFF }, { 0x10F46, 0x10F50 }, { 0x10F82, 0x10F85 }, { 0x11001, 0x11001 }, @@ -306,6 +307,9 @@ static const struct interval zero_width[] = { { 0x11A59, 0x11A5B }, { 0x11A8A, 0x11A96 }, { 0x11A98, 0x11A99 }, +{ 0x11B60, 0x11B60 }, +{ 0x11B62, 0x11B64 }, +{ 0x11B66, 0x11B66 }, { 0x11C30, 0x11C36 }, { 0x11C38, 0x11C3D }, { 0x11C3F, 0x11C3F }, @@ -362,6 +366,10 @@ static const struct interval zero_width[] = { { 0x1E2EC, 0x1E2EF }, { 0x1E4EC, 0x1E4EF }, { 0x1E5EE, 0x1E5EF }, +{ 0x1E6E3, 0x1E6E3 }, +{ 0x1E6E6, 0x1E6E6 }, +{ 0x1E6EE, 0x1E6EF }, +{ 0x1E6F5, 0x1E6F5 }, { 0x1E8D0, 0x1E8D6 }, { 0x1E944, 0x1E94A }, { 0xE0001, 0xE0001 }, @@ -429,10 +437,10 @@ static const struct interval double_width[] = { { 0xFF01, 0xFF60 }, { 0xFFE0, 0xFFE6 }, { 0x16FE0, 0x16FE4 }, -{ 0x16FF0, 0x16FF1 }, -{ 0x17000, 0x187F7 }, -{ 0x18800, 0x18CD5 }, -{ 0x18CFF, 0x18D08 }, +{ 0x16FF0, 0x16FF6 }, +{ 0x17000, 0x18CD5 }, +{ 0x18CFF, 0x18D1E }, +{ 0x18D80, 0x18DF2 }, { 0x1AFF0, 0x1AFF3 }, { 0x1AFF5, 0x1AFFB }, { 0x1AFFD, 0x1AFFE }, @@ -474,7 +482,7 @@ static const struct interval double_width[] = { { 0x1F680, 0x1F6C5 }, { 0x1F6CC, 0x1F6CC }, { 0x1F6D0, 0x1F6D2 }, -{ 0x1F6D5, 0x1F6D7 }, +{ 0x1F6D5, 0x1F6D8 }, { 0x1F6DC, 0x1F6DF }, { 0x1F6EB, 0x1F6EC }, { 0x1F6F4, 0x1F6FC }, @@ -484,11 +492,12 @@ static const struct interval double_width[] = { { 0x1F93C, 0x1F945 }, { 0x1F947, 0x1F9FF }, { 0x1FA70, 0x1FA7C }, -{ 0x1FA80, 0x1FA89 }, -{ 0x1FA8F, 0x1FAC6 }, -{ 0x1FACE, 0x1FADC }, -{ 0x1FADF, 0x1FAE9 }, -{ 0x1FAF0, 0x1FAF8 }, +{ 0x1FA80, 0x1FA8A }, +{ 0x1FA8E, 0x1FAC6 }, +{ 0x1FAC8, 0x1FAC8 }, +{ 0x1FACD, 0x1FADC }, +{ 0x1FADF, 0x1FAEA }, +{ 0x1FAEF, 0x1FAF8 }, { 0x20000, 0x2FFFD }, { 0x30000, 0x3FFFD } }; diff --git a/upload-pack.c b/upload-pack.c index 4f26f6afc7..9b9b149068 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -476,20 +476,17 @@ static void create_pack_file(struct upload_pack_data *pack_data, static int do_got_oid(struct upload_pack_data *data, const struct object_id *oid) { - int we_knew_they_have = 0; struct object *o = parse_object_with_flags(the_repository, oid, PARSE_OBJECT_SKIP_HASH_CHECK | PARSE_OBJECT_DISCARD_TREE); if (!o) die("oops (%s)", oid_to_hex(oid)); + if (o->type == OBJ_COMMIT) { struct commit_list *parents; struct commit *commit = (struct commit *)o; - if (o->flags & THEY_HAVE) - we_knew_they_have = 1; - else - o->flags |= THEY_HAVE; + if (!data->oldest_have || (commit->date < data->oldest_have)) data->oldest_have = commit->date; for (parents = commit->parents; @@ -497,11 +494,13 @@ static int do_got_oid(struct upload_pack_data *data, const struct object_id *oid parents = parents->next) parents->item->object.flags |= THEY_HAVE; } - if (!we_knew_they_have) { - add_object_array(o, NULL, &data->have_obj); - return 1; - } - return 0; + + if (o->flags & THEY_HAVE) + return 0; + o->flags |= THEY_HAVE; + + add_object_array(o, NULL, &data->have_obj); + return 1; } static int got_oid(struct upload_pack_data *data, @@ -7,6 +7,7 @@ #include "git-compat-util.h" #include "gettext.h" #include "trace2.h" +#include "strbuf.h" static void vfreportf(FILE *f, const char *prefix, const char *err, va_list params) { @@ -375,14 +376,32 @@ void bug_fl(const char *file, int line, const char *fmt, ...) va_end(ap); } -NORETURN void you_still_use_that(const char *command_name) + +NORETURN void you_still_use_that(const char *command_name, const char *hint) { + struct strbuf percent_encoded = STRBUF_INIT; + strbuf_add_percentencode(&percent_encoded, + command_name, + STRBUF_ENCODE_SLASH); + + fprintf(stderr, + _("'%s' is nominated for removal.\n"), command_name); + + if (hint) + fputs(hint, stderr); + fprintf(stderr, - _("'%s' is nominated for removal.\n" - "If you still use this command, please add an extra\n" - "option, '--i-still-use-this', on the command line\n" - "and let us know you still use it by sending an e-mail\n" - "to <git@vger.kernel.org>. Thanks.\n"), - command_name); + _("If you still use this command, here's what you can do:\n" + "\n" + "- read https://git-scm.com/docs/BreakingChanges.html\n" + "- check if anyone has discussed this on the mailing\n" + " list and if they came up with something that can\n" + " help you: https://lore.kernel.org/git/?q=%s\n" + "- send an email to <git@vger.kernel.org> to let us\n" + " know that you still use this command and were unable\n" + " to determine a suitable replacement\n" + "\n"), + percent_encoded.buf); + strbuf_release(&percent_encoded); die(_("refusing to run without --i-still-use-this")); } diff --git a/wt-status.c b/wt-status.c index 454601afa1..d6917f0a83 100644 --- a/wt-status.c +++ b/wt-status.c @@ -2051,13 +2051,13 @@ static void wt_shortstatus_status(struct string_list_item *it, static void wt_shortstatus_other(struct string_list_item *it, struct wt_status *s, const char *sign) { + color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", sign); if (s->null_termination) { - fprintf(s->fp, "%s %s%c", sign, it->string, 0); + fprintf(s->fp, " %s%c", it->string, 0); } else { struct strbuf onebuf = STRBUF_INIT; const char *one; one = quote_path(it->string, s->prefix, &onebuf, QUOTE_PATH_QUOTE_SP); - color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", sign); fprintf(s->fp, " %s\n", one); strbuf_release(&onebuf); } diff --git a/xdiff-interface.h b/xdiff-interface.h index 1ed430b622..dfc55daddf 100644 --- a/xdiff-interface.h +++ b/xdiff-interface.h @@ -28,9 +28,9 @@ * from an error internal to xdiff, xdiff itself will see that * non-zero return and translate it to -1. * - * See "diff_grep" in diffcore-pickaxe.c for a trick to work around - * this, i.e. using the "consume_callback_data" to note the desired - * early return. + * See "diff_grep" in diffcore-pickaxe.c and "quick_consume" in diff.c + * for a trick to work around this, i.e. using the "consume_callback_data" + * to note the desired early return. */ typedef int (*xdiff_emit_line_fn)(void *, char *, unsigned long); typedef void (*xdiff_emit_hunk_fn)(void *data, |
