summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/main.yml4
-rw-r--r--Documentation/RelNotes/2.30.1.txt55
-rw-r--r--Documentation/RelNotes/2.31.0.txt53
-rw-r--r--Documentation/diff-generate-patch.txt6
-rw-r--r--Documentation/diff-options.txt51
-rw-r--r--Documentation/git-fsck.txt8
-rw-r--r--Documentation/git-log.txt46
-rw-r--r--Documentation/git-ls-files.txt8
-rw-r--r--Documentation/git-show.txt7
-rw-r--r--Documentation/git-worktree.txt74
-rw-r--r--Documentation/rev-list-options.txt5
-rw-r--r--Documentation/technical/index-format.txt42
-rw-r--r--Documentation/technical/packfile-uri.txt7
-rw-r--r--Makefile29
-rw-r--r--builtin/branch.c47
-rw-r--r--builtin/checkout.c3
-rw-r--r--builtin/describe.c2
-rw-r--r--builtin/diff-files.c5
-rw-r--r--builtin/diff.c9
-rw-r--r--builtin/gc.c8
-rw-r--r--builtin/grep.c5
-rw-r--r--builtin/log.c22
-rw-r--r--builtin/ls-files.c81
-rw-r--r--builtin/merge.c3
-rw-r--r--builtin/name-rev.c10
-rw-r--r--builtin/pack-objects.c10
-rw-r--r--builtin/show-ref.c2
-rw-r--r--builtin/sparse-checkout.c5
-rw-r--r--builtin/tag.c44
-rw-r--r--builtin/worktree.c110
-rw-r--r--cache-tree.c64
-rw-r--r--cache-tree.h2
-rw-r--r--cache.h1
-rw-r--r--commit-graph.c32
-rw-r--r--commit.c18
-rw-r--r--commit.h2
-rw-r--r--diff-merges.c146
-rw-r--r--diff-merges.h24
-rw-r--r--dir.c17
-rw-r--r--dir.h2
-rw-r--r--fmt-merge-msg.c3
-rw-r--r--fsmonitor.c27
-rw-r--r--git-compat-util.h7
-rw-r--r--grep.c101
-rw-r--r--grep.h1
-rw-r--r--hash-lookup.c18
-rw-r--r--hash-lookup.h10
-rw-r--r--log-tree.c30
-rw-r--r--ls-refs.c8
-rw-r--r--merge-ort.c671
-rw-r--r--name-hash.c3
-rw-r--r--oid-array.c8
-rw-r--r--pack-bitmap-write.c8
-rw-r--r--ref-filter.c74
-rw-r--r--refs.c116
-rw-r--r--refs.h27
-rw-r--r--repository.c6
-rw-r--r--rerere.c75
-rw-r--r--revision.c40
-rw-r--r--revision.h9
-rw-r--r--run-command.h9
-rw-r--r--sequencer.c16
-rw-r--r--shallow.c2
-rw-r--r--t/annotate-tests.sh8
-rw-r--r--t/helper/test-ref-store.c13
-rwxr-xr-xt/perf/p5303-many-packs.sh12
-rwxr-xr-xt/t0000-basic.sh570
-rwxr-xr-xt/t0500-progress-display.sh3
-rwxr-xr-xt/t1092-sparse-checkout-compatibility.sh301
-rwxr-xr-xt/t1405-main-ref-store.sh8
-rwxr-xr-xt/t1406-submodule-ref-store.sh10
-rwxr-xr-xt/t2402-worktree-list.sh96
-rwxr-xr-xt/t3012-ls-files-dedup.sh66
-rwxr-xr-xt/t3415-rebase-autosquash.sh8
-rwxr-xr-xt/t4013-diff-various.sh11
-rw-r--r--t/t4013/diff.log_--cc_-m_-p_master200
-rw-r--r--t/t4013/diff.log_--diff-merges=first-parent_master56
-rw-r--r--t/t4013/diff.log_-c_-m_-p_master200
-rw-r--r--t/t4013/diff.log_-p_--diff-merges=first-parent_master137
-rwxr-xr-xt/t7104-reset-hard.sh2
-rwxr-xr-xt/t7800-difftool.sh38
-rwxr-xr-xt/t7900-maintenance.sh33
-rw-r--r--t/test-lib-functions.sh42
-rw-r--r--t/test-lib.sh8
-rw-r--r--tree-walk.c33
-rw-r--r--unpack-trees.c13
-rw-r--r--upload-pack.c2
-rw-r--r--worktree.c91
-rw-r--r--worktree.h23
89 files changed, 3287 insertions, 1035 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index aef6643648..f6885e88ee 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -123,6 +123,7 @@ jobs:
runs-on: windows-latest
needs: [windows-build]
strategy:
+ fail-fast: false
matrix:
nr: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
steps:
@@ -227,6 +228,7 @@ jobs:
runs-on: windows-latest
needs: [vs-build, windows-build]
strategy:
+ fail-fast: false
matrix:
nr: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
steps:
@@ -272,6 +274,7 @@ jobs:
needs: ci-config
if: needs.ci-config.outputs.enabled == 'yes'
strategy:
+ fail-fast: false
matrix:
vector:
- jobname: linux-clang
@@ -309,6 +312,7 @@ jobs:
needs: ci-config
if: needs.ci-config.outputs.enabled == 'yes'
strategy:
+ fail-fast: false
matrix:
vector:
- jobname: linux-musl
diff --git a/Documentation/RelNotes/2.30.1.txt b/Documentation/RelNotes/2.30.1.txt
new file mode 100644
index 0000000000..249ef1492f
--- /dev/null
+++ b/Documentation/RelNotes/2.30.1.txt
@@ -0,0 +1,55 @@
+Git v2.30.1 Release Notes
+=========================
+
+This release is primarily to merge fixes accumulated on the 'master'
+front to prepare for 2.31 release that are still relevant to 2.30.x
+maintenance track.
+
+Fixes since v2.30
+-----------------
+
+ * "git fetch --recurse-submodules" failed to update a submodule
+ when it has an uninitialized (hence of no interest to the user)
+ sub-submodule, which has been corrected.
+
+ * Command line error of "git rebase" are diagnosed earlier.
+
+ * "git stash" did not work well in a sparsely checked out working
+ tree.
+
+ * Some tests expect that "ls -l" output has either '-' or 'x' for
+ group executable bit, but setgid bit can be inherited from parent
+ directory and make these fields 'S' or 's' instead, causing test
+ failures.
+
+ * "git for-each-repo --config=<var> <cmd>" should not run <cmd> for
+ any repository when the configuration variable <var> is not defined
+ even once.
+
+ * "git mergetool --tool-help" was broken in 2.29 and failed to list
+ all the available tools.
+
+ * Fix for procedure to building CI test environment for mac.
+
+ * Newline characters in the host and path part of git:// URL are
+ now forbidden.
+
+ * When more than one commit with the same patch ID appears on one
+ side, "git log --cherry-pick A...B" did not exclude them all when a
+ commit with the same patch ID appears on the other side. Now it
+ does.
+
+ * Documentation for "git fsck" lost stale bits that has become
+ incorrect.
+
+ * Doc for packfile URI feature has been clarified.
+
+ * The implementation of "git branch --sort" wrt the detached HEAD
+ display has always been hacky, which has been cleaned up.
+
+ * Our setting of GitHub CI test jobs were a bit too eager to give up
+ once there is even one failure found. Tweak the knob to allow
+ other jobs keep running even when we see a failure, so that we can
+ find more failures in a single run.
+
+Also contains minor documentation updates and code clean-ups.
diff --git a/Documentation/RelNotes/2.31.0.txt b/Documentation/RelNotes/2.31.0.txt
index 7f53db7de9..905c9aa52b 100644
--- a/Documentation/RelNotes/2.31.0.txt
+++ b/Documentation/RelNotes/2.31.0.txt
@@ -48,6 +48,13 @@ UI, Workflows & Features
standard input. Also, it now does not lose refs whey they point
at the same object.
+ * "git log" learned a new "--diff-merges=<how>" option.
+
+ * "git ls-files" can and does show multiple entries when the index is
+ unmerged, which is a source for confusion unless -s/-u option is in
+ use. A new option --deduplicate has been introduced.
+
+
Performance, Internal Implementation, Development Support etc.
* A 3-year old test that was not testing anything useful has been
@@ -75,6 +82,43 @@ Performance, Internal Implementation, Development Support etc.
* "git fetch" learns to treat ref updates atomically in all-or-none
fashion, just like "git push" does, with the new "--atomic" option.
+ * The peel_ref() API has been replaced with peel_iterated_oid().
+
+ * The .use_shell flag in struct child_process that is passed to
+ run_command() API has been clarified with a bit more documentation.
+
+ * Document, clean-up and optimize the code around the cache-tree
+ extension in the index.
+
+ * The ls-refs protocol operation has been optimized to narrow the
+ sub-hierarchy of refs/ it walks to produce response.
+
+ * When removing many branches and tags, the code used to do so one
+ ref at a time. There is another API it can use to delete multiple
+ refs, and it makes quite a lot of performance difference when the
+ refs are packed.
+
+ * The "pack-objects" command needs to iterate over all the tags when
+ automatic tag following is enabled, but it actually iterated over
+ all refs and then discarded everything outside "refs/tags/"
+ hierarchy, which was quite wasteful.
+
+ * A perf script was made more portable.
+ (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
+ them to be removed. Force compilation failure on exotic platforms
+ where variadic macros are not available to find out who screams in
+ such a way that we can easily revert if it turns out that the world
+ is not yet ready.
+
Fixes since v2.30
-----------------
@@ -127,6 +171,13 @@ Fixes since v2.30
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).
+
* 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).
@@ -146,3 +197,5 @@ Fixes since v2.30
(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).
diff --git a/Documentation/diff-generate-patch.txt b/Documentation/diff-generate-patch.txt
index b10ff4caa6..2db8eacc3e 100644
--- a/Documentation/diff-generate-patch.txt
+++ b/Documentation/diff-generate-patch.txt
@@ -81,9 +81,9 @@ Combined diff format
Any diff-generating command can take the `-c` or `--cc` option to
produce a 'combined diff' when showing a merge. This is the default
format when showing merges with linkgit:git-diff[1] or
-linkgit:git-show[1]. Note also that you can give the `-m` option to any
-of these commands to force generation of diffs with individual parents
-of a merge.
+linkgit:git-show[1]. Note also that you can give suitable
+`--diff-merges` option to any of these commands to force generation of
+diffs in specific format.
A "combined diff" format looks like this:
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index 746b144c76..e5733ccb2d 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -33,6 +33,57 @@ endif::git-diff[]
show the patch by default, or to cancel the effect of `--patch`.
endif::git-format-patch[]
+ifdef::git-log[]
+--diff-merges=(off|none|first-parent|1|separate|m|combined|c|dense-combined|cc)::
+--no-diff-merges::
+ Specify diff format to be used for merge commits. Default is
+ {diff-merges-default} unless `--first-parent` is in use, in which case
+ `first-parent` is the default.
++
+--diff-merges=(off|none):::
+--no-diff-merges:::
+ Disable output of diffs for merge commits. Useful to override
+ implied value.
++
+--diff-merges=first-parent:::
+--diff-merges=1:::
+ This option makes merge commits show the full diff with
+ respect to the first parent only.
++
+--diff-merges=separate:::
+--diff-merges=m:::
+-m:::
+ This makes merge commits show the full diff with respect to
+ each of the parents. Separate log entry and diff is generated
+ for each parent. `-m` doesn't produce any output without `-p`.
++
+--diff-merges=combined:::
+--diff-merges=c:::
+-c:::
+ With this option, diff output for a merge commit shows the
+ differences from each of the parents to the merge result
+ simultaneously instead of showing pairwise diff between a
+ parent and the result one at a time. Furthermore, it lists
+ only files which were modified from all parents. `-c` implies
+ `-p`.
++
+--diff-merges=dense-combined:::
+--diff-merges=cc:::
+--cc:::
+ With this option the output produced by
+ `--diff-merges=combined` is further compressed by omitting
+ uninteresting hunks whose contents in the parents have only
+ two variants and the merge result picks one of them without
+ modification. `--cc` implies `-p`.
+
+--combined-all-paths::
+ This flag causes combined diffs (used for merge commits) to
+ list the name of the file from all parents. It thus only has
+ effect when `--diff-merges=[dense-]combined` is in use, and
+ is likely only useful if filename changes are detected (i.e.
+ when either rename or copy detection have been requested).
+endif::git-log[]
+
-U<n>::
--unified=<n>::
Generate diffs with <n> lines of context instead of
diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt
index d72d15be5b..bd596619c0 100644
--- a/Documentation/git-fsck.txt
+++ b/Documentation/git-fsck.txt
@@ -129,14 +129,6 @@ using 'git commit-graph verify'. See linkgit:git-commit-graph[1].
Extracted Diagnostics
---------------------
-expect dangling commits - potential heads - due to lack of head information::
- You haven't specified any nodes as heads so it won't be
- possible to differentiate between un-parented commits and
- root nodes.
-
-missing sha1 directory '<dir>'::
- The directory holding the sha1 objects is missing.
-
unreachable <type> <object>::
The <type> object <object>, isn't actually referred to directly
or indirectly in any of the trees or commits seen. This can
diff --git a/Documentation/git-log.txt b/Documentation/git-log.txt
index dd189a353a..1bbf865a1b 100644
--- a/Documentation/git-log.txt
+++ b/Documentation/git-log.txt
@@ -107,47 +107,15 @@ DIFF FORMATTING
By default, `git log` does not generate any diff output. The options
below can be used to show the changes made by each commit.
-Note that unless one of `-c`, `--cc`, or `-m` is given, merge commits
-will never show a diff, even if a diff format like `--patch` is
-selected, nor will they match search options like `-S`. The exception is
-when `--first-parent` is in use, in which merges are treated like normal
-single-parent commits (this can be overridden by providing a
-combined-diff option or with `--no-diff-merges`).
-
--c::
- With this option, diff output for a merge commit
- shows the differences from each of the parents to the merge result
- simultaneously instead of showing pairwise diff between a parent
- and the result one at a time. Furthermore, it lists only files
- which were modified from all parents.
-
---cc::
- This flag implies the `-c` option and further compresses the
- patch output by omitting uninteresting hunks whose contents in
- the parents have only two variants and the merge result picks
- one of them without modification.
-
---combined-all-paths::
- This flag causes combined diffs (used for merge commits) to
- list the name of the file from all parents. It thus only has
- effect when -c or --cc are specified, and is likely only
- useful if filename changes are detected (i.e. when either
- rename or copy detection have been requested).
-
--m::
- This flag makes the merge commits show the full diff like
- regular commits; for each merge parent, a separate log entry
- and diff is generated. An exception is that only diff against
- the first parent is shown when `--first-parent` option is given;
- in that case, the output represents the changes the merge
- brought _into_ the then-current branch.
-
---diff-merges=off::
---no-diff-merges::
- Disable output of diffs for merge commits (default). Useful to
- override `-m`, `-c`, or `--cc`.
+Note that unless one of `--diff-merges` variants (including short
+`-m`, `-c`, and `--cc` options) is explicitly given, merge commits
+will not show a diff, even if a diff format like `--patch` is
+selected, nor will they match search options like `-S`. The exception
+is when `--first-parent` is in use, in which case `first-parent` is
+the default format.
:git-log: 1
+:diff-merges-default: `off`
include::diff-options.txt[]
include::diff-generate-patch.txt[]
diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt
index 0a3b5265b3..6d11ab506b 100644
--- a/Documentation/git-ls-files.txt
+++ b/Documentation/git-ls-files.txt
@@ -13,6 +13,7 @@ SYNOPSIS
(--[cached|deleted|others|ignored|stage|unmerged|killed|modified])*
(-[c|d|o|i|s|u|k|m])*
[--eol]
+ [--deduplicate]
[-x <pattern>|--exclude=<pattern>]
[-X <file>|--exclude-from=<file>]
[--exclude-per-directory=<file>]
@@ -80,6 +81,13 @@ OPTIONS
\0 line termination on output and do not quote filenames.
See OUTPUT below for more information.
+--deduplicate::
+ When only filenames are shown, suppress duplicates that may
+ come from having multiple stages during a merge, or giving
+ `--deleted` and `--modified` option at the same time.
+ When any of the `-t`, `--unmerged`, or `--stage` option is
+ in use, this option has no effect.
+
-x <pattern>::
--exclude=<pattern>::
Skip untracked files matching pattern.
diff --git a/Documentation/git-show.txt b/Documentation/git-show.txt
index fcf528c1b3..2b1bc7288d 100644
--- a/Documentation/git-show.txt
+++ b/Documentation/git-show.txt
@@ -45,10 +45,13 @@ include::pretty-options.txt[]
include::pretty-formats.txt[]
-COMMON DIFF OPTIONS
--------------------
+DIFF FORMATTING
+---------------
+The options below can be used to change the way `git show` generates
+diff output.
:git-log: 1
+:diff-merges-default: `dense-combined`
include::diff-options.txt[]
include::diff-generate-patch.txt[]
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index 02a706c4c0..f1bb1fa5f5 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -97,8 +97,9 @@ list::
List details of each working tree. The main working tree is listed first,
followed by each of the linked working trees. The output details include
whether the working tree is bare, the revision currently checked out, the
-branch currently checked out (or "detached HEAD" if none), and "locked" if
-the worktree is locked.
+branch currently checked out (or "detached HEAD" if none), "locked" if
+the worktree is locked, "prunable" if the worktree can be pruned by `prune`
+command.
lock::
@@ -231,9 +232,14 @@ This can also be set up as the default behaviour by using the
-v::
--verbose::
With `prune`, report all removals.
++
+With `list`, output additional information about worktrees (see below).
--expire <time>::
With `prune`, only expire unused working trees older than `<time>`.
++
+With `list`, annotate missing working trees as prunable if they are
+older than `<time>`.
--reason <string>::
With `lock`, an explanation why the working tree is locked.
@@ -372,13 +378,46 @@ $ git worktree list
/path/to/other-linked-worktree 1234abc (detached HEAD)
------------
+The command also shows annotations for each working tree, according to its state.
+These annotations are:
+
+ * `locked`, if the working tree is locked.
+ * `prunable`, if the working tree can be pruned via `git worktree prune`.
+
+------------
+$ git worktree list
+/path/to/linked-worktree abcd1234 [master]
+/path/to/locked-worktreee acbd5678 (brancha) locked
+/path/to/prunable-worktree 5678abc (detached HEAD) prunable
+------------
+
+For these annotations, a reason might also be available and this can be
+seen using the verbose mode. The annotation is then moved to the next line
+indented followed by the additional information.
+
+------------
+$ git worktree list --verbose
+/path/to/linked-worktree abcd1234 [master]
+/path/to/locked-worktree-no-reason abcd5678 (detached HEAD) locked
+/path/to/locked-worktree-with-reason 1234abcd (brancha)
+ locked: working tree path is mounted on a portable device
+/path/to/prunable-worktree 5678abc1 (detached HEAD)
+ prunable: gitdir file points to non-existent location
+------------
+
+Note that the annotation is moved to the next line if the additional
+information is available, otherwise it stays on the same line as the
+working tree itself.
+
Porcelain Format
~~~~~~~~~~~~~~~~
The porcelain format has a line per attribute. Attributes are listed with a
label and value separated by a single space. Boolean attributes (like `bare`
and `detached`) are listed as a label only, and are present only
-if the value is true. The first attribute of a working tree is always
-`worktree`, an empty line indicates the end of the record. For example:
+if the value is true. Some attributes (like `locked`) can be listed as a label
+only or with a value depending upon whether a reason is available. The first
+attribute of a working tree is always `worktree`, an empty line indicates the
+end of the record. For example:
------------
$ git worktree list --porcelain
@@ -393,6 +432,33 @@ worktree /path/to/other-linked-worktree
HEAD 1234abc1234abc1234abc1234abc1234abc1234a
detached
+worktree /path/to/linked-worktree-locked-no-reason
+HEAD 5678abc5678abc5678abc5678abc5678abc5678c
+branch refs/heads/locked-no-reason
+locked
+
+worktree /path/to/linked-worktree-locked-with-reason
+HEAD 3456def3456def3456def3456def3456def3456b
+branch refs/heads/locked-with-reason
+locked reason why is locked
+
+worktree /path/to/linked-worktree-prunable
+HEAD 1233def1234def1234def1234def1234def1234b
+detached
+prunable gitdir file points to non-existent location
+
+------------
+
+If the lock reason contains "unusual" characters such as newline, they
+are escaped and the entire reason is quoted as explained for the
+configuration variable `core.quotePath` (see linkgit:git-config[1]).
+For Example:
+
+------------
+$ git worktree list --porcelain
+...
+locked "reason\nwhy is locked"
+...
------------
EXAMPLES
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index 002379056a..96cc89d157 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -130,6 +130,11 @@ parents) and `--max-parents=-1` (negative numbers denote no upper limit).
this option allows you to ignore the individual commits
brought in to your history by such a merge.
+ifdef::git-log[]
+ This option also changes default diff format for merge commits
+ to `first-parent`, see `--diff-merges=first-parent` for details.
+endif::git-log[]
+
--not::
Reverses the meaning of the '{caret}' prefix (or lack thereof)
for all following revision specifiers, up to the next `--not`.
diff --git a/Documentation/technical/index-format.txt b/Documentation/technical/index-format.txt
index 69edf46c03..b633482b1b 100644
--- a/Documentation/technical/index-format.txt
+++ b/Documentation/technical/index-format.txt
@@ -26,7 +26,7 @@ Git index format
Extensions are identified by signature. Optional extensions can
be ignored if Git does not understand them.
- Git currently supports cached tree and resolve undo extensions.
+ Git currently supports cache tree and resolve undo extensions.
4-byte extension signature. If the first byte is 'A'..'Z' the
extension is optional and can be ignored.
@@ -136,14 +136,35 @@ Git index format
== Extensions
-=== Cached tree
-
- Cached tree extension contains pre-computed hashes for trees that can
- be derived from the index. It helps speed up tree object generation
- from index for a new commit.
-
- When a path is updated in index, the path must be invalidated and
- removed from tree cache.
+=== Cache tree
+
+ Since the index does not record entries for directories, the cache
+ entries cannot describe tree objects that already exist in the object
+ database for regions of the index that are unchanged from an existing
+ commit. The cache tree extension stores a recursive tree structure that
+ describes the trees that already exist and completely match sections of
+ the cache entries. This speeds up tree object generation from the index
+ for a new commit by only computing the trees that are "new" to that
+ commit. It also assists when comparing the index to another tree, such
+ as `HEAD^{tree}`, since sections of the index can be skipped when a tree
+ comparison demonstrates equality.
+
+ The recursive tree structure uses nodes that store a number of cache
+ entries, a list of subnodes, and an object ID (OID). The OID references
+ the existing tree for that node, if it is known to exist. The subnodes
+ correspond to subdirectories that themselves have cache tree nodes. The
+ number of cache entries corresponds to the number of cache entries in
+ the index that describe paths within that tree's directory.
+
+ The extension tracks the full directory structure in the cache tree
+ extension, but this is generally smaller than the full cache entry list.
+
+ When a path is updated in index, Git invalidates all nodes of the
+ recursive cache tree corresponding to the parent directories of that
+ path. We store these tree nodes as being "invalid" by using "-1" as the
+ number of cache entries. Invalid nodes still store a span of index
+ entries, allowing Git to focus its efforts when reconstructing a full
+ cache tree.
The signature for this extension is { 'T', 'R', 'E', 'E' }.
@@ -174,7 +195,8 @@ Git index format
first entry represents the root level of the repository, followed by the
first subtree--let's call this A--of the root level (with its name
relative to the root level), followed by the first subtree of A (with
- its name relative to A), ...
+ its name relative to A), and so on. The specified number of subtrees
+ indicates when the current level of the recursive stack is complete.
=== Resolve undo
diff --git a/Documentation/technical/packfile-uri.txt b/Documentation/technical/packfile-uri.txt
index 318713abc3..f7eabc6c76 100644
--- a/Documentation/technical/packfile-uri.txt
+++ b/Documentation/technical/packfile-uri.txt
@@ -37,8 +37,11 @@ at least so that we can test the client.
This is the implementation: a feature, marked experimental, that allows the
server to be configured by one or more `uploadpack.blobPackfileUri=<sha1>
<uri>` entries. Whenever the list of objects to be sent is assembled, all such
-blobs are excluded, replaced with URIs. The client will download those URIs,
-expecting them to each point to packfiles containing single blobs.
+blobs are excluded, replaced with URIs. As noted in "Future work" below, the
+server can evolve in the future to support excluding other objects (or other
+implementations of servers could be made that support excluding other objects)
+without needing a protocol change, so clients should not expect that packfiles
+downloaded in this way only contain single blobs.
Client design
-------------
diff --git a/Makefile b/Makefile
index 4edfda3e00..b797033c58 100644
--- a/Makefile
+++ b/Makefile
@@ -777,20 +777,6 @@ BUILT_INS += git-status$X
BUILT_INS += git-switch$X
BUILT_INS += git-whatchanged$X
-# what 'all' will build and 'install' will install in gitexecdir,
-# excluding programs for built-in commands
-ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS)
-ALL_COMMANDS_TO_INSTALL = $(ALL_PROGRAMS)
-ifeq (,$(SKIP_DASHED_BUILT_INS))
-ALL_COMMANDS_TO_INSTALL += $(BUILT_INS)
-else
-# git-upload-pack, git-receive-pack and git-upload-archive are special: they
-# are _expected_ to be present in the `bin/` directory in their dashed form.
-ALL_COMMANDS_TO_INSTALL += git-receive-pack$(X)
-ALL_COMMANDS_TO_INSTALL += git-upload-archive$(X)
-ALL_COMMANDS_TO_INSTALL += git-upload-pack$(X)
-endif
-
# what 'all' will build but not install in gitexecdir
OTHER_PROGRAMS = git$X
@@ -874,6 +860,7 @@ LIB_OBJS += date.o
LIB_OBJS += decorate.o
LIB_OBJS += delta-islands.o
LIB_OBJS += diff-delta.o
+LIB_OBJS += diff-merges.o
LIB_OBJS += diff-lib.o
LIB_OBJS += diff-no-index.o
LIB_OBJS += diff.o
@@ -1226,6 +1213,20 @@ ifdef DEVELOPER
include config.mak.dev
endif
+# what 'all' will build and 'install' will install in gitexecdir,
+# excluding programs for built-in commands
+ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS)
+ALL_COMMANDS_TO_INSTALL = $(ALL_PROGRAMS)
+ifeq (,$(SKIP_DASHED_BUILT_INS))
+ALL_COMMANDS_TO_INSTALL += $(BUILT_INS)
+else
+# git-upload-pack, git-receive-pack and git-upload-archive are special: they
+# are _expected_ to be present in the `bin/` directory in their dashed form.
+ALL_COMMANDS_TO_INSTALL += git-receive-pack$(X)
+ALL_COMMANDS_TO_INSTALL += git-upload-archive$(X)
+ALL_COMMANDS_TO_INSTALL += git-upload-pack$(X)
+endif
+
ALL_CFLAGS = $(DEVELOPER_CFLAGS) $(CPPFLAGS) $(CFLAGS)
ALL_LDFLAGS = $(LDFLAGS)
diff --git a/builtin/branch.c b/builtin/branch.c
index 8c0b428104..bcc00bcf18 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -202,6 +202,9 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
int remote_branch = 0;
struct strbuf bname = STRBUF_INIT;
unsigned allowed_interpret;
+ struct string_list refs_to_delete = STRING_LIST_INIT_DUP;
+ struct string_list_item *item;
+ int branch_name_pos;
switch (kinds) {
case FILTER_REFS_REMOTES:
@@ -219,6 +222,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
default:
die(_("cannot use -a with -d"));
}
+ branch_name_pos = strcspn(fmt, "%");
if (!force) {
head_rev = lookup_commit_reference(the_repository, &head_oid);
@@ -265,30 +269,35 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
goto next;
}
- if (delete_ref(NULL, name, is_null_oid(&oid) ? NULL : &oid,
- REF_NO_DEREF)) {
- error(remote_branch
- ? _("Error deleting remote-tracking branch '%s'")
- : _("Error deleting branch '%s'"),
- bname.buf);
- ret = 1;
- goto next;
- }
- if (!quiet) {
- printf(remote_branch
- ? _("Deleted remote-tracking branch %s (was %s).\n")
- : _("Deleted branch %s (was %s).\n"),
- bname.buf,
- (flags & REF_ISBROKEN) ? "broken"
- : (flags & REF_ISSYMREF) ? target
- : find_unique_abbrev(&oid, DEFAULT_ABBREV));
- }
- delete_branch_config(bname.buf);
+ item = string_list_append(&refs_to_delete, name);
+ item->util = xstrdup((flags & REF_ISBROKEN) ? "broken"
+ : (flags & REF_ISSYMREF) ? target
+ : find_unique_abbrev(&oid, DEFAULT_ABBREV));
next:
free(target);
}
+ if (delete_refs(NULL, &refs_to_delete, REF_NO_DEREF))
+ ret = 1;
+
+ for_each_string_list_item(item, &refs_to_delete) {
+ char *describe_ref = item->util;
+ char *name = item->string;
+ if (!ref_exists(name)) {
+ char *refname = name + branch_name_pos;
+ if (!quiet)
+ printf(remote_branch
+ ? _("Deleted remote-tracking branch %s (was %s).\n")
+ : _("Deleted branch %s (was %s).\n"),
+ name + branch_name_pos, describe_ref);
+
+ delete_branch_config(refname);
+ }
+ free(describe_ref);
+ }
+ string_list_clear(&refs_to_delete, 0);
+
free(name);
strbuf_release(&bname);
diff --git a/builtin/checkout.c b/builtin/checkout.c
index c9ba23c279..2d6550bc3c 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -821,9 +821,6 @@ static int merge_working_tree(const struct checkout_opts *opts,
}
}
- if (!active_cache_tree)
- active_cache_tree = cache_tree();
-
if (!cache_tree_fully_valid(active_cache_tree))
cache_tree_update(&the_index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR);
diff --git a/builtin/describe.c b/builtin/describe.c
index 7668591d57..40482d8e9f 100644
--- a/builtin/describe.c
+++ b/builtin/describe.c
@@ -194,7 +194,7 @@ static int get_name(const char *path, const struct object_id *oid, int flag, voi
}
/* Is it annotated? */
- if (!peel_ref(path, &peeled)) {
+ if (!peel_iterated_oid(oid, &peeled)) {
is_annotated = !oideq(oid, &peeled);
} else {
oidcpy(&peeled, oid);
diff --git a/builtin/diff-files.c b/builtin/diff-files.c
index 1e352dd8f7..4742a4559b 100644
--- a/builtin/diff-files.c
+++ b/builtin/diff-files.c
@@ -7,6 +7,7 @@
#include "cache.h"
#include "config.h"
#include "diff.h"
+#include "diff-merges.h"
#include "commit.h"
#include "revision.h"
#include "builtin.h"
@@ -69,9 +70,9 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix)
* was not asked to. "diff-files -c -p" should not densify
* (the user should ask with "diff-files --cc" explicitly).
*/
- if (rev.max_count == -1 && !rev.combine_merges &&
+ if (rev.max_count == -1 &&
(rev.diffopt.output_format & DIFF_FORMAT_PATCH))
- rev.combine_merges = rev.dense_combined_merges = 1;
+ diff_merges_set_dense_combined_if_unset(&rev);
if (read_cache_preload(&rev.diffopt.pathspec) < 0) {
perror("read_cache_preload");
diff --git a/builtin/diff.c b/builtin/diff.c
index 780c33877f..5cfe1717e8 100644
--- a/builtin/diff.c
+++ b/builtin/diff.c
@@ -13,6 +13,7 @@
#include "blob.h"
#include "tag.h"
#include "diff.h"
+#include "diff-merges.h"
#include "diffcore.h"
#include "revision.h"
#include "log-tree.h"
@@ -216,8 +217,8 @@ static int builtin_diff_combined(struct rev_info *revs,
if (argc > 1)
usage(builtin_diff_usage);
- if (!revs->dense_combined_merges && !revs->combine_merges)
- revs->dense_combined_merges = revs->combine_merges = 1;
+ diff_merges_set_dense_combined_if_unset(revs);
+
for (i = 1; i < ents; i++)
oid_array_append(&parents, &ent[i].item->oid);
diff_tree_combined(&ent[0].item->oid, &parents, revs);
@@ -265,9 +266,9 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv
* dense one, --cc can be explicitly asked for, or just rely
* on the default).
*/
- if (revs->max_count == -1 && !revs->combine_merges &&
+ if (revs->max_count == -1 &&
(revs->diffopt.output_format & DIFF_FORMAT_PATCH))
- revs->combine_merges = revs->dense_combined_merges = 1;
+ diff_merges_set_dense_combined_if_unset(revs);
setup_work_tree();
if (read_cache_preload(&revs->diffopt.pathspec) < 0) {
diff --git a/builtin/gc.c b/builtin/gc.c
index ae34362e93..4c40594d66 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -769,7 +769,7 @@ static int dfs_on_ref(const char *refname,
struct commit_list *stack = NULL;
struct commit *commit;
- if (!peel_ref(refname, &peeled))
+ if (!peel_iterated_oid(oid, &peeled))
oid = &peeled;
if (oid_object_info(the_repository, oid, NULL) != OBJ_COMMIT)
return 0;
@@ -897,6 +897,12 @@ static int maintenance_task_prefetch(struct maintenance_run_opts *opts)
struct string_list_item *item;
struct string_list remotes = STRING_LIST_INIT_DUP;
+ git_config_set_multivar_gently("log.excludedecoration",
+ "refs/prefetch/",
+ "refs/prefetch/",
+ CONFIG_FLAGS_FIXED_VALUE |
+ CONFIG_FLAGS_MULTI_REPLACE);
+
if (for_each_remote(append_remote, &remotes)) {
error(_("failed to fill remotes"));
result = 1;
diff --git a/builtin/grep.c b/builtin/grep.c
index ca259af441..55d06c9513 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"),
diff --git a/builtin/log.c b/builtin/log.c
index fd282def43..d0cbaaf68a 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -12,6 +12,7 @@
#include "color.h"
#include "commit.h"
#include "diff.h"
+#include "diff-merges.h"
#include "revision.h"
#include "log-tree.h"
#include "builtin.h"
@@ -607,15 +608,10 @@ static int show_tree_object(const struct object_id *oid,
static void show_setup_revisions_tweak(struct rev_info *rev,
struct setup_revision_opt *opt)
{
- if (rev->ignore_merges < 0) {
- /* There was no "-m" variant on the command line */
- rev->ignore_merges = 0;
- if (!rev->first_parent_only && !rev->combine_merges) {
- /* No "--first-parent", "-c", or "--cc" */
- rev->combine_merges = 1;
- rev->dense_combined_merges = 1;
- }
- }
+ if (rev->first_parent_only)
+ diff_merges_default_to_first_parent(rev);
+ else
+ diff_merges_default_to_dense_combined(rev);
if (!rev->diffopt.output_format)
rev->diffopt.output_format = DIFF_FORMAT_PATCH;
}
@@ -736,12 +732,8 @@ static void log_setup_revisions_tweak(struct rev_info *rev,
rev->prune_data.nr == 1)
rev->diffopt.flags.follow_renames = 1;
- /* Turn --cc/-c into -p --cc/-c when -p was not given */
- if (!rev->diffopt.output_format && rev->combine_merges)
- rev->diffopt.output_format = DIFF_FORMAT_PATCH;
-
- if (rev->first_parent_only && rev->ignore_merges < 0)
- rev->ignore_merges = 0;
+ if (rev->first_parent_only)
+ diff_merges_default_to_first_parent(rev);
}
int cmd_log(int argc, const char **argv, const char *prefix)
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index c8eae899b8..f6f9e483b2 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -35,6 +35,7 @@ static int line_terminator = '\n';
static int debug_mode;
static int show_eol;
static int recurse_submodules;
+static int skipping_duplicates;
static const char *prefix;
static int max_prefix_len;
@@ -312,45 +313,59 @@ static void show_files(struct repository *repo, struct dir_struct *dir)
if (show_killed)
show_killed_files(repo->index, dir);
}
- if (show_cached || show_stage) {
- for (i = 0; i < repo->index->cache_nr; i++) {
- const struct cache_entry *ce = repo->index->cache[i];
- construct_fullname(&fullname, repo, ce);
+ if (!(show_cached || show_stage || show_deleted || show_modified))
+ return;
+ for (i = 0; i < repo->index->cache_nr; i++) {
+ const struct cache_entry *ce = repo->index->cache[i];
+ struct stat st;
+ int stat_err;
- if ((dir->flags & DIR_SHOW_IGNORED) &&
- !ce_excluded(dir, repo->index, fullname.buf, ce))
- continue;
- if (show_unmerged && !ce_stage(ce))
- continue;
- if (ce->ce_flags & CE_UPDATE)
- continue;
+ construct_fullname(&fullname, repo, ce);
+
+ if ((dir->flags & DIR_SHOW_IGNORED) &&
+ !ce_excluded(dir, repo->index, fullname.buf, ce))
+ continue;
+ if (ce->ce_flags & CE_UPDATE)
+ continue;
+ if ((show_cached || show_stage) &&
+ (!show_unmerged || ce_stage(ce))) {
show_ce(repo, dir, ce, fullname.buf,
ce_stage(ce) ? tag_unmerged :
(ce_skip_worktree(ce) ? tag_skip_worktree :
tag_cached));
+ if (skipping_duplicates)
+ goto skip_to_next_name;
}
- }
- if (show_deleted || show_modified) {
- for (i = 0; i < repo->index->cache_nr; i++) {
- const struct cache_entry *ce = repo->index->cache[i];
- struct stat st;
- int err;
- construct_fullname(&fullname, repo, ce);
-
- if ((dir->flags & DIR_SHOW_IGNORED) &&
- !ce_excluded(dir, repo->index, fullname.buf, ce))
- continue;
- if (ce->ce_flags & CE_UPDATE)
- continue;
- if (ce_skip_worktree(ce))
- continue;
- err = lstat(fullname.buf, &st);
- if (show_deleted && err)
- show_ce(repo, dir, ce, fullname.buf, tag_removed);
- if (show_modified && ie_modified(repo->index, ce, &st, 0))
- show_ce(repo, dir, ce, fullname.buf, tag_modified);
+ if (!(show_deleted || show_modified))
+ continue;
+ if (ce_skip_worktree(ce))
+ continue;
+ stat_err = lstat(fullname.buf, &st);
+ if (stat_err && (errno != ENOENT && errno != ENOTDIR))
+ error_errno("cannot lstat '%s'", fullname.buf);
+ if (stat_err && show_deleted) {
+ show_ce(repo, dir, ce, fullname.buf, tag_removed);
+ if (skipping_duplicates)
+ goto skip_to_next_name;
+ }
+ if (show_modified &&
+ (stat_err || ie_modified(repo->index, ce, &st, 0))) {
+ show_ce(repo, dir, ce, fullname.buf, tag_modified);
+ if (skipping_duplicates)
+ goto skip_to_next_name;
+ }
+ continue;
+
+skip_to_next_name:
+ {
+ int j;
+ struct cache_entry **cache = repo->index->cache;
+ for (j = i + 1; j < repo->index->cache_nr; j++)
+ if (strcmp(ce->name, cache[j]->name))
+ break;
+ i = j - 1; /* compensate for the for loop */
}
}
@@ -578,6 +593,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
N_("pretend that paths removed since <tree-ish> are still present")),
OPT__ABBREV(&abbrev),
OPT_BOOL(0, "debug", &debug_mode, N_("show debugging data")),
+ OPT_BOOL(0, "deduplicate", &skipping_duplicates,
+ N_("suppress duplicate entries")),
OPT_END()
};
@@ -617,6 +634,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
* you also show the stage information.
*/
show_stage = 1;
+ if (show_tag || show_stage)
+ skipping_duplicates = 0;
if (dir.exclude_per_dir)
exc_given = 1;
diff --git a/builtin/merge.c b/builtin/merge.c
index 1cff730715..eb00b273e6 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -14,6 +14,7 @@
#include "lockfile.h"
#include "run-command.h"
#include "diff.h"
+#include "diff-merges.h"
#include "refs.h"
#include "refspec.h"
#include "commit.h"
@@ -409,7 +410,7 @@ static void squash_message(struct commit *commit, struct commit_list *remotehead
printf(_("Squash commit -- not updating HEAD\n"));
repo_init_revisions(the_repository, &rev, NULL);
- rev.ignore_merges = 1;
+ diff_merges_suppress(&rev);
rev.commit_format = CMIT_FMT_MEDIUM;
commit->object.flags |= UNINTERESTING;
diff --git a/builtin/name-rev.c b/builtin/name-rev.c
index 3fe71a8c01..b221d30014 100644
--- a/builtin/name-rev.c
+++ b/builtin/name-rev.c
@@ -390,10 +390,10 @@ static void name_tips(void)
}
}
-static const unsigned char *nth_tip_table_ent(size_t ix, void *table_)
+static const struct object_id *nth_tip_table_ent(size_t ix, const void *table_)
{
- struct tip_table_entry *table = table_;
- return table[ix].oid.hash;
+ const struct tip_table_entry *table = table_;
+ return &table[ix].oid;
}
static const char *get_exact_ref_match(const struct object *o)
@@ -408,8 +408,8 @@ static const char *get_exact_ref_match(const struct object *o)
tip_table.sorted = 1;
}
- found = hash_pos(o->oid.hash, tip_table.table, tip_table.nr,
- nth_tip_table_ent);
+ found = oid_pos(&o->oid, tip_table.table, tip_table.nr,
+ nth_tip_table_ent);
if (0 <= found)
return tip_table.table[found].refname;
return NULL;
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index 5b0c4489e2..13cde5896a 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -634,7 +634,7 @@ static int mark_tagged(const char *path, const struct object_id *oid, int flag,
if (entry)
entry->tagged = 1;
- if (!peel_ref(path, &peeled)) {
+ if (!peel_iterated_oid(oid, &peeled)) {
entry = packlist_find(&to_pack, &peeled);
if (entry)
entry->tagged = 1;
@@ -2814,13 +2814,11 @@ static void add_tag_chain(const struct object_id *oid)
}
}
-static int add_ref_tag(const char *path, const struct object_id *oid, int flag, void *cb_data)
+static int add_ref_tag(const char *tag, const struct object_id *oid, int flag, void *cb_data)
{
struct object_id peeled;
- if (starts_with(path, "refs/tags/") && /* is a tag? */
- !peel_ref(path, &peeled) && /* peelable? */
- obj_is_packed(&peeled)) /* object packed? */
+ if (!peel_iterated_oid(oid, &peeled) && obj_is_packed(&peeled))
add_tag_chain(oid);
return 0;
}
@@ -3751,7 +3749,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
}
cleanup_preferred_base();
if (include_tag && nr_result)
- for_each_ref(add_ref_tag, NULL);
+ for_each_tag_ref(add_ref_tag, NULL);
stop_progress(&progress_state);
trace2_region_leave("pack-objects", "enumerate-objects",
the_repository);
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index ae60b4acf2..7f8a5332f8 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -40,7 +40,7 @@ static void show_one(const char *refname, const struct object_id *oid)
if (!deref_tags)
return;
- if (!peel_ref(refname, &peeled)) {
+ if (!peel_iterated_oid(oid, &peeled)) {
hex = find_unique_abbrev(&peeled, abbrev);
printf("%s %s^{}\n", hex, refname);
}
diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c
index e3140db2a0..2306a9ad98 100644
--- a/builtin/sparse-checkout.c
+++ b/builtin/sparse-checkout.c
@@ -22,11 +22,6 @@ static char const * const builtin_sparse_checkout_usage[] = {
NULL
};
-static char *get_sparse_checkout_filename(void)
-{
- return git_pathdup("info/sparse-checkout");
-}
-
static void write_patterns_to_file(FILE *fp, struct pattern_list *pl)
{
int i;
diff --git a/builtin/tag.c b/builtin/tag.c
index 24d35b746d..e8b85eefd8 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -72,10 +72,10 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting,
}
typedef int (*each_tag_name_fn)(const char *name, const char *ref,
- const struct object_id *oid, const void *cb_data);
+ const struct object_id *oid, void *cb_data);
static int for_each_tag_name(const char **argv, each_tag_name_fn fn,
- const void *cb_data)
+ void *cb_data)
{
const char **p;
struct strbuf ref = STRBUF_INIT;
@@ -97,18 +97,42 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn,
return had_error;
}
-static int delete_tag(const char *name, const char *ref,
- const struct object_id *oid, const void *cb_data)
+static int collect_tags(const char *name, const char *ref,
+ const struct object_id *oid, void *cb_data)
{
- if (delete_ref(NULL, ref, oid, 0))
- return 1;
- printf(_("Deleted tag '%s' (was %s)\n"), name,
- find_unique_abbrev(oid, DEFAULT_ABBREV));
+ struct string_list *ref_list = cb_data;
+
+ string_list_append(ref_list, ref);
+ ref_list->items[ref_list->nr - 1].util = oiddup(oid);
return 0;
}
+static int delete_tags(const char **argv)
+{
+ int result;
+ struct string_list refs_to_delete = STRING_LIST_INIT_DUP;
+ struct string_list_item *item;
+
+ result = for_each_tag_name(argv, collect_tags, (void *)&refs_to_delete);
+ if (delete_refs(NULL, &refs_to_delete, REF_NO_DEREF))
+ result = 1;
+
+ for_each_string_list_item(item, &refs_to_delete) {
+ const char *name = item->string;
+ struct object_id *oid = item->util;
+ if (!ref_exists(name))
+ printf(_("Deleted tag '%s' (was %s)\n"),
+ item->string + 10,
+ find_unique_abbrev(oid, DEFAULT_ABBREV));
+
+ free(oid);
+ }
+ string_list_clear(&refs_to_delete, 0);
+ return result;
+}
+
static int verify_tag(const char *name, const char *ref,
- const struct object_id *oid, const void *cb_data)
+ const struct object_id *oid, void *cb_data)
{
int flags;
const struct ref_format *format = cb_data;
@@ -512,7 +536,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
if (filter.reachable_from || filter.unreachable_from)
die(_("--merged and --no-merged options are only allowed in list mode"));
if (cmdmode == 'd')
- return for_each_tag_name(argv, delete_tag, NULL);
+ return delete_tags(argv);
if (cmdmode == 'v') {
if (format.format && verify_ref_format(&format))
usage_with_options(git_tag_usage, options);
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 71287b2da6..1cd5c2016e 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -12,6 +12,7 @@
#include "submodule.h"
#include "utf8.h"
#include "worktree.h"
+#include "quote.h"
static const char * const worktree_usage[] = {
N_("git worktree add [<options>] <path> [<commit-ish>]"),
@@ -67,79 +68,6 @@ static void delete_worktrees_dir_if_empty(void)
rmdir(git_path("worktrees")); /* ignore failed removal */
}
-/*
- * Return true if worktree entry should be pruned, along with the reason for
- * pruning. Otherwise, return false and the worktree's path, or NULL if it
- * cannot be determined. Caller is responsible for freeing returned path.
- */
-static int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath)
-{
- struct stat st;
- char *path;
- int fd;
- size_t len;
- ssize_t read_result;
-
- *wtpath = NULL;
- if (!is_directory(git_path("worktrees/%s", id))) {
- strbuf_addstr(reason, _("not a valid directory"));
- return 1;
- }
- if (file_exists(git_path("worktrees/%s/locked", id)))
- return 0;
- if (stat(git_path("worktrees/%s/gitdir", id), &st)) {
- strbuf_addstr(reason, _("gitdir file does not exist"));
- return 1;
- }
- fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY);
- if (fd < 0) {
- strbuf_addf(reason, _("unable to read gitdir file (%s)"),
- strerror(errno));
- return 1;
- }
- len = xsize_t(st.st_size);
- path = xmallocz(len);
-
- read_result = read_in_full(fd, path, len);
- if (read_result < 0) {
- strbuf_addf(reason, _("unable to read gitdir file (%s)"),
- strerror(errno));
- close(fd);
- free(path);
- return 1;
- }
- close(fd);
-
- if (read_result != len) {
- strbuf_addf(reason,
- _("short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"),
- (uintmax_t)len, (uintmax_t)read_result);
- free(path);
- return 1;
- }
- while (len && (path[len - 1] == '\n' || path[len - 1] == '\r'))
- len--;
- if (!len) {
- strbuf_addstr(reason, _("invalid gitdir file"));
- free(path);
- return 1;
- }
- path[len] = '\0';
- if (!file_exists(path)) {
- if (stat(git_path("worktrees/%s/index", id), &st) ||
- st.st_mtime <= expire) {
- strbuf_addstr(reason, _("gitdir file points to non-existent location"));
- free(path);
- return 1;
- } else {
- *wtpath = path;
- return 0;
- }
- }
- *wtpath = path;
- return 0;
-}
-
static void prune_worktree(const char *id, const char *reason)
{
if (show_only || verbose)
@@ -195,7 +123,7 @@ static void prune_worktrees(void)
if (is_dot_or_dotdot(d->d_name))
continue;
strbuf_reset(&reason);
- if (should_prune_worktree(d->d_name, &reason, &path))
+ if (should_prune_worktree(d->d_name, &reason, &path, expire))
prune_worktree(d->d_name, reason.buf);
else if (path)
string_list_append(&kept, path)->util = xstrdup(d->d_name);
@@ -642,6 +570,8 @@ static int add(int ac, const char **av, const char *prefix)
static void show_worktree_porcelain(struct worktree *wt)
{
+ const char *reason;
+
printf("worktree %s\n", wt->path);
if (wt->is_bare)
printf("bare\n");
@@ -652,6 +582,20 @@ static void show_worktree_porcelain(struct worktree *wt)
else if (wt->head_ref)
printf("branch %s\n", wt->head_ref);
}
+
+ reason = worktree_lock_reason(wt);
+ if (reason && *reason) {
+ struct strbuf sb = STRBUF_INIT;
+ quote_c_style(reason, &sb, NULL, 0);
+ printf("locked %s\n", sb.buf);
+ strbuf_release(&sb);
+ } else if (reason)
+ printf("locked\n");
+
+ reason = worktree_prune_reason(wt, expire);
+ if (reason)
+ printf("prunable %s\n", reason);
+
printf("\n");
}
@@ -660,6 +604,7 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
struct strbuf sb = STRBUF_INIT;
int cur_path_len = strlen(wt->path);
int path_adj = cur_path_len - utf8_strwidth(wt->path);
+ const char *reason;
strbuf_addf(&sb, "%-*s ", 1 + path_maxlen + path_adj, wt->path);
if (wt->is_bare)
@@ -677,9 +622,18 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
strbuf_addstr(&sb, "(error)");
}
- if (!is_main_worktree(wt) && worktree_lock_reason(wt))
+ reason = worktree_lock_reason(wt);
+ if (verbose && reason && *reason)
+ strbuf_addf(&sb, "\n\tlocked: %s", reason);
+ else if (reason)
strbuf_addstr(&sb, " locked");
+ reason = worktree_prune_reason(wt, expire);
+ if (verbose && reason)
+ strbuf_addf(&sb, "\n\tprunable: %s", reason);
+ else if (reason)
+ strbuf_addstr(&sb, " prunable");
+
printf("%s\n", sb.buf);
strbuf_release(&sb);
}
@@ -723,12 +677,18 @@ static int list(int ac, const char **av, const char *prefix)
struct option options[] = {
OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")),
+ OPT__VERBOSE(&verbose, N_("show extended annotations and reasons, if available")),
+ OPT_EXPIRY_DATE(0, "expire", &expire,
+ N_("add 'prunable' annotation to worktrees older than <time>")),
OPT_END()
};
+ expire = TIME_MAX;
ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
if (ac)
usage_with_options(worktree_usage, options);
+ else if (verbose && porcelain)
+ die(_("--verbose and --porcelain are mutually exclusive"));
else {
struct worktree **worktrees = get_worktrees();
int path_maxlen = 0, abbrev = DEFAULT_ABBREV, i;
diff --git a/cache-tree.c b/cache-tree.c
index a537a806c1..2fb483d3c0 100644
--- a/cache-tree.c
+++ b/cache-tree.c
@@ -45,7 +45,7 @@ static int subtree_name_cmp(const char *one, int onelen,
return memcmp(one, two, onelen);
}
-static int subtree_pos(struct cache_tree *it, const char *path, int pathlen)
+int cache_tree_subtree_pos(struct cache_tree *it, const char *path, int pathlen)
{
struct cache_tree_sub **down = it->down;
int lo, hi;
@@ -72,7 +72,7 @@ static struct cache_tree_sub *find_subtree(struct cache_tree *it,
int create)
{
struct cache_tree_sub *down;
- int pos = subtree_pos(it, path, pathlen);
+ int pos = cache_tree_subtree_pos(it, path, pathlen);
if (0 <= pos)
return it->down[pos];
if (!create)
@@ -123,7 +123,7 @@ static int do_invalidate_path(struct cache_tree *it, const char *path)
it->entry_count = -1;
if (!*slash) {
int pos;
- pos = subtree_pos(it, path, namelen);
+ pos = cache_tree_subtree_pos(it, path, namelen);
if (0 <= pos) {
cache_tree_free(&it->down[pos]->cache_tree);
free(it->down[pos]);
@@ -151,16 +151,15 @@ void cache_tree_invalidate_path(struct index_state *istate, const char *path)
istate->cache_changed |= CACHE_TREE_CHANGED;
}
-static int verify_cache(struct cache_entry **cache,
- int entries, int flags)
+static int verify_cache(struct index_state *istate, int flags)
{
- int i, funny;
+ unsigned i, funny;
int silent = flags & WRITE_TREE_SILENT;
/* Verify that the tree is merged */
funny = 0;
- for (i = 0; i < entries; i++) {
- const struct cache_entry *ce = cache[i];
+ for (i = 0; i < istate->cache_nr; i++) {
+ const struct cache_entry *ce = istate->cache[i];
if (ce_stage(ce)) {
if (silent)
return -1;
@@ -180,17 +179,19 @@ static int verify_cache(struct cache_entry **cache,
* stage 0 entries.
*/
funny = 0;
- for (i = 0; i < entries - 1; i++) {
+ for (i = 0; i + 1 < istate->cache_nr; i++) {
/* path/file always comes after path because of the way
* the cache is sorted. Also path can appear only once,
* which means conflicting one would immediately follow.
*/
- const char *this_name = cache[i]->name;
- const char *next_name = cache[i+1]->name;
- int this_len = strlen(this_name);
- if (this_len < strlen(next_name) &&
- strncmp(this_name, next_name, this_len) == 0 &&
- next_name[this_len] == '/') {
+ const struct cache_entry *this_ce = istate->cache[i];
+ const struct cache_entry *next_ce = istate->cache[i + 1];
+ const char *this_name = this_ce->name;
+ const char *next_name = next_ce->name;
+ int this_len = ce_namelen(this_ce);
+ if (this_len < ce_namelen(next_ce) &&
+ next_name[this_len] == '/' &&
+ strncmp(this_name, next_name, this_len) == 0) {
if (10 < ++funny) {
fprintf(stderr, "...\n");
break;
@@ -434,15 +435,21 @@ static int update_one(struct cache_tree *it,
int cache_tree_update(struct index_state *istate, int flags)
{
- struct cache_tree *it = istate->cache_tree;
- struct cache_entry **cache = istate->cache;
- int entries = istate->cache_nr;
- int skip, i = verify_cache(cache, entries, flags);
+ int skip, i;
+
+ i = verify_cache(istate, flags);
if (i)
return i;
+
+ if (!istate->cache_tree)
+ istate->cache_tree = cache_tree();
+
trace_performance_enter();
- i = update_one(it, cache, entries, "", 0, &skip, flags);
+ trace2_region_enter("cache_tree", "update", the_repository);
+ i = update_one(istate->cache_tree, istate->cache, istate->cache_nr,
+ "", 0, &skip, flags);
+ trace2_region_leave("cache_tree", "update", the_repository);
trace_performance_leave("cache_tree_update");
if (i < 0)
return i;
@@ -492,7 +499,9 @@ static void write_one(struct strbuf *buffer, struct cache_tree *it,
void cache_tree_write(struct strbuf *sb, struct cache_tree *root)
{
+ trace2_region_enter("cache_tree", "write", the_repository);
write_one(sb, root, "", 0);
+ trace2_region_leave("cache_tree", "write", the_repository);
}
static struct cache_tree *read_one(const char **buffer, unsigned long *size_p)
@@ -581,9 +590,16 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p)
struct cache_tree *cache_tree_read(const char *buffer, unsigned long size)
{
+ struct cache_tree *result;
+
if (buffer[0])
return NULL; /* not the whole tree */
- return read_one(&buffer, &size);
+
+ trace2_region_enter("cache_tree", "read", the_repository);
+ result = read_one(&buffer, &size);
+ trace2_region_leave("cache_tree", "read", the_repository);
+
+ return result;
}
static struct cache_tree *cache_tree_find(struct cache_tree *it, const char *path)
@@ -622,9 +638,6 @@ static int write_index_as_tree_internal(struct object_id *oid,
cache_tree_valid = 0;
}
- if (!index_state->cache_tree)
- index_state->cache_tree = cache_tree();
-
if (!cache_tree_valid && cache_tree_update(index_state, flags) < 0)
return WRITE_TREE_UNMERGED_INDEX;
@@ -733,10 +746,13 @@ void prime_cache_tree(struct repository *r,
struct index_state *istate,
struct tree *tree)
{
+ trace2_region_enter("cache-tree", "prime_cache_tree", the_repository);
cache_tree_free(&istate->cache_tree);
istate->cache_tree = cache_tree();
+
prime_cache_tree_rec(r, istate->cache_tree, tree);
istate->cache_changed |= CACHE_TREE_CHANGED;
+ trace2_region_leave("cache-tree", "prime_cache_tree", the_repository);
}
/*
diff --git a/cache-tree.h b/cache-tree.h
index 639bfa5340..8efeccebfc 100644
--- a/cache-tree.h
+++ b/cache-tree.h
@@ -27,6 +27,8 @@ void cache_tree_free(struct cache_tree **);
void cache_tree_invalidate_path(struct index_state *, const char *);
struct cache_tree_sub *cache_tree_sub(struct cache_tree *, const char *);
+int cache_tree_subtree_pos(struct cache_tree *it, const char *path, int pathlen);
+
void cache_tree_write(struct strbuf *, struct cache_tree *root);
struct cache_tree *cache_tree_read(const char *buffer, unsigned long size);
diff --git a/cache.h b/cache.h
index f22d12ecb0..d928149614 100644
--- a/cache.h
+++ b/cache.h
@@ -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/commit-graph.c b/commit-graph.c
index f3486ec18f..6541060271 100644
--- a/commit-graph.c
+++ b/commit-graph.c
@@ -1012,10 +1012,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,
@@ -1043,10 +1043,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 +1074,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;
@@ -1143,10 +1143,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;
@@ -1458,7 +1458,7 @@ static int add_ref_to_set(const char *refname,
struct object_id peeled;
struct refs_cb_data *data = (struct refs_cb_data *)cb_data;
- if (!peel_ref(refname, &peeled))
+ if (!peel_iterated_oid(oid, &peeled))
oid = &peeled;
if (oid_object_info(the_repository, oid, NULL) == OBJ_COMMIT)
oidset_insert(data->commits, oid);
diff --git a/commit.c b/commit.c
index bab8d5ab07..fd2831dad3 100644
--- a/commit.c
+++ b/commit.c
@@ -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];
diff --git a/commit.h b/commit.h
index f4e7b0158e..ecacf9ade3 100644
--- a/commit.h
+++ b/commit.h
@@ -239,7 +239,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/diff-merges.c b/diff-merges.c
new file mode 100644
index 0000000000..146bb50316
--- /dev/null
+++ b/diff-merges.c
@@ -0,0 +1,146 @@
+#include "diff-merges.h"
+
+#include "revision.h"
+
+static void suppress(struct rev_info *revs)
+{
+ revs->separate_merges = 0;
+ revs->first_parent_merges = 0;
+ revs->combine_merges = 0;
+ revs->dense_combined_merges = 0;
+ revs->combined_all_paths = 0;
+ revs->combined_imply_patch = 0;
+ revs->merges_need_diff = 0;
+}
+
+static void set_separate(struct rev_info *revs)
+{
+ suppress(revs);
+ revs->separate_merges = 1;
+}
+
+static void set_first_parent(struct rev_info *revs)
+{
+ set_separate(revs);
+ revs->first_parent_merges = 1;
+}
+
+static void set_m(struct rev_info *revs)
+{
+ /*
+ * To "diff-index", "-m" means "match missing", and to the "log"
+ * family of commands, it means "show full diff for merges". Set
+ * both fields appropriately.
+ */
+ set_separate(revs);
+ revs->match_missing = 1;
+}
+
+static void set_combined(struct rev_info *revs)
+{
+ suppress(revs);
+ revs->combine_merges = 1;
+ revs->dense_combined_merges = 0;
+}
+
+static void set_dense_combined(struct rev_info *revs)
+{
+ suppress(revs);
+ revs->combine_merges = 1;
+ revs->dense_combined_merges = 1;
+}
+
+static void set_diff_merges(struct rev_info *revs, const char *optarg)
+{
+ if (!strcmp(optarg, "off") || !strcmp(optarg, "none")) {
+ suppress(revs);
+ /* Return early to leave revs->merges_need_diff unset */
+ return;
+ }
+
+ if (!strcmp(optarg, "1") || !strcmp(optarg, "first-parent"))
+ set_first_parent(revs);
+ else if (!strcmp(optarg, "m") || !strcmp(optarg, "separate"))
+ set_separate(revs);
+ else if (!strcmp(optarg, "c") || !strcmp(optarg, "combined"))
+ set_combined(revs);
+ else if (!strcmp(optarg, "cc") || !strcmp(optarg, "dense-combined"))
+ set_dense_combined(revs);
+ else
+ die(_("unknown value for --diff-merges: %s"), optarg);
+
+ /* The flag is cleared by set_xxx() functions, so don't move this up */
+ revs->merges_need_diff = 1;
+}
+
+/*
+ * Public functions. They are in the order they are called.
+ */
+
+int diff_merges_parse_opts(struct rev_info *revs, const char **argv)
+{
+ int argcount = 1;
+ const char *optarg;
+ const char *arg = argv[0];
+
+ if (!strcmp(arg, "-m")) {
+ set_m(revs);
+ } else if (!strcmp(arg, "-c")) {
+ set_combined(revs);
+ revs->combined_imply_patch = 1;
+ } else if (!strcmp(arg, "--cc")) {
+ set_dense_combined(revs);
+ revs->combined_imply_patch = 1;
+ } else if (!strcmp(arg, "--no-diff-merges")) {
+ suppress(revs);
+ } else if (!strcmp(arg, "--combined-all-paths")) {
+ revs->combined_all_paths = 1;
+ } else if ((argcount = parse_long_opt("diff-merges", argv, &optarg))) {
+ set_diff_merges(revs, optarg);
+ } else
+ return 0;
+
+ revs->explicit_diff_merges = 1;
+ return argcount;
+}
+
+void diff_merges_suppress(struct rev_info *revs)
+{
+ suppress(revs);
+}
+
+void diff_merges_default_to_first_parent(struct rev_info *revs)
+{
+ if (!revs->explicit_diff_merges)
+ revs->separate_merges = 1;
+ if (revs->separate_merges)
+ revs->first_parent_merges = 1;
+}
+
+void diff_merges_default_to_dense_combined(struct rev_info *revs)
+{
+ if (!revs->explicit_diff_merges)
+ set_dense_combined(revs);
+}
+
+void diff_merges_set_dense_combined_if_unset(struct rev_info *revs)
+{
+ if (!revs->combine_merges)
+ set_dense_combined(revs);
+}
+
+void diff_merges_setup_revs(struct rev_info *revs)
+{
+ if (revs->combine_merges == 0)
+ revs->dense_combined_merges = 0;
+ if (revs->separate_merges == 0)
+ revs->first_parent_merges = 0;
+ if (revs->combined_all_paths && !revs->combine_merges)
+ die("--combined-all-paths makes no sense without -c or --cc");
+ if (revs->combined_imply_patch)
+ revs->diff = 1;
+ if (revs->combined_imply_patch || revs->merges_need_diff) {
+ if (!revs->diffopt.output_format)
+ revs->diffopt.output_format = DIFF_FORMAT_PATCH;
+ }
+}
diff --git a/diff-merges.h b/diff-merges.h
new file mode 100644
index 0000000000..659467c99a
--- /dev/null
+++ b/diff-merges.h
@@ -0,0 +1,24 @@
+#ifndef DIFF_MERGES_H
+#define DIFF_MERGES_H
+
+/*
+ * diff-merges - utility module to handle command-line options for
+ * selection of particular diff format of merge commits
+ * representation.
+ */
+
+struct rev_info;
+
+int diff_merges_parse_opts(struct rev_info *revs, const char **argv);
+
+void diff_merges_suppress(struct rev_info *revs);
+
+void diff_merges_default_to_first_parent(struct rev_info *revs);
+
+void diff_merges_default_to_dense_combined(struct rev_info *revs);
+
+void diff_merges_set_dense_combined_if_unset(struct rev_info *revs);
+
+void diff_merges_setup_revs(struct rev_info *revs);
+
+#endif
diff --git a/dir.c b/dir.c
index d637461da5..d153a63bbd 100644
--- a/dir.c
+++ b/dir.c
@@ -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;
diff --git a/dir.h b/dir.h
index a3c40dec51..facfae4740 100644
--- a/dir.h
+++ b/dir.h
@@ -448,6 +448,8 @@ int is_empty_dir(const char *dir);
void setup_standard_excludes(struct dir_struct *dir);
+char *get_sparse_checkout_filename(void);
+int get_sparse_checkout_patterns(struct pattern_list *pl);
/* Constants for remove_dir_recursively: */
diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c
index 9a664a4a58..46f6015c44 100644
--- a/fmt-merge-msg.c
+++ b/fmt-merge-msg.c
@@ -2,6 +2,7 @@
#include "refs.h"
#include "object-store.h"
#include "diff.h"
+#include "diff-merges.h"
#include "revision.h"
#include "tag.h"
#include "string-list.h"
@@ -670,7 +671,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
head = lookup_commit_or_die(&head_oid, "HEAD");
repo_init_revisions(the_repository, &rev, NULL);
rev.commit_format = CMIT_FMT_ONELINE;
- rev.ignore_merges = 1;
+ diff_merges_suppress(&rev);
rev.limited = 1;
strbuf_complete_line(out);
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);
diff --git a/git-compat-util.h b/git-compat-util.h
index 104993b975..5d5e47fbe2 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -1176,9 +1176,12 @@ static inline int regexec_buf(const regex_t *preg, const char *buf, size_t size,
#endif
#endif
-#if defined(__GNUC__) || (_MSC_VER >= 1400) || defined(__C99_MACRO_WITH_VA_ARGS)
+/*
+ * This is always defined as a first step towards making the use of variadic
+ * macros unconditional. If it causes compilation problems on your platform,
+ * please report it to the Git mailing list at git@vger.kernel.org.
+ */
#define HAVE_VARIADIC_MACROS 1
-#endif
/* usage.c: only to be used for testing BUG() implementation (see test-tool) */
extern int BUG_exit_code;
diff --git a/grep.c b/grep.c
index efeb6dc58d..21f0ee03be 100644
--- a/grep.c
+++ b/grep.c
@@ -400,8 +400,6 @@ static void compile_pcre1_regexp(struct grep_pat *p, const struct grep_opt *opt)
#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;
@@ -508,8 +506,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 +531,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;
}
}
@@ -616,8 +609,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];
@@ -812,87 +803,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 +883,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 +903,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 +926,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) {
diff --git a/grep.h b/grep.h
index b5c4e223a8..5248c6ef7e 100644
--- a/grep.h
+++ b/grep.h
@@ -136,7 +136,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
diff --git a/hash-lookup.c b/hash-lookup.c
index 1191856a32..b98ed5e11e 100644
--- a/hash-lookup.c
+++ b/hash-lookup.c
@@ -1,9 +1,9 @@
#include "cache.h"
#include "hash-lookup.h"
-static uint32_t take2(const unsigned char *hash)
+static uint32_t take2(const struct object_id *oid, size_t ofs)
{
- return ((hash[0] << 8) | hash[1]);
+ return ((oid->hash[ofs] << 8) | oid->hash[ofs + 1]);
}
/*
@@ -47,11 +47,11 @@ static uint32_t take2(const unsigned char *hash)
*/
/*
* The table should contain "nr" elements.
- * The hash of element i (between 0 and nr - 1) should be returned
+ * The oid of element i (between 0 and nr - 1) should be returned
* by "fn(i, table)".
*/
-int hash_pos(const unsigned char *hash, void *table, size_t nr,
- hash_access_fn fn)
+int oid_pos(const struct object_id *oid, const void *table, size_t nr,
+ oid_access_fn fn)
{
size_t hi = nr;
size_t lo = 0;
@@ -64,9 +64,9 @@ int hash_pos(const unsigned char *hash, void *table, size_t nr,
size_t lov, hiv, miv, ofs;
for (ofs = 0; ofs < the_hash_algo->rawsz - 2; ofs += 2) {
- lov = take2(fn(0, table) + ofs);
- hiv = take2(fn(nr - 1, table) + ofs);
- miv = take2(hash + ofs);
+ lov = take2(fn(0, table), ofs);
+ hiv = take2(fn(nr - 1, table), ofs);
+ miv = take2(oid, ofs);
if (miv < lov)
return -1;
if (hiv < miv)
@@ -88,7 +88,7 @@ int hash_pos(const unsigned char *hash, void *table, size_t nr,
do {
int cmp;
- cmp = hashcmp(fn(mi, table), hash);
+ cmp = oidcmp(fn(mi, table), oid);
if (!cmp)
return mi;
if (cmp > 0)
diff --git a/hash-lookup.h b/hash-lookup.h
index 5d476dec72..dbd71ebaf7 100644
--- a/hash-lookup.h
+++ b/hash-lookup.h
@@ -1,12 +1,12 @@
#ifndef HASH_LOOKUP_H
#define HASH_LOOKUP_H
-typedef const unsigned char *hash_access_fn(size_t index, void *table);
+typedef const struct object_id *oid_access_fn(size_t index, const void *table);
-int hash_pos(const unsigned char *hash,
- void *table,
- size_t nr,
- hash_access_fn fn);
+int oid_pos(const struct object_id *oid,
+ const void *table,
+ size_t nr,
+ oid_access_fn fn);
/*
* Searches for hash in table, using the given fanout table to determine the
diff --git a/log-tree.c b/log-tree.c
index fd0dde97ec..e048467650 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -899,15 +899,21 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
int showed_log;
struct commit_list *parents;
struct object_id *oid;
+ int is_merge;
+ int all_need_diff = opt->diff || opt->diffopt.flags.exit_with_status;
- if (!opt->diff && !opt->diffopt.flags.exit_with_status)
+ if (!all_need_diff && !opt->merges_need_diff)
return 0;
parse_commit_or_die(commit);
oid = get_commit_tree_oid(commit);
- /* Root commit? */
parents = get_saved_parents(opt, commit);
+ is_merge = parents && parents->next;
+ if (!is_merge && !all_need_diff)
+ return 0;
+
+ /* Root commit? */
if (!parents) {
if (opt->show_root_diff) {
diff_root_tree_oid(oid, "", &opt->diffopt);
@@ -916,16 +922,16 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
return !opt->loginfo;
}
- /* More than one parent? */
- if (parents->next) {
- if (opt->ignore_merges)
- return 0;
- else if (opt->combine_merges)
+ if (is_merge) {
+ if (opt->combine_merges)
return do_diff_combined(opt, commit);
- else if (!opt->first_parent_only) {
- /* If we show multiple diffs, show the parent info */
- log->parent = parents->item;
- }
+ if (opt->separate_merges) {
+ if (!opt->first_parent_merges) {
+ /* Show parent info for multiple diffs */
+ log->parent = parents->item;
+ }
+ } else
+ return 0;
}
showed_log = 0;
@@ -941,7 +947,7 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
/* Set up the log info for the next parent, if any.. */
parents = parents->next;
- if (!parents || opt->first_parent_only)
+ if (!parents || opt->first_parent_merges)
break;
log->parent = parents->item;
opt->loginfo = log;
diff --git a/ls-refs.c b/ls-refs.c
index a1e0b473e4..5ff5473869 100644
--- a/ls-refs.c
+++ b/ls-refs.c
@@ -63,7 +63,7 @@ static int send_ref(const char *refname, const struct object_id *oid,
if (data->peel) {
struct object_id peeled;
- if (!peel_ref(refname, &peeled))
+ if (!peel_iterated_oid(oid, &peeled))
strbuf_addf(&refline, " peeled:%s", oid_to_hex(&peeled));
}
@@ -90,6 +90,7 @@ int ls_refs(struct repository *r, struct strvec *keys,
struct ls_refs_data data;
memset(&data, 0, sizeof(data));
+ strvec_init(&data.prefixes);
git_config(ls_refs_config, NULL);
@@ -109,7 +110,10 @@ int ls_refs(struct repository *r, struct strvec *keys,
die(_("expected flush after ls-refs arguments"));
head_ref_namespaced(send_ref, &data);
- for_each_namespaced_ref(send_ref, &data);
+ if (!data.prefixes.nr)
+ strvec_push(&data.prefixes, "");
+ for_each_fullref_in_prefixes(get_git_namespace(), data.prefixes.v,
+ send_ref, &data, 0);
packet_flush(1);
strvec_clear(&data.prefixes);
return 0;
diff --git a/merge-ort.c b/merge-ort.c
index d36a92b59b..6900ab9e7f 100644
--- a/merge-ort.c
+++ b/merge-ort.c
@@ -25,8 +25,11 @@
#include "diff.h"
#include "diffcore.h"
#include "dir.h"
+#include "ll-merge.h"
#include "object-store.h"
+#include "revision.h"
#include "strmap.h"
+#include "submodule.h"
#include "tree.h"
#include "unpack-trees.h"
#include "xdiff-interface.h"
@@ -349,6 +352,25 @@ static int err(struct merge_options *opt, const char *err, ...)
return -1;
}
+static void format_commit(struct strbuf *sb,
+ int indent,
+ struct commit *commit)
+{
+ struct merge_remote_desc *desc;
+ struct pretty_print_context ctx = {0};
+ ctx.abbrev = DEFAULT_ABBREV;
+
+ strbuf_addchars(sb, ' ', indent);
+ desc = merge_remote_util(commit);
+ if (desc) {
+ strbuf_addf(sb, "virtual %s\n", desc->name);
+ return;
+ }
+
+ format_commit_message(commit, "%h %s", sb, &ctx);
+ strbuf_addch(sb, '\n');
+}
+
__attribute__((format (printf, 4, 5)))
static void path_msg(struct merge_options *opt,
const char *path,
@@ -370,6 +392,36 @@ static void path_msg(struct merge_options *opt,
strbuf_addch(sb, '\n');
}
+/* add a string to a strbuf, but converting "/" to "_" */
+static void add_flattened_path(struct strbuf *out, const char *s)
+{
+ size_t i = out->len;
+ strbuf_addstr(out, s);
+ for (; i < out->len; i++)
+ if (out->buf[i] == '/')
+ out->buf[i] = '_';
+}
+
+static char *unique_path(struct strmap *existing_paths,
+ const char *path,
+ const char *branch)
+{
+ struct strbuf newpath = STRBUF_INIT;
+ int suffix = 0;
+ size_t base_len;
+
+ strbuf_addf(&newpath, "%s~", path);
+ add_flattened_path(&newpath, branch);
+
+ base_len = newpath.len;
+ while (strmap_contains(existing_paths, newpath.buf)) {
+ strbuf_setlen(&newpath, base_len);
+ strbuf_addf(&newpath, "_%d", suffix++);
+ }
+
+ return strbuf_detach(&newpath, NULL);
+}
+
/*** Function Grouping: functions related to collect_merge_info() ***/
static void setup_path_info(struct merge_options *opt,
@@ -628,6 +680,249 @@ static int collect_merge_info(struct merge_options *opt,
/*** Function Grouping: functions related to threeway content merges ***/
+static int find_first_merges(struct repository *repo,
+ const char *path,
+ struct commit *a,
+ struct commit *b,
+ struct object_array *result)
+{
+ int i, j;
+ struct object_array merges = OBJECT_ARRAY_INIT;
+ struct commit *commit;
+ int contains_another;
+
+ char merged_revision[GIT_MAX_HEXSZ + 2];
+ const char *rev_args[] = { "rev-list", "--merges", "--ancestry-path",
+ "--all", merged_revision, NULL };
+ struct rev_info revs;
+ struct setup_revision_opt rev_opts;
+
+ memset(result, 0, sizeof(struct object_array));
+ memset(&rev_opts, 0, sizeof(rev_opts));
+
+ /* get all revisions that merge commit a */
+ xsnprintf(merged_revision, sizeof(merged_revision), "^%s",
+ oid_to_hex(&a->object.oid));
+ repo_init_revisions(repo, &revs, NULL);
+ rev_opts.submodule = path;
+ /* FIXME: can't handle linked worktrees in submodules yet */
+ revs.single_worktree = path != NULL;
+ setup_revisions(ARRAY_SIZE(rev_args)-1, rev_args, &revs, &rev_opts);
+
+ /* save all revisions from the above list that contain b */
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
+ while ((commit = get_revision(&revs)) != NULL) {
+ struct object *o = &(commit->object);
+ if (in_merge_bases(b, commit))
+ add_object_array(o, NULL, &merges);
+ }
+ reset_revision_walk();
+
+ /* Now we've got all merges that contain a and b. Prune all
+ * merges that contain another found merge and save them in
+ * result.
+ */
+ for (i = 0; i < merges.nr; i++) {
+ struct commit *m1 = (struct commit *) merges.objects[i].item;
+
+ contains_another = 0;
+ for (j = 0; j < merges.nr; j++) {
+ struct commit *m2 = (struct commit *) merges.objects[j].item;
+ if (i != j && in_merge_bases(m2, m1)) {
+ contains_another = 1;
+ break;
+ }
+ }
+
+ if (!contains_another)
+ add_object_array(merges.objects[i].item, NULL, result);
+ }
+
+ object_array_clear(&merges);
+ return result->nr;
+}
+
+static int merge_submodule(struct merge_options *opt,
+ const char *path,
+ const struct object_id *o,
+ const struct object_id *a,
+ const struct object_id *b,
+ struct object_id *result)
+{
+ struct commit *commit_o, *commit_a, *commit_b;
+ int parent_count;
+ struct object_array merges;
+ struct strbuf sb = STRBUF_INIT;
+
+ int i;
+ int search = !opt->priv->call_depth;
+
+ /* store fallback answer in result in case we fail */
+ oidcpy(result, opt->priv->call_depth ? o : a);
+
+ /* we can not handle deletion conflicts */
+ if (is_null_oid(o))
+ return 0;
+ if (is_null_oid(a))
+ return 0;
+ if (is_null_oid(b))
+ return 0;
+
+ if (add_submodule_odb(path)) {
+ path_msg(opt, path, 0,
+ _("Failed to merge submodule %s (not checked out)"),
+ path);
+ return 0;
+ }
+
+ if (!(commit_o = lookup_commit_reference(opt->repo, o)) ||
+ !(commit_a = lookup_commit_reference(opt->repo, a)) ||
+ !(commit_b = lookup_commit_reference(opt->repo, b))) {
+ path_msg(opt, path, 0,
+ _("Failed to merge submodule %s (commits not present)"),
+ path);
+ return 0;
+ }
+
+ /* check whether both changes are forward */
+ if (!in_merge_bases(commit_o, commit_a) ||
+ !in_merge_bases(commit_o, commit_b)) {
+ path_msg(opt, path, 0,
+ _("Failed to merge submodule %s "
+ "(commits don't follow merge-base)"),
+ path);
+ return 0;
+ }
+
+ /* Case #1: a is contained in b or vice versa */
+ if (in_merge_bases(commit_a, commit_b)) {
+ oidcpy(result, b);
+ path_msg(opt, path, 1,
+ _("Note: Fast-forwarding submodule %s to %s"),
+ path, oid_to_hex(b));
+ return 1;
+ }
+ if (in_merge_bases(commit_b, commit_a)) {
+ oidcpy(result, a);
+ path_msg(opt, path, 1,
+ _("Note: Fast-forwarding submodule %s to %s"),
+ path, oid_to_hex(a));
+ return 1;
+ }
+
+ /*
+ * Case #2: There are one or more merges that contain a and b in
+ * the submodule. If there is only one, then present it as a
+ * suggestion to the user, but leave it marked unmerged so the
+ * user needs to confirm the resolution.
+ */
+
+ /* Skip the search if makes no sense to the calling context. */
+ if (!search)
+ return 0;
+
+ /* find commit which merges them */
+ parent_count = find_first_merges(opt->repo, path, commit_a, commit_b,
+ &merges);
+ switch (parent_count) {
+ case 0:
+ path_msg(opt, path, 0, _("Failed to merge submodule %s"), path);
+ break;
+
+ case 1:
+ format_commit(&sb, 4,
+ (struct commit *)merges.objects[0].item);
+ path_msg(opt, path, 0,
+ _("Failed to merge submodule %s, but a possible merge "
+ "resolution exists:\n%s\n"),
+ path, sb.buf);
+ path_msg(opt, path, 1,
+ _("If this is correct simply add it to the index "
+ "for example\n"
+ "by using:\n\n"
+ " git update-index --cacheinfo 160000 %s \"%s\"\n\n"
+ "which will accept this suggestion.\n"),
+ oid_to_hex(&merges.objects[0].item->oid), path);
+ strbuf_release(&sb);
+ break;
+ default:
+ for (i = 0; i < merges.nr; i++)
+ format_commit(&sb, 4,
+ (struct commit *)merges.objects[i].item);
+ path_msg(opt, path, 0,
+ _("Failed to merge submodule %s, but multiple "
+ "possible merges exist:\n%s"), path, sb.buf);
+ strbuf_release(&sb);
+ }
+
+ object_array_clear(&merges);
+ return 0;
+}
+
+static int merge_3way(struct merge_options *opt,
+ const char *path,
+ const struct object_id *o,
+ const struct object_id *a,
+ const struct object_id *b,
+ const char *pathnames[3],
+ const int extra_marker_size,
+ mmbuffer_t *result_buf)
+{
+ mmfile_t orig, src1, src2;
+ struct ll_merge_options ll_opts = {0};
+ char *base, *name1, *name2;
+ int merge_status;
+
+ ll_opts.renormalize = opt->renormalize;
+ ll_opts.extra_marker_size = extra_marker_size;
+ ll_opts.xdl_opts = opt->xdl_opts;
+
+ if (opt->priv->call_depth) {
+ ll_opts.virtual_ancestor = 1;
+ ll_opts.variant = 0;
+ } else {
+ switch (opt->recursive_variant) {
+ case MERGE_VARIANT_OURS:
+ ll_opts.variant = XDL_MERGE_FAVOR_OURS;
+ break;
+ case MERGE_VARIANT_THEIRS:
+ ll_opts.variant = XDL_MERGE_FAVOR_THEIRS;
+ break;
+ default:
+ ll_opts.variant = 0;
+ break;
+ }
+ }
+
+ assert(pathnames[0] && pathnames[1] && pathnames[2] && opt->ancestor);
+ if (pathnames[0] == pathnames[1] && pathnames[1] == pathnames[2]) {
+ base = mkpathdup("%s", opt->ancestor);
+ name1 = mkpathdup("%s", opt->branch1);
+ name2 = mkpathdup("%s", opt->branch2);
+ } else {
+ base = mkpathdup("%s:%s", opt->ancestor, pathnames[0]);
+ name1 = mkpathdup("%s:%s", opt->branch1, pathnames[1]);
+ name2 = mkpathdup("%s:%s", opt->branch2, pathnames[2]);
+ }
+
+ read_mmblob(&orig, o);
+ read_mmblob(&src1, a);
+ read_mmblob(&src2, b);
+
+ merge_status = ll_merge(result_buf, path, &orig, base,
+ &src1, name1, &src2, name2,
+ opt->repo->index, &ll_opts);
+
+ free(base);
+ free(name1);
+ free(name2);
+ free(orig.ptr);
+ free(src1.ptr);
+ free(src2.ptr);
+ return merge_status;
+}
+
static int handle_content_merge(struct merge_options *opt,
const char *path,
const struct version_info *o,
@@ -637,7 +932,130 @@ static int handle_content_merge(struct merge_options *opt,
const int extra_marker_size,
struct version_info *result)
{
- die("Not yet implemented");
+ /*
+ * path is the target location where we want to put the file, and
+ * is used to determine any normalization rules in ll_merge.
+ *
+ * The normal case is that path and all entries in pathnames are
+ * identical, though renames can affect which path we got one of
+ * the three blobs to merge on various sides of history.
+ *
+ * extra_marker_size is the amount to extend conflict markers in
+ * ll_merge; this is neeed if we have content merges of content
+ * merges, which happens for example with rename/rename(2to1) and
+ * rename/add conflicts.
+ */
+ unsigned clean = 1;
+
+ /*
+ * handle_content_merge() needs both files to be of the same type, i.e.
+ * both files OR both submodules OR both symlinks. Conflicting types
+ * needs to be handled elsewhere.
+ */
+ assert((S_IFMT & a->mode) == (S_IFMT & b->mode));
+
+ /* Merge modes */
+ if (a->mode == b->mode || a->mode == o->mode)
+ result->mode = b->mode;
+ else {
+ /* must be the 100644/100755 case */
+ assert(S_ISREG(a->mode));
+ result->mode = a->mode;
+ clean = (b->mode == o->mode);
+ /*
+ * FIXME: If opt->priv->call_depth && !clean, then we really
+ * should not make result->mode match either a->mode or
+ * b->mode; that causes t6036 "check conflicting mode for
+ * regular file" to fail. It would be best to use some other
+ * mode, but we'll confuse all kinds of stuff if we use one
+ * where S_ISREG(result->mode) isn't true, and if we use
+ * something like 0100666, then tree-walk.c's calls to
+ * canon_mode() will just normalize that to 100644 for us and
+ * thus not solve anything.
+ *
+ * Figure out if there's some kind of way we can work around
+ * this...
+ */
+ }
+
+ /*
+ * Trivial oid merge.
+ *
+ * Note: While one might assume that the next four lines would
+ * be unnecessary due to the fact that match_mask is often
+ * setup and already handled, renames don't always take care
+ * of that.
+ */
+ if (oideq(&a->oid, &b->oid) || oideq(&a->oid, &o->oid))
+ oidcpy(&result->oid, &b->oid);
+ else if (oideq(&b->oid, &o->oid))
+ oidcpy(&result->oid, &a->oid);
+
+ /* Remaining rules depend on file vs. submodule vs. symlink. */
+ else if (S_ISREG(a->mode)) {
+ mmbuffer_t result_buf;
+ int ret = 0, merge_status;
+ int two_way;
+
+ /*
+ * If 'o' is different type, treat it as null so we do a
+ * two-way merge.
+ */
+ two_way = ((S_IFMT & o->mode) != (S_IFMT & a->mode));
+
+ merge_status = merge_3way(opt, path,
+ two_way ? &null_oid : &o->oid,
+ &a->oid, &b->oid,
+ pathnames, extra_marker_size,
+ &result_buf);
+
+ if ((merge_status < 0) || !result_buf.ptr)
+ ret = err(opt, _("Failed to execute internal merge"));
+
+ if (!ret &&
+ write_object_file(result_buf.ptr, result_buf.size,
+ blob_type, &result->oid))
+ ret = err(opt, _("Unable to add %s to database"),
+ path);
+
+ free(result_buf.ptr);
+ if (ret)
+ return -1;
+ clean &= (merge_status == 0);
+ path_msg(opt, path, 1, _("Auto-merging %s"), path);
+ } else if (S_ISGITLINK(a->mode)) {
+ int two_way = ((S_IFMT & o->mode) != (S_IFMT & a->mode));
+ clean = merge_submodule(opt, pathnames[0],
+ two_way ? &null_oid : &o->oid,
+ &a->oid, &b->oid, &result->oid);
+ if (opt->priv->call_depth && two_way && !clean) {
+ result->mode = o->mode;
+ oidcpy(&result->oid, &o->oid);
+ }
+ } else if (S_ISLNK(a->mode)) {
+ if (opt->priv->call_depth) {
+ clean = 0;
+ result->mode = o->mode;
+ oidcpy(&result->oid, &o->oid);
+ } else {
+ switch (opt->recursive_variant) {
+ case MERGE_VARIANT_NORMAL:
+ clean = 0;
+ oidcpy(&result->oid, &a->oid);
+ break;
+ case MERGE_VARIANT_OURS:
+ oidcpy(&result->oid, &a->oid);
+ break;
+ case MERGE_VARIANT_THEIRS:
+ oidcpy(&result->oid, &b->oid);
+ break;
+ }
+ }
+ } else
+ BUG("unsupported object type in the tree: %06o for %s",
+ a->mode, path);
+
+ return clean;
}
/*** Function Grouping: functions related to detect_and_process_renames(), ***
@@ -1366,6 +1784,8 @@ static void process_entry(struct merge_options *opt,
struct conflict_info *ci,
struct directory_versions *dir_metadata)
{
+ int df_file_index = 0;
+
VERIFY_CI(ci);
assert(ci->filemask >= 0 && ci->filemask <= 7);
/* ci->match_mask == 7 was handled in collect_merge_info_callback() */
@@ -1380,14 +1800,108 @@ static void process_entry(struct merge_options *opt,
assert(ci->df_conflict);
}
- if (ci->df_conflict) {
- die("Not yet implemented.");
+ if (ci->df_conflict && ci->merged.result.mode == 0) {
+ int i;
+
+ /*
+ * directory no longer in the way, but we do have a file we
+ * need to place here so we need to clean away the "directory
+ * merges to nothing" result.
+ */
+ ci->df_conflict = 0;
+ assert(ci->filemask != 0);
+ ci->merged.clean = 0;
+ ci->merged.is_null = 0;
+ /* and we want to zero out any directory-related entries */
+ ci->match_mask = (ci->match_mask & ~ci->dirmask);
+ ci->dirmask = 0;
+ for (i = MERGE_BASE; i <= MERGE_SIDE2; i++) {
+ if (ci->filemask & (1 << i))
+ continue;
+ ci->stages[i].mode = 0;
+ oidcpy(&ci->stages[i].oid, &null_oid);
+ }
+ } else if (ci->df_conflict && ci->merged.result.mode != 0) {
+ /*
+ * This started out as a D/F conflict, and the entries in
+ * the competing directory were not removed by the merge as
+ * evidenced by write_completed_directory() writing a value
+ * to ci->merged.result.mode.
+ */
+ struct conflict_info *new_ci;
+ const char *branch;
+ const char *old_path = path;
+ int i;
+
+ assert(ci->merged.result.mode == S_IFDIR);
+
+ /*
+ * If filemask is 1, we can just ignore the file as having
+ * been deleted on both sides. We do not want to overwrite
+ * ci->merged.result, since it stores the tree for all the
+ * files under it.
+ */
+ if (ci->filemask == 1) {
+ ci->filemask = 0;
+ return;
+ }
+
+ /*
+ * This file still exists on at least one side, and we want
+ * the directory to remain here, so we need to move this
+ * path to some new location.
+ */
+ new_ci = xcalloc(1, sizeof(*new_ci));
+ /* We don't really want new_ci->merged.result copied, but it'll
+ * be overwritten below so it doesn't matter. We also don't
+ * want any directory mode/oid values copied, but we'll zero
+ * those out immediately. We do want the rest of ci copied.
+ */
+ memcpy(new_ci, ci, sizeof(*ci));
+ new_ci->match_mask = (new_ci->match_mask & ~new_ci->dirmask);
+ new_ci->dirmask = 0;
+ for (i = MERGE_BASE; i <= MERGE_SIDE2; i++) {
+ if (new_ci->filemask & (1 << i))
+ continue;
+ /* zero out any entries related to directories */
+ new_ci->stages[i].mode = 0;
+ oidcpy(&new_ci->stages[i].oid, &null_oid);
+ }
+
+ /*
+ * Find out which side this file came from; note that we
+ * cannot just use ci->filemask, because renames could cause
+ * the filemask to go back to 7. So we use dirmask, then
+ * pick the opposite side's index.
+ */
+ df_file_index = (ci->dirmask & (1 << 1)) ? 2 : 1;
+ branch = (df_file_index == 1) ? opt->branch1 : opt->branch2;
+ path = unique_path(&opt->priv->paths, path, branch);
+ strmap_put(&opt->priv->paths, path, new_ci);
+
+ path_msg(opt, path, 0,
+ _("CONFLICT (file/directory): directory in the way "
+ "of %s from %s; moving it to %s instead."),
+ old_path, branch, path);
+
+ /*
+ * Zero out the filemask for the old ci. At this point, ci
+ * was just an entry for a directory, so we don't need to
+ * do anything more with it.
+ */
+ ci->filemask = 0;
+
+ /*
+ * Now note that we're working on the new entry (path was
+ * updated above.
+ */
+ ci = new_ci;
}
/*
* NOTE: Below there is a long switch-like if-elseif-elseif... block
* which the code goes through even for the df_conflict cases
- * above. Well, it will once we don't die-not-implemented above.
+ * above.
*/
if (ci->match_mask) {
ci->merged.clean = 1;
@@ -1411,21 +1925,142 @@ static void process_entry(struct merge_options *opt,
} else if (ci->filemask >= 6 &&
(S_IFMT & ci->stages[1].mode) !=
(S_IFMT & ci->stages[2].mode)) {
- /*
- * Two different items from (file/submodule/symlink)
- */
- die("Not yet implemented.");
+ /* Two different items from (file/submodule/symlink) */
+ if (opt->priv->call_depth) {
+ /* Just use the version from the merge base */
+ ci->merged.clean = 0;
+ oidcpy(&ci->merged.result.oid, &ci->stages[0].oid);
+ ci->merged.result.mode = ci->stages[0].mode;
+ ci->merged.is_null = (ci->merged.result.mode == 0);
+ } else {
+ /* Handle by renaming one or both to separate paths. */
+ unsigned o_mode = ci->stages[0].mode;
+ unsigned a_mode = ci->stages[1].mode;
+ unsigned b_mode = ci->stages[2].mode;
+ struct conflict_info *new_ci;
+ const char *a_path = NULL, *b_path = NULL;
+ int rename_a = 0, rename_b = 0;
+
+ new_ci = xmalloc(sizeof(*new_ci));
+
+ if (S_ISREG(a_mode))
+ rename_a = 1;
+ else if (S_ISREG(b_mode))
+ rename_b = 1;
+ else {
+ rename_a = 1;
+ rename_b = 1;
+ }
+
+ path_msg(opt, path, 0,
+ _("CONFLICT (distinct types): %s had different "
+ "types on each side; renamed %s of them so "
+ "each can be recorded somewhere."),
+ path,
+ (rename_a && rename_b) ? _("both") : _("one"));
+
+ ci->merged.clean = 0;
+ memcpy(new_ci, ci, sizeof(*new_ci));
+
+ /* Put b into new_ci, removing a from stages */
+ new_ci->merged.result.mode = ci->stages[2].mode;
+ oidcpy(&new_ci->merged.result.oid, &ci->stages[2].oid);
+ new_ci->stages[1].mode = 0;
+ oidcpy(&new_ci->stages[1].oid, &null_oid);
+ new_ci->filemask = 5;
+ if ((S_IFMT & b_mode) != (S_IFMT & o_mode)) {
+ new_ci->stages[0].mode = 0;
+ oidcpy(&new_ci->stages[0].oid, &null_oid);
+ new_ci->filemask = 4;
+ }
+
+ /* Leave only a in ci, fixing stages. */
+ ci->merged.result.mode = ci->stages[1].mode;
+ oidcpy(&ci->merged.result.oid, &ci->stages[1].oid);
+ ci->stages[2].mode = 0;
+ oidcpy(&ci->stages[2].oid, &null_oid);
+ ci->filemask = 3;
+ if ((S_IFMT & a_mode) != (S_IFMT & o_mode)) {
+ ci->stages[0].mode = 0;
+ oidcpy(&ci->stages[0].oid, &null_oid);
+ ci->filemask = 2;
+ }
+
+ /* Insert entries into opt->priv_paths */
+ assert(rename_a || rename_b);
+ if (rename_a) {
+ a_path = unique_path(&opt->priv->paths,
+ path, opt->branch1);
+ strmap_put(&opt->priv->paths, a_path, ci);
+ }
+
+ if (rename_b)
+ b_path = unique_path(&opt->priv->paths,
+ path, opt->branch2);
+ else
+ b_path = path;
+ strmap_put(&opt->priv->paths, b_path, new_ci);
+
+ if (rename_a && rename_b) {
+ strmap_remove(&opt->priv->paths, path, 0);
+ /*
+ * We removed path from opt->priv->paths. path
+ * will also eventually need to be freed, but
+ * it may still be used by e.g. ci->pathnames.
+ * So, store it in another string-list for now.
+ */
+ string_list_append(&opt->priv->paths_to_free,
+ path);
+ }
+
+ /*
+ * Do special handling for b_path since process_entry()
+ * won't be called on it specially.
+ */
+ strmap_put(&opt->priv->conflicted, b_path, new_ci);
+ record_entry_for_tree(dir_metadata, b_path,
+ &new_ci->merged);
+
+ /*
+ * Remaining code for processing this entry should
+ * think in terms of processing a_path.
+ */
+ if (a_path)
+ path = a_path;
+ }
} else if (ci->filemask >= 6) {
- /*
- * TODO: Needs a two-way or three-way content merge, but we're
- * just being lazy and copying the version from HEAD and
- * leaving it as conflicted.
- */
- ci->merged.clean = 0;
- ci->merged.result.mode = ci->stages[1].mode;
- oidcpy(&ci->merged.result.oid, &ci->stages[1].oid);
- /* When we fix above, we'll call handle_content_merge() */
- (void)handle_content_merge;
+ /* Need a two-way or three-way content merge */
+ struct version_info merged_file;
+ unsigned clean_merge;
+ struct version_info *o = &ci->stages[0];
+ struct version_info *a = &ci->stages[1];
+ struct version_info *b = &ci->stages[2];
+
+ clean_merge = handle_content_merge(opt, path, o, a, b,
+ ci->pathnames,
+ opt->priv->call_depth * 2,
+ &merged_file);
+ ci->merged.clean = clean_merge &&
+ !ci->df_conflict && !ci->path_conflict;
+ ci->merged.result.mode = merged_file.mode;
+ ci->merged.is_null = (merged_file.mode == 0);
+ oidcpy(&ci->merged.result.oid, &merged_file.oid);
+ if (clean_merge && ci->df_conflict) {
+ assert(df_file_index == 1 || df_file_index == 2);
+ ci->filemask = 1 << df_file_index;
+ ci->stages[df_file_index].mode = merged_file.mode;
+ oidcpy(&ci->stages[df_file_index].oid, &merged_file.oid);
+ }
+ if (!clean_merge) {
+ const char *reason = _("content");
+ if (ci->filemask == 6)
+ reason = _("add/add");
+ if (S_ISGITLINK(merged_file.mode))
+ reason = _("submodule");
+ path_msg(opt, path, 0,
+ _("CONFLICT (%s): Merge conflict in %s"),
+ reason, path);
+ }
} else if (ci->filemask == 3 || ci->filemask == 5) {
/* Modify/delete */
const char *modify_branch, *delete_branch;
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/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/ref-filter.c b/ref-filter.c
index ee337df232..fd994e1874 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -1920,64 +1920,6 @@ static int filter_pattern_match(struct ref_filter *filter, const char *refname)
return match_pattern(filter, refname);
}
-static int qsort_strcmp(const void *va, const void *vb)
-{
- const char *a = *(const char **)va;
- const char *b = *(const char **)vb;
-
- return strcmp(a, b);
-}
-
-static void find_longest_prefixes_1(struct string_list *out,
- struct strbuf *prefix,
- const char **patterns, size_t nr)
-{
- size_t i;
-
- for (i = 0; i < nr; i++) {
- char c = patterns[i][prefix->len];
- if (!c || is_glob_special(c)) {
- string_list_append(out, prefix->buf);
- return;
- }
- }
-
- i = 0;
- while (i < nr) {
- size_t end;
-
- /*
- * Set "end" to the index of the element _after_ the last one
- * in our group.
- */
- for (end = i + 1; end < nr; end++) {
- if (patterns[i][prefix->len] != patterns[end][prefix->len])
- break;
- }
-
- strbuf_addch(prefix, patterns[i][prefix->len]);
- find_longest_prefixes_1(out, prefix, patterns + i, end - i);
- strbuf_setlen(prefix, prefix->len - 1);
-
- i = end;
- }
-}
-
-static void find_longest_prefixes(struct string_list *out,
- const char **patterns)
-{
- struct strvec sorted = STRVEC_INIT;
- struct strbuf prefix = STRBUF_INIT;
-
- strvec_pushv(&sorted, patterns);
- QSORT(sorted.v, sorted.nr, qsort_strcmp);
-
- find_longest_prefixes_1(out, &prefix, sorted.v, sorted.nr);
-
- strvec_clear(&sorted);
- strbuf_release(&prefix);
-}
-
/*
* This is the same as for_each_fullref_in(), but it tries to iterate
* only over the patterns we'll care about. Note that it _doesn't_ do a full
@@ -1988,10 +1930,6 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,
void *cb_data,
int broken)
{
- struct string_list prefixes = STRING_LIST_INIT_DUP;
- struct string_list_item *prefix;
- int ret;
-
if (!filter->match_as_path) {
/*
* in this case, the patterns are applied after
@@ -2015,16 +1953,8 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,
return for_each_fullref_in("", cb, cb_data, broken);
}
- find_longest_prefixes(&prefixes, filter->name_patterns);
-
- for_each_string_list_item(prefix, &prefixes) {
- ret = for_each_fullref_in(prefix->string, cb, cb_data, broken);
- if (ret)
- break;
- }
-
- string_list_clear(&prefixes, 0);
- return ret;
+ return for_each_fullref_in_prefixes(NULL, filter->name_patterns,
+ cb, cb_data, broken);
}
/*
diff --git a/refs.c b/refs.c
index 03968ad787..a665ed5e10 100644
--- a/refs.c
+++ b/refs.c
@@ -1564,6 +1564,93 @@ int for_each_rawref(each_ref_fn fn, void *cb_data)
return refs_for_each_rawref(get_main_ref_store(the_repository), fn, cb_data);
}
+static int qsort_strcmp(const void *va, const void *vb)
+{
+ const char *a = *(const char **)va;
+ const char *b = *(const char **)vb;
+
+ return strcmp(a, b);
+}
+
+static void find_longest_prefixes_1(struct string_list *out,
+ struct strbuf *prefix,
+ const char **patterns, size_t nr)
+{
+ size_t i;
+
+ for (i = 0; i < nr; i++) {
+ char c = patterns[i][prefix->len];
+ if (!c || is_glob_special(c)) {
+ string_list_append(out, prefix->buf);
+ return;
+ }
+ }
+
+ i = 0;
+ while (i < nr) {
+ size_t end;
+
+ /*
+ * Set "end" to the index of the element _after_ the last one
+ * in our group.
+ */
+ for (end = i + 1; end < nr; end++) {
+ if (patterns[i][prefix->len] != patterns[end][prefix->len])
+ break;
+ }
+
+ strbuf_addch(prefix, patterns[i][prefix->len]);
+ find_longest_prefixes_1(out, prefix, patterns + i, end - i);
+ strbuf_setlen(prefix, prefix->len - 1);
+
+ i = end;
+ }
+}
+
+static void find_longest_prefixes(struct string_list *out,
+ const char **patterns)
+{
+ struct strvec sorted = STRVEC_INIT;
+ struct strbuf prefix = STRBUF_INIT;
+
+ strvec_pushv(&sorted, patterns);
+ QSORT(sorted.v, sorted.nr, qsort_strcmp);
+
+ find_longest_prefixes_1(out, &prefix, sorted.v, sorted.nr);
+
+ strvec_clear(&sorted);
+ strbuf_release(&prefix);
+}
+
+int for_each_fullref_in_prefixes(const char *namespace,
+ const char **patterns,
+ each_ref_fn fn, void *cb_data,
+ unsigned int broken)
+{
+ struct string_list prefixes = STRING_LIST_INIT_DUP;
+ struct string_list_item *prefix;
+ struct strbuf buf = STRBUF_INIT;
+ int ret = 0, namespace_len;
+
+ find_longest_prefixes(&prefixes, patterns);
+
+ if (namespace)
+ strbuf_addstr(&buf, namespace);
+ namespace_len = buf.len;
+
+ for_each_string_list_item(prefix, &prefixes) {
+ strbuf_addstr(&buf, prefix->string);
+ ret = for_each_fullref_in(buf.buf, fn, cb_data, broken);
+ if (ret)
+ break;
+ strbuf_setlen(&buf, namespace_len);
+ }
+
+ string_list_clear(&prefixes, 0);
+ strbuf_release(&buf);
+ return ret;
+}
+
static int refs_read_special_head(struct ref_store *ref_store,
const char *refname, struct object_id *oid,
struct strbuf *referent, unsigned int *type)
@@ -1916,31 +2003,14 @@ int refs_pack_refs(struct ref_store *refs, unsigned int flags)
return refs->be->pack_refs(refs, flags);
}
-int refs_peel_ref(struct ref_store *refs, const char *refname,
- struct object_id *oid)
+int peel_iterated_oid(const struct object_id *base, struct object_id *peeled)
{
- int flag;
- struct object_id base;
-
- if (current_ref_iter && current_ref_iter->refname == refname) {
- struct object_id peeled;
-
- if (ref_iterator_peel(current_ref_iter, &peeled))
- return -1;
- oidcpy(oid, &peeled);
- return 0;
- }
-
- if (refs_read_ref_full(refs, refname,
- RESOLVE_REF_READING, &base, &flag))
- return -1;
+ if (current_ref_iter &&
+ (current_ref_iter->oid == base ||
+ oideq(current_ref_iter->oid, base)))
+ return ref_iterator_peel(current_ref_iter, peeled);
- return peel_object(&base, oid);
-}
-
-int peel_ref(const char *refname, struct object_id *oid)
-{
- return refs_peel_ref(get_main_ref_store(the_repository), refname, oid);
+ return peel_object(base, peeled);
}
int refs_create_symref(struct ref_store *refs,
diff --git a/refs.h b/refs.h
index ff05d2e9fe..48970dfc7e 100644
--- a/refs.h
+++ b/refs.h
@@ -118,16 +118,16 @@ int is_branch(const char *refname);
int refs_init_db(struct strbuf *err);
/*
- * If refname is a non-symbolic reference that refers to a tag object,
- * and the tag can be (recursively) dereferenced to a non-tag object,
- * store the object ID of the referred-to object to oid and return 0.
- * If any of these conditions are not met, return a non-zero value.
- * Symbolic references are considered unpeelable, even if they
- * ultimately resolve to a peelable tag.
+ * Return the peeled value of the oid currently being iterated via
+ * for_each_ref(), etc. This is equivalent to calling:
+ *
+ * peel_object(oid, &peeled);
+ *
+ * with the "oid" value given to the each_ref_fn callback, except
+ * that some ref storage may be able to answer the query without
+ * actually loading the object in memory.
*/
-int refs_peel_ref(struct ref_store *refs, const char *refname,
- struct object_id *oid);
-int peel_ref(const char *refname, struct object_id *oid);
+int peel_iterated_oid(const struct object_id *base, struct object_id *peeled);
/**
* Resolve refname in the nested "gitlink" repository in the specified
@@ -348,6 +348,15 @@ int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data,
unsigned int broken);
/**
+ * iterate all refs in "patterns" by partitioning patterns into disjoint sets
+ * and iterating the longest-common prefix of each set.
+ *
+ * callers should be prepared to ignore references that they did not ask for.
+ */
+int for_each_fullref_in_prefixes(const char *namespace, const char **patterns,
+ each_ref_fn fn, void *cb_data,
+ unsigned int broken);
+/**
* iterate refs from the respective area.
*/
int for_each_tag_ref(each_ref_fn fn, void *cb_data);
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);
}
diff --git a/rerere.c b/rerere.c
index d6928c1b5c..dee60dc6df 100644
--- a/rerere.c
+++ b/rerere.c
@@ -11,6 +11,7 @@
#include "pathspec.h"
#include "object-store.h"
#include "hash-lookup.h"
+#include "strmap.h"
#define RESOLVED 0
#define PUNTED 1
@@ -23,26 +24,27 @@ static int rerere_enabled = -1;
/* automatically update cleanly resolved paths to the index */
static int rerere_autoupdate;
-static int rerere_dir_nr;
-static int rerere_dir_alloc;
-
#define RR_HAS_POSTIMAGE 1
#define RR_HAS_PREIMAGE 2
-static struct rerere_dir {
- unsigned char hash[GIT_MAX_HEXSZ];
+struct rerere_dir {
int status_alloc, status_nr;
unsigned char *status;
-} **rerere_dir;
+ char name[FLEX_ARRAY];
+};
+
+static struct strmap rerere_dirs = STRMAP_INIT;
static void free_rerere_dirs(void)
{
- int i;
- for (i = 0; i < rerere_dir_nr; i++) {
- free(rerere_dir[i]->status);
- free(rerere_dir[i]);
+ struct hashmap_iter iter;
+ struct strmap_entry *ent;
+
+ strmap_for_each_entry(&rerere_dirs, &iter, ent) {
+ struct rerere_dir *rr_dir = ent->value;
+ free(rr_dir->status);
+ free(rr_dir);
}
- FREE_AND_NULL(rerere_dir);
- rerere_dir_nr = rerere_dir_alloc = 0;
+ strmap_clear(&rerere_dirs, 0);
}
static void free_rerere_id(struct string_list_item *item)
@@ -52,7 +54,7 @@ static void free_rerere_id(struct string_list_item *item)
static const char *rerere_id_hex(const struct rerere_id *id)
{
- return hash_to_hex(id->collection->hash);
+ return id->collection->name;
}
static void fit_variant(struct rerere_dir *rr_dir, int variant)
@@ -115,7 +117,7 @@ static int is_rr_file(const char *name, const char *filename, int *variant)
static void scan_rerere_dir(struct rerere_dir *rr_dir)
{
struct dirent *de;
- DIR *dir = opendir(git_path("rr-cache/%s", hash_to_hex(rr_dir->hash)));
+ DIR *dir = opendir(git_path("rr-cache/%s", rr_dir->name));
if (!dir)
return;
@@ -133,39 +135,21 @@ static void scan_rerere_dir(struct rerere_dir *rr_dir)
closedir(dir);
}
-static const unsigned char *rerere_dir_hash(size_t i, void *table)
-{
- struct rerere_dir **rr_dir = table;
- return rr_dir[i]->hash;
-}
-
static struct rerere_dir *find_rerere_dir(const char *hex)
{
- unsigned char hash[GIT_MAX_RAWSZ];
struct rerere_dir *rr_dir;
- int pos;
-
- if (get_sha1_hex(hex, hash))
- return NULL; /* BUG */
- pos = hash_pos(hash, rerere_dir, rerere_dir_nr, rerere_dir_hash);
- if (pos < 0) {
- rr_dir = xmalloc(sizeof(*rr_dir));
- hashcpy(rr_dir->hash, hash);
+
+ rr_dir = strmap_get(&rerere_dirs, hex);
+ if (!rr_dir) {
+ FLEX_ALLOC_STR(rr_dir, name, hex);
rr_dir->status = NULL;
rr_dir->status_nr = 0;
rr_dir->status_alloc = 0;
- pos = -1 - pos;
-
- /* Make sure the array is big enough ... */
- ALLOC_GROW(rerere_dir, rerere_dir_nr + 1, rerere_dir_alloc);
- /* ... and add it in. */
- rerere_dir_nr++;
- MOVE_ARRAY(rerere_dir + pos + 1, rerere_dir + pos,
- rerere_dir_nr - pos - 1);
- rerere_dir[pos] = rr_dir;
+ strmap_put(&rerere_dirs, hex, rr_dir);
+
scan_rerere_dir(rr_dir);
}
- return rerere_dir[pos];
+ return rr_dir;
}
static int has_rerere_resolution(const struct rerere_id *id)
@@ -1178,6 +1162,14 @@ static void prune_one(struct rerere_id *id,
unlink_rr_item(id);
}
+/* Does the basename in "path" look plausibly like an rr-cache entry? */
+static int is_rr_cache_dirname(const char *path)
+{
+ struct object_id oid;
+ const char *end;
+ return !parse_oid_hex(path, &oid, &end) && !*end;
+}
+
void rerere_gc(struct repository *r, struct string_list *rr)
{
struct string_list to_remove = STRING_LIST_INIT_DUP;
@@ -1205,10 +1197,11 @@ void rerere_gc(struct repository *r, struct string_list *rr)
if (is_dot_or_dotdot(e->d_name))
continue;
- rr_dir = find_rerere_dir(e->d_name);
- if (!rr_dir)
+ if (!is_rr_cache_dirname(e->d_name))
continue; /* or should we remove e->d_name? */
+ rr_dir = find_rerere_dir(e->d_name);
+
now_empty = 1;
for (id.variant = 0, id.collection = rr_dir;
id.variant < id.collection->status_nr;
diff --git a/revision.c b/revision.c
index 0b5c723140..3efd994160 100644
--- a/revision.c
+++ b/revision.c
@@ -5,6 +5,7 @@
#include "tree.h"
#include "commit.h"
#include "diff.h"
+#include "diff-merges.h"
#include "refs.h"
#include "revision.h"
#include "repository.h"
@@ -1808,7 +1809,6 @@ void repo_init_revisions(struct repository *r,
revs->repo = r;
revs->abbrev = DEFAULT_ABBREV;
- revs->ignore_merges = -1;
revs->simplify_history = 1;
revs->pruning.repo = r;
revs->pruning.flags.recursive = 1;
@@ -2343,34 +2343,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
revs->diff = 1;
revs->diffopt.flags.recursive = 1;
revs->diffopt.flags.tree_in_recursive = 1;
- } else if (!strcmp(arg, "-m")) {
- /*
- * To "diff-index", "-m" means "match missing", and to the "log"
- * family of commands, it means "show full diff for merges". Set
- * both fields appropriately.
- */
- revs->ignore_merges = 0;
- revs->match_missing = 1;
- } else if ((argcount = parse_long_opt("diff-merges", argv, &optarg))) {
- if (!strcmp(optarg, "off")) {
- revs->ignore_merges = 1;
- } else {
- die(_("unknown value for --diff-merges: %s"), optarg);
- }
+ } else if ((argcount = diff_merges_parse_opts(revs, argv))) {
return argcount;
- } else if (!strcmp(arg, "--no-diff-merges")) {
- revs->ignore_merges = 1;
- } else if (!strcmp(arg, "-c")) {
- revs->diff = 1;
- revs->dense_combined_merges = 0;
- revs->combine_merges = 1;
- } else if (!strcmp(arg, "--combined-all-paths")) {
- revs->diff = 1;
- revs->combined_all_paths = 1;
- } else if (!strcmp(arg, "--cc")) {
- revs->diff = 1;
- revs->dense_combined_merges = 1;
- revs->combine_merges = 1;
} else if (!strcmp(arg, "-v")) {
revs->verbose_header = 1;
} else if (!strcmp(arg, "--pretty")) {
@@ -2491,8 +2465,6 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
} else if ((argcount = parse_long_opt("grep", argv, &optarg))) {
add_message_grep(revs, optarg);
return argcount;
- } else if (!strcmp(arg, "--grep-debug")) {
- revs->grep_filter.debug = 1;
} else if (!strcmp(arg, "--basic-regexp")) {
revs->grep_filter.pattern_type_option = GREP_PATTERN_TYPE_BRE;
} else if (!strcmp(arg, "--extended-regexp") || !strcmp(arg, "-E")) {
@@ -2867,12 +2839,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
copy_pathspec(&revs->diffopt.pathspec,
&revs->prune_data);
}
- if (revs->combine_merges && revs->ignore_merges < 0)
- revs->ignore_merges = 0;
- if (revs->ignore_merges < 0)
- revs->ignore_merges = 1;
- if (revs->combined_all_paths && !revs->combine_merges)
- die("--combined-all-paths makes no sense without -c or --cc");
+
+ diff_merges_setup_revs(revs);
revs->diffopt.abbrev = revs->abbrev;
diff --git a/revision.h b/revision.h
index 086ff10280..e6be3c845e 100644
--- a/revision.h
+++ b/revision.h
@@ -191,11 +191,16 @@ struct rev_info {
match_missing:1,
no_commit_id:1,
verbose_header:1,
+ always_show_header:1,
+ /* Diff-merge flags */
+ explicit_diff_merges: 1,
+ merges_need_diff: 1,
+ separate_merges: 1,
combine_merges:1,
combined_all_paths:1,
+ combined_imply_patch:1,
dense_combined_merges:1,
- always_show_header:1;
- int ignore_merges:2;
+ first_parent_merges:1;
/* Format info */
int show_notes;
diff --git a/run-command.h b/run-command.h
index 6472b38bde..d08414a92e 100644
--- a/run-command.h
+++ b/run-command.h
@@ -126,8 +126,15 @@ struct child_process {
*/
unsigned silent_exec_failure:1;
- unsigned stdout_to_stderr:1;
+ /**
+ * Run the command from argv[0] using a shell (but note that we may
+ * still optimize out the shell call if the command contains no
+ * metacharacters). Note that further arguments to the command in
+ * argv[1], etc, do not need to be shell-quoted.
+ */
unsigned use_shell:1;
+
+ unsigned stdout_to_stderr:1;
unsigned clean_on_exit:1;
unsigned wait_after_clean:1;
void (*clean_on_exit_handler)(struct child_process *process);
diff --git a/sequencer.c b/sequencer.c
index 8909a46770..d2332d3e17 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -679,9 +679,6 @@ static int do_recursive_merge(struct repository *r,
static struct object_id *get_cache_tree_oid(struct index_state *istate)
{
- if (!istate->cache_tree)
- istate->cache_tree = cache_tree();
-
if (!cache_tree_fully_valid(istate->cache_tree))
if (cache_tree_update(istate, 0)) {
error(_("unable to update cache tree"));
@@ -943,6 +940,7 @@ N_("you have staged changes in your working tree\n"
#define CLEANUP_MSG (1<<3)
#define VERIFY_MSG (1<<4)
#define CREATE_ROOT_COMMIT (1<<5)
+#define VERBATIM_MSG (1<<6)
static int run_command_silent_on_success(struct child_process *cmd)
{
@@ -979,6 +977,9 @@ static int run_git_commit(const char *defmsg,
{
struct child_process cmd = CHILD_PROCESS_INIT;
+ if ((flags & CLEANUP_MSG) && (flags & VERBATIM_MSG))
+ BUG("CLEANUP_MSG and VERBATIM_MSG are mutually exclusive");
+
cmd.git_cmd = 1;
if (is_rebase_i(opts) && read_env_script(&cmd.env_array)) {
@@ -1012,6 +1013,8 @@ static int run_git_commit(const char *defmsg,
strvec_pushl(&cmd.args, "-C", "HEAD", NULL);
if ((flags & CLEANUP_MSG))
strvec_push(&cmd.args, "--cleanup=strip");
+ if ((flags & VERBATIM_MSG))
+ strvec_push(&cmd.args, "--cleanup=verbatim");
if ((flags & EDIT_MSG))
strvec_push(&cmd.args, "-e");
else if (!(flags & CLEANUP_MSG) &&
@@ -1380,6 +1383,9 @@ static int try_to_commit(struct repository *r,
enum commit_msg_cleanup_mode cleanup;
int res = 0;
+ if ((flags & CLEANUP_MSG) && (flags & VERBATIM_MSG))
+ BUG("CLEANUP_MSG and VERBATIM_MSG are mutually exclusive");
+
if (parse_head(r, &current_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);
diff --git a/shallow.c b/shallow.c
index 91b9e1073c..9ed18eb884 100644
--- a/shallow.c
+++ b/shallow.c
@@ -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)
diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh
index ee5d2d4cf8..29ce89090d 100644
--- a/t/annotate-tests.sh
+++ b/t/annotate-tests.sh
@@ -483,12 +483,12 @@ test_expect_success 'setup -L :funcname with userdiff driver' '
echo "fortran-* diff=fortran" >.gitattributes &&
fortran_file=fortran-external-function &&
orig_file="$TEST_DIRECTORY/t4018/$fortran_file" &&
- cp $orig_file . &&
- git add $fortran_file &&
+ cp "$orig_file" . &&
+ git add "$fortran_file" &&
GIT_AUTHOR_NAME="A" GIT_AUTHOR_EMAIL="A@test.git" \
git commit -m "add fortran file" &&
- sed -e "s/ChangeMe/IWasChanged/" <"$orig_file" >$fortran_file &&
- git add $fortran_file &&
+ sed -e "s/ChangeMe/IWasChanged/" <"$orig_file" >"$fortran_file" &&
+ git add "$fortran_file" &&
GIT_AUTHOR_NAME="B" GIT_AUTHOR_EMAIL="B@test.git" \
git commit -m "change fortran file"
'
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
index 759e69dc54..bba5f841c6 100644
--- a/t/helper/test-ref-store.c
+++ b/t/helper/test-ref-store.c
@@ -72,18 +72,6 @@ static int cmd_pack_refs(struct ref_store *refs, const char **argv)
return refs_pack_refs(refs, flags);
}
-static int cmd_peel_ref(struct ref_store *refs, const char **argv)
-{
- const char *refname = notnull(*argv++, "refname");
- struct object_id oid;
- int ret;
-
- ret = refs_peel_ref(refs, refname, &oid);
- if (!ret)
- puts(oid_to_hex(&oid));
- return ret;
-}
-
static int cmd_create_symref(struct ref_store *refs, const char **argv)
{
const char *refname = notnull(*argv++, "refname");
@@ -255,7 +243,6 @@ struct command {
static struct command commands[] = {
{ "pack-refs", cmd_pack_refs },
- { "peel-ref", cmd_peel_ref },
{ "create-symref", cmd_create_symref },
{ "delete-refs", cmd_delete_refs },
{ "rename-ref", cmd_rename_ref },
diff --git a/t/perf/p5303-many-packs.sh b/t/perf/p5303-many-packs.sh
index f4c2ab0584..ce0c42cc9f 100755
--- a/t/perf/p5303-many-packs.sh
+++ b/t/perf/p5303-many-packs.sh
@@ -21,10 +21,14 @@ repack_into_n () {
mkdir staging &&
git rev-list --first-parent HEAD |
- sed -n '1~5p' |
- head -n "$1" |
- perl -e 'print reverse <>' \
- >pushes
+ perl -e '
+ my $n = shift;
+ while (<>) {
+ last unless @commits < $n;
+ push @commits, $_ if $. % 5 == 1;
+ }
+ print reverse @commits;
+ ' "$1" >pushes
# create base packfile
head -n 1 pushes |
diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
index f4ba2e8c85..a6e570d674 100755
--- a/t/t0000-basic.sh
+++ b/t/t0000-basic.sh
@@ -135,32 +135,32 @@ check_sub_test_lib_test_err () {
)
}
-test_expect_success 'pretend we have a fully passing test suite' "
- run_sub_test_lib_test full-pass '3 passing tests' <<-\\EOF &&
+test_expect_success 'pretend we have a fully passing test suite' '
+ run_sub_test_lib_test full-pass "3 passing tests" <<-\EOF &&
for i in 1 2 3
do
- test_expect_success \"passing test #\$i\" 'true'
+ test_expect_success "passing test #$i" "true"
done
test_done
EOF
- check_sub_test_lib_test full-pass <<-\\EOF
+ check_sub_test_lib_test full-pass <<-\EOF
> ok 1 - passing test #1
> ok 2 - passing test #2
> ok 3 - passing test #3
> # passed all 3 test(s)
> 1..3
EOF
-"
+'
-test_expect_success 'pretend we have a partially passing test suite' "
+test_expect_success 'pretend we have a partially passing test suite' '
run_sub_test_lib_test_err \
- partial-pass '2/3 tests passing' <<-\\EOF &&
- test_expect_success 'passing test #1' 'true'
- test_expect_success 'failing test #2' 'false'
- test_expect_success 'passing test #3' 'true'
+ partial-pass "2/3 tests passing" <<-\EOF &&
+ test_expect_success "passing test #1" "true"
+ test_expect_success "failing test #2" "false"
+ test_expect_success "passing test #3" "true"
test_done
EOF
- check_sub_test_lib_test partial-pass <<-\\EOF
+ check_sub_test_lib_test partial-pass <<-\EOF
> ok 1 - passing test #1
> not ok 2 - failing test #2
# false
@@ -168,44 +168,44 @@ test_expect_success 'pretend we have a partially passing test suite' "
> # failed 1 among 3 test(s)
> 1..3
EOF
-"
+'
-test_expect_success 'pretend we have a known breakage' "
- run_sub_test_lib_test failing-todo 'A failing TODO test' <<-\\EOF &&
- test_expect_success 'passing test' 'true'
- test_expect_failure 'pretend we have a known breakage' 'false'
+test_expect_success 'pretend we have a known breakage' '
+ run_sub_test_lib_test failing-todo "A failing TODO test" <<-\EOF &&
+ test_expect_success "passing test" "true"
+ test_expect_failure "pretend we have a known breakage" "false"
test_done
EOF
- check_sub_test_lib_test failing-todo <<-\\EOF
+ check_sub_test_lib_test failing-todo <<-\EOF
> ok 1 - passing test
> not ok 2 - pretend we have a known breakage # TODO known breakage
> # still have 1 known breakage(s)
> # passed all remaining 1 test(s)
> 1..2
EOF
-"
+'
-test_expect_success 'pretend we have fixed a known breakage' "
- run_sub_test_lib_test passing-todo 'A passing TODO test' <<-\\EOF &&
- test_expect_failure 'pretend we have fixed a known breakage' 'true'
+test_expect_success 'pretend we have fixed a known breakage' '
+ run_sub_test_lib_test passing-todo "A passing TODO test" <<-\EOF &&
+ test_expect_failure "pretend we have fixed a known breakage" "true"
test_done
EOF
- check_sub_test_lib_test passing-todo <<-\\EOF
+ check_sub_test_lib_test passing-todo <<-\EOF
> ok 1 - pretend we have fixed a known breakage # TODO known breakage vanished
> # 1 known breakage(s) vanished; please update test(s)
> 1..1
EOF
-"
+'
-test_expect_success 'pretend we have fixed one of two known breakages (run in sub test-lib)' "
+test_expect_success 'pretend we have fixed one of two known breakages (run in sub test-lib)' '
run_sub_test_lib_test partially-passing-todos \
- '2 TODO tests, one passing' <<-\\EOF &&
- test_expect_failure 'pretend we have a known breakage' 'false'
- test_expect_success 'pretend we have a passing test' 'true'
- test_expect_failure 'pretend we have fixed another known breakage' 'true'
+ "2 TODO tests, one passing" <<-\EOF &&
+ test_expect_failure "pretend we have a known breakage" "false"
+ test_expect_success "pretend we have a passing test" "true"
+ test_expect_failure "pretend we have fixed another known breakage" "true"
test_done
EOF
- check_sub_test_lib_test partially-passing-todos <<-\\EOF
+ check_sub_test_lib_test partially-passing-todos <<-\EOF
> not ok 1 - pretend we have a known breakage # TODO known breakage
> ok 2 - pretend we have a passing test
> ok 3 - pretend we have fixed another known breakage # TODO known breakage vanished
@@ -214,17 +214,17 @@ test_expect_success 'pretend we have fixed one of two known breakages (run in su
> # passed all remaining 1 test(s)
> 1..3
EOF
-"
+'
-test_expect_success 'pretend we have a pass, fail, and known breakage' "
+test_expect_success 'pretend we have a pass, fail, and known breakage' '
run_sub_test_lib_test_err \
- mixed-results1 'mixed results #1' <<-\\EOF &&
- test_expect_success 'passing test' 'true'
- test_expect_success 'failing test' 'false'
- test_expect_failure 'pretend we have a known breakage' 'false'
+ mixed-results1 "mixed results #1" <<-\EOF &&
+ test_expect_success "passing test" "true"
+ test_expect_success "failing test" "false"
+ test_expect_failure "pretend we have a known breakage" "false"
test_done
EOF
- check_sub_test_lib_test mixed-results1 <<-\\EOF
+ check_sub_test_lib_test mixed-results1 <<-\EOF
> ok 1 - passing test
> not ok 2 - failing test
> # false
@@ -233,24 +233,24 @@ test_expect_success 'pretend we have a pass, fail, and known breakage' "
> # failed 1 among remaining 2 test(s)
> 1..3
EOF
-"
+'
-test_expect_success 'pretend we have a mix of all possible results' "
+test_expect_success 'pretend we have a mix of all possible results' '
run_sub_test_lib_test_err \
- mixed-results2 'mixed results #2' <<-\\EOF &&
- test_expect_success 'passing test' 'true'
- test_expect_success 'passing test' 'true'
- test_expect_success 'passing test' 'true'
- test_expect_success 'passing test' 'true'
- test_expect_success 'failing test' 'false'
- test_expect_success 'failing test' 'false'
- test_expect_success 'failing test' 'false'
- test_expect_failure 'pretend we have a known breakage' 'false'
- test_expect_failure 'pretend we have a known breakage' 'false'
- test_expect_failure 'pretend we have fixed a known breakage' 'true'
+ mixed-results2 "mixed results #2" <<-\EOF &&
+ test_expect_success "passing test" "true"
+ test_expect_success "passing test" "true"
+ test_expect_success "passing test" "true"
+ test_expect_success "passing test" "true"
+ test_expect_success "failing test" "false"
+ test_expect_success "failing test" "false"
+ test_expect_success "failing test" "false"
+ test_expect_failure "pretend we have a known breakage" "false"
+ test_expect_failure "pretend we have a known breakage" "false"
+ test_expect_failure "pretend we have fixed a known breakage" "true"
test_done
EOF
- check_sub_test_lib_test mixed-results2 <<-\\EOF
+ check_sub_test_lib_test mixed-results2 <<-\EOF
> ok 1 - passing test
> ok 2 - passing test
> ok 3 - passing test
@@ -269,7 +269,7 @@ test_expect_success 'pretend we have a mix of all possible results' "
> # failed 3 among remaining 7 test(s)
> 1..10
EOF
-"
+'
test_expect_success C_LOCALE_OUTPUT 'test --verbose' '
run_sub_test_lib_test_err \
@@ -321,18 +321,18 @@ test_expect_success 'test --verbose-only' '
EOF
'
-test_expect_success 'GIT_SKIP_TESTS' "
+test_expect_success 'GIT_SKIP_TESTS' '
(
- GIT_SKIP_TESTS='git.2' && export GIT_SKIP_TESTS &&
+ GIT_SKIP_TESTS="git.2" && export GIT_SKIP_TESTS &&
run_sub_test_lib_test git-skip-tests-basic \
- 'GIT_SKIP_TESTS' <<-\\EOF &&
+ "GIT_SKIP_TESTS" <<-\EOF &&
for i in 1 2 3
do
- test_expect_success \"passing test #\$i\" 'true'
+ test_expect_success "passing test #$i" "true"
done
test_done
EOF
- check_sub_test_lib_test git-skip-tests-basic <<-\\EOF
+ check_sub_test_lib_test git-skip-tests-basic <<-\EOF
> ok 1 - passing test #1
> ok 2 # skip passing test #2 (GIT_SKIP_TESTS)
> ok 3 - passing test #3
@@ -340,20 +340,20 @@ test_expect_success 'GIT_SKIP_TESTS' "
> 1..3
EOF
)
-"
+'
-test_expect_success 'GIT_SKIP_TESTS several tests' "
+test_expect_success 'GIT_SKIP_TESTS several tests' '
(
- GIT_SKIP_TESTS='git.2 git.5' && export GIT_SKIP_TESTS &&
+ GIT_SKIP_TESTS="git.2 git.5" && export GIT_SKIP_TESTS &&
run_sub_test_lib_test git-skip-tests-several \
- 'GIT_SKIP_TESTS several tests' <<-\\EOF &&
+ "GIT_SKIP_TESTS several tests" <<-\EOF &&
for i in 1 2 3 4 5 6
do
- test_expect_success \"passing test #\$i\" 'true'
+ test_expect_success "passing test #$i" "true"
done
test_done
EOF
- check_sub_test_lib_test git-skip-tests-several <<-\\EOF
+ check_sub_test_lib_test git-skip-tests-several <<-\EOF
> ok 1 - passing test #1
> ok 2 # skip passing test #2 (GIT_SKIP_TESTS)
> ok 3 - passing test #3
@@ -364,20 +364,20 @@ test_expect_success 'GIT_SKIP_TESTS several tests' "
> 1..6
EOF
)
-"
+'
-test_expect_success 'GIT_SKIP_TESTS sh pattern' "
+test_expect_success 'GIT_SKIP_TESTS sh pattern' '
(
- GIT_SKIP_TESTS='git.[2-5]' && export GIT_SKIP_TESTS &&
+ GIT_SKIP_TESTS="git.[2-5]" && export GIT_SKIP_TESTS &&
run_sub_test_lib_test git-skip-tests-sh-pattern \
- 'GIT_SKIP_TESTS sh pattern' <<-\\EOF &&
+ "GIT_SKIP_TESTS sh pattern" <<-\EOF &&
for i in 1 2 3 4 5 6
do
- test_expect_success \"passing test #\$i\" 'true'
+ test_expect_success "passing test #$i" "true"
done
test_done
EOF
- check_sub_test_lib_test git-skip-tests-sh-pattern <<-\\EOF
+ check_sub_test_lib_test git-skip-tests-sh-pattern <<-\EOF
> ok 1 - passing test #1
> ok 2 # skip passing test #2 (GIT_SKIP_TESTS)
> ok 3 # skip passing test #3 (GIT_SKIP_TESTS)
@@ -388,37 +388,37 @@ test_expect_success 'GIT_SKIP_TESTS sh pattern' "
> 1..6
EOF
)
-"
+'
-test_expect_success 'GIT_SKIP_TESTS entire suite' "
+test_expect_success 'GIT_SKIP_TESTS entire suite' '
(
- GIT_SKIP_TESTS='git' && export GIT_SKIP_TESTS &&
+ GIT_SKIP_TESTS="git" && export GIT_SKIP_TESTS &&
run_sub_test_lib_test git-skip-tests-entire-suite \
- 'GIT_SKIP_TESTS entire suite' <<-\\EOF &&
+ "GIT_SKIP_TESTS entire suite" <<-\EOF &&
for i in 1 2 3
do
- test_expect_success \"passing test #\$i\" 'true'
+ test_expect_success "passing test #$i" "true"
done
test_done
EOF
- check_sub_test_lib_test git-skip-tests-entire-suite <<-\\EOF
+ check_sub_test_lib_test git-skip-tests-entire-suite <<-\EOF
> 1..0 # SKIP skip all tests in git
EOF
)
-"
+'
-test_expect_success 'GIT_SKIP_TESTS does not skip unmatched suite' "
+test_expect_success 'GIT_SKIP_TESTS does not skip unmatched suite' '
(
- GIT_SKIP_TESTS='notgit' && export GIT_SKIP_TESTS &&
+ GIT_SKIP_TESTS="notgit" && export GIT_SKIP_TESTS &&
run_sub_test_lib_test git-skip-tests-unmatched-suite \
- 'GIT_SKIP_TESTS does not skip unmatched suite' <<-\\EOF &&
+ "GIT_SKIP_TESTS does not skip unmatched suite" <<-\EOF &&
for i in 1 2 3
do
- test_expect_success \"passing test #\$i\" 'true'
+ test_expect_success "passing test #$i" "true"
done
test_done
EOF
- check_sub_test_lib_test git-skip-tests-unmatched-suite <<-\\EOF
+ check_sub_test_lib_test git-skip-tests-unmatched-suite <<-\EOF
> ok 1 - passing test #1
> ok 2 - passing test #2
> ok 3 - passing test #3
@@ -426,18 +426,18 @@ test_expect_success 'GIT_SKIP_TESTS does not skip unmatched suite' "
> 1..3
EOF
)
-"
+'
-test_expect_success '--run basic' "
+test_expect_success '--run basic' '
run_sub_test_lib_test run-basic \
- '--run basic' --run='1,3,5' <<-\\EOF &&
+ "--run basic" --run="1,3,5" <<-\EOF &&
for i in 1 2 3 4 5 6
do
- test_expect_success \"passing test #\$i\" 'true'
+ test_expect_success "passing test #$i" "true"
done
test_done
EOF
- check_sub_test_lib_test run-basic <<-\\EOF
+ check_sub_test_lib_test run-basic <<-\EOF
> ok 1 - passing test #1
> ok 2 # skip passing test #2 (--run)
> ok 3 - passing test #3
@@ -447,18 +447,18 @@ test_expect_success '--run basic' "
> # passed all 6 test(s)
> 1..6
EOF
-"
+'
-test_expect_success '--run with a range' "
+test_expect_success '--run with a range' '
run_sub_test_lib_test run-range \
- '--run with a range' --run='1-3' <<-\\EOF &&
+ "--run with a range" --run="1-3" <<-\EOF &&
for i in 1 2 3 4 5 6
do
- test_expect_success \"passing test #\$i\" 'true'
+ test_expect_success "passing test #$i" "true"
done
test_done
EOF
- check_sub_test_lib_test run-range <<-\\EOF
+ check_sub_test_lib_test run-range <<-\EOF
> ok 1 - passing test #1
> ok 2 - passing test #2
> ok 3 - passing test #3
@@ -468,18 +468,18 @@ test_expect_success '--run with a range' "
> # passed all 6 test(s)
> 1..6
EOF
-"
+'
-test_expect_success '--run with two ranges' "
+test_expect_success '--run with two ranges' '
run_sub_test_lib_test run-two-ranges \
- '--run with two ranges' --run='1-2,5-6' <<-\\EOF &&
+ "--run with two ranges" --run="1-2,5-6" <<-\EOF &&
for i in 1 2 3 4 5 6
do
- test_expect_success \"passing test #\$i\" 'true'
+ test_expect_success "passing test #$i" "true"
done
test_done
EOF
- check_sub_test_lib_test run-two-ranges <<-\\EOF
+ check_sub_test_lib_test run-two-ranges <<-\EOF
> ok 1 - passing test #1
> ok 2 - passing test #2
> ok 3 # skip passing test #3 (--run)
@@ -489,18 +489,18 @@ test_expect_success '--run with two ranges' "
> # passed all 6 test(s)
> 1..6
EOF
-"
+'
-test_expect_success '--run with a left open range' "
+test_expect_success '--run with a left open range' '
run_sub_test_lib_test run-left-open-range \
- '--run with a left open range' --run='-3' <<-\\EOF &&
+ "--run with a left open range" --run="-3" <<-\EOF &&
for i in 1 2 3 4 5 6
do
- test_expect_success \"passing test #\$i\" 'true'
+ test_expect_success "passing test #$i" "true"
done
test_done
EOF
- check_sub_test_lib_test run-left-open-range <<-\\EOF
+ check_sub_test_lib_test run-left-open-range <<-\EOF
> ok 1 - passing test #1
> ok 2 - passing test #2
> ok 3 - passing test #3
@@ -510,18 +510,18 @@ test_expect_success '--run with a left open range' "
> # passed all 6 test(s)
> 1..6
EOF
-"
+'
-test_expect_success '--run with a right open range' "
+test_expect_success '--run with a right open range' '
run_sub_test_lib_test run-right-open-range \
- '--run with a right open range' --run='4-' <<-\\EOF &&
+ "--run with a right open range" --run="4-" <<-\EOF &&
for i in 1 2 3 4 5 6
do
- test_expect_success \"passing test #\$i\" 'true'
+ test_expect_success "passing test #$i" "true"
done
test_done
EOF
- check_sub_test_lib_test run-right-open-range <<-\\EOF
+ check_sub_test_lib_test run-right-open-range <<-\EOF
> ok 1 # skip passing test #1 (--run)
> ok 2 # skip passing test #2 (--run)
> ok 3 # skip passing test #3 (--run)
@@ -531,18 +531,18 @@ test_expect_success '--run with a right open range' "
> # passed all 6 test(s)
> 1..6
EOF
-"
+'
-test_expect_success '--run with basic negation' "
+test_expect_success '--run with basic negation' '
run_sub_test_lib_test run-basic-neg \
- '--run with basic negation' --run='"'!3'"' <<-\\EOF &&
+ "--run with basic negation" --run="!3" <<-\EOF &&
for i in 1 2 3 4 5 6
do
- test_expect_success \"passing test #\$i\" 'true'
+ test_expect_success "passing test #$i" "true"
done
test_done
EOF
- check_sub_test_lib_test run-basic-neg <<-\\EOF
+ check_sub_test_lib_test run-basic-neg <<-\EOF
> ok 1 - passing test #1
> ok 2 - passing test #2
> ok 3 # skip passing test #3 (--run)
@@ -552,18 +552,18 @@ test_expect_success '--run with basic negation' "
> # passed all 6 test(s)
> 1..6
EOF
-"
+'
-test_expect_success '--run with two negations' "
+test_expect_success '--run with two negations' '
run_sub_test_lib_test run-two-neg \
- '--run with two negations' --run='"'!3,!6'"' <<-\\EOF &&
+ "--run with two negations" --run="!3,!6" <<-\EOF &&
for i in 1 2 3 4 5 6
do
- test_expect_success \"passing test #\$i\" 'true'
+ test_expect_success "passing test #$i" "true"
done
test_done
EOF
- check_sub_test_lib_test run-two-neg <<-\\EOF
+ check_sub_test_lib_test run-two-neg <<-\EOF
> ok 1 - passing test #1
> ok 2 - passing test #2
> ok 3 # skip passing test #3 (--run)
@@ -573,18 +573,18 @@ test_expect_success '--run with two negations' "
> # passed all 6 test(s)
> 1..6
EOF
-"
+'
-test_expect_success '--run a range and negation' "
+test_expect_success '--run a range and negation' '
run_sub_test_lib_test run-range-and-neg \
- '--run a range and negation' --run='"'-4,!2'"' <<-\\EOF &&
+ "--run a range and negation" --run="-4,!2" <<-\EOF &&
for i in 1 2 3 4 5 6
do
- test_expect_success \"passing test #\$i\" 'true'
+ test_expect_success "passing test #$i" "true"
done
test_done
EOF
- check_sub_test_lib_test run-range-and-neg <<-\\EOF
+ check_sub_test_lib_test run-range-and-neg <<-\EOF
> ok 1 - passing test #1
> ok 2 # skip passing test #2 (--run)
> ok 3 - passing test #3
@@ -594,18 +594,18 @@ test_expect_success '--run a range and negation' "
> # passed all 6 test(s)
> 1..6
EOF
-"
+'
-test_expect_success '--run range negation' "
+test_expect_success '--run range negation' '
run_sub_test_lib_test run-range-neg \
- '--run range negation' --run='"'!1-3'"' <<-\\EOF &&
+ "--run range negation" --run="!1-3" <<-\EOF &&
for i in 1 2 3 4 5 6
do
- test_expect_success \"passing test #\$i\" 'true'
+ test_expect_success "passing test #$i" "true"
done
test_done
EOF
- check_sub_test_lib_test run-range-neg <<-\\EOF
+ check_sub_test_lib_test run-range-neg <<-\EOF
> ok 1 # skip passing test #1 (--run)
> ok 2 # skip passing test #2 (--run)
> ok 3 # skip passing test #3 (--run)
@@ -615,19 +615,19 @@ test_expect_success '--run range negation' "
> # passed all 6 test(s)
> 1..6
EOF
-"
+'
-test_expect_success '--run include, exclude and include' "
+test_expect_success '--run include, exclude and include' '
run_sub_test_lib_test run-inc-neg-inc \
- '--run include, exclude and include' \
- --run='"'1-5,!1-3,2'"' <<-\\EOF &&
+ "--run include, exclude and include" \
+ --run="1-5,!1-3,2" <<-\EOF &&
for i in 1 2 3 4 5 6
do
- test_expect_success \"passing test #\$i\" 'true'
+ test_expect_success "passing test #$i" "true"
done
test_done
EOF
- check_sub_test_lib_test run-inc-neg-inc <<-\\EOF
+ check_sub_test_lib_test run-inc-neg-inc <<-\EOF
> ok 1 # skip passing test #1 (--run)
> ok 2 - passing test #2
> ok 3 # skip passing test #3 (--run)
@@ -637,19 +637,19 @@ test_expect_success '--run include, exclude and include' "
> # passed all 6 test(s)
> 1..6
EOF
-"
+'
-test_expect_success '--run include, exclude and include, comma separated' "
+test_expect_success '--run include, exclude and include, comma separated' '
run_sub_test_lib_test run-inc-neg-inc-comma \
- '--run include, exclude and include, comma separated' \
- --run=1-5,\!1-3,2 <<-\\EOF &&
+ "--run include, exclude and include, comma separated" \
+ --run=1-5,!1-3,2 <<-\EOF &&
for i in 1 2 3 4 5 6
do
- test_expect_success \"passing test #\$i\" 'true'
+ test_expect_success "passing test #$i" "true"
done
test_done
EOF
- check_sub_test_lib_test run-inc-neg-inc-comma <<-\\EOF
+ check_sub_test_lib_test run-inc-neg-inc-comma <<-\EOF
> ok 1 # skip passing test #1 (--run)
> ok 2 - passing test #2
> ok 3 # skip passing test #3 (--run)
@@ -659,19 +659,19 @@ test_expect_success '--run include, exclude and include, comma separated' "
> # passed all 6 test(s)
> 1..6
EOF
-"
+'
-test_expect_success '--run exclude and include' "
+test_expect_success '--run exclude and include' '
run_sub_test_lib_test run-neg-inc \
- '--run exclude and include' \
- --run='"'!3-,5'"' <<-\\EOF &&
+ "--run exclude and include" \
+ --run="!3-,5" <<-\EOF &&
for i in 1 2 3 4 5 6
do
- test_expect_success \"passing test #\$i\" 'true'
+ test_expect_success "passing test #$i" "true"
done
test_done
EOF
- check_sub_test_lib_test run-neg-inc <<-\\EOF
+ check_sub_test_lib_test run-neg-inc <<-\EOF
> ok 1 - passing test #1
> ok 2 - passing test #2
> ok 3 # skip passing test #3 (--run)
@@ -681,19 +681,19 @@ test_expect_success '--run exclude and include' "
> # passed all 6 test(s)
> 1..6
EOF
-"
+'
-test_expect_success '--run empty selectors' "
+test_expect_success '--run empty selectors' '
run_sub_test_lib_test run-empty-sel \
- '--run empty selectors' \
- --run='1,,3,,,5' <<-\\EOF &&
+ "--run empty selectors" \
+ --run="1,,3,,,5" <<-\EOF &&
for i in 1 2 3 4 5 6
do
- test_expect_success \"passing test #\$i\" 'true'
+ test_expect_success "passing test #$i" "true"
done
test_done
EOF
- check_sub_test_lib_test run-empty-sel <<-\\EOF
+ check_sub_test_lib_test run-empty-sel <<-\EOF
> ok 1 - passing test #1
> ok 2 # skip passing test #2 (--run)
> ok 3 - passing test #3
@@ -703,20 +703,20 @@ test_expect_success '--run empty selectors' "
> # passed all 6 test(s)
> 1..6
EOF
-"
+'
-test_expect_success '--run substring selector' "
+test_expect_success '--run substring selector' '
run_sub_test_lib_test run-substring-selector \
- '--run empty selectors' \
- --run='relevant' <<-\\EOF &&
- test_expect_success \"relevant test\" 'true'
+ "--run empty selectors" \
+ --run="relevant" <<-\EOF &&
+ test_expect_success "relevant test" "true"
for i in 1 2 3 4 5 6
do
- test_expect_success \"other test #\$i\" 'true'
+ test_expect_success "other test #$i" "true"
done
test_done
EOF
- check_sub_test_lib_test run-substring-selector <<-\\EOF
+ check_sub_test_lib_test run-substring-selector <<-\EOF
> ok 1 - relevant test
> ok 2 # skip other test #1 (--run)
> ok 3 # skip other test #2 (--run)
@@ -727,167 +727,165 @@ test_expect_success '--run substring selector' "
> # passed all 7 test(s)
> 1..7
EOF
-"
+'
-test_expect_success '--run keyword selection' "
+test_expect_success '--run keyword selection' '
run_sub_test_lib_test_err run-inv-range-start \
- '--run invalid range start' \
- --run='a-5' <<-\\EOF &&
- test_expect_success \"passing test #1\" 'true'
+ "--run invalid range start" \
+ --run="a-5" <<-\EOF &&
+ test_expect_success "passing test #1" "true"
test_done
EOF
check_sub_test_lib_test_err run-inv-range-start \
- <<-\\EOF_OUT 3<<-\\EOF_ERR
+ <<-\EOF_OUT 3<<-EOF_ERR
> FATAL: Unexpected exit with code 1
EOF_OUT
- > error: --run: invalid non-numeric in range start: 'a-5'
+ > error: --run: invalid non-numeric in range start: ${SQ}a-5${SQ}
EOF_ERR
-"
+'
-test_expect_success '--run invalid range end' "
+test_expect_success '--run invalid range end' '
run_sub_test_lib_test_err run-inv-range-end \
- '--run invalid range end' \
- --run='1-z' <<-\\EOF &&
- test_expect_success \"passing test #1\" 'true'
+ "--run invalid range end" \
+ --run="1-z" <<-\EOF &&
+ test_expect_success "passing test #1" "true"
test_done
EOF
check_sub_test_lib_test_err run-inv-range-end \
- <<-\\EOF_OUT 3<<-\\EOF_ERR
+ <<-\EOF_OUT 3<<-EOF_ERR
> FATAL: Unexpected exit with code 1
EOF_OUT
- > error: --run: invalid non-numeric in range end: '1-z'
+ > error: --run: invalid non-numeric in range end: ${SQ}1-z${SQ}
EOF_ERR
-"
-
-
-test_set_prereq HAVEIT
-haveit=no
-test_expect_success HAVEIT 'test runs if prerequisite is satisfied' '
- test_have_prereq HAVEIT &&
- haveit=yes
-'
-donthaveit=yes
-test_expect_success DONTHAVEIT 'unmet prerequisite causes test to be skipped' '
- donthaveit=no
-'
-if test -z "$GIT_TEST_FAIL_PREREQS_INTERNAL" -a $haveit$donthaveit != yesyes
-then
- say "bug in test framework: prerequisite tags do not work reliably"
- exit 1
-fi
-
-test_set_prereq HAVETHIS
-haveit=no
-test_expect_success HAVETHIS,HAVEIT 'test runs if prerequisites are satisfied' '
- test_have_prereq HAVEIT &&
- test_have_prereq HAVETHIS &&
- haveit=yes
-'
-donthaveit=yes
-test_expect_success HAVEIT,DONTHAVEIT 'unmet prerequisites causes test to be skipped' '
- donthaveit=no
-'
-donthaveiteither=yes
-test_expect_success DONTHAVEIT,HAVEIT 'unmet prerequisites causes test to be skipped' '
- donthaveiteither=no
-'
-if test -z "$GIT_TEST_FAIL_PREREQS_INTERNAL" -a $haveit$donthaveit$donthaveiteither != yesyesyes
-then
- say "bug in test framework: multiple prerequisite tags do not work reliably"
- exit 1
-fi
-
-test_lazy_prereq LAZY_TRUE true
-havetrue=no
-test_expect_success LAZY_TRUE 'test runs if lazy prereq is satisfied' '
- havetrue=yes
-'
-donthavetrue=yes
-test_expect_success !LAZY_TRUE 'missing lazy prereqs skip tests' '
- donthavetrue=no
-'
-
-if test -z "$GIT_TEST_FAIL_PREREQS_INTERNAL" -a "$havetrue$donthavetrue" != yesyes
-then
- say 'bug in test framework: lazy prerequisites do not work'
- exit 1
-fi
-
-test_lazy_prereq LAZY_FALSE false
-nothavefalse=no
-test_expect_success !LAZY_FALSE 'negative lazy prereqs checked' '
- nothavefalse=yes
-'
-havefalse=yes
-test_expect_success LAZY_FALSE 'missing negative lazy prereqs will skip' '
- havefalse=no
-'
-
-if test -z "$GIT_TEST_FAIL_PREREQS_INTERNAL" -a "$nothavefalse$havefalse" != yesyes
-then
- say 'bug in test framework: negative lazy prerequisites do not work'
- exit 1
-fi
-
-clean=no
-test_expect_success 'tests clean up after themselves' '
- test_when_finished clean=yes
'
-if test -z "$GIT_TEST_FAIL_PREREQS_INTERNAL" -a $clean != yes
-then
- say "bug in test framework: basic cleanup command does not work reliably"
- exit 1
-fi
+test_expect_success 'tests respect prerequisites' '
+ run_sub_test_lib_test prereqs "tests respect prereqs" <<-\EOF &&
-test_lazy_prereq NESTED_INNER '
- >inner &&
- rm -f outer
-'
-test_lazy_prereq NESTED_PREREQ '
- >outer &&
- test_have_prereq NESTED_INNER &&
- echo "can create new file in cwd" >file &&
- test -f outer &&
- test ! -f inner
+ test_set_prereq HAVEIT
+ test_expect_success HAVEIT "prereq is satisfied" "true"
+ test_expect_success "have_prereq works" "
+ test_have_prereq HAVEIT
+ "
+ test_expect_success DONTHAVEIT "prereq not satisfied" "false"
+
+ test_set_prereq HAVETHIS
+ test_expect_success HAVETHIS,HAVEIT "multiple prereqs" "true"
+ test_expect_success HAVEIT,DONTHAVEIT "mixed prereqs (yes,no)" "false"
+ test_expect_success DONTHAVEIT,HAVEIT "mixed prereqs (no,yes)" "false"
+
+ test_done
+ EOF
+
+ check_sub_test_lib_test prereqs <<-\EOF
+ ok 1 - prereq is satisfied
+ ok 2 - have_prereq works
+ ok 3 # skip prereq not satisfied (missing DONTHAVEIT)
+ ok 4 - multiple prereqs
+ ok 5 # skip mixed prereqs (yes,no) (missing DONTHAVEIT of HAVEIT,DONTHAVEIT)
+ ok 6 # skip mixed prereqs (no,yes) (missing DONTHAVEIT of DONTHAVEIT,HAVEIT)
+ # passed all 6 test(s)
+ 1..6
+ EOF
'
-test_expect_success NESTED_PREREQ 'evaluating nested lazy prereqs dont interfere with each other' '
- nestedworks=yes
+
+test_expect_success 'tests respect lazy prerequisites' '
+ run_sub_test_lib_test lazy-prereqs "respect lazy prereqs" <<-\EOF &&
+
+ test_lazy_prereq LAZY_TRUE true
+ test_expect_success LAZY_TRUE "lazy prereq is satisifed" "true"
+ test_expect_success !LAZY_TRUE "negative lazy prereq" "false"
+
+ test_lazy_prereq LAZY_FALSE false
+ test_expect_success LAZY_FALSE "lazy prereq not satisfied" "false"
+ test_expect_success !LAZY_FALSE "negative false prereq" "true"
+
+ test_done
+ EOF
+
+ check_sub_test_lib_test lazy-prereqs <<-\EOF
+ ok 1 - lazy prereq is satisifed
+ ok 2 # skip negative lazy prereq (missing !LAZY_TRUE)
+ ok 3 # skip lazy prereq not satisfied (missing LAZY_FALSE)
+ ok 4 - negative false prereq
+ # passed all 4 test(s)
+ 1..4
+ EOF
'
-if test -z "$GIT_TEST_FAIL_PREREQS_INTERNAL" && test "$nestedworks" != yes
-then
- say 'bug in test framework: nested lazy prerequisites do not work'
- exit 1
-fi
+test_expect_success 'nested lazy prerequisites' '
+ run_sub_test_lib_test nested-lazy "nested lazy prereqs" <<-\EOF &&
+
+ test_lazy_prereq NESTED_INNER "
+ >inner &&
+ rm -f outer
+ "
+ test_lazy_prereq NESTED_PREREQ "
+ >outer &&
+ test_have_prereq NESTED_INNER &&
+ echo can create new file in cwd >file &&
+ test_path_is_file outer &&
+ test_path_is_missing inner
+ "
+ test_expect_success NESTED_PREREQ "evaluate nested prereq" "true"
-test_expect_success 'lazy prereqs do not turn off tracing' "
+ test_done
+ EOF
+
+ check_sub_test_lib_test nested-lazy <<-\EOF
+ ok 1 - evaluate nested prereq
+ # passed all 1 test(s)
+ 1..1
+ EOF
+'
+
+test_expect_success 'lazy prereqs do not turn off tracing' '
run_sub_test_lib_test lazy-prereq-and-tracing \
- 'lazy prereqs and -x' -v -x <<-\\EOF &&
+ "lazy prereqs and -x" -v -x <<-\EOF &&
test_lazy_prereq LAZY true
- test_expect_success lazy 'test_have_prereq LAZY && echo trace'
+ test_expect_success lazy "test_have_prereq LAZY && echo trace"
+
+ test_done
+ EOF
+
+ grep "echo trace" lazy-prereq-and-tracing/err
+'
+test_expect_success 'tests clean up after themselves' '
+ run_sub_test_lib_test cleanup "test with cleanup" <<-\EOF &&
+ clean=no
+ test_expect_success "do cleanup" "
+ test_when_finished clean=yes
+ "
+ test_expect_success "cleanup happened" "
+ test $clean = yes
+ "
test_done
EOF
- grep 'echo trace' lazy-prereq-and-tracing/err
-"
+ check_sub_test_lib_test cleanup <<-\EOF
+ ok 1 - do cleanup
+ ok 2 - cleanup happened
+ # passed all 2 test(s)
+ 1..2
+ EOF
+'
-test_expect_success 'tests clean up even on failures' "
+test_expect_success 'tests clean up even on failures' '
run_sub_test_lib_test_err \
- failing-cleanup 'Failing tests with cleanup commands' <<-\\EOF &&
- test_expect_success 'tests clean up even after a failure' '
+ failing-cleanup "Failing tests with cleanup commands" <<-\EOF &&
+ test_expect_success "tests clean up even after a failure" "
touch clean-after-failure &&
test_when_finished rm clean-after-failure &&
(exit 1)
- '
- test_expect_success 'failure to clean up causes the test to fail' '
+ "
+ test_expect_success "failure to clean up causes the test to fail" "
test_when_finished \"(exit 2)\"
- '
+ "
test_done
EOF
- check_sub_test_lib_test failing-cleanup <<-\\EOF
+ check_sub_test_lib_test failing-cleanup <<-\EOF
> not ok 1 - tests clean up even after a failure
> # Z
> # touch clean-after-failure &&
@@ -896,30 +894,30 @@ test_expect_success 'tests clean up even on failures' "
> # Z
> not ok 2 - failure to clean up causes the test to fail
> # Z
- > # test_when_finished \"(exit 2)\"
+ > # test_when_finished "(exit 2)"
> # Z
> # failed 2 among 2 test(s)
> 1..2
EOF
-"
+'
-test_expect_success 'test_atexit is run' "
+test_expect_success 'test_atexit is run' '
run_sub_test_lib_test_err \
- atexit-cleanup 'Run atexit commands' -i <<-\\EOF &&
- test_expect_success 'tests clean up even after a failure' '
+ atexit-cleanup "Run atexit commands" -i <<-\EOF &&
+ test_expect_success "tests clean up even after a failure" "
> ../../clean-atexit &&
test_atexit rm ../../clean-atexit &&
> ../../also-clean-atexit &&
test_atexit rm ../../also-clean-atexit &&
> ../../dont-clean-atexit &&
(exit 1)
- '
+ "
test_done
EOF
test_path_is_file dont-clean-atexit &&
test_path_is_missing clean-atexit &&
test_path_is_missing also-clean-atexit
-"
+'
test_expect_success 'test_oid provides sane info by default' '
test_oid zero >actual &&
diff --git a/t/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/t1405-main-ref-store.sh b/t/t1405-main-ref-store.sh
index 602e21957b..a237d9880e 100755
--- a/t/t1405-main-ref-store.sh
+++ b/t/t1405-main-ref-store.sh
@@ -17,13 +17,6 @@ test_expect_success 'pack_refs(PACK_REFS_ALL | PACK_REFS_PRUNE)' '
N=`find .git/refs -type f | wc -l`
'
-test_expect_success 'peel_ref(new-tag)' '
- git rev-parse HEAD >expected &&
- git tag -a -m new-tag new-tag HEAD &&
- $RUN peel-ref refs/tags/new-tag >actual &&
- test_cmp expected actual
-'
-
test_expect_success 'create_symref(FOO, refs/heads/main)' '
$RUN create-symref FOO refs/heads/main nothing &&
echo refs/heads/main >expected &&
@@ -32,6 +25,7 @@ test_expect_success 'create_symref(FOO, refs/heads/main)' '
'
test_expect_success 'delete_refs(FOO, refs/tags/new-tag)' '
+ git tag -a -m new-tag new-tag HEAD &&
git rev-parse FOO -- &&
git rev-parse refs/tags/new-tag -- &&
m=$(git rev-parse main) &&
diff --git a/t/t1406-submodule-ref-store.sh b/t/t1406-submodule-ref-store.sh
index a68c8f11a2..0a87058971 100755
--- a/t/t1406-submodule-ref-store.sh
+++ b/t/t1406-submodule-ref-store.sh
@@ -14,7 +14,8 @@ test_expect_success 'setup' '
(
cd sub &&
test_commit first &&
- git checkout -b new-main
+ git checkout -b new-main &&
+ git tag -a -m new-tag new-tag HEAD
)
'
@@ -22,13 +23,6 @@ test_expect_success 'pack_refs() not allowed' '
test_must_fail $RUN pack-refs 3
'
-test_expect_success 'peel_ref(new-tag)' '
- git -C sub rev-parse HEAD >expected &&
- git -C sub tag -a -m new-tag new-tag HEAD &&
- $RUN peel-ref refs/tags/new-tag >actual &&
- test_cmp expected actual
-'
-
test_expect_success 'create_symref() not allowed' '
test_must_fail $RUN create-symref FOO refs/heads/main nothing
'
diff --git a/t/t2402-worktree-list.sh b/t/t2402-worktree-list.sh
index 821a20f3d6..42d35d9ae8 100755
--- a/t/t2402-worktree-list.sh
+++ b/t/t2402-worktree-list.sh
@@ -69,11 +69,107 @@ test_expect_success '"list" all worktrees with locked annotation' '
git worktree add --detach locked main &&
git worktree add --detach unlocked main &&
git worktree lock locked &&
+ test_when_finished "git worktree unlock locked" &&
git worktree list >out &&
grep "/locked *[0-9a-f].* locked$" out &&
! grep "/unlocked *[0-9a-f].* locked$" out
'
+test_expect_success '"list" all worktrees --porcelain with locked' '
+ test_when_finished "rm -rf locked1 locked2 unlocked out actual expect && git worktree prune" &&
+ echo "locked" >expect &&
+ echo "locked with reason" >>expect &&
+ git worktree add --detach locked1 &&
+ git worktree add --detach locked2 &&
+ # unlocked worktree should not be annotated with "locked"
+ git worktree add --detach unlocked &&
+ git worktree lock locked1 &&
+ test_when_finished "git worktree unlock locked1" &&
+ git worktree lock locked2 --reason "with reason" &&
+ test_when_finished "git worktree unlock locked2" &&
+ git worktree list --porcelain >out &&
+ grep "^locked" out >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '"list" all worktrees --porcelain with locked reason newline escaped' '
+ test_when_finished "rm -rf locked_lf locked_crlf out actual expect && git worktree prune" &&
+ printf "locked \"locked\\\\r\\\\nreason\"\n" >expect &&
+ printf "locked \"locked\\\\nreason\"\n" >>expect &&
+ git worktree add --detach locked_lf &&
+ git worktree add --detach locked_crlf &&
+ git worktree lock locked_lf --reason "$(printf "locked\nreason")" &&
+ test_when_finished "git worktree unlock locked_lf" &&
+ git worktree lock locked_crlf --reason "$(printf "locked\r\nreason")" &&
+ test_when_finished "git worktree unlock locked_crlf" &&
+ git worktree list --porcelain >out &&
+ grep "^locked" out >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '"list" all worktrees with prunable annotation' '
+ test_when_finished "rm -rf prunable unprunable out && git worktree prune" &&
+ git worktree add --detach prunable &&
+ git worktree add --detach unprunable &&
+ rm -rf prunable &&
+ git worktree list >out &&
+ grep "/prunable *[0-9a-f].* prunable$" out &&
+ ! grep "/unprunable *[0-9a-f].* prunable$"
+'
+
+test_expect_success '"list" all worktrees --porcelain with prunable' '
+ test_when_finished "rm -rf prunable out && git worktree prune" &&
+ git worktree add --detach prunable &&
+ rm -rf prunable &&
+ git worktree list --porcelain >out &&
+ sed -n "/^worktree .*\/prunable$/,/^$/p" <out >only_prunable &&
+ test_i18ngrep "^prunable gitdir file points to non-existent location$" only_prunable
+'
+
+test_expect_success '"list" all worktrees with prunable consistent with "prune"' '
+ test_when_finished "rm -rf prunable unprunable out && git worktree prune" &&
+ git worktree add --detach prunable &&
+ git worktree add --detach unprunable &&
+ rm -rf prunable &&
+ git worktree list >out &&
+ grep "/prunable *[0-9a-f].* prunable$" out &&
+ ! grep "/unprunable *[0-9a-f].* unprunable$" out &&
+ git worktree prune --verbose >out &&
+ test_i18ngrep "^Removing worktrees/prunable" out &&
+ test_i18ngrep ! "^Removing worktrees/unprunable" out
+'
+
+test_expect_success '"list" --verbose and --porcelain mutually exclusive' '
+ test_must_fail git worktree list --verbose --porcelain
+'
+
+test_expect_success '"list" all worktrees --verbose with locked' '
+ test_when_finished "rm -rf locked1 locked2 out actual expect && git worktree prune" &&
+ git worktree add locked1 --detach &&
+ git worktree add locked2 --detach &&
+ git worktree lock locked1 &&
+ test_when_finished "git worktree unlock locked1" &&
+ git worktree lock locked2 --reason "with reason" &&
+ test_when_finished "git worktree unlock locked2" &&
+ echo "$(git -C locked2 rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >expect &&
+ printf "\tlocked: with reason\n" >>expect &&
+ git worktree list --verbose >out &&
+ grep "/locked1 *[0-9a-f].* locked$" out &&
+ sed -n "s/ */ /g;/\/locked2 *[0-9a-f].*$/,/locked: .*$/p" <out >actual &&
+ test_cmp actual expect
+'
+
+test_expect_success '"list" all worktrees --verbose with prunable' '
+ test_when_finished "rm -rf prunable out actual expect && git worktree prune" &&
+ git worktree add prunable --detach &&
+ echo "$(git -C prunable rev-parse --show-toplevel) $(git rev-parse --short HEAD) (detached HEAD)" >expect &&
+ printf "\tprunable: gitdir file points to non-existent location\n" >>expect &&
+ rm -rf prunable &&
+ git worktree list --verbose >out &&
+ sed -n "s/ */ /g;/\/prunable *[0-9a-f].*$/,/prunable: .*$/p" <out >actual &&
+ test_i18ncmp actual expect
+'
+
test_expect_success 'bare repo setup' '
git init --bare bare1 &&
echo "data" >file1 &&
diff --git a/t/t3012-ls-files-dedup.sh b/t/t3012-ls-files-dedup.sh
new file mode 100755
index 0000000000..2682b1f43a
--- /dev/null
+++ b/t/t3012-ls-files-dedup.sh
@@ -0,0 +1,66 @@
+#!/bin/sh
+
+test_description='git ls-files --deduplicate test'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ >a.txt &&
+ >b.txt &&
+ >delete.txt &&
+ git add a.txt b.txt delete.txt &&
+ git commit -m base &&
+ echo a >a.txt &&
+ echo b >b.txt &&
+ echo delete >delete.txt &&
+ git add a.txt b.txt delete.txt &&
+ git commit -m tip &&
+ git tag tip &&
+ git reset --hard HEAD^ &&
+ echo change >a.txt &&
+ git commit -a -m side &&
+ git tag side
+'
+
+test_expect_success 'git ls-files --deduplicate to show unique unmerged path' '
+ test_must_fail git merge tip &&
+ git ls-files --deduplicate >actual &&
+ cat >expect <<-\EOF &&
+ a.txt
+ b.txt
+ delete.txt
+ EOF
+ test_cmp expect actual &&
+ git merge --abort
+'
+
+test_expect_success 'git ls-files -d -m --deduplicate with different display options' '
+ git reset --hard side &&
+ test_must_fail git merge tip &&
+ rm delete.txt &&
+ git ls-files -d -m --deduplicate >actual &&
+ cat >expect <<-\EOF &&
+ a.txt
+ delete.txt
+ EOF
+ test_cmp expect actual &&
+ git ls-files -d -m -t --deduplicate >actual &&
+ cat >expect <<-\EOF &&
+ C a.txt
+ C a.txt
+ C a.txt
+ R delete.txt
+ C delete.txt
+ EOF
+ test_cmp expect actual &&
+ git ls-files -d -m -c --deduplicate >actual &&
+ cat >expect <<-\EOF &&
+ a.txt
+ b.txt
+ delete.txt
+ EOF
+ test_cmp expect actual &&
+ git merge --abort
+'
+
+test_done
diff --git a/t/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/t4013-diff-various.sh b/t/t4013-diff-various.sh
index 45f68ebcdb..ce6aa3914f 100755
--- a/t/t4013-diff-various.sh
+++ b/t/t4013-diff-various.sh
@@ -178,6 +178,7 @@ process_diffs () {
V=$(git version | sed -e 's/^git version //' -e 's/\./\\./g')
while read magic cmd
do
+ status=success
case "$magic" in
'' | '#'*)
continue ;;
@@ -186,6 +187,10 @@ do
label="$magic-$cmd"
case "$magic" in
noellipses) ;;
+ failure)
+ status=failure
+ magic=
+ label="$cmd" ;;
*)
BUG "unknown magic $magic" ;;
esac ;;
@@ -198,7 +203,7 @@ do
expect="$TEST_DIRECTORY/t4013/diff.$test"
actual="$pfx-diff.$test"
- test_expect_success "git $cmd # magic is ${magic:-(not used)}" '
+ test_expect_$status "git $cmd # magic is ${magic:-(not used)}" '
{
echo "$ git $cmd"
case "$magic" in
@@ -326,8 +331,12 @@ log --no-diff-merges -p --first-parent master
log --diff-merges=off -p --first-parent master
log --first-parent --diff-merges=off -p master
log -p --first-parent master
+log -p --diff-merges=first-parent master
+log --diff-merges=first-parent master
log -m -p --first-parent master
log -m -p master
+log --cc -m -p master
+log -c -m -p master
log -SF master
log -S F master
log -SF -p master
diff --git a/t/t4013/diff.log_--cc_-m_-p_master b/t/t4013/diff.log_--cc_-m_-p_master
new file mode 100644
index 0000000000..7c217cf348
--- /dev/null
+++ b/t/t4013/diff.log_--cc_-m_-p_master
@@ -0,0 +1,200 @@
+$ git log --cc -m -p master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0)
+Merge: 9a6d494 c7a2ab9
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:04:00 2006 +0000
+
+ Merge branch 'side'
+
+diff --git a/dir/sub b/dir/sub
+index cead32e..992913c 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -4,3 +4,5 @@ C
+ D
+ E
+ F
++1
++2
+diff --git a/file0 b/file0
+index b414108..10a8a9f 100644
+--- a/file0
++++ b/file0
+@@ -4,3 +4,6 @@
+ 4
+ 5
+ 6
++A
++B
++C
+
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a)
+Merge: 9a6d494 c7a2ab9
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:04:00 2006 +0000
+
+ Merge branch 'side'
+
+diff --git a/dir/sub b/dir/sub
+index 7289e35..992913c 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,4 +1,8 @@
+ A
+ B
++C
++D
++E
++F
+ 1
+ 2
+diff --git a/file0 b/file0
+index f4615da..10a8a9f 100644
+--- a/file0
++++ b/file0
+@@ -1,6 +1,9 @@
+ 1
+ 2
+ 3
++4
++5
++6
+ A
+ B
+ C
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+diff --git a/file3 b/file3
+deleted file mode 100644
+index 7289e35..0000000
+--- a/file3
++++ /dev/null
+@@ -1,4 +0,0 @@
+-A
+-B
+-1
+-2
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+$
diff --git a/t/t4013/diff.log_--diff-merges=first-parent_master b/t/t4013/diff.log_--diff-merges=first-parent_master
new file mode 100644
index 0000000000..fa63a557dd
--- /dev/null
+++ b/t/t4013/diff.log_--diff-merges=first-parent_master
@@ -0,0 +1,56 @@
+$ git log --diff-merges=first-parent master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494 c7a2ab9
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:04:00 2006 +0000
+
+ Merge branch 'side'
+
+diff --git a/dir/sub b/dir/sub
+index cead32e..992913c 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -4,3 +4,5 @@ C
+ D
+ E
+ F
++1
++2
+diff --git a/file0 b/file0
+index b414108..10a8a9f 100644
+--- a/file0
++++ b/file0
+@@ -4,3 +4,6 @@
+ 4
+ 5
+ 6
++A
++B
++C
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+$
diff --git a/t/t4013/diff.log_-c_-m_-p_master b/t/t4013/diff.log_-c_-m_-p_master
new file mode 100644
index 0000000000..b660f3d5f2
--- /dev/null
+++ b/t/t4013/diff.log_-c_-m_-p_master
@@ -0,0 +1,200 @@
+$ git log -c -m -p master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0)
+Merge: 9a6d494 c7a2ab9
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:04:00 2006 +0000
+
+ Merge branch 'side'
+
+diff --git a/dir/sub b/dir/sub
+index cead32e..992913c 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -4,3 +4,5 @@ C
+ D
+ E
+ F
++1
++2
+diff --git a/file0 b/file0
+index b414108..10a8a9f 100644
+--- a/file0
++++ b/file0
+@@ -4,3 +4,6 @@
+ 4
+ 5
+ 6
++A
++B
++C
+
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (from c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a)
+Merge: 9a6d494 c7a2ab9
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:04:00 2006 +0000
+
+ Merge branch 'side'
+
+diff --git a/dir/sub b/dir/sub
+index 7289e35..992913c 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,4 +1,8 @@
+ A
+ B
++C
++D
++E
++F
+ 1
+ 2
+diff --git a/file0 b/file0
+index f4615da..10a8a9f 100644
+--- a/file0
++++ b/file0
+@@ -1,6 +1,9 @@
+ 1
+ 2
+ 3
++4
++5
++6
+ A
+ B
+ C
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+diff --git a/file3 b/file3
+deleted file mode 100644
+index 7289e35..0000000
+--- a/file3
++++ /dev/null
+@@ -1,4 +0,0 @@
+-A
+-B
+-1
+-2
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+$
diff --git a/t/t4013/diff.log_-p_--diff-merges=first-parent_master b/t/t4013/diff.log_-p_--diff-merges=first-parent_master
new file mode 100644
index 0000000000..9538a27511
--- /dev/null
+++ b/t/t4013/diff.log_-p_--diff-merges=first-parent_master
@@ -0,0 +1,137 @@
+$ git log -p --diff-merges=first-parent master
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
+Merge: 9a6d494 c7a2ab9
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:04:00 2006 +0000
+
+ Merge branch 'side'
+
+diff --git a/dir/sub b/dir/sub
+index cead32e..992913c 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -4,3 +4,5 @@ C
+ D
+ E
+ F
++1
++2
+diff --git a/file0 b/file0
+index b414108..10a8a9f 100644
+--- a/file0
++++ b/file0
+@@ -4,3 +4,6 @@
+ 4
+ 5
+ 6
++A
++B
++C
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:03:00 2006 +0000
+
+ Side
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:02:00 2006 +0000
+
+ Third
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:01:00 2006 +0000
+
+ Second
+
+ This is the second commit.
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a
+Author: A U Thor <author@example.com>
+Date: Mon Jun 26 00:00:00 2006 +0000
+
+ Initial
+$
diff --git a/t/t7104-reset-hard.sh b/t/t7104-reset-hard.sh
index 16faa07813..7948ec392b 100755
--- a/t/t7104-reset-hard.sh
+++ b/t/t7104-reset-hard.sh
@@ -33,7 +33,7 @@ test_expect_success 'reset --hard should restore unmerged ones' '
'
-test_expect_success 'reset --hard did not corrupt index or cached-tree' '
+test_expect_success 'reset --hard did not corrupt index or cache-tree' '
T=$(git write-tree) &&
rm -f .git/index &&
diff --git a/t/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/t7900-maintenance.sh b/t/t7900-maintenance.sh
index 1074009cc0..78ccf4b33f 100755
--- a/t/t7900-maintenance.sh
+++ b/t/t7900-maintenance.sh
@@ -149,7 +149,31 @@ test_expect_success 'prefetch multiple remotes' '
git log prefetch/remote2/two &&
git fetch --all &&
test_cmp_rev refs/remotes/remote1/one refs/prefetch/remote1/one &&
- test_cmp_rev refs/remotes/remote2/two refs/prefetch/remote2/two
+ test_cmp_rev refs/remotes/remote2/two refs/prefetch/remote2/two &&
+
+ test_cmp_config refs/prefetch/ log.excludedecoration &&
+ git log --oneline --decorate --all >log &&
+ ! grep "prefetch" log
+'
+
+test_expect_success 'prefetch and existing log.excludeDecoration values' '
+ git config --unset-all log.excludeDecoration &&
+ git config log.excludeDecoration refs/remotes/remote1/ &&
+ git maintenance run --task=prefetch &&
+
+ git config --get-all log.excludeDecoration >out &&
+ grep refs/remotes/remote1/ out &&
+ grep refs/prefetch/ out &&
+
+ git log --oneline --decorate --all >log &&
+ ! grep "prefetch" log &&
+ ! grep "remote1" log &&
+ grep "remote2" log &&
+
+ # a second run does not change the config
+ git maintenance run --task=prefetch &&
+ git log --oneline --decorate --all >log2 &&
+ test_cmp log log2
'
test_expect_success 'loose-objects task' '
@@ -232,6 +256,13 @@ test_expect_success 'incremental-repack task' '
HEAD
^HEAD~1
EOF
+
+ # Delete refs that have not been repacked in these packs.
+ git for-each-ref --format="delete %(refname)" \
+ refs/prefetch refs/tags >refs &&
+ git update-ref --stdin <refs &&
+
+ # Replace the object directory with this pack layout.
rm -f $packDir/pack-* &&
rm -f $packDir/loose-* &&
ls $packDir/*.pack >packs-before &&
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index 6bca002316..aca17f8945 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -1683,3 +1683,45 @@ test_subcommand () {
grep "\[$expr\]"
fi
}
+
+# Check that the given command was invoked as part of the
+# trace2-format trace on stdin.
+#
+# test_region [!] <category> <label> git <command> <args>...
+#
+# For example, to look for trace2_region_enter("index", "do_read_index", repo)
+# in an invocation of "git checkout HEAD~1", run
+#
+# GIT_TRACE2_EVENT="$(pwd)/trace.txt" GIT_TRACE2_EVENT_NESTING=10 \
+# git checkout HEAD~1 &&
+# test_region index do_read_index <trace.txt
+#
+# If the first parameter passed is !, this instead checks that
+# the given region was not entered.
+#
+test_region () {
+ local expect_exit=0
+ if test "$1" = "!"
+ then
+ expect_exit=1
+ shift
+ fi
+
+ grep -e '"region_enter".*"category":"'"$1"'","label":"'"$2"\" "$3"
+ exitcode=$?
+
+ if test $exitcode != $expect_exit
+ then
+ return 1
+ fi
+
+ grep -e '"region_leave".*"category":"'"$1"'","label":"'"$2"\" "$3"
+ exitcode=$?
+
+ if test $exitcode != $expect_exit
+ then
+ return 1
+ fi
+
+ return 0
+}
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 03c1c0836f..76062db296 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -163,8 +163,8 @@ parse_option () {
;;
--stress-jobs=*)
stress=t;
- stress=${opt#--*=}
- case "$stress" in
+ stress_jobs=${opt#--*=}
+ case "$stress_jobs" in
*[!0-9]*|0*|"")
echo "error: --stress-jobs=<N> requires the number of jobs to run" >&2
exit 1
@@ -262,9 +262,9 @@ then
: # Don't stress test again.
elif test -n "$stress"
then
- if test "$stress" != t
+ if test -n "$stress_jobs"
then
- job_count=$stress
+ job_count=$stress_jobs
elif test -n "$GIT_TEST_STRESS_LOAD"
then
job_count="$GIT_TEST_STRESS_LOAD"
diff --git a/tree-walk.c b/tree-walk.c
index 0160294712..2d6226d5f1 100644
--- a/tree-walk.c
+++ b/tree-walk.c
@@ -4,6 +4,7 @@
#include "object-store.h"
#include "tree.h"
#include "pathspec.h"
+#include "json-writer.h"
static const char *get_mode(const char *str, unsigned int *modep)
{
@@ -167,6 +168,25 @@ int tree_entry_gently(struct tree_desc *desc, struct name_entry *entry)
return 1;
}
+static int traverse_trees_atexit_registered;
+static int traverse_trees_count;
+static int traverse_trees_cur_depth;
+static int traverse_trees_max_depth;
+
+static void trace2_traverse_trees_statistics_atexit(void)
+{
+ struct json_writer jw = JSON_WRITER_INIT;
+
+ jw_object_begin(&jw, 0);
+ jw_object_intmax(&jw, "traverse_trees_count", traverse_trees_count);
+ jw_object_intmax(&jw, "traverse_trees_max_depth", traverse_trees_max_depth);
+ jw_end(&jw);
+
+ trace2_data_json("traverse_trees", the_repository, "statistics", &jw);
+
+ jw_release(&jw);
+}
+
void setup_traverse_info(struct traverse_info *info, const char *base)
{
size_t pathlen = strlen(base);
@@ -180,6 +200,11 @@ void setup_traverse_info(struct traverse_info *info, const char *base)
info->namelen = pathlen;
if (pathlen)
info->prev = &dummy;
+
+ if (trace2_is_enabled() && !traverse_trees_atexit_registered) {
+ atexit(trace2_traverse_trees_statistics_atexit);
+ traverse_trees_atexit_registered = 1;
+ }
}
char *make_traverse_path(char *path, size_t pathlen,
@@ -416,6 +441,12 @@ int traverse_trees(struct index_state *istate,
int interesting = 1;
char *traverse_path;
+ traverse_trees_count++;
+ traverse_trees_cur_depth++;
+
+ if (traverse_trees_cur_depth > traverse_trees_max_depth)
+ traverse_trees_max_depth = traverse_trees_cur_depth;
+
if (n >= ARRAY_SIZE(entry))
BUG("traverse_trees() called with too many trees (%d)", n);
@@ -515,6 +546,8 @@ int traverse_trees(struct index_state *istate,
free(traverse_path);
info->traverse_path = NULL;
strbuf_release(&base);
+
+ traverse_trees_cur_depth--;
return error;
}
diff --git a/unpack-trees.c b/unpack-trees.c
index 323280dd48..f5f668f532 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -1549,14 +1549,10 @@ static void mark_new_skip_worktree(struct pattern_list *pl,
static void populate_from_existing_patterns(struct unpack_trees_options *o,
struct pattern_list *pl)
{
- char *sparse = git_pathdup("info/sparse-checkout");
-
- pl->use_cone_patterns = core_sparse_checkout_cone;
- if (add_patterns_from_file_to_list(sparse, "", 0, pl, NULL) < 0)
+ if (get_sparse_checkout_patterns(pl) < 0)
o->skip_sparse_checkout = 1;
else
o->pl = pl;
- free(sparse);
}
@@ -1580,6 +1576,8 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
die("unpack_trees takes at most %d trees", MAX_UNPACK_TREES);
trace_performance_enter();
+ trace2_region_enter("unpack_trees", "unpack_trees", the_repository);
+
if (!core_apply_sparse_checkout || !o->update)
o->skip_sparse_checkout = 1;
if (!o->skip_sparse_checkout && !o->pl) {
@@ -1653,7 +1651,9 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
}
trace_performance_enter();
+ trace2_region_enter("unpack_trees", "traverse_trees", the_repository);
ret = traverse_trees(o->src_index, len, t, &info);
+ trace2_region_leave("unpack_trees", "traverse_trees", the_repository);
trace_performance_leave("traverse_trees");
if (ret < 0)
goto return_failed;
@@ -1722,8 +1722,6 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
if (!ret) {
if (git_env_bool("GIT_TEST_CHECK_CACHE_TREE", 0))
cache_tree_verify(the_repository, &o->result);
- if (!o->result.cache_tree)
- o->result.cache_tree = cache_tree();
if (!cache_tree_fully_valid(o->result.cache_tree))
cache_tree_update(&o->result,
WRITE_TREE_SILENT |
@@ -1741,6 +1739,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
done:
if (free_pattern_list)
clear_pattern_list(&pl);
+ trace2_region_leave("unpack_trees", "unpack_trees", the_repository);
trace_performance_leave("unpack_trees");
return ret;
diff --git a/upload-pack.c b/upload-pack.c
index 3b66bf92ba..4ab55ce2b5 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -1232,7 +1232,7 @@ static int send_ref(const char *refname, const struct object_id *oid,
packet_write_fmt(1, "%s %s\n", oid_to_hex(oid), refname_nons);
}
capabilities = NULL;
- if (!peel_ref(refname, &peeled))
+ if (!peel_iterated_oid(oid, &peeled))
packet_write_fmt(1, "%s %s^{}\n", oid_to_hex(&peeled), refname_nons);
return 0;
}
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)
/*