diff options
250 files changed, 7380 insertions, 2590 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index aef6643648..5f2f884b92 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -123,6 +123,7 @@ jobs: runs-on: windows-latest needs: [windows-build] strategy: + fail-fast: false matrix: nr: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] steps: @@ -227,6 +228,7 @@ jobs: runs-on: windows-latest needs: [vs-build, windows-build] strategy: + fail-fast: false matrix: nr: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] steps: @@ -272,6 +274,7 @@ jobs: needs: ci-config if: needs.ci-config.outputs.enabled == 'yes' strategy: + fail-fast: false matrix: vector: - jobname: linux-clang @@ -286,7 +289,7 @@ jobs: - jobname: osx-gcc cc: gcc pool: macos-latest - - jobname: GETTEXT_POISON + - jobname: linux-gcc-default cc: gcc pool: ubuntu-latest env: @@ -309,6 +312,7 @@ jobs: needs: ci-config if: needs.ci-config.outputs.enabled == 'yes' strategy: + fail-fast: false matrix: vector: - jobname: linux-musl @@ -336,7 +340,7 @@ jobs: if: needs.ci-config.outputs.enabled == 'yes' env: jobname: StaticAnalysis - runs-on: ubuntu-latest + runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v1 - run: ci/install-dependencies.sh diff --git a/.travis.yml b/.travis.yml index 05f3e3f8d7..908330a0a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,7 @@ compiler: matrix: include: - - env: jobname=GETTEXT_POISON + - env: jobname=linux-gcc-default os: linux compiler: addons: diff --git a/Documentation/MyFirstContribution.txt b/Documentation/MyFirstContribution.txt index 7c9a037cc2..af0a9da62e 100644 --- a/Documentation/MyFirstContribution.txt +++ b/Documentation/MyFirstContribution.txt @@ -664,7 +664,7 @@ mention the right animal somewhere: ---- test_expect_success 'runs correctly with no args and good output' ' git psuh >actual && - test_i18ngrep Pony actual + grep Pony actual ' ---- diff --git a/Documentation/RelNotes/2.30.1.txt b/Documentation/RelNotes/2.30.1.txt new file mode 100644 index 0000000000..249ef1492f --- /dev/null +++ b/Documentation/RelNotes/2.30.1.txt @@ -0,0 +1,55 @@ +Git v2.30.1 Release Notes +========================= + +This release is primarily to merge fixes accumulated on the 'master' +front to prepare for 2.31 release that are still relevant to 2.30.x +maintenance track. + +Fixes since v2.30 +----------------- + + * "git fetch --recurse-submodules" failed to update a submodule + when it has an uninitialized (hence of no interest to the user) + sub-submodule, which has been corrected. + + * Command line error of "git rebase" are diagnosed earlier. + + * "git stash" did not work well in a sparsely checked out working + tree. + + * Some tests expect that "ls -l" output has either '-' or 'x' for + group executable bit, but setgid bit can be inherited from parent + directory and make these fields 'S' or 's' instead, causing test + failures. + + * "git for-each-repo --config=<var> <cmd>" should not run <cmd> for + any repository when the configuration variable <var> is not defined + even once. + + * "git mergetool --tool-help" was broken in 2.29 and failed to list + all the available tools. + + * Fix for procedure to building CI test environment for mac. + + * Newline characters in the host and path part of git:// URL are + now forbidden. + + * When more than one commit with the same patch ID appears on one + side, "git log --cherry-pick A...B" did not exclude them all when a + commit with the same patch ID appears on the other side. Now it + does. + + * Documentation for "git fsck" lost stale bits that has become + incorrect. + + * Doc for packfile URI feature has been clarified. + + * The implementation of "git branch --sort" wrt the detached HEAD + display has always been hacky, which has been cleaned up. + + * Our setting of GitHub CI test jobs were a bit too eager to give up + once there is even one failure found. Tweak the knob to allow + other jobs keep running even when we see a failure, so that we can + find more failures in a single run. + +Also contains minor documentation updates and code clean-ups. diff --git a/Documentation/RelNotes/2.31.0.txt b/Documentation/RelNotes/2.31.0.txt index 7f53db7de9..175a644f82 100644 --- a/Documentation/RelNotes/2.31.0.txt +++ b/Documentation/RelNotes/2.31.0.txt @@ -14,6 +14,8 @@ Backward incompatible and other important changes * The development community has adopted Contributor Covenant v2.0 to update from v1.4 that we have been using. + * The support for deprecated PCRE1 library has been dropped. + UI, Workflows & Features @@ -48,6 +50,42 @@ UI, Workflows & Features standard input. Also, it now does not lose refs whey they point at the same object. + * "git log" learned a new "--diff-merges=<how>" option. + + * "git ls-files" can and does show multiple entries when the index is + unmerged, which is a source for confusion unless -s/-u option is in + use. A new option --deduplicate has been introduced. + + * `git worktree list` now annotates worktrees as prunable, shows + locked and prunable attributes in --porcelain mode, and gained + a --verbose option. + + * "git clone" tries to locally check out the branch pointed at by + HEAD of the remote repository after it is done, but the protocol + did not convey the information necessary to do so when copying an + empty repository. The protocol v2 learned how to do so. + + * There are other ways than ".." for a single token to denote a + "commit range", namely "<rev>^!" and "<rev>^-<n>", but "git + range-diff" did not understand them. + + * The "git range-diff" command learned "--(left|right)-only" option + to show only one side of the compared range. + + * "git mergetool" feeds three versions (base, local and remote) of + a conflicted path unmodified. The command learned to optionally + prepare these files with unconflicted parts already resolved. + + * The .mailmap is documented to be read only from the root level of a + working tree, but a stray file in a bare repository also was read + by accident, which has been corrected. + + * "git maintenance" tool learned a new "pack-refs" maintenance task. + + * The error message given when a configuration variable that is + expected to have a boolean value has been improved. + + Performance, Internal Implementation, Development Support etc. * A 3-year old test that was not testing anything useful has been @@ -75,45 +113,92 @@ Performance, Internal Implementation, Development Support etc. * "git fetch" learns to treat ref updates atomically in all-or-none fashion, just like "git push" does, with the new "--atomic" option. + * The peel_ref() API has been replaced with peel_iterated_oid(). + + * The .use_shell flag in struct child_process that is passed to + run_command() API has been clarified with a bit more documentation. + + * Document, clean-up and optimize the code around the cache-tree + extension in the index. + + * The ls-refs protocol operation has been optimized to narrow the + sub-hierarchy of refs/ it walks to produce response. + + * When removing many branches and tags, the code used to do so one + ref at a time. There is another API it can use to delete multiple + refs, and it makes quite a lot of performance difference when the + refs are packed. + + * The "pack-objects" command needs to iterate over all the tags when + automatic tag following is enabled, but it actually iterated over + all refs and then discarded everything outside "refs/tags/" + hierarchy, which was quite wasteful. + + * A perf script was made more portable. + + * Our setting of GitHub CI test jobs were a bit too eager to give up + once there is even one failure found. Tweak the knob to allow + other jobs keep running even when we see a failure, so that we can + find more failures in a single run. + + * We've carried compatibility codepaths for compilers without + variadic macros for quite some time, but the world may be ready for + them to be removed. Force compilation failure on exotic platforms + where variadic macros are not available to find out who screams in + such a way that we can easily revert if it turns out that the world + is not yet ready. + + * Code clean-up to ensure our use of hashtables using object names as + keys use the "struct object_id" objects, not the raw hash values. + + * Lose the debugging aid that may have been useful in the past, but + no longer is, in the "grep" codepaths. + + * Some pretty-format specifiers do not need the data in commit object + (e.g. "%H"), but we were over-eager to load and parse it, which has + been made even lazier. + + * Get rid of "GETTEXT_POISON" support altogether, which may or may + not be controversial. + + * Introduce an on-disk file to record revindex for packdata, which + traditionally was always created on the fly and only in-core. + + * The commit-graph learned to use corrected commit dates instead of + the generation number to help topological revision traversal. + + * Piecemeal of rewrite of "git bisect" in C continues. + Fixes since v2.30 ----------------- * Diagnose command line error of "git rebase" early. - (merge ca5120c339 rs/rebase-commit-validation later to maint). * Clean up option descriptions in "git cmd --help". - (merge e73fe3dd02 zh/arg-help-format later to maint). * "git stash" did not work well in a sparsely checked out working tree. - (merge ba359fd507 en/stash-apply-sparse-checkout later to maint). * Some tests expect that "ls -l" output has either '-' or 'x' for group executable bit, but setgid bit can be inherited from parent directory and make these fields 'S' or 's' instead, causing test failures. - (merge ea8bbf2a4e mt/t4129-with-setgid-dir later to maint). * "git for-each-repo --config=<var> <cmd>" should not run <cmd> for any repository when the configuration variable <var> is not defined even once. - (merge 6c62f01552 ds/for-each-repo-noopfix later to maint). * Fix 2.29 regression where "git mergetool --tool-help" fails to list all the available tools. - (merge 80f5a16798 pb/mergetool-tool-help-fix later to maint). * Fix for procedure to building CI test environment for mac. - (merge 3831132ace jc/macos-install-dependencies-fix later to maint). * The implementation of "git branch --sort" wrt the detached HEAD display has always been hacky, which has been cleaned up. - (merge 4045f659bd ab/branch-sort later to maint). * Newline characters in the host and path part of git:// URL are now forbidden. - (merge 6aed56736b jk/forbid-lf-in-git-url later to maint). * "git diff" showed a submodule working tree with untracked cruft as "Submodule commit <objectname>-dirty", but a natural expectation is @@ -125,24 +210,62 @@ Fixes since v2.30 side, "git log --cherry-pick A...B" did not exclude them all when a commit with the same patch ID appears on the other side. Now it does. - (merge c9e3a4e76d jk/log-cherry-pick-duplicate-patches later to maint). + + * Documentation for "git fsck" lost stale bits that has become + incorrect. + + * Doc fix for packfile URI feature. + + * When "git rebase -i" processes "fixup" insn, there is no reason to + clean up the commit log message, but we did the usual stripspace + processing. This has been corrected. + (merge f7d42ceec5 js/rebase-i-commit-cleanup-fix later to maint). + + * Fix in passing custom args from "git clone" to "upload-pack" on the + other side. + (merge ad6b5fefbd jv/upload-pack-filter-spec-quotefix later to maint). + + * The command line completion (in contrib/) completed "git branch -d" + with branch names, but "git branch -D" offered tagnames in addition, + which has been corrected. "git branch -M" had the same problem. + (merge 27dc071b9a jk/complete-branch-force-delete later to maint). + + * When commands are started from a subdirectory, they may have to + compare the path to the subdirectory (called prefix and found out + from $(pwd)) with the tracked paths. On macOS, $(pwd) and + readdir() yield decomposed path, while the tracked paths are + usually normalized to the precomposed form, causing mismatch. This + has been fixed by taking the same approach used to normalize the + command line arguments. + (merge 5c327502db tb/precompose-prefix-too later to maint). + + * Even though invocations of "die()" were logged to the trace2 + system, "BUG()"s were not, which has been corrected. + (merge 0a9dde4a04 jt/trace2-BUG later to maint). + + * "git grep --untracked" is meant to be "let's ALSO find in these + files on the filesystem" when looking for matches in the working + tree files, and does not make any sense if the primary search is + done against the index, or the tree objects. The "--cached" and + "--untracked" options have been marked as mutually incompatible. + (merge 0c5d83b248 mt/grep-cached-untracked later to maint). + + * Fix "git fsck --name-objects" which apparently has not been used by + anybody who is motivated enough to report breakage. + (merge e89f89361c js/fsck-name-objects-fix later to maint). + + * Avoid individual tests in t5411 from getting affected by each other + by forcing them to use separate output files during the test. + (merge 822ee894f6 jx/t5411-unique-filenames later to maint). + + * Test to make sure "git rev-parse one-thing one-thing" gives + the same thing twice (when one-thing is --since=X). + (merge a5cdca4520 ew/rev-parse-since-test later to maint). + + * When certain features (e.g. grafts) used in the repository are + incompatible with the use of the commit-graph, we used to silently + turned commit-graph off; we now tell the user what we are doing. + (merge c85eec7fc3 js/commit-graph-warning later to maint). * Other code cleanup, docfix, build fix, etc. - (merge 505a276596 pk/subsub-fetch-fix-take-2 later to maint). - (merge 33fc56253b fc/t6030-bisect-reset-removes-auxiliary-files later to maint). - (merge 7efc378205 ta/doc-typofix later to maint). - (merge 1f4e9319c7 pb/doc-modules-git-work-tree-typofix later to maint). - (merge 04f6b0a192 ma/t1300-cleanup later to maint). - (merge 7b77f5a13e ma/doc-pack-format-varint-for-sizes later to maint). - (merge cc2d43be2b nk/perf-fsmonitor-cleanup later to maint). - (merge c8302c6c00 ar/t6016-modernise later to maint). - (merge 0454986e78 jc/sign-off later to maint). - (merge 155067ab4f vv/send-email-with-less-secure-apps-access later to maint). - (merge acaabcf391 jk/t5516-deflake later to maint). - (merge a1e03535db ad/t4129-setfacl-target-fix later to maint). - (merge b356d23638 ug/doc-lose-dircache later to maint). - (merge 9371c0e9dd ab/gettext-charset-comment-fix later to maint). - (merge 52fc4f195c dl/p4-encode-after-kw-expansion later to maint). - (merge 4eb56b56e7 bc/doc-status-short later to maint). - (merge a4a1ca22ef tb/local-clone-race-doc later to maint). - (merge 6a8c89d053 ma/more-opaque-lock-file later to maint). + (merge e3f5da7e60 sg/t7800-difftool-robustify later to maint). diff --git a/Documentation/config.txt b/Documentation/config.txt index 6ba50b1104..d08e83a148 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -398,6 +398,8 @@ include::config/interactive.txt[] include::config/log.txt[] +include::config/lsrefs.txt[] + include::config/mailinfo.txt[] include::config/mailmap.txt[] diff --git a/Documentation/config/init.txt b/Documentation/config/init.txt index dc77f8c844..79c79d6617 100644 --- a/Documentation/config/init.txt +++ b/Documentation/config/init.txt @@ -4,4 +4,4 @@ init.templateDir:: init.defaultBranch:: Allows overriding the default branch name e.g. when initializing - a new repository or when cloning an empty repository. + a new repository. diff --git a/Documentation/config/lsrefs.txt b/Documentation/config/lsrefs.txt new file mode 100644 index 0000000000..adeda0f24d --- /dev/null +++ b/Documentation/config/lsrefs.txt @@ -0,0 +1,9 @@ +lsrefs.unborn:: + May be "advertise" (the default), "allow", or "ignore". If "advertise", + the server will respond to the client sending "unborn" (as described in + protocol-v2.txt) and will advertise support for this feature during the + protocol v2 capability advertisement. "allow" is the same as + "advertise" except that the server will not advertise support for this + feature; this is useful for load-balanced servers that cannot be + updated atomically (for example), since the administrator could + configure "allow", then after a delay, configure "advertise". diff --git a/Documentation/config/maintenance.txt b/Documentation/config/maintenance.txt index a5ead09e4b..18f0562131 100644 --- a/Documentation/config/maintenance.txt +++ b/Documentation/config/maintenance.txt @@ -15,8 +15,9 @@ maintenance.strategy:: * `none`: This default setting implies no task are run at any schedule. * `incremental`: This setting optimizes for performing small maintenance activities that do not delete any data. This does not schedule the `gc` - task, but runs the `prefetch` and `commit-graph` tasks hourly and the - `loose-objects` and `incremental-repack` tasks daily. + task, but runs the `prefetch` and `commit-graph` tasks hourly, the + `loose-objects` and `incremental-repack` tasks daily, and the `pack-refs` + task weekly. maintenance.<task>.enabled:: This boolean config option controls whether the maintenance task diff --git a/Documentation/config/mergetool.txt b/Documentation/config/mergetool.txt index 16a27443a3..90f76f5b9b 100644 --- a/Documentation/config/mergetool.txt +++ b/Documentation/config/mergetool.txt @@ -13,6 +13,11 @@ mergetool.<tool>.cmd:: merged; 'MERGED' contains the name of the file to which the merge tool should write the results of a successful merge. +mergetool.<tool>.hideResolved:: + Allows the user to override the global `mergetool.hideResolved` value + for a specific tool. See `mergetool.hideResolved` for the full + description. + mergetool.<tool>.trustExitCode:: For a custom merge command, specify whether the exit code of the merge command can be used to determine whether the merge was @@ -40,6 +45,16 @@ mergetool.meld.useAutoMerge:: value of `false` avoids using `--auto-merge` altogether, and is the default value. +mergetool.hideResolved:: + 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 + represent the versions of the file from before Git's conflict + resolution. This flag causes 'LOCAL' and 'REMOTE' to be overwriten so + that only the unresolved conflicts are presented to the merge tool. Can + be configured per-tool via the `mergetool.<tool>.hideResolved` + configuration variable. Defaults to `true`. + mergetool.keepBackup:: After performing a merge, the original file with conflict markers can be saved as a file with a `.orig` extension. If this variable diff --git a/Documentation/config/pack.txt b/Documentation/config/pack.txt index 837f1b1679..3da4ea98e2 100644 --- a/Documentation/config/pack.txt +++ b/Documentation/config/pack.txt @@ -133,3 +133,10 @@ pack.writeBitmapHashCache:: between an older, bitmapped pack and objects that have been pushed since the last gc). The downside is that it consumes 4 bytes per object of disk space. Defaults to true. + +pack.writeReverseIndex:: + When true, git will write a corresponding .rev file (see: + link:../technical/pack-format.html[Documentation/technical/pack-format.txt]) + for each new packfile that it writes in all places except for + linkgit:git-fast-import[1] and in the bulk checkin mechanism. + Defaults to false. diff --git a/Documentation/diff-generate-patch.txt b/Documentation/diff-generate-patch.txt index b10ff4caa6..2db8eacc3e 100644 --- a/Documentation/diff-generate-patch.txt +++ b/Documentation/diff-generate-patch.txt @@ -81,9 +81,9 @@ Combined diff format Any diff-generating command can take the `-c` or `--cc` option to produce a 'combined diff' when showing a merge. This is the default format when showing merges with linkgit:git-diff[1] or -linkgit:git-show[1]. Note also that you can give the `-m` option to any -of these commands to force generation of diffs with individual parents -of a merge. +linkgit:git-show[1]. Note also that you can give suitable +`--diff-merges` option to any of these commands to force generation of +diffs in specific format. A "combined diff" format looks like this: diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 746b144c76..e5733ccb2d 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -33,6 +33,57 @@ endif::git-diff[] show the patch by default, or to cancel the effect of `--patch`. endif::git-format-patch[] +ifdef::git-log[] +--diff-merges=(off|none|first-parent|1|separate|m|combined|c|dense-combined|cc):: +--no-diff-merges:: + Specify diff format to be used for merge commits. Default is + {diff-merges-default} unless `--first-parent` is in use, in which case + `first-parent` is the default. ++ +--diff-merges=(off|none)::: +--no-diff-merges::: + Disable output of diffs for merge commits. Useful to override + implied value. ++ +--diff-merges=first-parent::: +--diff-merges=1::: + This option makes merge commits show the full diff with + respect to the first parent only. ++ +--diff-merges=separate::: +--diff-merges=m::: +-m::: + This makes merge commits show the full diff with respect to + each of the parents. Separate log entry and diff is generated + for each parent. `-m` doesn't produce any output without `-p`. ++ +--diff-merges=combined::: +--diff-merges=c::: +-c::: + With this option, diff output for a merge commit shows the + differences from each of the parents to the merge result + simultaneously instead of showing pairwise diff between a + parent and the result one at a time. Furthermore, it lists + only files which were modified from all parents. `-c` implies + `-p`. ++ +--diff-merges=dense-combined::: +--diff-merges=cc::: +--cc::: + With this option the output produced by + `--diff-merges=combined` is further compressed by omitting + uninteresting hunks whose contents in the parents have only + two variants and the merge result picks one of them without + modification. `--cc` implies `-p`. + +--combined-all-paths:: + This flag causes combined diffs (used for merge commits) to + list the name of the file from all parents. It thus only has + effect when `--diff-merges=[dense-]combined` is in use, and + is likely only useful if filename changes are detected (i.e. + when either rename or copy detection have been requested). +endif::git-log[] + -U<n>:: --unified=<n>:: Generate diffs with <n> lines of context instead of diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt index adaa1782a8..eb815c2248 100644 --- a/Documentation/git-branch.txt +++ b/Documentation/git-branch.txt @@ -78,8 +78,8 @@ renaming. If <newbranch> exists, -M must be used to force the rename to happen. The `-c` and `-C` options have the exact same semantics as `-m` and -`-M`, except instead of the branch being renamed it along with its -config and reflog will be copied to a new name. +`-M`, except instead of the branch being renamed, it will be copied to a +new name, along with its config and reflog. With a `-d` or `-D` option, `<branchname>` will be deleted. You may specify more than one branch for deletion. If the branch currently diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt index d72d15be5b..bd596619c0 100644 --- a/Documentation/git-fsck.txt +++ b/Documentation/git-fsck.txt @@ -129,14 +129,6 @@ using 'git commit-graph verify'. See linkgit:git-commit-graph[1]. Extracted Diagnostics --------------------- -expect dangling commits - potential heads - due to lack of head information:: - You haven't specified any nodes as heads so it won't be - possible to differentiate between un-parented commits and - root nodes. - -missing sha1 directory '<dir>':: - The directory holding the sha1 objects is missing. - unreachable <type> <object>:: The <type> object <object>, isn't actually referred to directly or indirectly in any of the trees or commits seen. This can diff --git a/Documentation/git-index-pack.txt b/Documentation/git-index-pack.txt index af0c26232c..69ba904d44 100644 --- a/Documentation/git-index-pack.txt +++ b/Documentation/git-index-pack.txt @@ -9,17 +9,18 @@ git-index-pack - Build pack index file for an existing packed archive SYNOPSIS -------- [verse] -'git index-pack' [-v] [-o <index-file>] <pack-file> +'git index-pack' [-v] [-o <index-file>] [--[no-]rev-index] <pack-file> 'git index-pack' --stdin [--fix-thin] [--keep] [-v] [-o <index-file>] - [<pack-file>] + [--[no-]rev-index] [<pack-file>] DESCRIPTION ----------- Reads a packed archive (.pack) from the specified file, and -builds a pack index file (.idx) for it. The packed archive -together with the pack index can then be placed in the -objects/pack/ directory of a Git repository. +builds a pack index file (.idx) for it. Optionally writes a +reverse-index (.rev) for the specified pack. The packed +archive together with the pack index can then be placed in +the objects/pack/ directory of a Git repository. OPTIONS @@ -35,6 +36,13 @@ OPTIONS fails if the name of packed archive does not end with .pack). +--[no-]rev-index:: + When this flag is provided, generate a reverse index + (a `.rev` file) corresponding to the given pack. If + `--verify` is given, ensure that the existing + reverse index is correct. Takes precedence over + `pack.writeReverseIndex`. + --stdin:: When this flag is provided, the pack is read from stdin instead and a copy is then written to <pack-file>. If diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt index dd189a353a..1bbf865a1b 100644 --- a/Documentation/git-log.txt +++ b/Documentation/git-log.txt @@ -107,47 +107,15 @@ DIFF FORMATTING By default, `git log` does not generate any diff output. The options below can be used to show the changes made by each commit. -Note that unless one of `-c`, `--cc`, or `-m` is given, merge commits -will never show a diff, even if a diff format like `--patch` is -selected, nor will they match search options like `-S`. The exception is -when `--first-parent` is in use, in which merges are treated like normal -single-parent commits (this can be overridden by providing a -combined-diff option or with `--no-diff-merges`). - --c:: - With this option, diff output for a merge commit - shows the differences from each of the parents to the merge result - simultaneously instead of showing pairwise diff between a parent - and the result one at a time. Furthermore, it lists only files - which were modified from all parents. - ---cc:: - This flag implies the `-c` option and further compresses the - patch output by omitting uninteresting hunks whose contents in - the parents have only two variants and the merge result picks - one of them without modification. - ---combined-all-paths:: - This flag causes combined diffs (used for merge commits) to - list the name of the file from all parents. It thus only has - effect when -c or --cc are specified, and is likely only - useful if filename changes are detected (i.e. when either - rename or copy detection have been requested). - --m:: - This flag makes the merge commits show the full diff like - regular commits; for each merge parent, a separate log entry - and diff is generated. An exception is that only diff against - the first parent is shown when `--first-parent` option is given; - in that case, the output represents the changes the merge - brought _into_ the then-current branch. - ---diff-merges=off:: ---no-diff-merges:: - Disable output of diffs for merge commits (default). Useful to - override `-m`, `-c`, or `--cc`. +Note that unless one of `--diff-merges` variants (including short +`-m`, `-c`, and `--cc` options) is explicitly given, merge commits +will not show a diff, even if a diff format like `--patch` is +selected, nor will they match search options like `-S`. The exception +is when `--first-parent` is in use, in which case `first-parent` is +the default format. :git-log: 1 +:diff-merges-default: `off` include::diff-options.txt[] include::diff-generate-patch.txt[] diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt index 0a3b5265b3..6d11ab506b 100644 --- a/Documentation/git-ls-files.txt +++ b/Documentation/git-ls-files.txt @@ -13,6 +13,7 @@ SYNOPSIS (--[cached|deleted|others|ignored|stage|unmerged|killed|modified])* (-[c|d|o|i|s|u|k|m])* [--eol] + [--deduplicate] [-x <pattern>|--exclude=<pattern>] [-X <file>|--exclude-from=<file>] [--exclude-per-directory=<file>] @@ -80,6 +81,13 @@ OPTIONS \0 line termination on output and do not quote filenames. See OUTPUT below for more information. +--deduplicate:: + When only filenames are shown, suppress duplicates that may + come from having multiple stages during a merge, or giving + `--deleted` and `--modified` option at the same time. + When any of the `-t`, `--unmerged`, or `--stage` option is + in use, this option has no effect. + -x <pattern>:: --exclude=<pattern>:: Skip untracked files matching pattern. diff --git a/Documentation/git-maintenance.txt b/Documentation/git-maintenance.txt index 3b432171d6..80ddd33ceb 100644 --- a/Documentation/git-maintenance.txt +++ b/Documentation/git-maintenance.txt @@ -145,6 +145,12 @@ incremental-repack:: which is a special case that attempts to repack all pack-files into a single pack-file. +pack-refs:: + The `pack-refs` task collects the loose reference files and + collects them into a single file. This speeds up operations that + need to iterate across many references. See linkgit:git-pack-refs[1] + for more information. + OPTIONS ------- --auto:: diff --git a/Documentation/git-mergetool--lib.txt b/Documentation/git-mergetool--lib.txt index 4da9d24096..3e8f59ac0e 100644 --- a/Documentation/git-mergetool--lib.txt +++ b/Documentation/git-mergetool--lib.txt @@ -38,6 +38,10 @@ get_merge_tool_cmd:: get_merge_tool_path:: returns the custom path for a merge tool. +initialize_merge_tool:: + bring merge tool specific functions into scope so they can be used or + overridden. + run_merge_tool:: launches a merge tool given the tool name and a true/false flag to indicate whether a merge base is present. diff --git a/Documentation/git-range-diff.txt b/Documentation/git-range-diff.txt index 9701c1e5fd..fe350d7f40 100644 --- a/Documentation/git-range-diff.txt +++ b/Documentation/git-range-diff.txt @@ -10,6 +10,7 @@ SYNOPSIS [verse] 'git range-diff' [--color=[<when>]] [--no-color] [<diff-options>] [--no-dual-color] [--creation-factor=<factor>] + [--left-only | --right-only] ( <range1> <range2> | <rev1>...<rev2> | <base> <rev1> <rev2> ) DESCRIPTION @@ -28,6 +29,17 @@ Finally, the list of matching commits is shown in the order of the second commit range, with unmatched commits being inserted just after all of their ancestors have been shown. +There are three ways to specify the commit ranges: + +- `<range1> <range2>`: Either commit range can be of the form + `<base>..<rev>`, `<rev>^!` or `<rev>^-<n>`. See `SPECIFYING RANGES` + in linkgit:gitrevisions[7] for more details. + +- `<rev1>...<rev2>`. This is equivalent to + `<rev2>..<rev1> <rev1>..<rev2>`. + +- `<base> <rev1> <rev2>`: This is equivalent to `<base>..<rev1> + <base>..<rev2>`. OPTIONS ------- @@ -57,6 +69,14 @@ to revert to color all lines according to the outer diff markers See the ``Algorithm`` section below for an explanation why this is needed. +--left-only:: + Suppress commits that are missing from the first specified range + (or the "left range" when using the `<rev1>...<rev2>` format). + +--right-only:: + Suppress commits that are missing from the second specified range + (or the "right range" when using the `<rev1>...<rev2>` format). + --[no-]notes[=<ref>]:: This flag is passed to the `git log` program (see linkgit:git-log[1]) that generates the patches. diff --git a/Documentation/git-shortlog.txt b/Documentation/git-shortlog.txt index c16cc3b608..c9c7f3065c 100644 --- a/Documentation/git-shortlog.txt +++ b/Documentation/git-shortlog.txt @@ -113,6 +113,10 @@ MAPPING AUTHORS See linkgit:gitmailmap[5]. +Note that if `git shortlog` is run outside of a repository (to process +log contents on standard input), it will look for a `.mailmap` file in +the current directory. + GIT --- Part of the linkgit:git[1] suite diff --git a/Documentation/git-show.txt b/Documentation/git-show.txt index fcf528c1b3..2b1bc7288d 100644 --- a/Documentation/git-show.txt +++ b/Documentation/git-show.txt @@ -45,10 +45,13 @@ include::pretty-options.txt[] include::pretty-formats.txt[] -COMMON DIFF OPTIONS -------------------- +DIFF FORMATTING +--------------- +The options below can be used to change the way `git show` generates +diff output. :git-log: 1 +:diff-merges-default: `dense-combined` include::diff-options.txt[] include::diff-generate-patch.txt[] diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt index 31f1beb65b..f1197d641b 100644 --- a/Documentation/git-stash.txt +++ b/Documentation/git-stash.txt @@ -8,8 +8,8 @@ git-stash - Stash the changes in a dirty working directory away SYNOPSIS -------- [verse] -'git stash' list [<options>] -'git stash' show [<options>] [<stash>] +'git stash' list [<log-options>] +'git stash' show [<diff-options>] [<stash>] 'git stash' drop [-q|--quiet] [<stash>] 'git stash' ( pop | apply ) [--index] [-q|--quiet] [<stash>] 'git stash' branch <branchname> [<stash>] @@ -67,7 +67,7 @@ save [-p|--patch] [-k|--[no-]keep-index] [-u|--include-untracked] [-a|--all] [-q Instead, all non-option arguments are concatenated to form the stash message. -list [<options>]:: +list [<log-options>]:: List the stash entries that you currently have. Each 'stash entry' is listed with its name (e.g. `stash@{0}` is the latest entry, `stash@{1}` is @@ -83,7 +83,7 @@ stash@{1}: On master: 9cc0589... Add git-stash The command takes options applicable to the 'git log' command to control what is shown and how. See linkgit:git-log[1]. -show [<options>] [<stash>]:: +show [<diff-options>] [<stash>]:: Show the changes recorded in the stash entry as a diff between the stashed contents and the commit back when the stash entry was first diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt index 02a706c4c0..f1bb1fa5f5 100644 --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@ -97,8 +97,9 @@ list:: List details of each working tree. The main working tree is listed first, followed by each of the linked working trees. The output details include whether the working tree is bare, the revision currently checked out, the -branch currently checked out (or "detached HEAD" if none), and "locked" if -the worktree is locked. +branch currently checked out (or "detached HEAD" if none), "locked" if +the worktree is locked, "prunable" if the worktree can be pruned by `prune` +command. lock:: @@ -231,9 +232,14 @@ This can also be set up as the default behaviour by using the -v:: --verbose:: With `prune`, report all removals. ++ +With `list`, output additional information about worktrees (see below). --expire <time>:: With `prune`, only expire unused working trees older than `<time>`. ++ +With `list`, annotate missing working trees as prunable if they are +older than `<time>`. --reason <string>:: With `lock`, an explanation why the working tree is locked. @@ -372,13 +378,46 @@ $ git worktree list /path/to/other-linked-worktree 1234abc (detached HEAD) ------------ +The command also shows annotations for each working tree, according to its state. +These annotations are: + + * `locked`, if the working tree is locked. + * `prunable`, if the working tree can be pruned via `git worktree prune`. + +------------ +$ git worktree list +/path/to/linked-worktree abcd1234 [master] +/path/to/locked-worktreee acbd5678 (brancha) locked +/path/to/prunable-worktree 5678abc (detached HEAD) prunable +------------ + +For these annotations, a reason might also be available and this can be +seen using the verbose mode. The annotation is then moved to the next line +indented followed by the additional information. + +------------ +$ git worktree list --verbose +/path/to/linked-worktree abcd1234 [master] +/path/to/locked-worktree-no-reason abcd5678 (detached HEAD) locked +/path/to/locked-worktree-with-reason 1234abcd (brancha) + locked: working tree path is mounted on a portable device +/path/to/prunable-worktree 5678abc1 (detached HEAD) + prunable: gitdir file points to non-existent location +------------ + +Note that the annotation is moved to the next line if the additional +information is available, otherwise it stays on the same line as the +working tree itself. + Porcelain Format ~~~~~~~~~~~~~~~~ The porcelain format has a line per attribute. Attributes are listed with a label and value separated by a single space. Boolean attributes (like `bare` and `detached`) are listed as a label only, and are present only -if the value is true. The first attribute of a working tree is always -`worktree`, an empty line indicates the end of the record. For example: +if the value is true. Some attributes (like `locked`) can be listed as a label +only or with a value depending upon whether a reason is available. The first +attribute of a working tree is always `worktree`, an empty line indicates the +end of the record. For example: ------------ $ git worktree list --porcelain @@ -393,6 +432,33 @@ worktree /path/to/other-linked-worktree HEAD 1234abc1234abc1234abc1234abc1234abc1234a detached +worktree /path/to/linked-worktree-locked-no-reason +HEAD 5678abc5678abc5678abc5678abc5678abc5678c +branch refs/heads/locked-no-reason +locked + +worktree /path/to/linked-worktree-locked-with-reason +HEAD 3456def3456def3456def3456def3456def3456b +branch refs/heads/locked-with-reason +locked reason why is locked + +worktree /path/to/linked-worktree-prunable +HEAD 1233def1234def1234def1234def1234def1234b +detached +prunable gitdir file points to non-existent location + +------------ + +If the lock reason contains "unusual" characters such as newline, they +are escaped and the entire reason is quoted as explained for the +configuration variable `core.quotePath` (see linkgit:git-config[1]). +For Example: + +------------ +$ git worktree list --porcelain +... +locked "reason\nwhy is locked" +... ------------ EXAMPLES diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt index 002379056a..96cc89d157 100644 --- a/Documentation/rev-list-options.txt +++ b/Documentation/rev-list-options.txt @@ -130,6 +130,11 @@ parents) and `--max-parents=-1` (negative numbers denote no upper limit). this option allows you to ignore the individual commits brought in to your history by such a merge. +ifdef::git-log[] + This option also changes default diff format for merge commits + to `first-parent`, see `--diff-merges=first-parent` for details. +endif::git-log[] + --not:: Reverses the meaning of the '{caret}' prefix (or lack thereof) for all following revision specifiers, up to the next `--not`. diff --git a/Documentation/technical/commit-graph-format.txt b/Documentation/technical/commit-graph-format.txt index b3b58880b9..b6658eff18 100644 --- a/Documentation/technical/commit-graph-format.txt +++ b/Documentation/technical/commit-graph-format.txt @@ -4,11 +4,7 @@ Git commit graph format The Git commit graph stores a list of commit OIDs and some associated metadata, including: -- The generation number of the commit. Commits with no parents have - generation number 1; commits with parents have generation number - one more than the maximum generation number of its parents. We - reserve zero as special, and can be used to mark a generation - number invalid or as "not computed". +- The generation number of the commit. - The root tree OID. @@ -86,13 +82,33 @@ CHUNK DATA: position. If there are more than two parents, the second value has its most-significant bit on and the other bits store an array position into the Extra Edge List chunk. - * The next 8 bytes store the generation number of the commit and + * The next 8 bytes store the topological level (generation number v1) + of the commit and the commit time in seconds since EPOCH. The generation number uses the higher 30 bits of the first 4 bytes, while the commit time uses the 32 bits of the second 4 bytes, along with the lowest 2 bits of the lowest byte, storing the 33rd and 34th bit of the commit time. + Generation Data (ID: {'G', 'D', 'A', 'T' }) (N * 4 bytes) [Optional] + * This list of 4-byte values store corrected commit date offsets for the + commits, arranged in the same order as commit data chunk. + * If the corrected commit date offset cannot be stored within 31 bits, + the value has its most-significant bit on and the other bits store + the position of corrected commit date into the Generation Data Overflow + chunk. + * Generation Data chunk is present only when commit-graph file is written + by compatible versions of Git and in case of split commit-graph chains, + the topmost layer also has Generation Data chunk. + + Generation Data Overflow (ID: {'G', 'D', 'O', 'V' }) [Optional] + * This list of 8-byte values stores the corrected commit date offsets + for commits with corrected commit date offsets that cannot be + stored within 31 bits. + * Generation Data Overflow chunk is present only when Generation Data + chunk is present and atleast one corrected commit date offset cannot + be stored within 31 bits. + Extra Edge List (ID: {'E', 'D', 'G', 'E'}) [Optional] This list of 4-byte values store the second through nth parents for all octopus merges. The second parent value in the commit data stores diff --git a/Documentation/technical/commit-graph.txt b/Documentation/technical/commit-graph.txt index f14a7659aa..f05e7bda1a 100644 --- a/Documentation/technical/commit-graph.txt +++ b/Documentation/technical/commit-graph.txt @@ -38,14 +38,31 @@ A consumer may load the following info for a commit from the graph: Values 1-4 satisfy the requirements of parse_commit_gently(). -Define the "generation number" of a commit recursively as follows: +There are two definitions of generation number: +1. Corrected committer dates (generation number v2) +2. Topological levels (generation nummber v1) - * A commit with no parents (a root commit) has generation number one. +Define "corrected committer date" of a commit recursively as follows: - * A commit with at least one parent has generation number one more than - the largest generation number among its parents. + * A commit with no parents (a root commit) has corrected committer date + equal to its committer date. -Equivalently, the generation number of a commit A is one more than the + * A commit with at least one parent has corrected committer date equal to + the maximum of its commiter date and one more than the largest corrected + committer date among its parents. + + * As a special case, a root commit with timestamp zero has corrected commit + date of 1, to be able to distinguish it from GENERATION_NUMBER_ZERO + (that is, an uncomputed corrected commit date). + +Define the "topological level" of a commit recursively as follows: + + * A commit with no parents (a root commit) has topological level of one. + + * A commit with at least one parent has topological level one more than + the largest topological level among its parents. + +Equivalently, the topological level of a commit A is one more than the length of a longest path from A to a root commit. The recursive definition is easier to use for computation and observing the following property: @@ -60,6 +77,9 @@ is easier to use for computation and observing the following property: generation numbers, then we always expand the boundary commit with highest generation number and can easily detect the stopping condition. +The property applies to both versions of generation number, that is both +corrected committer dates and topological levels. + This property can be used to significantly reduce the time it takes to walk commits and determine topological relationships. Without generation numbers, the general heuristic is the following: @@ -67,7 +87,9 @@ numbers, the general heuristic is the following: If A and B are commits with commit time X and Y, respectively, and X < Y, then A _probably_ cannot reach B. -This heuristic is currently used whenever the computation is allowed to +In absence of corrected commit dates (for example, old versions of Git or +mixed generation graph chains), +this heuristic is currently used whenever the computation is allowed to violate topological relationships due to clock skew (such as "git log" with default order), but is not used when the topological order is required (such as merge base calculations, "git log --graph"). @@ -77,7 +99,7 @@ in the commit graph. We can treat these commits as having "infinite" generation number and walk until reaching commits with known generation number. -We use the macro GENERATION_NUMBER_INFINITY = 0xFFFFFFFF to mark commits not +We use the macro GENERATION_NUMBER_INFINITY to mark commits not in the commit-graph file. If a commit-graph file was written by a version of Git that did not compute generation numbers, then those commits will have generation number represented by the macro GENERATION_NUMBER_ZERO = 0. @@ -93,12 +115,12 @@ fully-computed generation numbers. Using strict inequality may result in walking a few extra commits, but the simplicity in dealing with commits with generation number *_INFINITY or *_ZERO is valuable. -We use the macro GENERATION_NUMBER_MAX = 0x3FFFFFFF to for commits whose -generation numbers are computed to be at least this value. We limit at -this value since it is the largest value that can be stored in the -commit-graph file using the 30 bits available to generation numbers. This -presents another case where a commit can have generation number equal to -that of a parent. +We use the macro GENERATION_NUMBER_V1_MAX = 0x3FFFFFFF for commits whose +topological levels (generation number v1) are computed to be at least +this value. We limit at this value since it is the largest value that +can be stored in the commit-graph file using the 30 bits available +to topological levels. This presents another case where a commit can +have generation number equal to that of a parent. Design Details -------------- @@ -267,6 +289,35 @@ The merge strategy values (2 for the size multiple, 64,000 for the maximum number of commits) could be extracted into config settings for full flexibility. +## Handling Mixed Generation Number Chains + +With the introduction of generation number v2 and generation data chunk, the +following scenario is possible: + +1. "New" Git writes a commit-graph with the corrected commit dates. +2. "Old" Git writes a split commit-graph on top without corrected commit dates. + +A naive approach of using the newest available generation number from +each layer would lead to violated expectations: the lower layer would +use corrected commit dates which are much larger than the topological +levels of the higher layer. For this reason, Git inspects the topmost +layer to see if the layer is missing corrected commit dates. In such a case +Git only uses topological level for generation numbers. + +When writing a new layer in split commit-graph, we write corrected commit +dates if the topmost layer has corrected commit dates written. This +guarantees that if a layer has corrected commit dates, all lower layers +must have corrected commit dates as well. + +When merging layers, we do not consider whether the merged layers had corrected +commit dates. Instead, the new layer will have corrected commit dates if the +layer below the new layer has corrected commit dates. + +While writing or merging layers, if the new layer is the only layer, it will +have corrected commit dates when written by compatible versions of Git. Thus, +rewriting split commit-graph as a single file (`--split=replace`) creates a +single layer with corrected commit dates. + ## Deleting graph-{hash} files After a new tip file is written, some `graph-{hash}` files may no longer diff --git a/Documentation/technical/index-format.txt b/Documentation/technical/index-format.txt index 69edf46c03..b633482b1b 100644 --- a/Documentation/technical/index-format.txt +++ b/Documentation/technical/index-format.txt @@ -26,7 +26,7 @@ Git index format Extensions are identified by signature. Optional extensions can be ignored if Git does not understand them. - Git currently supports cached tree and resolve undo extensions. + Git currently supports cache tree and resolve undo extensions. 4-byte extension signature. If the first byte is 'A'..'Z' the extension is optional and can be ignored. @@ -136,14 +136,35 @@ Git index format == Extensions -=== Cached tree - - Cached tree extension contains pre-computed hashes for trees that can - be derived from the index. It helps speed up tree object generation - from index for a new commit. - - When a path is updated in index, the path must be invalidated and - removed from tree cache. +=== Cache tree + + Since the index does not record entries for directories, the cache + entries cannot describe tree objects that already exist in the object + database for regions of the index that are unchanged from an existing + commit. The cache tree extension stores a recursive tree structure that + describes the trees that already exist and completely match sections of + the cache entries. This speeds up tree object generation from the index + for a new commit by only computing the trees that are "new" to that + commit. It also assists when comparing the index to another tree, such + as `HEAD^{tree}`, since sections of the index can be skipped when a tree + comparison demonstrates equality. + + The recursive tree structure uses nodes that store a number of cache + entries, a list of subnodes, and an object ID (OID). The OID references + the existing tree for that node, if it is known to exist. The subnodes + correspond to subdirectories that themselves have cache tree nodes. The + number of cache entries corresponds to the number of cache entries in + the index that describe paths within that tree's directory. + + The extension tracks the full directory structure in the cache tree + extension, but this is generally smaller than the full cache entry list. + + When a path is updated in index, Git invalidates all nodes of the + recursive cache tree corresponding to the parent directories of that + path. We store these tree nodes as being "invalid" by using "-1" as the + number of cache entries. Invalid nodes still store a span of index + entries, allowing Git to focus its efforts when reconstructing a full + cache tree. The signature for this extension is { 'T', 'R', 'E', 'E' }. @@ -174,7 +195,8 @@ Git index format first entry represents the root level of the repository, followed by the first subtree--let's call this A--of the root level (with its name relative to the root level), followed by the first subtree of A (with - its name relative to A), ... + its name relative to A), and so on. The specified number of subtrees + indicates when the current level of the recursive stack is complete. === Resolve undo diff --git a/Documentation/technical/pack-format.txt b/Documentation/technical/pack-format.txt index 96d2fc589f..8833b71c8b 100644 --- a/Documentation/technical/pack-format.txt +++ b/Documentation/technical/pack-format.txt @@ -274,6 +274,26 @@ Pack file entry: <+ Index checksum of all of the above. +== pack-*.rev files have the format: + + - A 4-byte magic number '0x52494458' ('RIDX'). + + - A 4-byte version identifier (= 1). + + - A 4-byte hash function identifier (= 1 for SHA-1, 2 for SHA-256). + + - A table of index positions (one per packed object, num_objects in + total, each a 4-byte unsigned integer in network order), sorted by + their corresponding offsets in the packfile. + + - A trailer, containing a: + + checksum of the corresponding packfile, and + + a checksum of all of the above. + +All 4-byte numbers are in network order. + == multi-pack-index (MIDX) files have the following format: The multi-pack-index files refer to multiple pack-files and loose objects. diff --git a/Documentation/technical/packfile-uri.txt b/Documentation/technical/packfile-uri.txt index 318713abc3..f7eabc6c76 100644 --- a/Documentation/technical/packfile-uri.txt +++ b/Documentation/technical/packfile-uri.txt @@ -37,8 +37,11 @@ at least so that we can test the client. This is the implementation: a feature, marked experimental, that allows the server to be configured by one or more `uploadpack.blobPackfileUri=<sha1> <uri>` entries. Whenever the list of objects to be sent is assembled, all such -blobs are excluded, replaced with URIs. The client will download those URIs, -expecting them to each point to packfiles containing single blobs. +blobs are excluded, replaced with URIs. As noted in "Future work" below, the +server can evolve in the future to support excluding other objects (or other +implementations of servers could be made that support excluding other objects) +without needing a protocol change, so clients should not expect that packfiles +downloaded in this way only contain single blobs. Client design ------------- diff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt index 85daeb5d9e..f772d90eaf 100644 --- a/Documentation/technical/protocol-v2.txt +++ b/Documentation/technical/protocol-v2.txt @@ -192,11 +192,20 @@ ls-refs takes in the following arguments: When specified, only references having a prefix matching one of the provided prefixes are displayed. +If the 'unborn' feature is advertised the following argument can be +included in the client's request. + + unborn + The server will send information about HEAD even if it is a symref + pointing to an unborn branch in the form "unborn HEAD + symref-target:<target>". + The output of ls-refs is as follows: output = *ref flush-pkt - ref = PKT-LINE(obj-id SP refname *(SP ref-attribute) LF) + obj-id-or-unborn = (obj-id | "unborn") + ref = PKT-LINE(obj-id-or-unborn SP refname *(SP ref-attribute) LF) ref-attribute = (symref | peeled) symref = "symref-target:" symref-target peeled = "peeled:" obj-id @@ -29,18 +29,11 @@ all:: # Perl-compatible regular expressions instead of standard or extended # POSIX regular expressions. # -# USE_LIBPCRE is a synonym for USE_LIBPCRE2, define USE_LIBPCRE1 -# instead if you'd like to use the legacy version 1 of the PCRE -# library. Support for version 1 will likely be removed in some future -# release of Git, as upstream has all but abandoned it. -# -# When using USE_LIBPCRE1, define NO_LIBPCRE1_JIT if you want to -# disable JIT even if supported by your library. +# Only libpcre version 2 is supported. USE_LIBPCRE2 is a synonym for +# USE_LIBPCRE, support for the old USE_LIBPCRE1 has been removed. # # Define LIBPCREDIR=/foo/bar if your PCRE header and library files are -# in /foo/bar/include and /foo/bar/lib directories. Which version of -# PCRE this points to determined by the USE_LIBPCRE1 and USE_LIBPCRE2 -# variables. +# in /foo/bar/include and /foo/bar/lib directories. # # Define HAVE_ALLOCA_H if you have working alloca(3) defined in that header. # @@ -722,6 +715,7 @@ TEST_BUILTINS_OBJS += test-online-cpus.o TEST_BUILTINS_OBJS += test-parse-options.o TEST_BUILTINS_OBJS += test-parse-pathspec-file.o TEST_BUILTINS_OBJS += test-path-utils.o +TEST_BUILTINS_OBJS += test-pcre2-config.o TEST_BUILTINS_OBJS += test-pkt-line.o TEST_BUILTINS_OBJS += test-prio-queue.o TEST_BUILTINS_OBJS += test-proc-receive.o @@ -777,20 +771,6 @@ BUILT_INS += git-status$X BUILT_INS += git-switch$X BUILT_INS += git-whatchanged$X -# what 'all' will build and 'install' will install in gitexecdir, -# excluding programs for built-in commands -ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS) -ALL_COMMANDS_TO_INSTALL = $(ALL_PROGRAMS) -ifeq (,$(SKIP_DASHED_BUILT_INS)) -ALL_COMMANDS_TO_INSTALL += $(BUILT_INS) -else -# git-upload-pack, git-receive-pack and git-upload-archive are special: they -# are _expected_ to be present in the `bin/` directory in their dashed form. -ALL_COMMANDS_TO_INSTALL += git-receive-pack$(X) -ALL_COMMANDS_TO_INSTALL += git-upload-archive$(X) -ALL_COMMANDS_TO_INSTALL += git-upload-pack$(X) -endif - # what 'all' will build but not install in gitexecdir OTHER_PROGRAMS = git$X @@ -874,6 +854,7 @@ LIB_OBJS += date.o LIB_OBJS += decorate.o LIB_OBJS += delta-islands.o LIB_OBJS += diff-delta.o +LIB_OBJS += diff-merges.o LIB_OBJS += diff-lib.o LIB_OBJS += diff-no-index.o LIB_OBJS += diff.o @@ -1226,6 +1207,20 @@ ifdef DEVELOPER include config.mak.dev endif +# what 'all' will build and 'install' will install in gitexecdir, +# excluding programs for built-in commands +ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS) +ALL_COMMANDS_TO_INSTALL = $(ALL_PROGRAMS) +ifeq (,$(SKIP_DASHED_BUILT_INS)) +ALL_COMMANDS_TO_INSTALL += $(BUILT_INS) +else +# git-upload-pack, git-receive-pack and git-upload-archive are special: they +# are _expected_ to be present in the `bin/` directory in their dashed form. +ALL_COMMANDS_TO_INSTALL += git-receive-pack$(X) +ALL_COMMANDS_TO_INSTALL += git-upload-archive$(X) +ALL_COMMANDS_TO_INSTALL += git-upload-pack$(X) +endif + ALL_CFLAGS = $(DEVELOPER_CFLAGS) $(CPPFLAGS) $(CFLAGS) ALL_LDFLAGS = $(LDFLAGS) @@ -1359,26 +1354,17 @@ ifdef NO_LIBGEN_H COMPAT_OBJS += compat/basename.o endif +ifdef USE_LIBPCRE1 +$(error The USE_LIBPCRE1 build option has been removed, use version 2 with USE_LIBPCRE) +endif + USE_LIBPCRE2 ?= $(USE_LIBPCRE) ifneq (,$(USE_LIBPCRE2)) - ifdef USE_LIBPCRE1 -$(error Only set USE_LIBPCRE2 (or its alias USE_LIBPCRE) or USE_LIBPCRE1, not both!) - endif - BASIC_CFLAGS += -DUSE_LIBPCRE2 EXTLIBS += -lpcre2-8 endif -ifdef USE_LIBPCRE1 - BASIC_CFLAGS += -DUSE_LIBPCRE1 - EXTLIBS += -lpcre - -ifdef NO_LIBPCRE1_JIT - BASIC_CFLAGS += -DNO_LIBPCRE1_JIT -endif -endif - ifdef LIBPCREDIR BASIC_CFLAGS += -I$(LIBPCREDIR)/include EXTLIBS += -L$(LIBPCREDIR)/$(lib) $(CC_LD_DYNPATH)$(LIBPCREDIR)/$(lib) @@ -2726,9 +2712,7 @@ GIT-BUILD-OPTIONS: FORCE @echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@+ @echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@+ @echo NO_EXPAT=\''$(subst ','\'',$(subst ','\'',$(NO_EXPAT)))'\' >>$@+ - @echo USE_LIBPCRE1=\''$(subst ','\'',$(subst ','\'',$(USE_LIBPCRE1)))'\' >>$@+ @echo USE_LIBPCRE2=\''$(subst ','\'',$(subst ','\'',$(USE_LIBPCRE2)))'\' >>$@+ - @echo NO_LIBPCRE1_JIT=\''$(subst ','\'',$(subst ','\'',$(NO_LIBPCRE1_JIT)))'\' >>$@+ @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@+ @echo NO_PTHREADS=\''$(subst ','\'',$(subst ','\'',$(NO_PTHREADS)))'\' >>$@+ @echo NO_PYTHON=\''$(subst ','\'',$(subst ','\'',$(NO_PYTHON)))'\' >>$@+ diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index 709eb713a3..d69e13335d 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -21,16 +21,15 @@ static GIT_PATH_FUNC(git_path_bisect_first_parent, "BISECT_FIRST_PARENT") static const char * const git_bisect_helper_usage[] = { N_("git bisect--helper --bisect-reset [<commit>]"), - N_("git bisect--helper --bisect-write [--no-log] <state> <revision> <good_term> <bad_term>"), - N_("git bisect--helper --bisect-check-and-set-terms <command> <good_term> <bad_term>"), N_("git bisect--helper --bisect-next-check <good_term> <bad_term> [<term>]"), N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"), N_("git bisect--helper --bisect-start [--term-{new,bad}=<term> --term-{old,good}=<term>]" " [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<paths>...]"), N_("git bisect--helper --bisect-next"), - N_("git bisect--helper --bisect-auto-next"), N_("git bisect--helper --bisect-state (bad|new) [<rev>]"), N_("git bisect--helper --bisect-state (good|old) [<rev>...]"), + N_("git bisect--helper --bisect-replay <filename>"), + N_("git bisect--helper --bisect-skip [(<rev>|<range>)...]"), NULL }; @@ -904,28 +903,148 @@ static enum bisect_error bisect_state(struct bisect_terms *terms, const char **a return bisect_auto_next(terms, NULL); } +static enum bisect_error bisect_log(void) +{ + int fd, status; + const char* filename = git_path_bisect_log(); + + if (is_empty_or_missing_file(filename)) + return error(_("We are not bisecting.")); + + fd = open(filename, O_RDONLY); + if (fd < 0) + return BISECT_FAILED; + + status = copy_fd(fd, STDOUT_FILENO); + close(fd); + return status ? BISECT_FAILED : BISECT_OK; +} + +static int process_replay_line(struct bisect_terms *terms, struct strbuf *line) +{ + const char *p = line->buf + strspn(line->buf, " \t"); + char *word_end, *rev; + + if ((!skip_prefix(p, "git bisect", &p) && + !skip_prefix(p, "git-bisect", &p)) || !isspace(*p)) + return 0; + p += strspn(p, " \t"); + + word_end = (char *)p + strcspn(p, " \t"); + rev = word_end + strspn(word_end, " \t"); + *word_end = '\0'; /* NUL-terminate the word */ + + get_terms(terms); + if (check_and_set_terms(terms, p)) + return -1; + + if (!strcmp(p, "start")) { + struct strvec argv = STRVEC_INIT; + int res; + sq_dequote_to_strvec(rev, &argv); + res = bisect_start(terms, argv.v, argv.nr); + strvec_clear(&argv); + return res; + } + + if (one_of(p, terms->term_good, + terms->term_bad, "skip", NULL)) + return bisect_write(p, rev, terms, 0); + + if (!strcmp(p, "terms")) { + struct strvec argv = STRVEC_INIT; + int res; + sq_dequote_to_strvec(rev, &argv); + res = bisect_terms(terms, argv.nr == 1 ? argv.v[0] : NULL); + strvec_clear(&argv); + return res; + } + error(_("'%s'?? what are you talking about?"), p); + + return -1; +} + +static enum bisect_error bisect_replay(struct bisect_terms *terms, const char *filename) +{ + FILE *fp = NULL; + enum bisect_error res = BISECT_OK; + struct strbuf line = STRBUF_INIT; + + if (is_empty_or_missing_file(filename)) + return error(_("cannot read file '%s' for replaying"), filename); + + if (bisect_reset(NULL)) + return BISECT_FAILED; + + fp = fopen(filename, "r"); + if (!fp) + return BISECT_FAILED; + + while ((strbuf_getline(&line, fp) != EOF) && !res) + res = process_replay_line(terms, &line); + + strbuf_release(&line); + fclose(fp); + + if (res) + return BISECT_FAILED; + + return bisect_auto_next(terms, NULL); +} + +static enum bisect_error bisect_skip(struct bisect_terms *terms, const char **argv, int argc) +{ + int i; + enum bisect_error res; + struct strvec argv_state = STRVEC_INIT; + + strvec_push(&argv_state, "skip"); + + for (i = 0; i < argc; i++) { + const char *dotdot = strstr(argv[i], ".."); + + if (dotdot) { + struct rev_info revs; + struct commit *commit; + + init_revisions(&revs, NULL); + setup_revisions(2, argv + i - 1, &revs, NULL); + + if (prepare_revision_walk(&revs)) + die(_("revision walk setup failed\n")); + while ((commit = get_revision(&revs)) != NULL) + strvec_push(&argv_state, + oid_to_hex(&commit->object.oid)); + + reset_revision_walk(); + } else { + strvec_push(&argv_state, argv[i]); + } + } + res = bisect_state(terms, argv_state.v, argv_state.nr); + + strvec_clear(&argv_state); + return res; +} + int cmd_bisect__helper(int argc, const char **argv, const char *prefix) { enum { BISECT_RESET = 1, - BISECT_WRITE, - CHECK_AND_SET_TERMS, BISECT_NEXT_CHECK, BISECT_TERMS, BISECT_START, BISECT_AUTOSTART, BISECT_NEXT, - BISECT_AUTO_NEXT, - BISECT_STATE + BISECT_STATE, + BISECT_LOG, + BISECT_REPLAY, + BISECT_SKIP } cmdmode = 0; int res = 0, nolog = 0; struct option options[] = { OPT_CMDMODE(0, "bisect-reset", &cmdmode, N_("reset the bisection state"), BISECT_RESET), - OPT_CMDMODE(0, "bisect-write", &cmdmode, - N_("write out the bisection state in BISECT_LOG"), BISECT_WRITE), - OPT_CMDMODE(0, "check-and-set-terms", &cmdmode, - N_("check and set terms in a bisection state"), CHECK_AND_SET_TERMS), OPT_CMDMODE(0, "bisect-next-check", &cmdmode, N_("check whether bad or good terms exist"), BISECT_NEXT_CHECK), OPT_CMDMODE(0, "bisect-terms", &cmdmode, @@ -934,10 +1053,14 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) N_("start the bisect session"), BISECT_START), OPT_CMDMODE(0, "bisect-next", &cmdmode, N_("find the next bisection commit"), BISECT_NEXT), - OPT_CMDMODE(0, "bisect-auto-next", &cmdmode, - N_("verify the next bisection state then checkout the next bisection commit"), BISECT_AUTO_NEXT), OPT_CMDMODE(0, "bisect-state", &cmdmode, N_("mark the state of ref (or refs)"), BISECT_STATE), + OPT_CMDMODE(0, "bisect-log", &cmdmode, + N_("list the bisection steps so far"), BISECT_LOG), + OPT_CMDMODE(0, "bisect-replay", &cmdmode, + N_("replay the bisection process from the given file"), BISECT_REPLAY), + OPT_CMDMODE(0, "bisect-skip", &cmdmode, + N_("skip some commits for checkout"), BISECT_SKIP), OPT_BOOL(0, "no-log", &nolog, N_("no log for BISECT_WRITE")), OPT_END() @@ -955,18 +1078,7 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) case BISECT_RESET: if (argc > 1) return error(_("--bisect-reset requires either no argument or a commit")); - return !!bisect_reset(argc ? argv[0] : NULL); - case BISECT_WRITE: - if (argc != 4 && argc != 5) - return error(_("--bisect-write requires either 4 or 5 arguments")); - set_terms(&terms, argv[3], argv[2]); - res = bisect_write(argv[0], argv[1], &terms, nolog); - break; - case CHECK_AND_SET_TERMS: - if (argc != 3) - return error(_("--check-and-set-terms requires 3 arguments")); - set_terms(&terms, argv[2], argv[1]); - res = check_and_set_terms(&terms, argv[0]); + res = bisect_reset(argc ? argv[0] : NULL); break; case BISECT_NEXT_CHECK: if (argc != 2 && argc != 3) @@ -989,17 +1101,26 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) get_terms(&terms); res = bisect_next(&terms, prefix); break; - case BISECT_AUTO_NEXT: - if (argc) - return error(_("--bisect-auto-next requires 0 arguments")); - get_terms(&terms); - res = bisect_auto_next(&terms, prefix); - break; case BISECT_STATE: set_terms(&terms, "bad", "good"); get_terms(&terms); res = bisect_state(&terms, argv, argc); break; + case BISECT_LOG: + if (argc) + return error(_("--bisect-log requires 0 arguments")); + res = bisect_log(); + break; + case BISECT_REPLAY: + if (argc != 1) + return error(_("no logfile given")); + set_terms(&terms, "bad", "good"); + res = bisect_replay(&terms, argv[0]); + break; + case BISECT_SKIP: + set_terms(&terms, "bad", "good"); + res = bisect_skip(&terms, argv, argc); + break; default: BUG("unknown subcommand %d", cmdmode); } diff --git a/builtin/branch.c b/builtin/branch.c index 8c0b428104..bcc00bcf18 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -202,6 +202,9 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, int remote_branch = 0; struct strbuf bname = STRBUF_INIT; unsigned allowed_interpret; + struct string_list refs_to_delete = STRING_LIST_INIT_DUP; + struct string_list_item *item; + int branch_name_pos; switch (kinds) { case FILTER_REFS_REMOTES: @@ -219,6 +222,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, default: die(_("cannot use -a with -d")); } + branch_name_pos = strcspn(fmt, "%"); if (!force) { head_rev = lookup_commit_reference(the_repository, &head_oid); @@ -265,30 +269,35 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, goto next; } - if (delete_ref(NULL, name, is_null_oid(&oid) ? NULL : &oid, - REF_NO_DEREF)) { - error(remote_branch - ? _("Error deleting remote-tracking branch '%s'") - : _("Error deleting branch '%s'"), - bname.buf); - ret = 1; - goto next; - } - if (!quiet) { - printf(remote_branch - ? _("Deleted remote-tracking branch %s (was %s).\n") - : _("Deleted branch %s (was %s).\n"), - bname.buf, - (flags & REF_ISBROKEN) ? "broken" - : (flags & REF_ISSYMREF) ? target - : find_unique_abbrev(&oid, DEFAULT_ABBREV)); - } - delete_branch_config(bname.buf); + item = string_list_append(&refs_to_delete, name); + item->util = xstrdup((flags & REF_ISBROKEN) ? "broken" + : (flags & REF_ISSYMREF) ? target + : find_unique_abbrev(&oid, DEFAULT_ABBREV)); next: free(target); } + if (delete_refs(NULL, &refs_to_delete, REF_NO_DEREF)) + ret = 1; + + for_each_string_list_item(item, &refs_to_delete) { + char *describe_ref = item->util; + char *name = item->string; + if (!ref_exists(name)) { + char *refname = name + branch_name_pos; + if (!quiet) + printf(remote_branch + ? _("Deleted remote-tracking branch %s (was %s).\n") + : _("Deleted branch %s (was %s).\n"), + name + branch_name_pos, describe_ref); + + delete_branch_config(refname); + } + free(describe_ref); + } + string_list_clear(&refs_to_delete, 0); + free(name); strbuf_release(&bname); diff --git a/builtin/checkout.c b/builtin/checkout.c index c9ba23c279..2d6550bc3c 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -821,9 +821,6 @@ static int merge_working_tree(const struct checkout_opts *opts, } } - if (!active_cache_tree) - active_cache_tree = cache_tree(); - if (!cache_tree_fully_valid(active_cache_tree)) cache_tree_update(&the_index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR); diff --git a/builtin/clone.c b/builtin/clone.c index e335734b4c..51e844a2de 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -979,7 +979,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) int err = 0, complete_refs_before_fetch = 1; int submodule_progress; - struct strvec ref_prefixes = STRVEC_INIT; + struct transport_ls_refs_options transport_ls_refs_options = + TRANSPORT_LS_REFS_OPTIONS_INIT; packet_trace_identity("clone"); @@ -1257,14 +1258,17 @@ int cmd_clone(int argc, const char **argv, const char *prefix) transport->smart_options->check_self_contained_and_connected = 1; - strvec_push(&ref_prefixes, "HEAD"); - refspec_ref_prefixes(&remote->fetch, &ref_prefixes); + strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD"); + refspec_ref_prefixes(&remote->fetch, + &transport_ls_refs_options.ref_prefixes); if (option_branch) - expand_ref_prefix(&ref_prefixes, option_branch); + expand_ref_prefix(&transport_ls_refs_options.ref_prefixes, + option_branch); if (!option_no_tags) - strvec_push(&ref_prefixes, "refs/tags/"); + strvec_push(&transport_ls_refs_options.ref_prefixes, + "refs/tags/"); - refs = transport_get_remote_refs(transport, &ref_prefixes); + refs = transport_get_remote_refs(transport, &transport_ls_refs_options); if (refs) { int hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport)); @@ -1326,8 +1330,19 @@ int cmd_clone(int argc, const char **argv, const char *prefix) remote_head = NULL; option_no_checkout = 1; if (!option_bare) { - const char *branch = git_default_branch_name(0); - char *ref = xstrfmt("refs/heads/%s", branch); + const char *branch; + char *ref; + + if (transport_ls_refs_options.unborn_head_target && + skip_prefix(transport_ls_refs_options.unborn_head_target, + "refs/heads/", &branch)) { + ref = transport_ls_refs_options.unborn_head_target; + transport_ls_refs_options.unborn_head_target = NULL; + create_symref("HEAD", ref, reflog_msg.buf); + } else { + branch = git_default_branch_name(0); + ref = xstrfmt("refs/heads/%s", branch); + } install_branch_config(0, branch, remote_name, ref); free(ref); @@ -1380,6 +1395,7 @@ cleanup: strbuf_release(&key); junk_mode = JUNK_LEAVE_ALL; - strvec_clear(&ref_prefixes); + strvec_clear(&transport_ls_refs_options.ref_prefixes); + free(transport_ls_refs_options.unborn_head_target); return err; } diff --git a/builtin/describe.c b/builtin/describe.c index 7668591d57..40482d8e9f 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -194,7 +194,7 @@ static int get_name(const char *path, const struct object_id *oid, int flag, voi } /* Is it annotated? */ - if (!peel_ref(path, &peeled)) { + if (!peel_iterated_oid(oid, &peeled)) { is_annotated = !oideq(oid, &peeled); } else { oidcpy(&peeled, oid); diff --git a/builtin/diff-files.c b/builtin/diff-files.c index 1e352dd8f7..bb85266102 100644 --- a/builtin/diff-files.c +++ b/builtin/diff-files.c @@ -7,6 +7,7 @@ #include "cache.h" #include "config.h" #include "diff.h" +#include "diff-merges.h" #include "commit.h" #include "revision.h" #include "builtin.h" @@ -35,7 +36,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) */ rev.diffopt.ita_invisible_in_index = 1; - precompose_argv(argc, argv); + prefix = precompose_argv_prefix(argc, argv, prefix); argc = setup_revisions(argc, argv, &rev, NULL); while (1 < argc && argv[1][0] == '-') { @@ -69,9 +70,9 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) * was not asked to. "diff-files -c -p" should not densify * (the user should ask with "diff-files --cc" explicitly). */ - if (rev.max_count == -1 && !rev.combine_merges && + if (rev.max_count == -1 && (rev.diffopt.output_format & DIFF_FORMAT_PATCH)) - rev.combine_merges = rev.dense_combined_merges = 1; + diff_merges_set_dense_combined_if_unset(&rev); if (read_cache_preload(&rev.diffopt.pathspec) < 0) { perror("read_cache_preload"); diff --git a/builtin/diff-index.c b/builtin/diff-index.c index 7f5281c461..c33d7af478 100644 --- a/builtin/diff-index.c +++ b/builtin/diff-index.c @@ -25,7 +25,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ repo_init_revisions(the_repository, &rev, prefix); rev.abbrev = 0; - precompose_argv(argc, argv); + prefix = precompose_argv_prefix(argc, argv, prefix); argc = setup_revisions(argc, argv, &rev, NULL); for (i = 1; i < argc; i++) { diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index 9fc95e959f..178d12f07f 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -126,7 +126,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) memset(&s_r_opt, 0, sizeof(s_r_opt)); s_r_opt.tweak = diff_tree_tweak_rev; - precompose_argv(argc, argv); + prefix = precompose_argv_prefix(argc, argv, prefix); argc = setup_revisions(argc, argv, opt, &s_r_opt); memset(&w, 0, sizeof(w)); diff --git a/builtin/diff.c b/builtin/diff.c index 780c33877f..0f4859abf7 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -13,6 +13,7 @@ #include "blob.h" #include "tag.h" #include "diff.h" +#include "diff-merges.h" #include "diffcore.h" #include "revision.h" #include "log-tree.h" @@ -216,8 +217,8 @@ static int builtin_diff_combined(struct rev_info *revs, if (argc > 1) usage(builtin_diff_usage); - if (!revs->dense_combined_merges && !revs->combine_merges) - revs->dense_combined_merges = revs->combine_merges = 1; + diff_merges_set_dense_combined_if_unset(revs); + for (i = 1; i < ents; i++) oid_array_append(&parents, &ent[i].item->oid); diff_tree_combined(&ent[0].item->oid, &parents, revs); @@ -265,9 +266,9 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv * dense one, --cc can be explicitly asked for, or just rely * on the default). */ - if (revs->max_count == -1 && !revs->combine_merges && + if (revs->max_count == -1 && (revs->diffopt.output_format & DIFF_FORMAT_PATCH)) - revs->combine_merges = revs->dense_combined_merges = 1; + diff_merges_set_dense_combined_if_unset(revs); setup_work_tree(); if (read_cache_preload(&revs->diffopt.pathspec) < 0) { @@ -452,7 +453,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) init_diff_ui_defaults(); git_config(git_diff_ui_config, NULL); - precompose_argv(argc, argv); + prefix = precompose_argv_prefix(argc, argv, prefix); repo_init_revisions(the_repository, &rev, prefix); diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index 58b7c1fbdc..c2d96f4c89 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -220,7 +220,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) version = discover_version(&reader); switch (version) { case protocol_v2: - get_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL, args.stateless_rpc); + get_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL, + args.stateless_rpc); break; case protocol_v1: case protocol_v0: diff --git a/builtin/fetch.c b/builtin/fetch.c index 91f3d20696..0b90de87c7 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -1455,7 +1455,8 @@ static int do_fetch(struct transport *transport, int autotags = (transport->remote->fetch_tags == 1); int retcode = 0; const struct ref *remote_refs; - struct strvec ref_prefixes = STRVEC_INIT; + struct transport_ls_refs_options transport_ls_refs_options = + TRANSPORT_LS_REFS_OPTIONS_INIT; int must_list_refs = 1; if (tags == TAGS_DEFAULT) { @@ -1475,7 +1476,7 @@ static int do_fetch(struct transport *transport, if (rs->nr) { int i; - refspec_ref_prefixes(rs, &ref_prefixes); + refspec_ref_prefixes(rs, &transport_ls_refs_options.ref_prefixes); /* * We can avoid listing refs if all of them are exact @@ -1489,22 +1490,25 @@ static int do_fetch(struct transport *transport, } } } else if (transport->remote && transport->remote->fetch.nr) - refspec_ref_prefixes(&transport->remote->fetch, &ref_prefixes); + refspec_ref_prefixes(&transport->remote->fetch, + &transport_ls_refs_options.ref_prefixes); if (tags == TAGS_SET || tags == TAGS_DEFAULT) { must_list_refs = 1; - if (ref_prefixes.nr) - strvec_push(&ref_prefixes, "refs/tags/"); + if (transport_ls_refs_options.ref_prefixes.nr) + strvec_push(&transport_ls_refs_options.ref_prefixes, + "refs/tags/"); } if (must_list_refs) { trace2_region_enter("fetch", "remote_refs", the_repository); - remote_refs = transport_get_remote_refs(transport, &ref_prefixes); + remote_refs = transport_get_remote_refs(transport, + &transport_ls_refs_options); trace2_region_leave("fetch", "remote_refs", the_repository); } else remote_refs = NULL; - strvec_clear(&ref_prefixes); + strvec_clear(&transport_ls_refs_options.ref_prefixes); ref_map = get_ref_map(transport->remote, remote_refs, rs, tags, &autotags); diff --git a/builtin/gc.c b/builtin/gc.c index ae34362e93..6db9cb39e6 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -54,7 +54,6 @@ static const char *prune_worktrees_expire = "3.months.ago"; static unsigned long big_pack_threshold; static unsigned long max_delta_cache_size = DEFAULT_DELTA_CACHE_SIZE; -static struct strvec pack_refs_cmd = STRVEC_INIT; static struct strvec reflog = STRVEC_INIT; static struct strvec repack = STRVEC_INIT; static struct strvec prune = STRVEC_INIT; @@ -163,6 +162,15 @@ static void gc_config(void) git_config(git_default_config, NULL); } +struct maintenance_run_opts; +static int maintenance_task_pack_refs(MAYBE_UNUSED struct maintenance_run_opts *opts) +{ + struct strvec pack_refs_cmd = STRVEC_INIT; + strvec_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL); + + return run_command_v_opt(pack_refs_cmd.v, RUN_GIT_CMD); +} + static int too_many_loose_objects(void) { /* @@ -518,8 +526,8 @@ static void gc_before_repack(void) if (done++) return; - if (pack_refs && run_command_v_opt(pack_refs_cmd.v, RUN_GIT_CMD)) - die(FAILED_RUN, pack_refs_cmd.v[0]); + if (pack_refs && maintenance_task_pack_refs(NULL)) + die(FAILED_RUN, "pack-refs"); if (prune_reflogs && run_command_v_opt(reflog.v, RUN_GIT_CMD)) die(FAILED_RUN, reflog.v[0]); @@ -556,7 +564,6 @@ int cmd_gc(int argc, const char **argv, const char *prefix) if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_gc_usage, builtin_gc_options); - strvec_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL); strvec_pushl(&reflog, "reflog", "expire", "--all", NULL); strvec_pushl(&repack, "repack", "-d", "-l", NULL); strvec_pushl(&prune, "prune", "--expire", NULL); @@ -769,7 +776,7 @@ static int dfs_on_ref(const char *refname, struct commit_list *stack = NULL; struct commit *commit; - if (!peel_ref(refname, &peeled)) + if (!peel_iterated_oid(oid, &peeled)) oid = &peeled; if (oid_object_info(the_repository, oid, NULL) != OBJ_COMMIT) return 0; @@ -897,6 +904,12 @@ static int maintenance_task_prefetch(struct maintenance_run_opts *opts) struct string_list_item *item; struct string_list remotes = STRING_LIST_INIT_DUP; + git_config_set_multivar_gently("log.excludedecoration", + "refs/prefetch/", + "refs/prefetch/", + CONFIG_FLAGS_FIXED_VALUE | + CONFIG_FLAGS_MULTI_REPLACE); + if (for_each_remote(append_remote, &remotes)) { error(_("failed to fill remotes")); result = 1; @@ -1218,6 +1231,7 @@ enum maintenance_task_label { TASK_INCREMENTAL_REPACK, TASK_GC, TASK_COMMIT_GRAPH, + TASK_PACK_REFS, /* Leave as final value */ TASK__COUNT @@ -1249,6 +1263,11 @@ static struct maintenance_task tasks[] = { maintenance_task_commit_graph, should_write_commit_graph, }, + [TASK_PACK_REFS] = { + "pack-refs", + maintenance_task_pack_refs, + NULL, + }, }; static int compare_tasks_by_selection(const void *a_, const void *b_) @@ -1333,6 +1352,8 @@ static void initialize_maintenance_strategy(void) tasks[TASK_INCREMENTAL_REPACK].schedule = SCHEDULE_DAILY; tasks[TASK_LOOSE_OBJECTS].enabled = 1; tasks[TASK_LOOSE_OBJECTS].schedule = SCHEDULE_DAILY; + tasks[TASK_PACK_REFS].enabled = 1; + tasks[TASK_PACK_REFS].schedule = SCHEDULE_WEEKLY; } } diff --git a/builtin/grep.c b/builtin/grep.c index ca259af441..e348e6bb32 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -216,8 +216,6 @@ static void start_threads(struct grep_opt *opt) int err; struct grep_opt *o = grep_opt_dup(opt); o->output = strbuf_out; - if (i) - o->debug = 0; compile_grep_patterns(o); err = pthread_create(&threads[i], NULL, run, o); @@ -936,9 +934,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) N_("indicate hit with exit status without output")), OPT_BOOL(0, "all-match", &opt.all_match, N_("show only matches from files that match all patterns")), - OPT_SET_INT_F(0, "debug", &opt.debug, - N_("show parse tree for grep expression"), - 1, PARSE_OPT_HIDDEN), OPT_GROUP(""), { OPTION_STRING, 'O', "open-files-in-pager", &show_in_pager, N_("pager"), N_("show matching files in the pager"), @@ -1157,6 +1152,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (!use_index && (untracked || cached)) die(_("--cached or --untracked cannot be used with --no-index")); + if (untracked && cached) + die(_("--untracked cannot be used with --cached")); + if (!use_index || untracked) { int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude; hit = grep_directory(&opt, &pathspec, use_exclude, use_index); diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 557bd2f348..54f74c4874 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -17,7 +17,7 @@ #include "promisor-remote.h" static const char index_pack_usage[] = -"git index-pack [-v] [-o <index-file>] [--keep | --keep=<msg>] [--verify] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])"; +"git index-pack [-v] [-o <index-file>] [--keep | --keep=<msg>] [--[no-]rev-index] [--verify] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])"; struct object_entry { struct pack_idx_entry idx; @@ -1436,15 +1436,15 @@ static void fix_unresolved_deltas(struct hashfile *f) free(sorted_by_pos); } -static const char *derive_filename(const char *pack_name, const char *suffix, - struct strbuf *buf) +static const char *derive_filename(const char *pack_name, const char *strip, + const char *suffix, struct strbuf *buf) { size_t len; - if (!strip_suffix(pack_name, ".pack", &len)) - die(_("packfile name '%s' does not end with '.pack'"), - pack_name); + if (!strip_suffix(pack_name, strip, &len) || !len || + pack_name[len - 1] != '.') + die(_("packfile name '%s' does not end with '.%s'"), + pack_name, strip); strbuf_add(buf, pack_name, len); - strbuf_addch(buf, '.'); strbuf_addstr(buf, suffix); return buf->buf; } @@ -1459,7 +1459,7 @@ static void write_special_file(const char *suffix, const char *msg, int msg_len = strlen(msg); if (pack_name) - filename = derive_filename(pack_name, suffix, &name_buf); + filename = derive_filename(pack_name, "pack", suffix, &name_buf); else filename = odb_pack_name(&name_buf, hash, suffix); @@ -1484,12 +1484,14 @@ static void write_special_file(const char *suffix, const char *msg, static void final(const char *final_pack_name, const char *curr_pack_name, const char *final_index_name, const char *curr_index_name, + const char *final_rev_index_name, const char *curr_rev_index_name, const char *keep_msg, const char *promisor_msg, unsigned char *hash) { const char *report = "pack"; struct strbuf pack_name = STRBUF_INIT; struct strbuf index_name = STRBUF_INIT; + struct strbuf rev_index_name = STRBUF_INIT; int err; if (!from_stdin) { @@ -1524,6 +1526,16 @@ static void final(const char *final_pack_name, const char *curr_pack_name, } else chmod(final_index_name, 0444); + if (curr_rev_index_name) { + if (final_rev_index_name != curr_rev_index_name) { + if (!final_rev_index_name) + final_rev_index_name = odb_pack_name(&rev_index_name, hash, "rev"); + if (finalize_object_file(curr_rev_index_name, final_rev_index_name)) + die(_("cannot store reverse index file")); + } else + chmod(final_rev_index_name, 0444); + } + if (do_fsck_object) { struct packed_git *p; p = add_packed_git(final_index_name, strlen(final_index_name), 0); @@ -1553,6 +1565,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name, } } + strbuf_release(&rev_index_name); strbuf_release(&index_name); strbuf_release(&pack_name); } @@ -1578,6 +1591,12 @@ static int git_index_pack_config(const char *k, const char *v, void *cb) } return 0; } + if (!strcmp(k, "pack.writereverseindex")) { + if (git_config_bool(k, v)) + opts->flags |= WRITE_REV; + else + opts->flags &= ~WRITE_REV; + } return git_default_config(k, v, cb); } @@ -1695,12 +1714,14 @@ static void show_pack_info(int stat_only) int cmd_index_pack(int argc, const char **argv, const char *prefix) { - int i, fix_thin_pack = 0, verify = 0, stat_only = 0; + int i, fix_thin_pack = 0, verify = 0, stat_only = 0, rev_index; const char *curr_index; - const char *index_name = NULL, *pack_name = NULL; + const char *curr_rev_index = NULL; + const char *index_name = NULL, *pack_name = NULL, *rev_index_name = NULL; const char *keep_msg = NULL; const char *promisor_msg = NULL; struct strbuf index_name_buf = STRBUF_INIT; + struct strbuf rev_index_name_buf = STRBUF_INIT; struct pack_idx_entry **idx_objects; struct pack_idx_option opts; unsigned char pack_hash[GIT_MAX_RAWSZ]; @@ -1727,6 +1748,11 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (prefix && chdir(prefix)) die(_("Cannot come back to cwd")); + if (git_env_bool(GIT_TEST_WRITE_REV_INDEX, 0)) + rev_index = 1; + else + rev_index = !!(opts.flags & (WRITE_REV_VERIFY | WRITE_REV)); + for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -1805,6 +1831,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (hash_algo == GIT_HASH_UNKNOWN) die(_("unknown hash algorithm '%s'"), arg); repo_set_hash_algo(the_repository, hash_algo); + } else if (!strcmp(arg, "--rev-index")) { + rev_index = 1; + } else if (!strcmp(arg, "--no-rev-index")) { + rev_index = 0; } else usage(index_pack_usage); continue; @@ -1824,7 +1854,16 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (from_stdin && hash_algo) die(_("--object-format cannot be used with --stdin")); if (!index_name && pack_name) - index_name = derive_filename(pack_name, "idx", &index_name_buf); + index_name = derive_filename(pack_name, "pack", "idx", &index_name_buf); + + opts.flags &= ~(WRITE_REV | WRITE_REV_VERIFY); + if (rev_index) { + opts.flags |= verify ? WRITE_REV_VERIFY : WRITE_REV; + if (index_name) + rev_index_name = derive_filename(index_name, + "idx", "rev", + &rev_index_name_buf); + } if (verify) { if (!index_name) @@ -1878,11 +1917,16 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) for (i = 0; i < nr_objects; i++) idx_objects[i] = &objects[i].idx; curr_index = write_idx_file(index_name, idx_objects, nr_objects, &opts, pack_hash); + if (rev_index) + curr_rev_index = write_rev_file(rev_index_name, idx_objects, + nr_objects, pack_hash, + opts.flags); free(idx_objects); if (!verify) final(pack_name, curr_pack, index_name, curr_index, + rev_index_name, curr_rev_index, keep_msg, promisor_msg, pack_hash); else @@ -1893,10 +1937,13 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) free(objects); strbuf_release(&index_name_buf); + strbuf_release(&rev_index_name_buf); if (pack_name == NULL) free((void *) curr_pack); if (index_name == NULL) free((void *) curr_index); + if (rev_index_name == NULL) + free((void *) curr_rev_index); /* * Let the caller know this pack is not self contained diff --git a/builtin/log.c b/builtin/log.c index fd282def43..be29c3f89f 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -12,6 +12,7 @@ #include "color.h" #include "commit.h" #include "diff.h" +#include "diff-merges.h" #include "revision.h" #include "log-tree.h" #include "builtin.h" @@ -607,15 +608,10 @@ static int show_tree_object(const struct object_id *oid, static void show_setup_revisions_tweak(struct rev_info *rev, struct setup_revision_opt *opt) { - if (rev->ignore_merges < 0) { - /* There was no "-m" variant on the command line */ - rev->ignore_merges = 0; - if (!rev->first_parent_only && !rev->combine_merges) { - /* No "--first-parent", "-c", or "--cc" */ - rev->combine_merges = 1; - rev->dense_combined_merges = 1; - } - } + if (rev->first_parent_only) + diff_merges_default_to_first_parent(rev); + else + diff_merges_default_to_dense_combined(rev); if (!rev->diffopt.output_format) rev->diffopt.output_format = DIFF_FORMAT_PATCH; } @@ -736,12 +732,8 @@ static void log_setup_revisions_tweak(struct rev_info *rev, rev->prune_data.nr == 1) rev->diffopt.flags.follow_renames = 1; - /* Turn --cc/-c into -p --cc/-c when -p was not given */ - if (!rev->diffopt.output_format && rev->combine_merges) - rev->diffopt.output_format = DIFF_FORMAT_PATCH; - - if (rev->first_parent_only && rev->ignore_merges < 0) - rev->ignore_merges = 0; + if (rev->first_parent_only) + diff_merges_default_to_first_parent(rev); } int cmd_log(int argc, const char **argv, const char *prefix) @@ -1231,14 +1223,20 @@ static void make_cover_letter(struct rev_info *rev, int use_separate_file, */ struct diff_options opts; struct strvec other_arg = STRVEC_INIT; + struct range_diff_options range_diff_opts = { + .creation_factor = rev->creation_factor, + .dual_color = 1, + .diffopt = &opts, + .other_arg = &other_arg + }; + diff_setup(&opts); opts.file = rev->diffopt.file; opts.use_color = rev->diffopt.use_color; diff_setup_done(&opts); fprintf_ln(rev->diffopt.file, "%s", rev->rdiff_title); get_notes_args(&other_arg, rev); - show_range_diff(rev->rdiff1, rev->rdiff2, - rev->creation_factor, 1, &opts, &other_arg); + show_range_diff(rev->rdiff1, rev->rdiff2, &range_diff_opts); strvec_clear(&other_arg); } } @@ -1680,7 +1678,7 @@ static void infer_range_diff_ranges(struct strbuf *r1, struct commit *head) { const char *head_oid = oid_to_hex(&head->object.oid); - int prev_is_range = !!strstr(prev, ".."); + int prev_is_range = is_range_diff_range(prev); if (prev_is_range) strbuf_addstr(r1, prev); diff --git a/builtin/ls-files.c b/builtin/ls-files.c index c8eae899b8..f6f9e483b2 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -35,6 +35,7 @@ static int line_terminator = '\n'; static int debug_mode; static int show_eol; static int recurse_submodules; +static int skipping_duplicates; static const char *prefix; static int max_prefix_len; @@ -312,45 +313,59 @@ static void show_files(struct repository *repo, struct dir_struct *dir) if (show_killed) show_killed_files(repo->index, dir); } - if (show_cached || show_stage) { - for (i = 0; i < repo->index->cache_nr; i++) { - const struct cache_entry *ce = repo->index->cache[i]; - construct_fullname(&fullname, repo, ce); + if (!(show_cached || show_stage || show_deleted || show_modified)) + return; + for (i = 0; i < repo->index->cache_nr; i++) { + const struct cache_entry *ce = repo->index->cache[i]; + struct stat st; + int stat_err; - if ((dir->flags & DIR_SHOW_IGNORED) && - !ce_excluded(dir, repo->index, fullname.buf, ce)) - continue; - if (show_unmerged && !ce_stage(ce)) - continue; - if (ce->ce_flags & CE_UPDATE) - continue; + construct_fullname(&fullname, repo, ce); + + if ((dir->flags & DIR_SHOW_IGNORED) && + !ce_excluded(dir, repo->index, fullname.buf, ce)) + continue; + if (ce->ce_flags & CE_UPDATE) + continue; + if ((show_cached || show_stage) && + (!show_unmerged || ce_stage(ce))) { show_ce(repo, dir, ce, fullname.buf, ce_stage(ce) ? tag_unmerged : (ce_skip_worktree(ce) ? tag_skip_worktree : tag_cached)); + if (skipping_duplicates) + goto skip_to_next_name; } - } - if (show_deleted || show_modified) { - for (i = 0; i < repo->index->cache_nr; i++) { - const struct cache_entry *ce = repo->index->cache[i]; - struct stat st; - int err; - construct_fullname(&fullname, repo, ce); - - if ((dir->flags & DIR_SHOW_IGNORED) && - !ce_excluded(dir, repo->index, fullname.buf, ce)) - continue; - if (ce->ce_flags & CE_UPDATE) - continue; - if (ce_skip_worktree(ce)) - continue; - err = lstat(fullname.buf, &st); - if (show_deleted && err) - show_ce(repo, dir, ce, fullname.buf, tag_removed); - if (show_modified && ie_modified(repo->index, ce, &st, 0)) - show_ce(repo, dir, ce, fullname.buf, tag_modified); + if (!(show_deleted || show_modified)) + continue; + if (ce_skip_worktree(ce)) + continue; + stat_err = lstat(fullname.buf, &st); + if (stat_err && (errno != ENOENT && errno != ENOTDIR)) + error_errno("cannot lstat '%s'", fullname.buf); + if (stat_err && show_deleted) { + show_ce(repo, dir, ce, fullname.buf, tag_removed); + if (skipping_duplicates) + goto skip_to_next_name; + } + if (show_modified && + (stat_err || ie_modified(repo->index, ce, &st, 0))) { + show_ce(repo, dir, ce, fullname.buf, tag_modified); + if (skipping_duplicates) + goto skip_to_next_name; + } + continue; + +skip_to_next_name: + { + int j; + struct cache_entry **cache = repo->index->cache; + for (j = i + 1; j < repo->index->cache_nr; j++) + if (strcmp(ce->name, cache[j]->name)) + break; + i = j - 1; /* compensate for the for loop */ } } @@ -578,6 +593,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) N_("pretend that paths removed since <tree-ish> are still present")), OPT__ABBREV(&abbrev), OPT_BOOL(0, "debug", &debug_mode, N_("show debugging data")), + OPT_BOOL(0, "deduplicate", &skipping_duplicates, + N_("suppress duplicate entries")), OPT_END() }; @@ -617,6 +634,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) * you also show the stage information. */ show_stage = 1; + if (show_tag || show_stage) + skipping_duplicates = 0; if (dir.exclude_per_dir) exc_given = 1; diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 092917eca2..ef604752a0 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -45,7 +45,8 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) int show_symref_target = 0; const char *uploadpack = NULL; const char **pattern = NULL; - struct strvec ref_prefixes = STRVEC_INIT; + struct transport_ls_refs_options transport_options = + TRANSPORT_LS_REFS_OPTIONS_INIT; int i; struct string_list server_options = STRING_LIST_INIT_DUP; @@ -94,9 +95,9 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) } if (flags & REF_TAGS) - strvec_push(&ref_prefixes, "refs/tags/"); + strvec_push(&transport_options.ref_prefixes, "refs/tags/"); if (flags & REF_HEADS) - strvec_push(&ref_prefixes, "refs/heads/"); + strvec_push(&transport_options.ref_prefixes, "refs/heads/"); remote = remote_get(dest); if (!remote) { @@ -118,7 +119,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) if (server_options.nr) transport->server_options = &server_options; - ref = transport_get_remote_refs(transport, &ref_prefixes); + ref = transport_get_remote_refs(transport, &transport_options); if (ref) { int hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport)); repo_set_hash_algo(the_repository, hash_algo); diff --git a/builtin/merge.c b/builtin/merge.c index 1cff730715..eb00b273e6 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -14,6 +14,7 @@ #include "lockfile.h" #include "run-command.h" #include "diff.h" +#include "diff-merges.h" #include "refs.h" #include "refspec.h" #include "commit.h" @@ -409,7 +410,7 @@ static void squash_message(struct commit *commit, struct commit_list *remotehead printf(_("Squash commit -- not updating HEAD\n")); repo_init_revisions(the_repository, &rev, NULL); - rev.ignore_merges = 1; + diff_merges_suppress(&rev); rev.commit_format = CMIT_FMT_MEDIUM; commit->object.flags |= UNINTERESTING; diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 3fe71a8c01..b221d30014 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -390,10 +390,10 @@ static void name_tips(void) } } -static const unsigned char *nth_tip_table_ent(size_t ix, void *table_) +static const struct object_id *nth_tip_table_ent(size_t ix, const void *table_) { - struct tip_table_entry *table = table_; - return table[ix].oid.hash; + const struct tip_table_entry *table = table_; + return &table[ix].oid; } static const char *get_exact_ref_match(const struct object *o) @@ -408,8 +408,8 @@ static const char *get_exact_ref_match(const struct object *o) tip_table.sorted = 1; } - found = hash_pos(o->oid.hash, tip_table.table, tip_table.nr, - nth_tip_table_ent); + found = oid_pos(&o->oid, tip_table.table, tip_table.nr, + nth_tip_table_ent); if (0 <= found) return tip_table.table[found].refname; return NULL; diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 5b0c4489e2..6d62aaf59a 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -634,7 +634,7 @@ static int mark_tagged(const char *path, const struct object_id *oid, int flag, if (entry) entry->tagged = 1; - if (!peel_ref(path, &peeled)) { + if (!peel_iterated_oid(oid, &peeled)) { entry = packlist_find(&to_pack, &peeled); if (entry) entry->tagged = 1; @@ -2814,13 +2814,11 @@ static void add_tag_chain(const struct object_id *oid) } } -static int add_ref_tag(const char *path, const struct object_id *oid, int flag, void *cb_data) +static int add_ref_tag(const char *tag, const struct object_id *oid, int flag, void *cb_data) { struct object_id peeled; - if (starts_with(path, "refs/tags/") && /* is a tag? */ - !peel_ref(path, &peeled) && /* peelable? */ - obj_is_packed(&peeled)) /* object packed? */ + if (!peel_iterated_oid(oid, &peeled) && obj_is_packed(&peeled)) add_tag_chain(oid); return 0; } @@ -2955,6 +2953,13 @@ static int git_pack_config(const char *k, const char *v, void *cb) pack_idx_opts.version); return 0; } + if (!strcmp(k, "pack.writereverseindex")) { + if (git_config_bool(k, v)) + pack_idx_opts.flags |= WRITE_REV; + else + pack_idx_opts.flags &= ~WRITE_REV; + return 0; + } if (!strcmp(k, "uploadpack.blobpackfileuri")) { struct configured_exclusion *ex = xmalloc(sizeof(*ex)); const char *oid_end, *pack_end; @@ -3594,6 +3599,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) reset_pack_idx_option(&pack_idx_opts); git_config(git_pack_config, NULL); + if (git_env_bool(GIT_TEST_WRITE_REV_INDEX, 0)) + pack_idx_opts.flags |= WRITE_REV; progress = isatty(2); argc = parse_options(argc, argv, prefix, pack_objects_options, @@ -3751,7 +3758,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) } cleanup_preferred_base(); if (include_tag && nr_result) - for_each_ref(add_ref_tag, NULL); + for_each_tag_ref(add_ref_tag, NULL); stop_progress(&progress_state); trace2_region_leave("pack-objects", "enumerate-objects", the_repository); diff --git a/builtin/range-diff.c b/builtin/range-diff.c index 24c4162f74..78bc9fa770 100644 --- a/builtin/range-diff.c +++ b/builtin/range-diff.c @@ -3,6 +3,7 @@ #include "parse-options.h" #include "range-diff.h" #include "config.h" +#include "revision.h" static const char * const builtin_range_diff_usage[] = { N_("git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"), @@ -13,18 +14,27 @@ NULL int cmd_range_diff(int argc, const char **argv, const char *prefix) { - int creation_factor = RANGE_DIFF_CREATION_FACTOR_DEFAULT; struct diff_options diffopt = { NULL }; struct strvec other_arg = STRVEC_INIT; - int simple_color = -1; + struct range_diff_options range_diff_opts = { + .creation_factor = RANGE_DIFF_CREATION_FACTOR_DEFAULT, + .diffopt = &diffopt, + .other_arg = &other_arg + }; + int simple_color = -1, left_only = 0, right_only = 0; struct option range_diff_options[] = { - OPT_INTEGER(0, "creation-factor", &creation_factor, + OPT_INTEGER(0, "creation-factor", + &range_diff_opts.creation_factor, N_("Percentage by which creation is weighted")), OPT_BOOL(0, "no-dual-color", &simple_color, N_("use simple diff colors")), OPT_PASSTHRU_ARGV(0, "notes", &other_arg, N_("notes"), N_("passed to 'git log'"), PARSE_OPT_OPTARG), + OPT_BOOL(0, "left-only", &left_only, + N_("only emit output related to the first range")), + OPT_BOOL(0, "right-only", &right_only, + N_("only emit output related to the second range")), OPT_END() }; struct option *options; @@ -46,12 +56,12 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix) diffopt.use_color = 1; if (argc == 2) { - if (!strstr(argv[0], "..")) - die(_("no .. in range: '%s'"), argv[0]); + if (!is_range_diff_range(argv[0])) + die(_("not a commit range: '%s'"), argv[0]); strbuf_addstr(&range1, argv[0]); - if (!strstr(argv[1], "..")) - die(_("no .. in range: '%s'"), argv[1]); + if (!is_range_diff_range(argv[1])) + die(_("not a commit range: '%s'"), argv[1]); strbuf_addstr(&range2, argv[1]); } else if (argc == 3) { strbuf_addf(&range1, "%s..%s", argv[0], argv[1]); @@ -81,8 +91,10 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix) } FREE_AND_NULL(options); - res = show_range_diff(range1.buf, range2.buf, creation_factor, - simple_color < 1, &diffopt, &other_arg); + range_diff_opts.dual_color = simple_color < 1; + range_diff_opts.left_only = left_only; + range_diff_opts.right_only = right_only; + res = show_range_diff(range1.buf, range2.buf, &range_diff_opts); strvec_clear(&other_arg); strbuf_release(&range1); diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index d49d050e6e..b89ce31bf2 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -764,7 +764,7 @@ static void prepare_push_cert_sha1(struct child_process *proc) memset(&sigcheck, '\0', sizeof(sigcheck)); - bogs = parse_signature(push_cert.buf, push_cert.len); + bogs = parse_signed_buffer(push_cert.buf, push_cert.len); check_signature(push_cert.buf, bogs, push_cert.buf + bogs, push_cert.len - bogs, &sigcheck); @@ -2050,7 +2050,7 @@ static void queue_commands_from_cert(struct command **tail, die("malformed push certificate %.*s", 100, push_cert->buf); else boc += 2; - eoc = push_cert->buf + parse_signature(push_cert->buf, push_cert->len); + eoc = push_cert->buf + parse_signed_buffer(push_cert->buf, push_cert->len); while (boc < eoc) { const char *eol = memchr(boc, '\n', eoc - boc); diff --git a/builtin/reflog.c b/builtin/reflog.c index ca1d8079f3..09541d1c80 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -602,6 +602,9 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) */ if (cb.cmd.stalefix) { repo_init_revisions(the_repository, &cb.cmd.revs, prefix); + cb.cmd.revs.do_not_die_on_missing_tree = 1; + cb.cmd.revs.ignore_missing = 1; + cb.cmd.revs.ignore_missing_links = 1; if (flags & EXPIRE_REFLOGS_VERBOSE) printf(_("Marking reachable objects...")); mark_reachable_objects(&cb.cmd.revs, 0, 0, NULL); diff --git a/builtin/repack.c b/builtin/repack.c index 2158b48f4c..01440de2d5 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -209,6 +209,7 @@ static struct { } exts[] = { {".pack"}, {".idx"}, + {".rev", 1}, {".bitmap", 1}, {".promisor", 1}, }; diff --git a/builtin/show-ref.c b/builtin/show-ref.c index ae60b4acf2..7f8a5332f8 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -40,7 +40,7 @@ static void show_one(const char *refname, const struct object_id *oid) if (!deref_tags) return; - if (!peel_ref(refname, &peeled)) { + if (!peel_iterated_oid(oid, &peeled)) { hex = find_unique_abbrev(&peeled, abbrev); printf("%s %s^{}\n", hex, refname); } diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index e3140db2a0..2306a9ad98 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -22,11 +22,6 @@ static char const * const builtin_sparse_checkout_usage[] = { NULL }; -static char *get_sparse_checkout_filename(void) -{ - return git_pathdup("info/sparse-checkout"); -} - static void write_patterns_to_file(FILE *fp, struct pattern_list *pl) { int i; diff --git a/builtin/stash.c b/builtin/stash.c index 9bc85f91cd..6f2b58f6ab 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -87,7 +87,7 @@ static const char * const git_stash_save_usage[] = { NULL }; -static const char *ref_stash = "refs/stash"; +static const char ref_stash[] = "refs/stash"; static struct strbuf stash_index_path = STRBUF_INIT; /* diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index c2bd882d17..9d505a6329 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -1257,7 +1257,7 @@ static int compute_summary_module_list(struct object_id *head_oid, git_config(git_diff_basic_config, NULL); init_revisions(&rev, info->prefix); rev.abbrev = 0; - precompose_argv(diff_args.nr, diff_args.v); + precompose_argv_prefix(diff_args.nr, diff_args.v, NULL); setup_revisions(diff_args.nr, diff_args.v, &rev, NULL); rev.diffopt.output_format = DIFF_FORMAT_NO_OUTPUT | DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = submodule_summary_callback; diff --git a/builtin/tag.c b/builtin/tag.c index 24d35b746d..4237dc724c 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -72,10 +72,10 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, } typedef int (*each_tag_name_fn)(const char *name, const char *ref, - const struct object_id *oid, const void *cb_data); + const struct object_id *oid, void *cb_data); static int for_each_tag_name(const char **argv, each_tag_name_fn fn, - const void *cb_data) + void *cb_data) { const char **p; struct strbuf ref = STRBUF_INIT; @@ -97,18 +97,42 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn, return had_error; } -static int delete_tag(const char *name, const char *ref, - const struct object_id *oid, const void *cb_data) +static int collect_tags(const char *name, const char *ref, + const struct object_id *oid, void *cb_data) { - if (delete_ref(NULL, ref, oid, 0)) - return 1; - printf(_("Deleted tag '%s' (was %s)\n"), name, - find_unique_abbrev(oid, DEFAULT_ABBREV)); + struct string_list *ref_list = cb_data; + + string_list_append(ref_list, ref); + ref_list->items[ref_list->nr - 1].util = oiddup(oid); return 0; } +static int delete_tags(const char **argv) +{ + int result; + struct string_list refs_to_delete = STRING_LIST_INIT_DUP; + struct string_list_item *item; + + result = for_each_tag_name(argv, collect_tags, (void *)&refs_to_delete); + if (delete_refs(NULL, &refs_to_delete, REF_NO_DEREF)) + result = 1; + + for_each_string_list_item(item, &refs_to_delete) { + const char *name = item->string; + struct object_id *oid = item->util; + if (!ref_exists(name)) + printf(_("Deleted tag '%s' (was %s)\n"), + item->string + 10, + find_unique_abbrev(oid, DEFAULT_ABBREV)); + + free(oid); + } + string_list_clear(&refs_to_delete, 0); + return result; +} + static int verify_tag(const char *name, const char *ref, - const struct object_id *oid, const void *cb_data) + const struct object_id *oid, void *cb_data) { int flags; const struct ref_format *format = cb_data; @@ -174,11 +198,17 @@ static void write_tag_body(int fd, const struct object_id *oid) { unsigned long size; enum object_type type; - char *buf, *sp; + char *buf, *sp, *orig; + struct strbuf payload = STRBUF_INIT; + struct strbuf signature = STRBUF_INIT; - buf = read_object_file(oid, &type, &size); + orig = buf = read_object_file(oid, &type, &size); if (!buf) return; + if (parse_signature(buf, size, &payload, &signature)) { + buf = payload.buf; + size = payload.len; + } /* skip header */ sp = strstr(buf, "\n\n"); @@ -187,9 +217,11 @@ static void write_tag_body(int fd, const struct object_id *oid) return; } sp += 2; /* skip the 2 LFs */ - write_or_die(fd, sp, parse_signature(sp, buf + size - sp)); + write_or_die(fd, sp, buf + size - sp); - free(buf); + free(orig); + strbuf_release(&payload); + strbuf_release(&signature); } static int build_tag_object(struct strbuf *buf, int sign, struct object_id *result) @@ -512,7 +544,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (filter.reachable_from || filter.unreachable_from) die(_("--merged and --no-merged options are only allowed in list mode")); if (cmdmode == 'd') - return for_each_tag_name(argv, delete_tag, NULL); + return delete_tags(argv); if (cmdmode == 'v') { if (format.format && verify_ref_format(&format)) usage_with_options(git_tag_usage, options); diff --git a/builtin/worktree.c b/builtin/worktree.c index 71287b2da6..1cd5c2016e 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -12,6 +12,7 @@ #include "submodule.h" #include "utf8.h" #include "worktree.h" +#include "quote.h" static const char * const worktree_usage[] = { N_("git worktree add [<options>] <path> [<commit-ish>]"), @@ -67,79 +68,6 @@ static void delete_worktrees_dir_if_empty(void) rmdir(git_path("worktrees")); /* ignore failed removal */ } -/* - * Return true if worktree entry should be pruned, along with the reason for - * pruning. Otherwise, return false and the worktree's path, or NULL if it - * cannot be determined. Caller is responsible for freeing returned path. - */ -static int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath) -{ - struct stat st; - char *path; - int fd; - size_t len; - ssize_t read_result; - - *wtpath = NULL; - if (!is_directory(git_path("worktrees/%s", id))) { - strbuf_addstr(reason, _("not a valid directory")); - return 1; - } - if (file_exists(git_path("worktrees/%s/locked", id))) - return 0; - if (stat(git_path("worktrees/%s/gitdir", id), &st)) { - strbuf_addstr(reason, _("gitdir file does not exist")); - return 1; - } - fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY); - if (fd < 0) { - strbuf_addf(reason, _("unable to read gitdir file (%s)"), - strerror(errno)); - return 1; - } - len = xsize_t(st.st_size); - path = xmallocz(len); - - read_result = read_in_full(fd, path, len); - if (read_result < 0) { - strbuf_addf(reason, _("unable to read gitdir file (%s)"), - strerror(errno)); - close(fd); - free(path); - return 1; - } - close(fd); - - if (read_result != len) { - strbuf_addf(reason, - _("short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"), - (uintmax_t)len, (uintmax_t)read_result); - free(path); - return 1; - } - while (len && (path[len - 1] == '\n' || path[len - 1] == '\r')) - len--; - if (!len) { - strbuf_addstr(reason, _("invalid gitdir file")); - free(path); - return 1; - } - path[len] = '\0'; - if (!file_exists(path)) { - if (stat(git_path("worktrees/%s/index", id), &st) || - st.st_mtime <= expire) { - strbuf_addstr(reason, _("gitdir file points to non-existent location")); - free(path); - return 1; - } else { - *wtpath = path; - return 0; - } - } - *wtpath = path; - return 0; -} - static void prune_worktree(const char *id, const char *reason) { if (show_only || verbose) @@ -195,7 +123,7 @@ static void prune_worktrees(void) if (is_dot_or_dotdot(d->d_name)) continue; strbuf_reset(&reason); - if (should_prune_worktree(d->d_name, &reason, &path)) + if (should_prune_worktree(d->d_name, &reason, &path, expire)) prune_worktree(d->d_name, reason.buf); else if (path) string_list_append(&kept, path)->util = xstrdup(d->d_name); @@ -642,6 +570,8 @@ static int add(int ac, const char **av, const char *prefix) static void show_worktree_porcelain(struct worktree *wt) { + const char *reason; + printf("worktree %s\n", wt->path); if (wt->is_bare) printf("bare\n"); @@ -652,6 +582,20 @@ static void show_worktree_porcelain(struct worktree *wt) else if (wt->head_ref) printf("branch %s\n", wt->head_ref); } + + reason = worktree_lock_reason(wt); + if (reason && *reason) { + struct strbuf sb = STRBUF_INIT; + quote_c_style(reason, &sb, NULL, 0); + printf("locked %s\n", sb.buf); + strbuf_release(&sb); + } else if (reason) + printf("locked\n"); + + reason = worktree_prune_reason(wt, expire); + if (reason) + printf("prunable %s\n", reason); + printf("\n"); } @@ -660,6 +604,7 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len) struct strbuf sb = STRBUF_INIT; int cur_path_len = strlen(wt->path); int path_adj = cur_path_len - utf8_strwidth(wt->path); + const char *reason; strbuf_addf(&sb, "%-*s ", 1 + path_maxlen + path_adj, wt->path); if (wt->is_bare) @@ -677,9 +622,18 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len) strbuf_addstr(&sb, "(error)"); } - if (!is_main_worktree(wt) && worktree_lock_reason(wt)) + reason = worktree_lock_reason(wt); + if (verbose && reason && *reason) + strbuf_addf(&sb, "\n\tlocked: %s", reason); + else if (reason) strbuf_addstr(&sb, " locked"); + reason = worktree_prune_reason(wt, expire); + if (verbose && reason) + strbuf_addf(&sb, "\n\tprunable: %s", reason); + else if (reason) + strbuf_addstr(&sb, " prunable"); + printf("%s\n", sb.buf); strbuf_release(&sb); } @@ -723,12 +677,18 @@ static int list(int ac, const char **av, const char *prefix) struct option options[] = { OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")), + OPT__VERBOSE(&verbose, N_("show extended annotations and reasons, if available")), + OPT_EXPIRY_DATE(0, "expire", &expire, + N_("add 'prunable' annotation to worktrees older than <time>")), OPT_END() }; + expire = TIME_MAX; ac = parse_options(ac, av, prefix, options, worktree_usage, 0); if (ac) usage_with_options(worktree_usage, options); + else if (verbose && porcelain) + die(_("--verbose and --porcelain are mutually exclusive")); else { struct worktree **worktrees = get_worktrees(); int path_maxlen = 0, abbrev = DEFAULT_ABBREV, i; diff --git a/cache-tree.c b/cache-tree.c index a537a806c1..2fb483d3c0 100644 --- a/cache-tree.c +++ b/cache-tree.c @@ -45,7 +45,7 @@ static int subtree_name_cmp(const char *one, int onelen, return memcmp(one, two, onelen); } -static int subtree_pos(struct cache_tree *it, const char *path, int pathlen) +int cache_tree_subtree_pos(struct cache_tree *it, const char *path, int pathlen) { struct cache_tree_sub **down = it->down; int lo, hi; @@ -72,7 +72,7 @@ static struct cache_tree_sub *find_subtree(struct cache_tree *it, int create) { struct cache_tree_sub *down; - int pos = subtree_pos(it, path, pathlen); + int pos = cache_tree_subtree_pos(it, path, pathlen); if (0 <= pos) return it->down[pos]; if (!create) @@ -123,7 +123,7 @@ static int do_invalidate_path(struct cache_tree *it, const char *path) it->entry_count = -1; if (!*slash) { int pos; - pos = subtree_pos(it, path, namelen); + pos = cache_tree_subtree_pos(it, path, namelen); if (0 <= pos) { cache_tree_free(&it->down[pos]->cache_tree); free(it->down[pos]); @@ -151,16 +151,15 @@ void cache_tree_invalidate_path(struct index_state *istate, const char *path) istate->cache_changed |= CACHE_TREE_CHANGED; } -static int verify_cache(struct cache_entry **cache, - int entries, int flags) +static int verify_cache(struct index_state *istate, int flags) { - int i, funny; + unsigned i, funny; int silent = flags & WRITE_TREE_SILENT; /* Verify that the tree is merged */ funny = 0; - for (i = 0; i < entries; i++) { - const struct cache_entry *ce = cache[i]; + for (i = 0; i < istate->cache_nr; i++) { + const struct cache_entry *ce = istate->cache[i]; if (ce_stage(ce)) { if (silent) return -1; @@ -180,17 +179,19 @@ static int verify_cache(struct cache_entry **cache, * stage 0 entries. */ funny = 0; - for (i = 0; i < entries - 1; i++) { + for (i = 0; i + 1 < istate->cache_nr; i++) { /* path/file always comes after path because of the way * the cache is sorted. Also path can appear only once, * which means conflicting one would immediately follow. */ - const char *this_name = cache[i]->name; - const char *next_name = cache[i+1]->name; - int this_len = strlen(this_name); - if (this_len < strlen(next_name) && - strncmp(this_name, next_name, this_len) == 0 && - next_name[this_len] == '/') { + const struct cache_entry *this_ce = istate->cache[i]; + const struct cache_entry *next_ce = istate->cache[i + 1]; + const char *this_name = this_ce->name; + const char *next_name = next_ce->name; + int this_len = ce_namelen(this_ce); + if (this_len < ce_namelen(next_ce) && + next_name[this_len] == '/' && + strncmp(this_name, next_name, this_len) == 0) { if (10 < ++funny) { fprintf(stderr, "...\n"); break; @@ -434,15 +435,21 @@ static int update_one(struct cache_tree *it, int cache_tree_update(struct index_state *istate, int flags) { - struct cache_tree *it = istate->cache_tree; - struct cache_entry **cache = istate->cache; - int entries = istate->cache_nr; - int skip, i = verify_cache(cache, entries, flags); + int skip, i; + + i = verify_cache(istate, flags); if (i) return i; + + if (!istate->cache_tree) + istate->cache_tree = cache_tree(); + trace_performance_enter(); - i = update_one(it, cache, entries, "", 0, &skip, flags); + trace2_region_enter("cache_tree", "update", the_repository); + i = update_one(istate->cache_tree, istate->cache, istate->cache_nr, + "", 0, &skip, flags); + trace2_region_leave("cache_tree", "update", the_repository); trace_performance_leave("cache_tree_update"); if (i < 0) return i; @@ -492,7 +499,9 @@ static void write_one(struct strbuf *buffer, struct cache_tree *it, void cache_tree_write(struct strbuf *sb, struct cache_tree *root) { + trace2_region_enter("cache_tree", "write", the_repository); write_one(sb, root, "", 0); + trace2_region_leave("cache_tree", "write", the_repository); } static struct cache_tree *read_one(const char **buffer, unsigned long *size_p) @@ -581,9 +590,16 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p) struct cache_tree *cache_tree_read(const char *buffer, unsigned long size) { + struct cache_tree *result; + if (buffer[0]) return NULL; /* not the whole tree */ - return read_one(&buffer, &size); + + trace2_region_enter("cache_tree", "read", the_repository); + result = read_one(&buffer, &size); + trace2_region_leave("cache_tree", "read", the_repository); + + return result; } static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *path) @@ -622,9 +638,6 @@ static int write_index_as_tree_internal(struct object_id *oid, cache_tree_valid = 0; } - if (!index_state->cache_tree) - index_state->cache_tree = cache_tree(); - if (!cache_tree_valid && cache_tree_update(index_state, flags) < 0) return WRITE_TREE_UNMERGED_INDEX; @@ -733,10 +746,13 @@ void prime_cache_tree(struct repository *r, struct index_state *istate, struct tree *tree) { + trace2_region_enter("cache-tree", "prime_cache_tree", the_repository); cache_tree_free(&istate->cache_tree); istate->cache_tree = cache_tree(); + prime_cache_tree_rec(r, istate->cache_tree, tree); istate->cache_changed |= CACHE_TREE_CHANGED; + trace2_region_leave("cache-tree", "prime_cache_tree", the_repository); } /* diff --git a/cache-tree.h b/cache-tree.h index 639bfa5340..8efeccebfc 100644 --- a/cache-tree.h +++ b/cache-tree.h @@ -27,6 +27,8 @@ void cache_tree_free(struct cache_tree **); void cache_tree_invalidate_path(struct index_state *, const char *); struct cache_tree_sub *cache_tree_sub(struct cache_tree *, const char *); +int cache_tree_subtree_pos(struct cache_tree *it, const char *path, int pathlen); + void cache_tree_write(struct strbuf *, struct cache_tree *root); struct cache_tree *cache_tree_read(const char *buffer, unsigned long size); @@ -328,6 +328,7 @@ struct index_state { struct ewah_bitmap *fsmonitor_dirty; struct mem_pool *ce_mem_pool; struct progress *progress; + struct repository *repo; }; /* Name hashing */ diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh index 0b1184e04a..67852d0d37 100755 --- a/ci/install-dependencies.sh +++ b/ci/install-dependencies.sh @@ -72,7 +72,7 @@ Documentation) test -n "$ALREADY_HAVE_ASCIIDOCTOR" || sudo gem install --version 1.5.8 asciidoctor ;; -linux-gcc-4.8|GETTEXT_POISON) +linux-gcc-default|linux-gcc-4.8) sudo apt-get -q update sudo apt-get -q -y install $UBUNTU_COMMON_PKGS ;; @@ -220,8 +220,7 @@ osx-clang|osx-gcc) # Travis CI OS X export GIT_SKIP_TESTS="t9810 t9816" ;; -GETTEXT_POISON) - export GIT_TEST_GETTEXT_POISON=true +linux-gcc-default) ;; Linux32) CC=gcc diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh index 50e0b90073..a66b5e8c75 100755 --- a/ci/run-build-and-tests.sh +++ b/ci/run-build-and-tests.sh @@ -24,6 +24,7 @@ linux-gcc) export GIT_TEST_MULTI_PACK_INDEX=1 export GIT_TEST_ADD_I_USE_BUILTIN=1 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master + export GIT_TEST_WRITE_REV_INDEX=1 make test ;; linux-clang) diff --git a/commit-graph.c b/commit-graph.c index f3486ec18f..ed31843fa5 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -38,11 +38,13 @@ void git_test_write_commit_graph_or_die(void) #define GRAPH_CHUNKID_OIDFANOUT 0x4f494446 /* "OIDF" */ #define GRAPH_CHUNKID_OIDLOOKUP 0x4f49444c /* "OIDL" */ #define GRAPH_CHUNKID_DATA 0x43444154 /* "CDAT" */ +#define GRAPH_CHUNKID_GENERATION_DATA 0x47444154 /* "GDAT" */ +#define GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW 0x47444f56 /* "GDOV" */ #define GRAPH_CHUNKID_EXTRAEDGES 0x45444745 /* "EDGE" */ #define GRAPH_CHUNKID_BLOOMINDEXES 0x42494458 /* "BIDX" */ #define GRAPH_CHUNKID_BLOOMDATA 0x42444154 /* "BDAT" */ #define GRAPH_CHUNKID_BASE 0x42415345 /* "BASE" */ -#define MAX_NUM_CHUNKS 7 +#define MAX_NUM_CHUNKS 9 #define GRAPH_DATA_WIDTH (the_hash_algo->rawsz + 16) @@ -61,9 +63,13 @@ void git_test_write_commit_graph_or_die(void) #define GRAPH_MIN_SIZE (GRAPH_HEADER_SIZE + 4 * GRAPH_CHUNKLOOKUP_WIDTH \ + GRAPH_FANOUT_SIZE + the_hash_algo->rawsz) +#define CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW (1ULL << 31) + /* Remember to update object flag allocation in object.h */ #define REACHABLE (1u<<15) +define_commit_slab(topo_level_slab, uint32_t); + /* Keep track of the order in which commits are added to our list. */ define_commit_slab(commit_pos, int); static struct commit_pos commit_pos = COMMIT_SLAB_INIT(1, commit_pos); @@ -99,7 +105,7 @@ uint32_t commit_graph_position(const struct commit *c) return data ? data->graph_pos : COMMIT_NOT_FROM_GRAPH; } -uint32_t commit_graph_generation(const struct commit *c) +timestamp_t commit_graph_generation(const struct commit *c) { struct commit_graph_data *data = commit_graph_data_slab_peek(&commit_graph_data_slab, c); @@ -139,13 +145,17 @@ static struct commit_graph_data *commit_graph_data_at(const struct commit *c) return data; } +/* + * Should be used only while writing commit-graph as it compares + * generation value of commits by directly accessing commit-slab. + */ static int commit_gen_cmp(const void *va, const void *vb) { const struct commit *a = *(const struct commit **)va; const struct commit *b = *(const struct commit **)vb; - uint32_t generation_a = commit_graph_generation(a); - uint32_t generation_b = commit_graph_generation(b); + const timestamp_t generation_a = commit_graph_data_at(a)->generation; + const timestamp_t generation_b = commit_graph_data_at(b)->generation; /* lower generation commits first */ if (generation_a < generation_b) return -1; @@ -205,16 +215,24 @@ static int commit_graph_compatible(struct repository *r) if (read_replace_refs) { prepare_replace_object(r); - if (hashmap_get_size(&r->objects->replace_map->map)) + if (hashmap_get_size(&r->objects->replace_map->map)) { + warning(_("repository contains replace objects; " + "skipping commit-graph")); return 0; + } } prepare_commit_graft(r); if (r->parsed_objects && - (r->parsed_objects->grafts_nr || r->parsed_objects->substituted_parent)) + (r->parsed_objects->grafts_nr || r->parsed_objects->substituted_parent)) { + warning(_("repository contains (deprecated) grafts; " + "skipping commit-graph")); return 0; - if (is_repository_shallow(r)) + } + if (is_repository_shallow(r)) { + warning(_("repository is shallow; skipping commit-graph")); return 0; + } return 1; } @@ -388,6 +406,20 @@ struct commit_graph *parse_commit_graph(struct repository *r, graph->chunk_commit_data = data + chunk_offset; break; + case GRAPH_CHUNKID_GENERATION_DATA: + if (graph->chunk_generation_data) + chunk_repeated = 1; + else + graph->chunk_generation_data = data + chunk_offset; + break; + + case GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW: + if (graph->chunk_generation_data_overflow) + chunk_repeated = 1; + else + graph->chunk_generation_data_overflow = data + chunk_offset; + break; + case GRAPH_CHUNKID_EXTRAEDGES: if (graph->chunk_extra_edges) chunk_repeated = 1; @@ -590,6 +622,31 @@ static struct commit_graph *load_commit_graph_chain(struct repository *r, return graph_chain; } +/* + * returns 1 if and only if all graphs in the chain have + * corrected commit dates stored in the generation_data chunk. + */ +static int validate_mixed_generation_chain(struct commit_graph *g) +{ + int read_generation_data = 1; + struct commit_graph *p = g; + + while (read_generation_data && p) { + read_generation_data = p->read_generation_data; + p = p->base_graph; + } + + if (read_generation_data) + return 1; + + while (g) { + g->read_generation_data = 0; + g = g->base_graph; + } + + return 0; +} + struct commit_graph *read_commit_graph_one(struct repository *r, struct object_directory *odb) { @@ -598,6 +655,8 @@ struct commit_graph *read_commit_graph_one(struct repository *r, if (!g) g = load_commit_graph_chain(r, odb); + validate_mixed_generation_chain(g); + return g; } @@ -673,6 +732,20 @@ int generation_numbers_enabled(struct repository *r) return !!first_generation; } +int corrected_commit_dates_enabled(struct repository *r) +{ + struct commit_graph *g; + if (!prepare_commit_graph(r)) + return 0; + + g = r->objects->commit_graph; + + if (!g->num_commits) + return 0; + + return g->read_generation_data; +} + struct bloom_filter_settings *get_bloom_filter_settings(struct repository *r) { struct commit_graph *g = r->objects->commit_graph; @@ -748,17 +821,41 @@ static void fill_commit_graph_info(struct commit *item, struct commit_graph *g, { const unsigned char *commit_data; struct commit_graph_data *graph_data; - uint32_t lex_index; + uint32_t lex_index, offset_pos; + uint64_t date_high, date_low, offset; while (pos < g->num_commits_in_base) g = g->base_graph; + if (pos >= g->num_commits + g->num_commits_in_base) + die(_("invalid commit position. commit-graph is likely corrupt")); + lex_index = pos - g->num_commits_in_base; commit_data = g->chunk_commit_data + GRAPH_DATA_WIDTH * lex_index; graph_data = commit_graph_data_at(item); graph_data->graph_pos = pos; - graph_data->generation = get_be32(commit_data + g->hash_len + 8) >> 2; + + date_high = get_be32(commit_data + g->hash_len + 8) & 0x3; + date_low = get_be32(commit_data + g->hash_len + 12); + item->date = (timestamp_t)((date_high << 32) | date_low); + + if (g->read_generation_data) { + offset = (timestamp_t)get_be32(g->chunk_generation_data + sizeof(uint32_t) * lex_index); + + if (offset & CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW) { + if (!g->chunk_generation_data_overflow) + die(_("commit-graph requires overflow generation data but has none")); + + offset_pos = offset ^ CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW; + graph_data->generation = get_be64(g->chunk_generation_data_overflow + 8 * offset_pos); + } else + graph_data->generation = item->date + offset; + } else + graph_data->generation = get_be32(commit_data + g->hash_len + 8) >> 2; + + if (g->topo_levels) + *topo_level_slab_at(g->topo_levels, item) = get_be32(commit_data + g->hash_len + 8) >> 2; } static inline void set_commit_tree(struct commit *c, struct tree *t) @@ -772,38 +869,22 @@ static int fill_commit_in_graph(struct repository *r, { uint32_t edge_value; uint32_t *parent_data_ptr; - uint64_t date_low, date_high; struct commit_list **pptr; - struct commit_graph_data *graph_data; const unsigned char *commit_data; uint32_t lex_index; while (pos < g->num_commits_in_base) g = g->base_graph; - if (pos >= g->num_commits + g->num_commits_in_base) - die(_("invalid commit position. commit-graph is likely corrupt")); + fill_commit_graph_info(item, g, pos); - /* - * Store the "full" position, but then use the - * "local" position for the rest of the calculation. - */ - graph_data = commit_graph_data_at(item); - graph_data->graph_pos = pos; lex_index = pos - g->num_commits_in_base; - commit_data = g->chunk_commit_data + (g->hash_len + 16) * lex_index; item->object.parsed = 1; set_commit_tree(item, NULL); - date_high = get_be32(commit_data + g->hash_len + 8) & 0x3; - date_low = get_be32(commit_data + g->hash_len + 12); - item->date = (timestamp_t)((date_high << 32) | date_low); - - graph_data->generation = get_be32(commit_data + g->hash_len + 8) >> 2; - pptr = &item->parents; edge_value = get_be32(commit_data + g->hash_len); @@ -943,6 +1024,7 @@ struct write_commit_graph_context { struct oid_array oids; struct packed_commit_list commits; int num_extra_edges; + int num_generation_data_overflows; unsigned long approx_nr_objects; struct progress *progress; int progress_done; @@ -961,8 +1043,11 @@ struct write_commit_graph_context { report_progress:1, split:1, changed_paths:1, - order_by_pack:1; + order_by_pack:1, + write_generation_data:1, + trust_generation_numbers:1; + struct topo_level_slab *topo_levels; const struct commit_graph_opts *opts; size_t total_bloom_filter_data_size; const struct bloom_filter_settings *bloom_settings; @@ -1012,10 +1097,10 @@ static int write_graph_chunk_oids(struct hashfile *f, return 0; } -static const unsigned char *commit_to_sha1(size_t index, void *table) +static const struct object_id *commit_to_oid(size_t index, const void *table) { - struct commit **commits = table; - return commits[index]->object.oid.hash; + const struct commit * const *commits = table; + return &commits[index]->object.oid; } static int write_graph_chunk_data(struct hashfile *f, @@ -1032,7 +1117,7 @@ static int write_graph_chunk_data(struct hashfile *f, uint32_t packedDate[2]; display_progress(ctx->progress, ++ctx->progress_cnt); - if (parse_commit_no_graph(*list)) + if (repo_parse_commit_no_graph(ctx->r, *list)) die(_("unable to parse commit %s"), oid_to_hex(&(*list)->object.oid)); tree = get_commit_tree_oid(*list); @@ -1043,10 +1128,10 @@ static int write_graph_chunk_data(struct hashfile *f, if (!parent) edge_value = GRAPH_PARENT_NONE; else { - edge_value = hash_pos(parent->item->object.oid.hash, - ctx->commits.list, - ctx->commits.nr, - commit_to_sha1); + edge_value = oid_pos(&parent->item->object.oid, + ctx->commits.list, + ctx->commits.nr, + commit_to_oid); if (edge_value >= 0) edge_value += ctx->new_num_commits_in_base; @@ -1074,10 +1159,10 @@ static int write_graph_chunk_data(struct hashfile *f, else if (parent->next) edge_value = GRAPH_EXTRA_EDGES_NEEDED | num_extra_edges; else { - edge_value = hash_pos(parent->item->object.oid.hash, - ctx->commits.list, - ctx->commits.nr, - commit_to_sha1); + edge_value = oid_pos(&parent->item->object.oid, + ctx->commits.list, + ctx->commits.nr, + commit_to_oid); if (edge_value >= 0) edge_value += ctx->new_num_commits_in_base; @@ -1109,7 +1194,7 @@ static int write_graph_chunk_data(struct hashfile *f, else packedDate[0] = 0; - packedDate[0] |= htonl(commit_graph_data_at(*list)->generation << 2); + packedDate[0] |= htonl(*topo_level_slab_at(ctx->topo_levels, *list) << 2); packedDate[1] = htonl((*list)->date); hashwrite(f, packedDate, 8); @@ -1120,6 +1205,47 @@ static int write_graph_chunk_data(struct hashfile *f, return 0; } +static int write_graph_chunk_generation_data(struct hashfile *f, + struct write_commit_graph_context *ctx) +{ + int i, num_generation_data_overflows = 0; + + for (i = 0; i < ctx->commits.nr; i++) { + struct commit *c = ctx->commits.list[i]; + timestamp_t offset; + repo_parse_commit(ctx->r, c); + offset = commit_graph_data_at(c)->generation - c->date; + display_progress(ctx->progress, ++ctx->progress_cnt); + + if (offset > GENERATION_NUMBER_V2_OFFSET_MAX) { + offset = CORRECTED_COMMIT_DATE_OFFSET_OVERFLOW | num_generation_data_overflows; + num_generation_data_overflows++; + } + + hashwrite_be32(f, offset); + } + + return 0; +} + +static int write_graph_chunk_generation_data_overflow(struct hashfile *f, + struct write_commit_graph_context *ctx) +{ + int i; + for (i = 0; i < ctx->commits.nr; i++) { + struct commit *c = ctx->commits.list[i]; + timestamp_t offset = commit_graph_data_at(c)->generation - c->date; + display_progress(ctx->progress, ++ctx->progress_cnt); + + if (offset > GENERATION_NUMBER_V2_OFFSET_MAX) { + hashwrite_be32(f, offset >> 32); + hashwrite_be32(f, (uint32_t) offset); + } + } + + return 0; +} + static int write_graph_chunk_extra_edges(struct hashfile *f, struct write_commit_graph_context *ctx) { @@ -1143,10 +1269,10 @@ static int write_graph_chunk_extra_edges(struct hashfile *f, /* Since num_parents > 2, this initializer is safe. */ for (parent = (*list)->parents->next; parent; parent = parent->next) { - int edge_value = hash_pos(parent->item->object.oid.hash, - ctx->commits.list, - ctx->commits.nr, - commit_to_sha1); + int edge_value = oid_pos(&parent->item->object.oid, + ctx->commits.list, + ctx->commits.nr, + commit_to_oid); if (edge_value >= 0) edge_value += ctx->new_num_commits_in_base; @@ -1306,11 +1432,11 @@ static void close_reachable(struct write_commit_graph_context *ctx) if (!commit) continue; if (ctx->split) { - if ((!parse_commit(commit) && + if ((!repo_parse_commit(ctx->r, commit) && commit_graph_position(commit) == COMMIT_NOT_FROM_GRAPH) || flags == COMMIT_GRAPH_SPLIT_REPLACE) add_missing_parents(ctx, commit); - } else if (!parse_commit_no_graph(commit)) + } else if (!repo_parse_commit_no_graph(ctx->r, commit)) add_missing_parents(ctx, commit); } stop_progress(&ctx->progress); @@ -1329,6 +1455,59 @@ static void close_reachable(struct write_commit_graph_context *ctx) stop_progress(&ctx->progress); } +static void compute_topological_levels(struct write_commit_graph_context *ctx) +{ + int i; + struct commit_list *list = NULL; + + if (ctx->report_progress) + ctx->progress = start_delayed_progress( + _("Computing commit graph topological levels"), + ctx->commits.nr); + for (i = 0; i < ctx->commits.nr; i++) { + struct commit *c = ctx->commits.list[i]; + uint32_t level; + + repo_parse_commit(ctx->r, c); + level = *topo_level_slab_at(ctx->topo_levels, c); + + display_progress(ctx->progress, i + 1); + if (level != GENERATION_NUMBER_ZERO) + continue; + + commit_list_insert(c, &list); + while (list) { + struct commit *current = list->item; + struct commit_list *parent; + int all_parents_computed = 1; + uint32_t max_level = 0; + + for (parent = current->parents; parent; parent = parent->next) { + repo_parse_commit(ctx->r, parent->item); + level = *topo_level_slab_at(ctx->topo_levels, parent->item); + + if (level == GENERATION_NUMBER_ZERO) { + all_parents_computed = 0; + commit_list_insert(parent->item, &list); + break; + } + + if (level > max_level) + max_level = level; + } + + if (all_parents_computed) { + pop_commit(&list); + + if (max_level > GENERATION_NUMBER_V1_MAX - 1) + max_level = GENERATION_NUMBER_V1_MAX - 1; + *topo_level_slab_at(ctx->topo_levels, current) = max_level + 1; + } + } + } + stop_progress(&ctx->progress); +} + static void compute_generation_numbers(struct write_commit_graph_context *ctx) { int i; @@ -1338,42 +1517,56 @@ static void compute_generation_numbers(struct write_commit_graph_context *ctx) ctx->progress = start_delayed_progress( _("Computing commit graph generation numbers"), ctx->commits.nr); + + if (!ctx->trust_generation_numbers) { + for (i = 0; i < ctx->commits.nr; i++) { + struct commit *c = ctx->commits.list[i]; + repo_parse_commit(ctx->r, c); + commit_graph_data_at(c)->generation = GENERATION_NUMBER_ZERO; + } + } + for (i = 0; i < ctx->commits.nr; i++) { - uint32_t generation = commit_graph_data_at(ctx->commits.list[i])->generation; + struct commit *c = ctx->commits.list[i]; + timestamp_t corrected_commit_date; + + repo_parse_commit(ctx->r, c); + corrected_commit_date = commit_graph_data_at(c)->generation; display_progress(ctx->progress, i + 1); - if (generation != GENERATION_NUMBER_INFINITY && - generation != GENERATION_NUMBER_ZERO) + if (corrected_commit_date != GENERATION_NUMBER_ZERO) continue; - commit_list_insert(ctx->commits.list[i], &list); + commit_list_insert(c, &list); while (list) { struct commit *current = list->item; struct commit_list *parent; int all_parents_computed = 1; - uint32_t max_generation = 0; + timestamp_t max_corrected_commit_date = 0; for (parent = current->parents; parent; parent = parent->next) { - generation = commit_graph_data_at(parent->item)->generation; + repo_parse_commit(ctx->r, parent->item); + corrected_commit_date = commit_graph_data_at(parent->item)->generation; - if (generation == GENERATION_NUMBER_INFINITY || - generation == GENERATION_NUMBER_ZERO) { + if (corrected_commit_date == GENERATION_NUMBER_ZERO) { all_parents_computed = 0; commit_list_insert(parent->item, &list); break; - } else if (generation > max_generation) { - max_generation = generation; } + + if (corrected_commit_date > max_corrected_commit_date) + max_corrected_commit_date = corrected_commit_date; } if (all_parents_computed) { - struct commit_graph_data *data = commit_graph_data_at(current); - - data->generation = max_generation + 1; pop_commit(&list); - if (data->generation > GENERATION_NUMBER_MAX) - data->generation = GENERATION_NUMBER_MAX; + if (current->date && current->date > max_corrected_commit_date) + max_corrected_commit_date = current->date - 1; + commit_graph_data_at(current)->generation = max_corrected_commit_date + 1; + + if (commit_graph_data_at(current)->generation - current->date > GENERATION_NUMBER_V2_OFFSET_MAX) + ctx->num_generation_data_overflows++; } } } @@ -1458,7 +1651,7 @@ static int add_ref_to_set(const char *refname, struct object_id peeled; struct refs_cb_data *data = (struct refs_cb_data *)cb_data; - if (!peel_ref(refname, &peeled)) + if (!peel_iterated_oid(oid, &peeled)) oid = &peeled; if (oid_object_info(the_repository, oid, NULL) == OBJ_COMMIT) oidset_insert(data->commits, oid); @@ -1593,9 +1786,9 @@ static void copy_oids_to_commits(struct write_commit_graph_context *ctx) continue; if (ctx->split && flags == COMMIT_GRAPH_SPLIT_REPLACE) - parse_commit(ctx->commits.list[ctx->commits.nr]); + repo_parse_commit(ctx->r, ctx->commits.list[ctx->commits.nr]); else - parse_commit_no_graph(ctx->commits.list[ctx->commits.nr]); + repo_parse_commit_no_graph(ctx->r, ctx->commits.list[ctx->commits.nr]); num_parents = commit_list_count(ctx->commits.list[ctx->commits.nr]->parents); if (num_parents > 2) @@ -1707,6 +1900,21 @@ static int write_commit_graph_file(struct write_commit_graph_context *ctx) chunks[2].id = GRAPH_CHUNKID_DATA; chunks[2].size = (hashsz + 16) * ctx->commits.nr; chunks[2].write_fn = write_graph_chunk_data; + + if (git_env_bool(GIT_TEST_COMMIT_GRAPH_NO_GDAT, 0)) + ctx->write_generation_data = 0; + if (ctx->write_generation_data) { + chunks[num_chunks].id = GRAPH_CHUNKID_GENERATION_DATA; + chunks[num_chunks].size = sizeof(uint32_t) * ctx->commits.nr; + chunks[num_chunks].write_fn = write_graph_chunk_generation_data; + num_chunks++; + } + if (ctx->num_generation_data_overflows) { + chunks[num_chunks].id = GRAPH_CHUNKID_GENERATION_DATA_OVERFLOW; + chunks[num_chunks].size = sizeof(timestamp_t) * ctx->num_generation_data_overflows; + chunks[num_chunks].write_fn = write_graph_chunk_generation_data_overflow; + num_chunks++; + } if (ctx->num_extra_edges) { chunks[num_chunks].id = GRAPH_CHUNKID_EXTRAEDGES; chunks[num_chunks].size = 4 * ctx->num_extra_edges; @@ -1918,6 +2126,13 @@ static void split_graph_merge_strategy(struct write_commit_graph_context *ctx) if (i < ctx->num_commit_graphs_after) ctx->commit_graph_hash_after[i] = xstrdup(oid_to_hex(&g->oid)); + /* + * If the topmost remaining layer has generation data chunk, the + * resultant layer also has generation data chunk. + */ + if (i == ctx->num_commit_graphs_after - 2) + ctx->write_generation_data = !!g->chunk_generation_data; + i--; g = g->base_graph; } @@ -2109,6 +2324,7 @@ int write_commit_graph(struct object_directory *odb, int res = 0; int replace = 0; struct bloom_filter_settings bloom_settings = DEFAULT_BLOOM_FILTER_SETTINGS; + struct topo_level_slab topo_levels; prepare_repo_settings(the_repository); if (!the_repository->settings.core_commit_graph) { @@ -2126,6 +2342,8 @@ int write_commit_graph(struct object_directory *odb, ctx->split = flags & COMMIT_GRAPH_WRITE_SPLIT ? 1 : 0; ctx->opts = opts; ctx->total_bloom_filter_data_size = 0; + ctx->write_generation_data = 1; + ctx->num_generation_data_overflows = 0; bloom_settings.bits_per_entry = git_env_ulong("GIT_TEST_BLOOM_SETTINGS_BITS_PER_ENTRY", bloom_settings.bits_per_entry); @@ -2135,11 +2353,23 @@ int write_commit_graph(struct object_directory *odb, bloom_settings.max_changed_paths); ctx->bloom_settings = &bloom_settings; + init_topo_level_slab(&topo_levels); + ctx->topo_levels = &topo_levels; + + prepare_commit_graph(ctx->r); + if (ctx->r->objects->commit_graph) { + struct commit_graph *g = ctx->r->objects->commit_graph; + + while (g) { + g->topo_levels = &topo_levels; + g = g->base_graph; + } + } + if (flags & COMMIT_GRAPH_WRITE_BLOOM_FILTERS) ctx->changed_paths = 1; if (!(flags & COMMIT_GRAPH_NO_WRITE_BLOOM_FILTERS)) { struct commit_graph *g; - prepare_commit_graph_one(ctx->r, ctx->odb); g = ctx->r->objects->commit_graph; @@ -2151,10 +2381,7 @@ int write_commit_graph(struct object_directory *odb, } if (ctx->split) { - struct commit_graph *g; - prepare_commit_graph(ctx->r); - - g = ctx->r->objects->commit_graph; + struct commit_graph *g = ctx->r->objects->commit_graph; while (g) { ctx->num_commit_graphs_before++; @@ -2178,9 +2405,6 @@ int write_commit_graph(struct object_directory *odb, ctx->approx_nr_objects = approximate_object_count(); - if (ctx->append) - prepare_commit_graph_one(ctx->r, ctx->odb); - if (ctx->append && ctx->r->objects->commit_graph) { struct commit_graph *g = ctx->r->objects->commit_graph; for (i = 0; i < g->num_commits; i++) { @@ -2227,7 +2451,11 @@ int write_commit_graph(struct object_directory *odb, } else ctx->num_commit_graphs_after = 1; - compute_generation_numbers(ctx); + ctx->trust_generation_numbers = validate_mixed_generation_chain(ctx->r->objects->commit_graph); + + compute_topological_levels(ctx); + if (ctx->write_generation_data) + compute_generation_numbers(ctx); if (ctx->changed_paths) compute_bloom_filters(ctx); @@ -2355,8 +2583,8 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags) for (i = 0; i < g->num_commits; i++) { struct commit *graph_commit, *odb_commit; struct commit_list *graph_parents, *odb_parents; - uint32_t max_generation = 0; - uint32_t generation; + timestamp_t max_generation = 0; + timestamp_t generation; display_progress(progress, i + 1); hashcpy(cur_oid.hash, g->chunk_oid_lookup + g->hash_len * i); @@ -2420,16 +2648,17 @@ int verify_commit_graph(struct repository *r, struct commit_graph *g, int flags) continue; /* - * If one of our parents has generation GENERATION_NUMBER_MAX, then - * our generation is also GENERATION_NUMBER_MAX. Decrement to avoid - * extra logic in the following condition. + * If we are using topological level and one of our parents has + * generation GENERATION_NUMBER_V1_MAX, then our generation is + * also GENERATION_NUMBER_V1_MAX. Decrement to avoid extra logic + * in the following condition. */ - if (max_generation == GENERATION_NUMBER_MAX) + if (!g->read_generation_data && max_generation == GENERATION_NUMBER_V1_MAX) max_generation--; generation = commit_graph_generation(graph_commit); - if (generation != max_generation + 1) - graph_report(_("commit-graph generation for commit %s is %u != %u"), + if (generation < max_generation + 1) + graph_report(_("commit-graph generation for commit %s is %"PRItime" < %"PRItime), oid_to_hex(&cur_oid), generation, max_generation + 1); diff --git a/commit-graph.h b/commit-graph.h index f8e92500c6..97f3497c27 100644 --- a/commit-graph.h +++ b/commit-graph.h @@ -6,6 +6,7 @@ #include "oidset.h" #define GIT_TEST_COMMIT_GRAPH "GIT_TEST_COMMIT_GRAPH" +#define GIT_TEST_COMMIT_GRAPH_NO_GDAT "GIT_TEST_COMMIT_GRAPH_NO_GDAT" #define GIT_TEST_COMMIT_GRAPH_DIE_ON_PARSE "GIT_TEST_COMMIT_GRAPH_DIE_ON_PARSE" #define GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS "GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS" @@ -63,16 +64,20 @@ struct commit_graph { struct object_directory *odb; uint32_t num_commits_in_base; + unsigned int read_generation_data; struct commit_graph *base_graph; const uint32_t *chunk_oid_fanout; const unsigned char *chunk_oid_lookup; const unsigned char *chunk_commit_data; + const unsigned char *chunk_generation_data; + const unsigned char *chunk_generation_data_overflow; const unsigned char *chunk_extra_edges; const unsigned char *chunk_base_graphs; const unsigned char *chunk_bloom_indexes; const unsigned char *chunk_bloom_data; + struct topo_level_slab *topo_levels; struct bloom_filter_settings *bloom_filter_settings; }; @@ -90,6 +95,12 @@ struct commit_graph *parse_commit_graph(struct repository *r, */ int generation_numbers_enabled(struct repository *r); +/* + * Return 1 if and only if the repository has a commit-graph + * file and generation data chunk has been written for the file. + */ +int corrected_commit_dates_enabled(struct repository *r); + struct bloom_filter_settings *get_bloom_filter_settings(struct repository *r); enum commit_graph_write_flags { @@ -144,12 +155,12 @@ void disable_commit_graph(struct repository *r); struct commit_graph_data { uint32_t graph_pos; - uint32_t generation; + timestamp_t generation; }; /* * Commits should be parsed before accessing generation, graph positions. */ -uint32_t commit_graph_generation(const struct commit *); +timestamp_t commit_graph_generation(const struct commit *); uint32_t commit_graph_position(const struct commit *); #endif diff --git a/commit-reach.c b/commit-reach.c index 50175b159e..e38771ca5a 100644 --- a/commit-reach.c +++ b/commit-reach.c @@ -32,14 +32,14 @@ static int queue_has_nonstale(struct prio_queue *queue) static struct commit_list *paint_down_to_common(struct repository *r, struct commit *one, int n, struct commit **twos, - int min_generation) + timestamp_t min_generation) { struct prio_queue queue = { compare_commits_by_gen_then_commit_date }; struct commit_list *result = NULL; int i; - uint32_t last_gen = GENERATION_NUMBER_INFINITY; + timestamp_t last_gen = GENERATION_NUMBER_INFINITY; - if (!min_generation) + if (!min_generation && !corrected_commit_dates_enabled(r)) queue.compare = compare_commits_by_commit_date; one->object.flags |= PARENT1; @@ -58,10 +58,10 @@ static struct commit_list *paint_down_to_common(struct repository *r, struct commit *commit = prio_queue_get(&queue); struct commit_list *parents; int flags; - uint32_t generation = commit_graph_generation(commit); + timestamp_t generation = commit_graph_generation(commit); if (min_generation && generation > last_gen) - BUG("bad generation skip %8x > %8x at %s", + BUG("bad generation skip %"PRItime" > %"PRItime" at %s", generation, last_gen, oid_to_hex(&commit->object.oid)); last_gen = generation; @@ -177,12 +177,12 @@ static int remove_redundant(struct repository *r, struct commit **array, int cnt repo_parse_commit(r, array[i]); for (i = 0; i < cnt; i++) { struct commit_list *common; - uint32_t min_generation = commit_graph_generation(array[i]); + timestamp_t min_generation = commit_graph_generation(array[i]); if (redundant[i]) continue; for (j = filled = 0; j < cnt; j++) { - uint32_t curr_generation; + timestamp_t curr_generation; if (i == j || redundant[j]) continue; filled_index[filled] = j; @@ -321,7 +321,7 @@ int repo_in_merge_bases_many(struct repository *r, struct commit *commit, { struct commit_list *bases; int ret = 0, i; - uint32_t generation, max_generation = GENERATION_NUMBER_ZERO; + timestamp_t generation, max_generation = GENERATION_NUMBER_ZERO; if (repo_parse_commit(r, commit)) return ret; @@ -470,7 +470,7 @@ static int in_commit_list(const struct commit_list *want, struct commit *c) static enum contains_result contains_test(struct commit *candidate, const struct commit_list *want, struct contains_cache *cache, - uint32_t cutoff) + timestamp_t cutoff) { enum contains_result *cached = contains_cache_at(cache, candidate); @@ -506,11 +506,11 @@ static enum contains_result contains_tag_algo(struct commit *candidate, { struct contains_stack contains_stack = { 0, 0, NULL }; enum contains_result result; - uint32_t cutoff = GENERATION_NUMBER_INFINITY; + timestamp_t cutoff = GENERATION_NUMBER_INFINITY; const struct commit_list *p; for (p = want; p; p = p->next) { - uint32_t generation; + timestamp_t generation; struct commit *c = p->item; load_commit_graph_info(the_repository, c); generation = commit_graph_generation(c); @@ -566,8 +566,8 @@ static int compare_commits_by_gen(const void *_a, const void *_b) const struct commit *a = *(const struct commit * const *)_a; const struct commit *b = *(const struct commit * const *)_b; - uint32_t generation_a = commit_graph_generation(a); - uint32_t generation_b = commit_graph_generation(b); + timestamp_t generation_a = commit_graph_generation(a); + timestamp_t generation_b = commit_graph_generation(b); if (generation_a < generation_b) return -1; @@ -580,7 +580,7 @@ int can_all_from_reach_with_flag(struct object_array *from, unsigned int with_flag, unsigned int assign_flag, time_t min_commit_date, - uint32_t min_generation) + timestamp_t min_generation) { struct commit **list = NULL; int i; @@ -681,13 +681,13 @@ int can_all_from_reach(struct commit_list *from, struct commit_list *to, time_t min_commit_date = cutoff_by_min_date ? from->item->date : 0; struct commit_list *from_iter = from, *to_iter = to; int result; - uint32_t min_generation = GENERATION_NUMBER_INFINITY; + timestamp_t min_generation = GENERATION_NUMBER_INFINITY; while (from_iter) { add_object_array(&from_iter->item->object, NULL, &from_objs); if (!parse_commit(from_iter->item)) { - uint32_t generation; + timestamp_t generation; if (from_iter->item->date < min_commit_date) min_commit_date = from_iter->item->date; @@ -701,7 +701,7 @@ int can_all_from_reach(struct commit_list *from, struct commit_list *to, while (to_iter) { if (!parse_commit(to_iter->item)) { - uint32_t generation; + timestamp_t generation; if (to_iter->item->date < min_commit_date) min_commit_date = to_iter->item->date; @@ -741,13 +741,13 @@ struct commit_list *get_reachable_subset(struct commit **from, int nr_from, struct commit_list *found_commits = NULL; struct commit **to_last = to + nr_to; struct commit **from_last = from + nr_from; - uint32_t min_generation = GENERATION_NUMBER_INFINITY; + timestamp_t min_generation = GENERATION_NUMBER_INFINITY; int num_to_find = 0; struct prio_queue queue = { compare_commits_by_gen_then_commit_date }; for (item = to; item < to_last; item++) { - uint32_t generation; + timestamp_t generation; struct commit *c = *item; parse_commit(c); diff --git a/commit-reach.h b/commit-reach.h index b49ad71a31..148b56fea5 100644 --- a/commit-reach.h +++ b/commit-reach.h @@ -87,7 +87,7 @@ int can_all_from_reach_with_flag(struct object_array *from, unsigned int with_flag, unsigned int assign_flag, time_t min_commit_date, - uint32_t min_generation); + timestamp_t min_generation); int can_all_from_reach(struct commit_list *from, struct commit_list *to, int commit_date_cutoff); @@ -105,23 +105,23 @@ static timestamp_t parse_commit_date(const char *buf, const char *tail) return parse_timestamp(dateptr, NULL, 10); } -static const unsigned char *commit_graft_sha1_access(size_t index, void *table) +static const struct object_id *commit_graft_oid_access(size_t index, const void *table) { - struct commit_graft **commit_graft_table = table; - return commit_graft_table[index]->oid.hash; + const struct commit_graft * const *commit_graft_table = table; + return &commit_graft_table[index]->oid; } -int commit_graft_pos(struct repository *r, const unsigned char *sha1) +int commit_graft_pos(struct repository *r, const struct object_id *oid) { - return hash_pos(sha1, r->parsed_objects->grafts, - r->parsed_objects->grafts_nr, - commit_graft_sha1_access); + return oid_pos(oid, r->parsed_objects->grafts, + r->parsed_objects->grafts_nr, + commit_graft_oid_access); } int register_commit_graft(struct repository *r, struct commit_graft *graft, int ignore_dups) { - int pos = commit_graft_pos(r, graft->oid.hash); + int pos = commit_graft_pos(r, &graft->oid); if (0 <= pos) { if (ignore_dups) @@ -232,7 +232,7 @@ struct commit_graft *lookup_commit_graft(struct repository *r, const struct obje { int pos; prepare_commit_graft(r); - pos = commit_graft_pos(r, oid->hash); + pos = commit_graft_pos(r, oid); if (pos < 0) return NULL; return r->parsed_objects->grafts[pos]; @@ -753,8 +753,8 @@ int compare_commits_by_author_date(const void *a_, const void *b_, int compare_commits_by_gen_then_commit_date(const void *a_, const void *b_, void *unused) { const struct commit *a = a_, *b = b_; - const uint32_t generation_a = commit_graph_generation(a), - generation_b = commit_graph_generation(b); + const timestamp_t generation_a = commit_graph_generation(a), + generation_b = commit_graph_generation(b); /* newer commits first */ if (generation_a < generation_b) @@ -995,7 +995,7 @@ static const char *gpg_sig_headers[] = { "gpgsig-sha256", }; -static int do_sign_commit(struct strbuf *buf, const char *keyid) +int sign_with_header(struct strbuf *buf, const char *keyid) { struct strbuf sig = STRBUF_INIT; int inspos, copypos; @@ -1035,21 +1035,32 @@ static int do_sign_commit(struct strbuf *buf, const char *keyid) return 0; } + + int parse_signed_commit(const struct commit *commit, - struct strbuf *payload, struct strbuf *signature) + struct strbuf *payload, struct strbuf *signature, + const struct git_hash_algo *algop) { - unsigned long size; const char *buffer = get_commit_buffer(commit, &size); - int in_signature, saw_signature = -1; - const char *line, *tail; - const char *gpg_sig_header = gpg_sig_headers[hash_algo_by_ptr(the_hash_algo)]; - int gpg_sig_header_len = strlen(gpg_sig_header); + int ret = parse_buffer_signed_by_header(buffer, size, payload, signature, algop); + + unuse_commit_buffer(commit, buffer); + return ret; +} + +int parse_buffer_signed_by_header(const char *buffer, + unsigned long size, + struct strbuf *payload, + struct strbuf *signature, + const struct git_hash_algo *algop) +{ + int in_signature = 0, saw_signature = 0, other_signature = 0; + const char *line, *tail, *p; + const char *gpg_sig_header = gpg_sig_headers[hash_algo_by_ptr(algop)]; line = buffer; tail = buffer + size; - in_signature = 0; - saw_signature = 0; while (line < tail) { const char *sig = NULL; const char *next = memchr(line, '\n', tail - line); @@ -1057,9 +1068,15 @@ int parse_signed_commit(const struct commit *commit, next = next ? next + 1 : tail; if (in_signature && line[0] == ' ') sig = line + 1; - else if (starts_with(line, gpg_sig_header) && - line[gpg_sig_header_len] == ' ') - sig = line + gpg_sig_header_len + 1; + else if (skip_prefix(line, gpg_sig_header, &p) && + *p == ' ') { + sig = line + strlen(gpg_sig_header) + 1; + other_signature = 0; + } + else if (starts_with(line, "gpgsig")) + other_signature = 1; + else if (other_signature && line[0] != ' ') + other_signature = 0; if (sig) { strbuf_add(signature, sig, next - sig); saw_signature = 1; @@ -1068,12 +1085,12 @@ int parse_signed_commit(const struct commit *commit, if (*line == '\n') /* dump the whole remainder of the buffer */ next = tail; - strbuf_add(payload, line, next - line); + if (!other_signature) + strbuf_add(payload, line, next - line); in_signature = 0; } line = next; } - unuse_commit_buffer(commit, buffer); return saw_signature; } @@ -1082,23 +1099,29 @@ int remove_signature(struct strbuf *buf) const char *line = buf->buf; const char *tail = buf->buf + buf->len; int in_signature = 0; - const char *sig_start = NULL; - const char *sig_end = NULL; + struct sigbuf { + const char *start; + const char *end; + } sigs[2], *sigp = &sigs[0]; + int i; + const char *orig_buf = buf->buf; + + memset(sigs, 0, sizeof(sigs)); while (line < tail) { const char *next = memchr(line, '\n', tail - line); next = next ? next + 1 : tail; if (in_signature && line[0] == ' ') - sig_end = next; + sigp->end = next; else if (starts_with(line, "gpgsig")) { int i; for (i = 1; i < GIT_HASH_NALGOS; i++) { const char *p; if (skip_prefix(line, gpg_sig_headers[i], &p) && *p == ' ') { - sig_start = line; - sig_end = next; + sigp->start = line; + sigp->end = next; in_signature = 1; } } @@ -1106,15 +1129,18 @@ int remove_signature(struct strbuf *buf) if (*line == '\n') /* dump the whole remainder of the buffer */ next = tail; + if (in_signature && sigp - sigs != ARRAY_SIZE(sigs)) + sigp++; in_signature = 0; } line = next; } - if (sig_start) - strbuf_remove(buf, sig_start - buf->buf, sig_end - sig_start); + for (i = ARRAY_SIZE(sigs) - 1; i >= 0; i--) + if (sigs[i].start) + strbuf_remove(buf, sigs[i].start - orig_buf, sigs[i].end - sigs[i].start); - return sig_start != NULL; + return sigs[0].start != NULL; } static void handle_signed_tag(struct commit *parent, struct commit_extra_header ***tail) @@ -1122,8 +1148,10 @@ static void handle_signed_tag(struct commit *parent, struct commit_extra_header struct merge_remote_desc *desc; struct commit_extra_header *mergetag; char *buf; - unsigned long size, len; + unsigned long size; enum object_type type; + struct strbuf payload = STRBUF_INIT; + struct strbuf signature = STRBUF_INIT; desc = merge_remote_util(parent); if (!desc || !desc->obj) @@ -1131,8 +1159,7 @@ static void handle_signed_tag(struct commit *parent, struct commit_extra_header buf = read_object_file(&desc->obj->oid, &type, &size); if (!buf || type != OBJ_TAG) goto free_return; - len = parse_signature(buf, size); - if (size == len) + if (!parse_signature(buf, size, &payload, &signature)) goto free_return; /* * We could verify this signature and either omit the tag when @@ -1151,6 +1178,8 @@ static void handle_signed_tag(struct commit *parent, struct commit_extra_header **tail = mergetag; *tail = &mergetag->next; + strbuf_release(&payload); + strbuf_release(&signature); return; free_return: @@ -1165,7 +1194,7 @@ int check_commit_signature(const struct commit *commit, struct signature_check * sigc->result = 'N'; - if (parse_signed_commit(commit, &payload, &signature) <= 0) + if (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0) goto out; ret = check_signature(payload.buf, payload.len, signature.buf, signature.len, sigc); @@ -1515,7 +1544,7 @@ int commit_tree_extended(const char *msg, size_t msg_len, if (encoding_is_utf8 && !verify_utf8(&buffer)) fprintf(stderr, _(commit_utf8_warn)); - if (sign_commit && do_sign_commit(&buffer, sign_commit)) { + if (sign_commit && sign_with_header(&buffer, sign_commit)) { result = -1; goto out; } @@ -11,9 +11,10 @@ #include "commit-slab.h" #define COMMIT_NOT_FROM_GRAPH 0xFFFFFFFF -#define GENERATION_NUMBER_INFINITY 0xFFFFFFFF -#define GENERATION_NUMBER_MAX 0x3FFFFFFF +#define GENERATION_NUMBER_INFINITY ((1ULL << 63) - 1) +#define GENERATION_NUMBER_V1_MAX 0x3FFFFFFF #define GENERATION_NUMBER_ZERO 0 +#define GENERATION_NUMBER_V2_OFFSET_MAX ((1ULL << 31) - 1) struct commit_list { struct commit *item; @@ -88,9 +89,10 @@ static inline int repo_parse_commit(struct repository *r, struct commit *item) return repo_parse_commit_gently(r, item, 0); } -static inline int parse_commit_no_graph(struct commit *commit) +static inline int repo_parse_commit_no_graph(struct repository *r, + struct commit *commit) { - return repo_parse_commit_internal(the_repository, commit, 0, 0); + return repo_parse_commit_internal(r, commit, 0, 0); } #ifndef NO_THE_REPOSITORY_COMPATIBILITY_MACROS @@ -239,7 +241,7 @@ typedef int (*each_commit_graft_fn)(const struct commit_graft *, void *); struct commit_graft *read_graft_line(struct strbuf *line); /* commit_graft_pos returns an index into r->parsed_objects->grafts. */ -int commit_graft_pos(struct repository *r, const unsigned char *sha1); +int commit_graft_pos(struct repository *r, const struct object_id *oid); int register_commit_graft(struct repository *r, struct commit_graft *, int); void prepare_commit_graft(struct repository *r); struct commit_graft *lookup_commit_graft(struct repository *r, const struct object_id *oid); @@ -317,7 +319,8 @@ void set_merge_remote_desc(struct commit *commit, struct commit *get_merge_parent(const char *name); int parse_signed_commit(const struct commit *commit, - struct strbuf *message, struct strbuf *signature); + struct strbuf *message, struct strbuf *signature, + const struct git_hash_algo *algop); int remove_signature(struct strbuf *buf); /* @@ -359,4 +362,13 @@ int compare_commits_by_gen_then_commit_date(const void *a_, const void *b_, void LAST_ARG_MUST_BE_NULL int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...); +/* Sign a commit or tag buffer, storing the result in a header. */ +int sign_with_header(struct strbuf *buf, const char *keyid); +/* Parse the signature out of a header. */ +int parse_buffer_signed_by_header(const char *buffer, + unsigned long size, + struct strbuf *payload, + struct strbuf *signature, + const struct git_hash_algo *algop); + #endif /* COMMIT_H */ diff --git a/compat/precompose_utf8.c b/compat/precompose_utf8.c index 136250fbf6..ec560565a8 100644 --- a/compat/precompose_utf8.c +++ b/compat/precompose_utf8.c @@ -60,32 +60,46 @@ void probe_utf8_pathname_composition(void) strbuf_release(&path); } - -void precompose_argv(int argc, const char **argv) +static inline const char *precompose_string_if_needed(const char *in) { - int i = 0; - const char *oldarg; - char *newarg; - iconv_t ic_precompose; + size_t inlen; + size_t outlen; + if (has_non_ascii(in, (size_t)-1, &inlen)) { + iconv_t ic_prec; + char *out; + if (precomposed_unicode < 0) + git_config_get_bool("core.precomposeunicode", &precomposed_unicode); + if (precomposed_unicode != 1) + return in; + ic_prec = iconv_open(repo_encoding, path_encoding); + if (ic_prec == (iconv_t) -1) + return in; + + out = reencode_string_iconv(in, inlen, ic_prec, 0, &outlen); + if (out) { + if (outlen == inlen && !memcmp(in, out, outlen)) + free(out); /* no need to return indentical */ + else + in = out; + } + iconv_close(ic_prec); - if (precomposed_unicode != 1) - return; + } + return in; +} - ic_precompose = iconv_open(repo_encoding, path_encoding); - if (ic_precompose == (iconv_t) -1) - return; +const char *precompose_argv_prefix(int argc, const char **argv, const char *prefix) +{ + int i = 0; while (i < argc) { - size_t namelen; - oldarg = argv[i]; - if (has_non_ascii(oldarg, (size_t)-1, &namelen)) { - newarg = reencode_string_iconv(oldarg, namelen, ic_precompose, 0, NULL); - if (newarg) - argv[i] = newarg; - } + argv[i] = precompose_string_if_needed(argv[i]); i++; } - iconv_close(ic_precompose); + if (prefix) { + prefix = precompose_string_if_needed(prefix); + } + return prefix; } diff --git a/compat/precompose_utf8.h b/compat/precompose_utf8.h index 6f843d3e1a..d70b84665c 100644 --- a/compat/precompose_utf8.h +++ b/compat/precompose_utf8.h @@ -28,7 +28,7 @@ typedef struct { struct dirent_prec_psx *dirent_nfc; } PREC_DIR; -void precompose_argv(int argc, const char **argv); +const char *precompose_argv_prefix(int argc, const char **argv, const char *prefix); void probe_utf8_pathname_composition(void); PREC_DIR *precompose_utf8_opendir(const char *dirname); @@ -1155,15 +1155,6 @@ static void die_bad_number(const char *name, const char *value) if (!value) value = ""; - if (!strcmp(name, "GIT_TEST_GETTEXT_POISON")) - /* - * We explicitly *don't* use _() here since it would - * cause an infinite loop with _() needing to call - * use_gettext_poison(). This is why marked up - * translations with N_() above. - */ - die(bad_numeric, value, name, error_type); - if (!(cf && cf->name)) die(_(bad_numeric), value, name, _(error_type)); @@ -1189,6 +1180,20 @@ static void die_bad_number(const char *name, const char *value) } } +NORETURN +static void die_bad_bool(const char *name, const char *value) +{ + if (!strcmp(name, "GIT_TEST_GETTEXT_POISON")) + /* + * We explicitly *don't* use _() here since it would + * cause an infinite loop with _() needing to call + * use_gettext_poison(). + */ + die("bad boolean config value '%s' for '%s'", value, name); + else + die(_("bad boolean config value '%s' for '%s'"), value, name); +} + int git_config_int(const char *name, const char *value) { int ret; @@ -1261,8 +1266,10 @@ int git_config_bool_or_int(const char *name, const char *value, int *is_bool) int git_config_bool(const char *name, const char *value) { - int discard; - return !!git_config_bool_or_int(name, value, &discard); + int v = git_parse_maybe_bool(value); + if (v < 0) + die_bad_bool(name, value); + return v; } int git_config_string(const char **dest, const char *var, const char *value) diff --git a/config.mak.uname b/config.mak.uname index 198ab1e58f..e22d4b6d67 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -664,7 +664,6 @@ else NO_GETTEXT = USE_GETTEXT_SCHEME = fallthrough USE_LIBPCRE= YesPlease - NO_LIBPCRE1_JIT = UnfortunatelyYes NO_CURL = USE_NED_ALLOCATOR = YesPlease else diff --git a/configure.ac b/configure.ac index 66aedb9288..031e8d3fee 100644 --- a/configure.ac +++ b/configure.ac @@ -252,16 +252,17 @@ GIT_PARSE_WITH([openssl])) # Define USE_LIBPCRE if you have and want to use libpcre. Various # commands such as log and grep offer runtime options to use # Perl-compatible regular expressions instead of standard or extended -# POSIX regular expressions. -# -# USE_LIBPCRE is a synonym for USE_LIBPCRE2, define USE_LIBPCRE1 -# instead if you'd like to use the legacy version 1 of the PCRE -# library. Support for version 1 will likely be removed in some future -# release of Git, as upstream has all but abandoned it. +# POSIX regular expressions. Only libpcre version 2 is supported. # # Define LIBPCREDIR=/foo/bar if your PCRE header and library files are in # /foo/bar/include and /foo/bar/lib directories. # + +AC_ARG_WITH(libpcre1, +AS_HELP_STRING([--with-libpcre1],[DEPRECATED]), + AC_MSG_ERROR([support for --with-libpcre1 has been removed. Use --with-libpcre2!]) + ) + AC_ARG_WITH(libpcre, AS_HELP_STRING([--with-libpcre],[synonym for --with-libpcre2]), if test "$withval" = "no"; then @@ -277,22 +278,6 @@ AS_HELP_STRING([--with-libpcre],[synonym for --with-libpcre2]), GIT_CONF_SUBST([LIBPCREDIR]) fi) -AC_ARG_WITH(libpcre1, -AS_HELP_STRING([--with-libpcre1],[support Perl-compatible regexes via libpcre1 (default is NO)]) -AS_HELP_STRING([], [ARG can be also prefix for libpcre library and headers]), - if test "$withval" = "no"; then - USE_LIBPCRE1= - elif test "$withval" = "yes"; then - USE_LIBPCRE1=YesPlease - else - USE_LIBPCRE1=YesPlease - LIBPCREDIR=$withval - AC_MSG_NOTICE([Setting LIBPCREDIR to $LIBPCREDIR]) - dnl USE_LIBPCRE1 can still be modified below, so don't substitute - dnl it yet. - GIT_CONF_SUBST([LIBPCREDIR]) - fi) - AC_ARG_WITH(libpcre2, AS_HELP_STRING([--with-libpcre2],[support Perl-compatible regexes via libpcre2 (default is NO)]) AS_HELP_STRING([], [ARG can be also prefix for libpcre library and headers]), @@ -300,10 +285,6 @@ AS_HELP_STRING([], [ARG can be also prefix for libpcre library and hea AC_MSG_ERROR([Only supply one of --with-libpcre or its synonym --with-libpcre2!]) fi - if test -n "$USE_LIBPCRE1"; then - AC_MSG_ERROR([Only supply one of --with-libpcre1 or --with-libpcre2!]) - fi - if test "$withval" = "no"; then USE_LIBPCRE2= elif test "$withval" = "yes"; then @@ -554,25 +535,9 @@ GIT_CONF_SUBST([NEEDS_SSL_WITH_CRYPTO]) GIT_CONF_SUBST([NO_OPENSSL]) # -# Handle the USE_LIBPCRE1 and USE_LIBPCRE2 options potentially set -# above. +# Handle the USE_LIBPCRE options potentially set above. # -if test -n "$USE_LIBPCRE1"; then - -GIT_STASH_FLAGS($LIBPCREDIR) - -AC_CHECK_LIB([pcre], [pcre_version], -[USE_LIBPCRE1=YesPlease], -[USE_LIBPCRE1=]) - -GIT_UNSTASH_FLAGS($LIBPCREDIR) - -GIT_CONF_SUBST([USE_LIBPCRE1]) - -fi - - if test -n "$USE_LIBPCRE2"; then GIT_STASH_FLAGS($LIBPCREDIR) @@ -376,7 +376,8 @@ struct ref **get_remote_heads(struct packet_reader *reader, } /* Returns 1 when a valid ref has been added to `list`, 0 otherwise */ -static int process_ref_v2(struct packet_reader *reader, struct ref ***list) +static int process_ref_v2(struct packet_reader *reader, struct ref ***list, + char **unborn_head_target) { int ret = 1; int i = 0; @@ -397,6 +398,25 @@ static int process_ref_v2(struct packet_reader *reader, struct ref ***list) goto out; } + if (!strcmp("unborn", line_sections.items[i].string)) { + i++; + if (unborn_head_target && + !strcmp("HEAD", line_sections.items[i++].string)) { + /* + * Look for the symref target (if any). If found, + * return it to the caller. + */ + for (; i < line_sections.nr; i++) { + const char *arg = line_sections.items[i].string; + + if (skip_prefix(arg, "symref-target:", &arg)) { + *unborn_head_target = xstrdup(arg); + break; + } + } + } + goto out; + } if (parse_oid_hex_algop(line_sections.items[i++].string, &old_oid, &end, reader->hash_algo) || *end) { ret = 0; @@ -453,12 +473,16 @@ void check_stateless_delimiter(int stateless_rpc, struct ref **get_remote_refs(int fd_out, struct packet_reader *reader, struct ref **list, int for_push, - const struct strvec *ref_prefixes, + struct transport_ls_refs_options *transport_options, const struct string_list *server_options, int stateless_rpc) { int i; const char *hash_name; + struct strvec *ref_prefixes = transport_options ? + &transport_options->ref_prefixes : NULL; + char **unborn_head_target = transport_options ? + &transport_options->unborn_head_target : NULL; *list = NULL; if (server_supports_v2("ls-refs", 1)) @@ -488,6 +512,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader, if (!for_push) packet_write_fmt(fd_out, "peel\n"); packet_write_fmt(fd_out, "symrefs\n"); + if (server_supports_feature("ls-refs", "unborn", 0)) + packet_write_fmt(fd_out, "unborn\n"); for (i = 0; ref_prefixes && i < ref_prefixes->nr; i++) { packet_write_fmt(fd_out, "ref-prefix %s\n", ref_prefixes->v[i]); @@ -496,7 +522,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader, /* Process response from server */ while (packet_reader_read(reader) == PACKET_READ_NORMAL) { - if (!process_ref_v2(reader, &list)) + if (!process_ref_v2(reader, &list, unborn_head_target)) die(_("invalid ls-refs response: %s"), reader->line); } diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt index c151dd7257..ac3dbc079a 100644 --- a/contrib/buildsystems/CMakeLists.txt +++ b/contrib/buildsystems/CMakeLists.txt @@ -910,9 +910,7 @@ set(PYTHON_PATH /usr/bin/python) set(TAR tar) set(NO_CURL ) set(NO_EXPAT ) -set(USE_LIBPCRE1 ) set(USE_LIBPCRE2 ) -set(NO_LIBPCRE1_JIT ) set(NO_PERL ) set(NO_PTHREADS ) set(NO_PYTHON ) @@ -949,8 +947,6 @@ file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "PYTHON_PATH='${PYTHON_PATH}'\ file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "TAR='${TAR}'\n") file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_CURL='${NO_CURL}'\n") file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_EXPAT='${NO_EXPAT}'\n") -file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "USE_LIBPCRE1='${USE_LIBPCRE1}'\n") -file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_LIBPCRE1_JIT='${NO_LIBPCRE1_JIT}'\n") file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_PERL='${NO_PERL}'\n") file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_PTHREADS='${NO_PTHREADS}'\n") file(APPEND ${CMAKE_BINARY_DIR}/GIT-BUILD-OPTIONS "NO_UNIX_SOCKETS='${NO_UNIX_SOCKETS}'\n") diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 4b1f4264a6..7dc6cd8eb8 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -1447,8 +1447,10 @@ _git_branch () while [ $c -lt $cword ]; do i="${words[c]}" case "$i" in - -d|--delete|-m|--move) only_local_ref="y" ;; - -r|--remotes) has_r="y" ;; + -d|-D|--delete|-m|-M|--move|-c|-C|--copy) + only_local_ref="y" ;; + -r|--remotes) + has_r="y" ;; esac ((c++)) done diff --git a/diff-merges.c b/diff-merges.c new file mode 100644 index 0000000000..146bb50316 --- /dev/null +++ b/diff-merges.c @@ -0,0 +1,146 @@ +#include "diff-merges.h" + +#include "revision.h" + +static void suppress(struct rev_info *revs) +{ + revs->separate_merges = 0; + revs->first_parent_merges = 0; + revs->combine_merges = 0; + revs->dense_combined_merges = 0; + revs->combined_all_paths = 0; + revs->combined_imply_patch = 0; + revs->merges_need_diff = 0; +} + +static void set_separate(struct rev_info *revs) +{ + suppress(revs); + revs->separate_merges = 1; +} + +static void set_first_parent(struct rev_info *revs) +{ + set_separate(revs); + revs->first_parent_merges = 1; +} + +static void set_m(struct rev_info *revs) +{ + /* + * To "diff-index", "-m" means "match missing", and to the "log" + * family of commands, it means "show full diff for merges". Set + * both fields appropriately. + */ + set_separate(revs); + revs->match_missing = 1; +} + +static void set_combined(struct rev_info *revs) +{ + suppress(revs); + revs->combine_merges = 1; + revs->dense_combined_merges = 0; +} + +static void set_dense_combined(struct rev_info *revs) +{ + suppress(revs); + revs->combine_merges = 1; + revs->dense_combined_merges = 1; +} + +static void set_diff_merges(struct rev_info *revs, const char *optarg) +{ + if (!strcmp(optarg, "off") || !strcmp(optarg, "none")) { + suppress(revs); + /* Return early to leave revs->merges_need_diff unset */ + return; + } + + if (!strcmp(optarg, "1") || !strcmp(optarg, "first-parent")) + set_first_parent(revs); + else if (!strcmp(optarg, "m") || !strcmp(optarg, "separate")) + set_separate(revs); + else if (!strcmp(optarg, "c") || !strcmp(optarg, "combined")) + set_combined(revs); + else if (!strcmp(optarg, "cc") || !strcmp(optarg, "dense-combined")) + set_dense_combined(revs); + else + die(_("unknown value for --diff-merges: %s"), optarg); + + /* The flag is cleared by set_xxx() functions, so don't move this up */ + revs->merges_need_diff = 1; +} + +/* + * Public functions. They are in the order they are called. + */ + +int diff_merges_parse_opts(struct rev_info *revs, const char **argv) +{ + int argcount = 1; + const char *optarg; + const char *arg = argv[0]; + + if (!strcmp(arg, "-m")) { + set_m(revs); + } else if (!strcmp(arg, "-c")) { + set_combined(revs); + revs->combined_imply_patch = 1; + } else if (!strcmp(arg, "--cc")) { + set_dense_combined(revs); + revs->combined_imply_patch = 1; + } else if (!strcmp(arg, "--no-diff-merges")) { + suppress(revs); + } else if (!strcmp(arg, "--combined-all-paths")) { + revs->combined_all_paths = 1; + } else if ((argcount = parse_long_opt("diff-merges", argv, &optarg))) { + set_diff_merges(revs, optarg); + } else + return 0; + + revs->explicit_diff_merges = 1; + return argcount; +} + +void diff_merges_suppress(struct rev_info *revs) +{ + suppress(revs); +} + +void diff_merges_default_to_first_parent(struct rev_info *revs) +{ + if (!revs->explicit_diff_merges) + revs->separate_merges = 1; + if (revs->separate_merges) + revs->first_parent_merges = 1; +} + +void diff_merges_default_to_dense_combined(struct rev_info *revs) +{ + if (!revs->explicit_diff_merges) + set_dense_combined(revs); +} + +void diff_merges_set_dense_combined_if_unset(struct rev_info *revs) +{ + if (!revs->combine_merges) + set_dense_combined(revs); +} + +void diff_merges_setup_revs(struct rev_info *revs) +{ + if (revs->combine_merges == 0) + revs->dense_combined_merges = 0; + if (revs->separate_merges == 0) + revs->first_parent_merges = 0; + if (revs->combined_all_paths && !revs->combine_merges) + die("--combined-all-paths makes no sense without -c or --cc"); + if (revs->combined_imply_patch) + revs->diff = 1; + if (revs->combined_imply_patch || revs->merges_need_diff) { + if (!revs->diffopt.output_format) + revs->diffopt.output_format = DIFF_FORMAT_PATCH; + } +} diff --git a/diff-merges.h b/diff-merges.h new file mode 100644 index 0000000000..659467c99a --- /dev/null +++ b/diff-merges.h @@ -0,0 +1,24 @@ +#ifndef DIFF_MERGES_H +#define DIFF_MERGES_H + +/* + * diff-merges - utility module to handle command-line options for + * selection of particular diff format of merge commits + * representation. + */ + +struct rev_info; + +int diff_merges_parse_opts(struct rev_info *revs, const char **argv); + +void diff_merges_suppress(struct rev_info *revs); + +void diff_merges_default_to_first_parent(struct rev_info *revs); + +void diff_merges_default_to_dense_combined(struct rev_info *revs); + +void diff_merges_set_dense_combined_if_unset(struct rev_info *revs); + +void diff_merges_setup_revs(struct rev_info *revs); + +#endif diff --git a/diffcore-rename.c b/diffcore-rename.c index 90db9ebd6d..8fe6c9384b 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -465,6 +465,7 @@ void diffcore_rename(struct diff_options *options) int num_destinations, dst_cnt; struct progress *progress = NULL; + trace2_region_enter("diff", "setup", options->repo); if (!minimum_score) minimum_score = DEFAULT_RENAME_SCORE; @@ -510,14 +511,17 @@ void diffcore_rename(struct diff_options *options) register_rename_src(p); } } + trace2_region_leave("diff", "setup", options->repo); if (rename_dst_nr == 0 || rename_src_nr == 0) goto cleanup; /* nothing to do */ + trace2_region_enter("diff", "exact renames", options->repo); /* * We really want to cull the candidates list early * with cheap tests in order to avoid doing deltas. */ rename_count = find_exact_renames(options); + trace2_region_leave("diff", "exact renames", options->repo); /* Did we only want exact renames? */ if (minimum_score == MAX_SCORE) @@ -545,6 +549,7 @@ void diffcore_rename(struct diff_options *options) break; } + trace2_region_enter("diff", "inexact renames", options->repo); if (options->show_rename_progress) { progress = start_delayed_progress( _("Performing inexact rename detection"), @@ -600,11 +605,13 @@ void diffcore_rename(struct diff_options *options) if (detect_rename == DIFF_DETECT_COPY) rename_count += find_renames(mx, dst_cnt, minimum_score, 1); free(mx); + trace2_region_leave("diff", "inexact renames", options->repo); cleanup: /* At this point, we have found some renames and copies and they * are recorded in rename_dst. The original list is still in *q. */ + trace2_region_enter("diff", "write back to queue", options->repo); DIFF_QUEUE_CLEAR(&outq); for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; @@ -680,5 +687,6 @@ void diffcore_rename(struct diff_options *options) strintmap_clear(break_idx); FREE_AND_NULL(break_idx); } + trace2_region_leave("diff", "write back to queue", options->repo); return; } @@ -2998,6 +2998,23 @@ void setup_standard_excludes(struct dir_struct *dir) } } +char *get_sparse_checkout_filename(void) +{ + return git_pathdup("info/sparse-checkout"); +} + +int get_sparse_checkout_patterns(struct pattern_list *pl) +{ + int res; + char *sparse_filename = get_sparse_checkout_filename(); + + pl->use_cone_patterns = core_sparse_checkout_cone; + res = add_patterns_from_file_to_list(sparse_filename, "", 0, pl, NULL); + + free(sparse_filename); + return res; +} + int remove_path(const char *name) { char *slash; @@ -448,6 +448,8 @@ int is_empty_dir(const char *dir); void setup_standard_excludes(struct dir_struct *dir); +char *get_sparse_checkout_filename(void); +int get_sparse_checkout_patterns(struct pattern_list *pl); /* Constants for remove_dir_recursively: */ diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c index 9a664a4a58..1e51492a05 100644 --- a/fmt-merge-msg.c +++ b/fmt-merge-msg.c @@ -2,6 +2,7 @@ #include "refs.h" #include "object-store.h" #include "diff.h" +#include "diff-merges.h" #include "revision.h" #include "tag.h" #include "string-list.h" @@ -509,22 +510,28 @@ static void fmt_merge_msg_sigs(struct strbuf *out) for (i = 0; i < origins.nr; i++) { struct object_id *oid = origins.items[i].util; enum object_type type; - unsigned long size, len; + unsigned long size; char *buf = read_object_file(oid, &type, &size); + char *origbuf = buf; + unsigned long len = size; struct signature_check sigc = { NULL }; - struct strbuf sig = STRBUF_INIT; + struct strbuf payload = STRBUF_INIT, sig = STRBUF_INIT; if (!buf || type != OBJ_TAG) goto next; - len = parse_signature(buf, size); - if (size == len) - ; /* merely annotated */ - else if (check_signature(buf, len, buf + len, size - len, &sigc) && - !sigc.gpg_output) - strbuf_addstr(&sig, "gpg verification failed.\n"); - else - strbuf_addstr(&sig, sigc.gpg_output); + if (!parse_signature(buf, size, &payload, &sig)) + ;/* merely annotated */ + else { + buf = payload.buf; + len = payload.len; + if (check_signature(payload.buf, payload.len, sig.buf, + sig.len, &sigc) && + !sigc.gpg_output) + strbuf_addstr(&sig, "gpg verification failed.\n"); + else + strbuf_addstr(&sig, sigc.gpg_output); + } signature_check_clear(&sigc); if (!tag_number++) { @@ -547,9 +554,10 @@ static void fmt_merge_msg_sigs(struct strbuf *out) strlen(origins.items[i].string)); fmt_tag_signature(&tagbuf, &sig, buf, len); } + strbuf_release(&payload); strbuf_release(&sig); next: - free(buf); + free(origbuf); } if (tagbuf.len) { strbuf_addch(out, '\n'); @@ -670,7 +678,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out, head = lookup_commit_or_die(&head_oid, "HEAD"); repo_init_revisions(the_repository, &rev, NULL); rev.commit_format = CMIT_FMT_ONELINE; - rev.ignore_merges = 1; + diff_merges_suppress(&rev); rev.limited = 1; strbuf_complete_line(out); @@ -463,6 +463,11 @@ static int fsck_walk_commit(struct commit *commit, void *data, struct fsck_optio generation += power * (name[--len] - '0'); if (power > 1 && len && name[len - 1] == '~') name_prefix_len = len - 1; + else { + /* Maybe a non-first parent, e.g. HEAD^2 */ + generation = 0; + name_prefix_len = len; + } } } diff --git a/fsmonitor.c b/fsmonitor.c index ca031c3abb..fe9e9d7baf 100644 --- a/fsmonitor.c +++ b/fsmonitor.c @@ -13,14 +13,19 @@ struct trace_key trace_fsmonitor = TRACE_KEY_INIT(FSMONITOR); +static void assert_index_minimum(struct index_state *istate, size_t pos) +{ + if (pos > istate->cache_nr) + BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" > %u)", + (uintmax_t)pos, istate->cache_nr); +} + static void fsmonitor_ewah_callback(size_t pos, void *is) { struct index_state *istate = (struct index_state *)is; struct cache_entry *ce; - if (pos >= istate->cache_nr) - BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" >= %u)", - (uintmax_t)pos, istate->cache_nr); + assert_index_minimum(istate, pos + 1); ce = istate->cache[pos]; ce->ce_flags &= ~CE_FSMONITOR_VALID; @@ -82,10 +87,8 @@ int read_fsmonitor_extension(struct index_state *istate, const void *data, } istate->fsmonitor_dirty = fsmonitor_dirty; - if (!istate->split_index && - istate->fsmonitor_dirty->bit_size > istate->cache_nr) - BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" > %u)", - (uintmax_t)istate->fsmonitor_dirty->bit_size, istate->cache_nr); + if (!istate->split_index) + assert_index_minimum(istate, istate->fsmonitor_dirty->bit_size); trace_printf_key(&trace_fsmonitor, "read fsmonitor extension successful"); return 0; @@ -110,10 +113,8 @@ void write_fsmonitor_extension(struct strbuf *sb, struct index_state *istate) uint32_t ewah_size = 0; int fixup = 0; - if (!istate->split_index && - istate->fsmonitor_dirty->bit_size > istate->cache_nr) - BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" > %u)", - (uintmax_t)istate->fsmonitor_dirty->bit_size, istate->cache_nr); + if (!istate->split_index) + assert_index_minimum(istate, istate->fsmonitor_dirty->bit_size); put_be32(&hdr_version, INDEX_EXTENSION_VERSION2); strbuf_add(sb, &hdr_version, sizeof(uint32_t)); @@ -335,9 +336,7 @@ void tweak_fsmonitor(struct index_state *istate) } /* Mark all previously saved entries as dirty */ - if (istate->fsmonitor_dirty->bit_size > istate->cache_nr) - BUG("fsmonitor_dirty has more entries than the index (%"PRIuMAX" > %u)", - (uintmax_t)istate->fsmonitor_dirty->bit_size, istate->cache_nr); + assert_index_minimum(istate, istate->fsmonitor_dirty->bit_size); ewah_each_bit(istate->fsmonitor_dirty, fsmonitor_ewah_callback, istate); refresh_fsmonitor(istate); @@ -65,14 +65,6 @@ const char *get_preferred_languages(void) return NULL; } -int use_gettext_poison(void) -{ - static int poison_requested = -1; - if (poison_requested == -1) - poison_requested = git_env_bool("GIT_TEST_GETTEXT_POISON", 0); - return poison_requested; -} - #ifndef NO_GETTEXT static int test_vsnprintf(const char *fmt, ...) { @@ -117,8 +109,6 @@ void git_setup_gettext(void) if (!podir) podir = p = system_path(GIT_LOCALE_PATH); - use_gettext_poison(); /* getenv() reentrancy paranoia */ - if (!is_directory(podir)) { free(p); return; @@ -28,15 +28,12 @@ #define FORMAT_PRESERVING(n) __attribute__((format_arg(n))) -int use_gettext_poison(void); - #ifndef NO_GETTEXT void git_setup_gettext(void); int gettext_width(const char *s); #else static inline void git_setup_gettext(void) { - use_gettext_poison(); /* getenv() reentrancy paranoia */ } static inline int gettext_width(const char *s) { @@ -48,14 +45,12 @@ static inline FORMAT_PRESERVING(1) const char *_(const char *msgid) { if (!*msgid) return ""; - return use_gettext_poison() ? "# GETTEXT POISON #" : gettext(msgid); + return gettext(msgid); } static inline FORMAT_PRESERVING(1) FORMAT_PRESERVING(2) const char *Q_(const char *msgid, const char *plu, unsigned long n) { - if (use_gettext_poison()) - return "# GETTEXT POISON #"; return ngettext(msgid, plu, n); } diff --git a/git-bisect.sh b/git-bisect.sh index 1f3f6e9fc5..6a7afaea8d 100755 --- a/git-bisect.sh +++ b/git-bisect.sh @@ -39,21 +39,6 @@ _x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40" TERM_BAD=bad TERM_GOOD=good -bisect_skip() { - all='' - for arg in "$@" - do - case "$arg" in - *..*) - revs=$(git rev-list "$arg") || die "$(eval_gettext "Bad rev input: \$arg")" ;; - *) - revs=$(git rev-parse --sq-quote "$arg") ;; - esac - all="$all $revs" - done - eval git bisect--helper --bisect-state 'skip' $all -} - bisect_visualize() { git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD fail || exit @@ -77,38 +62,6 @@ bisect_visualize() { eval '"$@"' --bisect -- $(cat "$GIT_DIR/BISECT_NAMES") } -bisect_replay () { - file="$1" - test "$#" -eq 1 || die "$(gettext "No logfile given")" - test -r "$file" || die "$(eval_gettext "cannot read \$file for replaying")" - git bisect--helper --bisect-reset || exit - oIFS="$IFS" IFS="$IFS$(printf '\015')" - while read git bisect command rev tail - do - test "$git $bisect" = "git bisect" || test "$git" = "git-bisect" || continue - if test "$git" = "git-bisect" - then - rev="$command" - command="$bisect" - fi - get_terms - git bisect--helper --check-and-set-terms "$command" "$TERM_GOOD" "$TERM_BAD" || exit - get_terms - case "$command" in - start) - eval "git bisect--helper --bisect-start $rev $tail" ;; - "$TERM_GOOD"|"$TERM_BAD"|skip) - git bisect--helper --bisect-write "$command" "$rev" "$TERM_GOOD" "$TERM_BAD" || exit;; - terms) - git bisect--helper --bisect-terms $rev || exit;; - *) - die "$(gettext "?? what are you talking about?")" ;; - esac - done <"$file" - IFS="$oIFS" - git bisect--helper --bisect-auto-next || exit -} - bisect_run () { git bisect--helper --bisect-next-check $TERM_GOOD $TERM_BAD fail || exit @@ -169,11 +122,6 @@ exit code \$res from '\$command' is < 0 or >= 128" >&2 done } -bisect_log () { - test -s "$GIT_DIR/BISECT_LOG" || die "$(gettext "We are not bisecting.")" - cat "$GIT_DIR/BISECT_LOG" -} - get_terms () { if test -s "$GIT_DIR/BISECT_TERMS" then @@ -199,7 +147,7 @@ case "$#" in bad|good|new|old|"$TERM_BAD"|"$TERM_GOOD") git bisect--helper --bisect-state "$cmd" "$@" ;; skip) - bisect_skip "$@" ;; + git bisect--helper --bisect-skip "$@" || exit;; next) # Not sure we want "next" at the UI level anymore. git bisect--helper --bisect-next "$@" || exit ;; @@ -208,9 +156,9 @@ case "$#" in reset) git bisect--helper --bisect-reset "$@" ;; replay) - bisect_replay "$@" ;; + git bisect--helper --bisect-replay "$@" || exit;; log) - bisect_log ;; + git bisect--helper --bisect-log || exit ;; run) bisect_run "$@" ;; terms) diff --git a/git-compat-util.h b/git-compat-util.h index 104993b975..838246289c 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -252,9 +252,9 @@ typedef unsigned long uintptr_t; #ifdef PRECOMPOSE_UNICODE #include "compat/precompose_utf8.h" #else -static inline void precompose_argv(int argc, const char **argv) +static inline const char *precompose_argv_prefix(int argc, const char **argv, const char *prefix) { - ; /* nothing */ + return prefix; } #define probe_utf8_pathname_composition() #endif @@ -1176,9 +1176,12 @@ static inline int regexec_buf(const regex_t *preg, const char *buf, size_t size, #endif #endif -#if defined(__GNUC__) || (_MSC_VER >= 1400) || defined(__C99_MACRO_WITH_VA_ARGS) +/* + * This is always defined as a first step towards making the use of variadic + * macros unconditional. If it causes compilation problems on your platform, + * please report it to the Git mailing list at git@vger.kernel.org. + */ #define HAVE_VARIADIC_MACROS 1 -#endif /* usage.c: only to be used for testing BUG() implementation (see test-tool) */ extern int BUG_exit_code; diff --git a/git-difftool--helper.sh b/git-difftool--helper.sh index 46af3e60b7..992124cc67 100755 --- a/git-difftool--helper.sh +++ b/git-difftool--helper.sh @@ -61,6 +61,9 @@ launch_merge_tool () { export BASE eval $GIT_DIFFTOOL_EXTCMD '"$LOCAL"' '"$REMOTE"' else + initialize_merge_tool "$merge_tool" + # ignore the error from the above --- run_merge_tool + # will diagnose unusable tool by itself run_merge_tool "$merge_tool" fi } @@ -79,6 +82,9 @@ if test -n "$GIT_DIFFTOOL_DIRDIFF" then LOCAL="$1" REMOTE="$2" + initialize_merge_tool "$merge_tool" + # ignore the error from the above --- run_merge_tool + # will diagnose unusable tool by itself run_merge_tool "$merge_tool" false else # Launch the merge tool on each path provided by 'git diff' diff --git a/git-mergetool--lib.sh b/git-mergetool--lib.sh index 78f3647ed9..542a6a75eb 100644 --- a/git-mergetool--lib.sh +++ b/git-mergetool--lib.sh @@ -166,6 +166,10 @@ setup_tool () { return 1 } + hide_resolved_enabled () { + return 0 + } + translate_merge_tool_path () { echo "$1" } @@ -250,6 +254,10 @@ trust_exit_code () { fi } +initialize_merge_tool () { + # Bring tool-specific functions into scope + setup_tool "$1" || return 1 +} # Entry point for running tools run_merge_tool () { @@ -261,9 +269,6 @@ run_merge_tool () { merge_tool_path=$(get_merge_tool_path "$1") || exit base_present="$2" - # Bring tool-specific functions into scope - setup_tool "$1" || return 1 - if merge_mode then run_merge_cmd "$1" diff --git a/git-mergetool.sh b/git-mergetool.sh index e3f6d543fb..911470a5b2 100755 --- a/git-mergetool.sh +++ b/git-mergetool.sh @@ -239,6 +239,13 @@ checkout_staged_file () { fi } +hide_resolved () { + git merge-file --ours -q -p "$LOCAL" "$BASE" "$REMOTE" >"$LCONFL" + git merge-file --theirs -q -p "$LOCAL" "$BASE" "$REMOTE" >"$RCONFL" + mv -- "$LCONFL" "$LOCAL" + mv -- "$RCONFL" "$REMOTE" +} + merge_file () { MERGED="$1" @@ -265,6 +272,8 @@ merge_file () { ext= esac + initialize_merge_tool "$merge_tool" || return + mergetool_tmpdir_init if test "$MERGETOOL_TMPDIR" != "." @@ -276,7 +285,9 @@ merge_file () { BACKUP="$MERGETOOL_TMPDIR/${BASE}_BACKUP_$$$ext" LOCAL="$MERGETOOL_TMPDIR/${BASE}_LOCAL_$$$ext" + LCONFL="$MERGETOOL_TMPDIR/${BASE}_LOCAL_LCONFL_$$$ext" REMOTE="$MERGETOOL_TMPDIR/${BASE}_REMOTE_$$$ext" + RCONFL="$MERGETOOL_TMPDIR/${BASE}_REMOTE_RCONFL_$$$ext" BASE="$MERGETOOL_TMPDIR/${BASE}_BASE_$$$ext" base_mode= local_mode= remote_mode= @@ -322,6 +333,45 @@ merge_file () { checkout_staged_file 2 "$MERGED" "$LOCAL" checkout_staged_file 3 "$MERGED" "$REMOTE" + # hideResolved preferences hierarchy. + global_config="mergetool.hideResolved" + tool_config="mergetool.${merge_tool}.hideResolved" + + if enabled=$(git config --type=bool "$tool_config") + then + # The user has a specific preference for a specific tool and no + # other preferences should override that. + : ; + elif enabled=$(git config --type=bool "$global_config") + then + # The user has a general preference for all tools. + # + # 'true' means the user likes the feature so we should use it + # where possible but tool authors can still override. + # + # 'false' means the user doesn't like the feature so we should + # not use it anywhere. + if test "$enabled" = true && hide_resolved_enabled + then + enabled=true + else + enabled=false + fi + else + # The user does not have a preference. Ask the tool. + if hide_resolved_enabled + then + enabled=true + else + enabled=false + fi + fi + + if test "$enabled" = true + then + hide_resolved + fi + if test -z "$local_mode" || test -z "$remote_mode" then echo "Deleted merge conflict for '$MERGED':" diff --git a/git-sh-i18n.sh b/git-sh-i18n.sh index 8eef60b43f..e3d9f4836d 100644 --- a/git-sh-i18n.sh +++ b/git-sh-i18n.sh @@ -17,12 +17,7 @@ export TEXTDOMAINDIR # First decide what scheme to use... GIT_INTERNAL_GETTEXT_SH_SCHEME=fallthrough -if test -n "$GIT_TEST_GETTEXT_POISON" && - git env--helper --type=bool --default=0 --exit-code \ - GIT_TEST_GETTEXT_POISON -then - GIT_INTERNAL_GETTEXT_SH_SCHEME=poison -elif test -n "@@USE_GETTEXT_SCHEME@@" +if test -n "@@USE_GETTEXT_SCHEME@@" then GIT_INTERNAL_GETTEXT_SH_SCHEME="@@USE_GETTEXT_SCHEME@@" elif test -n "$GIT_INTERNAL_GETTEXT_TEST_FALLBACKS" @@ -63,21 +58,6 @@ gettext_without_eval_gettext) ) } ;; -poison) - # Emit garbage so that tests that incorrectly rely on translatable - # strings will fail. - gettext () { - printf "%s" "# GETTEXT POISON #" - } - - eval_gettext () { - printf "%s" "# GETTEXT POISON #" - } - - eval_ngettext () { - printf "%s" "# GETTEXT POISON #" - } - ;; *) gettext () { printf "%s" "$1" @@ -423,7 +423,7 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv) int nongit_ok; prefix = setup_git_directory_gently(&nongit_ok); } - + prefix = precompose_argv_prefix(argc, argv, prefix); if (use_pager == -1 && p->option & (RUN_SETUP | RUN_SETUP_GENTLY) && !(p->option & DELAY_PAGER_CONFIG)) use_pager = check_pager_config(p->cmd); diff --git a/gpg-interface.c b/gpg-interface.c index b499270836..127aecfc2b 100644 --- a/gpg-interface.c +++ b/gpg-interface.c @@ -1,4 +1,5 @@ #include "cache.h" +#include "commit.h" #include "config.h" #include "run-command.h" #include "strbuf.h" @@ -345,7 +346,7 @@ void print_signature_buffer(const struct signature_check *sigc, unsigned flags) fputs(output, stderr); } -size_t parse_signature(const char *buf, size_t size) +size_t parse_signed_buffer(const char *buf, size_t size) { size_t len = 0; size_t match = size; @@ -361,6 +362,18 @@ size_t parse_signature(const char *buf, size_t size) return match; } +int parse_signature(const char *buf, size_t size, struct strbuf *payload, struct strbuf *signature) +{ + size_t match = parse_signed_buffer(buf, size); + if (match != size) { + strbuf_add(payload, buf, match); + remove_signature(payload); + strbuf_add(signature, buf + match, size - match); + return 1; + } + return 0; +} + void set_signing_key(const char *key) { free(configured_signing_key); diff --git a/gpg-interface.h b/gpg-interface.h index f4e9b4f371..80567e4894 100644 --- a/gpg-interface.h +++ b/gpg-interface.h @@ -38,12 +38,19 @@ struct signature_check { void signature_check_clear(struct signature_check *sigc); /* + * Look at a GPG signed tag object. If such a signature exists, store it in + * signature and the signed content in payload. Return 1 if a signature was + * found, and 0 otherwise. + */ +int parse_signature(const char *buf, size_t size, struct strbuf *payload, struct strbuf *signature); + +/* * Look at GPG signed content (e.g. a signed tag object), whose * payload is followed by a detached signature on it. Return the * offset where the embedded detached signature begins, or the end of * the data when there is no such signature. */ -size_t parse_signature(const char *buf, size_t size); +size_t parse_signed_buffer(const char *buf, size_t size); /* * Create a detached signature for the contents of "buffer" and append @@ -166,11 +166,6 @@ void grep_init(struct grep_opt *opt, struct repository *repo, const char *prefix pcre2_malloc, pcre2_free, NULL); #endif -#ifdef USE_LIBPCRE1 - pcre_malloc = malloc; - pcre_free = free; -#endif - *opt = grep_defaults; opt->repo = repo; @@ -223,17 +218,7 @@ static void grep_set_pattern_type_option(enum grep_pattern_type pattern_type, st break; case GREP_PATTERN_TYPE_PCRE: -#ifdef USE_LIBPCRE2 opt->pcre2 = 1; -#else - /* - * It's important that pcre1 always be assigned to - * even when there's no USE_LIBPCRE* defined. We still - * call the PCRE stub function, it just dies with - * "cannot use Perl-compatible regexes[...]". - */ - opt->pcre1 = 1; -#endif break; } } @@ -377,92 +362,6 @@ static int is_fixed(const char *s, size_t len) return 1; } -#ifdef USE_LIBPCRE1 -static void compile_pcre1_regexp(struct grep_pat *p, const struct grep_opt *opt) -{ - const char *error; - int erroffset; - int options = PCRE_MULTILINE; - int study_options = 0; - - if (opt->ignore_case) { - if (!opt->ignore_locale && has_non_ascii(p->pattern)) - p->pcre1_tables = pcre_maketables(); - options |= PCRE_CASELESS; - } - if (!opt->ignore_locale && is_utf8_locale() && has_non_ascii(p->pattern)) - options |= PCRE_UTF8; - - p->pcre1_regexp = pcre_compile(p->pattern, options, &error, &erroffset, - p->pcre1_tables); - if (!p->pcre1_regexp) - compile_regexp_failed(p, error); - -#if defined(PCRE_CONFIG_JIT) && !defined(NO_LIBPCRE1_JIT) - pcre_config(PCRE_CONFIG_JIT, &p->pcre1_jit_on); - if (opt->debug) - fprintf(stderr, "pcre1_jit_on=%d\n", p->pcre1_jit_on); - - if (p->pcre1_jit_on) - study_options = PCRE_STUDY_JIT_COMPILE; -#endif - - p->pcre1_extra_info = pcre_study(p->pcre1_regexp, study_options, &error); - if (!p->pcre1_extra_info && error) - die("%s", error); -} - -static int pcre1match(struct grep_pat *p, const char *line, const char *eol, - regmatch_t *match, int eflags) -{ - int ovector[30], ret, flags = PCRE_NO_UTF8_CHECK; - - if (eflags & REG_NOTBOL) - flags |= PCRE_NOTBOL; - - ret = pcre_exec(p->pcre1_regexp, p->pcre1_extra_info, line, - eol - line, 0, flags, ovector, - ARRAY_SIZE(ovector)); - - if (ret < 0 && ret != PCRE_ERROR_NOMATCH) - die("pcre_exec failed with error code %d", ret); - if (ret > 0) { - ret = 0; - match->rm_so = ovector[0]; - match->rm_eo = ovector[1]; - } - - return ret; -} - -static void free_pcre1_regexp(struct grep_pat *p) -{ - pcre_free(p->pcre1_regexp); -#ifdef PCRE_CONFIG_JIT - if (p->pcre1_jit_on) - pcre_free_study(p->pcre1_extra_info); - else -#endif - pcre_free(p->pcre1_extra_info); - pcre_free((void *)p->pcre1_tables); -} -#else /* !USE_LIBPCRE1 */ -static void compile_pcre1_regexp(struct grep_pat *p, const struct grep_opt *opt) -{ - die("cannot use Perl-compatible regexes when not compiled with USE_LIBPCRE"); -} - -static int pcre1match(struct grep_pat *p, const char *line, const char *eol, - regmatch_t *match, int eflags) -{ - return 1; -} - -static void free_pcre1_regexp(struct grep_pat *p) -{ -} -#endif /* !USE_LIBPCRE1 */ - #ifdef USE_LIBPCRE2 static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt) { @@ -492,7 +391,23 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt } if (!opt->ignore_locale && is_utf8_locale() && has_non_ascii(p->pattern) && !(!opt->ignore_case && (p->fixed || p->is_fixed))) - options |= PCRE2_UTF; + options |= (PCRE2_UTF | PCRE2_MATCH_INVALID_UTF); + + /* Work around https://bugs.exim.org/show_bug.cgi?id=2642 fixed in 10.36 */ + if (PCRE2_MATCH_INVALID_UTF && options & (PCRE2_UTF | PCRE2_CASELESS)) { + struct strbuf buf; + int len; + int err; + + if ((len = pcre2_config(PCRE2_CONFIG_VERSION, NULL)) < 0) + BUG("pcre2_config(..., NULL) failed: %d", len); + strbuf_init(&buf, len + 1); + if ((err = pcre2_config(PCRE2_CONFIG_VERSION, buf.buf)) < 0) + BUG("pcre2_config(..., buf.buf) failed: %d", err); + if (versioncmp(buf.buf, "10.36") < 0) + options |= PCRE2_NO_START_OPTIMIZE; + strbuf_release(&buf); + } p->pcre2_pattern = pcre2_compile((PCRE2_SPTR)p->pattern, p->patternlen, options, &error, &erroffset, @@ -508,8 +423,6 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt } pcre2_config(PCRE2_CONFIG_JIT, &p->pcre2_jit_on); - if (opt->debug) - fprintf(stderr, "pcre2_jit_on=%d\n", p->pcre2_jit_on); if (p->pcre2_jit_on) { jitret = pcre2_jit_compile(p->pcre2_pattern, PCRE2_JIT_COMPLETE); if (jitret) @@ -535,9 +448,6 @@ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt BUG("pcre2_pattern_info() failed: %d", patinforet); if (jitsizearg == 0) { p->pcre2_jit_on = 0; - if (opt->debug) - fprintf(stderr, "pcre2_jit_on=%d: (*NO_JIT) in regex\n", - p->pcre2_jit_on); return; } } @@ -588,11 +498,6 @@ static void free_pcre2_pattern(struct grep_pat *p) #else /* !USE_LIBPCRE2 */ static void compile_pcre2_pattern(struct grep_pat *p, const struct grep_opt *opt) { - /* - * Unreachable until USE_LIBPCRE2 becomes synonymous with - * USE_LIBPCRE. See the sibling comment in - * grep_set_pattern_type_option(). - */ die("cannot use Perl-compatible regexes when not compiled with USE_LIBPCRE"); } @@ -616,8 +521,6 @@ static void compile_fixed_regexp(struct grep_pat *p, struct grep_opt *opt) if (opt->ignore_case) regflags |= REG_ICASE; err = regcomp(&p->regexp, sb.buf, regflags); - if (opt->debug) - fprintf(stderr, "fixed %s\n", sb.buf); strbuf_release(&sb); if (err) { char errbuf[1024]; @@ -693,11 +596,6 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) return; } - if (opt->pcre1) { - compile_pcre1_regexp(p, opt); - return; - } - if (p->ignore_case) regflags |= REG_ICASE; if (opt->extended_regexp_option) @@ -812,87 +710,6 @@ static struct grep_expr *compile_pattern_expr(struct grep_pat **list) return compile_pattern_or(list); } -static void indent(int in) -{ - while (in-- > 0) - fputc(' ', stderr); -} - -static void dump_grep_pat(struct grep_pat *p) -{ - switch (p->token) { - case GREP_AND: fprintf(stderr, "*and*"); break; - case GREP_OPEN_PAREN: fprintf(stderr, "*(*"); break; - case GREP_CLOSE_PAREN: fprintf(stderr, "*)*"); break; - case GREP_NOT: fprintf(stderr, "*not*"); break; - case GREP_OR: fprintf(stderr, "*or*"); break; - - case GREP_PATTERN: fprintf(stderr, "pattern"); break; - case GREP_PATTERN_HEAD: fprintf(stderr, "pattern_head"); break; - case GREP_PATTERN_BODY: fprintf(stderr, "pattern_body"); break; - } - - switch (p->token) { - default: break; - case GREP_PATTERN_HEAD: - fprintf(stderr, "<head %d>", p->field); break; - case GREP_PATTERN_BODY: - fprintf(stderr, "<body>"); break; - } - switch (p->token) { - default: break; - case GREP_PATTERN_HEAD: - case GREP_PATTERN_BODY: - case GREP_PATTERN: - fprintf(stderr, "%.*s", (int)p->patternlen, p->pattern); - break; - } - fputc('\n', stderr); -} - -static void dump_grep_expression_1(struct grep_expr *x, int in) -{ - indent(in); - switch (x->node) { - case GREP_NODE_TRUE: - fprintf(stderr, "true\n"); - break; - case GREP_NODE_ATOM: - dump_grep_pat(x->u.atom); - break; - case GREP_NODE_NOT: - fprintf(stderr, "(not\n"); - dump_grep_expression_1(x->u.unary, in+1); - indent(in); - fprintf(stderr, ")\n"); - break; - case GREP_NODE_AND: - fprintf(stderr, "(and\n"); - dump_grep_expression_1(x->u.binary.left, in+1); - dump_grep_expression_1(x->u.binary.right, in+1); - indent(in); - fprintf(stderr, ")\n"); - break; - case GREP_NODE_OR: - fprintf(stderr, "(or\n"); - dump_grep_expression_1(x->u.binary.left, in+1); - dump_grep_expression_1(x->u.binary.right, in+1); - indent(in); - fprintf(stderr, ")\n"); - break; - } -} - -static void dump_grep_expression(struct grep_opt *opt) -{ - struct grep_expr *x = opt->pattern_expression; - - if (opt->all_match) - fprintf(stderr, "[all-match]\n"); - dump_grep_expression_1(x, 0); - fflush(NULL); -} - static struct grep_expr *grep_true_expr(void) { struct grep_expr *z = xcalloc(1, sizeof(*z)); @@ -973,7 +790,7 @@ static struct grep_expr *grep_splice_or(struct grep_expr *x, struct grep_expr *y return z; } -static void compile_grep_patterns_real(struct grep_opt *opt) +void compile_grep_patterns(struct grep_opt *opt) { struct grep_pat *p; struct grep_expr *header_expr = prep_header_patterns(opt); @@ -993,7 +810,7 @@ static void compile_grep_patterns_real(struct grep_opt *opt) if (opt->all_match || header_expr) opt->extended = 1; - else if (!opt->extended && !opt->debug) + else if (!opt->extended) return; p = opt->pattern_list; @@ -1016,13 +833,6 @@ static void compile_grep_patterns_real(struct grep_opt *opt) opt->all_match = 1; } -void compile_grep_patterns(struct grep_opt *opt) -{ - compile_grep_patterns_real(opt); - if (opt->debug) - dump_grep_expression(opt); -} - static void free_pattern_expr(struct grep_expr *x) { switch (x->node) { @@ -1051,9 +861,7 @@ void free_grep_patterns(struct grep_opt *opt) case GREP_PATTERN: /* atom */ case GREP_PATTERN_HEAD: case GREP_PATTERN_BODY: - if (p->pcre1_regexp) - free_pcre1_regexp(p); - else if (p->pcre2_pattern) + if (p->pcre2_pattern) free_pcre2_pattern(p); else regfree(&p->regexp); @@ -1116,9 +924,7 @@ static int patmatch(struct grep_pat *p, char *line, char *eol, { int hit; - if (p->pcre1_regexp) - hit = !pcre1match(p, line, eol, match, eflags); - else if (p->pcre2_pattern) + if (p->pcre2_pattern) hit = !pcre2match(p, line, eol, match, eflags); else hit = !regexec_buf(&p->regexp, line, eol - line, 1, match, @@ -1,15 +1,6 @@ #ifndef GREP_H #define GREP_H #include "color.h" -#ifdef USE_LIBPCRE1 -#include <pcre.h> -#ifndef PCRE_NO_UTF8_CHECK -#define PCRE_NO_UTF8_CHECK 0 -#endif -#else -typedef int pcre; -typedef int pcre_extra; -#endif #ifdef USE_LIBPCRE2 #define PCRE2_CODE_UNIT_WIDTH 8 #include <pcre2.h> @@ -18,6 +9,10 @@ typedef int pcre2_code; typedef int pcre2_match_data; typedef int pcre2_compile_context; #endif +#ifndef PCRE2_MATCH_INVALID_UTF +/* PCRE2_MATCH_* dummy also with !USE_LIBPCRE2, for test-pcre2-config.c */ +#define PCRE2_MATCH_INVALID_UTF 0 +#endif #include "thread-utils.h" #include "userdiff.h" @@ -71,10 +66,6 @@ struct grep_pat { size_t patternlen; enum grep_header_field field; regex_t regexp; - pcre *pcre1_regexp; - pcre_extra *pcre1_extra_info; - const unsigned char *pcre1_tables; - int pcre1_jit_on; pcre2_code *pcre2_pattern; pcre2_match_data *pcre2_match_data; pcre2_compile_context *pcre2_compile_context; @@ -136,7 +127,6 @@ struct grep_opt { int word_regexp; int fixed; int all_match; - int debug; #define GREP_BINARY_DEFAULT 0 #define GREP_BINARY_NOMATCH 1 #define GREP_BINARY_TEXT 2 @@ -144,7 +134,6 @@ struct grep_opt { int allow_textconv; int extended; int use_reflog_filter; - int pcre1; int pcre2; int relative; int pathname; diff --git a/hash-lookup.c b/hash-lookup.c index 1191856a32..b98ed5e11e 100644 --- a/hash-lookup.c +++ b/hash-lookup.c @@ -1,9 +1,9 @@ #include "cache.h" #include "hash-lookup.h" -static uint32_t take2(const unsigned char *hash) +static uint32_t take2(const struct object_id *oid, size_t ofs) { - return ((hash[0] << 8) | hash[1]); + return ((oid->hash[ofs] << 8) | oid->hash[ofs + 1]); } /* @@ -47,11 +47,11 @@ static uint32_t take2(const unsigned char *hash) */ /* * The table should contain "nr" elements. - * The hash of element i (between 0 and nr - 1) should be returned + * The oid of element i (between 0 and nr - 1) should be returned * by "fn(i, table)". */ -int hash_pos(const unsigned char *hash, void *table, size_t nr, - hash_access_fn fn) +int oid_pos(const struct object_id *oid, const void *table, size_t nr, + oid_access_fn fn) { size_t hi = nr; size_t lo = 0; @@ -64,9 +64,9 @@ int hash_pos(const unsigned char *hash, void *table, size_t nr, size_t lov, hiv, miv, ofs; for (ofs = 0; ofs < the_hash_algo->rawsz - 2; ofs += 2) { - lov = take2(fn(0, table) + ofs); - hiv = take2(fn(nr - 1, table) + ofs); - miv = take2(hash + ofs); + lov = take2(fn(0, table), ofs); + hiv = take2(fn(nr - 1, table), ofs); + miv = take2(oid, ofs); if (miv < lov) return -1; if (hiv < miv) @@ -88,7 +88,7 @@ int hash_pos(const unsigned char *hash, void *table, size_t nr, do { int cmp; - cmp = hashcmp(fn(mi, table), hash); + cmp = oidcmp(fn(mi, table), oid); if (!cmp) return mi; if (cmp > 0) diff --git a/hash-lookup.h b/hash-lookup.h index 5d476dec72..dbd71ebaf7 100644 --- a/hash-lookup.h +++ b/hash-lookup.h @@ -1,12 +1,12 @@ #ifndef HASH_LOOKUP_H #define HASH_LOOKUP_H -typedef const unsigned char *hash_access_fn(size_t index, void *table); +typedef const struct object_id *oid_access_fn(size_t index, const void *table); -int hash_pos(const unsigned char *hash, - void *table, - size_t nr, - hash_access_fn fn); +int oid_pos(const struct object_id *oid, + const void *table, + size_t nr, + oid_access_fn fn); /* * Searches for hash in table, using the given fanout table to determine the diff --git a/log-tree.c b/log-tree.c index fd0dde97ec..80b8a07ec4 100644 --- a/log-tree.c +++ b/log-tree.c @@ -502,7 +502,7 @@ static void show_signature(struct rev_info *opt, struct commit *commit) struct signature_check sigc = { 0 }; int status; - if (parse_signed_commit(commit, &payload, &signature) <= 0) + if (parse_signed_commit(commit, &payload, &signature, the_hash_algo) <= 0) goto out; status = check_signature(payload.buf, payload.len, signature.buf, @@ -548,7 +548,8 @@ static int show_one_mergetag(struct commit *commit, struct strbuf verify_message; struct signature_check sigc = { 0 }; int status, nth; - size_t payload_size; + struct strbuf payload = STRBUF_INIT; + struct strbuf signature = STRBUF_INIT; hash_object_file(the_hash_algo, extra->value, extra->len, type_name(OBJ_TAG), &oid); @@ -571,13 +572,11 @@ static int show_one_mergetag(struct commit *commit, strbuf_addf(&verify_message, "parent #%d, tagged '%s'\n", nth + 1, tag->tag); - payload_size = parse_signature(extra->value, extra->len); status = -1; - if (extra->len > payload_size) { + if (parse_signature(extra->value, extra->len, &payload, &signature)) { /* could have a good signature */ - status = check_signature(extra->value, payload_size, - extra->value + payload_size, - extra->len - payload_size, &sigc); + status = check_signature(payload.buf, payload.len, + signature.buf, signature.len, &sigc); if (sigc.gpg_output) strbuf_addstr(&verify_message, sigc.gpg_output); else @@ -588,6 +587,8 @@ static int show_one_mergetag(struct commit *commit, show_sig_lines(opt, status, verify_message.buf); strbuf_release(&verify_message); + strbuf_release(&payload); + strbuf_release(&signature); return 0; } @@ -808,6 +809,11 @@ void show_log(struct rev_info *opt) if (cmit_fmt_is_mail(ctx.fmt) && opt->rdiff1) { struct diff_queue_struct dq; struct diff_options opts; + struct range_diff_options range_diff_opts = { + .creation_factor = opt->creation_factor, + .dual_color = 1, + .diffopt = &opts + }; memcpy(&dq, &diff_queued_diff, sizeof(diff_queued_diff)); DIFF_QUEUE_CLEAR(&diff_queued_diff); @@ -822,8 +828,7 @@ void show_log(struct rev_info *opt) opts.file = opt->diffopt.file; opts.use_color = opt->diffopt.use_color; diff_setup_done(&opts); - show_range_diff(opt->rdiff1, opt->rdiff2, - opt->creation_factor, 1, &opts, NULL); + show_range_diff(opt->rdiff1, opt->rdiff2, &range_diff_opts); memcpy(&diff_queued_diff, &dq, sizeof(diff_queued_diff)); } @@ -899,15 +904,21 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log int showed_log; struct commit_list *parents; struct object_id *oid; + int is_merge; + int all_need_diff = opt->diff || opt->diffopt.flags.exit_with_status; - if (!opt->diff && !opt->diffopt.flags.exit_with_status) + if (!all_need_diff && !opt->merges_need_diff) return 0; parse_commit_or_die(commit); oid = get_commit_tree_oid(commit); - /* Root commit? */ parents = get_saved_parents(opt, commit); + is_merge = parents && parents->next; + if (!is_merge && !all_need_diff) + return 0; + + /* Root commit? */ if (!parents) { if (opt->show_root_diff) { diff_root_tree_oid(oid, "", &opt->diffopt); @@ -916,16 +927,16 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log return !opt->loginfo; } - /* More than one parent? */ - if (parents->next) { - if (opt->ignore_merges) - return 0; - else if (opt->combine_merges) + if (is_merge) { + if (opt->combine_merges) return do_diff_combined(opt, commit); - else if (!opt->first_parent_only) { - /* If we show multiple diffs, show the parent info */ - log->parent = parents->item; - } + if (opt->separate_merges) { + if (!opt->first_parent_merges) { + /* Show parent info for multiple diffs */ + log->parent = parents->item; + } + } else + return 0; } showed_log = 0; @@ -941,7 +952,7 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log /* Set up the log info for the next parent, if any.. */ parents = parents->next; - if (!parents || opt->first_parent_only) + if (!parents || opt->first_parent_merges) break; log->parent = parents->item; opt->loginfo = log; @@ -7,6 +7,39 @@ #include "pkt-line.h" #include "config.h" +static int config_read; +static int advertise_unborn; +static int allow_unborn; + +static void ensure_config_read(void) +{ + const char *str = NULL; + + if (config_read) + return; + + if (repo_config_get_string_tmp(the_repository, "lsrefs.unborn", &str)) { + /* + * If there is no such config, advertise and allow it by + * default. + */ + advertise_unborn = 1; + allow_unborn = 1; + } else { + if (!strcmp(str, "advertise")) { + advertise_unborn = 1; + allow_unborn = 1; + } else if (!strcmp(str, "allow")) { + allow_unborn = 1; + } else if (!strcmp(str, "ignore")) { + /* do nothing */ + } else { + die(_("invalid value '%s' for lsrefs.unborn"), str); + } + } + config_read = 1; +} + /* * Check if one of the prefixes is a prefix of the ref. * If no prefixes were provided, all refs match. @@ -32,6 +65,7 @@ struct ls_refs_data { unsigned peel; unsigned symrefs; struct strvec prefixes; + unsigned unborn : 1; }; static int send_ref(const char *refname, const struct object_id *oid, @@ -47,7 +81,10 @@ static int send_ref(const char *refname, const struct object_id *oid, if (!ref_match(&data->prefixes, refname_nons)) return 0; - strbuf_addf(&refline, "%s %s", oid_to_hex(oid), refname_nons); + if (oid) + strbuf_addf(&refline, "%s %s", oid_to_hex(oid), refname_nons); + else + strbuf_addf(&refline, "unborn %s", refname_nons); if (data->symrefs && flag & REF_ISSYMREF) { struct object_id unused; const char *symref_target = resolve_ref_unsafe(refname, 0, @@ -61,9 +98,9 @@ static int send_ref(const char *refname, const struct object_id *oid, strip_namespace(symref_target)); } - if (data->peel) { + if (data->peel && oid) { struct object_id peeled; - if (!peel_ref(refname, &peeled)) + if (!peel_iterated_oid(oid, &peeled)) strbuf_addf(&refline, " peeled:%s", oid_to_hex(&peeled)); } @@ -74,6 +111,23 @@ static int send_ref(const char *refname, const struct object_id *oid, return 0; } +static void send_possibly_unborn_head(struct ls_refs_data *data) +{ + struct strbuf namespaced = STRBUF_INIT; + struct object_id oid; + int flag; + int oid_is_null; + + strbuf_addf(&namespaced, "%sHEAD", get_git_namespace()); + if (!resolve_ref_unsafe(namespaced.buf, 0, &oid, &flag)) + return; /* bad ref */ + oid_is_null = is_null_oid(&oid); + if (!oid_is_null || + (data->unborn && data->symrefs && (flag & REF_ISSYMREF))) + send_ref(namespaced.buf, oid_is_null ? NULL : &oid, flag, data); + strbuf_release(&namespaced); +} + static int ls_refs_config(const char *var, const char *value, void *data) { /* @@ -90,7 +144,9 @@ int ls_refs(struct repository *r, struct strvec *keys, struct ls_refs_data data; memset(&data, 0, sizeof(data)); + strvec_init(&data.prefixes); + ensure_config_read(); git_config(ls_refs_config, NULL); while (packet_reader_read(request) == PACKET_READ_NORMAL) { @@ -103,14 +159,30 @@ int ls_refs(struct repository *r, struct strvec *keys, data.symrefs = 1; else if (skip_prefix(arg, "ref-prefix ", &out)) strvec_push(&data.prefixes, out); + else if (!strcmp("unborn", arg)) + data.unborn = allow_unborn; } if (request->status != PACKET_READ_FLUSH) die(_("expected flush after ls-refs arguments")); - head_ref_namespaced(send_ref, &data); - for_each_namespaced_ref(send_ref, &data); + send_possibly_unborn_head(&data); + if (!data.prefixes.nr) + strvec_push(&data.prefixes, ""); + for_each_fullref_in_prefixes(get_git_namespace(), data.prefixes.v, + send_ref, &data, 0); packet_flush(1); strvec_clear(&data.prefixes); return 0; } + +int ls_refs_advertise(struct repository *r, struct strbuf *value) +{ + if (value) { + ensure_config_read(); + if (advertise_unborn) + strbuf_addstr(value, "unborn"); + } + + return 1; +} @@ -6,5 +6,6 @@ struct strvec; struct packet_reader; int ls_refs(struct repository *r, struct strvec *keys, struct packet_reader *request); +int ls_refs_advertise(struct repository *r, struct strbuf *value); #endif /* LS_REFS_H */ @@ -225,7 +225,8 @@ int read_mailmap(struct string_list *map) if (!git_mailmap_blob && is_bare_repository()) git_mailmap_blob = "HEAD:.mailmap"; - err |= read_mailmap_file(map, ".mailmap"); + if (!startup_info->have_repository || !is_bare_repository()) + err |= read_mailmap_file(map, ".mailmap"); if (startup_info->have_repository) err |= read_mailmap_blob(map, git_mailmap_blob); err |= read_mailmap_file(map, git_mailmap_file); diff --git a/merge-ort.c b/merge-ort.c index d36a92b59b..931b91438c 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -25,8 +25,11 @@ #include "diff.h" #include "diffcore.h" #include "dir.h" +#include "ll-merge.h" #include "object-store.h" +#include "revision.h" #include "strmap.h" +#include "submodule.h" #include "tree.h" #include "unpack-trees.h" #include "xdiff-interface.h" @@ -50,14 +53,42 @@ enum merge_side { struct rename_info { /* + * All variables that are arrays of size 3 correspond to data tracked + * for the sides in enum merge_side. Index 0 is almost always unused + * because we often only need to track information for MERGE_SIDE1 and + * MERGE_SIDE2 (MERGE_BASE can't have rename information since renames + * are determined relative to what changed since the MERGE_BASE). + */ + + /* * pairs: pairing of filenames from diffcore_rename() - * - * Index 1 and 2 correspond to sides 1 & 2 as used in - * conflict_info.stages. Index 0 unused. */ struct diff_queue_struct pairs[3]; /* + * dirs_removed: directories removed on a given side of history. + */ + struct strset dirs_removed[3]; + + /* + * dir_rename_count: tracking where parts of a directory were renamed to + * + * When files in a directory are renamed, they may not all go to the + * same location. Each strmap here tracks: + * old_dir => {new_dir => int} + * That is, dir_rename_count[side] is a strmap to a strintmap. + */ + struct strmap dir_rename_count[3]; + + /* + * dir_renames: computed directory renames + * + * This is a map of old_dir => new_dir and is derived in part from + * dir_rename_count. + */ + struct strmap dir_renames[3]; + + /* * needed_limit: value needed for inexact rename detection to run * * If the current rename limit wasn't high enough for inexact @@ -140,12 +171,15 @@ struct merge_options_internal { struct rename_info renames; /* - * current_dir_name: temporary var used in collect_merge_info_callback() + * current_dir_name, toplevel_dir: temporary vars * - * Used to set merged_info.directory_name; see documentation for that - * variable and the requirements placed on that field. + * These are used in collect_merge_info_callback(), and will set the + * various merged_info.directory_name for the various paths we get; + * see documentation for that variable and the requirements placed on + * that field. */ const char *current_dir_name; + const char *toplevel_dir; /* call_depth: recursion level counter for merging merge bases */ int call_depth; @@ -280,8 +314,12 @@ static void free_strmap_strings(struct strmap *map) static void clear_or_reinit_internal_opts(struct merge_options_internal *opti, int reinitialize) { + struct rename_info *renames = &opti->renames; + int i; void (*strmap_func)(struct strmap *, int) = reinitialize ? strmap_partial_clear : strmap_clear; + void (*strset_func)(struct strset *) = + reinitialize ? strset_partial_clear : strset_clear; /* * We marked opti->paths with strdup_strings = 0, so that we @@ -311,6 +349,23 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti, string_list_clear(&opti->paths_to_free, 0); opti->paths_to_free.strdup_strings = 0; + /* Free memory used by various renames maps */ + for (i = MERGE_SIDE1; i <= MERGE_SIDE2; ++i) { + struct hashmap_iter iter; + struct strmap_entry *entry; + + strset_func(&renames->dirs_removed[i]); + + strmap_for_each_entry(&renames->dir_rename_count[i], + &iter, entry) { + struct strintmap *counts = entry->value; + strintmap_clear(counts); + } + strmap_func(&renames->dir_rename_count[i], 1); + + strmap_func(&renames->dir_renames[i], 0); + } + if (!reinitialize) { struct hashmap_iter iter; struct strmap_entry *e; @@ -349,6 +404,25 @@ static int err(struct merge_options *opt, const char *err, ...) return -1; } +static void format_commit(struct strbuf *sb, + int indent, + struct commit *commit) +{ + struct merge_remote_desc *desc; + struct pretty_print_context ctx = {0}; + ctx.abbrev = DEFAULT_ABBREV; + + strbuf_addchars(sb, ' ', indent); + desc = merge_remote_util(commit); + if (desc) { + strbuf_addf(sb, "virtual %s\n", desc->name); + return; + } + + format_commit_message(commit, "%h %s", sb, &ctx); + strbuf_addch(sb, '\n'); +} + __attribute__((format (printf, 4, 5))) static void path_msg(struct merge_options *opt, const char *path, @@ -370,6 +444,36 @@ static void path_msg(struct merge_options *opt, strbuf_addch(sb, '\n'); } +/* add a string to a strbuf, but converting "/" to "_" */ +static void add_flattened_path(struct strbuf *out, const char *s) +{ + size_t i = out->len; + strbuf_addstr(out, s); + for (; i < out->len; i++) + if (out->buf[i] == '/') + out->buf[i] = '_'; +} + +static char *unique_path(struct strmap *existing_paths, + const char *path, + const char *branch) +{ + struct strbuf newpath = STRBUF_INIT; + int suffix = 0; + size_t base_len; + + strbuf_addf(&newpath, "%s~", path); + add_flattened_path(&newpath, branch); + + base_len = newpath.len; + while (strmap_contains(existing_paths, newpath.buf)) { + strbuf_setlen(&newpath, base_len); + strbuf_addf(&newpath, "_%d", suffix++); + } + + return strbuf_detach(&newpath, NULL); +} + /*** Function Grouping: functions related to collect_merge_info() ***/ static void setup_path_info(struct merge_options *opt, @@ -431,6 +535,27 @@ static void setup_path_info(struct merge_options *opt, result->util = mi; } +static void collect_rename_info(struct merge_options *opt, + struct name_entry *names, + const char *dirname, + const char *fullname, + unsigned filemask, + unsigned dirmask, + unsigned match_mask) +{ + struct rename_info *renames = &opt->priv->renames; + + /* Update dirs_removed, as needed */ + if (dirmask == 1 || dirmask == 3 || dirmask == 5) { + /* absent_mask = 0x07 - dirmask; sides = absent_mask/2 */ + unsigned sides = (0x07 - dirmask)/2; + if (sides & 1) + strset_add(&renames->dirs_removed[1], fullname); + if (sides & 2) + strset_add(&renames->dirs_removed[2], fullname); + } +} + static int collect_merge_info_callback(int n, unsigned long mask, unsigned long dirmask, @@ -532,6 +657,12 @@ static int collect_merge_info_callback(int n, } /* + * Gather additional information used in rename detection. + */ + collect_rename_info(opt, names, dirname, fullpath, + filemask, dirmask, match_mask); + + /* * Record information about the path so we can resolve later in * process_entries. */ @@ -606,10 +737,10 @@ static int collect_merge_info(struct merge_options *opt, int ret; struct tree_desc t[3]; struct traverse_info info; - const char *toplevel_dir_placeholder = ""; - opt->priv->current_dir_name = toplevel_dir_placeholder; - setup_traverse_info(&info, toplevel_dir_placeholder); + opt->priv->toplevel_dir = ""; + opt->priv->current_dir_name = opt->priv->toplevel_dir; + setup_traverse_info(&info, opt->priv->toplevel_dir); info.fn = collect_merge_info_callback; info.data = opt; info.show_all_errors = 1; @@ -621,13 +752,258 @@ static int collect_merge_info(struct merge_options *opt, init_tree_desc(t + 1, side1->buffer, side1->size); init_tree_desc(t + 2, side2->buffer, side2->size); + trace2_region_enter("merge", "traverse_trees", opt->repo); ret = traverse_trees(NULL, 3, t, &info); + trace2_region_leave("merge", "traverse_trees", opt->repo); return ret; } /*** Function Grouping: functions related to threeway content merges ***/ +static int find_first_merges(struct repository *repo, + const char *path, + struct commit *a, + struct commit *b, + struct object_array *result) +{ + int i, j; + struct object_array merges = OBJECT_ARRAY_INIT; + struct commit *commit; + int contains_another; + + char merged_revision[GIT_MAX_HEXSZ + 2]; + const char *rev_args[] = { "rev-list", "--merges", "--ancestry-path", + "--all", merged_revision, NULL }; + struct rev_info revs; + struct setup_revision_opt rev_opts; + + memset(result, 0, sizeof(struct object_array)); + memset(&rev_opts, 0, sizeof(rev_opts)); + + /* get all revisions that merge commit a */ + xsnprintf(merged_revision, sizeof(merged_revision), "^%s", + oid_to_hex(&a->object.oid)); + repo_init_revisions(repo, &revs, NULL); + rev_opts.submodule = path; + /* FIXME: can't handle linked worktrees in submodules yet */ + revs.single_worktree = path != NULL; + setup_revisions(ARRAY_SIZE(rev_args)-1, rev_args, &revs, &rev_opts); + + /* save all revisions from the above list that contain b */ + if (prepare_revision_walk(&revs)) + die("revision walk setup failed"); + while ((commit = get_revision(&revs)) != NULL) { + struct object *o = &(commit->object); + if (in_merge_bases(b, commit)) + add_object_array(o, NULL, &merges); + } + reset_revision_walk(); + + /* Now we've got all merges that contain a and b. Prune all + * merges that contain another found merge and save them in + * result. + */ + for (i = 0; i < merges.nr; i++) { + struct commit *m1 = (struct commit *) merges.objects[i].item; + + contains_another = 0; + for (j = 0; j < merges.nr; j++) { + struct commit *m2 = (struct commit *) merges.objects[j].item; + if (i != j && in_merge_bases(m2, m1)) { + contains_another = 1; + break; + } + } + + if (!contains_another) + add_object_array(merges.objects[i].item, NULL, result); + } + + object_array_clear(&merges); + return result->nr; +} + +static int merge_submodule(struct merge_options *opt, + const char *path, + const struct object_id *o, + const struct object_id *a, + const struct object_id *b, + struct object_id *result) +{ + struct commit *commit_o, *commit_a, *commit_b; + int parent_count; + struct object_array merges; + struct strbuf sb = STRBUF_INIT; + + int i; + int search = !opt->priv->call_depth; + + /* store fallback answer in result in case we fail */ + oidcpy(result, opt->priv->call_depth ? o : a); + + /* we can not handle deletion conflicts */ + if (is_null_oid(o)) + return 0; + if (is_null_oid(a)) + return 0; + if (is_null_oid(b)) + return 0; + + if (add_submodule_odb(path)) { + path_msg(opt, path, 0, + _("Failed to merge submodule %s (not checked out)"), + path); + return 0; + } + + if (!(commit_o = lookup_commit_reference(opt->repo, o)) || + !(commit_a = lookup_commit_reference(opt->repo, a)) || + !(commit_b = lookup_commit_reference(opt->repo, b))) { + path_msg(opt, path, 0, + _("Failed to merge submodule %s (commits not present)"), + path); + return 0; + } + + /* check whether both changes are forward */ + if (!in_merge_bases(commit_o, commit_a) || + !in_merge_bases(commit_o, commit_b)) { + path_msg(opt, path, 0, + _("Failed to merge submodule %s " + "(commits don't follow merge-base)"), + path); + return 0; + } + + /* Case #1: a is contained in b or vice versa */ + if (in_merge_bases(commit_a, commit_b)) { + oidcpy(result, b); + path_msg(opt, path, 1, + _("Note: Fast-forwarding submodule %s to %s"), + path, oid_to_hex(b)); + return 1; + } + if (in_merge_bases(commit_b, commit_a)) { + oidcpy(result, a); + path_msg(opt, path, 1, + _("Note: Fast-forwarding submodule %s to %s"), + path, oid_to_hex(a)); + return 1; + } + + /* + * Case #2: There are one or more merges that contain a and b in + * the submodule. If there is only one, then present it as a + * suggestion to the user, but leave it marked unmerged so the + * user needs to confirm the resolution. + */ + + /* Skip the search if makes no sense to the calling context. */ + if (!search) + return 0; + + /* find commit which merges them */ + parent_count = find_first_merges(opt->repo, path, commit_a, commit_b, + &merges); + switch (parent_count) { + case 0: + path_msg(opt, path, 0, _("Failed to merge submodule %s"), path); + break; + + case 1: + format_commit(&sb, 4, + (struct commit *)merges.objects[0].item); + path_msg(opt, path, 0, + _("Failed to merge submodule %s, but a possible merge " + "resolution exists:\n%s\n"), + path, sb.buf); + path_msg(opt, path, 1, + _("If this is correct simply add it to the index " + "for example\n" + "by using:\n\n" + " git update-index --cacheinfo 160000 %s \"%s\"\n\n" + "which will accept this suggestion.\n"), + oid_to_hex(&merges.objects[0].item->oid), path); + strbuf_release(&sb); + break; + default: + for (i = 0; i < merges.nr; i++) + format_commit(&sb, 4, + (struct commit *)merges.objects[i].item); + path_msg(opt, path, 0, + _("Failed to merge submodule %s, but multiple " + "possible merges exist:\n%s"), path, sb.buf); + strbuf_release(&sb); + } + + object_array_clear(&merges); + return 0; +} + +static int merge_3way(struct merge_options *opt, + const char *path, + const struct object_id *o, + const struct object_id *a, + const struct object_id *b, + const char *pathnames[3], + const int extra_marker_size, + mmbuffer_t *result_buf) +{ + mmfile_t orig, src1, src2; + struct ll_merge_options ll_opts = {0}; + char *base, *name1, *name2; + int merge_status; + + ll_opts.renormalize = opt->renormalize; + ll_opts.extra_marker_size = extra_marker_size; + ll_opts.xdl_opts = opt->xdl_opts; + + if (opt->priv->call_depth) { + ll_opts.virtual_ancestor = 1; + ll_opts.variant = 0; + } else { + switch (opt->recursive_variant) { + case MERGE_VARIANT_OURS: + ll_opts.variant = XDL_MERGE_FAVOR_OURS; + break; + case MERGE_VARIANT_THEIRS: + ll_opts.variant = XDL_MERGE_FAVOR_THEIRS; + break; + default: + ll_opts.variant = 0; + break; + } + } + + assert(pathnames[0] && pathnames[1] && pathnames[2] && opt->ancestor); + if (pathnames[0] == pathnames[1] && pathnames[1] == pathnames[2]) { + base = mkpathdup("%s", opt->ancestor); + name1 = mkpathdup("%s", opt->branch1); + name2 = mkpathdup("%s", opt->branch2); + } else { + base = mkpathdup("%s:%s", opt->ancestor, pathnames[0]); + name1 = mkpathdup("%s:%s", opt->branch1, pathnames[1]); + name2 = mkpathdup("%s:%s", opt->branch2, pathnames[2]); + } + + read_mmblob(&orig, o); + read_mmblob(&src1, a); + read_mmblob(&src2, b); + + merge_status = ll_merge(result_buf, path, &orig, base, + &src1, name1, &src2, name2, + opt->repo->index, &ll_opts); + + free(base); + free(name1); + free(name2); + free(orig.ptr); + free(src1.ptr); + free(src2.ptr); + return merge_status; +} + static int handle_content_merge(struct merge_options *opt, const char *path, const struct version_info *o, @@ -637,7 +1013,130 @@ static int handle_content_merge(struct merge_options *opt, const int extra_marker_size, struct version_info *result) { - die("Not yet implemented"); + /* + * path is the target location where we want to put the file, and + * is used to determine any normalization rules in ll_merge. + * + * The normal case is that path and all entries in pathnames are + * identical, though renames can affect which path we got one of + * the three blobs to merge on various sides of history. + * + * extra_marker_size is the amount to extend conflict markers in + * ll_merge; this is neeed if we have content merges of content + * merges, which happens for example with rename/rename(2to1) and + * rename/add conflicts. + */ + unsigned clean = 1; + + /* + * handle_content_merge() needs both files to be of the same type, i.e. + * both files OR both submodules OR both symlinks. Conflicting types + * needs to be handled elsewhere. + */ + assert((S_IFMT & a->mode) == (S_IFMT & b->mode)); + + /* Merge modes */ + if (a->mode == b->mode || a->mode == o->mode) + result->mode = b->mode; + else { + /* must be the 100644/100755 case */ + assert(S_ISREG(a->mode)); + result->mode = a->mode; + clean = (b->mode == o->mode); + /* + * 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 + * 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 + * something like 0100666, then tree-walk.c's calls to + * canon_mode() will just normalize that to 100644 for us and + * thus not solve anything. + * + * Figure out if there's some kind of way we can work around + * this... + */ + } + + /* + * Trivial oid merge. + * + * Note: While one might assume that the next four lines would + * be unnecessary due to the fact that match_mask is often + * setup and already handled, renames don't always take care + * of that. + */ + if (oideq(&a->oid, &b->oid) || oideq(&a->oid, &o->oid)) + oidcpy(&result->oid, &b->oid); + else if (oideq(&b->oid, &o->oid)) + oidcpy(&result->oid, &a->oid); + + /* Remaining rules depend on file vs. submodule vs. symlink. */ + else if (S_ISREG(a->mode)) { + mmbuffer_t result_buf; + int ret = 0, merge_status; + int two_way; + + /* + * If 'o' is different type, treat it as null so we do a + * two-way merge. + */ + two_way = ((S_IFMT & o->mode) != (S_IFMT & a->mode)); + + merge_status = merge_3way(opt, path, + two_way ? &null_oid : &o->oid, + &a->oid, &b->oid, + pathnames, extra_marker_size, + &result_buf); + + if ((merge_status < 0) || !result_buf.ptr) + ret = err(opt, _("Failed to execute internal merge")); + + if (!ret && + write_object_file(result_buf.ptr, result_buf.size, + blob_type, &result->oid)) + ret = err(opt, _("Unable to add %s to database"), + path); + + free(result_buf.ptr); + if (ret) + return -1; + clean &= (merge_status == 0); + path_msg(opt, path, 1, _("Auto-merging %s"), path); + } else if (S_ISGITLINK(a->mode)) { + int two_way = ((S_IFMT & o->mode) != (S_IFMT & a->mode)); + clean = merge_submodule(opt, pathnames[0], + two_way ? &null_oid : &o->oid, + &a->oid, &b->oid, &result->oid); + if (opt->priv->call_depth && two_way && !clean) { + result->mode = o->mode; + oidcpy(&result->oid, &o->oid); + } + } else if (S_ISLNK(a->mode)) { + if (opt->priv->call_depth) { + clean = 0; + result->mode = o->mode; + oidcpy(&result->oid, &o->oid); + } else { + switch (opt->recursive_variant) { + case MERGE_VARIANT_NORMAL: + clean = 0; + oidcpy(&result->oid, &a->oid); + break; + case MERGE_VARIANT_OURS: + oidcpy(&result->oid, &a->oid); + break; + case MERGE_VARIANT_THEIRS: + oidcpy(&result->oid, &b->oid); + break; + } + } + } else + BUG("unsupported object type in the tree: %06o for %s", + a->mode, path); + + return clean; } /*** Function Grouping: functions related to detect_and_process_renames(), *** @@ -645,6 +1144,649 @@ static int handle_content_merge(struct merge_options *opt, /*** Function Grouping: functions related to directory rename detection ***/ +struct collision_info { + struct string_list source_files; + unsigned reported_already:1; +}; + +/* + * Return a new string that replaces the beginning portion (which matches + * rename_info->key), with rename_info->util.new_dir. In perl-speak: + * new_path_name = (old_path =~ s/rename_info->key/rename_info->value/); + * NOTE: + * Caller must ensure that old_path starts with rename_info->key + '/'. + */ +static char *apply_dir_rename(struct strmap_entry *rename_info, + const char *old_path) +{ + struct strbuf new_path = STRBUF_INIT; + const char *old_dir = rename_info->key; + const char *new_dir = rename_info->value; + int oldlen, newlen, new_dir_len; + + oldlen = strlen(old_dir); + if (*new_dir == '\0') + /* + * If someone renamed/merged a subdirectory into the root + * directory (e.g. 'some/subdir' -> ''), then we want to + * avoid returning + * '' + '/filename' + * as the rename; we need to make old_path + oldlen advance + * past the '/' character. + */ + oldlen++; + new_dir_len = strlen(new_dir); + newlen = new_dir_len + (strlen(old_path) - oldlen) + 1; + strbuf_grow(&new_path, newlen); + strbuf_add(&new_path, new_dir, new_dir_len); + strbuf_addstr(&new_path, &old_path[oldlen]); + + return strbuf_detach(&new_path, NULL); +} + +static int path_in_way(struct strmap *paths, const char *path, unsigned side_mask) +{ + 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)); +} + +/* + * See if there is a directory rename for path, and if there are any file + * level conflicts on the given side for the renamed location. If there is + * a rename and there are no conflicts, return the new name. Otherwise, + * return NULL. + */ +static char *handle_path_level_conflicts(struct merge_options *opt, + const char *path, + unsigned side_index, + struct strmap_entry *rename_info, + struct strmap *collisions) +{ + char *new_path = NULL; + struct collision_info *c_info; + int clean = 1; + struct strbuf collision_paths = STRBUF_INIT; + + /* + * entry has the mapping of old directory name to new directory name + * that we want to apply to path. + */ + new_path = apply_dir_rename(rename_info, path); + if (!new_path) + BUG("Failed to apply directory rename!"); + + /* + * The caller needs to have ensured that it has pre-populated + * collisions with all paths that map to new_path. Do a quick check + * to ensure that's the case. + */ + c_info = strmap_get(collisions, new_path); + if (c_info == NULL) + BUG("c_info is NULL"); + + /* + * Check for one-sided add/add/.../add conflicts, i.e. + * where implicit renames from the other side doing + * directory rename(s) can affect this side of history + * to put multiple paths into the same location. Warn + * and bail on directory renames for such paths. + */ + if (c_info->reported_already) { + clean = 0; + } else if (path_in_way(&opt->priv->paths, new_path, 1 << side_index)) { + c_info->reported_already = 1; + strbuf_add_separated_string_list(&collision_paths, ", ", + &c_info->source_files); + path_msg(opt, new_path, 0, + _("CONFLICT (implicit dir rename): Existing file/dir " + "at %s in the way of implicit directory rename(s) " + "putting the following path(s) there: %s."), + new_path, collision_paths.buf); + clean = 0; + } else if (c_info->source_files.nr > 1) { + c_info->reported_already = 1; + strbuf_add_separated_string_list(&collision_paths, ", ", + &c_info->source_files); + path_msg(opt, new_path, 0, + _("CONFLICT (implicit dir rename): Cannot map more " + "than one path to %s; implicit directory renames " + "tried to put these paths there: %s"), + new_path, collision_paths.buf); + clean = 0; + } + + /* Free memory we no longer need */ + strbuf_release(&collision_paths); + if (!clean && new_path) { + free(new_path); + return NULL; + } + + return new_path; +} + +static void dirname_munge(char *filename) +{ + char *slash = strrchr(filename, '/'); + if (!slash) + slash = filename; + *slash = '\0'; +} + +static void increment_count(struct strmap *dir_rename_count, + char *old_dir, + char *new_dir) +{ + struct strintmap *counts; + struct strmap_entry *e; + + /* Get the {new_dirs -> counts} mapping using old_dir */ + e = strmap_get_entry(dir_rename_count, old_dir); + if (e) { + counts = e->value; + } else { + counts = xmalloc(sizeof(*counts)); + strintmap_init_with_options(counts, 0, NULL, 1); + strmap_put(dir_rename_count, old_dir, counts); + } + + /* Increment the count for new_dir */ + strintmap_incr(counts, new_dir, 1); +} + +static void update_dir_rename_counts(struct strmap *dir_rename_count, + struct strset *dirs_removed, + const char *oldname, + const char *newname) +{ + char *old_dir = xstrdup(oldname); + char *new_dir = xstrdup(newname); + char new_dir_first_char = new_dir[0]; + int first_time_in_loop = 1; + + while (1) { + dirname_munge(old_dir); + dirname_munge(new_dir); + + /* + * When renaming + * "a/b/c/d/e/foo.c" -> "a/b/some/thing/else/e/foo.c" + * then this suggests that both + * a/b/c/d/e/ => a/b/some/thing/else/e/ + * a/b/c/d/ => a/b/some/thing/else/ + * so we want to increment counters for both. We do NOT, + * however, also want to suggest that there was the following + * rename: + * a/b/c/ => a/b/some/thing/ + * so we need to quit at that point. + * + * Note the when first_time_in_loop, we only strip off the + * basename, and we don't care if that's different. + */ + if (!first_time_in_loop) { + char *old_sub_dir = strchr(old_dir, '\0')+1; + char *new_sub_dir = strchr(new_dir, '\0')+1; + if (!*new_dir) { + /* + * Special case when renaming to root directory, + * i.e. when new_dir == "". In this case, we had + * something like + * a/b/subdir => subdir + * and so dirname_munge() sets things up so that + * old_dir = "a/b\0subdir\0" + * new_dir = "\0ubdir\0" + * We didn't have a '/' to overwrite a '\0' onto + * in new_dir, so we have to compare differently. + */ + if (new_dir_first_char != old_sub_dir[0] || + strcmp(old_sub_dir+1, new_sub_dir)) + break; + } else { + if (strcmp(old_sub_dir, new_sub_dir)) + break; + } + } + + if (strset_contains(dirs_removed, old_dir)) + increment_count(dir_rename_count, old_dir, new_dir); + else + break; + + /* If we hit toplevel directory ("") for old or new dir, quit */ + if (!*old_dir || !*new_dir) + break; + + first_time_in_loop = 0; + } + + /* Free resources we don't need anymore */ + free(old_dir); + free(new_dir); +} + +static void compute_rename_counts(struct diff_queue_struct *pairs, + struct strmap *dir_rename_count, + struct strset *dirs_removed) +{ + int i; + + for (i = 0; i < pairs->nr; ++i) { + struct diff_filepair *pair = pairs->queue[i]; + + /* File not part of directory rename if it wasn't renamed */ + if (pair->status != 'R') + continue; + + /* + * Make dir_rename_count contain a map of a map: + * old_directory -> {new_directory -> count} + * In other words, for every pair look at the directories for + * the old filename and the new filename and count how many + * times that pairing occurs. + */ + update_dir_rename_counts(dir_rename_count, dirs_removed, + pair->one->path, + pair->two->path); + } +} + +static void get_provisional_directory_renames(struct merge_options *opt, + unsigned side, + int *clean) +{ + struct hashmap_iter iter; + struct strmap_entry *entry; + struct rename_info *renames = &opt->priv->renames; + + compute_rename_counts(&renames->pairs[side], + &renames->dir_rename_count[side], + &renames->dirs_removed[side]); + /* + * Collapse + * dir_rename_count: old_directory -> {new_directory -> count} + * down to + * dir_renames: old_directory -> best_new_directory + * where best_new_directory is the one with the unique highest count. + */ + strmap_for_each_entry(&renames->dir_rename_count[side], &iter, entry) { + const char *source_dir = entry->key; + struct strintmap *counts = entry->value; + struct hashmap_iter count_iter; + struct strmap_entry *count_entry; + int max = 0; + int bad_max = 0; + const char *best = NULL; + + strintmap_for_each_entry(counts, &count_iter, count_entry) { + const char *target_dir = count_entry->key; + intptr_t count = (intptr_t)count_entry->value; + + if (count == max) + bad_max = max; + else if (count > max) { + max = count; + best = target_dir; + } + } + + if (bad_max == max) { + path_msg(opt, source_dir, 0, + _("CONFLICT (directory rename split): " + "Unclear where to rename %s to; it was " + "renamed to multiple other directories, with " + "no destination getting a majority of the " + "files."), + source_dir); + /* + * We should mark this as unclean IF something attempts + * to use this rename. We do not yet have the logic + * in place to detect if this directory rename is being + * used, and optimizations that reduce the number of + * renames cause this to falsely trigger. For now, + * just disable it, causing t6423 testcase 2a to break. + * We'll later fix the detection, and when we do we + * will re-enable setting *clean to 0 (and thereby fix + * t6423 testcase 2a). + */ + /* *clean = 0; */ + } else { + strmap_put(&renames->dir_renames[side], + source_dir, (void*)best); + } + } +} + +static void handle_directory_level_conflicts(struct merge_options *opt) +{ + struct hashmap_iter iter; + struct strmap_entry *entry; + struct string_list duplicated = STRING_LIST_INIT_NODUP; + struct rename_info *renames = &opt->priv->renames; + struct strmap *side1_dir_renames = &renames->dir_renames[MERGE_SIDE1]; + struct strmap *side2_dir_renames = &renames->dir_renames[MERGE_SIDE2]; + int i; + + strmap_for_each_entry(side1_dir_renames, &iter, entry) { + if (strmap_contains(side2_dir_renames, entry->key)) + string_list_append(&duplicated, entry->key); + } + + for (i = 0; i < duplicated.nr; i++) { + strmap_remove(side1_dir_renames, duplicated.items[i].string, 0); + strmap_remove(side2_dir_renames, duplicated.items[i].string, 0); + } + string_list_clear(&duplicated, 0); +} + +static struct strmap_entry *check_dir_renamed(const char *path, + struct strmap *dir_renames) +{ + char *temp = xstrdup(path); + char *end; + struct strmap_entry *e = NULL; + + while ((end = strrchr(temp, '/'))) { + *end = '\0'; + e = strmap_get_entry(dir_renames, temp); + if (e) + break; + } + free(temp); + return e; +} + +static void compute_collisions(struct strmap *collisions, + struct strmap *dir_renames, + struct diff_queue_struct *pairs) +{ + int i; + + strmap_init_with_options(collisions, NULL, 0); + if (strmap_empty(dir_renames)) + return; + + /* + * Multiple files can be mapped to the same path due to directory + * renames done by the other side of history. Since that other + * side of history could have merged multiple directories into one, + * if our side of history added the same file basename to each of + * those directories, then all N of them would get implicitly + * renamed by the directory rename detection into the same path, + * and we'd get an add/add/.../add conflict, and all those adds + * from *this* side of history. This is not representable in the + * index, and users aren't going to easily be able to make sense of + * it. So we need to provide a good warning about what's + * 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. + */ + for (i = 0; i < pairs->nr; ++i) { + struct strmap_entry *rename_info; + struct collision_info *collision_info; + char *new_path; + struct diff_filepair *pair = pairs->queue[i]; + + if (pair->status != 'A' && pair->status != 'R') + continue; + rename_info = check_dir_renamed(pair->two->path, dir_renames); + if (!rename_info) + continue; + + new_path = apply_dir_rename(rename_info, pair->two->path); + assert(new_path); + collision_info = strmap_get(collisions, new_path); + if (collision_info) { + free(new_path); + } else { + collision_info = xcalloc(1, + sizeof(struct collision_info)); + string_list_init(&collision_info->source_files, 0); + strmap_put(collisions, new_path, collision_info); + } + string_list_insert(&collision_info->source_files, + pair->two->path); + } +} + +static char *check_for_directory_rename(struct merge_options *opt, + const char *path, + unsigned side_index, + struct strmap *dir_renames, + struct strmap *dir_rename_exclusions, + struct strmap *collisions, + int *clean_merge) +{ + char *new_path = NULL; + struct strmap_entry *rename_info; + struct strmap_entry *otherinfo = NULL; + const char *new_dir; + + if (strmap_empty(dir_renames)) + return new_path; + rename_info = check_dir_renamed(path, dir_renames); + if (!rename_info) + return new_path; + /* old_dir = rename_info->key; */ + new_dir = rename_info->value; + + /* + * This next part is a little weird. We do not want to do an + * implicit rename into a directory we renamed on our side, because + * that will result in a spurious rename/rename(1to2) conflict. An + * example: + * Base commit: dumbdir/afile, otherdir/bfile + * Side 1: smrtdir/afile, otherdir/bfile + * Side 2: dumbdir/afile, dumbdir/bfile + * Here, while working on Side 1, we could notice that otherdir was + * renamed/merged to dumbdir, and change the diff_filepair for + * otherdir/bfile into a rename into dumbdir/bfile. However, Side + * 2 will notice the rename from dumbdir to smrtdir, and do the + * transitive rename to move it from dumbdir/bfile to + * smrtdir/bfile. That gives us bfile in dumbdir vs being in + * smrtdir, a rename/rename(1to2) conflict. We really just want + * the file to end up in smrtdir. And the way to achieve that is + * 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. + * + * As it turns out, this also prevents N-way transient rename + * confusion; See testcases 9c and 9d of t6043. + */ + otherinfo = strmap_get_entry(dir_rename_exclusions, new_dir); + if (otherinfo) { + path_msg(opt, rename_info->key, 1, + _("WARNING: Avoiding applying %s -> %s rename " + "to %s, because %s itself was renamed."), + rename_info->key, new_dir, path, new_dir); + return NULL; + } + + new_path = handle_path_level_conflicts(opt, path, side_index, + rename_info, collisions); + *clean_merge &= (new_path != NULL); + + return new_path; +} + +static void apply_directory_rename_modifications(struct merge_options *opt, + struct diff_filepair *pair, + char *new_path) +{ + /* + * The basic idea is to get the conflict_info from opt->priv->paths + * at old path, and insert it into new_path; basically just this: + * ci = strmap_get(&opt->priv->paths, old_path); + * strmap_remove(&opt->priv->paths, old_path, 0); + * strmap_put(&opt->priv->paths, new_path, ci); + * However, there are some factors complicating this: + * - opt->priv->paths may already have an entry at new_path + * - Each ci tracks its containing directory, so we need to + * update that + * - If another ci has the same containing directory, then + * the two char*'s MUST point to the same location. See the + * comment in struct merged_info. strcmp equality is not + * enough; we need pointer equality. + * - opt->priv->paths must hold the parent directories of any + * entries that are added. So, if this directory rename + * causes entirely new directories, we must recursively add + * parent directories. + * - For each parent directory added to opt->priv->paths, we + * also need to get its parent directory stored in its + * conflict_info->merged.directory_name with all the same + * requirements about pointer equality. + */ + struct string_list dirs_to_insert = STRING_LIST_INIT_NODUP; + struct conflict_info *ci, *new_ci; + struct strmap_entry *entry; + const char *branch_with_new_path, *branch_with_dir_rename; + const char *old_path = pair->two->path; + const char *parent_name; + const char *cur_path; + int i, len; + + entry = strmap_get_entry(&opt->priv->paths, old_path); + old_path = entry->key; + ci = entry->value; + VERIFY_CI(ci); + + /* Find parent directories missing from opt->priv->paths */ + cur_path = new_path; + while (1) { + /* Find the parent directory of cur_path */ + char *last_slash = strrchr(cur_path, '/'); + if (last_slash) { + parent_name = xstrndup(cur_path, last_slash - cur_path); + } else { + parent_name = opt->priv->toplevel_dir; + break; + } + + /* Look it up in opt->priv->paths */ + entry = strmap_get_entry(&opt->priv->paths, parent_name); + if (entry) { + free((char*)parent_name); + parent_name = entry->key; /* reuse known pointer */ + break; + } + + /* Record this is one of the directories we need to insert */ + string_list_append(&dirs_to_insert, parent_name); + cur_path = parent_name; + } + + /* Traverse dirs_to_insert and insert them into opt->priv->paths */ + for (i = dirs_to_insert.nr-1; i >= 0; --i) { + struct conflict_info *dir_ci; + char *cur_dir = dirs_to_insert.items[i].string; + + dir_ci = xcalloc(1, sizeof(*dir_ci)); + + dir_ci->merged.directory_name = parent_name; + len = strlen(parent_name); + /* len+1 because of trailing '/' character */ + dir_ci->merged.basename_offset = (len > 0 ? len+1 : len); + dir_ci->dirmask = ci->filemask; + strmap_put(&opt->priv->paths, cur_dir, dir_ci); + + parent_name = cur_dir; + } + + /* + * We are removing old_path from opt->priv->paths. old_path also will + * eventually need to be freed, but it may still be used by e.g. + * ci->pathnames. So, store it in another string-list for now. + */ + string_list_append(&opt->priv->paths_to_free, old_path); + + assert(ci->filemask == 2 || ci->filemask == 4); + assert(ci->dirmask == 0); + strmap_remove(&opt->priv->paths, old_path, 0); + + branch_with_new_path = (ci->filemask == 2) ? opt->branch1 : opt->branch2; + branch_with_dir_rename = (ci->filemask == 2) ? opt->branch2 : opt->branch1; + + /* Now, finally update ci and stick it into opt->priv->paths */ + ci->merged.directory_name = parent_name; + len = strlen(parent_name); + ci->merged.basename_offset = (len > 0 ? len+1 : len); + new_ci = strmap_get(&opt->priv->paths, new_path); + if (!new_ci) { + /* Place ci back into opt->priv->paths, but at new_path */ + strmap_put(&opt->priv->paths, new_path, ci); + } else { + int index; + + /* A few sanity checks */ + VERIFY_CI(new_ci); + assert(ci->filemask == 2 || ci->filemask == 4); + assert((new_ci->filemask & ci->filemask) == 0); + assert(!new_ci->merged.clean); + + /* Copy stuff from ci into new_ci */ + new_ci->filemask |= ci->filemask; + if (new_ci->dirmask) + new_ci->df_conflict = 1; + index = (ci->filemask >> 1); + new_ci->pathnames[index] = ci->pathnames[index]; + new_ci->stages[index].mode = ci->stages[index].mode; + oidcpy(&new_ci->stages[index].oid, &ci->stages[index].oid); + + free(ci); + ci = new_ci; + } + + if (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_TRUE) { + /* Notify user of updated path */ + if (pair->status == 'A') + path_msg(opt, new_path, 1, + _("Path updated: %s added in %s inside a " + "directory that was renamed in %s; moving " + "it to %s."), + old_path, branch_with_new_path, + branch_with_dir_rename, new_path); + else + path_msg(opt, new_path, 1, + _("Path updated: %s renamed to %s in %s, " + "inside a directory that was renamed in %s; " + "moving it to %s."), + pair->one->path, old_path, branch_with_new_path, + branch_with_dir_rename, new_path); + } else { + /* + * opt->detect_directory_renames has the value + * MERGE_DIRECTORY_RENAMES_CONFLICT, so mark these as conflicts. + */ + ci->path_conflict = 1; + if (pair->status == 'A') + path_msg(opt, new_path, 0, + _("CONFLICT (file location): %s added in %s " + "inside a directory that was renamed in %s, " + "suggesting it should perhaps be moved to " + "%s."), + old_path, branch_with_new_path, + branch_with_dir_rename, new_path); + else + path_msg(opt, new_path, 0, + _("CONFLICT (file location): %s renamed to %s " + "in %s, inside a directory that was renamed " + "in %s, suggesting it should perhaps be " + "moved to %s."), + pair->one->path, old_path, branch_with_new_path, + branch_with_dir_rename, new_path); + } + + /* + * Finally, record the new location. + */ + pair->two->path = new_path; +} + /*** Function Grouping: functions related to regular rename detection ***/ static int process_renames(struct merge_options *opt, @@ -663,12 +1805,28 @@ static int process_renames(struct merge_options *opt, const char *rename_branch = NULL, *delete_branch = NULL; old_ent = strmap_get_entry(&opt->priv->paths, pair->one->path); - oldpath = old_ent->key; - oldinfo = old_ent->value; - new_ent = strmap_get_entry(&opt->priv->paths, pair->two->path); - newpath = new_ent->key; - newinfo = new_ent->value; + if (old_ent) { + oldpath = old_ent->key; + oldinfo = old_ent->value; + } + newpath = pair->two->path; + if (new_ent) { + newpath = new_ent->key; + newinfo = new_ent->value; + } + + /* + * 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 + * we don't even need the rename; in either case, we can + * skip it. If oldinfo->merged.clean, then the other side + * of history had no changes to oldpath and we don't need + * the rename and can skip it. + */ + if (!oldinfo || oldinfo->merged.clean) + continue; /* * diff_filepairs have copies of pathnames, thus we have to @@ -949,9 +2107,12 @@ static void detect_regular_renames(struct merge_options *opt, diff_opts.show_rename_progress = opt->show_rename_progress; diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; diff_setup_done(&diff_opts); + + trace2_region_enter("diff", "diffcore_rename", opt->repo); diff_tree_oid(&merge_base->object.oid, &side->object.oid, "", &diff_opts); diffcore_std(&diff_opts); + trace2_region_leave("diff", "diffcore_rename", opt->repo); if (diff_opts.needed_rename_limit > renames->needed_limit) renames->needed_limit = diff_opts.needed_rename_limit; @@ -970,22 +2131,44 @@ static void detect_regular_renames(struct merge_options *opt, */ static int collect_renames(struct merge_options *opt, struct diff_queue_struct *result, - unsigned side_index) + unsigned side_index, + struct strmap *dir_renames_for_side, + struct strmap *rename_exclusions) { int i, clean = 1; + struct strmap collisions; struct diff_queue_struct *side_pairs; + struct hashmap_iter iter; + struct strmap_entry *entry; struct rename_info *renames = &opt->priv->renames; side_pairs = &renames->pairs[side_index]; + compute_collisions(&collisions, dir_renames_for_side, side_pairs); for (i = 0; i < side_pairs->nr; ++i) { struct diff_filepair *p = side_pairs->queue[i]; + char *new_path; /* non-NULL only with directory renames */ - if (p->status != 'R') { + if (p->status != 'A' && p->status != 'R') { diff_free_filepair(p); continue; } + new_path = check_for_directory_rename(opt, p->two->path, + side_index, + dir_renames_for_side, + rename_exclusions, + &collisions, + &clean); + + if (p->status != 'R' && !new_path) { + diff_free_filepair(p); + continue; + } + + if (new_path) + apply_directory_rename_modifications(opt, p, new_path); + /* * p->score comes back from diffcore_rename_extended() with * the similarity of the renamed file. The similarity is @@ -1000,6 +2183,20 @@ static int collect_renames(struct merge_options *opt, result->queue[result->nr++] = p; } + /* Free each value in the collisions map */ + strmap_for_each_entry(&collisions, &iter, entry) { + struct collision_info *info = entry->value; + string_list_clear(&info->source_files, 0); + } + /* + * In compute_collisions(), we set collisions.strdup_strings to 0 + * so that we wouldn't have to make another copy of the new_path + * allocated by apply_dir_rename(). But now that we've used them + * and have no other references to these strings, it is time to + * deallocate them. + */ + free_strmap_strings(&collisions); + strmap_clear(&collisions, 1); return clean; } @@ -1010,21 +2207,42 @@ static int detect_and_process_renames(struct merge_options *opt, { struct diff_queue_struct combined; struct rename_info *renames = &opt->priv->renames; - int s, clean = 1; + int need_dir_renames, s, clean = 1; memset(&combined, 0, sizeof(combined)); + trace2_region_enter("merge", "regular renames", opt->repo); detect_regular_renames(opt, merge_base, side1, MERGE_SIDE1); detect_regular_renames(opt, merge_base, side2, MERGE_SIDE2); + trace2_region_leave("merge", "regular renames", opt->repo); + + trace2_region_enter("merge", "directory renames", opt->repo); + need_dir_renames = + !opt->priv->call_depth && + (opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_TRUE || + opt->detect_directory_renames == MERGE_DIRECTORY_RENAMES_CONFLICT); + + if (need_dir_renames) { + get_provisional_directory_renames(opt, MERGE_SIDE1, &clean); + get_provisional_directory_renames(opt, MERGE_SIDE2, &clean); + handle_directory_level_conflicts(opt); + } ALLOC_GROW(combined.queue, renames->pairs[1].nr + renames->pairs[2].nr, combined.alloc); - clean &= collect_renames(opt, &combined, MERGE_SIDE1); - clean &= collect_renames(opt, &combined, MERGE_SIDE2); + clean &= collect_renames(opt, &combined, MERGE_SIDE1, + &renames->dir_renames[2], + &renames->dir_renames[1]); + clean &= collect_renames(opt, &combined, MERGE_SIDE2, + &renames->dir_renames[1], + &renames->dir_renames[2]); QSORT(combined.queue, combined.nr, compare_pairs); + trace2_region_leave("merge", "directory renames", opt->repo); + trace2_region_enter("merge", "process renames", opt->repo); clean &= process_renames(opt, &combined); + trace2_region_leave("merge", "process renames", opt->repo); /* Free memory for renames->pairs[] and combined */ for (s = MERGE_SIDE1; s <= MERGE_SIDE2; s++) { @@ -1366,6 +2584,8 @@ static void process_entry(struct merge_options *opt, struct conflict_info *ci, struct directory_versions *dir_metadata) { + int df_file_index = 0; + VERIFY_CI(ci); assert(ci->filemask >= 0 && ci->filemask <= 7); /* ci->match_mask == 7 was handled in collect_merge_info_callback() */ @@ -1380,14 +2600,108 @@ static void process_entry(struct merge_options *opt, assert(ci->df_conflict); } - if (ci->df_conflict) { - die("Not yet implemented."); + if (ci->df_conflict && ci->merged.result.mode == 0) { + int i; + + /* + * directory no longer in the way, but we do have a file we + * need to place here so we need to clean away the "directory + * merges to nothing" result. + */ + ci->df_conflict = 0; + assert(ci->filemask != 0); + ci->merged.clean = 0; + ci->merged.is_null = 0; + /* and we want to zero out any directory-related entries */ + ci->match_mask = (ci->match_mask & ~ci->dirmask); + ci->dirmask = 0; + for (i = MERGE_BASE; i <= MERGE_SIDE2; i++) { + if (ci->filemask & (1 << i)) + continue; + ci->stages[i].mode = 0; + oidcpy(&ci->stages[i].oid, &null_oid); + } + } else if (ci->df_conflict && ci->merged.result.mode != 0) { + /* + * This started out as a D/F conflict, and the entries in + * the competing directory were not removed by the merge as + * evidenced by write_completed_directory() writing a value + * to ci->merged.result.mode. + */ + struct conflict_info *new_ci; + const char *branch; + const char *old_path = path; + int i; + + assert(ci->merged.result.mode == S_IFDIR); + + /* + * If filemask is 1, we can just ignore the file as having + * been deleted on both sides. We do not want to overwrite + * ci->merged.result, since it stores the tree for all the + * files under it. + */ + if (ci->filemask == 1) { + ci->filemask = 0; + return; + } + + /* + * This file still exists on at least one side, and we want + * the directory to remain here, so we need to move this + * path to some new location. + */ + new_ci = xcalloc(1, sizeof(*new_ci)); + /* We don't really want new_ci->merged.result copied, but it'll + * be overwritten below so it doesn't matter. We also don't + * want any directory mode/oid values copied, but we'll zero + * those out immediately. We do want the rest of ci copied. + */ + memcpy(new_ci, ci, sizeof(*ci)); + new_ci->match_mask = (new_ci->match_mask & ~new_ci->dirmask); + new_ci->dirmask = 0; + for (i = MERGE_BASE; i <= MERGE_SIDE2; i++) { + if (new_ci->filemask & (1 << i)) + continue; + /* zero out any entries related to directories */ + new_ci->stages[i].mode = 0; + oidcpy(&new_ci->stages[i].oid, &null_oid); + } + + /* + * Find out which side this file came from; note that we + * cannot just use ci->filemask, because renames could cause + * the filemask to go back to 7. So we use dirmask, then + * pick the opposite side's index. + */ + df_file_index = (ci->dirmask & (1 << 1)) ? 2 : 1; + branch = (df_file_index == 1) ? opt->branch1 : opt->branch2; + path = unique_path(&opt->priv->paths, path, branch); + strmap_put(&opt->priv->paths, path, new_ci); + + path_msg(opt, path, 0, + _("CONFLICT (file/directory): directory in the way " + "of %s from %s; moving it to %s instead."), + old_path, branch, path); + + /* + * Zero out the filemask for the old ci. At this point, ci + * was just an entry for a directory, so we don't need to + * do anything more with it. + */ + ci->filemask = 0; + + /* + * Now note that we're working on the new entry (path was + * updated above. + */ + ci = new_ci; } /* * NOTE: Below there is a long switch-like if-elseif-elseif... block * which the code goes through even for the df_conflict cases - * above. Well, it will once we don't die-not-implemented above. + * above. */ if (ci->match_mask) { ci->merged.clean = 1; @@ -1411,21 +2725,142 @@ static void process_entry(struct merge_options *opt, } else if (ci->filemask >= 6 && (S_IFMT & ci->stages[1].mode) != (S_IFMT & ci->stages[2].mode)) { - /* - * Two different items from (file/submodule/symlink) - */ - die("Not yet implemented."); + /* Two different items from (file/submodule/symlink) */ + if (opt->priv->call_depth) { + /* Just use the version from the merge base */ + ci->merged.clean = 0; + oidcpy(&ci->merged.result.oid, &ci->stages[0].oid); + ci->merged.result.mode = ci->stages[0].mode; + ci->merged.is_null = (ci->merged.result.mode == 0); + } else { + /* Handle by renaming one or both to separate paths. */ + unsigned o_mode = ci->stages[0].mode; + unsigned a_mode = ci->stages[1].mode; + unsigned b_mode = ci->stages[2].mode; + struct conflict_info *new_ci; + const char *a_path = NULL, *b_path = NULL; + int rename_a = 0, rename_b = 0; + + new_ci = xmalloc(sizeof(*new_ci)); + + if (S_ISREG(a_mode)) + rename_a = 1; + else if (S_ISREG(b_mode)) + rename_b = 1; + else { + rename_a = 1; + rename_b = 1; + } + + path_msg(opt, path, 0, + _("CONFLICT (distinct types): %s had different " + "types on each side; renamed %s of them so " + "each can be recorded somewhere."), + path, + (rename_a && rename_b) ? _("both") : _("one")); + + ci->merged.clean = 0; + memcpy(new_ci, ci, sizeof(*new_ci)); + + /* Put b into new_ci, removing a from stages */ + new_ci->merged.result.mode = ci->stages[2].mode; + oidcpy(&new_ci->merged.result.oid, &ci->stages[2].oid); + new_ci->stages[1].mode = 0; + oidcpy(&new_ci->stages[1].oid, &null_oid); + new_ci->filemask = 5; + if ((S_IFMT & b_mode) != (S_IFMT & o_mode)) { + new_ci->stages[0].mode = 0; + oidcpy(&new_ci->stages[0].oid, &null_oid); + new_ci->filemask = 4; + } + + /* Leave only a in ci, fixing stages. */ + ci->merged.result.mode = ci->stages[1].mode; + oidcpy(&ci->merged.result.oid, &ci->stages[1].oid); + ci->stages[2].mode = 0; + oidcpy(&ci->stages[2].oid, &null_oid); + ci->filemask = 3; + if ((S_IFMT & a_mode) != (S_IFMT & o_mode)) { + ci->stages[0].mode = 0; + oidcpy(&ci->stages[0].oid, &null_oid); + ci->filemask = 2; + } + + /* Insert entries into opt->priv_paths */ + assert(rename_a || rename_b); + if (rename_a) { + a_path = unique_path(&opt->priv->paths, + path, opt->branch1); + strmap_put(&opt->priv->paths, a_path, ci); + } + + if (rename_b) + b_path = unique_path(&opt->priv->paths, + path, opt->branch2); + else + b_path = path; + strmap_put(&opt->priv->paths, b_path, new_ci); + + if (rename_a && rename_b) { + strmap_remove(&opt->priv->paths, path, 0); + /* + * We removed path from opt->priv->paths. path + * will also eventually need to be freed, but + * it may still be used by e.g. ci->pathnames. + * So, store it in another string-list for now. + */ + string_list_append(&opt->priv->paths_to_free, + path); + } + + /* + * Do special handling for b_path since process_entry() + * won't be called on it specially. + */ + strmap_put(&opt->priv->conflicted, b_path, new_ci); + record_entry_for_tree(dir_metadata, b_path, + &new_ci->merged); + + /* + * Remaining code for processing this entry should + * think in terms of processing a_path. + */ + if (a_path) + path = a_path; + } } else if (ci->filemask >= 6) { - /* - * TODO: Needs a two-way or three-way content merge, but we're - * just being lazy and copying the version from HEAD and - * leaving it as conflicted. - */ - ci->merged.clean = 0; - ci->merged.result.mode = ci->stages[1].mode; - oidcpy(&ci->merged.result.oid, &ci->stages[1].oid); - /* When we fix above, we'll call handle_content_merge() */ - (void)handle_content_merge; + /* Need a two-way or three-way content merge */ + struct version_info merged_file; + unsigned clean_merge; + struct version_info *o = &ci->stages[0]; + struct version_info *a = &ci->stages[1]; + struct version_info *b = &ci->stages[2]; + + clean_merge = handle_content_merge(opt, path, o, a, b, + ci->pathnames, + opt->priv->call_depth * 2, + &merged_file); + ci->merged.clean = clean_merge && + !ci->df_conflict && !ci->path_conflict; + ci->merged.result.mode = merged_file.mode; + ci->merged.is_null = (merged_file.mode == 0); + oidcpy(&ci->merged.result.oid, &merged_file.oid); + if (clean_merge && ci->df_conflict) { + assert(df_file_index == 1 || df_file_index == 2); + ci->filemask = 1 << df_file_index; + ci->stages[df_file_index].mode = merged_file.mode; + oidcpy(&ci->stages[df_file_index].oid, &merged_file.oid); + } + if (!clean_merge) { + const char *reason = _("content"); + if (ci->filemask == 6) + reason = _("add/add"); + if (S_ISGITLINK(merged_file.mode)) + reason = _("submodule"); + path_msg(opt, path, 0, + _("CONFLICT (%s): Merge conflict in %s"), + reason, path); + } } else if (ci->filemask == 3 || ci->filemask == 5) { /* Modify/delete */ const char *modify_branch, *delete_branch; @@ -1489,20 +2924,30 @@ static void process_entries(struct merge_options *opt, STRING_LIST_INIT_NODUP, NULL, 0 }; + trace2_region_enter("merge", "process_entries setup", opt->repo); if (strmap_empty(&opt->priv->paths)) { oidcpy(result_oid, opt->repo->hash_algo->empty_tree); return; } /* Hack to pre-allocate plist to the desired size */ + trace2_region_enter("merge", "plist grow", opt->repo); ALLOC_GROW(plist.items, strmap_get_size(&opt->priv->paths), plist.alloc); + trace2_region_leave("merge", "plist grow", opt->repo); /* Put every entry from paths into plist, then sort */ + trace2_region_enter("merge", "plist copy", opt->repo); strmap_for_each_entry(&opt->priv->paths, &iter, e) { string_list_append(&plist, e->key)->util = e->value; } + trace2_region_leave("merge", "plist copy", opt->repo); + + trace2_region_enter("merge", "plist special sort", opt->repo); plist.cmp = string_list_df_name_compare; string_list_sort(&plist); + trace2_region_leave("merge", "plist special sort", opt->repo); + + trace2_region_leave("merge", "process_entries setup", opt->repo); /* * Iterate over the items in reverse order, so we can handle paths @@ -1513,6 +2958,7 @@ static void process_entries(struct merge_options *opt, * (because it allows us to know whether the directory is still in * the way when it is time to process the file at the same path). */ + trace2_region_enter("merge", "processing", opt->repo); for (entry = &plist.items[plist.nr-1]; entry >= plist.items; --entry) { char *path = entry->string; /* @@ -1531,7 +2977,9 @@ static void process_entries(struct merge_options *opt, process_entry(opt, path, ci, &dir_metadata); } } + trace2_region_leave("merge", "processing", opt->repo); + trace2_region_enter("merge", "process_entries cleanup", opt->repo); if (dir_metadata.offsets.nr != 1 || (uintptr_t)dir_metadata.offsets.items[0].util != 0) { printf("dir_metadata.offsets.nr = %d (should be 1)\n", @@ -1546,6 +2994,7 @@ static void process_entries(struct merge_options *opt, string_list_clear(&plist, 0); string_list_clear(&dir_metadata.versions, 0); string_list_clear(&dir_metadata.offsets, 0); + trace2_region_leave("merge", "process_entries cleanup", opt->repo); } /*** Function Grouping: functions related to merge_switch_to_result() ***/ @@ -1704,12 +3153,15 @@ void merge_switch_to_result(struct merge_options *opt, if (result->clean >= 0 && update_worktree_and_index) { struct merge_options_internal *opti = result->priv; + trace2_region_enter("merge", "checkout", opt->repo); if (checkout(opt, head, result->tree)) { /* failure to function */ result->clean = -1; return; } + trace2_region_leave("merge", "checkout", opt->repo); + trace2_region_enter("merge", "record_conflicted", opt->repo); if (record_conflicted_index_entries(opt, opt->repo->index, &opti->paths, &opti->conflicted)) { @@ -1717,6 +3169,7 @@ void merge_switch_to_result(struct merge_options *opt, result->clean = -1; return; } + trace2_region_leave("merge", "record_conflicted", opt->repo); } if (display_update_msgs) { @@ -1726,6 +3179,8 @@ void merge_switch_to_result(struct merge_options *opt, struct string_list olist = STRING_LIST_INIT_NODUP; int i; + trace2_region_enter("merge", "display messages", opt->repo); + /* Hack to pre-allocate olist to the desired size */ ALLOC_GROW(olist.items, strmap_get_size(&opti->output), olist.alloc); @@ -1747,6 +3202,8 @@ void merge_switch_to_result(struct merge_options *opt, /* Also include needed rename limit adjustment now */ diff_warn_rename_limit("merge.renamelimit", opti->renames.needed_limit, 0); + + trace2_region_leave("merge", "display messages", opt->repo); } merge_finalize(opt, result); @@ -1784,7 +3241,11 @@ static struct commit *make_virtual_commit(struct repository *repo, static void merge_start(struct merge_options *opt, struct merge_result *result) { + struct rename_info *renames; + int i; + /* Sanity checks on opt */ + trace2_region_enter("merge", "sanity checks", opt->repo); assert(opt->repo); assert(opt->branch1 && opt->branch2); @@ -1811,13 +3272,43 @@ static void merge_start(struct merge_options *opt, struct merge_result *result) assert(opt->obuf.len == 0); assert(opt->priv == NULL); + if (result->priv) { + opt->priv = result->priv; + result->priv = NULL; + /* + * opt->priv non-NULL means we had results from a previous + * run; do a few sanity checks that user didn't mess with + * it in an obvious fashion. + */ + assert(opt->priv->call_depth == 0); + assert(!opt->priv->toplevel_dir || + 0 == strlen(opt->priv->toplevel_dir)); + } + trace2_region_leave("merge", "sanity checks", opt->repo); /* Default to histogram diff. Actually, just hardcode it...for now. */ opt->xdl_opts = DIFF_WITH_ALG(opt, HISTOGRAM_DIFF); /* Initialization of opt->priv, our internal merge data */ + trace2_region_enter("merge", "allocate/init", opt->repo); + if (opt->priv) { + clear_or_reinit_internal_opts(opt->priv, 1); + trace2_region_leave("merge", "allocate/init", opt->repo); + return; + } opt->priv = xcalloc(1, sizeof(*opt->priv)); + /* Initialization of various renames fields */ + renames = &opt->priv->renames; + for (i = MERGE_SIDE1; i <= MERGE_SIDE2; i++) { + strset_init_with_options(&renames->dirs_removed[i], + NULL, 0); + strmap_init_with_options(&renames->dir_rename_count[i], + NULL, 1); + strmap_init_with_options(&renames->dir_renames[i], + NULL, 0); + } + /* * Although we initialize opt->priv->paths with strdup_strings=0, * that's just to avoid making yet another copy of an allocated @@ -1837,6 +3328,8 @@ static void merge_start(struct merge_options *opt, struct merge_result *result) * subset of the overall paths that have special output. */ strmap_init(&opt->priv->output); + + trace2_region_leave("merge", "allocate/init", opt->repo); } /*** Function Grouping: merge_incore_*() and their internal variants ***/ @@ -1852,6 +3345,7 @@ static void merge_ort_nonrecursive_internal(struct merge_options *opt, { struct object_id working_tree_oid; + trace2_region_enter("merge", "collect_merge_info", opt->repo); if (collect_merge_info(opt, merge_base, side1, side2) != 0) { /* * TRANSLATORS: The %s arguments are: 1) tree hash of a merge @@ -1864,10 +3358,16 @@ static void merge_ort_nonrecursive_internal(struct merge_options *opt, result->clean = -1; return; } + trace2_region_leave("merge", "collect_merge_info", opt->repo); + trace2_region_enter("merge", "renames", opt->repo); result->clean = detect_and_process_renames(opt, merge_base, side1, side2); + trace2_region_leave("merge", "renames", opt->repo); + + trace2_region_enter("merge", "process_entries", opt->repo); process_entries(opt, &working_tree_oid); + trace2_region_leave("merge", "process_entries", opt->repo); /* Set return values */ result->tree = parse_tree_indirect(&working_tree_oid); @@ -1968,9 +3468,15 @@ void merge_incore_nonrecursive(struct merge_options *opt, struct tree *side2, struct merge_result *result) { + trace2_region_enter("merge", "incore_nonrecursive", opt->repo); + + trace2_region_enter("merge", "merge_start", opt->repo); assert(opt->ancestor != NULL); merge_start(opt, result); + trace2_region_leave("merge", "merge_start", opt->repo); + merge_ort_nonrecursive_internal(opt, merge_base, side1, side2, result); + trace2_region_leave("merge", "incore_nonrecursive", opt->repo); } void merge_incore_recursive(struct merge_options *opt, @@ -1979,9 +3485,15 @@ void merge_incore_recursive(struct merge_options *opt, struct commit *side2, struct merge_result *result) { + trace2_region_enter("merge", "incore_recursive", opt->repo); + /* We set the ancestor label based on the merge_bases */ assert(opt->ancestor == NULL); + trace2_region_enter("merge", "merge_start", opt->repo); merge_start(opt, result); + trace2_region_leave("merge", "merge_start", opt->repo); + merge_ort_internal(opt, merge_bases, side1, side2, result); + trace2_region_leave("merge", "incore_recursive", opt->repo); } diff --git a/name-hash.c b/name-hash.c index 5d3c7b12c1..4e03fac9bb 100644 --- a/name-hash.c +++ b/name-hash.c @@ -7,6 +7,7 @@ */ #include "cache.h" #include "thread-utils.h" +#include "trace2.h" struct dir_entry { struct hashmap_entry ent; @@ -577,6 +578,7 @@ static void lazy_init_name_hash(struct index_state *istate) if (istate->name_hash_initialized) return; trace_performance_enter(); + trace2_region_enter("index", "name-hash-init", istate->repo); hashmap_init(&istate->name_hash, cache_entry_cmp, NULL, istate->cache_nr); hashmap_init(&istate->dir_hash, dir_entry_cmp, NULL, istate->cache_nr); @@ -597,6 +599,7 @@ static void lazy_init_name_hash(struct index_state *istate) } istate->name_hash_initialized = 1; + trace2_region_leave("index", "name-hash-init", istate->repo); trace_performance_leave("initialize name hash"); } diff --git a/object-store.h b/object-store.h index c4fc9dd74e..541dab0858 100644 --- a/object-store.h +++ b/object-store.h @@ -85,6 +85,9 @@ struct packed_git { multi_pack_index:1; unsigned char hash[GIT_MAX_RAWSZ]; struct revindex_entry *revindex; + const uint32_t *revindex_data; + const uint32_t *revindex_map; + size_t revindex_size; /* something like ".git/objects/pack/xxxxx.pack" */ char pack_name[FLEX_ARRAY]; /* more */ }; diff --git a/oid-array.c b/oid-array.c index 889b311f22..73ba76e9e9 100644 --- a/oid-array.c +++ b/oid-array.c @@ -22,16 +22,16 @@ void oid_array_sort(struct oid_array *array) array->sorted = 1; } -static const unsigned char *sha1_access(size_t index, void *table) +static const struct object_id *oid_access(size_t index, const void *table) { - struct object_id *array = table; - return array[index].hash; + const struct object_id *array = table; + return &array[index]; } int oid_array_lookup(struct oid_array *array, const struct object_id *oid) { oid_array_sort(array); - return hash_pos(oid->hash, array->oid, array->nr, sha1_access); + return oid_pos(oid, array->oid, array->nr, oid_access); } void oid_array_clear(struct oid_array *array) diff --git a/pack-bitmap-write.c b/pack-bitmap-write.c index 92460a6126..88d9e696a5 100644 --- a/pack-bitmap-write.c +++ b/pack-bitmap-write.c @@ -610,10 +610,10 @@ static inline void dump_bitmap(struct hashfile *f, struct ewah_bitmap *bitmap) die("Failed to write bitmap index"); } -static const unsigned char *sha1_access(size_t pos, void *table) +static const struct object_id *oid_access(size_t pos, const void *table) { - struct pack_idx_entry **index = table; - return index[pos]->oid.hash; + const struct pack_idx_entry * const *index = table; + return &index[pos]->oid; } static void write_selected_commits_v1(struct hashfile *f, @@ -626,7 +626,7 @@ static void write_selected_commits_v1(struct hashfile *f, struct bitmapped_commit *stored = &writer.selected[i]; int commit_pos = - hash_pos(stored->commit->object.oid.hash, index, index_nr, sha1_access); + oid_pos(&stored->commit->object.oid, index, index_nr, oid_access); if (commit_pos < 0) BUG("trying to write commit not in index"); diff --git a/pack-revindex.c b/pack-revindex.c index 5e69bc7372..83fe4de773 100644 --- a/pack-revindex.c +++ b/pack-revindex.c @@ -2,6 +2,7 @@ #include "pack-revindex.h" #include "object-store.h" #include "packfile.h" +#include "config.h" struct revindex_entry { off_t offset; @@ -164,16 +165,133 @@ static void create_pack_revindex(struct packed_git *p) sort_revindex(p->revindex, num_ent, p->pack_size); } -int load_pack_revindex(struct packed_git *p) +static int create_pack_revindex_in_memory(struct packed_git *p) { - if (!p->revindex) { - if (open_pack_index(p)) - return -1; - create_pack_revindex(p); - } + if (git_env_bool(GIT_TEST_REV_INDEX_DIE_IN_MEMORY, 0)) + die("dying as requested by '%s'", + GIT_TEST_REV_INDEX_DIE_IN_MEMORY); + if (open_pack_index(p)) + return -1; + create_pack_revindex(p); return 0; } +static char *pack_revindex_filename(struct packed_git *p) +{ + size_t len; + if (!strip_suffix(p->pack_name, ".pack", &len)) + BUG("pack_name does not end in .pack"); + return xstrfmt("%.*s.rev", (int)len, p->pack_name); +} + +#define RIDX_HEADER_SIZE (12) +#define RIDX_MIN_SIZE (RIDX_HEADER_SIZE + (2 * the_hash_algo->rawsz)) + +struct revindex_header { + uint32_t signature; + uint32_t version; + uint32_t hash_id; +}; + +static int load_revindex_from_disk(char *revindex_name, + uint32_t num_objects, + const uint32_t **data_p, size_t *len_p) +{ + int fd, ret = 0; + struct stat st; + void *data = NULL; + size_t revindex_size; + struct revindex_header *hdr; + + fd = git_open(revindex_name); + + if (fd < 0) { + ret = -1; + goto cleanup; + } + if (fstat(fd, &st)) { + ret = error_errno(_("failed to read %s"), revindex_name); + goto cleanup; + } + + revindex_size = xsize_t(st.st_size); + + if (revindex_size < RIDX_MIN_SIZE) { + ret = error(_("reverse-index file %s is too small"), revindex_name); + goto cleanup; + } + + if (revindex_size - RIDX_MIN_SIZE != st_mult(sizeof(uint32_t), num_objects)) { + ret = error(_("reverse-index file %s is corrupt"), revindex_name); + goto cleanup; + } + + data = xmmap(NULL, revindex_size, PROT_READ, MAP_PRIVATE, fd, 0); + hdr = data; + + if (ntohl(hdr->signature) != RIDX_SIGNATURE) { + ret = error(_("reverse-index file %s has unknown signature"), revindex_name); + goto cleanup; + } + if (ntohl(hdr->version) != 1) { + ret = error(_("reverse-index file %s has unsupported version %"PRIu32), + revindex_name, ntohl(hdr->version)); + goto cleanup; + } + if (!(ntohl(hdr->hash_id) == 1 || ntohl(hdr->hash_id) == 2)) { + ret = error(_("reverse-index file %s has unsupported hash id %"PRIu32), + revindex_name, ntohl(hdr->hash_id)); + goto cleanup; + } + +cleanup: + if (ret) { + if (data) + munmap(data, revindex_size); + } else { + *len_p = revindex_size; + *data_p = (const uint32_t *)data; + } + + close(fd); + return ret; +} + +static int load_pack_revindex_from_disk(struct packed_git *p) +{ + char *revindex_name; + int ret; + if (open_pack_index(p)) + return -1; + + revindex_name = pack_revindex_filename(p); + + ret = load_revindex_from_disk(revindex_name, + p->num_objects, + &p->revindex_map, + &p->revindex_size); + if (ret) + goto cleanup; + + p->revindex_data = (const uint32_t *)((const char *)p->revindex_map + RIDX_HEADER_SIZE); + +cleanup: + free(revindex_name); + return ret; +} + +int load_pack_revindex(struct packed_git *p) +{ + if (p->revindex || p->revindex_data) + return 0; + + if (!load_pack_revindex_from_disk(p)) + return 0; + else if (!create_pack_revindex_in_memory(p)) + return 0; + return -1; +} + int offset_to_pack_pos(struct packed_git *p, off_t ofs, uint32_t *pos) { unsigned lo, hi; @@ -203,18 +321,28 @@ int offset_to_pack_pos(struct packed_git *p, off_t ofs, uint32_t *pos) uint32_t pack_pos_to_index(struct packed_git *p, uint32_t pos) { - if (!p->revindex) + if (!(p->revindex || p->revindex_data)) BUG("pack_pos_to_index: reverse index not yet loaded"); if (p->num_objects <= pos) BUG("pack_pos_to_index: out-of-bounds object at %"PRIu32, pos); - return p->revindex[pos].nr; + + if (p->revindex) + return p->revindex[pos].nr; + else + return get_be32(p->revindex_data + pos); } off_t pack_pos_to_offset(struct packed_git *p, uint32_t pos) { - if (!p->revindex) + if (!(p->revindex || p->revindex_data)) BUG("pack_pos_to_index: reverse index not yet loaded"); if (p->num_objects < pos) BUG("pack_pos_to_offset: out-of-bounds object at %"PRIu32, pos); - return p->revindex[pos].offset; + + if (p->revindex) + return p->revindex[pos].offset; + else if (pos == p->num_objects) + return p->pack_size - the_hash_algo->rawsz; + else + return nth_packed_object_offset(p, pack_pos_to_index(p, pos)); } diff --git a/pack-revindex.h b/pack-revindex.h index 6e0320b08b..ba7c82c125 100644 --- a/pack-revindex.h +++ b/pack-revindex.h @@ -16,11 +16,21 @@ * can be found */ + +#define RIDX_SIGNATURE 0x52494458 /* "RIDX" */ +#define RIDX_VERSION 1 + +#define GIT_TEST_WRITE_REV_INDEX "GIT_TEST_WRITE_REV_INDEX" +#define GIT_TEST_REV_INDEX_DIE_IN_MEMORY "GIT_TEST_REV_INDEX_DIE_IN_MEMORY" + struct packed_git; /* * load_pack_revindex populates the revindex's internal data-structures for the * given pack, returning zero on success and a negative value otherwise. + * + * If a '.rev' file is present it is mmap'd, and pointers are assigned into it + * (instead of using the in-memory variant). */ int load_pack_revindex(struct packed_git *p); @@ -55,7 +65,9 @@ uint32_t pack_pos_to_index(struct packed_git *p, uint32_t pos); * If the reverse index has not yet been loaded, or the position is out of * bounds, this function aborts. * - * This function runs in constant time. + * This function runs in constant time under both in-memory and on-disk reverse + * indexes, but an additional step is taken to consult the corresponding .idx + * file when using the on-disk format. */ off_t pack_pos_to_offset(struct packed_git *p, uint32_t pos); diff --git a/pack-write.c b/pack-write.c index e9bb3fd949..680c36755d 100644 --- a/pack-write.c +++ b/pack-write.c @@ -167,6 +167,113 @@ const char *write_idx_file(const char *index_name, struct pack_idx_entry **objec return index_name; } +static int pack_order_cmp(const void *va, const void *vb, void *ctx) +{ + struct pack_idx_entry **objects = ctx; + + off_t oa = objects[*(uint32_t*)va]->offset; + off_t ob = objects[*(uint32_t*)vb]->offset; + + if (oa < ob) + return -1; + if (oa > ob) + return 1; + return 0; +} + +static void write_rev_header(struct hashfile *f) +{ + uint32_t oid_version; + switch (hash_algo_by_ptr(the_hash_algo)) { + case GIT_HASH_SHA1: + oid_version = 1; + break; + case GIT_HASH_SHA256: + oid_version = 2; + break; + default: + die("write_rev_header: unknown hash version"); + } + + hashwrite_be32(f, RIDX_SIGNATURE); + hashwrite_be32(f, RIDX_VERSION); + hashwrite_be32(f, oid_version); +} + +static void write_rev_index_positions(struct hashfile *f, + struct pack_idx_entry **objects, + uint32_t nr_objects) +{ + uint32_t *pack_order; + uint32_t i; + + ALLOC_ARRAY(pack_order, nr_objects); + for (i = 0; i < nr_objects; i++) + pack_order[i] = i; + QSORT_S(pack_order, nr_objects, pack_order_cmp, objects); + + for (i = 0; i < nr_objects; i++) + hashwrite_be32(f, pack_order[i]); + + free(pack_order); +} + +static void write_rev_trailer(struct hashfile *f, const unsigned char *hash) +{ + hashwrite(f, hash, the_hash_algo->rawsz); +} + +const char *write_rev_file(const char *rev_name, + struct pack_idx_entry **objects, + uint32_t nr_objects, + const unsigned char *hash, + unsigned flags) +{ + struct hashfile *f; + int fd; + + if ((flags & WRITE_REV) && (flags & WRITE_REV_VERIFY)) + die(_("cannot both write and verify reverse index")); + + if (flags & WRITE_REV) { + if (!rev_name) { + struct strbuf tmp_file = STRBUF_INIT; + fd = odb_mkstemp(&tmp_file, "pack/tmp_rev_XXXXXX"); + rev_name = strbuf_detach(&tmp_file, NULL); + } else { + unlink(rev_name); + fd = open(rev_name, O_CREAT|O_EXCL|O_WRONLY, 0600); + if (fd < 0) + die_errno("unable to create '%s'", rev_name); + } + f = hashfd(fd, rev_name); + } else if (flags & WRITE_REV_VERIFY) { + struct stat statbuf; + if (stat(rev_name, &statbuf)) { + if (errno == ENOENT) { + /* .rev files are optional */ + return NULL; + } else + die_errno(_("could not stat: %s"), rev_name); + } + f = hashfd_check(rev_name); + } else + return NULL; + + write_rev_header(f); + + write_rev_index_positions(f, objects, nr_objects); + write_rev_trailer(f, hash); + + if (rev_name && adjust_shared_perm(rev_name) < 0) + die(_("failed to make %s readable"), rev_name); + + finalize_hashfile(f, NULL, CSUM_HASH_IN_STREAM | CSUM_CLOSE | + ((flags & WRITE_IDX_VERIFY) ? 0 : CSUM_FSYNC)); + + return rev_name; +} + off_t write_pack_header(struct hashfile *f, uint32_t nr_entries) { struct pack_header hdr; @@ -342,7 +449,7 @@ void finish_tmp_packfile(struct strbuf *name_buffer, struct pack_idx_option *pack_idx_opts, unsigned char hash[]) { - const char *idx_tmp_name; + const char *idx_tmp_name, *rev_tmp_name = NULL; int basename_len = name_buffer->len; if (adjust_shared_perm(pack_tmp_name)) @@ -353,6 +460,9 @@ void finish_tmp_packfile(struct strbuf *name_buffer, if (adjust_shared_perm(idx_tmp_name)) die_errno("unable to make temporary index file readable"); + rev_tmp_name = write_rev_file(NULL, written_list, nr_written, hash, + pack_idx_opts->flags); + strbuf_addf(name_buffer, "%s.pack", hash_to_hex(hash)); if (rename(pack_tmp_name, name_buffer->buf)) @@ -366,6 +476,14 @@ void finish_tmp_packfile(struct strbuf *name_buffer, strbuf_setlen(name_buffer, basename_len); + if (rev_tmp_name) { + strbuf_addf(name_buffer, "%s.rev", hash_to_hex(hash)); + if (rename(rev_tmp_name, name_buffer->buf)) + die_errno("unable to rename temporary reverse-index file"); + } + + strbuf_setlen(name_buffer, basename_len); + free((void *)idx_tmp_name); } @@ -42,6 +42,8 @@ struct pack_idx_option { /* flag bits */ #define WRITE_IDX_VERIFY 01 /* verify only, do not write the idx file */ #define WRITE_IDX_STRICT 02 +#define WRITE_REV 04 +#define WRITE_REV_VERIFY 010 uint32_t version; uint32_t off32_limit; @@ -91,6 +93,8 @@ struct ref; void write_promisor_file(const char *promisor_name, struct ref **sought, int nr_sought); +const char *write_rev_file(const char *rev_name, struct pack_idx_entry **objects, uint32_t nr_objects, const unsigned char *hash, unsigned flags); + /* * The "hdr" output buffer should be at least this big, which will handle sizes * up to 2^67. diff --git a/packfile.c b/packfile.c index 4b938b4372..1fec12ac5f 100644 --- a/packfile.c +++ b/packfile.c @@ -324,11 +324,21 @@ void close_pack_index(struct packed_git *p) } } +void close_pack_revindex(struct packed_git *p) { + if (!p->revindex_map) + return; + + munmap((void *)p->revindex_map, p->revindex_size); + p->revindex_map = NULL; + p->revindex_data = NULL; +} + void close_pack(struct packed_git *p) { close_pack_windows(p); close_pack_fd(p); close_pack_index(p); + close_pack_revindex(p); } void close_object_store(struct raw_object_store *o) @@ -351,7 +361,7 @@ void close_object_store(struct raw_object_store *o) void unlink_pack_path(const char *pack_name, int force_delete) { - static const char *exts[] = {".pack", ".idx", ".keep", ".bitmap", ".promisor"}; + static const char *exts[] = {".pack", ".idx", ".rev", ".keep", ".bitmap", ".promisor"}; int i; struct strbuf buf = STRBUF_INIT; size_t plen; @@ -853,6 +863,7 @@ static void prepare_pack(const char *full_name, size_t full_name_len, if (!strcmp(file_name, "multi-pack-index")) return; if (ends_with(file_name, ".idx") || + ends_with(file_name, ".rev") || ends_with(file_name, ".pack") || ends_with(file_name, ".bitmap") || ends_with(file_name, ".keep") || diff --git a/packfile.h b/packfile.h index a58fc738e0..4cfec9e8d3 100644 --- a/packfile.h +++ b/packfile.h @@ -90,6 +90,7 @@ uint32_t get_pack_fanout(struct packed_git *p, uint32_t value); unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned long *); void close_pack_windows(struct packed_git *); +void close_pack_revindex(struct packed_git *); void close_pack(struct packed_git *); void close_object_store(struct raw_object_store *o); void unuse_pack(struct pack_window **); diff --git a/parse-options.c b/parse-options.c index f0507432ee..fbea16eaf5 100644 --- a/parse-options.c +++ b/parse-options.c @@ -869,7 +869,7 @@ int parse_options(int argc, const char **argv, const char *prefix, usage_with_options(usagestr, options); } - precompose_argv(argc, argv); + precompose_argv_prefix(argc, argv, NULL); free(real_options); free(ctx.alias_groups); return parse_options_end(&ctx); @@ -284,23 +284,5 @@ Perl: Testing marked strings ---------------------- -Even if you've correctly marked porcelain strings for translation -something in the test suite might still depend on the US English -version of the strings, e.g. to grep some error message or other -output. - -To smoke out issues like these, Git tested with a translation mode that -emits gibberish on every call to gettext. To use it run the test suite -with it, e.g.: - - cd t && GIT_TEST_GETTEXT_POISON=true prove -j 9 ./t[0-9]*.sh - -If tests break with it you should inspect them manually and see if -what you're translating is sane, i.e. that you're not translating -plumbing output. - -If not you should replace calls to grep with test_i18ngrep, or -test_cmp calls with test_i18ncmp. If that's not enough you can skip -the whole test by making it depend on the C_LOCALE_OUTPUT -prerequisite. See existing test files with this prerequisite for -examples. +Git's tests are run under LANG=C LC_ALL=C. So the tests do not need be +changed to account for translations as they're added. @@ -783,6 +783,7 @@ enum trunc_type { }; struct format_commit_context { + struct repository *repository; const struct commit *commit; const struct pretty_print_context *pretty_ctx; unsigned commit_header_parsed:1; @@ -1373,10 +1374,13 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */ return 2; } - /* For the rest we have to parse the commit header. */ - if (!c->commit_header_parsed) + if (!c->commit_header_parsed) { + msg = c->message = + repo_logmsg_reencode(c->repository, commit, + &c->commit_encoding, "UTF-8"); parse_commit_header(c); + } switch (placeholder[0]) { case 'a': /* author ... */ @@ -1667,6 +1671,7 @@ void repo_format_commit_message(struct repository *r, const struct pretty_print_context *pretty_ctx) { struct format_commit_context context = { + .repository = r, .commit = commit, .pretty_ctx = pretty_ctx, .wrap_start = sb->len @@ -1674,18 +1679,14 @@ void repo_format_commit_message(struct repository *r, const char *output_enc = pretty_ctx->output_encoding; const char *utf8 = "UTF-8"; - /* - * convert a commit message to UTF-8 first - * as far as 'format_commit_item' assumes it in UTF-8 - */ - context.message = repo_logmsg_reencode(r, commit, - &context.commit_encoding, - utf8); - strbuf_expand(sb, format, format_commit_item, &context); rewrap_message_tail(sb, &context, 0, 0, 0); - /* then convert a commit message to an actual output encoding */ + /* + * Convert output to an actual output encoding; note that + * format_commit_item() will always use UTF-8, so we don't + * have to bother if that's what the output wants. + */ if (output_enc) { if (same_encoding(utf8, output_enc)) output_enc = NULL; diff --git a/range-diff.c b/range-diff.c index b9950f10c8..a3cc7c94a3 100644 --- a/range-diff.c +++ b/range-diff.c @@ -11,6 +11,7 @@ #include "pretty.h" #include "userdiff.h" #include "apply.h" +#include "revision.h" struct patch_util { /* For the search for an exact match */ @@ -80,6 +81,8 @@ static int read_patches(const char *range, struct string_list *list, finish_command(&cp); return -1; } + if (finish_command(&cp)) + return -1; line = contents.buf; size = contents.len; @@ -97,10 +100,10 @@ static int read_patches(const char *range, struct string_list *list, if (get_oid(p, &util->oid)) { error(_("could not parse commit '%s'"), p); free(util); + free(current_filename); string_list_clear(list, 1); strbuf_release(&buf); strbuf_release(&contents); - finish_command(&cp); return -1; } util->matching = -1; @@ -112,10 +115,10 @@ static int read_patches(const char *range, struct string_list *list, error(_("could not parse first line of `log` output: " "did not start with 'commit ': '%s'"), line); + free(current_filename); string_list_clear(list, 1); strbuf_release(&buf); strbuf_release(&contents); - finish_command(&cp); return -1; } @@ -133,9 +136,16 @@ static int read_patches(const char *range, struct string_list *list, orig_len = len; len = parse_git_diff_header(&root, &linenr, 0, line, len, size, &patch); - if (len < 0) - die(_("could not parse git header '%.*s'"), - orig_len, line); + if (len < 0) { + error(_("could not parse git header '%.*s'"), + orig_len, line); + free(util); + free(current_filename); + string_list_clear(list, 1); + strbuf_release(&buf); + strbuf_release(&contents); + return -1; + } strbuf_addstr(&buf, " ## "); if (patch.is_new > 0) strbuf_addf(&buf, "%s (new)", patch.new_name); @@ -218,9 +228,6 @@ static int read_patches(const char *range, struct string_list *list, strbuf_release(&buf); free(current_filename); - if (finish_command(&cp)) - return -1; - return 0; } @@ -458,12 +465,35 @@ static void patch_diff(const char *a, const char *b, diff_flush(diffopt); } +static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data) +{ + return data; +} + static void output(struct string_list *a, struct string_list *b, - struct diff_options *diffopt) + struct range_diff_options *range_diff_opts) { struct strbuf buf = STRBUF_INIT, dashes = STRBUF_INIT; int patch_no_width = decimal_width(1 + (a->nr > b->nr ? a->nr : b->nr)); int i = 0, j = 0; + struct diff_options opts; + struct strbuf indent = STRBUF_INIT; + + if (range_diff_opts->diffopt) + memcpy(&opts, range_diff_opts->diffopt, sizeof(opts)); + else + diff_setup(&opts); + + if (!opts.output_format) + opts.output_format = DIFF_FORMAT_PATCH; + opts.flags.suppress_diff_headers = 1; + opts.flags.dual_color_diffed_diffs = + range_diff_opts->dual_color; + opts.flags.suppress_hunk_header_line_count = 1; + opts.output_prefix = output_prefix_cb; + strbuf_addstr(&indent, " "); + opts.output_prefix_data = &indent; + diff_setup_done(&opts); /* * We assume the user is really more interested in the second argument @@ -484,7 +514,8 @@ static void output(struct string_list *a, struct string_list *b, /* Show unmatched LHS commit whose predecessors were shown. */ if (i < a->nr && a_util->matching < 0) { - output_pair_header(diffopt, patch_no_width, + if (!range_diff_opts->right_only) + output_pair_header(&opts, patch_no_width, &buf, &dashes, a_util, NULL); i++; continue; @@ -492,7 +523,8 @@ static void output(struct string_list *a, struct string_list *b, /* Show unmatched RHS commits. */ while (j < b->nr && b_util->matching < 0) { - output_pair_header(diffopt, patch_no_width, + if (!range_diff_opts->left_only) + output_pair_header(&opts, patch_no_width, &buf, &dashes, NULL, b_util); b_util = ++j < b->nr ? b->items[j].util : NULL; } @@ -500,63 +532,41 @@ static void output(struct string_list *a, struct string_list *b, /* Show matching LHS/RHS pair. */ if (j < b->nr) { a_util = a->items[b_util->matching].util; - output_pair_header(diffopt, patch_no_width, + output_pair_header(&opts, patch_no_width, &buf, &dashes, a_util, b_util); - if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT)) + if (!(opts.output_format & DIFF_FORMAT_NO_OUTPUT)) patch_diff(a->items[b_util->matching].string, - b->items[j].string, diffopt); + b->items[j].string, &opts); a_util->shown = 1; j++; } } strbuf_release(&buf); strbuf_release(&dashes); -} - -static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data) -{ - return data; + strbuf_release(&indent); } int show_range_diff(const char *range1, const char *range2, - int creation_factor, int dual_color, - const struct diff_options *diffopt, - const struct strvec *other_arg) + struct range_diff_options *range_diff_opts) { int res = 0; struct string_list branch1 = STRING_LIST_INIT_DUP; struct string_list branch2 = STRING_LIST_INIT_DUP; - if (read_patches(range1, &branch1, other_arg)) + if (range_diff_opts->left_only && range_diff_opts->right_only) + res = error(_("--left-only and --right-only are mutually exclusive")); + + if (!res && read_patches(range1, &branch1, range_diff_opts->other_arg)) res = error(_("could not parse log for '%s'"), range1); - if (!res && read_patches(range2, &branch2, other_arg)) + if (!res && read_patches(range2, &branch2, range_diff_opts->other_arg)) res = error(_("could not parse log for '%s'"), range2); if (!res) { - struct diff_options opts; - struct strbuf indent = STRBUF_INIT; - - if (diffopt) - memcpy(&opts, diffopt, sizeof(opts)); - else - diff_setup(&opts); - - if (!opts.output_format) - opts.output_format = DIFF_FORMAT_PATCH; - opts.flags.suppress_diff_headers = 1; - opts.flags.dual_color_diffed_diffs = dual_color; - opts.flags.suppress_hunk_header_line_count = 1; - opts.output_prefix = output_prefix_cb; - strbuf_addstr(&indent, " "); - opts.output_prefix_data = &indent; - diff_setup_done(&opts); - find_exact_matches(&branch1, &branch2); - get_correspondences(&branch1, &branch2, creation_factor); - output(&branch1, &branch2, &opts); - - strbuf_release(&indent); + get_correspondences(&branch1, &branch2, + range_diff_opts->creation_factor); + output(&branch1, &branch2, range_diff_opts); } string_list_clear(&branch1, 1); @@ -564,3 +574,31 @@ int show_range_diff(const char *range1, const char *range2, return res; } + +int is_range_diff_range(const char *arg) +{ + char *copy = xstrdup(arg); /* setup_revisions() modifies it */ + const char *argv[] = { "", copy, "--", NULL }; + int i, positive = 0, negative = 0; + struct rev_info revs; + + init_revisions(&revs, NULL); + if (setup_revisions(3, argv, &revs, NULL) == 1) { + for (i = 0; i < revs.pending.nr; i++) + if (revs.pending.objects[i].item->flags & UNINTERESTING) + negative++; + else + positive++; + for (i = 0; i < revs.pending.nr; i++) { + struct object *obj = revs.pending.objects[i].item; + + if (obj->type == OBJ_COMMIT) + clear_commit_marks((struct commit *)obj, + ALL_REV_FLAGS); + } + } + + free(copy); + object_array_clear(&revs.pending); + return negative > 0 && positive > 0; +} diff --git a/range-diff.h b/range-diff.h index 583ced2e8e..04ffe217be 100644 --- a/range-diff.h +++ b/range-diff.h @@ -6,14 +6,25 @@ #define RANGE_DIFF_CREATION_FACTOR_DEFAULT 60 +struct range_diff_options { + int creation_factor; + unsigned dual_color:1; + unsigned left_only:1, right_only:1; + const struct diff_options *diffopt; /* may be NULL */ + const struct strvec *other_arg; /* may be NULL */ +}; + /* - * Compare series of commits in RANGE1 and RANGE2, and emit to the - * standard output. NULL can be passed to DIFFOPT to use the built-in - * default. + * Compare series of commits in `range1` and `range2`, and emit to the + * standard output. */ int show_range_diff(const char *range1, const char *range2, - int creation_factor, int dual_color, - const struct diff_options *diffopt, - const struct strvec *other_arg); + struct range_diff_options *opts); + +/* + * Determine whether the given argument is usable as a range argument of `git + * range-diff`, e.g. A..B. + */ +int is_range_diff_range(const char *arg); #endif diff --git a/ref-filter.c b/ref-filter.c index ee337df232..bade6528ee 100644 --- a/ref-filter.c +++ b/ref-filter.c @@ -1210,12 +1210,20 @@ static void grab_person(const char *who, struct atom_value *val, int deref, void } static void find_subpos(const char *buf, - const char **sub, unsigned long *sublen, - const char **body, unsigned long *bodylen, - unsigned long *nonsiglen, - const char **sig, unsigned long *siglen) + const char **sub, size_t *sublen, + const char **body, size_t *bodylen, + size_t *nonsiglen, + const char **sig, size_t *siglen) { + struct strbuf payload = STRBUF_INIT; + struct strbuf signature = STRBUF_INIT; const char *eol; + const char *end = buf + strlen(buf); + const char *sigstart; + + /* parse signature first; we might not even have a subject line */ + parse_signature(buf, end - buf, &payload, &signature); + /* skip past header until we hit empty line */ while (*buf && *buf != '\n') { eol = strchrnul(buf, '\n'); @@ -1226,16 +1234,14 @@ static void find_subpos(const char *buf, /* skip any empty lines */ while (*buf == '\n') buf++; - - /* parse signature first; we might not even have a subject line */ - *sig = buf + parse_signature(buf, strlen(buf)); - *siglen = strlen(*sig); + *sig = strbuf_detach(&signature, siglen); + sigstart = buf + parse_signed_buffer(buf, strlen(buf)); /* subject is first non-empty line */ *sub = buf; /* subject goes to first empty line before signature begins */ if ((eol = strstr(*sub, "\n\n"))) { - eol = eol < *sig ? eol : *sig; + eol = eol < sigstart ? eol : sigstart; /* check if message uses CRLF */ } else if (! (eol = strstr(*sub, "\r\n\r\n"))) { /* treat whole message as subject */ @@ -1253,7 +1259,7 @@ static void find_subpos(const char *buf, buf++; *body = buf; *bodylen = strlen(buf); - *nonsiglen = *sig - buf; + *nonsiglen = sigstart - buf; } /* @@ -1285,12 +1291,13 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf) { int i; const char *subpos = NULL, *bodypos = NULL, *sigpos = NULL; - unsigned long sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0; + size_t sublen = 0, bodylen = 0, nonsiglen = 0, siglen = 0; for (i = 0; i < used_atom_cnt; i++) { struct used_atom *atom = &used_atom[i]; const char *name = atom->name; struct atom_value *v = &val[i]; + if (!!deref != (*name == '*')) continue; if (deref) @@ -1322,7 +1329,7 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf) v->s = xmemdupz(sigpos, siglen); else if (atom->u.contents.option == C_LINES) { struct strbuf s = STRBUF_INIT; - const char *contents_end = bodylen + bodypos - siglen; + const char *contents_end = bodypos + nonsiglen; /* Size is the length of the message after removing the signature */ append_lines(&s, subpos, contents_end - subpos, atom->u.contents.nlines); @@ -1336,7 +1343,9 @@ static void grab_sub_body_contents(struct atom_value *val, int deref, void *buf) v->s = strbuf_detach(&s, NULL); } else if (atom->u.contents.option == C_BARE) v->s = xstrdup(subpos); + } + free((void *)sigpos); } /* @@ -1920,64 +1929,6 @@ static int filter_pattern_match(struct ref_filter *filter, const char *refname) return match_pattern(filter, refname); } -static int qsort_strcmp(const void *va, const void *vb) -{ - const char *a = *(const char **)va; - const char *b = *(const char **)vb; - - return strcmp(a, b); -} - -static void find_longest_prefixes_1(struct string_list *out, - struct strbuf *prefix, - const char **patterns, size_t nr) -{ - size_t i; - - for (i = 0; i < nr; i++) { - char c = patterns[i][prefix->len]; - if (!c || is_glob_special(c)) { - string_list_append(out, prefix->buf); - return; - } - } - - i = 0; - while (i < nr) { - size_t end; - - /* - * Set "end" to the index of the element _after_ the last one - * in our group. - */ - for (end = i + 1; end < nr; end++) { - if (patterns[i][prefix->len] != patterns[end][prefix->len]) - break; - } - - strbuf_addch(prefix, patterns[i][prefix->len]); - find_longest_prefixes_1(out, prefix, patterns + i, end - i); - strbuf_setlen(prefix, prefix->len - 1); - - i = end; - } -} - -static void find_longest_prefixes(struct string_list *out, - const char **patterns) -{ - struct strvec sorted = STRVEC_INIT; - struct strbuf prefix = STRBUF_INIT; - - strvec_pushv(&sorted, patterns); - QSORT(sorted.v, sorted.nr, qsort_strcmp); - - find_longest_prefixes_1(out, &prefix, sorted.v, sorted.nr); - - strvec_clear(&sorted); - strbuf_release(&prefix); -} - /* * This is the same as for_each_fullref_in(), but it tries to iterate * only over the patterns we'll care about. Note that it _doesn't_ do a full @@ -1988,10 +1939,6 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter, void *cb_data, int broken) { - struct string_list prefixes = STRING_LIST_INIT_DUP; - struct string_list_item *prefix; - int ret; - if (!filter->match_as_path) { /* * in this case, the patterns are applied after @@ -2015,16 +1962,8 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter, return for_each_fullref_in("", cb, cb_data, broken); } - find_longest_prefixes(&prefixes, filter->name_patterns); - - for_each_string_list_item(prefix, &prefixes) { - ret = for_each_fullref_in(prefix->string, cb, cb_data, broken); - if (ret) - break; - } - - string_list_clear(&prefixes, 0); - return ret; + return for_each_fullref_in_prefixes(NULL, filter->name_patterns, + cb, cb_data, broken); } /* @@ -1564,6 +1564,93 @@ int for_each_rawref(each_ref_fn fn, void *cb_data) return refs_for_each_rawref(get_main_ref_store(the_repository), fn, cb_data); } +static int qsort_strcmp(const void *va, const void *vb) +{ + const char *a = *(const char **)va; + const char *b = *(const char **)vb; + + return strcmp(a, b); +} + +static void find_longest_prefixes_1(struct string_list *out, + struct strbuf *prefix, + const char **patterns, size_t nr) +{ + size_t i; + + for (i = 0; i < nr; i++) { + char c = patterns[i][prefix->len]; + if (!c || is_glob_special(c)) { + string_list_append(out, prefix->buf); + return; + } + } + + i = 0; + while (i < nr) { + size_t end; + + /* + * Set "end" to the index of the element _after_ the last one + * in our group. + */ + for (end = i + 1; end < nr; end++) { + if (patterns[i][prefix->len] != patterns[end][prefix->len]) + break; + } + + strbuf_addch(prefix, patterns[i][prefix->len]); + find_longest_prefixes_1(out, prefix, patterns + i, end - i); + strbuf_setlen(prefix, prefix->len - 1); + + i = end; + } +} + +static void find_longest_prefixes(struct string_list *out, + const char **patterns) +{ + struct strvec sorted = STRVEC_INIT; + struct strbuf prefix = STRBUF_INIT; + + strvec_pushv(&sorted, patterns); + QSORT(sorted.v, sorted.nr, qsort_strcmp); + + find_longest_prefixes_1(out, &prefix, sorted.v, sorted.nr); + + strvec_clear(&sorted); + strbuf_release(&prefix); +} + +int for_each_fullref_in_prefixes(const char *namespace, + const char **patterns, + each_ref_fn fn, void *cb_data, + unsigned int broken) +{ + struct string_list prefixes = STRING_LIST_INIT_DUP; + struct string_list_item *prefix; + struct strbuf buf = STRBUF_INIT; + int ret = 0, namespace_len; + + find_longest_prefixes(&prefixes, patterns); + + if (namespace) + strbuf_addstr(&buf, namespace); + namespace_len = buf.len; + + for_each_string_list_item(prefix, &prefixes) { + strbuf_addstr(&buf, prefix->string); + ret = for_each_fullref_in(buf.buf, fn, cb_data, broken); + if (ret) + break; + strbuf_setlen(&buf, namespace_len); + } + + string_list_clear(&prefixes, 0); + strbuf_release(&buf); + return ret; +} + static int refs_read_special_head(struct ref_store *ref_store, const char *refname, struct object_id *oid, struct strbuf *referent, unsigned int *type) @@ -1916,31 +2003,14 @@ int refs_pack_refs(struct ref_store *refs, unsigned int flags) return refs->be->pack_refs(refs, flags); } -int refs_peel_ref(struct ref_store *refs, const char *refname, - struct object_id *oid) +int peel_iterated_oid(const struct object_id *base, struct object_id *peeled) { - int flag; - struct object_id base; - - if (current_ref_iter && current_ref_iter->refname == refname) { - struct object_id peeled; - - if (ref_iterator_peel(current_ref_iter, &peeled)) - return -1; - oidcpy(oid, &peeled); - return 0; - } - - if (refs_read_ref_full(refs, refname, - RESOLVE_REF_READING, &base, &flag)) - return -1; + if (current_ref_iter && + (current_ref_iter->oid == base || + oideq(current_ref_iter->oid, base))) + return ref_iterator_peel(current_ref_iter, peeled); - return peel_object(&base, oid); -} - -int peel_ref(const char *refname, struct object_id *oid) -{ - return refs_peel_ref(get_main_ref_store(the_repository), refname, oid); + return peel_object(base, peeled); } int refs_create_symref(struct ref_store *refs, @@ -118,16 +118,16 @@ int is_branch(const char *refname); int refs_init_db(struct strbuf *err); /* - * If refname is a non-symbolic reference that refers to a tag object, - * and the tag can be (recursively) dereferenced to a non-tag object, - * store the object ID of the referred-to object to oid and return 0. - * If any of these conditions are not met, return a non-zero value. - * Symbolic references are considered unpeelable, even if they - * ultimately resolve to a peelable tag. + * Return the peeled value of the oid currently being iterated via + * for_each_ref(), etc. This is equivalent to calling: + * + * peel_object(oid, &peeled); + * + * with the "oid" value given to the each_ref_fn callback, except + * that some ref storage may be able to answer the query without + * actually loading the object in memory. */ -int refs_peel_ref(struct ref_store *refs, const char *refname, - struct object_id *oid); -int peel_ref(const char *refname, struct object_id *oid); +int peel_iterated_oid(const struct object_id *base, struct object_id *peeled); /** * Resolve refname in the nested "gitlink" repository in the specified @@ -348,6 +348,15 @@ int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data, unsigned int broken); /** + * iterate all refs in "patterns" by partitioning patterns into disjoint sets + * and iterating the longest-common prefix of each set. + * + * callers should be prepared to ignore references that they did not ask for. + */ +int for_each_fullref_in_prefixes(const char *namespace, const char **patterns, + each_ref_fn fn, void *cb_data, + unsigned int broken); +/** * iterate refs from the respective area. */ int for_each_tag_ref(each_ref_fn fn, void *cb_data); @@ -6,6 +6,8 @@ #include "hashmap.h" #include "refspec.h" +struct transport_ls_refs_options; + /** * The API gives access to the configuration related to remotes. It handles * all three configuration mechanisms historically and currently used by Git, @@ -196,7 +198,7 @@ struct ref **get_remote_heads(struct packet_reader *reader, /* Used for protocol v2 in order to retrieve refs from a remote */ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader, struct ref **list, int for_push, - const struct strvec *ref_prefixes, + struct transport_ls_refs_options *transport_options, const struct string_list *server_options, int stateless_rpc); diff --git a/repository.c b/repository.c index a4174ddb06..c98298acd0 100644 --- a/repository.c +++ b/repository.c @@ -264,6 +264,12 @@ int repo_read_index(struct repository *repo) if (!repo->index) repo->index = xcalloc(1, sizeof(*repo->index)); + /* Complete the double-reference */ + if (!repo->index->repo) + repo->index->repo = repo; + else if (repo->index->repo != repo) + BUG("repo's index should point back at itself"); + return read_index_from(repo->index, repo->index_file, repo->gitdir); } @@ -11,6 +11,7 @@ #include "pathspec.h" #include "object-store.h" #include "hash-lookup.h" +#include "strmap.h" #define RESOLVED 0 #define PUNTED 1 @@ -23,26 +24,27 @@ static int rerere_enabled = -1; /* automatically update cleanly resolved paths to the index */ static int rerere_autoupdate; -static int rerere_dir_nr; -static int rerere_dir_alloc; - #define RR_HAS_POSTIMAGE 1 #define RR_HAS_PREIMAGE 2 -static struct rerere_dir { - unsigned char hash[GIT_MAX_HEXSZ]; +struct rerere_dir { int status_alloc, status_nr; unsigned char *status; -} **rerere_dir; + char name[FLEX_ARRAY]; +}; + +static struct strmap rerere_dirs = STRMAP_INIT; static void free_rerere_dirs(void) { - int i; - for (i = 0; i < rerere_dir_nr; i++) { - free(rerere_dir[i]->status); - free(rerere_dir[i]); + struct hashmap_iter iter; + struct strmap_entry *ent; + + strmap_for_each_entry(&rerere_dirs, &iter, ent) { + struct rerere_dir *rr_dir = ent->value; + free(rr_dir->status); + free(rr_dir); } - FREE_AND_NULL(rerere_dir); - rerere_dir_nr = rerere_dir_alloc = 0; + strmap_clear(&rerere_dirs, 0); } static void free_rerere_id(struct string_list_item *item) @@ -52,7 +54,7 @@ static void free_rerere_id(struct string_list_item *item) static const char *rerere_id_hex(const struct rerere_id *id) { - return hash_to_hex(id->collection->hash); + return id->collection->name; } static void fit_variant(struct rerere_dir *rr_dir, int variant) @@ -115,7 +117,7 @@ static int is_rr_file(const char *name, const char *filename, int *variant) static void scan_rerere_dir(struct rerere_dir *rr_dir) { struct dirent *de; - DIR *dir = opendir(git_path("rr-cache/%s", hash_to_hex(rr_dir->hash))); + DIR *dir = opendir(git_path("rr-cache/%s", rr_dir->name)); if (!dir) return; @@ -133,39 +135,21 @@ static void scan_rerere_dir(struct rerere_dir *rr_dir) closedir(dir); } -static const unsigned char *rerere_dir_hash(size_t i, void *table) -{ - struct rerere_dir **rr_dir = table; - return rr_dir[i]->hash; -} - static struct rerere_dir *find_rerere_dir(const char *hex) { - unsigned char hash[GIT_MAX_RAWSZ]; struct rerere_dir *rr_dir; - int pos; - - if (get_sha1_hex(hex, hash)) - return NULL; /* BUG */ - pos = hash_pos(hash, rerere_dir, rerere_dir_nr, rerere_dir_hash); - if (pos < 0) { - rr_dir = xmalloc(sizeof(*rr_dir)); - hashcpy(rr_dir->hash, hash); + + rr_dir = strmap_get(&rerere_dirs, hex); + if (!rr_dir) { + FLEX_ALLOC_STR(rr_dir, name, hex); rr_dir->status = NULL; rr_dir->status_nr = 0; rr_dir->status_alloc = 0; - pos = -1 - pos; - - /* Make sure the array is big enough ... */ - ALLOC_GROW(rerere_dir, rerere_dir_nr + 1, rerere_dir_alloc); - /* ... and add it in. */ - rerere_dir_nr++; - MOVE_ARRAY(rerere_dir + pos + 1, rerere_dir + pos, - rerere_dir_nr - pos - 1); - rerere_dir[pos] = rr_dir; + strmap_put(&rerere_dirs, hex, rr_dir); + scan_rerere_dir(rr_dir); } - return rerere_dir[pos]; + return rr_dir; } static int has_rerere_resolution(const struct rerere_id *id) @@ -1178,6 +1162,14 @@ static void prune_one(struct rerere_id *id, unlink_rr_item(id); } +/* Does the basename in "path" look plausibly like an rr-cache entry? */ +static int is_rr_cache_dirname(const char *path) +{ + struct object_id oid; + const char *end; + return !parse_oid_hex(path, &oid, &end) && !*end; +} + void rerere_gc(struct repository *r, struct string_list *rr) { struct string_list to_remove = STRING_LIST_INIT_DUP; @@ -1205,10 +1197,11 @@ void rerere_gc(struct repository *r, struct string_list *rr) if (is_dot_or_dotdot(e->d_name)) continue; - rr_dir = find_rerere_dir(e->d_name); - if (!rr_dir) + if (!is_rr_cache_dirname(e->d_name)) continue; /* or should we remove e->d_name? */ + rr_dir = find_rerere_dir(e->d_name); + now_empty = 1; for (id.variant = 0, id.collection = rr_dir; id.variant < id.collection->status_nr; diff --git a/revision.c b/revision.c index 0b5c723140..b78733f508 100644 --- a/revision.c +++ b/revision.c @@ -5,6 +5,7 @@ #include "tree.h" #include "commit.h" #include "diff.h" +#include "diff-merges.h" #include "refs.h" #include "revision.h" #include "repository.h" @@ -1808,7 +1809,6 @@ void repo_init_revisions(struct repository *r, revs->repo = r; revs->abbrev = DEFAULT_ABBREV; - revs->ignore_merges = -1; revs->simplify_history = 1; revs->pruning.repo = r; revs->pruning.flags.recursive = 1; @@ -2343,34 +2343,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg revs->diff = 1; revs->diffopt.flags.recursive = 1; revs->diffopt.flags.tree_in_recursive = 1; - } else if (!strcmp(arg, "-m")) { - /* - * To "diff-index", "-m" means "match missing", and to the "log" - * family of commands, it means "show full diff for merges". Set - * both fields appropriately. - */ - revs->ignore_merges = 0; - revs->match_missing = 1; - } else if ((argcount = parse_long_opt("diff-merges", argv, &optarg))) { - if (!strcmp(optarg, "off")) { - revs->ignore_merges = 1; - } else { - die(_("unknown value for --diff-merges: %s"), optarg); - } + } else if ((argcount = diff_merges_parse_opts(revs, argv))) { return argcount; - } else if (!strcmp(arg, "--no-diff-merges")) { - revs->ignore_merges = 1; - } else if (!strcmp(arg, "-c")) { - revs->diff = 1; - revs->dense_combined_merges = 0; - revs->combine_merges = 1; - } else if (!strcmp(arg, "--combined-all-paths")) { - revs->diff = 1; - revs->combined_all_paths = 1; - } else if (!strcmp(arg, "--cc")) { - revs->diff = 1; - revs->dense_combined_merges = 1; - revs->combine_merges = 1; } else if (!strcmp(arg, "-v")) { revs->verbose_header = 1; } else if (!strcmp(arg, "--pretty")) { @@ -2491,8 +2465,6 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg } else if ((argcount = parse_long_opt("grep", argv, &optarg))) { add_message_grep(revs, optarg); return argcount; - } else if (!strcmp(arg, "--grep-debug")) { - revs->grep_filter.debug = 1; } else if (!strcmp(arg, "--basic-regexp")) { revs->grep_filter.pattern_type_option = GREP_PATTERN_TYPE_BRE; } else if (!strcmp(arg, "--extended-regexp") || !strcmp(arg, "-E")) { @@ -2867,12 +2839,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s copy_pathspec(&revs->diffopt.pathspec, &revs->prune_data); } - if (revs->combine_merges && revs->ignore_merges < 0) - revs->ignore_merges = 0; - if (revs->ignore_merges < 0) - revs->ignore_merges = 1; - if (revs->combined_all_paths && !revs->combine_merges) - die("--combined-all-paths makes no sense without -c or --cc"); + + diff_merges_setup_revs(revs); revs->diffopt.abbrev = revs->abbrev; @@ -3302,7 +3270,7 @@ define_commit_slab(indegree_slab, int); define_commit_slab(author_date_slab, timestamp_t); struct topo_walk_info { - uint32_t min_generation; + timestamp_t min_generation; struct prio_queue explore_queue; struct prio_queue indegree_queue; struct prio_queue topo_queue; @@ -3370,7 +3338,7 @@ static void explore_walk_step(struct rev_info *revs) } static void explore_to_depth(struct rev_info *revs, - uint32_t gen_cutoff) + timestamp_t gen_cutoff) { struct topo_walk_info *info = revs->topo_walk_info; struct commit *c; @@ -3399,6 +3367,9 @@ static void indegree_walk_step(struct rev_info *revs) struct commit *parent = p->item; int *pi = indegree_slab_at(&info->indegree, parent); + if (repo_parse_commit_gently(revs->repo, parent, 1) < 0) + return; + if (*pi) (*pi)++; else @@ -3412,7 +3383,7 @@ static void indegree_walk_step(struct rev_info *revs) } static void compute_indegrees_to_depth(struct rev_info *revs, - uint32_t gen_cutoff) + timestamp_t gen_cutoff) { struct topo_walk_info *info = revs->topo_walk_info; struct commit *c; @@ -3470,7 +3441,7 @@ static void init_topo_walk(struct rev_info *revs) info->min_generation = GENERATION_NUMBER_INFINITY; for (list = revs->commits; list; list = list->next) { struct commit *c = list->item; - uint32_t generation; + timestamp_t generation; if (repo_parse_commit_gently(revs->repo, c, 1)) continue; @@ -3538,7 +3509,7 @@ static void expand_topo_walk(struct rev_info *revs, struct commit *commit) for (p = commit->parents; p; p = p->next) { struct commit *parent = p->item; int *pi; - uint32_t generation; + timestamp_t generation; if (parent->object.flags & UNINTERESTING) continue; diff --git a/revision.h b/revision.h index 086ff10280..e6be3c845e 100644 --- a/revision.h +++ b/revision.h @@ -191,11 +191,16 @@ struct rev_info { match_missing:1, no_commit_id:1, verbose_header:1, + always_show_header:1, + /* Diff-merge flags */ + explicit_diff_merges: 1, + merges_need_diff: 1, + separate_merges: 1, combine_merges:1, combined_all_paths:1, + combined_imply_patch:1, dense_combined_merges:1, - always_show_header:1; - int ignore_merges:2; + first_parent_merges:1; /* Format info */ int show_notes; diff --git a/run-command.h b/run-command.h index 6472b38bde..d08414a92e 100644 --- a/run-command.h +++ b/run-command.h @@ -126,8 +126,15 @@ struct child_process { */ unsigned silent_exec_failure:1; - unsigned stdout_to_stderr:1; + /** + * Run the command from argv[0] using a shell (but note that we may + * still optimize out the shell call if the command contains no + * metacharacters). Note that further arguments to the command in + * argv[1], etc, do not need to be shell-quoted. + */ unsigned use_shell:1; + + unsigned stdout_to_stderr:1; unsigned clean_on_exit:1; unsigned wait_after_clean:1; void (*clean_on_exit_handler)(struct child_process *process); diff --git a/sequencer.c b/sequencer.c index 8909a46770..d2332d3e17 100644 --- a/sequencer.c +++ b/sequencer.c @@ -679,9 +679,6 @@ static int do_recursive_merge(struct repository *r, static struct object_id *get_cache_tree_oid(struct index_state *istate) { - if (!istate->cache_tree) - istate->cache_tree = cache_tree(); - if (!cache_tree_fully_valid(istate->cache_tree)) if (cache_tree_update(istate, 0)) { error(_("unable to update cache tree")); @@ -943,6 +940,7 @@ 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) { @@ -979,6 +977,9 @@ 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) && read_env_script(&cmd.env_array)) { @@ -1012,6 +1013,8 @@ 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) && @@ -1380,6 +1383,9 @@ 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; @@ -1454,6 +1460,8 @@ 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; @@ -2002,7 +2010,7 @@ 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 |= CLEANUP_MSG; + flags |= VERBATIM_MSG; msg_file = rebase_path_fixup_msg(); } else { const char *dest = git_path_squash_msg(r); @@ -73,7 +73,7 @@ struct protocol_capability { static struct protocol_capability capabilities[] = { { "agent", agent_advertise, NULL }, - { "ls-refs", always_advertise, ls_refs }, + { "ls-refs", ls_refs_advertise, ls_refs }, { "fetch", upload_pack_advertise, upload_pack_v2 }, { "server-option", always_advertise, NULL }, { "object-format", object_format_advertise, NULL }, @@ -41,7 +41,7 @@ int register_shallow(struct repository *r, const struct object_id *oid) int unregister_shallow(const struct object_id *oid) { - int pos = commit_graft_pos(the_repository, oid->hash); + int pos = commit_graft_pos(the_repository, oid); if (pos < 0) return -1; if (pos + 1 < the_repository->parsed_objects->grafts_nr) @@ -358,12 +358,6 @@ whether this mode is active, and e.g. skip some tests that are hard to refactor to deal with it. The "SYMLINKS" prerequisite is currently excluded as so much relies on it, but this might change in the future. -GIT_TEST_GETTEXT_POISON=<boolean> turns all strings marked for -translation into gibberish if true. Used for spotting those tests that -need to be marked with a C_LOCALE_OUTPUT prerequisite when adding more -strings for translation. See "Testing marked strings" in po/README for -details. - GIT_TEST_SPLIT_INDEX=<boolean> forces split-index mode on the whole test suite. Accept any boolean values that are accepted by git-config. @@ -393,6 +387,9 @@ GIT_TEST_COMMIT_GRAPH=<boolean>, when true, forces the commit-graph to be written after every 'git commit' command, and overrides the 'core.commitGraph' setting to true. +GIT_TEST_COMMIT_GRAPH_NO_GDAT=<boolean>, when true, forces the +commit-graph to be written without generation data chunk. + GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS=<boolean>, when true, forces commit-graph write to compute and write changed path Bloom filters for every 'git commit-graph write', as if the `--changed-paths` option was @@ -439,6 +436,9 @@ GIT_TEST_DEFAULT_HASH=<hash-algo> specifies which hash algorithm to use in the test scripts. Recognized values for <hash-algo> are "sha1" and "sha256". +GIT_TEST_WRITE_REV_INDEX=<boolean>, when true enables the +'pack.writeReverseIndex' setting. + Naming Tests ------------ @@ -1104,18 +1104,6 @@ use these, and "test_set_prereq" for how to define your own. Git was compiled with support for PCRE. Wrap any tests that use git-grep --perl-regexp or git-grep -P in these. - - LIBPCRE1 - - Git was compiled with PCRE v1 support via - USE_LIBPCRE1=YesPlease. Wrap any PCRE using tests that for some - reason need v1 of the PCRE library instead of v2 in these. - - - LIBPCRE2 - - Git was compiled with PCRE v2 support via - USE_LIBPCRE2=YesPlease. Wrap any PCRE using tests that for some - reason need v2 of the PCRE library instead of v1 in these. - - CASE_INSENSITIVE_FS Test is run on a case insensitive file system. diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh index ee5d2d4cf8..29ce89090d 100644 --- a/t/annotate-tests.sh +++ b/t/annotate-tests.sh @@ -483,12 +483,12 @@ test_expect_success 'setup -L :funcname with userdiff driver' ' echo "fortran-* diff=fortran" >.gitattributes && fortran_file=fortran-external-function && orig_file="$TEST_DIRECTORY/t4018/$fortran_file" && - cp $orig_file . && - git add $fortran_file && + cp "$orig_file" . && + git add "$fortran_file" && GIT_AUTHOR_NAME="A" GIT_AUTHOR_EMAIL="A@test.git" \ git commit -m "add fortran file" && - sed -e "s/ChangeMe/IWasChanged/" <"$orig_file" >$fortran_file && - git add $fortran_file && + sed -e "s/ChangeMe/IWasChanged/" <"$orig_file" >"$fortran_file" && + git add "$fortran_file" && GIT_AUTHOR_NAME="B" GIT_AUTHOR_EMAIL="B@test.git" \ git commit -m "change fortran file" ' diff --git a/t/helper/test-pcre2-config.c b/t/helper/test-pcre2-config.c new file mode 100644 index 0000000000..5258fdddba --- /dev/null +++ b/t/helper/test-pcre2-config.c @@ -0,0 +1,12 @@ +#include "test-tool.h" +#include "cache.h" +#include "grep.h" + +int cmd__pcre2_config(int argc, const char **argv) +{ + if (argc == 2 && !strcmp(argv[1], "has-PCRE2_MATCH_INVALID_UTF")) { + int value = PCRE2_MATCH_INVALID_UTF; + return !value; + } + return 1; +} diff --git a/t/helper/test-read-graph.c b/t/helper/test-read-graph.c index 5f585a1725..75927b2c81 100644 --- a/t/helper/test-read-graph.c +++ b/t/helper/test-read-graph.c @@ -33,6 +33,10 @@ int cmd__read_graph(int argc, const char **argv) printf(" oid_lookup"); if (graph->chunk_commit_data) printf(" commit_metadata"); + if (graph->chunk_generation_data) + printf(" generation_data"); + if (graph->chunk_generation_data_overflow) + printf(" generation_data_overflow"); if (graph->chunk_extra_edges) printf(" extra_edges"); if (graph->chunk_bloom_indexes) diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c index 759e69dc54..bba5f841c6 100644 --- a/t/helper/test-ref-store.c +++ b/t/helper/test-ref-store.c @@ -72,18 +72,6 @@ static int cmd_pack_refs(struct ref_store *refs, const char **argv) return refs_pack_refs(refs, flags); } -static int cmd_peel_ref(struct ref_store *refs, const char **argv) -{ - const char *refname = notnull(*argv++, "refname"); - struct object_id oid; - int ret; - - ret = refs_peel_ref(refs, refname, &oid); - if (!ret) - puts(oid_to_hex(&oid)); - return ret; -} - static int cmd_create_symref(struct ref_store *refs, const char **argv) { const char *refname = notnull(*argv++, "refname"); @@ -255,7 +243,6 @@ struct command { static struct command commands[] = { { "pack-refs", cmd_pack_refs }, - { "peel-ref", cmd_peel_ref }, { "create-symref", cmd_create_symref }, { "delete-refs", cmd_delete_refs }, { "rename-ref", cmd_rename_ref }, diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c index 9d6d14d929..f97cd9f48a 100644 --- a/t/helper/test-tool.c +++ b/t/helper/test-tool.c @@ -46,6 +46,7 @@ static struct test_cmd cmds[] = { { "parse-options", cmd__parse_options }, { "parse-pathspec-file", cmd__parse_pathspec_file }, { "path-utils", cmd__path_utils }, + { "pcre2-config", cmd__pcre2_config }, { "pkt-line", cmd__pkt_line }, { "prio-queue", cmd__prio_queue }, { "proc-receive", cmd__proc_receive}, diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h index a6470ff62c..28072c0ad5 100644 --- a/t/helper/test-tool.h +++ b/t/helper/test-tool.h @@ -35,6 +35,7 @@ int cmd__online_cpus(int argc, const char **argv); int cmd__parse_options(int argc, const char **argv); int cmd__parse_pathspec_file(int argc, const char** argv); int cmd__path_utils(int argc, const char **argv); +int cmd__pcre2_config(int argc, const char **argv); int cmd__pkt_line(int argc, const char **argv); int cmd__prio_queue(int argc, const char **argv); int cmd__proc_receive(int argc, const char **argv); diff --git a/t/helper/test-trace2.c b/t/helper/test-trace2.c index 823f33ceff..f93633f895 100644 --- a/t/helper/test-trace2.c +++ b/t/helper/test-trace2.c @@ -198,6 +198,14 @@ static int ut_006data(int argc, const char **argv) return 0; } +static int ut_007bug(int argc, const char **argv) +{ + /* + * Exercise BUG() to ensure that the message is printed to trace2. + */ + BUG("the bug message"); +} + /* * Usage: * test-tool trace2 <ut_name_1> <ut_usage_1> @@ -214,6 +222,7 @@ static struct unit_test ut_table[] = { { ut_004child, "004child", "[<child_command_line>]" }, { ut_005exec, "005exec", "<git_command_args>" }, { ut_006data, "006data", "[<category> <key> <value>]+" }, + { ut_007bug, "007bug", "" }, }; /* clang-format on */ diff --git a/t/lib-gettext.sh b/t/lib-gettext.sh index 2139b427ca..cc6bb2cdea 100644 --- a/t/lib-gettext.sh +++ b/t/lib-gettext.sh @@ -17,7 +17,7 @@ else . "$GIT_BUILD_DIR"/git-sh-i18n fi -if test_have_prereq GETTEXT && test_have_prereq C_LOCALE_OUTPUT +if test_have_prereq GETTEXT then # is_IS.UTF-8 on Solaris and FreeBSD, is_IS.utf8 on Debian is_IS_locale=$(locale -a 2>/dev/null | diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh index b72c051f47..172d7459ff 100644 --- a/t/lib-rebase.sh +++ b/t/lib-rebase.sh @@ -29,7 +29,6 @@ set_fake_editor () { */COMMIT_EDITMSG) test -z "$EXPECT_HEADER_COUNT" || test "$EXPECT_HEADER_COUNT" = "$(sed -n '1s/^# This is a combination of \(.*\) commits\./\1/p' < "$1")" || - test "# # GETTEXT POISON #" = "$(sed -n '1p' < "$1")" || exit test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1" test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1" diff --git a/t/perf/p4205-log-pretty-formats.sh b/t/perf/p4205-log-pretty-formats.sh index 7c26f4f337..609fecd65d 100755 --- a/t/perf/p4205-log-pretty-formats.sh +++ b/t/perf/p4205-log-pretty-formats.sh @@ -6,7 +6,7 @@ test_description='Tests the performance of various pretty format placeholders' test_perf_default_repo -for format in %H %h %T %t %P %p %h-%h-%h +for format in %H %h %T %t %P %p %h-%h-%h %an-%ae-%s do test_perf "log with $format" " git log --format=\"$format\" >/dev/null diff --git a/t/perf/p5303-many-packs.sh b/t/perf/p5303-many-packs.sh index f4c2ab0584..ce0c42cc9f 100755 --- a/t/perf/p5303-many-packs.sh +++ b/t/perf/p5303-many-packs.sh @@ -21,10 +21,14 @@ repack_into_n () { mkdir staging && git rev-list --first-parent HEAD | - sed -n '1~5p' | - head -n "$1" | - perl -e 'print reverse <>' \ - >pushes + perl -e ' + my $n = shift; + while (<>) { + last unless @commits < $n; + push @commits, $_ if $. % 5 == 1; + } + print reverse @commits; + ' "$1" >pushes # create base packfile head -n 1 pushes | diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index f4ba2e8c85..a6e570d674 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -135,32 +135,32 @@ check_sub_test_lib_test_err () { ) } -test_expect_success 'pretend we have a fully passing test suite' " - run_sub_test_lib_test full-pass '3 passing tests' <<-\\EOF && +test_expect_success 'pretend we have a fully passing test suite' ' + run_sub_test_lib_test full-pass "3 passing tests" <<-\EOF && for i in 1 2 3 do - test_expect_success \"passing test #\$i\" 'true' + test_expect_success "passing test #$i" "true" done test_done EOF - check_sub_test_lib_test full-pass <<-\\EOF + check_sub_test_lib_test full-pass <<-\EOF > ok 1 - passing test #1 > ok 2 - passing test #2 > ok 3 - passing test #3 > # passed all 3 test(s) > 1..3 EOF -" +' -test_expect_success 'pretend we have a partially passing test suite' " +test_expect_success 'pretend we have a partially passing test suite' ' run_sub_test_lib_test_err \ - partial-pass '2/3 tests passing' <<-\\EOF && - test_expect_success 'passing test #1' 'true' - test_expect_success 'failing test #2' 'false' - test_expect_success 'passing test #3' 'true' + partial-pass "2/3 tests passing" <<-\EOF && + test_expect_success "passing test #1" "true" + test_expect_success "failing test #2" "false" + test_expect_success "passing test #3" "true" test_done EOF - check_sub_test_lib_test partial-pass <<-\\EOF + check_sub_test_lib_test partial-pass <<-\EOF > ok 1 - passing test #1 > not ok 2 - failing test #2 # false @@ -168,44 +168,44 @@ test_expect_success 'pretend we have a partially passing test suite' " > # failed 1 among 3 test(s) > 1..3 EOF -" +' -test_expect_success 'pretend we have a known breakage' " - run_sub_test_lib_test failing-todo 'A failing TODO test' <<-\\EOF && - test_expect_success 'passing test' 'true' - test_expect_failure 'pretend we have a known breakage' 'false' +test_expect_success 'pretend we have a known breakage' ' + run_sub_test_lib_test failing-todo "A failing TODO test" <<-\EOF && + test_expect_success "passing test" "true" + test_expect_failure "pretend we have a known breakage" "false" test_done EOF - check_sub_test_lib_test failing-todo <<-\\EOF + check_sub_test_lib_test failing-todo <<-\EOF > ok 1 - passing test > not ok 2 - pretend we have a known breakage # TODO known breakage > # still have 1 known breakage(s) > # passed all remaining 1 test(s) > 1..2 EOF -" +' -test_expect_success 'pretend we have fixed a known breakage' " - run_sub_test_lib_test passing-todo 'A passing TODO test' <<-\\EOF && - test_expect_failure 'pretend we have fixed a known breakage' 'true' +test_expect_success 'pretend we have fixed a known breakage' ' + run_sub_test_lib_test passing-todo "A passing TODO test" <<-\EOF && + test_expect_failure "pretend we have fixed a known breakage" "true" test_done EOF - check_sub_test_lib_test passing-todo <<-\\EOF + check_sub_test_lib_test passing-todo <<-\EOF > ok 1 - pretend we have fixed a known breakage # TODO known breakage vanished > # 1 known breakage(s) vanished; please update test(s) > 1..1 EOF -" +' -test_expect_success 'pretend we have fixed one of two known breakages (run in sub test-lib)' " +test_expect_success 'pretend we have fixed one of two known breakages (run in sub test-lib)' ' run_sub_test_lib_test partially-passing-todos \ - '2 TODO tests, one passing' <<-\\EOF && - test_expect_failure 'pretend we have a known breakage' 'false' - test_expect_success 'pretend we have a passing test' 'true' - test_expect_failure 'pretend we have fixed another known breakage' 'true' + "2 TODO tests, one passing" <<-\EOF && + test_expect_failure "pretend we have a known breakage" "false" + test_expect_success "pretend we have a passing test" "true" + test_expect_failure "pretend we have fixed another known breakage" "true" test_done EOF - check_sub_test_lib_test partially-passing-todos <<-\\EOF + check_sub_test_lib_test partially-passing-todos <<-\EOF > not ok 1 - pretend we have a known breakage # TODO known breakage > ok 2 - pretend we have a passing test > ok 3 - pretend we have fixed another known breakage # TODO known breakage vanished @@ -214,17 +214,17 @@ test_expect_success 'pretend we have fixed one of two known breakages (run in su > # passed all remaining 1 test(s) > 1..3 EOF -" +' -test_expect_success 'pretend we have a pass, fail, and known breakage' " +test_expect_success 'pretend we have a pass, fail, and known breakage' ' run_sub_test_lib_test_err \ - mixed-results1 'mixed results #1' <<-\\EOF && - test_expect_success 'passing test' 'true' - test_expect_success 'failing test' 'false' - test_expect_failure 'pretend we have a known breakage' 'false' + mixed-results1 "mixed results #1" <<-\EOF && + test_expect_success "passing test" "true" + test_expect_success "failing test" "false" + test_expect_failure "pretend we have a known breakage" "false" test_done EOF - check_sub_test_lib_test mixed-results1 <<-\\EOF + check_sub_test_lib_test mixed-results1 <<-\EOF > ok 1 - passing test > not ok 2 - failing test > # false @@ -233,24 +233,24 @@ test_expect_success 'pretend we have a pass, fail, and known breakage' " > # failed 1 among remaining 2 test(s) > 1..3 EOF -" +' -test_expect_success 'pretend we have a mix of all possible results' " +test_expect_success 'pretend we have a mix of all possible results' ' run_sub_test_lib_test_err \ - mixed-results2 'mixed results #2' <<-\\EOF && - test_expect_success 'passing test' 'true' - test_expect_success 'passing test' 'true' - test_expect_success 'passing test' 'true' - test_expect_success 'passing test' 'true' - test_expect_success 'failing test' 'false' - test_expect_success 'failing test' 'false' - test_expect_success 'failing test' 'false' - test_expect_failure 'pretend we have a known breakage' 'false' - test_expect_failure 'pretend we have a known breakage' 'false' - test_expect_failure 'pretend we have fixed a known breakage' 'true' + mixed-results2 "mixed results #2" <<-\EOF && + test_expect_success "passing test" "true" + test_expect_success "passing test" "true" + test_expect_success "passing test" "true" + test_expect_success "passing test" "true" + test_expect_success "failing test" "false" + test_expect_success "failing test" "false" + test_expect_success "failing test" "false" + test_expect_failure "pretend we have a known breakage" "false" + test_expect_failure "pretend we have a known breakage" "false" + test_expect_failure "pretend we have fixed a known breakage" "true" test_done EOF - check_sub_test_lib_test mixed-results2 <<-\\EOF + check_sub_test_lib_test mixed-results2 <<-\EOF > ok 1 - passing test > ok 2 - passing test > ok 3 - passing test @@ -269,7 +269,7 @@ test_expect_success 'pretend we have a mix of all possible results' " > # failed 3 among remaining 7 test(s) > 1..10 EOF -" +' test_expect_success C_LOCALE_OUTPUT 'test --verbose' ' run_sub_test_lib_test_err \ @@ -321,18 +321,18 @@ test_expect_success 'test --verbose-only' ' EOF ' -test_expect_success 'GIT_SKIP_TESTS' " +test_expect_success 'GIT_SKIP_TESTS' ' ( - GIT_SKIP_TESTS='git.2' && export GIT_SKIP_TESTS && + GIT_SKIP_TESTS="git.2" && export GIT_SKIP_TESTS && run_sub_test_lib_test git-skip-tests-basic \ - 'GIT_SKIP_TESTS' <<-\\EOF && + "GIT_SKIP_TESTS" <<-\EOF && for i in 1 2 3 do - test_expect_success \"passing test #\$i\" 'true' + test_expect_success "passing test #$i" "true" done test_done EOF - check_sub_test_lib_test git-skip-tests-basic <<-\\EOF + check_sub_test_lib_test git-skip-tests-basic <<-\EOF > ok 1 - passing test #1 > ok 2 # skip passing test #2 (GIT_SKIP_TESTS) > ok 3 - passing test #3 @@ -340,20 +340,20 @@ test_expect_success 'GIT_SKIP_TESTS' " > 1..3 EOF ) -" +' -test_expect_success 'GIT_SKIP_TESTS several tests' " +test_expect_success 'GIT_SKIP_TESTS several tests' ' ( - GIT_SKIP_TESTS='git.2 git.5' && export GIT_SKIP_TESTS && + GIT_SKIP_TESTS="git.2 git.5" && export GIT_SKIP_TESTS && run_sub_test_lib_test git-skip-tests-several \ - 'GIT_SKIP_TESTS several tests' <<-\\EOF && + "GIT_SKIP_TESTS several tests" <<-\EOF && for i in 1 2 3 4 5 6 do - test_expect_success \"passing test #\$i\" 'true' + test_expect_success "passing test #$i" "true" done test_done EOF - check_sub_test_lib_test git-skip-tests-several <<-\\EOF + check_sub_test_lib_test git-skip-tests-several <<-\EOF > ok 1 - passing test #1 > ok 2 # skip passing test #2 (GIT_SKIP_TESTS) > ok 3 - passing test #3 @@ -364,20 +364,20 @@ test_expect_success 'GIT_SKIP_TESTS several tests' " > 1..6 EOF ) -" +' -test_expect_success 'GIT_SKIP_TESTS sh pattern' " +test_expect_success 'GIT_SKIP_TESTS sh pattern' ' ( - GIT_SKIP_TESTS='git.[2-5]' && export GIT_SKIP_TESTS && + GIT_SKIP_TESTS="git.[2-5]" && export GIT_SKIP_TESTS && run_sub_test_lib_test git-skip-tests-sh-pattern \ - 'GIT_SKIP_TESTS sh pattern' <<-\\EOF && + "GIT_SKIP_TESTS sh pattern" <<-\EOF && for i in 1 2 3 4 5 6 do - test_expect_success \"passing test #\$i\" 'true' + test_expect_success "passing test #$i" "true" done test_done EOF - check_sub_test_lib_test git-skip-tests-sh-pattern <<-\\EOF + check_sub_test_lib_test git-skip-tests-sh-pattern <<-\EOF > ok 1 - passing test #1 > ok 2 # skip passing test #2 (GIT_SKIP_TESTS) > ok 3 # skip passing test #3 (GIT_SKIP_TESTS) @@ -388,37 +388,37 @@ test_expect_success 'GIT_SKIP_TESTS sh pattern' " > 1..6 EOF ) -" +' -test_expect_success 'GIT_SKIP_TESTS entire suite' " +test_expect_success 'GIT_SKIP_TESTS entire suite' ' ( - GIT_SKIP_TESTS='git' && export GIT_SKIP_TESTS && + GIT_SKIP_TESTS="git" && export GIT_SKIP_TESTS && run_sub_test_lib_test git-skip-tests-entire-suite \ - 'GIT_SKIP_TESTS entire suite' <<-\\EOF && + "GIT_SKIP_TESTS entire suite" <<-\EOF && for i in 1 2 3 do - test_expect_success \"passing test #\$i\" 'true' + test_expect_success "passing test #$i" "true" done test_done EOF - check_sub_test_lib_test git-skip-tests-entire-suite <<-\\EOF + check_sub_test_lib_test git-skip-tests-entire-suite <<-\EOF > 1..0 # SKIP skip all tests in git EOF ) -" +' -test_expect_success 'GIT_SKIP_TESTS does not skip unmatched suite' " +test_expect_success 'GIT_SKIP_TESTS does not skip unmatched suite' ' ( - GIT_SKIP_TESTS='notgit' && export GIT_SKIP_TESTS && + GIT_SKIP_TESTS="notgit" && export GIT_SKIP_TESTS && run_sub_test_lib_test git-skip-tests-unmatched-suite \ - 'GIT_SKIP_TESTS does not skip unmatched suite' <<-\\EOF && + "GIT_SKIP_TESTS does not skip unmatched suite" <<-\EOF && for i in 1 2 3 do - test_expect_success \"passing test #\$i\" 'true' + test_expect_success "passing test #$i" "true" done test_done EOF - check_sub_test_lib_test git-skip-tests-unmatched-suite <<-\\EOF + check_sub_test_lib_test git-skip-tests-unmatched-suite <<-\EOF > ok 1 - passing test #1 > ok 2 - passing test #2 > ok 3 - passing test #3 @@ -426,18 +426,18 @@ test_expect_success 'GIT_SKIP_TESTS does not skip unmatched suite' " > 1..3 EOF ) -" +' -test_expect_success '--run basic' " +test_expect_success '--run basic' ' run_sub_test_lib_test run-basic \ - '--run basic' --run='1,3,5' <<-\\EOF && + "--run basic" --run="1,3,5" <<-\EOF && for i in 1 2 3 4 5 6 do - test_expect_success \"passing test #\$i\" 'true' + test_expect_success "passing test #$i" "true" done test_done EOF - check_sub_test_lib_test run-basic <<-\\EOF + check_sub_test_lib_test run-basic <<-\EOF > ok 1 - passing test #1 > ok 2 # skip passing test #2 (--run) > ok 3 - passing test #3 @@ -447,18 +447,18 @@ test_expect_success '--run basic' " > # passed all 6 test(s) > 1..6 EOF -" +' -test_expect_success '--run with a range' " +test_expect_success '--run with a range' ' run_sub_test_lib_test run-range \ - '--run with a range' --run='1-3' <<-\\EOF && + "--run with a range" --run="1-3" <<-\EOF && for i in 1 2 3 4 5 6 do - test_expect_success \"passing test #\$i\" 'true' + test_expect_success "passing test #$i" "true" done test_done EOF - check_sub_test_lib_test run-range <<-\\EOF + check_sub_test_lib_test run-range <<-\EOF > ok 1 - passing test #1 > ok 2 - passing test #2 > ok 3 - passing test #3 @@ -468,18 +468,18 @@ test_expect_success '--run with a range' " > # passed all 6 test(s) > 1..6 EOF -" +' -test_expect_success '--run with two ranges' " +test_expect_success '--run with two ranges' ' run_sub_test_lib_test run-two-ranges \ - '--run with two ranges' --run='1-2,5-6' <<-\\EOF && + "--run with two ranges" --run="1-2,5-6" <<-\EOF && for i in 1 2 3 4 5 6 do - test_expect_success \"passing test #\$i\" 'true' + test_expect_success "passing test #$i" "true" done test_done EOF - check_sub_test_lib_test run-two-ranges <<-\\EOF + check_sub_test_lib_test run-two-ranges <<-\EOF > ok 1 - passing test #1 > ok 2 - passing test #2 > ok 3 # skip passing test #3 (--run) @@ -489,18 +489,18 @@ test_expect_success '--run with two ranges' " > # passed all 6 test(s) > 1..6 EOF -" +' -test_expect_success '--run with a left open range' " +test_expect_success '--run with a left open range' ' run_sub_test_lib_test run-left-open-range \ - '--run with a left open range' --run='-3' <<-\\EOF && + "--run with a left open range" --run="-3" <<-\EOF && for i in 1 2 3 4 5 6 do - test_expect_success \"passing test #\$i\" 'true' + test_expect_success "passing test #$i" "true" done test_done EOF - check_sub_test_lib_test run-left-open-range <<-\\EOF + check_sub_test_lib_test run-left-open-range <<-\EOF > ok 1 - passing test #1 > ok 2 - passing test #2 > ok 3 - passing test #3 @@ -510,18 +510,18 @@ test_expect_success '--run with a left open range' " > # passed all 6 test(s) > 1..6 EOF -" +' -test_expect_success '--run with a right open range' " +test_expect_success '--run with a right open range' ' run_sub_test_lib_test run-right-open-range \ - '--run with a right open range' --run='4-' <<-\\EOF && + "--run with a right open range" --run="4-" <<-\EOF && for i in 1 2 3 4 5 6 do - test_expect_success \"passing test #\$i\" 'true' + test_expect_success "passing test #$i" "true" done test_done EOF - check_sub_test_lib_test run-right-open-range <<-\\EOF + check_sub_test_lib_test run-right-open-range <<-\EOF > ok 1 # skip passing test #1 (--run) > ok 2 # skip passing test #2 (--run) > ok 3 # skip passing test #3 (--run) @@ -531,18 +531,18 @@ test_expect_success '--run with a right open range' " > # passed all 6 test(s) > 1..6 EOF -" +' -test_expect_success '--run with basic negation' " +test_expect_success '--run with basic negation' ' run_sub_test_lib_test run-basic-neg \ - '--run with basic negation' --run='"'!3'"' <<-\\EOF && + "--run with basic negation" --run="!3" <<-\EOF && for i in 1 2 3 4 5 6 do - test_expect_success \"passing test #\$i\" 'true' + test_expect_success "passing test #$i" "true" done test_done EOF - check_sub_test_lib_test run-basic-neg <<-\\EOF + check_sub_test_lib_test run-basic-neg <<-\EOF > ok 1 - passing test #1 > ok 2 - passing test #2 > ok 3 # skip passing test #3 (--run) @@ -552,18 +552,18 @@ test_expect_success '--run with basic negation' " > # passed all 6 test(s) > 1..6 EOF -" +' -test_expect_success '--run with two negations' " +test_expect_success '--run with two negations' ' run_sub_test_lib_test run-two-neg \ - '--run with two negations' --run='"'!3,!6'"' <<-\\EOF && + "--run with two negations" --run="!3,!6" <<-\EOF && for i in 1 2 3 4 5 6 do - test_expect_success \"passing test #\$i\" 'true' + test_expect_success "passing test #$i" "true" done test_done EOF - check_sub_test_lib_test run-two-neg <<-\\EOF + check_sub_test_lib_test run-two-neg <<-\EOF > ok 1 - passing test #1 > ok 2 - passing test #2 > ok 3 # skip passing test #3 (--run) @@ -573,18 +573,18 @@ test_expect_success '--run with two negations' " > # passed all 6 test(s) > 1..6 EOF -" +' -test_expect_success '--run a range and negation' " +test_expect_success '--run a range and negation' ' run_sub_test_lib_test run-range-and-neg \ - '--run a range and negation' --run='"'-4,!2'"' <<-\\EOF && + "--run a range and negation" --run="-4,!2" <<-\EOF && for i in 1 2 3 4 5 6 do - test_expect_success \"passing test #\$i\" 'true' + test_expect_success "passing test #$i" "true" done test_done EOF - check_sub_test_lib_test run-range-and-neg <<-\\EOF + check_sub_test_lib_test run-range-and-neg <<-\EOF > ok 1 - passing test #1 > ok 2 # skip passing test #2 (--run) > ok 3 - passing test #3 @@ -594,18 +594,18 @@ test_expect_success '--run a range and negation' " > # passed all 6 test(s) > 1..6 EOF -" +' -test_expect_success '--run range negation' " +test_expect_success '--run range negation' ' run_sub_test_lib_test run-range-neg \ - '--run range negation' --run='"'!1-3'"' <<-\\EOF && + "--run range negation" --run="!1-3" <<-\EOF && for i in 1 2 3 4 5 6 do - test_expect_success \"passing test #\$i\" 'true' + test_expect_success "passing test #$i" "true" done test_done EOF - check_sub_test_lib_test run-range-neg <<-\\EOF + check_sub_test_lib_test run-range-neg <<-\EOF > ok 1 # skip passing test #1 (--run) > ok 2 # skip passing test #2 (--run) > ok 3 # skip passing test #3 (--run) @@ -615,19 +615,19 @@ test_expect_success '--run range negation' " > # passed all 6 test(s) > 1..6 EOF -" +' -test_expect_success '--run include, exclude and include' " +test_expect_success '--run include, exclude and include' ' run_sub_test_lib_test run-inc-neg-inc \ - '--run include, exclude and include' \ - --run='"'1-5,!1-3,2'"' <<-\\EOF && + "--run include, exclude and include" \ + --run="1-5,!1-3,2" <<-\EOF && for i in 1 2 3 4 5 6 do - test_expect_success \"passing test #\$i\" 'true' + test_expect_success "passing test #$i" "true" done test_done EOF - check_sub_test_lib_test run-inc-neg-inc <<-\\EOF + check_sub_test_lib_test run-inc-neg-inc <<-\EOF > ok 1 # skip passing test #1 (--run) > ok 2 - passing test #2 > ok 3 # skip passing test #3 (--run) @@ -637,19 +637,19 @@ test_expect_success '--run include, exclude and include' " > # passed all 6 test(s) > 1..6 EOF -" +' -test_expect_success '--run include, exclude and include, comma separated' " +test_expect_success '--run include, exclude and include, comma separated' ' run_sub_test_lib_test run-inc-neg-inc-comma \ - '--run include, exclude and include, comma separated' \ - --run=1-5,\!1-3,2 <<-\\EOF && + "--run include, exclude and include, comma separated" \ + --run=1-5,!1-3,2 <<-\EOF && for i in 1 2 3 4 5 6 do - test_expect_success \"passing test #\$i\" 'true' + test_expect_success "passing test #$i" "true" done test_done EOF - check_sub_test_lib_test run-inc-neg-inc-comma <<-\\EOF + check_sub_test_lib_test run-inc-neg-inc-comma <<-\EOF > ok 1 # skip passing test #1 (--run) > ok 2 - passing test #2 > ok 3 # skip passing test #3 (--run) @@ -659,19 +659,19 @@ test_expect_success '--run include, exclude and include, comma separated' " > # passed all 6 test(s) > 1..6 EOF -" +' -test_expect_success '--run exclude and include' " +test_expect_success '--run exclude and include' ' run_sub_test_lib_test run-neg-inc \ - '--run exclude and include' \ - --run='"'!3-,5'"' <<-\\EOF && + "--run exclude and include" \ + --run="!3-,5" <<-\EOF && for i in 1 2 3 4 5 6 do - test_expect_success \"passing test #\$i\" 'true' + test_expect_success "passing test #$i" "true" done test_done EOF - check_sub_test_lib_test run-neg-inc <<-\\EOF + check_sub_test_lib_test run-neg-inc <<-\EOF > ok 1 - passing test #1 > ok 2 - passing test #2 > ok 3 # skip passing test #3 (--run) @@ -681,19 +681,19 @@ test_expect_success '--run exclude and include' " > # passed all 6 test(s) > 1..6 EOF -" +' -test_expect_success '--run empty selectors' " +test_expect_success '--run empty selectors' ' run_sub_test_lib_test run-empty-sel \ - '--run empty selectors' \ - --run='1,,3,,,5' <<-\\EOF && + "--run empty selectors" \ + --run="1,,3,,,5" <<-\EOF && for i in 1 2 3 4 5 6 do - test_expect_success \"passing test #\$i\" 'true' + test_expect_success "passing test #$i" "true" done test_done EOF - check_sub_test_lib_test run-empty-sel <<-\\EOF + check_sub_test_lib_test run-empty-sel <<-\EOF > ok 1 - passing test #1 > ok 2 # skip passing test #2 (--run) > ok 3 - passing test #3 @@ -703,20 +703,20 @@ test_expect_success '--run empty selectors' " > # passed all 6 test(s) > 1..6 EOF -" +' -test_expect_success '--run substring selector' " +test_expect_success '--run substring selector' ' run_sub_test_lib_test run-substring-selector \ - '--run empty selectors' \ - --run='relevant' <<-\\EOF && - test_expect_success \"relevant test\" 'true' + "--run empty selectors" \ + --run="relevant" <<-\EOF && + test_expect_success "relevant test" "true" for i in 1 2 3 4 5 6 do - test_expect_success \"other test #\$i\" 'true' + test_expect_success "other test #$i" "true" done test_done EOF - check_sub_test_lib_test run-substring-selector <<-\\EOF + check_sub_test_lib_test run-substring-selector <<-\EOF > ok 1 - relevant test > ok 2 # skip other test #1 (--run) > ok 3 # skip other test #2 (--run) @@ -727,167 +727,165 @@ test_expect_success '--run substring selector' " > # passed all 7 test(s) > 1..7 EOF -" +' -test_expect_success '--run keyword selection' " +test_expect_success '--run keyword selection' ' run_sub_test_lib_test_err run-inv-range-start \ - '--run invalid range start' \ - --run='a-5' <<-\\EOF && - test_expect_success \"passing test #1\" 'true' + "--run invalid range start" \ + --run="a-5" <<-\EOF && + test_expect_success "passing test #1" "true" test_done EOF check_sub_test_lib_test_err run-inv-range-start \ - <<-\\EOF_OUT 3<<-\\EOF_ERR + <<-\EOF_OUT 3<<-EOF_ERR > FATAL: Unexpected exit with code 1 EOF_OUT - > error: --run: invalid non-numeric in range start: 'a-5' + > error: --run: invalid non-numeric in range start: ${SQ}a-5${SQ} EOF_ERR -" +' -test_expect_success '--run invalid range end' " +test_expect_success '--run invalid range end' ' run_sub_test_lib_test_err run-inv-range-end \ - '--run invalid range end' \ - --run='1-z' <<-\\EOF && - test_expect_success \"passing test #1\" 'true' + "--run invalid range end" \ + --run="1-z" <<-\EOF && + test_expect_success "passing test #1" "true" test_done EOF check_sub_test_lib_test_err run-inv-range-end \ - <<-\\EOF_OUT 3<<-\\EOF_ERR + <<-\EOF_OUT 3<<-EOF_ERR > FATAL: Unexpected exit with code 1 EOF_OUT - > error: --run: invalid non-numeric in range end: '1-z' + > error: --run: invalid non-numeric in range end: ${SQ}1-z${SQ} EOF_ERR -" - - -test_set_prereq HAVEIT -haveit=no -test_expect_success HAVEIT 'test runs if prerequisite is satisfied' ' - test_have_prereq HAVEIT && - haveit=yes -' -donthaveit=yes -test_expect_success DONTHAVEIT 'unmet prerequisite causes test to be skipped' ' - donthaveit=no -' -if test -z "$GIT_TEST_FAIL_PREREQS_INTERNAL" -a $haveit$donthaveit != yesyes -then - say "bug in test framework: prerequisite tags do not work reliably" - exit 1 -fi - -test_set_prereq HAVETHIS -haveit=no -test_expect_success HAVETHIS,HAVEIT 'test runs if prerequisites are satisfied' ' - test_have_prereq HAVEIT && - test_have_prereq HAVETHIS && - haveit=yes -' -donthaveit=yes -test_expect_success HAVEIT,DONTHAVEIT 'unmet prerequisites causes test to be skipped' ' - donthaveit=no -' -donthaveiteither=yes -test_expect_success DONTHAVEIT,HAVEIT 'unmet prerequisites causes test to be skipped' ' - donthaveiteither=no -' -if test -z "$GIT_TEST_FAIL_PREREQS_INTERNAL" -a $haveit$donthaveit$donthaveiteither != yesyesyes -then - say "bug in test framework: multiple prerequisite tags do not work reliably" - exit 1 -fi - -test_lazy_prereq LAZY_TRUE true -havetrue=no -test_expect_success LAZY_TRUE 'test runs if lazy prereq is satisfied' ' - havetrue=yes -' -donthavetrue=yes -test_expect_success !LAZY_TRUE 'missing lazy prereqs skip tests' ' - donthavetrue=no -' - -if test -z "$GIT_TEST_FAIL_PREREQS_INTERNAL" -a "$havetrue$donthavetrue" != yesyes -then - say 'bug in test framework: lazy prerequisites do not work' - exit 1 -fi - -test_lazy_prereq LAZY_FALSE false -nothavefalse=no -test_expect_success !LAZY_FALSE 'negative lazy prereqs checked' ' - nothavefalse=yes -' -havefalse=yes -test_expect_success LAZY_FALSE 'missing negative lazy prereqs will skip' ' - havefalse=no -' - -if test -z "$GIT_TEST_FAIL_PREREQS_INTERNAL" -a "$nothavefalse$havefalse" != yesyes -then - say 'bug in test framework: negative lazy prerequisites do not work' - exit 1 -fi - -clean=no -test_expect_success 'tests clean up after themselves' ' - test_when_finished clean=yes ' -if test -z "$GIT_TEST_FAIL_PREREQS_INTERNAL" -a $clean != yes -then - say "bug in test framework: basic cleanup command does not work reliably" - exit 1 -fi +test_expect_success 'tests respect prerequisites' ' + run_sub_test_lib_test prereqs "tests respect prereqs" <<-\EOF && -test_lazy_prereq NESTED_INNER ' - >inner && - rm -f outer -' -test_lazy_prereq NESTED_PREREQ ' - >outer && - test_have_prereq NESTED_INNER && - echo "can create new file in cwd" >file && - test -f outer && - test ! -f inner + test_set_prereq HAVEIT + test_expect_success HAVEIT "prereq is satisfied" "true" + test_expect_success "have_prereq works" " + test_have_prereq HAVEIT + " + test_expect_success DONTHAVEIT "prereq not satisfied" "false" + + test_set_prereq HAVETHIS + test_expect_success HAVETHIS,HAVEIT "multiple prereqs" "true" + test_expect_success HAVEIT,DONTHAVEIT "mixed prereqs (yes,no)" "false" + test_expect_success DONTHAVEIT,HAVEIT "mixed prereqs (no,yes)" "false" + + test_done + EOF + + check_sub_test_lib_test prereqs <<-\EOF + ok 1 - prereq is satisfied + ok 2 - have_prereq works + ok 3 # skip prereq not satisfied (missing DONTHAVEIT) + ok 4 - multiple prereqs + ok 5 # skip mixed prereqs (yes,no) (missing DONTHAVEIT of HAVEIT,DONTHAVEIT) + ok 6 # skip mixed prereqs (no,yes) (missing DONTHAVEIT of DONTHAVEIT,HAVEIT) + # passed all 6 test(s) + 1..6 + EOF ' -test_expect_success NESTED_PREREQ 'evaluating nested lazy prereqs dont interfere with each other' ' - nestedworks=yes + +test_expect_success 'tests respect lazy prerequisites' ' + run_sub_test_lib_test lazy-prereqs "respect lazy prereqs" <<-\EOF && + + test_lazy_prereq LAZY_TRUE true + test_expect_success LAZY_TRUE "lazy prereq is satisifed" "true" + test_expect_success !LAZY_TRUE "negative lazy prereq" "false" + + test_lazy_prereq LAZY_FALSE false + test_expect_success LAZY_FALSE "lazy prereq not satisfied" "false" + test_expect_success !LAZY_FALSE "negative false prereq" "true" + + test_done + EOF + + check_sub_test_lib_test lazy-prereqs <<-\EOF + ok 1 - lazy prereq is satisifed + ok 2 # skip negative lazy prereq (missing !LAZY_TRUE) + ok 3 # skip lazy prereq not satisfied (missing LAZY_FALSE) + ok 4 - negative false prereq + # passed all 4 test(s) + 1..4 + EOF ' -if test -z "$GIT_TEST_FAIL_PREREQS_INTERNAL" && test "$nestedworks" != yes -then - say 'bug in test framework: nested lazy prerequisites do not work' - exit 1 -fi +test_expect_success 'nested lazy prerequisites' ' + run_sub_test_lib_test nested-lazy "nested lazy prereqs" <<-\EOF && + + test_lazy_prereq NESTED_INNER " + >inner && + rm -f outer + " + test_lazy_prereq NESTED_PREREQ " + >outer && + test_have_prereq NESTED_INNER && + echo can create new file in cwd >file && + test_path_is_file outer && + test_path_is_missing inner + " + test_expect_success NESTED_PREREQ "evaluate nested prereq" "true" -test_expect_success 'lazy prereqs do not turn off tracing' " + test_done + EOF + + check_sub_test_lib_test nested-lazy <<-\EOF + ok 1 - evaluate nested prereq + # passed all 1 test(s) + 1..1 + EOF +' + +test_expect_success 'lazy prereqs do not turn off tracing' ' run_sub_test_lib_test lazy-prereq-and-tracing \ - 'lazy prereqs and -x' -v -x <<-\\EOF && + "lazy prereqs and -x" -v -x <<-\EOF && test_lazy_prereq LAZY true - test_expect_success lazy 'test_have_prereq LAZY && echo trace' + test_expect_success lazy "test_have_prereq LAZY && echo trace" + + test_done + EOF + + grep "echo trace" lazy-prereq-and-tracing/err +' +test_expect_success 'tests clean up after themselves' ' + run_sub_test_lib_test cleanup "test with cleanup" <<-\EOF && + clean=no + test_expect_success "do cleanup" " + test_when_finished clean=yes + " + test_expect_success "cleanup happened" " + test $clean = yes + " test_done EOF - grep 'echo trace' lazy-prereq-and-tracing/err -" + check_sub_test_lib_test cleanup <<-\EOF + ok 1 - do cleanup + ok 2 - cleanup happened + # passed all 2 test(s) + 1..2 + EOF +' -test_expect_success 'tests clean up even on failures' " +test_expect_success 'tests clean up even on failures' ' run_sub_test_lib_test_err \ - failing-cleanup 'Failing tests with cleanup commands' <<-\\EOF && - test_expect_success 'tests clean up even after a failure' ' + failing-cleanup "Failing tests with cleanup commands" <<-\EOF && + test_expect_success "tests clean up even after a failure" " touch clean-after-failure && test_when_finished rm clean-after-failure && (exit 1) - ' - test_expect_success 'failure to clean up causes the test to fail' ' + " + test_expect_success "failure to clean up causes the test to fail" " test_when_finished \"(exit 2)\" - ' + " test_done EOF - check_sub_test_lib_test failing-cleanup <<-\\EOF + check_sub_test_lib_test failing-cleanup <<-\EOF > not ok 1 - tests clean up even after a failure > # Z > # touch clean-after-failure && @@ -896,30 +894,30 @@ test_expect_success 'tests clean up even on failures' " > # Z > not ok 2 - failure to clean up causes the test to fail > # Z - > # test_when_finished \"(exit 2)\" + > # test_when_finished "(exit 2)" > # Z > # failed 2 among 2 test(s) > 1..2 EOF -" +' -test_expect_success 'test_atexit is run' " +test_expect_success 'test_atexit is run' ' run_sub_test_lib_test_err \ - atexit-cleanup 'Run atexit commands' -i <<-\\EOF && - test_expect_success 'tests clean up even after a failure' ' + atexit-cleanup "Run atexit commands" -i <<-\EOF && + test_expect_success "tests clean up even after a failure" " > ../../clean-atexit && test_atexit rm ../../clean-atexit && > ../../also-clean-atexit && test_atexit rm ../../also-clean-atexit && > ../../dont-clean-atexit && (exit 1) - ' + " test_done EOF test_path_is_file dont-clean-atexit && test_path_is_missing clean-atexit && test_path_is_missing also-clean-atexit -" +' test_expect_success 'test_oid provides sane info by default' ' test_oid zero >actual && diff --git a/t/t0017-env-helper.sh b/t/t0017-env-helper.sh index c1ecf6aeac..4a159f99e4 100755 --- a/t/t0017-env-helper.sh +++ b/t/t0017-env-helper.sh @@ -86,14 +86,14 @@ test_expect_success 'env--helper reads config thanks to trace2' ' git config -f home/cycle include.path .gitconfig && test_must_fail \ - env HOME="$(pwd)/home" GIT_TEST_GETTEXT_POISON=false \ + env HOME="$(pwd)/home" \ git config -l 2>err && grep "exceeded maximum include depth" err && test_must_fail \ - env HOME="$(pwd)/home" GIT_TEST_GETTEXT_POISON=true \ - git -C cycle env--helper --type=bool --default=0 --exit-code GIT_TEST_GETTEXT_POISON 2>err && - grep "# GETTEXT POISON #" err + env HOME="$(pwd)/home" GIT_TEST_ENV_HELPER=true \ + git -C cycle env--helper --type=bool --default=0 --exit-code GIT_TEST_ENV_HELPER 2>err && + grep "exceeded maximum include depth" err ' test_done diff --git a/t/t0090-cache-tree.sh b/t/t0090-cache-tree.sh index 5a633690bf..9bf66c9e68 100755 --- a/t/t0090-cache-tree.sh +++ b/t/t0090-cache-tree.sh @@ -10,40 +10,36 @@ cache-tree extension. cmp_cache_tree () { test-tool dump-cache-tree | sed -e '/#(ref)/d' >actual && sed "s/$OID_REGEX/SHA/" <actual >filtered && - test_cmp "$1" filtered + test_cmp "$1" filtered && + rm filtered } # We don't bother with actually checking the SHA1: # test-tool dump-cache-tree already verifies that all existing data is # correct. -generate_expected_cache_tree_rec () { - dir="$1${1:+/}" && - parent="$2" && - # ls-files might have foo/bar, foo/bar/baz, and foo/bar/quux - # We want to count only foo because it's the only direct child - git ls-files >files && - subtrees=$(grep / files|cut -d / -f 1|uniq) && - subtree_count=$(echo "$subtrees"|awk -v c=0 '$1 != "" {++c} END {print c}') && - entries=$(wc -l <files) && - printf "SHA $dir (%d entries, %d subtrees)\n" "$entries" "$subtree_count" && - for subtree in $subtrees - do - cd "$subtree" - generate_expected_cache_tree_rec "$dir$subtree" "$dir" || return 1 - cd .. - done && - dir=$parent -} - generate_expected_cache_tree () { - ( - generate_expected_cache_tree_rec - ) + pathspec="$1" && + dir="$2${2:+/}" && + git ls-tree --name-only HEAD -- "$pathspec" >files && + git ls-tree --name-only -d HEAD -- "$pathspec" >subtrees && + printf "SHA %s (%d entries, %d subtrees)\n" "$dir" $(wc -l <files) $(wc -l <subtrees) && + while read subtree + do + generate_expected_cache_tree "$pathspec/$subtree/" "$subtree" || return 1 + done <subtrees } test_cache_tree () { - generate_expected_cache_tree >expect && - cmp_cache_tree expect + generate_expected_cache_tree "." >expect && + cmp_cache_tree expect && + rm expect actual files subtrees && + git status --porcelain -- ':!status' ':!expected.status' >status && + if test -n "$1" + then + test_cmp "$1" status + else + test_must_be_empty status + fi } test_invalid_cache_tree () { @@ -54,7 +50,7 @@ test_invalid_cache_tree () { } test_no_cache_tree () { - : >expect && + >expect && cmp_cache_tree expect } @@ -83,18 +79,6 @@ test_expect_success 'git-add in subdir invalidates cache-tree' ' test_invalid_cache_tree ' -cat >before <<\EOF -SHA (3 entries, 2 subtrees) -SHA dir1/ (1 entries, 0 subtrees) -SHA dir2/ (1 entries, 0 subtrees) -EOF - -cat >expect <<\EOF -invalid (2 subtrees) -invalid dir1/ (0 subtrees) -SHA dir2/ (1 entries, 0 subtrees) -EOF - test_expect_success 'git-add in subdir does not invalidate sibling cache-tree' ' git tag no-children && test_when_finished "git reset --hard no-children; git read-tree HEAD" && @@ -102,9 +86,20 @@ test_expect_success 'git-add in subdir does not invalidate sibling cache-tree' ' test_commit dir1/a && test_commit dir2/b && echo "I changed this file" >dir1/a && + test_when_finished "rm before" && + cat >before <<-\EOF && + SHA (3 entries, 2 subtrees) + SHA dir1/ (1 entries, 0 subtrees) + SHA dir2/ (1 entries, 0 subtrees) + EOF cmp_cache_tree before && echo "I changed this file" >dir1/a && git add dir1/a && + cat >expect <<-\EOF && + invalid (2 subtrees) + invalid dir1/ (0 subtrees) + SHA dir2/ (1 entries, 0 subtrees) + EOF cmp_cache_tree expect ' @@ -133,6 +128,7 @@ test_expect_success 'second commit has cache-tree' ' ' test_expect_success PERL 'commit --interactive gives cache-tree on partial commit' ' + test_when_finished "git reset --hard" && cat <<-\EOT >foo.c && int foo() { @@ -159,7 +155,10 @@ test_expect_success PERL 'commit --interactive gives cache-tree on partial commi EOT test_write_lines p 1 "" s n y q | git commit --interactive -m foo && - test_cache_tree + cat <<-\EOF >expected.status && + M foo.c + EOF + test_cache_tree expected.status ' test_expect_success PERL 'commit -p with shrinking cache-tree' ' @@ -250,7 +249,10 @@ test_expect_success 'partial commit gives cache-tree' ' git add one.t && echo "some other change" >two.t && git commit two.t -m partial && - test_cache_tree + cat <<-\EOF >expected.status && + M one.t + EOF + test_cache_tree expected.status ' test_expect_success 'no phantom error when switching trees' ' diff --git a/t/t0205-gettext-poison.sh b/t/t0205-gettext-poison.sh deleted file mode 100755 index f9fa16ad83..0000000000 --- a/t/t0205-gettext-poison.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2010 Ævar Arnfjörð Bjarmason -# - -test_description='Gettext Shell poison' - -GIT_TEST_GETTEXT_POISON=true -export GIT_TEST_GETTEXT_POISON -. ./lib-gettext.sh - -test_expect_success 'sanity: $GIT_INTERNAL_GETTEXT_SH_SCHEME" is poison' ' - test "$GIT_INTERNAL_GETTEXT_SH_SCHEME" = "poison" -' - -test_expect_success 'gettext: our gettext() fallback has poison semantics' ' - printf "# GETTEXT POISON #" >expect && - gettext "test" >actual && - test_cmp expect actual && - printf "# GETTEXT POISON #" >expect && - gettext "test more words" >actual && - test_cmp expect actual -' - -test_expect_success 'eval_gettext: our eval_gettext() fallback has poison semantics' ' - printf "# GETTEXT POISON #" >expect && - eval_gettext "test" >actual && - test_cmp expect actual && - printf "# GETTEXT POISON #" >expect && - eval_gettext "test more words" >actual && - test_cmp expect actual -' - -test_expect_success "gettext: invalid GIT_TEST_GETTEXT_POISON value doesn't infinitely loop" " - test_must_fail env GIT_TEST_GETTEXT_POISON=xyz git version 2>error && - grep \"fatal: bad numeric config value 'xyz' for 'GIT_TEST_GETTEXT_POISON': invalid unit\" error -" - -test_done diff --git a/t/t0210-trace2-normal.sh b/t/t0210-trace2-normal.sh index ce7574edb1..0cf3a63b75 100755 --- a/t/t0210-trace2-normal.sh +++ b/t/t0210-trace2-normal.sh @@ -147,6 +147,25 @@ test_expect_success 'normal stream, error event' ' test_cmp expect actual ' +# Verb 007bug +# +# Check that BUG writes to trace2 + +test_expect_success 'BUG messages are written to trace2' ' + test_when_finished "rm trace.normal actual expect" && + test_must_fail env GIT_TRACE2="$(pwd)/trace.normal" test-tool trace2 007bug && + perl "$TEST_DIRECTORY/t0210/scrub_normal.perl" <trace.normal >actual && + cat >expect <<-EOF && + version $V + start _EXE_ trace2 007bug + cmd_name trace2 (trace2) + error the bug message + exit elapsed:_TIME_ code:99 + atexit elapsed:_TIME_ code:99 + EOF + test_cmp expect actual +' + sane_unset GIT_TRACE2_BRIEF # Now test without environment variables and get all Trace2 settings diff --git a/t/t0500-progress-display.sh b/t/t0500-progress-display.sh index 1ed1df351c..84cce345e7 100755 --- a/t/t0500-progress-display.sh +++ b/t/t0500-progress-display.sh @@ -303,8 +303,7 @@ test_expect_success 'progress generates traces' ' "Working hard" <in 2>stderr && # t0212/parse_events.perl intentionally omits regions and data. - grep -e "region_enter" -e "\"category\":\"progress\"" trace.event && - grep -e "region_leave" -e "\"category\":\"progress\"" trace.event && + test_region progress "Working hard" trace.event && grep "\"key\":\"total_objects\",\"value\":\"40\"" trace.event && grep "\"key\":\"total_bytes\",\"value\":\"409600\"" trace.event ' diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh new file mode 100755 index 0000000000..8cd3e5a8d2 --- /dev/null +++ b/t/t1092-sparse-checkout-compatibility.sh @@ -0,0 +1,301 @@ +#!/bin/sh + +test_description='compare full workdir to sparse workdir' + +. ./test-lib.sh + +test_expect_success 'setup' ' + git init initial-repo && + ( + cd initial-repo && + echo a >a && + echo "after deep" >e && + echo "after folder1" >g && + echo "after x" >z && + mkdir folder1 folder2 deep x && + mkdir deep/deeper1 deep/deeper2 && + mkdir deep/deeper1/deepest && + echo "after deeper1" >deep/e && + echo "after deepest" >deep/deeper1/e && + cp a folder1 && + cp a folder2 && + cp a x && + cp a deep && + cp a deep/deeper1 && + cp a deep/deeper2 && + cp a deep/deeper1/deepest && + cp -r deep/deeper1/deepest deep/deeper2 && + git add . && + git commit -m "initial commit" && + git checkout -b base && + for dir in folder1 folder2 deep + do + git checkout -b update-$dir && + echo "updated $dir" >$dir/a && + git commit -a -m "update $dir" || return 1 + done && + + git checkout -b rename-base base && + echo >folder1/larger-content <<-\EOF && + matching + lines + help + inexact + renames + EOF + cp folder1/larger-content folder2/ && + cp folder1/larger-content deep/deeper1/ && + git add . && + git commit -m "add interesting rename content" && + + git checkout -b rename-out-to-out rename-base && + mv folder1/a folder2/b && + mv folder1/larger-content folder2/edited-content && + echo >>folder2/edited-content && + git add . && + git commit -m "rename folder1/... to folder2/..." && + + git checkout -b rename-out-to-in rename-base && + mv folder1/a deep/deeper1/b && + mv folder1/larger-content deep/deeper1/edited-content && + echo >>deep/deeper1/edited-content && + git add . && + git commit -m "rename folder1/... to deep/deeper1/..." && + + git checkout -b rename-in-to-out rename-base && + mv deep/deeper1/a folder1/b && + mv deep/deeper1/larger-content folder1/edited-content && + echo >>folder1/edited-content && + git add . && + git commit -m "rename deep/deeper1/... to folder1/..." && + + git checkout -b deepest base && + echo "updated deepest" >deep/deeper1/deepest/a && + git commit -a -m "update deepest" && + + git checkout -f base && + git reset --hard + ) +' + +init_repos () { + rm -rf full-checkout sparse-checkout sparse-index && + + # create repos in initial state + cp -r initial-repo full-checkout && + git -C full-checkout reset --hard && + + cp -r initial-repo sparse-checkout && + git -C sparse-checkout reset --hard && + git -C sparse-checkout sparse-checkout init --cone && + + # initialize sparse-checkout definitions + git -C sparse-checkout sparse-checkout set deep +} + +run_on_sparse () { + ( + cd sparse-checkout && + $* >../sparse-checkout-out 2>../sparse-checkout-err + ) +} + +run_on_all () { + ( + cd full-checkout && + $* >../full-checkout-out 2>../full-checkout-err + ) && + run_on_sparse $* +} + +test_all_match () { + run_on_all $* && + test_cmp full-checkout-out sparse-checkout-out && + test_cmp full-checkout-err sparse-checkout-err +} + +test_expect_success 'status with options' ' + init_repos && + test_all_match git status --porcelain=v2 && + test_all_match git status --porcelain=v2 -z -u && + test_all_match git status --porcelain=v2 -uno && + run_on_all "touch README.md" && + test_all_match git status --porcelain=v2 && + test_all_match git status --porcelain=v2 -z -u && + test_all_match git status --porcelain=v2 -uno && + test_all_match git add README.md && + test_all_match git status --porcelain=v2 && + test_all_match git status --porcelain=v2 -z -u && + test_all_match git status --porcelain=v2 -uno +' + +test_expect_success 'add, commit, checkout' ' + init_repos && + + write_script edit-contents <<-\EOF && + echo text >>$1 + EOF + run_on_all "../edit-contents README.md" && + + test_all_match git add README.md && + test_all_match git status --porcelain=v2 && + test_all_match git commit -m "Add README.md" && + + test_all_match git checkout HEAD~1 && + test_all_match git checkout - && + + run_on_all "../edit-contents README.md" && + + test_all_match git add -A && + test_all_match git status --porcelain=v2 && + test_all_match git commit -m "Extend README.md" && + + test_all_match git checkout HEAD~1 && + test_all_match git checkout - && + + run_on_all "../edit-contents deep/newfile" && + + test_all_match git status --porcelain=v2 -uno && + test_all_match git status --porcelain=v2 && + test_all_match git add . && + test_all_match git status --porcelain=v2 && + test_all_match git commit -m "add deep/newfile" && + + test_all_match git checkout HEAD~1 && + test_all_match git checkout - +' + +test_expect_success 'checkout and reset --hard' ' + init_repos && + + test_all_match git checkout update-folder1 && + test_all_match git status --porcelain=v2 && + + test_all_match git checkout update-deep && + test_all_match git status --porcelain=v2 && + + test_all_match git checkout -b reset-test && + test_all_match git reset --hard deepest && + test_all_match git reset --hard update-folder1 && + test_all_match git reset --hard update-folder2 +' + +test_expect_success 'diff --staged' ' + init_repos && + + write_script edit-contents <<-\EOF && + echo text >>README.md + EOF + run_on_all "../edit-contents" && + + test_all_match git diff && + test_all_match git diff --staged && + test_all_match git add README.md && + test_all_match git diff && + test_all_match git diff --staged +' + +test_expect_success 'diff with renames' ' + init_repos && + + for branch in rename-out-to-out rename-out-to-in rename-in-to-out + do + test_all_match git checkout rename-base && + test_all_match git checkout $branch -- .&& + test_all_match git diff --staged --no-renames && + test_all_match git diff --staged --find-renames || return 1 + done +' + +test_expect_success 'log with pathspec outside sparse definition' ' + init_repos && + + test_all_match git log -- a && + test_all_match git log -- folder1/a && + test_all_match git log -- folder2/a && + test_all_match git log -- deep/a && + test_all_match git log -- deep/deeper1/a && + test_all_match git log -- deep/deeper1/deepest/a && + + test_all_match git checkout update-folder1 && + test_all_match git log -- folder1/a +' + +test_expect_success 'blame with pathspec inside sparse definition' ' + init_repos && + + test_all_match git blame a && + test_all_match git blame deep/a && + test_all_match git blame deep/deeper1/a && + test_all_match git blame deep/deeper1/deepest/a +' + +# TODO: blame currently does not support blaming files outside of the +# sparse definition. It complains that the file doesn't exist locally. +test_expect_failure 'blame with pathspec outside sparse definition' ' + init_repos && + + test_all_match git blame folder1/a && + test_all_match git blame folder2/a && + test_all_match git blame deep/deeper2/a && + test_all_match git blame deep/deeper2/deepest/a +' + +# TODO: reset currently does not behave as expected when in a +# sparse-checkout. +test_expect_failure 'checkout and reset (mixed)' ' + init_repos && + + test_all_match git checkout -b reset-test update-deep && + test_all_match git reset deepest && + test_all_match git reset update-folder1 && + test_all_match git reset update-folder2 +' + +test_expect_success 'merge' ' + init_repos && + + test_all_match git checkout -b merge update-deep && + test_all_match git merge -m "folder1" update-folder1 && + test_all_match git rev-parse HEAD^{tree} && + test_all_match git merge -m "folder2" update-folder2 && + test_all_match git rev-parse HEAD^{tree} +' + +test_expect_success 'merge with outside renames' ' + init_repos && + + for type in out-to-out out-to-in in-to-out + do + test_all_match git reset --hard && + test_all_match git checkout -f -b merge-$type update-deep && + test_all_match git merge -m "$type" rename-$type && + test_all_match git rev-parse HEAD^{tree} || return 1 + done +' + +test_expect_success 'clean' ' + init_repos && + + echo bogus >>.gitignore && + run_on_all cp ../.gitignore . && + test_all_match git add .gitignore && + test_all_match git commit -m ignore-bogus-files && + + run_on_sparse mkdir folder1 && + run_on_all touch folder1/bogus && + + test_all_match git status --porcelain=v2 && + test_all_match git clean -f && + test_all_match git status --porcelain=v2 && + + test_all_match git clean -xf && + test_all_match git status --porcelain=v2 && + + test_all_match git clean -xdf && + test_all_match git status --porcelain=v2 && + + test_path_is_dir sparse-checkout/folder1 +' + +test_done diff --git a/t/t1300-config.sh b/t/t1300-config.sh index 936d72041b..e0dd5d65ce 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -675,6 +675,13 @@ test_expect_success 'invalid unit' ' test_i18ngrep "bad numeric config value .1auto. for .aninvalid.unit. in file .git/config: invalid unit" actual ' +test_expect_success 'invalid unit boolean' ' + git config commit.gpgsign "1true" && + test_cmp_config 1true commit.gpgsign && + test_must_fail git config --bool --get commit.gpgsign 2>actual && + test_i18ngrep "bad boolean config value .1true. for .commit.gpgsign." actual +' + test_expect_success 'line number is reported correctly' ' printf "[bool]\n\tvar\n" >invalid && test_must_fail git config -f invalid --path bool.var 2>actual && diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh index 938ca17d78..ccbb116c01 100755 --- a/t/t1305-config-include.sh +++ b/t/t1305-config-include.sh @@ -352,9 +352,7 @@ test_expect_success 'include cycles are detected' ' git init --bare cycle && git -C cycle config include.path cycle && git config -f cycle/cycle include.path config && - test_must_fail \ - env GIT_TEST_GETTEXT_POISON=false \ - git -C cycle config --get-all test.value 2>stderr && + test_must_fail git -C cycle config --get-all test.value 2>stderr && grep "exceeded maximum include depth" stderr ' diff --git a/t/t1405-main-ref-store.sh b/t/t1405-main-ref-store.sh index 602e21957b..a237d9880e 100755 --- a/t/t1405-main-ref-store.sh +++ b/t/t1405-main-ref-store.sh @@ -17,13 +17,6 @@ test_expect_success 'pack_refs(PACK_REFS_ALL | PACK_REFS_PRUNE)' ' N=`find .git/refs -type f | wc -l` ' -test_expect_success 'peel_ref(new-tag)' ' - git rev-parse HEAD >expected && - git tag -a -m new-tag new-tag HEAD && - $RUN peel-ref refs/tags/new-tag >actual && - test_cmp expected actual -' - test_expect_success 'create_symref(FOO, refs/heads/main)' ' $RUN create-symref FOO refs/heads/main nothing && echo refs/heads/main >expected && @@ -32,6 +25,7 @@ test_expect_success 'create_symref(FOO, refs/heads/main)' ' ' test_expect_success 'delete_refs(FOO, refs/tags/new-tag)' ' + git tag -a -m new-tag new-tag HEAD && git rev-parse FOO -- && git rev-parse refs/tags/new-tag -- && m=$(git rev-parse main) && diff --git a/t/t1406-submodule-ref-store.sh b/t/t1406-submodule-ref-store.sh index a68c8f11a2..0a87058971 100755 --- a/t/t1406-submodule-ref-store.sh +++ b/t/t1406-submodule-ref-store.sh @@ -14,7 +14,8 @@ test_expect_success 'setup' ' ( cd sub && test_commit first && - git checkout -b new-main + git checkout -b new-main && + git tag -a -m new-tag new-tag HEAD ) ' @@ -22,13 +23,6 @@ test_expect_success 'pack_refs() not allowed' ' test_must_fail $RUN pack-refs 3 ' -test_expect_success 'peel_ref(new-tag)' ' - git -C sub rev-parse HEAD >expected && - git -C sub tag -a -m new-tag new-tag HEAD && - $RUN peel-ref refs/tags/new-tag >actual && - test_cmp expected actual -' - test_expect_success 'create_symref() not allowed' ' test_must_fail $RUN create-symref FOO refs/heads/main nothing ' diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh index ecccaa0634..27b9080251 100755 --- a/t/t1410-reflog.sh +++ b/t/t1410-reflog.sh @@ -158,6 +158,32 @@ test_expect_success 'reflog expire' ' check_fsck "dangling commit $K" ' +test_expect_success '--stale-fix handles missing objects generously' ' + git -c core.logAllRefUpdates=false fast-import --date-format=now <<-EOS && + commit refs/heads/stale-fix + mark :1 + committer Author <a@uth.or> now + data <<EOF + start stale fix + EOF + M 100644 inline file + data <<EOF + contents + EOF + commit refs/heads/stale-fix + committer Author <a@uth.or> now + data <<EOF + stale fix branch tip + EOF + from :1 + EOS + + parent_oid=$(git rev-parse stale-fix^) && + test_when_finished "recover $parent_oid" && + corrupt $parent_oid && + git reflog expire --stale-fix +' + test_expect_success 'prune and fsck' ' git prune && diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index a30fc5f74a..231243152b 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -40,17 +40,13 @@ test_expect_success 'HEAD is part of refs, valid objects appear valid' ' # specific corruption you test afterwards, lest a later test trip over # it. -test_expect_success 'setup: helpers for corruption tests' ' - sha1_file() { - remainder=${1#??} && - firsttwo=${1%$remainder} && - echo ".git/objects/$firsttwo/$remainder" - } && +sha1_file () { + git rev-parse --git-path objects/$(test_oid_to_path "$1") +} - remove_object() { - rm "$(sha1_file "$1")" - } -' +remove_object () { + rm "$(sha1_file "$1")" +} test_expect_success 'object with bad sha1' ' sha=$(echo blob | git hash-object -w --stdin) && @@ -662,13 +658,15 @@ test_expect_success 'fsck --name-objects' ' git init name-objects && ( cd name-objects && + git config core.logAllRefUpdates false && test_commit julius caesar.t && - test_commit augustus && - test_commit caesar && + test_commit augustus44 && + test_commit caesar && remove_object $(git rev-parse julius:caesar.t) && - test_must_fail git fsck --name-objects >out && tree=$(git rev-parse --verify julius:) && - test_i18ngrep "$tree (refs/tags/julius:" out + git tag -d julius && + test_must_fail git fsck --name-objects >out && + test_i18ngrep "$tree (refs/tags/augustus44\\^:" out ) ' diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh index abdda360d5..deae916707 100755 --- a/t/t1500-rev-parse.sh +++ b/t/t1500-rev-parse.sh @@ -243,4 +243,19 @@ test_expect_success 'showing the superproject correctly' ' test_cmp expect out ' +# at least one external project depends on this behavior: +test_expect_success 'rev-parse --since= unsqueezed ordering' ' + x1=--since=1970-01-01T00:00:01Z && + x2=--since=1970-01-01T00:00:02Z && + x3=--since=1970-01-01T00:00:03Z && + git rev-parse $x1 $x1 $x3 $x2 >actual && + cat >expect <<-EOF && + --max-age=1 + --max-age=1 + --max-age=3 + --max-age=2 + EOF + test_cmp expect actual +' + test_done diff --git a/t/t2402-worktree-list.sh b/t/t2402-worktree-list.sh index 821a20f3d6..42d35d9ae8 100755 --- a/t/t2402-worktree-list.sh +++ b/t/t2402-worktree-list.sh @@ -69,11 +69,107 @@ test_expect_success '"list" all worktrees with locked annotation' ' git worktree add --detach locked main && git worktree add --detach unlocked main && git worktree lock locked && + test_when_finished "git worktree unlock locked" && git worktree list >out && grep "/locked *[0-9a-f].* locked$" out && ! grep "/unlocked *[0-9a-f].* locked$" out ' +test_expect_success '"list" all worktrees --porcelain with locked' ' + test_when_finished "rm -rf locked1 locked2 unlocked out actual expect && git worktree prune" && + echo "locked" >expect && + echo "locked with reason" >>expect && + git worktree add --detach locked1 && + git worktree add --detach locked2 && + # unlocked worktree should not be annotated with "locked" + git worktree add --detach unlocked && + git worktree lock locked1 && + test_when_finished "git worktree unlock locked1" && + git worktree lock locked2 --reason "with reason" && + test_when_finished "git worktree unlock locked2" && + git worktree list --porcelain >out && + grep "^locked" out >actual && + test_cmp expect actual +' + +test_expect_success '"list" all worktrees --porcelain with locked reason newline escaped' ' + test_when_finished "rm -rf locked_lf locked_crlf out actual expect && git worktree prune" && + printf "locked \"locked\\\\r\\\\nreason\"\n" >expect && + printf "locked \"locked\\\\nreason\"\n" >>expect && + git worktree add --detach locked_lf && + git worktree add --detach locked_crlf && + git worktree lock locked_lf --reason "$(printf "locked\nreason")" && + test_when_finished "git worktree unlock locked_lf" && + git worktree lock locked_crlf --reason "$(printf "locked\r\nreason")" && + test_when_finished "git worktree unlock locked_crlf" && + git worktree list --porcelain >out && + grep "^locked" out >actual && + test_cmp expect actual +' + +test_expect_success '"list" all worktrees with prunable annotation' ' + test_when_finished "rm -rf prunable unprunable out && git worktree prune" && + git worktree add --detach prunable && + git worktree add --detach unprunable && + rm -rf prunable && + git worktree list >out && + grep "/prunable *[0-9a-f].* prunable$" out && + ! grep "/unprunable *[0-9a-f].* prunable$" +' + +test_expect_success '"list" all worktrees --porcelain with prunable' ' + test_when_finished "rm -rf prunable out && git worktree prune" && + git worktree add --detach prunable && + rm -rf prunable && + git worktree list --porcelain >out && + sed -n "/^worktree .*\/prunable$/,/^$/p" <out >only_prunable && + test_i18ngrep "^prunable gitdir file points to non-existent location$" only_prunable +' + +test_expect_success '"list" all worktrees with prunable consistent with "prune"' ' + test_when_finished "rm -rf prunable unprunable out && git worktree prune" && + git worktree add --detach prunable && + git worktree add --detach unprunable && + rm -rf prunable && + git worktree list >out && + grep "/prunable *[0-9a-f].* prunable$" out && + ! grep "/unprunable *[0-9a-f].* unprunable$" out && + git worktree prune --verbose >out && + test_i18ngrep "^Removing worktrees/prunable" out && + test_i18ngrep ! "^Removing worktrees/unprunable" out +' + +test_expect_success '"list" --verbose and --porcelain mutually exclusive' ' + test_must_fail git worktree list --verbose --porcelain +' + +test_expect_success '"list" all worktrees --verbose with locked' ' + test_when_finished "rm -rf locked1 locked2 out actual expect && git worktree prune" && + git worktree add locked1 --detach && + git worktree add locked2 --detach && + git worktree lock locked1 && + test_when_finished "git worktree unlock locked1" && + git worktree lock locked2 --reason "with reason" && + test_when_finished "git worktree unlock locked2" && + echo "$(git -C locked2 rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >expect && + printf "\tlocked: with reason\n" >>expect && + git worktree list --verbose >out && + grep "/locked1 *[0-9a-f].* locked$" out && + sed -n "s/ */ /g;/\/locked2 *[0-9a-f].*$/,/locked: .*$/p" <out >actual && + test_cmp actual expect +' + +test_expect_success '"list" all worktrees --verbose with prunable' ' + test_when_finished "rm -rf prunable out actual expect && git worktree prune" && + git worktree add prunable --detach && + echo "$(git -C prunable rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >expect && + printf "\tprunable: gitdir file points to non-existent location\n" >>expect && + rm -rf prunable && + git worktree list --verbose >out && + sed -n "s/ */ /g;/\/prunable *[0-9a-f].*$/,/prunable: .*$/p" <out >actual && + test_i18ncmp actual expect +' + test_expect_success 'bare repo setup' ' git init --bare bare1 && echo "data" >file1 && diff --git a/t/t3012-ls-files-dedup.sh b/t/t3012-ls-files-dedup.sh new file mode 100755 index 0000000000..2682b1f43a --- /dev/null +++ b/t/t3012-ls-files-dedup.sh @@ -0,0 +1,66 @@ +#!/bin/sh + +test_description='git ls-files --deduplicate test' + +. ./test-lib.sh + +test_expect_success 'setup' ' + >a.txt && + >b.txt && + >delete.txt && + git add a.txt b.txt delete.txt && + git commit -m base && + echo a >a.txt && + echo b >b.txt && + echo delete >delete.txt && + git add a.txt b.txt delete.txt && + git commit -m tip && + git tag tip && + git reset --hard HEAD^ && + echo change >a.txt && + git commit -a -m side && + git tag side +' + +test_expect_success 'git ls-files --deduplicate to show unique unmerged path' ' + test_must_fail git merge tip && + git ls-files --deduplicate >actual && + cat >expect <<-\EOF && + a.txt + b.txt + delete.txt + EOF + test_cmp expect actual && + git merge --abort +' + +test_expect_success 'git ls-files -d -m --deduplicate with different display options' ' + git reset --hard side && + test_must_fail git merge tip && + rm delete.txt && + git ls-files -d -m --deduplicate >actual && + cat >expect <<-\EOF && + a.txt + delete.txt + EOF + test_cmp expect actual && + git ls-files -d -m -t --deduplicate >actual && + cat >expect <<-\EOF && + C a.txt + C a.txt + C a.txt + R delete.txt + C delete.txt + EOF + test_cmp expect actual && + git ls-files -d -m -c --deduplicate >actual && + cat >expect <<-\EOF && + a.txt + b.txt + delete.txt + EOF + test_cmp expect actual && + git merge --abort +' + +test_done diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh index ce24b5d735..1b26c4c2ef 100755 --- a/t/t3206-range-diff.sh +++ b/t/t3206-range-diff.sh @@ -153,6 +153,19 @@ test_expect_success 'simple A B C (unmodified)' ' test_cmp expect actual ' +test_expect_success 'A^! and A^-<n> (unmodified)' ' + git range-diff --no-color topic^! unmodified^-1 >actual && + cat >expect <<-EOF && + 1: $(test_oid t4) = 1: $(test_oid u4) s/12/B/ + EOF + test_cmp expect actual +' + +test_expect_success 'A^{/..} is not mistaken for a range' ' + test_must_fail git range-diff topic^.. topic^{/..} 2>error && + test_i18ngrep "not a commit range" error +' + test_expect_success 'trivial reordering' ' git range-diff --no-color main topic reordered >actual && cat >expect <<-EOF && @@ -720,4 +733,19 @@ test_expect_success 'format-patch --range-diff with multiple notes' ' test_cmp expect actual ' +test_expect_success '--left-only/--right-only' ' + git switch --orphan left-right && + test_commit first && + test_commit unmatched && + test_commit common && + git switch -C left-right first && + git cherry-pick common && + + git range-diff -s --left-only ...common >actual && + head_oid=$(git rev-parse --short HEAD) && + common_oid=$(git rev-parse --short common) && + echo "1: $head_oid = 2: $common_oid common" >expect && + test_cmp expect actual +' + test_done diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh index c02729d5ea..77a313f62e 100755 --- a/t/t3406-rebase-message.sh +++ b/t/t3406-rebase-message.sh @@ -67,13 +67,6 @@ test_expect_success 'rebase -n overrides config rebase.stat config' ' ! grep "^ fileX | *1 +$" diffstat.txt ' -# Output to stderr: -# -# "Does not point to a valid commit: invalid-ref" -# -# NEEDSWORK: This "grep" is fine in real non-C locales, but -# GIT_TEST_GETTEXT_POISON poisons the refname along with the enclosing -# error message. test_expect_success 'rebase --onto outputs the invalid ref' ' test_must_fail git rebase --onto invalid-ref HEAD HEAD 2>err && test_i18ngrep "invalid-ref" err diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh index e7087befd4..36f169d7f1 100755 --- a/t/t3415-rebase-autosquash.sh +++ b/t/t3415-rebase-autosquash.sh @@ -443,4 +443,12 @@ 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_done diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index dff1228669..7547f11a5c 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -243,7 +243,7 @@ test_expect_success 'refresh index before checking if it is up-to-date' ' test_path_is_missing frotz/nitfol ' -test_expect_success 'choking "git rm" should not let it die with cruft' ' +choke_git_rm_setup() { git reset -q --hard && test_when_finished "rm -f .git/index.lock && git reset -q --hard" && i=0 && @@ -252,12 +252,24 @@ test_expect_success 'choking "git rm" should not let it die with cruft' ' do echo "100644 $hash 0 some-file-$i" i=$(( $i + 1 )) - done | git update-index --index-info && + done | git update-index --index-info +} + +test_expect_success 'choking "git rm" should not let it die with cruft (induce SIGPIPE)' ' + choke_git_rm_setup && # git command is intentionally placed upstream of pipe to induce SIGPIPE git rm -n "some-file-*" | : && test_path_is_missing .git/index.lock ' + +test_expect_success !MINGW 'choking "git rm" should not let it die with cruft (induce and check SIGPIPE)' ' + choke_git_rm_setup && + OUT=$( ((trap "" PIPE; git rm -n "some-file-*"; echo $? 1>&3) | :) 3>&1 ) && + test_match_signal 13 "$OUT" && + test_path_is_missing .git/index.lock +' + test_expect_success 'Resolving by removal is not a warning-worthy event' ' git reset -q --hard && test_when_finished "rm -f .git/index.lock msg && git reset -q --hard" && diff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh index f075c7f1f3..b26a97aef4 100755 --- a/t/t3905-stash-include-untracked.sh +++ b/t/t3905-stash-include-untracked.sh @@ -8,16 +8,16 @@ test_description='Test git stash --include-untracked' . ./test-lib.sh test_expect_success 'stash save --include-untracked some dirty working directory' ' - echo 1 > file && + echo 1 >file && git add file && test_tick && git commit -m initial && - echo 2 > file && + echo 2 >file && git add file && - echo 3 > file && + echo 3 >file && test_tick && - echo 1 > file2 && - echo 1 > HEAD && + echo 1 >file2 && + echo 1 >HEAD && mkdir untracked && echo untracked >untracked/untracked && git stash --include-untracked && @@ -25,48 +25,50 @@ test_expect_success 'stash save --include-untracked some dirty working directory git diff-index --cached --quiet HEAD ' -cat > expect <<EOF -?? actual -?? expect -EOF - test_expect_success 'stash save --include-untracked cleaned the untracked files' ' + cat >expect <<-EOF && + ?? actual + ?? expect + EOF + git status --porcelain >actual && test_cmp expect actual ' -tracked=$(git rev-parse --short $(echo 1 | git hash-object --stdin)) -untracked=$(git rev-parse --short $(echo untracked | git hash-object --stdin)) -cat > expect.diff <<EOF -diff --git a/HEAD b/HEAD -new file mode 100644 -index 0000000..$tracked ---- /dev/null -+++ b/HEAD -@@ -0,0 +1 @@ -+1 -diff --git a/file2 b/file2 -new file mode 100644 -index 0000000..$tracked ---- /dev/null -+++ b/file2 -@@ -0,0 +1 @@ -+1 -diff --git a/untracked/untracked b/untracked/untracked -new file mode 100644 -index 0000000..$untracked ---- /dev/null -+++ b/untracked/untracked -@@ -0,0 +1 @@ -+untracked -EOF -cat > expect.lstree <<EOF -HEAD -file2 -untracked -EOF - test_expect_success 'stash save --include-untracked stashed the untracked files' ' + one_blob=$(echo 1 | git hash-object --stdin) && + tracked=$(git rev-parse --short "$one_blob") && + untracked_blob=$(echo untracked | git hash-object --stdin) && + untracked=$(git rev-parse --short "$untracked_blob") && + cat >expect.diff <<-EOF && + diff --git a/HEAD b/HEAD + new file mode 100644 + index 0000000..$tracked + --- /dev/null + +++ b/HEAD + @@ -0,0 +1 @@ + +1 + diff --git a/file2 b/file2 + new file mode 100644 + index 0000000..$tracked + --- /dev/null + +++ b/file2 + @@ -0,0 +1 @@ + +1 + diff --git a/untracked/untracked b/untracked/untracked + new file mode 100644 + index 0000000..$untracked + --- /dev/null + +++ b/untracked/untracked + @@ -0,0 +1 @@ + +untracked + EOF + cat >expect.lstree <<-EOF && + HEAD + file2 + untracked + EOF + test_path_is_missing file2 && test_path_is_missing untracked && test_path_is_missing HEAD && @@ -83,57 +85,64 @@ test_expect_success 'stash save --patch --all fails' ' test_must_fail git stash --patch --all ' -git clean --force --quiet +test_expect_success 'clean up untracked/untracked file to prepare for next tests' ' + git clean --force --quiet -cat > expect <<EOF - M file -?? HEAD -?? actual -?? expect -?? file2 -?? untracked/ -EOF +' test_expect_success 'stash pop after save --include-untracked leaves files untracked again' ' + cat >expect <<-EOF && + M file + ?? HEAD + ?? actual + ?? expect + ?? file2 + ?? untracked/ + EOF + git stash pop && git status --porcelain >actual && test_cmp expect actual && - test "1" = "$(cat file2)" && - test untracked = "$(cat untracked/untracked)" + echo 1 >expect_file2 && + test_cmp expect_file2 file2 && + echo untracked >untracked_expect && + test_cmp untracked_expect untracked/untracked ' -git clean --force --quiet -d +test_expect_success 'clean up untracked/ directory to prepare for next tests' ' + git clean --force --quiet -d +' test_expect_success 'stash save -u dirty index' ' - echo 4 > file3 && + echo 4 >file3 && git add file3 && test_tick && git stash -u ' -blob=$(git rev-parse --short $(echo 4 | git hash-object --stdin)) -cat > expect <<EOF -diff --git a/file3 b/file3 -new file mode 100644 -index 0000000..$blob ---- /dev/null -+++ b/file3 -@@ -0,0 +1 @@ -+4 -EOF - test_expect_success 'stash save --include-untracked dirty index got stashed' ' + four_blob=$(echo 4 | git hash-object --stdin) && + blob=$(git rev-parse --short "$four_blob") && + cat >expect <<-EOF && + diff --git a/file3 b/file3 + new file mode 100644 + index 0000000..$blob + --- /dev/null + +++ b/file3 + @@ -0,0 +1 @@ + +4 + EOF + git stash pop --index && + test_when_finished "git reset" && git diff --cached >actual && test_cmp expect actual ' -git reset > /dev/null - # Must direct output somewhere where it won't be considered an untracked file test_expect_success 'stash save --include-untracked -q is quiet' ' - echo 1 > file5 && - git stash save --include-untracked --quiet > .git/stash-output.out 2>&1 && + echo 1 >file5 && + git stash save --include-untracked --quiet >.git/stash-output.out 2>&1 && test_line_count = 0 .git/stash-output.out && rm -f .git/stash-output.out ' @@ -141,35 +150,34 @@ test_expect_success 'stash save --include-untracked -q is quiet' ' test_expect_success 'stash save --include-untracked removed files' ' rm -f file && git stash save --include-untracked && - echo 1 > expect && + echo 1 >expect && + test_when_finished "rm -f expect" && test_cmp expect file ' -rm -f expect - test_expect_success 'stash save --include-untracked removed files got stashed' ' git stash pop && test_path_is_missing file ' -cat > .gitignore <<EOF -.gitignore -ignored -ignored.d/ -EOF - test_expect_success 'stash save --include-untracked respects .gitignore' ' - echo ignored > ignored && + cat >.gitignore <<-EOF && + .gitignore + ignored + ignored.d/ + EOF + + echo ignored >ignored && mkdir ignored.d && echo ignored >ignored.d/untracked && git stash -u && - test -s ignored && - test -s ignored.d/untracked && - test -s .gitignore + test_file_not_empty ignored && + test_file_not_empty ignored.d/untracked && + test_file_not_empty .gitignore ' test_expect_success 'stash save -u can stash with only untracked files different' ' - echo 4 > file4 && + echo 4 >file4 && git stash -u && test_path_is_missing file4 ' @@ -183,9 +191,9 @@ test_expect_success 'stash save --all does not respect .gitignore' ' test_expect_success 'stash save --all is stash poppable' ' git stash pop && - test -s ignored && - test -s ignored.d/untracked && - test -s .gitignore + test_file_not_empty ignored && + test_file_not_empty ignored.d/untracked && + test_file_not_empty .gitignore ' test_expect_success 'stash push --include-untracked with pathspec' ' @@ -214,17 +222,17 @@ test_expect_success 'stash push with $IFS character' ' test_path_is_file bar ' -cat > .gitignore <<EOF -ignored -ignored.d/* -EOF - test_expect_success 'stash previously ignored file' ' + cat >.gitignore <<-EOF && + ignored + ignored.d/* + EOF + git reset HEAD && git add .gitignore && git commit -m "Add .gitignore" && >ignored.d/foo && - echo "!ignored.d/foo" >> .gitignore && + echo "!ignored.d/foo" >>.gitignore && git stash save --include-untracked && test_path_is_missing ignored.d/foo && git stash pop && diff --git a/t/t3910-mac-os-precompose.sh b/t/t3910-mac-os-precompose.sh index a0b9208ce8..898267a6bd 100755 --- a/t/t3910-mac-os-precompose.sh +++ b/t/t3910-mac-os-precompose.sh @@ -194,6 +194,22 @@ test_expect_failure 'handle existing decomposed filenames' ' test_must_be_empty untracked ' +test_expect_success "unicode decomposed: git restore -p . " ' + DIRNAMEPWD=dir.Odiarnfc && + DIRNAMEINREPO=dir.$Adiarnfc && + export DIRNAMEPWD DIRNAMEINREPO && + git init "$DIRNAMEPWD" && + ( + cd "$DIRNAMEPWD" && + mkdir "$DIRNAMEINREPO" && + cd "$DIRNAMEINREPO" && + echo "Initial" >file && + git add file && + echo "More stuff" >>file && + echo y | git restore -p . + ) +' + # Test if the global core.precomposeunicode stops autosensing # Must be the last test case test_expect_success "respect git config --global core.precomposeunicode" ' diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index 45f68ebcdb..ce6aa3914f 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -178,6 +178,7 @@ process_diffs () { V=$(git version | sed -e 's/^git version //' -e 's/\./\\./g') while read magic cmd do + status=success case "$magic" in '' | '#'*) continue ;; @@ -186,6 +187,10 @@ do label="$magic-$cmd" case "$magic" in noellipses) ;; + failure) + status=failure + magic= + label="$cmd" ;; *) BUG "unknown magic $magic" ;; esac ;; @@ -198,7 +203,7 @@ do expect="$TEST_DIRECTORY/t4013/diff.$test" actual="$pfx-diff.$test" - test_expect_success "git $cmd # magic is ${magic:-(not used)}" ' + test_expect_$status "git $cmd # magic is ${magic:-(not used)}" ' { echo "$ git $cmd" case "$magic" in @@ -326,8 +331,12 @@ log --no-diff-merges -p --first-parent master log --diff-merges=off -p --first-parent master log --first-parent --diff-merges=off -p master log -p --first-parent master +log -p --diff-merges=first-parent master +log --diff-merges=first-parent master log -m -p --first-parent master log -m -p master +log --cc -m -p master +log -c -m -p master log -SF master log -S F master log -SF -p master diff --git a/t/t4013/diff.log_--cc_-m_-p_master b/t/t4013/diff.log_--cc_-m_-p_master new file mode 100644 index 0000000000..7c217cf348 --- /dev/null +++ b/t/t4013/diff.log_--cc_-m_-p_master @@ -0,0 +1,200 @@ +$ git log --cc -m -p master +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0) +Merge: 9a6d494 c7a2ab9 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:04:00 2006 +0000 + + Merge branch 'side' + +diff --git a/dir/sub b/dir/sub +index cead32e..992913c 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -4,3 +4,5 @@ C + D + E + F ++1 ++2 +diff --git a/file0 b/file0 +index b414108..10a8a9f 100644 +--- a/file0 ++++ b/file0 +@@ -4,3 +4,6 @@ + 4 + 5 + 6 ++A ++B ++C + +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a) +Merge: 9a6d494 c7a2ab9 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:04:00 2006 +0000 + + Merge branch 'side' + +diff --git a/dir/sub b/dir/sub +index 7289e35..992913c 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,4 +1,8 @@ + A + B ++C ++D ++E ++F + 1 + 2 +diff --git a/file0 b/file0 +index f4615da..10a8a9f 100644 +--- a/file0 ++++ b/file0 +@@ -1,6 +1,9 @@ + 1 + 2 + 3 ++4 ++5 ++6 + A + B + C +diff --git a/file1 b/file1 +new file mode 100644 +index 0000000..b1e6722 +--- /dev/null ++++ b/file1 +@@ -0,0 +1,3 @@ ++A ++B ++C +diff --git a/file2 b/file2 +deleted file mode 100644 +index 01e79c3..0000000 +--- a/file2 ++++ /dev/null +@@ -1,3 +0,0 @@ +-1 +-2 +-3 +diff --git a/file3 b/file3 +deleted file mode 100644 +index 7289e35..0000000 +--- a/file3 ++++ /dev/null +@@ -1,4 +0,0 @@ +-A +-B +-1 +-2 + +commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:03:00 2006 +0000 + + Side + +diff --git a/dir/sub b/dir/sub +index 35d242b..7289e35 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++1 ++2 +diff --git a/file0 b/file0 +index 01e79c3..f4615da 100644 +--- a/file0 ++++ b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++A ++B ++C +diff --git a/file3 b/file3 +new file mode 100644 +index 0000000..7289e35 +--- /dev/null ++++ b/file3 +@@ -0,0 +1,4 @@ ++A ++B ++1 ++2 + +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third + +diff --git a/dir/sub b/dir/sub +index 8422d40..cead32e 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -2,3 +2,5 @@ A + B + C + D ++E ++F +diff --git a/file1 b/file1 +new file mode 100644 +index 0000000..b1e6722 +--- /dev/null ++++ b/file1 +@@ -0,0 +1,3 @@ ++A ++B ++C + +commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:01:00 2006 +0000 + + Second + + This is the second commit. + +diff --git a/dir/sub b/dir/sub +index 35d242b..8422d40 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++C ++D +diff --git a/file0 b/file0 +index 01e79c3..b414108 100644 +--- a/file0 ++++ b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++4 ++5 ++6 +diff --git a/file2 b/file2 +deleted file mode 100644 +index 01e79c3..0000000 +--- a/file2 ++++ /dev/null +@@ -1,3 +0,0 @@ +-1 +-2 +-3 + +commit 444ac553ac7612cc88969031b02b3767fb8a353a +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:00:00 2006 +0000 + + Initial +$ diff --git a/t/t4013/diff.log_--diff-merges=first-parent_master b/t/t4013/diff.log_--diff-merges=first-parent_master new file mode 100644 index 0000000000..fa63a557dd --- /dev/null +++ b/t/t4013/diff.log_--diff-merges=first-parent_master @@ -0,0 +1,56 @@ +$ git log --diff-merges=first-parent master +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 +Merge: 9a6d494 c7a2ab9 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:04:00 2006 +0000 + + Merge branch 'side' + +diff --git a/dir/sub b/dir/sub +index cead32e..992913c 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -4,3 +4,5 @@ C + D + E + F ++1 ++2 +diff --git a/file0 b/file0 +index b414108..10a8a9f 100644 +--- a/file0 ++++ b/file0 +@@ -4,3 +4,6 @@ + 4 + 5 + 6 ++A ++B ++C + +commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:03:00 2006 +0000 + + Side + +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third + +commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:01:00 2006 +0000 + + Second + + This is the second commit. + +commit 444ac553ac7612cc88969031b02b3767fb8a353a +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:00:00 2006 +0000 + + Initial +$ diff --git a/t/t4013/diff.log_-c_-m_-p_master b/t/t4013/diff.log_-c_-m_-p_master new file mode 100644 index 0000000000..b660f3d5f2 --- /dev/null +++ b/t/t4013/diff.log_-c_-m_-p_master @@ -0,0 +1,200 @@ +$ git log -c -m -p master +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0) +Merge: 9a6d494 c7a2ab9 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:04:00 2006 +0000 + + Merge branch 'side' + +diff --git a/dir/sub b/dir/sub +index cead32e..992913c 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -4,3 +4,5 @@ C + D + E + F ++1 ++2 +diff --git a/file0 b/file0 +index b414108..10a8a9f 100644 +--- a/file0 ++++ b/file0 +@@ -4,3 +4,6 @@ + 4 + 5 + 6 ++A ++B ++C + +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a) +Merge: 9a6d494 c7a2ab9 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:04:00 2006 +0000 + + Merge branch 'side' + +diff --git a/dir/sub b/dir/sub +index 7289e35..992913c 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,4 +1,8 @@ + A + B ++C ++D ++E ++F + 1 + 2 +diff --git a/file0 b/file0 +index f4615da..10a8a9f 100644 +--- a/file0 ++++ b/file0 +@@ -1,6 +1,9 @@ + 1 + 2 + 3 ++4 ++5 ++6 + A + B + C +diff --git a/file1 b/file1 +new file mode 100644 +index 0000000..b1e6722 +--- /dev/null ++++ b/file1 +@@ -0,0 +1,3 @@ ++A ++B ++C +diff --git a/file2 b/file2 +deleted file mode 100644 +index 01e79c3..0000000 +--- a/file2 ++++ /dev/null +@@ -1,3 +0,0 @@ +-1 +-2 +-3 +diff --git a/file3 b/file3 +deleted file mode 100644 +index 7289e35..0000000 +--- a/file3 ++++ /dev/null +@@ -1,4 +0,0 @@ +-A +-B +-1 +-2 + +commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:03:00 2006 +0000 + + Side + +diff --git a/dir/sub b/dir/sub +index 35d242b..7289e35 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++1 ++2 +diff --git a/file0 b/file0 +index 01e79c3..f4615da 100644 +--- a/file0 ++++ b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++A ++B ++C +diff --git a/file3 b/file3 +new file mode 100644 +index 0000000..7289e35 +--- /dev/null ++++ b/file3 +@@ -0,0 +1,4 @@ ++A ++B ++1 ++2 + +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third + +diff --git a/dir/sub b/dir/sub +index 8422d40..cead32e 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -2,3 +2,5 @@ A + B + C + D ++E ++F +diff --git a/file1 b/file1 +new file mode 100644 +index 0000000..b1e6722 +--- /dev/null ++++ b/file1 +@@ -0,0 +1,3 @@ ++A ++B ++C + +commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:01:00 2006 +0000 + + Second + + This is the second commit. + +diff --git a/dir/sub b/dir/sub +index 35d242b..8422d40 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++C ++D +diff --git a/file0 b/file0 +index 01e79c3..b414108 100644 +--- a/file0 ++++ b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++4 ++5 ++6 +diff --git a/file2 b/file2 +deleted file mode 100644 +index 01e79c3..0000000 +--- a/file2 ++++ /dev/null +@@ -1,3 +0,0 @@ +-1 +-2 +-3 + +commit 444ac553ac7612cc88969031b02b3767fb8a353a +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:00:00 2006 +0000 + + Initial +$ diff --git a/t/t4013/diff.log_-p_--diff-merges=first-parent_master b/t/t4013/diff.log_-p_--diff-merges=first-parent_master new file mode 100644 index 0000000000..9538a27511 --- /dev/null +++ b/t/t4013/diff.log_-p_--diff-merges=first-parent_master @@ -0,0 +1,137 @@ +$ git log -p --diff-merges=first-parent master +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 +Merge: 9a6d494 c7a2ab9 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:04:00 2006 +0000 + + Merge branch 'side' + +diff --git a/dir/sub b/dir/sub +index cead32e..992913c 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -4,3 +4,5 @@ C + D + E + F ++1 ++2 +diff --git a/file0 b/file0 +index b414108..10a8a9f 100644 +--- a/file0 ++++ b/file0 +@@ -4,3 +4,6 @@ + 4 + 5 + 6 ++A ++B ++C + +commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:03:00 2006 +0000 + + Side + +diff --git a/dir/sub b/dir/sub +index 35d242b..7289e35 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++1 ++2 +diff --git a/file0 b/file0 +index 01e79c3..f4615da 100644 +--- a/file0 ++++ b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++A ++B ++C +diff --git a/file3 b/file3 +new file mode 100644 +index 0000000..7289e35 +--- /dev/null ++++ b/file3 +@@ -0,0 +1,4 @@ ++A ++B ++1 ++2 + +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third + +diff --git a/dir/sub b/dir/sub +index 8422d40..cead32e 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -2,3 +2,5 @@ A + B + C + D ++E ++F +diff --git a/file1 b/file1 +new file mode 100644 +index 0000000..b1e6722 +--- /dev/null ++++ b/file1 +@@ -0,0 +1,3 @@ ++A ++B ++C + +commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:01:00 2006 +0000 + + Second + + This is the second commit. + +diff --git a/dir/sub b/dir/sub +index 35d242b..8422d40 100644 +--- a/dir/sub ++++ b/dir/sub +@@ -1,2 +1,4 @@ + A + B ++C ++D +diff --git a/file0 b/file0 +index 01e79c3..b414108 100644 +--- a/file0 ++++ b/file0 +@@ -1,3 +1,6 @@ + 1 + 2 + 3 ++4 ++5 ++6 +diff --git a/file2 b/file2 +deleted file mode 100644 +index 01e79c3..0000000 +--- a/file2 ++++ /dev/null +@@ -1,3 +0,0 @@ +-1 +-2 +-3 + +commit 444ac553ac7612cc88969031b02b3767fb8a353a +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:00:00 2006 +0000 + + Initial +$ diff --git a/t/t4203-mailmap.sh b/t/t4203-mailmap.sh index 621f9962d5..93caf9a46d 100755 --- a/t/t4203-mailmap.sh +++ b/t/t4203-mailmap.sh @@ -889,4 +889,47 @@ test_expect_success 'empty syntax: setup' ' test_cmp expect actual ' +test_expect_success 'set up mailmap location tests' ' + git init --bare loc-bare && + git --git-dir=loc-bare --work-tree=. commit \ + --allow-empty -m foo --author="Orig <orig@example.com>" && + echo "New <new@example.com> <orig@example.com>" >loc-bare/.mailmap +' + +test_expect_success 'bare repo with --work-tree finds mailmap at top-level' ' + git -C loc-bare --work-tree=. log -1 --format=%aE >actual && + echo new@example.com >expect && + test_cmp expect actual +' + +test_expect_success 'bare repo does not look in current directory' ' + git -C loc-bare log -1 --format=%aE >actual && + echo orig@example.com >expect && + test_cmp expect actual +' + +test_expect_success 'non-git shortlog respects mailmap in current dir' ' + git --git-dir=loc-bare log -1 >input && + nongit cp "$TRASH_DIRECTORY/loc-bare/.mailmap" . && + nongit git shortlog -s <input >actual && + echo " 1 New" >expect && + test_cmp expect actual +' + +test_expect_success 'shortlog on stdin respects mailmap from repo' ' + cp loc-bare/.mailmap . && + git shortlog -s <input >actual && + echo " 1 New" >expect && + test_cmp expect actual +' + +test_expect_success 'find top-level mailmap from subdir' ' + git clone loc-bare loc-wt && + cp loc-bare/.mailmap loc-wt && + mkdir loc-wt/subdir && + git -C loc-wt/subdir log -1 --format=%aE >actual && + echo new@example.com >expect && + test_cmp expect actual +' + test_done diff --git a/t/t4216-log-bloom.sh b/t/t4216-log-bloom.sh index 0f16c4b9d5..50f206db55 100755 --- a/t/t4216-log-bloom.sh +++ b/t/t4216-log-bloom.sh @@ -43,11 +43,11 @@ test_expect_success 'setup test - repo, commits, commit graph, log outputs' ' ' graph_read_expect () { - NUM_CHUNKS=5 + NUM_CHUNKS=6 cat >expect <<- EOF header: 43475048 1 $(test_oid oid_version) $NUM_CHUNKS 0 num_commits: $1 - chunks: oid_fanout oid_lookup commit_metadata bloom_indexes bloom_data + chunks: oid_fanout oid_lookup commit_metadata generation_data bloom_indexes bloom_data EOF test-tool read-graph >actual && test_cmp expect actual diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 3ebb0d3b65..7204799a0b 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -431,15 +431,33 @@ test_expect_success TAR_HUGE,LONG_IS_64BIT 'system tar can read our huge size' ' test_cmp expect actual ' -test_expect_success TIME_IS_64BIT 'set up repository with far-future commit' ' +test_expect_success TIME_IS_64BIT 'set up repository with far-future (2^34 - 1) commit' ' + rm -f .git/index && + echo foo >file && + git add file && + GIT_COMMITTER_DATE="@17179869183 +0000" \ + git commit -m "tempori parendum" +' + +test_expect_success TIME_IS_64BIT 'generate tar with far-future mtime' ' + git archive HEAD >future.tar +' + +test_expect_success TAR_HUGE,TIME_IS_64BIT,TIME_T_IS_64BIT 'system tar can read our future mtime' ' + echo 2514 >expect && + tar_info future.tar | cut -d" " -f2 >actual && + test_cmp expect actual +' + +test_expect_success TIME_IS_64BIT 'set up repository with far-far-future (2^36 + 1) commit' ' rm -f .git/index && echo content >file && git add file && - GIT_COMMITTER_DATE="@68719476737 +0000" \ + GIT_TEST_COMMIT_GRAPH=0 GIT_COMMITTER_DATE="@68719476737 +0000" \ git commit -m "tempori parendum" ' -test_expect_success TIME_IS_64BIT 'generate tar with future mtime' ' +test_expect_success TIME_IS_64BIT 'generate tar with far-far-future mtime' ' git archive HEAD >future.tar ' diff --git a/t/t5004-archive-corner-cases.sh b/t/t5004-archive-corner-cases.sh index 3e7b23cb32..2d32d0ed12 100755 --- a/t/t5004-archive-corner-cases.sh +++ b/t/t5004-archive-corner-cases.sh @@ -153,7 +153,8 @@ test_expect_success ZIPINFO 'zip archive with many entries' ' # check the number of entries in the ZIP file directory expr 65536 + 256 >expect && - "$ZIPINFO" many.zip | head -2 | sed -n "2s/.* //p" >actual && + "$ZIPINFO" -h many.zip >zipinfo && + sed -n "2s/.* //p" <zipinfo >actual && test_cmp expect actual ' diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh index 2ed0c1544d..5b15f1aa0f 100755 --- a/t/t5318-commit-graph.sh +++ b/t/t5318-commit-graph.sh @@ -76,7 +76,7 @@ graph_git_behavior 'no graph' full commits/3 commits/1 graph_read_expect() { OPTIONAL="" NUM_CHUNKS=3 - if test ! -z $2 + if test ! -z "$2" then OPTIONAL=" $2" NUM_CHUNKS=$((3 + $(echo "$2" | wc -w))) @@ -103,14 +103,14 @@ test_expect_success 'exit with correct error on bad input to --stdin-commits' ' # valid commit and tree OID git rev-parse HEAD HEAD^{tree} >in && git commit-graph write --stdin-commits <in && - graph_read_expect 3 + graph_read_expect 3 generation_data ' test_expect_success 'write graph' ' cd "$TRASH_DIRECTORY/full" && git commit-graph write && test_path_is_file $objdir/info/commit-graph && - graph_read_expect "3" + graph_read_expect "3" generation_data ' test_expect_success POSIXPERM 'write graph has correct permissions' ' @@ -219,7 +219,7 @@ test_expect_success 'write graph with merges' ' cd "$TRASH_DIRECTORY/full" && git commit-graph write && test_path_is_file $objdir/info/commit-graph && - graph_read_expect "10" "extra_edges" + graph_read_expect "10" "generation_data extra_edges" ' graph_git_behavior 'merge 1 vs 2' full merge/1 merge/2 @@ -254,7 +254,7 @@ test_expect_success 'write graph with new commit' ' cd "$TRASH_DIRECTORY/full" && git commit-graph write && test_path_is_file $objdir/info/commit-graph && - graph_read_expect "11" "extra_edges" + graph_read_expect "11" "generation_data extra_edges" ' graph_git_behavior 'full graph, commit 8 vs merge 1' full commits/8 merge/1 @@ -264,7 +264,7 @@ test_expect_success 'write graph with nothing new' ' cd "$TRASH_DIRECTORY/full" && git commit-graph write && test_path_is_file $objdir/info/commit-graph && - graph_read_expect "11" "extra_edges" + graph_read_expect "11" "generation_data extra_edges" ' graph_git_behavior 'cleared graph, commit 8 vs merge 1' full commits/8 merge/1 @@ -274,7 +274,7 @@ test_expect_success 'build graph from latest pack with closure' ' cd "$TRASH_DIRECTORY/full" && cat new-idx | git commit-graph write --stdin-packs && test_path_is_file $objdir/info/commit-graph && - graph_read_expect "9" "extra_edges" + graph_read_expect "9" "generation_data extra_edges" ' graph_git_behavior 'graph from pack, commit 8 vs merge 1' full commits/8 merge/1 @@ -287,7 +287,7 @@ test_expect_success 'build graph from commits with closure' ' git rev-parse merge/1 >>commits-in && cat commits-in | git commit-graph write --stdin-commits && test_path_is_file $objdir/info/commit-graph && - graph_read_expect "6" + graph_read_expect "6" "generation_data" ' graph_git_behavior 'graph from commits, commit 8 vs merge 1' full commits/8 merge/1 @@ -297,7 +297,7 @@ test_expect_success 'build graph from commits with append' ' cd "$TRASH_DIRECTORY/full" && git rev-parse merge/3 | git commit-graph write --stdin-commits --append && test_path_is_file $objdir/info/commit-graph && - graph_read_expect "10" "extra_edges" + graph_read_expect "10" "generation_data extra_edges" ' graph_git_behavior 'append graph, commit 8 vs merge 1' full commits/8 merge/1 @@ -307,7 +307,7 @@ test_expect_success 'build graph using --reachable' ' cd "$TRASH_DIRECTORY/full" && git commit-graph write --reachable && test_path_is_file $objdir/info/commit-graph && - graph_read_expect "11" "extra_edges" + graph_read_expect "11" "generation_data extra_edges" ' graph_git_behavior 'append graph, commit 8 vs merge 1' full commits/8 merge/1 @@ -328,7 +328,7 @@ test_expect_success 'write graph in bare repo' ' cd "$TRASH_DIRECTORY/bare" && git commit-graph write && test_path_is_file $baredir/info/commit-graph && - graph_read_expect "11" "extra_edges" + graph_read_expect "11" "generation_data extra_edges" ' graph_git_behavior 'bare repo with graph, commit 8 vs merge 1' bare commits/8 merge/1 @@ -446,6 +446,27 @@ test_expect_success 'warn on improper hash version' ' ) ' +test_expect_success 'lower layers have overflow chunk' ' + cd "$TRASH_DIRECTORY/full" && + UNIX_EPOCH_ZERO="@0 +0000" && + FUTURE_DATE="@2147483646 +0000" && + rm -f .git/objects/info/commit-graph && + test_commit --date "$FUTURE_DATE" future-1 && + test_commit --date "$UNIX_EPOCH_ZERO" old-1 && + git commit-graph write --reachable && + test_commit --date "$FUTURE_DATE" future-2 && + test_commit --date "$UNIX_EPOCH_ZERO" old-2 && + git commit-graph write --reachable --split=no-merge && + test_commit extra && + git commit-graph write --reachable --split=no-merge && + git commit-graph write --reachable && + graph_read_expect 16 "generation_data generation_data_overflow extra_edges" && + mv .git/objects/info/commit-graph commit-graph-upgraded && + git commit-graph write --reachable && + graph_read_expect 16 "generation_data generation_data_overflow extra_edges" && + test_cmp .git/objects/info/commit-graph commit-graph-upgraded +' + # the verify tests below expect the commit-graph to contain # exactly the commits reachable from the commits/8 branch. # If the file changes the set of commits in the list, then the @@ -454,8 +475,9 @@ test_expect_success 'warn on improper hash version' ' test_expect_success 'git commit-graph verify' ' cd "$TRASH_DIRECTORY/full" && - git rev-parse commits/8 | git commit-graph write --stdin-commits && - git commit-graph verify >output + git rev-parse commits/8 | GIT_TEST_COMMIT_GRAPH_NO_GDAT=1 git commit-graph write --stdin-commits && + git commit-graph verify >output && + graph_read_expect 9 extra_edges ' NUM_COMMITS=9 @@ -741,4 +763,56 @@ test_expect_success 'corrupt commit-graph write (missing tree)' ' ) ' +# We test the overflow-related code with the following repo history: +# +# 4:F - 5:N - 6:U +# / \ +# 1:U - 2:N - 3:U M:N +# \ / +# 7:N - 8:F - 9:N +# +# Here the commits denoted by U have committer date of zero seconds +# since Unix epoch, the commits denoted by N have committer date +# starting from 1112354055 seconds since Unix epoch (default committer +# date for the test suite), and the commits denoted by F have committer +# date of (2 ^ 31 - 2) seconds since Unix epoch. +# +# The largest offset observed is 2 ^ 31, just large enough to overflow. +# + +test_expect_success 'set up and verify repo with generation data overflow chunk' ' + objdir=".git/objects" && + UNIX_EPOCH_ZERO="@0 +0000" && + FUTURE_DATE="@2147483646 +0000" && + test_oid_cache <<-EOF && + oid_version sha1:1 + oid_version sha256:2 + EOF + cd "$TRASH_DIRECTORY" && + mkdir repo && + cd repo && + git init && + test_commit --date "$UNIX_EPOCH_ZERO" 1 && + test_commit 2 && + test_commit --date "$UNIX_EPOCH_ZERO" 3 && + git commit-graph write --reachable && + graph_read_expect 3 generation_data && + test_commit --date "$FUTURE_DATE" 4 && + test_commit 5 && + test_commit --date "$UNIX_EPOCH_ZERO" 6 && + git branch left && + git reset --hard 3 && + test_commit 7 && + test_commit --date "$FUTURE_DATE" 8 && + test_commit 9 && + git branch right && + git reset --hard 3 && + test_merge M left right && + git commit-graph write --reachable && + graph_read_expect 10 "generation_data generation_data_overflow" && + git commit-graph verify +' + +graph_git_behavior 'generation data overflow chunk repo' repo left right + test_done diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index 297de502a9..2fc3aadbd1 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -710,8 +710,9 @@ test_expect_success 'expire respects .keep files' ' PACKA=$(ls .git/objects/pack/a-pack*\.pack | sed s/\.pack\$//) && touch $PACKA.keep && git multi-pack-index expire && - ls -S .git/objects/pack/a-pack* | grep $PACKA >a-pack-files && - test_line_count = 3 a-pack-files && + test_path_is_file $PACKA.idx && + test_path_is_file $PACKA.keep && + test_path_is_file $PACKA.pack && test-tool read-midx .git/objects | grep idx >midx-list && test_line_count = 2 midx-list ) diff --git a/t/t5324-split-commit-graph.sh b/t/t5324-split-commit-graph.sh index 4d3842b83b..8e90f3423b 100755 --- a/t/t5324-split-commit-graph.sh +++ b/t/t5324-split-commit-graph.sh @@ -13,11 +13,11 @@ test_expect_success 'setup repo' ' infodir=".git/objects/info" && graphdir="$infodir/commit-graphs" && test_oid_cache <<-EOM - shallow sha1:1760 - shallow sha256:2064 + shallow sha1:2132 + shallow sha256:2436 - base sha1:1376 - base sha256:1496 + base sha1:1408 + base sha256:1528 oid_version sha1:1 oid_version sha256:2 @@ -31,9 +31,9 @@ graph_read_expect() { NUM_BASE=$2 fi cat >expect <<- EOF - header: 43475048 1 $(test_oid oid_version) 3 $NUM_BASE + header: 43475048 1 $(test_oid oid_version) 4 $NUM_BASE num_commits: $1 - chunks: oid_fanout oid_lookup commit_metadata + chunks: oid_fanout oid_lookup commit_metadata generation_data EOF test-tool read-graph >output && test_cmp expect output @@ -453,4 +453,185 @@ test_expect_success 'prevent regression for duplicate commits across layers' ' git -C dup commit-graph verify ' +NUM_FIRST_LAYER_COMMITS=64 +NUM_SECOND_LAYER_COMMITS=16 +NUM_THIRD_LAYER_COMMITS=7 +NUM_FOURTH_LAYER_COMMITS=8 +NUM_FIFTH_LAYER_COMMITS=16 +SECOND_LAYER_SEQUENCE_START=$(($NUM_FIRST_LAYER_COMMITS + 1)) +SECOND_LAYER_SEQUENCE_END=$(($SECOND_LAYER_SEQUENCE_START + $NUM_SECOND_LAYER_COMMITS - 1)) +THIRD_LAYER_SEQUENCE_START=$(($SECOND_LAYER_SEQUENCE_END + 1)) +THIRD_LAYER_SEQUENCE_END=$(($THIRD_LAYER_SEQUENCE_START + $NUM_THIRD_LAYER_COMMITS - 1)) +FOURTH_LAYER_SEQUENCE_START=$(($THIRD_LAYER_SEQUENCE_END + 1)) +FOURTH_LAYER_SEQUENCE_END=$(($FOURTH_LAYER_SEQUENCE_START + $NUM_FOURTH_LAYER_COMMITS - 1)) +FIFTH_LAYER_SEQUENCE_START=$(($FOURTH_LAYER_SEQUENCE_END + 1)) +FIFTH_LAYER_SEQUENCE_END=$(($FIFTH_LAYER_SEQUENCE_START + $NUM_FIFTH_LAYER_COMMITS - 1)) + +# Current split graph chain: +# +# 16 commits (No GDAT) +# ------------------------ +# 64 commits (GDAT) +# +test_expect_success 'setup repo for mixed generation commit-graph-chain' ' + graphdir=".git/objects/info/commit-graphs" && + test_oid_cache <<-EOF && + oid_version sha1:1 + oid_version sha256:2 + EOF + git init mixed && + ( + cd mixed && + git config core.commitGraph true && + git config gc.writeCommitGraph false && + for i in $(test_seq $NUM_FIRST_LAYER_COMMITS) + do + test_commit $i && + git branch commits/$i || return 1 + done && + git commit-graph write --reachable --split && + graph_read_expect $NUM_FIRST_LAYER_COMMITS && + test_line_count = 1 $graphdir/commit-graph-chain && + for i in $(test_seq $SECOND_LAYER_SEQUENCE_START $SECOND_LAYER_SEQUENCE_END) + do + test_commit $i && + git branch commits/$i || return 1 + done && + GIT_TEST_COMMIT_GRAPH_NO_GDAT=1 git commit-graph write --reachable --split=no-merge && + test_line_count = 2 $graphdir/commit-graph-chain && + test-tool read-graph >output && + cat >expect <<-EOF && + header: 43475048 1 $(test_oid oid_version) 4 1 + num_commits: $NUM_SECOND_LAYER_COMMITS + chunks: oid_fanout oid_lookup commit_metadata + EOF + test_cmp expect output && + git commit-graph verify && + cat $graphdir/commit-graph-chain + ) +' + +# The new layer will be added without generation data chunk as it was not +# present on the layer underneath it. +# +# 7 commits (No GDAT) +# ------------------------ +# 16 commits (No GDAT) +# ------------------------ +# 64 commits (GDAT) +# +test_expect_success 'do not write generation data chunk if not present on existing tip' ' + git clone mixed mixed-no-gdat && + ( + cd mixed-no-gdat && + for i in $(test_seq $THIRD_LAYER_SEQUENCE_START $THIRD_LAYER_SEQUENCE_END) + do + test_commit $i && + git branch commits/$i || return 1 + done && + git commit-graph write --reachable --split=no-merge && + test_line_count = 3 $graphdir/commit-graph-chain && + test-tool read-graph >output && + cat >expect <<-EOF && + header: 43475048 1 $(test_oid oid_version) 4 2 + num_commits: $NUM_THIRD_LAYER_COMMITS + chunks: oid_fanout oid_lookup commit_metadata + EOF + test_cmp expect output && + git commit-graph verify + ) +' + +# Number of commits in each layer of the split-commit graph before merge: +# +# 8 commits (No GDAT) +# ------------------------ +# 7 commits (No GDAT) +# ------------------------ +# 16 commits (No GDAT) +# ------------------------ +# 64 commits (GDAT) +# +# The top two layers are merged and do not have generation data chunk as layer below them does +# not have generation data chunk. +# +# 15 commits (No GDAT) +# ------------------------ +# 16 commits (No GDAT) +# ------------------------ +# 64 commits (GDAT) +# +test_expect_success 'do not write generation data chunk if the topmost remaining layer does not have generation data chunk' ' + git clone mixed-no-gdat mixed-merge-no-gdat && + ( + cd mixed-merge-no-gdat && + for i in $(test_seq $FOURTH_LAYER_SEQUENCE_START $FOURTH_LAYER_SEQUENCE_END) + do + test_commit $i && + git branch commits/$i || return 1 + done && + git commit-graph write --reachable --split --size-multiple 1 && + test_line_count = 3 $graphdir/commit-graph-chain && + test-tool read-graph >output && + cat >expect <<-EOF && + header: 43475048 1 $(test_oid oid_version) 4 2 + num_commits: $(($NUM_THIRD_LAYER_COMMITS + $NUM_FOURTH_LAYER_COMMITS)) + chunks: oid_fanout oid_lookup commit_metadata + EOF + test_cmp expect output && + git commit-graph verify + ) +' + +# Number of commits in each layer of the split-commit graph before merge: +# +# 16 commits (No GDAT) +# ------------------------ +# 15 commits (No GDAT) +# ------------------------ +# 16 commits (No GDAT) +# ------------------------ +# 64 commits (GDAT) +# +# The top three layers are merged and has generation data chunk as the topmost remaining layer +# has generation data chunk. +# +# 47 commits (GDAT) +# ------------------------ +# 64 commits (GDAT) +# +test_expect_success 'write generation data chunk if topmost remaining layer has generation data chunk' ' + git clone mixed-merge-no-gdat mixed-merge-gdat && + ( + cd mixed-merge-gdat && + for i in $(test_seq $FIFTH_LAYER_SEQUENCE_START $FIFTH_LAYER_SEQUENCE_END) + do + test_commit $i && + git branch commits/$i || return 1 + done && + git commit-graph write --reachable --split --size-multiple 1 && + test_line_count = 2 $graphdir/commit-graph-chain && + test-tool read-graph >output && + cat >expect <<-EOF && + header: 43475048 1 $(test_oid oid_version) 5 1 + num_commits: $(($NUM_SECOND_LAYER_COMMITS + $NUM_THIRD_LAYER_COMMITS + $NUM_FOURTH_LAYER_COMMITS + $NUM_FIFTH_LAYER_COMMITS)) + chunks: oid_fanout oid_lookup commit_metadata generation_data + EOF + test_cmp expect output + ) +' + +test_expect_success 'write generation data chunk when commit-graph chain is replaced' ' + git clone mixed mixed-replace && + ( + cd mixed-replace && + git commit-graph write --reachable --split=replace && + test_path_is_file $graphdir/commit-graph-chain && + test_line_count = 1 $graphdir/commit-graph-chain && + verify_chain_files_exist $graphdir && + graph_read_expect $(($NUM_FIRST_LAYER_COMMITS + $NUM_SECOND_LAYER_COMMITS)) && + git commit-graph verify + ) +' + test_done diff --git a/t/t5325-reverse-index.sh b/t/t5325-reverse-index.sh new file mode 100755 index 0000000000..da453f68d6 --- /dev/null +++ b/t/t5325-reverse-index.sh @@ -0,0 +1,120 @@ +#!/bin/sh + +test_description='on-disk reverse index' +. ./test-lib.sh + +# The below tests want control over the 'pack.writeReverseIndex' setting +# themselves to assert various combinations of it with other options. +sane_unset GIT_TEST_WRITE_REV_INDEX + +packdir=.git/objects/pack + +test_expect_success 'setup' ' + test_commit base && + + pack=$(git pack-objects --all $packdir/pack) && + rev=$packdir/pack-$pack.rev && + + test_path_is_missing $rev +' + +test_index_pack () { + rm -f $rev && + conf=$1 && + shift && + # remove the index since Windows won't overwrite an existing file + rm $packdir/pack-$pack.idx && + git -c pack.writeReverseIndex=$conf index-pack "$@" \ + $packdir/pack-$pack.pack +} + +test_expect_success 'index-pack with pack.writeReverseIndex' ' + test_index_pack "" && + test_path_is_missing $rev && + + test_index_pack false && + test_path_is_missing $rev && + + test_index_pack true && + test_path_is_file $rev +' + +test_expect_success 'index-pack with --[no-]rev-index' ' + for conf in "" true false + do + test_index_pack "$conf" --rev-index && + test_path_exists $rev && + + test_index_pack "$conf" --no-rev-index && + test_path_is_missing $rev + done +' + +test_expect_success 'index-pack can verify reverse indexes' ' + test_when_finished "rm -f $rev" && + test_index_pack true && + + test_path_is_file $rev && + git index-pack --rev-index --verify $packdir/pack-$pack.pack && + + # Intentionally corrupt the reverse index. + chmod u+w $rev && + printf "xxxx" | dd of=$rev bs=1 count=4 conv=notrunc && + + test_must_fail git index-pack --rev-index --verify \ + $packdir/pack-$pack.pack 2>err && + grep "validation error" err +' + +test_expect_success 'index-pack infers reverse index name with -o' ' + git index-pack --rev-index -o other.idx $packdir/pack-$pack.pack && + test_path_is_file other.idx && + test_path_is_file other.rev +' + +test_expect_success 'pack-objects respects pack.writeReverseIndex' ' + test_when_finished "rm -fr pack-1-*" && + + git -c pack.writeReverseIndex= pack-objects --all pack-1 && + test_path_is_missing pack-1-*.rev && + + git -c pack.writeReverseIndex=false pack-objects --all pack-1 && + test_path_is_missing pack-1-*.rev && + + git -c pack.writeReverseIndex=true pack-objects --all pack-1 && + test_path_is_file pack-1-*.rev +' + +test_expect_success 'reverse index is not generated when available on disk' ' + test_index_pack true && + test_path_is_file $rev && + + git rev-parse HEAD >tip && + GIT_TEST_REV_INDEX_DIE_IN_MEMORY=1 git cat-file \ + --batch-check="%(objectsize:disk)" <tip +' + +test_expect_success 'revindex in-memory vs on-disk' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + test_commit commit && + + git rev-list --objects --no-object-names --all >objects && + + git -c pack.writeReverseIndex=false repack -ad && + test_path_is_missing $packdir/pack-*.rev && + git cat-file --batch-check="%(objectsize:disk) %(objectname)" \ + <objects >in-core && + + git -c pack.writeReverseIndex=true repack -ad && + test_path_is_file $packdir/pack-*.rev && + git cat-file --batch-check="%(objectsize:disk) %(objectname)" \ + <objects >on-disk && + + test_cmp on-disk in-core + ) +' +test_done diff --git a/t/t5411/common-functions.sh b/t/t5411/common-functions.sh index 344d13f61a..6694858e18 100644 --- a/t/t5411/common-functions.sh +++ b/t/t5411/common-functions.sh @@ -36,9 +36,8 @@ create_commits_in () { # without having to worry about future changes of the commit ID and spaces # of the output. Single quotes are replaced with double quotes, because # it is boring to prepare unquoted single quotes in expect text. We also -# remove some locale error messages, which break test if we turn on -# `GIT_TEST_GETTEXT_POISON=true` in order to test unintentional translations -# on plumbing commands. +# remove some locale error messages. The emitted human-readable errors are +# redundant to the more machine-readable output the tests already assert. make_user_friendly_and_stable_output () { sed \ -e "s/ *\$//" \ @@ -59,3 +58,18 @@ filter_out_user_friendly_and_stable_output () { make_user_friendly_and_stable_output | sed -n ${1+"$@"} } + +test_cmp_refs () { + indir= + if test "$1" = "-C" + then + shift + indir="$1" + shift + fi + indir=${indir:+"$indir"/} + cat >show-ref.expect && + git ${indir:+ -C "$indir"} show-ref >show-ref.pristine && + make_user_friendly_and_stable_output <show-ref.pristine >show-ref.filtered && + test_cmp show-ref.expect show-ref.filtered +} diff --git a/t/t5411/once-0010-report-status-v1.sh b/t/t5411/once-0010-report-status-v1.sh index cb431a9c91..1233a46eac 100644 --- a/t/t5411/once-0010-report-status-v1.sh +++ b/t/t5411/once-0010-report-status-v1.sh @@ -83,12 +83,9 @@ test_expect_success "proc-receive: report status v1" ' EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/for/main/topic1 <COMMIT-A> refs/heads/foo <COMMIT-B> refs/heads/main EOF - test_cmp expect actual ' diff --git a/t/t5411/test-0000-standard-git-push.sh b/t/t5411/test-0000-standard-git-push.sh index 47b058af7e..e1e0175c12 100644 --- a/t/t5411/test-0000-standard-git-push.sh +++ b/t/t5411/test-0000-standard-git-push.sh @@ -19,13 +19,11 @@ test_expect_success "git-push ($PROTOCOL)" ' * [new branch] HEAD -> next EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-B> refs/heads/main <COMMIT-A> refs/heads/next EOF - test_cmp expect actual ' # Refs of upstream : main(B) next(A) @@ -35,24 +33,22 @@ test_expect_success "git-push --atomic ($PROTOCOL)" ' test_must_fail git -C workbench push --atomic origin \ main \ $B:refs/heads/next \ - >out 2>&1 && + >out-$test_count 2>&1 && filter_out_user_friendly_and_stable_output \ -e "/^To / { p; }" \ -e "/^ ! / { p; }" \ - <out >actual && + <out-$test_count >actual && cat >expect <<-EOF && To <URL/of/upstream.git> ! [rejected] main -> main (non-fast-forward) ! [rejected] <COMMIT-B> -> next (atomic push failed) EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-B> refs/heads/main <COMMIT-A> refs/heads/next EOF - test_cmp expect actual ' # Refs of upstream : main(B) next(A) @@ -65,8 +61,8 @@ test_expect_success "non-fast-forward git-push ($PROTOCOL)" ' push origin \ main \ $B:refs/heads/next \ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && remote: # pre-receive hook remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/next @@ -77,13 +73,11 @@ test_expect_success "non-fast-forward git-push ($PROTOCOL)" ' ! [rejected] main -> main (non-fast-forward) EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-B> refs/heads/main <COMMIT-B> refs/heads/next EOF - test_cmp expect actual ' # Refs of upstream : main(B) next(B) @@ -119,15 +113,13 @@ test_expect_success "git-push -f ($PROTOCOL)" ' * [new branch] HEAD -> a/b/c EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/a/b/c <COMMIT-A> refs/heads/main <COMMIT-A> refs/review/main/topic <TAG-v123> refs/tags/v123 EOF - test_cmp expect actual ' # Refs of upstream : main(A) tags/v123 refs/review/main/topic(A) a/b/c(A) diff --git a/t/t5411/test-0001-standard-git-push--porcelain.sh b/t/t5411/test-0001-standard-git-push--porcelain.sh index bbead12bbb..bcbda72341 100644 --- a/t/t5411/test-0001-standard-git-push--porcelain.sh +++ b/t/t5411/test-0001-standard-git-push--porcelain.sh @@ -20,13 +20,11 @@ test_expect_success "git-push ($PROTOCOL/porcelain)" ' Done EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-B> refs/heads/main <COMMIT-A> refs/heads/next EOF - test_cmp expect actual ' # Refs of upstream : main(B) next(A) @@ -36,25 +34,23 @@ test_expect_success "git-push --atomic ($PROTOCOL/porcelain)" ' test_must_fail git -C workbench push --atomic --porcelain origin \ main \ $B:refs/heads/next \ - >out 2>&1 && + >out-$test_count 2>&1 && filter_out_user_friendly_and_stable_output \ -e "s/^# GETTEXT POISON #//" \ -e "/^To / { p; }" \ -e "/^! / { p; }" \ - <out >actual && + <out-$test_count >actual && cat >expect <<-EOF && To <URL/of/upstream.git> ! refs/heads/main:refs/heads/main [rejected] (non-fast-forward) ! <COMMIT-B>:refs/heads/next [rejected] (atomic push failed) EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-B> refs/heads/main <COMMIT-A> refs/heads/next EOF - test_cmp expect actual ' # Refs of upstream : main(B) next(A) @@ -67,8 +63,8 @@ test_expect_success "non-fast-forward git-push ($PROTOCOL/porcelain)" ' push --porcelain origin \ main \ $B:refs/heads/next \ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && remote: # pre-receive hook remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/next @@ -80,13 +76,11 @@ test_expect_success "non-fast-forward git-push ($PROTOCOL/porcelain)" ' Done EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-B> refs/heads/main <COMMIT-B> refs/heads/next EOF - test_cmp expect actual ' # Refs of upstream : main(B) next(B) @@ -123,15 +117,13 @@ test_expect_success "git-push -f ($PROTOCOL/porcelain)" ' Done EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/a/b/c <COMMIT-A> refs/heads/main <COMMIT-A> refs/review/main/topic <TAG-v123> refs/tags/v123 EOF - test_cmp expect actual ' # Refs of upstream : main(A) tags/v123 refs/review/main/topic(A) a/b/c(A) diff --git a/t/t5411/test-0002-pre-receive-declined.sh b/t/t5411/test-0002-pre-receive-declined.sh index e7d113a158..0c3490c9b1 100644 --- a/t/t5411/test-0002-pre-receive-declined.sh +++ b/t/t5411/test-0002-pre-receive-declined.sh @@ -12,20 +12,18 @@ test_expect_success "git-push is declined ($PROTOCOL)" ' test_must_fail git -C workbench push origin \ $B:refs/heads/main \ HEAD:refs/heads/next \ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && To <URL/of/upstream.git> ! [remote rejected] <COMMIT-B> -> main (pre-receive hook declined) ! [remote rejected] HEAD -> next (pre-receive hook declined) EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "cleanup ($PROTOCOL)" ' diff --git a/t/t5411/test-0003-pre-receive-declined--porcelain.sh b/t/t5411/test-0003-pre-receive-declined--porcelain.sh index cc0cca6a47..e9c9db5d1f 100644 --- a/t/t5411/test-0003-pre-receive-declined--porcelain.sh +++ b/t/t5411/test-0003-pre-receive-declined--porcelain.sh @@ -12,8 +12,8 @@ test_expect_success "git-push is declined ($PROTOCOL/porcelain)" ' test_must_fail git -C workbench push --porcelain origin \ $B:refs/heads/main \ HEAD:refs/heads/next \ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && To <URL/of/upstream.git> ! <COMMIT-B>:refs/heads/main [remote rejected] (pre-receive hook declined) @@ -21,12 +21,10 @@ test_expect_success "git-push is declined ($PROTOCOL/porcelain)" ' Done EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "cleanup ($PROTOCOL/porcelain)" ' diff --git a/t/t5411/test-0011-no-hook-error.sh b/t/t5411/test-0011-no-hook-error.sh index c50830982f..3ef136e6ef 100644 --- a/t/t5411/test-0011-no-hook-error.sh +++ b/t/t5411/test-0011-no-hook-error.sh @@ -5,8 +5,8 @@ test_expect_success "proc-receive: no hook, fail to push special ref ($PROTOCOL) test_must_fail git -C workbench push origin \ HEAD:next \ HEAD:refs/for/main/topic \ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && remote: # pre-receive hook remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next @@ -19,13 +19,11 @@ test_expect_success "proc-receive: no hook, fail to push special ref ($PROTOCOL) ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook) EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main <COMMIT-A> refs/heads/next EOF - test_cmp expect actual ' # Refs of upstream : main(A) next(A) @@ -41,8 +39,8 @@ test_expect_success "proc-receive: no hook, all failed for atomic push ($PROTOCO test_must_fail git -C workbench push --atomic origin \ $B:main \ HEAD:next \ - HEAD:refs/for/main/topic >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + HEAD:refs/for/main/topic >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && remote: # pre-receive hook remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main @@ -55,10 +53,8 @@ test_expect_success "proc-receive: no hook, all failed for atomic push ($PROTOCO ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook) EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' diff --git a/t/t5411/test-0012-no-hook-error--porcelain.sh b/t/t5411/test-0012-no-hook-error--porcelain.sh index 14ea433481..19f66fbd7d 100644 --- a/t/t5411/test-0012-no-hook-error--porcelain.sh +++ b/t/t5411/test-0012-no-hook-error--porcelain.sh @@ -5,8 +5,8 @@ test_expect_success "proc-receive: no hook, fail to push special ref ($PROTOCOL/ test_must_fail git -C workbench push --porcelain origin \ HEAD:next \ HEAD:refs/for/main/topic \ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && remote: # pre-receive hook remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next @@ -20,13 +20,11 @@ test_expect_success "proc-receive: no hook, fail to push special ref ($PROTOCOL/ Done EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main <COMMIT-A> refs/heads/next EOF - test_cmp expect actual ' # Refs of upstream : main(A) next(A) @@ -42,8 +40,8 @@ test_expect_success "proc-receive: no hook, all failed for atomic push ($PROTOCO test_must_fail git -C workbench push --porcelain --atomic origin \ $B:main \ HEAD:next \ - HEAD:refs/for/main/topic >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + HEAD:refs/for/main/topic >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && remote: # pre-receive hook remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main @@ -57,10 +55,8 @@ test_expect_success "proc-receive: no hook, all failed for atomic push ($PROTOCO Done EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' diff --git a/t/t5411/test-0013-bad-protocol.sh b/t/t5411/test-0013-bad-protocol.sh index b9be12be77..095e613f6f 100644 --- a/t/t5411/test-0013-bad-protocol.sh +++ b/t/t5411/test-0013-bad-protocol.sh @@ -11,8 +11,8 @@ test_expect_success "setup proc-receive hook (unknown version, $PROTOCOL)" ' test_expect_success "proc-receive: bad protocol (unknown version, $PROTOCOL)" ' test_must_fail git -C workbench push origin \ HEAD:refs/for/main/topic \ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && # Check status report for git-push sed -n \ @@ -34,12 +34,9 @@ test_expect_success "proc-receive: bad protocol (unknown version, $PROTOCOL)" ' EOF test_cmp expect actual-error && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "setup proc-receive hook (hook --die-read-version, $PROTOCOL)" ' @@ -55,25 +52,22 @@ test_expect_success "setup proc-receive hook (hook --die-read-version, $PROTOCOL test_expect_success "proc-receive: bad protocol (hook --die-read-version, $PROTOCOL)" ' test_must_fail git -C workbench push origin \ HEAD:refs/for/main/topic \ - >out 2>&1 && + >out-$test_count 2>&1 && filter_out_user_friendly_and_stable_output \ -e "/^To / { p; }" \ -e "/^ ! / { p; }" \ - <out >actual && + <out-$test_count >actual && cat >expect <<-EOF && To <URL/of/upstream.git> ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook) EOF test_cmp expect actual && - grep "remote: fatal: die with the --die-read-version option" out && - grep "remote: error: fail to negotiate version with proc-receive hook" out && + grep "remote: fatal: die with the --die-read-version option" out-$test_count && + grep "remote: error: fail to negotiate version with proc-receive hook" out-$test_count && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "setup proc-receive hook (hook --die-write-version, $PROTOCOL)" ' @@ -89,25 +83,22 @@ test_expect_success "setup proc-receive hook (hook --die-write-version, $PROTOCO test_expect_success "proc-receive: bad protocol (hook --die-write-version, $PROTOCOL)" ' test_must_fail git -C workbench push origin \ HEAD:refs/for/main/topic \ - >out 2>&1 && + >out-$test_count 2>&1 && filter_out_user_friendly_and_stable_output \ -e "/^To / { p; }" \ -e "/^ ! / { p; }" \ - <out >actual && + <out-$test_count >actual && cat >expect <<-EOF && To <URL/of/upstream.git> ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook) EOF test_cmp expect actual && - grep "remote: fatal: die with the --die-write-version option" out && - grep "remote: error: fail to negotiate version with proc-receive hook" out && + grep "remote: fatal: die with the --die-write-version option" out-$test_count && + grep "remote: error: fail to negotiate version with proc-receive hook" out-$test_count && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "setup proc-receive hook (hook --die-read-commands, $PROTOCOL)" ' @@ -123,24 +114,21 @@ test_expect_success "setup proc-receive hook (hook --die-read-commands, $PROTOCO test_expect_success "proc-receive: bad protocol (hook --die-read-commands, $PROTOCOL)" ' test_must_fail git -C workbench push origin \ HEAD:refs/for/main/topic \ - >out 2>&1 && + >out-$test_count 2>&1 && filter_out_user_friendly_and_stable_output \ -e "/^To / { p; }" \ -e "/^ ! / { p; }" \ - <out >actual && + <out-$test_count >actual && cat >expect <<-EOF && To <URL/of/upstream.git> ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook) EOF test_cmp expect actual && - grep "remote: fatal: die with the --die-read-commands option" out && + grep "remote: fatal: die with the --die-read-commands option" out-$test_count && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "setup proc-receive hook (hook --die-read-push-options, $PROTOCOL)" ' @@ -158,24 +146,21 @@ test_expect_success "proc-receive: bad protocol (hook --die-read-push-options, $ test_must_fail git -C workbench push origin \ -o reviewers=user1,user2 \ HEAD:refs/for/main/topic \ - >out 2>&1 && + >out-$test_count 2>&1 && filter_out_user_friendly_and_stable_output \ -e "/^To / { p; }" \ -e "/^ ! / { p; }" \ - <out >actual && + <out-$test_count >actual && cat >expect <<-EOF && To <URL/of/upstream.git> ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook) EOF test_cmp expect actual && - grep "remote: fatal: die with the --die-read-push-options option" out && + grep "remote: fatal: die with the --die-read-push-options option" out-$test_count && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "setup proc-receive hook (hook --die-write-report, $PROTOCOL)" ' @@ -191,24 +176,21 @@ test_expect_success "setup proc-receive hook (hook --die-write-report, $PROTOCOL test_expect_success "proc-receive: bad protocol (hook --die-write-report, $PROTOCOL)" ' test_must_fail git -C workbench push origin \ HEAD:refs/for/main/topic \ - >out 2>&1 && + >out-$test_count 2>&1 && filter_out_user_friendly_and_stable_output \ -e "/^To / { p; }" \ -e "/^ ! / { p; }" \ - <out >actual && + <out-$test_count >actual && cat >expect <<-EOF && To <URL/of/upstream.git> ! [remote rejected] HEAD -> refs/for/main/topic (fail to run proc-receive hook) EOF test_cmp expect actual && - grep "remote: fatal: die with the --die-write-report option" out && + grep "remote: fatal: die with the --die-write-report option" out-$test_count && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "setup proc-receive hook (no report, $PROTOCOL)" ' @@ -224,8 +206,8 @@ test_expect_success "setup proc-receive hook (no report, $PROTOCOL)" ' test_expect_success "proc-receive: bad protocol (no report, $PROTOCOL)" ' test_must_fail git -C workbench push origin \ HEAD:refs/heads/next \ - HEAD:refs/for/main/topic >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + HEAD:refs/for/main/topic >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && remote: # pre-receive hook remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next @@ -240,13 +222,10 @@ test_expect_success "proc-receive: bad protocol (no report, $PROTOCOL)" ' EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main <COMMIT-A> refs/heads/next EOF - test_cmp expect actual ' # Refs of upstream : main(A) next(A) @@ -270,8 +249,8 @@ test_expect_success "setup proc-receive hook (no ref, $PROTOCOL)" ' test_expect_success "proc-receive: bad protocol (no ref, $PROTOCOL)" ' test_must_fail git -C workbench push origin \ HEAD:refs/for/main/topic\ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && remote: # pre-receive hook remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic @@ -284,12 +263,9 @@ test_expect_success "proc-receive: bad protocol (no ref, $PROTOCOL)" ' EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "setup proc-receive hook (unknown status, $PROTOCOL)" ' @@ -306,8 +282,8 @@ test_expect_success "setup proc-receive hook (unknown status, $PROTOCOL)" ' test_expect_success "proc-receive: bad protocol (unknown status, $PROTOCOL)" ' test_must_fail git -C workbench push origin \ HEAD:refs/for/main/topic \ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && remote: # pre-receive hook remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic @@ -320,10 +296,7 @@ test_expect_success "proc-receive: bad protocol (unknown status, $PROTOCOL)" ' EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' diff --git a/t/t5411/test-0014-bad-protocol--porcelain.sh b/t/t5411/test-0014-bad-protocol--porcelain.sh index fdb4569109..a44649789c 100644 --- a/t/t5411/test-0014-bad-protocol--porcelain.sh +++ b/t/t5411/test-0014-bad-protocol--porcelain.sh @@ -11,8 +11,8 @@ test_expect_success "setup proc-receive hook (unknown version, $PROTOCOL/porcela test_expect_success "proc-receive: bad protocol (unknown version, $PROTOCOL/porcelain)" ' test_must_fail git -C workbench push --porcelain origin \ HEAD:refs/for/main/topic \ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && # Check status report for git-push sed -n \ @@ -34,12 +34,9 @@ test_expect_success "proc-receive: bad protocol (unknown version, $PROTOCOL/porc EOF test_cmp expect actual-error && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "setup proc-receive hook (hook --die-read-version, $PROTOCOL/porcelain)" ' @@ -55,25 +52,22 @@ test_expect_success "setup proc-receive hook (hook --die-read-version, $PROTOCOL test_expect_success "proc-receive: bad protocol (hook --die-read-version, $PROTOCOL/porcelain)" ' test_must_fail git -C workbench push --porcelain origin \ HEAD:refs/for/main/topic \ - >out 2>&1 && + >out-$test_count 2>&1 && filter_out_user_friendly_and_stable_output \ -e "/^To / { p; n; p; n; p; }" \ - <out >actual && + <out-$test_count >actual && cat >expect <<-EOF && To <URL/of/upstream.git> ! HEAD:refs/for/main/topic [remote rejected] (fail to run proc-receive hook) Done EOF test_cmp expect actual && - grep "remote: fatal: die with the --die-read-version option" out && - grep "remote: error: fail to negotiate version with proc-receive hook" out && + grep "remote: fatal: die with the --die-read-version option" out-$test_count && + grep "remote: error: fail to negotiate version with proc-receive hook" out-$test_count && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "setup proc-receive hook (hook --die-write-version, $PROTOCOL/porcelain)" ' @@ -89,25 +83,22 @@ test_expect_success "setup proc-receive hook (hook --die-write-version, $PROTOCO test_expect_success "proc-receive: bad protocol (hook --die-write-version, $PROTOCOL/porcelain)" ' test_must_fail git -C workbench push --porcelain origin \ HEAD:refs/for/main/topic \ - >out 2>&1 && + >out-$test_count 2>&1 && filter_out_user_friendly_and_stable_output \ -e "/^To / { p; n; p; n; p; }" \ - <out >actual && + <out-$test_count >actual && cat >expect <<-EOF && To <URL/of/upstream.git> ! HEAD:refs/for/main/topic [remote rejected] (fail to run proc-receive hook) Done EOF test_cmp expect actual && - grep "remote: fatal: die with the --die-write-version option" out && - grep "remote: error: fail to negotiate version with proc-receive hook" out && + grep "remote: fatal: die with the --die-write-version option" out-$test_count && + grep "remote: error: fail to negotiate version with proc-receive hook" out-$test_count && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "setup proc-receive hook (hook --die-read-commands, $PROTOCOL/porcelain)" ' @@ -123,24 +114,21 @@ test_expect_success "setup proc-receive hook (hook --die-read-commands, $PROTOCO test_expect_success "proc-receive: bad protocol (hook --die-read-commands, $PROTOCOL/porcelain)" ' test_must_fail git -C workbench push --porcelain origin \ HEAD:refs/for/main/topic \ - >out 2>&1 && + >out-$test_count 2>&1 && filter_out_user_friendly_and_stable_output \ -e "/^To / { p; n; p; n; p; }" \ - <out >actual && + <out-$test_count >actual && cat >expect <<-EOF && To <URL/of/upstream.git> ! HEAD:refs/for/main/topic [remote rejected] (fail to run proc-receive hook) Done EOF test_cmp expect actual && - grep "remote: fatal: die with the --die-read-commands option" out && + grep "remote: fatal: die with the --die-read-commands option" out-$test_count && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "setup proc-receive hook (hook --die-read-push-options, $PROTOCOL/porcelain)" ' @@ -158,24 +146,21 @@ test_expect_success "proc-receive: bad protocol (hook --die-read-push-options, $ test_must_fail git -C workbench push --porcelain origin \ -o reviewers=user1,user2 \ HEAD:refs/for/main/topic \ - >out 2>&1 && + >out-$test_count 2>&1 && filter_out_user_friendly_and_stable_output \ -e "/^To / { p; n; p; n; p; }" \ - <out >actual && + <out-$test_count >actual && cat >expect <<-EOF && To <URL/of/upstream.git> ! HEAD:refs/for/main/topic [remote rejected] (fail to run proc-receive hook) Done EOF test_cmp expect actual && - grep "remote: fatal: die with the --die-read-push-options option" out && + grep "remote: fatal: die with the --die-read-push-options option" out-$test_count && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "setup proc-receive hook (hook --die-write-report, $PROTOCOL/porcelain)" ' @@ -191,24 +176,21 @@ test_expect_success "setup proc-receive hook (hook --die-write-report, $PROTOCOL test_expect_success "proc-receive: bad protocol (hook --die-write-report, $PROTOCOL/porcelain)" ' test_must_fail git -C workbench push --porcelain origin \ HEAD:refs/for/main/topic \ - >out 2>&1 && + >out-$test_count 2>&1 && filter_out_user_friendly_and_stable_output \ -e "/^To / { p; n; p; n; p; }" \ - <out >actual && + <out-$test_count >actual && cat >expect <<-EOF && To <URL/of/upstream.git> ! HEAD:refs/for/main/topic [remote rejected] (fail to run proc-receive hook) Done EOF test_cmp expect actual && - grep "remote: fatal: die with the --die-write-report option" out && + grep "remote: fatal: die with the --die-write-report option" out-$test_count && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "setup proc-receive hook (no report, $PROTOCOL/porcelain)" ' @@ -224,8 +206,8 @@ test_expect_success "setup proc-receive hook (no report, $PROTOCOL/porcelain)" ' test_expect_success "proc-receive: bad protocol (no report, $PROTOCOL/porcelain)" ' test_must_fail git -C workbench push --porcelain origin \ HEAD:refs/heads/next \ - HEAD:refs/for/main/topic >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + HEAD:refs/for/main/topic >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && remote: # pre-receive hook remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next @@ -241,13 +223,10 @@ test_expect_success "proc-receive: bad protocol (no report, $PROTOCOL/porcelain) EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main <COMMIT-A> refs/heads/next EOF - test_cmp expect actual ' # Refs of upstream : main(A) next(A) @@ -270,8 +249,8 @@ test_expect_success "setup proc-receive hook (no ref, $PROTOCOL/porcelain)" ' test_expect_success "proc-receive: bad protocol (no ref, $PROTOCOL/porcelain)" ' test_must_fail git -C workbench push --porcelain origin \ HEAD:refs/for/main/topic\ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && remote: # pre-receive hook remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic @@ -285,12 +264,9 @@ test_expect_success "proc-receive: bad protocol (no ref, $PROTOCOL/porcelain)" ' EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "setup proc-receive hook (unknown status, $PROTOCOL/porcelain)" ' @@ -307,8 +283,8 @@ test_expect_success "setup proc-receive hook (unknown status, $PROTOCOL/porcelai test_expect_success "proc-receive: bad protocol (unknown status, $PROTOCOL/porcelain)" ' test_must_fail git -C workbench push --porcelain origin \ HEAD:refs/for/main/topic \ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && remote: # pre-receive hook remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic @@ -322,10 +298,7 @@ test_expect_success "proc-receive: bad protocol (unknown status, $PROTOCOL/porce EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' diff --git a/t/t5411/test-0020-report-ng.sh b/t/t5411/test-0020-report-ng.sh index 5a9e0daf2d..ad2c8f6535 100644 --- a/t/t5411/test-0020-report-ng.sh +++ b/t/t5411/test-0020-report-ng.sh @@ -12,8 +12,8 @@ test_expect_success "setup proc-receive hook (ng, no message, $PROTOCOL)" ' test_expect_success "proc-receive: fail to update (ng, no message, $PROTOCOL)" ' test_must_fail git -C workbench push origin \ HEAD:refs/for/main/topic \ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && remote: # pre-receive hook remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic @@ -24,12 +24,10 @@ test_expect_success "proc-receive: fail to update (ng, no message, $PROTOCOL)" ' ! [remote rejected] HEAD -> refs/for/main/topic (failed) EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "setup proc-receive hook (ng message, $PROTOCOL)" ' @@ -46,8 +44,8 @@ test_expect_success "setup proc-receive hook (ng message, $PROTOCOL)" ' test_expect_success "proc-receive: fail to update (ng, with message, $PROTOCOL)" ' test_must_fail git -C workbench push origin \ HEAD:refs/for/main/topic \ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && remote: # pre-receive hook remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic @@ -58,10 +56,8 @@ test_expect_success "proc-receive: fail to update (ng, with message, $PROTOCOL)" ! [remote rejected] HEAD -> refs/for/main/topic (error msg) EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' diff --git a/t/t5411/test-0021-report-ng--porcelain.sh b/t/t5411/test-0021-report-ng--porcelain.sh index 93475a83cf..d8ae9d3414 100644 --- a/t/t5411/test-0021-report-ng--porcelain.sh +++ b/t/t5411/test-0021-report-ng--porcelain.sh @@ -12,8 +12,8 @@ test_expect_success "setup proc-receive hook (ng, no message, $PROTOCOL/porcelai test_expect_success "proc-receive: fail to update (ng, no message, $PROTOCOL/porcelain)" ' test_must_fail git -C workbench push --porcelain origin \ HEAD:refs/for/main/topic \ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && remote: # pre-receive hook remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic @@ -25,12 +25,10 @@ test_expect_success "proc-receive: fail to update (ng, no message, $PROTOCOL/por Done EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "setup proc-receive hook (ng message, $PROTOCOL/porcelain)" ' @@ -47,8 +45,8 @@ test_expect_success "setup proc-receive hook (ng message, $PROTOCOL/porcelain)" test_expect_success "proc-receive: fail to update (ng, with message, $PROTOCOL/porcelain)" ' test_must_fail git -C workbench push --porcelain origin \ HEAD:refs/for/main/topic \ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && remote: # pre-receive hook remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic @@ -60,10 +58,8 @@ test_expect_success "proc-receive: fail to update (ng, with message, $PROTOCOL/p Done EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' diff --git a/t/t5411/test-0022-report-unexpect-ref.sh b/t/t5411/test-0022-report-unexpect-ref.sh index f8be8a0ba1..dbed467186 100644 --- a/t/t5411/test-0022-report-unexpect-ref.sh +++ b/t/t5411/test-0022-report-unexpect-ref.sh @@ -13,8 +13,8 @@ test_expect_success "proc-receive: report unexpected ref ($PROTOCOL)" ' test_must_fail git -C workbench push origin \ $B:refs/heads/main \ HEAD:refs/for/main/topic \ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && remote: # pre-receive hook remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main @@ -30,12 +30,10 @@ test_expect_success "proc-receive: report unexpected ref ($PROTOCOL)" ' ! [remote rejected] HEAD -> refs/for/main/topic (proc-receive failed to report status) EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-B> refs/heads/main EOF - test_cmp expect actual ' # Refs of upstream : main(B) diff --git a/t/t5411/test-0023-report-unexpect-ref--porcelain.sh b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh index 778150fa03..e89096fa13 100644 --- a/t/t5411/test-0023-report-unexpect-ref--porcelain.sh +++ b/t/t5411/test-0023-report-unexpect-ref--porcelain.sh @@ -13,8 +13,8 @@ test_expect_success "proc-receive: report unexpected ref ($PROTOCOL/porcelain)" test_must_fail git -C workbench push --porcelain origin \ $B:refs/heads/main \ HEAD:refs/for/main/topic \ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && remote: # pre-receive hook remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main @@ -31,12 +31,10 @@ test_expect_success "proc-receive: report unexpected ref ($PROTOCOL/porcelain)" Done EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-B> refs/heads/main EOF - test_cmp expect actual ' # Refs of upstream : main(B) diff --git a/t/t5411/test-0024-report-unknown-ref.sh b/t/t5411/test-0024-report-unknown-ref.sh index d4e74e4681..77204244b8 100644 --- a/t/t5411/test-0024-report-unknown-ref.sh +++ b/t/t5411/test-0024-report-unknown-ref.sh @@ -12,8 +12,8 @@ test_expect_success "setup proc-receive hook (unexpected ref, $PROTOCOL)" ' test_expect_success "proc-receive: report unknown reference ($PROTOCOL)" ' test_must_fail git -C workbench push origin \ HEAD:refs/for/a/b/c/my/topic \ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && remote: # pre-receive hook remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic @@ -25,10 +25,8 @@ test_expect_success "proc-receive: report unknown reference ($PROTOCOL)" ' ! [remote rejected] HEAD -> refs/for/a/b/c/my/topic (proc-receive failed to report status) EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' diff --git a/t/t5411/test-0025-report-unknown-ref--porcelain.sh b/t/t5411/test-0025-report-unknown-ref--porcelain.sh index 039e8b6163..eeb1ce6b2c 100644 --- a/t/t5411/test-0025-report-unknown-ref--porcelain.sh +++ b/t/t5411/test-0025-report-unknown-ref--porcelain.sh @@ -12,8 +12,8 @@ test_expect_success "setup proc-receive hook (unexpected ref, $PROTOCOL/porcelai test_expect_success "proc-receive: report unknown reference ($PROTOCOL/porcelain)" ' test_must_fail git -C workbench push --porcelain origin \ HEAD:refs/for/a/b/c/my/topic \ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && remote: # pre-receive hook remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/a/b/c/my/topic @@ -26,10 +26,8 @@ test_expect_success "proc-receive: report unknown reference ($PROTOCOL/porcelain Done EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' diff --git a/t/t5411/test-0026-push-options.sh b/t/t5411/test-0026-push-options.sh index e88edb16a4..1ec2cb95bc 100644 --- a/t/t5411/test-0026-push-options.sh +++ b/t/t5411/test-0026-push-options.sh @@ -16,16 +16,14 @@ test_expect_success "proc-receive: not support push options ($PROTOCOL)" ' -o reviewer=user1 \ origin \ HEAD:refs/for/main/topic \ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && test_i18ngrep "fatal: the receiving end does not support push options" \ actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "enable push options ($PROTOCOL)" ' @@ -69,13 +67,11 @@ test_expect_success "proc-receive: ignore push-options for version 0 ($PROTOCOL) * [new reference] HEAD -> refs/for/main/topic EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main <COMMIT-A> refs/heads/next EOF - test_cmp expect actual ' test_expect_success "restore proc-receive hook ($PROTOCOL)" ' @@ -123,13 +119,11 @@ test_expect_success "proc-receive: push with options ($PROTOCOL)" ' * [new reference] HEAD -> refs/for/main/topic EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main <COMMIT-A> refs/heads/next EOF - test_cmp expect actual ' # Refs of upstream : main(A) next(A) diff --git a/t/t5411/test-0027-push-options--porcelain.sh b/t/t5411/test-0027-push-options--porcelain.sh index 3a6561b5ea..447fbfec0c 100644 --- a/t/t5411/test-0027-push-options--porcelain.sh +++ b/t/t5411/test-0027-push-options--porcelain.sh @@ -17,16 +17,14 @@ test_expect_success "proc-receive: not support push options ($PROTOCOL/porcelain -o reviewer=user1 \ origin \ HEAD:refs/for/main/topic \ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && test_i18ngrep "fatal: the receiving end does not support push options" \ actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "enable push options ($PROTOCOL/porcelain)" ' @@ -72,13 +70,11 @@ test_expect_success "proc-receive: ignore push-options for version 0 ($PROTOCOL/ Done EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main <COMMIT-A> refs/heads/next EOF - test_cmp expect actual ' test_expect_success "restore proc-receive hook ($PROTOCOL/porcelain)" ' @@ -128,13 +124,11 @@ test_expect_success "proc-receive: push with options ($PROTOCOL/porcelain)" ' Done EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main <COMMIT-A> refs/heads/next EOF - test_cmp expect actual ' # Refs of upstream : main(A) next(A) diff --git a/t/t5411/test-0030-report-ok.sh b/t/t5411/test-0030-report-ok.sh index 5d6feef118..8acb4f204f 100644 --- a/t/t5411/test-0030-report-ok.sh +++ b/t/t5411/test-0030-report-ok.sh @@ -26,10 +26,8 @@ test_expect_success "proc-receive: ok ($PROTOCOL)" ' * [new reference] HEAD -> refs/for/main/topic EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' diff --git a/t/t5411/test-0031-report-ok--porcelain.sh b/t/t5411/test-0031-report-ok--porcelain.sh index 91666d32df..a967718046 100644 --- a/t/t5411/test-0031-report-ok--porcelain.sh +++ b/t/t5411/test-0031-report-ok--porcelain.sh @@ -27,10 +27,8 @@ test_expect_success "proc-receive: ok ($PROTOCOL/porcelain)" ' Done EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' diff --git a/t/t5411/test-0032-report-with-options.sh b/t/t5411/test-0032-report-with-options.sh index a0faf5c7ff..437ade012d 100644 --- a/t/t5411/test-0032-report-with-options.sh +++ b/t/t5411/test-0032-report-with-options.sh @@ -13,8 +13,8 @@ test_expect_success "setup proc-receive hook (option without matching ok, $PROTO test_expect_success "proc-receive: report option without matching ok ($PROTOCOL)" ' test_must_fail git -C workbench push origin \ HEAD:refs/for/main/topic \ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && remote: # pre-receive hook remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic @@ -247,10 +247,7 @@ test_expect_success "proc-receive: report with multiple rewrites ($PROTOCOL)" ' EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' diff --git a/t/t5411/test-0033-report-with-options--porcelain.sh b/t/t5411/test-0033-report-with-options--porcelain.sh index 32ae26bcfb..11486720ee 100644 --- a/t/t5411/test-0033-report-with-options--porcelain.sh +++ b/t/t5411/test-0033-report-with-options--porcelain.sh @@ -13,8 +13,8 @@ test_expect_success "setup proc-receive hook (option without matching ok, $PROTO test_expect_success "proc-receive: report option without matching ok ($PROTOCOL/porcelain)" ' test_must_fail git -C workbench push --porcelain origin \ HEAD:refs/for/main/topic \ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && remote: # pre-receive hook remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/main/topic @@ -256,10 +256,7 @@ test_expect_success "proc-receive: report with multiple rewrites ($PROTOCOL/porc EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' diff --git a/t/t5411/test-0034-report-ft.sh b/t/t5411/test-0034-report-ft.sh index c355c290d2..6e0d08b327 100644 --- a/t/t5411/test-0034-report-ft.sh +++ b/t/t5411/test-0034-report-ft.sh @@ -28,13 +28,11 @@ test_expect_success "proc-receive: fall throught, let receive-pack to execute ($ * [new reference] <COMMIT-B> -> refs/for/main/topic EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-B> refs/for/main/topic <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' # Refs of upstream : main(A) refs/for/main/topic(A) diff --git a/t/t5411/test-0035-report-ft--porcelain.sh b/t/t5411/test-0035-report-ft--porcelain.sh index 8ce4e58f2a..81bae9f2ec 100644 --- a/t/t5411/test-0035-report-ft--porcelain.sh +++ b/t/t5411/test-0035-report-ft--porcelain.sh @@ -29,13 +29,11 @@ test_expect_success "proc-receive: fall throught, let receive-pack to execute ($ Done EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-B> refs/for/main/topic <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' # Refs of upstream : main(A) refs/for/main/topic(A) diff --git a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh index fad8eea8a0..be9b18b2b6 100644 --- a/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh +++ b/t/t5411/test-0036-report-multi-rewrite-for-one-ref.sh @@ -65,12 +65,10 @@ test_expect_success "proc-receive: multiple rewrite for one ref, no refname for <OID-A>..<OID-B> HEAD -> refs/changes/25/125/1 EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "proc-receive: check remote-tracking #1 ($PROTOCOL)" ' @@ -142,12 +140,10 @@ test_expect_success "proc-receive: multiple rewrites for one ref, no refname for + <OID-B>...<OID-A> HEAD -> refs/changes/25/125/1 (forced update) EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "proc-receive: check remote-tracking #2 ($PROTOCOL)" ' @@ -205,12 +201,10 @@ test_expect_success "proc-receive: multiple rewrites for one ref ($PROTOCOL)" ' <OID-A>..<OID-B> HEAD -> refs/changes/24/124/2 EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "proc-receive: check remote-tracking #3 ($PROTOCOL)" ' diff --git a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh index dc254d57eb..95fb89c031 100644 --- a/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh +++ b/t/t5411/test-0037-report-multi-rewrite-for-one-ref--porcelain.sh @@ -51,12 +51,10 @@ test_expect_success "proc-receive: multiple rewrite for one ref, no refname for Done EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "setup proc-receive hook (multiple rewrites for one ref, no refname for the 2nd rewrite, $PROTOCOL/porcelain)" ' @@ -114,12 +112,10 @@ test_expect_success "proc-receive: multiple rewrites for one ref, no refname for Done EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' test_expect_success "setup proc-receive hook (multiple rewrites for one ref, $PROTOCOL/porcelain)" ' @@ -163,10 +159,8 @@ test_expect_success "proc-receive: multiple rewrites for one ref ($PROTOCOL/porc Done EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' diff --git a/t/t5411/test-0038-report-mixed-refs.sh b/t/t5411/test-0038-report-mixed-refs.sh index 0d071ebaa6..5e005299cc 100644 --- a/t/t5411/test-0038-report-mixed-refs.sh +++ b/t/t5411/test-0038-report-mixed-refs.sh @@ -24,8 +24,8 @@ test_expect_success "proc-receive: report update of mixed refs ($PROTOCOL)" ' HEAD:refs/heads/foo \ HEAD:refs/for/main/topic \ HEAD:refs/for/next/topic3 \ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && remote: # pre-receive hook remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main @@ -65,15 +65,13 @@ test_expect_success "proc-receive: report update of mixed refs ($PROTOCOL)" ' ! [remote rejected] HEAD -> refs/for/next/topic3 (proc-receive failed to report status) EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/bar <COMMIT-A> refs/heads/baz <COMMIT-A> refs/heads/foo <COMMIT-B> refs/heads/main EOF - test_cmp expect actual ' # Refs of upstream : main(B) foo(A) bar(A)) baz(A) diff --git a/t/t5411/test-0039-report-mixed-refs--porcelain.sh b/t/t5411/test-0039-report-mixed-refs--porcelain.sh index d8409912fd..8f891c5385 100644 --- a/t/t5411/test-0039-report-mixed-refs--porcelain.sh +++ b/t/t5411/test-0039-report-mixed-refs--porcelain.sh @@ -24,8 +24,8 @@ test_expect_success "proc-receive: report update of mixed refs ($PROTOCOL/porcel HEAD:refs/heads/foo \ HEAD:refs/for/main/topic \ HEAD:refs/for/next/topic3 \ - >out 2>&1 && - make_user_friendly_and_stable_output <out >actual && + >out-$test_count 2>&1 && + make_user_friendly_and_stable_output <out-$test_count >actual && cat >expect <<-EOF && remote: # pre-receive hook remote: pre-receive< <COMMIT-A> <COMMIT-B> refs/heads/main @@ -66,15 +66,13 @@ test_expect_success "proc-receive: report update of mixed refs ($PROTOCOL/porcel Done EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/bar <COMMIT-A> refs/heads/baz <COMMIT-A> refs/heads/foo <COMMIT-B> refs/heads/main EOF - test_cmp expect actual ' # Refs of upstream : main(B) foo(A) bar(A)) baz(A) diff --git a/t/t5411/test-0040-process-all-refs.sh b/t/t5411/test-0040-process-all-refs.sh index 2565302a17..fdcdcc7c2e 100644 --- a/t/t5411/test-0040-process-all-refs.sh +++ b/t/t5411/test-0040-process-all-refs.sh @@ -92,14 +92,12 @@ test_expect_success "proc-receive: process all refs ($PROTOCOL)" ' + <OID-B>...<OID-A> HEAD -> refs/pull/124/head (forced update) EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-B> refs/heads/bar <COMMIT-A> refs/heads/baz <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' # Refs of upstream : main(A) bar(A) baz(B) diff --git a/t/t5411/test-0041-process-all-refs--porcelain.sh b/t/t5411/test-0041-process-all-refs--porcelain.sh index e21420b60d..73b35fe0aa 100644 --- a/t/t5411/test-0041-process-all-refs--porcelain.sh +++ b/t/t5411/test-0041-process-all-refs--porcelain.sh @@ -93,14 +93,12 @@ test_expect_success "proc-receive: process all refs ($PROTOCOL/porcelain)" ' Done EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-B> refs/heads/bar <COMMIT-A> refs/heads/baz <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' # Refs of upstream : main(A) bar(A) baz(B) diff --git a/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh b/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh index 2e29518ec5..7214647ada 100644 --- a/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh +++ b/t/t5411/test-0050-proc-receive-refs-with-modifiers.sh @@ -50,12 +50,10 @@ test_expect_success "proc-receive: update branch and new tag ($PROTOCOL)" ' * [new reference] v123 -> refs/pull/124/head EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main EOF - test_cmp expect actual ' # Refs of upstream : main(A) @@ -63,14 +61,12 @@ test_expect_success "proc-receive: update branch and new tag ($PROTOCOL)" ' test_expect_success "setup upstream: create tags/v123 ($PROTOCOL)" ' git -C "$upstream" update-ref refs/heads/topic $A && git -C "$upstream" update-ref refs/tags/v123 $TAG && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main <COMMIT-A> refs/heads/topic <TAG-v123> refs/tags/v123 EOF - test_cmp expect actual ' test_expect_success "setup proc-receive hook ($PROTOCOL)" ' @@ -125,11 +121,9 @@ test_expect_success "proc-receive: create/delete branch, and delete tag ($PROTOC * [new reference] <COMMIT-A> -> refs/pull/124/head EOF test_cmp expect actual && - git -C "$upstream" show-ref >out && - make_user_friendly_and_stable_output <out >actual && - cat >expect <<-EOF && + + test_cmp_refs -C "$upstream" <<-EOF <COMMIT-A> refs/heads/main <COMMIT-B> refs/heads/topic EOF - test_cmp expect actual ' diff --git a/t/t5544-pack-objects-hook.sh b/t/t5544-pack-objects-hook.sh index 4357af1525..dd5f44d986 100755 --- a/t/t5544-pack-objects-hook.sh +++ b/t/t5544-pack-objects-hook.sh @@ -59,4 +59,14 @@ test_expect_success 'hook does not run from repo config' ' test_path_is_missing .git/hook.stdout ' +test_expect_success 'hook works with partial clone' ' + clear_hook_results && + test_config_global uploadpack.packObjectsHook ./hook && + test_config_global uploadpack.allowFilter true && + git clone --bare --no-local --filter=blob:none . dst.git && + git -C dst.git rev-list --objects --missing=allow-any --no-object-names --all >objects && + git -C dst.git cat-file --batch-check="%(objecttype)" <objects >types && + ! grep blob types +' + test_done diff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh index 5d682706ae..e845d621f6 100755 --- a/t/t5604-clone-reference.sh +++ b/t/t5604-clone-reference.sh @@ -329,7 +329,7 @@ test_expect_success SYMLINKS 'clone repo with symlinked or unknown files at obje for raw in $(ls T*.raw) do sed -e "s!/../!/Y/!; s![0-9a-f]\{38,\}!Z!" -e "/commit-graph/d" \ - -e "/multi-pack-index/d" <$raw >$raw.de-sha-1 && + -e "/multi-pack-index/d" -e "/rev/d" <$raw >$raw.de-sha-1 && sort $raw.de-sha-1 >$raw.de-sha || return 1 done && diff --git a/t/t5606-clone-options.sh b/t/t5606-clone-options.sh index 5d6e63a841..52e5789fb0 100755 --- a/t/t5606-clone-options.sh +++ b/t/t5606-clone-options.sh @@ -105,11 +105,13 @@ test_expect_success 'redirected clone -v does show progress' ' ' test_expect_success 'chooses correct default initial branch name' ' - git init --bare empty && + GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \ + git -c init.defaultBranch=foo init --bare empty && + test_config -C empty lsrefs.unborn advertise && GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \ git -c init.defaultBranch=up clone empty whats-up && - test refs/heads/up = $(git -C whats-up symbolic-ref HEAD) && - test refs/heads/up = $(git -C whats-up config branch.up.merge) + test refs/heads/foo = $(git -C whats-up symbolic-ref HEAD) && + test refs/heads/foo = $(git -C whats-up config branch.foo.merge) ' test_expect_success 'guesses initial branch name correctly' ' diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh index d9143b4bd2..509f379d49 100755 --- a/t/t5701-git-serve.sh +++ b/t/t5701-git-serve.sh @@ -15,7 +15,7 @@ test_expect_success 'test capability advertisement' ' cat >expect <<-EOF && version 2 agent=git/$(git version | cut -d" " -f3) - ls-refs + ls-refs=unborn fetch=shallow server-option object-format=$(test_oid algo) diff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh index 3d994e0b1b..9113d209c5 100755 --- a/t/t5702-protocol-v2.sh +++ b/t/t5702-protocol-v2.sh @@ -212,6 +212,31 @@ test_expect_success 'clone with file:// using protocol v2' ' grep "ref-prefix refs/tags/" log ' +test_expect_success 'clone of empty repo propagates name of default branch' ' + test_when_finished "rm -rf file_empty_parent file_empty_child" && + + GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \ + git -c init.defaultBranch=mydefaultbranch init file_empty_parent && + + GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \ + git -c init.defaultBranch=main -c protocol.version=2 \ + clone "file://$(pwd)/file_empty_parent" file_empty_child && + grep "refs/heads/mydefaultbranch" file_empty_child/.git/HEAD +' + +test_expect_success '...but not if explicitly forbidden by config' ' + test_when_finished "rm -rf file_empty_parent file_empty_child" && + + GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \ + git -c init.defaultBranch=mydefaultbranch init file_empty_parent && + test_config -C file_empty_parent lsrefs.unborn ignore && + + GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \ + git -c init.defaultBranch=main -c protocol.version=2 \ + clone "file://$(pwd)/file_empty_parent" file_empty_child && + ! grep "refs/heads/mydefaultbranch" file_empty_child/.git/HEAD +' + test_expect_success 'fetch with file:// using protocol v2' ' test_when_finished "rm -f log" && @@ -851,8 +876,10 @@ test_expect_success 'part of packfile response provided as URI' ' test -f h2found && # Ensure that there are exactly 6 files (3 .pack and 3 .idx). - ls http_child/.git/objects/pack/* >filelist && - test_line_count = 6 filelist + ls http_child/.git/objects/pack/*.pack >packlist && + ls http_child/.git/objects/pack/*.idx >idxlist && + test_line_count = 3 idxlist && + test_line_count = 3 packlist ' test_expect_success 'fetching with valid packfile URI but invalid hash fails' ' @@ -905,8 +932,10 @@ test_expect_success 'packfile-uri with transfer.fsckobjects' ' clone "$HTTPD_URL/smart/http_parent" http_child && # Ensure that there are exactly 4 files (2 .pack and 2 .idx). - ls http_child/.git/objects/pack/* >filelist && - test_line_count = 4 filelist + ls http_child/.git/objects/pack/*.pack >packlist && + ls http_child/.git/objects/pack/*.idx >idxlist && + test_line_count = 2 idxlist && + test_line_count = 2 packlist ' test_expect_success 'packfile-uri with transfer.fsckobjects fails on bad object' ' diff --git a/t/t5703-upload-pack-ref-in-want.sh b/t/t5703-upload-pack-ref-in-want.sh index 64d8f99325..e9e471621d 100755 --- a/t/t5703-upload-pack-ref-in-want.sh +++ b/t/t5703-upload-pack-ref-in-want.sh @@ -19,7 +19,8 @@ get_actual_commits () { test-tool pkt-line unpack-sideband <out >o.pack && git index-pack o.pack && git verify-pack -v o.idx >objs && - grep commit objs | cut -d" " -f1 | sort >actual_commits + sed -n -e 's/\([0-9a-f][0-9a-f]*\) commit .*/\1/p' objs >objs.sed && + sort >actual_commits <objs.sed } check_output () { diff --git a/t/t6404-recursive-merge.sh b/t/t6404-recursive-merge.sh index c7ab7048f5..eaf48e941e 100755 --- a/t/t6404-recursive-merge.sh +++ b/t/t6404-recursive-merge.sh @@ -18,6 +18,8 @@ GIT_COMMITTER_DATE="2006-12-12 23:28:00 +0100" export GIT_COMMITTER_DATE test_expect_success 'setup tests' ' + GIT_TEST_COMMIT_GRAPH=0 && + export GIT_TEST_COMMIT_GRAPH && echo 1 >a1 && git add a1 && GIT_AUTHOR_DATE="2006-12-12 23:00:00" git commit -m 1 a1 && @@ -69,7 +71,7 @@ test_expect_success 'setup tests' ' ' test_expect_success 'combined merge conflicts' ' - test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git merge -m final G + test_must_fail git merge -m final G ' test_expect_success 'result contains a conflict' ' @@ -85,6 +87,7 @@ test_expect_success 'result contains a conflict' ' ' test_expect_success 'virtual trees were processed' ' + # TODO: fragile test, relies on ambigious merge-base resolution git ls-files --stage >out && cat >expect <<-EOF && diff --git a/t/t6500-gc.sh b/t/t6500-gc.sh index 4a3b8f48ac..f76586f808 100755 --- a/t/t6500-gc.sh +++ b/t/t6500-gc.sh @@ -106,17 +106,17 @@ test_expect_success 'auto gc with too many loose objects does not attempt to cre test_commit "$(test_oid obj2)" && # Our first gc will create a pack; our second will create a second pack git gc --auto && - ls .git/objects/pack | sort >existing_packs && + ls .git/objects/pack/pack-*.pack | sort >existing_packs && test_commit "$(test_oid obj3)" && test_commit "$(test_oid obj4)" && git gc --auto 2>err && test_i18ngrep ! "^warning:" err && - ls .git/objects/pack/ | sort >post_packs && + ls .git/objects/pack/pack-*.pack | sort >post_packs && comm -1 -3 existing_packs post_packs >new && comm -2 -3 existing_packs post_packs >del && test_line_count = 0 del && # No packs are deleted - test_line_count = 2 new # There is one new pack and its .idx + test_line_count = 1 new # There is one new pack ' test_expect_success 'gc --no-quiet' ' diff --git a/t/t6600-test-reach.sh b/t/t6600-test-reach.sh index f807276337..e2d33a8a4c 100755 --- a/t/t6600-test-reach.sh +++ b/t/t6600-test-reach.sh @@ -55,10 +55,13 @@ test_expect_success 'setup' ' git show-ref -s commit-5-5 | git commit-graph write --stdin-commits && mv .git/objects/info/commit-graph commit-graph-half && chmod u+w commit-graph-half && + GIT_TEST_COMMIT_GRAPH_NO_GDAT=1 git commit-graph write --reachable && + mv .git/objects/info/commit-graph commit-graph-no-gdat && + chmod u+w commit-graph-no-gdat && git config core.commitGraph true ' -run_three_modes () { +run_all_modes () { test_when_finished rm -rf .git/objects/info/commit-graph && "$@" <input >actual && test_cmp expect actual && @@ -67,11 +70,14 @@ run_three_modes () { test_cmp expect actual && cp commit-graph-half .git/objects/info/commit-graph && "$@" <input >actual && + test_cmp expect actual && + cp commit-graph-no-gdat .git/objects/info/commit-graph && + "$@" <input >actual && test_cmp expect actual } -test_three_modes () { - run_three_modes test-tool reach "$@" +test_all_modes () { + run_all_modes test-tool reach "$@" } test_expect_success 'ref_newer:miss' ' @@ -80,7 +86,7 @@ test_expect_success 'ref_newer:miss' ' B:commit-4-9 EOF echo "ref_newer(A,B):0" >expect && - test_three_modes ref_newer + test_all_modes ref_newer ' test_expect_success 'ref_newer:hit' ' @@ -89,7 +95,7 @@ test_expect_success 'ref_newer:hit' ' B:commit-2-3 EOF echo "ref_newer(A,B):1" >expect && - test_three_modes ref_newer + test_all_modes ref_newer ' test_expect_success 'in_merge_bases:hit' ' @@ -98,7 +104,7 @@ test_expect_success 'in_merge_bases:hit' ' B:commit-8-8 EOF echo "in_merge_bases(A,B):1" >expect && - test_three_modes in_merge_bases + test_all_modes in_merge_bases ' test_expect_success 'in_merge_bases:miss' ' @@ -107,7 +113,7 @@ test_expect_success 'in_merge_bases:miss' ' B:commit-5-9 EOF echo "in_merge_bases(A,B):0" >expect && - test_three_modes in_merge_bases + test_all_modes in_merge_bases ' test_expect_success 'in_merge_bases_many:hit' ' @@ -117,7 +123,7 @@ test_expect_success 'in_merge_bases_many:hit' ' X:commit-5-7 EOF echo "in_merge_bases_many(A,X):1" >expect && - test_three_modes in_merge_bases_many + test_all_modes in_merge_bases_many ' test_expect_success 'in_merge_bases_many:miss' ' @@ -127,7 +133,7 @@ test_expect_success 'in_merge_bases_many:miss' ' X:commit-8-6 EOF echo "in_merge_bases_many(A,X):0" >expect && - test_three_modes in_merge_bases_many + test_all_modes in_merge_bases_many ' test_expect_success 'in_merge_bases_many:miss-heuristic' ' @@ -137,7 +143,7 @@ test_expect_success 'in_merge_bases_many:miss-heuristic' ' X:commit-6-6 EOF echo "in_merge_bases_many(A,X):0" >expect && - test_three_modes in_merge_bases_many + test_all_modes in_merge_bases_many ' test_expect_success 'is_descendant_of:hit' ' @@ -148,7 +154,7 @@ test_expect_success 'is_descendant_of:hit' ' X:commit-1-1 EOF echo "is_descendant_of(A,X):1" >expect && - test_three_modes is_descendant_of + test_all_modes is_descendant_of ' test_expect_success 'is_descendant_of:miss' ' @@ -159,7 +165,7 @@ test_expect_success 'is_descendant_of:miss' ' X:commit-7-6 EOF echo "is_descendant_of(A,X):0" >expect && - test_three_modes is_descendant_of + test_all_modes is_descendant_of ' test_expect_success 'get_merge_bases_many' ' @@ -174,7 +180,7 @@ test_expect_success 'get_merge_bases_many' ' git rev-parse commit-5-6 \ commit-4-7 | sort } >expect && - test_three_modes get_merge_bases_many + test_all_modes get_merge_bases_many ' test_expect_success 'reduce_heads' ' @@ -196,7 +202,7 @@ test_expect_success 'reduce_heads' ' commit-2-8 \ commit-1-10 | sort } >expect && - test_three_modes reduce_heads + test_all_modes reduce_heads ' test_expect_success 'can_all_from_reach:hit' ' @@ -219,7 +225,7 @@ test_expect_success 'can_all_from_reach:hit' ' Y:commit-8-1 EOF echo "can_all_from_reach(X,Y):1" >expect && - test_three_modes can_all_from_reach + test_all_modes can_all_from_reach ' test_expect_success 'can_all_from_reach:miss' ' @@ -241,7 +247,7 @@ test_expect_success 'can_all_from_reach:miss' ' Y:commit-8-5 EOF echo "can_all_from_reach(X,Y):0" >expect && - test_three_modes can_all_from_reach + test_all_modes can_all_from_reach ' test_expect_success 'can_all_from_reach_with_flag: tags case' ' @@ -264,7 +270,7 @@ test_expect_success 'can_all_from_reach_with_flag: tags case' ' Y:commit-8-1 EOF echo "can_all_from_reach_with_flag(X,_,_,0,0):1" >expect && - test_three_modes can_all_from_reach_with_flag + test_all_modes can_all_from_reach_with_flag ' test_expect_success 'commit_contains:hit' ' @@ -280,8 +286,8 @@ test_expect_success 'commit_contains:hit' ' X:commit-9-3 EOF echo "commit_contains(_,A,X,_):1" >expect && - test_three_modes commit_contains && - test_three_modes commit_contains --tag + test_all_modes commit_contains && + test_all_modes commit_contains --tag ' test_expect_success 'commit_contains:miss' ' @@ -297,8 +303,8 @@ test_expect_success 'commit_contains:miss' ' X:commit-9-3 EOF echo "commit_contains(_,A,X,_):0" >expect && - test_three_modes commit_contains && - test_three_modes commit_contains --tag + test_all_modes commit_contains && + test_all_modes commit_contains --tag ' test_expect_success 'rev-list: basic topo-order' ' @@ -310,7 +316,7 @@ test_expect_success 'rev-list: basic topo-order' ' commit-6-2 commit-5-2 commit-4-2 commit-3-2 commit-2-2 commit-1-2 \ commit-6-1 commit-5-1 commit-4-1 commit-3-1 commit-2-1 commit-1-1 \ >expect && - run_three_modes git rev-list --topo-order commit-6-6 + run_all_modes git rev-list --topo-order commit-6-6 ' test_expect_success 'rev-list: first-parent topo-order' ' @@ -322,7 +328,7 @@ test_expect_success 'rev-list: first-parent topo-order' ' commit-6-2 \ commit-6-1 commit-5-1 commit-4-1 commit-3-1 commit-2-1 commit-1-1 \ >expect && - run_three_modes git rev-list --first-parent --topo-order commit-6-6 + run_all_modes git rev-list --first-parent --topo-order commit-6-6 ' test_expect_success 'rev-list: range topo-order' ' @@ -334,7 +340,7 @@ test_expect_success 'rev-list: range topo-order' ' commit-6-2 commit-5-2 commit-4-2 \ commit-6-1 commit-5-1 commit-4-1 \ >expect && - run_three_modes git rev-list --topo-order commit-3-3..commit-6-6 + run_all_modes git rev-list --topo-order commit-3-3..commit-6-6 ' test_expect_success 'rev-list: range topo-order' ' @@ -346,7 +352,7 @@ test_expect_success 'rev-list: range topo-order' ' commit-6-2 commit-5-2 commit-4-2 \ commit-6-1 commit-5-1 commit-4-1 \ >expect && - run_three_modes git rev-list --topo-order commit-3-8..commit-6-6 + run_all_modes git rev-list --topo-order commit-3-8..commit-6-6 ' test_expect_success 'rev-list: first-parent range topo-order' ' @@ -358,7 +364,7 @@ test_expect_success 'rev-list: first-parent range topo-order' ' commit-6-2 \ commit-6-1 commit-5-1 commit-4-1 \ >expect && - run_three_modes git rev-list --first-parent --topo-order commit-3-8..commit-6-6 + run_all_modes git rev-list --first-parent --topo-order commit-3-8..commit-6-6 ' test_expect_success 'rev-list: ancestry-path topo-order' ' @@ -368,7 +374,7 @@ test_expect_success 'rev-list: ancestry-path topo-order' ' commit-6-4 commit-5-4 commit-4-4 commit-3-4 \ commit-6-3 commit-5-3 commit-4-3 \ >expect && - run_three_modes git rev-list --topo-order --ancestry-path commit-3-3..commit-6-6 + run_all_modes git rev-list --topo-order --ancestry-path commit-3-3..commit-6-6 ' test_expect_success 'rev-list: symmetric difference topo-order' ' @@ -382,7 +388,7 @@ test_expect_success 'rev-list: symmetric difference topo-order' ' commit-3-8 commit-2-8 commit-1-8 \ commit-3-7 commit-2-7 commit-1-7 \ >expect && - run_three_modes git rev-list --topo-order commit-3-8...commit-6-6 + run_all_modes git rev-list --topo-order commit-3-8...commit-6-6 ' test_expect_success 'get_reachable_subset:all' ' @@ -402,7 +408,7 @@ test_expect_success 'get_reachable_subset:all' ' commit-1-7 \ commit-5-6 | sort ) >expect && - test_three_modes get_reachable_subset + test_all_modes get_reachable_subset ' test_expect_success 'get_reachable_subset:some' ' @@ -420,7 +426,7 @@ test_expect_success 'get_reachable_subset:some' ' git rev-parse commit-3-3 \ commit-1-7 | sort ) >expect && - test_three_modes get_reachable_subset + test_all_modes get_reachable_subset ' test_expect_success 'get_reachable_subset:none' ' @@ -434,7 +440,7 @@ test_expect_success 'get_reachable_subset:none' ' Y:commit-2-8 EOF echo "get_reachable_subset(X,Y)" >expect && - test_three_modes get_reachable_subset + test_all_modes get_reachable_subset ' test_done diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 943a7d5c1d..400b83a49e 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -20,6 +20,13 @@ tag_exists () { git show-ref --quiet --verify refs/tags/"$1" } +test_expect_success 'setup' ' + test_oid_cache <<-EOM + othersigheader sha1:gpgsig-sha256 + othersigheader sha256:gpgsig + EOM +' + test_expect_success 'listing all tags in an empty tree should succeed' ' git tag -l && git tag @@ -1374,6 +1381,24 @@ test_expect_success GPG \ 'test_config gpg.program echo && test_must_fail git tag -s -m tail tag-gpg-failure' +# try to produce invalid signature +test_expect_success GPG 'git verifies tag is valid with double signature' ' + git tag -s -m tail tag-gpg-double-sig && + git cat-file tag tag-gpg-double-sig >tag && + othersigheader=$(test_oid othersigheader) && + sed -ne "/^\$/q;p" tag >new-tag && + cat <<-EOM >>new-tag && + $othersigheader -----BEGIN PGP SIGNATURE----- + someinvaliddata + -----END PGP SIGNATURE----- + EOM + sed -e "1,/^tagger/d" tag >>new-tag && + new_tag=$(git hash-object -t tag -w new-tag) && + git update-ref refs/tags/tag-gpg-double-sig $new_tag && + git verify-tag tag-gpg-double-sig && + git fsck +' + # try to sign with bad user.signingkey test_expect_success GPGSM \ 'git tag -s fails if gpgsm is misconfigured (bad key)' \ diff --git a/t/t7104-reset-hard.sh b/t/t7104-reset-hard.sh index 16faa07813..7948ec392b 100755 --- a/t/t7104-reset-hard.sh +++ b/t/t7104-reset-hard.sh @@ -33,7 +33,7 @@ test_expect_success 'reset --hard should restore unmerged ones' ' ' -test_expect_success 'reset --hard did not corrupt index or cached-tree' ' +test_expect_success 'reset --hard did not corrupt index or cache-tree' ' T=$(git write-tree) && rm -f .git/index && diff --git a/t/t7201-co.sh b/t/t7201-co.sh index daf8678b8a..7f6e23a4bb 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -248,7 +248,7 @@ test_expect_success 'checkout to detach HEAD' ' rev=$(git rev-parse --short renamer^) && git checkout -f renamer && git clean -f && - GIT_TEST_GETTEXT_POISON=false git checkout renamer^ 2>messages && + git checkout renamer^ 2>messages && grep "HEAD is now at $rev" messages && test_line_count -gt 1 messages && H=$(git rev-parse --verify HEAD) && diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh index f4bf925bdd..8df5a74f1d 100755 --- a/t/t7510-signed-commit.sh +++ b/t/t7510-signed-commit.sh @@ -175,7 +175,7 @@ test_expect_success GPG 'show signed commit with signature' ' git cat-file commit initial >cat && grep -v -e "gpg: " -e "Warning: " show >show.commit && grep -e "gpg: " -e "Warning: " show >show.gpg && - grep -v "^ " cat | grep -v "^$(test_oid header) " >cat.commit && + grep -v "^ " cat | grep -v "^gpgsig.* " >cat.commit && test_cmp show.commit commit && test_cmp show.gpg verify.2 && test_cmp cat.commit verify.1 @@ -337,4 +337,45 @@ test_expect_success GPG 'show double signature with custom format' ' test_cmp expect actual ' + +test_expect_success GPG 'verify-commit verifies multiply signed commits' ' + git init multiply-signed && + cd multiply-signed && + test_commit first && + echo 1 >second && + git add second && + tree=$(git write-tree) && + parent=$(git rev-parse HEAD^{commit}) && + git commit --gpg-sign -m second && + git cat-file commit HEAD && + # Avoid trailing whitespace. + sed -e "s/^Q//" -e "s/^Z/ /" >commit <<-EOF && + Qtree $tree + Qparent $parent + Qauthor A U Thor <author@example.com> 1112912653 -0700 + Qcommitter C O Mitter <committer@example.com> 1112912653 -0700 + Qgpgsig -----BEGIN PGP SIGNATURE----- + QZ + Q iHQEABECADQWIQRz11h0S+chaY7FTocTtvUezd5DDQUCX/uBDRYcY29tbWl0dGVy + Q QGV4YW1wbGUuY29tAAoJEBO29R7N3kMNd+8AoK1I8mhLHviPH+q2I5fIVgPsEtYC + Q AKCTqBh+VabJceXcGIZuF0Ry+udbBQ== + Q =tQ0N + Q -----END PGP SIGNATURE----- + Qgpgsig-sha256 -----BEGIN PGP SIGNATURE----- + QZ + Q iHQEABECADQWIQRz11h0S+chaY7FTocTtvUezd5DDQUCX/uBIBYcY29tbWl0dGVy + Q QGV4YW1wbGUuY29tAAoJEBO29R7N3kMN/NEAn0XO9RYSBj2dFyozi0JKSbssYMtO + Q AJwKCQ1BQOtuwz//IjU8TiS+6S4iUw== + Q =pIwP + Q -----END PGP SIGNATURE----- + Q + Qsecond + EOF + head=$(git hash-object -t commit -w commit) && + git reset --hard $head && + git verify-commit $head 2>actual && + grep "Good signature from" actual && + ! grep "BAD signature from" actual +' + test_done diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index 04b0095072..8cc64729ad 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -842,4 +842,22 @@ test_expect_success 'mergetool --tool-help shows recognized tools' ' grep meld mergetools ' +test_expect_success 'mergetool hideResolved' ' + test_config mergetool.hideResolved true && + test_when_finished "git reset --hard" && + git checkout -b test${test_count}_b main && + test_write_lines >file1 base "" a && + git commit -a -m "base" && + test_write_lines >file1 base "" c && + git commit -a -m "remote update" && + git checkout -b test${test_count}_a HEAD~ && + test_write_lines >file1 local "" b && + git commit -a -m "local update" && + test_must_fail git merge test${test_count}_b && + yes "" | git mergetool file1 && + test_write_lines >expect local "" c && + test_cmp expect file1 && + git commit -m "test resolved with mergetool" +' + test_done diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 9662abc1e7..9192c141ff 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -443,20 +443,20 @@ run_dir_diff_test () { run_dir_diff_test 'difftool -d' ' git difftool -d $symlinks --extcmd ls branch >output && - grep sub output && - grep file output + grep "^sub$" output && + grep "^file$" output ' run_dir_diff_test 'difftool --dir-diff' ' git difftool --dir-diff $symlinks --extcmd ls branch >output && - grep sub output && - grep file output + grep "^sub$" output && + grep "^file$" output ' run_dir_diff_test 'difftool --dir-diff ignores --prompt' ' git difftool --dir-diff $symlinks --prompt --extcmd ls branch >output && - grep sub output && - grep file output + grep "^sub$" output && + grep "^file$" output ' run_dir_diff_test 'difftool --dir-diff branch from subdirectory' ' @@ -465,11 +465,11 @@ run_dir_diff_test 'difftool --dir-diff branch from subdirectory' ' git difftool --dir-diff $symlinks --extcmd ls branch >output && # "sub" must only exist in "right" # "file" and "file2" must be listed in both "left" and "right" - grep sub output >sub-output && + grep "^sub$" output >sub-output && test_line_count = 1 sub-output && - grep file"$" output >file-output && + grep "^file$" output >file-output && test_line_count = 2 file-output && - grep file2 output >file2-output && + grep "^file2$" output >file2-output && test_line_count = 2 file2-output ) ' @@ -480,11 +480,11 @@ run_dir_diff_test 'difftool --dir-diff v1 from subdirectory' ' git difftool --dir-diff $symlinks --extcmd ls v1 >output && # "sub" and "file" exist in both v1 and HEAD. # "file2" is unchanged. - grep sub output >sub-output && + grep "^sub$" output >sub-output && test_line_count = 2 sub-output && - grep file output >file-output && + grep "^file$" output >file-output && test_line_count = 2 file-output && - ! grep file2 output + ! grep "^file2$" output ) ' @@ -494,9 +494,9 @@ run_dir_diff_test 'difftool --dir-diff branch from subdirectory w/ pathspec' ' git difftool --dir-diff $symlinks --extcmd ls branch -- .>output && # "sub" only exists in "right" # "file" and "file2" must not be listed - grep sub output >sub-output && + grep "^sub$" output >sub-output && test_line_count = 1 sub-output && - ! grep file output + ! grep "^file$" output ) ' @@ -506,9 +506,9 @@ run_dir_diff_test 'difftool --dir-diff v1 from subdirectory w/ pathspec' ' git difftool --dir-diff $symlinks --extcmd ls v1 -- .>output && # "sub" exists in v1 and HEAD # "file" is filtered out by the pathspec - grep sub output >sub-output && + grep "^sub$" output >sub-output && test_line_count = 2 sub-output && - ! grep file output + ! grep "^file$" output ) ' @@ -521,8 +521,8 @@ run_dir_diff_test 'difftool --dir-diff from subdirectory with GIT_DIR set' ' cd sub && git difftool --dir-diff $symlinks --extcmd ls \ branch -- sub >output && - grep sub output && - ! grep file output + grep "^sub$" output && + ! grep "^file$" output ) ' @@ -530,7 +530,7 @@ run_dir_diff_test 'difftool --dir-diff when worktree file is missing' ' test_when_finished git reset --hard && rm file2 && git difftool --dir-diff $symlinks --extcmd ls branch main >output && - grep file2 output + grep "^file2$" output ' run_dir_diff_test 'difftool --dir-diff with unmerged files' ' diff --git a/t/t7812-grep-icase-non-ascii.sh b/t/t7812-grep-icase-non-ascii.sh index 03dba6685a..e5d1e4ea68 100755 --- a/t/t7812-grep-icase-non-ascii.sh +++ b/t/t7812-grep-icase-non-ascii.sh @@ -57,7 +57,12 @@ test_expect_success GETTEXT_LOCALE,LIBPCRE2 'PCRE v2: setup invalid UTF-8 data' printf "\\200\\n" >invalid-0x80 && echo "ævar" >expected && cat expected >>invalid-0x80 && - git add invalid-0x80 + git add invalid-0x80 && + + # Test for PCRE2_MATCH_INVALID_UTF bug + # https://bugs.exim.org/show_bug.cgi?id=2642 + printf "\\345Aæ\\n" >invalid-0xe5 && + git add invalid-0xe5 ' test_expect_success GETTEXT_LOCALE,LIBPCRE2 'PCRE v2: grep ASCII from invalid UTF-8 data' ' @@ -67,6 +72,13 @@ test_expect_success GETTEXT_LOCALE,LIBPCRE2 'PCRE v2: grep ASCII from invalid UT test_cmp expected actual ' +test_expect_success GETTEXT_LOCALE,LIBPCRE2 'PCRE v2: grep ASCII from invalid UTF-8 data (PCRE2 bug #2642)' ' + git grep -h "Aæ" invalid-0xe5 >actual && + test_cmp invalid-0xe5 actual && + git grep -h "(*NO_JIT)Aæ" invalid-0xe5 >actual && + test_cmp invalid-0xe5 actual +' + test_expect_success GETTEXT_LOCALE,LIBPCRE2 'PCRE v2: grep non-ASCII from invalid UTF-8 data' ' git grep -h "æ" invalid-0x80 >actual && test_cmp expected actual && @@ -74,14 +86,41 @@ test_expect_success GETTEXT_LOCALE,LIBPCRE2 'PCRE v2: grep non-ASCII from invali test_cmp expected actual ' +test_expect_success GETTEXT_LOCALE,LIBPCRE2 'PCRE v2: grep non-ASCII from invalid UTF-8 data (PCRE2 bug #2642)' ' + git grep -h "Aæ" invalid-0xe5 >actual && + test_cmp invalid-0xe5 actual && + git grep -h "(*NO_JIT)Aæ" invalid-0xe5 >actual && + test_cmp invalid-0xe5 actual +' + +test_lazy_prereq PCRE2_MATCH_INVALID_UTF ' + test-tool pcre2-config has-PCRE2_MATCH_INVALID_UTF +' + test_expect_success GETTEXT_LOCALE,LIBPCRE2 'PCRE v2: grep non-ASCII from invalid UTF-8 data with -i' ' test_might_fail git grep -hi "Æ" invalid-0x80 >actual && - if test -s actual - then - test_cmp expected actual - fi && - test_must_fail git grep -hi "(*NO_JIT)Æ" invalid-0x80 >actual && - ! test_cmp expected actual + test_might_fail git grep -hi "(*NO_JIT)Æ" invalid-0x80 >actual +' + +test_expect_success GETTEXT_LOCALE,LIBPCRE2,PCRE2_MATCH_INVALID_UTF 'PCRE v2: grep non-ASCII from invalid UTF-8 data with -i' ' + git grep -hi "Æ" invalid-0x80 >actual && + test_cmp expected actual && + git grep -hi "(*NO_JIT)Æ" invalid-0x80 >actual && + test_cmp expected actual +' + +test_expect_success GETTEXT_LOCALE,LIBPCRE2,PCRE2_MATCH_INVALID_UTF 'PCRE v2: grep non-ASCII from invalid UTF-8 data with -i (PCRE2 bug #2642)' ' + git grep -hi "Æ" invalid-0xe5 >actual && + test_cmp invalid-0xe5 actual && + git grep -hi "(*NO_JIT)Æ" invalid-0xe5 >actual && + test_cmp invalid-0xe5 actual && + + # Only the case of grepping the ASCII part in a way that + # relies on -i fails + git grep -hi "aÆ" invalid-0xe5 >actual && + test_cmp invalid-0xe5 actual && + git grep -hi "(*NO_JIT)aÆ" invalid-0xe5 >actual && + test_cmp invalid-0xe5 actual ' test_done diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh index 1074009cc0..286b18db3c 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh @@ -149,7 +149,31 @@ test_expect_success 'prefetch multiple remotes' ' git log prefetch/remote2/two && git fetch --all && test_cmp_rev refs/remotes/remote1/one refs/prefetch/remote1/one && - test_cmp_rev refs/remotes/remote2/two refs/prefetch/remote2/two + test_cmp_rev refs/remotes/remote2/two refs/prefetch/remote2/two && + + test_cmp_config refs/prefetch/ log.excludedecoration && + git log --oneline --decorate --all >log && + ! grep "prefetch" log +' + +test_expect_success 'prefetch and existing log.excludeDecoration values' ' + git config --unset-all log.excludeDecoration && + git config log.excludeDecoration refs/remotes/remote1/ && + git maintenance run --task=prefetch && + + git config --get-all log.excludeDecoration >out && + grep refs/remotes/remote1/ out && + grep refs/prefetch/ out && + + git log --oneline --decorate --all >log && + ! grep "prefetch" log && + ! grep "remote1" log && + grep "remote2" log && + + # a second run does not change the config + git maintenance run --task=prefetch && + git log --oneline --decorate --all >log2 && + test_cmp log log2 ' test_expect_success 'loose-objects task' ' @@ -232,6 +256,13 @@ test_expect_success 'incremental-repack task' ' HEAD ^HEAD~1 EOF + + # Delete refs that have not been repacked in these packs. + git for-each-ref --format="delete %(refname)" \ + refs/prefetch refs/tags >refs && + git update-ref --stdin <refs && + + # Replace the object directory with this pack layout. rm -f $packDir/pack-* && rm -f $packDir/loose-* && ls $packDir/*.pack >packs-before && @@ -312,6 +343,18 @@ test_expect_success 'maintenance.incremental-repack.auto' ' test_subcommand git multi-pack-index write --no-progress <trace-B ' +test_expect_success 'pack-refs task' ' + for n in $(test_seq 1 5) + do + git branch -f to-pack/$n HEAD || return 1 + done && + GIT_TRACE2_EVENT="$(pwd)/pack-refs.txt" \ + git maintenance run --task=pack-refs && + ls .git/refs/heads/ >after && + test_must_be_empty after && + test_subcommand git pack-refs --all --prune <pack-refs.txt +' + test_expect_success '--auto and --schedule incompatible' ' test_must_fail git maintenance run --auto --schedule=daily 2>err && test_i18ngrep "at most one" err @@ -365,18 +408,32 @@ test_expect_success 'maintenance.strategy inheritance' ' git maintenance run --schedule=hourly --quiet && GIT_TRACE2_EVENT="$(pwd)/incremental-daily.txt" \ git maintenance run --schedule=daily --quiet && + GIT_TRACE2_EVENT="$(pwd)/incremental-weekly.txt" \ + git maintenance run --schedule=weekly --quiet && test_subcommand git commit-graph write --split --reachable \ --no-progress <incremental-hourly.txt && test_subcommand ! git prune-packed --quiet <incremental-hourly.txt && test_subcommand ! git multi-pack-index write --no-progress \ <incremental-hourly.txt && + test_subcommand ! git pack-refs --all --prune \ + <incremental-hourly.txt && test_subcommand git commit-graph write --split --reachable \ --no-progress <incremental-daily.txt && test_subcommand git prune-packed --quiet <incremental-daily.txt && test_subcommand git multi-pack-index write --no-progress \ <incremental-daily.txt && + test_subcommand ! git pack-refs --all --prune \ + <incremental-daily.txt && + + test_subcommand git commit-graph write --split --reachable \ + --no-progress <incremental-weekly.txt && + test_subcommand git prune-packed --quiet <incremental-weekly.txt && + test_subcommand git multi-pack-index write --no-progress \ + <incremental-weekly.txt && + test_subcommand git pack-refs --all --prune \ + <incremental-weekly.txt && # Modify defaults git config maintenance.commit-graph.schedule daily && diff --git a/t/t9151-svn-mergeinfo.sh b/t/t9151-svn-mergeinfo.sh index 696ace2462..1fbe84feb1 100755 --- a/t/t9151-svn-mergeinfo.sh +++ b/t/t9151-svn-mergeinfo.sh @@ -12,49 +12,46 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME test_expect_success 'load svn dump' " svnadmin load -q '$rawsvnrepo' \ - < '$TEST_DIRECTORY/t9151/svn-mergeinfo.dump' && + <'$TEST_DIRECTORY/t9151/svn-mergeinfo.dump' && git svn init --minimize-url -R svnmerge \ --rewrite-root=http://svn.example.org \ -T trunk -b branches '$svnrepo' && git svn fetch --all - " +" test_expect_success 'all svn merges became git merge commits' ' - unmarked=$(git rev-list --parents --all --grep=Merge | - grep -v " .* " | cut -f1 -d" ") && - [ -z "$unmarked" ] - ' + git rev-list --all --no-merges --grep=Merge >unmarked && + test_must_be_empty unmarked +' test_expect_success 'cherry picks did not become git merge commits' ' - bad_cherries=$(git rev-list --parents --all --grep=Cherry | - grep " .* " | cut -f1 -d" ") && - [ -z "$bad_cherries" ] - ' + git rev-list --all --merges --grep=Cherry >bad-cherries && + test_must_be_empty bad-cherries +' test_expect_success 'svn non-merge merge commits did not become git merge commits' ' - bad_non_merges=$(git rev-list --parents --all --grep=non-merge | - grep " .* " | cut -f1 -d" ") && - [ -z "$bad_non_merges" ] - ' + git rev-list --all --merges --grep=non-merge >bad-non-merges && + test_must_be_empty bad-non-merges +' test_expect_success 'commit made to merged branch is reachable from the merge' ' before_commit=$(git rev-list --all --grep="trunk commit before merging trunk to b2") && merge_commit=$(git rev-list --all --grep="Merge trunk to b2") && - not_reachable=$(git rev-list -1 $before_commit --not $merge_commit) && - [ -z "$not_reachable" ] - ' + git rev-list -1 $before_commit --not $merge_commit >not-reachable && + test_must_be_empty not-reachable +' test_expect_success 'merging two branches in one commit is detected correctly' ' f1_commit=$(git rev-list --all --grep="make f1 branch from trunk") && f2_commit=$(git rev-list --all --grep="make f2 branch from trunk") && merge_commit=$(git rev-list --all --grep="Merge f1 and f2 to trunk") && - not_reachable=$(git rev-list -1 $f1_commit $f2_commit --not $merge_commit) && - [ -z "$not_reachable" ] - ' + git rev-list -1 $f1_commit $f2_commit --not $merge_commit >not-reachable && + test_must_be_empty not-reachable +' test_expect_failure 'everything got merged in the end' ' - unmerged=$(git rev-list --all --not main) && - [ -z "$unmerged" ] - ' + git rev-list --all --not main >unmerged && + test_must_be_empty unmerged +' test_done diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index 3d17e932a0..8f1caf8025 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -1632,7 +1632,10 @@ test_expect_success 'O: blank lines not necessary after other commands' ' INPUT_END git fast-import <input && - test 8 = $(find .git/objects/pack -type f | grep -v multi-pack-index | wc -l) && + ls -la .git/objects/pack/pack-*.pack >packlist && + ls -la .git/objects/pack/pack-*.pack >idxlist && + test_line_count = 4 idxlist && + test_line_count = 4 packlist && test $(git rev-parse refs/tags/O3-2nd) = $(git rev-parse O3^) && git log --reverse --pretty=oneline O3 | sed s/^.*z// >actual && test_cmp expect actual diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index fb67262fc8..04ce884ef5 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -2366,7 +2366,6 @@ test_expect_success 'sourcing the completion script clears cached commands' ' ' test_expect_success 'sourcing the completion script clears cached merge strategies' ' - GIT_TEST_GETTEXT_POISON=false && __git_compute_merge_strategies && verbose test -n "$__git_merge_strategies" && . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" && diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 6bca002316..05dc2cc6be 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -218,6 +218,12 @@ test_commit () { --signoff) signoff="$1" ;; + --date) + notick=yes + GIT_COMMITTER_DATE="$2" + GIT_AUTHOR_DATE="$2" + shift + ;; -C) indir="$2" shift @@ -1016,19 +1022,16 @@ test_cmp_bin () { cmp "$@" } -# Use this instead of test_cmp to compare files that contain expected and -# actual output from git commands that can be translated. When running -# under GIT_TEST_GETTEXT_POISON this pretends that the command produced expected -# results. +# Wrapper for test_cmp which used to be used for +# GIT_TEST_GETTEXT_POISON=false. Only here as a shim for other +# in-flight changes. Should not be used and will be removed soon. test_i18ncmp () { - ! test_have_prereq C_LOCALE_OUTPUT || test_cmp "$@" + test_cmp "$@" } -# Use this instead of "grep expected-string actual" to see if the -# output from a git command that can be translated either contains an -# expected string, or does not contain an unwanted one. When running -# under GIT_TEST_GETTEXT_POISON this pretends that the command produced expected -# results. +# Wrapper for grep which used to be used for +# GIT_TEST_GETTEXT_POISON=false. Only here as a shim for other +# in-flight changes. Should not be used and will be removed soon. test_i18ngrep () { eval "last_arg=\${$#}" @@ -1041,12 +1044,6 @@ test_i18ngrep () { BUG "too few parameters to test_i18ngrep" fi - if test_have_prereq !C_LOCALE_OUTPUT - then - # pretend success - return 0 - fi - if test "x!" = "x$1" then shift @@ -1683,3 +1680,45 @@ test_subcommand () { grep "\[$expr\]" fi } + +# Check that the given command was invoked as part of the +# trace2-format trace on stdin. +# +# test_region [!] <category> <label> git <command> <args>... +# +# For example, to look for trace2_region_enter("index", "do_read_index", repo) +# in an invocation of "git checkout HEAD~1", run +# +# GIT_TRACE2_EVENT="$(pwd)/trace.txt" GIT_TRACE2_EVENT_NESTING=10 \ +# git checkout HEAD~1 && +# test_region index do_read_index <trace.txt +# +# If the first parameter passed is !, this instead checks that +# the given region was not entered. +# +test_region () { + local expect_exit=0 + if test "$1" = "!" + then + expect_exit=1 + shift + fi + + grep -e '"region_enter".*"category":"'"$1"'","label":"'"$2"\" "$3" + exitcode=$? + + if test $exitcode != $expect_exit + then + return 1 + fi + + grep -e '"region_leave".*"category":"'"$1"'","label":"'"$2"\" "$3" + exitcode=$? + + if test $exitcode != $expect_exit + then + return 1 + fi + + return 0 +} diff --git a/t/test-lib.sh b/t/test-lib.sh index 03c1c0836f..431adba0fb 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -163,8 +163,8 @@ parse_option () { ;; --stress-jobs=*) stress=t; - stress=${opt#--*=} - case "$stress" in + stress_jobs=${opt#--*=} + case "$stress_jobs" in *[!0-9]*|0*|"") echo "error: --stress-jobs=<N> requires the number of jobs to run" >&2 exit 1 @@ -262,9 +262,9 @@ then : # Don't stress test again. elif test -n "$stress" then - if test "$stress" != t + if test -n "$stress_jobs" then - job_count=$stress + job_count=$stress_jobs elif test -n "$GIT_TEST_STRESS_LOAD" then job_count="$GIT_TEST_STRESS_LOAD" @@ -404,15 +404,6 @@ TZ=UTC export LANG LC_ALL PAGER TZ EDITOR=: -# GIT_TEST_GETTEXT_POISON should not influence git commands executed -# during initialization of test-lib and the test repo. Back it up, -# unset and then restore after initialization is finished. -if test -n "$GIT_TEST_GETTEXT_POISON" -then - GIT_TEST_GETTEXT_POISON_ORIG=$GIT_TEST_GETTEXT_POISON - unset GIT_TEST_GETTEXT_POISON -fi - # A call to "unset" with no arguments causes at least Solaris 10 # /usr/xpg4/bin/sh and /bin/ksh to bail out. So keep the unsets # deriving from the command substitution clustered with the other @@ -1524,21 +1515,14 @@ esac test -z "$NO_PERL" && test_set_prereq PERL test -z "$NO_PTHREADS" && test_set_prereq PTHREADS test -z "$NO_PYTHON" && test_set_prereq PYTHON -test -n "$USE_LIBPCRE1$USE_LIBPCRE2" && test_set_prereq PCRE -test -n "$USE_LIBPCRE1" && test_set_prereq LIBPCRE1 +test -n "$USE_LIBPCRE2" && test_set_prereq PCRE test -n "$USE_LIBPCRE2" && test_set_prereq LIBPCRE2 test -z "$NO_GETTEXT" && test_set_prereq GETTEXT -if test -n "$GIT_TEST_GETTEXT_POISON_ORIG" -then - GIT_TEST_GETTEXT_POISON=$GIT_TEST_GETTEXT_POISON_ORIG - export GIT_TEST_GETTEXT_POISON - unset GIT_TEST_GETTEXT_POISON_ORIG -fi - -test_lazy_prereq C_LOCALE_OUTPUT ' - ! test_bool_env GIT_TEST_GETTEXT_POISON false -' +# Used to be used for GIT_TEST_GETTEXT_POISON=false. Only here as a +# shim for other in-flight changes. Should not be used and will be +# removed soon. +test_set_prereq C_LOCALE_OUTPUT if test -z "$GIT_TEST_CHECK_CACHE_TREE" then @@ -13,26 +13,27 @@ const char *tag_type = "tag"; static int run_gpg_verify(const char *buf, unsigned long size, unsigned flags) { struct signature_check sigc; - size_t payload_size; + struct strbuf payload = STRBUF_INIT; + struct strbuf signature = STRBUF_INIT; int ret; memset(&sigc, 0, sizeof(sigc)); - payload_size = parse_signature(buf, size); - - if (size == payload_size) { + if (!parse_signature(buf, size, &payload, &signature)) { if (flags & GPG_VERIFY_VERBOSE) - write_in_full(1, buf, payload_size); + write_in_full(1, buf, size); return error("no signature found"); } - ret = check_signature(buf, payload_size, buf + payload_size, - size - payload_size, &sigc); + ret = check_signature(payload.buf, payload.len, signature.buf, + signature.len, &sigc); if (!(flags & GPG_VERIFY_OMIT_STATUS)) print_signature_buffer(&sigc, flags); signature_check_clear(&sigc); + strbuf_release(&payload); + strbuf_release(&signature); return ret; } diff --git a/tmp-objdir.c b/tmp-objdir.c index 42ed4db5d3..b8d880e362 100644 --- a/tmp-objdir.c +++ b/tmp-objdir.c @@ -185,9 +185,11 @@ static int pack_copy_priority(const char *name) return 1; if (ends_with(name, ".pack")) return 2; - if (ends_with(name, ".idx")) + if (ends_with(name, ".rev")) return 3; - return 4; + if (ends_with(name, ".idx")) + return 4; + return 5; } static int pack_copy_cmp(const char *a, const char *b) diff --git a/transport-helper.c b/transport-helper.c index 5f6e0b3bd8..49b7fb4dcb 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -1162,13 +1162,14 @@ static int has_attribute(const char *attrs, const char *attr) } static struct ref *get_refs_list(struct transport *transport, int for_push, - const struct strvec *ref_prefixes) + struct transport_ls_refs_options *transport_options) { get_helper(transport); if (process_connect(transport, for_push)) { do_take_over(transport); - return transport->vtable->get_refs_list(transport, for_push, ref_prefixes); + return transport->vtable->get_refs_list(transport, for_push, + transport_options); } return get_refs_list_using_list(transport, for_push); diff --git a/transport-internal.h b/transport-internal.h index 27c9daffc4..b60f1ba907 100644 --- a/transport-internal.h +++ b/transport-internal.h @@ -4,6 +4,7 @@ struct ref; struct transport; struct strvec; +struct transport_ls_refs_options; struct transport_vtable { /** @@ -18,19 +19,12 @@ struct transport_vtable { * the transport to try to share connections, for_push is a * hint as to whether the ultimate operation is a push or a fetch. * - * If communicating using protocol v2 a list of prefixes can be - * provided to be sent to the server to enable it to limit the ref - * advertisement. Since ref filtering is done on the server's end, and - * only when using protocol v2, this list will be ignored when not - * using protocol v2 meaning this function can return refs which don't - * match the provided ref_prefixes. - * * If the transport is able to determine the remote hash for * the ref without a huge amount of effort, it should store it * in the ref's old_sha1 field; otherwise it should be all 0. **/ struct ref *(*get_refs_list)(struct transport *transport, int for_push, - const struct strvec *ref_prefixes); + struct transport_ls_refs_options *transport_options); /** * Fetch the objects for the given refs. Note that this gets diff --git a/transport.c b/transport.c index 679a35e7c1..b13fab5dc3 100644 --- a/transport.c +++ b/transport.c @@ -127,7 +127,7 @@ struct bundle_transport_data { static struct ref *get_refs_from_bundle(struct transport *transport, int for_push, - const struct strvec *ref_prefixes) + struct transport_ls_refs_options *transport_options) { struct bundle_transport_data *data = transport->data; struct ref *result = NULL; @@ -280,7 +280,7 @@ static void die_if_server_options(struct transport *transport) * remote refs. */ static struct ref *handshake(struct transport *transport, int for_push, - const struct strvec *ref_prefixes, + struct transport_ls_refs_options *options, int must_list_refs) { struct git_transport_data *data = transport->data; @@ -303,7 +303,7 @@ static struct ref *handshake(struct transport *transport, int for_push, trace2_data_string("transfer", NULL, "server-sid", server_sid); if (must_list_refs) get_remote_refs(data->fd[1], &reader, &refs, for_push, - ref_prefixes, + options, transport->server_options, transport->stateless_rpc); break; @@ -334,9 +334,9 @@ static struct ref *handshake(struct transport *transport, int for_push, } static struct ref *get_refs_via_connect(struct transport *transport, int for_push, - const struct strvec *ref_prefixes) + struct transport_ls_refs_options *options) { - return handshake(transport, for_push, ref_prefixes, 1); + return handshake(transport, for_push, options, 1); } static int fetch_refs_via_pack(struct transport *transport, @@ -1252,19 +1252,20 @@ int transport_push(struct repository *r, int porcelain = flags & TRANSPORT_PUSH_PORCELAIN; int pretend = flags & TRANSPORT_PUSH_DRY_RUN; int push_ret, ret, err; - struct strvec ref_prefixes = STRVEC_INIT; + struct transport_ls_refs_options transport_options = + TRANSPORT_LS_REFS_OPTIONS_INIT; if (check_push_refs(local_refs, rs) < 0) return -1; - refspec_ref_prefixes(rs, &ref_prefixes); + refspec_ref_prefixes(rs, &transport_options.ref_prefixes); trace2_region_enter("transport_push", "get_refs_list", r); remote_refs = transport->vtable->get_refs_list(transport, 1, - &ref_prefixes); + &transport_options); trace2_region_leave("transport_push", "get_refs_list", r); - strvec_clear(&ref_prefixes); + strvec_clear(&transport_options.ref_prefixes); if (flags & TRANSPORT_PUSH_ALL) match_flags |= MATCH_REFS_ALL; @@ -1380,12 +1381,12 @@ int transport_push(struct repository *r, } const struct ref *transport_get_remote_refs(struct transport *transport, - const struct strvec *ref_prefixes) + struct transport_ls_refs_options *transport_options) { if (!transport->got_remote_refs) { transport->remote_refs = transport->vtable->get_refs_list(transport, 0, - ref_prefixes); + transport_options); transport->got_remote_refs = 1; } diff --git a/transport.h b/transport.h index 24558c027d..24e15799e7 100644 --- a/transport.h +++ b/transport.h @@ -233,17 +233,32 @@ int transport_push(struct repository *repo, struct refspec *rs, int flags, unsigned int * reject_reasons); +struct transport_ls_refs_options { + /* + * Optionally, a list of ref prefixes can be provided which can be sent + * to the server (when communicating using protocol v2) to enable it to + * limit the ref advertisement. Since ref filtering is done on the + * server's end (and only when using protocol v2), + * transport_get_remote_refs() could return refs which don't match the + * provided ref_prefixes. + */ + struct strvec ref_prefixes; + + /* + * If unborn_head_target is not NULL, and the remote reports HEAD as + * pointing to an unborn branch, transport_get_remote_refs() stores the + * unborn branch in unborn_head_target. It should be freed by the + * caller. + */ + char *unborn_head_target; +}; +#define TRANSPORT_LS_REFS_OPTIONS_INIT { STRVEC_INIT } + /* * Retrieve refs from a remote. - * - * Optionally a list of ref prefixes can be provided which can be sent to the - * server (when communicating using protocol v2) to enable it to limit the ref - * advertisement. Since ref filtering is done on the server's end (and only - * when using protocol v2), this can return refs which don't match the provided - * ref_prefixes. */ const struct ref *transport_get_remote_refs(struct transport *transport, - const struct strvec *ref_prefixes); + struct transport_ls_refs_options *transport_options); /* * Fetch the hash algorithm used by a remote. diff --git a/tree-walk.c b/tree-walk.c index 0160294712..2d6226d5f1 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -4,6 +4,7 @@ #include "object-store.h" #include "tree.h" #include "pathspec.h" +#include "json-writer.h" static const char *get_mode(const char *str, unsigned int *modep) { @@ -167,6 +168,25 @@ int tree_entry_gently(struct tree_desc *desc, struct name_entry *entry) return 1; } +static int traverse_trees_atexit_registered; +static int traverse_trees_count; +static int traverse_trees_cur_depth; +static int traverse_trees_max_depth; + +static void trace2_traverse_trees_statistics_atexit(void) +{ + struct json_writer jw = JSON_WRITER_INIT; + + jw_object_begin(&jw, 0); + jw_object_intmax(&jw, "traverse_trees_count", traverse_trees_count); + jw_object_intmax(&jw, "traverse_trees_max_depth", traverse_trees_max_depth); + jw_end(&jw); + + trace2_data_json("traverse_trees", the_repository, "statistics", &jw); + + jw_release(&jw); +} + void setup_traverse_info(struct traverse_info *info, const char *base) { size_t pathlen = strlen(base); @@ -180,6 +200,11 @@ void setup_traverse_info(struct traverse_info *info, const char *base) info->namelen = pathlen; if (pathlen) info->prev = &dummy; + + if (trace2_is_enabled() && !traverse_trees_atexit_registered) { + atexit(trace2_traverse_trees_statistics_atexit); + traverse_trees_atexit_registered = 1; + } } char *make_traverse_path(char *path, size_t pathlen, @@ -416,6 +441,12 @@ int traverse_trees(struct index_state *istate, int interesting = 1; char *traverse_path; + traverse_trees_count++; + traverse_trees_cur_depth++; + + if (traverse_trees_cur_depth > traverse_trees_max_depth) + traverse_trees_max_depth = traverse_trees_cur_depth; + if (n >= ARRAY_SIZE(entry)) BUG("traverse_trees() called with too many trees (%d)", n); @@ -515,6 +546,8 @@ int traverse_trees(struct index_state *istate, free(traverse_path); info->traverse_path = NULL; strbuf_release(&base); + + traverse_trees_cur_depth--; return error; } diff --git a/unpack-trees.c b/unpack-trees.c index 323280dd48..f5f668f532 100644 --- a/unpack-trees.c +++ b/unpack-trees.c @@ -1549,14 +1549,10 @@ static void mark_new_skip_worktree(struct pattern_list *pl, static void populate_from_existing_patterns(struct unpack_trees_options *o, struct pattern_list *pl) { - char *sparse = git_pathdup("info/sparse-checkout"); - - pl->use_cone_patterns = core_sparse_checkout_cone; - if (add_patterns_from_file_to_list(sparse, "", 0, pl, NULL) < 0) + if (get_sparse_checkout_patterns(pl) < 0) o->skip_sparse_checkout = 1; else o->pl = pl; - free(sparse); } @@ -1580,6 +1576,8 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES); trace_performance_enter(); + trace2_region_enter("unpack_trees", "unpack_trees", the_repository); + if (!core_apply_sparse_checkout || !o->update) o->skip_sparse_checkout = 1; if (!o->skip_sparse_checkout && !o->pl) { @@ -1653,7 +1651,9 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options } trace_performance_enter(); + trace2_region_enter("unpack_trees", "traverse_trees", the_repository); ret = traverse_trees(o->src_index, len, t, &info); + trace2_region_leave("unpack_trees", "traverse_trees", the_repository); trace_performance_leave("traverse_trees"); if (ret < 0) goto return_failed; @@ -1722,8 +1722,6 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options if (!ret) { if (git_env_bool("GIT_TEST_CHECK_CACHE_TREE", 0)) cache_tree_verify(the_repository, &o->result); - if (!o->result.cache_tree) - o->result.cache_tree = cache_tree(); if (!cache_tree_fully_valid(o->result.cache_tree)) cache_tree_update(&o->result, WRITE_TREE_SILENT | @@ -1741,6 +1739,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options done: if (free_pattern_list) clear_pattern_list(&pl); + trace2_region_leave("unpack_trees", "unpack_trees", the_repository); trace_performance_leave("unpack_trees"); return ret; diff --git a/upload-pack.c b/upload-pack.c index 3b66bf92ba..e19583ae0f 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -305,14 +305,7 @@ static void create_pack_file(struct upload_pack_data *pack_data, if (pack_data->filter_options.choice) { const char *spec = expand_list_objects_filter_spec(&pack_data->filter_options); - if (pack_objects.use_shell) { - struct strbuf buf = STRBUF_INIT; - sq_quote_buf(&buf, spec); - strvec_pushf(&pack_objects.args, "--filter=%s", buf.buf); - strbuf_release(&buf); - } else { - strvec_pushf(&pack_objects.args, "--filter=%s", spec); - } + strvec_pushf(&pack_objects.args, "--filter=%s", spec); } if (uri_protocols) { for (i = 0; i < uri_protocols->nr; i++) @@ -500,7 +493,7 @@ static int got_oid(struct upload_pack_data *data, static int ok_to_give_up(struct upload_pack_data *data) { - uint32_t min_generation = GENERATION_NUMBER_ZERO; + timestamp_t min_generation = GENERATION_NUMBER_ZERO; if (!data->have_obj.nr) return 0; @@ -1232,7 +1225,7 @@ static int send_ref(const char *refname, const struct object_id *oid, packet_write_fmt(1, "%s %s\n", oid_to_hex(oid), refname_nons); } capabilities = NULL; - if (!peel_ref(refname, &peeled)) + if (!peel_iterated_oid(oid, &peeled)) packet_write_fmt(1, "%s %s^{}\n", oid_to_hex(&peeled), refname_nons); return 0; } @@ -266,6 +266,10 @@ int BUG_exit_code; static NORETURN void BUG_vfl(const char *file, int line, const char *fmt, va_list params) { char prefix[256]; + va_list params_copy; + static int in_bug; + + va_copy(params_copy, params); /* truncation via snprintf is OK here */ if (file) @@ -274,6 +278,13 @@ static NORETURN void BUG_vfl(const char *file, int line, const char *fmt, va_lis snprintf(prefix, sizeof(prefix), "BUG: "); vreportf(prefix, fmt, params); + + if (in_bug) + abort(); + in_bug = 1; + + trace2_cmd_error_va(fmt, params_copy); + if (BUG_exit_code) exit(BUG_exit_code); abort(); diff --git a/worktree.c b/worktree.c index 821b233479..e00858540e 100644 --- a/worktree.c +++ b/worktree.c @@ -15,6 +15,7 @@ void free_worktrees(struct worktree **worktrees) free(worktrees[i]->id); free(worktrees[i]->head_ref); free(worktrees[i]->lock_reason); + free(worktrees[i]->prune_reason); free(worktrees[i]); } free (worktrees); @@ -224,7 +225,8 @@ int is_main_worktree(const struct worktree *wt) const char *worktree_lock_reason(struct worktree *wt) { - assert(!is_main_worktree(wt)); + if (is_main_worktree(wt)) + return NULL; if (!wt->lock_reason_valid) { struct strbuf path = STRBUF_INIT; @@ -245,6 +247,25 @@ const char *worktree_lock_reason(struct worktree *wt) return wt->lock_reason; } +const char *worktree_prune_reason(struct worktree *wt, timestamp_t expire) +{ + struct strbuf reason = STRBUF_INIT; + char *path = NULL; + + if (is_main_worktree(wt)) + return NULL; + if (wt->prune_reason_valid) + return wt->prune_reason; + + if (should_prune_worktree(wt->id, &reason, &path, expire)) + wt->prune_reason = strbuf_detach(&reason, NULL); + wt->prune_reason_valid = 1; + + strbuf_release(&reason); + free(path); + return wt->prune_reason; +} + /* convenient wrapper to deal with NULL strbuf */ static void strbuf_addf_gently(struct strbuf *buf, const char *fmt, ...) { @@ -741,3 +762,71 @@ done: strbuf_release(&realdotgit); strbuf_release(&dotgit); } + +int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath, timestamp_t expire) +{ + struct stat st; + char *path; + int fd; + size_t len; + ssize_t read_result; + + *wtpath = NULL; + if (!is_directory(git_path("worktrees/%s", id))) { + strbuf_addstr(reason, _("not a valid directory")); + return 1; + } + if (file_exists(git_path("worktrees/%s/locked", id))) + return 0; + if (stat(git_path("worktrees/%s/gitdir", id), &st)) { + strbuf_addstr(reason, _("gitdir file does not exist")); + return 1; + } + fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY); + if (fd < 0) { + strbuf_addf(reason, _("unable to read gitdir file (%s)"), + strerror(errno)); + return 1; + } + len = xsize_t(st.st_size); + path = xmallocz(len); + + read_result = read_in_full(fd, path, len); + if (read_result < 0) { + strbuf_addf(reason, _("unable to read gitdir file (%s)"), + strerror(errno)); + close(fd); + free(path); + return 1; + } + close(fd); + + if (read_result != len) { + strbuf_addf(reason, + _("short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"), + (uintmax_t)len, (uintmax_t)read_result); + free(path); + return 1; + } + while (len && (path[len - 1] == '\n' || path[len - 1] == '\r')) + len--; + if (!len) { + strbuf_addstr(reason, _("invalid gitdir file")); + free(path); + return 1; + } + path[len] = '\0'; + if (!file_exists(path)) { + if (stat(git_path("worktrees/%s/index", id), &st) || + st.st_mtime <= expire) { + strbuf_addstr(reason, _("gitdir file points to non-existent location")); + free(path); + return 1; + } else { + *wtpath = path; + return 0; + } + } + *wtpath = path; + return 0; +} diff --git a/worktree.h b/worktree.h index f38e6fd5a2..8b7c408132 100644 --- a/worktree.h +++ b/worktree.h @@ -11,11 +11,13 @@ struct worktree { char *id; char *head_ref; /* NULL if HEAD is broken or detached */ char *lock_reason; /* private - use worktree_lock_reason */ + char *prune_reason; /* private - use worktree_prune_reason */ struct object_id head_oid; int is_detached; int is_bare; int is_current; int lock_reason_valid; /* private */ + int prune_reason_valid; /* private */ }; /* @@ -73,6 +75,27 @@ int is_main_worktree(const struct worktree *wt); */ const char *worktree_lock_reason(struct worktree *wt); +/* + * Return the reason string if the given worktree should be pruned, otherwise + * NULL if it should not be pruned. `expire` defines a grace period to prune + * the worktree when its path does not exist. + */ +const char *worktree_prune_reason(struct worktree *wt, timestamp_t expire); + +/* + * Return true if worktree entry should be pruned, along with the reason for + * pruning. Otherwise, return false and the worktree's path in `wtpath`, or + * NULL if it cannot be determined. Caller is responsible for freeing + * returned path. + * + * `expire` defines a grace period to prune the worktree when its path + * does not exist. + */ +int should_prune_worktree(const char *id, + struct strbuf *reason, + char **wtpath, + timestamp_t expire); + #define WT_VALIDATE_WORKTREE_MISSING_OK (1 << 0) /* |