diff options
197 files changed, 4719 insertions, 2019 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f6885e88ee..5f2f884b92 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -289,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: @@ -340,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 index 3760bd17f7..249ef1492f 100644 --- a/Documentation/RelNotes/2.30.1.txt +++ b/Documentation/RelNotes/2.30.1.txt @@ -44,4 +44,12 @@ Fixes since v2.30 * 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 905c9aa52b..aae9204663 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 @@ -54,6 +56,10 @@ UI, Workflows & Features 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. + Performance, Internal Implementation, Development Support etc. @@ -104,13 +110,11 @@ Performance, Internal Implementation, Development Support etc. hierarchy, which was quite wasteful. * A perf script was made more portable. - (merge f08b6c553d jk/p5303-sed-portability-fix later to maint). * 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. - (merge 2b0e14f640 pb/ci-matrix-wo-shortcut later to maint). * We've carried compatibility codepaths for compilers without variadic macros for quite some time, but the world may be ready for @@ -119,45 +123,52 @@ Performance, Internal Implementation, Development Support etc. 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. + 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 @@ -169,33 +180,34 @@ 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. - (merge 28cc00a13d ab/fsck-doc-fix later to maint). * Doc fix for packfile URI feature. - (merge bfc2a36ff2 jt/packfile-as-uri-doc later to maint). + + * 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). * 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 4a5ec7d166 js/skip-dashed-built-ins-from-config-mak later to maint). - (merge 6eaf624dea pb/blame-funcname-range-userdiff 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/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/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-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-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-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/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/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/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 @@ -1360,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) @@ -2727,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/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/diff-files.c b/builtin/diff-files.c index 4742a4559b..bb85266102 100644 --- a/builtin/diff-files.c +++ b/builtin/diff-files.c @@ -36,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] == '-') { 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 5cfe1717e8..0f4859abf7 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -453,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/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 d0cbaaf68a..be29c3f89f 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -1223,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); } } @@ -1672,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-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/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 13cde5896a..6d62aaf59a 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -2953,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; @@ -3592,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, 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/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/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/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/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 3f1a8d4f1b..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,13 +179,13 @@ 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 struct cache_entry *this_ce = cache[i]; - const struct cache_entry *next_ce = cache[i + 1]; + 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); @@ -436,16 +435,20 @@ 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(); trace2_region_enter("cache_tree", "update", the_repository); - i = update_one(it, cache, entries, "", 0, &skip, flags); + 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) @@ -635,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; 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 b8c1b034a4..f70886c2d3 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; @@ -388,6 +398,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 +614,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 +647,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 +724,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 +813,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 +861,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 +1016,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 +1035,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 +1089,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 +1109,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 +1120,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 +1151,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 +1186,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 +1197,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 +1261,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 +1424,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 +1447,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 +1509,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++; } } } @@ -1593,9 +1778,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 +1892,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 +2118,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 +2316,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 +2334,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 +2345,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 +2373,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 +2397,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 +2443,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 +2575,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 +2640,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) @@ -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); 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)); 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/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: */ @@ -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 5d5e47fbe2..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 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); @@ -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 e048467650..e7dab99950 100644 --- a/log-tree.c +++ b/log-tree.c @@ -808,6 +808,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 +827,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)); } @@ -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,7 +98,7 @@ 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_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) { /* @@ -92,6 +146,7 @@ int ls_refs(struct repository *r, struct strvec *keys, 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) { @@ -104,12 +159,14 @@ 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); + send_possibly_unborn_head(&data); if (!data.prefixes.nr) strvec_push(&data.prefixes, ""); for_each_fullref_in_prefixes(get_git_namespace(), data.prefixes.v, @@ -118,3 +175,14 @@ int ls_refs(struct repository *r, struct strvec *keys, 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 6900ab9e7f..931b91438c 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -53,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 @@ -143,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; @@ -283,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 @@ -314,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; @@ -483,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, @@ -584,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. */ @@ -658,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; @@ -673,7 +752,9 @@ 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; } @@ -1063,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, @@ -1081,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 @@ -1367,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; @@ -1388,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 @@ -1418,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; } @@ -1428,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++) { @@ -2124,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 @@ -2148,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; /* @@ -2166,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", @@ -2181,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() ***/ @@ -2339,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)) { @@ -2352,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) { @@ -2361,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); @@ -2382,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); @@ -2419,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); @@ -2446,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 @@ -2472,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 ***/ @@ -2487,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 @@ -2499,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); @@ -2603,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, @@ -2614,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 @@ -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 c7a0e8d3d7..b78733f508 100644 --- a/revision.c +++ b/revision.c @@ -2465,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")) { @@ -3272,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; @@ -3340,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; @@ -3369,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 @@ -3382,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; @@ -3440,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; @@ -3508,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/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/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-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/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/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/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/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/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/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/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/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/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/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 76062db296..431adba0fb 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -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 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/unpack-trees.c b/unpack-trees.c index af6e9b9c2f..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); } @@ -1726,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 | diff --git a/upload-pack.c b/upload-pack.c index 4ab55ce2b5..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; @@ -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) /* |