summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/CodingGuidelines7
-rw-r--r--Documentation/RelNotes/2.36.0.txt105
-rw-r--r--Documentation/SubmittingPatches36
-rw-r--r--Documentation/config/advice.txt3
-rw-r--r--Documentation/config/fetch.txt25
-rw-r--r--Documentation/config/gpg.txt4
-rw-r--r--Documentation/config/stash.txt7
-rw-r--r--Documentation/config/submodule.txt37
-rw-r--r--Documentation/diff-options.txt21
-rw-r--r--Documentation/git-branch.txt19
-rw-r--r--Documentation/git-check-ignore.txt4
-rw-r--r--Documentation/git-checkout-index.txt10
-rw-r--r--Documentation/git-mktree.txt2
-rw-r--r--Documentation/gitattributes.txt11
-rw-r--r--Documentation/glossary-content.txt13
-rw-r--r--Documentation/rev-list-options.txt22
-rw-r--r--Documentation/technical/multi-pack-index.txt1
-rw-r--r--Documentation/technical/pack-format.txt13
-rw-r--r--Makefile45
-rw-r--r--advice.c1
-rw-r--r--advice.h1
-rw-r--r--apply.c5
-rw-r--r--blame.c2
-rw-r--r--branch.c277
-rw-r--r--branch.h60
-rw-r--r--builtin/add.c14
-rw-r--r--builtin/branch.c70
-rw-r--r--builtin/checkout-index.c41
-rw-r--r--builtin/checkout.c22
-rw-r--r--builtin/clean.c3
-rw-r--r--builtin/clone.c13
-rw-r--r--builtin/diff.c6
-rw-r--r--builtin/fetch.c28
-rw-r--r--builtin/hash-object.c9
-rw-r--r--builtin/log.c15
-rw-r--r--builtin/ls-remote.c3
-rw-r--r--builtin/merge.c6
-rw-r--r--builtin/pack-objects.c8
-rw-r--r--builtin/patch-id.c9
-rw-r--r--builtin/pull.c6
-rw-r--r--builtin/rebase.c101
-rw-r--r--builtin/receive-pack.c9
-rw-r--r--builtin/reset.c12
-rw-r--r--builtin/sparse-checkout.c2
-rw-r--r--builtin/stash.c10
-rw-r--r--builtin/submodule--helper.c38
-rw-r--r--builtin/update-index.c17
-rw-r--r--cache.h1
-rwxr-xr-xci/lib.sh1
-rw-r--r--compat/winansi.c6
-rw-r--r--compat/zlib-uncompress2.c11
-rw-r--r--config.mak.uname15
-rw-r--r--configure.ac13
-rw-r--r--connect.c4
-rw-r--r--contrib/buildsystems/CMakeLists.txt2
-rw-r--r--contrib/completion/git-completion.bash57
-rw-r--r--contrib/scalar/scalar.c22
-rw-r--r--contrib/scalar/scalar.txt10
-rwxr-xr-xcontrib/scalar/t/t9099-scalar.sh8
-rwxr-xr-xcontrib/subtree/git-subtree.sh4
-rw-r--r--diff-merges.c14
-rw-r--r--diff.c221
-rw-r--r--diff.h5
-rw-r--r--fetch-negotiator.c2
-rw-r--r--fetch-pack.c40
-rw-r--r--git-compat-util.h31
-rw-r--r--git-sh-setup.sh1
-rw-r--r--ll-merge.c40
-rw-r--r--ll-merge.h9
-rw-r--r--log-tree.c118
-rw-r--r--merge-blobs.c5
-rw-r--r--merge-ort.c55
-rw-r--r--merge-ort.h10
-rw-r--r--merge-recursive.c9
-rw-r--r--merge-recursive.h2
-rw-r--r--midx.c38
-rw-r--r--midx.h1
-rw-r--r--notes-merge.c5
-rw-r--r--pack-bitmap.c4
-rw-r--r--pack-revindex.c20
-rw-r--r--parse-options.h5
-rw-r--r--perl/Git.pm21
-rw-r--r--read-cache.c10
-rw-r--r--refs.c62
-rw-r--r--refs.h15
-rw-r--r--refs/files-backend.c57
-rw-r--r--refs/packed-backend.c28
-rw-r--r--refs/packed-backend.h7
-rw-r--r--refs/refs-internal.h1
-rw-r--r--reftable/block.c28
-rw-r--r--reftable/block_test.c22
-rw-r--r--reftable/blocksource.c6
-rw-r--r--reftable/generic.c41
-rw-r--r--reftable/iter.c4
-rw-r--r--reftable/merged.c33
-rw-r--r--reftable/pq.c3
-rw-r--r--reftable/pq_test.c27
-rw-r--r--reftable/reader.c112
-rw-r--r--reftable/readwrite_test.c67
-rw-r--r--reftable/record.c366
-rw-r--r--reftable/record.h49
-rw-r--r--reftable/record_test.c197
-rw-r--r--reftable/reftable-record.h14
-rw-r--r--reftable/reftable.c115
-rw-r--r--reftable/stack.c10
-rw-r--r--reftable/stack_test.c3
-rw-r--r--reftable/system.h11
-rw-r--r--reftable/writer.c43
-rw-r--r--remote.c3
-rw-r--r--repo-settings.c9
-rw-r--r--repository.h2
-rw-r--r--rerere.c9
-rw-r--r--reset.c149
-rw-r--r--reset.h48
-rw-r--r--revision.c30
-rw-r--r--revision.h9
-rw-r--r--sequencer.c64
-rw-r--r--sequencer.h3
-rw-r--r--shallow.c2
-rw-r--r--submodule-config.c61
-rw-r--r--submodule-config.h34
-rw-r--r--submodule.c11
-rw-r--r--submodule.h3
-rw-r--r--t/helper/test-csprng.c29
-rw-r--r--t/helper/test-ref-store.c3
-rw-r--r--t/helper/test-reftable.c9
-rw-r--r--t/helper/test-tool.c1
-rw-r--r--t/helper/test-tool.h1
-rw-r--r--t/lib-bitmap.sh185
-rw-r--r--t/lib-read-tree-m-3way.sh168
-rwxr-xr-xt/perf/p2000-sparse-operations.sh2
-rwxr-xr-xt/t0001-init.sh3
-rwxr-xr-xt/t0012-help.sh7
-rwxr-xr-xt/t0015-hash.sh6
-rwxr-xr-xt/t0027-auto-crlf.sh6
-rwxr-xr-xt/t0051-windows-named-pipe.sh7
-rwxr-xr-xt/t1007-hash-object.sh1
-rwxr-xr-xt/t1092-sparse-checkout-compatibility.sh282
-rwxr-xr-xt/t1405-main-ref-store.sh8
-rwxr-xr-xt/t1410-reflog.sh5
-rwxr-xr-xt/t1416-ref-transaction-hooks.sh50
-rwxr-xr-xt/t3200-branch.sh17
-rwxr-xr-xt/t3207-branch-submodule.sh292
-rwxr-xr-xt/t3406-rebase-message.sh23
-rwxr-xr-xt/t3418-rebase-continue.sh26
-rwxr-xr-xt/t3903-stash.sh15
-rwxr-xr-xt/t4069-remerge-diff.sh291
-rwxr-xr-xt/t4202-log.sh13
-rwxr-xr-xt/t4204-patch-id.sh95
-rwxr-xr-xt/t5310-pack-bitmaps.sh28
-rwxr-xr-xt/t5312-prune-corruption.sh10
-rwxr-xr-xt/t5326-multi-pack-bitmaps.sh186
-rwxr-xr-xt/t5327-multi-pack-bitmaps-rev.sh23
-rwxr-xr-xt/t5403-post-checkout-hook.sh67
-rwxr-xr-xt/t5500-fetch-pack.sh24
-rwxr-xr-xt/t5510-fetch.sh11
-rwxr-xr-xt/t5511-refspec.sh1
-rwxr-xr-xt/t5516-fetch-push.sh8
-rwxr-xr-xt/t5702-protocol-v2.sh2
-rwxr-xr-xt/t6012-rev-list-simplify.sh18
-rwxr-xr-xt/t6404-recursive-merge.sh9
-rwxr-xr-xt/t6406-merge-attr.sh9
-rwxr-xr-xt/t7700-repack.sh4
-rwxr-xr-xt/t9102-git-svn-deep-rmdir.sh1
-rwxr-xr-xt/t9123-git-svn-rebuild-with-rewriteroot.sh1
-rwxr-xr-xt/t9128-git-svn-cmd-branch.sh1
-rwxr-xr-xt/t9167-git-svn-cmd-branch-subproject.sh1
-rwxr-xr-xt/t9902-completion.sh219
-rw-r--r--tmp-objdir.c5
-rw-r--r--tmp-objdir.h6
-rw-r--r--transport.c8
-rw-r--r--transport.h10
-rw-r--r--worktree.c11
-rw-r--r--wrapper.c81
174 files changed, 4506 insertions, 1534 deletions
diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines
index 0e27b5395d..c37c43186e 100644
--- a/Documentation/CodingGuidelines
+++ b/Documentation/CodingGuidelines
@@ -26,6 +26,13 @@ code. For Git in general, a few rough rules are:
go and fix it up."
Cf. http://lkml.iu.edu/hypermail/linux/kernel/1001.3/01069.html
+ - Log messages to explain your changes are as important as the
+ changes themselves. Clearly written code and in-code comments
+ explain how the code works and what is assumed from the surrounding
+ context. The log messages explain what the changes wanted to
+ achieve and why the changes were necessary (more on this in the
+ accompanying SubmittingPatches document).
+
Make your code readable and sensible, and don't try to be clever.
As for more concrete guidelines, just imitate the existing code
diff --git a/Documentation/RelNotes/2.36.0.txt b/Documentation/RelNotes/2.36.0.txt
index 94db2458c7..4e8309701b 100644
--- a/Documentation/RelNotes/2.36.0.txt
+++ b/Documentation/RelNotes/2.36.0.txt
@@ -19,6 +19,23 @@ UI, Workflows & Features
* Assorted updates to "git cat-file", especially "-h".
+ * The command line completion (in contrib/) learns to complete
+ arguments to give to "git sparse-checkout" command.
+
+ * "git log --remerge-diff" shows the difference from mechanical merge
+ result and the result that is actually recorded in a merge commit.
+
+ * "git log" and friends learned an option --exclude-first-parent-only
+ to propagate UNINTERESTING bit down only along the first-parent
+ chain, just like --first-parent option shows commits that lack the
+ UNINTERESTING bit only along the first-parent chain.
+
+ * The command line completion script (in contrib/) learned to
+ complete all Git subcommands, including the ones that are normally
+ hidden, when GIT_COMPLETION_SHOW_ALL_COMMANDS is used.
+
+ * "git branch" learned the "--recurse-submodules" option.
+
Performance, Internal Implementation, Development Support etc.
@@ -41,6 +58,13 @@ Performance, Internal Implementation, Development Support etc.
all. Start the process of renaming it to "--annotate-stdin".
(merge a2585719b3 jc/name-rev-stdin later to maint).
+ * "git update-index", "git checkout-index", and "git clean" are
+ taught to work better with the sparse checkout feature.
+
+ * Use an internal call to reset_head() helper function instead of
+ spawning "git checkout" in "rebase", and update code paths that are
+ involved in the change.
+
Fixes since v2.35
-----------------
@@ -103,6 +127,73 @@ Fixes since v2.35
* Update the logic to compute alignment requirement for our mem-pool.
(merge e38bcc66d8 jc/mem-pool-alignment later to maint).
+ * Pick a better random number generator and use it when we prepare
+ temporary filenames.
+ (merge 47efda967c bc/csprng-mktemps later to maint).
+
+ * Update the contributor-facing documents on proposed log messages.
+ (merge cdba0295b0 jc/doc-log-messages later to maint).
+
+ * When "git fetch --prune" failed to prune the refs it wanted to
+ prune, the command issued error messages but exited with exit
+ status 0, which has been corrected.
+ (merge c9e04d905e tg/fetch-prune-exit-code-fix later to maint).
+
+ * Problems identified by Coverity in the reftable code have been
+ corrected.
+ (merge 01033de49f hn/reftable-coverity-fixes later to maint).
+
+ * A bug that made multi-pack bitmap and the object order out-of-sync,
+ making the .midx data corrupt, has been fixed.
+ (merge f8b60cf99b tb/midx-bitmap-corruption-fix later to maint).
+
+ * The build procedure has been taught to notice older version of zlib
+ and enable our replacement uncompress2() automatically.
+ (merge 07564773c2 ab/auto-detect-zlib-compress2 later to maint).
+
+ * Interaction between fetch.negotiationAlgorithm and
+ feature.experimental configuration variables has been corrected.
+ (merge 714edc620c en/fetch-negotiation-default-fix later to maint).
+
+ * "git diff --diff-filter=aR" is now parsed correctly.
+ (merge 75408ca949 js/diff-filter-negation-fix later to maint).
+
+ * When "git subtree" wants to create a merge, it used "git merge" and
+ let it be affected by end-user's "merge.ff" configuration, which
+ has been corrected.
+ (merge 9158a3564a tk/subtree-merge-not-ff-only later to maint).
+
+ * Unlike "git apply", "git patch-id" did not handle patches with
+ hunks that has only 1 line in either preimage or postimage, which
+ has been corrected.
+ (merge 757e75c81e jz/patch-id-hunk-header-parsing-fix later to maint).
+
+ * "receive-pack" checks if it will do any ref updates (various
+ conditions could reject a push) before received objects are taken
+ out of the temporary directory used for quarantine purposes, so
+ that a push that is known-to-fail will not leave crufts that a
+ future "gc" needs to clean up.
+ (merge 5407764069 cb/clear-quarantine-early-on-all-ref-update-errors later to maint).
+
+ * Because a deletion of ref would need to remove it from both the
+ loose ref store and the packed ref store, a delete-ref operation
+ that logically removes one ref may end up invoking ref-transaction
+ hook twice, which has been corrected.
+ (merge 2ed1b64ebd ps/avoid-unnecessary-hook-invocation-with-packed-refs later to maint).
+
+ * When there is no object to write .bitmap file for, "git
+ multi-pack-index" triggered an error, instead of just skipping,
+ which has been corrected.
+ (merge eb57277ba3 tb/midx-no-bitmap-for-no-objects later to maint).
+
+ * "git cmd -h" outside a repository should error out cleanly for many
+ commands, but instead it hit a BUG(), which has been corrected.
+ (merge 87ad07d735 js/short-help-outside-repo-fix later to maint).
+
+ * "working tree" and "per-worktree ref" were in glossary, but
+ "worktree" itself wasn't, which has been corrected.
+ (merge 2df5387ed0 jc/glossary-worktree later to maint).
+
* Other code cleanup, docfix, build fix, etc.
(merge cfc5cf428b jc/find-header later to maint).
(merge 40e7cfdd46 jh/p4-fix-use-of-process-error-exception later to maint).
@@ -110,3 +201,17 @@ Fixes since v2.35
(merge 0a6adc26e2 rs/grep-expr-cleanup later to maint).
(merge 4ed7dfa713 po/readme-mention-contributor-hints later to maint).
(merge 6046f7a91c en/plug-leaks-in-merge later to maint).
+ (merge 8c591dbfce bc/clarify-eol-attr later to maint).
+ (merge 518e15db74 rs/parse-options-lithelp-help later to maint).
+ (merge cbac0076ef gh/doc-typos later to maint).
+ (merge ce14de03db ab/no-errno-from-resolve-ref-unsafe later to maint).
+ (merge 2826ffad8c rc/negotiate-only-typofix later to maint).
+ (merge 0f03f04c5c en/sparse-checkout-leakfix later to maint).
+ (merge 74f3390dde sy/diff-usage-typofix later to maint).
+ (merge 45d0212a71 ll/doc-mktree-typofix later to maint).
+ (merge e9b272e4c1 js/no-more-legacy-stash later to maint).
+ (merge 6798b08e84 ab/do-not-hide-failures-in-git-dot-pm later to maint).
+ (merge 9325285df4 po/doc-check-ignore-markup-fix later to maint).
+ (merge cd26cd6c7c sy/modernize-t-lib-read-tree-m-3way later to maint).
+ (merge d17294a05e ab/hash-object-leakfix later to maint).
+ (merge b8403129d3 jd/t0015-modernize later to maint).
diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
index 92b80d94d4..a6121d1d42 100644
--- a/Documentation/SubmittingPatches
+++ b/Documentation/SubmittingPatches
@@ -110,6 +110,35 @@ run `git diff --check` on your changes before you commit.
[[describe-changes]]
=== Describe your changes well.
+The log message that explains your changes is just as important as the
+changes themselves. Your code may be clearly written with in-code
+comment to sufficiently explain how it works with the surrounding
+code, but those who need to fix or enhance your code in the future
+will need to know _why_ your code does what it does, for a few
+reasons:
+
+. Your code may be doing something differently from what you wanted it
+ to do. Writing down what you actually wanted to achieve will help
+ them fix your code and make it do what it should have been doing
+ (also, you often discover your own bugs yourself, while writing the
+ log message to summarize the thought behind it).
+
+. Your code may be doing things that were only necessary for your
+ immediate needs (e.g. "do X to directories" without implementing or
+ even designing what is to be done on files). Writing down why you
+ excluded what the code does not do will help guide future developers.
+ Writing down "we do X to directories, because directories have
+ characteristic Y" would help them infer "oh, files also have the same
+ characteristic Y, so perhaps doing X to them would also make sense?".
+ Saying "we don't do the same X to files, because ..." will help them
+ decide if the reasoning is sound (in which case they do not waste
+ time extending your code to cover files), or reason differently (in
+ which case, they can explain why they extend your code to cover
+ files, too).
+
+The goal of your log message is to convey the _why_ behind your
+change to help future developers.
+
The first line of the commit message should be a short description (50
characters is the soft limit, see DISCUSSION in linkgit:git-commit[1]),
and should skip the full stop. It is also conventional in most cases to
@@ -142,6 +171,13 @@ The body should provide a meaningful commit message, which:
. alternate solutions considered but discarded, if any.
+[[present-tense]]
+The problem statement that describes the status quo is written in the
+present tense. Write "The code does X when it is given input Y",
+instead of "The code used to do Y when given input X". You do not
+have to say "Currently"---the status quo in the problem statement is
+about the code _without_ your change, by project convention.
+
[[imperative-mood]]
Describe your changes in imperative mood, e.g. "make xyzzy do frotz"
instead of "[This patch] makes xyzzy do frotz" or "[I] changed xyzzy
diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt
index 063eec2511..adee26fbbb 100644
--- a/Documentation/config/advice.txt
+++ b/Documentation/config/advice.txt
@@ -116,6 +116,9 @@ advice.*::
submoduleAlternateErrorStrategyDie::
Advice shown when a submodule.alternateErrorStrategy option
configured to "die" causes a fatal error.
+ submodulesNotUpdated::
+ Advice shown when a user runs a submodule command that fails
+ because `git submodule update --init` was not run.
addIgnoredFile::
Advice shown if a user attempts to add an ignored file to
the index.
diff --git a/Documentation/config/fetch.txt b/Documentation/config/fetch.txt
index 63748c02b7..cd65d236b4 100644
--- a/Documentation/config/fetch.txt
+++ b/Documentation/config/fetch.txt
@@ -56,18 +56,19 @@ fetch.output::
OUTPUT in linkgit:git-fetch[1] for detail.
fetch.negotiationAlgorithm::
- Control how information about the commits in the local repository is
- sent when negotiating the contents of the packfile to be sent by the
- server. Set to "skipping" to use an algorithm that skips commits in an
- effort to converge faster, but may result in a larger-than-necessary
- packfile; or set to "noop" to not send any information at all, which
- will almost certainly result in a larger-than-necessary packfile, but
- will skip the negotiation step.
- The default is "default" which instructs Git to use the default algorithm
- that never skips commits (unless the server has acknowledged it or one
- of its descendants). If `feature.experimental` is enabled, then this
- setting defaults to "skipping".
- Unknown values will cause 'git fetch' to error out.
+ Control how information about the commits in the local repository
+ is sent when negotiating the contents of the packfile to be sent by
+ the server. Set to "consecutive" to use an algorithm that walks
+ over consecutive commits checking each one. Set to "skipping" to
+ use an algorithm that skips commits in an effort to converge
+ faster, but may result in a larger-than-necessary packfile; or set
+ to "noop" to not send any information at all, which will almost
+ certainly result in a larger-than-necessary packfile, but will skip
+ the negotiation step. Set to "default" to override settings made
+ previously and use the default behaviour. The default is normally
+ "consecutive", but if `feature.experimental` is true, then the
+ default is "skipping". Unknown values will cause 'git fetch' to
+ error out.
+
See also the `--negotiate-only` and `--negotiation-tip` options to
linkgit:git-fetch[1].
diff --git a/Documentation/config/gpg.txt b/Documentation/config/gpg.txt
index 0cb189a077..86892ada77 100644
--- a/Documentation/config/gpg.txt
+++ b/Documentation/config/gpg.txt
@@ -37,7 +37,7 @@ gpg.minTrustLevel::
gpg.ssh.defaultKeyCommand::
This command that will be run when user.signingkey is not set and a ssh
signature is requested. On successful exit a valid ssh public key is
- expected in the first line of its output. To automatically use the first
+ expected in the first line of its output. To automatically use the first
available key from your ssh-agent set this to "ssh-add -L".
gpg.ssh.allowedSignersFile::
@@ -66,7 +66,7 @@ This way only committers with an already valid key can add or change keys in the
+
Since OpensSSH 8.8 this file allows specifying a key lifetime using valid-after &
valid-before options. Git will mark signatures as valid if the signing key was
-valid at the time of the signatures creation. This allows users to change a
+valid at the time of the signature's creation. This allows users to change a
signing key without invalidating all previously made signatures.
+
Using a SSH CA key with the cert-authority option
diff --git a/Documentation/config/stash.txt b/Documentation/config/stash.txt
index 9ed775281f..b9f609ed76 100644
--- a/Documentation/config/stash.txt
+++ b/Documentation/config/stash.txt
@@ -1,10 +1,3 @@
-stash.useBuiltin::
- Unused configuration variable. Used in Git versions 2.22 to
- 2.26 as an escape hatch to enable the legacy shellscript
- implementation of stash. Now the built-in rewrite of it in C
- is always used. Setting this will emit a warning, to alert any
- remaining users that setting this now does nothing.
-
stash.showIncludeUntracked::
If this is set to true, the `git stash show` command will show
the untracked files of a stash entry. Defaults to false. See
diff --git a/Documentation/config/submodule.txt b/Documentation/config/submodule.txt
index ee454f8126..6490527b45 100644
--- a/Documentation/config/submodule.txt
+++ b/Documentation/config/submodule.txt
@@ -59,18 +59,33 @@ submodule.active::
submodule.recurse::
A boolean indicating if commands should enable the `--recurse-submodules`
- option by default.
- Applies to all commands that support this option
- (`checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`, `reset`,
- `restore` and `switch`) except `clone` and `ls-files`.
+ option by default. Defaults to false.
++
+When set to true, it can be deactivated via the
+`--no-recurse-submodules` option. Note that some Git commands
+lacking this option may call some of the above commands affected by
+`submodule.recurse`; for instance `git remote update` will call
+`git fetch` but does not have a `--no-recurse-submodules` option.
+For these commands a workaround is to temporarily change the
+configuration value by using `git -c submodule.recurse=0`.
++
+The following list shows the commands that accept
+`--recurse-submodules` and whether they are supported by this
+setting.
+
+* `checkout`, `fetch`, `grep`, `pull`, `push`, `read-tree`,
+`reset`, `restore` and `switch` are always supported.
+* `clone` and `ls-files` are not supported.
+* `branch` is supported only if `submodule.propagateBranches` is
+enabled
+
+submodule.propagateBranches::
+ [EXPERIMENTAL] A boolean that enables branching support when
+ using `--recurse-submodules` or `submodule.recurse=true`.
+ Enabling this will allow certain commands to accept
+ `--recurse-submodules` and certain commands that already accept
+ `--recurse-submodules` will now consider branches.
Defaults to false.
- When set to true, it can be deactivated via the
- `--no-recurse-submodules` option. Note that some Git commands
- lacking this option may call some of the above commands affected by
- `submodule.recurse`; for instance `git remote update` will call
- `git fetch` but does not have a `--no-recurse-submodules` option.
- For these commands a workaround is to temporarily change the
- configuration value by using `git -c submodule.recurse=0`.
submodule.fetchJobs::
Specifies how many submodules are fetched/cloned at the same time.
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index c89d530d3d..3674ac48e9 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -34,7 +34,7 @@ endif::git-diff[]
endif::git-format-patch[]
ifdef::git-log[]
---diff-merges=(off|none|on|first-parent|1|separate|m|combined|c|dense-combined|cc)::
+--diff-merges=(off|none|on|first-parent|1|separate|m|combined|c|dense-combined|cc|remerge|r)::
--no-diff-merges::
Specify diff format to be used for merge commits. Default is
{diff-merges-default} unless `--first-parent` is in use, in which case
@@ -64,6 +64,18 @@ ifdef::git-log[]
each of the parents. Separate log entry and diff is generated
for each parent.
+
+--diff-merges=remerge:::
+--diff-merges=r:::
+--remerge-diff:::
+ With this option, two-parent merge commits are remerged to
+ create a temporary tree object -- potentially containing files
+ with conflict markers and such. A diff is then shown between
+ that temporary tree and the actual merge commit.
++
+The output emitted when this option is used is subject to change, and
+so is its interaction with other options (unless explicitly
+documented).
++
--diff-merges=combined:::
--diff-merges=c:::
-c:::
@@ -616,11 +628,8 @@ ifndef::git-format-patch[]
Also, these upper-case letters can be downcased to exclude. E.g.
`--diff-filter=ad` excludes added and deleted paths.
+
-Note that not all diffs can feature all types. For instance, diffs
-from the index to the working tree can never have Added entries
-(because the set of paths included in the diff is limited by what is in
-the index). Similarly, copied and renamed entries cannot appear if
-detection for those types is disabled.
+Note that not all diffs can feature all types. For instance, copied and
+renamed entries cannot appear if detection for those types is disabled.
-S<string>::
Look for differences that change the number of occurrences of
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 731e340cbc..c8b4f9ce3c 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -16,7 +16,8 @@ SYNOPSIS
[--points-at <object>] [--format=<format>]
[(-r | --remotes) | (-a | --all)]
[--list] [<pattern>...]
-'git branch' [--track[=(direct|inherit)] | --no-track] [-f] <branchname> [<start-point>]
+'git branch' [--track[=(direct|inherit)] | --no-track] [-f]
+ [--recurse-submodules] <branchname> [<start-point>]
'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
'git branch' --unset-upstream [<branchname>]
'git branch' (-m | -M) [<oldbranch>] <newbranch>
@@ -235,6 +236,22 @@ how the `branch.<name>.remote` and `branch.<name>.merge` options are used.
Do not set up "upstream" configuration, even if the
branch.autoSetupMerge configuration variable is set.
+--recurse-submodules::
+ THIS OPTION IS EXPERIMENTAL! Causes the current command to
+ recurse into submodules if `submodule.propagateBranches` is
+ enabled. See `submodule.propagateBranches` in
+ linkgit:git-config[1]. Currently, only branch creation is
+ supported.
++
+When used in branch creation, a new branch <branchname> will be created
+in the superproject and all of the submodules in the superproject's
+<start-point>. In submodules, the branch will point to the submodule
+commit in the superproject's <start-point> but the branch's tracking
+information will be set up based on the submodule's branches and remotes
+e.g. `git branch --recurse-submodules topic origin/main` will create the
+submodule branch "topic" that points to the submodule commit in the
+superproject's "origin/main", but tracks the submodule's "origin/main".
+
--set-upstream::
As this option had confusing syntax, it is no longer supported.
Please use `--track` or `--set-upstream-to` instead.
diff --git a/Documentation/git-check-ignore.txt b/Documentation/git-check-ignore.txt
index 0c3924a63d..2892799e32 100644
--- a/Documentation/git-check-ignore.txt
+++ b/Documentation/git-check-ignore.txt
@@ -33,7 +33,7 @@ OPTIONS
Instead of printing the paths that are excluded, for each path
that matches an exclude pattern, print the exclude pattern
together with the path. (Matching an exclude pattern usually
- means the path is excluded, but if the pattern begins with '!'
+ means the path is excluded, but if the pattern begins with "`!`"
then it is a negated pattern and matching it means the path is
NOT excluded.)
+
@@ -77,7 +77,7 @@ If `--verbose` is specified, the output is a series of lines of the form:
<pathname> is the path of a file being queried, <pattern> is the
matching pattern, <source> is the pattern's source file, and <linenum>
is the line number of the pattern within that source. If the pattern
-contained a `!` prefix or `/` suffix, it will be preserved in the
+contained a "`!`" prefix or "`/`" suffix, it will be preserved in the
output. <source> will be an absolute path when referring to the file
configured by `core.excludesFile`, or relative to the repository root
when referring to `.git/info/exclude` or a per-directory exclude file.
diff --git a/Documentation/git-checkout-index.txt b/Documentation/git-checkout-index.txt
index 4d33e7be0f..01dbd5cbf5 100644
--- a/Documentation/git-checkout-index.txt
+++ b/Documentation/git-checkout-index.txt
@@ -12,6 +12,7 @@ SYNOPSIS
'git checkout-index' [-u] [-q] [-a] [-f] [-n] [--prefix=<string>]
[--stage=<number>|all]
[--temp]
+ [--ignore-skip-worktree-bits]
[-z] [--stdin]
[--] [<file>...]
@@ -37,8 +38,9 @@ OPTIONS
-a::
--all::
- checks out all files in the index. Cannot be used
- together with explicit filenames.
+ checks out all files in the index except for those with the
+ skip-worktree bit set (see `--ignore-skip-worktree-bits`).
+ Cannot be used together with explicit filenames.
-n::
--no-create::
@@ -59,6 +61,10 @@ OPTIONS
write the content to temporary files. The temporary name
associations will be written to stdout.
+--ignore-skip-worktree-bits::
+ Check out all files, including those with the skip-worktree bit
+ set.
+
--stdin::
Instead of taking list of paths from the command line,
read list of paths from the standard input. Paths are
diff --git a/Documentation/git-mktree.txt b/Documentation/git-mktree.txt
index 27fe2b32e1..76b44f4da1 100644
--- a/Documentation/git-mktree.txt
+++ b/Documentation/git-mktree.txt
@@ -31,7 +31,7 @@ OPTIONS
--batch::
Allow building of more than one tree object before exiting. Each
- tree is separated by as single blank line. The final new-line is
+ tree is separated by a single blank line. The final new-line is
optional. Note - if the `-z` option is used, lines are terminated
with NUL.
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index 83fd4e19a4..60984a4682 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -160,11 +160,12 @@ unspecified.
^^^^^
This attribute sets a specific line-ending style to be used in the
-working directory. It enables end-of-line conversion without any
-content checks, effectively setting the `text` attribute. Note that
-setting this attribute on paths which are in the index with CRLF line
-endings may make the paths to be considered dirty. Adding the path to
-the index again will normalize the line endings in the index.
+working directory. This attribute has effect only if the `text`
+attribute is set or unspecified, or if it is set to `auto` and the file
+is detected as text. Note that setting this attribute on paths which
+are in the index with CRLF line endings may make the paths to be
+considered dirty. Adding the path to the index again will normalize the
+line endings in the index.
Set to string value "crlf"::
diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt
index c077971335..aa2f41f5e7 100644
--- a/Documentation/glossary-content.txt
+++ b/Documentation/glossary-content.txt
@@ -312,7 +312,7 @@ Pathspecs are used on the command line of "git ls-files", "git
ls-tree", "git add", "git grep", "git diff", "git checkout",
and many other commands to
limit the scope of operations to some subset of the tree or
-worktree. See the documentation of each command for whether
+working tree. See the documentation of each command for whether
paths are relative to the current directory or toplevel. The
pathspec syntax is as follows:
+
@@ -446,7 +446,7 @@ exclude;;
interface than the <<def_plumbing,plumbing>>.
[[def_per_worktree_ref]]per-worktree ref::
- Refs that are per-<<def_working_tree,worktree>>, rather than
+ Refs that are per-<<def_worktree,worktree>>, rather than
global. This is presently only <<def_HEAD,HEAD>> and any refs
that start with `refs/bisect/`, but might later include other
unusual refs.
@@ -669,3 +669,12 @@ The most notable example is `HEAD`.
The tree of actual checked out files. The working tree normally
contains the contents of the <<def_HEAD,HEAD>> commit's tree,
plus any local changes that you have made but not yet committed.
+
+[[def_worktree]]worktree::
+ A repository can have zero (i.e. bare repository) or one or
+ more worktrees attached to it. One "worktree" consists of a
+ "working tree" and repository metadata, most of which are
+ shared among other worktrees of a single repository, and
+ some of which are maintained separately per worktree
+ (e.g. the index, HEAD and pseudorefs like MERGE_HEAD,
+ per-worktree refs and per-worktree configuration file).
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index 43a86fa562..fd4f4e26c9 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -122,19 +122,27 @@ again. Equivalent forms are `--min-parents=0` (any commit has 0 or more
parents) and `--max-parents=-1` (negative numbers denote no upper limit).
--first-parent::
- Follow only the first parent commit upon seeing a merge
- commit. This option can give a better overview when
- viewing the evolution of a particular topic branch,
- because merges into a topic branch tend to be only about
- adjusting to updated upstream from time to time, and
- this option allows you to ignore the individual commits
- brought in to your history by such a merge.
+ When finding commits to include, follow only the first
+ parent commit upon seeing a merge commit. This option
+ can give a better overview when viewing the evolution of
+ a particular topic branch, because merges into a topic
+ branch tend to be only about adjusting to updated upstream
+ from time to time, and this option allows you to ignore
+ the individual commits brought in to your history by such
+ a merge.
ifdef::git-log[]
+
This option also changes default diff format for merge commits
to `first-parent`, see `--diff-merges=first-parent` for details.
endif::git-log[]
+--exclude-first-parent-only::
+ When finding commits to exclude (with a '{caret}'), follow only
+ the first parent commit upon seeing a merge commit.
+ This can be used to find the set of changes in a topic branch
+ from the point where it diverged from the remote branch, given
+ that arbitrary merges can be valid topic branch changes.
+
--not::
Reverses the meaning of the '{caret}' prefix (or lack thereof)
for all following revision specifiers, up to the next `--not`.
diff --git a/Documentation/technical/multi-pack-index.txt b/Documentation/technical/multi-pack-index.txt
index b39c69da8c..f2221d2b44 100644
--- a/Documentation/technical/multi-pack-index.txt
+++ b/Documentation/technical/multi-pack-index.txt
@@ -24,6 +24,7 @@ and their offsets into multiple packfiles. It contains:
** An offset within the jth packfile for the object.
* If large offsets are required, we use another list of large
offsets similar to version 2 pack-indexes.
+- An optional list of objects in pseudo-pack order (used with MIDX bitmaps).
Thus, we can provide O(log N) lookup time for any number
of packfiles.
diff --git a/Documentation/technical/pack-format.txt b/Documentation/technical/pack-format.txt
index 8d2f42f29e..6d3efb7d16 100644
--- a/Documentation/technical/pack-format.txt
+++ b/Documentation/technical/pack-format.txt
@@ -376,6 +376,11 @@ CHUNK DATA:
[Optional] Object Large Offsets (ID: {'L', 'O', 'F', 'F'})
8-byte offsets into large packfiles.
+ [Optional] Bitmap pack order (ID: {'R', 'I', 'D', 'X'})
+ A list of MIDX positions (one per object in the MIDX, num_objects in
+ total, each a 4-byte unsigned integer in network byte order), sorted
+ according to their relative bitmap/pseudo-pack positions.
+
TRAILER:
Index checksum of the above contents.
@@ -456,9 +461,5 @@ In short, a MIDX's pseudo-pack is the de-duplicated concatenation of
objects in packs stored by the MIDX, laid out in pack order, and the
packs arranged in MIDX order (with the preferred pack coming first).
-Finally, note that the MIDX's reverse index is not stored as a chunk in
-the multi-pack-index itself. This is done because the reverse index
-includes the checksum of the pack or MIDX to which it belongs, which
-makes it impossible to write in the MIDX. To avoid races when rewriting
-the MIDX, a MIDX reverse index includes the MIDX's checksum in its
-filename (e.g., `multi-pack-index-xyz.rev`).
+The MIDX's reverse index is stored in the optional 'RIDX' chunk within
+the MIDX itself.
diff --git a/Makefile b/Makefile
index 8e07003840..6f0b4b775f 100644
--- a/Makefile
+++ b/Makefile
@@ -234,6 +234,14 @@ all::
# Define NO_TRUSTABLE_FILEMODE if your filesystem may claim to support
# the executable mode bit, but doesn't really do so.
#
+# Define CSPRNG_METHOD to "arc4random" if your system has arc4random and
+# arc4random_buf, "libbsd" if your system has those functions from libbsd,
+# "getrandom" if your system has getrandom, "getentropy" if your system has
+# getentropy, "rtlgenrandom" for RtlGenRandom (Windows only), or "openssl" if
+# you'd want to use the OpenSSL CSPRNG. You may set multiple options with
+# spaces, in which case a suitable option will be chosen. If unset or set to
+# anything else, defaults to using "/dev/urandom".
+#
# Define NEEDS_MODE_TRANSLATION if your OS strays from the typical file type
# bits in mode values (e.g. z/OS defines I_SFMT to 0xFF000000 as opposed to the
# usual 0xF000).
@@ -256,8 +264,6 @@ all::
#
# Define NO_DEFLATE_BOUND if your zlib does not have deflateBound.
#
-# Define NO_UNCOMPRESS2 if your zlib does not have uncompress2.
-#
# Define NO_NORETURN if using buggy versions of gcc 4.6+ and profile feedback,
# as the compiler can crash (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=49299)
#
@@ -693,6 +699,7 @@ TEST_BUILTINS_OBJS += test-bloom.o
TEST_BUILTINS_OBJS += test-chmtime.o
TEST_BUILTINS_OBJS += test-config.o
TEST_BUILTINS_OBJS += test-crontab.o
+TEST_BUILTINS_OBJS += test-csprng.o
TEST_BUILTINS_OBJS += test-ctype.o
TEST_BUILTINS_OBJS += test-date.o
TEST_BUILTINS_OBJS += test-delta.o
@@ -862,6 +869,7 @@ LIB_OBJS += commit-reach.o
LIB_OBJS += commit.o
LIB_OBJS += compat/obstack.o
LIB_OBJS += compat/terminal.o
+LIB_OBJS += compat/zlib-uncompress2.o
LIB_OBJS += config.o
LIB_OBJS += connect.o
LIB_OBJS += connected.o
@@ -1195,7 +1203,8 @@ THIRD_PARTY_SOURCES += compat/regex/%
THIRD_PARTY_SOURCES += sha1collisiondetection/%
THIRD_PARTY_SOURCES += sha1dc/%
-GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB)
+# xdiff and reftable libs may in turn depend on what is in libgit.a
+GITLIBS = common-main.o $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(LIB_FILE)
EXTLIBS =
GIT_USER_AGENT = git/$(GIT_VERSION)
@@ -1727,11 +1736,6 @@ ifdef NO_DEFLATE_BOUND
BASIC_CFLAGS += -DNO_DEFLATE_BOUND
endif
-ifdef NO_UNCOMPRESS2
- BASIC_CFLAGS += -DNO_UNCOMPRESS2
- REFTABLE_OBJS += compat/zlib-uncompress2.o
-endif
-
ifdef NO_POSIX_GOODIES
BASIC_CFLAGS += -DNO_POSIX_GOODIES
endif
@@ -1909,6 +1913,31 @@ ifdef HAVE_GETDELIM
BASIC_CFLAGS += -DHAVE_GETDELIM
endif
+ifneq ($(findstring arc4random,$(CSPRNG_METHOD)),)
+ BASIC_CFLAGS += -DHAVE_ARC4RANDOM
+endif
+
+ifneq ($(findstring libbsd,$(CSPRNG_METHOD)),)
+ BASIC_CFLAGS += -DHAVE_ARC4RANDOM_LIBBSD
+ EXTLIBS += -lbsd
+endif
+
+ifneq ($(findstring getrandom,$(CSPRNG_METHOD)),)
+ BASIC_CFLAGS += -DHAVE_GETRANDOM
+endif
+
+ifneq ($(findstring getentropy,$(CSPRNG_METHOD)),)
+ BASIC_CFLAGS += -DHAVE_GETENTROPY
+endif
+
+ifneq ($(findstring rtlgenrandom,$(CSPRNG_METHOD)),)
+ BASIC_CFLAGS += -DHAVE_RTLGENRANDOM
+endif
+
+ifneq ($(findstring openssl,$(CSPRNG_METHOD)),)
+ BASIC_CFLAGS += -DHAVE_OPENSSL_CSPRNG
+endif
+
ifneq ($(PROCFS_EXECUTABLE_PATH),)
procfs_executable_path_SQ = $(subst ','\'',$(PROCFS_EXECUTABLE_PATH))
BASIC_CFLAGS += '-DPROCFS_EXECUTABLE_PATH="$(procfs_executable_path_SQ)"'
diff --git a/advice.c b/advice.c
index 1dfc91d176..e00d30254c 100644
--- a/advice.c
+++ b/advice.c
@@ -70,6 +70,7 @@ static struct {
[ADVICE_STATUS_HINTS] = { "statusHints", 1 },
[ADVICE_STATUS_U_OPTION] = { "statusUoption", 1 },
[ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { "submoduleAlternateErrorStrategyDie", 1 },
+ [ADVICE_SUBMODULES_NOT_UPDATED] = { "submodulesNotUpdated", 1 },
[ADVICE_UPDATE_SPARSE_PATH] = { "updateSparsePath", 1 },
[ADVICE_WAITING_FOR_EDITOR] = { "waitingForEditor", 1 },
};
diff --git a/advice.h b/advice.h
index 601265fd10..a7521d6087 100644
--- a/advice.h
+++ b/advice.h
@@ -44,6 +44,7 @@ struct string_list;
ADVICE_STATUS_HINTS,
ADVICE_STATUS_U_OPTION,
ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,
+ ADVICE_SUBMODULES_NOT_UPDATED,
ADVICE_UPDATE_SPARSE_PATH,
ADVICE_WAITING_FOR_EDITOR,
ADVICE_SKIPPED_CHERRY_PICKS,
diff --git a/apply.c b/apply.c
index f66d48ba2f..0912307bd9 100644
--- a/apply.c
+++ b/apply.c
@@ -3494,7 +3494,7 @@ static int three_way_merge(struct apply_state *state,
{
mmfile_t base_file, our_file, their_file;
mmbuffer_t result = { NULL };
- int status;
+ enum ll_merge_result status;
/* resolve trivial cases first */
if (oideq(base, ours))
@@ -3511,6 +3511,9 @@ static int three_way_merge(struct apply_state *state,
&their_file, "theirs",
state->repo->index,
NULL);
+ if (status == LL_MERGE_BINARY_CONFLICT)
+ warning("Cannot merge binary files: %s (%s vs. %s)",
+ path, "ours", "theirs");
free(base_file.ptr);
free(our_file.ptr);
free(their_file.ptr);
diff --git a/blame.c b/blame.c
index 206c295660..083d99fdbc 100644
--- a/blame.c
+++ b/blame.c
@@ -2615,7 +2615,7 @@ void assign_blame(struct blame_scoreboard *sb, int opt)
else {
commit->object.flags |= UNINTERESTING;
if (commit->object.parsed)
- mark_parents_uninteresting(commit);
+ mark_parents_uninteresting(sb->revs, commit);
}
/* treat root commit as boundary */
if (!commit->parents && !sb->show_root)
diff --git a/branch.c b/branch.c
index 5d20a2e848..6b31df539a 100644
--- a/branch.c
+++ b/branch.c
@@ -8,6 +8,8 @@
#include "sequencer.h"
#include "commit.h"
#include "worktree.h"
+#include "submodule-config.h"
+#include "run-command.h"
struct tracking {
struct refspec_item spec;
@@ -218,9 +220,11 @@ static int inherit_tracking(struct tracking *tracking, const char *orig_ref)
}
/*
- * This is called when new_ref is branched off of orig_ref, and tries
- * to infer the settings for branch.<new_ref>.{remote,merge} from the
- * config.
+ * Used internally to set the branch.<new_ref>.{remote,merge} config
+ * settings so that branch 'new_ref' tracks 'orig_ref'. Unlike
+ * dwim_and_setup_tracking(), this does not do DWIM, i.e. "origin/main"
+ * will not be expanded to "refs/remotes/origin/main", so it is not safe
+ * for 'orig_ref' to be raw user input.
*/
static void setup_tracking(const char *new_ref, const char *orig_ref,
enum branch_track track, int quiet)
@@ -235,7 +239,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
if (track != BRANCH_TRACK_INHERIT)
for_each_remote(find_tracked_branch, &tracking);
else if (inherit_tracking(&tracking, orig_ref))
- return;
+ goto cleanup;
if (!tracking.matches)
switch (track) {
@@ -245,7 +249,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
case BRANCH_TRACK_INHERIT:
break;
default:
- return;
+ goto cleanup;
}
if (tracking.matches > 1)
@@ -258,7 +262,8 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
tracking.remote, tracking.srcs) < 0)
exit(-1);
- string_list_clear(tracking.srcs, 0);
+cleanup:
+ string_list_clear(&tracking_srcs, 0);
}
int read_branch_desc(struct strbuf *buf, const char *branch_name)
@@ -346,31 +351,37 @@ N_("\n"
"will track its remote counterpart, you may want to use\n"
"\"git push -u\" to set the upstream config as you push.");
-void create_branch(struct repository *r,
- const char *name, const char *start_name,
- int force, int clobber_head_ok, int reflog,
- int quiet, enum branch_track track)
+/**
+ * DWIMs a user-provided ref to determine the starting point for a
+ * branch and validates it, where:
+ *
+ * - r is the repository to validate the branch for
+ *
+ * - start_name is the ref that we would like to test. This is
+ * expanded with DWIM and assigned to out_real_ref.
+ *
+ * - track is the tracking mode of the new branch. If tracking is
+ * explicitly requested, start_name must be a branch (because
+ * otherwise start_name cannot be tracked)
+ *
+ * - out_oid is an out parameter containing the object_id of start_name
+ *
+ * - out_real_ref is an out parameter containing the full, 'real' form
+ * of start_name e.g. refs/heads/main instead of main
+ *
+ */
+static void dwim_branch_start(struct repository *r, const char *start_name,
+ enum branch_track track, char **out_real_ref,
+ struct object_id *out_oid)
{
struct commit *commit;
struct object_id oid;
char *real_ref;
- struct strbuf ref = STRBUF_INIT;
- int forcing = 0;
- int dont_change_ref = 0;
int explicit_tracking = 0;
if (track == BRANCH_TRACK_EXPLICIT || track == BRANCH_TRACK_OVERRIDE)
explicit_tracking = 1;
- if ((track == BRANCH_TRACK_OVERRIDE || clobber_head_ok)
- ? validate_branchname(name, &ref)
- : validate_new_branchname(name, &ref, force)) {
- if (!force)
- dont_change_ref = 1;
- else
- forcing = 1;
- }
-
real_ref = NULL;
if (get_oid_mb(start_name, &oid)) {
if (explicit_tracking) {
@@ -407,40 +418,218 @@ void create_branch(struct repository *r,
if ((commit = lookup_commit_reference(r, &oid)) == NULL)
die(_("not a valid branch point: '%s'"), start_name);
- oidcpy(&oid, &commit->object.oid);
+ if (out_real_ref) {
+ *out_real_ref = real_ref;
+ real_ref = NULL;
+ }
+ if (out_oid)
+ oidcpy(out_oid, &commit->object.oid);
+
+ FREE_AND_NULL(real_ref);
+}
+
+void create_branch(struct repository *r,
+ const char *name, const char *start_name,
+ int force, int clobber_head_ok, int reflog,
+ int quiet, enum branch_track track, int dry_run)
+{
+ struct object_id oid;
+ char *real_ref;
+ struct strbuf ref = STRBUF_INIT;
+ int forcing = 0;
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
+ char *msg;
+
+ if (track == BRANCH_TRACK_OVERRIDE)
+ BUG("'track' cannot be BRANCH_TRACK_OVERRIDE. Did you mean to call dwim_and_setup_tracking()?");
+ if (clobber_head_ok && !force)
+ BUG("'clobber_head_ok' can only be used with 'force'");
+
+ if (clobber_head_ok ?
+ validate_branchname(name, &ref) :
+ validate_new_branchname(name, &ref, force)) {
+ forcing = 1;
+ }
+
+ dwim_branch_start(r, start_name, track, &real_ref, &oid);
+ if (dry_run)
+ goto cleanup;
if (reflog)
log_all_ref_updates = LOG_REFS_NORMAL;
- if (!dont_change_ref) {
- struct ref_transaction *transaction;
- struct strbuf err = STRBUF_INIT;
- char *msg;
-
- if (forcing)
- msg = xstrfmt("branch: Reset to %s", start_name);
- else
- msg = xstrfmt("branch: Created from %s", start_name);
-
- transaction = ref_transaction_begin(&err);
- if (!transaction ||
- ref_transaction_update(transaction, ref.buf,
- &oid, forcing ? NULL : null_oid(),
- 0, msg, &err) ||
- ref_transaction_commit(transaction, &err))
- die("%s", err.buf);
- ref_transaction_free(transaction);
- strbuf_release(&err);
- free(msg);
- }
+ if (forcing)
+ msg = xstrfmt("branch: Reset to %s", start_name);
+ else
+ msg = xstrfmt("branch: Created from %s", start_name);
+ transaction = ref_transaction_begin(&err);
+ if (!transaction ||
+ ref_transaction_update(transaction, ref.buf,
+ &oid, forcing ? NULL : null_oid(),
+ 0, msg, &err) ||
+ ref_transaction_commit(transaction, &err))
+ die("%s", err.buf);
+ ref_transaction_free(transaction);
+ strbuf_release(&err);
+ free(msg);
if (real_ref && track)
setup_tracking(ref.buf + 11, real_ref, track, quiet);
+cleanup:
strbuf_release(&ref);
free(real_ref);
}
+void dwim_and_setup_tracking(struct repository *r, const char *new_ref,
+ const char *orig_ref, enum branch_track track,
+ int quiet)
+{
+ char *real_orig_ref;
+ dwim_branch_start(r, orig_ref, track, &real_orig_ref, NULL);
+ setup_tracking(new_ref, real_orig_ref, track, quiet);
+}
+
+/**
+ * Creates a branch in a submodule by calling
+ * create_branches_recursively() in a child process. The child process
+ * is necessary because install_branch_config_multiple_remotes() (which
+ * is called by setup_tracking()) does not support writing configs to
+ * submodules.
+ */
+static int submodule_create_branch(struct repository *r,
+ const struct submodule *submodule,
+ const char *name, const char *start_oid,
+ const char *tracking_name, int force,
+ int reflog, int quiet,
+ enum branch_track track, int dry_run)
+{
+ int ret = 0;
+ struct child_process child = CHILD_PROCESS_INIT;
+ struct strbuf child_err = STRBUF_INIT;
+ struct strbuf out_buf = STRBUF_INIT;
+ char *out_prefix = xstrfmt("submodule '%s': ", submodule->name);
+ child.git_cmd = 1;
+ child.err = -1;
+ child.stdout_to_stderr = 1;
+
+ prepare_other_repo_env(&child.env_array, r->gitdir);
+ /*
+ * submodule_create_branch() is indirectly invoked by "git
+ * branch", but we cannot invoke "git branch" in the child
+ * process. "git branch" accepts a branch name and start point,
+ * where the start point is assumed to provide both the OID
+ * (start_oid) and the branch to use for tracking
+ * (tracking_name). But when recursing through submodules,
+ * start_oid and tracking name need to be specified separately
+ * (see create_branches_recursively()).
+ */
+ strvec_pushl(&child.args, "submodule--helper", "create-branch", NULL);
+ if (dry_run)
+ strvec_push(&child.args, "--dry-run");
+ if (force)
+ strvec_push(&child.args, "--force");
+ if (quiet)
+ strvec_push(&child.args, "--quiet");
+ if (reflog)
+ strvec_push(&child.args, "--create-reflog");
+ if (track == BRANCH_TRACK_ALWAYS || track == BRANCH_TRACK_EXPLICIT)
+ strvec_push(&child.args, "--track");
+
+ strvec_pushl(&child.args, name, start_oid, tracking_name, NULL);
+
+ if ((ret = start_command(&child)))
+ return ret;
+ ret = finish_command(&child);
+ strbuf_read(&child_err, child.err, 0);
+ strbuf_add_lines(&out_buf, out_prefix, child_err.buf, child_err.len);
+
+ if (ret)
+ fprintf(stderr, "%s", out_buf.buf);
+ else
+ printf("%s", out_buf.buf);
+
+ strbuf_release(&child_err);
+ strbuf_release(&out_buf);
+ return ret;
+}
+
+void create_branches_recursively(struct repository *r, const char *name,
+ const char *start_commitish,
+ const char *tracking_name, int force,
+ int reflog, int quiet, enum branch_track track,
+ int dry_run)
+{
+ int i = 0;
+ char *branch_point = NULL;
+ struct object_id super_oid;
+ struct submodule_entry_list submodule_entry_list;
+
+ /* Perform dwim on start_commitish to get super_oid and branch_point. */
+ dwim_branch_start(r, start_commitish, BRANCH_TRACK_NEVER,
+ &branch_point, &super_oid);
+
+ /*
+ * If we were not given an explicit name to track, then assume we are at
+ * the top level and, just like the non-recursive case, the tracking
+ * name is the branch point.
+ */
+ if (!tracking_name)
+ tracking_name = branch_point;
+
+ submodules_of_tree(r, &super_oid, &submodule_entry_list);
+ /*
+ * Before creating any branches, first check that the branch can
+ * be created in every submodule.
+ */
+ for (i = 0; i < submodule_entry_list.entry_nr; i++) {
+ if (submodule_entry_list.entries[i].repo == NULL) {
+ if (advice_enabled(ADVICE_SUBMODULES_NOT_UPDATED))
+ advise(_("You may try updating the submodules using 'git checkout %s && git submodule update --init'"),
+ start_commitish);
+ die(_("submodule '%s': unable to find submodule"),
+ submodule_entry_list.entries[i].submodule->name);
+ }
+
+ if (submodule_create_branch(
+ submodule_entry_list.entries[i].repo,
+ submodule_entry_list.entries[i].submodule, name,
+ oid_to_hex(&submodule_entry_list.entries[i]
+ .name_entry->oid),
+ tracking_name, force, reflog, quiet, track, 1))
+ die(_("submodule '%s': cannot create branch '%s'"),
+ submodule_entry_list.entries[i].submodule->name,
+ name);
+ }
+
+ create_branch(the_repository, name, start_commitish, force, 0, reflog, quiet,
+ BRANCH_TRACK_NEVER, dry_run);
+ if (dry_run)
+ return;
+ /*
+ * NEEDSWORK If tracking was set up in the superproject but not the
+ * submodule, users might expect "git branch --recurse-submodules" to
+ * fail or give a warning, but this is not yet implemented because it is
+ * tedious to determine whether or not tracking was set up in the
+ * superproject.
+ */
+ setup_tracking(name, tracking_name, track, quiet);
+
+ for (i = 0; i < submodule_entry_list.entry_nr; i++) {
+ if (submodule_create_branch(
+ submodule_entry_list.entries[i].repo,
+ submodule_entry_list.entries[i].submodule, name,
+ oid_to_hex(&submodule_entry_list.entries[i]
+ .name_entry->oid),
+ tracking_name, force, reflog, quiet, track, 0))
+ die(_("submodule '%s': cannot create branch '%s'"),
+ submodule_entry_list.entries[i].submodule->name,
+ name);
+ repo_clear(submodule_entry_list.entries[i].repo);
+ }
+}
+
void remove_merge_branch_state(struct repository *r)
{
unlink(git_path_merge_head(r));
diff --git a/branch.h b/branch.h
index 815dcd40c0..04df2aa5b5 100644
--- a/branch.h
+++ b/branch.h
@@ -18,6 +18,28 @@ extern enum branch_track git_branch_track;
/* Functions for acting on the information about branches. */
+/**
+ * Sets branch.<new_ref>.{remote,merge} config settings such that
+ * new_ref tracks orig_ref according to the specified tracking mode.
+ *
+ * - new_ref is the name of the branch that we are setting tracking
+ * for.
+ *
+ * - orig_ref is the name of the ref that is 'upstream' of new_ref.
+ * orig_ref will be expanded with DWIM so that the config settings
+ * are in the correct format e.g. "refs/remotes/origin/main" instead
+ * of "origin/main".
+ *
+ * - track is the tracking mode e.g. BRANCH_TRACK_REMOTE causes
+ * new_ref to track orig_ref directly, whereas BRANCH_TRACK_INHERIT
+ * causes new_ref to track whatever orig_ref tracks.
+ *
+ * - quiet suppresses tracking information.
+ */
+void dwim_and_setup_tracking(struct repository *r, const char *new_ref,
+ const char *orig_ref, enum branch_track track,
+ int quiet);
+
/*
* Creates a new branch, where:
*
@@ -30,8 +52,8 @@ extern enum branch_track git_branch_track;
*
* - force enables overwriting an existing (non-head) branch
*
- * - clobber_head_ok allows the currently checked out (hence existing)
- * branch to be overwritten; without 'force', it has no effect.
+ * - clobber_head_ok, when enabled with 'force', allows the currently
+ * checked out (head) branch to be overwritten
*
* - reflog creates a reflog for the branch
*
@@ -40,13 +62,45 @@ extern enum branch_track git_branch_track;
* - track causes the new branch to be configured to merge the remote branch
* that start_name is a tracking branch for (if any).
*
+ * - dry_run causes the branch to be validated but not created.
+ *
*/
void create_branch(struct repository *r,
const char *name, const char *start_name,
int force, int clobber_head_ok,
- int reflog, int quiet, enum branch_track track);
+ int reflog, int quiet, enum branch_track track,
+ int dry_run);
/*
+ * Creates a new branch in a repository and its submodules (and its
+ * submodules, recursively). The parameters are mostly analogous to
+ * those of create_branch() except for start_name, which is represented
+ * by two different parameters:
+ *
+ * - start_commitish is the commit-ish, in repository r, that determines
+ * which commits the branches will point to. The superproject branch
+ * will point to the commit of start_commitish and the submodule
+ * branches will point to the gitlink commit oids in start_commitish's
+ * tree.
+ *
+ * - tracking_name is the name of the ref, in repository r, that will be
+ * used to set up tracking information. This value is propagated to
+ * all submodules, which will evaluate the ref using their own ref
+ * stores. If NULL, this defaults to start_commitish.
+ *
+ * When this function is called on the superproject, start_commitish
+ * can be any user-provided ref and tracking_name can be NULL (similar
+ * to create_branches()). But when recursing through submodules,
+ * start_commitish is the plain gitlink commit oid. Since the oid cannot
+ * be used for tracking information, tracking_name is propagated and
+ * used for tracking instead.
+ */
+void create_branches_recursively(struct repository *r, const char *name,
+ const char *start_commitish,
+ const char *tracking_name, int force,
+ int reflog, int quiet, enum branch_track track,
+ int dry_run);
+/*
* Check if 'name' can be a valid name for a branch; die otherwise.
* Return 1 if the named branch already exists; return 0 otherwise.
* Fill ref with the full refname for the branch.
diff --git a/builtin/add.c b/builtin/add.c
index 84dff3e796..3ffb86a433 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -32,7 +32,6 @@ static int add_renormalize;
static int pathspec_file_nul;
static int include_sparse;
static const char *pathspec_from_file;
-static int legacy_stash_p; /* support for the scripted `git stash` */
struct update_callback_data {
int flags;
@@ -388,8 +387,6 @@ static struct option builtin_add_options[] = {
N_("override the executable bit of the listed files")),
OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo,
N_("warn when adding an embedded repository")),
- OPT_HIDDEN_BOOL(0, "legacy-stash-p", &legacy_stash_p,
- N_("backend for `git stash -p`")),
OPT_PATHSPEC_FROM_FILE(&pathspec_from_file),
OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul),
OPT_END(),
@@ -512,17 +509,6 @@ int cmd_add(int argc, const char **argv, const char *prefix)
die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--interactive/--patch");
exit(interactive_add(argv + 1, prefix, patch_interactive));
}
- if (legacy_stash_p) {
- struct pathspec pathspec;
-
- parse_pathspec(&pathspec, 0,
- PATHSPEC_PREFER_FULL |
- PATHSPEC_SYMLINK_LEADING_PATH |
- PATHSPEC_PREFIX_ORIGIN,
- prefix, argv);
-
- return run_add_interactive(NULL, "--patch=stash", &pathspec);
- }
if (edit_interactive) {
if (pathspec_from_file)
diff --git a/builtin/branch.c b/builtin/branch.c
index 4ce2a24754..5d00d0b8d3 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -27,7 +27,8 @@
static const char * const builtin_branch_usage[] = {
N_("git branch [<options>] [-r | -a] [--merged] [--no-merged]"),
- N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"),
+ N_("git branch [<options>] [-f] [--recurse-submodules] <branch-name> [<start-point>]"),
+ N_("git branch [<options>] [-l] [<pattern>...]"),
N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."),
N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"),
N_("git branch [<options>] (-c | -C) [<old-branch>] <new-branch>"),
@@ -38,6 +39,8 @@ static const char * const builtin_branch_usage[] = {
static const char *head;
static struct object_id head_oid;
+static int recurse_submodules = 0;
+static int submodule_propagate_branches = 0;
static int branch_use_color = -1;
static char branch_colors[][COLOR_MAXLEN] = {
@@ -99,6 +102,15 @@ static int git_branch_config(const char *var, const char *value, void *cb)
return config_error_nonbool(var);
return color_parse(value, branch_colors[slot]);
}
+ if (!strcmp(var, "submodule.recurse")) {
+ recurse_submodules = git_config_bool(var, value);
+ return 0;
+ }
+ if (!strcasecmp(var, "submodule.propagateBranches")) {
+ submodule_propagate_branches = git_config_bool(var, value);
+ return 0;
+ }
+
return git_color_default_config(var, value, cb);
}
@@ -621,14 +633,16 @@ static int edit_branch_description(const char *branch_name)
int cmd_branch(int argc, const char **argv, const char *prefix)
{
- int delete = 0, rename = 0, copy = 0, force = 0, list = 0;
- int show_current = 0;
- int reflog = 0, edit_description = 0;
- int quiet = 0, unset_upstream = 0;
+ /* possible actions */
+ int delete = 0, rename = 0, copy = 0, list = 0,
+ unset_upstream = 0, show_current = 0, edit_description = 0;
const char *new_upstream = NULL;
+ int noncreate_actions = 0;
+ /* possible options */
+ int reflog = 0, quiet = 0, icase = 0, force = 0,
+ recurse_submodules_explicit = 0;
enum branch_track track;
struct ref_filter filter;
- int icase = 0;
static struct ref_sorting *sorting;
struct string_list sorting_options = STRING_LIST_INIT_DUP;
struct ref_format format = REF_FORMAT_INIT;
@@ -677,6 +691,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
OPT_CALLBACK(0, "points-at", &filter.points_at, N_("object"),
N_("print only branches of the object"), parse_opt_object_name),
OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
+ OPT_BOOL(0, "recurse-submodules", &recurse_submodules_explicit, N_("recurse through submodules")),
OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")),
OPT_END(),
};
@@ -713,10 +728,23 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
filter.reachable_from || filter.unreachable_from || filter.points_at.nr)
list = 1;
- if (!!delete + !!rename + !!copy + !!new_upstream + !!show_current +
- list + edit_description + unset_upstream > 1)
+ noncreate_actions = !!delete + !!rename + !!copy + !!new_upstream +
+ !!show_current + !!list + !!edit_description +
+ !!unset_upstream;
+ if (noncreate_actions > 1)
usage_with_options(builtin_branch_usage, options);
+ if (recurse_submodules_explicit) {
+ if (!submodule_propagate_branches)
+ die(_("branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled"));
+ if (noncreate_actions)
+ die(_("--recurse-submodules can only be used to create branches"));
+ }
+
+ recurse_submodules =
+ (recurse_submodules || recurse_submodules_explicit) &&
+ submodule_propagate_branches;
+
if (filter.abbrev == -1)
filter.abbrev = DEFAULT_ABBREV;
filter.ignore_case = icase;
@@ -828,12 +856,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
if (!ref_exists(branch->refname))
die(_("branch '%s' does not exist"), branch->name);
- /*
- * create_branch takes care of setting up the tracking
- * info and making sure new_upstream is correct
- */
- create_branch(the_repository, branch->name, new_upstream,
- 0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE);
+ dwim_and_setup_tracking(the_repository, branch->name,
+ new_upstream, BRANCH_TRACK_OVERRIDE,
+ quiet);
} else if (unset_upstream) {
struct branch *branch = branch_get(argv[0]);
struct strbuf buf = STRBUF_INIT;
@@ -857,7 +882,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
strbuf_addf(&buf, "branch.%s.merge", branch->name);
git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);
strbuf_release(&buf);
- } else if (argc > 0 && argc <= 2) {
+ } else if (!noncreate_actions && argc > 0 && argc <= 2) {
+ const char *branch_name = argv[0];
+ const char *start_name = argc == 2 ? argv[1] : head;
+
if (filter.kind != FILTER_REFS_BRANCHES)
die(_("The -a, and -r, options to 'git branch' do not take a branch name.\n"
"Did you mean to use: -a|-r --list <pattern>?"));
@@ -865,10 +893,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
if (track == BRANCH_TRACK_OVERRIDE)
die(_("the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead."));
- create_branch(the_repository,
- argv[0], (argc == 2) ? argv[1] : head,
- force, 0, reflog, quiet, track);
-
+ if (recurse_submodules) {
+ create_branches_recursively(the_repository, branch_name,
+ start_name, NULL, force,
+ reflog, quiet, track, 0);
+ return 0;
+ }
+ create_branch(the_repository, branch_name, start_name, force, 0,
+ reflog, quiet, track, 0);
} else
usage_with_options(builtin_branch_usage, options);
diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c
index e21620d964..97e06e8c52 100644
--- a/builtin/checkout-index.c
+++ b/builtin/checkout-index.c
@@ -7,6 +7,7 @@
#define USE_THE_INDEX_COMPATIBILITY_MACROS
#include "builtin.h"
#include "config.h"
+#include "dir.h"
#include "lockfile.h"
#include "quote.h"
#include "cache-tree.h"
@@ -17,6 +18,7 @@
#define CHECKOUT_ALL 4
static int nul_term_line;
static int checkout_stage; /* default to checkout stage0 */
+static int ignore_skip_worktree; /* default to 0 */
static int to_tempfile;
static char topath[4][TEMPORARY_FILENAME_LENGTH + 1];
@@ -65,6 +67,8 @@ static int checkout_file(const char *name, const char *prefix)
int namelen = strlen(name);
int pos = cache_name_pos(name, namelen);
int has_same_name = 0;
+ int is_file = 0;
+ int is_skipped = 1;
int did_checkout = 0;
int errs = 0;
@@ -78,6 +82,12 @@ static int checkout_file(const char *name, const char *prefix)
break;
has_same_name = 1;
pos++;
+ if (S_ISSPARSEDIR(ce->ce_mode))
+ break;
+ is_file = 1;
+ if (!ignore_skip_worktree && ce_skip_worktree(ce))
+ break;
+ is_skipped = 0;
if (ce_stage(ce) != checkout_stage
&& (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
continue;
@@ -106,6 +116,11 @@ static int checkout_file(const char *name, const char *prefix)
fprintf(stderr, "git checkout-index: %s ", name);
if (!has_same_name)
fprintf(stderr, "is not in the cache");
+ else if (!is_file)
+ fprintf(stderr, "is a sparse directory");
+ else if (is_skipped)
+ fprintf(stderr, "has skip-worktree enabled; "
+ "use '--ignore-skip-worktree-bits' to checkout");
else if (checkout_stage)
fprintf(stderr, "does not exist at stage %d",
checkout_stage);
@@ -121,10 +136,27 @@ static int checkout_all(const char *prefix, int prefix_length)
int i, errs = 0;
struct cache_entry *last_ce = NULL;
- /* TODO: audit for interaction with sparse-index. */
- ensure_full_index(&the_index);
for (i = 0; i < active_nr ; i++) {
struct cache_entry *ce = active_cache[i];
+
+ if (S_ISSPARSEDIR(ce->ce_mode)) {
+ if (!ce_skip_worktree(ce))
+ BUG("sparse directory '%s' does not have skip-worktree set", ce->name);
+
+ /*
+ * If the current entry is a sparse directory and skip-worktree
+ * entries are being checked out, expand the index and continue
+ * the loop on the current index position (now pointing to the
+ * first entry inside the expanded sparse directory).
+ */
+ if (ignore_skip_worktree) {
+ ensure_full_index(&the_index);
+ ce = active_cache[i];
+ }
+ }
+
+ if (!ignore_skip_worktree && ce_skip_worktree(ce))
+ continue;
if (ce_stage(ce) != checkout_stage
&& (CHECKOUT_ALL != checkout_stage || !ce_stage(ce)))
continue;
@@ -185,6 +217,8 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
struct option builtin_checkout_index_options[] = {
OPT_BOOL('a', "all", &all,
N_("check out all files in the index")),
+ OPT_BOOL(0, "ignore-skip-worktree-bits", &ignore_skip_worktree,
+ N_("do not skip files with skip-worktree set")),
OPT__FORCE(&force, N_("force overwrite of existing files"), 0),
OPT__QUIET(&quiet,
N_("no warning for existing files and files not in index")),
@@ -212,6 +246,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
git_config(git_default_config, NULL);
prefix_length = prefix ? strlen(prefix) : 0;
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+
if (read_cache() < 0) {
die("invalid cache");
}
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 8f010412a9..d9b31bbb6d 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -246,6 +246,7 @@ static int checkout_merged(int pos, const struct checkout *state,
struct cache_entry *ce = active_cache[pos];
const char *path = ce->name;
mmfile_t ancestor, ours, theirs;
+ enum ll_merge_result merge_status;
int status;
struct object_id oid;
mmbuffer_t result_buf;
@@ -276,13 +277,16 @@ static int checkout_merged(int pos, const struct checkout *state,
memset(&ll_opts, 0, sizeof(ll_opts));
git_config_get_bool("merge.renormalize", &renormalize);
ll_opts.renormalize = renormalize;
- status = ll_merge(&result_buf, path, &ancestor, "base",
- &ours, "ours", &theirs, "theirs",
- state->istate, &ll_opts);
+ merge_status = ll_merge(&result_buf, path, &ancestor, "base",
+ &ours, "ours", &theirs, "theirs",
+ state->istate, &ll_opts);
free(ancestor.ptr);
free(ours.ptr);
free(theirs.ptr);
- if (status < 0 || !result_buf.ptr) {
+ if (merge_status == LL_MERGE_BINARY_CONFLICT)
+ warning("Cannot merge binary files: %s (%s vs. %s)",
+ path, "ours", "theirs");
+ if (merge_status < 0 || !result_buf.ptr) {
free(result_buf.ptr);
return error(_("path '%s': cannot merge"), path);
}
@@ -905,7 +909,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
opts->new_branch_force ? 1 : 0,
opts->new_branch_log,
opts->quiet,
- opts->track);
+ opts->track,
+ 0);
free(new_branch_info->name);
free(new_branch_info->refname);
new_branch_info->name = xstrdup(opts->new_branch);
@@ -1603,9 +1608,10 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
opts->show_progress = -1;
git_config(git_checkout_config, opts);
-
- prepare_repo_settings(the_repository);
- the_repository->settings.command_requires_full_index = 0;
+ if (the_repository->gitdir) {
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+ }
opts->track = BRANCH_TRACK_UNSPECIFIED;
diff --git a/builtin/clean.c b/builtin/clean.c
index 3ff02bbbff..5466636e66 100644
--- a/builtin/clean.c
+++ b/builtin/clean.c
@@ -1009,6 +1009,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
dir.flags |= DIR_KEEP_UNTRACKED_CONTENTS;
}
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+
if (read_cache() < 0)
die(_("index file corrupt"));
diff --git a/builtin/clone.c b/builtin/clone.c
index 9c29093b35..0d80b135c9 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1235,7 +1235,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
}
else {
const char *branch;
- char *ref;
+ const char *ref;
+ char *ref_free = NULL;
if (option_branch)
die(_("Remote branch %s not found in upstream %s"),
@@ -1251,17 +1252,16 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
skip_prefix(transport_ls_refs_options.unborn_head_target,
"refs/heads/", &branch)) {
ref = transport_ls_refs_options.unborn_head_target;
- transport_ls_refs_options.unborn_head_target = NULL;
create_symref("HEAD", ref, reflog_msg.buf);
} else {
branch = git_default_branch_name(0);
- ref = xstrfmt("refs/heads/%s", branch);
+ ref_free = xstrfmt("refs/heads/%s", branch);
+ ref = ref_free;
}
if (!option_bare)
install_branch_config(0, branch, remote_name, ref);
-
- free(ref);
+ free(ref_free);
}
write_refspec_config(src_ref_prefix, our_head_points_at,
@@ -1313,7 +1313,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
UNLEAK(repo);
junk_mode = JUNK_LEAVE_ALL;
- strvec_clear(&transport_ls_refs_options.ref_prefixes);
- free(transport_ls_refs_options.unborn_head_target);
+ transport_ls_refs_options_release(&transport_ls_refs_options);
return err;
}
diff --git a/builtin/diff.c b/builtin/diff.c
index fa4683377e..bb7fafca61 100644
--- a/builtin/diff.c
+++ b/builtin/diff.c
@@ -28,9 +28,9 @@ static const char builtin_diff_usage[] =
"git diff [<options>] [<commit>] [--] [<path>...]\n"
" or: git diff [<options>] --cached [--merge-base] [<commit>] [--] [<path>...]\n"
" or: git diff [<options>] [--merge-base] <commit> [<commit>...] <commit> [--] [<path>...]\n"
-" or: git diff [<options>] <commit>...<commit>] [--] [<path>...]\n"
-" or: git diff [<options>] <blob> <blob>]\n"
-" or: git diff [<options>] --no-index [--] <path> <path>]\n"
+" or: git diff [<options>] <commit>...<commit> [--] [<path>...]\n"
+" or: git diff [<options>] <blob> <blob>\n"
+" or: git diff [<options>] --no-index [--] <path> <path>\n"
COMMON_DIFF_OPTIONS_HELP;
static const char *blob_path(struct object_array_entry *entry)
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 5b3b18a72f..79ee959185 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -1094,12 +1094,15 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
struct ref *rm;
char *url;
int want_status;
- int summary_width = transport_summary_width(ref_map);
+ int summary_width = 0;
rc = open_fetch_head(&fetch_head);
if (rc)
return -1;
+ if (verbosity >= 0)
+ summary_width = transport_summary_width(ref_map);
+
if (raw_url)
url = transport_anonymize_url(raw_url);
else
@@ -1345,7 +1348,6 @@ static int prune_refs(struct refspec *rs, struct ref *ref_map,
int url_len, i, result = 0;
struct ref *ref, *stale_refs = get_stale_heads(rs, ref_map);
char *url;
- int summary_width = transport_summary_width(stale_refs);
const char *dangling_msg = dry_run
? _(" (%s will become dangling)")
: _(" (%s has become dangling)");
@@ -1374,6 +1376,8 @@ static int prune_refs(struct refspec *rs, struct ref *ref_map,
}
if (verbosity >= 0) {
+ int summary_width = transport_summary_width(stale_refs);
+
for (ref = stale_refs; ref; ref = ref->next) {
struct strbuf sb = STRBUF_INIT;
if (!shown_url) {
@@ -1594,7 +1598,7 @@ static int do_fetch(struct transport *transport,
} else
remote_refs = NULL;
- strvec_clear(&transport_ls_refs_options.ref_prefixes);
+ transport_ls_refs_options_release(&transport_ls_refs_options);
ref_map = get_ref_map(transport->remote, remote_refs, rs,
tags, &autotags);
@@ -1610,12 +1614,14 @@ static int do_fetch(struct transport *transport,
* don't care whether --tags was specified.
*/
if (rs->nr) {
- prune_refs(rs, ref_map, transport->url);
+ retcode = prune_refs(rs, ref_map, transport->url);
} else {
- prune_refs(&transport->remote->fetch,
- ref_map,
- transport->url);
+ retcode = prune_refs(&transport->remote->fetch,
+ ref_map,
+ transport->url);
}
+ if (retcode != 0)
+ retcode = 1;
}
if (fetch_and_consume_refs(transport, ref_map, worktrees)) {
free_refs(ref_map);
@@ -2015,8 +2021,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
}
git_config(git_fetch_config, NULL);
- prepare_repo_settings(the_repository);
- the_repository->settings.command_requires_full_index = 0;
+ if (the_repository->gitdir) {
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+ }
argc = parse_options(argc, argv, prefix,
builtin_fetch_options, builtin_fetch_usage, 0);
@@ -2052,7 +2060,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
}
if (negotiate_only && !negotiation_tip.nr)
- die(_("--negotiate-only needs one or more --negotiate-tip=*"));
+ die(_("--negotiate-only needs one or more --negotiation-tip=*"));
if (deepen_relative) {
if (deepen_relative < 0)
diff --git a/builtin/hash-object.c b/builtin/hash-object.c
index c7b3ad74c6..db9b253527 100644
--- a/builtin/hash-object.c
+++ b/builtin/hash-object.c
@@ -92,6 +92,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix)
int nongit = 0;
unsigned flags = HASH_FORMAT_CHECK;
const char *vpath = NULL;
+ char *vpath_free = NULL;
const struct option hash_object_options[] = {
OPT_STRING('t', NULL, &type, N_("type"), N_("object type")),
OPT_BIT('w', NULL, &flags, N_("write the object into the object database"),
@@ -114,8 +115,10 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix)
else
prefix = setup_git_directory_gently(&nongit);
- if (vpath && prefix)
- vpath = prefix_filename(prefix, vpath);
+ if (vpath && prefix) {
+ vpath_free = prefix_filename(prefix, vpath);
+ vpath = vpath_free;
+ }
git_config(git_default_config, NULL);
@@ -156,5 +159,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix)
if (stdin_paths)
hash_stdin_paths(type, no_filters, flags, literally);
+ free(vpath_free);
+
return 0;
}
diff --git a/builtin/log.c b/builtin/log.c
index 4b493408cc..093d0d2655 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -35,6 +35,7 @@
#include "repository.h"
#include "commit-reach.h"
#include "range-diff.h"
+#include "tmp-objdir.h"
#define MAIL_DEFAULT_WRAP 72
#define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100
@@ -422,6 +423,13 @@ static int cmd_log_walk(struct rev_info *rev)
int saved_nrl = 0;
int saved_dcctc = 0;
+ if (rev->remerge_diff) {
+ rev->remerge_objdir = tmp_objdir_create("remerge-diff");
+ if (!rev->remerge_objdir)
+ die(_("unable to create temporary object directory"));
+ tmp_objdir_replace_primary_odb(rev->remerge_objdir, 1);
+ }
+
if (rev->early_output)
setup_early_output();
@@ -464,6 +472,11 @@ static int cmd_log_walk(struct rev_info *rev)
rev->diffopt.no_free = 0;
diff_free(&rev->diffopt);
+ if (rev->remerge_diff) {
+ tmp_objdir_destroy(rev->remerge_objdir);
+ rev->remerge_objdir = NULL;
+ }
+
if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF &&
rev->diffopt.flags.check_failed) {
return 02;
@@ -1958,6 +1971,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
die(_("--name-status does not make sense"));
if (rev.diffopt.output_format & DIFF_FORMAT_CHECKDIFF)
die(_("--check does not make sense"));
+ if (rev.remerge_diff)
+ die(_("--remerge-diff does not make sense"));
if (!use_patch_format &&
(!rev.diffopt.output_format ||
diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c
index 44448fa61d..d856085e94 100644
--- a/builtin/ls-remote.c
+++ b/builtin/ls-remote.c
@@ -155,6 +155,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)
ref_array_clear(&ref_array);
if (transport_disconnect(transport))
- return 1;
+ status = 1;
+ transport_ls_refs_options_release(&transport_options);
return status;
}
diff --git a/builtin/merge.c b/builtin/merge.c
index 133831d42f..a94a03384a 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -1568,8 +1568,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (autostash)
create_autostash(the_repository,
- git_path_merge_autostash(the_repository),
- "merge");
+ git_path_merge_autostash(the_repository));
if (checkout_fast_forward(the_repository,
&head_commit->object.oid,
&commit->object.oid,
@@ -1640,8 +1639,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (autostash)
create_autostash(the_repository,
- git_path_merge_autostash(the_repository),
- "merge");
+ git_path_merge_autostash(the_repository));
/* We are going to make a new commit. */
git_committer_info(IDENT_STRICT);
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index ba2006f221..87cb7b45c3 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -3976,9 +3976,11 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
read_replace_refs = 0;
sparse = git_env_bool("GIT_TEST_PACK_SPARSE", -1);
- prepare_repo_settings(the_repository);
- if (sparse < 0)
- sparse = the_repository->settings.pack_use_sparse;
+ if (the_repository->gitdir) {
+ prepare_repo_settings(the_repository);
+ if (sparse < 0)
+ sparse = the_repository->settings.pack_use_sparse;
+ }
reset_pack_idx_option(&pack_idx_opts);
git_config(git_pack_config, NULL);
diff --git a/builtin/patch-id.c b/builtin/patch-id.c
index 822ffff51f..881fcf3273 100644
--- a/builtin/patch-id.c
+++ b/builtin/patch-id.c
@@ -32,8 +32,12 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after)
n = strspn(q, digits);
if (q[n] == ',') {
q += n + 1;
+ *p_before = atoi(q);
n = strspn(q, digits);
+ } else {
+ *p_before = 1;
}
+
if (n == 0 || q[n] != ' ' || q[n+1] != '+')
return 0;
@@ -41,13 +45,14 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after)
n = strspn(r, digits);
if (r[n] == ',') {
r += n + 1;
+ *p_after = atoi(r);
n = strspn(r, digits);
+ } else {
+ *p_after = 1;
}
if (n == 0)
return 0;
- *p_before = atoi(q);
- *p_after = atoi(r);
return 1;
}
diff --git a/builtin/pull.c b/builtin/pull.c
index 8f37880a48..3768552e68 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -994,8 +994,10 @@ int cmd_pull(int argc, const char **argv, const char *prefix)
set_reflog_message(argc, argv);
git_config(git_pull_config, NULL);
- prepare_repo_settings(the_repository);
- the_repository->settings.command_requires_full_index = 0;
+ if (the_repository->gitdir) {
+ prepare_repo_settings(the_repository);
+ the_repository->settings.command_requires_full_index = 0;
+ }
argc = parse_options(argc, argv, prefix, pull_options, pull_usage, 0);
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 2e6d8fa34e..d858add3fe 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -571,7 +571,8 @@ static int finish_rebase(struct rebase_options *opts)
static int move_to_original_branch(struct rebase_options *opts)
{
- struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;
+ struct strbuf branch_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT;
+ struct reset_head_opts ropts = { 0 };
int ret;
if (!opts->head_name)
@@ -580,16 +581,17 @@ static int move_to_original_branch(struct rebase_options *opts)
if (!opts->onto)
BUG("move_to_original_branch without onto");
- strbuf_addf(&orig_head_reflog, "rebase finished: %s onto %s",
+ strbuf_addf(&branch_reflog, "rebase finished: %s onto %s",
opts->head_name, oid_to_hex(&opts->onto->object.oid));
strbuf_addf(&head_reflog, "rebase finished: returning to %s",
opts->head_name);
- ret = reset_head(the_repository, NULL, "", opts->head_name,
- RESET_HEAD_REFS_ONLY,
- orig_head_reflog.buf, head_reflog.buf,
- DEFAULT_REFLOG_ACTION);
+ ropts.branch = opts->head_name;
+ ropts.flags = RESET_HEAD_REFS_ONLY;
+ ropts.branch_msg = branch_reflog.buf;
+ ropts.head_msg = head_reflog.buf;
+ ret = reset_head(the_repository, &ropts);
- strbuf_release(&orig_head_reflog);
+ strbuf_release(&branch_reflog);
strbuf_release(&head_reflog);
return ret;
}
@@ -671,13 +673,15 @@ static int run_am(struct rebase_options *opts)
status = run_command(&format_patch);
if (status) {
+ struct reset_head_opts ropts = { 0 };
unlink(rebased_patches);
free(rebased_patches);
strvec_clear(&am.args);
- reset_head(the_repository, &opts->orig_head, "checkout",
- opts->head_name, 0,
- "HEAD", NULL, DEFAULT_REFLOG_ACTION);
+ ropts.oid = &opts->orig_head;
+ ropts.branch = opts->head_name;
+ ropts.default_reflog_action = DEFAULT_REFLOG_ACTION;
+ reset_head(the_repository, &ropts);
error(_("\ngit encountered an error while preparing the "
"patches to replay\n"
"these revisions:\n"
@@ -813,6 +817,26 @@ static int rebase_config(const char *var, const char *value, void *data)
return git_default_config(var, value, data);
}
+static int checkout_up_to_date(struct rebase_options *options)
+{
+ struct strbuf buf = STRBUF_INIT;
+ struct reset_head_opts ropts = { 0 };
+ int ret = 0;
+
+ strbuf_addf(&buf, "%s: checkout %s",
+ getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
+ options->switch_to);
+ ropts.oid = &options->orig_head;
+ ropts.branch = options->head_name;
+ ropts.flags = RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
+ ropts.head_msg = buf.buf;
+ if (reset_head(the_repository, &ropts) < 0)
+ ret = error(_("could not switch to %s"), options->switch_to);
+ strbuf_release(&buf);
+
+ return ret;
+}
+
/*
* Determines whether the commits in from..to are linear, i.e. contain
* no merge commits. This function *expects* `from` to be an ancestor of
@@ -1018,6 +1042,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
int reschedule_failed_exec = -1;
int allow_preemptive_ff = 1;
int preserve_merges_selected = 0;
+ struct reset_head_opts ropts = { 0 };
struct option builtin_rebase_options[] = {
OPT_STRING(0, "onto", &options.onto_name,
N_("revision"),
@@ -1255,9 +1280,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
rerere_clear(the_repository, &merge_rr);
string_list_clear(&merge_rr, 1);
-
- if (reset_head(the_repository, NULL, "reset", NULL, RESET_HEAD_HARD,
- NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)
+ ropts.flags = RESET_HEAD_HARD;
+ if (reset_head(the_repository, &ropts) < 0)
die(_("could not discard worktree changes"));
remove_branch_state(the_repository, 0);
if (read_basic_state(&options))
@@ -1274,9 +1298,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (read_basic_state(&options))
exit(1);
- if (reset_head(the_repository, &options.orig_head, "reset",
- options.head_name, RESET_HEAD_HARD,
- NULL, NULL, DEFAULT_REFLOG_ACTION) < 0)
+ ropts.oid = &options.orig_head;
+ ropts.branch = options.head_name;
+ ropts.flags = RESET_HEAD_HARD;
+ ropts.default_reflog_action = DEFAULT_REFLOG_ACTION;
+ if (reset_head(the_repository, &ropts) < 0)
die(_("could not move back to %s"),
oid_to_hex(&options.orig_head));
remove_branch_state(the_repository, 0);
@@ -1642,10 +1668,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (repo_read_index(the_repository) < 0)
die(_("could not read index"));
- if (options.autostash) {
- create_autostash(the_repository, state_dir_path("autostash", &options),
- DEFAULT_REFLOG_ACTION);
- }
+ if (options.autostash)
+ create_autostash(the_repository,
+ state_dir_path("autostash", &options));
+
if (require_clean_work_tree(the_repository, "rebase",
_("Please commit or stash them."), 1, 1)) {
@@ -1674,21 +1700,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (!(options.flags & REBASE_FORCE)) {
/* Lazily switch to the target branch if needed... */
if (options.switch_to) {
- strbuf_reset(&buf);
- strbuf_addf(&buf, "%s: checkout %s",
- getenv(GIT_REFLOG_ACTION_ENVIRONMENT),
- options.switch_to);
- if (reset_head(the_repository,
- &options.orig_head, "checkout",
- options.head_name,
- RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
- NULL, buf.buf,
- DEFAULT_REFLOG_ACTION) < 0) {
- ret = error(_("could not switch to "
- "%s"),
- options.switch_to);
+ ret = checkout_up_to_date(&options);
+ if (ret)
goto cleanup;
- }
}
if (!(options.flags & REBASE_NO_QUIET))
@@ -1755,10 +1769,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
strbuf_addf(&msg, "%s: checkout %s",
getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);
- if (reset_head(the_repository, &options.onto->object.oid, "checkout", NULL,
- RESET_HEAD_DETACH | RESET_ORIG_HEAD |
- RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
- NULL, msg.buf, DEFAULT_REFLOG_ACTION))
+ ropts.oid = &options.onto->object.oid;
+ ropts.orig_head = &options.orig_head,
+ ropts.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |
+ RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
+ ropts.head_msg = msg.buf;
+ ropts.default_reflog_action = DEFAULT_REFLOG_ACTION;
+ if (reset_head(the_repository, &ropts))
die(_("Could not detach HEAD"));
strbuf_release(&msg);
@@ -1773,9 +1790,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
strbuf_addf(&msg, "rebase finished: %s onto %s",
options.head_name ? options.head_name : "detached HEAD",
oid_to_hex(&options.onto->object.oid));
- reset_head(the_repository, NULL, "Fast-forwarded", options.head_name,
- RESET_HEAD_REFS_ONLY, "HEAD", msg.buf,
- DEFAULT_REFLOG_ACTION);
+ memset(&ropts, 0, sizeof(ropts));
+ ropts.branch = options.head_name;
+ ropts.flags = RESET_HEAD_REFS_ONLY;
+ ropts.head_msg = msg.buf;
+ reset_head(the_repository, &ropts);
strbuf_release(&msg);
ret = finish_rebase(&options);
goto cleanup;
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index c427ca09aa..d10aeb7e78 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1962,6 +1962,15 @@ static void execute_commands(struct command *commands,
}
/*
+ * If there is no command ready to run, should return directly to destroy
+ * temporary data in the quarantine area.
+ */
+ for (cmd = commands; cmd && cmd->error_string; cmd = cmd->next)
+ ; /* nothing */
+ if (!cmd)
+ return;
+
+ /*
* Now we'll start writing out refs, which means the objects need
* to be in their final positions so that other processes can see them.
*/
diff --git a/builtin/reset.c b/builtin/reset.c
index b97745ee94..75b8d86481 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -204,10 +204,16 @@ static int pathspec_needs_expanded_index(const struct pathspec *pathspec)
/*
* Special case: if the pattern is a path inside the cone
* followed by only wildcards, the pattern cannot match
- * partial sparse directories, so we don't expand the index.
+ * partial sparse directories, so we know we don't need to
+ * expand the index.
+ *
+ * Examples:
+ * - in-cone/foo***: doesn't need expanded index
+ * - not-in-cone/bar*: may need expanded index
+ * - **.c: may need expanded index
*/
- if (path_in_cone_mode_sparse_checkout(item.original, &the_index) &&
- strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len)
+ if (strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len &&
+ path_in_cone_mode_sparse_checkout(item.original, &the_index))
continue;
for (pos = 0; pos < active_nr; pos++) {
diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c
index 2b0e1db2d2..a311483a7d 100644
--- a/builtin/sparse-checkout.c
+++ b/builtin/sparse-checkout.c
@@ -185,6 +185,8 @@ static void clean_tracked_sparse_directories(struct repository *r)
item->string);
}
+ strvec_clear(&s);
+ clear_pathspec(&p);
dir_clear(&dir);
}
diff --git a/builtin/stash.c b/builtin/stash.c
index 9638c56303..5897febfbe 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -788,7 +788,6 @@ static int list_stash(int argc, const char **argv, const char *prefix)
static int show_stat = 1;
static int show_patch;
static int show_include_untracked;
-static int use_legacy_stash;
static int git_stash_config(const char *var, const char *value, void *cb)
{
@@ -804,10 +803,6 @@ static int git_stash_config(const char *var, const char *value, void *cb)
show_include_untracked = git_config_bool(var, value);
return 0;
}
- if (!strcmp(var, "stash.usebuiltin")) {
- use_legacy_stash = !git_config_bool(var, value);
- return 0;
- }
return git_diff_basic_config(var, value, cb);
}
@@ -1782,11 +1777,6 @@ int cmd_stash(int argc, const char **argv, const char *prefix)
git_config(git_stash_config, NULL);
- if (use_legacy_stash ||
- !git_env_bool("GIT_TEST_STASH_USE_BUILTIN", -1))
- warning(_("the stash.useBuiltin support has been removed!\n"
- "See its entry in 'git help config' for details."));
-
argc = parse_options(argc, argv, prefix, options, git_stash_usage,
PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH);
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index c5d3fc3817..33c82c3ab9 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -20,6 +20,7 @@
#include "diff.h"
#include "object-store.h"
#include "advice.h"
+#include "branch.h"
#define OPT_QUIET (1 << 0)
#define OPT_CACHED (1 << 1)
@@ -2984,6 +2985,42 @@ static int module_set_branch(int argc, const char **argv, const char *prefix)
return !!ret;
}
+static int module_create_branch(int argc, const char **argv, const char *prefix)
+{
+ enum branch_track track;
+ int quiet = 0, force = 0, reflog = 0, dry_run = 0;
+
+ struct option options[] = {
+ OPT__QUIET(&quiet, N_("print only error messages")),
+ OPT__FORCE(&force, N_("force creation"), 0),
+ OPT_BOOL(0, "create-reflog", &reflog,
+ N_("create the branch's reflog")),
+ OPT_SET_INT('t', "track", &track,
+ N_("set up tracking mode (see git-pull(1))"),
+ BRANCH_TRACK_EXPLICIT),
+ OPT__DRY_RUN(&dry_run,
+ N_("show whether the branch would be created")),
+ OPT_END()
+ };
+ const char *const usage[] = {
+ N_("git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] [-n|--dry-run] <name> <start_oid> <start_name>"),
+ NULL
+ };
+
+ git_config(git_default_config, NULL);
+ track = git_branch_track;
+ argc = parse_options(argc, argv, prefix, options, usage, 0);
+
+ if (argc != 3)
+ usage_with_options(usage, options);
+
+ if (!quiet && !dry_run)
+ printf_ln(_("creating branch '%s'"), argv[0]);
+
+ create_branches_recursively(the_repository, argv[0], argv[1], argv[2],
+ force, reflog, quiet, track, dry_run);
+ return 0;
+}
struct add_data {
const char *prefix;
const char *branch;
@@ -3390,6 +3427,7 @@ static struct cmd_struct commands[] = {
{"config", module_config, 0},
{"set-url", module_set_url, 0},
{"set-branch", module_set_branch, 0},
+ {"create-branch", module_create_branch, 0},
};
int cmd_submodule__helper(int argc, const char **argv, const char *prefix)
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 7e0a0d9bf8..aafe7eeac2 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -606,7 +606,7 @@ static struct cache_entry *read_one_ent(const char *which,
error("%s: not in %s branch.", path, which);
return NULL;
}
- if (mode == S_IFDIR) {
+ if (!the_index.sparse_index && mode == S_IFDIR) {
if (which)
error("%s: not a blob in %s branch.", path, which);
return NULL;
@@ -743,8 +743,6 @@ static int do_reupdate(int ac, const char **av,
*/
has_head = 0;
redo:
- /* TODO: audit for interaction with sparse-index. */
- ensure_full_index(&the_index);
for (pos = 0; pos < active_nr; pos++) {
const struct cache_entry *ce = active_cache[pos];
struct cache_entry *old = NULL;
@@ -761,6 +759,16 @@ static int do_reupdate(int ac, const char **av,
discard_cache_entry(old);
continue; /* unchanged */
}
+
+ /* At this point, we know the contents of the sparse directory are
+ * modified with respect to HEAD, so we expand the index and restart
+ * to process each path individually
+ */
+ if (S_ISSPARSEDIR(ce->ce_mode)) {
+ ensure_full_index(&the_index);
+ goto redo;
+ }
+
/* Be careful. The working tree may not have the
* path anymore, in which case, under 'allow_remove',
* or worse yet 'allow_replace', active_nr may decrease.
@@ -1088,6 +1096,9 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
git_config(git_default_config, NULL);
+ prepare_repo_settings(r);
+ the_repository->settings.command_requires_full_index = 0;
+
/* we will diagnose later if it turns out that we need to update it */
newfd = hold_locked_index(&lock_file, 0);
if (newfd < 0)
diff --git a/cache.h b/cache.h
index 4148b6322d..825ec17198 100644
--- a/cache.h
+++ b/cache.h
@@ -18,7 +18,6 @@
#include "repository.h"
#include "mem-pool.h"
-#include <zlib.h>
typedef struct git_zstream {
z_stream z;
unsigned long avail_in;
diff --git a/ci/lib.sh b/ci/lib.sh
index 9d28ab50fb..cbc2f8f1ca 100755
--- a/ci/lib.sh
+++ b/ci/lib.sh
@@ -197,7 +197,6 @@ esac
case "$jobname" in
linux32)
CC=gcc
- MAKEFLAGS="$MAKEFLAGS NO_UNCOMPRESS2=1"
;;
linux-musl)
CC=gcc
diff --git a/compat/winansi.c b/compat/winansi.c
index 4fceecf14c..936a80a5f0 100644
--- a/compat/winansi.c
+++ b/compat/winansi.c
@@ -3,6 +3,12 @@
*/
#undef NOGDI
+
+/*
+ * Including the appropriate header file for RtlGenRandom causes MSVC to see a
+ * redefinition of types in an incompatible way when including headers below.
+ */
+#undef HAVE_RTLGENRANDOM
#include "../git-compat-util.h"
#include <wingdi.h>
#include <winreg.h>
diff --git a/compat/zlib-uncompress2.c b/compat/zlib-uncompress2.c
index 722610b971..77a1b08048 100644
--- a/compat/zlib-uncompress2.c
+++ b/compat/zlib-uncompress2.c
@@ -1,3 +1,6 @@
+#include "git-compat-util.h"
+
+#if ZLIB_VERNUM < 0x1290
/* taken from zlib's uncompr.c
commit cacf7f1d4e3d44d871b605da3b647f07d718623f
@@ -8,16 +11,11 @@
*/
-#include "../reftable/system.h"
-#define z_const
-
/*
* Copyright (C) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler
* For conditions of distribution and use, see copyright notice in zlib.h
*/
-#include <zlib.h>
-
/* clang-format off */
/* ===========================================================================
@@ -93,3 +91,6 @@ int ZEXPORT uncompress2 (
err == Z_BUF_ERROR && left + stream.avail_out ? Z_DATA_ERROR :
err;
}
+#else
+static void *dummy_variable = &dummy_variable;
+#endif
diff --git a/config.mak.uname b/config.mak.uname
index c48db45106..4352ea39e9 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -66,7 +66,6 @@ ifeq ($(uname_S),Linux)
# centos7/rhel7 provides gcc 4.8.5 and zlib 1.2.7.
ifneq ($(findstring .el7.,$(uname_R)),)
BASIC_CFLAGS += -std=c99
- NO_UNCOMPRESS2 = YesPlease
endif
endif
ifeq ($(uname_S),GNU/kFreeBSD)
@@ -146,6 +145,7 @@ ifeq ($(uname_S),Darwin)
HAVE_BSD_SYSCTL = YesPlease
FREAD_READS_DIRECTORIES = UnfortunatelyYes
HAVE_NS_GET_EXECUTABLE_PATH = YesPlease
+ CSPRNG_METHOD = arc4random
# Workaround for `gettext` being keg-only and not even being linked via
# `brew link --force gettext`, should be obsolete as of
@@ -261,15 +261,12 @@ ifeq ($(uname_S),FreeBSD)
HAVE_PATHS_H = YesPlease
HAVE_BSD_SYSCTL = YesPlease
HAVE_BSD_KERN_PROC_SYSCTL = YesPlease
+ CSPRNG_METHOD = arc4random
PAGER_ENV = LESS=FRX LV=-c MORE=FRX
FREAD_READS_DIRECTORIES = UnfortunatelyYes
FILENO_IS_A_MACRO = UnfortunatelyYes
endif
ifeq ($(uname_S),OpenBSD)
- # Versions < 7.0 need compatibility layer
- ifeq ($(shell expr "$(uname_R)" : "[1-6]\."),2)
- NO_UNCOMPRESS2 = UnfortunatelyYes
- endif
NO_STRCASESTR = YesPlease
NO_MEMMEM = YesPlease
USE_ST_TIMESPEC = YesPlease
@@ -279,6 +276,7 @@ ifeq ($(uname_S),OpenBSD)
HAVE_PATHS_H = YesPlease
HAVE_BSD_SYSCTL = YesPlease
HAVE_BSD_KERN_PROC_SYSCTL = YesPlease
+ CSPRNG_METHOD = arc4random
PROCFS_EXECUTABLE_PATH = /proc/curproc/file
FREAD_READS_DIRECTORIES = UnfortunatelyYes
FILENO_IS_A_MACRO = UnfortunatelyYes
@@ -290,6 +288,7 @@ ifeq ($(uname_S),MirBSD)
NEEDS_LIBICONV = YesPlease
HAVE_PATHS_H = YesPlease
HAVE_BSD_SYSCTL = YesPlease
+ CSPRNG_METHOD = arc4random
endif
ifeq ($(uname_S),NetBSD)
ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2)
@@ -301,6 +300,7 @@ ifeq ($(uname_S),NetBSD)
HAVE_PATHS_H = YesPlease
HAVE_BSD_SYSCTL = YesPlease
HAVE_BSD_KERN_PROC_SYSCTL = YesPlease
+ CSPRNG_METHOD = arc4random
PROCFS_EXECUTABLE_PATH = /proc/curproc/exe
endif
ifeq ($(uname_S),AIX)
@@ -430,6 +430,7 @@ ifeq ($(uname_S),Windows)
NO_STRTOUMAX = YesPlease
NO_MKDTEMP = YesPlease
NO_INTTYPES_H = YesPlease
+ CSPRNG_METHOD = rtlgenrandom
# VS2015 with UCRT claims that snprintf and friends are C99 compliant,
# so we don't need this:
#
@@ -525,7 +526,6 @@ ifeq ($(uname_S),Interix)
endif
endif
ifeq ($(uname_S),Minix)
- NO_UNCOMPRESS2 = YesPlease
NO_IPV6 = YesPlease
NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
NO_NSEC = YesPlease
@@ -581,7 +581,6 @@ ifeq ($(uname_S),NONSTOP_KERNEL)
NO_SETENV = YesPlease
NO_UNSETENV = YesPlease
NO_MKDTEMP = YesPlease
- NO_UNCOMPRESS2 = YesPlease
# Currently libiconv-1.9.1.
OLD_ICONV = UnfortunatelyYes
NO_REGEX = NeedsStartEnd
@@ -599,6 +598,7 @@ ifeq ($(uname_S),NONSTOP_KERNEL)
NO_MMAP = YesPlease
NO_POLL = YesPlease
NO_INTPTR_T = UnfortunatelyYes
+ CSPRNG_METHOD = openssl
SANE_TOOL_PATH = /usr/coreutils/bin:/usr/local/bin
SHELL_PATH = /usr/coreutils/bin/bash
endif
@@ -634,6 +634,7 @@ ifeq ($(uname_S),MINGW)
NO_POSIX_GOODIES = UnfortunatelyYes
DEFAULT_HELP_FORMAT = html
HAVE_PLATFORM_PROCINFO = YesPlease
+ CSPRNG_METHOD = rtlgenrandom
BASIC_LDFLAGS += -municode
COMPAT_CFLAGS += -DNOGDI -Icompat -Icompat/win32
COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
diff --git a/configure.ac b/configure.ac
index d60d494ee4..5ee25ec95c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -664,22 +664,9 @@ AC_LINK_IFELSE([ZLIBTEST_SRC],
NO_DEFLATE_BOUND=yes])
LIBS="$old_LIBS"
-AC_DEFUN([ZLIBTEST_UNCOMPRESS2_SRC], [
-AC_LANG_PROGRAM([#include <zlib.h>],
- [uncompress2(NULL,NULL,NULL,NULL);])])
-AC_MSG_CHECKING([for uncompress2 in -lz])
-old_LIBS="$LIBS"
-LIBS="$LIBS -lz"
-AC_LINK_IFELSE([ZLIBTEST_UNCOMPRESS2_SRC],
- [AC_MSG_RESULT([yes])],
- [AC_MSG_RESULT([no])
- NO_UNCOMPRESS2=yes])
-LIBS="$old_LIBS"
-
GIT_UNSTASH_FLAGS($ZLIB_PATH)
GIT_CONF_SUBST([NO_DEFLATE_BOUND])
-GIT_CONF_SUBST([NO_UNCOMPRESS2])
#
# Define NEEDS_SOCKET if linking with libc is not enough (SunOS,
diff --git a/connect.c b/connect.c
index eaf7d6d261..afc79a6236 100644
--- a/connect.c
+++ b/connect.c
@@ -379,7 +379,7 @@ struct ref **get_remote_heads(struct packet_reader *reader,
/* Returns 1 when a valid ref has been added to `list`, 0 otherwise */
static int process_ref_v2(struct packet_reader *reader, struct ref ***list,
- char **unborn_head_target)
+ const char **unborn_head_target)
{
int ret = 1;
int i = 0;
@@ -483,7 +483,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,
const char *hash_name;
struct strvec *ref_prefixes = transport_options ?
&transport_options->ref_prefixes : NULL;
- char **unborn_head_target = transport_options ?
+ const char **unborn_head_target = transport_options ?
&transport_options->unborn_head_target : NULL;
*list = NULL;
diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt
index 5100f56bb3..e44232f85d 100644
--- a/contrib/buildsystems/CMakeLists.txt
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -260,7 +260,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
_CONSOLE DETECT_MSYS_TTY STRIP_EXTENSION=".exe" NO_SYMLINK_HEAD UNRELIABLE_FSTAT
NOGDI OBJECT_CREATION_MODE=1 __USE_MINGW_ANSI_STDIO=0
USE_NED_ALLOCATOR OVERRIDE_STRDUP MMAP_PREVENTS_DELETE USE_WIN32_MMAP
- UNICODE _UNICODE HAVE_WPGMPTR ENSURE_MSYSTEM_IS_SET)
+ UNICODE _UNICODE HAVE_WPGMPTR ENSURE_MSYSTEM_IS_SET HAVE_RTLGENRANDOM)
list(APPEND compat_SOURCES compat/mingw.c compat/winansi.c compat/win32/path-utils.c
compat/win32/pthread.c compat/win32mmap.c compat/win32/syslog.c
compat/win32/trace2_win32_process_info.c compat/win32/dirent.c
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 377d6c5494..49a328aa8a 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -49,6 +49,11 @@
# and git-switch completion (e.g., completing "foo" when "origin/foo"
# exists).
#
+# GIT_COMPLETION_SHOW_ALL_COMMANDS
+#
+# When set to "1" suggest all commands, including plumbing commands
+# which are hidden by default (e.g. "cat-file" on "git ca<TAB>").
+#
# GIT_COMPLETION_SHOW_ALL
#
# When set to "1" suggest all options, including options which are
@@ -2986,9 +2991,37 @@ _git_show_branch ()
__git_complete_revlist
}
+__gitcomp_directories ()
+{
+ local _tmp_dir _tmp_completions _found=0
+
+ # Get the directory of the current token; this differs from dirname
+ # in that it keeps up to the final trailing slash. If no slash found
+ # that's fine too.
+ [[ "$cur" =~ .*/ ]]
+ _tmp_dir=$BASH_REMATCH
+
+ # Find possible directory completions, adding trailing '/' characters,
+ # de-quoting, and handling unusual characters.
+ while IFS= read -r -d $'\0' c ; do
+ # If there are directory completions, find ones that start
+ # with "$cur", the current token, and put those in COMPREPLY
+ if [[ $c == "$cur"* ]]; then
+ COMPREPLY+=("$c/")
+ _found=1
+ fi
+ done < <(git ls-tree -z -d --name-only HEAD $_tmp_dir)
+
+ if [[ $_found == 0 ]] && [[ "$cur" =~ /$ ]]; then
+ # No possible further completions any deeper, so assume we're at
+ # a leaf directory and just consider it complete
+ __gitcomp_direct_append "$cur "
+ fi
+}
+
_git_sparse_checkout ()
{
- local subcommands="list init set disable"
+ local subcommands="list init set disable add reapply"
local subcommand="$(__git_find_on_cmdline "$subcommands")"
if [ -z "$subcommand" ]; then
__gitcomp "$subcommands"
@@ -2996,14 +3029,14 @@ _git_sparse_checkout ()
fi
case "$subcommand,$cur" in
- init,--*)
- __gitcomp "--cone"
- ;;
- set,--*)
- __gitcomp "--stdin"
- ;;
- *)
+ *,--*)
+ __gitcomp_builtin sparse-checkout_$subcommand "" "--"
;;
+ set,*|add,*)
+ if [ "$(__git config core.sparseCheckoutCone)" == "true" ] ||
+ [ -n "$(__git_find_on_cmdline --cone)" ]; then
+ __gitcomp_directories
+ fi
esac
}
@@ -3455,7 +3488,13 @@ __git_main ()
then
__gitcomp "$GIT_TESTING_PORCELAIN_COMMAND_LIST"
else
- __gitcomp "$(__git --list-cmds=list-mainporcelain,others,nohelpers,alias,list-complete,config)"
+ local list_cmds=list-mainporcelain,others,nohelpers,alias,list-complete,config
+
+ if test "${GIT_COMPLETION_SHOW_ALL_COMMANDS-}" = "1"
+ then
+ list_cmds=builtins,$list_cmds
+ fi
+ __gitcomp "$(__git --list-cmds=$list_cmds)"
fi
;;
esac
diff --git a/contrib/scalar/scalar.c b/contrib/scalar/scalar.c
index 1ce9c2b00e..7db2a97416 100644
--- a/contrib/scalar/scalar.c
+++ b/contrib/scalar/scalar.c
@@ -808,6 +808,25 @@ int cmd_main(int argc, const char **argv)
struct strbuf scalar_usage = STRBUF_INIT;
int i;
+ while (argc > 1 && *argv[1] == '-') {
+ if (!strcmp(argv[1], "-C")) {
+ if (argc < 3)
+ die(_("-C requires a <directory>"));
+ if (chdir(argv[2]) < 0)
+ die_errno(_("could not change to '%s'"),
+ argv[2]);
+ argc -= 2;
+ argv += 2;
+ } else if (!strcmp(argv[1], "-c")) {
+ if (argc < 3)
+ die(_("-c requires a <key>=<value> argument"));
+ git_config_push_parameter(argv[2]);
+ argc -= 2;
+ argv += 2;
+ } else
+ break;
+ }
+
if (argc > 1) {
argv++;
argc--;
@@ -818,7 +837,8 @@ int cmd_main(int argc, const char **argv)
}
strbuf_addstr(&scalar_usage,
- N_("scalar <command> [<options>]\n\nCommands:\n"));
+ N_("scalar [-C <directory>] [-c <key>=<value>] "
+ "<command> [<options>]\n\nCommands:\n"));
for (i = 0; builtins[i].name; i++)
strbuf_addf(&scalar_usage, "\t%s\n", builtins[i].name);
diff --git a/contrib/scalar/scalar.txt b/contrib/scalar/scalar.txt
index f416d63728..cf4e5b889c 100644
--- a/contrib/scalar/scalar.txt
+++ b/contrib/scalar/scalar.txt
@@ -36,6 +36,16 @@ The `scalar` command implements various subcommands, and different options
depending on the subcommand. With the exception of `clone`, `list` and
`reconfigure --all`, all subcommands expect to be run in an enlistment.
+The following options can be specified _before_ the subcommand:
+
+-C <directory>::
+ Before running the subcommand, change the working directory. This
+ option imitates the same option of linkgit:git[1].
+
+-c <key>=<value>::
+ For the duration of running the specified subcommand, configure this
+ setting. This option imitates the same option of linkgit:git[1].
+
COMMANDS
--------
diff --git a/contrib/scalar/t/t9099-scalar.sh b/contrib/scalar/t/t9099-scalar.sh
index 2e1502ad45..89781568f4 100755
--- a/contrib/scalar/t/t9099-scalar.sh
+++ b/contrib/scalar/t/t9099-scalar.sh
@@ -85,4 +85,12 @@ test_expect_success 'scalar delete with enlistment' '
test_path_is_missing cloned
'
+test_expect_success 'scalar supports -c/-C' '
+ test_when_finished "scalar delete sub" &&
+ git init sub &&
+ scalar -C sub -c status.aheadBehind=bogus register &&
+ test -z "$(git -C sub config --local status.aheadBehind)" &&
+ test true = "$(git -C sub config core.preloadIndex)"
+'
+
test_done
diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh
index 71f1fd94bd..1af1d9653e 100755
--- a/contrib/subtree/git-subtree.sh
+++ b/contrib/subtree/git-subtree.sh
@@ -975,10 +975,10 @@ cmd_merge () {
if test -n "$arg_addmerge_message"
then
- git merge -Xsubtree="$arg_prefix" \
+ git merge --no-ff -Xsubtree="$arg_prefix" \
--message="$arg_addmerge_message" "$rev"
else
- git merge -Xsubtree="$arg_prefix" $rev
+ git merge --no-ff -Xsubtree="$arg_prefix" $rev
fi
}
diff --git a/diff-merges.c b/diff-merges.c
index 5060ccd890..a833fd747a 100644
--- a/diff-merges.c
+++ b/diff-merges.c
@@ -17,12 +17,14 @@ static void suppress(struct rev_info *revs)
revs->combined_all_paths = 0;
revs->merges_imply_patch = 0;
revs->merges_need_diff = 0;
+ revs->remerge_diff = 0;
}
static void set_separate(struct rev_info *revs)
{
suppress(revs);
revs->separate_merges = 1;
+ revs->simplify_history = 0;
}
static void set_first_parent(struct rev_info *revs)
@@ -45,6 +47,13 @@ static void set_dense_combined(struct rev_info *revs)
revs->dense_combined_merges = 1;
}
+static void set_remerge_diff(struct rev_info *revs)
+{
+ suppress(revs);
+ revs->remerge_diff = 1;
+ revs->simplify_history = 0;
+}
+
static diff_merges_setup_func_t func_by_opt(const char *optarg)
{
if (!strcmp(optarg, "off") || !strcmp(optarg, "none"))
@@ -57,6 +66,8 @@ static diff_merges_setup_func_t func_by_opt(const char *optarg)
return set_combined;
else if (!strcmp(optarg, "cc") || !strcmp(optarg, "dense-combined"))
return set_dense_combined;
+ else if (!strcmp(optarg, "r") || !strcmp(optarg, "remerge"))
+ return set_remerge_diff;
else if (!strcmp(optarg, "m") || !strcmp(optarg, "on"))
return set_to_default;
return NULL;
@@ -110,6 +121,9 @@ int diff_merges_parse_opts(struct rev_info *revs, const char **argv)
} else if (!strcmp(arg, "--cc")) {
set_dense_combined(revs);
revs->merges_imply_patch = 1;
+ } else if (!strcmp(arg, "--remerge-diff")) {
+ set_remerge_diff(revs);
+ revs->merges_imply_patch = 1;
} else if (!strcmp(arg, "--no-diff-merges")) {
suppress(revs);
} else if (!strcmp(arg, "--combined-all-paths")) {
diff --git a/diff.c b/diff.c
index c862771a58..7d5cfd325e 100644
--- a/diff.c
+++ b/diff.c
@@ -28,6 +28,7 @@
#include "help.h"
#include "promisor-remote.h"
#include "dir.h"
+#include "strmap.h"
#ifdef NO_FAST_WORKING_DIRECTORY
#define FAST_WORKING_DIRECTORY 0
@@ -3353,6 +3354,31 @@ struct userdiff_driver *get_textconv(struct repository *r,
return userdiff_get_textconv(r, one->driver);
}
+static struct strbuf *additional_headers(struct diff_options *o,
+ const char *path)
+{
+ if (!o->additional_path_headers)
+ return NULL;
+ return strmap_get(o->additional_path_headers, path);
+}
+
+static void add_formatted_headers(struct strbuf *msg,
+ struct strbuf *more_headers,
+ const char *line_prefix,
+ const char *meta,
+ const char *reset)
+{
+ char *next, *newline;
+
+ for (next = more_headers->buf; *next; next = newline) {
+ newline = strchrnul(next, '\n');
+ strbuf_addf(msg, "%s%s%.*s%s\n", line_prefix, meta,
+ (int)(newline - next), next, reset);
+ if (*newline)
+ newline++;
+ }
+}
+
static void builtin_diff(const char *name_a,
const char *name_b,
struct diff_filespec *one,
@@ -3411,6 +3437,17 @@ static void builtin_diff(const char *name_a,
b_two = quote_two(b_prefix, name_b + (*name_b == '/'));
lbl[0] = DIFF_FILE_VALID(one) ? a_one : "/dev/null";
lbl[1] = DIFF_FILE_VALID(two) ? b_two : "/dev/null";
+ if (!DIFF_FILE_VALID(one) && !DIFF_FILE_VALID(two)) {
+ /*
+ * We should only reach this point for pairs from
+ * create_filepairs_for_header_only_notifications(). For
+ * these, we should avoid the "/dev/null" special casing
+ * above, meaning we avoid showing such pairs as either
+ * "new file" or "deleted file" below.
+ */
+ lbl[0] = a_one;
+ lbl[1] = b_two;
+ }
strbuf_addf(&header, "%s%sdiff --git %s %s%s\n", line_prefix, meta, a_one, b_two, reset);
if (lbl[0][0] == '/') {
/* /dev/null */
@@ -4275,6 +4312,7 @@ static void fill_metainfo(struct strbuf *msg,
const char *set = diff_get_color(use_color, DIFF_METAINFO);
const char *reset = diff_get_color(use_color, DIFF_RESET);
const char *line_prefix = diff_line_prefix(o);
+ struct strbuf *more_headers = NULL;
*must_show_header = 1;
strbuf_init(msg, PATH_MAX * 2 + 300);
@@ -4311,6 +4349,11 @@ static void fill_metainfo(struct strbuf *msg,
default:
*must_show_header = 0;
}
+ if ((more_headers = additional_headers(o, name))) {
+ add_formatted_headers(msg, more_headers,
+ line_prefix, set, reset);
+ *must_show_header = 1;
+ }
if (one && two && !oideq(&one->oid, &two->oid)) {
const unsigned hexsz = the_hash_algo->hexsz;
int abbrev = o->abbrev ? o->abbrev : DEFAULT_ABBREV;
@@ -4570,6 +4613,43 @@ void repo_diff_setup(struct repository *r, struct diff_options *options)
prep_parse_options(options);
}
+static const char diff_status_letters[] = {
+ DIFF_STATUS_ADDED,
+ DIFF_STATUS_COPIED,
+ DIFF_STATUS_DELETED,
+ DIFF_STATUS_MODIFIED,
+ DIFF_STATUS_RENAMED,
+ DIFF_STATUS_TYPE_CHANGED,
+ DIFF_STATUS_UNKNOWN,
+ DIFF_STATUS_UNMERGED,
+ DIFF_STATUS_FILTER_AON,
+ DIFF_STATUS_FILTER_BROKEN,
+ '\0',
+};
+
+static unsigned int filter_bit['Z' + 1];
+
+static void prepare_filter_bits(void)
+{
+ int i;
+
+ if (!filter_bit[DIFF_STATUS_ADDED]) {
+ for (i = 0; diff_status_letters[i]; i++)
+ filter_bit[(int) diff_status_letters[i]] = (1 << i);
+ }
+}
+
+static unsigned filter_bit_tst(char status, const struct diff_options *opt)
+{
+ return opt->filter & filter_bit[(int) status];
+}
+
+unsigned diff_filter_bit(char status)
+{
+ prepare_filter_bits();
+ return filter_bit[(int) status];
+}
+
void diff_setup_done(struct diff_options *options)
{
unsigned check_mask = DIFF_FORMAT_NAME |
@@ -4683,6 +4763,12 @@ void diff_setup_done(struct diff_options *options)
if (!options->use_color || external_diff())
options->color_moved = 0;
+ if (options->filter_not) {
+ if (!options->filter)
+ options->filter = ~filter_bit[DIFF_STATUS_FILTER_AON];
+ options->filter &= ~options->filter_not;
+ }
+
FREE_AND_NULL(options->parseopts);
}
@@ -4774,43 +4860,6 @@ static int parse_dirstat_opt(struct diff_options *options, const char *params)
return 1;
}
-static const char diff_status_letters[] = {
- DIFF_STATUS_ADDED,
- DIFF_STATUS_COPIED,
- DIFF_STATUS_DELETED,
- DIFF_STATUS_MODIFIED,
- DIFF_STATUS_RENAMED,
- DIFF_STATUS_TYPE_CHANGED,
- DIFF_STATUS_UNKNOWN,
- DIFF_STATUS_UNMERGED,
- DIFF_STATUS_FILTER_AON,
- DIFF_STATUS_FILTER_BROKEN,
- '\0',
-};
-
-static unsigned int filter_bit['Z' + 1];
-
-static void prepare_filter_bits(void)
-{
- int i;
-
- if (!filter_bit[DIFF_STATUS_ADDED]) {
- for (i = 0; diff_status_letters[i]; i++)
- filter_bit[(int) diff_status_letters[i]] = (1 << i);
- }
-}
-
-static unsigned filter_bit_tst(char status, const struct diff_options *opt)
-{
- return opt->filter & filter_bit[(int) status];
-}
-
-unsigned diff_filter_bit(char status)
-{
- prepare_filter_bits();
- return filter_bit[(int) status];
-}
-
static int diff_opt_diff_filter(const struct option *option,
const char *optarg, int unset)
{
@@ -4820,21 +4869,6 @@ static int diff_opt_diff_filter(const struct option *option,
BUG_ON_OPT_NEG(unset);
prepare_filter_bits();
- /*
- * If there is a negation e.g. 'd' in the input, and we haven't
- * initialized the filter field with another --diff-filter, start
- * from full set of bits, except for AON.
- */
- if (!opt->filter) {
- for (i = 0; (optch = optarg[i]) != '\0'; i++) {
- if (optch < 'a' || 'z' < optch)
- continue;
- opt->filter = (1 << (ARRAY_SIZE(diff_status_letters) - 1)) - 1;
- opt->filter &= ~filter_bit[DIFF_STATUS_FILTER_AON];
- break;
- }
- }
-
for (i = 0; (optch = optarg[i]) != '\0'; i++) {
unsigned int bit;
int negate;
@@ -4851,7 +4885,7 @@ static int diff_opt_diff_filter(const struct option *option,
return error(_("unknown change class '%c' in --diff-filter=%s"),
optarg[i], optarg);
if (negate)
- opt->filter &= ~bit;
+ opt->filter_not |= bit;
else
opt->filter |= bit;
}
@@ -5803,12 +5837,27 @@ int diff_unmodified_pair(struct diff_filepair *p)
static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o)
{
- if (diff_unmodified_pair(p))
+ int include_conflict_headers =
+ (additional_headers(o, p->one->path) &&
+ (!o->filter || filter_bit_tst(DIFF_STATUS_UNMERGED, o)));
+
+ /*
+ * Check if we can return early without showing a diff. Note that
+ * diff_filepair only stores {oid, path, mode, is_valid}
+ * information for each path, and thus diff_unmodified_pair() only
+ * considers those bits of info. However, we do not want pairs
+ * created by create_filepairs_for_header_only_notifications()
+ * (which always look like unmodified pairs) to be ignored, so
+ * return early if both p is unmodified AND we don't want to
+ * include_conflict_headers.
+ */
+ if (diff_unmodified_pair(p) && !include_conflict_headers)
return;
+ /* Actually, we can also return early to avoid showing tree diffs */
if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
(DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
- return; /* no tree diffs in patch format */
+ return;
run_diff(p, o);
}
@@ -5839,10 +5888,17 @@ static void diff_flush_checkdiff(struct diff_filepair *p,
run_checkdiff(p, o);
}
-int diff_queue_is_empty(void)
+int diff_queue_is_empty(struct diff_options *o)
{
struct diff_queue_struct *q = &diff_queued_diff;
int i;
+ int include_conflict_headers =
+ (o->additional_path_headers &&
+ (!o->filter || filter_bit_tst(DIFF_STATUS_UNMERGED, o)));
+
+ if (include_conflict_headers)
+ return 0;
+
for (i = 0; i < q->nr; i++)
if (!diff_unmodified_pair(q->queue[i]))
return 0;
@@ -6276,6 +6332,54 @@ void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc)
warning(_(rename_limit_advice), varname, needed);
}
+static void create_filepairs_for_header_only_notifications(struct diff_options *o)
+{
+ struct strset present;
+ struct diff_queue_struct *q = &diff_queued_diff;
+ struct hashmap_iter iter;
+ struct strmap_entry *e;
+ int i;
+
+ strset_init_with_options(&present, /*pool*/ NULL, /*strdup*/ 0);
+
+ /*
+ * Find out which paths exist in diff_queued_diff, preferring
+ * one->path for any pair that has multiple paths.
+ */
+ for (i = 0; i < q->nr; i++) {
+ struct diff_filepair *p = q->queue[i];
+ char *path = p->one->path ? p->one->path : p->two->path;
+
+ if (strmap_contains(o->additional_path_headers, path))
+ strset_add(&present, path);
+ }
+
+ /*
+ * Loop over paths in additional_path_headers; for each NOT already
+ * in diff_queued_diff, create a synthetic filepair and insert that
+ * into diff_queued_diff.
+ */
+ strmap_for_each_entry(o->additional_path_headers, &iter, e) {
+ if (!strset_contains(&present, e->key)) {
+ struct diff_filespec *one, *two;
+ struct diff_filepair *p;
+
+ one = alloc_filespec(e->key);
+ two = alloc_filespec(e->key);
+ fill_filespec(one, null_oid(), 0, 0);
+ fill_filespec(two, null_oid(), 0, 0);
+ p = diff_queue(q, one, two);
+ p->status = DIFF_STATUS_MODIFIED;
+ }
+ }
+
+ /* Re-sort the filepairs */
+ diffcore_fix_diff_index();
+
+ /* Cleanup */
+ strset_clear(&present);
+}
+
static void diff_flush_patch_all_file_pairs(struct diff_options *o)
{
int i;
@@ -6288,6 +6392,9 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o)
if (o->color_moved)
o->emitted_symbols = &esm;
+ if (o->additional_path_headers)
+ create_filepairs_for_header_only_notifications(o);
+
for (i = 0; i < q->nr; i++) {
struct diff_filepair *p = q->queue[i];
if (check_pair_status(p))
@@ -6358,7 +6465,7 @@ void diff_flush(struct diff_options *options)
* Order: raw, stat, summary, patch
* or: name/name-status/checkdiff (other bits clear)
*/
- if (!q->nr)
+ if (!q->nr && !options->additional_path_headers)
goto free_queue;
if (output_format & (DIFF_FORMAT_RAW |
diff --git a/diff.h b/diff.h
index 8ba85c5e60..8ae18e5ab1 100644
--- a/diff.h
+++ b/diff.h
@@ -283,7 +283,7 @@ struct diff_options {
struct diff_flags flags;
/* diff-filter bits */
- unsigned int filter;
+ unsigned int filter, filter_not;
int use_color;
@@ -395,6 +395,7 @@ struct diff_options {
struct repository *repo;
struct option *parseopts;
+ struct strmap *additional_path_headers;
int no_free;
};
@@ -593,7 +594,7 @@ void diffcore_fix_diff_index(void);
" show all files diff when -S is used and hit is found.\n" \
" -a --text treat all files as text.\n"
-int diff_queue_is_empty(void);
+int diff_queue_is_empty(struct diff_options *o);
void diff_flush(struct diff_options*);
void diff_free(struct diff_options*);
void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc);
diff --git a/fetch-negotiator.c b/fetch-negotiator.c
index 273390229f..874797d767 100644
--- a/fetch-negotiator.c
+++ b/fetch-negotiator.c
@@ -18,7 +18,7 @@ void fetch_negotiator_init(struct repository *r,
noop_negotiator_init(negotiator);
return;
- case FETCH_NEGOTIATION_DEFAULT:
+ case FETCH_NEGOTIATION_CONSECUTIVE:
default_negotiator_init(negotiator);
return;
}
diff --git a/fetch-pack.c b/fetch-pack.c
index dd6ec449f2..87657907e7 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -696,26 +696,30 @@ static void mark_complete_and_common_ref(struct fetch_negotiator *negotiator,
trace2_region_enter("fetch-pack", "parse_remote_refs_and_find_cutoff", NULL);
for (ref = *refs; ref; ref = ref->next) {
- struct object *o;
+ struct commit *commit;
+
+ commit = lookup_commit_in_graph(the_repository, &ref->old_oid);
+ if (!commit) {
+ struct object *o;
- if (!has_object_file_with_flags(&ref->old_oid,
+ if (!has_object_file_with_flags(&ref->old_oid,
OBJECT_INFO_QUICK |
- OBJECT_INFO_SKIP_FETCH_OBJECT))
- continue;
- o = parse_object(the_repository, &ref->old_oid);
- if (!o)
- continue;
+ OBJECT_INFO_SKIP_FETCH_OBJECT))
+ continue;
+ o = parse_object(the_repository, &ref->old_oid);
+ if (!o || o->type != OBJ_COMMIT)
+ continue;
+
+ commit = (struct commit *)o;
+ }
/*
* We already have it -- which may mean that we were
* in sync with the other side at some time after
* that (it is OK if we guess wrong here).
*/
- if (o->type == OBJ_COMMIT) {
- struct commit *commit = (struct commit *)o;
- if (!cutoff || cutoff < commit->date)
- cutoff = commit->date;
- }
+ if (!cutoff || cutoff < commit->date)
+ cutoff = commit->date;
}
trace2_region_leave("fetch-pack", "parse_remote_refs_and_find_cutoff", NULL);
@@ -1415,9 +1419,17 @@ static int process_ack(struct fetch_negotiator *negotiator,
* otherwise.
*/
if (*received_ready && reader->status != PACKET_READ_DELIM)
- die(_("expected packfile to be sent after 'ready'"));
+ /*
+ * TRANSLATORS: The parameter will be 'ready', a protocol
+ * keyword.
+ */
+ die(_("expected packfile to be sent after '%s'"), "ready");
if (!*received_ready && reader->status != PACKET_READ_FLUSH)
- die(_("expected no other sections to be sent after no 'ready'"));
+ /*
+ * TRANSLATORS: The parameter will be 'ready', a protocol
+ * keyword.
+ */
+ die(_("expected no other sections to be sent after no '%s'"), "ready");
return 0;
}
diff --git a/git-compat-util.h b/git-compat-util.h
index 1229c8296b..876907b9df 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -197,6 +197,12 @@
#endif
#include <windows.h>
#define GIT_WINDOWS_NATIVE
+#ifdef HAVE_RTLGENRANDOM
+/* This is required to get access to RtlGenRandom. */
+#define SystemFunction036 NTAPI SystemFunction036
+#include <NTSecAPI.h>
+#undef SystemFunction036
+#endif
#endif
#include <unistd.h>
@@ -267,6 +273,12 @@
#else
#include <stdint.h>
#endif
+#ifdef HAVE_ARC4RANDOM_LIBBSD
+#include <bsd/stdlib.h>
+#endif
+#ifdef HAVE_GETRANDOM
+#include <sys/random.h>
+#endif
#ifdef NO_INTPTR_T
/*
* On I16LP32, ILP32 and LP64 "long" is the safe bet, however
@@ -1386,6 +1398,18 @@ void unleak_memory(const void *ptr, size_t len);
#define UNLEAK(var) do {} while (0)
#endif
+#define z_const
+#include <zlib.h>
+
+#if ZLIB_VERNUM < 0x1290
+/*
+ * This is uncompress2, which is only available in zlib >= 1.2.9
+ * (released as of early 2017). See compat/zlib-uncompress2.c.
+ */
+int uncompress2(Bytef *dest, uLongf *destLen, const Bytef *source,
+ uLong *sourceLen);
+#endif
+
/*
* This include must come after system headers, since it introduces macros that
* replace system names.
@@ -1432,4 +1456,11 @@ static inline void *container_of_or_null_offset(void *ptr, size_t offset)
void sleep_millisec(int millisec);
+/*
+ * Generate len bytes from the system cryptographically secure PRNG.
+ * Returns 0 on success and -1 on error, setting errno. The inability to
+ * satisfy the full request is an error.
+ */
+int csprng_bytes(void *buf, size_t len);
+
#endif
diff --git a/git-sh-setup.sh b/git-sh-setup.sh
index b93f39288c..d92df37e99 100644
--- a/git-sh-setup.sh
+++ b/git-sh-setup.sh
@@ -101,7 +101,6 @@ $LONG_USAGE")"
case "$1" in
-h)
echo "$LONG_USAGE"
- case "$0" in *git-legacy-stash) exit 129;; esac
exit
esac
fi
diff --git a/ll-merge.c b/ll-merge.c
index 261657578c..a937cec59a 100644
--- a/ll-merge.c
+++ b/ll-merge.c
@@ -14,7 +14,7 @@
struct ll_merge_driver;
-typedef int (*ll_merge_fn)(const struct ll_merge_driver *,
+typedef enum ll_merge_result (*ll_merge_fn)(const struct ll_merge_driver *,
mmbuffer_t *result,
const char *path,
mmfile_t *orig, const char *orig_name,
@@ -49,7 +49,7 @@ void reset_merge_attributes(void)
/*
* Built-in low-levels
*/
-static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
+static enum ll_merge_result ll_binary_merge(const struct ll_merge_driver *drv_unused,
mmbuffer_t *result,
const char *path,
mmfile_t *orig, const char *orig_name,
@@ -58,6 +58,7 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
const struct ll_merge_options *opts,
int marker_size)
{
+ enum ll_merge_result ret;
mmfile_t *stolen;
assert(opts);
@@ -68,16 +69,19 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
*/
if (opts->virtual_ancestor) {
stolen = orig;
+ ret = LL_MERGE_OK;
} else {
switch (opts->variant) {
default:
- warning("Cannot merge binary files: %s (%s vs. %s)",
- path, name1, name2);
- /* fallthru */
+ ret = LL_MERGE_BINARY_CONFLICT;
+ stolen = src1;
+ break;
case XDL_MERGE_FAVOR_OURS:
+ ret = LL_MERGE_OK;
stolen = src1;
break;
case XDL_MERGE_FAVOR_THEIRS:
+ ret = LL_MERGE_OK;
stolen = src2;
break;
}
@@ -87,16 +91,10 @@ static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
result->size = stolen->size;
stolen->ptr = NULL;
- /*
- * With -Xtheirs or -Xours, we have cleanly merged;
- * otherwise we got a conflict.
- */
- return opts->variant == XDL_MERGE_FAVOR_OURS ||
- opts->variant == XDL_MERGE_FAVOR_THEIRS ?
- 0 : 1;
+ return ret;
}
-static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
+static enum ll_merge_result ll_xdl_merge(const struct ll_merge_driver *drv_unused,
mmbuffer_t *result,
const char *path,
mmfile_t *orig, const char *orig_name,
@@ -105,7 +103,9 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
const struct ll_merge_options *opts,
int marker_size)
{
+ enum ll_merge_result ret;
xmparam_t xmp;
+ int status;
assert(opts);
if (orig->size > MAX_XDIFF_SIZE ||
@@ -133,10 +133,12 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
xmp.ancestor = orig_name;
xmp.file1 = name1;
xmp.file2 = name2;
- return xdl_merge(orig, src1, src2, &xmp, result);
+ status = xdl_merge(orig, src1, src2, &xmp, result);
+ ret = (status > 0) ? LL_MERGE_CONFLICT : status;
+ return ret;
}
-static int ll_union_merge(const struct ll_merge_driver *drv_unused,
+static enum ll_merge_result ll_union_merge(const struct ll_merge_driver *drv_unused,
mmbuffer_t *result,
const char *path,
mmfile_t *orig, const char *orig_name,
@@ -178,7 +180,7 @@ static void create_temp(mmfile_t *src, char *path, size_t len)
/*
* User defined low-level merge driver support.
*/
-static int ll_ext_merge(const struct ll_merge_driver *fn,
+static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn,
mmbuffer_t *result,
const char *path,
mmfile_t *orig, const char *orig_name,
@@ -194,6 +196,7 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
const char *args[] = { NULL, NULL };
int status, fd, i;
struct stat st;
+ enum ll_merge_result ret;
assert(opts);
sq_quote_buf(&path_sq, path);
@@ -236,7 +239,8 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
unlink_or_warn(temp[i]);
strbuf_release(&cmd);
strbuf_release(&path_sq);
- return status;
+ ret = (status > 0) ? LL_MERGE_CONFLICT : status;
+ return ret;
}
/*
@@ -362,7 +366,7 @@ static void normalize_file(mmfile_t *mm, const char *path, struct index_state *i
}
}
-int ll_merge(mmbuffer_t *result_buf,
+enum ll_merge_result ll_merge(mmbuffer_t *result_buf,
const char *path,
mmfile_t *ancestor, const char *ancestor_label,
mmfile_t *ours, const char *our_label,
diff --git a/ll-merge.h b/ll-merge.h
index aceb1b2413..e4a20e81a3 100644
--- a/ll-merge.h
+++ b/ll-merge.h
@@ -82,13 +82,20 @@ struct ll_merge_options {
long xdl_opts;
};
+enum ll_merge_result {
+ LL_MERGE_ERROR = -1,
+ LL_MERGE_OK = 0,
+ LL_MERGE_CONFLICT,
+ LL_MERGE_BINARY_CONFLICT,
+};
+
/**
* Perform a three-way single-file merge in core. This is a thin wrapper
* around `xdl_merge` that takes the path and any merge backend specified in
* `.gitattributes` or `.git/info/attributes` into account.
* Returns 0 for a clean merge.
*/
-int ll_merge(mmbuffer_t *result_buf,
+enum ll_merge_result ll_merge(mmbuffer_t *result_buf,
const char *path,
mmfile_t *ancestor, const char *ancestor_label,
mmfile_t *ours, const char *our_label,
diff --git a/log-tree.c b/log-tree.c
index d3e7a40b64..25165e2a91 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -1,12 +1,15 @@
#include "cache.h"
+#include "commit-reach.h"
#include "config.h"
#include "diff.h"
#include "object-store.h"
#include "repository.h"
+#include "tmp-objdir.h"
#include "commit.h"
#include "tag.h"
#include "graph.h"
#include "log-tree.h"
+#include "merge-ort.h"
#include "reflog-walk.h"
#include "refs.h"
#include "string-list.h"
@@ -16,6 +19,7 @@
#include "line-log.h"
#include "help.h"
#include "range-diff.h"
+#include "strmap.h"
static struct decoration name_decoration = { "object names" };
static int decoration_loaded;
@@ -849,7 +853,7 @@ int log_tree_diff_flush(struct rev_info *opt)
opt->shown_dashes = 0;
diffcore_std(&opt->diffopt);
- if (diff_queue_is_empty()) {
+ if (diff_queue_is_empty(&opt->diffopt)) {
int saved_fmt = opt->diffopt.output_format;
opt->diffopt.output_format = DIFF_FORMAT_NO_OUTPUT;
diff_flush(&opt->diffopt);
@@ -904,6 +908,106 @@ static int do_diff_combined(struct rev_info *opt, struct commit *commit)
return !opt->loginfo;
}
+static void setup_additional_headers(struct diff_options *o,
+ struct strmap *all_headers)
+{
+ struct hashmap_iter iter;
+ struct strmap_entry *entry;
+
+ /*
+ * Make o->additional_path_headers contain the subset of all_headers
+ * that match o->pathspec. If there aren't any that match o->pathspec,
+ * then make o->additional_path_headers be NULL.
+ */
+
+ if (!o->pathspec.nr) {
+ o->additional_path_headers = all_headers;
+ return;
+ }
+
+ o->additional_path_headers = xmalloc(sizeof(struct strmap));
+ strmap_init_with_options(o->additional_path_headers, NULL, 0);
+ strmap_for_each_entry(all_headers, &iter, entry) {
+ if (match_pathspec(the_repository->index, &o->pathspec,
+ entry->key, strlen(entry->key),
+ 0 /* prefix */, NULL /* seen */,
+ 0 /* is_dir */))
+ strmap_put(o->additional_path_headers,
+ entry->key, entry->value);
+ }
+ if (!strmap_get_size(o->additional_path_headers)) {
+ strmap_clear(o->additional_path_headers, 0);
+ FREE_AND_NULL(o->additional_path_headers);
+ }
+}
+
+static void cleanup_additional_headers(struct diff_options *o)
+{
+ if (!o->pathspec.nr) {
+ o->additional_path_headers = NULL;
+ return;
+ }
+ if (!o->additional_path_headers)
+ return;
+
+ strmap_clear(o->additional_path_headers, 0);
+ FREE_AND_NULL(o->additional_path_headers);
+}
+
+static int do_remerge_diff(struct rev_info *opt,
+ struct commit_list *parents,
+ struct object_id *oid,
+ struct commit *commit)
+{
+ struct merge_options o;
+ struct commit_list *bases;
+ struct merge_result res = {0};
+ struct pretty_print_context ctx = {0};
+ struct commit *parent1 = parents->item;
+ struct commit *parent2 = parents->next->item;
+ struct strbuf parent1_desc = STRBUF_INIT;
+ struct strbuf parent2_desc = STRBUF_INIT;
+
+ /* Setup merge options */
+ init_merge_options(&o, the_repository);
+ o.show_rename_progress = 0;
+ o.record_conflict_msgs_as_headers = 1;
+ o.msg_header_prefix = "remerge";
+
+ ctx.abbrev = DEFAULT_ABBREV;
+ format_commit_message(parent1, "%h (%s)", &parent1_desc, &ctx);
+ format_commit_message(parent2, "%h (%s)", &parent2_desc, &ctx);
+ o.branch1 = parent1_desc.buf;
+ o.branch2 = parent2_desc.buf;
+
+ /* Parse the relevant commits and get the merge bases */
+ parse_commit_or_die(parent1);
+ parse_commit_or_die(parent2);
+ bases = get_merge_bases(parent1, parent2);
+
+ /* Re-merge the parents */
+ merge_incore_recursive(&o, bases, parent1, parent2, &res);
+
+ /* Show the diff */
+ setup_additional_headers(&opt->diffopt, res.path_messages);
+ diff_tree_oid(&res.tree->object.oid, oid, "", &opt->diffopt);
+ log_tree_diff_flush(opt);
+
+ /* Cleanup */
+ cleanup_additional_headers(&opt->diffopt);
+ strbuf_release(&parent1_desc);
+ strbuf_release(&parent2_desc);
+ merge_finalize(&o, &res);
+
+ /* Clean up the contents of the temporary object directory */
+ if (opt->remerge_objdir)
+ tmp_objdir_discard_objects(opt->remerge_objdir);
+ else
+ BUG("did a remerge diff without remerge_objdir?!?");
+
+ return !opt->loginfo;
+}
+
/*
* Show the diff of a commit.
*
@@ -938,6 +1042,18 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log
}
if (is_merge) {
+ int octopus = (parents->next->next != NULL);
+
+ if (opt->remerge_diff) {
+ if (octopus) {
+ show_log(opt);
+ fprintf(opt->diffopt.file,
+ "diff: warning: Skipping remerge-diff "
+ "for octopus merges.\n");
+ return 1;
+ }
+ return do_remerge_diff(opt, parents, oid, commit);
+ }
if (opt->combine_merges)
return do_diff_combined(opt, commit);
if (opt->separate_merges) {
diff --git a/merge-blobs.c b/merge-blobs.c
index ee0a0e90c9..8138090f81 100644
--- a/merge-blobs.c
+++ b/merge-blobs.c
@@ -36,7 +36,7 @@ static void *three_way_filemerge(struct index_state *istate,
mmfile_t *their,
unsigned long *size)
{
- int merge_status;
+ enum ll_merge_result merge_status;
mmbuffer_t res;
/*
@@ -50,6 +50,9 @@ static void *three_way_filemerge(struct index_state *istate,
istate, NULL);
if (merge_status < 0)
return NULL;
+ if (merge_status == LL_MERGE_BINARY_CONFLICT)
+ warning("Cannot merge binary files: %s (%s vs. %s)",
+ path, ".our", ".their");
*size = res.size;
return res.ptr;
diff --git a/merge-ort.c b/merge-ort.c
index d455fca7d2..d85b1cd99e 100644
--- a/merge-ort.c
+++ b/merge-ort.c
@@ -634,17 +634,51 @@ static void path_msg(struct merge_options *opt,
const char *fmt, ...)
{
va_list ap;
- struct strbuf *sb = strmap_get(&opt->priv->output, path);
+ struct strbuf *sb, *dest;
+ struct strbuf tmp = STRBUF_INIT;
+
+ if (opt->record_conflict_msgs_as_headers && omittable_hint)
+ return; /* Do not record mere hints in headers */
+ if (opt->record_conflict_msgs_as_headers && opt->priv->call_depth)
+ return; /* Do not record inner merge issues in headers */
+ sb = strmap_get(&opt->priv->output, path);
if (!sb) {
sb = xmalloc(sizeof(*sb));
strbuf_init(sb, 0);
strmap_put(&opt->priv->output, path, sb);
}
+ dest = (opt->record_conflict_msgs_as_headers ? &tmp : sb);
+
va_start(ap, fmt);
- strbuf_vaddf(sb, fmt, ap);
+ strbuf_vaddf(dest, fmt, ap);
va_end(ap);
+ if (opt->record_conflict_msgs_as_headers) {
+ int i_sb = 0, i_tmp = 0;
+
+ /* Start with the specified prefix */
+ if (opt->msg_header_prefix)
+ strbuf_addf(sb, "%s ", opt->msg_header_prefix);
+
+ /* Copy tmp to sb, adding spaces after newlines */
+ strbuf_grow(sb, sb->len + 2*tmp.len); /* more than sufficient */
+ for (; i_tmp < tmp.len; i_tmp++, i_sb++) {
+ /* Copy next character from tmp to sb */
+ sb->buf[sb->len + i_sb] = tmp.buf[i_tmp];
+
+ /* If we copied a newline, add a space */
+ if (tmp.buf[i_tmp] == '\n')
+ sb->buf[++i_sb] = ' ';
+ }
+ /* Update length and ensure it's NUL-terminated */
+ sb->len += i_sb;
+ sb->buf[sb->len] = '\0';
+
+ strbuf_release(&tmp);
+ }
+
+ /* Add final newline character to sb */
strbuf_addch(sb, '\n');
}
@@ -1743,7 +1777,7 @@ static int merge_3way(struct merge_options *opt,
mmfile_t orig, src1, src2;
struct ll_merge_options ll_opts = {0};
char *base, *name1, *name2;
- int merge_status;
+ enum ll_merge_result merge_status;
if (!opt->priv->attr_index.initialized)
initialize_attr_index(opt);
@@ -1787,6 +1821,10 @@ static int merge_3way(struct merge_options *opt,
merge_status = ll_merge(result_buf, path, &orig, base,
&src1, name1, &src2, name2,
&opt->priv->attr_index, &ll_opts);
+ if (merge_status == LL_MERGE_BINARY_CONFLICT)
+ path_msg(opt, path, 0,
+ "warning: Cannot merge binary files: %s (%s vs. %s)",
+ path, name1, name2);
free(base);
free(name1);
@@ -2416,7 +2454,7 @@ static void apply_directory_rename_modifications(struct merge_options *opt,
*/
ci->path_conflict = 1;
if (pair->status == 'A')
- path_msg(opt, new_path, 0,
+ path_msg(opt, new_path, 1,
_("CONFLICT (file location): %s added in %s "
"inside a directory that was renamed in %s, "
"suggesting it should perhaps be moved to "
@@ -2424,7 +2462,7 @@ static void apply_directory_rename_modifications(struct merge_options *opt,
old_path, branch_with_new_path,
branch_with_dir_rename, new_path);
else
- path_msg(opt, new_path, 0,
+ path_msg(opt, new_path, 1,
_("CONFLICT (file location): %s renamed to %s "
"in %s, inside a directory that was renamed "
"in %s, suggesting it should perhaps be "
@@ -4259,6 +4297,9 @@ void merge_switch_to_result(struct merge_options *opt,
struct string_list olist = STRING_LIST_INIT_NODUP;
int i;
+ if (opt->record_conflict_msgs_as_headers)
+ BUG("Either display conflict messages or record them as headers, not both");
+
trace2_region_enter("merge", "display messages", opt->repo);
/* Hack to pre-allocate olist to the desired size */
@@ -4360,6 +4401,9 @@ static void merge_start(struct merge_options *opt, struct merge_result *result)
assert(opt->recursive_variant >= MERGE_VARIANT_NORMAL &&
opt->recursive_variant <= MERGE_VARIANT_THEIRS);
+ if (opt->msg_header_prefix)
+ assert(opt->record_conflict_msgs_as_headers);
+
/*
* detect_renames, verbosity, buffer_output, and obuf are ignored
* fields that were used by "recursive" rather than "ort" -- but
@@ -4560,6 +4604,7 @@ redo:
trace2_region_leave("merge", "process_entries", opt->repo);
/* Set return values */
+ result->path_messages = &opt->priv->output;
result->tree = parse_tree_indirect(&working_tree_oid);
/* existence of conflicted entries implies unclean */
result->clean &= strmap_empty(&opt->priv->conflicted);
diff --git a/merge-ort.h b/merge-ort.h
index c011864ffe..fe599b8786 100644
--- a/merge-ort.h
+++ b/merge-ort.h
@@ -5,6 +5,7 @@
struct commit;
struct tree;
+struct strmap;
struct merge_result {
/*
@@ -24,6 +25,15 @@ struct merge_result {
struct tree *tree;
/*
+ * Special messages and conflict notices for various paths
+ *
+ * This is a map of pathnames to strbufs. It contains various
+ * warning/conflict/notice messages (possibly multiple per path)
+ * that callers may want to use.
+ */
+ struct strmap *path_messages;
+
+ /*
* Additional metadata used by merge_switch_to_result() or future calls
* to merge_incore_*(). Includes data needed to update the index (if
* !clean) and to print "CONFLICT" messages. Not for external use.
diff --git a/merge-recursive.c b/merge-recursive.c
index d9457797db..9ec1e6d043 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -1044,7 +1044,7 @@ static int merge_3way(struct merge_options *opt,
mmfile_t orig, src1, src2;
struct ll_merge_options ll_opts = {0};
char *base, *name1, *name2;
- int merge_status;
+ enum ll_merge_result merge_status;
ll_opts.renormalize = opt->renormalize;
ll_opts.extra_marker_size = extra_marker_size;
@@ -1090,6 +1090,9 @@ static int merge_3way(struct merge_options *opt,
merge_status = ll_merge(result_buf, a->path, &orig, base,
&src1, name1, &src2, name2,
opt->repo->index, &ll_opts);
+ if (merge_status == LL_MERGE_BINARY_CONFLICT)
+ warning("Cannot merge binary files: %s (%s vs. %s)",
+ a->path, name1, name2);
free(base);
free(name1);
@@ -3711,6 +3714,10 @@ static int merge_start(struct merge_options *opt, struct tree *head)
assert(opt->priv == NULL);
+ /* Not supported; option specific to merge-ort */
+ assert(!opt->record_conflict_msgs_as_headers);
+ assert(!opt->msg_header_prefix);
+
/* Sanity check on repo state; index must match head */
if (repo_index_has_changes(opt->repo, head, &sb)) {
err(opt, _("Your local changes to the following files would be overwritten by merge:\n %s"),
diff --git a/merge-recursive.h b/merge-recursive.h
index 0795a1d3ec..b88000e3c2 100644
--- a/merge-recursive.h
+++ b/merge-recursive.h
@@ -46,6 +46,8 @@ struct merge_options {
/* miscellaneous control options */
const char *subtree_shift;
unsigned renormalize : 1;
+ unsigned record_conflict_msgs_as_headers : 1;
+ const char *msg_header_prefix;
/* internal fields used by the implementation */
struct merge_options_internal *priv;
diff --git a/midx.c b/midx.c
index 837b46b2af..865170bad0 100644
--- a/midx.c
+++ b/midx.c
@@ -33,6 +33,7 @@
#define MIDX_CHUNKID_OIDLOOKUP 0x4f49444c /* "OIDL" */
#define MIDX_CHUNKID_OBJECTOFFSETS 0x4f4f4646 /* "OOFF" */
#define MIDX_CHUNKID_LARGEOFFSETS 0x4c4f4646 /* "LOFF" */
+#define MIDX_CHUNKID_REVINDEX 0x52494458 /* "RIDX" */
#define MIDX_CHUNK_FANOUT_SIZE (sizeof(uint32_t) * 256)
#define MIDX_CHUNK_OFFSET_WIDTH (2 * sizeof(uint32_t))
#define MIDX_CHUNK_LARGE_OFFSET_WIDTH (sizeof(uint64_t))
@@ -161,6 +162,9 @@ struct multi_pack_index *load_multi_pack_index(const char *object_dir, int local
pair_chunk(cf, MIDX_CHUNKID_LARGEOFFSETS, &m->chunk_large_offsets);
+ if (git_env_bool("GIT_TEST_MIDX_READ_RIDX", 1))
+ pair_chunk(cf, MIDX_CHUNKID_REVINDEX, &m->chunk_revindex);
+
m->num_objects = ntohl(m->chunk_oid_fanout[255]);
CALLOC_ARRAY(m->pack_names, m->num_packs);
@@ -833,6 +837,18 @@ static int write_midx_large_offsets(struct hashfile *f,
return 0;
}
+static int write_midx_revindex(struct hashfile *f,
+ void *data)
+{
+ struct write_midx_context *ctx = data;
+ uint32_t i;
+
+ for (i = 0; i < ctx->entries_nr; i++)
+ hashwrite_be32(f, ctx->pack_order[i]);
+
+ return 0;
+}
+
struct midx_pack_order_data {
uint32_t nr;
uint32_t pack;
@@ -1061,6 +1077,9 @@ static int write_midx_bitmap(char *midx_name, unsigned char *midx_hash,
char *bitmap_name = xstrfmt("%s-%s.bitmap", midx_name, hash_to_hex(midx_hash));
int ret;
+ if (!ctx->entries_nr)
+ BUG("cannot write a bitmap without any objects");
+
if (flags & MIDX_WRITE_BITMAP_HASH_CACHE)
options |= BITMAP_OPT_HASH_CACHE;
@@ -1385,6 +1404,12 @@ static int write_midx_internal(const char *object_dir,
goto cleanup;
}
+ if (!ctx.entries_nr) {
+ if (flags & MIDX_WRITE_BITMAP)
+ warning(_("refusing to write multi-pack .bitmap without any objects"));
+ flags &= ~(MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP);
+ }
+
cf = init_chunkfile(f);
add_chunk(cf, MIDX_CHUNKID_PACKNAMES, pack_name_concat_len,
@@ -1403,16 +1428,21 @@ static int write_midx_internal(const char *object_dir,
(size_t)ctx.num_large_offsets * MIDX_CHUNK_LARGE_OFFSET_WIDTH,
write_midx_large_offsets);
+ if (flags & (MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP)) {
+ ctx.pack_order = midx_pack_order(&ctx);
+ add_chunk(cf, MIDX_CHUNKID_REVINDEX,
+ ctx.entries_nr * sizeof(uint32_t),
+ write_midx_revindex);
+ }
+
write_midx_header(f, get_num_chunks(cf), ctx.nr - dropped_packs);
write_chunkfile(cf, &ctx);
finalize_hashfile(f, midx_hash, CSUM_FSYNC | CSUM_HASH_IN_STREAM);
free_chunkfile(cf);
- if (flags & (MIDX_WRITE_REV_INDEX | MIDX_WRITE_BITMAP))
- ctx.pack_order = midx_pack_order(&ctx);
-
- if (flags & MIDX_WRITE_REV_INDEX)
+ if (flags & MIDX_WRITE_REV_INDEX &&
+ git_env_bool("GIT_TEST_MIDX_WRITE_REV", 0))
write_midx_reverse_index(midx_name.buf, midx_hash, &ctx);
if (flags & MIDX_WRITE_BITMAP) {
if (write_midx_bitmap(midx_name.buf, midx_hash, &ctx,
diff --git a/midx.h b/midx.h
index b7d79a515c..22e8e53288 100644
--- a/midx.h
+++ b/midx.h
@@ -36,6 +36,7 @@ struct multi_pack_index {
const unsigned char *chunk_oid_lookup;
const unsigned char *chunk_object_offsets;
const unsigned char *chunk_large_offsets;
+ const unsigned char *chunk_revindex;
const char **pack_names;
struct packed_git **packs;
diff --git a/notes-merge.c b/notes-merge.c
index b4a3a903e8..01d596920e 100644
--- a/notes-merge.c
+++ b/notes-merge.c
@@ -344,7 +344,7 @@ static int ll_merge_in_worktree(struct notes_merge_options *o,
{
mmbuffer_t result_buf;
mmfile_t base, local, remote;
- int status;
+ enum ll_merge_result status;
read_mmblob(&base, &p->base);
read_mmblob(&local, &p->local);
@@ -358,6 +358,9 @@ static int ll_merge_in_worktree(struct notes_merge_options *o,
free(local.ptr);
free(remote.ptr);
+ if (status == LL_MERGE_BINARY_CONFLICT)
+ warning("Cannot merge binary files: %s (%s vs. %s)",
+ oid_to_hex(&p->obj), o->local_ref, o->remote_ref);
if ((status < 0) || !result_buf.ptr)
die("Failed to execute internal merge");
diff --git a/pack-bitmap.c b/pack-bitmap.c
index f772d3cb7f..9c666cdb8b 100644
--- a/pack-bitmap.c
+++ b/pack-bitmap.c
@@ -358,7 +358,9 @@ static int open_midx_bitmap_1(struct bitmap_index *bitmap_git,
cleanup:
munmap(bitmap_git->map, bitmap_git->map_size);
bitmap_git->map_size = 0;
+ bitmap_git->map_pos = 0;
bitmap_git->map = NULL;
+ bitmap_git->midx = NULL;
return -1;
}
@@ -405,6 +407,8 @@ static int open_pack_bitmap_1(struct bitmap_index *bitmap_git, struct packed_git
munmap(bitmap_git->map, bitmap_git->map_size);
bitmap_git->map = NULL;
bitmap_git->map_size = 0;
+ bitmap_git->map_pos = 0;
+ bitmap_git->pack = NULL;
return -1;
}
diff --git a/pack-revindex.c b/pack-revindex.c
index 70d0fbafcb..08dc160167 100644
--- a/pack-revindex.c
+++ b/pack-revindex.c
@@ -298,9 +298,29 @@ int load_midx_revindex(struct multi_pack_index *m)
{
struct strbuf revindex_name = STRBUF_INIT;
int ret;
+
if (m->revindex_data)
return 0;
+ if (m->chunk_revindex) {
+ /*
+ * If the MIDX `m` has a `RIDX` chunk, then use its contents for
+ * the reverse index instead of trying to load a separate `.rev`
+ * file.
+ *
+ * Note that we do *not* set `m->revindex_map` here, since we do
+ * not want to accidentally call munmap() in the middle of the
+ * MIDX.
+ */
+ trace2_data_string("load_midx_revindex", the_repository,
+ "source", "midx");
+ m->revindex_data = (const uint32_t *)m->chunk_revindex;
+ return 0;
+ }
+
+ trace2_data_string("load_midx_revindex", the_repository,
+ "source", "rev");
+
get_midx_rev_filename(&revindex_name, m);
ret = load_revindex_from_disk(revindex_name.buf,
diff --git a/parse-options.h b/parse-options.h
index cbeb97b90e..659a4c28b2 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -85,6 +85,11 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
* token to explain the kind of argument this option wants. Does not
* begin in capital letter, and does not end with a full stop.
* Should be wrapped by N_() for translation.
+ * Is automatically enclosed in brackets when printed, unless it
+ * contains any of the following characters: ()<>[]|
+ * E.g. "name" is shown as "<name>" to indicate that a name value
+ * needs to be supplied, not the literal string "name", but
+ * "<start>,<end>" and "(this|that)" are printed verbatim.
*
* `help`::
* the short help associated to what the option does.
diff --git a/perl/Git.pm b/perl/Git.pm
index 090a7df63f..080cdc2a21 100644
--- a/perl/Git.pm
+++ b/perl/Git.pm
@@ -1686,6 +1686,16 @@ sub _setup_git_cmd_env {
# by searching for it at proper places.
sub _execv_git_cmd { exec('git', @_); }
+sub _is_sig {
+ my ($v, $n) = @_;
+
+ # We are avoiding a "use POSIX qw(SIGPIPE SIGABRT)" in the hot
+ # Git.pm codepath.
+ require POSIX;
+ no strict 'refs';
+ $v == *{"POSIX::$n"}->();
+}
+
# Close pipe to a subprocess.
sub _cmd_close {
my $ctx = shift @_;
@@ -1698,9 +1708,16 @@ sub _cmd_close {
} elsif ($? >> 8) {
# The caller should pepper this.
throw Git::Error::Command($ctx, $? >> 8);
+ } elsif ($? & 127 && _is_sig($? & 127, "SIGPIPE")) {
+ # we might e.g. closed a live stream; the command
+ # dying of SIGPIPE would drive us here.
+ } elsif ($? & 127 && _is_sig($? & 127, "SIGABRT")) {
+ die sprintf('BUG?: got SIGABRT ($? = %d, $? & 127 = %d) when closing pipe',
+ $?, $? & 127);
+ } elsif ($? & 127) {
+ die sprintf('got signal ($? = %d, $? & 127 = %d) when closing pipe',
+ $?, $? & 127);
}
- # else we might e.g. closed a live stream; the command
- # dying of SIGPIPE would drive us here.
}
}
diff --git a/read-cache.c b/read-cache.c
index 63a792e578..79b9b99ebf 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -1340,9 +1340,6 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK;
int new_only = option & ADD_CACHE_NEW_ONLY;
- if (!(option & ADD_CACHE_KEEP_CACHE_TREE))
- cache_tree_invalidate_path(istate, ce->name);
-
/*
* If this entry's path sorts after the last entry in the index,
* we can avoid searching for it.
@@ -1353,6 +1350,13 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
else
pos = index_name_stage_pos(istate, ce->name, ce_namelen(ce), ce_stage(ce), EXPAND_SPARSE);
+ /*
+ * Cache tree path should be invalidated only after index_name_stage_pos,
+ * in case it expands a sparse index.
+ */
+ if (!(option & ADD_CACHE_KEEP_CACHE_TREE))
+ cache_tree_invalidate_path(istate, ce->name);
+
/* existing match? Just replace it. */
if (pos >= 0) {
if (!new_only)
diff --git a/refs.c b/refs.c
index addb26293b..d680de3bc0 100644
--- a/refs.c
+++ b/refs.c
@@ -269,10 +269,9 @@ char *refs_resolve_refdup(struct ref_store *refs,
struct object_id *oid, int *flags)
{
const char *result;
- int ignore_errno;
result = refs_resolve_ref_unsafe(refs, refname, resolve_flags,
- oid, flags, &ignore_errno);
+ oid, flags);
return xstrdup_or_null(result);
}
@@ -294,11 +293,10 @@ struct ref_filter {
int read_ref_full(const char *refname, int resolve_flags, struct object_id *oid, int *flags)
{
- int ignore_errno;
struct ref_store *refs = get_main_ref_store(the_repository);
if (refs_resolve_ref_unsafe(refs, refname, resolve_flags,
- oid, flags, &ignore_errno))
+ oid, flags))
return 0;
return -1;
}
@@ -310,9 +308,8 @@ int read_ref(const char *refname, struct object_id *oid)
int refs_ref_exists(struct ref_store *refs, const char *refname)
{
- int ignore_errno;
return !!refs_resolve_ref_unsafe(refs, refname, RESOLVE_REF_READING,
- NULL, NULL, &ignore_errno);
+ NULL, NULL);
}
int ref_exists(const char *refname)
@@ -656,15 +653,13 @@ int expand_ref(struct repository *repo, const char *str, int len,
struct object_id *this_result;
int flag;
struct ref_store *refs = get_main_ref_store(repo);
- int ignore_errno;
this_result = refs_found ? &oid_from_ref : oid;
strbuf_reset(&fullref);
strbuf_addf(&fullref, *p, len, str);
r = refs_resolve_ref_unsafe(refs, fullref.buf,
RESOLVE_REF_READING,
- this_result, &flag,
- &ignore_errno);
+ this_result, &flag);
if (r) {
if (!refs_found++)
*ref = xstrdup(r);
@@ -693,14 +688,12 @@ int repo_dwim_log(struct repository *r, const char *str, int len,
for (p = ref_rev_parse_rules; *p; p++) {
struct object_id hash;
const char *ref, *it;
- int ignore_errno;
strbuf_reset(&path);
strbuf_addf(&path, *p, len, str);
ref = refs_resolve_ref_unsafe(refs, path.buf,
RESOLVE_REF_READING,
- oid ? &hash : NULL, NULL,
- &ignore_errno);
+ oid ? &hash : NULL, NULL);
if (!ref)
continue;
if (refs_reflog_exists(refs, path.buf))
@@ -800,7 +793,7 @@ int refs_delete_ref(struct ref_store *refs, const char *msg,
struct ref_transaction *transaction;
struct strbuf err = STRBUF_INIT;
- transaction = ref_store_transaction_begin(refs, &err);
+ transaction = ref_store_transaction_begin(refs, 0, &err);
if (!transaction ||
ref_transaction_delete(transaction, refname, old_oid,
flags, msg, &err) ||
@@ -1005,6 +998,7 @@ int read_ref_at(struct ref_store *refs, const char *refname,
}
struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,
+ unsigned int flags,
struct strbuf *err)
{
struct ref_transaction *tr;
@@ -1012,12 +1006,13 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,
CALLOC_ARRAY(tr, 1);
tr->ref_store = refs;
+ tr->flags = flags;
return tr;
}
struct ref_transaction *ref_transaction_begin(struct strbuf *err)
{
- return ref_store_transaction_begin(get_main_ref_store(the_repository), err);
+ return ref_store_transaction_begin(get_main_ref_store(the_repository), 0, err);
}
void ref_transaction_free(struct ref_transaction *transaction)
@@ -1156,7 +1151,7 @@ int refs_update_ref(struct ref_store *refs, const char *msg,
struct strbuf err = STRBUF_INIT;
int ret = 0;
- t = ref_store_transaction_begin(refs, &err);
+ t = ref_store_transaction_begin(refs, 0, &err);
if (!t ||
ref_transaction_update(t, refname, new_oid, old_oid, flags, msg,
&err) ||
@@ -1390,10 +1385,9 @@ int refs_head_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
{
struct object_id oid;
int flag;
- int ignore_errno;
if (refs_resolve_ref_unsafe(refs, "HEAD", RESOLVE_REF_READING,
- &oid, &flag, &ignore_errno))
+ &oid, &flag))
return fn("HEAD", &oid, flag, cb_data);
return 0;
@@ -1682,15 +1676,13 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
const char *refname,
int resolve_flags,
struct object_id *oid,
- int *flags, int *failure_errno)
+ int *flags)
{
static struct strbuf sb_refname = STRBUF_INIT;
struct object_id unused_oid;
int unused_flags;
int symref_count;
- assert(failure_errno);
-
if (!oid)
oid = &unused_oid;
if (!flags)
@@ -1700,10 +1692,8 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
- !refname_is_safe(refname)) {
- *failure_errno = EINVAL;
+ !refname_is_safe(refname))
return NULL;
- }
/*
* dwim_ref() uses REF_ISBROKEN to distinguish between
@@ -1718,9 +1708,10 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
for (symref_count = 0; symref_count < SYMREF_MAXDEPTH; symref_count++) {
unsigned int read_flags = 0;
+ int failure_errno;
if (refs_read_raw_ref(refs, refname, oid, &sb_refname,
- &read_flags, failure_errno)) {
+ &read_flags, &failure_errno)) {
*flags |= read_flags;
/* In reading mode, refs must eventually resolve */
@@ -1732,9 +1723,9 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
* may show errors besides ENOENT if there are
* similarly-named refs.
*/
- if (*failure_errno != ENOENT &&
- *failure_errno != EISDIR &&
- *failure_errno != ENOTDIR)
+ if (failure_errno != ENOENT &&
+ failure_errno != EISDIR &&
+ failure_errno != ENOTDIR)
return NULL;
oidclr(oid);
@@ -1760,16 +1751,13 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
}
if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
- !refname_is_safe(refname)) {
- *failure_errno = EINVAL;
+ !refname_is_safe(refname))
return NULL;
- }
*flags |= REF_ISBROKEN | REF_BAD_NAME;
}
}
- *failure_errno = ELOOP;
return NULL;
}
@@ -1784,10 +1772,8 @@ int refs_init_db(struct strbuf *err)
const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
struct object_id *oid, int *flags)
{
- int ignore_errno;
-
return refs_resolve_ref_unsafe(get_main_ref_store(the_repository), refname,
- resolve_flags, oid, flags, &ignore_errno);
+ resolve_flags, oid, flags);
}
int resolve_gitlink_ref(const char *submodule, const char *refname,
@@ -1795,15 +1781,14 @@ int resolve_gitlink_ref(const char *submodule, const char *refname,
{
struct ref_store *refs;
int flags;
- int ignore_errno;
refs = get_submodule_ref_store(submodule);
if (!refs)
return -1;
- if (!refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags,
- &ignore_errno) || is_null_oid(oid))
+ if (!refs_resolve_ref_unsafe(refs, refname, 0, oid, &flags) ||
+ is_null_oid(oid))
return -1;
return 0;
}
@@ -2082,6 +2067,9 @@ static int run_transaction_hook(struct ref_transaction *transaction,
const char *hook;
int ret = 0, i;
+ if (transaction->flags & REF_TRANSACTION_SKIP_HOOK)
+ return 0;
+
hook = find_hook("reference-transaction");
if (!hook)
return ret;
diff --git a/refs.h b/refs.h
index 8f91a7f9ff..ff859d5951 100644
--- a/refs.h
+++ b/refs.h
@@ -58,11 +58,6 @@ struct worktree;
* resolved. The function returns NULL for such ref names.
* Caps and underscores refers to the special refs, such as HEAD,
* FETCH_HEAD and friends, that all live outside of the refs/ directory.
- *
- * Callers should not inspect "errno" on failure, but rather pass in a
- * "failure_errno" parameter, on failure the "errno" will indicate the
- * type of failure encountered, but not necessarily one that came from
- * a syscall. We might have faked it up.
*/
#define RESOLVE_REF_READING 0x01
#define RESOLVE_REF_NO_RECURSE 0x02
@@ -72,7 +67,7 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
const char *refname,
int resolve_flags,
struct object_id *oid,
- int *flags, int *failure_errno);
+ int *flags);
const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
struct object_id *oid, int *flags);
@@ -231,7 +226,7 @@ char *repo_default_branch_name(struct repository *r, int quiet);
* struct strbuf err = STRBUF_INIT;
* int ret = 0;
*
- * transaction = ref_store_transaction_begin(refs, &err);
+ * transaction = ref_store_transaction_begin(refs, 0, &err);
* if (!transaction ||
* ref_transaction_update(...) ||
* ref_transaction_create(...) ||
@@ -569,10 +564,16 @@ enum action_on_err {
};
/*
+ * Skip executing the reference-transaction hook.
+ */
+#define REF_TRANSACTION_SKIP_HOOK (1 << 0)
+
+/*
* Begin a reference transaction. The reference transaction must
* be freed by calling ref_transaction_free().
*/
struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,
+ unsigned int flags,
struct strbuf *err);
struct ref_transaction *ref_transaction_begin(struct strbuf *err);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 43a3b882d7..f59589d6cc 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -277,11 +277,10 @@ static void loose_fill_ref_dir(struct ref_store *ref_store,
create_dir_entry(dir->cache, refname.buf,
refname.len));
} else {
- int ignore_errno;
if (!refs_resolve_ref_unsafe(&refs->base,
refname.buf,
RESOLVE_REF_READING,
- &oid, &flag, &ignore_errno)) {
+ &oid, &flag)) {
oidclr(&oid);
flag |= REF_ISBROKEN;
} else if (is_null_oid(&oid)) {
@@ -1006,7 +1005,6 @@ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs,
{
struct strbuf ref_file = STRBUF_INIT;
struct ref_lock *lock;
- int ignore_errno;
files_assert_main_repository(refs, "lock_ref_oid_basic");
assert(err);
@@ -1034,7 +1032,7 @@ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs,
}
if (!refs_resolve_ref_unsafe(&refs->base, lock->ref_name, 0,
- &lock->old_oid, NULL, &ignore_errno))
+ &lock->old_oid, NULL))
oidclr(&lock->old_oid);
goto out;
@@ -1116,7 +1114,8 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)
if (check_refname_format(r->name, 0))
return;
- transaction = ref_store_transaction_begin(&refs->base, &err);
+ transaction = ref_store_transaction_begin(&refs->base,
+ REF_TRANSACTION_SKIP_HOOK, &err);
if (!transaction)
goto cleanup;
ref_transaction_add_update(
@@ -1187,7 +1186,8 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags)
struct strbuf err = STRBUF_INIT;
struct ref_transaction *transaction;
- transaction = ref_store_transaction_begin(refs->packed_ref_store, &err);
+ transaction = ref_store_transaction_begin(refs->packed_ref_store,
+ REF_TRANSACTION_SKIP_HOOK, &err);
if (!transaction)
return -1;
@@ -1244,6 +1244,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg,
{
struct files_ref_store *refs =
files_downcast(ref_store, REF_STORE_WRITE, "delete_refs");
+ struct ref_transaction *transaction = NULL;
struct strbuf err = STRBUF_INIT;
int i, result = 0;
@@ -1253,10 +1254,15 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg,
if (packed_refs_lock(refs->packed_ref_store, 0, &err))
goto error;
- if (refs_delete_refs(refs->packed_ref_store, msg, refnames, flags)) {
- packed_refs_unlock(refs->packed_ref_store);
+ transaction = ref_store_transaction_begin(refs->packed_ref_store,
+ REF_TRANSACTION_SKIP_HOOK, &err);
+ if (!transaction)
+ goto error;
+
+ result = packed_refs_delete_refs(refs->packed_ref_store,
+ transaction, msg, refnames, flags);
+ if (result)
goto error;
- }
packed_refs_unlock(refs->packed_ref_store);
@@ -1267,6 +1273,7 @@ static int files_delete_refs(struct ref_store *ref_store, const char *msg,
result |= error(_("could not remove reference %s"), refname);
}
+ ref_transaction_free(transaction);
strbuf_release(&err);
return result;
@@ -1283,6 +1290,7 @@ error:
else
error(_("could not delete references: %s"), err.buf);
+ ref_transaction_free(transaction);
strbuf_release(&err);
return -1;
}
@@ -1399,7 +1407,6 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
struct strbuf tmp_renamed_log = STRBUF_INIT;
int log, ret;
struct strbuf err = STRBUF_INIT;
- int ignore_errno;
files_reflog_path(refs, &sb_oldref, oldrefname);
files_reflog_path(refs, &sb_newref, newrefname);
@@ -1413,7 +1420,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
if (!refs_resolve_ref_unsafe(&refs->base, oldrefname,
RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
- &orig_oid, &flag, &ignore_errno)) {
+ &orig_oid, &flag)) {
ret = error("refname %s not found", oldrefname);
goto out;
}
@@ -1459,7 +1466,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store,
*/
if (!copy && refs_resolve_ref_unsafe(&refs->base, newrefname,
RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
- NULL, NULL, &ignore_errno) &&
+ NULL, NULL) &&
refs_delete_ref(&refs->base, NULL, newrefname,
NULL, REF_NO_DEREF)) {
if (errno == EISDIR) {
@@ -1828,12 +1835,10 @@ static int commit_ref_update(struct files_ref_store *refs,
*/
int head_flag;
const char *head_ref;
- int ignore_errno;
head_ref = refs_resolve_ref_unsafe(&refs->base, "HEAD",
RESOLVE_REF_READING,
- NULL, &head_flag,
- &ignore_errno);
+ NULL, &head_flag);
if (head_ref && (head_flag & REF_ISSYMREF) &&
!strcmp(head_ref, lock->ref_name)) {
struct strbuf log_err = STRBUF_INIT;
@@ -1877,12 +1882,10 @@ static void update_symref_reflog(struct files_ref_store *refs,
{
struct strbuf err = STRBUF_INIT;
struct object_id new_oid;
- int ignore_errno;
if (logmsg &&
refs_resolve_ref_unsafe(&refs->base, target,
- RESOLVE_REF_READING, &new_oid, NULL,
- &ignore_errno) &&
+ RESOLVE_REF_READING, &new_oid, NULL) &&
files_log_ref_write(refs, refname, &lock->old_oid,
&new_oid, logmsg, 0, &err)) {
error("%s", err.buf);
@@ -2156,7 +2159,6 @@ static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator)
(struct files_reflog_iterator *)ref_iterator;
struct dir_iterator *diter = iter->dir_iterator;
int ok;
- int ignore_errno;
while ((ok = dir_iterator_advance(diter)) == ITER_OK) {
int flags;
@@ -2170,8 +2172,7 @@ static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator)
if (!refs_resolve_ref_unsafe(iter->ref_store,
diter->relative_path, 0,
- &iter->oid, &flags,
- &ignore_errno)) {
+ &iter->oid, &flags)) {
error("bad ref for %s", diter->path.buf);
continue;
}
@@ -2515,11 +2516,9 @@ static int lock_ref_for_update(struct files_ref_store *refs,
* the transaction, so we have to read it here
* to record and possibly check old_oid:
*/
- int ignore_errno;
if (!refs_resolve_ref_unsafe(&refs->base,
referent.buf, 0,
- &lock->old_oid, NULL,
- &ignore_errno)) {
+ &lock->old_oid, NULL)) {
if (update->flags & REF_HAVE_OLD) {
strbuf_addf(err, "cannot lock ref '%s': "
"error reading reference",
@@ -2762,7 +2761,8 @@ static int files_transaction_prepare(struct ref_store *ref_store,
*/
if (!packed_transaction) {
packed_transaction = ref_store_transaction_begin(
- refs->packed_ref_store, err);
+ refs->packed_ref_store,
+ REF_TRANSACTION_SKIP_HOOK, err);
if (!packed_transaction) {
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
@@ -3033,7 +3033,8 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,
&affected_refnames))
BUG("initial ref transaction called with existing refs");
- packed_transaction = ref_store_transaction_begin(refs->packed_ref_store, err);
+ packed_transaction = ref_store_transaction_begin(refs->packed_ref_store,
+ REF_TRANSACTION_SKIP_HOOK, err);
if (!packed_transaction) {
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
@@ -3208,14 +3209,12 @@ static int files_reflog_expire(struct ref_store *ref_store,
if ((expire_flags & EXPIRE_REFLOGS_UPDATE_REF) &&
!is_null_oid(&cb.last_kept_oid)) {
- int ignore_errno;
int type;
const char *ref;
ref = refs_resolve_ref_unsafe(&refs->base, refname,
RESOLVE_REF_NO_RECURSE,
- NULL, &type,
- &ignore_errno);
+ NULL, &type);
update = !!(ref && !(type & REF_ISSYMREF));
}
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index d91a2018f6..27dd8c3922 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1521,15 +1521,10 @@ static int packed_initial_transaction_commit(struct ref_store *ref_store,
static int packed_delete_refs(struct ref_store *ref_store, const char *msg,
struct string_list *refnames, unsigned int flags)
{
- struct packed_ref_store *refs =
- packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs");
struct strbuf err = STRBUF_INIT;
struct ref_transaction *transaction;
- struct string_list_item *item;
int ret;
- (void)refs; /* We need the check above, but don't use the variable */
-
if (!refnames->nr)
return 0;
@@ -1539,10 +1534,30 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg,
* updates into a single transaction.
*/
- transaction = ref_store_transaction_begin(ref_store, &err);
+ transaction = ref_store_transaction_begin(ref_store, 0, &err);
if (!transaction)
return -1;
+ ret = packed_refs_delete_refs(ref_store, transaction,
+ msg, refnames, flags);
+
+ ref_transaction_free(transaction);
+ return ret;
+}
+
+int packed_refs_delete_refs(struct ref_store *ref_store,
+ struct ref_transaction *transaction,
+ const char *msg,
+ struct string_list *refnames,
+ unsigned int flags)
+{
+ struct strbuf err = STRBUF_INIT;
+ struct string_list_item *item;
+ int ret;
+
+ /* Assert that the ref store refers to a packed backend. */
+ packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs");
+
for_each_string_list_item(item, refnames) {
if (ref_transaction_delete(transaction, item->string, NULL,
flags, msg, &err)) {
@@ -1562,7 +1577,6 @@ static int packed_delete_refs(struct ref_store *ref_store, const char *msg,
error(_("could not delete references: %s"), err.buf);
}
- ref_transaction_free(transaction);
strbuf_release(&err);
return ret;
}
diff --git a/refs/packed-backend.h b/refs/packed-backend.h
index 9dd8a344c3..52e0490753 100644
--- a/refs/packed-backend.h
+++ b/refs/packed-backend.h
@@ -3,6 +3,7 @@
struct repository;
struct ref_transaction;
+struct string_list;
/*
* Support for storing references in a `packed-refs` file.
@@ -27,6 +28,12 @@ int packed_refs_lock(struct ref_store *ref_store, int flags, struct strbuf *err)
void packed_refs_unlock(struct ref_store *ref_store);
int packed_refs_is_locked(struct ref_store *ref_store);
+int packed_refs_delete_refs(struct ref_store *ref_store,
+ struct ref_transaction *transaction,
+ const char *msg,
+ struct string_list *refnames,
+ unsigned int flags);
+
/*
* Return true if `transaction` really needs to be carried out against
* the specified packed_ref_store, or false if it can be skipped
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 7ff6fba4f0..6e15db3ca4 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -213,6 +213,7 @@ struct ref_transaction {
size_t nr;
enum ref_transaction_state state;
void *backend_data;
+ unsigned int flags;
};
/*
diff --git a/reftable/block.c b/reftable/block.c
index 855e3f5c94..2170748c5e 100644
--- a/reftable/block.c
+++ b/reftable/block.c
@@ -188,13 +188,16 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
uint32_t full_block_size = table_block_size;
uint8_t typ = block->data[header_off];
uint32_t sz = get_be24(block->data + header_off + 1);
-
+ int err = 0;
uint16_t restart_count = 0;
uint32_t restart_start = 0;
uint8_t *restart_bytes = NULL;
+ uint8_t *uncompressed = NULL;
- if (!reftable_is_block_type(typ))
- return REFTABLE_FORMAT_ERROR;
+ if (!reftable_is_block_type(typ)) {
+ err = REFTABLE_FORMAT_ERROR;
+ goto done;
+ }
if (typ == BLOCK_TYPE_LOG) {
int block_header_skip = 4 + header_off;
@@ -203,7 +206,7 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
uLongf src_len = block->len - block_header_skip;
/* Log blocks specify the *uncompressed* size in their header.
*/
- uint8_t *uncompressed = reftable_malloc(sz);
+ uncompressed = reftable_malloc(sz);
/* Copy over the block header verbatim. It's not compressed. */
memcpy(uncompressed, block->data, block_header_skip);
@@ -212,16 +215,19 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
if (Z_OK !=
uncompress2(uncompressed + block_header_skip, &dst_len,
block->data + block_header_skip, &src_len)) {
- reftable_free(uncompressed);
- return REFTABLE_ZLIB_ERROR;
+ err = REFTABLE_ZLIB_ERROR;
+ goto done;
}
- if (dst_len + block_header_skip != sz)
- return REFTABLE_FORMAT_ERROR;
+ if (dst_len + block_header_skip != sz) {
+ err = REFTABLE_FORMAT_ERROR;
+ goto done;
+ }
/* We're done with the input data. */
reftable_block_done(block);
block->data = uncompressed;
+ uncompressed = NULL;
block->len = sz;
block->source = malloc_block_source();
full_block_size = src_len + block_header_skip;
@@ -251,7 +257,9 @@ int block_reader_init(struct block_reader *br, struct reftable_block *block,
br->restart_count = restart_count;
br->restart_bytes = restart_bytes;
- return 0;
+done:
+ reftable_free(uncompressed);
+ return err;
}
static uint32_t block_reader_restart_offset(struct block_reader *br, int i)
@@ -413,7 +421,7 @@ int block_reader_seek(struct block_reader *br, struct block_iter *it,
done:
strbuf_release(&key);
strbuf_release(&next.last_key);
- reftable_record_destroy(&rec);
+ reftable_record_release(&rec);
return err;
}
diff --git a/reftable/block_test.c b/reftable/block_test.c
index 4b3ea262dc..fa2ee092ec 100644
--- a/reftable/block_test.c
+++ b/reftable/block_test.c
@@ -26,8 +26,9 @@ static void test_block_read_write(void)
struct block_writer bw = {
.last_key = STRBUF_INIT,
};
- struct reftable_ref_record ref = { NULL };
- struct reftable_record rec = { NULL };
+ struct reftable_record rec = {
+ .type = BLOCK_TYPE_REF,
+ };
int i = 0;
int n;
struct block_reader br = { 0 };
@@ -40,7 +41,6 @@ static void test_block_read_write(void)
block.source = malloc_block_source();
block_writer_init(&bw, BLOCK_TYPE_REF, block.data, block_size,
header_off, hash_size(GIT_SHA1_FORMAT_ID));
- reftable_record_from_ref(&rec, &ref);
for (i = 0; i < N; i++) {
char name[100];
@@ -48,14 +48,14 @@ static void test_block_read_write(void)
snprintf(name, sizeof(name), "branch%02d", i);
memset(hash, i, sizeof(hash));
- ref.refname = name;
- ref.value_type = REFTABLE_REF_VAL1;
- ref.value.val1 = hash;
+ rec.u.ref.refname = name;
+ rec.u.ref.value_type = REFTABLE_REF_VAL1;
+ rec.u.ref.value.val1 = hash;
names[i] = xstrdup(name);
n = block_writer_add(&bw, &rec);
- ref.refname = NULL;
- ref.value_type = REFTABLE_REF_DELETION;
+ rec.u.ref.refname = NULL;
+ rec.u.ref.value_type = REFTABLE_REF_DELETION;
EXPECT(n == 0);
}
@@ -74,7 +74,7 @@ static void test_block_read_write(void)
if (r > 0) {
break;
}
- EXPECT_STREQ(names[j], ref.refname);
+ EXPECT_STREQ(names[j], rec.u.ref.refname);
j++;
}
@@ -92,7 +92,7 @@ static void test_block_read_write(void)
n = block_iter_next(&it, &rec);
EXPECT(n == 0);
- EXPECT_STREQ(names[i], ref.refname);
+ EXPECT_STREQ(names[i], rec.u.ref.refname);
want.len--;
n = block_reader_seek(&br, &it, &want);
@@ -100,7 +100,7 @@ static void test_block_read_write(void)
n = block_iter_next(&it, &rec);
EXPECT(n == 0);
- EXPECT_STREQ(names[10 * (i / 10)], ref.refname);
+ EXPECT_STREQ(names[10 * (i / 10)], rec.u.ref.refname);
block_iter_close(&it);
}
diff --git a/reftable/blocksource.c b/reftable/blocksource.c
index 0044eecd9a..2605371c28 100644
--- a/reftable/blocksource.c
+++ b/reftable/blocksource.c
@@ -134,8 +134,10 @@ int reftable_block_source_from_file(struct reftable_block_source *bs,
}
err = fstat(fd, &st);
- if (err < 0)
- return -1;
+ if (err < 0) {
+ close(fd);
+ return REFTABLE_IO_ERROR;
+ }
p = reftable_calloc(sizeof(struct file_block_source));
p->size = st.st_size;
diff --git a/reftable/generic.c b/reftable/generic.c
index 7a8a738d86..b27d152e89 100644
--- a/reftable/generic.c
+++ b/reftable/generic.c
@@ -7,6 +7,7 @@ https://developers.google.com/open-source/licenses/bsd
*/
#include "basics.h"
+#include "constants.h"
#include "record.h"
#include "generic.h"
#include "reftable-iterator.h"
@@ -15,23 +16,21 @@ https://developers.google.com/open-source/licenses/bsd
int reftable_table_seek_ref(struct reftable_table *tab,
struct reftable_iterator *it, const char *name)
{
- struct reftable_ref_record ref = {
- .refname = (char *)name,
- };
- struct reftable_record rec = { NULL };
- reftable_record_from_ref(&rec, &ref);
+ struct reftable_record rec = { .type = BLOCK_TYPE_REF,
+ .u.ref = {
+ .refname = (char *)name,
+ } };
return tab->ops->seek_record(tab->table_arg, it, &rec);
}
int reftable_table_seek_log(struct reftable_table *tab,
struct reftable_iterator *it, const char *name)
{
- struct reftable_log_record log = {
- .refname = (char *)name,
- .update_index = ~((uint64_t)0),
- };
- struct reftable_record rec = { NULL };
- reftable_record_from_log(&rec, &log);
+ struct reftable_record rec = { .type = BLOCK_TYPE_LOG,
+ .u.log = {
+ .refname = (char *)name,
+ .update_index = ~((uint64_t)0),
+ } };
return tab->ops->seek_record(tab->table_arg, it, &rec);
}
@@ -129,17 +128,25 @@ void reftable_iterator_destroy(struct reftable_iterator *it)
int reftable_iterator_next_ref(struct reftable_iterator *it,
struct reftable_ref_record *ref)
{
- struct reftable_record rec = { NULL };
- reftable_record_from_ref(&rec, ref);
- return iterator_next(it, &rec);
+ struct reftable_record rec = {
+ .type = BLOCK_TYPE_REF,
+ .u.ref = *ref,
+ };
+ int err = iterator_next(it, &rec);
+ *ref = rec.u.ref;
+ return err;
}
int reftable_iterator_next_log(struct reftable_iterator *it,
struct reftable_log_record *log)
{
- struct reftable_record rec = { NULL };
- reftable_record_from_log(&rec, log);
- return iterator_next(it, &rec);
+ struct reftable_record rec = {
+ .type = BLOCK_TYPE_LOG,
+ .u.log = *log,
+ };
+ int err = iterator_next(it, &rec);
+ *log = rec.u.log;
+ return err;
}
int iterator_next(struct reftable_iterator *it, struct reftable_record *rec)
diff --git a/reftable/iter.c b/reftable/iter.c
index 93d04f735b..a8d174c040 100644
--- a/reftable/iter.c
+++ b/reftable/iter.c
@@ -32,7 +32,7 @@ static int filtering_ref_iterator_next(void *iter_arg,
struct reftable_record *rec)
{
struct filtering_ref_iterator *fri = iter_arg;
- struct reftable_ref_record *ref = rec->data;
+ struct reftable_ref_record *ref = &rec->u.ref;
int err = 0;
while (1) {
err = reftable_iterator_next_ref(&fri->it, ref);
@@ -127,7 +127,7 @@ static int indexed_table_ref_iter_next_block(struct indexed_table_ref_iter *it)
static int indexed_table_ref_iter_next(void *p, struct reftable_record *rec)
{
struct indexed_table_ref_iter *it = p;
- struct reftable_ref_record *ref = rec->data;
+ struct reftable_ref_record *ref = &rec->u.ref;
while (1) {
int err = block_iter_next(&it->cur, rec);
diff --git a/reftable/merged.c b/reftable/merged.c
index e5b53da6db..2a6efa110d 100644
--- a/reftable/merged.c
+++ b/reftable/merged.c
@@ -30,7 +30,7 @@ static int merged_iter_init(struct merged_iter *mi)
if (err > 0) {
reftable_iterator_destroy(&mi->stack[i]);
- reftable_record_destroy(&rec);
+ reftable_record_release(&rec);
} else {
struct pq_entry e = {
.rec = rec,
@@ -57,18 +57,17 @@ static void merged_iter_close(void *p)
static int merged_iter_advance_nonnull_subiter(struct merged_iter *mi,
size_t idx)
{
- struct reftable_record rec = reftable_new_record(mi->typ);
struct pq_entry e = {
- .rec = rec,
+ .rec = reftable_new_record(mi->typ),
.index = idx,
};
- int err = iterator_next(&mi->stack[idx], &rec);
+ int err = iterator_next(&mi->stack[idx], &e.rec);
if (err < 0)
return err;
if (err > 0) {
reftable_iterator_destroy(&mi->stack[idx]);
- reftable_record_destroy(&rec);
+ reftable_record_release(&e.rec);
return 0;
}
@@ -126,11 +125,11 @@ static int merged_iter_next_entry(struct merged_iter *mi,
if (err < 0) {
return err;
}
- reftable_record_destroy(&top.rec);
+ reftable_record_release(&top.rec);
}
reftable_record_copy_from(rec, &entry.rec, hash_size(mi->hash_id));
- reftable_record_destroy(&entry.rec);
+ reftable_record_release(&entry.rec);
strbuf_release(&entry_key);
return 0;
}
@@ -290,11 +289,12 @@ int reftable_merged_table_seek_ref(struct reftable_merged_table *mt,
struct reftable_iterator *it,
const char *name)
{
- struct reftable_ref_record ref = {
- .refname = (char *)name,
+ struct reftable_record rec = {
+ .type = BLOCK_TYPE_REF,
+ .u.ref = {
+ .refname = (char *)name,
+ },
};
- struct reftable_record rec = { NULL };
- reftable_record_from_ref(&rec, &ref);
return merged_table_seek_record(mt, it, &rec);
}
@@ -302,12 +302,11 @@ int reftable_merged_table_seek_log_at(struct reftable_merged_table *mt,
struct reftable_iterator *it,
const char *name, uint64_t update_index)
{
- struct reftable_log_record log = {
- .refname = (char *)name,
- .update_index = update_index,
- };
- struct reftable_record rec = { NULL };
- reftable_record_from_log(&rec, &log);
+ struct reftable_record rec = { .type = BLOCK_TYPE_LOG,
+ .u.log = {
+ .refname = (char *)name,
+ .update_index = update_index,
+ } };
return merged_table_seek_record(mt, it, &rec);
}
diff --git a/reftable/pq.c b/reftable/pq.c
index efc474017a..96ca6dd37b 100644
--- a/reftable/pq.c
+++ b/reftable/pq.c
@@ -74,6 +74,7 @@ struct pq_entry merged_iter_pqueue_remove(struct merged_iter_pqueue *pq)
void merged_iter_pqueue_add(struct merged_iter_pqueue *pq, struct pq_entry e)
{
int i = 0;
+
if (pq->len == pq->cap) {
pq->cap = 2 * pq->cap + 1;
pq->heap = reftable_realloc(pq->heap,
@@ -98,7 +99,7 @@ void merged_iter_pqueue_release(struct merged_iter_pqueue *pq)
{
int i = 0;
for (i = 0; i < pq->len; i++) {
- reftable_record_destroy(&pq->heap[i].rec);
+ reftable_record_release(&pq->heap[i].rec);
}
FREE_AND_NULL(pq->heap);
pq->len = pq->cap = 0;
diff --git a/reftable/pq_test.c b/reftable/pq_test.c
index c9bb05e37b..7de5e886f3 100644
--- a/reftable/pq_test.c
+++ b/reftable/pq_test.c
@@ -31,7 +31,7 @@ static void test_pq(void)
int N = ARRAY_SIZE(names) - 1;
struct merged_iter_pqueue pq = { NULL };
- const char *last = NULL;
+ char *last = NULL;
int i = 0;
for (i = 0; i < N; i++) {
@@ -42,12 +42,10 @@ static void test_pq(void)
i = 1;
do {
- struct reftable_record rec =
- reftable_new_record(BLOCK_TYPE_REF);
- struct pq_entry e = { 0 };
-
- reftable_record_as_ref(&rec)->refname = names[i];
- e.rec = rec;
+ struct pq_entry e = { .rec = { .type = BLOCK_TYPE_REF,
+ .u.ref = {
+ .refname = names[i],
+ } } };
merged_iter_pqueue_add(&pq, e);
merged_iter_pqueue_check(pq);
i = (i * 7) % N;
@@ -55,19 +53,18 @@ static void test_pq(void)
while (!merged_iter_pqueue_is_empty(pq)) {
struct pq_entry e = merged_iter_pqueue_remove(&pq);
- struct reftable_ref_record *ref =
- reftable_record_as_ref(&e.rec);
-
+ struct reftable_record *rec = &e.rec;
merged_iter_pqueue_check(pq);
+ EXPECT(reftable_record_type(rec) == BLOCK_TYPE_REF);
if (last) {
- EXPECT(strcmp(last, ref->refname) < 0);
+ EXPECT(strcmp(last, rec->u.ref.refname) < 0);
}
- last = ref->refname;
- ref->refname = NULL;
- reftable_free(ref);
+ // this is names[i], so don't dealloc.
+ last = rec->u.ref.refname;
+ rec->u.ref.refname = NULL;
+ reftable_record_release(rec);
}
-
for (i = 0; i < N; i++) {
reftable_free(names[i]);
}
diff --git a/reftable/reader.c b/reftable/reader.c
index 006709a645..00906e7a2d 100644
--- a/reftable/reader.c
+++ b/reftable/reader.c
@@ -239,8 +239,7 @@ static int table_iter_next_in_block(struct table_iter *ti,
{
int res = block_iter_next(&ti->bi, rec);
if (res == 0 && reftable_record_type(rec) == BLOCK_TYPE_REF) {
- ((struct reftable_ref_record *)rec->data)->update_index +=
- ti->r->min_update_index;
+ rec->u.ref.update_index += ti->r->min_update_index;
}
return res;
@@ -290,28 +289,33 @@ int reader_init_block_reader(struct reftable_reader *r, struct block_reader *br,
err = reader_get_block(r, &block, next_off, guess_block_size);
if (err < 0)
- return err;
+ goto done;
block_size = extract_block_size(block.data, &block_typ, next_off,
r->version);
- if (block_size < 0)
- return block_size;
-
+ if (block_size < 0) {
+ err = block_size;
+ goto done;
+ }
if (want_typ != BLOCK_TYPE_ANY && block_typ != want_typ) {
- reftable_block_done(&block);
- return 1;
+ err = 1;
+ goto done;
}
if (block_size > guess_block_size) {
reftable_block_done(&block);
err = reader_get_block(r, &block, next_off, block_size);
if (err < 0) {
- return err;
+ goto done;
}
}
- return block_reader_init(br, &block, header_off, r->block_size,
- hash_size(r->hash_id));
+ err = block_reader_init(br, &block, header_off, r->block_size,
+ hash_size(r->hash_id));
+done:
+ reftable_block_done(&block);
+
+ return err;
}
static int table_iter_next_block(struct table_iter *dest,
@@ -475,7 +479,7 @@ static int reader_seek_linear(struct reftable_reader *r, struct table_iter *ti,
done:
block_iter_close(&next.bi);
- reftable_record_destroy(&rec);
+ reftable_record_release(&rec);
strbuf_release(&want_key);
strbuf_release(&got_key);
return err;
@@ -485,34 +489,35 @@ static int reader_seek_indexed(struct reftable_reader *r,
struct reftable_iterator *it,
struct reftable_record *rec)
{
- struct reftable_index_record want_index = { .last_key = STRBUF_INIT };
- struct reftable_record want_index_rec = { NULL };
- struct reftable_index_record index_result = { .last_key = STRBUF_INIT };
- struct reftable_record index_result_rec = { NULL };
+ struct reftable_record want_index = {
+ .type = BLOCK_TYPE_INDEX, .u.idx = { .last_key = STRBUF_INIT }
+ };
+ struct reftable_record index_result = {
+ .type = BLOCK_TYPE_INDEX,
+ .u.idx = { .last_key = STRBUF_INIT },
+ };
struct table_iter index_iter = TABLE_ITER_INIT;
struct table_iter next = TABLE_ITER_INIT;
int err = 0;
- reftable_record_key(rec, &want_index.last_key);
- reftable_record_from_index(&want_index_rec, &want_index);
- reftable_record_from_index(&index_result_rec, &index_result);
-
+ reftable_record_key(rec, &want_index.u.idx.last_key);
err = reader_start(r, &index_iter, reftable_record_type(rec), 1);
if (err < 0)
goto done;
- err = reader_seek_linear(r, &index_iter, &want_index_rec);
+ err = reader_seek_linear(r, &index_iter, &want_index);
while (1) {
- err = table_iter_next(&index_iter, &index_result_rec);
+ err = table_iter_next(&index_iter, &index_result);
table_iter_block_done(&index_iter);
if (err != 0)
goto done;
- err = reader_table_iter_at(r, &next, index_result.offset, 0);
+ err = reader_table_iter_at(r, &next, index_result.u.idx.offset,
+ 0);
if (err != 0)
goto done;
- err = block_iter_seek(&next.bi, &want_index.last_key);
+ err = block_iter_seek(&next.bi, &want_index.u.idx.last_key);
if (err < 0)
goto done;
@@ -540,8 +545,8 @@ static int reader_seek_indexed(struct reftable_reader *r,
done:
block_iter_close(&next.bi);
table_iter_close(&index_iter);
- reftable_record_release(&want_index_rec);
- reftable_record_release(&index_result_rec);
+ reftable_record_release(&want_index);
+ reftable_record_release(&index_result);
return err;
}
@@ -590,11 +595,12 @@ static int reader_seek(struct reftable_reader *r, struct reftable_iterator *it,
int reftable_reader_seek_ref(struct reftable_reader *r,
struct reftable_iterator *it, const char *name)
{
- struct reftable_ref_record ref = {
- .refname = (char *)name,
+ struct reftable_record rec = {
+ .type = BLOCK_TYPE_REF,
+ .u.ref = {
+ .refname = (char *)name,
+ },
};
- struct reftable_record rec = { NULL };
- reftable_record_from_ref(&rec, &ref);
return reader_seek(r, it, &rec);
}
@@ -602,12 +608,11 @@ int reftable_reader_seek_log_at(struct reftable_reader *r,
struct reftable_iterator *it, const char *name,
uint64_t update_index)
{
- struct reftable_log_record log = {
- .refname = (char *)name,
- .update_index = update_index,
- };
- struct reftable_record rec = { NULL };
- reftable_record_from_log(&rec, &log);
+ struct reftable_record rec = { .type = BLOCK_TYPE_LOG,
+ .u.log = {
+ .refname = (char *)name,
+ .update_index = update_index,
+ } };
return reader_seek(r, it, &rec);
}
@@ -641,6 +646,8 @@ int reftable_new_reader(struct reftable_reader **p,
void reftable_reader_free(struct reftable_reader *r)
{
+ if (!r)
+ return;
reader_close(r);
reftable_free(r);
}
@@ -649,31 +656,33 @@ static int reftable_reader_refs_for_indexed(struct reftable_reader *r,
struct reftable_iterator *it,
uint8_t *oid)
{
- struct reftable_obj_record want = {
- .hash_prefix = oid,
- .hash_prefix_len = r->object_id_len,
+ struct reftable_record want = {
+ .type = BLOCK_TYPE_OBJ,
+ .u.obj = {
+ .hash_prefix = oid,
+ .hash_prefix_len = r->object_id_len,
+ },
};
- struct reftable_record want_rec = { NULL };
struct reftable_iterator oit = { NULL };
- struct reftable_obj_record got = { NULL };
- struct reftable_record got_rec = { NULL };
+ struct reftable_record got = {
+ .type = BLOCK_TYPE_OBJ,
+ .u.obj = { 0 },
+ };
int err = 0;
struct indexed_table_ref_iter *itr = NULL;
/* Look through the reverse index. */
- reftable_record_from_obj(&want_rec, &want);
- err = reader_seek(r, &oit, &want_rec);
+ err = reader_seek(r, &oit, &want);
if (err != 0)
goto done;
/* read out the reftable_obj_record */
- reftable_record_from_obj(&got_rec, &got);
- err = iterator_next(&oit, &got_rec);
+ err = iterator_next(&oit, &got);
if (err < 0)
goto done;
- if (err > 0 ||
- memcmp(want.hash_prefix, got.hash_prefix, r->object_id_len)) {
+ if (err > 0 || memcmp(want.u.obj.hash_prefix, got.u.obj.hash_prefix,
+ r->object_id_len)) {
/* didn't find it; return empty iterator */
iterator_set_empty(it);
err = 0;
@@ -681,15 +690,16 @@ static int reftable_reader_refs_for_indexed(struct reftable_reader *r,
}
err = new_indexed_table_ref_iter(&itr, r, oid, hash_size(r->hash_id),
- got.offsets, got.offset_len);
+ got.u.obj.offsets,
+ got.u.obj.offset_len);
if (err < 0)
goto done;
- got.offsets = NULL;
+ got.u.obj.offsets = NULL;
iterator_from_indexed_table_ref_iter(it, itr);
done:
reftable_iterator_destroy(&oit);
- reftable_record_release(&got_rec);
+ reftable_record_release(&got);
return err;
}
diff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c
index 70c7aedba2..605ba0f9fd 100644
--- a/reftable/readwrite_test.c
+++ b/reftable/readwrite_test.c
@@ -288,6 +288,71 @@ static void test_log_write_read(void)
reader_close(&rd);
}
+static void test_log_zlib_corruption(void)
+{
+ struct reftable_write_options opts = {
+ .block_size = 256,
+ };
+ struct reftable_iterator it = { 0 };
+ struct reftable_reader rd = { 0 };
+ struct reftable_block_source source = { 0 };
+ struct strbuf buf = STRBUF_INIT;
+ struct reftable_writer *w =
+ reftable_new_writer(&strbuf_add_void, &buf, &opts);
+ const struct reftable_stats *stats = NULL;
+ uint8_t hash1[GIT_SHA1_RAWSZ] = { 1 };
+ uint8_t hash2[GIT_SHA1_RAWSZ] = { 2 };
+ char message[100] = { 0 };
+ int err, i, n;
+
+ struct reftable_log_record log = {
+ .refname = "refname",
+ .value_type = REFTABLE_LOG_UPDATE,
+ .value = {
+ .update = {
+ .new_hash = hash1,
+ .old_hash = hash2,
+ .name = "My Name",
+ .email = "myname@invalid",
+ .message = message,
+ },
+ },
+ };
+
+ for (i = 0; i < sizeof(message) - 1; i++)
+ message[i] = (uint8_t)(rand() % 64 + ' ');
+
+ reftable_writer_set_limits(w, 1, 1);
+
+ err = reftable_writer_add_log(w, &log);
+ EXPECT_ERR(err);
+
+ n = reftable_writer_close(w);
+ EXPECT(n == 0);
+
+ stats = writer_stats(w);
+ EXPECT(stats->log_stats.blocks > 0);
+ reftable_writer_free(w);
+ w = NULL;
+
+ /* corrupt the data. */
+ buf.buf[50] ^= 0x99;
+
+ block_source_from_strbuf(&source, &buf);
+
+ err = init_reader(&rd, &source, "file.log");
+ EXPECT_ERR(err);
+
+ err = reftable_reader_seek_log(&rd, &it, "refname");
+ EXPECT(err == REFTABLE_ZLIB_ERROR);
+
+ reftable_iterator_destroy(&it);
+
+ /* cleanup. */
+ strbuf_release(&buf);
+ reader_close(&rd);
+}
+
static void test_table_read_write_sequential(void)
{
char **names;
@@ -631,7 +696,6 @@ static void test_write_key_order(void)
err = reftable_writer_add_ref(w, &refs[0]);
EXPECT_ERR(err);
err = reftable_writer_add_ref(w, &refs[1]);
- printf("%d\n", err);
EXPECT(err == REFTABLE_API_ERROR);
reftable_writer_close(w);
reftable_writer_free(w);
@@ -667,6 +731,7 @@ static void test_corrupt_table(void)
int readwrite_test_main(int argc, const char *argv[])
{
+ RUN_TEST(test_log_zlib_corruption);
RUN_TEST(test_corrupt_table);
RUN_TEST(test_corrupt_table_empty);
RUN_TEST(test_log_write_read);
diff --git a/reftable/record.c b/reftable/record.c
index 6a5dac32dc..fbaa1fbef5 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -15,6 +15,10 @@ https://developers.google.com/open-source/licenses/bsd
#include "reftable-error.h"
#include "basics.h"
+static struct reftable_record_vtable *
+reftable_record_vtable(struct reftable_record *rec);
+static void *reftable_record_data(struct reftable_record *rec);
+
int get_var_int(uint64_t *dest, struct string_view *in)
{
int ptr = 0;
@@ -72,7 +76,7 @@ int reftable_is_block_type(uint8_t typ)
return 0;
}
-uint8_t *reftable_ref_record_val1(struct reftable_ref_record *rec)
+uint8_t *reftable_ref_record_val1(const struct reftable_ref_record *rec)
{
switch (rec->value_type) {
case REFTABLE_REF_VAL1:
@@ -84,7 +88,7 @@ uint8_t *reftable_ref_record_val1(struct reftable_ref_record *rec)
}
}
-uint8_t *reftable_ref_record_val2(struct reftable_ref_record *rec)
+uint8_t *reftable_ref_record_val2(const struct reftable_ref_record *rec)
{
switch (rec->value_type) {
case REFTABLE_REF_VAL2:
@@ -251,24 +255,24 @@ static void hex_format(char *dest, uint8_t *src, int hash_size)
}
}
-void reftable_ref_record_print(struct reftable_ref_record *ref,
- uint32_t hash_id)
+static void reftable_ref_record_print_sz(const struct reftable_ref_record *ref,
+ int hash_size)
{
- char hex[2 * GIT_SHA256_RAWSZ + 1] = { 0 }; /* BUG */
+ char hex[GIT_MAX_HEXSZ + 1] = { 0 }; /* BUG */
printf("ref{%s(%" PRIu64 ") ", ref->refname, ref->update_index);
switch (ref->value_type) {
case REFTABLE_REF_SYMREF:
printf("=> %s", ref->value.symref);
break;
case REFTABLE_REF_VAL2:
- hex_format(hex, ref->value.val2.value, hash_size(hash_id));
+ hex_format(hex, ref->value.val2.value, hash_size);
printf("val 2 %s", hex);
hex_format(hex, ref->value.val2.target_value,
- hash_size(hash_id));
+ hash_size);
printf("(T %s)", hex);
break;
case REFTABLE_REF_VAL1:
- hex_format(hex, ref->value.val1, hash_size(hash_id));
+ hex_format(hex, ref->value.val1, hash_size);
printf("val 1 %s", hex);
break;
case REFTABLE_REF_DELETION:
@@ -278,6 +282,11 @@ void reftable_ref_record_print(struct reftable_ref_record *ref,
printf("}\n");
}
+void reftable_ref_record_print(const struct reftable_ref_record *ref,
+ uint32_t hash_id) {
+ reftable_ref_record_print_sz(ref, hash_size(hash_id));
+}
+
static void reftable_ref_record_release_void(void *rec)
{
reftable_ref_record_release(rec);
@@ -430,6 +439,21 @@ static int reftable_ref_record_is_deletion_void(const void *p)
(const struct reftable_ref_record *)p);
}
+
+static int reftable_ref_record_equal_void(const void *a,
+ const void *b, int hash_size)
+{
+ struct reftable_ref_record *ra = (struct reftable_ref_record *) a;
+ struct reftable_ref_record *rb = (struct reftable_ref_record *) b;
+ return reftable_ref_record_equal(ra, rb, hash_size);
+}
+
+static void reftable_ref_record_print_void(const void *rec,
+ int hash_size)
+{
+ reftable_ref_record_print_sz((struct reftable_ref_record *) rec, hash_size);
+}
+
static struct reftable_record_vtable reftable_ref_record_vtable = {
.key = &reftable_ref_record_key,
.type = BLOCK_TYPE_REF,
@@ -439,6 +463,8 @@ static struct reftable_record_vtable reftable_ref_record_vtable = {
.decode = &reftable_ref_record_decode,
.release = &reftable_ref_record_release_void,
.is_deletion = &reftable_ref_record_is_deletion_void,
+ .equal = &reftable_ref_record_equal_void,
+ .print = &reftable_ref_record_print_void,
};
static void reftable_obj_record_key(const void *r, struct strbuf *dest)
@@ -457,6 +483,21 @@ static void reftable_obj_record_release(void *rec)
memset(obj, 0, sizeof(struct reftable_obj_record));
}
+static void reftable_obj_record_print(const void *rec, int hash_size)
+{
+ const struct reftable_obj_record *obj = rec;
+ char hex[GIT_MAX_HEXSZ + 1] = { 0 };
+ struct strbuf offset_str = STRBUF_INIT;
+ int i;
+
+ for (i = 0; i < obj->offset_len; i++)
+ strbuf_addf(&offset_str, "%" PRIu64 " ", obj->offsets[i]);
+ hex_format(hex, obj->hash_prefix, obj->hash_prefix_len);
+ printf("prefix %s (len %d), offsets [%s]\n",
+ hex, obj->hash_prefix_len, offset_str.buf);
+ strbuf_release(&offset_str);
+}
+
static void reftable_obj_record_copy_from(void *rec, const void *src_rec,
int hash_size)
{
@@ -465,12 +506,14 @@ static void reftable_obj_record_copy_from(void *rec, const void *src_rec,
(const struct reftable_obj_record *)src_rec;
reftable_obj_record_release(obj);
- *obj = *src;
- obj->hash_prefix = reftable_malloc(obj->hash_prefix_len);
- memcpy(obj->hash_prefix, src->hash_prefix, obj->hash_prefix_len);
+ obj->hash_prefix = reftable_malloc(src->hash_prefix_len);
+ obj->hash_prefix_len = src->hash_prefix_len;
+ if (src->hash_prefix_len)
+ memcpy(obj->hash_prefix, src->hash_prefix, obj->hash_prefix_len);
- obj->offsets = reftable_malloc(obj->offset_len * sizeof(uint64_t));
- COPY_ARRAY(obj->offsets, src->offsets, obj->offset_len);
+ obj->offsets = reftable_malloc(src->offset_len * sizeof(uint64_t));
+ obj->offset_len = src->offset_len;
+ COPY_ARRAY(obj->offsets, src->offsets, src->offset_len);
}
static uint8_t reftable_obj_record_val_type(const void *rec)
@@ -572,6 +615,25 @@ static int not_a_deletion(const void *p)
return 0;
}
+static int reftable_obj_record_equal_void(const void *a, const void *b, int hash_size)
+{
+ struct reftable_obj_record *ra = (struct reftable_obj_record *) a;
+ struct reftable_obj_record *rb = (struct reftable_obj_record *) b;
+
+ if (ra->hash_prefix_len != rb->hash_prefix_len
+ || ra->offset_len != rb->offset_len)
+ return 0;
+
+ if (ra->hash_prefix_len &&
+ memcmp(ra->hash_prefix, rb->hash_prefix, ra->hash_prefix_len))
+ return 0;
+ if (ra->offset_len &&
+ memcmp(ra->offsets, rb->offsets, ra->offset_len * sizeof(uint64_t)))
+ return 0;
+
+ return 1;
+}
+
static struct reftable_record_vtable reftable_obj_record_vtable = {
.key = &reftable_obj_record_key,
.type = BLOCK_TYPE_OBJ,
@@ -580,32 +642,43 @@ static struct reftable_record_vtable reftable_obj_record_vtable = {
.encode = &reftable_obj_record_encode,
.decode = &reftable_obj_record_decode,
.release = &reftable_obj_record_release,
- .is_deletion = not_a_deletion,
+ .is_deletion = &not_a_deletion,
+ .equal = &reftable_obj_record_equal_void,
+ .print = &reftable_obj_record_print,
};
-void reftable_log_record_print(struct reftable_log_record *log,
- uint32_t hash_id)
+static void reftable_log_record_print_sz(struct reftable_log_record *log,
+ int hash_size)
{
- char hex[GIT_SHA256_RAWSZ + 1] = { 0 };
+ char hex[GIT_MAX_HEXSZ + 1] = { 0 };
switch (log->value_type) {
case REFTABLE_LOG_DELETION:
- printf("log{%s(%" PRIu64 ") delete", log->refname,
+ printf("log{%s(%" PRIu64 ") delete\n", log->refname,
log->update_index);
break;
case REFTABLE_LOG_UPDATE:
printf("log{%s(%" PRIu64 ") %s <%s> %" PRIu64 " %04d\n",
- log->refname, log->update_index, log->value.update.name,
- log->value.update.email, log->value.update.time,
+ log->refname, log->update_index,
+ log->value.update.name ? log->value.update.name : "",
+ log->value.update.email ? log->value.update.email : "",
+ log->value.update.time,
log->value.update.tz_offset);
- hex_format(hex, log->value.update.old_hash, hash_size(hash_id));
+ hex_format(hex, log->value.update.old_hash, hash_size);
printf("%s => ", hex);
- hex_format(hex, log->value.update.new_hash, hash_size(hash_id));
- printf("%s\n\n%s\n}\n", hex, log->value.update.message);
+ hex_format(hex, log->value.update.new_hash, hash_size);
+ printf("%s\n\n%s\n}\n", hex,
+ log->value.update.message ? log->value.update.message : "");
break;
}
}
+void reftable_log_record_print(struct reftable_log_record *log,
+ uint32_t hash_id)
+{
+ reftable_log_record_print_sz(log, hash_size(hash_id));
+}
+
static void reftable_log_record_key(const void *r, struct strbuf *dest)
{
const struct reftable_log_record *rec =
@@ -881,8 +954,16 @@ static int zero_hash_eq(uint8_t *a, uint8_t *b, int sz)
return !memcmp(a, b, sz);
}
-int reftable_log_record_equal(struct reftable_log_record *a,
- struct reftable_log_record *b, int hash_size)
+static int reftable_log_record_equal_void(const void *a,
+ const void *b, int hash_size)
+{
+ return reftable_log_record_equal((struct reftable_log_record *) a,
+ (struct reftable_log_record *) b,
+ hash_size);
+}
+
+int reftable_log_record_equal(const struct reftable_log_record *a,
+ const struct reftable_log_record *b, int hash_size)
{
if (!(null_streq(a->refname, b->refname) &&
a->update_index == b->update_index &&
@@ -915,6 +996,11 @@ static int reftable_log_record_is_deletion_void(const void *p)
(const struct reftable_log_record *)p);
}
+static void reftable_log_record_print_void(const void *rec, int hash_size)
+{
+ reftable_log_record_print_sz((struct reftable_log_record*)rec, hash_size);
+}
+
static struct reftable_record_vtable reftable_log_record_vtable = {
.key = &reftable_log_record_key,
.type = BLOCK_TYPE_LOG,
@@ -924,60 +1010,10 @@ static struct reftable_record_vtable reftable_log_record_vtable = {
.decode = &reftable_log_record_decode,
.release = &reftable_log_record_release_void,
.is_deletion = &reftable_log_record_is_deletion_void,
+ .equal = &reftable_log_record_equal_void,
+ .print = &reftable_log_record_print_void,
};
-struct reftable_record reftable_new_record(uint8_t typ)
-{
- struct reftable_record rec = { NULL };
- switch (typ) {
- case BLOCK_TYPE_REF: {
- struct reftable_ref_record *r =
- reftable_calloc(sizeof(struct reftable_ref_record));
- reftable_record_from_ref(&rec, r);
- return rec;
- }
-
- case BLOCK_TYPE_OBJ: {
- struct reftable_obj_record *r =
- reftable_calloc(sizeof(struct reftable_obj_record));
- reftable_record_from_obj(&rec, r);
- return rec;
- }
- case BLOCK_TYPE_LOG: {
- struct reftable_log_record *r =
- reftable_calloc(sizeof(struct reftable_log_record));
- reftable_record_from_log(&rec, r);
- return rec;
- }
- case BLOCK_TYPE_INDEX: {
- struct reftable_index_record empty = { .last_key =
- STRBUF_INIT };
- struct reftable_index_record *r =
- reftable_calloc(sizeof(struct reftable_index_record));
- *r = empty;
- reftable_record_from_index(&rec, r);
- return rec;
- }
- }
- abort();
- return rec;
-}
-
-/* clear out the record, yielding the reftable_record data that was
- * encapsulated. */
-static void *reftable_record_yield(struct reftable_record *rec)
-{
- void *p = rec->data;
- rec->data = NULL;
- return p;
-}
-
-void reftable_record_destroy(struct reftable_record *rec)
-{
- reftable_record_release(rec);
- reftable_free(reftable_record_yield(rec));
-}
-
static void reftable_index_record_key(const void *r, struct strbuf *dest)
{
const struct reftable_index_record *rec = r;
@@ -1042,6 +1078,21 @@ static int reftable_index_record_decode(void *rec, struct strbuf key,
return start.len - in.len;
}
+static int reftable_index_record_equal(const void *a, const void *b, int hash_size)
+{
+ struct reftable_index_record *ia = (struct reftable_index_record *) a;
+ struct reftable_index_record *ib = (struct reftable_index_record *) b;
+
+ return ia->offset == ib->offset && !strbuf_cmp(&ia->last_key, &ib->last_key);
+}
+
+static void reftable_index_record_print(const void *rec, int hash_size)
+{
+ const struct reftable_index_record *idx = rec;
+ /* TODO: escape null chars? */
+ printf("\"%s\" %" PRIu64 "\n", idx->last_key.buf, idx->offset);
+}
+
static struct reftable_record_vtable reftable_index_record_vtable = {
.key = &reftable_index_record_key,
.type = BLOCK_TYPE_INDEX,
@@ -1051,95 +1102,66 @@ static struct reftable_record_vtable reftable_index_record_vtable = {
.decode = &reftable_index_record_decode,
.release = &reftable_index_record_release,
.is_deletion = &not_a_deletion,
+ .equal = &reftable_index_record_equal,
+ .print = &reftable_index_record_print,
};
void reftable_record_key(struct reftable_record *rec, struct strbuf *dest)
{
- rec->ops->key(rec->data, dest);
+ reftable_record_vtable(rec)->key(reftable_record_data(rec), dest);
}
uint8_t reftable_record_type(struct reftable_record *rec)
{
- return rec->ops->type;
+ return rec->type;
}
int reftable_record_encode(struct reftable_record *rec, struct string_view dest,
int hash_size)
{
- return rec->ops->encode(rec->data, dest, hash_size);
+ return reftable_record_vtable(rec)->encode(reftable_record_data(rec),
+ dest, hash_size);
}
void reftable_record_copy_from(struct reftable_record *rec,
struct reftable_record *src, int hash_size)
{
- assert(src->ops->type == rec->ops->type);
+ assert(src->type == rec->type);
- rec->ops->copy_from(rec->data, src->data, hash_size);
+ reftable_record_vtable(rec)->copy_from(reftable_record_data(rec),
+ reftable_record_data(src),
+ hash_size);
}
uint8_t reftable_record_val_type(struct reftable_record *rec)
{
- return rec->ops->val_type(rec->data);
+ return reftable_record_vtable(rec)->val_type(reftable_record_data(rec));
}
int reftable_record_decode(struct reftable_record *rec, struct strbuf key,
uint8_t extra, struct string_view src, int hash_size)
{
- return rec->ops->decode(rec->data, key, extra, src, hash_size);
+ return reftable_record_vtable(rec)->decode(reftable_record_data(rec),
+ key, extra, src, hash_size);
}
void reftable_record_release(struct reftable_record *rec)
{
- rec->ops->release(rec->data);
+ reftable_record_vtable(rec)->release(reftable_record_data(rec));
}
int reftable_record_is_deletion(struct reftable_record *rec)
{
- return rec->ops->is_deletion(rec->data);
+ return reftable_record_vtable(rec)->is_deletion(
+ reftable_record_data(rec));
}
-void reftable_record_from_ref(struct reftable_record *rec,
- struct reftable_ref_record *ref_rec)
+int reftable_record_equal(struct reftable_record *a, struct reftable_record *b, int hash_size)
{
- assert(!rec->ops);
- rec->data = ref_rec;
- rec->ops = &reftable_ref_record_vtable;
-}
-
-void reftable_record_from_obj(struct reftable_record *rec,
- struct reftable_obj_record *obj_rec)
-{
- assert(!rec->ops);
- rec->data = obj_rec;
- rec->ops = &reftable_obj_record_vtable;
-}
-
-void reftable_record_from_index(struct reftable_record *rec,
- struct reftable_index_record *index_rec)
-{
- assert(!rec->ops);
- rec->data = index_rec;
- rec->ops = &reftable_index_record_vtable;
-}
-
-void reftable_record_from_log(struct reftable_record *rec,
- struct reftable_log_record *log_rec)
-{
- assert(!rec->ops);
- rec->data = log_rec;
- rec->ops = &reftable_log_record_vtable;
-}
-
-struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *rec)
-{
- assert(reftable_record_type(rec) == BLOCK_TYPE_REF);
- return rec->data;
-}
-
-struct reftable_log_record *reftable_record_as_log(struct reftable_record *rec)
-{
- assert(reftable_record_type(rec) == BLOCK_TYPE_LOG);
- return rec->data;
+ if (a->type != b->type)
+ return 0;
+ return reftable_record_vtable(a)->equal(
+ reftable_record_data(a), reftable_record_data(b), hash_size);
}
static int hash_equal(uint8_t *a, uint8_t *b, int hash_size)
@@ -1150,13 +1172,15 @@ static int hash_equal(uint8_t *a, uint8_t *b, int hash_size)
return a == b;
}
-int reftable_ref_record_equal(struct reftable_ref_record *a,
- struct reftable_ref_record *b, int hash_size)
+int reftable_ref_record_equal(const struct reftable_ref_record *a,
+ const struct reftable_ref_record *b, int hash_size)
{
assert(hash_size > 0);
- if (!(0 == strcmp(a->refname, b->refname) &&
- a->update_index == b->update_index &&
- a->value_type == b->value_type))
+ if (!null_streq(a->refname, b->refname))
+ return 0;
+
+ if (a->update_index != b->update_index ||
+ a->value_type != b->value_type)
return 0;
switch (a->value_type) {
@@ -1210,3 +1234,81 @@ void string_view_consume(struct string_view *s, int n)
s->buf += n;
s->len -= n;
}
+
+static void *reftable_record_data(struct reftable_record *rec)
+{
+ switch (rec->type) {
+ case BLOCK_TYPE_REF:
+ return &rec->u.ref;
+ case BLOCK_TYPE_LOG:
+ return &rec->u.log;
+ case BLOCK_TYPE_INDEX:
+ return &rec->u.idx;
+ case BLOCK_TYPE_OBJ:
+ return &rec->u.obj;
+ }
+ abort();
+}
+
+static struct reftable_record_vtable *
+reftable_record_vtable(struct reftable_record *rec)
+{
+ switch (rec->type) {
+ case BLOCK_TYPE_REF:
+ return &reftable_ref_record_vtable;
+ case BLOCK_TYPE_LOG:
+ return &reftable_log_record_vtable;
+ case BLOCK_TYPE_INDEX:
+ return &reftable_index_record_vtable;
+ case BLOCK_TYPE_OBJ:
+ return &reftable_obj_record_vtable;
+ }
+ abort();
+}
+
+struct reftable_record reftable_new_record(uint8_t typ)
+{
+ struct reftable_record clean = {
+ .type = typ,
+ };
+
+ /* the following is involved, but the naive solution (just return
+ * `clean` as is, except for BLOCK_TYPE_INDEX), returns a garbage
+ * clean.u.obj.offsets pointer on Windows VS CI. Go figure.
+ */
+ switch (typ) {
+ case BLOCK_TYPE_OBJ:
+ {
+ struct reftable_obj_record obj = { 0 };
+ clean.u.obj = obj;
+ break;
+ }
+ case BLOCK_TYPE_INDEX:
+ {
+ struct reftable_index_record idx = {
+ .last_key = STRBUF_INIT,
+ };
+ clean.u.idx = idx;
+ break;
+ }
+ case BLOCK_TYPE_REF:
+ {
+ struct reftable_ref_record ref = { 0 };
+ clean.u.ref = ref;
+ break;
+ }
+ case BLOCK_TYPE_LOG:
+ {
+ struct reftable_log_record log = { 0 };
+ clean.u.log = log;
+ break;
+ }
+ }
+ return clean;
+}
+
+void reftable_record_print(struct reftable_record *rec, int hash_size)
+{
+ printf("'%c': ", rec->type);
+ reftable_record_vtable(rec)->print(reftable_record_data(rec), hash_size);
+}
diff --git a/reftable/record.h b/reftable/record.h
index 498e8c50bf..fd80cd451d 100644
--- a/reftable/record.h
+++ b/reftable/record.h
@@ -58,18 +58,18 @@ struct reftable_record_vtable {
/* is this a tombstone? */
int (*is_deletion)(const void *rec);
-};
-/* record is a generic wrapper for different types of records. */
-struct reftable_record {
- void *data;
- struct reftable_record_vtable *ops;
+ /* Are two records equal? This assumes they have the same type. Returns 0 for non-equal. */
+ int (*equal)(const void *a, const void *b, int hash_size);
+
+ /* Print on stdout, for debugging. */
+ void (*print)(const void *rec, int hash_size);
};
/* returns true for recognized block types. Block start with the block type. */
int reftable_is_block_type(uint8_t typ);
-/* creates a malloced record of the given type. Dispose with record_destroy */
+/* return an initialized record for the given type */
struct reftable_record reftable_new_record(uint8_t typ);
/* Encode `key` into `dest`. Sets `is_restart` to indicate a restart. Returns
@@ -97,8 +97,25 @@ struct reftable_obj_record {
int offset_len;
};
-/* see struct record_vtable */
+/* record is a generic wrapper for different types of records. It is normally
+ * created on the stack, or embedded within another struct. If the type is
+ * known, a fresh instance can be initialized explicitly. Otherwise, use
+ * reftable_new_record() to initialize generically (as the index_record is not
+ * valid as 0-initialized structure)
+ */
+struct reftable_record {
+ uint8_t type;
+ union {
+ struct reftable_ref_record ref;
+ struct reftable_log_record log;
+ struct reftable_obj_record obj;
+ struct reftable_index_record idx;
+ } u;
+};
+/* see struct record_vtable */
+int reftable_record_equal(struct reftable_record *a, struct reftable_record *b, int hash_size);
+void reftable_record_print(struct reftable_record *rec, int hash_size);
void reftable_record_key(struct reftable_record *rec, struct strbuf *dest);
uint8_t reftable_record_type(struct reftable_record *rec);
void reftable_record_copy_from(struct reftable_record *rec,
@@ -111,25 +128,9 @@ int reftable_record_decode(struct reftable_record *rec, struct strbuf key,
int hash_size);
int reftable_record_is_deletion(struct reftable_record *rec);
-/* zeroes out the embedded record */
+/* frees and zeroes out the embedded record */
void reftable_record_release(struct reftable_record *rec);
-/* clear and deallocate embedded record, and zero `rec`. */
-void reftable_record_destroy(struct reftable_record *rec);
-
-/* initialize generic records from concrete records. The generic record should
- * be zeroed out. */
-void reftable_record_from_obj(struct reftable_record *rec,
- struct reftable_obj_record *objrec);
-void reftable_record_from_index(struct reftable_record *rec,
- struct reftable_index_record *idxrec);
-void reftable_record_from_ref(struct reftable_record *rec,
- struct reftable_ref_record *refrec);
-void reftable_record_from_log(struct reftable_record *rec,
- struct reftable_log_record *logrec);
-struct reftable_ref_record *reftable_record_as_ref(struct reftable_record *ref);
-struct reftable_log_record *reftable_record_as_log(struct reftable_record *ref);
-
/* for qsort. */
int reftable_ref_record_compare_name(const void *a, const void *b);
diff --git a/reftable/record_test.c b/reftable/record_test.c
index f4ad7cace4..f91ea5e883 100644
--- a/reftable/record_test.c
+++ b/reftable/record_test.c
@@ -16,24 +16,20 @@
static void test_copy(struct reftable_record *rec)
{
- struct reftable_record copy =
- reftable_new_record(reftable_record_type(rec));
+ struct reftable_record copy = { 0 };
+ uint8_t typ;
+
+ typ = reftable_record_type(rec);
+ copy = reftable_new_record(typ);
reftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);
/* do it twice to catch memory leaks */
reftable_record_copy_from(&copy, rec, GIT_SHA1_RAWSZ);
- switch (reftable_record_type(&copy)) {
- case BLOCK_TYPE_REF:
- EXPECT(reftable_ref_record_equal(reftable_record_as_ref(&copy),
- reftable_record_as_ref(rec),
- GIT_SHA1_RAWSZ));
- break;
- case BLOCK_TYPE_LOG:
- EXPECT(reftable_log_record_equal(reftable_record_as_log(&copy),
- reftable_record_as_log(rec),
- GIT_SHA1_RAWSZ));
- break;
- }
- reftable_record_destroy(&copy);
+ EXPECT(reftable_record_equal(rec, &copy, GIT_SHA1_RAWSZ));
+
+ puts("testing print coverage:\n");
+ reftable_record_print(&copy, GIT_SHA1_RAWSZ);
+
+ reftable_record_release(&copy);
}
static void test_varint_roundtrip(void)
@@ -106,61 +102,58 @@ static void test_reftable_ref_record_roundtrip(void)
int i = 0;
for (i = REFTABLE_REF_DELETION; i < REFTABLE_NR_REF_VALUETYPES; i++) {
- struct reftable_ref_record in = { NULL };
- struct reftable_ref_record out = { NULL };
- struct reftable_record rec_out = { NULL };
+ struct reftable_record in = {
+ .type = BLOCK_TYPE_REF,
+ };
+ struct reftable_record out = { .type = BLOCK_TYPE_REF };
struct strbuf key = STRBUF_INIT;
- struct reftable_record rec = { NULL };
uint8_t buffer[1024] = { 0 };
struct string_view dest = {
.buf = buffer,
.len = sizeof(buffer),
};
-
int n, m;
- in.value_type = i;
+ in.u.ref.value_type = i;
switch (i) {
case REFTABLE_REF_DELETION:
break;
case REFTABLE_REF_VAL1:
- in.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
- set_hash(in.value.val1, 1);
+ in.u.ref.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
+ set_hash(in.u.ref.value.val1, 1);
break;
case REFTABLE_REF_VAL2:
- in.value.val2.value = reftable_malloc(GIT_SHA1_RAWSZ);
- set_hash(in.value.val2.value, 1);
- in.value.val2.target_value =
+ in.u.ref.value.val2.value =
reftable_malloc(GIT_SHA1_RAWSZ);
- set_hash(in.value.val2.target_value, 2);
+ set_hash(in.u.ref.value.val2.value, 1);
+ in.u.ref.value.val2.target_value =
+ reftable_malloc(GIT_SHA1_RAWSZ);
+ set_hash(in.u.ref.value.val2.target_value, 2);
break;
case REFTABLE_REF_SYMREF:
- in.value.symref = xstrdup("target");
+ in.u.ref.value.symref = xstrdup("target");
break;
}
- in.refname = xstrdup("refs/heads/master");
+ in.u.ref.refname = xstrdup("refs/heads/master");
- reftable_record_from_ref(&rec, &in);
- test_copy(&rec);
+ test_copy(&in);
- EXPECT(reftable_record_val_type(&rec) == i);
+ EXPECT(reftable_record_val_type(&in) == i);
- reftable_record_key(&rec, &key);
- n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
+ reftable_record_key(&in, &key);
+ n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ);
EXPECT(n > 0);
/* decode into a non-zero reftable_record to test for leaks. */
-
- reftable_record_from_ref(&rec_out, &out);
- m = reftable_record_decode(&rec_out, key, i, dest,
- GIT_SHA1_RAWSZ);
+ m = reftable_record_decode(&out, key, i, dest, GIT_SHA1_RAWSZ);
EXPECT(n == m);
- EXPECT(reftable_ref_record_equal(&in, &out, GIT_SHA1_RAWSZ));
- reftable_record_release(&rec_out);
+ EXPECT(reftable_ref_record_equal(&in.u.ref, &out.u.ref,
+ GIT_SHA1_RAWSZ));
+ reftable_record_release(&in);
strbuf_release(&key);
- reftable_ref_record_release(&in);
+ reftable_record_release(&out);
}
}
@@ -187,7 +180,8 @@ static void test_reftable_log_record_equal(void)
static void test_reftable_log_record_roundtrip(void)
{
int i;
- struct reftable_log_record in[2] = {
+
+ struct reftable_log_record in[] = {
{
.refname = xstrdup("refs/heads/master"),
.update_index = 42,
@@ -208,12 +202,26 @@ static void test_reftable_log_record_roundtrip(void)
.refname = xstrdup("refs/heads/master"),
.update_index = 22,
.value_type = REFTABLE_LOG_DELETION,
+ },
+ {
+ .refname = xstrdup("branch"),
+ .update_index = 33,
+ .value_type = REFTABLE_LOG_UPDATE,
+ .value = {
+ .update = {
+ .old_hash = reftable_malloc(GIT_SHA1_RAWSZ),
+ .new_hash = reftable_malloc(GIT_SHA1_RAWSZ),
+ /* rest of fields left empty. */
+ },
+ },
}
};
set_test_hash(in[0].value.update.new_hash, 1);
set_test_hash(in[0].value.update.old_hash, 2);
+ set_test_hash(in[2].value.update.new_hash, 3);
+ set_test_hash(in[2].value.update.old_hash, 4);
for (i = 0; i < ARRAY_SIZE(in); i++) {
- struct reftable_record rec = { NULL };
+ struct reftable_record rec = { .type = BLOCK_TYPE_LOG };
struct strbuf key = STRBUF_INIT;
uint8_t buffer[1024] = { 0 };
struct string_view dest = {
@@ -221,23 +229,25 @@ static void test_reftable_log_record_roundtrip(void)
.len = sizeof(buffer),
};
/* populate out, to check for leaks. */
- struct reftable_log_record out = {
- .refname = xstrdup("old name"),
- .value_type = REFTABLE_LOG_UPDATE,
- .value = {
- .update = {
- .new_hash = reftable_calloc(GIT_SHA1_RAWSZ),
- .old_hash = reftable_calloc(GIT_SHA1_RAWSZ),
- .name = xstrdup("old name"),
- .email = xstrdup("old@email"),
- .message = xstrdup("old message"),
+ struct reftable_record out = {
+ .type = BLOCK_TYPE_LOG,
+ .u.log = {
+ .refname = xstrdup("old name"),
+ .value_type = REFTABLE_LOG_UPDATE,
+ .value = {
+ .update = {
+ .new_hash = reftable_calloc(GIT_SHA1_RAWSZ),
+ .old_hash = reftable_calloc(GIT_SHA1_RAWSZ),
+ .name = xstrdup("old name"),
+ .email = xstrdup("old@email"),
+ .message = xstrdup("old message"),
+ },
},
},
};
- struct reftable_record rec_out = { NULL };
int n, m, valtype;
- reftable_record_from_log(&rec, &in[i]);
+ rec.u.log = in[i];
test_copy(&rec);
@@ -245,16 +255,16 @@ static void test_reftable_log_record_roundtrip(void)
n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
EXPECT(n >= 0);
- reftable_record_from_log(&rec_out, &out);
valtype = reftable_record_val_type(&rec);
- m = reftable_record_decode(&rec_out, key, valtype, dest,
+ m = reftable_record_decode(&out, key, valtype, dest,
GIT_SHA1_RAWSZ);
EXPECT(n == m);
- EXPECT(reftable_log_record_equal(&in[i], &out, GIT_SHA1_RAWSZ));
+ EXPECT(reftable_log_record_equal(&in[i], &out.u.log,
+ GIT_SHA1_RAWSZ));
reftable_log_record_release(&in[i]);
strbuf_release(&key);
- reftable_record_release(&rec_out);
+ reftable_record_release(&out);
}
}
@@ -322,47 +332,43 @@ static void test_reftable_obj_record_roundtrip(void)
} };
int i = 0;
for (i = 0; i < ARRAY_SIZE(recs); i++) {
- struct reftable_obj_record in = recs[i];
uint8_t buffer[1024] = { 0 };
struct string_view dest = {
.buf = buffer,
.len = sizeof(buffer),
};
- struct reftable_record rec = { NULL };
+ struct reftable_record in = {
+ .type = BLOCK_TYPE_OBJ,
+ .u.obj = recs[i],
+ };
struct strbuf key = STRBUF_INIT;
- struct reftable_obj_record out = { NULL };
- struct reftable_record rec_out = { NULL };
+ struct reftable_record out = { .type = BLOCK_TYPE_OBJ };
int n, m;
uint8_t extra;
- reftable_record_from_obj(&rec, &in);
- test_copy(&rec);
- reftable_record_key(&rec, &key);
- n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
+ test_copy(&in);
+ reftable_record_key(&in, &key);
+ n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ);
EXPECT(n > 0);
- extra = reftable_record_val_type(&rec);
- reftable_record_from_obj(&rec_out, &out);
- m = reftable_record_decode(&rec_out, key, extra, dest,
+ extra = reftable_record_val_type(&in);
+ m = reftable_record_decode(&out, key, extra, dest,
GIT_SHA1_RAWSZ);
EXPECT(n == m);
- EXPECT(in.hash_prefix_len == out.hash_prefix_len);
- EXPECT(in.offset_len == out.offset_len);
-
- EXPECT(!memcmp(in.hash_prefix, out.hash_prefix,
- in.hash_prefix_len));
- EXPECT(0 == memcmp(in.offsets, out.offsets,
- sizeof(uint64_t) * in.offset_len));
+ EXPECT(reftable_record_equal(&in, &out, GIT_SHA1_RAWSZ));
strbuf_release(&key);
- reftable_record_release(&rec_out);
+ reftable_record_release(&out);
}
}
static void test_reftable_index_record_roundtrip(void)
{
- struct reftable_index_record in = {
- .offset = 42,
- .last_key = STRBUF_INIT,
+ struct reftable_record in = {
+ .type = BLOCK_TYPE_INDEX,
+ .u.idx = {
+ .offset = 42,
+ .last_key = STRBUF_INIT,
+ },
};
uint8_t buffer[1024] = { 0 };
struct string_view dest = {
@@ -370,31 +376,30 @@ static void test_reftable_index_record_roundtrip(void)
.len = sizeof(buffer),
};
struct strbuf key = STRBUF_INIT;
- struct reftable_record rec = { NULL };
- struct reftable_index_record out = { .last_key = STRBUF_INIT };
- struct reftable_record out_rec = { NULL };
+ struct reftable_record out = {
+ .type = BLOCK_TYPE_INDEX,
+ .u.idx = { .last_key = STRBUF_INIT },
+ };
int n, m;
uint8_t extra;
- strbuf_addstr(&in.last_key, "refs/heads/master");
- reftable_record_from_index(&rec, &in);
- reftable_record_key(&rec, &key);
- test_copy(&rec);
+ strbuf_addstr(&in.u.idx.last_key, "refs/heads/master");
+ reftable_record_key(&in, &key);
+ test_copy(&in);
- EXPECT(0 == strbuf_cmp(&key, &in.last_key));
- n = reftable_record_encode(&rec, dest, GIT_SHA1_RAWSZ);
+ EXPECT(0 == strbuf_cmp(&key, &in.u.idx.last_key));
+ n = reftable_record_encode(&in, dest, GIT_SHA1_RAWSZ);
EXPECT(n > 0);
- extra = reftable_record_val_type(&rec);
- reftable_record_from_index(&out_rec, &out);
- m = reftable_record_decode(&out_rec, key, extra, dest, GIT_SHA1_RAWSZ);
+ extra = reftable_record_val_type(&in);
+ m = reftable_record_decode(&out, key, extra, dest, GIT_SHA1_RAWSZ);
EXPECT(m == n);
- EXPECT(in.offset == out.offset);
+ EXPECT(reftable_record_equal(&in, &out, GIT_SHA1_RAWSZ));
- reftable_record_release(&out_rec);
+ reftable_record_release(&out);
strbuf_release(&key);
- strbuf_release(&in.last_key);
+ strbuf_release(&in.u.idx.last_key);
}
int record_test_main(int argc, const char *argv[])
diff --git a/reftable/reftable-record.h b/reftable/reftable-record.h
index 5370d2288c..67104f8fbf 100644
--- a/reftable/reftable-record.h
+++ b/reftable/reftable-record.h
@@ -49,25 +49,25 @@ struct reftable_ref_record {
/* Returns the first hash, or NULL if `rec` is not of type
* REFTABLE_REF_VAL1 or REFTABLE_REF_VAL2. */
-uint8_t *reftable_ref_record_val1(struct reftable_ref_record *rec);
+uint8_t *reftable_ref_record_val1(const struct reftable_ref_record *rec);
/* Returns the second hash, or NULL if `rec` is not of type
* REFTABLE_REF_VAL2. */
-uint8_t *reftable_ref_record_val2(struct reftable_ref_record *rec);
+uint8_t *reftable_ref_record_val2(const struct reftable_ref_record *rec);
/* returns whether 'ref' represents a deletion */
int reftable_ref_record_is_deletion(const struct reftable_ref_record *ref);
/* prints a reftable_ref_record onto stdout. Useful for debugging. */
-void reftable_ref_record_print(struct reftable_ref_record *ref,
+void reftable_ref_record_print(const struct reftable_ref_record *ref,
uint32_t hash_id);
/* frees and nulls all pointer values inside `ref`. */
void reftable_ref_record_release(struct reftable_ref_record *ref);
/* returns whether two reftable_ref_records are the same. Useful for testing. */
-int reftable_ref_record_equal(struct reftable_ref_record *a,
- struct reftable_ref_record *b, int hash_size);
+int reftable_ref_record_equal(const struct reftable_ref_record *a,
+ const struct reftable_ref_record *b, int hash_size);
/* reftable_log_record holds a reflog entry */
struct reftable_log_record {
@@ -104,8 +104,8 @@ int reftable_log_record_is_deletion(const struct reftable_log_record *log);
void reftable_log_record_release(struct reftable_log_record *log);
/* returns whether two records are equal. Useful for testing. */
-int reftable_log_record_equal(struct reftable_log_record *a,
- struct reftable_log_record *b, int hash_size);
+int reftable_log_record_equal(const struct reftable_log_record *a,
+ const struct reftable_log_record *b, int hash_size);
/* dumps a reftable_log_record on stdout, for debugging/testing. */
void reftable_log_record_print(struct reftable_log_record *log,
diff --git a/reftable/reftable.c b/reftable/reftable.c
deleted file mode 100644
index 0e4607a7cd..0000000000
--- a/reftable/reftable.c
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
-Copyright 2020 Google LLC
-
-Use of this source code is governed by a BSD-style
-license that can be found in the LICENSE file or at
-https://developers.google.com/open-source/licenses/bsd
-*/
-
-#include "basics.h"
-#include "record.h"
-#include "generic.h"
-#include "reftable-iterator.h"
-#include "reftable-generic.h"
-
-int reftable_table_seek_ref(struct reftable_table *tab,
- struct reftable_iterator *it, const char *name)
-{
- struct reftable_ref_record ref = {
- .refname = (char *)name,
- };
- struct reftable_record rec = { NULL };
- reftable_record_from_ref(&rec, &ref);
- return tab->ops->seek_record(tab->table_arg, it, &rec);
-}
-
-int reftable_table_read_ref(struct reftable_table *tab, const char *name,
- struct reftable_ref_record *ref)
-{
- struct reftable_iterator it = { NULL };
- int err = reftable_table_seek_ref(tab, &it, name);
- if (err)
- goto done;
-
- err = reftable_iterator_next_ref(&it, ref);
- if (err)
- goto done;
-
- if (strcmp(ref->refname, name) ||
- reftable_ref_record_is_deletion(ref)) {
- reftable_ref_record_release(ref);
- err = 1;
- goto done;
- }
-
-done:
- reftable_iterator_destroy(&it);
- return err;
-}
-
-uint64_t reftable_table_max_update_index(struct reftable_table *tab)
-{
- return tab->ops->max_update_index(tab->table_arg);
-}
-
-uint64_t reftable_table_min_update_index(struct reftable_table *tab)
-{
- return tab->ops->min_update_index(tab->table_arg);
-}
-
-uint32_t reftable_table_hash_id(struct reftable_table *tab)
-{
- return tab->ops->hash_id(tab->table_arg);
-}
-
-void reftable_iterator_destroy(struct reftable_iterator *it)
-{
- if (!it->ops) {
- return;
- }
- it->ops->close(it->iter_arg);
- it->ops = NULL;
- FREE_AND_NULL(it->iter_arg);
-}
-
-int reftable_iterator_next_ref(struct reftable_iterator *it,
- struct reftable_ref_record *ref)
-{
- struct reftable_record rec = { NULL };
- reftable_record_from_ref(&rec, ref);
- return iterator_next(it, &rec);
-}
-
-int reftable_iterator_next_log(struct reftable_iterator *it,
- struct reftable_log_record *log)
-{
- struct reftable_record rec = { NULL };
- reftable_record_from_log(&rec, log);
- return iterator_next(it, &rec);
-}
-
-int iterator_next(struct reftable_iterator *it, struct reftable_record *rec)
-{
- return it->ops->next(it->iter_arg, rec);
-}
-
-static int empty_iterator_next(void *arg, struct reftable_record *rec)
-{
- return 1;
-}
-
-static void empty_iterator_close(void *arg)
-{
-}
-
-static struct reftable_iterator_vtable empty_vtable = {
- .next = &empty_iterator_next,
- .close = &empty_iterator_close,
-};
-
-void iterator_set_empty(struct reftable_iterator *it)
-{
- assert(!it->ops);
- it->iter_arg = NULL;
- it->ops = &empty_vtable;
-}
diff --git a/reftable/stack.c b/reftable/stack.c
index 56bf5f2d84..ddbdf1b9c8 100644
--- a/reftable/stack.c
+++ b/reftable/stack.c
@@ -889,7 +889,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
struct strbuf new_table_path = STRBUF_INIT;
int err = 0;
int have_lock = 0;
- int lock_file_fd = 0;
+ int lock_file_fd = -1;
int compact_count = last - first + 1;
char **listp = NULL;
char **delete_on_success =
@@ -923,7 +923,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
}
/* Don't want to write to the lock for now. */
close(lock_file_fd);
- lock_file_fd = 0;
+ lock_file_fd = -1;
have_lock = 1;
err = stack_uptodate(st);
@@ -1031,7 +1031,7 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
goto done;
}
err = close(lock_file_fd);
- lock_file_fd = 0;
+ lock_file_fd = -1;
if (err < 0) {
err = REFTABLE_IO_ERROR;
unlink(new_table_path.buf);
@@ -1068,9 +1068,9 @@ done:
listp++;
}
free_names(subtable_locks);
- if (lock_file_fd > 0) {
+ if (lock_file_fd >= 0) {
close(lock_file_fd);
- lock_file_fd = 0;
+ lock_file_fd = -1;
}
if (have_lock) {
unlink(lock_file_name.buf);
diff --git a/reftable/stack_test.c b/reftable/stack_test.c
index f4c743db80..19fe4e2008 100644
--- a/reftable/stack_test.c
+++ b/reftable/stack_test.c
@@ -90,7 +90,7 @@ static void test_read_file(void)
EXPECT(0 == strcmp(want[i], names[i]));
}
free_names(names);
- remove(fn);
+ (void) remove(fn);
}
static void test_parse_names(void)
@@ -839,6 +839,7 @@ static void test_reftable_stack_auto_compaction(void)
EXPECT_ERR(err);
err = reftable_stack_auto_compact(st);
+ EXPECT_ERR(err);
EXPECT(i < 3 || st->merged->stack_len < 2 * fastlog2(i));
}
diff --git a/reftable/system.h b/reftable/system.h
index 4907306c0c..18f9207dfe 100644
--- a/reftable/system.h
+++ b/reftable/system.h
@@ -16,17 +16,6 @@ https://developers.google.com/open-source/licenses/bsd
#include "hash.h" /* hash ID, sizes.*/
#include "dir.h" /* remove_dir_recursively, for tests.*/
-#include <zlib.h>
-
-#ifdef NO_UNCOMPRESS2
-/*
- * This is uncompress2, which is only available in zlib >= 1.2.9
- * (released as of early 2017)
- */
-int uncompress2(Bytef *dest, uLongf *destLen, const Bytef *source,
- uLong *sourceLen);
-#endif
-
int hash_size(uint32_t id);
#endif
diff --git a/reftable/writer.c b/reftable/writer.c
index 35c8649c9b..944c2329ab 100644
--- a/reftable/writer.c
+++ b/reftable/writer.c
@@ -150,6 +150,8 @@ void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min,
void reftable_writer_free(struct reftable_writer *w)
{
+ if (!w)
+ return;
reftable_free(w->block);
reftable_free(w);
}
@@ -254,8 +256,10 @@ done:
int reftable_writer_add_ref(struct reftable_writer *w,
struct reftable_ref_record *ref)
{
- struct reftable_record rec = { NULL };
- struct reftable_ref_record copy = *ref;
+ struct reftable_record rec = {
+ .type = BLOCK_TYPE_REF,
+ .u.ref = *ref,
+ };
int err = 0;
if (ref->refname == NULL)
@@ -264,8 +268,7 @@ int reftable_writer_add_ref(struct reftable_writer *w,
ref->update_index > w->max_update_index)
return REFTABLE_API_ERROR;
- reftable_record_from_ref(&rec, &copy);
- copy.update_index -= w->min_update_index;
+ rec.u.ref.update_index -= w->min_update_index;
err = writer_add_record(w, &rec);
if (err < 0)
@@ -304,7 +307,10 @@ int reftable_writer_add_refs(struct reftable_writer *w,
static int reftable_writer_add_log_verbatim(struct reftable_writer *w,
struct reftable_log_record *log)
{
- struct reftable_record rec = { NULL };
+ struct reftable_record rec = {
+ .type = BLOCK_TYPE_LOG,
+ .u.log = *log,
+ };
if (w->block_writer &&
block_writer_type(w->block_writer) == BLOCK_TYPE_REF) {
int err = writer_finish_public_section(w);
@@ -314,8 +320,6 @@ static int reftable_writer_add_log_verbatim(struct reftable_writer *w,
w->next -= w->pending_padding;
w->pending_padding = 0;
-
- reftable_record_from_log(&rec, log);
return writer_add_record(w, &rec);
}
@@ -396,8 +400,10 @@ static int writer_finish_section(struct reftable_writer *w)
w->index_len = 0;
w->index_cap = 0;
for (i = 0; i < idx_len; i++) {
- struct reftable_record rec = { NULL };
- reftable_record_from_index(&rec, idx + i);
+ struct reftable_record rec = {
+ .type = BLOCK_TYPE_INDEX,
+ .u.idx = idx[i],
+ };
if (block_writer_add(w->block_writer, &rec) == 0) {
continue;
}
@@ -465,17 +471,17 @@ static void write_object_record(void *void_arg, void *key)
{
struct write_record_arg *arg = void_arg;
struct obj_index_tree_node *entry = key;
- struct reftable_obj_record obj_rec = {
- .hash_prefix = (uint8_t *)entry->hash.buf,
- .hash_prefix_len = arg->w->stats.object_id_len,
- .offsets = entry->offsets,
- .offset_len = entry->offset_len,
- };
- struct reftable_record rec = { NULL };
+ struct reftable_record
+ rec = { .type = BLOCK_TYPE_OBJ,
+ .u.obj = {
+ .hash_prefix = (uint8_t *)entry->hash.buf,
+ .hash_prefix_len = arg->w->stats.object_id_len,
+ .offsets = entry->offsets,
+ .offset_len = entry->offset_len,
+ } };
if (arg->err < 0)
goto done;
- reftable_record_from_obj(&rec, &obj_rec);
arg->err = block_writer_add(arg->w->block_writer, &rec);
if (arg->err == 0)
goto done;
@@ -488,7 +494,8 @@ static void write_object_record(void *void_arg, void *key)
arg->err = block_writer_add(arg->w->block_writer, &rec);
if (arg->err == 0)
goto done;
- obj_rec.offset_len = 0;
+
+ rec.u.obj.offset_len = 0;
arg->err = block_writer_add(arg->w->block_writer, &rec);
/* Should be able to write into a fresh block. */
diff --git a/remote.c b/remote.c
index a6d8ec6c1a..c97c626eaa 100644
--- a/remote.c
+++ b/remote.c
@@ -508,9 +508,8 @@ static void read_config(struct repository *repo)
repo->remote_state->current_branch = NULL;
if (startup_info->have_repository) {
- int ignore_errno;
const char *head_ref = refs_resolve_ref_unsafe(
- get_main_ref_store(repo), "HEAD", 0, NULL, &flag, &ignore_errno);
+ get_main_ref_store(repo), "HEAD", 0, NULL, &flag);
if (head_ref && (flag & REF_ISSYMREF) &&
skip_prefix(head_ref, "refs/heads/", &head_ref)) {
repo->remote_state->current_branch = make_branch(
diff --git a/repo-settings.c b/repo-settings.c
index 00ca5571a1..b4fbd16cdc 100644
--- a/repo-settings.c
+++ b/repo-settings.c
@@ -26,7 +26,7 @@ void prepare_repo_settings(struct repository *r)
/* Defaults */
r->settings.index_version = -1;
r->settings.core_untracked_cache = UNTRACKED_CACHE_KEEP;
- r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_DEFAULT;
+ r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_CONSECUTIVE;
/* Booleans config or default, cascades to other settings */
repo_cfg_bool(r, "feature.manyfiles", &manyfiles, 0);
@@ -81,10 +81,17 @@ void prepare_repo_settings(struct repository *r)
}
if (!repo_config_get_string(r, "fetch.negotiationalgorithm", &strval)) {
+ int fetch_default = r->settings.fetch_negotiation_algorithm;
if (!strcasecmp(strval, "skipping"))
r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_SKIPPING;
else if (!strcasecmp(strval, "noop"))
r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_NOOP;
+ else if (!strcasecmp(strval, "consecutive"))
+ r->settings.fetch_negotiation_algorithm = FETCH_NEGOTIATION_CONSECUTIVE;
+ else if (!strcasecmp(strval, "default"))
+ r->settings.fetch_negotiation_algorithm = fetch_default;
+ else
+ die("unknown fetch negotiation algorithm '%s'", strval);
}
/*
diff --git a/repository.h b/repository.h
index 2b5cf97f31..ca837cb9e9 100644
--- a/repository.h
+++ b/repository.h
@@ -20,7 +20,7 @@ enum untracked_cache_setting {
};
enum fetch_negotiation_setting {
- FETCH_NEGOTIATION_DEFAULT,
+ FETCH_NEGOTIATION_CONSECUTIVE,
FETCH_NEGOTIATION_SKIPPING,
FETCH_NEGOTIATION_NOOP,
};
diff --git a/rerere.c b/rerere.c
index d83d58df4f..d26627c593 100644
--- a/rerere.c
+++ b/rerere.c
@@ -609,19 +609,20 @@ static int try_merge(struct index_state *istate,
const struct rerere_id *id, const char *path,
mmfile_t *cur, mmbuffer_t *result)
{
- int ret;
+ enum ll_merge_result ret;
mmfile_t base = {NULL, 0}, other = {NULL, 0};
if (read_mmfile(&base, rerere_path(id, "preimage")) ||
- read_mmfile(&other, rerere_path(id, "postimage")))
- ret = 1;
- else
+ read_mmfile(&other, rerere_path(id, "postimage"))) {
+ ret = LL_MERGE_CONFLICT;
+ } else {
/*
* A three-way merge. Note that this honors user-customizable
* low-level merge driver settings.
*/
ret = ll_merge(result, path, &base, NULL, cur, "", &other, "",
istate, NULL);
+ }
free(base.ptr);
free(other.ptr);
diff --git a/reset.c b/reset.c
index 0881e63691..e3383a9334 100644
--- a/reset.c
+++ b/reset.c
@@ -9,37 +9,106 @@
#include "unpack-trees.h"
#include "hook.h"
-int reset_head(struct repository *r, struct object_id *oid, const char *action,
- const char *switch_to_branch, unsigned flags,
- const char *reflog_orig_head, const char *reflog_head,
- const char *default_reflog_action)
+static int update_refs(const struct reset_head_opts *opts,
+ const struct object_id *oid,
+ const struct object_id *head)
{
- unsigned detach_head = flags & RESET_HEAD_DETACH;
- unsigned reset_hard = flags & RESET_HEAD_HARD;
- unsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
- unsigned refs_only = flags & RESET_HEAD_REFS_ONLY;
- unsigned update_orig_head = flags & RESET_ORIG_HEAD;
- struct object_id head_oid;
+ unsigned detach_head = opts->flags & RESET_HEAD_DETACH;
+ unsigned run_hook = opts->flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK;
+ unsigned update_orig_head = opts->flags & RESET_ORIG_HEAD;
+ const struct object_id *orig_head = opts->orig_head;
+ const char *switch_to_branch = opts->branch;
+ const char *reflog_branch = opts->branch_msg;
+ const char *reflog_head = opts->head_msg;
+ const char *reflog_orig_head = opts->orig_head_msg;
+ const char *default_reflog_action = opts->default_reflog_action;
+ struct object_id *old_orig = NULL, oid_old_orig;
+ struct strbuf msg = STRBUF_INIT;
+ const char *reflog_action;
+ size_t prefix_len;
+ int ret;
+
+ if ((update_orig_head && !reflog_orig_head) || !reflog_head) {
+ if (!default_reflog_action)
+ BUG("default_reflog_action must be given when reflog messages are omitted");
+ reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);
+ strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action :
+ default_reflog_action);
+ }
+ prefix_len = msg.len;
+
+ if (update_orig_head) {
+ if (!get_oid("ORIG_HEAD", &oid_old_orig))
+ old_orig = &oid_old_orig;
+ if (head) {
+ if (!reflog_orig_head) {
+ strbuf_addstr(&msg, "updating ORIG_HEAD");
+ reflog_orig_head = msg.buf;
+ }
+ update_ref(reflog_orig_head, "ORIG_HEAD",
+ orig_head ? orig_head : head,
+ old_orig, 0, UPDATE_REFS_MSG_ON_ERR);
+ } else if (old_orig)
+ delete_ref(NULL, "ORIG_HEAD", old_orig, 0);
+ }
+
+ if (!reflog_head) {
+ strbuf_setlen(&msg, prefix_len);
+ strbuf_addstr(&msg, "updating HEAD");
+ reflog_head = msg.buf;
+ }
+ if (!switch_to_branch)
+ ret = update_ref(reflog_head, "HEAD", oid, head,
+ detach_head ? REF_NO_DEREF : 0,
+ UPDATE_REFS_MSG_ON_ERR);
+ else {
+ ret = update_ref(reflog_branch ? reflog_branch : reflog_head,
+ switch_to_branch, oid, NULL, 0,
+ UPDATE_REFS_MSG_ON_ERR);
+ if (!ret)
+ ret = create_symref("HEAD", switch_to_branch,
+ reflog_head);
+ }
+ if (!ret && run_hook)
+ run_hooks_l("post-checkout",
+ oid_to_hex(head ? head : null_oid()),
+ oid_to_hex(oid), "1", NULL);
+ strbuf_release(&msg);
+ return ret;
+}
+
+int reset_head(struct repository *r, const struct reset_head_opts *opts)
+{
+ const struct object_id *oid = opts->oid;
+ const char *switch_to_branch = opts->branch;
+ unsigned reset_hard = opts->flags & RESET_HEAD_HARD;
+ unsigned refs_only = opts->flags & RESET_HEAD_REFS_ONLY;
+ unsigned update_orig_head = opts->flags & RESET_ORIG_HEAD;
+ struct object_id *head = NULL, head_oid;
struct tree_desc desc[2] = { { NULL }, { NULL } };
struct lock_file lock = LOCK_INIT;
struct unpack_trees_options unpack_tree_opts = { 0 };
struct tree *tree;
- const char *reflog_action;
- struct strbuf msg = STRBUF_INIT;
- size_t prefix_len;
- struct object_id *orig = NULL, oid_orig,
- *old_orig = NULL, oid_old_orig;
+ const char *action;
int ret = 0, nr = 0;
if (switch_to_branch && !starts_with(switch_to_branch, "refs/"))
BUG("Not a fully qualified branch: '%s'", switch_to_branch);
+ if (opts->orig_head_msg && !update_orig_head)
+ BUG("ORIG_HEAD reflog message given without updating ORIG_HEAD");
+
+ if (opts->branch_msg && !opts->branch)
+ BUG("branch reflog message given without a branch");
+
if (!refs_only && repo_hold_locked_index(r, &lock, LOCK_REPORT_ON_ERROR) < 0) {
ret = -1;
goto leave_reset_head;
}
- if ((!oid || !reset_hard) && get_oid("HEAD", &head_oid)) {
+ if (!get_oid("HEAD", &head_oid)) {
+ head = &head_oid;
+ } else if (!oid || !reset_hard) {
ret = error(_("could not determine HEAD revision"));
goto leave_reset_head;
}
@@ -48,8 +117,9 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,
oid = &head_oid;
if (refs_only)
- goto reset_head_refs;
+ return update_refs(opts, oid, head);
+ action = reset_hard ? "reset" : "checkout";
setup_unpack_trees_porcelain(&unpack_tree_opts, action);
unpack_tree_opts.head_idx = 1;
unpack_tree_opts.src_index = r->index;
@@ -59,7 +129,7 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,
unpack_tree_opts.merge = 1;
unpack_tree_opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */
init_checkout_metadata(&unpack_tree_opts.meta, switch_to_branch, oid, NULL);
- if (!detach_head)
+ if (reset_hard)
unpack_tree_opts.reset = UNPACK_RESET_PROTECT_UNTRACKED;
if (repo_read_index_unmerged(r) < 0) {
@@ -91,49 +161,10 @@ int reset_head(struct repository *r, struct object_id *oid, const char *action,
goto leave_reset_head;
}
-reset_head_refs:
- reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT);
- strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : default_reflog_action);
- prefix_len = msg.len;
-
- if (update_orig_head) {
- if (!get_oid("ORIG_HEAD", &oid_old_orig))
- old_orig = &oid_old_orig;
- if (!get_oid("HEAD", &oid_orig)) {
- orig = &oid_orig;
- if (!reflog_orig_head) {
- strbuf_addstr(&msg, "updating ORIG_HEAD");
- reflog_orig_head = msg.buf;
- }
- update_ref(reflog_orig_head, "ORIG_HEAD", orig,
- old_orig, 0, UPDATE_REFS_MSG_ON_ERR);
- } else if (old_orig)
- delete_ref(NULL, "ORIG_HEAD", old_orig, 0);
- }
-
- if (!reflog_head) {
- strbuf_setlen(&msg, prefix_len);
- strbuf_addstr(&msg, "updating HEAD");
- reflog_head = msg.buf;
- }
- if (!switch_to_branch)
- ret = update_ref(reflog_head, "HEAD", oid, orig,
- detach_head ? REF_NO_DEREF : 0,
- UPDATE_REFS_MSG_ON_ERR);
- else {
- ret = update_ref(reflog_head, switch_to_branch, oid,
- NULL, 0, UPDATE_REFS_MSG_ON_ERR);
- if (!ret)
- ret = create_symref("HEAD", switch_to_branch,
- reflog_head);
- }
- if (run_hook)
- run_hooks_l("post-checkout",
- oid_to_hex(orig ? orig : null_oid()),
- oid_to_hex(oid), "1", NULL);
+ if (oid != &head_oid || update_orig_head || switch_to_branch)
+ ret = update_refs(opts, oid, head);
leave_reset_head:
- strbuf_release(&msg);
rollback_lock_file(&lock);
clear_unpack_trees_porcelain(&unpack_tree_opts);
while (nr)
diff --git a/reset.h b/reset.h
index 12f83c78e2..a28f81829d 100644
--- a/reset.h
+++ b/reset.h
@@ -6,15 +6,55 @@
#define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION"
+/* Request a detached checkout */
#define RESET_HEAD_DETACH (1<<0)
+/* Request a reset rather than a checkout */
#define RESET_HEAD_HARD (1<<1)
+/* Run the post-checkout hook */
#define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2)
+/* Only update refs, do not touch the worktree */
#define RESET_HEAD_REFS_ONLY (1<<3)
+/* Update ORIG_HEAD as well as HEAD */
#define RESET_ORIG_HEAD (1<<4)
-int reset_head(struct repository *r, struct object_id *oid, const char *action,
- const char *switch_to_branch, unsigned flags,
- const char *reflog_orig_head, const char *reflog_head,
- const char *default_reflog_action);
+struct reset_head_opts {
+ /*
+ * The commit to checkout/reset to. Defaults to HEAD.
+ */
+ const struct object_id *oid;
+ /*
+ * Optional value to set ORIG_HEAD. Defaults to HEAD.
+ */
+ const struct object_id *orig_head;
+ /*
+ * Optional branch to switch to.
+ */
+ const char *branch;
+ /*
+ * Flags defined above.
+ */
+ unsigned flags;
+ /*
+ * Optional reflog message for branch, defaults to head_msg.
+ */
+ const char *branch_msg;
+ /*
+ * Optional reflog message for HEAD, if this omitted but oid or branch
+ * are given then default_reflog_action must be given.
+ */
+ const char *head_msg;
+ /*
+ * Optional reflog message for ORIG_HEAD, if this omitted and flags
+ * contains RESET_ORIG_HEAD then default_reflog_action must be given.
+ */
+ const char *orig_head_msg;
+ /*
+ * Action to use in default reflog messages, only required if a ref is
+ * being updated and the reflog messages above are omitted.
+ */
+ const char *default_reflog_action;
+};
+
+int reset_head(struct repository *r, const struct reset_head_opts *opts);
#endif
diff --git a/revision.c b/revision.c
index a39fd1c278..4c55c55880 100644
--- a/revision.c
+++ b/revision.c
@@ -273,7 +273,7 @@ static void commit_stack_clear(struct commit_stack *stack)
stack->nr = stack->alloc = 0;
}
-static void mark_one_parent_uninteresting(struct commit *commit,
+static void mark_one_parent_uninteresting(struct rev_info *revs, struct commit *commit,
struct commit_stack *pending)
{
struct commit_list *l;
@@ -290,20 +290,26 @@ static void mark_one_parent_uninteresting(struct commit *commit,
* wasn't uninteresting), in which case we need
* to mark its parents recursively too..
*/
- for (l = commit->parents; l; l = l->next)
+ for (l = commit->parents; l; l = l->next) {
commit_stack_push(pending, l->item);
+ if (revs && revs->exclude_first_parent_only)
+ break;
+ }
}
-void mark_parents_uninteresting(struct commit *commit)
+void mark_parents_uninteresting(struct rev_info *revs, struct commit *commit)
{
struct commit_stack pending = COMMIT_STACK_INIT;
struct commit_list *l;
- for (l = commit->parents; l; l = l->next)
- mark_one_parent_uninteresting(l->item, &pending);
+ for (l = commit->parents; l; l = l->next) {
+ mark_one_parent_uninteresting(revs, l->item, &pending);
+ if (revs && revs->exclude_first_parent_only)
+ break;
+ }
while (pending.nr > 0)
- mark_one_parent_uninteresting(commit_stack_pop(&pending),
+ mark_one_parent_uninteresting(revs, commit_stack_pop(&pending),
&pending);
commit_stack_clear(&pending);
@@ -441,7 +447,7 @@ static struct commit *handle_commit(struct rev_info *revs,
if (repo_parse_commit(revs->repo, commit) < 0)
die("unable to parse commit %s", name);
if (flags & UNINTERESTING) {
- mark_parents_uninteresting(commit);
+ mark_parents_uninteresting(revs, commit);
if (!revs->topo_order || !generation_numbers_enabled(the_repository))
revs->limited = 1;
@@ -1124,7 +1130,7 @@ static int process_parents(struct rev_info *revs, struct commit *commit,
if (repo_parse_commit_gently(revs->repo, p, 1) < 0)
continue;
if (p->parents)
- mark_parents_uninteresting(p);
+ mark_parents_uninteresting(revs, p);
if (p->object.flags & SEEN)
continue;
p->object.flags |= (SEEN | NOT_USER_GIVEN);
@@ -1132,6 +1138,8 @@ static int process_parents(struct rev_info *revs, struct commit *commit,
commit_list_insert_by_date(p, list);
if (queue)
prio_queue_put(queue, p);
+ if (revs->exclude_first_parent_only)
+ break;
}
return 0;
}
@@ -1422,7 +1430,7 @@ static int limit_list(struct rev_info *revs)
if (process_parents(revs, commit, &original_list, NULL) < 0)
return -1;
if (obj->flags & UNINTERESTING) {
- mark_parents_uninteresting(commit);
+ mark_parents_uninteresting(revs, commit);
slop = still_interesting(original_list, date, slop, &interesting_cache);
if (slop)
continue;
@@ -2223,6 +2231,8 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
return argcount;
} else if (!strcmp(arg, "--first-parent")) {
revs->first_parent_only = 1;
+ } else if (!strcmp(arg, "--exclude-first-parent-only")) {
+ revs->exclude_first_parent_only = 1;
} else if (!strcmp(arg, "--ancestry-path")) {
revs->ancestry_path = 1;
revs->simplify_history = 0;
@@ -3357,7 +3367,7 @@ static void explore_walk_step(struct rev_info *revs)
return;
if (c->object.flags & UNINTERESTING)
- mark_parents_uninteresting(c);
+ mark_parents_uninteresting(revs, c);
for (p = c->parents; p; p = p->next)
test_flag_and_insert(&info->explore_queue, p->item, TOPO_WALK_EXPLORED);
diff --git a/revision.h b/revision.h
index 5a507db202..b9c2421687 100644
--- a/revision.h
+++ b/revision.h
@@ -158,6 +158,7 @@ struct rev_info {
bisect:1,
ancestry_path:1,
first_parent_only:1,
+ exclude_first_parent_only:1,
line_level_traverse:1,
tree_blobs_in_commit_order:1,
@@ -195,7 +196,8 @@ struct rev_info {
combine_merges:1,
combined_all_paths:1,
dense_combined_merges:1,
- first_parent_merges:1;
+ first_parent_merges:1,
+ remerge_diff:1;
/* Format info */
int show_notes;
@@ -315,6 +317,9 @@ struct rev_info {
/* misc. flags related to '--no-kept-objects' */
unsigned keep_pack_cache_flags;
+
+ /* Location where temporary objects for remerge-diff are written. */
+ struct tmp_objdir *remerge_objdir;
};
int ref_excluded(struct string_list *, const char *path);
@@ -399,7 +404,7 @@ const char *get_revision_mark(const struct rev_info *revs,
void put_revision_mark(const struct rev_info *revs,
const struct commit *commit);
-void mark_parents_uninteresting(struct commit *commit);
+void mark_parents_uninteresting(struct rev_info *revs, struct commit *commit);
void mark_tree_uninteresting(struct repository *r, struct tree *tree);
void mark_trees_uninteresting_sparse(struct repository *r, struct oidset *trees);
diff --git a/sequencer.c b/sequencer.c
index 5213d16e97..9b697158c5 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -1281,7 +1281,6 @@ void print_commit_summary(struct repository *r,
struct strbuf author_ident = STRBUF_INIT;
struct strbuf committer_ident = STRBUF_INIT;
struct ref_store *refs;
- int resolve_errno;
commit = lookup_commit(r, oid);
if (!commit)
@@ -1332,12 +1331,9 @@ void print_commit_summary(struct repository *r,
diff_setup_done(&rev.diffopt);
refs = get_main_ref_store(the_repository);
- head = refs_resolve_ref_unsafe(refs, "HEAD", 0, NULL, NULL,
- &resolve_errno);
- if (!head) {
- errno = resolve_errno;
- die_errno(_("unable to resolve HEAD after creating commit"));
- }
+ head = refs_resolve_ref_unsafe(refs, "HEAD", 0, NULL, NULL);
+ if (!head)
+ die(_("unable to resolve HEAD after creating commit"));
if (!strcmp(head, "HEAD"))
head = _("detached HEAD");
else
@@ -3588,7 +3584,7 @@ static int do_label(struct repository *r, const char *name, int len)
strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
strbuf_addf(&msg, "rebase (label) '%.*s'", len, name);
- transaction = ref_store_transaction_begin(refs, &err);
+ transaction = ref_store_transaction_begin(refs, 0, &err);
if (!transaction) {
error("%s", err.buf);
ret = -1;
@@ -4089,8 +4085,7 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)
return -1;
}
-void create_autostash(struct repository *r, const char *path,
- const char *default_reflog_action)
+void create_autostash(struct repository *r, const char *path)
{
struct strbuf buf = STRBUF_INIT;
struct lock_file lock_file = LOCK_INIT;
@@ -4105,6 +4100,7 @@ void create_autostash(struct repository *r, const char *path,
if (has_unstaged_changes(r, 1) ||
has_uncommitted_changes(r, 1)) {
struct child_process stash = CHILD_PROCESS_INIT;
+ struct reset_head_opts ropts = { .flags = RESET_HEAD_HARD };
struct object_id oid;
strvec_pushl(&stash.args,
@@ -4126,11 +4122,8 @@ void create_autostash(struct repository *r, const char *path,
path);
write_file(path, "%s", oid_to_hex(&oid));
printf(_("Created autostash: %s\n"), buf.buf);
- if (reset_head(r, NULL, "reset --hard",
- NULL, RESET_HEAD_HARD, NULL, NULL,
- default_reflog_action) < 0)
+ if (reset_head(r, &ropts) < 0)
die(_("could not reset --hard"));
-
if (discard_index(r->index) < 0 ||
repo_read_index(r) < 0)
die(_("could not read index"));
@@ -4215,47 +4208,26 @@ int apply_autostash_oid(const char *stash_oid)
return apply_save_autostash_oid(stash_oid, 1);
}
-static int run_git_checkout(struct repository *r, struct replay_opts *opts,
- const char *commit, const char *action)
-{
- struct child_process cmd = CHILD_PROCESS_INIT;
- int ret;
-
- cmd.git_cmd = 1;
-
- if (startup_info->original_cwd) {
- cmd.dir = startup_info->original_cwd;
- strvec_pushf(&cmd.env_array, "%s=%s",
- GIT_WORK_TREE_ENVIRONMENT, r->worktree);
- }
- strvec_push(&cmd.args, "checkout");
- strvec_push(&cmd.args, commit);
- strvec_pushf(&cmd.env_array, GIT_REFLOG_ACTION "=%s", action);
-
- if (opts->verbose)
- ret = run_command(&cmd);
- else
- ret = run_command_silent_on_success(&cmd);
-
- if (!ret)
- discard_index(r->index);
-
- return ret;
-}
-
static int checkout_onto(struct repository *r, struct replay_opts *opts,
const char *onto_name, const struct object_id *onto,
const struct object_id *orig_head)
{
- const char *action = reflog_message(opts, "start", "checkout %s", onto_name);
-
- if (run_git_checkout(r, opts, oid_to_hex(onto), action)) {
+ struct reset_head_opts ropts = {
+ .oid = onto,
+ .orig_head = orig_head,
+ .flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD |
+ RESET_HEAD_RUN_POST_CHECKOUT_HOOK,
+ .head_msg = reflog_message(opts, "start", "checkout %s",
+ onto_name),
+ .default_reflog_action = "rebase"
+ };
+ if (reset_head(r, &ropts)) {
apply_autostash(rebase_path_autostash());
sequencer_remove_state(opts);
return error(_("could not detach HEAD"));
}
- return update_ref(NULL, "ORIG_HEAD", orig_head, NULL, 0, UPDATE_REFS_MSG_ON_ERR);
+ return 0;
}
static int stopped_at_head(struct repository *r)
diff --git a/sequencer.h b/sequencer.h
index 05a7d2ba6b..da64473636 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -197,8 +197,7 @@ void commit_post_rewrite(struct repository *r,
const struct commit *current_head,
const struct object_id *new_head);
-void create_autostash(struct repository *r, const char *path,
- const char *default_reflog_action);
+void create_autostash(struct repository *r, const char *path);
int save_autostash(const char *path);
int apply_autostash(const char *path);
int apply_autostash_oid(const char *stash_oid);
diff --git a/shallow.c b/shallow.c
index 9ed18eb884..71e5876f37 100644
--- a/shallow.c
+++ b/shallow.c
@@ -603,7 +603,7 @@ static int mark_uninteresting(const char *refname, const struct object_id *oid,
if (!commit)
return 0;
commit->object.flags |= UNINTERESTING;
- mark_parents_uninteresting(commit);
+ mark_parents_uninteresting(NULL, commit);
return 0;
}
diff --git a/submodule-config.c b/submodule-config.c
index f95344028b..c9f54bc72d 100644
--- a/submodule-config.c
+++ b/submodule-config.c
@@ -7,6 +7,7 @@
#include "strbuf.h"
#include "object-store.h"
#include "parse-options.h"
+#include "tree-walk.h"
/*
* submodule cache lookup structure
@@ -726,6 +727,66 @@ const struct submodule *submodule_from_path(struct repository *r,
return config_from(r->submodule_cache, treeish_name, path, lookup_path);
}
+/**
+ * Used internally by submodules_of_tree(). Recurses into 'treeish_name'
+ * and appends submodule entries to 'out'. The submodule_cache expects
+ * a root-level treeish_name and paths, so keep track of these values
+ * with 'root_tree' and 'prefix'.
+ */
+static void traverse_tree_submodules(struct repository *r,
+ const struct object_id *root_tree,
+ char *prefix,
+ const struct object_id *treeish_name,
+ struct submodule_entry_list *out)
+{
+ struct tree_desc tree;
+ struct submodule_tree_entry *st_entry;
+ struct name_entry *name_entry;
+ char *tree_path = NULL;
+
+ name_entry = xmalloc(sizeof(*name_entry));
+
+ fill_tree_descriptor(r, &tree, treeish_name);
+ while (tree_entry(&tree, name_entry)) {
+ if (prefix)
+ tree_path =
+ mkpathdup("%s/%s", prefix, name_entry->path);
+ else
+ tree_path = xstrdup(name_entry->path);
+
+ if (S_ISGITLINK(name_entry->mode) &&
+ is_tree_submodule_active(r, root_tree, tree_path)) {
+ st_entry = xmalloc(sizeof(*st_entry));
+ st_entry->name_entry = xmalloc(sizeof(*st_entry->name_entry));
+ *st_entry->name_entry = *name_entry;
+ st_entry->submodule =
+ submodule_from_path(r, root_tree, tree_path);
+ st_entry->repo = xmalloc(sizeof(*st_entry->repo));
+ if (repo_submodule_init(st_entry->repo, r, tree_path,
+ root_tree))
+ FREE_AND_NULL(st_entry->repo);
+
+ ALLOC_GROW(out->entries, out->entry_nr + 1,
+ out->entry_alloc);
+ out->entries[out->entry_nr++] = *st_entry;
+ } else if (S_ISDIR(name_entry->mode))
+ traverse_tree_submodules(r, root_tree, tree_path,
+ &name_entry->oid, out);
+ free(tree_path);
+ }
+}
+
+void submodules_of_tree(struct repository *r,
+ const struct object_id *treeish_name,
+ struct submodule_entry_list *out)
+{
+ CALLOC_ARRAY(out->entries, 0);
+ out->entry_nr = 0;
+ out->entry_alloc = 0;
+
+ traverse_tree_submodules(r, treeish_name, NULL, treeish_name, out);
+}
+
void submodule_free(struct repository *r)
{
if (r->submodule_cache)
diff --git a/submodule-config.h b/submodule-config.h
index 65875b94ea..fa229a8b97 100644
--- a/submodule-config.h
+++ b/submodule-config.h
@@ -6,6 +6,7 @@
#include "hashmap.h"
#include "submodule.h"
#include "strbuf.h"
+#include "tree-walk.h"
/**
* The submodule config cache API allows to read submodule
@@ -101,4 +102,37 @@ int check_submodule_name(const char *name);
void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules);
void update_clone_config_from_gitmodules(int *max_jobs);
+/*
+ * Submodule entry that contains relevant information about a
+ * submodule in a tree.
+ */
+struct submodule_tree_entry {
+ /* The submodule's tree entry. */
+ struct name_entry *name_entry;
+ /*
+ * A struct repository corresponding to the submodule. May be
+ * NULL if the submodule has not been updated.
+ */
+ struct repository *repo;
+ /*
+ * A struct submodule containing the submodule config in the
+ * tree's .gitmodules.
+ */
+ const struct submodule *submodule;
+};
+
+struct submodule_entry_list {
+ struct submodule_tree_entry *entries;
+ int entry_nr;
+ int entry_alloc;
+};
+
+/**
+ * Given a treeish, return all submodules in the tree and its subtrees,
+ * but excluding nested submodules. Callers that require nested
+ * submodules are expected to recurse into the submodules themselves.
+ */
+void submodules_of_tree(struct repository *r,
+ const struct object_id *treeish_name,
+ struct submodule_entry_list *ret);
#endif /* SUBMODULE_CONFIG_H */
diff --git a/submodule.c b/submodule.c
index c689070524..5ace18a7d9 100644
--- a/submodule.c
+++ b/submodule.c
@@ -267,7 +267,9 @@ int option_parse_recurse_submodules_worktree_updater(const struct option *opt,
* ie, the config looks like: "[submodule] active\n".
* Since that is an invalid pathspec, we should inform the user.
*/
-int is_submodule_active(struct repository *repo, const char *path)
+int is_tree_submodule_active(struct repository *repo,
+ const struct object_id *treeish_name,
+ const char *path)
{
int ret = 0;
char *key = NULL;
@@ -275,7 +277,7 @@ int is_submodule_active(struct repository *repo, const char *path)
const struct string_list *sl;
const struct submodule *module;
- module = submodule_from_path(repo, null_oid(), path);
+ module = submodule_from_path(repo, treeish_name, path);
/* early return if there isn't a path->module mapping */
if (!module)
@@ -317,6 +319,11 @@ int is_submodule_active(struct repository *repo, const char *path)
return ret;
}
+int is_submodule_active(struct repository *repo, const char *path)
+{
+ return is_tree_submodule_active(repo, null_oid(), path);
+}
+
int is_submodule_populated_gently(const char *path, int *return_error_code)
{
int ret = 0;
diff --git a/submodule.h b/submodule.h
index 6bd2c99fd9..784ceffc0e 100644
--- a/submodule.h
+++ b/submodule.h
@@ -54,6 +54,9 @@ int git_default_submodule_config(const char *var, const char *value, void *cb);
struct option;
int option_parse_recurse_submodules_worktree_updater(const struct option *opt,
const char *arg, int unset);
+int is_tree_submodule_active(struct repository *repo,
+ const struct object_id *treeish_name,
+ const char *path);
int is_submodule_active(struct repository *repo, const char *path);
/*
* Determine if a submodule has been populated at a given 'path' by checking if
diff --git a/t/helper/test-csprng.c b/t/helper/test-csprng.c
new file mode 100644
index 0000000000..65d14973c5
--- /dev/null
+++ b/t/helper/test-csprng.c
@@ -0,0 +1,29 @@
+#include "test-tool.h"
+#include "git-compat-util.h"
+
+
+int cmd__csprng(int argc, const char **argv)
+{
+ unsigned long count;
+ unsigned char buf[1024];
+
+ if (argc > 2) {
+ fprintf(stderr, "usage: %s [<size>]\n", argv[0]);
+ return 2;
+ }
+
+ count = (argc == 2) ? strtoul(argv[1], NULL, 0) : -1L;
+
+ while (count) {
+ unsigned long chunk = count < sizeof(buf) ? count : sizeof(buf);
+ if (csprng_bytes(buf, chunk) < 0) {
+ perror("failed to read");
+ return 5;
+ }
+ if (fwrite(buf, chunk, 1, stdout) != chunk)
+ return 1;
+ count -= chunk;
+ }
+
+ return 0;
+}
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
index 3e4ddaee70..9646d85fc8 100644
--- a/t/helper/test-ref-store.c
+++ b/t/helper/test-ref-store.c
@@ -180,10 +180,9 @@ static int cmd_resolve_ref(struct ref_store *refs, const char **argv)
int resolve_flags = arg_flags(*argv++, "resolve-flags", empty_flags);
int flags;
const char *ref;
- int ignore_errno;
ref = refs_resolve_ref_unsafe(refs, refname, resolve_flags,
- &oid, &flags, &ignore_errno);
+ &oid, &flags);
printf("%s %s 0x%x\n", oid_to_hex(&oid), ref ? ref : "(null)", flags);
return ref ? 0 : 1;
}
diff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c
index 26b03d7b78..1f0a28cbb6 100644
--- a/t/helper/test-reftable.c
+++ b/t/helper/test-reftable.c
@@ -3,15 +3,16 @@
int cmd__reftable(int argc, const char **argv)
{
+ /* test from simple to complex. */
basics_test_main(argc, argv);
+ record_test_main(argc, argv);
block_test_main(argc, argv);
- merged_test_main(argc, argv);
+ tree_test_main(argc, argv);
pq_test_main(argc, argv);
- record_test_main(argc, argv);
- refname_test_main(argc, argv);
readwrite_test_main(argc, argv);
+ merged_test_main(argc, argv);
stack_test_main(argc, argv);
- tree_test_main(argc, argv);
+ refname_test_main(argc, argv);
return 0;
}
diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c
index 338a57b104..e6ec69cf32 100644
--- a/t/helper/test-tool.c
+++ b/t/helper/test-tool.c
@@ -20,6 +20,7 @@ static struct test_cmd cmds[] = {
{ "chmtime", cmd__chmtime },
{ "config", cmd__config },
{ "crontab", cmd__crontab },
+ { "csprng", cmd__csprng },
{ "ctype", cmd__ctype },
{ "date", cmd__date },
{ "delta", cmd__delta },
diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h
index 48cee1f4a2..20756eefdd 100644
--- a/t/helper/test-tool.h
+++ b/t/helper/test-tool.h
@@ -10,6 +10,7 @@ int cmd__bloom(int argc, const char **argv);
int cmd__chmtime(int argc, const char **argv);
int cmd__config(int argc, const char **argv);
int cmd__crontab(int argc, const char **argv);
+int cmd__csprng(int argc, const char **argv);
int cmd__ctype(int argc, const char **argv);
int cmd__date(int argc, const char **argv);
int cmd__delta(int argc, const char **argv);
diff --git a/t/lib-bitmap.sh b/t/lib-bitmap.sh
index 21d0392dda..a95537e759 100644
--- a/t/lib-bitmap.sh
+++ b/t/lib-bitmap.sh
@@ -1,6 +1,9 @@
# Helpers for scripts testing bitmap functionality; see t5310 for
# example usage.
+objdir=.git/objects
+midx=$objdir/pack/multi-pack-index
+
# Compare a file containing rev-list bitmap traversal output to its non-bitmap
# counterpart. You can't just use test_cmp for this, because the two produce
# subtly different output:
@@ -264,3 +267,185 @@ have_delta () {
midx_checksum () {
test-tool read-midx --checksum "$1"
}
+
+# midx_pack_source <obj>
+midx_pack_source () {
+ test-tool read-midx --show-objects .git/objects | grep "^$1 " | cut -f2
+}
+
+test_rev_exists () {
+ commit="$1"
+ kind="$2"
+
+ test_expect_success "reverse index exists ($kind)" '
+ GIT_TRACE2_EVENT=$(pwd)/event.trace \
+ git rev-list --test-bitmap "$commit" &&
+
+ if test "rev" = "$kind"
+ then
+ test_path_is_file $midx-$(midx_checksum $objdir).rev
+ fi &&
+ grep "\"category\":\"load_midx_revindex\",\"key\":\"source\",\"value\":\"$kind\"" event.trace
+ '
+}
+
+midx_bitmap_core () {
+ rev_kind="${1:-midx}"
+
+ setup_bitmap_history
+
+ test_expect_success 'create single-pack midx with bitmaps' '
+ git repack -ad &&
+ git multi-pack-index write --bitmap &&
+ test_path_is_file $midx &&
+ test_path_is_file $midx-$(midx_checksum $objdir).bitmap
+ '
+
+ test_rev_exists HEAD "$rev_kind"
+
+ basic_bitmap_tests
+
+ test_expect_success 'create new additional packs' '
+ for i in $(test_seq 1 16)
+ do
+ test_commit "$i" &&
+ git repack -d || return 1
+ done &&
+
+ git checkout -b other2 HEAD~8 &&
+ for i in $(test_seq 1 8)
+ do
+ test_commit "side-$i" &&
+ git repack -d || return 1
+ done &&
+ git checkout second
+ '
+
+ test_expect_success 'create multi-pack midx with bitmaps' '
+ git multi-pack-index write --bitmap &&
+
+ ls $objdir/pack/pack-*.pack >packs &&
+ test_line_count = 25 packs &&
+
+ test_path_is_file $midx &&
+ test_path_is_file $midx-$(midx_checksum $objdir).bitmap
+ '
+
+ test_rev_exists HEAD "$rev_kind"
+
+ basic_bitmap_tests
+
+ test_expect_success '--no-bitmap is respected when bitmaps exist' '
+ git multi-pack-index write --bitmap &&
+
+ test_commit respect--no-bitmap &&
+ git repack -d &&
+
+ test_path_is_file $midx &&
+ test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
+
+ git multi-pack-index write --no-bitmap &&
+
+ test_path_is_file $midx &&
+ test_path_is_missing $midx-$(midx_checksum $objdir).bitmap &&
+ test_path_is_missing $midx-$(midx_checksum $objdir).rev
+ '
+
+ test_expect_success 'setup midx with base from later pack' '
+ # Write a and b so that "a" is a delta on top of base "b", since Git
+ # prefers to delete contents out of a base rather than add to a shorter
+ # object.
+ test_seq 1 128 >a &&
+ test_seq 1 130 >b &&
+
+ git add a b &&
+ git commit -m "initial commit" &&
+
+ a=$(git rev-parse HEAD:a) &&
+ b=$(git rev-parse HEAD:b) &&
+
+ # In the first pack, "a" is stored as a delta to "b".
+ p1=$(git pack-objects .git/objects/pack/pack <<-EOF
+ $a
+ $b
+ EOF
+ ) &&
+
+ # In the second pack, "a" is missing, and "b" is not a delta nor base to
+ # any other object.
+ p2=$(git pack-objects .git/objects/pack/pack <<-EOF
+ $b
+ $(git rev-parse HEAD)
+ $(git rev-parse HEAD^{tree})
+ EOF
+ ) &&
+
+ git prune-packed &&
+ # Use the second pack as the preferred source, so that "b" occurs
+ # earlier in the MIDX object order, rendering "a" unusable for pack
+ # reuse.
+ git multi-pack-index write --bitmap --preferred-pack=pack-$p2.idx &&
+
+ have_delta $a $b &&
+ test $(midx_pack_source $a) != $(midx_pack_source $b)
+ '
+
+ rev_list_tests 'full bitmap with backwards delta'
+
+ test_expect_success 'clone with bitmaps enabled' '
+ git clone --no-local --bare . clone-reverse-delta.git &&
+ test_when_finished "rm -fr clone-reverse-delta.git" &&
+
+ git rev-parse HEAD >expect &&
+ git --git-dir=clone-reverse-delta.git rev-parse HEAD >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success 'changing the preferred pack does not corrupt bitmaps' '
+ rm -fr repo &&
+ git init repo &&
+ test_when_finished "rm -fr repo" &&
+ (
+ cd repo &&
+
+ test_commit A &&
+ test_commit B &&
+
+ git rev-list --objects --no-object-names HEAD^ >A.objects &&
+ git rev-list --objects --no-object-names HEAD^.. >B.objects &&
+
+ A=$(git pack-objects $objdir/pack/pack <A.objects) &&
+ B=$(git pack-objects $objdir/pack/pack <B.objects) &&
+
+ cat >indexes <<-EOF &&
+ pack-$A.idx
+ pack-$B.idx
+ EOF
+
+ git multi-pack-index write --bitmap --stdin-packs \
+ --preferred-pack=pack-$A.pack <indexes &&
+ git rev-list --test-bitmap A &&
+
+ git multi-pack-index write --bitmap --stdin-packs \
+ --preferred-pack=pack-$B.pack <indexes &&
+ git rev-list --test-bitmap A
+ )
+ '
+}
+
+midx_bitmap_partial_tests () {
+ rev_kind="${1:-midx}"
+
+ test_expect_success 'setup partial bitmaps' '
+ test_commit packed &&
+ git repack &&
+ test_commit loose &&
+ git multi-pack-index write --bitmap 2>err &&
+ test_path_is_file $midx &&
+ test_path_is_file $midx-$(midx_checksum $objdir).bitmap
+ '
+
+ test_rev_exists HEAD~ "$rev_kind"
+
+ basic_bitmap_tests HEAD~
+}
diff --git a/t/lib-read-tree-m-3way.sh b/t/lib-read-tree-m-3way.sh
index 168329adbc..2da25b3144 100644
--- a/t/lib-read-tree-m-3way.sh
+++ b/t/lib-read-tree-m-3way.sh
@@ -3,21 +3,21 @@
mkdir Z
for a in N D M
do
- for b in N D M
- do
- p=$a$b
+ for b in N D M
+ do
+ p=$a$b
echo This is $p from the original tree. >$p
echo This is Z/$p from the original tree. >Z/$p
- test_expect_success \
- "adding test file $p and Z/$p" \
- 'git update-index --add $p &&
- git update-index --add Z/$p'
+ test_expect_success "adding test file $p and Z/$p" '
+ git update-index --add $p &&
+ git update-index --add Z/$p
+ '
done
done
echo This is SS from the original tree. >SS
-test_expect_success \
- 'adding test file SS' \
- 'git update-index --add SS'
+test_expect_success 'adding test file SS' '
+ git update-index --add SS
+'
cat >TT <<\EOF
This is a trivial merge sample text.
Branch A is expected to upcase this word, here.
@@ -30,12 +30,12 @@ At the very end, here comes another line, that is
the word, expected to be upcased by Branch B.
This concludes the trivial merge sample file.
EOF
-test_expect_success \
- 'adding test file TT' \
- 'git update-index --add TT'
-test_expect_success \
- 'prepare initial tree' \
- 'tree_O=$(git write-tree)'
+test_expect_success 'adding test file TT' '
+ git update-index --add TT
+'
+test_expect_success 'prepare initial tree' '
+ tree_O=$(git write-tree)
+'
################################################################
# Branch A and B makes the changes according to the above matrix.
@@ -45,48 +45,48 @@ test_expect_success \
to_remove=$(echo D? Z/D?)
rm -f $to_remove
-test_expect_success \
- 'change in branch A (removal)' \
- 'git update-index --remove $to_remove'
+test_expect_success 'change in branch A (removal)' '
+ git update-index --remove $to_remove
+'
for p in M? Z/M?
do
- echo This is modified $p in the branch A. >$p
- test_expect_success \
- 'change in branch A (modification)' \
- "git update-index $p"
+ echo This is modified $p in the branch A. >$p
+ test_expect_success 'change in branch A (modification)' '
+ git update-index $p
+ '
done
for p in AN AA Z/AN Z/AA
do
- echo This is added $p in the branch A. >$p
- test_expect_success \
- 'change in branch A (addition)' \
- "git update-index --add $p"
+ echo This is added $p in the branch A. >$p
+ test_expect_success 'change in branch A (addition)' '
+ git update-index --add $p
+ '
done
echo This is SS from the modified tree. >SS
echo This is LL from the modified tree. >LL
-test_expect_success \
- 'change in branch A (addition)' \
- 'git update-index --add LL &&
- git update-index SS'
+test_expect_success 'change in branch A (addition)' '
+ git update-index --add LL &&
+ git update-index SS
+'
mv TT TT-
sed -e '/Branch A/s/word/WORD/g' <TT- >TT
rm -f TT-
-test_expect_success \
- 'change in branch A (edit)' \
- 'git update-index TT'
+test_expect_success 'change in branch A (edit)' '
+ git update-index TT
+'
mkdir DF
echo Branch A makes a file at DF/DF, creating a directory DF. >DF/DF
-test_expect_success \
- 'change in branch A (change file to directory)' \
- 'git update-index --add DF/DF'
+test_expect_success 'change in branch A (change file to directory)' '
+ git update-index --add DF/DF
+'
-test_expect_success \
- 'recording branch A tree' \
- 'tree_A=$(git write-tree)'
+test_expect_success 'recording branch A tree' '
+ tree_A=$(git write-tree)
+'
################################################################
# Branch B
@@ -94,65 +94,65 @@ test_expect_success \
rm -rf [NDMASLT][NDMASLT] Z DF
mkdir Z
-test_expect_success \
- 'reading original tree and checking out' \
- 'git read-tree $tree_O &&
- git checkout-index -a'
+test_expect_success 'reading original tree and checking out' '
+ git read-tree $tree_O &&
+ git checkout-index -a
+'
to_remove=$(echo ?D Z/?D)
rm -f $to_remove
-test_expect_success \
- 'change in branch B (removal)' \
- "git update-index --remove $to_remove"
+test_expect_success 'change in branch B (removal)' '
+ git update-index --remove $to_remove
+'
for p in ?M Z/?M
do
- echo This is modified $p in the branch B. >$p
- test_expect_success \
- 'change in branch B (modification)' \
- "git update-index $p"
+ echo This is modified $p in the branch B. >$p
+ test_expect_success 'change in branch B (modification)' '
+ git update-index $p
+ '
done
for p in NA AA Z/NA Z/AA
do
- echo This is added $p in the branch B. >$p
- test_expect_success \
- 'change in branch B (addition)' \
- "git update-index --add $p"
+ echo This is added $p in the branch B. >$p
+ test_expect_success 'change in branch B (addition)' '
+ git update-index --add $p
+ '
done
echo This is SS from the modified tree. >SS
echo This is LL from the modified tree. >LL
-test_expect_success \
- 'change in branch B (addition and modification)' \
- 'git update-index --add LL &&
- git update-index SS'
+test_expect_success 'change in branch B (addition and modification)' '
+ git update-index --add LL &&
+ git update-index SS
+'
mv TT TT-
sed -e '/Branch B/s/word/WORD/g' <TT- >TT
rm -f TT-
-test_expect_success \
- 'change in branch B (modification)' \
- 'git update-index TT'
+test_expect_success 'change in branch B (modification)' '
+ git update-index TT
+'
echo Branch B makes a file at DF. >DF
-test_expect_success \
- 'change in branch B (addition of a file to conflict with directory)' \
- 'git update-index --add DF'
-
-test_expect_success \
- 'recording branch B tree' \
- 'tree_B=$(git write-tree)'
-
-test_expect_success \
- 'keep contents of 3 trees for easy access' \
- 'rm -f .git/index &&
- git read-tree $tree_O &&
- mkdir .orig-O &&
- git checkout-index --prefix=.orig-O/ -f -q -a &&
- rm -f .git/index &&
- git read-tree $tree_A &&
- mkdir .orig-A &&
- git checkout-index --prefix=.orig-A/ -f -q -a &&
- rm -f .git/index &&
- git read-tree $tree_B &&
- mkdir .orig-B &&
- git checkout-index --prefix=.orig-B/ -f -q -a'
+test_expect_success 'change in branch B (addition of a file to conflict with directory)' '
+ git update-index --add DF
+'
+
+test_expect_success 'recording branch B tree' '
+ tree_B=$(git write-tree)
+'
+
+test_expect_success 'keep contents of 3 trees for easy access' '
+ rm -f .git/index &&
+ git read-tree $tree_O &&
+ mkdir .orig-O &&
+ git checkout-index --prefix=.orig-O/ -f -q -a &&
+ rm -f .git/index &&
+ git read-tree $tree_A &&
+ mkdir .orig-A &&
+ git checkout-index --prefix=.orig-A/ -f -q -a &&
+ rm -f .git/index &&
+ git read-tree $tree_B &&
+ mkdir .orig-B &&
+ git checkout-index --prefix=.orig-B/ -f -q -a
+'
diff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh
index cb777c74a2..2a7106b949 100755
--- a/t/perf/p2000-sparse-operations.sh
+++ b/t/perf/p2000-sparse-operations.sh
@@ -117,5 +117,7 @@ test_perf_on_all git diff
test_perf_on_all git diff --cached
test_perf_on_all git blame $SPARSE_CONE/a
test_perf_on_all git blame $SPARSE_CONE/f3/a
+test_perf_on_all git checkout-index -f --all
+test_perf_on_all git update-index --add --remove $SPARSE_CONE/a
test_done
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index 3235ab4d53..d479303efa 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -6,7 +6,8 @@ TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
check_config () {
- if test -d "$1" && test -f "$1/config" && test -d "$1/refs"
+ if test_path_is_dir "$1" &&
+ test_path_is_file "$1/config" && test_path_is_dir "$1/refs"
then
: happy
else
diff --git a/t/t0012-help.sh b/t/t0012-help.sh
index 91b68c74a1..cbd725ccac 100755
--- a/t/t0012-help.sh
+++ b/t/t0012-help.sh
@@ -139,13 +139,18 @@ test_expect_success 'git help --config-sections-for-completion' '
'
test_expect_success 'generate builtin list' '
+ mkdir -p sub &&
git --list-cmds=builtins >builtins
'
while read builtin
do
test_expect_success "$builtin can handle -h" '
- test_expect_code 129 git $builtin -h >output 2>&1 &&
+ (
+ GIT_CEILING_DIRECTORIES=$(pwd) &&
+ export GIT_CEILING_DIRECTORIES &&
+ test_expect_code 129 git -C sub $builtin -h >output 2>&1
+ ) &&
test_i18ngrep usage output
'
done <builtins
diff --git a/t/t0015-hash.sh b/t/t0015-hash.sh
index 291e9061f3..086822fc45 100755
--- a/t/t0015-hash.sh
+++ b/t/t0015-hash.sh
@@ -15,7 +15,7 @@ test_expect_success 'test basic SHA-1 hash values' '
grep c12252ceda8be8994d5fa0290a47231c1d16aae3 actual &&
printf "abcdefghijklmnopqrstuvwxyz" | test-tool sha1 >actual &&
grep 32d10c7b8cf96570ca04ce37f2a19d84240d3a89 actual &&
- perl -e "$| = 1; print q{aaaaaaaaaa} for 1..100000;" | \
+ perl -e "$| = 1; print q{aaaaaaaaaa} for 1..100000;" |
test-tool sha1 >actual &&
grep 34aa973cd4c4daa4f61eeb2bdbad27316534016f actual &&
printf "blob 0\0" | test-tool sha1 >actual &&
@@ -38,10 +38,10 @@ test_expect_success 'test basic SHA-256 hash values' '
printf "abcdefghijklmnopqrstuvwxyz" | test-tool sha256 >actual &&
grep 71c480df93d6ae2f1efad1447c66c9525e316218cf51fc8d9ed832f2daf18b73 actual &&
# Try to exercise the chunking code by turning autoflush on.
- perl -e "$| = 1; print q{aaaaaaaaaa} for 1..100000;" | \
+ perl -e "$| = 1; print q{aaaaaaaaaa} for 1..100000;" |
test-tool sha256 >actual &&
grep cdc76e5c9914fb9281a1c7e284d73e67f1809a48a497200e046d39ccc7112cd0 actual &&
- perl -e "$| = 1; print q{abcdefghijklmnopqrstuvwxyz} for 1..100000;" | \
+ perl -e "$| = 1; print q{abcdefghijklmnopqrstuvwxyz} for 1..100000;" |
test-tool sha256 >actual &&
grep e406ba321ca712ad35a698bf0af8d61fc4dc40eca6bdcea4697962724ccbde35 actual &&
printf "blob 0\0" | test-tool sha256 >actual &&
diff --git a/t/t0027-auto-crlf.sh b/t/t0027-auto-crlf.sh
index 4a5c5c602c..c5f7ac63b0 100755
--- a/t/t0027-auto-crlf.sh
+++ b/t/t0027-auto-crlf.sh
@@ -597,6 +597,12 @@ do
# auto: core.autocrlf=false and core.eol unset(or native) uses native eol
checkout_files auto "$id" "" false "" $NL CRLF CRLF_mix_LF LF_mix_CR LF_nul
checkout_files auto "$id" "" false native $NL CRLF CRLF_mix_LF LF_mix_CR LF_nul
+ # core.autocrlf false, .gitattributes sets eol
+ checkout_files "" "$id" "lf" false "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+ checkout_files "" "$id" "crlf" false "" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
+ # core.autocrlf true, .gitattributes sets eol
+ checkout_files "" "$id" "lf" true "" LF CRLF CRLF_mix_LF LF_mix_CR LF_nul
+ checkout_files "" "$id" "crlf" true "" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul
done
# The rest of the tests are unique; do the usual linting.
diff --git a/t/t0051-windows-named-pipe.sh b/t/t0051-windows-named-pipe.sh
index 10ac92d225..412f413360 100755
--- a/t/t0051-windows-named-pipe.sh
+++ b/t/t0051-windows-named-pipe.sh
@@ -3,8 +3,13 @@
test_description='Windows named pipes'
. ./test-lib.sh
+if ! test_have_prereq MINGW
+then
+ skip_all='skipping Windows-specific tests'
+ test_done
+fi
-test_expect_success MINGW 'o_append write to named pipe' '
+test_expect_success 'o_append write to named pipe' '
GIT_TRACE="$(pwd)/expect" git status >/dev/null 2>&1 &&
{ test-tool windows-named-pipe t0051 >actual 2>&1 & } &&
pid=$! &&
diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh
index 64b340f227..ac5ad8c740 100755
--- a/t/t1007-hash-object.sh
+++ b/t/t1007-hash-object.sh
@@ -2,6 +2,7 @@
test_description="git hash-object"
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
echo_without_newline() {
diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh
index 4ba1617752..f3a059e5af 100755
--- a/t/t1092-sparse-checkout-compatibility.sh
+++ b/t/t1092-sparse-checkout-compatibility.sh
@@ -593,13 +593,11 @@ test_expect_success 'reset with pathspecs outside sparse definition' '
test_sparse_match git reset update-folder1 -- folder1 &&
git -C full-checkout reset update-folder1 -- folder1 &&
- test_sparse_match git status --porcelain=v2 &&
- test_all_match git rev-parse HEAD:folder1 &&
+ test_all_match git ls-files -s -- folder1 &&
test_sparse_match git reset update-folder2 -- folder2/a &&
git -C full-checkout reset update-folder2 -- folder2/a &&
- test_sparse_match git status --porcelain=v2 &&
- test_all_match git rev-parse HEAD:folder2/a
+ test_all_match git ls-files -s -- folder2/a
'
test_expect_success 'reset with wildcard pathspec' '
@@ -629,6 +627,173 @@ test_expect_success 'reset with wildcard pathspec' '
test_all_match git ls-files -s -- folder1
'
+test_expect_success 'update-index modify outside sparse definition' '
+ init_repos &&
+
+ write_script edit-contents <<-\EOF &&
+ echo text >>$1
+ EOF
+
+ # Create & modify folder1/a
+ # Note that this setup is a manual way of reaching the erroneous
+ # condition in which a `skip-worktree` enabled, outside-of-cone file
+ # exists on disk. It is used here to ensure `update-index` is stable
+ # and behaves predictably if such a condition occurs.
+ run_on_sparse mkdir -p folder1 &&
+ run_on_sparse cp ../initial-repo/folder1/a folder1/a &&
+ run_on_all ../edit-contents folder1/a &&
+
+ # If file has skip-worktree enabled, update-index does not modify the
+ # index entry
+ test_sparse_match git update-index folder1/a &&
+ test_sparse_match git status --porcelain=v2 &&
+ test_must_be_empty sparse-checkout-out &&
+
+ # When skip-worktree is disabled (even on files outside sparse cone), file
+ # is updated in the index
+ test_sparse_match git update-index --no-skip-worktree folder1/a &&
+ test_all_match git status --porcelain=v2 &&
+ test_all_match git update-index folder1/a &&
+ test_all_match git status --porcelain=v2
+'
+
+test_expect_success 'update-index --add outside sparse definition' '
+ init_repos &&
+
+ write_script edit-contents <<-\EOF &&
+ echo text >>$1
+ EOF
+
+ # Create folder1, add new file
+ run_on_sparse mkdir -p folder1 &&
+ run_on_all ../edit-contents folder1/b &&
+
+ # The *untracked* out-of-cone file is added to the index because it does
+ # not have a `skip-worktree` bit to signal that it should be ignored
+ # (unlike in `git add`, which will fail due to the file being outside
+ # the sparse checkout definition).
+ test_all_match git update-index --add folder1/b &&
+ test_all_match git status --porcelain=v2
+'
+
+# NEEDSWORK: `--remove`, unlike the rest of `update-index`, does not ignore
+# `skip-worktree` entries by default and will remove them from the index.
+# The `--ignore-skip-worktree-entries` flag must be used in conjunction with
+# `--remove` to ignore the `skip-worktree` entries and prevent their removal
+# from the index.
+test_expect_success 'update-index --remove outside sparse definition' '
+ init_repos &&
+
+ # When --ignore-skip-worktree-entries is _not_ specified:
+ # out-of-cone, not-on-disk files are removed from the index
+ test_sparse_match git update-index --remove folder1/a &&
+ cat >expect <<-EOF &&
+ D folder1/a
+ EOF
+ test_sparse_match git diff --cached --name-status &&
+ test_cmp expect sparse-checkout-out &&
+
+ # Reset the state
+ test_all_match git reset --hard &&
+
+ # When --ignore-skip-worktree-entries is specified, out-of-cone
+ # (skip-worktree) files are ignored
+ test_sparse_match git update-index --remove --ignore-skip-worktree-entries folder1/a &&
+ test_sparse_match git diff --cached --name-status &&
+ test_must_be_empty sparse-checkout-out &&
+
+ # Reset the state
+ test_all_match git reset --hard &&
+
+ # --force-remove supercedes --ignore-skip-worktree-entries, removing
+ # a skip-worktree file from the index (and disk) when both are specified
+ # with --remove
+ test_sparse_match git update-index --force-remove --ignore-skip-worktree-entries folder1/a &&
+ cat >expect <<-EOF &&
+ D folder1/a
+ EOF
+ test_sparse_match git diff --cached --name-status &&
+ test_cmp expect sparse-checkout-out
+'
+
+test_expect_success 'update-index with directories' '
+ init_repos &&
+
+ # update-index will exit silently when provided with a directory name
+ # containing a trailing slash
+ test_all_match git update-index deep/ folder1/ &&
+ grep "Ignoring path deep/" sparse-checkout-err &&
+ grep "Ignoring path folder1/" sparse-checkout-err &&
+
+ # When update-index is given a directory name WITHOUT a trailing slash, it will
+ # behave in different ways depending on the status of the directory on disk:
+ # * if it exists, the command exits with an error ("add individual files instead")
+ # * if it does NOT exist (e.g., in a sparse-checkout), it is assumed to be a
+ # file and either triggers an error ("does not exist and --remove not passed")
+ # or is ignored completely (when using --remove)
+ test_all_match test_must_fail git update-index deep &&
+ run_on_all test_must_fail git update-index folder1 &&
+ test_must_fail git -C full-checkout update-index --remove folder1 &&
+ test_sparse_match git update-index --remove folder1 &&
+ test_all_match git status --porcelain=v2
+'
+
+test_expect_success 'update-index --again file outside sparse definition' '
+ init_repos &&
+
+ test_all_match git checkout -b test-reupdate &&
+
+ # Update HEAD without modifying the index to introduce a difference in
+ # folder1/a
+ test_sparse_match git reset --soft update-folder1 &&
+
+ # Because folder1/a differs in the index vs HEAD,
+ # `git update-index --no-skip-worktree --again` will effectively perform
+ # `git update-index --no-skip-worktree folder1/a` and remove the skip-worktree
+ # flag from folder1/a
+ test_sparse_match git update-index --no-skip-worktree --again &&
+ test_sparse_match git status --porcelain=v2 &&
+
+ cat >expect <<-EOF &&
+ D folder1/a
+ EOF
+ test_sparse_match git diff --name-status &&
+ test_cmp expect sparse-checkout-out
+'
+
+test_expect_success 'update-index --cacheinfo' '
+ init_repos &&
+
+ deep_a_oid=$(git -C full-checkout rev-parse update-deep:deep/a) &&
+ folder2_oid=$(git -C full-checkout rev-parse update-folder2:folder2) &&
+ folder1_a_oid=$(git -C full-checkout rev-parse update-folder1:folder1/a) &&
+
+ test_all_match git update-index --cacheinfo 100644 $deep_a_oid deep/a &&
+ test_all_match git status --porcelain=v2 &&
+
+ # Cannot add sparse directory, even in sparse index case
+ test_all_match test_must_fail git update-index --add --cacheinfo 040000 $folder2_oid folder2/ &&
+
+ # Sparse match only: the new outside-of-cone entry is added *without* skip-worktree,
+ # so `git status` reports it as "deleted" in the worktree
+ test_sparse_match git update-index --add --cacheinfo 100644 $folder1_a_oid folder1/a &&
+ test_sparse_match git status --porcelain=v2 &&
+ cat >expect <<-EOF &&
+ MD folder1/a
+ EOF
+ test_sparse_match git status --short -- folder1/a &&
+ test_cmp expect sparse-checkout-out &&
+
+ # To return folder1/a to "normal" for a sparse checkout (ignored &
+ # outside-of-cone), add the skip-worktree flag.
+ test_sparse_match git update-index --skip-worktree folder1/a &&
+ cat >expect <<-EOF &&
+ S folder1/a
+ EOF
+ test_sparse_match git ls-files -t -- folder1/a &&
+ test_cmp expect sparse-checkout-out
+'
+
test_expect_success 'merge, cherry-pick, and rebase' '
init_repos &&
@@ -754,6 +919,74 @@ test_expect_success 'cherry-pick with conflicts' '
test_all_match test_must_fail git cherry-pick to-cherry-pick
'
+test_expect_success 'checkout-index inside sparse definition' '
+ init_repos &&
+
+ run_on_all rm -f deep/a &&
+ test_all_match git checkout-index -- deep/a &&
+ test_all_match git status --porcelain=v2 &&
+
+ echo test >>new-a &&
+ run_on_all cp ../new-a a &&
+ test_all_match test_must_fail git checkout-index -- a &&
+ test_all_match git checkout-index -f -- a &&
+ test_all_match git status --porcelain=v2
+'
+
+test_expect_success 'checkout-index outside sparse definition' '
+ init_repos &&
+
+ # Without --ignore-skip-worktree-bits, outside-of-cone files will trigger
+ # an error
+ test_sparse_match test_must_fail git checkout-index -- folder1/a &&
+ test_i18ngrep "folder1/a has skip-worktree enabled" sparse-checkout-err &&
+ test_path_is_missing folder1/a &&
+
+ # With --ignore-skip-worktree-bits, outside-of-cone files are checked out
+ test_sparse_match git checkout-index --ignore-skip-worktree-bits -- folder1/a &&
+ test_cmp sparse-checkout/folder1/a sparse-index/folder1/a &&
+ test_cmp sparse-checkout/folder1/a full-checkout/folder1/a &&
+
+ run_on_sparse rm -rf folder1 &&
+ echo test >new-a &&
+ run_on_sparse mkdir -p folder1 &&
+ run_on_all cp ../new-a folder1/a &&
+
+ test_all_match test_must_fail git checkout-index --ignore-skip-worktree-bits -- folder1/a &&
+ test_all_match git checkout-index -f --ignore-skip-worktree-bits -- folder1/a &&
+ test_cmp sparse-checkout/folder1/a sparse-index/folder1/a &&
+ test_cmp sparse-checkout/folder1/a full-checkout/folder1/a
+'
+
+test_expect_success 'checkout-index with folders' '
+ init_repos &&
+
+ # Inside checkout definition
+ test_all_match test_must_fail git checkout-index -f -- deep/ &&
+
+ # Outside checkout definition
+ # Note: although all tests fail (as expected), the messaging differs. For
+ # non-sparse index checkouts, the error is that the "file" does not appear
+ # in the index; for sparse checkouts, the error is explicitly that the
+ # entry is a sparse directory.
+ run_on_all test_must_fail git checkout-index -f -- folder1/ &&
+ test_cmp full-checkout-err sparse-checkout-err &&
+ ! test_cmp full-checkout-err sparse-index-err &&
+ grep "is a sparse directory" sparse-index-err
+'
+
+test_expect_success 'checkout-index --all' '
+ init_repos &&
+
+ test_all_match git checkout-index --all &&
+ test_sparse_match test_path_is_missing folder1 &&
+
+ # --ignore-skip-worktree-bits will cause `skip-worktree` files to be
+ # checked out, causing the outside-of-cone `folder1` to exist on-disk
+ test_all_match git checkout-index --ignore-skip-worktree-bits --all &&
+ test_all_match test_path_exists folder1
+'
+
test_expect_success 'clean' '
init_repos &&
@@ -763,23 +996,42 @@ test_expect_success 'clean' '
test_all_match git commit -m "ignore bogus files" &&
run_on_sparse mkdir folder1 &&
+ run_on_all mkdir -p deep/untracked-deep &&
run_on_all touch folder1/bogus &&
+ run_on_all touch folder1/untracked &&
+ run_on_all touch deep/untracked-deep/bogus &&
+ run_on_all touch deep/untracked-deep/untracked &&
test_all_match git status --porcelain=v2 &&
test_all_match git clean -f &&
test_all_match git status --porcelain=v2 &&
test_sparse_match ls &&
test_sparse_match ls folder1 &&
+ run_on_all test_path_exists folder1/bogus &&
+ run_on_all test_path_is_missing folder1/untracked &&
+ run_on_all test_path_exists deep/untracked-deep/bogus &&
+ run_on_all test_path_exists deep/untracked-deep/untracked &&
+
+ test_all_match git clean -fd &&
+ test_all_match git status --porcelain=v2 &&
+ test_sparse_match ls &&
+ test_sparse_match ls folder1 &&
+ run_on_all test_path_exists folder1/bogus &&
+ run_on_all test_path_exists deep/untracked-deep/bogus &&
+ run_on_all test_path_is_missing deep/untracked-deep/untracked &&
test_all_match git clean -xf &&
test_all_match git status --porcelain=v2 &&
test_sparse_match ls &&
test_sparse_match ls folder1 &&
+ run_on_all test_path_is_missing folder1/bogus &&
+ run_on_all test_path_exists deep/untracked-deep/bogus &&
test_all_match git clean -xdf &&
test_all_match git status --porcelain=v2 &&
test_sparse_match ls &&
test_sparse_match ls folder1 &&
+ run_on_all test_path_is_missing deep/untracked-deep/bogus &&
test_sparse_match test_path_is_dir folder1
'
@@ -898,6 +1150,8 @@ test_expect_success 'sparse-index is not expanded' '
echo >>sparse-index/untracked.txt &&
ensure_not_expanded add . &&
+ ensure_not_expanded checkout-index -f a &&
+ ensure_not_expanded checkout-index -f --all &&
for ref in update-deep update-folder1 update-folder2 update-deep
do
echo >>sparse-index/README.md &&
@@ -926,6 +1180,8 @@ test_expect_success 'sparse-index is not expanded' '
# Wildcard identifies only full sparse directories, no index expansion
ensure_not_expanded reset deepest -- folder\* &&
+ ensure_not_expanded clean -fd &&
+
ensure_not_expanded checkout -f update-deep &&
test_config -C sparse-index pull.twohead ort &&
(
@@ -1001,6 +1257,24 @@ test_expect_success 'sparse index is not expanded: diff' '
ensure_not_expanded diff --cached
'
+test_expect_success 'sparse index is not expanded: update-index' '
+ init_repos &&
+
+ deep_a_oid=$(git -C full-checkout rev-parse update-deep:deep/a) &&
+ ensure_not_expanded update-index --cacheinfo 100644 $deep_a_oid deep/a &&
+
+ echo "test" >sparse-index/README.md &&
+ echo "test2" >sparse-index/a &&
+ rm -f sparse-index/deep/a &&
+
+ ensure_not_expanded update-index --add README.md &&
+ ensure_not_expanded update-index a &&
+ ensure_not_expanded update-index --remove deep/a &&
+
+ ensure_not_expanded reset --soft update-deep &&
+ ensure_not_expanded update-index --add --remove --again
+'
+
test_expect_success 'sparse index is not expanded: blame' '
init_repos &&
diff --git a/t/t1405-main-ref-store.sh b/t/t1405-main-ref-store.sh
index 1a3ee8845d..51f8291628 100755
--- a/t/t1405-main-ref-store.sh
+++ b/t/t1405-main-ref-store.sh
@@ -40,6 +40,12 @@ test_expect_success 'delete_refs(FOO, refs/tags/new-tag)' '
test_must_fail git rev-parse refs/tags/new-tag --
'
+# In reftable, we keep the reflogs around for deleted refs.
+test_expect_success !REFFILES 'delete-reflog(FOO, refs/tags/new-tag)' '
+ $RUN delete-reflog FOO &&
+ $RUN delete-reflog refs/tags/new-tag
+'
+
test_expect_success 'rename_refs(main, new-main)' '
git rev-parse main >expected &&
$RUN rename-ref refs/heads/main refs/heads/new-main &&
@@ -105,7 +111,7 @@ test_expect_success 'delete_reflog(HEAD)' '
test_must_fail git reflog exists HEAD
'
-test_expect_success 'create-reflog(HEAD)' '
+test_expect_success REFFILES 'create-reflog(HEAD)' '
$RUN create-reflog HEAD &&
git reflog exists HEAD
'
diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh
index d7ddf7612d..68f69bb543 100755
--- a/t/t1410-reflog.sh
+++ b/t/t1410-reflog.sh
@@ -341,7 +341,7 @@ test_expect_success 'stale dirs do not cause d/f conflicts (reflogs off)' '
# Each line is 114 characters, so we need 75 to still have a few before the
# last 8K. The 89-character padding on the final entry lines up our
# newline exactly.
-test_expect_success SHA1 'parsing reverse reflogs at BUFSIZ boundaries' '
+test_expect_success REFFILES,SHA1 'parsing reverse reflogs at BUFSIZ boundaries' '
git checkout -b reflogskip &&
zf=$(test_oid zero_2) &&
ident="abc <xyz> 0000000001 +0000" &&
@@ -418,7 +418,8 @@ test_expect_success 'expire with multiple worktrees' '
test_commit -C link-wt foobar &&
test_tick &&
git reflog expire --verbose --all --expire=$test_tick &&
- test_must_be_empty .git/worktrees/link-wt/logs/HEAD
+ test-tool ref-store worktree:link-wt for-each-reflog-ent HEAD >actual &&
+ test_must_be_empty actual
)
'
diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh
index 6c941027a8..4e1e84a91f 100755
--- a/t/t1416-ref-transaction-hooks.sh
+++ b/t/t1416-ref-transaction-hooks.sh
@@ -136,4 +136,54 @@ test_expect_success 'interleaving hook calls succeed' '
test_cmp expect target-repo.git/actual
'
+test_expect_success 'hook does not get called on packing refs' '
+ # Pack references first such that we are in a known state.
+ git pack-refs --all &&
+
+ write_script .git/hooks/reference-transaction <<-\EOF &&
+ echo "$@" >>actual
+ cat >>actual
+ EOF
+ rm -f actual &&
+
+ git update-ref refs/heads/unpacked-ref $POST_OID &&
+ git pack-refs --all &&
+
+ # We only expect a single hook invocation, which is the call to
+ # git-update-ref(1).
+ cat >expect <<-EOF &&
+ prepared
+ $ZERO_OID $POST_OID refs/heads/unpacked-ref
+ committed
+ $ZERO_OID $POST_OID refs/heads/unpacked-ref
+ EOF
+
+ test_cmp expect actual
+'
+
+test_expect_success 'deleting packed ref calls hook once' '
+ # Create a reference and pack it.
+ git update-ref refs/heads/to-be-deleted $POST_OID &&
+ git pack-refs --all &&
+
+ write_script .git/hooks/reference-transaction <<-\EOF &&
+ echo "$@" >>actual
+ cat >>actual
+ EOF
+ rm -f actual &&
+
+ git update-ref -d refs/heads/to-be-deleted $POST_OID &&
+
+ # We only expect a single hook invocation, which is the logical
+ # deletion.
+ cat >expect <<-EOF &&
+ prepared
+ $POST_OID $ZERO_OID refs/heads/to-be-deleted
+ committed
+ $POST_OID $ZERO_OID refs/heads/to-be-deleted
+ EOF
+
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index 1bc3795847..7a0ff75ba8 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -42,6 +42,23 @@ test_expect_success 'git branch abc should create a branch' '
git branch abc && test_path_is_file .git/refs/heads/abc
'
+test_expect_success 'git branch abc should fail when abc exists' '
+ test_must_fail git branch abc
+'
+
+test_expect_success 'git branch --force abc should fail when abc is checked out' '
+ test_when_finished git switch main &&
+ git switch abc &&
+ test_must_fail git branch --force abc HEAD~1
+'
+
+test_expect_success 'git branch --force abc should succeed when abc exists' '
+ git rev-parse HEAD~1 >expect &&
+ git branch --force abc HEAD~1 &&
+ git rev-parse abc >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'git branch a/b/c should create a branch' '
git branch a/b/c && test_path_is_file .git/refs/heads/a/b/c
'
diff --git a/t/t3207-branch-submodule.sh b/t/t3207-branch-submodule.sh
new file mode 100755
index 0000000000..0d93f7516c
--- /dev/null
+++ b/t/t3207-branch-submodule.sh
@@ -0,0 +1,292 @@
+#!/bin/sh
+
+test_description='git branch submodule tests'
+
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-rebase.sh
+
+pwd=$(pwd)
+
+# Creates a clean test environment in "pwd" by copying the repo setup
+# from test_dirs.
+reset_test () {
+ rm -fr super &&
+ rm -fr sub-sub-upstream &&
+ rm -fr sub-upstream &&
+ cp -r test_dirs/* .
+}
+
+# Tests that the expected branch does not exist
+test_no_branch () {
+ DIR=$1 &&
+ BRANCH_NAME=$2 &&
+ test_must_fail git -C "$DIR" rev-parse "$BRANCH_NAME" 2>err &&
+ grep "ambiguous argument .$BRANCH_NAME." err
+}
+
+test_expect_success 'setup superproject and submodule' '
+ mkdir test_dirs &&
+ (
+ cd test_dirs &&
+ git init super &&
+ test_commit -C super foo &&
+ git init sub-sub-upstream &&
+ test_commit -C sub-sub-upstream foo &&
+ git init sub-upstream &&
+ # Submodule in a submodule
+ git -C sub-upstream submodule add "${pwd}/test_dirs/sub-sub-upstream" sub-sub &&
+ git -C sub-upstream commit -m "add submodule" &&
+ # Regular submodule
+ git -C super submodule add "${pwd}/test_dirs/sub-upstream" sub &&
+ # Submodule in a subdirectory
+ git -C super submodule add "${pwd}/test_dirs/sub-sub-upstream" second/sub &&
+ git -C super commit -m "add submodule" &&
+ git -C super config submodule.propagateBranches true &&
+ git -C super/sub submodule update --init
+ ) &&
+ reset_test
+'
+
+# Test the argument parsing
+test_expect_success '--recurse-submodules should create branches' '
+ test_when_finished "reset_test" &&
+ (
+ cd super &&
+ git branch --recurse-submodules branch-a &&
+ git rev-parse branch-a &&
+ git -C sub rev-parse branch-a &&
+ git -C sub/sub-sub rev-parse branch-a &&
+ git -C second/sub rev-parse branch-a
+ )
+'
+
+test_expect_success '--recurse-submodules should die if submodule.propagateBranches is false' '
+ test_when_finished "reset_test" &&
+ (
+ cd super &&
+ echo "fatal: branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled" >expected &&
+ test_must_fail git -c submodule.propagateBranches=false branch --recurse-submodules branch-a 2>actual &&
+ test_cmp expected actual
+ )
+'
+
+test_expect_success '--recurse-submodules should fail when not creating branches' '
+ test_when_finished "reset_test" &&
+ (
+ cd super &&
+ git branch --recurse-submodules branch-a &&
+ echo "fatal: --recurse-submodules can only be used to create branches" >expected &&
+ test_must_fail git branch --recurse-submodules -D branch-a 2>actual &&
+ test_cmp expected actual &&
+ # Assert that the branches were not deleted
+ git rev-parse branch-a &&
+ git -C sub rev-parse branch-a
+ )
+'
+
+test_expect_success 'should respect submodule.recurse when creating branches' '
+ test_when_finished "reset_test" &&
+ (
+ cd super &&
+ git -c submodule.recurse=true branch branch-a &&
+ git rev-parse branch-a &&
+ git -C sub rev-parse branch-a
+ )
+'
+
+test_expect_success 'should ignore submodule.recurse when not creating branches' '
+ test_when_finished "reset_test" &&
+ (
+ cd super &&
+ git branch --recurse-submodules branch-a &&
+ git -c submodule.recurse=true branch -D branch-a &&
+ test_no_branch . branch-a &&
+ git -C sub rev-parse branch-a
+ )
+'
+
+# Test branch creation behavior
+test_expect_success 'should create branches based off commit id in superproject' '
+ test_when_finished "reset_test" &&
+ (
+ cd super &&
+ git branch --recurse-submodules branch-a &&
+ git checkout --recurse-submodules branch-a &&
+ git -C sub rev-parse HEAD >expected &&
+ # Move the tip of sub:branch-a so that it no longer matches the commit in super:branch-a
+ git -C sub checkout branch-a &&
+ test_commit -C sub bar &&
+ # Create a new branch-b branch with start-point=branch-a
+ git branch --recurse-submodules branch-b branch-a &&
+ git rev-parse branch-b &&
+ git -C sub rev-parse branch-b >actual &&
+ # Assert that the commit id of sub:second-branch matches super:branch-a and not sub:branch-a
+ test_cmp expected actual
+ )
+'
+
+test_expect_success 'should not create any branches if branch is not valid for all repos' '
+ test_when_finished "reset_test" &&
+ (
+ cd super &&
+ git -C sub branch branch-a &&
+ test_must_fail git branch --recurse-submodules branch-a 2>actual &&
+ test_no_branch . branch-a &&
+ grep "submodule .sub.: fatal: a branch named .branch-a. already exists" actual
+ )
+'
+
+test_expect_success 'should create branches if branch exists and --force is given' '
+ test_when_finished "reset_test" &&
+ (
+ cd super &&
+ git -C sub rev-parse HEAD >expected &&
+ test_commit -C sub baz &&
+ # branch-a in sub now points to a newer commit.
+ git -C sub branch branch-a HEAD &&
+ git -C sub rev-parse branch-a >actual-old-branch-a &&
+ git branch --recurse-submodules --force branch-a &&
+ git rev-parse branch-a &&
+ git -C sub rev-parse branch-a >actual-new-branch-a &&
+ test_cmp expected actual-new-branch-a &&
+ # assert that branch --force actually moved the sub
+ # branch
+ ! test_cmp expected actual-old-branch-a
+ )
+'
+
+test_expect_success 'should create branch when submodule is not in HEAD:.gitmodules' '
+ test_when_finished "reset_test" &&
+ (
+ cd super &&
+ git branch branch-a &&
+ git checkout -b branch-b &&
+ git submodule add ../sub-upstream sub2 &&
+ git -C sub2 submodule update --init &&
+ # branch-b now has a committed submodule not in branch-a
+ git commit -m "add second submodule" &&
+ git checkout branch-a &&
+ git branch --recurse-submodules branch-c branch-b &&
+ git checkout --recurse-submodules branch-c &&
+ git -C sub2 rev-parse branch-c &&
+ git -C sub2/sub-sub rev-parse branch-c
+ )
+'
+
+test_expect_success 'should not create branches in inactive submodules' '
+ test_when_finished "reset_test" &&
+ test_config -C super submodule.sub.active false &&
+ (
+ cd super &&
+ git branch --recurse-submodules branch-a &&
+ git rev-parse branch-a &&
+ test_no_branch sub branch-a
+ )
+'
+
+test_expect_success 'should set up tracking of local branches with track=always' '
+ test_when_finished "reset_test" &&
+ (
+ cd super &&
+ git -c branch.autoSetupMerge=always branch --recurse-submodules branch-a main &&
+ git -C sub rev-parse main &&
+ test_cmp_config -C sub . branch.branch-a.remote &&
+ test_cmp_config -C sub refs/heads/main branch.branch-a.merge
+ )
+'
+
+test_expect_success 'should set up tracking of local branches with explicit track' '
+ test_when_finished "reset_test" &&
+ (
+ cd super &&
+ git branch --track --recurse-submodules branch-a main &&
+ git -C sub rev-parse main &&
+ test_cmp_config -C sub . branch.branch-a.remote &&
+ test_cmp_config -C sub refs/heads/main branch.branch-a.merge
+ )
+'
+
+test_expect_success 'should not set up unnecessary tracking of local branches' '
+ test_when_finished "reset_test" &&
+ (
+ cd super &&
+ git branch --recurse-submodules branch-a main &&
+ git -C sub rev-parse main &&
+ test_cmp_config -C sub "" --default "" branch.branch-a.remote &&
+ test_cmp_config -C sub "" --default "" branch.branch-a.merge
+ )
+'
+
+reset_remote_test () {
+ rm -fr super-clone &&
+ reset_test
+}
+
+test_expect_success 'setup tests with remotes' '
+ (
+ cd test_dirs &&
+ (
+ cd super &&
+ git branch branch-a &&
+ git checkout -b branch-b &&
+ git submodule add ../sub-upstream sub2 &&
+ # branch-b now has a committed submodule not in branch-a
+ git commit -m "add second submodule"
+ ) &&
+ git clone --branch main --recurse-submodules super super-clone &&
+ git -C super-clone config submodule.propagateBranches true
+ ) &&
+ reset_remote_test
+'
+
+test_expect_success 'should get fatal error upon branch creation when submodule is not in .git/modules' '
+ test_when_finished "reset_remote_test" &&
+ (
+ cd super-clone &&
+ # This should succeed because super-clone has sub in .git/modules
+ git branch --recurse-submodules branch-a origin/branch-a &&
+ # This should fail because super-clone does not have sub2 .git/modules
+ test_must_fail git branch --recurse-submodules branch-b origin/branch-b 2>actual &&
+ grep "fatal: submodule .sub2.: unable to find submodule" actual &&
+ test_no_branch . branch-b &&
+ test_no_branch sub branch-b &&
+ # User can fix themselves by initializing the submodule
+ git checkout origin/branch-b &&
+ git submodule update --init --recursive &&
+ git branch --recurse-submodules branch-b origin/branch-b
+ )
+'
+
+test_expect_success 'should set up tracking of remote-tracking branches' '
+ test_when_finished "reset_remote_test" &&
+ (
+ cd super-clone &&
+ git branch --recurse-submodules branch-a origin/branch-a &&
+ test_cmp_config origin branch.branch-a.remote &&
+ test_cmp_config refs/heads/branch-a branch.branch-a.merge &&
+ # "origin/branch-a" does not exist for "sub", but it matches the refspec
+ # so tracking should be set up
+ test_cmp_config -C sub origin branch.branch-a.remote &&
+ test_cmp_config -C sub refs/heads/branch-a branch.branch-a.merge &&
+ test_cmp_config -C sub/sub-sub origin branch.branch-a.remote &&
+ test_cmp_config -C sub/sub-sub refs/heads/branch-a branch.branch-a.merge
+ )
+'
+
+test_expect_success 'should not fail when unable to set up tracking in submodule' '
+ test_when_finished "reset_remote_test" &&
+ (
+ cd super-clone &&
+ git remote rename origin ex-origin &&
+ git branch --recurse-submodules branch-a ex-origin/branch-a &&
+ test_cmp_config ex-origin branch.branch-a.remote &&
+ test_cmp_config refs/heads/branch-a branch.branch-a.merge &&
+ test_cmp_config -C sub "" --default "" branch.branch-a.remote &&
+ test_cmp_config -C sub "" --default "" branch.branch-a.merge
+ )
+'
+
+test_done
diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh
index 77a313f62e..d17b450e81 100755
--- a/t/t3406-rebase-message.sh
+++ b/t/t3406-rebase-message.sh
@@ -105,6 +105,29 @@ test_expect_success 'GIT_REFLOG_ACTION' '
test_cmp expect actual
'
+test_expect_success 'rebase --apply reflog' '
+ git checkout -b reflog-apply start &&
+ old_head_reflog="$(git log -g --format=%gs -1 HEAD)" &&
+
+ git rebase --apply Y &&
+
+ git log -g --format=%gs -4 HEAD >actual &&
+ cat >expect <<-EOF &&
+ rebase finished: returning to refs/heads/reflog-apply
+ rebase: Z
+ rebase: checkout Y
+ $old_head_reflog
+ EOF
+ test_cmp expect actual &&
+
+ git log -g --format=%gs -2 reflog-apply >actual &&
+ cat >expect <<-EOF &&
+ rebase finished: refs/heads/reflog-apply onto $(git rev-parse Y)
+ branch: Created from start
+ EOF
+ test_cmp expect actual
+'
+
test_expect_success 'rebase -i onto unrelated history' '
git init unrelated &&
test_commit -C unrelated 1 &&
diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh
index 22eca73aa3..130e2f9b55 100755
--- a/t/t3418-rebase-continue.sh
+++ b/t/t3418-rebase-continue.sh
@@ -308,4 +308,30 @@ test_expect_success 'there is no --no-reschedule-failed-exec in an ongoing rebas
test_expect_code 129 git rebase --edit-todo --no-reschedule-failed-exec
'
+test_orig_head_helper () {
+ test_when_finished 'git rebase --abort &&
+ git checkout topic &&
+ git reset --hard commit-new-file-F2-on-topic-branch' &&
+ git update-ref -d ORIG_HEAD &&
+ test_must_fail git rebase "$@" &&
+ test_cmp_rev ORIG_HEAD commit-new-file-F2-on-topic-branch
+}
+
+test_orig_head () {
+ type=$1
+ test_expect_success "rebase $type sets ORIG_HEAD correctly" '
+ git checkout topic &&
+ git reset --hard commit-new-file-F2-on-topic-branch &&
+ test_orig_head_helper $type main
+ '
+
+ test_expect_success "rebase $type <upstream> <branch> sets ORIG_HEAD correctly" '
+ git checkout main &&
+ test_orig_head_helper $type main topic
+ '
+}
+
+test_orig_head --apply
+test_orig_head --merge
+
test_done
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 686747e55a..b149e2af44 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -1272,7 +1272,6 @@ test_expect_success 'stash works when user.name and user.email are not set' '
>2 &&
git add 2 &&
test_config user.useconfigonly true &&
- test_config stash.usebuiltin true &&
(
sane_unset GIT_AUTHOR_NAME &&
sane_unset GIT_AUTHOR_EMAIL &&
@@ -1323,20 +1322,6 @@ test_expect_success 'stash handles skip-worktree entries nicely' '
git rev-parse --verify refs/stash:A.t
'
-test_expect_success 'stash -c stash.useBuiltin=false warning ' '
- expected="stash.useBuiltin support has been removed" &&
-
- git -c stash.useBuiltin=false stash 2>err &&
- test_i18ngrep "$expected" err &&
- env GIT_TEST_STASH_USE_BUILTIN=false git stash 2>err &&
- test_i18ngrep "$expected" err &&
-
- git -c stash.useBuiltin=true stash 2>err &&
- test_must_be_empty err &&
- env GIT_TEST_STASH_USE_BUILTIN=true git stash 2>err &&
- test_must_be_empty err
-'
-
test_expect_success 'git stash succeeds despite directory/file change' '
test_create_repo directory_file_switch_v1 &&
(
diff --git a/t/t4069-remerge-diff.sh b/t/t4069-remerge-diff.sh
new file mode 100755
index 0000000000..35f94957fc
--- /dev/null
+++ b/t/t4069-remerge-diff.sh
@@ -0,0 +1,291 @@
+#!/bin/sh
+
+test_description='remerge-diff handling'
+
+. ./test-lib.sh
+
+# This test is ort-specific
+if test "${GIT_TEST_MERGE_ALGORITHM}" != ort
+then
+ skip_all="GIT_TEST_MERGE_ALGORITHM != ort"
+ test_done
+fi
+
+test_expect_success 'setup basic merges' '
+ test_write_lines 1 2 3 4 5 6 7 8 9 >numbers &&
+ git add numbers &&
+ git commit -m base &&
+
+ git branch feature_a &&
+ git branch feature_b &&
+ git branch feature_c &&
+
+ git branch ab_resolution &&
+ git branch bc_resolution &&
+
+ git checkout feature_a &&
+ test_write_lines 1 2 three 4 5 6 7 eight 9 >numbers &&
+ git commit -a -m change_a &&
+
+ git checkout feature_b &&
+ test_write_lines 1 2 tres 4 5 6 7 8 9 >numbers &&
+ git commit -a -m change_b &&
+
+ git checkout feature_c &&
+ test_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers &&
+ git commit -a -m change_c &&
+
+ git checkout bc_resolution &&
+ git merge --ff-only feature_b &&
+ # no conflict
+ git merge feature_c &&
+
+ git checkout ab_resolution &&
+ git merge --ff-only feature_a &&
+ # conflicts!
+ test_must_fail git merge feature_b &&
+ # Resolve conflict...and make another change elsewhere
+ test_write_lines 1 2 drei 4 5 6 7 acht 9 >numbers &&
+ git add numbers &&
+ git merge --continue
+'
+
+test_expect_success 'remerge-diff on a clean merge' '
+ git log -1 --oneline bc_resolution >expect &&
+ git show --oneline --remerge-diff bc_resolution >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'remerge-diff with both a resolved conflict and an unrelated change' '
+ git log -1 --oneline ab_resolution >tmp &&
+ cat <<-EOF >>tmp &&
+ diff --git a/numbers b/numbers
+ remerge CONFLICT (content): Merge conflict in numbers
+ index a1fb731..6875544 100644
+ --- a/numbers
+ +++ b/numbers
+ @@ -1,13 +1,9 @@
+ 1
+ 2
+ -<<<<<<< b0ed5cb (change_a)
+ -three
+ -=======
+ -tres
+ ->>>>>>> 6cd3f82 (change_b)
+ +drei
+ 4
+ 5
+ 6
+ 7
+ -eight
+ +acht
+ 9
+ EOF
+ # Hashes above are sha1; rip them out so test works with sha256
+ sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect &&
+
+ git show --oneline --remerge-diff ab_resolution >tmp &&
+ sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'setup non-content conflicts' '
+ git switch --orphan base &&
+
+ test_write_lines 1 2 3 4 5 6 7 8 9 >numbers &&
+ test_write_lines a b c d e f g h i >letters &&
+ test_write_lines in the way >content &&
+ git add numbers letters content &&
+ git commit -m base &&
+
+ git branch side1 &&
+ git branch side2 &&
+
+ git checkout side1 &&
+ test_write_lines 1 2 three 4 5 6 7 8 9 >numbers &&
+ git mv letters letters_side1 &&
+ git mv content file_or_directory &&
+ git add numbers &&
+ git commit -m side1 &&
+
+ git checkout side2 &&
+ git rm numbers &&
+ git mv letters letters_side2 &&
+ mkdir file_or_directory &&
+ echo hello >file_or_directory/world &&
+ git add file_or_directory/world &&
+ git commit -m side2 &&
+
+ git checkout -b resolution side1 &&
+ test_must_fail git merge side2 &&
+ test_write_lines 1 2 three 4 5 6 7 8 9 >numbers &&
+ git add numbers &&
+ git add letters_side1 &&
+ git rm letters &&
+ git rm letters_side2 &&
+ git add file_or_directory~HEAD &&
+ git mv file_or_directory~HEAD wanted_content &&
+ git commit -m resolved
+'
+
+test_expect_success 'remerge-diff with non-content conflicts' '
+ git log -1 --oneline resolution >tmp &&
+ cat <<-EOF >>tmp &&
+ diff --git a/file_or_directory~HASH (side1) b/wanted_content
+ similarity index 100%
+ rename from file_or_directory~HASH (side1)
+ rename to wanted_content
+ remerge CONFLICT (file/directory): directory in the way of file_or_directory from HASH (side1); moving it to file_or_directory~HASH (side1) instead.
+ diff --git a/letters b/letters
+ remerge CONFLICT (rename/rename): letters renamed to letters_side1 in HASH (side1) and to letters_side2 in HASH (side2).
+ diff --git a/letters_side2 b/letters_side2
+ deleted file mode 100644
+ index b236ae5..0000000
+ --- a/letters_side2
+ +++ /dev/null
+ @@ -1,9 +0,0 @@
+ -a
+ -b
+ -c
+ -d
+ -e
+ -f
+ -g
+ -h
+ -i
+ diff --git a/numbers b/numbers
+ remerge CONFLICT (modify/delete): numbers deleted in HASH (side2) and modified in HASH (side1). Version HASH (side1) of numbers left in tree.
+ EOF
+ # We still have some sha1 hashes above; rip them out so test works
+ # with sha256
+ sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect &&
+
+ git show --oneline --remerge-diff resolution >tmp &&
+ sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'remerge-diff w/ diff-filter=U: all conflict headers, no diff content' '
+ git log -1 --oneline resolution >tmp &&
+ cat <<-EOF >>tmp &&
+ diff --git a/file_or_directory~HASH (side1) b/file_or_directory~HASH (side1)
+ remerge CONFLICT (file/directory): directory in the way of file_or_directory from HASH (side1); moving it to file_or_directory~HASH (side1) instead.
+ diff --git a/letters b/letters
+ remerge CONFLICT (rename/rename): letters renamed to letters_side1 in HASH (side1) and to letters_side2 in HASH (side2).
+ diff --git a/numbers b/numbers
+ remerge CONFLICT (modify/delete): numbers deleted in HASH (side2) and modified in HASH (side1). Version HASH (side1) of numbers left in tree.
+ EOF
+ # We still have some sha1 hashes above; rip them out so test works
+ # with sha256
+ sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect &&
+
+ git show --oneline --remerge-diff --diff-filter=U resolution >tmp &&
+ sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'remerge-diff w/ diff-filter=R: relevant file + conflict header' '
+ git log -1 --oneline resolution >tmp &&
+ cat <<-EOF >>tmp &&
+ diff --git a/file_or_directory~HASH (side1) b/wanted_content
+ similarity index 100%
+ rename from file_or_directory~HASH (side1)
+ rename to wanted_content
+ remerge CONFLICT (file/directory): directory in the way of file_or_directory from HASH (side1); moving it to file_or_directory~HASH (side1) instead.
+ EOF
+ # We still have some sha1 hashes above; rip them out so test works
+ # with sha256
+ sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect &&
+
+ git show --oneline --remerge-diff --diff-filter=R resolution >tmp &&
+ sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'remerge-diff w/ pathspec: limits to relevant file including conflict header' '
+ git log -1 --oneline resolution >tmp &&
+ cat <<-EOF >>tmp &&
+ diff --git a/letters b/letters
+ remerge CONFLICT (rename/rename): letters renamed to letters_side1 in HASH (side1) and to letters_side2 in HASH (side2).
+ diff --git a/letters_side2 b/letters_side2
+ deleted file mode 100644
+ index b236ae5..0000000
+ --- a/letters_side2
+ +++ /dev/null
+ @@ -1,9 +0,0 @@
+ -a
+ -b
+ -c
+ -d
+ -e
+ -f
+ -g
+ -h
+ -i
+ EOF
+ # We still have some sha1 hashes above; rip them out so test works
+ # with sha256
+ sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect &&
+
+ git show --oneline --remerge-diff resolution -- "letters*" >tmp &&
+ sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'setup non-content conflicts' '
+ git switch --orphan newbase &&
+
+ test_write_lines 1 2 3 4 5 6 7 8 9 >numbers &&
+ git add numbers &&
+ git commit -m base &&
+
+ git branch newside1 &&
+ git branch newside2 &&
+
+ git checkout newside1 &&
+ test_write_lines 1 2 three 4 5 6 7 8 9 >numbers &&
+ git add numbers &&
+ git commit -m side1 &&
+
+ git checkout newside2 &&
+ test_write_lines 1 2 drei 4 5 6 7 8 9 >numbers &&
+ git add numbers &&
+ git commit -m side2 &&
+
+ git checkout -b newresolution newside1 &&
+ test_must_fail git merge newside2 &&
+ git checkout --theirs numbers &&
+ git add -u numbers &&
+ git commit -m resolved
+'
+
+test_expect_success 'remerge-diff turns off history simplification' '
+ git log -1 --oneline newresolution >tmp &&
+ cat <<-EOF >>tmp &&
+ diff --git a/numbers b/numbers
+ remerge CONFLICT (content): Merge conflict in numbers
+ index 070e9e7..5335e78 100644
+ --- a/numbers
+ +++ b/numbers
+ @@ -1,10 +1,6 @@
+ 1
+ 2
+ -<<<<<<< 96f1e45 (side1)
+ -three
+ -=======
+ drei
+ ->>>>>>> 4fd522f (side2)
+ 4
+ 5
+ 6
+ EOF
+ # We still have some sha1 hashes above; rip them out so test works
+ # with sha256
+ sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >expect &&
+
+ git show --oneline --remerge-diff newresolution -- numbers >tmp &&
+ sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual &&
+ test_cmp expect actual
+'
+
+test_done
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index a7d5edf720..544f0aa82e 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -142,6 +142,19 @@ test_expect_success 'diff-filter=R' '
'
+test_expect_success 'multiple --diff-filter bits' '
+
+ git log -M --pretty="format:%s" --diff-filter=R HEAD >expect &&
+ git log -M --pretty="format:%s" --diff-filter=Ra HEAD >actual &&
+ test_cmp expect actual &&
+ git log -M --pretty="format:%s" --diff-filter=aR HEAD >actual &&
+ test_cmp expect actual &&
+ git log -M --pretty="format:%s" \
+ --diff-filter=a --diff-filter=R HEAD >actual &&
+ test_cmp expect actual
+
+'
+
test_expect_success 'diff-filter=C' '
git log -C -C --pretty="format:%s" --diff-filter=C HEAD >actual &&
diff --git a/t/t4204-patch-id.sh b/t/t4204-patch-id.sh
index 80f4a65b28..a730c0db98 100755
--- a/t/t4204-patch-id.sh
+++ b/t/t4204-patch-id.sh
@@ -38,7 +38,7 @@ calc_patch_id () {
shift
git patch-id "$@" >patch-id.output &&
sed "s/ .*//" patch-id.output >patch-id_"$patch_name" &&
- test_line_count -gt 0 patch-id_"$patch_name"
+ test_line_count -eq 1 patch-id_"$patch_name"
}
get_top_diff () {
@@ -166,40 +166,67 @@ test_expect_success 'patch-id respects config from subdir' '
)
'
-cat >nonl <<\EOF
-diff --git i/a w/a
-index e69de29..2e65efe 100644
---- i/a
-+++ w/a
-@@ -0,0 +1 @@
-+a
-\ No newline at end of file
-diff --git i/b w/b
-index e69de29..6178079 100644
---- i/b
-+++ w/b
-@@ -0,0 +1 @@
-+b
-EOF
-
-cat >withnl <<\EOF
-diff --git i/a w/a
-index e69de29..7898192 100644
---- i/a
-+++ w/a
-@@ -0,0 +1 @@
-+a
-diff --git i/b w/b
-index e69de29..6178079 100644
---- i/b
-+++ w/b
-@@ -0,0 +1 @@
-+b
-EOF
-
test_expect_success 'patch-id handles no-nl-at-eof markers' '
- cat nonl | calc_patch_id nonl &&
- cat withnl | calc_patch_id withnl &&
+ cat >nonl <<-\EOF &&
+ diff --git i/a w/a
+ index e69de29..2e65efe 100644
+ --- i/a
+ +++ w/a
+ @@ -0,0 +1 @@
+ +a
+ \ No newline at end of file
+ diff --git i/b w/b
+ index e69de29..6178079 100644
+ --- i/b
+ +++ w/b
+ @@ -0,0 +1 @@
+ +b
+ EOF
+ cat >withnl <<-\EOF &&
+ diff --git i/a w/a
+ index e69de29..7898192 100644
+ --- i/a
+ +++ w/a
+ @@ -0,0 +1 @@
+ +a
+ diff --git i/b w/b
+ index e69de29..6178079 100644
+ --- i/b
+ +++ w/b
+ @@ -0,0 +1 @@
+ +b
+ EOF
+ calc_patch_id nonl <nonl &&
+ calc_patch_id withnl <withnl &&
test_cmp patch-id_nonl patch-id_withnl
'
+
+test_expect_success 'patch-id handles diffs with one line of before/after' '
+ cat >diffu1 <<-\EOF &&
+ diff --git a/bar b/bar
+ index bdaf90f..31051f6 100644
+ --- a/bar
+ +++ b/bar
+ @@ -2 +2,2 @@
+ b
+ +c
+ diff --git a/car b/car
+ index 00750ed..2ae5e34 100644
+ --- a/car
+ +++ b/car
+ @@ -1 +1,2 @@
+ 3
+ +d
+ diff --git a/foo b/foo
+ index e439850..7146eb8 100644
+ --- a/foo
+ +++ b/foo
+ @@ -2 +2,2 @@
+ a
+ +e
+ EOF
+ calc_patch_id diffu1 <diffu1 &&
+ test_config patchid.stable true &&
+ calc_patch_id diffu1stable <diffu1
+'
test_done
diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh
index d05ab716f6..f775fc1ce6 100755
--- a/t/t5310-pack-bitmaps.sh
+++ b/t/t5310-pack-bitmaps.sh
@@ -397,4 +397,32 @@ test_expect_success 'pack.preferBitmapTips' '
)
'
+test_expect_success 'complains about multiple pack bitmaps' '
+ rm -fr repo &&
+ git init repo &&
+ test_when_finished "rm -fr repo" &&
+ (
+ cd repo &&
+
+ test_commit base &&
+
+ git repack -adb &&
+ bitmap="$(ls .git/objects/pack/pack-*.bitmap)" &&
+ mv "$bitmap" "$bitmap.bak" &&
+
+ test_commit other &&
+ git repack -ab &&
+
+ mv "$bitmap.bak" "$bitmap" &&
+
+ find .git/objects/pack -type f -name "*.pack" >packs &&
+ find .git/objects/pack -type f -name "*.bitmap" >bitmaps &&
+ test_line_count = 2 packs &&
+ test_line_count = 2 bitmaps &&
+
+ git rev-list --use-bitmap-index HEAD 2>err &&
+ grep "ignoring extra bitmap file" err
+ )
+'
+
test_done
diff --git a/t/t5312-prune-corruption.sh b/t/t5312-prune-corruption.sh
index ea889c088a..9d8e249ae8 100755
--- a/t/t5312-prune-corruption.sh
+++ b/t/t5312-prune-corruption.sh
@@ -22,8 +22,8 @@ test_expect_success 'disable reflogs' '
'
create_bogus_ref () {
- test_when_finished 'rm -f .git/refs/heads/bogus..name' &&
- echo $bogus >.git/refs/heads/bogus..name
+ test-tool ref-store main update-ref msg "refs/heads/bogus..name" $bogus $ZERO_OID REF_SKIP_REFNAME_VERIFICATION &&
+ test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/bogus..name"
}
test_expect_success 'create history reachable only from a bogus-named ref' '
@@ -113,7 +113,7 @@ test_expect_success 'pack-refs does not silently delete broken loose ref' '
# we do not want to count on running pack-refs to
# actually pack it, as it is perfectly reasonable to
# skip processing a broken ref
-test_expect_success 'create packed-refs file with broken ref' '
+test_expect_success REFFILES 'create packed-refs file with broken ref' '
rm -f .git/refs/heads/main &&
cat >.git/packed-refs <<-EOF &&
$missing refs/heads/main
@@ -124,13 +124,13 @@ test_expect_success 'create packed-refs file with broken ref' '
test_cmp expect actual
'
-test_expect_success 'pack-refs does not silently delete broken packed ref' '
+test_expect_success REFFILES 'pack-refs does not silently delete broken packed ref' '
git pack-refs --all --prune &&
git rev-parse refs/heads/main >actual &&
test_cmp expect actual
'
-test_expect_success 'pack-refs does not drop broken refs during deletion' '
+test_expect_success REFFILES 'pack-refs does not drop broken refs during deletion' '
git update-ref -d refs/heads/other &&
git rev-parse refs/heads/main >actual &&
test_cmp expect actual
diff --git a/t/t5326-multi-pack-bitmaps.sh b/t/t5326-multi-pack-bitmaps.sh
index e187f90f29..4fe57414c1 100755
--- a/t/t5326-multi-pack-bitmaps.sh
+++ b/t/t5326-multi-pack-bitmaps.sh
@@ -9,125 +9,13 @@ test_description='exercise basic multi-pack bitmap functionality'
GIT_TEST_MULTI_PACK_INDEX=0
GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0
-objdir=.git/objects
-midx=$objdir/pack/multi-pack-index
+# This test exercise multi-pack bitmap functionality where the object order is
+# stored and read from a special chunk within the MIDX, so use the default
+# behavior here.
+sane_unset GIT_TEST_MIDX_WRITE_REV
+sane_unset GIT_TEST_MIDX_READ_RIDX
-# midx_pack_source <obj>
-midx_pack_source () {
- test-tool read-midx --show-objects .git/objects | grep "^$1 " | cut -f2
-}
-
-setup_bitmap_history
-
-test_expect_success 'enable core.multiPackIndex' '
- git config core.multiPackIndex true
-'
-
-test_expect_success 'create single-pack midx with bitmaps' '
- git repack -ad &&
- git multi-pack-index write --bitmap &&
- test_path_is_file $midx &&
- test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
- test_path_is_file $midx-$(midx_checksum $objdir).rev
-'
-
-basic_bitmap_tests
-
-test_expect_success 'create new additional packs' '
- for i in $(test_seq 1 16)
- do
- test_commit "$i" &&
- git repack -d || return 1
- done &&
-
- git checkout -b other2 HEAD~8 &&
- for i in $(test_seq 1 8)
- do
- test_commit "side-$i" &&
- git repack -d || return 1
- done &&
- git checkout second
-'
-
-test_expect_success 'create multi-pack midx with bitmaps' '
- git multi-pack-index write --bitmap &&
-
- ls $objdir/pack/pack-*.pack >packs &&
- test_line_count = 25 packs &&
-
- test_path_is_file $midx &&
- test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
- test_path_is_file $midx-$(midx_checksum $objdir).rev
-'
-
-basic_bitmap_tests
-
-test_expect_success '--no-bitmap is respected when bitmaps exist' '
- git multi-pack-index write --bitmap &&
-
- test_commit respect--no-bitmap &&
- git repack -d &&
-
- test_path_is_file $midx &&
- test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
- test_path_is_file $midx-$(midx_checksum $objdir).rev &&
-
- git multi-pack-index write --no-bitmap &&
-
- test_path_is_file $midx &&
- test_path_is_missing $midx-$(midx_checksum $objdir).bitmap &&
- test_path_is_missing $midx-$(midx_checksum $objdir).rev
-'
-
-test_expect_success 'setup midx with base from later pack' '
- # Write a and b so that "a" is a delta on top of base "b", since Git
- # prefers to delete contents out of a base rather than add to a shorter
- # object.
- test_seq 1 128 >a &&
- test_seq 1 130 >b &&
-
- git add a b &&
- git commit -m "initial commit" &&
-
- a=$(git rev-parse HEAD:a) &&
- b=$(git rev-parse HEAD:b) &&
-
- # In the first pack, "a" is stored as a delta to "b".
- p1=$(git pack-objects .git/objects/pack/pack <<-EOF
- $a
- $b
- EOF
- ) &&
-
- # In the second pack, "a" is missing, and "b" is not a delta nor base to
- # any other object.
- p2=$(git pack-objects .git/objects/pack/pack <<-EOF
- $b
- $(git rev-parse HEAD)
- $(git rev-parse HEAD^{tree})
- EOF
- ) &&
-
- git prune-packed &&
- # Use the second pack as the preferred source, so that "b" occurs
- # earlier in the MIDX object order, rendering "a" unusable for pack
- # reuse.
- git multi-pack-index write --bitmap --preferred-pack=pack-$p2.idx &&
-
- have_delta $a $b &&
- test $(midx_pack_source $a) != $(midx_pack_source $b)
-'
-
-rev_list_tests 'full bitmap with backwards delta'
-
-test_expect_success 'clone with bitmaps enabled' '
- git clone --no-local --bare . clone-reverse-delta.git &&
- test_when_finished "rm -fr clone-reverse-delta.git" &&
-
- git rev-parse HEAD >expect &&
- git --git-dir=clone-reverse-delta.git rev-parse HEAD >actual &&
- test_cmp expect actual
-'
+midx_bitmap_core
bitmap_reuse_tests() {
from=$1
@@ -204,17 +92,7 @@ test_expect_success 'missing object closure fails gracefully' '
)
'
-test_expect_success 'setup partial bitmaps' '
- test_commit packed &&
- git repack &&
- test_commit loose &&
- git multi-pack-index write --bitmap 2>err &&
- test_path_is_file $midx &&
- test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
- test_path_is_file $midx-$(midx_checksum $objdir).rev
-'
-
-basic_bitmap_tests HEAD~
+midx_bitmap_partial_tests
test_expect_success 'removing a MIDX clears stale bitmaps' '
rm -fr repo &&
@@ -228,7 +106,6 @@ test_expect_success 'removing a MIDX clears stale bitmaps' '
# Write a MIDX and bitmap; remove the MIDX but leave the bitmap.
stale_bitmap=$midx-$(midx_checksum $objdir).bitmap &&
- stale_rev=$midx-$(midx_checksum $objdir).rev &&
rm $midx &&
# Then write a new MIDX.
@@ -238,9 +115,7 @@ test_expect_success 'removing a MIDX clears stale bitmaps' '
test_path_is_file $midx &&
test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
- test_path_is_file $midx-$(midx_checksum $objdir).rev &&
- test_path_is_missing $stale_bitmap &&
- test_path_is_missing $stale_rev
+ test_path_is_missing $stale_bitmap
)
'
@@ -261,7 +136,6 @@ test_expect_success 'pack.preferBitmapTips' '
git multi-pack-index write --bitmap &&
test_path_is_file $midx &&
test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
- test_path_is_file $midx-$(midx_checksum $objdir).rev &&
test-tool bitmap list-commits | sort >bitmaps &&
comm -13 bitmaps commits >before &&
@@ -271,7 +145,6 @@ test_expect_success 'pack.preferBitmapTips' '
<before | git update-ref --stdin &&
rm -fr $midx-$(midx_checksum $objdir).bitmap &&
- rm -fr $midx-$(midx_checksum $objdir).rev &&
rm -fr $midx &&
git -c pack.preferBitmapTips=refs/tags/include \
@@ -309,7 +182,6 @@ test_expect_success 'writing a bitmap with --refs-snapshot' '
grep "$(git rev-parse two)" bitmaps &&
rm -fr $midx-$(midx_checksum $objdir).bitmap &&
- rm -fr $midx-$(midx_checksum $objdir).rev &&
rm -fr $midx &&
# Then again, but with a refs snapshot which only sees
@@ -354,7 +226,6 @@ test_expect_success 'write a bitmap with --refs-snapshot (preferred tips)' '
) >snapshot &&
rm -fr $midx-$(midx_checksum $objdir).bitmap &&
- rm -fr $midx-$(midx_checksum $objdir).rev &&
rm -fr $midx &&
git multi-pack-index write --bitmap --refs-snapshot=snapshot &&
@@ -395,4 +266,45 @@ test_expect_success 'hash-cache values are propagated from pack bitmaps' '
)
'
+test_expect_success 'no .bitmap is written without any objects' '
+ rm -fr repo &&
+ git init repo &&
+ test_when_finished "rm -fr repo" &&
+ (
+ cd repo &&
+
+ empty="$(git pack-objects $objdir/pack/pack </dev/null)" &&
+ cat >packs <<-EOF &&
+ pack-$empty.idx
+ EOF
+
+ git multi-pack-index write --bitmap --stdin-packs \
+ <packs 2>err &&
+
+ grep "bitmap without any objects" err &&
+
+ test_path_is_file $midx &&
+ test_path_is_missing $midx-$(midx_checksum $objdir).bitmap
+ )
+'
+
+test_expect_success 'graceful fallback when missing reverse index' '
+ rm -fr repo &&
+ git init repo &&
+ test_when_finished "rm -fr repo" &&
+ (
+ cd repo &&
+
+ test_commit base &&
+
+ # write a pack and MIDX bitmap containing base
+ git repack -adb &&
+ git multi-pack-index write --bitmap &&
+
+ GIT_TEST_MIDX_READ_RIDX=0 \
+ git rev-list --use-bitmap-index HEAD 2>err &&
+ ! grep "ignoring extra bitmap file" err
+ )
+'
+
test_done
diff --git a/t/t5327-multi-pack-bitmaps-rev.sh b/t/t5327-multi-pack-bitmaps-rev.sh
new file mode 100755
index 0000000000..d30ba632c8
--- /dev/null
+++ b/t/t5327-multi-pack-bitmaps-rev.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+test_description='exercise basic multi-pack bitmap functionality (.rev files)'
+
+. ./test-lib.sh
+. "${TEST_DIRECTORY}/lib-bitmap.sh"
+
+# We'll be writing our own midx and bitmaps, so avoid getting confused by the
+# automatic ones.
+GIT_TEST_MULTI_PACK_INDEX=0
+GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0
+
+# Unlike t5326, this test exercise multi-pack bitmap functionality where the
+# object order is stored in a separate .rev file.
+GIT_TEST_MIDX_WRITE_REV=1
+GIT_TEST_MIDX_READ_RIDX=0
+export GIT_TEST_MIDX_WRITE_REV
+export GIT_TEST_MIDX_READ_RIDX
+
+midx_bitmap_core rev
+midx_bitmap_partial_tests rev
+
+test_done
diff --git a/t/t5403-post-checkout-hook.sh b/t/t5403-post-checkout-hook.sh
index 1ec9e23be7..d118181690 100755
--- a/t/t5403-post-checkout-hook.sh
+++ b/t/t5403-post-checkout-hook.sh
@@ -49,23 +49,60 @@ test_expect_success 'post-checkout receives the right args when not switching br
test $old = $new && test $flag = 0
'
-test_expect_success 'post-checkout is triggered on rebase' '
- test_when_finished "rm -f .git/post-checkout.args" &&
- git checkout -b rebase-test main &&
- rm -f .git/post-checkout.args &&
- git rebase rebase-on-me &&
- read old new flag <.git/post-checkout.args &&
- test $old != $new && test $flag = 1
-'
+test_rebase () {
+ args="$*" &&
+ test_expect_success "post-checkout is triggered on rebase $args" '
+ test_when_finished "rm -f .git/post-checkout.args" &&
+ git checkout -B rebase-test main &&
+ rm -f .git/post-checkout.args &&
+ git rebase $args rebase-on-me &&
+ read old new flag <.git/post-checkout.args &&
+ test_cmp_rev main $old &&
+ test_cmp_rev rebase-on-me $new &&
+ test $flag = 1
+ '
+
+ test_expect_success "post-checkout is triggered on rebase $args with fast-forward" '
+ test_when_finished "rm -f .git/post-checkout.args" &&
+ git checkout -B ff-rebase-test rebase-on-me^ &&
+ rm -f .git/post-checkout.args &&
+ git rebase $args rebase-on-me &&
+ read old new flag <.git/post-checkout.args &&
+ test_cmp_rev rebase-on-me^ $old &&
+ test_cmp_rev rebase-on-me $new &&
+ test $flag = 1
+ '
+
+ test_expect_success "rebase $args fast-forward branch checkout runs post-checkout hook" '
+ test_when_finished "test_might_fail git rebase --abort" &&
+ test_when_finished "rm -f .git/post-checkout.args" &&
+ git update-ref refs/heads/rebase-fast-forward three &&
+ git checkout two &&
+ rm -f .git/post-checkout.args &&
+ git rebase $args HEAD rebase-fast-forward &&
+ read old new flag <.git/post-checkout.args &&
+ test_cmp_rev two $old &&
+ test_cmp_rev three $new &&
+ test $flag = 1
+ '
+
+ test_expect_success "rebase $args checkout does not remove untracked files" '
+ test_when_finished "test_might_fail git rebase --abort" &&
+ test_when_finished "rm -f .git/post-checkout.args" &&
+ git update-ref refs/heads/rebase-fast-forward three &&
+ git checkout two &&
+ rm -f .git/post-checkout.args &&
+ echo untracked >three.t &&
+ test_when_finished "rm three.t" &&
+ test_must_fail git rebase $args HEAD rebase-fast-forward 2>err &&
+ grep "untracked working tree files would be overwritten by checkout" err &&
+ test_path_is_missing .git/post-checkout.args
-test_expect_success 'post-checkout is triggered on rebase with fast-forward' '
- test_when_finished "rm -f .git/post-checkout.args" &&
- git checkout -b ff-rebase-test rebase-on-me^ &&
- rm -f .git/post-checkout.args &&
- git rebase rebase-on-me &&
- read old new flag <.git/post-checkout.args &&
- test $old != $new && test $flag = 1
'
+}
+
+test_rebase --apply &&
+test_rebase --merge
test_expect_success 'post-checkout hook is triggered by clone' '
mkdir -p templates/hooks &&
diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh
index f0dc4e6968..ee6d2dde9f 100755
--- a/t/t5500-fetch-pack.sh
+++ b/t/t5500-fetch-pack.sh
@@ -927,7 +927,8 @@ test_expect_success 'fetching deepen' '
)
'
-test_expect_success 'use ref advertisement to prune "have" lines sent' '
+test_negotiation_algorithm_default () {
+ test_when_finished rm -rf clientv0 clientv2 &&
rm -rf server client &&
git init server &&
test_commit -C server both_have_1 &&
@@ -946,7 +947,7 @@ test_expect_success 'use ref advertisement to prune "have" lines sent' '
rm -f trace &&
cp -r client clientv0 &&
GIT_TRACE_PACKET="$(pwd)/trace" git -C clientv0 \
- fetch origin server_has both_have_2 &&
+ "$@" fetch origin server_has both_have_2 &&
grep "have $(git -C client rev-parse client_has)" trace &&
grep "have $(git -C client rev-parse both_have_2)" trace &&
! grep "have $(git -C client rev-parse both_have_2^)" trace &&
@@ -954,10 +955,27 @@ test_expect_success 'use ref advertisement to prune "have" lines sent' '
rm -f trace &&
cp -r client clientv2 &&
GIT_TRACE_PACKET="$(pwd)/trace" git -C clientv2 -c protocol.version=2 \
- fetch origin server_has both_have_2 &&
+ "$@" fetch origin server_has both_have_2 &&
grep "have $(git -C client rev-parse client_has)" trace &&
grep "have $(git -C client rev-parse both_have_2)" trace &&
! grep "have $(git -C client rev-parse both_have_2^)" trace
+}
+
+test_expect_success 'use ref advertisement to prune "have" lines sent' '
+ test_negotiation_algorithm_default
+'
+
+test_expect_success 'same as last but with config overrides' '
+ test_negotiation_algorithm_default \
+ -c feature.experimental=true \
+ -c fetch.negotiationAlgorithm=consecutive
+'
+
+test_expect_success 'ensure bogus fetch.negotiationAlgorithm yields error' '
+ test_when_finished rm -rf clientv0 &&
+ cp -r client clientv0 &&
+ test_must_fail git -C clientv0 --fetch.negotiationAlgorithm=bogus \
+ fetch origin server_has both_have_2
'
test_expect_success 'filtering by size' '
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 20f7110ec1..ef0da0a63b 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -164,6 +164,17 @@ test_expect_success 'fetch --prune --tags with refspec prunes based on refspec'
git rev-parse sometag
'
+test_expect_success REFFILES 'fetch --prune fails to delete branches' '
+ cd "$D" &&
+ git clone . prune-fail &&
+ cd prune-fail &&
+ git update-ref refs/remotes/origin/extrabranch main &&
+ : this will prevent --prune from locking packed-refs for deleting refs, but adding loose refs still succeeds &&
+ >.git/packed-refs.new &&
+
+ test_must_fail git fetch --prune origin
+'
+
test_expect_success 'fetch --atomic works with a single branch' '
test_when_finished "rm -rf \"$D\"/atomic" &&
diff --git a/t/t5511-refspec.sh b/t/t5511-refspec.sh
index be025b90f9..fc55681a3f 100755
--- a/t/t5511-refspec.sh
+++ b/t/t5511-refspec.sh
@@ -2,6 +2,7 @@
test_description='refspec parsing'
+TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
test_refspec () {
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 87881726ed..3137eb8d4d 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -1821,4 +1821,12 @@ test_expect_success 'refuse fetch to current branch of bare repository worktree'
git -C bare.git fetch -u .. HEAD:wt
'
+test_expect_success 'refuse to push a hidden ref, and make sure do not pollute the repository' '
+ mk_empty testrepo &&
+ git -C testrepo config receive.hiderefs refs/hidden &&
+ git -C testrepo config receive.unpackLimit 1 &&
+ test_must_fail git push testrepo HEAD:refs/hidden/foo &&
+ test_dir_is_empty testrepo/.git/objects/pack
+'
+
test_done
diff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh
index 900254947f..00ce9aec23 100755
--- a/t/t5702-protocol-v2.sh
+++ b/t/t5702-protocol-v2.sh
@@ -619,7 +619,7 @@ test_expect_success 'usage: --negotiate-only without --negotiation-tip' '
setup_negotiate_only "$SERVER" "$URI" &&
cat >err.expect <<-\EOF &&
- fatal: --negotiate-only needs one or more --negotiate-tip=*
+ fatal: --negotiate-only needs one or more --negotiation-tip=*
EOF
test_must_fail git -c protocol.version=2 -C client fetch \
diff --git a/t/t6012-rev-list-simplify.sh b/t/t6012-rev-list-simplify.sh
index 4fedc614fa..63fcccec32 100755
--- a/t/t6012-rev-list-simplify.sh
+++ b/t/t6012-rev-list-simplify.sh
@@ -16,13 +16,12 @@ unnote () {
}
#
-# Create a test repo with interesting commit graph:
+# Create a test repo with an interesting commit graph:
#
-# A--B----------G--H--I--K--L
-# \ \ / /
-# \ \ / /
-# C------E---F J
-# \_/
+# A-----B-----G--H--I--K--L
+# \ \ / /
+# \ \ / /
+# C--D--E--F J
#
# The commits are laid out from left-to-right starting with
# the root commit A and terminating at the tip commit L.
@@ -142,6 +141,13 @@ check_result 'I B A' --author-date-order -- file
check_result 'H' --first-parent -- another-file
check_result 'H' --first-parent --topo-order -- another-file
+check_result 'L K I H G B A' --first-parent L
+check_result 'F E D C' --exclude-first-parent-only F ^L
+check_result '' F ^L
+check_result 'L K I H G J' L ^F
+check_result 'L K I H G B J' --exclude-first-parent-only L ^F
+check_result 'L K I H G B' --exclude-first-parent-only --first-parent L ^F
+
check_result 'E C B A' --full-history E -- lost
test_expect_success 'full history simplification without parent' '
printf "%s\n" E C B A >expect &&
diff --git a/t/t6404-recursive-merge.sh b/t/t6404-recursive-merge.sh
index eaf48e941e..b8735c6db4 100755
--- a/t/t6404-recursive-merge.sh
+++ b/t/t6404-recursive-merge.sh
@@ -108,8 +108,13 @@ test_expect_success 'refuse to merge binary files' '
printf "\0\0" >binary-file &&
git add binary-file &&
git commit -m binary2 &&
- test_must_fail git merge F >merge.out 2>merge.err &&
- grep "Cannot merge binary files: binary-file (HEAD vs. F)" merge.err
+ if test "$GIT_TEST_MERGE_ALGORITHM" = ort
+ then
+ test_must_fail git merge F >merge_output
+ else
+ test_must_fail git merge F 2>merge_output
+ fi &&
+ grep "Cannot merge binary files: binary-file (HEAD vs. F)" merge_output
'
test_expect_success 'mark rename/delete as unmerged' '
diff --git a/t/t6406-merge-attr.sh b/t/t6406-merge-attr.sh
index 57e6af5eaa..99abefd44b 100755
--- a/t/t6406-merge-attr.sh
+++ b/t/t6406-merge-attr.sh
@@ -221,8 +221,13 @@ test_expect_success 'binary files with union attribute' '
printf "two\0" >bin.txt &&
git commit -am two &&
- test_must_fail git merge bin-main 2>stderr &&
- grep -i "warning.*cannot merge.*HEAD vs. bin-main" stderr
+ if test "$GIT_TEST_MERGE_ALGORITHM" = ort
+ then
+ test_must_fail git merge bin-main >output
+ else
+ test_must_fail git merge bin-main 2>output
+ fi &&
+ grep -i "warning.*cannot merge.*HEAD vs. bin-main" output
'
test_done
diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh
index e489869dd9..5922fb5bdd 100755
--- a/t/t7700-repack.sh
+++ b/t/t7700-repack.sh
@@ -312,16 +312,13 @@ test_expect_success 'cleans up MIDX when appropriate' '
checksum=$(midx_checksum $objdir) &&
test_path_is_file $midx &&
test_path_is_file $midx-$checksum.bitmap &&
- test_path_is_file $midx-$checksum.rev &&
test_commit repack-3 &&
GIT_TEST_MULTI_PACK_INDEX=0 git repack -Adb --write-midx &&
test_path_is_file $midx &&
test_path_is_missing $midx-$checksum.bitmap &&
- test_path_is_missing $midx-$checksum.rev &&
test_path_is_file $midx-$(midx_checksum $objdir).bitmap &&
- test_path_is_file $midx-$(midx_checksum $objdir).rev &&
test_commit repack-4 &&
GIT_TEST_MULTI_PACK_INDEX=0 git repack -Adb &&
@@ -354,7 +351,6 @@ test_expect_success '--write-midx with preferred bitmap tips' '
test_line_count = 1 before &&
rm -fr $midx-$(midx_checksum $objdir).bitmap &&
- rm -fr $midx-$(midx_checksum $objdir).rev &&
rm -fr $midx &&
# instead of constructing the snapshot ourselves (c.f., the test
diff --git a/t/t9102-git-svn-deep-rmdir.sh b/t/t9102-git-svn-deep-rmdir.sh
index 7b2049caa0..946ef85eb9 100755
--- a/t/t9102-git-svn-deep-rmdir.sh
+++ b/t/t9102-git-svn-deep-rmdir.sh
@@ -1,7 +1,6 @@
#!/bin/sh
test_description='git svn rmdir'
-TEST_PASSES_SANITIZE_LEAK=true
. ./lib-git-svn.sh
test_expect_success 'initialize repo' '
diff --git a/t/t9123-git-svn-rebuild-with-rewriteroot.sh b/t/t9123-git-svn-rebuild-with-rewriteroot.sh
index 3320b1f39c..ead404589e 100755
--- a/t/t9123-git-svn-rebuild-with-rewriteroot.sh
+++ b/t/t9123-git-svn-rebuild-with-rewriteroot.sh
@@ -5,7 +5,6 @@
test_description='git svn respects rewriteRoot during rebuild'
-TEST_PASSES_SANITIZE_LEAK=true
. ./lib-git-svn.sh
mkdir import
diff --git a/t/t9128-git-svn-cmd-branch.sh b/t/t9128-git-svn-cmd-branch.sh
index 9871f5abc9..783e3ba0c5 100755
--- a/t/t9128-git-svn-cmd-branch.sh
+++ b/t/t9128-git-svn-cmd-branch.sh
@@ -5,7 +5,6 @@
test_description='git svn partial-rebuild tests'
-TEST_PASSES_SANITIZE_LEAK=true
. ./lib-git-svn.sh
test_expect_success 'initialize svnrepo' '
diff --git a/t/t9167-git-svn-cmd-branch-subproject.sh b/t/t9167-git-svn-cmd-branch-subproject.sh
index d9fd111c10..d8128430a8 100755
--- a/t/t9167-git-svn-cmd-branch-subproject.sh
+++ b/t/t9167-git-svn-cmd-branch-subproject.sh
@@ -5,7 +5,6 @@
test_description='git svn branch for subproject clones'
-TEST_PASSES_SANITIZE_LEAK=true
. ./lib-git-svn.sh
test_expect_success 'initialize svnrepo' '
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 98c6280632..24117cb901 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -1444,6 +1444,144 @@ test_expect_success 'git checkout - with --detach, complete only references' '
EOF
'
+test_expect_success 'setup sparse-checkout tests' '
+ # set up sparse-checkout repo
+ git init sparse-checkout &&
+ (
+ cd sparse-checkout &&
+ mkdir -p folder1/0/1 folder2/0 folder3 &&
+ touch folder1/0/1/t.txt &&
+ touch folder2/0/t.txt &&
+ touch folder3/t.txt &&
+ git add . &&
+ git commit -am "Initial commit"
+ )
+'
+
+test_expect_success 'sparse-checkout completes subcommands' '
+ test_completion "git sparse-checkout " <<-\EOF
+ list Z
+ init Z
+ set Z
+ add Z
+ reapply Z
+ disable Z
+ EOF
+'
+
+test_expect_success 'cone mode sparse-checkout completes directory names' '
+ # initialize sparse-checkout definitions
+ git -C sparse-checkout sparse-checkout set --cone folder1/0 folder3 &&
+
+ # test tab completion
+ (
+ cd sparse-checkout &&
+ test_completion "git sparse-checkout set f" <<-\EOF
+ folder1/
+ folder2/
+ folder3/
+ EOF
+ ) &&
+
+ (
+ cd sparse-checkout &&
+ test_completion "git sparse-checkout set folder1/" <<-\EOF
+ folder1/0/
+ EOF
+ ) &&
+
+ (
+ cd sparse-checkout &&
+ test_completion "git sparse-checkout set folder1/0/" <<-\EOF
+ folder1/0/1/
+ EOF
+ ) &&
+
+ (
+ cd sparse-checkout/folder1 &&
+ test_completion "git sparse-checkout add 0" <<-\EOF
+ 0/
+ EOF
+ )
+'
+
+test_expect_success 'cone mode sparse-checkout completes directory names with spaces and accents' '
+ # reset sparse-checkout
+ git -C sparse-checkout sparse-checkout disable &&
+ (
+ cd sparse-checkout &&
+ mkdir "directory with spaces" &&
+ mkdir "directory-with-áccent" &&
+ >"directory with spaces/randomfile" &&
+ >"directory-with-áccent/randomfile" &&
+ git add . &&
+ git commit -m "Add directory with spaces and directory with accent" &&
+ git sparse-checkout set --cone "directory with spaces" \
+ "directory-with-áccent" &&
+ test_completion "git sparse-checkout add dir" <<-\EOF &&
+ directory with spaces/
+ directory-with-áccent/
+ EOF
+ rm -rf "directory with spaces" &&
+ rm -rf "directory-with-áccent" &&
+ git add . &&
+ git commit -m "Remove directory with spaces and directory with accent"
+ )
+'
+
+# use FUNNYNAMES to avoid running on Windows, which doesn't permit backslashes or tabs in paths
+test_expect_success FUNNYNAMES 'cone mode sparse-checkout completes directory names with backslashes and tabs' '
+ # reset sparse-checkout
+ git -C sparse-checkout sparse-checkout disable &&
+ (
+ cd sparse-checkout &&
+ mkdir "directory\with\backslashes" &&
+ mkdir "$(printf "directory\twith\ttabs")" &&
+ >"directory\with\backslashes/randomfile" &&
+ >"$(printf "directory\twith\ttabs")/randomfile" &&
+ git add . &&
+ git commit -m "Add directory with backslashes and directory with tabs" &&
+ git sparse-checkout set --cone "directory\with\backslashes" \
+ "$(printf "directory\twith\ttabs")" &&
+ test_completion "git sparse-checkout add dir" <<-\EOF &&
+ directory\with\backslashes/
+ directory with tabs/
+ EOF
+ rm -rf "directory\with\backslashes" &&
+ rm -rf "$(printf "directory\twith\ttabs")" &&
+ git add . &&
+ git commit -m "Remove directory with backslashes and directory with tabs"
+ )
+'
+
+test_expect_success 'non-cone mode sparse-checkout uses bash completion' '
+ # reset sparse-checkout repo to non-cone mode
+ git -C sparse-checkout sparse-checkout disable &&
+ git -C sparse-checkout sparse-checkout set --no-cone &&
+
+ (
+ cd sparse-checkout &&
+ # expected to be empty since we have not configured
+ # custom completion for non-cone mode
+ test_completion "git sparse-checkout set f" <<-\EOF
+
+ EOF
+ )
+'
+
+test_expect_success 'git sparse-checkout set --cone completes directory names' '
+ git -C sparse-checkout sparse-checkout disable &&
+
+ (
+ cd sparse-checkout &&
+ test_completion "git sparse-checkout set --cone f" <<-\EOF
+ folder1/
+ folder2/
+ folder3/
+ EOF
+ )
+'
+
test_expect_success 'git switch - with -d, complete all references' '
test_completion "git switch -d " <<-\EOF
HEAD Z
@@ -2396,27 +2534,33 @@ test_expect_success 'options with value' '
'
test_expect_success 'sourcing the completion script clears cached commands' '
- __git_compute_all_commands &&
- verbose test -n "$__git_all_commands" &&
- . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
- verbose test -z "$__git_all_commands"
+ (
+ __git_compute_all_commands &&
+ verbose test -n "$__git_all_commands" &&
+ . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
+ verbose test -z "$__git_all_commands"
+ )
'
test_expect_success 'sourcing the completion script clears cached merge strategies' '
- __git_compute_merge_strategies &&
- verbose test -n "$__git_merge_strategies" &&
- . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
- verbose test -z "$__git_merge_strategies"
+ (
+ __git_compute_merge_strategies &&
+ verbose test -n "$__git_merge_strategies" &&
+ . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
+ verbose test -z "$__git_merge_strategies"
+ )
'
test_expect_success 'sourcing the completion script clears cached --options' '
- __gitcomp_builtin checkout &&
- verbose test -n "$__gitcomp_builtin_checkout" &&
- __gitcomp_builtin notes_edit &&
- verbose test -n "$__gitcomp_builtin_notes_edit" &&
- . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
- verbose test -z "$__gitcomp_builtin_checkout" &&
- verbose test -z "$__gitcomp_builtin_notes_edit"
+ (
+ __gitcomp_builtin checkout &&
+ verbose test -n "$__gitcomp_builtin_checkout" &&
+ __gitcomp_builtin notes_edit &&
+ verbose test -n "$__gitcomp_builtin_notes_edit" &&
+ . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
+ verbose test -z "$__gitcomp_builtin_checkout" &&
+ verbose test -z "$__gitcomp_builtin_notes_edit"
+ )
'
test_expect_success 'option aliases are not shown by default' '
@@ -2424,12 +2568,45 @@ test_expect_success 'option aliases are not shown by default' '
'
test_expect_success 'option aliases are shown with GIT_COMPLETION_SHOW_ALL' '
- . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
- GIT_COMPLETION_SHOW_ALL=1 && export GIT_COMPLETION_SHOW_ALL &&
- test_completion "git clone --recurs" <<-\EOF
- --recurse-submodules Z
- --recursive Z
- EOF
+ (
+ . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
+ GIT_COMPLETION_SHOW_ALL=1 && export GIT_COMPLETION_SHOW_ALL &&
+ test_completion "git clone --recurs" <<-\EOF
+ --recurse-submodules Z
+ --recursive Z
+ EOF
+ )
+'
+
+test_expect_success 'plumbing commands are excluded without GIT_COMPLETION_SHOW_ALL_COMMANDS' '
+ (
+ . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
+ sane_unset GIT_TESTING_PORCELAIN_COMMAND_LIST &&
+
+ # Just mainporcelain, not plumbing commands
+ run_completion "git c" &&
+ grep checkout out &&
+ ! grep cat-file out
+ )
+'
+
+test_expect_success 'all commands are shown with GIT_COMPLETION_SHOW_ALL_COMMANDS (also main non-builtin)' '
+ (
+ . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" &&
+ GIT_COMPLETION_SHOW_ALL_COMMANDS=1 &&
+ export GIT_COMPLETION_SHOW_ALL_COMMANDS &&
+ sane_unset GIT_TESTING_PORCELAIN_COMMAND_LIST &&
+
+ # Both mainporcelain and plumbing commands
+ run_completion "git c" &&
+ grep checkout out &&
+ grep cat-file out &&
+
+ # Check "gitk", a "main" command, but not a built-in + more plumbing
+ run_completion "git g" &&
+ grep gitk out &&
+ grep get-tar-commit-id out
+ )
'
test_expect_success '__git_complete' '
diff --git a/tmp-objdir.c b/tmp-objdir.c
index 3d38eeab66..adf6033549 100644
--- a/tmp-objdir.c
+++ b/tmp-objdir.c
@@ -79,6 +79,11 @@ static void remove_tmp_objdir_on_signal(int signo)
raise(signo);
}
+void tmp_objdir_discard_objects(struct tmp_objdir *t)
+{
+ remove_dir_recursively(&t->path, REMOVE_DIR_KEEP_TOPLEVEL);
+}
+
/*
* These env_* functions are for setting up the child environment; the
* "replace" variant overrides the value of any existing variable with that
diff --git a/tmp-objdir.h b/tmp-objdir.h
index cda5ec7677..76efc7edee 100644
--- a/tmp-objdir.h
+++ b/tmp-objdir.h
@@ -47,6 +47,12 @@ int tmp_objdir_migrate(struct tmp_objdir *);
int tmp_objdir_destroy(struct tmp_objdir *);
/*
+ * Remove all objects from the temporary object directory, while leaving it
+ * around so more objects can be added.
+ */
+void tmp_objdir_discard_objects(struct tmp_objdir *);
+
+/*
* Add the temporary object directory as an alternate object store in the
* current process.
*/
diff --git a/transport.c b/transport.c
index 2a3e324154..253d6671b1 100644
--- a/transport.c
+++ b/transport.c
@@ -1292,7 +1292,7 @@ int transport_push(struct repository *r,
&transport_options);
trace2_region_leave("transport_push", "get_refs_list", r);
- strvec_clear(&transport_options.ref_prefixes);
+ transport_ls_refs_options_release(&transport_options);
if (flags & TRANSPORT_PUSH_ALL)
match_flags |= MATCH_REFS_ALL;
@@ -1420,6 +1420,12 @@ const struct ref *transport_get_remote_refs(struct transport *transport,
return transport->remote_refs;
}
+void transport_ls_refs_options_release(struct transport_ls_refs_options *opts)
+{
+ strvec_clear(&opts->ref_prefixes);
+ free((char *)opts->unborn_head_target);
+}
+
int transport_fetch_refs(struct transport *transport, struct ref *refs)
{
int rc;
diff --git a/transport.h b/transport.h
index 3f16e50c19..a0bc6a1e9e 100644
--- a/transport.h
+++ b/transport.h
@@ -257,15 +257,19 @@ struct transport_ls_refs_options {
/*
* If unborn_head_target is not NULL, and the remote reports HEAD as
* pointing to an unborn branch, transport_get_remote_refs() stores the
- * unborn branch in unborn_head_target. It should be freed by the
- * caller.
+ * unborn branch in unborn_head_target.
*/
- char *unborn_head_target;
+ const char *unborn_head_target;
};
#define TRANSPORT_LS_REFS_OPTIONS_INIT { \
.ref_prefixes = STRVEC_INIT, \
}
+/**
+ * Release the "struct transport_ls_refs_options".
+ */
+void transport_ls_refs_options_release(struct transport_ls_refs_options *opts);
+
/*
* Retrieve refs from a remote.
*/
diff --git a/worktree.c b/worktree.c
index 6f598dcfcd..e8f6f6ae6f 100644
--- a/worktree.c
+++ b/worktree.c
@@ -28,13 +28,11 @@ static void add_head_info(struct worktree *wt)
{
int flags;
const char *target;
- int ignore_errno;
target = refs_resolve_ref_unsafe(get_worktree_ref_store(wt),
"HEAD",
0,
- &wt->head_oid, &flags,
- &ignore_errno);
+ &wt->head_oid, &flags);
if (!target)
return;
@@ -416,7 +414,6 @@ const struct worktree *find_shared_symref(struct worktree **worktrees,
const char *symref_target;
struct ref_store *refs;
int flags;
- int ignore_errno;
if (wt->is_bare)
continue;
@@ -434,8 +431,7 @@ const struct worktree *find_shared_symref(struct worktree **worktrees,
refs = get_worktree_ref_store(wt);
symref_target = refs_resolve_ref_unsafe(refs, symref, 0,
- NULL, &flags,
- &ignore_errno);
+ NULL, &flags);
if ((flags & REF_ISSYMREF) &&
symref_target && !strcmp(symref_target, target)) {
existing = wt;
@@ -563,7 +559,6 @@ int other_head_refs(each_ref_fn fn, void *cb_data)
struct worktree *wt = *p;
struct object_id oid;
int flag;
- int ignore_errno;
if (wt->is_current)
continue;
@@ -573,7 +568,7 @@ int other_head_refs(each_ref_fn fn, void *cb_data)
if (refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
refname.buf,
RESOLVE_REF_READING,
- &oid, &flag, &ignore_errno))
+ &oid, &flag))
ret = fn(refname.buf, &oid, flag, cb_data);
if (ret)
break;
diff --git a/wrapper.c b/wrapper.c
index 36e12119d7..3258cdb171 100644
--- a/wrapper.c
+++ b/wrapper.c
@@ -463,8 +463,6 @@ int git_mkstemps_mode(char *pattern, int suffix_len, int mode)
static const int num_letters = ARRAY_SIZE(letters) - 1;
static const char x_pattern[] = "XXXXXX";
static const int num_x = ARRAY_SIZE(x_pattern) - 1;
- uint64_t value;
- struct timeval tv;
char *filename_template;
size_t len;
int fd, count;
@@ -485,12 +483,13 @@ int git_mkstemps_mode(char *pattern, int suffix_len, int mode)
* Replace pattern's XXXXXX characters with randomness.
* Try TMP_MAX different filenames.
*/
- gettimeofday(&tv, NULL);
- value = ((uint64_t)tv.tv_usec << 16) ^ tv.tv_sec ^ getpid();
filename_template = &pattern[len - num_x - suffix_len];
for (count = 0; count < TMP_MAX; ++count) {
- uint64_t v = value;
int i;
+ uint64_t v;
+ if (csprng_bytes(&v, sizeof(v)) < 0)
+ return error_errno("unable to get random bytes for temporary file");
+
/* Fill in the random bits. */
for (i = 0; i < num_x; i++) {
filename_template[i] = letters[v % num_letters];
@@ -506,12 +505,6 @@ int git_mkstemps_mode(char *pattern, int suffix_len, int mode)
*/
if (errno != EEXIST)
break;
- /*
- * This is a random value. It is only necessary that
- * the next TMP_MAX values generated by adding 7777 to
- * VALUE are different with (module 2^32).
- */
- value += 7777;
}
/* We return the null string if we can't find a unique file name. */
pattern[0] = '\0';
@@ -702,3 +695,69 @@ int open_nofollow(const char *path, int flags)
return open(path, flags);
#endif
}
+
+int csprng_bytes(void *buf, size_t len)
+{
+#if defined(HAVE_ARC4RANDOM) || defined(HAVE_ARC4RANDOM_LIBBSD)
+ /* This function never returns an error. */
+ arc4random_buf(buf, len);
+ return 0;
+#elif defined(HAVE_GETRANDOM)
+ ssize_t res;
+ char *p = buf;
+ while (len) {
+ res = getrandom(p, len, 0);
+ if (res < 0)
+ return -1;
+ len -= res;
+ p += res;
+ }
+ return 0;
+#elif defined(HAVE_GETENTROPY)
+ int res;
+ char *p = buf;
+ while (len) {
+ /* getentropy has a maximum size of 256 bytes. */
+ size_t chunk = len < 256 ? len : 256;
+ res = getentropy(p, chunk);
+ if (res < 0)
+ return -1;
+ len -= chunk;
+ p += chunk;
+ }
+ return 0;
+#elif defined(HAVE_RTLGENRANDOM)
+ if (!RtlGenRandom(buf, len))
+ return -1;
+ return 0;
+#elif defined(HAVE_OPENSSL_CSPRNG)
+ int res = RAND_bytes(buf, len);
+ if (res == 1)
+ return 0;
+ if (res == -1)
+ errno = ENOTSUP;
+ else
+ errno = EIO;
+ return -1;
+#else
+ ssize_t res;
+ char *p = buf;
+ int fd, err;
+ fd = open("/dev/urandom", O_RDONLY);
+ if (fd < 0)
+ return -1;
+ while (len) {
+ res = xread(fd, p, len);
+ if (res < 0) {
+ err = errno;
+ close(fd);
+ errno = err;
+ return -1;
+ }
+ len -= res;
+ p += res;
+ }
+ close(fd);
+ return 0;
+#endif
+}