aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/main.yml11
-rw-r--r--.gitlab-ci.yml44
-rw-r--r--CODE_OF_CONDUCT.md4
-rw-r--r--Documentation/CodingGuidelines4
-rw-r--r--Documentation/MyFirstContribution.txt5
-rw-r--r--Documentation/RelNotes/2.44.0.txt129
-rw-r--r--Documentation/SubmittingPatches33
-rw-r--r--Documentation/config/advice.txt162
-rw-r--r--Documentation/config/extensions.txt11
-rw-r--r--Documentation/config/fetch.txt6
-rw-r--r--Documentation/config/pack.txt16
-rw-r--r--Documentation/config/rebase.txt2
-rw-r--r--Documentation/fetch-options.txt5
-rw-r--r--Documentation/git-bisect.txt4
-rw-r--r--Documentation/git-branch.txt3
-rw-r--r--Documentation/git-clone.txt6
-rw-r--r--Documentation/git-init.txt7
-rw-r--r--Documentation/git-ls-files.txt23
-rw-r--r--Documentation/git-rebase.txt26
-rw-r--r--Documentation/git-rev-parse.txt3
-rw-r--r--Documentation/git.txt10
-rw-r--r--Documentation/gitattributes.txt24
-rw-r--r--Documentation/gitformat-pack.txt76
-rw-r--r--Documentation/glossary-content.txt14
-rw-r--r--Documentation/ref-storage-format.txt1
-rw-r--r--Documentation/rev-list-options.txt8
-rw-r--r--Documentation/technical/repository-version.txt5
-rw-r--r--Makefile19
-rw-r--r--README.md4
-rw-r--r--advice.c111
-rw-r--r--advice.h15
-rw-r--r--attr.c95
-rw-r--r--branch.c5
-rw-r--r--builtin/branch.c8
-rw-r--r--builtin/clone.c17
-rw-r--r--builtin/commit.c2
-rw-r--r--builtin/config.c20
-rw-r--r--builtin/fetch.c17
-rw-r--r--builtin/gc.c27
-rw-r--r--builtin/index-pack.c2
-rw-r--r--builtin/init-db.c16
-rw-r--r--builtin/merge.c27
-rw-r--r--builtin/pack-objects.c169
-rw-r--r--builtin/rebase.c2
-rw-r--r--builtin/rev-parse.c4
-rw-r--r--builtin/send-pack.c1
-rw-r--r--builtin/show-ref.c2
-rw-r--r--builtin/var.c2
-rw-r--r--builtin/worktree.c63
-rwxr-xr-xci/install-dependencies.sh10
-rwxr-xr-xci/install-docker-dependencies.sh7
-rwxr-xr-xci/lib.sh12
-rwxr-xr-xci/print-test-failures.sh2
-rwxr-xr-xci/run-build-and-minimal-fuzzers.sh19
-rw-r--r--commit-graph.c22
-rw-r--r--compat/mingw.c70
-rw-r--r--config.c26
-rw-r--r--config.h3
-rw-r--r--config.mak.uname13
-rw-r--r--contrib/completion/git-completion.bash26
-rw-r--r--contrib/completion/git-prompt.sh31
-rwxr-xr-xcontrib/subtree/git-subtree.sh30
-rwxr-xr-xcontrib/subtree/t/t7900-subtree.sh40
-rw-r--r--diff.c3
-rw-r--r--diffcore-delta.c4
-rw-r--r--ewah/bitmap.c9
-rw-r--r--ewah/ewok.h1
-rw-r--r--fetch-pack.c2
-rw-r--r--fsck.c133
-rw-r--r--git-compat-util.h9
-rwxr-xr-xgit-p4.py29
-rwxr-xr-xgitweb/gitweb.perl4
-rw-r--r--http-backend.c13
-rw-r--r--http-push.c2
-rw-r--r--merge-ll.c17
-rw-r--r--merge-ort.c21
-rw-r--r--midx.c151
-rw-r--r--midx.h12
-rw-r--r--neue0
-rw-r--r--oss-fuzz/dummy-cmd-main.c14
-rw-r--r--pack-bitmap-write.c9
-rw-r--r--pack-bitmap.c316
-rw-r--r--pack-bitmap.h19
-rw-r--r--pack-objects.c15
-rw-r--r--pack-objects.h1
-rw-r--r--pack-revindex.c50
-rw-r--r--pack-revindex.h3
-rw-r--r--parse-options.c21
-rw-r--r--path.c2
-rw-r--r--path.h2
-rw-r--r--pkt-line.c36
-rw-r--r--pkt-line.h4
-rw-r--r--refs.c75
-rw-r--r--refs.h7
-rw-r--r--refs/debug.c5
-rw-r--r--refs/files-backend.c38
-rw-r--r--refs/packed-backend.c2
-rw-r--r--refs/refs-internal.h5
-rw-r--r--reftable/block_test.c4
-rw-r--r--reftable/blocksource.c39
-rw-r--r--reftable/merged.c8
-rw-r--r--reftable/merged_test.c22
-rw-r--r--reftable/pq_test.c2
-rw-r--r--reftable/readwrite_test.c124
-rw-r--r--reftable/record.c17
-rw-r--r--reftable/record_test.c5
-rw-r--r--reftable/refname_test.c2
-rw-r--r--reftable/reftable-record.h11
-rw-r--r--reftable/reftable-writer.h1
-rw-r--r--reftable/stack.c253
-rw-r--r--reftable/stack.h3
-rw-r--r--reftable/stack_test.c27
-rw-r--r--reftable/test_framework.c5
-rw-r--r--reftable/test_framework.h2
-rw-r--r--reftable/writer.c12
-rw-r--r--reftable/writer.h1
-rw-r--r--remote-curl.c14
-rw-r--r--repository.c8
-rw-r--r--repository.h9
-rw-r--r--sequencer.c95
-rw-r--r--sequencer.h3
-rw-r--r--setup.c86
-rw-r--r--setup.h10
-rw-r--r--sideband.c5
-rw-r--r--strvec.h8
-rw-r--r--submodule-config.c140
-rw-r--r--submodule-config.h3
-rw-r--r--t/README3
-rw-r--r--t/helper/test-ctype.c70
-rw-r--r--t/helper/test-pkt-line.c59
-rw-r--r--t/helper/test-read-midx.c41
-rw-r--r--t/helper/test-submodule.c52
-rw-r--r--t/helper/test-tool.c1
-rw-r--r--t/helper/test-tool.h1
-rwxr-xr-xt/perf/p5332-multi-pack-reuse.sh81
-rwxr-xr-xt/t0001-init.sh70
-rwxr-xr-xt/t0003-attributes.sh76
-rwxr-xr-xt/t0018-advice.sh1
-rwxr-xr-xt/t0024-crlf-archive.sh13
-rwxr-xr-xt/t0035-safe-bare-repository.sh8
-rwxr-xr-xt/t0070-fundamental.sh62
-rwxr-xr-xt/t0600-reffiles-backend.sh384
-rwxr-xr-xt/t0601-reffiles-pack-refs.sh (renamed from t/t3210-pack-refs.sh)64
-rwxr-xr-xt/t1006-cat-file.sh36
-rwxr-xr-xt/t1401-symbolic-ref.sh5
-rwxr-xr-xt/t1403-show-ref.sh4
-rwxr-xr-xt/t1404-update-ref-errors.sh237
-rwxr-xr-xt/t1405-main-ref-store.sh10
-rwxr-xr-xt/t1407-worktree-ref-store.sh37
-rwxr-xr-xt/t1410-reflog.sh42
-rwxr-xr-xt/t1414-reflog-walk.sh11
-rwxr-xr-xt/t1415-worktree-refs.sh11
-rwxr-xr-xt/t1500-rev-parse.sh17
-rwxr-xr-xt/t1503-rev-parse-verify.sh5
-rwxr-xr-xt/t2017-checkout-orphan.sh2
-rwxr-xr-xt/t2400-worktree-add.sh6
-rwxr-xr-xt/t3200-branch.sh2
-rwxr-xr-xt/t3903-stash.sh12
-rwxr-xr-xt/t4001-diff-rename.sh24
-rwxr-xr-xt/t4013-diff-various.sh6
-rwxr-xr-xt/t4053-diff-no-index.sh12
-rwxr-xr-xt/t4129-apply-samemode.sh6
-rwxr-xr-xt/t4202-log.sh17
-rwxr-xr-xt/t5003-archive-zip.sh34
-rwxr-xr-xt/t5312-prune-corruption.sh26
-rwxr-xr-xt/t5319-multi-pack-index.sh35
-rwxr-xr-xt/t5332-multi-pack-reuse.sh203
-rwxr-xr-xt/t5510-fetch.sh3
-rwxr-xr-xt/t5514-fetch-multiple.sh161
-rwxr-xr-xt/t5541-http-push-smart.sh18
-rwxr-xr-xt/t5551-http-fetch-smart.sh18
-rwxr-xr-xt/t5601-clone.sh17
-rwxr-xr-xt/t6113-rev-list-bitmap-filters.sh2
-rwxr-xr-xt/t6135-pathspec-with-attrs.sh27
-rwxr-xr-xt/t6406-merge-attr.sh16
-rwxr-xr-xt/t7450-bad-git-dotfiles.sh26
-rwxr-xr-xt/t7501-commit-basic-functionality.sh98
-rwxr-xr-xt/t7527-builtin-fsmonitor.sh2
-rwxr-xr-xt/t7900-maintenance.sh45
-rwxr-xr-xt/t9500-gitweb-standalone-no-errors.sh5
-rwxr-xr-xt/t9902-completion.sh47
-rw-r--r--t/test-lib-functions.sh19
-rw-r--r--t/test-lib-github-workflow-markup.sh4
-rw-r--r--t/test-lib.sh15
-rw-r--r--t/unit-tests/t-ctype.c80
-rw-r--r--transport-helper.c32
-rw-r--r--transport.c1
-rw-r--r--worktree.c54
-rw-r--r--worktree.h12
-rw-r--r--write-or-die.c19
190 files changed, 4452 insertions, 1589 deletions
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 9fdbd54028..4d97da57ec 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -309,6 +309,17 @@ jobs:
with:
name: failed-tests-${{matrix.vector.jobname}}
path: ${{env.FAILED_TEST_ARTIFACTS}}
+ fuzz-smoke-test:
+ name: fuzz smoke test
+ needs: ci-config
+ if: needs.ci-config.outputs.enabled == 'yes'
+ env:
+ CC: clang
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - run: ci/install-dependencies.sh
+ - run: ci/run-build-and-minimal-fuzzers.sh
dockerized:
name: ${{matrix.vector.jobname}} (${{matrix.vector.image}})
needs: ci-config
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index cd98bcb18a..43bfbd8834 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -7,7 +7,7 @@ workflow:
- if: $CI_COMMIT_TAG
- if: $CI_COMMIT_REF_PROTECTED == "true"
-test:
+test:linux:
image: $image
before_script:
- ./ci/install-docker-dependencies.sh
@@ -51,3 +51,45 @@ test:
paths:
- t/failed-test-artifacts
when: on_failure
+
+test:osx:
+ image: $image
+ tags:
+ - saas-macos-medium-m1
+ variables:
+ TEST_OUTPUT_DIRECTORY: "/Volumes/RAMDisk"
+ before_script:
+ # Create a 4GB RAM disk that we use to store test output on. This small hack
+ # significantly speeds up tests by more than a factor of 2 because the
+ # macOS runners use network-attached storage as disks, which is _really_
+ # slow with the many small writes that our tests do.
+ - sudo diskutil apfs create $(hdiutil attach -nomount ram://8192000) RAMDisk
+ - ./ci/install-dependencies.sh
+ script:
+ - ./ci/run-build-and-tests.sh
+ after_script:
+ - |
+ if test "$CI_JOB_STATUS" != 'success'
+ then
+ ./ci/print-test-failures.sh
+ mv "$TEST_OUTPUT_DIRECTORY"/failed-test-artifacts t/
+ fi
+ parallel:
+ matrix:
+ - jobname: osx-clang
+ image: macos-13-xcode-14
+ CC: clang
+ artifacts:
+ paths:
+ - t/failed-test-artifacts
+ when: on_failure
+
+static-analysis:
+ image: ubuntu:22.04
+ variables:
+ jobname: StaticAnalysis
+ before_script:
+ - ./ci/install-docker-dependencies.sh
+ script:
+ - ./ci/run-static-analysis.sh
+ - ./ci/check-directional-formatting.bash
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 0215b1fd4c..e58917c50a 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -130,11 +130,11 @@ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
-Community Impact Guidelines were inspired by
+Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
-[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
+[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
at [https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
diff --git a/Documentation/CodingGuidelines b/Documentation/CodingGuidelines
index 8ed517a5ca..578587a471 100644
--- a/Documentation/CodingGuidelines
+++ b/Documentation/CodingGuidelines
@@ -450,7 +450,7 @@ For C programs:
one of the approved headers that includes it first for you. (The
approved headers currently include "builtin.h",
"t/helper/test-tool.h", "xdiff/xinclude.h", or
- "reftable/system.h"). You do not have to include more than one of
+ "reftable/system.h".) You do not have to include more than one of
these.
- A C file must directly include the header files that declare the
@@ -578,7 +578,7 @@ Externally Visible Names
. The variable name describes the effect of tweaking this knob.
The section and variable names that consist of multiple words are
- formed by concatenating the words without punctuations (e.g. `-`),
+ formed by concatenating the words without punctuation marks (e.g. `-`),
and are broken using bumpyCaps in documentation as a hint to the
reader.
diff --git a/Documentation/MyFirstContribution.txt b/Documentation/MyFirstContribution.txt
index 279f6a3e7c..f06563e981 100644
--- a/Documentation/MyFirstContribution.txt
+++ b/Documentation/MyFirstContribution.txt
@@ -35,8 +35,9 @@ announcements, design discussions, and more take place. Those interested in
contributing are welcome to post questions here. The Git list requires
plain-text-only emails and prefers inline and bottom-posting when replying to
mail; you will be CC'd in all replies to you. Optionally, you can subscribe to
-the list by sending an email to majordomo@vger.kernel.org with "subscribe git"
-in the body. The https://lore.kernel.org/git[archive] of this mailing list is
+the list by sending an email to <git+subscribe@vger.kernel.org>
+(see https://subspace.kernel.org/subscribing.html for details).
+The https://lore.kernel.org/git[archive] of this mailing list is
available to view in a browser.
==== https://groups.google.com/forum/#!forum/git-mentoring[git-mentoring@googlegroups.com]
diff --git a/Documentation/RelNotes/2.44.0.txt b/Documentation/RelNotes/2.44.0.txt
index e58095fc8d..7d3b75e796 100644
--- a/Documentation/RelNotes/2.44.0.txt
+++ b/Documentation/RelNotes/2.44.0.txt
@@ -32,7 +32,54 @@ UI, Workflows & Features
breaking change. "--ignore-other-worktrees" option is required to
unbreak you, if you are used to the current behaviour that "-B"
overrides the safety.
- (merge b23285a921 jc/checkout-B-branch-in-use later to maint).
+
+ * The builtin_objectmode attribute is populated for each path
+ without adding anything in .gitattributes files, which would be
+ useful in magic pathspec, e.g., ":(attr:builtin_objectmode=100755)"
+ to limit to executables.
+
+ * "git fetch" learned to pay attention to "fetch.all" configuration
+ variable, which pretends as if "--all" was passed from the command
+ line when no remote parameter was given.
+
+ * In addition to (rather cryptic) Security Identifiers, show username
+ and domain in the error message when we barf on mismatch between
+ the Git directory and the current user on Windows.
+
+ * The error message given when "git branch -d branch" fails due to
+ commits unique to the branch has been split into an error and a new
+ conditional advice message.
+
+ * When given an existing but unreadable file as a configuration file,
+ gitweb behaved as if the file did not exist at all, but now it
+ errors out. This is a change that may break backward compatibility.
+
+ * When $HOME/.gitignore is missing but XDG config file available, we
+ should write into the latter, not former. "git gc" and "git
+ maintenance" wrote into a wrong "global config" file, which have
+ been corrected.
+
+ * Define "special ref" as a very narrow set that consists of
+ FETCH_HEAD and MERGE_HEAD, and clarify everything else that used to
+ be classified as such are actually just pseudorefs.
+
+ * All conditional "advice" messages show how to turn them off, which
+ becomes repetitive. Setting advice.* configuration explicitly on
+ now omits the instruction part.
+
+ * The "disable repository discovery of a bare repository" check,
+ triggered by setting safe.bareRepository configuration variable to
+ 'explicit', has been loosened to exclude the ".git/" directory inside
+ a non-bare repository from the check. So you can do "cd .git &&
+ git cmd" to run a Git command that works on a bare repository without
+ explicitly specifying $GIT_DIR now.
+
+ * The completion script (in contrib/) learned more options that can
+ be used with "git log".
+
+ * The labels on conflict markers for the common ancestor, our version,
+ and the other version are available to custom 3-way merge driver
+ via %S, %X, and %Y placeholders.
Performance, Internal Implementation, Development Support etc.
@@ -64,6 +111,29 @@ Performance, Internal Implementation, Development Support etc.
non-default hash function into a repository that uses the reftable
backend.
+ * Streaming spans of packfile data used to be done only from a
+ single, primary, pack in a repository with multiple packfiles. It
+ has been extended to allow reuse from other packfiles, too.
+
+ * Comment updates to help developers not to attempt to modify
+ messages from plumbing commands that must stay constant.
+
+ It might make sense to reassess the plumbing needs every few years,
+ but that should be done as a separate effort.
+
+ * Move test-ctype helper to the unit-test framework.
+
+ * Instead of manually creating refs/ hierarchy on disk upon a
+ creation of a secondary worktree, which is only usable via the
+ files backend, use the refs API to populate it.
+
+ * CI for GitLab learned to drive macOS jobs.
+
+ * A few tests to "git commit -o <pathspec>" and "git commit -i
+ <pathspec>" has been added.
+
+ * Tests on ref API are moved around to prepare for reftable.
+
Fixes since v2.43
-----------------
@@ -154,6 +224,47 @@ Fixes since v2.43
* "git sparse-checkout (add|set) --[no-]cone --end-of-options" did
not handle "--end-of-options" correctly after a recent update.
+ * Unlike other environment variables that took the usual
+ true/false/yes/no as well as 0/1, GIT_FLUSH only understood 0/1,
+ which has been corrected.
+ (merge 556e68032f cp/git-flush-is-an-env-bool later to maint).
+
+ * Clearing in-core repository (happens during e.g., "git fetch
+ --recurse-submodules" with commit graph enabled) made in-core
+ commit object in an inconsistent state by discarding the necessary
+ data from commit-graph too early, which has been corrected.
+ (merge d70f554cdf jk/commit-graph-slab-clear-fix later to maint).
+
+ * Update to a new feature recently added, "git show-ref --exists".
+ (merge 0aabeaa562 tc/show-ref-exists-fix later to maint).
+
+ * oss-fuzz tests are built and run in CI.
+ (merge c4a9cf1df3 js/oss-fuzz-build-in-ci later to maint).
+
+ * Rename detection logic ignored the final line of a file if it is an
+ incomplete line.
+ (merge 1c5bc6971e en/diffcore-delta-final-line-fix later to maint).
+
+ * GitHub CI update.
+ (merge 0188b2c8e0 pb/ci-github-skip-logs-for-broken-tests later to maint).
+
+ * "git diff --no-rename A B" did not disable rename detection but did
+ not trigger an error from the command line parser.
+ (merge 457f96252f rs/parse-options-with-keep-unknown-abbrev-fix later to maint).
+
+ * "git archive --remote=<remote>" learned to talk over the smart
+ http (aka stateless) transport.
+ (merge 176cd68634 jx/remote-archive-over-smart-http later to maint).
+
+ * Fetching via protocol v0 over Smart HTTP transport sometimes failed
+ to correctly auto-follow tags.
+ (merge fba732c462 jk/fetch-auto-tag-following-fix later to maint).
+
+ * The documentation for the --exclude-per-directory option marked it
+ as deprecated, which confused readers into thinking there may be a
+ plan to remove it in the future, which was not our intention.
+ (merge 0009542cab jc/ls-files-doc-update later to maint).
+
* Other code cleanup, docfix, build fix, etc.
(merge 50f1abcff6 js/packfile-h-typofix later to maint).
(merge cbf498eb53 jb/reflog-expire-delete-dry-run-options later to maint).
@@ -178,3 +289,19 @@ Fixes since v2.43
(merge 63956c553d ml/doc-merge-updates later to maint).
(merge d57c671a51 en/header-cleanup later to maint).
(merge 5b7eec4bc5 rs/fast-import-simplify-mempool-allocation later to maint).
+ (merge 291873e5d6 js/contributor-docs-updates later to maint).
+ (merge 54d8a2531b jk/t1006-cat-file-objectsize-disk later to maint).
+ (merge 7033d5479b jx/sideband-chomp-newline-fix later to maint).
+ (merge 9cd30af991 ms/rebase-insnformat-doc-fix later to maint).
+ (merge 03bcc93769 cp/sideband-array-index-comment-fix later to maint).
+ (merge 993d38a066 jk/index-pack-lsan-false-positive-fix later to maint).
+ (merge 25aec06326 ib/rebase-reschedule-doc later to maint).
+ (merge 5aea3955bc rj/clarify-branch-doc-m later to maint).
+ (merge 9cce3be2df bk/bisect-doc-fix later to maint).
+ (merge 8f50984cf4 ne/doc-filter-blob-limit-fix later to maint).
+ (merge f10b0989b8 la/strvec-comment-fix later to maint).
+ (merge 8430b438f6 vd/fsck-submodule-url-test later to maint).
+ (merge f10031fadd nb/rebase-x-shell-docfix later to maint).
+ (merge af3d2c160f jc/majordomo-to-subspace later to maint).
+ (merge ee9895b0ff sd/negotiate-trace-fix later to maint).
+ (merge 976d0251ce jc/coc-whitespace-fix later to maint).
diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
index bce7f97815..e734a3f0f1 100644
--- a/Documentation/SubmittingPatches
+++ b/Documentation/SubmittingPatches
@@ -355,9 +355,21 @@ If you like, you can put extra tags at the end:
patch after a detailed analysis.
. `Tested-by:` is used to indicate that the person applied the patch
and found it to have the desired effect.
-
-You can also create your own tag or use one that's in common usage
-such as "Thanks-to:", "Based-on-patch-by:", or "Mentored-by:".
+. `Co-authored-by:` is used to indicate that people exchanged drafts
+ of a patch before submitting it.
+. `Helped-by:` is used to credit someone who suggested ideas for
+ changes without providing the precise changes in patch form.
+. `Mentored-by:` is used to credit someone with helping develop a
+ patch as part of a mentorship program (e.g., GSoC or Outreachy).
+. `Suggested-by:` is used to credit someone with suggesting the idea
+ for a patch.
+
+While you can also create your own trailer if the situation warrants it, we
+encourage you to instead use one of the common trailers in this project
+highlighted above.
+
+Only capitalize the very first letter of tags, i.e. favor
+"Signed-off-by" over "Signed-Off-By" and "Acked-by:" over "Acked-By".
[[git-tools]]
=== Generate your patch using Git tools out of your commits.
@@ -570,7 +582,7 @@ their trees themselves.
master).
* Read the Git mailing list, the maintainer regularly posts messages
- entitled "What's cooking in git.git" and "What's in git.git" giving
+ entitled "What's cooking in git.git" giving
the status of various proposed changes.
== GitHub CI[[GHCI]]
@@ -590,11 +602,12 @@ After the initial setup, CI will run whenever you push new changes
to your fork of Git on GitHub. You can monitor the test state of all your
branches here: `https://github.com/<Your GitHub handle>/git/actions/workflows/main.yml`
-If a branch did not pass all test cases then it is marked with a red
-cross. In that case you can click on the failing job and navigate to
-"ci/run-build-and-tests.sh" and/or "ci/print-test-failures.sh". You
-can also download "Artifacts" which are tarred (or zipped) archives
-with test data relevant for debugging.
+If a branch does not pass all test cases then it will be marked with a
+red +x+, instead of a green check. In that case, you can click on the
+failing job and navigate to "ci/run-build-and-tests.sh" and/or
+"ci/print-test-failures.sh". You can also download "Artifacts" which
+are zip archives containing tarred (or zipped) archives with test data
+relevant for debugging.
Then fix the problem and push your fix to your GitHub fork. This will
trigger a new CI build to ensure all tests pass.
@@ -686,7 +699,7 @@ message to an external program, and this is a handy way to drive
`git am`. However, if the message is MIME encoded, what is
piped into the program is the representation you see in your
`*Article*` buffer after unwrapping MIME. This is often not what
-you would want for two reasons. It tends to screw up non ASCII
+you would want for two reasons. It tends to screw up non-ASCII
characters (most notably in people's names), and also
whitespaces (fatal in patches). Running "C-u g" to display the
message in raw form before using "|" to run the pipe can work
diff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt
index 4d7e5d8759..c7ea70f2e2 100644
--- a/Documentation/config/advice.txt
+++ b/Documentation/config/advice.txt
@@ -1,30 +1,63 @@
advice.*::
These variables control various optional help messages designed to
- aid new users. All 'advice.*' variables default to 'true', and you
- can tell Git that you do not need help by setting these to 'false':
+ aid new users. When left unconfigured, Git will give the message
+ alongside instructions on how to squelch it. You can tell Git
+ that you do not need the help message by setting these to 'false':
+
--
+ addEmbeddedRepo::
+ Advice on what to do when you've accidentally added one
+ git repo inside of another.
+ addEmptyPathspec::
+ Advice shown if a user runs the add command without providing
+ the pathspec parameter.
+ addIgnoredFile::
+ Advice shown if a user attempts to add an ignored file to
+ the index.
+ amWorkDir::
+ Advice that shows the location of the patch file when
+ linkgit:git-am[1] fails to apply it.
ambiguousFetchRefspec::
Advice shown when a fetch refspec for multiple remotes maps to
the same remote-tracking branch namespace and causes branch
tracking set-up to fail.
+ checkoutAmbiguousRemoteBranchName::
+ Advice shown when the argument to
+ linkgit:git-checkout[1] and linkgit:git-switch[1]
+ ambiguously resolves to a
+ remote tracking branch on more than one remote in
+ situations where an unambiguous argument would have
+ otherwise caused a remote-tracking branch to be
+ checked out. See the `checkout.defaultRemote`
+ configuration variable for how to set a given remote
+ to be used by default in some situations where this
+ advice would be printed.
+ commitBeforeMerge::
+ Advice shown when linkgit:git-merge[1] refuses to
+ merge to avoid overwriting local changes.
+ detachedHead::
+ Advice shown when you used
+ linkgit:git-switch[1] or linkgit:git-checkout[1]
+ to move to the detached HEAD state, to instruct how to
+ create a local branch after the fact.
+ diverging::
+ Advice shown when a fast-forward is not possible.
fetchShowForcedUpdates::
Advice shown when linkgit:git-fetch[1] takes a long time
to calculate forced updates after ref updates, or to warn
that the check is disabled.
- pushUpdateRejected::
- Set this variable to 'false' if you want to disable
- 'pushNonFFCurrent', 'pushNonFFMatching', 'pushAlreadyExists',
- 'pushFetchFirst', 'pushNeedsForce', and 'pushRefNeedsUpdate'
- simultaneously.
- pushNonFFCurrent::
- Advice shown when linkgit:git-push[1] fails due to a
- non-fast-forward update to the current branch.
- pushNonFFMatching::
- Advice shown when you ran linkgit:git-push[1] and pushed
- 'matching refs' explicitly (i.e. you used ':', or
- specified a refspec that isn't your current branch) and
- it resulted in a non-fast-forward error.
+ forceDeleteBranch::
+ Advice shown when a user tries to delete a not fully merged
+ branch without the force option set.
+ ignoredHook::
+ Advice shown if a hook is ignored because the hook is not
+ set as executable.
+ implicitIdentity::
+ Advice on how to set your identity configuration when
+ your information is guessed from the system username and
+ domain name.
+ nestedTag::
+ Advice shown if a user attempts to recursively tag a tag object.
pushAlreadyExists::
Shown when linkgit:git-push[1] rejects an update that
does not qualify for fast-forwarding (e.g., a tag.)
@@ -37,6 +70,18 @@ advice.*::
tries to overwrite a remote ref that points at an
object that is not a commit-ish, or make the remote
ref point at an object that is not a commit-ish.
+ pushNonFFCurrent::
+ Advice shown when linkgit:git-push[1] fails due to a
+ non-fast-forward update to the current branch.
+ pushNonFFMatching::
+ Advice shown when you ran linkgit:git-push[1] and pushed
+ 'matching refs' explicitly (i.e. you used ':', or
+ specified a refspec that isn't your current branch) and
+ it resulted in a non-fast-forward error.
+ pushRefNeedsUpdate::
+ Shown when linkgit:git-push[1] rejects a forced update of
+ a branch when its remote-tracking ref has updates that we
+ do not have locally.
pushUnqualifiedRefname::
Shown when linkgit:git-push[1] gives up trying to
guess based on the source and destination refs what
@@ -44,10 +89,23 @@ advice.*::
we can still suggest that the user push to either
refs/heads/* or refs/tags/* based on the type of the
source object.
- pushRefNeedsUpdate::
- Shown when linkgit:git-push[1] rejects a forced update of
- a branch when its remote-tracking ref has updates that we
- do not have locally.
+ pushUpdateRejected::
+ Set this variable to 'false' if you want to disable
+ 'pushNonFFCurrent', 'pushNonFFMatching', 'pushAlreadyExists',
+ 'pushFetchFirst', 'pushNeedsForce', and 'pushRefNeedsUpdate'
+ simultaneously.
+ resetNoRefresh::
+ Advice to consider using the `--no-refresh` option to
+ linkgit:git-reset[1] when the command takes more than 2 seconds
+ to refresh the index after reset.
+ resolveConflict::
+ Advice shown by various commands when conflicts
+ prevent the operation from being performed.
+ rmHints::
+ In case of failure in the output of linkgit:git-rm[1],
+ show directions on how to proceed from the current state.
+ sequencerInUse::
+ Advice shown when a sequencer command is already in progress.
skippedCherryPicks::
Shown when linkgit:git-rebase[1] skips a commit that has already
been cherry-picked onto the upstream branch.
@@ -68,76 +126,22 @@ advice.*::
Advise to consider using the `-u` option to linkgit:git-status[1]
when the command takes more than 2 seconds to enumerate untracked
files.
- commitBeforeMerge::
- Advice shown when linkgit:git-merge[1] refuses to
- merge to avoid overwriting local changes.
- resetNoRefresh::
- Advice to consider using the `--no-refresh` option to
- linkgit:git-reset[1] when the command takes more than 2 seconds
- to refresh the index after reset.
- resolveConflict::
- Advice shown by various commands when conflicts
- prevent the operation from being performed.
- sequencerInUse::
- Advice shown when a sequencer command is already in progress.
- implicitIdentity::
- Advice on how to set your identity configuration when
- your information is guessed from the system username and
- domain name.
- detachedHead::
- Advice shown when you used
- linkgit:git-switch[1] or linkgit:git-checkout[1]
- to move to the detached HEAD state, to instruct how to
- create a local branch after the fact.
- suggestDetachingHead::
- Advice shown when linkgit:git-switch[1] refuses to detach HEAD
- without the explicit `--detach` option.
- checkoutAmbiguousRemoteBranchName::
- Advice shown when the argument to
- linkgit:git-checkout[1] and linkgit:git-switch[1]
- ambiguously resolves to a
- remote tracking branch on more than one remote in
- situations where an unambiguous argument would have
- otherwise caused a remote-tracking branch to be
- checked out. See the `checkout.defaultRemote`
- configuration variable for how to set a given remote
- to be used by default in some situations where this
- advice would be printed.
- amWorkDir::
- Advice that shows the location of the patch file when
- linkgit:git-am[1] fails to apply it.
- rmHints::
- In case of failure in the output of linkgit:git-rm[1],
- show directions on how to proceed from the current state.
- addEmbeddedRepo::
- Advice on what to do when you've accidentally added one
- git repo inside of another.
- ignoredHook::
- Advice shown if a hook is ignored because the hook is not
- set as executable.
- waitingForEditor::
- Print a message to the terminal whenever Git is waiting for
- editor input from the user.
- nestedTag::
- Advice shown if a user attempts to recursively tag a tag object.
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.
- addEmptyPathspec::
- Advice shown if a user runs the add command without providing
- the pathspec parameter.
+ suggestDetachingHead::
+ Advice shown when linkgit:git-switch[1] refuses to detach HEAD
+ without the explicit `--detach` option.
updateSparsePath::
Advice shown when either linkgit:git-add[1] or linkgit:git-rm[1]
is asked to update index entries outside the current sparse
checkout.
- diverging::
- Advice shown when a fast-forward is not possible.
+ waitingForEditor::
+ Print a message to the terminal whenever Git is waiting for
+ editor input from the user.
worktreeAddOrphan::
Advice shown when a user tries to create a worktree from an
invalid reference, to instruct how to create a new unborn
diff --git a/Documentation/config/extensions.txt b/Documentation/config/extensions.txt
index bccaec7a96..66db0e15da 100644
--- a/Documentation/config/extensions.txt
+++ b/Documentation/config/extensions.txt
@@ -7,6 +7,17 @@ Note that this setting should only be set by linkgit:git-init[1] or
linkgit:git-clone[1]. Trying to change it after initialization will not
work and will produce hard-to-diagnose issues.
+extensions.refStorage::
+ Specify the ref storage format to use. The acceptable values are:
++
+include::../ref-storage-format.txt[]
++
+It is an error to specify this key unless `core.repositoryFormatVersion` is 1.
++
+Note that this setting should only be set by linkgit:git-init[1] or
+linkgit:git-clone[1]. Trying to change it after initialization will not
+work and will produce hard-to-diagnose issues.
+
extensions.worktreeConfig::
If enabled, then worktrees will load config settings from the
`$GIT_DIR/config.worktree` file in addition to the
diff --git a/Documentation/config/fetch.txt b/Documentation/config/fetch.txt
index aea5b97477..d7dc461bd1 100644
--- a/Documentation/config/fetch.txt
+++ b/Documentation/config/fetch.txt
@@ -50,6 +50,12 @@ fetch.pruneTags::
refs. See also `remote.<name>.pruneTags` and the PRUNING
section of linkgit:git-fetch[1].
+fetch.all::
+ If true, fetch will attempt to update all available remotes.
+ This behavior can be overridden by passing `--no-all` or by
+ explicitly specifying one or more remote(s) to fetch from.
+ Defaults to false.
+
fetch.output::
Control how ref update status is printed. Valid values are
`full` and `compact`. Default value is `full`. See the
diff --git a/Documentation/config/pack.txt b/Documentation/config/pack.txt
index f50df9dbce..9c630863e6 100644
--- a/Documentation/config/pack.txt
+++ b/Documentation/config/pack.txt
@@ -28,11 +28,17 @@ all existing objects. You can force recompression by passing the -F option
to linkgit:git-repack[1].
pack.allowPackReuse::
- When true, and when reachability bitmaps are enabled,
- pack-objects will try to send parts of the bitmapped packfile
- verbatim. This can reduce memory and CPU usage to serve fetches,
- but might result in sending a slightly larger pack. Defaults to
- true.
+ When true or "single", and when reachability bitmaps are
+ enabled, pack-objects will try to send parts of the bitmapped
+ packfile verbatim. When "multi", and when a multi-pack
+ reachability bitmap is available, pack-objects will try to send
+ parts of all packs in the MIDX.
++
+ If only a single pack bitmap is available, and
+ `pack.allowPackReuse` is set to "multi", reuse parts of just the
+ bitmapped packfile. This can reduce memory and CPU usage to
+ serve fetches, but might result in sending a slightly larger
+ pack. Defaults to true.
pack.island::
An extended regular expression configuring a set of delta
diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt
index d59576dbb2..c6187ab28b 100644
--- a/Documentation/config/rebase.txt
+++ b/Documentation/config/rebase.txt
@@ -40,7 +40,7 @@ rebase.missingCommitsCheck::
rebase.instructionFormat::
A format string, as specified in linkgit:git-log[1], to be used for the
todo list during an interactive rebase. The format will
- automatically have the long commit hash prepended to the format.
+ automatically have the commit hash prepended to the format.
rebase.abbreviateCommands::
If set to true, `git rebase` will use abbreviated command names in the
diff --git a/Documentation/fetch-options.txt b/Documentation/fetch-options.txt
index a1d6633a4f..54ebb4452e 100644
--- a/Documentation/fetch-options.txt
+++ b/Documentation/fetch-options.txt
@@ -1,5 +1,6 @@
---all::
- Fetch all remotes.
+--[no-]all::
+ Fetch all remotes. This overrides the configuration variable
+ `fetch.all`.
-a::
--append::
diff --git a/Documentation/git-bisect.txt b/Documentation/git-bisect.txt
index aa02e46224..8e01f1d618 100644
--- a/Documentation/git-bisect.txt
+++ b/Documentation/git-bisect.txt
@@ -17,7 +17,7 @@ The command takes various subcommands, and different options depending
on the subcommand:
git bisect start [--term-(new|bad)=<term-new> --term-(old|good)=<term-old>]
- [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<paths>...]
+ [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<pathspec>...]
git bisect (bad|new|<term-new>) [<rev>]
git bisect (good|old|<term-old>) [<rev>...]
git bisect terms [--term-good | --term-bad]
@@ -299,7 +299,7 @@ Cutting down bisection by giving more parameters to bisect start
You can further cut down the number of trials, if you know what part of
the tree is involved in the problem you are tracking down, by specifying
-path parameters when issuing the `bisect start` command:
+pathspec parameters when issuing the `bisect start` command:
------------
$ git bisect start -- arch/i386 include/asm-i386
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 4395aa9354..0b08442932 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -312,7 +312,8 @@ superproject's "origin/main", but tracks the submodule's "origin/main".
option is omitted, the current HEAD will be used instead.
<oldbranch>::
- The name of an existing branch to rename.
+ The name of an existing branch. If this option is omitted,
+ the name of the current branch will be used instead.
<newbranch>::
The new name for an existing branch. The same restrictions as for
diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index c37c4a37f7..6e43eb9c20 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -311,6 +311,12 @@ or `--mirror` is given)
The result is Git repository can be separated from working
tree.
+--ref-format=<ref-format::
+
+Specify the given ref storage format for the repository. The valid values are:
++
+include::ref-storage-format.txt[]
+
-j <n>::
--jobs <n>::
The number of submodules fetched at the same time.
diff --git a/Documentation/git-init.txt b/Documentation/git-init.txt
index 6f0d2973bf..e8dc645bb5 100644
--- a/Documentation/git-init.txt
+++ b/Documentation/git-init.txt
@@ -11,6 +11,7 @@ SYNOPSIS
[verse]
'git init' [-q | --quiet] [--bare] [--template=<template-directory>]
[--separate-git-dir <git-dir>] [--object-format=<format>]
+ [--ref-format=<format>]
[-b <branch-name> | --initial-branch=<branch-name>]
[--shared[=<permissions>]] [<directory>]
@@ -57,6 +58,12 @@ values are 'sha1' and (if enabled) 'sha256'. 'sha1' is the default.
+
include::object-format-disclaimer.txt[]
+--ref-format=<format>::
+
+Specify the given ref storage format for the repository. The valid values are:
++
+include::ref-storage-format.txt[]
+
--template=<template-directory>::
Specify the directory from which templates will be used. (See the "TEMPLATE
diff --git a/Documentation/git-ls-files.txt b/Documentation/git-ls-files.txt
index f65a8cd91d..d08c7da8f4 100644
--- a/Documentation/git-ls-files.txt
+++ b/Documentation/git-ls-files.txt
@@ -119,8 +119,10 @@ OPTIONS
--exclude-per-directory=<file>::
Read additional exclude patterns that apply only to the
- directory and its subdirectories in <file>. Deprecated; use
- --exclude-standard instead.
+ directory and its subdirectories in <file>. If you are
+ trying to emulate the way Porcelain commands work, using
+ the `--exclude-standard` option instead is easier and more
+ thorough.
--exclude-standard::
Add the standard Git exclusions: .git/info/exclude, .gitignore
@@ -298,9 +300,8 @@ traversing the directory tree and finding files to show when the
flags --others or --ignored are specified. linkgit:gitignore[5]
specifies the format of exclude patterns.
-Generally, you should just use --exclude-standard, but for historical
-reasons the exclude patterns can be specified from the following
-places, in order:
+These exclude patterns can be specified from the following places,
+in order:
1. The command-line flag --exclude=<pattern> specifies a
single pattern. Patterns are ordered in the same order
@@ -322,6 +323,18 @@ top of the directory tree. A pattern read from a file specified
by --exclude-per-directory is relative to the directory that the
pattern file appears in.
+Generally, you should be able to use `--exclude-standard` when you
+want the exclude rules applied the same way as what Porcelain
+commands do. To emulate what `--exclude-standard` specifies, you
+can give `--exclude-per-directory=.gitignore`, and then specify:
+
+ 1. The file specified by the `core.excludesfile` configuration
+ variable, if exists, or the `$XDG_CONFIG_HOME/git/ignore` file.
+
+ 2. The `$GIT_DIR/info/exclude` file.
+
+via the `--exclude-from=` option.
+
SEE ALSO
--------
linkgit:git-read-tree[1], linkgit:gitignore[5]
diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt
index 1dd6555f66..06206521fc 100644
--- a/Documentation/git-rebase.txt
+++ b/Documentation/git-rebase.txt
@@ -523,7 +523,7 @@ See also INCOMPATIBLE OPTIONS below.
+
The commit list format can be changed by setting the configuration option
rebase.instructionFormat. A customized instruction format will automatically
-have the long commit hash prepended to the format.
+have the commit hash prepended to the format.
+
See also INCOMPATIBLE OPTIONS below.
@@ -626,13 +626,16 @@ See also INCOMPATIBLE OPTIONS below.
Automatically reschedule `exec` commands that failed. This only makes
sense in interactive mode (or when an `--exec` option was provided).
+
-Even though this option applies once a rebase is started, it's set for
-the whole rebase at the start based on either the
-`rebase.rescheduleFailedExec` configuration (see linkgit:git-config[1]
-or "CONFIGURATION" below) or whether this option is
-provided. Otherwise an explicit `--no-reschedule-failed-exec` at the
-start would be overridden by the presence of
-`rebase.rescheduleFailedExec=true` configuration.
+This option applies once a rebase is started. It is preserved for the whole
+rebase based on, in order, the command line option provided to the initial `git
+rebase`, the `rebase.rescheduleFailedExec` configuration (see
+linkgit:git-config[1] or "CONFIGURATION" below), or it defaults to false.
++
+Recording this option for the whole rebase is a convenience feature. Otherwise
+an explicit `--no-reschedule-failed-exec` at the start would be overridden by
+the presence of a `rebase.rescheduleFailedExec=true` configuration when `git
+rebase --continue` is invoked. Currently, you cannot pass
+`--[no-]reschedule-failed-exec` to `git rebase --continue`.
--update-refs::
--no-update-refs::
@@ -963,10 +966,9 @@ The interactive rebase will stop when a command fails (i.e. exits with
non-0 status) to give you an opportunity to fix the problem. You can
continue with `git rebase --continue`.
-The "exec" command launches the command in a shell (the one specified
-in `$SHELL`, or the default shell if `$SHELL` is not set), so you can
-use shell features (like "cd", ">", ";" ...). The command is run from
-the root of the working tree.
+The "exec" command launches the command in a shell (the default one, usually
+/bin/sh), so you can use shell features (like "cd", ">", ";" ...). The command
+is run from the root of the working tree.
----------------------------------
$ git rebase -i --exec "make test"
diff --git a/Documentation/git-rev-parse.txt b/Documentation/git-rev-parse.txt
index 912fab9f5e..546faf9017 100644
--- a/Documentation/git-rev-parse.txt
+++ b/Documentation/git-rev-parse.txt
@@ -307,6 +307,9 @@ The following options are unaffected by `--path-format`:
input, multiple algorithms may be printed, space-separated.
If not specified, the default is "storage".
+--show-ref-format::
+ Show the reference storage format used for the repository.
+
Other Options
~~~~~~~~~~~~~
diff --git a/Documentation/git.txt b/Documentation/git.txt
index bf9e6af695..da4e8d1303 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -556,6 +556,11 @@ double-quotes and respecting backslash escapes. E.g., the value
is always used. The default is "sha1".
See `--object-format` in linkgit:git-init[1].
+`GIT_DEFAULT_REF_FORMAT`::
+ If this variable is set, the default reference backend format for new
+ repositories will be set to this value. The default is "files".
+ See `--ref-format` in linkgit:git-init[1].
+
Git Commits
~~~~~~~~~~~
`GIT_AUTHOR_NAME`::
@@ -724,13 +729,12 @@ for further details.
waiting for someone with sufficient permissions to fix it.
`GIT_FLUSH`::
-// NEEDSWORK: make it into a usual Boolean environment variable
- If this environment variable is set to "1", then commands such
+ If this Boolean environment variable is set to true, then commands such
as 'git blame' (in incremental mode), 'git rev-list', 'git log',
'git check-attr' and 'git check-ignore' will
force a flush of the output stream after each record have been
flushed. If this
- variable is set to "0", the output of these commands will be done
+ variable is set to false, the output of these commands will be done
using completely buffered I/O. If this environment variable is
not set, Git will choose buffered or record-oriented flushing
based on whether stdout appears to be redirected to a file or not.
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index 8c1793c148..4338d023d9 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -100,6 +100,21 @@ for a path to `Unspecified` state. This can be done by listing
the name of the attribute prefixed with an exclamation point `!`.
+RESERVED BUILTIN_* ATTRIBUTES
+-----------------------------
+
+builtin_* is a reserved namespace for builtin attribute values. Any
+user defined attributes under this namespace will be ignored and
+trigger a warning.
+
+`builtin_objectmode`
+~~~~~~~~~~~~~~~~~~~~
+This attribute is for filtering files by their file bit modes (40000,
+120000, 160000, 100755, 100644). e.g. ':(attr:builtin_objectmode=160000)'.
+You may also check these values with `git check-attr builtin_objectmode -- <file>`.
+If the object is not in the index `git check-attr --cached` will return unspecified.
+
+
EFFECTS
-------
@@ -1122,11 +1137,11 @@ The `merge.*.name` variable gives the driver a human-readable
name.
The `merge.*.driver` variable's value is used to construct a
-command to run to merge ancestor's version (`%O`), current
+command to run to common ancestor's version (`%O`), current
version (`%A`) and the other branches' version (`%B`). These
three tokens are replaced with the names of temporary files that
hold the contents of these versions when the command line is
-built. Additionally, %L will be replaced with the conflict marker
+built. Additionally, `%L` will be replaced with the conflict marker
size (see below).
The merge driver is expected to leave the result of the merge in
@@ -1144,8 +1159,9 @@ When left unspecified, the driver itself is used for both
internal merge and the final merge.
The merge driver can learn the pathname in which the merged result
-will be stored via placeholder `%P`.
-
+will be stored via placeholder `%P`. The conflict labels to be used
+for the common ancestor, local head and other head can be passed by
+using '%S', '%X' and '%Y` respectively.
`conflict-marker-size`
^^^^^^^^^^^^^^^^^^^^^^
diff --git a/Documentation/gitformat-pack.txt b/Documentation/gitformat-pack.txt
index 9fcb29a9c8..d6ae229be5 100644
--- a/Documentation/gitformat-pack.txt
+++ b/Documentation/gitformat-pack.txt
@@ -396,6 +396,15 @@ CHUNK DATA:
is padded at the end with between 0 and 3 NUL bytes to make the
chunk size a multiple of 4 bytes.
+ Bitmapped Packfiles (ID: {'B', 'T', 'M', 'P'})
+ Stores a table of two 4-byte unsigned integers in network order.
+ Each table entry corresponds to a single pack (in the order that
+ they appear above in the `PNAM` chunk). The values for each table
+ entry are as follows:
+ - The first bit position (in pseudo-pack order, see below) to
+ contain an object from that pack.
+ - The number of bits whose objects are selected from that pack.
+
OID Fanout (ID: {'O', 'I', 'D', 'F'})
The ith entry, F[i], stores the number of OIDs with first
byte at most i. Thus F[255] stores the total
@@ -509,6 +518,73 @@ packs arranged in MIDX order (with the preferred pack coming first).
The MIDX's reverse index is stored in the optional 'RIDX' chunk within
the MIDX itself.
+=== `BTMP` chunk
+
+The Bitmapped Packfiles (`BTMP`) chunk encodes additional information
+about the objects in the multi-pack index's reachability bitmap. Recall
+that objects from the MIDX are arranged in "pseudo-pack" order (see
+above) for reachability bitmaps.
+
+From the example above, suppose we have packs "a", "b", and "c", with
+10, 15, and 20 objects, respectively. In pseudo-pack order, those would
+be arranged as follows:
+
+ |a,0|a,1|...|a,9|b,0|b,1|...|b,14|c,0|c,1|...|c,19|
+
+When working with single-pack bitmaps (or, equivalently, multi-pack
+reachability bitmaps with a preferred pack), linkgit:git-pack-objects[1]
+performs ``verbatim'' reuse, attempting to reuse chunks of the bitmapped
+or preferred packfile instead of adding objects to the packing list.
+
+When a chunk of bytes is reused from an existing pack, any objects
+contained therein do not need to be added to the packing list, saving
+memory and CPU time. But a chunk from an existing packfile can only be
+reused when the following conditions are met:
+
+ - The chunk contains only objects which were requested by the caller
+ (i.e. does not contain any objects which the caller didn't ask for
+ explicitly or implicitly).
+
+ - All objects stored in non-thin packs as offset- or reference-deltas
+ also include their base object in the resulting pack.
+
+The `BTMP` chunk encodes the necessary information in order to implement
+multi-pack reuse over a set of packfiles as described above.
+Specifically, the `BTMP` chunk encodes three pieces of information (all
+32-bit unsigned integers in network byte-order) for each packfile `p`
+that is stored in the MIDX, as follows:
+
+`bitmap_pos`:: The first bit position (in pseudo-pack order) in the
+ multi-pack index's reachability bitmap occupied by an object from `p`.
+
+`bitmap_nr`:: The number of bit positions (including the one at
+ `bitmap_pos`) that encode objects from that pack `p`.
+
+For example, the `BTMP` chunk corresponding to the above example (with
+packs ``a'', ``b'', and ``c'') would look like:
+
+[cols="1,2,2"]
+|===
+| |`bitmap_pos` |`bitmap_nr`
+
+|packfile ``a''
+|`0`
+|`10`
+
+|packfile ``b''
+|`10`
+|`15`
+
+|packfile ``c''
+|`25`
+|`20`
+|===
+
+With this information in place, we can treat each packfile as
+individually reusable in the same fashion as verbatim pack reuse is
+performed on individual packs prior to the implementation of the `BTMP`
+chunk.
+
== cruft packs
The cruft packs feature offer an alternative to Git's traditional mechanism of
diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt
index f7d98c11e3..d71b199955 100644
--- a/Documentation/glossary-content.txt
+++ b/Documentation/glossary-content.txt
@@ -638,6 +638,20 @@ The most notable example is `HEAD`.
An <<def_object,object>> used to temporarily store the contents of a
<<def_dirty,dirty>> working directory and the index for future reuse.
+[[def_special_ref]]special ref::
+ A ref that has different semantics than normal refs. These refs can be
+ accessed via normal Git commands but may not behave the same as a
+ normal ref in some cases.
++
+The following special refs are known to Git:
+
+ - "`FETCH_HEAD`" is written by linkgit:git-fetch[1] or linkgit:git-pull[1]. It
+ may refer to multiple object IDs. Each object ID is annotated with metadata
+ indicating where it was fetched from and its fetch status.
+
+ - "`MERGE_HEAD`" is written by linkgit:git-merge[1] when resolving merge
+ conflicts. It contains all commit IDs which are being merged.
+
[[def_submodule]]submodule::
A <<def_repository,repository>> that holds the history of a
separate project inside another repository (the latter of
diff --git a/Documentation/ref-storage-format.txt b/Documentation/ref-storage-format.txt
new file mode 100644
index 0000000000..1a65cac468
--- /dev/null
+++ b/Documentation/ref-storage-format.txt
@@ -0,0 +1 @@
+* `files` for loose files with packed-refs. This is the default.
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index 2bf239ff03..a583b52c61 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -947,10 +947,10 @@ ifdef::git-rev-list[]
+
The form '--filter=blob:none' omits all blobs.
+
-The form '--filter=blob:limit=<n>[kmg]' omits blobs larger than n bytes
-or units. n may be zero. The suffixes k, m, and g can be used to name
-units in KiB, MiB, or GiB. For example, 'blob:limit=1k' is the same
-as 'blob:limit=1024'.
+The form '--filter=blob:limit=<n>[kmg]' omits blobs of size at least n
+bytes or units. n may be zero. The suffixes k, m, and g can be used
+to name units in KiB, MiB, or GiB. For example, 'blob:limit=1k'
+is the same as 'blob:limit=1024'.
+
The form '--filter=object:type=(tag|commit|tree|blob)' omits all objects
which are not of the requested type.
diff --git a/Documentation/technical/repository-version.txt b/Documentation/technical/repository-version.txt
index 045a76756f..27be3741e6 100644
--- a/Documentation/technical/repository-version.txt
+++ b/Documentation/technical/repository-version.txt
@@ -100,3 +100,8 @@ If set, by default "git config" reads from both "config" and
multiple working directory mode, "config" file is shared while
"config.worktree" is per-working directory (i.e., it's in
GIT_COMMON_DIR/worktrees/<id>/config.worktree)
+
+==== `refStorage`
+
+Specifies the file format for the ref database. The only valid value
+is `files` (loose references with a packed-refs file).
diff --git a/Makefile b/Makefile
index 15990ff312..0f748a52e6 100644
--- a/Makefile
+++ b/Makefile
@@ -752,6 +752,10 @@ SCRIPTS = $(SCRIPT_SH_GEN) \
ETAGS_TARGET = TAGS
+# If you add a new fuzzer, please also make sure to run it in
+# ci/run-build-and-minimal-fuzzers.sh so that we make sure it still links and
+# runs in the future.
+FUZZ_OBJS += oss-fuzz/dummy-cmd-main.o
FUZZ_OBJS += oss-fuzz/fuzz-commit-graph.o
FUZZ_OBJS += oss-fuzz/fuzz-date.o
FUZZ_OBJS += oss-fuzz/fuzz-pack-headers.o
@@ -762,7 +766,7 @@ fuzz-objs: $(FUZZ_OBJS)
# Always build fuzz objects even if not testing, to prevent bit-rot.
all:: $(FUZZ_OBJS)
-FUZZ_PROGRAMS += $(patsubst %.o,%,$(FUZZ_OBJS))
+FUZZ_PROGRAMS += $(patsubst %.o,%,$(filter-out %dummy-cmd-main.o,$(FUZZ_OBJS)))
# Empty...
EXTRA_PROGRAMS =
@@ -792,7 +796,6 @@ 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
TEST_BUILTINS_OBJS += test-dir-iterator.o
@@ -1342,6 +1345,7 @@ THIRD_PARTY_SOURCES += sha1dc/%
UNIT_TEST_PROGRAMS += t-basic
UNIT_TEST_PROGRAMS += t-mem-pool
UNIT_TEST_PROGRAMS += t-strbuf
+UNIT_TEST_PROGRAMS += t-ctype
UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o
@@ -3850,16 +3854,17 @@ cover_db_html: cover_db
#
# make CC=clang CXX=clang++ \
# CFLAGS="-fsanitize=fuzzer-no-link,address" \
-# LIB_FUZZING_ENGINE="-fsanitize=fuzzer" \
+# LIB_FUZZING_ENGINE="-fsanitize=fuzzer,address" \
# fuzz-all
#
-FUZZ_CXXFLAGS ?= $(CFLAGS)
+FUZZ_CXXFLAGS ?= $(ALL_CFLAGS)
.PHONY: fuzz-all
-$(FUZZ_PROGRAMS): all
- $(QUIET_LINK)$(CXX) $(FUZZ_CXXFLAGS) $(LIB_OBJS) $(BUILTIN_OBJS) \
- $(XDIFF_OBJS) $(EXTLIBS) git.o $@.o $(LIB_FUZZING_ENGINE) -o $@
+$(FUZZ_PROGRAMS): %: %.o oss-fuzz/dummy-cmd-main.o $(GITLIBS) GIT-LDFLAGS
+ $(QUIET_LINK)$(CXX) $(FUZZ_CXXFLAGS) -o $@ $(ALL_LDFLAGS) \
+ -Wl,--allow-multiple-definition \
+ $(filter %.o,$^) $(filter %.a,$^) $(LIBS) $(LIB_FUZZING_ENGINE)
fuzz-all: $(FUZZ_PROGRAMS)
diff --git a/README.md b/README.md
index 2c3de2f9c8..665ce5f5a8 100644
--- a/README.md
+++ b/README.md
@@ -39,8 +39,8 @@ Those wishing to help with error message, usage and informational message
string translations (localization l10) should see [po/README.md][]
(a `po` file is a Portable Object file that holds the translations).
-To subscribe to the list, send an email with just "subscribe git" in
-the body to majordomo@vger.kernel.org (not the Git list). The mailing
+To subscribe to the list, send an email to <git+subscribe@vger.kernel.org>
+(see https://subspace.kernel.org/subscribing.html for details). The mailing
list archives are available at <https://lore.kernel.org/git/>,
<https://marc.info/?l=git> and other archival sites.
diff --git a/advice.c b/advice.c
index 50c79443ba..6e9098ff08 100644
--- a/advice.c
+++ b/advice.c
@@ -33,52 +33,56 @@ static const char *advise_get_color(enum color_advice ix)
return "";
}
+enum advice_level {
+ ADVICE_LEVEL_NONE = 0,
+ ADVICE_LEVEL_DISABLED,
+ ADVICE_LEVEL_ENABLED,
+};
+
static struct {
const char *key;
- int enabled;
+ enum advice_level level;
} advice_setting[] = {
- [ADVICE_ADD_EMBEDDED_REPO] = { "addEmbeddedRepo", 1 },
- [ADVICE_ADD_EMPTY_PATHSPEC] = { "addEmptyPathspec", 1 },
- [ADVICE_ADD_IGNORED_FILE] = { "addIgnoredFile", 1 },
- [ADVICE_AM_WORK_DIR] = { "amWorkDir", 1 },
- [ADVICE_AMBIGUOUS_FETCH_REFSPEC] = { "ambiguousFetchRefspec", 1 },
- [ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME] = { "checkoutAmbiguousRemoteBranchName", 1 },
- [ADVICE_COMMIT_BEFORE_MERGE] = { "commitBeforeMerge", 1 },
- [ADVICE_DETACHED_HEAD] = { "detachedHead", 1 },
- [ADVICE_SUGGEST_DETACHING_HEAD] = { "suggestDetachingHead", 1 },
- [ADVICE_DIVERGING] = { "diverging", 1 },
- [ADVICE_FETCH_SHOW_FORCED_UPDATES] = { "fetchShowForcedUpdates", 1 },
- [ADVICE_GRAFT_FILE_DEPRECATED] = { "graftFileDeprecated", 1 },
- [ADVICE_IGNORED_HOOK] = { "ignoredHook", 1 },
- [ADVICE_IMPLICIT_IDENTITY] = { "implicitIdentity", 1 },
- [ADVICE_NESTED_TAG] = { "nestedTag", 1 },
- [ADVICE_OBJECT_NAME_WARNING] = { "objectNameWarning", 1 },
- [ADVICE_PUSH_ALREADY_EXISTS] = { "pushAlreadyExists", 1 },
- [ADVICE_PUSH_FETCH_FIRST] = { "pushFetchFirst", 1 },
- [ADVICE_PUSH_NEEDS_FORCE] = { "pushNeedsForce", 1 },
- [ADVICE_PUSH_REF_NEEDS_UPDATE] = { "pushRefNeedsUpdate", 1 },
-
- /* make this an alias for backward compatibility */
- [ADVICE_PUSH_UPDATE_REJECTED_ALIAS] = { "pushNonFastForward", 1 },
-
- [ADVICE_PUSH_NON_FF_CURRENT] = { "pushNonFFCurrent", 1 },
- [ADVICE_PUSH_NON_FF_MATCHING] = { "pushNonFFMatching", 1 },
- [ADVICE_PUSH_UNQUALIFIED_REF_NAME] = { "pushUnqualifiedRefName", 1 },
- [ADVICE_PUSH_UPDATE_REJECTED] = { "pushUpdateRejected", 1 },
- [ADVICE_RESET_NO_REFRESH_WARNING] = { "resetNoRefresh", 1 },
- [ADVICE_RESOLVE_CONFLICT] = { "resolveConflict", 1 },
- [ADVICE_RM_HINTS] = { "rmHints", 1 },
- [ADVICE_SEQUENCER_IN_USE] = { "sequencerInUse", 1 },
- [ADVICE_SET_UPSTREAM_FAILURE] = { "setUpstreamFailure", 1 },
- [ADVICE_SKIPPED_CHERRY_PICKS] = { "skippedCherryPicks", 1 },
- [ADVICE_STATUS_AHEAD_BEHIND_WARNING] = { "statusAheadBehindWarning", 1 },
- [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 },
- [ADVICE_WORKTREE_ADD_ORPHAN] = { "worktreeAddOrphan", 1 },
+ [ADVICE_ADD_EMBEDDED_REPO] = { "addEmbeddedRepo" },
+ [ADVICE_ADD_EMPTY_PATHSPEC] = { "addEmptyPathspec" },
+ [ADVICE_ADD_IGNORED_FILE] = { "addIgnoredFile" },
+ [ADVICE_AMBIGUOUS_FETCH_REFSPEC] = { "ambiguousFetchRefspec" },
+ [ADVICE_AM_WORK_DIR] = { "amWorkDir" },
+ [ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME] = { "checkoutAmbiguousRemoteBranchName" },
+ [ADVICE_COMMIT_BEFORE_MERGE] = { "commitBeforeMerge" },
+ [ADVICE_DETACHED_HEAD] = { "detachedHead" },
+ [ADVICE_DIVERGING] = { "diverging" },
+ [ADVICE_FETCH_SHOW_FORCED_UPDATES] = { "fetchShowForcedUpdates" },
+ [ADVICE_FORCE_DELETE_BRANCH] = { "forceDeleteBranch" },
+ [ADVICE_GRAFT_FILE_DEPRECATED] = { "graftFileDeprecated" },
+ [ADVICE_IGNORED_HOOK] = { "ignoredHook" },
+ [ADVICE_IMPLICIT_IDENTITY] = { "implicitIdentity" },
+ [ADVICE_NESTED_TAG] = { "nestedTag" },
+ [ADVICE_OBJECT_NAME_WARNING] = { "objectNameWarning" },
+ [ADVICE_PUSH_ALREADY_EXISTS] = { "pushAlreadyExists" },
+ [ADVICE_PUSH_FETCH_FIRST] = { "pushFetchFirst" },
+ [ADVICE_PUSH_NEEDS_FORCE] = { "pushNeedsForce" },
+ [ADVICE_PUSH_NON_FF_CURRENT] = { "pushNonFFCurrent" },
+ [ADVICE_PUSH_NON_FF_MATCHING] = { "pushNonFFMatching" },
+ [ADVICE_PUSH_REF_NEEDS_UPDATE] = { "pushRefNeedsUpdate" },
+ [ADVICE_PUSH_UNQUALIFIED_REF_NAME] = { "pushUnqualifiedRefName" },
+ [ADVICE_PUSH_UPDATE_REJECTED] = { "pushUpdateRejected" },
+ [ADVICE_PUSH_UPDATE_REJECTED_ALIAS] = { "pushNonFastForward" }, /* backwards compatibility */
+ [ADVICE_RESET_NO_REFRESH_WARNING] = { "resetNoRefresh" },
+ [ADVICE_RESOLVE_CONFLICT] = { "resolveConflict" },
+ [ADVICE_RM_HINTS] = { "rmHints" },
+ [ADVICE_SEQUENCER_IN_USE] = { "sequencerInUse" },
+ [ADVICE_SET_UPSTREAM_FAILURE] = { "setUpstreamFailure" },
+ [ADVICE_SKIPPED_CHERRY_PICKS] = { "skippedCherryPicks" },
+ [ADVICE_STATUS_AHEAD_BEHIND_WARNING] = { "statusAheadBehindWarning" },
+ [ADVICE_STATUS_HINTS] = { "statusHints" },
+ [ADVICE_STATUS_U_OPTION] = { "statusUoption" },
+ [ADVICE_SUBMODULES_NOT_UPDATED] = { "submodulesNotUpdated" },
+ [ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { "submoduleAlternateErrorStrategyDie" },
+ [ADVICE_SUGGEST_DETACHING_HEAD] = { "suggestDetachingHead" },
+ [ADVICE_UPDATE_SPARSE_PATH] = { "updateSparsePath" },
+ [ADVICE_WAITING_FOR_EDITOR] = { "waitingForEditor" },
+ [ADVICE_WORKTREE_ADD_ORPHAN] = { "worktreeAddOrphan" },
};
static const char turn_off_instructions[] =
@@ -118,13 +122,13 @@ void advise(const char *advice, ...)
int advice_enabled(enum advice_type type)
{
- switch(type) {
- case ADVICE_PUSH_UPDATE_REJECTED:
- return advice_setting[ADVICE_PUSH_UPDATE_REJECTED].enabled &&
- advice_setting[ADVICE_PUSH_UPDATE_REJECTED_ALIAS].enabled;
- default:
- return advice_setting[type].enabled;
- }
+ int enabled = advice_setting[type].level != ADVICE_LEVEL_DISABLED;
+
+ if (type == ADVICE_PUSH_UPDATE_REJECTED)
+ return enabled &&
+ advice_enabled(ADVICE_PUSH_UPDATE_REJECTED_ALIAS);
+
+ return enabled;
}
void advise_if_enabled(enum advice_type type, const char *advice, ...)
@@ -135,7 +139,8 @@ void advise_if_enabled(enum advice_type type, const char *advice, ...)
return;
va_start(params, advice);
- vadvise(advice, 1, advice_setting[type].key, params);
+ vadvise(advice, !advice_setting[type].level, advice_setting[type].key,
+ params);
va_end(params);
}
@@ -164,7 +169,9 @@ int git_default_advice_config(const char *var, const char *value)
for (i = 0; i < ARRAY_SIZE(advice_setting); i++) {
if (strcasecmp(k, advice_setting[i].key))
continue;
- advice_setting[i].enabled = git_config_bool(var, value);
+ advice_setting[i].level = git_config_bool(var, value)
+ ? ADVICE_LEVEL_ENABLED
+ : ADVICE_LEVEL_DISABLED;
return 0;
}
diff --git a/advice.h b/advice.h
index 2affbe1426..9d4f49ae38 100644
--- a/advice.h
+++ b/advice.h
@@ -10,18 +10,18 @@ struct string_list;
* Add the new config variable to Documentation/config/advice.txt.
* Call advise_if_enabled to print your advice.
*/
- enum advice_type {
+enum advice_type {
ADVICE_ADD_EMBEDDED_REPO,
ADVICE_ADD_EMPTY_PATHSPEC,
ADVICE_ADD_IGNORED_FILE,
- ADVICE_AM_WORK_DIR,
ADVICE_AMBIGUOUS_FETCH_REFSPEC,
+ ADVICE_AM_WORK_DIR,
ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,
ADVICE_COMMIT_BEFORE_MERGE,
ADVICE_DETACHED_HEAD,
ADVICE_DIVERGING,
- ADVICE_SUGGEST_DETACHING_HEAD,
ADVICE_FETCH_SHOW_FORCED_UPDATES,
+ ADVICE_FORCE_DELETE_BRANCH,
ADVICE_GRAFT_FILE_DEPRECATED,
ADVICE_IGNORED_HOOK,
ADVICE_IMPLICIT_IDENTITY,
@@ -32,23 +32,24 @@ struct string_list;
ADVICE_PUSH_NEEDS_FORCE,
ADVICE_PUSH_NON_FF_CURRENT,
ADVICE_PUSH_NON_FF_MATCHING,
+ ADVICE_PUSH_REF_NEEDS_UPDATE,
ADVICE_PUSH_UNQUALIFIED_REF_NAME,
- ADVICE_PUSH_UPDATE_REJECTED_ALIAS,
ADVICE_PUSH_UPDATE_REJECTED,
- ADVICE_PUSH_REF_NEEDS_UPDATE,
+ ADVICE_PUSH_UPDATE_REJECTED_ALIAS,
ADVICE_RESET_NO_REFRESH_WARNING,
ADVICE_RESOLVE_CONFLICT,
ADVICE_RM_HINTS,
ADVICE_SEQUENCER_IN_USE,
ADVICE_SET_UPSTREAM_FAILURE,
+ ADVICE_SKIPPED_CHERRY_PICKS,
ADVICE_STATUS_AHEAD_BEHIND_WARNING,
ADVICE_STATUS_HINTS,
ADVICE_STATUS_U_OPTION,
- ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,
ADVICE_SUBMODULES_NOT_UPDATED,
+ ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,
+ ADVICE_SUGGEST_DETACHING_HEAD,
ADVICE_UPDATE_SPARSE_PATH,
ADVICE_WAITING_FOR_EDITOR,
- ADVICE_SKIPPED_CHERRY_PICKS,
ADVICE_WORKTREE_ADD_ORPHAN,
};
diff --git a/attr.c b/attr.c
index e62876dfd3..679e42258c 100644
--- a/attr.c
+++ b/attr.c
@@ -17,6 +17,7 @@
#include "utf8.h"
#include "quote.h"
#include "read-cache-ll.h"
+#include "refs.h"
#include "revision.h"
#include "object-store-ll.h"
#include "setup.h"
@@ -183,6 +184,15 @@ static void all_attrs_init(struct attr_hashmap *map, struct attr_check *check)
}
}
+/*
+ * Atribute name cannot begin with "builtin_" which
+ * is a reserved namespace for built in attributes values.
+ */
+static int attr_name_reserved(const char *name)
+{
+ return starts_with(name, "builtin_");
+}
+
static int attr_name_valid(const char *name, size_t namelen)
{
/*
@@ -315,7 +325,7 @@ static const char *parse_attr(const char *src, int lineno, const char *cp,
cp++;
len--;
}
- if (!attr_name_valid(cp, len)) {
+ if (!attr_name_valid(cp, len) || attr_name_reserved(cp)) {
report_invalid_attr(cp, len, src, lineno);
return NULL;
}
@@ -379,7 +389,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
name += strlen(ATTRIBUTE_MACRO_PREFIX);
name += strspn(name, blank);
namelen = strcspn(name, blank);
- if (!attr_name_valid(name, namelen)) {
+ if (!attr_name_valid(name, namelen) || attr_name_reserved(name)) {
report_invalid_attr(name, namelen, src, lineno);
goto fail_return;
}
@@ -1240,6 +1250,85 @@ static struct object_id *default_attr_source(void)
return &attr_source;
}
+static const char *interned_mode_string(unsigned int mode)
+{
+ static struct {
+ unsigned int val;
+ char str[7];
+ } mode_string[] = {
+ { .val = 0040000 },
+ { .val = 0100644 },
+ { .val = 0100755 },
+ { .val = 0120000 },
+ { .val = 0160000 },
+ };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mode_string); i++) {
+ if (mode_string[i].val != mode)
+ continue;
+ if (!*mode_string[i].str)
+ snprintf(mode_string[i].str, sizeof(mode_string[i].str),
+ "%06o", mode);
+ return mode_string[i].str;
+ }
+ BUG("Unsupported mode 0%o", mode);
+}
+
+static const char *builtin_object_mode_attr(struct index_state *istate, const char *path)
+{
+ unsigned int mode;
+
+ if (direction == GIT_ATTR_CHECKIN) {
+ struct object_id oid;
+ struct stat st;
+ if (lstat(path, &st))
+ die_errno(_("unable to stat '%s'"), path);
+ mode = canon_mode(st.st_mode);
+ if (S_ISDIR(mode)) {
+ /*
+ *`path` is either a directory or it is a submodule,
+ * in which case it is already indexed as submodule
+ * or it does not exist in the index yet and we need to
+ * check if we can resolve to a ref.
+ */
+ int pos = index_name_pos(istate, path, strlen(path));
+ if (pos >= 0) {
+ if (S_ISGITLINK(istate->cache[pos]->ce_mode))
+ mode = istate->cache[pos]->ce_mode;
+ } else if (resolve_gitlink_ref(path, "HEAD", &oid) == 0) {
+ mode = S_IFGITLINK;
+ }
+ }
+ } else {
+ /*
+ * For GIT_ATTR_CHECKOUT and GIT_ATTR_INDEX we only check
+ * for mode in the index.
+ */
+ int pos = index_name_pos(istate, path, strlen(path));
+ if (pos >= 0)
+ mode = istate->cache[pos]->ce_mode;
+ else
+ return ATTR__UNSET;
+ }
+
+ return interned_mode_string(mode);
+}
+
+
+static const char *compute_builtin_attr(struct index_state *istate,
+ const char *path,
+ const struct git_attr *attr) {
+ static const struct git_attr *object_mode_attr;
+
+ if (!object_mode_attr)
+ object_mode_attr = git_attr("builtin_objectmode");
+
+ if (attr == object_mode_attr)
+ return builtin_object_mode_attr(istate, path);
+ return ATTR__UNSET;
+}
+
void git_check_attr(struct index_state *istate,
const char *path,
struct attr_check *check)
@@ -1253,7 +1342,7 @@ void git_check_attr(struct index_state *istate,
unsigned int n = check->items[i].attr->attr_nr;
const char *value = check->all_attrs[n].value;
if (value == ATTR__UNKNOWN)
- value = ATTR__UNSET;
+ value = compute_builtin_attr(istate, path, check->all_attrs[n].attr);
check->items[i].value = value;
}
}
diff --git a/branch.c b/branch.c
index 534594f7f8..6719a181bd 100644
--- a/branch.c
+++ b/branch.c
@@ -817,8 +817,9 @@ void remove_merge_branch_state(struct repository *r)
unlink(git_path_merge_rr(r));
unlink(git_path_merge_msg(r));
unlink(git_path_merge_mode(r));
- unlink(git_path_auto_merge(r));
- save_autostash(git_path_merge_autostash(r));
+ refs_delete_ref(get_main_ref_store(r), "", "AUTO_MERGE",
+ NULL, REF_NO_DEREF);
+ save_autostash_ref(r, "MERGE_AUTOSTASH");
}
void remove_branch_state(struct repository *r, int verbose)
diff --git a/builtin/branch.c b/builtin/branch.c
index 0a32d1b6c8..cfb63cce5f 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -24,6 +24,7 @@
#include "ref-filter.h"
#include "worktree.h"
#include "help.h"
+#include "advice.h"
#include "commit-reach.h"
static const char * const builtin_branch_usage[] = {
@@ -190,9 +191,10 @@ static int check_branch_commit(const char *branchname, const char *refname,
return -1;
}
if (!force && !branch_merged(kinds, branchname, rev, head_rev)) {
- error(_("the branch '%s' is not fully merged.\n"
- "If you are sure you want to delete it, "
- "run 'git branch -D %s'"), branchname, branchname);
+ error(_("the branch '%s' is not fully merged"), branchname);
+ advise_if_enabled(ADVICE_FORCE_DELETE_BRANCH,
+ _("If you are sure you want to delete it, "
+ "run 'git branch -D %s'"), branchname);
return -1;
}
return 0;
diff --git a/builtin/clone.c b/builtin/clone.c
index 0605fa79aa..bad1b70ce8 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -71,6 +71,7 @@ static char *remote_name = NULL;
static char *option_branch = NULL;
static struct string_list option_not = STRING_LIST_INIT_NODUP;
static const char *real_git_dir;
+static const char *ref_format;
static char *option_upload_pack = "git-upload-pack";
static int option_verbosity;
static int option_progress = -1;
@@ -156,6 +157,8 @@ static struct option builtin_clone_options[] = {
N_("any cloned submodules will be shallow")),
OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
N_("separate git dir from working tree")),
+ OPT_STRING(0, "ref-format", &ref_format, N_("format"),
+ N_("specify the reference format to use")),
OPT_STRING_LIST('c', "config", &option_config, N_("key=value"),
N_("set config inside the new repository")),
OPT_STRING_LIST(0, "server-option", &server_options,
@@ -931,6 +934,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
int submodule_progress;
int filter_submodules = 0;
int hash_algo;
+ unsigned int ref_storage_format = REF_STORAGE_FORMAT_UNKNOWN;
const int do_not_override_repo_unix_permissions = -1;
struct transport_ls_refs_options transport_ls_refs_options =
@@ -956,6 +960,12 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
if (option_single_branch == -1)
option_single_branch = deepen ? 1 : 0;
+ if (ref_format) {
+ ref_storage_format = ref_storage_format_by_name(ref_format);
+ if (ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
+ die(_("unknown ref storage format '%s'"), ref_format);
+ }
+
if (option_mirror)
option_bare = 1;
@@ -1106,7 +1116,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
* repository, and reference backends may persist that information into
* their on-disk data structures.
*/
- init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, NULL,
+ init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN,
+ ref_storage_format, NULL,
do_not_override_repo_unix_permissions, INIT_DB_QUIET | INIT_DB_SKIP_REFDB);
if (real_git_dir) {
@@ -1289,9 +1300,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
* ours to the same thing.
*/
hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));
- initialize_repository_version(hash_algo, 1);
+ initialize_repository_version(hash_algo, the_repository->ref_storage_format, 1);
repo_set_hash_algo(the_repository, hash_algo);
- create_reference_database(NULL, 1);
+ create_reference_database(the_repository->ref_storage_format, NULL, 1);
/*
* Before fetching from the remote, download and install bundle
diff --git a/builtin/commit.c b/builtin/commit.c
index 65196a2827..6d1fa71676 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1877,7 +1877,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
&oid, flags);
}
- apply_autostash(git_path_merge_autostash(the_repository));
+ apply_autostash_ref(the_repository, "MERGE_AUTOSTASH");
cleanup:
strbuf_release(&author_ident);
diff --git a/builtin/config.c b/builtin/config.c
index 11a4d4ef14..b55bfae7d6 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -708,10 +708,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
}
if (use_global_config) {
- char *user_config, *xdg_config;
-
- git_global_config(&user_config, &xdg_config);
- if (!user_config)
+ given_config_source.file = git_global_config();
+ if (!given_config_source.file)
/*
* It is unknown if HOME/.gitconfig exists, so
* we do not know if we should write to XDG
@@ -719,19 +717,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
* is set and points at a sane location.
*/
die(_("$HOME not set"));
-
given_config_source.scope = CONFIG_SCOPE_GLOBAL;
-
- if (access_or_warn(user_config, R_OK, 0) &&
- xdg_config && !access_or_warn(xdg_config, R_OK, 0)) {
- given_config_source.file = xdg_config;
- free(user_config);
- } else {
- given_config_source.file = user_config;
- free(xdg_config);
- }
- }
- else if (use_system_config) {
+ } else if (use_system_config) {
given_config_source.file = git_system_config();
given_config_source.scope = CONFIG_SCOPE_SYSTEM;
} else if (use_local_config) {
@@ -760,7 +747,6 @@ int cmd_config(int argc, const char **argv, const char *prefix)
given_config_source.scope = CONFIG_SCOPE_COMMAND;
}
-
if (respect_includes_opt == -1)
config_options.respect_includes = !given_config_source.file;
else
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 119f1a72ac..3aedfd1bb6 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -100,6 +100,7 @@ static struct string_list negotiation_tip = STRING_LIST_INIT_NODUP;
struct fetch_config {
enum display_format display_format;
+ int all;
int prune;
int prune_tags;
int show_forced_updates;
@@ -113,6 +114,11 @@ static int git_fetch_config(const char *k, const char *v,
{
struct fetch_config *fetch_config = cb;
+ if (!strcmp(k, "fetch.all")) {
+ fetch_config->all = git_config_bool(k, v);
+ return 0;
+ }
+
if (!strcmp(k, "fetch.prune")) {
fetch_config->prune = git_config_bool(k, v);
return 0;
@@ -2130,7 +2136,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
const char *bundle_uri;
struct string_list list = STRING_LIST_INIT_DUP;
struct remote *remote = NULL;
- int all = 0, multiple = 0;
+ int all = -1, multiple = 0;
int result = 0;
int prune_tags_ok = 1;
int enable_auto_gc = 1;
@@ -2335,11 +2341,20 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
fetch_bundle_uri(the_repository, bundle_uri, NULL))
warning(_("failed to fetch bundles from '%s'"), bundle_uri);
+ if (all < 0) {
+ /*
+ * no --[no-]all given;
+ * only use config option if no remote was explicitly specified
+ */
+ all = (!argc) ? config.all : 0;
+ }
+
if (all) {
if (argc == 1)
die(_("fetch --all does not take a repository argument"));
else if (argc > 1)
die(_("fetch --all does not make sense with refspecs"));
+
(void) for_each_remote(get_one_remote_for_fetch, &list);
/* do not do fetch_multiple() of one */
diff --git a/builtin/gc.c b/builtin/gc.c
index 7c11d5ebef..cb80ced6cb 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -1543,19 +1543,18 @@ static int maintenance_register(int argc, const char **argv, const char *prefix)
if (!found) {
int rc;
- char *user_config = NULL, *xdg_config = NULL;
+ char *global_config_file = NULL;
if (!config_file) {
- git_global_config(&user_config, &xdg_config);
- config_file = user_config;
- if (!user_config)
- die(_("$HOME not set"));
+ global_config_file = git_global_config();
+ config_file = global_config_file;
}
+ if (!config_file)
+ die(_("$HOME not set"));
rc = git_config_set_multivar_in_file_gently(
config_file, "maintenance.repo", maintpath,
CONFIG_REGEX_NONE, 0);
- free(user_config);
- free(xdg_config);
+ free(global_config_file);
if (rc)
die(_("unable to add '%s' value of '%s'"),
@@ -1612,18 +1611,18 @@ static int maintenance_unregister(int argc, const char **argv, const char *prefi
if (found) {
int rc;
- char *user_config = NULL, *xdg_config = NULL;
+ char *global_config_file = NULL;
+
if (!config_file) {
- git_global_config(&user_config, &xdg_config);
- config_file = user_config;
- if (!user_config)
- die(_("$HOME not set"));
+ global_config_file = git_global_config();
+ config_file = global_config_file;
}
+ if (!config_file)
+ die(_("$HOME not set"));
rc = git_config_set_multivar_in_file_gently(
config_file, key, NULL, maintpath,
CONFIG_FLAGS_MULTI_REPLACE | CONFIG_FLAGS_FIXED_VALUE);
- free(user_config);
- free(xdg_config);
+ free(global_config_file);
if (rc &&
(!force || rc == CONFIG_NOTHING_SET))
diff --git a/builtin/index-pack.c b/builtin/index-pack.c
index 0841b6940a..1ea87e01f2 100644
--- a/builtin/index-pack.c
+++ b/builtin/index-pack.c
@@ -1255,6 +1255,7 @@ static void resolve_deltas(void)
base_cache_limit = delta_base_cache_limit * nr_threads;
if (nr_threads > 1 || getenv("GIT_FORCE_THREADS")) {
init_thread();
+ work_lock();
for (i = 0; i < nr_threads; i++) {
int ret = pthread_create(&thread_data[i].thread, NULL,
threaded_second_pass, thread_data + i);
@@ -1262,6 +1263,7 @@ static void resolve_deltas(void)
die(_("unable to create thread: %s"),
strerror(ret));
}
+ work_unlock();
for (i = 0; i < nr_threads; i++)
pthread_join(thread_data[i].thread, NULL);
cleanup_thread();
diff --git a/builtin/init-db.c b/builtin/init-db.c
index b89814a6f8..0170469b84 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -10,6 +10,8 @@
#include "object-file.h"
#include "parse-options.h"
#include "path.h"
+#include "refs.h"
+#include "repository.h"
#include "setup.h"
#include "strbuf.h"
@@ -56,6 +58,7 @@ static int shared_callback(const struct option *opt, const char *arg, int unset)
static const char *const init_db_usage[] = {
N_("git init [-q | --quiet] [--bare] [--template=<template-directory>]\n"
" [--separate-git-dir <git-dir>] [--object-format=<format>]\n"
+ " [--ref-format=<format>]\n"
" [-b <branch-name> | --initial-branch=<branch-name>]\n"
" [--shared[=<permissions>]] [<directory>]"),
NULL
@@ -75,8 +78,10 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
const char *template_dir = NULL;
unsigned int flags = 0;
const char *object_format = NULL;
+ const char *ref_format = NULL;
const char *initial_branch = NULL;
int hash_algo = GIT_HASH_UNKNOWN;
+ unsigned int ref_storage_format = REF_STORAGE_FORMAT_UNKNOWN;
int init_shared_repository = -1;
const struct option init_db_options[] = {
OPT_STRING(0, "template", &template_dir, N_("template-directory"),
@@ -94,6 +99,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
N_("override the name of the initial branch")),
OPT_STRING(0, "object-format", &object_format, N_("hash"),
N_("specify the hash algorithm to use")),
+ OPT_STRING(0, "ref-format", &ref_format, N_("format"),
+ N_("specify the reference format to use")),
OPT_END()
};
@@ -157,6 +164,12 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
die(_("unknown hash algorithm '%s'"), object_format);
}
+ if (ref_format) {
+ ref_storage_format = ref_storage_format_by_name(ref_format);
+ if (ref_storage_format == REF_STORAGE_FORMAT_UNKNOWN)
+ die(_("unknown ref storage format '%s'"), ref_format);
+ }
+
if (init_shared_repository != -1)
set_shared_repository(init_shared_repository);
@@ -235,5 +248,6 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
flags |= INIT_DB_EXIST_OK;
return init_db(git_dir, real_git_dir, template_dir, hash_algo,
- initial_branch, init_shared_repository, flags);
+ ref_storage_format, initial_branch,
+ init_shared_repository, flags);
}
diff --git a/builtin/merge.c b/builtin/merge.c
index ebbe05033e..8f819781cc 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -476,7 +476,7 @@ static void finish(struct commit *head_commit,
run_hooks_l("post-merge", squash ? "1" : "0", NULL);
if (new_head)
- apply_autostash(git_path_merge_autostash(the_repository));
+ apply_autostash_ref(the_repository, "MERGE_AUTOSTASH");
strbuf_release(&reflog_message);
}
@@ -1315,7 +1315,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (abort_current_merge) {
int nargc = 2;
const char *nargv[] = {"reset", "--merge", NULL};
- struct strbuf stash_oid = STRBUF_INIT;
+ char stash_oid_hex[GIT_MAX_HEXSZ + 1];
+ struct object_id stash_oid = {0};
if (orig_argc != 2)
usage_msg_opt(_("--abort expects no arguments"),
@@ -1324,17 +1325,17 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
if (!file_exists(git_path_merge_head(the_repository)))
die(_("There is no merge to abort (MERGE_HEAD missing)."));
- if (read_oneliner(&stash_oid, git_path_merge_autostash(the_repository),
- READ_ONELINER_SKIP_IF_EMPTY))
- unlink(git_path_merge_autostash(the_repository));
+ if (!read_ref("MERGE_AUTOSTASH", &stash_oid))
+ delete_ref("", "MERGE_AUTOSTASH", &stash_oid, REF_NO_DEREF);
/* Invoke 'git reset --merge' */
ret = cmd_reset(nargc, nargv, prefix);
- if (stash_oid.len)
- apply_autostash_oid(stash_oid.buf);
+ if (!is_null_oid(&stash_oid)) {
+ oid_to_hex_r(stash_oid_hex, &stash_oid);
+ apply_autostash_oid(stash_oid_hex);
+ }
- strbuf_release(&stash_oid);
goto done;
}
@@ -1563,13 +1564,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
}
if (autostash)
- create_autostash(the_repository,
- git_path_merge_autostash(the_repository));
+ create_autostash_ref(the_repository, "MERGE_AUTOSTASH");
if (checkout_fast_forward(the_repository,
&head_commit->object.oid,
&commit->object.oid,
overwrite_ignore)) {
- apply_autostash(git_path_merge_autostash(the_repository));
+ apply_autostash_ref(the_repository, "MERGE_AUTOSTASH");
ret = 1;
goto done;
}
@@ -1655,8 +1655,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
die_ff_impossible();
if (autostash)
- create_autostash(the_repository,
- git_path_merge_autostash(the_repository));
+ create_autostash_ref(the_repository, "MERGE_AUTOSTASH");
/* We are going to make a new commit. */
git_committer_info(IDENT_STRICT);
@@ -1741,7 +1740,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
else
fprintf(stderr, _("Merge with strategy %s failed.\n"),
use_strategies[0]->name);
- apply_autostash(git_path_merge_autostash(the_repository));
+ apply_autostash_ref(the_repository, "MERGE_AUTOSTASH");
ret = 2;
goto done;
} else if (best_strategy == wt_strategy)
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index 5c8bfe1035..d8c2128a97 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -218,13 +218,19 @@ static int thin;
static int num_preferred_base;
static struct progress *progress_state;
-static struct packed_git *reuse_packfile;
+static struct bitmapped_pack *reuse_packfiles;
+static size_t reuse_packfiles_nr;
+static size_t reuse_packfiles_used_nr;
static uint32_t reuse_packfile_objects;
static struct bitmap *reuse_packfile_bitmap;
static int use_bitmap_index_default = 1;
static int use_bitmap_index = -1;
-static int allow_pack_reuse = 1;
+static enum {
+ NO_PACK_REUSE = 0,
+ SINGLE_PACK_REUSE,
+ MULTI_PACK_REUSE,
+} allow_pack_reuse = SINGLE_PACK_REUSE;
static enum {
WRITE_BITMAP_FALSE = 0,
WRITE_BITMAP_QUIET,
@@ -1010,7 +1016,9 @@ static off_t find_reused_offset(off_t where)
return reused_chunks[lo-1].difference;
}
-static void write_reused_pack_one(size_t pos, struct hashfile *out,
+static void write_reused_pack_one(struct packed_git *reuse_packfile,
+ size_t pos, struct hashfile *out,
+ off_t pack_start,
struct pack_window **w_curs)
{
off_t offset, next, cur;
@@ -1020,7 +1028,8 @@ static void write_reused_pack_one(size_t pos, struct hashfile *out,
offset = pack_pos_to_offset(reuse_packfile, pos);
next = pack_pos_to_offset(reuse_packfile, pos + 1);
- record_reused_object(offset, offset - hashfile_total(out));
+ record_reused_object(offset,
+ offset - (hashfile_total(out) - pack_start));
cur = offset;
type = unpack_object_header(reuse_packfile, w_curs, &cur, &size);
@@ -1088,41 +1097,93 @@ static void write_reused_pack_one(size_t pos, struct hashfile *out,
copy_pack_data(out, reuse_packfile, w_curs, offset, next - offset);
}
-static size_t write_reused_pack_verbatim(struct hashfile *out,
+static size_t write_reused_pack_verbatim(struct bitmapped_pack *reuse_packfile,
+ struct hashfile *out,
+ off_t pack_start,
struct pack_window **w_curs)
{
- size_t pos = 0;
+ size_t pos = reuse_packfile->bitmap_pos;
+ size_t end;
+
+ if (pos % BITS_IN_EWORD) {
+ size_t word_pos = (pos / BITS_IN_EWORD);
+ size_t offset = pos % BITS_IN_EWORD;
+ size_t last;
+ eword_t word = reuse_packfile_bitmap->words[word_pos];
+
+ if (offset + reuse_packfile->bitmap_nr < BITS_IN_EWORD)
+ last = offset + reuse_packfile->bitmap_nr;
+ else
+ last = BITS_IN_EWORD;
+
+ for (; offset < last; offset++) {
+ if (word >> offset == 0)
+ return word_pos;
+ if (!bitmap_get(reuse_packfile_bitmap,
+ word_pos * BITS_IN_EWORD + offset))
+ return word_pos;
+ }
- while (pos < reuse_packfile_bitmap->word_alloc &&
- reuse_packfile_bitmap->words[pos] == (eword_t)~0)
- pos++;
+ pos += BITS_IN_EWORD - (pos % BITS_IN_EWORD);
+ }
+
+ /*
+ * Now we're going to copy as many whole eword_t's as possible.
+ * "end" is the index of the last whole eword_t we copy, but
+ * there may be additional bits to process. Those are handled
+ * individually by write_reused_pack().
+ *
+ * Begin by advancing to the first word boundary in range of the
+ * bit positions occupied by objects in "reuse_packfile". Then
+ * pick the last word boundary in the same range. If we have at
+ * least one word's worth of bits to process, continue on.
+ */
+ end = reuse_packfile->bitmap_pos + reuse_packfile->bitmap_nr;
+ if (end % BITS_IN_EWORD)
+ end -= end % BITS_IN_EWORD;
+ if (pos >= end)
+ return reuse_packfile->bitmap_pos / BITS_IN_EWORD;
- if (pos) {
- off_t to_write;
+ while (pos < end &&
+ reuse_packfile_bitmap->words[pos / BITS_IN_EWORD] == (eword_t)~0)
+ pos += BITS_IN_EWORD;
- written = (pos * BITS_IN_EWORD);
- to_write = pack_pos_to_offset(reuse_packfile, written)
- - sizeof(struct pack_header);
+ if (pos > end)
+ pos = end;
+
+ if (reuse_packfile->bitmap_pos < pos) {
+ off_t pack_start_off = pack_pos_to_offset(reuse_packfile->p, 0);
+ off_t pack_end_off = pack_pos_to_offset(reuse_packfile->p,
+ pos - reuse_packfile->bitmap_pos);
+
+ written += pos - reuse_packfile->bitmap_pos;
/* We're recording one chunk, not one object. */
- record_reused_object(sizeof(struct pack_header), 0);
+ record_reused_object(pack_start_off,
+ pack_start_off - (hashfile_total(out) - pack_start));
hashflush(out);
- copy_pack_data(out, reuse_packfile, w_curs,
- sizeof(struct pack_header), to_write);
+ copy_pack_data(out, reuse_packfile->p, w_curs,
+ pack_start_off, pack_end_off - pack_start_off);
display_progress(progress_state, written);
}
- return pos;
+ if (pos % BITS_IN_EWORD)
+ BUG("attempted to jump past a word boundary to %"PRIuMAX,
+ (uintmax_t)pos);
+ return pos / BITS_IN_EWORD;
}
-static void write_reused_pack(struct hashfile *f)
+static void write_reused_pack(struct bitmapped_pack *reuse_packfile,
+ struct hashfile *f)
{
- size_t i = 0;
+ size_t i = reuse_packfile->bitmap_pos / BITS_IN_EWORD;
uint32_t offset;
+ off_t pack_start = hashfile_total(f) - sizeof(struct pack_header);
struct pack_window *w_curs = NULL;
if (allow_ofs_delta)
- i = write_reused_pack_verbatim(f, &w_curs);
+ i = write_reused_pack_verbatim(reuse_packfile, f, pack_start,
+ &w_curs);
for (; i < reuse_packfile_bitmap->word_alloc; ++i) {
eword_t word = reuse_packfile_bitmap->words[i];
@@ -1133,16 +1194,23 @@ static void write_reused_pack(struct hashfile *f)
break;
offset += ewah_bit_ctz64(word >> offset);
+ if (pos + offset < reuse_packfile->bitmap_pos)
+ continue;
+ if (pos + offset >= reuse_packfile->bitmap_pos + reuse_packfile->bitmap_nr)
+ goto done;
/*
* Can use bit positions directly, even for MIDX
* bitmaps. See comment in try_partial_reuse()
* for why.
*/
- write_reused_pack_one(pos + offset, f, &w_curs);
+ write_reused_pack_one(reuse_packfile->p,
+ pos + offset - reuse_packfile->bitmap_pos,
+ f, pack_start, &w_curs);
display_progress(progress_state, ++written);
}
}
+done:
unuse_pack(&w_curs);
}
@@ -1194,9 +1262,14 @@ static void write_pack_file(void)
offset = write_pack_header(f, nr_remaining);
- if (reuse_packfile) {
+ if (reuse_packfiles_nr) {
assert(pack_to_stdout);
- write_reused_pack(f);
+ for (j = 0; j < reuse_packfiles_nr; j++) {
+ reused_chunks_nr = 0;
+ write_reused_pack(&reuse_packfiles[j], f);
+ if (reused_chunks_nr)
+ reuse_packfiles_used_nr++;
+ }
offset = hashfile_total(f);
}
@@ -3172,7 +3245,19 @@ static int git_pack_config(const char *k, const char *v,
return 0;
}
if (!strcmp(k, "pack.allowpackreuse")) {
- allow_pack_reuse = git_config_bool(k, v);
+ int res = git_parse_maybe_bool_text(v);
+ if (res < 0) {
+ if (!strcasecmp(v, "single"))
+ allow_pack_reuse = SINGLE_PACK_REUSE;
+ else if (!strcasecmp(v, "multi"))
+ allow_pack_reuse = MULTI_PACK_REUSE;
+ else
+ die(_("invalid pack.allowPackReuse value: '%s'"), v);
+ } else if (res) {
+ allow_pack_reuse = SINGLE_PACK_REUSE;
+ } else {
+ allow_pack_reuse = NO_PACK_REUSE;
+ }
return 0;
}
if (!strcmp(k, "pack.threads")) {
@@ -3931,7 +4016,7 @@ static void loosen_unused_packed_objects(void)
*/
static int pack_options_allow_reuse(void)
{
- return allow_pack_reuse &&
+ return allow_pack_reuse != NO_PACK_REUSE &&
pack_to_stdout &&
!ignore_packed_keep_on_disk &&
!ignore_packed_keep_in_core &&
@@ -3944,13 +4029,18 @@ static int get_object_list_from_bitmap(struct rev_info *revs)
if (!(bitmap_git = prepare_bitmap_walk(revs, 0)))
return -1;
- if (pack_options_allow_reuse() &&
- !reuse_partial_packfile_from_bitmap(
- bitmap_git,
- &reuse_packfile,
- &reuse_packfile_objects,
- &reuse_packfile_bitmap)) {
- assert(reuse_packfile_objects);
+ if (pack_options_allow_reuse())
+ reuse_partial_packfile_from_bitmap(bitmap_git,
+ &reuse_packfiles,
+ &reuse_packfiles_nr,
+ &reuse_packfile_bitmap,
+ allow_pack_reuse == MULTI_PACK_REUSE);
+
+ if (reuse_packfiles) {
+ reuse_packfile_objects = bitmap_popcount(reuse_packfile_bitmap);
+ if (!reuse_packfile_objects)
+ BUG("expected non-empty reuse bitmap");
+
nr_result += reuse_packfile_objects;
nr_seen += reuse_packfile_objects;
display_progress(progress_state, nr_seen);
@@ -4518,11 +4608,20 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
fprintf_ln(stderr,
_("Total %"PRIu32" (delta %"PRIu32"),"
" reused %"PRIu32" (delta %"PRIu32"),"
- " pack-reused %"PRIu32),
+ " pack-reused %"PRIu32" (from %"PRIuMAX")"),
written, written_delta, reused, reused_delta,
- reuse_packfile_objects);
+ reuse_packfile_objects,
+ (uintmax_t)reuse_packfiles_used_nr);
+
+ trace2_data_intmax("pack-objects", the_repository, "written", written);
+ trace2_data_intmax("pack-objects", the_repository, "written/delta", written_delta);
+ trace2_data_intmax("pack-objects", the_repository, "reused", reused);
+ trace2_data_intmax("pack-objects", the_repository, "reused/delta", reused_delta);
+ trace2_data_intmax("pack-objects", the_repository, "pack-reused", reuse_packfile_objects);
+ trace2_data_intmax("pack-objects", the_repository, "packs-reused", reuse_packfiles_used_nr);
cleanup:
+ clear_packing_data(&to_pack);
list_objects_filter_release(&filter_options);
strvec_clear(&rp);
diff --git a/builtin/rebase.c b/builtin/rebase.c
index 995818c28d..5b086f651a 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -515,7 +515,7 @@ static int finish_rebase(struct rebase_options *opts)
int ret = 0;
delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
- unlink(git_path_auto_merge(the_repository));
+ delete_ref(NULL, "AUTO_MERGE", NULL, REF_NO_DEREF);
apply_autostash(state_dir_path("autostash", opts));
/*
* We ignore errors in 'git maintenance run --auto', since the
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index 917f122440..d08987646a 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -1062,6 +1062,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
puts(the_hash_algo->name);
continue;
}
+ if (!strcmp(arg, "--show-ref-format")) {
+ puts(ref_storage_format_to_name(the_repository->ref_storage_format));
+ continue;
+ }
if (!strcmp(arg, "--end-of-options")) {
seen_end_of_options = 1;
if (filter & (DO_FLAGS | DO_REVS))
diff --git a/builtin/send-pack.c b/builtin/send-pack.c
index b7183be970..3df9eaad09 100644
--- a/builtin/send-pack.c
+++ b/builtin/send-pack.c
@@ -333,6 +333,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
}
if (!ret && !transport_refs_pushed(remote_refs))
+ /* stable plumbing output; do not modify or localize */
fprintf(stderr, "Everything up-to-date\n");
return ret;
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index aaa2c39b2f..79955c2856 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -238,7 +238,7 @@ static int cmd_show_ref__exists(const char **refs)
if (refs_read_raw_ref(get_main_ref_store(the_repository), ref,
&unused_oid, &unused_referent, &unused_type,
&failure_errno)) {
- if (failure_errno == ENOENT) {
+ if (failure_errno == ENOENT || failure_errno == EISDIR) {
error(_("reference does not exist"));
ret = 2;
} else {
diff --git a/builtin/var.c b/builtin/var.c
index 8cf7dd9e2e..cf5567208a 100644
--- a/builtin/var.c
+++ b/builtin/var.c
@@ -90,7 +90,7 @@ static char *git_config_val_global(int ident_flag UNUSED)
char *user, *xdg;
size_t unused;
- git_global_config(&user, &xdg);
+ git_global_config_paths(&user, &xdg);
if (xdg && *xdg) {
normalize_path_copy(xdg, xdg);
strbuf_addf(&buf, "%s\n", xdg);
diff --git a/builtin/worktree.c b/builtin/worktree.c
index cac83a9419..9c76b62b02 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -416,7 +416,6 @@ static int add_worktree(const char *path, const char *refname,
struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
struct strbuf sb = STRBUF_INIT, realpath = STRBUF_INIT;
const char *name;
- struct child_process cp = CHILD_PROCESS_INIT;
struct strvec child_env = STRVEC_INIT;
unsigned int counter = 0;
int len, ret;
@@ -424,7 +423,8 @@ static int add_worktree(const char *path, const char *refname,
struct commit *commit = NULL;
int is_branch = 0;
struct strbuf sb_name = STRBUF_INIT;
- struct worktree **worktrees;
+ struct worktree **worktrees, *wt = NULL;
+ struct ref_store *wt_refs;
worktrees = get_worktrees();
check_candidate_path(path, opts->force, worktrees, "add");
@@ -495,21 +495,33 @@ static int add_worktree(const char *path, const char *refname,
strbuf_realpath(&realpath, get_git_common_dir(), 1);
write_file(sb_git.buf, "gitdir: %s/worktrees/%s",
realpath.buf, name);
- /*
- * This is to keep resolve_ref() happy. We need a valid HEAD
- * or is_git_directory() will reject the directory. Any value which
- * looks like an object ID will do since it will be immediately
- * replaced by the symbolic-ref or update-ref invocation in the new
- * worktree.
- */
- strbuf_reset(&sb);
- strbuf_addf(&sb, "%s/HEAD", sb_repo.buf);
- write_file(sb.buf, "%s", oid_to_hex(null_oid()));
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
write_file(sb.buf, "../..");
/*
+ * Set up the ref store of the worktree and create the HEAD reference.
+ */
+ wt = get_linked_worktree(name, 1);
+ if (!wt) {
+ ret = error(_("could not find created worktree '%s'"), name);
+ goto done;
+ }
+ wt_refs = get_worktree_ref_store(wt);
+
+ ret = refs_init_db(wt_refs, REFS_INIT_DB_IS_WORKTREE, &sb);
+ if (ret)
+ goto done;
+
+ if (!is_branch && commit)
+ ret = refs_update_ref(wt_refs, NULL, "HEAD", &commit->object.oid,
+ NULL, 0, UPDATE_REFS_MSG_ON_ERR);
+ else
+ ret = refs_create_symref(wt_refs, "HEAD", symref.buf, NULL);
+ if (ret)
+ goto done;
+
+ /*
* If the current worktree has sparse-checkout enabled, then copy
* the sparse-checkout patterns from the current worktree.
*/
@@ -526,22 +538,6 @@ static int add_worktree(const char *path, const char *refname,
strvec_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf);
strvec_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path);
- cp.git_cmd = 1;
-
- if (!is_branch && commit) {
- strvec_pushl(&cp.args, "update-ref", "HEAD",
- oid_to_hex(&commit->object.oid), NULL);
- } else {
- strvec_pushl(&cp.args, "symbolic-ref", "HEAD",
- symref.buf, NULL);
- if (opts->quiet)
- strvec_push(&cp.args, "--quiet");
- }
-
- strvec_pushv(&cp.env, child_env.v);
- ret = run_command(&cp);
- if (ret)
- goto done;
if (opts->orphan &&
(ret = make_worktree_orphan(refname, opts, &child_env)))
@@ -587,6 +583,7 @@ done:
strbuf_release(&sb_git);
strbuf_release(&sb_name);
strbuf_release(&realpath);
+ free_worktree(wt);
return ret;
}
@@ -851,21 +848,21 @@ static int add(int ac, const char **av, const char *prefix)
const char *s = worktree_basename(path, &n);
new_branch = xstrndup(s, n);
} else if (opts.orphan) {
- // No-op
+ ; /* no-op */
} else if (opts.detach) {
- // Check HEAD
+ /* Check HEAD */
if (!strcmp(branch, "HEAD"))
can_use_local_refs(&opts);
} else if (ac < 2 && new_branch) {
- // DWIM: Infer --orphan when repo has no refs.
+ /* DWIM: Infer --orphan when repo has no refs. */
opts.orphan = dwim_orphan(&opts, !!opt_track, 0);
} else if (ac < 2) {
- // DWIM: Guess branch name from path.
+ /* DWIM: Guess branch name from path. */
const char *s = dwim_branch(path, &new_branch);
if (s)
branch = s;
- // DWIM: Infer --orphan when repo has no refs.
+ /* DWIM: Infer --orphan when repo has no refs. */
opts.orphan = (!s) && dwim_orphan(&opts, !!opt_track, 1);
} else if (ac == 2) {
struct object_id oid;
diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh
index 4f407530d3..b4e22de3cb 100755
--- a/ci/install-dependencies.sh
+++ b/ci/install-dependencies.sh
@@ -37,15 +37,13 @@ macos-*)
test -z "$BREW_INSTALL_PACKAGES" ||
brew install $BREW_INSTALL_PACKAGES
brew link --force gettext
- mkdir -p $HOME/bin
- (
- cd $HOME/bin
+
+ mkdir -p "$P4_PATH"
+ pushd "$P4_PATH"
wget -q "$P4WHENCE/bin.macosx1015x86_64/helix-core-server.tgz" &&
tar -xf helix-core-server.tgz &&
sudo xattr -d com.apple.quarantine p4 p4d 2>/dev/null || true
- )
- PATH="$PATH:${HOME}/bin"
- export PATH
+ popd
if test -n "$CC_PACKAGE"
then
diff --git a/ci/install-docker-dependencies.sh b/ci/install-docker-dependencies.sh
index 48c43f0f90..eb2c9e1eca 100755
--- a/ci/install-docker-dependencies.sh
+++ b/ci/install-docker-dependencies.sh
@@ -21,7 +21,7 @@ linux-musl)
apache2 apache2-http2 apache2-proxy apache2-ssl apache2-webdav apr-util-dbd_sqlite3 \
bash cvs gnupg perl-cgi perl-dbd-sqlite >/dev/null
;;
-linux-*)
+linux-*|StaticAnalysis)
# Required so that apt doesn't wait for user input on certain packages.
export DEBIAN_FRONTEND=noninteractive
@@ -31,6 +31,11 @@ linux-*)
perl-modules liberror-perl libauthen-sasl-perl libemail-valid-perl \
libdbd-sqlite3-perl libio-socket-ssl-perl libnet-smtp-ssl-perl ${CC_PACKAGE:-${CC:-gcc}} \
apache2 cvs cvsps gnupg libcgi-pm-perl subversion
+
+ if test "$jobname" = StaticAnalysis
+ then
+ apt install -q -y coccinelle
+ fi
;;
pedantic)
dnf -yq update >/dev/null &&
diff --git a/ci/lib.sh b/ci/lib.sh
index c749b21366..d5dd2f2697 100755
--- a/ci/lib.sh
+++ b/ci/lib.sh
@@ -252,7 +252,14 @@ then
CI_COMMIT="$CI_COMMIT_SHA"
case "$CI_JOB_IMAGE" in
macos-*)
- CI_OS_NAME=osx;;
+ # GitLab CI has Python installed via multiple package managers,
+ # most notably via asdf and Homebrew. Ensure that our builds
+ # pick up the Homebrew one by prepending it to our PATH as the
+ # asdf one breaks tests.
+ export PATH="$(brew --prefix)/bin:$PATH"
+
+ CI_OS_NAME=osx
+ ;;
alpine:*|fedora:*|ubuntu:*)
CI_OS_NAME=linux;;
*)
@@ -344,6 +351,9 @@ macos-*)
then
MAKEFLAGS="$MAKEFLAGS APPLE_COMMON_CRYPTO_SHA1=Yes"
fi
+
+ P4_PATH="$HOME/custom/p4"
+ export PATH="$P4_PATH:$PATH"
;;
esac
diff --git a/ci/print-test-failures.sh b/ci/print-test-failures.sh
index c33ad4e3a2..b1f80aeac3 100755
--- a/ci/print-test-failures.sh
+++ b/ci/print-test-failures.sh
@@ -8,7 +8,7 @@
# Tracing executed commands would produce too much noise in the loop below.
set +x
-cd t/
+cd "${TEST_OUTPUT_DIRECTORY:-t/}"
if ! ls test-results/*.exit >/dev/null 2>/dev/null
then
diff --git a/ci/run-build-and-minimal-fuzzers.sh b/ci/run-build-and-minimal-fuzzers.sh
new file mode 100755
index 0000000000..8ba486f659
--- /dev/null
+++ b/ci/run-build-and-minimal-fuzzers.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+#
+# Build and test Git's fuzzers
+#
+
+. ${0%/*}/lib.sh
+
+group "Build fuzzers" make \
+ CC=clang \
+ CXX=clang++ \
+ CFLAGS="-fsanitize=fuzzer-no-link,address" \
+ LIB_FUZZING_ENGINE="-fsanitize=fuzzer,address" \
+ fuzz-all
+
+for fuzzer in commit-graph date pack-headers pack-idx ; do
+ begin_group "fuzz-$fuzzer"
+ ./oss-fuzz/fuzz-$fuzzer -verbosity=0 -runs=1 || exit 1
+ end_group "fuzz-$fuzzer"
+done
diff --git a/commit-graph.c b/commit-graph.c
index bba316913c..45417d7412 100644
--- a/commit-graph.c
+++ b/commit-graph.c
@@ -806,6 +806,9 @@ struct bloom_filter_settings *get_bloom_filter_settings(struct repository *r)
void close_commit_graph(struct raw_object_store *o)
{
+ if (!o->commit_graph)
+ return;
+
clear_commit_graph_data_slab(&commit_graph_data_slab);
free_commit_graph(o->commit_graph);
o->commit_graph = NULL;
@@ -2616,19 +2619,16 @@ cleanup:
oid_array_clear(&ctx->oids);
clear_topo_level_slab(&topo_levels);
- if (ctx->commit_graph_filenames_after) {
- for (i = 0; i < ctx->num_commit_graphs_after; i++) {
- free(ctx->commit_graph_filenames_after[i]);
- free(ctx->commit_graph_hash_after[i]);
- }
-
- for (i = 0; i < ctx->num_commit_graphs_before; i++)
- free(ctx->commit_graph_filenames_before[i]);
+ for (i = 0; i < ctx->num_commit_graphs_before; i++)
+ free(ctx->commit_graph_filenames_before[i]);
+ free(ctx->commit_graph_filenames_before);
- free(ctx->commit_graph_filenames_after);
- free(ctx->commit_graph_filenames_before);
- free(ctx->commit_graph_hash_after);
+ for (i = 0; i < ctx->num_commit_graphs_after; i++) {
+ free(ctx->commit_graph_filenames_after[i]);
+ free(ctx->commit_graph_hash_after[i]);
}
+ free(ctx->commit_graph_filenames_after);
+ free(ctx->commit_graph_hash_after);
free(ctx);
diff --git a/compat/mingw.c b/compat/mingw.c
index 42053c1f65..238a84ddba 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -2684,6 +2684,30 @@ static PSID get_current_user_sid(void)
return result;
}
+static BOOL user_sid_to_user_name(PSID sid, LPSTR *str)
+{
+ SID_NAME_USE pe_use;
+ DWORD len_user = 0, len_domain = 0;
+ BOOL translate_sid_to_user;
+
+ /*
+ * returns only FALSE, because the string pointers are NULL
+ */
+ LookupAccountSidA(NULL, sid, NULL, &len_user, NULL, &len_domain,
+ &pe_use);
+ /*
+ * Alloc needed space of the strings
+ */
+ ALLOC_ARRAY((*str), (size_t)len_domain + (size_t)len_user);
+ translate_sid_to_user = LookupAccountSidA(NULL, sid,
+ (*str) + len_domain, &len_user, *str, &len_domain, &pe_use);
+ if (!translate_sid_to_user)
+ FREE_AND_NULL(*str);
+ else
+ (*str)[len_domain] = '/';
+ return translate_sid_to_user;
+}
+
static int acls_supported(const char *path)
{
size_t offset = offset_1st_component(path);
@@ -2765,27 +2789,47 @@ int is_path_owned_by_current_sid(const char *path, struct strbuf *report)
strbuf_addf(report, "'%s' is on a file system that does "
"not record ownership\n", path);
} else if (report) {
- LPSTR str1, str2, to_free1 = NULL, to_free2 = NULL;
+ LPSTR str1, str2, str3, str4, to_free1 = NULL,
+ to_free3 = NULL, to_local_free2 = NULL,
+ to_local_free4 = NULL;
- if (ConvertSidToStringSidA(sid, &str1))
+ if (user_sid_to_user_name(sid, &str1))
to_free1 = str1;
else
str1 = "(inconvertible)";
-
- if (!current_user_sid)
- str2 = "(none)";
- else if (!IsValidSid(current_user_sid))
- str2 = "(invalid)";
- else if (ConvertSidToStringSidA(current_user_sid, &str2))
- to_free2 = str2;
+ if (ConvertSidToStringSidA(sid, &str2))
+ to_local_free2 = str2;
else
str2 = "(inconvertible)";
+
+ if (!current_user_sid) {
+ str3 = "(none)";
+ str4 = "(none)";
+ }
+ else if (!IsValidSid(current_user_sid)) {
+ str3 = "(invalid)";
+ str4 = "(invalid)";
+ } else {
+ if (user_sid_to_user_name(current_user_sid,
+ &str3))
+ to_free3 = str3;
+ else
+ str3 = "(inconvertible)";
+ if (ConvertSidToStringSidA(current_user_sid,
+ &str4))
+ to_local_free4 = str4;
+ else
+ str4 = "(inconvertible)";
+ }
strbuf_addf(report,
"'%s' is owned by:\n"
- "\t'%s'\nbut the current user is:\n"
- "\t'%s'\n", path, str1, str2);
- LocalFree(to_free1);
- LocalFree(to_free2);
+ "\t%s (%s)\nbut the current user is:\n"
+ "\t%s (%s)\n",
+ path, str1, str2, str3, str4);
+ free(to_free1);
+ LocalFree(to_local_free2);
+ free(to_free3);
+ LocalFree(to_local_free4);
}
}
diff --git a/config.c b/config.c
index 9ff6ae1cb9..3cfeb3d8bd 100644
--- a/config.c
+++ b/config.c
@@ -95,7 +95,6 @@ static long config_file_ftell(struct config_source *conf)
return ftell(conf->u.file);
}
-
static int config_buf_fgetc(struct config_source *conf)
{
if (conf->u.buf.pos < conf->u.buf.len)
@@ -1988,7 +1987,27 @@ char *git_system_config(void)
return system_config;
}
-void git_global_config(char **user_out, char **xdg_out)
+char *git_global_config(void)
+{
+ char *user_config, *xdg_config;
+
+ git_global_config_paths(&user_config, &xdg_config);
+ if (!user_config) {
+ free(xdg_config);
+ return NULL;
+ }
+
+ if (access_or_warn(user_config, R_OK, 0) && xdg_config &&
+ !access_or_warn(xdg_config, R_OK, 0)) {
+ free(user_config);
+ return xdg_config;
+ } else {
+ free(xdg_config);
+ return user_config;
+ }
+}
+
+void git_global_config_paths(char **user_out, char **xdg_out)
{
char *user_config = xstrdup_or_null(getenv("GIT_CONFIG_GLOBAL"));
char *xdg_config = NULL;
@@ -2041,7 +2060,7 @@ static int do_git_config_sequence(const struct config_options *opts,
data, CONFIG_SCOPE_SYSTEM,
NULL);
- git_global_config(&user_config, &xdg_config);
+ git_global_config_paths(&user_config, &xdg_config);
if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))
ret += git_config_from_file_with_options(fn, xdg_config, data,
@@ -3418,7 +3437,6 @@ out_free:
write_err_out:
ret = write_error(get_lock_file_path(&lock));
goto out_free;
-
}
void git_config_set_multivar_in_file(const char *config_filename,
diff --git a/config.h b/config.h
index 14f881ecfa..5dba984f77 100644
--- a/config.h
+++ b/config.h
@@ -382,7 +382,8 @@ int config_error_nonbool(const char *);
#endif
char *git_system_config(void);
-void git_global_config(char **user, char **xdg);
+char *git_global_config(void);
+void git_global_config_paths(char **user, char **xdg);
int git_config_parse_parameter(const char *, config_fn_t fn, void *data);
diff --git a/config.mak.uname b/config.mak.uname
index 3bb03f423a..dacc95172d 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -158,6 +158,19 @@ ifeq ($(uname_S),Darwin)
ifeq ($(shell test -x /usr/local/opt/gettext/bin/msgfmt && echo y),y)
MSGFMT = /usr/local/opt/gettext/bin/msgfmt
endif
+ # On newer ARM-based machines the default installation path has changed to
+ # /opt/homebrew. Include it in our search paths so that the user does not
+ # have to configure this manually.
+ #
+ # Note that we do not employ the same workaround as above where we manually
+ # add gettext. The issue was fixed more than three years ago by now, and at
+ # that point there haven't been any ARM-based Macs yet.
+ else ifeq ($(shell test -d /opt/homebrew/ && echo y),y)
+ BASIC_CFLAGS += -I/opt/homebrew/include
+ BASIC_LDFLAGS += -L/opt/homebrew/lib
+ ifeq ($(shell test -x /opt/homebrew/bin/msgfmt && echo y),y)
+ MSGFMT = /opt/homebrew/bin/msgfmt
+ endif
endif
# The builtin FSMonitor on MacOS builds upon Simple-IPC. Both require
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 8c40ade494..c3408d4143 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -137,6 +137,9 @@ __git_eread ()
__git_pseudoref_exists ()
{
local ref=$1
+ local head
+
+ __git_find_repo_path
# If the reftable is in use, we have to shell out to 'git rev-parse'
# to determine whether the ref exists instead of looking directly in
@@ -144,9 +147,8 @@ __git_pseudoref_exists ()
# Bash builtins since executing Git commands are expensive on some
# platforms.
if __git_eread "$__git_repo_path/HEAD" head; then
- b="${head#ref: }"
- if [ "$b" == "refs/heads/.invalid" ]; then
- __git -C "$__git_repo_path" rev-parse --verify --quiet "$ref" 2>/dev/null
+ if [ "$head" == "ref: refs/heads/.invalid" ]; then
+ __git show-ref --exists "$ref"
return $?
fi
fi
@@ -1656,7 +1658,6 @@ __git_cherry_pick_inprogress_options=$__git_sequencer_inprogress_options
_git_cherry_pick ()
{
- __git_find_repo_path
if __git_pseudoref_exists CHERRY_PICK_HEAD; then
__gitcomp "$__git_cherry_pick_inprogress_options"
return
@@ -1807,7 +1808,7 @@ __git_diff_common_options="--stat --numstat --shortstat --summary
--output= --output-indicator-context=
--output-indicator-new= --output-indicator-old=
--ws-error-highlight=
- --pickaxe-all --pickaxe-regex
+ --pickaxe-all --pickaxe-regex --patch-with-raw
"
# Options for diff/difftool
@@ -2071,6 +2072,16 @@ __git_log_common_options="
--min-age= --until= --before=
--min-parents= --max-parents=
--no-min-parents --no-max-parents
+ --alternate-refs --ancestry-path
+ --author-date-order --basic-regexp
+ --bisect --boundary --exclude-first-parent-only
+ --exclude-hidden --extended-regexp
+ --fixed-strings --grep-reflog
+ --ignore-missing --left-only --perl-regexp
+ --reflog --regexp-ignore-case --remove-empty
+ --right-only --show-linear-break
+ --show-notes-by-default --show-pulls
+ --since-as-filter --single-worktree
"
# Options that go well for log and gitk (not shortlog)
__git_log_gitk_options="
@@ -2086,6 +2097,7 @@ __git_log_shortlog_options="
# Options accepted by log and show
__git_log_show_options="
--diff-merges --diff-merges= --no-diff-merges --dd --remerge-diff
+ --encoding=
"
__git_diff_merges_opts="off none on first-parent 1 separate m combined c dense-combined cc remerge r"
@@ -2169,6 +2181,8 @@ _git_log ()
--no-walk --no-walk= --do-walk
--parents --children
--expand-tabs --expand-tabs= --no-expand-tabs
+ --clear-decorations --decorate-refs=
+ --decorate-refs-exclude=
$merge
$__git_diff_common_options
"
@@ -2966,7 +2980,6 @@ _git_reset ()
_git_restore ()
{
- __git_find_repo_path
case "$prev" in
-s)
__git_complete_refs
@@ -2995,7 +3008,6 @@ __git_revert_inprogress_options=$__git_sequencer_inprogress_options
_git_revert ()
{
- __git_find_repo_path
if __git_pseudoref_exists REVERT_HEAD; then
__gitcomp "$__git_revert_inprogress_options"
return
diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh
index 2c030050ae..71f179cba3 100644
--- a/contrib/completion/git-prompt.sh
+++ b/contrib/completion/git-prompt.sh
@@ -408,7 +408,7 @@ __git_ps1 ()
local repo_info rev_parse_exit_code
repo_info="$(git rev-parse --git-dir --is-inside-git-dir \
- --is-bare-repository --is-inside-work-tree \
+ --is-bare-repository --is-inside-work-tree --show-ref-format \
--short HEAD 2>/dev/null)"
rev_parse_exit_code="$?"
@@ -421,6 +421,8 @@ __git_ps1 ()
short_sha="${repo_info##*$'\n'}"
repo_info="${repo_info%$'\n'*}"
fi
+ local ref_format="${repo_info##*$'\n'}"
+ repo_info="${repo_info%$'\n'*}"
local inside_worktree="${repo_info##*$'\n'}"
repo_info="${repo_info%$'\n'*}"
local bare_repo="${repo_info##*$'\n'}"
@@ -479,12 +481,25 @@ __git_ps1 ()
b="$(git symbolic-ref HEAD 2>/dev/null)"
else
local head=""
- if ! __git_eread "$g/HEAD" head; then
- return $exit
- fi
- # is it a symbolic ref?
- b="${head#ref: }"
- if [ "$head" = "$b" ]; then
+
+ case "$ref_format" in
+ files)
+ if ! __git_eread "$g/HEAD" head; then
+ return $exit
+ fi
+
+ if [[ $head == "ref: "* ]]; then
+ head="${head#ref: }"
+ else
+ head=""
+ fi
+ ;;
+ *)
+ head="$(git symbolic-ref HEAD 2>/dev/null)"
+ ;;
+ esac
+
+ if test -z "$head"; then
detached=yes
b="$(
case "${GIT_PS1_DESCRIBE_STYLE-}" in
@@ -502,6 +517,8 @@ __git_ps1 ()
b="$short_sha..."
b="($b)"
+ else
+ b="$head"
fi
fi
fi
diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh
index 3028029ac2..5dab3f506c 100755
--- a/contrib/subtree/git-subtree.sh
+++ b/contrib/subtree/git-subtree.sh
@@ -787,6 +787,22 @@ ensure_valid_ref_format () {
die "fatal: '$1' does not look like a ref"
}
+# Usage: check if a commit from another subtree should be
+# ignored from processing for splits
+should_ignore_subtree_split_commit () {
+ assert test $# = 1
+ local rev="$1"
+ if test -n "$(git log -1 --grep="git-subtree-dir:" $rev)"
+ then
+ if test -z "$(git log -1 --grep="git-subtree-mainline:" $rev)" &&
+ test -z "$(git log -1 --grep="git-subtree-dir: $arg_prefix$" $rev)"
+ then
+ return 0
+ fi
+ fi
+ return 1
+}
+
# Usage: process_split_commit REV PARENTS
process_split_commit () {
assert test $# = 2
@@ -972,7 +988,19 @@ cmd_split () {
eval "$grl" |
while read rev parents
do
- process_split_commit "$rev" "$parents"
+ if should_ignore_subtree_split_commit "$rev"
+ then
+ continue
+ fi
+ parsedparents=''
+ for parent in $parents
+ do
+ if ! should_ignore_subtree_split_commit "$parent"
+ then
+ parsedparents="$parsedparents$parent "
+ fi
+ done
+ process_split_commit "$rev" "$parsedparents"
done || exit $?
latest_new=$(cache_get latest_new) || exit $?
diff --git a/contrib/subtree/t/t7900-subtree.sh b/contrib/subtree/t/t7900-subtree.sh
index 49a21dd7c9..ca4df5be83 100755
--- a/contrib/subtree/t/t7900-subtree.sh
+++ b/contrib/subtree/t/t7900-subtree.sh
@@ -385,6 +385,46 @@ test_expect_success 'split sub dir/ with --rejoin' '
)
'
+# Tests that commits from other subtrees are not processed as
+# part of a split.
+#
+# This test performs the following:
+# - Creates Repo with subtrees 'subA' and 'subB'
+# - Creates commits in the repo including changes to subtrees
+# - Runs the following 'split' and commit' commands in order:
+# - Perform 'split' on subtree A
+# - Perform 'split' on subtree B
+# - Create new commits with changes to subtree A and B
+# - Perform split on subtree A
+# - Check that the commits in subtree B are not processed
+# as part of the subtree A split
+test_expect_success 'split with multiple subtrees' '
+ subtree_test_create_repo "$test_count" &&
+ subtree_test_create_repo "$test_count/subA" &&
+ subtree_test_create_repo "$test_count/subB" &&
+ test_create_commit "$test_count" main1 &&
+ test_create_commit "$test_count/subA" subA1 &&
+ test_create_commit "$test_count/subA" subA2 &&
+ test_create_commit "$test_count/subA" subA3 &&
+ test_create_commit "$test_count/subB" subB1 &&
+ git -C "$test_count" fetch ./subA HEAD &&
+ git -C "$test_count" subtree add --prefix=subADir FETCH_HEAD &&
+ git -C "$test_count" fetch ./subB HEAD &&
+ git -C "$test_count" subtree add --prefix=subBDir FETCH_HEAD &&
+ test_create_commit "$test_count" subADir/main-subA1 &&
+ test_create_commit "$test_count" subBDir/main-subB1 &&
+ git -C "$test_count" subtree split --prefix=subADir \
+ --squash --rejoin -m "Sub A Split 1" &&
+ git -C "$test_count" subtree split --prefix=subBDir \
+ --squash --rejoin -m "Sub B Split 1" &&
+ test_create_commit "$test_count" subADir/main-subA2 &&
+ test_create_commit "$test_count" subBDir/main-subB2 &&
+ git -C "$test_count" subtree split --prefix=subADir \
+ --squash --rejoin -m "Sub A Split 2" &&
+ test "$(git -C "$test_count" subtree split --prefix=subBDir \
+ --squash --rejoin -d -m "Sub B Split 1" 2>&1 | grep -w "\[1\]")" = ""
+'
+
test_expect_success 'split sub dir/ with --rejoin from scratch' '
subtree_test_create_repo "$test_count" &&
test_create_commit "$test_count" main1 &&
diff --git a/diff.c b/diff.c
index a89a6a6128..ccfa1fca0d 100644
--- a/diff.c
+++ b/diff.c
@@ -4384,7 +4384,8 @@ static void run_external_diff(const char *pgm,
add_external_diff_name(o->repo, &cmd.args, two);
if (other) {
strvec_push(&cmd.args, other);
- strvec_push(&cmd.args, xfrm_msg);
+ if (xfrm_msg)
+ strvec_push(&cmd.args, xfrm_msg);
}
}
diff --git a/diffcore-delta.c b/diffcore-delta.c
index 4927ab8fb0..ba6cbee76b 100644
--- a/diffcore-delta.c
+++ b/diffcore-delta.c
@@ -158,6 +158,10 @@ static struct spanhash_top *hash_chars(struct repository *r,
n = 0;
accum1 = accum2 = 0;
}
+ if (n > 0) {
+ hashval = (accum1 + accum2 * 0x61) % HASHBASE;
+ hash = add_spanhash(hash, hashval, n);
+ }
QSORT(hash->data, (size_t)1ul << hash->alloc_log2, spanhash_cmp);
return hash;
}
diff --git a/ewah/bitmap.c b/ewah/bitmap.c
index 7b525b1ecd..ac7e0af622 100644
--- a/ewah/bitmap.c
+++ b/ewah/bitmap.c
@@ -169,6 +169,15 @@ size_t bitmap_popcount(struct bitmap *self)
return count;
}
+int bitmap_is_empty(struct bitmap *self)
+{
+ size_t i;
+ for (i = 0; i < self->word_alloc; i++)
+ if (self->words[i])
+ return 0;
+ return 1;
+}
+
int bitmap_equals(struct bitmap *self, struct bitmap *other)
{
struct bitmap *big, *small;
diff --git a/ewah/ewok.h b/ewah/ewok.h
index 7eb8b9b630..c11d76c6f3 100644
--- a/ewah/ewok.h
+++ b/ewah/ewok.h
@@ -189,5 +189,6 @@ void bitmap_or_ewah(struct bitmap *self, struct ewah_bitmap *other);
void bitmap_or(struct bitmap *self, const struct bitmap *other);
size_t bitmap_popcount(struct bitmap *self);
+int bitmap_is_empty(struct bitmap *self);
#endif
diff --git a/fetch-pack.c b/fetch-pack.c
index 5b8aa0adc7..091f9a80a9 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -2216,7 +2216,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,
the_repository, "%d",
negotiation_round);
}
- trace2_region_enter("fetch-pack", "negotiate_using_fetch", the_repository);
+ trace2_region_leave("fetch-pack", "negotiate_using_fetch", the_repository);
trace2_data_intmax("negotiate_using_fetch", the_repository,
"total_rounds", negotiation_round);
clear_common_flag(acked_commits);
diff --git a/fsck.c b/fsck.c
index 1ad02fcdfa..8ded0a473a 100644
--- a/fsck.c
+++ b/fsck.c
@@ -20,7 +20,6 @@
#include "packfile.h"
#include "submodule-config.h"
#include "config.h"
-#include "credential.h"
#include "help.h"
static ssize_t max_tree_entry_len = 4096;
@@ -1047,138 +1046,6 @@ done:
return ret;
}
-static int starts_with_dot_slash(const char *const path)
-{
- return path_match_flags(path, PATH_MATCH_STARTS_WITH_DOT_SLASH |
- PATH_MATCH_XPLATFORM);
-}
-
-static int starts_with_dot_dot_slash(const char *const path)
-{
- return path_match_flags(path, PATH_MATCH_STARTS_WITH_DOT_DOT_SLASH |
- PATH_MATCH_XPLATFORM);
-}
-
-static int submodule_url_is_relative(const char *url)
-{
- return starts_with_dot_slash(url) || starts_with_dot_dot_slash(url);
-}
-
-/*
- * Count directory components that a relative submodule URL should chop
- * from the remote_url it is to be resolved against.
- *
- * In other words, this counts "../" components at the start of a
- * submodule URL.
- *
- * Returns the number of directory components to chop and writes a
- * pointer to the next character of url after all leading "./" and
- * "../" components to out.
- */
-static int count_leading_dotdots(const char *url, const char **out)
-{
- int result = 0;
- while (1) {
- if (starts_with_dot_dot_slash(url)) {
- result++;
- url += strlen("../");
- continue;
- }
- if (starts_with_dot_slash(url)) {
- url += strlen("./");
- continue;
- }
- *out = url;
- return result;
- }
-}
-/*
- * Check whether a transport is implemented by git-remote-curl.
- *
- * If it is, returns 1 and writes the URL that would be passed to
- * git-remote-curl to the "out" parameter.
- *
- * Otherwise, returns 0 and leaves "out" untouched.
- *
- * Examples:
- * http::https://example.com/repo.git -> 1, https://example.com/repo.git
- * https://example.com/repo.git -> 1, https://example.com/repo.git
- * git://example.com/repo.git -> 0
- *
- * This is for use in checking for previously exploitable bugs that
- * required a submodule URL to be passed to git-remote-curl.
- */
-static int url_to_curl_url(const char *url, const char **out)
-{
- /*
- * We don't need to check for case-aliases, "http.exe", and so
- * on because in the default configuration, is_transport_allowed
- * prevents URLs with those schemes from being cloned
- * automatically.
- */
- if (skip_prefix(url, "http::", out) ||
- skip_prefix(url, "https::", out) ||
- skip_prefix(url, "ftp::", out) ||
- skip_prefix(url, "ftps::", out))
- return 1;
- if (starts_with(url, "http://") ||
- starts_with(url, "https://") ||
- starts_with(url, "ftp://") ||
- starts_with(url, "ftps://")) {
- *out = url;
- return 1;
- }
- return 0;
-}
-
-static int check_submodule_url(const char *url)
-{
- const char *curl_url;
-
- if (looks_like_command_line_option(url))
- return -1;
-
- if (submodule_url_is_relative(url) || starts_with(url, "git://")) {
- char *decoded;
- const char *next;
- int has_nl;
-
- /*
- * This could be appended to an http URL and url-decoded;
- * check for malicious characters.
- */
- decoded = url_decode(url);
- has_nl = !!strchr(decoded, '\n');
-
- free(decoded);
- if (has_nl)
- return -1;
-
- /*
- * URLs which escape their root via "../" can overwrite
- * the host field and previous components, resolving to
- * URLs like https::example.com/submodule.git and
- * https:///example.com/submodule.git that were
- * susceptible to CVE-2020-11008.
- */
- if (count_leading_dotdots(url, &next) > 0 &&
- (*next == ':' || *next == '/'))
- return -1;
- }
-
- else if (url_to_curl_url(url, &curl_url)) {
- struct credential c = CREDENTIAL_INIT;
- int ret = 0;
- if (credential_from_url_gently(&c, curl_url, 1) ||
- !*c.host)
- ret = -1;
- credential_clear(&c);
- return ret;
- }
-
- return 0;
-}
-
struct fsck_gitmodules_data {
const struct object_id *oid;
struct fsck_options *options;
diff --git a/git-compat-util.h b/git-compat-util.h
index 603c97e3b3..7c2a6538e5 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -1015,6 +1015,15 @@ static inline unsigned long cast_size_t_to_ulong(size_t a)
return (unsigned long)a;
}
+static inline uint32_t cast_size_t_to_uint32_t(size_t a)
+{
+ if (a != (uint32_t)a)
+ die("object too large to read on this platform: %"
+ PRIuMAX" is cut off to %u",
+ (uintmax_t)a, (uint32_t)a);
+ return (uint32_t)a;
+}
+
static inline int cast_size_t_to_int(size_t a)
{
if (a > INT_MAX)
diff --git a/git-p4.py b/git-p4.py
index 0eb3bb4c47..28ab12c72b 100755
--- a/git-p4.py
+++ b/git-p4.py
@@ -689,8 +689,8 @@ def setP4ExecBit(file, mode):
if not isModeExec(mode):
p4Type = getP4OpenedType(file)
- p4Type = re.sub('^([cku]?)x(.*)', '\\1\\2', p4Type)
- p4Type = re.sub('(.*?\+.*?)x(.*?)', '\\1\\2', p4Type)
+ p4Type = re.sub(r'^([cku]?)x(.*)', r'\1\2', p4Type)
+ p4Type = re.sub(r'(.*?\+.*?)x(.*?)', r'\1\2', p4Type)
if p4Type[-1] == "+":
p4Type = p4Type[0:-1]
@@ -701,7 +701,7 @@ def getP4OpenedType(file):
"""Returns the perforce file type for the given file."""
result = p4_read_pipe(["opened", wildcard_encode(file)])
- match = re.match(".*\((.+)\)( \*exclusive\*)?\r?$", result)
+ match = re.match(r".*\((.+)\)( \*exclusive\*)?\r?$", result)
if match:
return match.group(1)
else:
@@ -757,7 +757,7 @@ def parseDiffTreeEntry(entry):
global _diff_tree_pattern
if not _diff_tree_pattern:
- _diff_tree_pattern = re.compile(':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)')
+ _diff_tree_pattern = re.compile(r':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)')
match = _diff_tree_pattern.match(entry)
if match:
@@ -918,9 +918,9 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
if len(result) > 0:
data = result[0].get('data')
if data:
- m = re.search('Too many rows scanned \(over (\d+)\)', data)
+ m = re.search(r'Too many rows scanned \(over (\d+)\)', data)
if not m:
- m = re.search('Request too large \(over (\d+)\)', data)
+ m = re.search(r'Request too large \(over (\d+)\)', data)
if m:
limit = int(m.group(1))
@@ -1452,7 +1452,7 @@ def wildcard_encode(path):
def wildcard_present(path):
- m = re.search("[*#@%]", path)
+ m = re.search(r"[*#@%]", path)
return m is not None
@@ -3048,7 +3048,7 @@ class P4Sync(Command, P4UserMap):
# Preserve everything in relative path name except leading
# //depot/; just look at first prefix as they all should
# be in the same depot.
- depot = re.sub("^(//[^/]+/).*", r'\1', prefixes[0])
+ depot = re.sub(r"^(//[^/]+/).*", r'\1', prefixes[0])
if p4PathStartsWith(path, depot):
path = path[len(depot):]
@@ -3603,7 +3603,7 @@ class P4Sync(Command, P4UserMap):
commitFound = True
else:
gitCommit = read_pipe(["git", "rev-list", "--max-count=1",
- "--reverse", ":/\[git-p4:.*change = %d\]" % changelist], ignore_error=True)
+ "--reverse", r":/\[git-p4:.*change = %d\]" % changelist], ignore_error=True)
if len(gitCommit) == 0:
print("importing label %s: could not find git commit for changelist %d" % (name, changelist))
else:
@@ -4182,7 +4182,7 @@ class P4Sync(Command, P4UserMap):
if len(self.changesFile) == 0:
revision = "#head"
- p = re.sub("\.\.\.$", "", p)
+ p = re.sub(r"\.\.\.$", "", p)
if not p.endswith("/"):
p += "/"
@@ -4251,7 +4251,8 @@ class P4Sync(Command, P4UserMap):
if self.tempBranches != []:
for branch in self.tempBranches:
read_pipe(["git", "update-ref", "-d", branch])
- os.rmdir(os.path.join(os.environ.get("GIT_DIR", ".git"), self.tempBranchLocation))
+ if len(read_pipe(["git", "for-each-ref", self.tempBranchLocation])) > 0:
+ die("There are unexpected temporary branches")
# Create a symbolic ref p4/HEAD pointing to p4/<branch> to allow
# a convenient shortcut refname "p4".
@@ -4291,7 +4292,7 @@ class P4Rebase(Command):
die("Cannot find upstream branchpoint for rebase")
# the branchpoint may be p4/foo~3, so strip off the parent
- upstream = re.sub("~[0-9]+$", "", upstream)
+ upstream = re.sub(r"~[0-9]+$", "", upstream)
print("Rebasing the current branch onto %s" % upstream)
oldHead = read_pipe(["git", "rev-parse", "HEAD"]).strip()
@@ -4320,8 +4321,8 @@ class P4Clone(P4Sync):
def defaultDestination(self, args):
# TODO: use common prefix of args?
depotPath = args[0]
- depotDir = re.sub("(@[^@]*)$", "", depotPath)
- depotDir = re.sub("(#[^#]*)$", "", depotDir)
+ depotDir = re.sub(r"(@[^@]*)$", "", depotPath)
+ depotDir = re.sub(r"(#[^#]*)$", "", depotDir)
depotDir = re.sub(r"\.\.\.$", "", depotDir)
depotDir = re.sub(r"/$", "", depotDir)
return os.path.split(depotDir)[1]
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index fc6d5dd522..ccd14e0e30 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -728,9 +728,11 @@ our $per_request_config = 1;
sub read_config_file {
my $filename = shift;
return unless defined $filename;
- # die if there are errors parsing config file
if (-e $filename) {
do $filename;
+ # die if there is a problem accessing the file
+ die $! if $!;
+ # die if there are errors parsing config file
die $@ if $@;
return 1;
}
diff --git a/http-backend.c b/http-backend.c
index ff07b87e64..1ed1e29d07 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -38,6 +38,7 @@ struct rpc_service {
static struct rpc_service rpc_service[] = {
{ "upload-pack", "uploadpack", 1, 1 },
{ "receive-pack", "receivepack", 0, -1 },
+ { "upload-archive", "uploadarchive", 0, -1 },
};
static struct string_list *get_parameters(void)
@@ -639,10 +640,15 @@ static void check_content_type(struct strbuf *hdr, const char *accepted_type)
static void service_rpc(struct strbuf *hdr, char *service_name)
{
- const char *argv[] = {NULL, "--stateless-rpc", ".", NULL};
+ struct strvec argv = STRVEC_INIT;
struct rpc_service *svc = select_service(hdr, service_name);
struct strbuf buf = STRBUF_INIT;
+ strvec_push(&argv, svc->name);
+ if (strcmp(service_name, "git-upload-archive"))
+ strvec_push(&argv, "--stateless-rpc");
+ strvec_push(&argv, ".");
+
strbuf_reset(&buf);
strbuf_addf(&buf, "application/x-git-%s-request", svc->name);
check_content_type(hdr, buf.buf);
@@ -655,9 +661,9 @@ static void service_rpc(struct strbuf *hdr, char *service_name)
end_headers(hdr);
- argv[0] = svc->name;
- run_service(argv, svc->buffer_input);
+ run_service(argv.v, svc->buffer_input);
strbuf_release(&buf);
+ strvec_clear(&argv);
}
static int dead;
@@ -723,6 +729,7 @@ static struct service_cmd {
{"GET", "/objects/pack/pack-[0-9a-f]{64}\\.idx$", get_idx_file},
{"POST", "/git-upload-pack$", service_rpc},
+ {"POST", "/git-upload-archive$", service_rpc},
{"POST", "/git-receive-pack$", service_rpc}
};
diff --git a/http-push.c b/http-push.c
index b4d0b2a6aa..12d1113741 100644
--- a/http-push.c
+++ b/http-push.c
@@ -1851,6 +1851,7 @@ int cmd_main(int argc, const char **argv)
if (oideq(&ref->old_oid, &ref->peer_ref->new_oid)) {
if (push_verbosely)
+ /* stable plumbing output; do not modify or localize */
fprintf(stderr, "'%s': up-to-date\n", ref->name);
if (helper_status)
printf("ok %s up to date\n", ref->name);
@@ -1871,6 +1872,7 @@ int cmd_main(int argc, const char **argv)
* commits at the remote end and likely
* we were not up to date to begin with.
*/
+ /* stable plumbing output; do not modify or localize */
error("remote '%s' is not an ancestor of\n"
"local '%s'.\n"
"Maybe you are not up-to-date and "
diff --git a/merge-ll.c b/merge-ll.c
index 1df58ebaac..5ffb045efb 100644
--- a/merge-ll.c
+++ b/merge-ll.c
@@ -185,9 +185,9 @@ static void create_temp(mmfile_t *src, char *path, size_t len)
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 UNUSED,
- mmfile_t *src1, const char *name1 UNUSED,
- mmfile_t *src2, const char *name2 UNUSED,
+ mmfile_t *orig, const char *orig_name,
+ mmfile_t *src1, const char *name1,
+ mmfile_t *src2, const char *name2,
const struct ll_merge_options *opts,
int marker_size)
{
@@ -222,6 +222,12 @@ static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn,
strbuf_addf(&cmd, "%d", marker_size);
else if (skip_prefix(format, "P", &format))
sq_quote_buf(&cmd, path);
+ else if (skip_prefix(format, "S", &format))
+ sq_quote_buf(&cmd, orig_name ? orig_name : "");
+ else if (skip_prefix(format, "X", &format))
+ sq_quote_buf(&cmd, name1 ? name1 : "");
+ else if (skip_prefix(format, "Y", &format))
+ sq_quote_buf(&cmd, name2 ? name2 : "");
else
strbuf_addch(&cmd, '%');
}
@@ -315,7 +321,12 @@ static int read_merge_config(const char *var, const char *value,
* %B - temporary file name for the other branches' version.
* %L - conflict marker length
* %P - the original path (safely quoted for the shell)
+ * %S - the revision for the merge base
+ * %X - the revision for our version
+ * %Y - the revision for their version
*
+ * If the file is not named indentically in all versions, then each
+ * revision is joined with the corresponding path, separated by a colon.
* The external merge driver should write the results in the
* file named by %A, and signal that it has done with zero exit
* status.
diff --git a/merge-ort.c b/merge-ort.c
index 77ba7f3020..8617babee4 100644
--- a/merge-ort.c
+++ b/merge-ort.c
@@ -38,6 +38,7 @@
#include "path.h"
#include "promisor-remote.h"
#include "read-cache-ll.h"
+#include "refs.h"
#include "revision.h"
#include "sparse-index.h"
#include "strmap.h"
@@ -2641,7 +2642,7 @@ static void apply_directory_rename_modifications(struct merge_options *opt,
oidcpy(&ci->stages[i].oid, null_oid());
}
- // Now we want to focus on new_ci, so reassign ci to it
+ /* Now we want to focus on new_ci, so reassign ci to it. */
ci = new_ci;
}
@@ -4659,9 +4660,6 @@ void merge_switch_to_result(struct merge_options *opt,
{
assert(opt->priv == NULL);
if (result->clean >= 0 && update_worktree_and_index) {
- const char *filename;
- FILE *fp;
-
trace2_region_enter("merge", "checkout", opt->repo);
if (checkout(opt, head, result->tree)) {
/* failure to function */
@@ -4687,10 +4685,17 @@ void merge_switch_to_result(struct merge_options *opt,
trace2_region_leave("merge", "record_conflicted", opt->repo);
trace2_region_enter("merge", "write_auto_merge", opt->repo);
- filename = git_path_auto_merge(opt->repo);
- fp = xfopen(filename, "w");
- fprintf(fp, "%s\n", oid_to_hex(&result->tree->object.oid));
- fclose(fp);
+ if (refs_update_ref(get_main_ref_store(opt->repo), "", "AUTO_MERGE",
+ &result->tree->object.oid, NULL, REF_NO_DEREF,
+ UPDATE_REFS_MSG_ON_ERR)) {
+ /* failure to function */
+ opt->priv = NULL;
+ result->clean = -1;
+ merge_finalize(opt, result);
+ trace2_region_leave("merge", "write_auto_merge",
+ opt->repo);
+ return;
+ }
trace2_region_leave("merge", "write_auto_merge", opt->repo);
}
if (display_update_msgs)
diff --git a/midx.c b/midx.c
index 1d14661dad..85e1c2cd12 100644
--- a/midx.c
+++ b/midx.c
@@ -21,6 +21,7 @@
#include "refs.h"
#include "revision.h"
#include "list-objects.h"
+#include "pack-revindex.h"
#define MIDX_SIGNATURE 0x4d494458 /* "MIDX" */
#define MIDX_VERSION 1
@@ -33,6 +34,7 @@
#define MIDX_CHUNK_ALIGNMENT 4
#define MIDX_CHUNKID_PACKNAMES 0x504e414d /* "PNAM" */
+#define MIDX_CHUNKID_BITMAPPEDPACKS 0x42544d50 /* "BTMP" */
#define MIDX_CHUNKID_OIDFANOUT 0x4f494446 /* "OIDF" */
#define MIDX_CHUNKID_OIDLOOKUP 0x4f49444c /* "OIDL" */
#define MIDX_CHUNKID_OBJECTOFFSETS 0x4f4f4646 /* "OOFF" */
@@ -41,6 +43,7 @@
#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))
+#define MIDX_CHUNK_BITMAPPED_PACKS_WIDTH (2 * sizeof(uint32_t))
#define MIDX_LARGE_OFFSET_NEEDED 0x80000000
#define PACK_EXPIRED UINT_MAX
@@ -175,6 +178,8 @@ struct multi_pack_index *load_multi_pack_index(const char *object_dir, int local
m->num_packs = get_be32(m->data + MIDX_BYTE_NUM_PACKS);
+ m->preferred_pack_idx = -1;
+
cf = init_chunkfile(NULL);
if (read_table_of_contents(cf, m->data, midx_size,
@@ -193,6 +198,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,
&m->chunk_large_offsets_len);
+ pair_chunk(cf, MIDX_CHUNKID_BITMAPPEDPACKS,
+ (const unsigned char **)&m->chunk_bitmapped_packs,
+ &m->chunk_bitmapped_packs_len);
if (git_env_bool("GIT_TEST_MIDX_READ_RIDX", 1))
pair_chunk(cf, MIDX_CHUNKID_REVINDEX, &m->chunk_revindex,
@@ -286,6 +294,26 @@ int prepare_midx_pack(struct repository *r, struct multi_pack_index *m, uint32_t
return 0;
}
+int nth_bitmapped_pack(struct repository *r, struct multi_pack_index *m,
+ struct bitmapped_pack *bp, uint32_t pack_int_id)
+{
+ if (!m->chunk_bitmapped_packs)
+ return error(_("MIDX does not contain the BTMP chunk"));
+
+ if (prepare_midx_pack(r, m, pack_int_id))
+ return error(_("could not load bitmapped pack %"PRIu32), pack_int_id);
+
+ bp->p = m->packs[pack_int_id];
+ bp->bitmap_pos = get_be32((char *)m->chunk_bitmapped_packs +
+ MIDX_CHUNK_BITMAPPED_PACKS_WIDTH * pack_int_id);
+ bp->bitmap_nr = get_be32((char *)m->chunk_bitmapped_packs +
+ MIDX_CHUNK_BITMAPPED_PACKS_WIDTH * pack_int_id +
+ sizeof(uint32_t));
+ bp->pack_int_id = pack_int_id;
+
+ return 0;
+}
+
int bsearch_midx(const struct object_id *oid, struct multi_pack_index *m, uint32_t *result)
{
return bsearch_hash(oid->hash, m->chunk_oid_fanout, m->chunk_oid_lookup,
@@ -403,7 +431,8 @@ static int cmp_idx_or_pack_name(const char *idx_or_pack_name,
return strcmp(idx_or_pack_name, idx_name);
}
-int midx_contains_pack(struct multi_pack_index *m, const char *idx_or_pack_name)
+int midx_locate_pack(struct multi_pack_index *m, const char *idx_or_pack_name,
+ uint32_t *pos)
{
uint32_t first = 0, last = m->num_packs;
@@ -414,8 +443,11 @@ int midx_contains_pack(struct multi_pack_index *m, const char *idx_or_pack_name)
current = m->pack_names[mid];
cmp = cmp_idx_or_pack_name(idx_or_pack_name, current);
- if (!cmp)
+ if (!cmp) {
+ if (pos)
+ *pos = mid;
return 1;
+ }
if (cmp > 0) {
first = mid + 1;
continue;
@@ -426,6 +458,28 @@ int midx_contains_pack(struct multi_pack_index *m, const char *idx_or_pack_name)
return 0;
}
+int midx_contains_pack(struct multi_pack_index *m, const char *idx_or_pack_name)
+{
+ return midx_locate_pack(m, idx_or_pack_name, NULL);
+}
+
+int midx_preferred_pack(struct multi_pack_index *m, uint32_t *pack_int_id)
+{
+ if (m->preferred_pack_idx == -1) {
+ if (load_midx_revindex(m) < 0) {
+ m->preferred_pack_idx = -2;
+ return -1;
+ }
+
+ m->preferred_pack_idx =
+ nth_midxed_pack_int_id(m, pack_pos_to_midx(m, 0));
+ } else if (m->preferred_pack_idx == -2)
+ return -1; /* no revindex */
+
+ *pack_int_id = m->preferred_pack_idx;
+ return 0;
+}
+
int prepare_multi_pack_index_one(struct repository *r, const char *object_dir, int local)
{
struct multi_pack_index *m;
@@ -468,13 +522,31 @@ static size_t write_midx_header(struct hashfile *f,
return MIDX_HEADER_SIZE;
}
+#define BITMAP_POS_UNKNOWN (~((uint32_t)0))
+
struct pack_info {
uint32_t orig_pack_int_id;
char *pack_name;
struct packed_git *p;
+
+ uint32_t bitmap_pos;
+ uint32_t bitmap_nr;
+
unsigned expired : 1;
};
+static void fill_pack_info(struct pack_info *info,
+ struct packed_git *p, const char *pack_name,
+ uint32_t orig_pack_int_id)
+{
+ memset(info, 0, sizeof(struct pack_info));
+
+ info->orig_pack_int_id = orig_pack_int_id;
+ info->pack_name = xstrdup(pack_name);
+ info->p = p;
+ info->bitmap_pos = BITMAP_POS_UNKNOWN;
+}
+
static int pack_info_compare(const void *_a, const void *_b)
{
struct pack_info *a = (struct pack_info *)_a;
@@ -515,6 +587,7 @@ static void add_pack_to_midx(const char *full_path, size_t full_path_len,
const char *file_name, void *data)
{
struct write_midx_context *ctx = data;
+ struct packed_git *p;
if (ends_with(file_name, ".idx")) {
display_progress(ctx->progress, ++ctx->pack_paths_checked);
@@ -541,27 +614,22 @@ static void add_pack_to_midx(const char *full_path, size_t full_path_len,
ALLOC_GROW(ctx->info, ctx->nr + 1, ctx->alloc);
- ctx->info[ctx->nr].p = add_packed_git(full_path,
- full_path_len,
- 0);
-
- if (!ctx->info[ctx->nr].p) {
+ p = add_packed_git(full_path, full_path_len, 0);
+ if (!p) {
warning(_("failed to add packfile '%s'"),
full_path);
return;
}
- if (open_pack_index(ctx->info[ctx->nr].p)) {
+ if (open_pack_index(p)) {
warning(_("failed to open pack-index '%s'"),
full_path);
- close_pack(ctx->info[ctx->nr].p);
- FREE_AND_NULL(ctx->info[ctx->nr].p);
+ close_pack(p);
+ free(p);
return;
}
- ctx->info[ctx->nr].pack_name = xstrdup(file_name);
- ctx->info[ctx->nr].orig_pack_int_id = ctx->nr;
- ctx->info[ctx->nr].expired = 0;
+ fill_pack_info(&ctx->info[ctx->nr], p, file_name, ctx->nr);
ctx->nr++;
}
}
@@ -817,6 +885,26 @@ static int write_midx_pack_names(struct hashfile *f, void *data)
return 0;
}
+static int write_midx_bitmapped_packs(struct hashfile *f, void *data)
+{
+ struct write_midx_context *ctx = data;
+ size_t i;
+
+ for (i = 0; i < ctx->nr; i++) {
+ struct pack_info *pack = &ctx->info[i];
+ if (pack->expired)
+ continue;
+
+ if (pack->bitmap_pos == BITMAP_POS_UNKNOWN && pack->bitmap_nr)
+ BUG("pack '%s' has no bitmap position, but has %d bitmapped object(s)",
+ pack->pack_name, pack->bitmap_nr);
+
+ hashwrite_be32(f, pack->bitmap_pos);
+ hashwrite_be32(f, pack->bitmap_nr);
+ }
+ return 0;
+}
+
static int write_midx_oid_fanout(struct hashfile *f,
void *data)
{
@@ -984,8 +1072,19 @@ static uint32_t *midx_pack_order(struct write_midx_context *ctx)
QSORT(data, ctx->entries_nr, midx_pack_order_cmp);
ALLOC_ARRAY(pack_order, ctx->entries_nr);
- for (i = 0; i < ctx->entries_nr; i++)
+ for (i = 0; i < ctx->entries_nr; i++) {
+ struct pack_midx_entry *e = &ctx->entries[data[i].nr];
+ struct pack_info *pack = &ctx->info[ctx->pack_perm[e->pack_int_id]];
+ if (pack->bitmap_pos == BITMAP_POS_UNKNOWN)
+ pack->bitmap_pos = i;
+ pack->bitmap_nr++;
pack_order[i] = data[i].nr;
+ }
+ for (i = 0; i < ctx->nr; i++) {
+ struct pack_info *pack = &ctx->info[ctx->pack_perm[i]];
+ if (pack->bitmap_pos == BITMAP_POS_UNKNOWN)
+ pack->bitmap_pos = 0;
+ }
free(data);
trace2_region_leave("midx", "midx_pack_order", the_repository);
@@ -1286,6 +1385,7 @@ static int write_midx_internal(const char *object_dir,
struct hashfile *f = NULL;
struct lock_file lk;
struct write_midx_context ctx = { 0 };
+ int bitmapped_packs_concat_len = 0;
int pack_name_concat_len = 0;
int dropped_packs = 0;
int result = 0;
@@ -1321,11 +1421,6 @@ static int write_midx_internal(const char *object_dir,
for (i = 0; i < ctx.m->num_packs; i++) {
ALLOC_GROW(ctx.info, ctx.nr + 1, ctx.alloc);
- ctx.info[ctx.nr].orig_pack_int_id = i;
- ctx.info[ctx.nr].pack_name = xstrdup(ctx.m->pack_names[i]);
- ctx.info[ctx.nr].p = ctx.m->packs[i];
- ctx.info[ctx.nr].expired = 0;
-
if (flags & MIDX_WRITE_REV_INDEX) {
/*
* If generating a reverse index, need to have
@@ -1341,10 +1436,10 @@ static int write_midx_internal(const char *object_dir,
if (open_pack_index(ctx.m->packs[i]))
die(_("could not open index for %s"),
ctx.m->packs[i]->pack_name);
- ctx.info[ctx.nr].p = ctx.m->packs[i];
}
- ctx.nr++;
+ fill_pack_info(&ctx.info[ctx.nr++], ctx.m->packs[i],
+ ctx.m->pack_names[i], i);
}
}
@@ -1503,8 +1598,10 @@ static int write_midx_internal(const char *object_dir,
}
for (i = 0; i < ctx.nr; i++) {
- if (!ctx.info[i].expired)
- pack_name_concat_len += strlen(ctx.info[i].pack_name) + 1;
+ if (ctx.info[i].expired)
+ continue;
+ pack_name_concat_len += strlen(ctx.info[i].pack_name) + 1;
+ bitmapped_packs_concat_len += 2 * sizeof(uint32_t);
}
/* Check that the preferred pack wasn't expired (if given). */
@@ -1564,6 +1661,9 @@ static int write_midx_internal(const char *object_dir,
add_chunk(cf, MIDX_CHUNKID_REVINDEX,
st_mult(ctx.entries_nr, sizeof(uint32_t)),
write_midx_revindex);
+ add_chunk(cf, MIDX_CHUNKID_BITMAPPEDPACKS,
+ bitmapped_packs_concat_len,
+ write_midx_bitmapped_packs);
}
write_midx_header(f, get_num_chunks(cf), ctx.nr - dropped_packs);
@@ -1603,8 +1703,13 @@ static int write_midx_internal(const char *object_dir,
flags) < 0) {
error(_("could not write multi-pack bitmap"));
result = 1;
+ clear_packing_data(&pdata);
+ free(commits);
goto cleanup;
}
+
+ clear_packing_data(&pdata);
+ free(commits);
}
/*
* NOTE: Do not use ctx.entries beyond this point, since it might
diff --git a/midx.h b/midx.h
index eb57a37519..b374a7afaf 100644
--- a/midx.h
+++ b/midx.h
@@ -6,6 +6,7 @@
struct object_id;
struct pack_entry;
struct repository;
+struct bitmapped_pack;
#define GIT_TEST_MULTI_PACK_INDEX "GIT_TEST_MULTI_PACK_INDEX"
#define GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP \
@@ -27,11 +28,14 @@ struct multi_pack_index {
unsigned char num_chunks;
uint32_t num_packs;
uint32_t num_objects;
+ int preferred_pack_idx;
int local;
const unsigned char *chunk_pack_names;
size_t chunk_pack_names_len;
+ const uint32_t *chunk_bitmapped_packs;
+ size_t chunk_bitmapped_packs_len;
const uint32_t *chunk_oid_fanout;
const unsigned char *chunk_oid_lookup;
const unsigned char *chunk_object_offsets;
@@ -57,6 +61,8 @@ void get_midx_rev_filename(struct strbuf *out, struct multi_pack_index *m);
struct multi_pack_index *load_multi_pack_index(const char *object_dir, int local);
int prepare_midx_pack(struct repository *r, struct multi_pack_index *m, uint32_t pack_int_id);
+int nth_bitmapped_pack(struct repository *r, struct multi_pack_index *m,
+ struct bitmapped_pack *bp, uint32_t pack_int_id);
int bsearch_midx(const struct object_id *oid, struct multi_pack_index *m, uint32_t *result);
off_t nth_midxed_offset(struct multi_pack_index *m, uint32_t pos);
uint32_t nth_midxed_pack_int_id(struct multi_pack_index *m, uint32_t pos);
@@ -64,7 +70,11 @@ struct object_id *nth_midxed_object_oid(struct object_id *oid,
struct multi_pack_index *m,
uint32_t n);
int fill_midx_entry(struct repository *r, const struct object_id *oid, struct pack_entry *e, struct multi_pack_index *m);
-int midx_contains_pack(struct multi_pack_index *m, const char *idx_or_pack_name);
+int midx_contains_pack(struct multi_pack_index *m,
+ const char *idx_or_pack_name);
+int midx_locate_pack(struct multi_pack_index *m, const char *idx_or_pack_name,
+ uint32_t *pos);
+int midx_preferred_pack(struct multi_pack_index *m, uint32_t *pack_int_id);
int prepare_multi_pack_index_one(struct repository *r, const char *object_dir, int local);
/*
diff --git a/neue b/neue
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/neue
diff --git a/oss-fuzz/dummy-cmd-main.c b/oss-fuzz/dummy-cmd-main.c
new file mode 100644
index 0000000000..071cb231ba
--- /dev/null
+++ b/oss-fuzz/dummy-cmd-main.c
@@ -0,0 +1,14 @@
+#include "git-compat-util.h"
+
+/*
+ * When linking the fuzzers, we link against common-main.o to pick up some
+ * symbols. However, even though we ignore common-main:main(), we still need to
+ * provide all the symbols it references. In the fuzzers' case, we need to
+ * provide a dummy cmd_main() for the linker to be happy. It will never be
+ * executed.
+ */
+
+int cmd_main(int argc, const char **argv) {
+ BUG("We should not execute cmd_main() from a fuzz target");
+ return 1;
+}
diff --git a/pack-bitmap-write.c b/pack-bitmap-write.c
index be4733e3bd..990a9498d7 100644
--- a/pack-bitmap-write.c
+++ b/pack-bitmap-write.c
@@ -195,6 +195,13 @@ struct bb_commit {
unsigned idx; /* within selected array */
};
+static void clear_bb_commit(struct bb_commit *commit)
+{
+ free_commit_list(commit->reverse_edges);
+ bitmap_free(commit->commit_mask);
+ bitmap_free(commit->bitmap);
+}
+
define_commit_slab(bb_data, struct bb_commit);
struct bitmap_builder {
@@ -336,7 +343,7 @@ next:
static void bitmap_builder_clear(struct bitmap_builder *bb)
{
- clear_bb_data(&bb->data);
+ deep_clear_bb_data(&bb->data, clear_bb_commit);
free(bb->commits);
bb->commits_nr = bb->commits_alloc = 0;
}
diff --git a/pack-bitmap.c b/pack-bitmap.c
index 0260890341..2baeabacee 100644
--- a/pack-bitmap.c
+++ b/pack-bitmap.c
@@ -51,13 +51,6 @@ struct bitmap_index {
struct packed_git *pack;
struct multi_pack_index *midx;
- /*
- * Mark the first `reuse_objects` in the packfile as reused:
- * they will be sent as-is without using them for repacking
- * calculations
- */
- uint32_t reuse_objects;
-
/* mmapped buffer of the whole bitmap index */
unsigned char *map;
size_t map_size; /* size of the mmaped buffer */
@@ -338,7 +331,7 @@ static int open_midx_bitmap_1(struct bitmap_index *bitmap_git,
struct stat st;
char *bitmap_name = midx_bitmap_filename(midx);
int fd = git_open(bitmap_name);
- uint32_t i;
+ uint32_t i, preferred_pack;
struct packed_git *preferred;
if (fd < 0) {
@@ -393,7 +386,12 @@ static int open_midx_bitmap_1(struct bitmap_index *bitmap_git,
}
}
- preferred = bitmap_git->midx->packs[midx_preferred_pack(bitmap_git)];
+ if (midx_preferred_pack(bitmap_git->midx, &preferred_pack) < 0) {
+ warning(_("could not determine MIDX preferred pack"));
+ goto cleanup;
+ }
+
+ preferred = bitmap_git->midx->packs[preferred_pack];
if (!is_pack_valid(preferred)) {
warning(_("preferred pack (%s) is invalid"),
preferred->pack_name);
@@ -1280,6 +1278,8 @@ static struct bitmap *find_objects(struct bitmap_index *bitmap_git,
base = fill_in_bitmap(bitmap_git, revs, base, seen);
}
+ object_list_free(&not_mapped);
+
return base;
}
@@ -1834,8 +1834,10 @@ cleanup:
* -1 means "stop trying further objects"; 0 means we may or may not have
* reused, but you can keep feeding bits.
*/
-static int try_partial_reuse(struct packed_git *pack,
- size_t pos,
+static int try_partial_reuse(struct bitmap_index *bitmap_git,
+ struct bitmapped_pack *pack,
+ size_t bitmap_pos,
+ uint32_t pack_pos,
struct bitmap *reuse,
struct pack_window **w_curs)
{
@@ -1843,40 +1845,18 @@ static int try_partial_reuse(struct packed_git *pack,
enum object_type type;
unsigned long size;
- /*
- * try_partial_reuse() is called either on (a) objects in the
- * bitmapped pack (in the case of a single-pack bitmap) or (b)
- * objects in the preferred pack of a multi-pack bitmap.
- * Importantly, the latter can pretend as if only a single pack
- * exists because:
- *
- * - The first pack->num_objects bits of a MIDX bitmap are
- * reserved for the preferred pack, and
- *
- * - Ties due to duplicate objects are always resolved in
- * favor of the preferred pack.
- *
- * Therefore we do not need to ever ask the MIDX for its copy of
- * an object by OID, since it will always select it from the
- * preferred pack. Likewise, the selected copy of the base
- * object for any deltas will reside in the same pack.
- *
- * This means that we can reuse pos when looking up the bit in
- * the reuse bitmap, too, since bits corresponding to the
- * preferred pack precede all bits from other packs.
- */
-
- if (pos >= pack->num_objects)
- return -1; /* not actually in the pack or MIDX preferred pack */
+ if (pack_pos >= pack->p->num_objects)
+ return -1; /* not actually in the pack */
- offset = delta_obj_offset = pack_pos_to_offset(pack, pos);
- type = unpack_object_header(pack, w_curs, &offset, &size);
+ offset = delta_obj_offset = pack_pos_to_offset(pack->p, pack_pos);
+ type = unpack_object_header(pack->p, w_curs, &offset, &size);
if (type < 0)
return -1; /* broken packfile, punt */
if (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA) {
off_t base_offset;
uint32_t base_pos;
+ uint32_t base_bitmap_pos;
/*
* Find the position of the base object so we can look it up
@@ -1886,24 +1866,48 @@ static int try_partial_reuse(struct packed_git *pack,
* and the normal slow path will complain about it in
* more detail.
*/
- base_offset = get_delta_base(pack, w_curs, &offset, type,
+ base_offset = get_delta_base(pack->p, w_curs, &offset, type,
delta_obj_offset);
if (!base_offset)
return 0;
- if (offset_to_pack_pos(pack, base_offset, &base_pos) < 0)
- return 0;
- /*
- * We assume delta dependencies always point backwards. This
- * lets us do a single pass, and is basically always true
- * due to the way OFS_DELTAs work. You would not typically
- * find REF_DELTA in a bitmapped pack, since we only bitmap
- * packs we write fresh, and OFS_DELTA is the default). But
- * let's double check to make sure the pack wasn't written with
- * odd parameters.
- */
- if (base_pos >= pos)
- return 0;
+ offset_to_pack_pos(pack->p, base_offset, &base_pos);
+
+ if (bitmap_is_midx(bitmap_git)) {
+ /*
+ * Cross-pack deltas are rejected for now, but could
+ * theoretically be supported in the future.
+ *
+ * We would need to ensure that we're sending both
+ * halves of the delta/base pair, regardless of whether
+ * or not the two cross a pack boundary. If they do,
+ * then we must convert the delta to an REF_DELTA to
+ * refer back to the base in the other pack.
+ * */
+ if (midx_pair_to_pack_pos(bitmap_git->midx,
+ pack->pack_int_id,
+ base_offset,
+ &base_bitmap_pos) < 0) {
+ return 0;
+ }
+ } else {
+ if (offset_to_pack_pos(pack->p, base_offset,
+ &base_pos) < 0)
+ return 0;
+ /*
+ * We assume delta dependencies always point backwards.
+ * This lets us do a single pass, and is basically
+ * always true due to the way OFS_DELTAs work. You would
+ * not typically find REF_DELTA in a bitmapped pack,
+ * since we only bitmap packs we write fresh, and
+ * OFS_DELTA is the default). But let's double check to
+ * make sure the pack wasn't written with odd
+ * parameters.
+ */
+ if (base_pos >= pack_pos)
+ return 0;
+ base_bitmap_pos = pack->bitmap_pos + base_pos;
+ }
/*
* And finally, if we're not sending the base as part of our
@@ -1913,77 +1917,89 @@ static int try_partial_reuse(struct packed_git *pack,
* to REF_DELTA on the fly. Better to just let the normal
* object_entry code path handle it.
*/
- if (!bitmap_get(reuse, base_pos))
+ if (!bitmap_get(reuse, base_bitmap_pos))
return 0;
}
/*
* If we got here, then the object is OK to reuse. Mark it.
*/
- bitmap_set(reuse, pos);
+ bitmap_set(reuse, bitmap_pos);
return 0;
}
-uint32_t midx_preferred_pack(struct bitmap_index *bitmap_git)
-{
- struct multi_pack_index *m = bitmap_git->midx;
- if (!m)
- BUG("midx_preferred_pack: requires non-empty MIDX");
- return nth_midxed_pack_int_id(m, pack_pos_to_midx(bitmap_git->midx, 0));
-}
-
-int reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git,
- struct packed_git **packfile_out,
- uint32_t *entries,
- struct bitmap **reuse_out)
+static void reuse_partial_packfile_from_bitmap_1(struct bitmap_index *bitmap_git,
+ struct bitmapped_pack *pack,
+ struct bitmap *reuse)
{
- struct repository *r = the_repository;
- struct packed_git *pack;
struct bitmap *result = bitmap_git->result;
- struct bitmap *reuse;
struct pack_window *w_curs = NULL;
- size_t i = 0;
- uint32_t offset;
- uint32_t objects_nr;
+ size_t pos = pack->bitmap_pos / BITS_IN_EWORD;
- assert(result);
+ if (!pack->bitmap_pos) {
+ /*
+ * If we're processing the first (in the case of a MIDX, the
+ * preferred pack) or the only (in the case of single-pack
+ * bitmaps) pack, then we can reuse whole words at a time.
+ *
+ * This is because we know that any deltas in this range *must*
+ * have their bases chosen from the same pack, since:
+ *
+ * - In the single pack case, there is no other pack to choose
+ * them from.
+ *
+ * - In the MIDX case, the first pack is the preferred pack, so
+ * all ties are broken in favor of that pack (i.e. the one
+ * we're currently processing). So any duplicate bases will be
+ * resolved in favor of the pack we're processing.
+ */
+ while (pos < result->word_alloc &&
+ pos < pack->bitmap_nr / BITS_IN_EWORD &&
+ result->words[pos] == (eword_t)~0)
+ pos++;
+ memset(reuse->words, 0xFF, pos * sizeof(eword_t));
+ }
- load_reverse_index(r, bitmap_git);
+ for (; pos < result->word_alloc; pos++) {
+ eword_t word = result->words[pos];
+ size_t offset;
- if (bitmap_is_midx(bitmap_git))
- pack = bitmap_git->midx->packs[midx_preferred_pack(bitmap_git)];
- else
- pack = bitmap_git->pack;
- objects_nr = pack->num_objects;
+ for (offset = 0; offset < BITS_IN_EWORD; offset++) {
+ size_t bit_pos;
+ uint32_t pack_pos;
- while (i < result->word_alloc && result->words[i] == (eword_t)~0)
- i++;
+ if (word >> offset == 0)
+ break;
- /*
- * Don't mark objects not in the packfile or preferred pack. This bitmap
- * marks objects eligible for reuse, but the pack-reuse code only
- * understands how to reuse a single pack. Since the preferred pack is
- * guaranteed to have all bases for its deltas (in a multi-pack bitmap),
- * we use it instead of another pack. In single-pack bitmaps, the choice
- * is made for us.
- */
- if (i > objects_nr / BITS_IN_EWORD)
- i = objects_nr / BITS_IN_EWORD;
+ offset += ewah_bit_ctz64(word >> offset);
+
+ bit_pos = pos * BITS_IN_EWORD + offset;
+ if (bit_pos < pack->bitmap_pos)
+ continue;
+ if (bit_pos >= pack->bitmap_pos + pack->bitmap_nr)
+ goto done;
- reuse = bitmap_word_alloc(i);
- memset(reuse->words, 0xFF, i * sizeof(eword_t));
+ if (bitmap_is_midx(bitmap_git)) {
+ uint32_t midx_pos;
+ off_t ofs;
- for (; i < result->word_alloc; ++i) {
- eword_t word = result->words[i];
- size_t pos = (i * BITS_IN_EWORD);
+ midx_pos = pack_pos_to_midx(bitmap_git->midx, bit_pos);
+ ofs = nth_midxed_offset(bitmap_git->midx, midx_pos);
- for (offset = 0; offset < BITS_IN_EWORD; ++offset) {
- if ((word >> offset) == 0)
- break;
+ if (offset_to_pack_pos(pack->p, ofs, &pack_pos) < 0)
+ BUG("could not find object in pack %s "
+ "at offset %"PRIuMAX" in MIDX",
+ pack_basename(pack->p), (uintmax_t)ofs);
+ } else {
+ pack_pos = cast_size_t_to_uint32_t(st_sub(bit_pos, pack->bitmap_pos));
+ if (pack_pos >= pack->p->num_objects)
+ BUG("advanced beyond the end of pack %s (%"PRIuMAX" > %"PRIu32")",
+ pack_basename(pack->p), (uintmax_t)pack_pos,
+ pack->p->num_objects);
+ }
- offset += ewah_bit_ctz64(word >> offset);
- if (try_partial_reuse(pack, pos + offset,
- reuse, &w_curs) < 0) {
+ if (try_partial_reuse(bitmap_git, pack, bit_pos,
+ pack_pos, reuse, &w_curs) < 0) {
/*
* try_partial_reuse indicated we couldn't reuse
* any bits, so there is no point in trying more
@@ -2000,11 +2016,97 @@ int reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git,
done:
unuse_pack(&w_curs);
+}
- *entries = bitmap_popcount(reuse);
- if (!*entries) {
- bitmap_free(reuse);
+static int bitmapped_pack_cmp(const void *va, const void *vb)
+{
+ const struct bitmapped_pack *a = va;
+ const struct bitmapped_pack *b = vb;
+
+ if (a->bitmap_pos < b->bitmap_pos)
return -1;
+ if (a->bitmap_pos > b->bitmap_pos)
+ return 1;
+ return 0;
+}
+
+void reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git,
+ struct bitmapped_pack **packs_out,
+ size_t *packs_nr_out,
+ struct bitmap **reuse_out,
+ int multi_pack_reuse)
+{
+ struct repository *r = the_repository;
+ struct bitmapped_pack *packs = NULL;
+ struct bitmap *result = bitmap_git->result;
+ struct bitmap *reuse;
+ size_t i;
+ size_t packs_nr = 0, packs_alloc = 0;
+ size_t word_alloc;
+ uint32_t objects_nr = 0;
+
+ assert(result);
+
+ load_reverse_index(r, bitmap_git);
+
+ if (bitmap_is_midx(bitmap_git)) {
+ for (i = 0; i < bitmap_git->midx->num_packs; i++) {
+ struct bitmapped_pack pack;
+ if (nth_bitmapped_pack(r, bitmap_git->midx, &pack, i) < 0) {
+ warning(_("unable to load pack: '%s', disabling pack-reuse"),
+ bitmap_git->midx->pack_names[i]);
+ free(packs);
+ return;
+ }
+
+ if (!pack.bitmap_nr)
+ continue;
+
+ if (!multi_pack_reuse && pack.bitmap_pos) {
+ /*
+ * If we're only reusing a single pack, skip
+ * over any packs which are not positioned at
+ * the beginning of the MIDX bitmap.
+ *
+ * This is consistent with the existing
+ * single-pack reuse behavior, which only reuses
+ * parts of the MIDX's preferred pack.
+ */
+ continue;
+ }
+
+ ALLOC_GROW(packs, packs_nr + 1, packs_alloc);
+ memcpy(&packs[packs_nr++], &pack, sizeof(pack));
+
+ objects_nr += pack.p->num_objects;
+
+ if (!multi_pack_reuse)
+ break;
+ }
+
+ QSORT(packs, packs_nr, bitmapped_pack_cmp);
+ } else {
+ ALLOC_GROW(packs, packs_nr + 1, packs_alloc);
+
+ packs[packs_nr].p = bitmap_git->pack;
+ packs[packs_nr].bitmap_nr = bitmap_git->pack->num_objects;
+ packs[packs_nr].bitmap_pos = 0;
+
+ objects_nr = packs[packs_nr++].bitmap_nr;
+ }
+
+ word_alloc = objects_nr / BITS_IN_EWORD;
+ if (objects_nr % BITS_IN_EWORD)
+ word_alloc++;
+ reuse = bitmap_word_alloc(word_alloc);
+
+ for (i = 0; i < packs_nr; i++)
+ reuse_partial_packfile_from_bitmap_1(bitmap_git, &packs[i], reuse);
+
+ if (bitmap_is_empty(reuse)) {
+ free(packs);
+ bitmap_free(reuse);
+ return;
}
/*
@@ -2012,9 +2114,9 @@ done:
* need to be handled separately.
*/
bitmap_and_not(result, reuse);
- *packfile_out = pack;
+ *packs_out = packs;
+ *packs_nr_out = packs_nr;
*reuse_out = reuse;
- return 0;
}
int bitmap_walk_contains(struct bitmap_index *bitmap_git,
diff --git a/pack-bitmap.h b/pack-bitmap.h
index 5273a6a019..c7dea13217 100644
--- a/pack-bitmap.h
+++ b/pack-bitmap.h
@@ -52,6 +52,15 @@ typedef int (*show_reachable_fn)(
struct bitmap_index;
+struct bitmapped_pack {
+ struct packed_git *p;
+
+ uint32_t bitmap_pos;
+ uint32_t bitmap_nr;
+
+ uint32_t pack_int_id; /* MIDX only */
+};
+
struct bitmap_index *prepare_bitmap_git(struct repository *r);
struct bitmap_index *prepare_midx_bitmap_git(struct multi_pack_index *midx);
void count_bitmap_commit_list(struct bitmap_index *, uint32_t *commits,
@@ -68,11 +77,11 @@ int test_bitmap_hashes(struct repository *r);
struct bitmap_index *prepare_bitmap_walk(struct rev_info *revs,
int filter_provided_objects);
-uint32_t midx_preferred_pack(struct bitmap_index *bitmap_git);
-int reuse_partial_packfile_from_bitmap(struct bitmap_index *,
- struct packed_git **packfile,
- uint32_t *entries,
- struct bitmap **reuse_out);
+void reuse_partial_packfile_from_bitmap(struct bitmap_index *bitmap_git,
+ struct bitmapped_pack **packs_out,
+ size_t *packs_nr_out,
+ struct bitmap **reuse_out,
+ int multi_pack_reuse);
int rebuild_existing_bitmaps(struct bitmap_index *, struct packing_data *mapping,
kh_oid_map_t *reused_bitmaps, int show_progress);
void free_bitmap_index(struct bitmap_index *);
diff --git a/pack-objects.c b/pack-objects.c
index f403ca6986..a9d9855063 100644
--- a/pack-objects.c
+++ b/pack-objects.c
@@ -151,6 +151,21 @@ void prepare_packing_data(struct repository *r, struct packing_data *pdata)
init_recursive_mutex(&pdata->odb_lock);
}
+void clear_packing_data(struct packing_data *pdata)
+{
+ if (!pdata)
+ return;
+
+ free(pdata->cruft_mtime);
+ free(pdata->in_pack);
+ free(pdata->in_pack_by_idx);
+ free(pdata->in_pack_pos);
+ free(pdata->index);
+ free(pdata->layer);
+ free(pdata->objects);
+ free(pdata->tree_depth);
+}
+
struct object_entry *packlist_alloc(struct packing_data *pdata,
const struct object_id *oid)
{
diff --git a/pack-objects.h b/pack-objects.h
index 0d78db40cb..b9898a4e64 100644
--- a/pack-objects.h
+++ b/pack-objects.h
@@ -169,6 +169,7 @@ struct packing_data {
};
void prepare_packing_data(struct repository *r, struct packing_data *pdata);
+void clear_packing_data(struct packing_data *pdata);
/* Protect access to object database */
static inline void packing_data_lock(struct packing_data *pdata)
diff --git a/pack-revindex.c b/pack-revindex.c
index acf1dd9786..a7624d8be8 100644
--- a/pack-revindex.c
+++ b/pack-revindex.c
@@ -520,19 +520,12 @@ static int midx_pack_order_cmp(const void *va, const void *vb)
return 0;
}
-int midx_to_pack_pos(struct multi_pack_index *m, uint32_t at, uint32_t *pos)
+static int midx_key_to_pack_pos(struct multi_pack_index *m,
+ struct midx_pack_key *key,
+ uint32_t *pos)
{
- struct midx_pack_key key;
uint32_t *found;
- if (!m->revindex_data)
- BUG("midx_to_pack_pos: reverse index not yet loaded");
- if (m->num_objects <= at)
- BUG("midx_to_pack_pos: out-of-bounds object at %"PRIu32, at);
-
- key.pack = nth_midxed_pack_int_id(m, at);
- key.offset = nth_midxed_offset(m, at);
- key.midx = m;
/*
* The preferred pack sorts first, so determine its identifier by
* looking at the first object in pseudo-pack order.
@@ -542,14 +535,43 @@ int midx_to_pack_pos(struct multi_pack_index *m, uint32_t at, uint32_t *pos)
* implicitly is preferred (and includes all its objects, since ties are
* broken first by pack identifier).
*/
- key.preferred_pack = nth_midxed_pack_int_id(m, pack_pos_to_midx(m, 0));
+ if (midx_preferred_pack(key->midx, &key->preferred_pack) < 0)
+ return error(_("could not determine preferred pack"));
- found = bsearch(&key, m->revindex_data, m->num_objects,
- sizeof(*m->revindex_data), midx_pack_order_cmp);
+ found = bsearch(key, m->revindex_data, m->num_objects,
+ sizeof(*m->revindex_data),
+ midx_pack_order_cmp);
if (!found)
- return error("bad offset for revindex");
+ return -1;
*pos = found - m->revindex_data;
return 0;
}
+
+int midx_to_pack_pos(struct multi_pack_index *m, uint32_t at, uint32_t *pos)
+{
+ struct midx_pack_key key;
+
+ if (!m->revindex_data)
+ BUG("midx_to_pack_pos: reverse index not yet loaded");
+ if (m->num_objects <= at)
+ BUG("midx_to_pack_pos: out-of-bounds object at %"PRIu32, at);
+
+ key.pack = nth_midxed_pack_int_id(m, at);
+ key.offset = nth_midxed_offset(m, at);
+ key.midx = m;
+
+ return midx_key_to_pack_pos(m, &key, pos);
+}
+
+int midx_pair_to_pack_pos(struct multi_pack_index *m, uint32_t pack_int_id,
+ off_t ofs, uint32_t *pos)
+{
+ struct midx_pack_key key = {
+ .pack = pack_int_id,
+ .offset = ofs,
+ .midx = m,
+ };
+ return midx_key_to_pack_pos(m, &key, pos);
+}
diff --git a/pack-revindex.h b/pack-revindex.h
index 6dd47efea1..422c2487ae 100644
--- a/pack-revindex.h
+++ b/pack-revindex.h
@@ -142,4 +142,7 @@ uint32_t pack_pos_to_midx(struct multi_pack_index *m, uint32_t pos);
*/
int midx_to_pack_pos(struct multi_pack_index *midx, uint32_t at, uint32_t *pos);
+int midx_pair_to_pack_pos(struct multi_pack_index *midx, uint32_t pack_id,
+ off_t ofs, uint32_t *pos);
+
#endif
diff --git a/parse-options.c b/parse-options.c
index 4ce2b7ca16..63a99dea6e 100644
--- a/parse-options.c
+++ b/parse-options.c
@@ -357,6 +357,7 @@ static enum parse_opt_result parse_long_opt(
const char *arg_end = strchrnul(arg, '=');
const struct option *abbrev_option = NULL, *ambiguous_option = NULL;
enum opt_parsed abbrev_flags = OPT_LONG, ambiguous_flags = OPT_LONG;
+ int allow_abbrev = !(p->flags & PARSE_OPT_KEEP_UNKNOWN_OPT);
for (; options->type != OPTION_END; options++) {
const char *rest, *long_name = options->long_name;
@@ -367,12 +368,16 @@ static enum parse_opt_result parse_long_opt(
if (!long_name)
continue;
-again:
+ if (!starts_with(arg, "no-") &&
+ !(options->flags & PARSE_OPT_NONEG) &&
+ skip_prefix(long_name, "no-", &long_name))
+ opt_flags |= OPT_UNSET;
+
if (!skip_prefix(arg, long_name, &rest))
rest = NULL;
if (!rest) {
/* abbreviated? */
- if (!(p->flags & PARSE_OPT_KEEP_UNKNOWN_OPT) &&
+ if (allow_abbrev &&
!strncmp(long_name, arg, arg_end - arg)) {
is_abbreviated:
if (abbrev_option &&
@@ -396,22 +401,18 @@ is_abbreviated:
if (options->flags & PARSE_OPT_NONEG)
continue;
/* negated and abbreviated very much? */
- if (starts_with("no-", arg)) {
+ if (allow_abbrev && starts_with("no-", arg)) {
flags |= OPT_UNSET;
goto is_abbreviated;
}
/* negated? */
- if (!starts_with(arg, "no-")) {
- if (skip_prefix(long_name, "no-", &long_name)) {
- opt_flags |= OPT_UNSET;
- goto again;
- }
+ if (!starts_with(arg, "no-"))
continue;
- }
flags |= OPT_UNSET;
if (!skip_prefix(arg + 3, long_name, &rest)) {
/* abbreviated and negated? */
- if (starts_with(long_name, arg + 3))
+ if (allow_abbrev &&
+ starts_with(long_name, arg + 3))
goto is_abbreviated;
else
continue;
diff --git a/path.c b/path.c
index 67e2690efe..0fb527918b 100644
--- a/path.c
+++ b/path.c
@@ -1588,7 +1588,5 @@ REPO_GIT_PATH_FUNC(merge_msg, "MERGE_MSG")
REPO_GIT_PATH_FUNC(merge_rr, "MERGE_RR")
REPO_GIT_PATH_FUNC(merge_mode, "MERGE_MODE")
REPO_GIT_PATH_FUNC(merge_head, "MERGE_HEAD")
-REPO_GIT_PATH_FUNC(merge_autostash, "MERGE_AUTOSTASH")
-REPO_GIT_PATH_FUNC(auto_merge, "AUTO_MERGE")
REPO_GIT_PATH_FUNC(fetch_head, "FETCH_HEAD")
REPO_GIT_PATH_FUNC(shallow, "shallow")
diff --git a/path.h b/path.h
index 639372edd9..b3233c51fa 100644
--- a/path.h
+++ b/path.h
@@ -175,8 +175,6 @@ const char *git_path_merge_msg(struct repository *r);
const char *git_path_merge_rr(struct repository *r);
const char *git_path_merge_mode(struct repository *r);
const char *git_path_merge_head(struct repository *r);
-const char *git_path_merge_autostash(struct repository *r);
-const char *git_path_auto_merge(struct repository *r);
const char *git_path_fetch_head(struct repository *r);
const char *git_path_shallow(struct repository *r);
diff --git a/pkt-line.c b/pkt-line.c
index 236dd3a3ee..24479eae4d 100644
--- a/pkt-line.c
+++ b/pkt-line.c
@@ -463,8 +463,32 @@ enum packet_read_status packet_read_with_status(int fd, char **src_buffer,
}
if ((options & PACKET_READ_CHOMP_NEWLINE) &&
- len && buffer[len-1] == '\n')
- len--;
+ len && buffer[len-1] == '\n') {
+ if (options & PACKET_READ_USE_SIDEBAND) {
+ int band = *buffer & 0xff;
+ switch (band) {
+ case 1:
+ /* Chomp newline for payload */
+ len--;
+ break;
+ case 2:
+ case 3:
+ /*
+ * Do not chomp newline for progress and error
+ * message.
+ */
+ break;
+ default:
+ /*
+ * Bad sideband, let's leave it to
+ * demultiplex_sideband() to catch this error.
+ */
+ break;
+ }
+ } else {
+ len--;
+ }
+ }
buffer[len] = 0;
if (options & PACKET_READ_REDACT_URI_PATH &&
@@ -593,17 +617,19 @@ void packet_reader_init(struct packet_reader *reader, int fd,
reader->options = options;
reader->me = "git";
reader->hash_algo = &hash_algos[GIT_HASH_SHA1];
+ strbuf_init(&reader->scratch, 0);
}
enum packet_read_status packet_reader_read(struct packet_reader *reader)
{
- struct strbuf scratch = STRBUF_INIT;
-
if (reader->line_peeked) {
reader->line_peeked = 0;
return reader->status;
}
+ if (reader->use_sideband)
+ reader->options |= PACKET_READ_USE_SIDEBAND;
+
/*
* Consume all progress packets until a primary payload packet is
* received
@@ -621,7 +647,7 @@ enum packet_read_status packet_reader_read(struct packet_reader *reader)
break;
if (demultiplex_sideband(reader->me, reader->status,
reader->buffer, reader->pktlen, 1,
- &scratch, &sideband_type))
+ &reader->scratch, &sideband_type))
break;
}
diff --git a/pkt-line.h b/pkt-line.h
index aedef56286..3b33cc64f3 100644
--- a/pkt-line.h
+++ b/pkt-line.h
@@ -84,6 +84,7 @@ void packet_fflush(FILE *f);
#define PACKET_READ_DIE_ON_ERR_PACKET (1u<<2)
#define PACKET_READ_GENTLE_ON_READ_ERROR (1u<<3)
#define PACKET_READ_REDACT_URI_PATH (1u<<4)
+#define PACKET_READ_USE_SIDEBAND (1u<<5)
int packet_read(int fd, char *buffer, unsigned size, int options);
/*
@@ -193,6 +194,9 @@ struct packet_reader {
/* hash algorithm in use */
const struct git_hash_algo *hash_algo;
+
+ /* hold temporary sideband message */
+ struct strbuf scratch;
};
/*
diff --git a/refs.c b/refs.c
index 2f58a3460a..c633abf284 100644
--- a/refs.c
+++ b/refs.c
@@ -33,17 +33,33 @@
/*
* List of all available backends
*/
-static struct ref_storage_be *refs_backends = &refs_be_files;
+static const struct ref_storage_be *refs_backends[] = {
+ [REF_STORAGE_FORMAT_FILES] = &refs_be_files,
+};
-static struct ref_storage_be *find_ref_storage_backend(const char *name)
+static const struct ref_storage_be *find_ref_storage_backend(unsigned int ref_storage_format)
{
- struct ref_storage_be *be;
- for (be = refs_backends; be; be = be->next)
- if (!strcmp(be->name, name))
- return be;
+ if (ref_storage_format < ARRAY_SIZE(refs_backends))
+ return refs_backends[ref_storage_format];
return NULL;
}
+unsigned int ref_storage_format_by_name(const char *name)
+{
+ for (unsigned int i = 0; i < ARRAY_SIZE(refs_backends); i++)
+ if (refs_backends[i] && !strcmp(refs_backends[i]->name, name))
+ return i;
+ return REF_STORAGE_FORMAT_UNKNOWN;
+}
+
+const char *ref_storage_format_to_name(unsigned int ref_storage_format)
+{
+ const struct ref_storage_be *be = find_ref_storage_backend(ref_storage_format);
+ if (!be)
+ return "unknown";
+ return be->name;
+}
+
/*
* How to handle various characters in refnames:
* 0: An acceptable character for refs
@@ -1823,13 +1839,10 @@ done:
static int is_special_ref(const char *refname)
{
/*
- * Special references get written and read directly via the filesystem
- * by the subsystems that create them. Thus, they must not go through
- * the reference backend but must instead be read directly. It is
- * arguable whether this behaviour is sensible, or whether it's simply
- * a leaky abstraction enabled by us only having a single reference
- * backend implementation. But at least for a subset of references it
- * indeed does make sense to treat them specially:
+ * Special references are refs that have different semantics compared
+ * to "normal" refs. These refs can thus not be stored in the ref
+ * backend, but must always be accessed via the filesystem. The
+ * following refs are special:
*
* - FETCH_HEAD may contain multiple object IDs, and each one of them
* carries additional metadata like where it came from.
@@ -1837,30 +1850,12 @@ static int is_special_ref(const char *refname)
* - MERGE_HEAD may contain multiple object IDs when merging multiple
* heads.
*
- * There are some exceptions that you might expect to see on this list
- * but which are handled exclusively via the reference backend:
- *
- * - BISECT_EXPECTED_REV
- *
- * - CHERRY_PICK_HEAD
- *
- * - HEAD
- *
- * - ORIG_HEAD
- *
- * - "rebase-apply/" and "rebase-merge/" contain all of the state for
- * rebases, including some reference-like files. These are
- * exclusively read and written via the filesystem and never go
- * through the refdb.
- *
- * Writing or deleting references must consistently go either through
- * the filesystem (special refs) or through the reference backend
- * (normal ones).
+ * Reading, writing or deleting references must consistently go either
+ * through the filesystem (special refs) or through the reference
+ * backend (normal ones).
*/
static const char * const special_refs[] = {
- "AUTO_MERGE",
"FETCH_HEAD",
- "MERGE_AUTOSTASH",
"MERGE_HEAD",
};
size_t i;
@@ -1981,11 +1976,9 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
}
/* backend functions */
-int refs_init_db(struct strbuf *err)
+int refs_init_db(struct ref_store *refs, int flags, struct strbuf *err)
{
- struct ref_store *refs = get_main_ref_store(the_repository);
-
- return refs->be->init_db(refs, err);
+ return refs->be->init_db(refs, flags, err);
}
const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
@@ -2082,12 +2075,12 @@ static struct ref_store *ref_store_init(struct repository *repo,
const char *gitdir,
unsigned int flags)
{
- const char *be_name = "files";
- struct ref_storage_be *be = find_ref_storage_backend(be_name);
+ const struct ref_storage_be *be;
struct ref_store *refs;
+ be = find_ref_storage_backend(repo->ref_storage_format);
if (!be)
- BUG("reference backend %s is unknown", be_name);
+ BUG("reference backend is unknown");
refs = be->init(repo, gitdir, flags);
return refs;
diff --git a/refs.h b/refs.h
index ff113bb12a..303c5fac4d 100644
--- a/refs.h
+++ b/refs.h
@@ -11,6 +11,9 @@ struct string_list;
struct string_list_item;
struct worktree;
+unsigned int ref_storage_format_by_name(const char *name);
+const char *ref_storage_format_to_name(unsigned int ref_storage_format);
+
/*
* Resolve a reference, recursively following symbolic refererences.
*
@@ -123,7 +126,9 @@ int should_autocreate_reflog(const char *refname);
int is_branch(const char *refname);
-int refs_init_db(struct strbuf *err);
+#define REFS_INIT_DB_IS_WORKTREE (1 << 0)
+
+int refs_init_db(struct ref_store *refs, int flags, struct strbuf *err);
/*
* Return the peeled value of the oid currently being iterated via
diff --git a/refs/debug.c b/refs/debug.c
index 83b7a0ba65..634681ca44 100644
--- a/refs/debug.c
+++ b/refs/debug.c
@@ -33,10 +33,10 @@ struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_stor
return (struct ref_store *)res;
}
-static int debug_init_db(struct ref_store *refs, struct strbuf *err)
+static int debug_init_db(struct ref_store *refs, int flags, struct strbuf *err)
{
struct debug_ref_store *drefs = (struct debug_ref_store *)refs;
- int res = drefs->refs->be->init_db(drefs->refs, err);
+ int res = drefs->refs->be->init_db(drefs->refs, flags, err);
trace_printf_key(&trace_refs, "init_db: %d\n", res);
return res;
}
@@ -426,7 +426,6 @@ static int debug_reflog_expire(struct ref_store *ref_store, const char *refname,
}
struct ref_storage_be refs_be_debug = {
- .next = NULL,
.name = "debug",
.init = NULL,
.init_db = debug_init_db,
diff --git a/refs/files-backend.c b/refs/files-backend.c
index 6734f2a309..75dcc21ecb 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -3218,28 +3218,52 @@ static int files_reflog_expire(struct ref_store *ref_store,
return -1;
}
-static int files_init_db(struct ref_store *ref_store, struct strbuf *err UNUSED)
+static int files_init_db(struct ref_store *ref_store,
+ int flags,
+ struct strbuf *err UNUSED)
{
struct files_ref_store *refs =
files_downcast(ref_store, REF_STORE_WRITE, "init_db");
struct strbuf sb = STRBUF_INIT;
/*
- * Create .git/refs/{heads,tags}
+ * We need to create a "refs" dir in any case so that older versions of
+ * Git can tell that this is a repository. This serves two main purposes:
+ *
+ * - Clients will know to stop walking the parent-directory chain when
+ * detecting the Git repository. Otherwise they may end up detecting
+ * a Git repository in a parent directory instead.
+ *
+ * - Instead of failing to detect a repository with unknown reference
+ * format altogether, old clients will print an error saying that
+ * they do not understand the reference format extension.
*/
- files_ref_path(refs, &sb, "refs/heads");
+ strbuf_addf(&sb, "%s/refs", ref_store->gitdir);
safe_create_dir(sb.buf, 1);
+ adjust_shared_perm(sb.buf);
- strbuf_reset(&sb);
- files_ref_path(refs, &sb, "refs/tags");
- safe_create_dir(sb.buf, 1);
+ /*
+ * There is no need to create directories for common refs when creating
+ * a worktree ref store.
+ */
+ if (!(flags & REFS_INIT_DB_IS_WORKTREE)) {
+ /*
+ * Create .git/refs/{heads,tags}
+ */
+ strbuf_reset(&sb);
+ files_ref_path(refs, &sb, "refs/heads");
+ safe_create_dir(sb.buf, 1);
+
+ strbuf_reset(&sb);
+ files_ref_path(refs, &sb, "refs/tags");
+ safe_create_dir(sb.buf, 1);
+ }
strbuf_release(&sb);
return 0;
}
struct ref_storage_be refs_be_files = {
- .next = NULL,
.name = "files",
.init = files_ref_store_create,
.init_db = files_init_db,
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index e46906e612..a499a91c7e 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1245,6 +1245,7 @@ static const char PACKED_REFS_HEADER[] =
"# pack-refs with: peeled fully-peeled sorted \n";
static int packed_init_db(struct ref_store *ref_store UNUSED,
+ int flags UNUSED,
struct strbuf *err UNUSED)
{
/* Nothing to do. */
@@ -1704,7 +1705,6 @@ static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_s
}
struct ref_storage_be refs_be_packed = {
- .next = NULL,
.name = "packed",
.init = packed_ref_store_create,
.init_db = packed_init_db,
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 4af83bf9a5..82219829b0 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -529,7 +529,9 @@ typedef struct ref_store *ref_store_init_fn(struct repository *repo,
const char *gitdir,
unsigned int flags);
-typedef int ref_init_db_fn(struct ref_store *refs, struct strbuf *err);
+typedef int ref_init_db_fn(struct ref_store *refs,
+ int flags,
+ struct strbuf *err);
typedef int ref_transaction_prepare_fn(struct ref_store *refs,
struct ref_transaction *transaction,
@@ -663,7 +665,6 @@ typedef int read_symbolic_ref_fn(struct ref_store *ref_store, const char *refnam
struct strbuf *referent);
struct ref_storage_be {
- struct ref_storage_be *next;
const char *name;
ref_store_init_fn *init;
ref_init_db_fn *init_db;
diff --git a/reftable/block_test.c b/reftable/block_test.c
index c00bbc8aed..dedb05c7d8 100644
--- a/reftable/block_test.c
+++ b/reftable/block_test.c
@@ -49,13 +49,11 @@ static void test_block_read_write(void)
for (i = 0; i < N; i++) {
char name[100];
- uint8_t hash[GIT_SHA1_RAWSZ];
snprintf(name, sizeof(name), "branch%02d", i);
- memset(hash, i, sizeof(hash));
rec.u.ref.refname = name;
rec.u.ref.value_type = REFTABLE_REF_VAL1;
- rec.u.ref.value.val1 = hash;
+ memset(rec.u.ref.value.val1, i, GIT_SHA1_RAWSZ);
names[i] = xstrdup(name);
n = block_writer_add(&bw, &rec);
diff --git a/reftable/blocksource.c b/reftable/blocksource.c
index a1ea304429..8c41e3c70f 100644
--- a/reftable/blocksource.c
+++ b/reftable/blocksource.c
@@ -76,8 +76,8 @@ struct reftable_block_source malloc_block_source(void)
}
struct file_block_source {
- int fd;
uint64_t size;
+ unsigned char *data;
};
static uint64_t file_size(void *b)
@@ -87,19 +87,12 @@ static uint64_t file_size(void *b)
static void file_return_block(void *b, struct reftable_block *dest)
{
- if (dest->len)
- memset(dest->data, 0xff, dest->len);
- reftable_free(dest->data);
}
-static void file_close(void *b)
+static void file_close(void *v)
{
- int fd = ((struct file_block_source *)b)->fd;
- if (fd > 0) {
- close(fd);
- ((struct file_block_source *)b)->fd = 0;
- }
-
+ struct file_block_source *b = v;
+ munmap(b->data, b->size);
reftable_free(b);
}
@@ -108,9 +101,7 @@ static int file_read_block(void *v, struct reftable_block *dest, uint64_t off,
{
struct file_block_source *b = v;
assert(off + size <= b->size);
- dest->data = reftable_malloc(size);
- if (pread_in_full(b->fd, dest->data, size, off) != size)
- return -1;
+ dest->data = b->data + off;
dest->len = size;
return size;
}
@@ -125,26 +116,26 @@ static struct reftable_block_source_vtable file_vtable = {
int reftable_block_source_from_file(struct reftable_block_source *bs,
const char *name)
{
- struct stat st = { 0 };
- int err = 0;
- int fd = open(name, O_RDONLY);
- struct file_block_source *p = NULL;
+ struct file_block_source *p;
+ struct stat st;
+ int fd;
+
+ fd = open(name, O_RDONLY);
if (fd < 0) {
- if (errno == ENOENT) {
+ if (errno == ENOENT)
return REFTABLE_NOT_EXIST_ERROR;
- }
return -1;
}
- err = fstat(fd, &st);
- if (err < 0) {
+ if (fstat(fd, &st) < 0) {
close(fd);
return REFTABLE_IO_ERROR;
}
- p = reftable_calloc(sizeof(struct file_block_source));
+ p = reftable_calloc(sizeof(*p));
p->size = st.st_size;
- p->fd = fd;
+ p->data = xmmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ close(fd);
assert(!bs->ops);
bs->ops = &file_vtable;
diff --git a/reftable/merged.c b/reftable/merged.c
index 574394092d..c258ce953e 100644
--- a/reftable/merged.c
+++ b/reftable/merged.c
@@ -123,12 +123,12 @@ static int merged_iter_next_entry(struct merged_iter *mi,
reftable_record_release(&top.rec);
}
- reftable_record_copy_from(rec, &entry.rec, hash_size(mi->hash_id));
+ reftable_record_release(rec);
+ *rec = entry.rec;
done:
- reftable_record_release(&entry.rec);
- strbuf_release(&mi->entry_key);
- strbuf_release(&mi->key);
+ if (err)
+ reftable_record_release(&entry.rec);
return err;
}
diff --git a/reftable/merged_test.c b/reftable/merged_test.c
index 0d6e0d4bf5..bf090b474e 100644
--- a/reftable/merged_test.c
+++ b/reftable/merged_test.c
@@ -42,7 +42,7 @@ static void write_test_table(struct strbuf *buf,
}
}
- w = reftable_new_writer(&strbuf_add_void, buf, &opts);
+ w = reftable_new_writer(&strbuf_add_void, &noop_flush, buf, &opts);
reftable_writer_set_limits(w, min, max);
for (i = 0; i < n; i++) {
@@ -70,7 +70,7 @@ static void write_test_log_table(struct strbuf *buf,
.exact_log_message = 1,
};
struct reftable_writer *w = NULL;
- w = reftable_new_writer(&strbuf_add_void, buf, &opts);
+ w = reftable_new_writer(&strbuf_add_void, &noop_flush, buf, &opts);
reftable_writer_set_limits(w, update_index, update_index);
for (i = 0; i < n; i++) {
@@ -122,13 +122,11 @@ static void readers_destroy(struct reftable_reader **readers, size_t n)
static void test_merged_between(void)
{
- uint8_t hash1[GIT_SHA1_RAWSZ] = { 1, 2, 3, 0 };
-
struct reftable_ref_record r1[] = { {
.refname = "b",
.update_index = 1,
.value_type = REFTABLE_REF_VAL1,
- .value.val1 = hash1,
+ .value.val1 = { 1, 2, 3, 0 },
} };
struct reftable_ref_record r2[] = { {
.refname = "a",
@@ -164,26 +162,24 @@ static void test_merged_between(void)
static void test_merged(void)
{
- uint8_t hash1[GIT_SHA1_RAWSZ] = { 1 };
- uint8_t hash2[GIT_SHA1_RAWSZ] = { 2 };
struct reftable_ref_record r1[] = {
{
.refname = "a",
.update_index = 1,
.value_type = REFTABLE_REF_VAL1,
- .value.val1 = hash1,
+ .value.val1 = { 1 },
},
{
.refname = "b",
.update_index = 1,
.value_type = REFTABLE_REF_VAL1,
- .value.val1 = hash1,
+ .value.val1 = { 1 },
},
{
.refname = "c",
.update_index = 1,
.value_type = REFTABLE_REF_VAL1,
- .value.val1 = hash1,
+ .value.val1 = { 1 },
}
};
struct reftable_ref_record r2[] = { {
@@ -196,13 +192,13 @@ static void test_merged(void)
.refname = "c",
.update_index = 3,
.value_type = REFTABLE_REF_VAL1,
- .value.val1 = hash2,
+ .value.val1 = { 2 },
},
{
.refname = "d",
.update_index = 3,
.value_type = REFTABLE_REF_VAL1,
- .value.val1 = hash1,
+ .value.val1 = { 1 },
},
};
@@ -416,7 +412,7 @@ static void test_default_write_opts(void)
struct reftable_write_options opts = { 0 };
struct strbuf buf = STRBUF_INIT;
struct reftable_writer *w =
- reftable_new_writer(&strbuf_add_void, &buf, &opts);
+ reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
struct reftable_ref_record rec = {
.refname = "master",
diff --git a/reftable/pq_test.c b/reftable/pq_test.c
index 011b5c7502..c202eff848 100644
--- a/reftable/pq_test.c
+++ b/reftable/pq_test.c
@@ -60,7 +60,7 @@ static void test_pq(void)
if (last) {
EXPECT(strcmp(last, rec->u.ref.refname) < 0);
}
- // this is names[i], so don't dealloc.
+ /* this is names[i], so don't dealloc. */
last = rec->u.ref.refname;
rec->u.ref.refname = NULL;
reftable_record_release(rec);
diff --git a/reftable/readwrite_test.c b/reftable/readwrite_test.c
index 79cd4e4c28..6b99daeaf2 100644
--- a/reftable/readwrite_test.c
+++ b/reftable/readwrite_test.c
@@ -51,7 +51,7 @@ static void write_table(char ***names, struct strbuf *buf, int N,
.hash_id = hash_id,
};
struct reftable_writer *w =
- reftable_new_writer(&strbuf_add_void, buf, &opts);
+ reftable_new_writer(&strbuf_add_void, &noop_flush, buf, &opts);
struct reftable_ref_record ref = { NULL };
int i = 0, n;
struct reftable_log_record log = { NULL };
@@ -59,18 +59,15 @@ static void write_table(char ***names, struct strbuf *buf, int N,
*names = reftable_calloc(sizeof(char *) * (N + 1));
reftable_writer_set_limits(w, update_index, update_index);
for (i = 0; i < N; i++) {
- uint8_t hash[GIT_SHA256_RAWSZ] = { 0 };
char name[100];
int n;
- set_test_hash(hash, i);
-
snprintf(name, sizeof(name), "refs/heads/branch%02d", i);
ref.refname = name;
ref.update_index = update_index;
ref.value_type = REFTABLE_REF_VAL1;
- ref.value.val1 = hash;
+ set_test_hash(ref.value.val1, i);
(*names)[i] = xstrdup(name);
n = reftable_writer_add_ref(w, &ref);
@@ -133,7 +130,7 @@ static void test_log_buffer_size(void)
.message = "commit: 9\n",
} } };
struct reftable_writer *w =
- reftable_new_writer(&strbuf_add_void, &buf, &opts);
+ reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
/* This tests buffer extension for log compression. Must use a random
hash, to ensure that the compressed part is larger than the original.
@@ -174,7 +171,7 @@ static void test_log_overflow(void)
.message = msg,
} } };
struct reftable_writer *w =
- reftable_new_writer(&strbuf_add_void, &buf, &opts);
+ reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
uint8_t hash1[GIT_SHA1_RAWSZ] = {1}, hash2[GIT_SHA1_RAWSZ] = { 2 };
@@ -205,7 +202,7 @@ static void test_log_write_read(void)
struct reftable_block_source source = { NULL };
struct strbuf buf = STRBUF_INIT;
struct reftable_writer *w =
- reftable_new_writer(&strbuf_add_void, &buf, &opts);
+ reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
const struct reftable_stats *stats = NULL;
reftable_writer_set_limits(w, 0, N);
for (i = 0; i < N; i++) {
@@ -297,7 +294,7 @@ static void test_log_zlib_corruption(void)
struct reftable_block_source source = { 0 };
struct strbuf buf = STRBUF_INIT;
struct reftable_writer *w =
- reftable_new_writer(&strbuf_add_void, &buf, &opts);
+ reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
const struct reftable_stats *stats = NULL;
uint8_t hash1[GIT_SHA1_RAWSZ] = { 1 };
uint8_t hash2[GIT_SHA1_RAWSZ] = { 2 };
@@ -538,7 +535,7 @@ static void test_table_refs_for(int indexed)
struct strbuf buf = STRBUF_INIT;
struct reftable_writer *w =
- reftable_new_writer(&strbuf_add_void, &buf, &opts);
+ reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
struct reftable_iterator it = { NULL };
int j;
@@ -549,8 +546,6 @@ static void test_table_refs_for(int indexed)
uint8_t hash[GIT_SHA1_RAWSZ];
char fill[51] = { 0 };
char name[100];
- uint8_t hash1[GIT_SHA1_RAWSZ];
- uint8_t hash2[GIT_SHA1_RAWSZ];
struct reftable_ref_record ref = { NULL };
memset(hash, i, sizeof(hash));
@@ -560,11 +555,9 @@ static void test_table_refs_for(int indexed)
name[40] = 0;
ref.refname = name;
- set_test_hash(hash1, i / 4);
- set_test_hash(hash2, 3 + i / 4);
ref.value_type = REFTABLE_REF_VAL2;
- ref.value.val2.value = hash1;
- ref.value.val2.target_value = hash2;
+ set_test_hash(ref.value.val2.value, i / 4);
+ set_test_hash(ref.value.val2.target_value, 3 + i / 4);
/* 80 bytes / entry, so 3 entries per block. Yields 17
*/
@@ -572,8 +565,8 @@ static void test_table_refs_for(int indexed)
n = reftable_writer_add_ref(w, &ref);
EXPECT(n == 0);
- if (!memcmp(hash1, want_hash, GIT_SHA1_RAWSZ) ||
- !memcmp(hash2, want_hash, GIT_SHA1_RAWSZ)) {
+ if (!memcmp(ref.value.val2.value, want_hash, GIT_SHA1_RAWSZ) ||
+ !memcmp(ref.value.val2.target_value, want_hash, GIT_SHA1_RAWSZ)) {
want_names[want_names_len++] = xstrdup(name);
}
}
@@ -635,7 +628,7 @@ static void test_write_empty_table(void)
struct reftable_write_options opts = { 0 };
struct strbuf buf = STRBUF_INIT;
struct reftable_writer *w =
- reftable_new_writer(&strbuf_add_void, &buf, &opts);
+ reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
struct reftable_block_source source = { NULL };
struct reftable_reader *rd = NULL;
struct reftable_ref_record rec = { NULL };
@@ -673,12 +666,11 @@ static void test_write_object_id_min_length(void)
};
struct strbuf buf = STRBUF_INIT;
struct reftable_writer *w =
- reftable_new_writer(&strbuf_add_void, &buf, &opts);
- uint8_t hash[GIT_SHA1_RAWSZ] = {42};
+ reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
struct reftable_ref_record ref = {
.update_index = 1,
.value_type = REFTABLE_REF_VAL1,
- .value.val1 = hash,
+ .value.val1 = {42},
};
int err;
int i;
@@ -709,12 +701,11 @@ static void test_write_object_id_length(void)
};
struct strbuf buf = STRBUF_INIT;
struct reftable_writer *w =
- reftable_new_writer(&strbuf_add_void, &buf, &opts);
- uint8_t hash[GIT_SHA1_RAWSZ] = {42};
+ reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
struct reftable_ref_record ref = {
.update_index = 1,
.value_type = REFTABLE_REF_VAL1,
- .value.val1 = hash,
+ .value.val1 = {42},
};
int err;
int i;
@@ -744,7 +735,7 @@ static void test_write_empty_key(void)
struct reftable_write_options opts = { 0 };
struct strbuf buf = STRBUF_INIT;
struct reftable_writer *w =
- reftable_new_writer(&strbuf_add_void, &buf, &opts);
+ reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
struct reftable_ref_record ref = {
.refname = "",
.update_index = 1,
@@ -767,7 +758,7 @@ static void test_write_key_order(void)
struct reftable_write_options opts = { 0 };
struct strbuf buf = STRBUF_INIT;
struct reftable_writer *w =
- reftable_new_writer(&strbuf_add_void, &buf, &opts);
+ reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
struct reftable_ref_record refs[2] = {
{
.refname = "b",
@@ -797,6 +788,84 @@ static void test_write_key_order(void)
strbuf_release(&buf);
}
+static void test_write_multiple_indices(void)
+{
+ struct reftable_write_options opts = {
+ .block_size = 100,
+ };
+ struct strbuf writer_buf = STRBUF_INIT, buf = STRBUF_INIT;
+ struct reftable_block_source source = { 0 };
+ struct reftable_iterator it = { 0 };
+ const struct reftable_stats *stats;
+ struct reftable_writer *writer;
+ struct reftable_reader *reader;
+ int err, i;
+
+ writer = reftable_new_writer(&strbuf_add_void, &noop_flush, &writer_buf, &opts);
+ reftable_writer_set_limits(writer, 1, 1);
+ for (i = 0; i < 100; i++) {
+ struct reftable_ref_record ref = {
+ .update_index = 1,
+ .value_type = REFTABLE_REF_VAL1,
+ .value.val1 = {i},
+ };
+
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "refs/heads/%04d", i);
+ ref.refname = buf.buf,
+
+ err = reftable_writer_add_ref(writer, &ref);
+ EXPECT_ERR(err);
+ }
+
+ for (i = 0; i < 100; i++) {
+ unsigned char hash[GIT_SHA1_RAWSZ] = {i};
+ struct reftable_log_record log = {
+ .update_index = 1,
+ .value_type = REFTABLE_LOG_UPDATE,
+ .value.update = {
+ .old_hash = hash,
+ .new_hash = hash,
+ },
+ };
+
+ strbuf_reset(&buf);
+ strbuf_addf(&buf, "refs/heads/%04d", i);
+ log.refname = buf.buf,
+
+ err = reftable_writer_add_log(writer, &log);
+ EXPECT_ERR(err);
+ }
+
+ reftable_writer_close(writer);
+
+ /*
+ * The written data should be sufficiently large to result in indices
+ * for each of the block types.
+ */
+ stats = reftable_writer_stats(writer);
+ EXPECT(stats->ref_stats.index_offset > 0);
+ EXPECT(stats->obj_stats.index_offset > 0);
+ EXPECT(stats->log_stats.index_offset > 0);
+
+ block_source_from_strbuf(&source, &writer_buf);
+ err = reftable_new_reader(&reader, &source, "filename");
+ EXPECT_ERR(err);
+
+ /*
+ * Seeking the log uses the log index now. In case there is any
+ * confusion regarding indices we would notice here.
+ */
+ err = reftable_reader_seek_log(reader, &it, "");
+ EXPECT_ERR(err);
+
+ reftable_iterator_destroy(&it);
+ reftable_writer_free(writer);
+ reftable_reader_free(reader);
+ strbuf_release(&writer_buf);
+ strbuf_release(&buf);
+}
+
static void test_corrupt_table_empty(void)
{
struct strbuf buf = STRBUF_INIT;
@@ -846,5 +915,6 @@ int readwrite_test_main(int argc, const char *argv[])
RUN_TEST(test_log_overflow);
RUN_TEST(test_write_object_id_length);
RUN_TEST(test_write_object_id_min_length);
+ RUN_TEST(test_write_multiple_indices);
return 0;
}
diff --git a/reftable/record.c b/reftable/record.c
index fbaa1fbef5..5c3fbb7b2a 100644
--- a/reftable/record.c
+++ b/reftable/record.c
@@ -76,7 +76,7 @@ int reftable_is_block_type(uint8_t typ)
return 0;
}
-uint8_t *reftable_ref_record_val1(const struct reftable_ref_record *rec)
+const unsigned char *reftable_ref_record_val1(const struct reftable_ref_record *rec)
{
switch (rec->value_type) {
case REFTABLE_REF_VAL1:
@@ -88,7 +88,7 @@ uint8_t *reftable_ref_record_val1(const struct reftable_ref_record *rec)
}
}
-uint8_t *reftable_ref_record_val2(const struct reftable_ref_record *rec)
+const unsigned char *reftable_ref_record_val2(const struct reftable_ref_record *rec)
{
switch (rec->value_type) {
case REFTABLE_REF_VAL2:
@@ -219,13 +219,10 @@ static void reftable_ref_record_copy_from(void *rec, const void *src_rec,
case REFTABLE_REF_DELETION:
break;
case REFTABLE_REF_VAL1:
- ref->value.val1 = reftable_malloc(hash_size);
memcpy(ref->value.val1, src->value.val1, hash_size);
break;
case REFTABLE_REF_VAL2:
- ref->value.val2.value = reftable_malloc(hash_size);
memcpy(ref->value.val2.value, src->value.val2.value, hash_size);
- ref->value.val2.target_value = reftable_malloc(hash_size);
memcpy(ref->value.val2.target_value,
src->value.val2.target_value, hash_size);
break;
@@ -242,7 +239,7 @@ static char hexdigit(int c)
return 'a' + (c - 10);
}
-static void hex_format(char *dest, uint8_t *src, int hash_size)
+static void hex_format(char *dest, const unsigned char *src, int hash_size)
{
assert(hash_size > 0);
if (src) {
@@ -299,11 +296,8 @@ void reftable_ref_record_release(struct reftable_ref_record *ref)
reftable_free(ref->value.symref);
break;
case REFTABLE_REF_VAL2:
- reftable_free(ref->value.val2.target_value);
- reftable_free(ref->value.val2.value);
break;
case REFTABLE_REF_VAL1:
- reftable_free(ref->value.val1);
break;
case REFTABLE_REF_DELETION:
break;
@@ -394,7 +388,6 @@ static int reftable_ref_record_decode(void *rec, struct strbuf key,
return -1;
}
- r->value.val1 = reftable_malloc(hash_size);
memcpy(r->value.val1, in.buf, hash_size);
string_view_consume(&in, hash_size);
break;
@@ -404,11 +397,9 @@ static int reftable_ref_record_decode(void *rec, struct strbuf key,
return -1;
}
- r->value.val2.value = reftable_malloc(hash_size);
memcpy(r->value.val2.value, in.buf, hash_size);
string_view_consume(&in, hash_size);
- r->value.val2.target_value = reftable_malloc(hash_size);
memcpy(r->value.val2.target_value, in.buf, hash_size);
string_view_consume(&in, hash_size);
break;
@@ -1164,7 +1155,7 @@ int reftable_record_equal(struct reftable_record *a, struct reftable_record *b,
reftable_record_data(a), reftable_record_data(b), hash_size);
}
-static int hash_equal(uint8_t *a, uint8_t *b, int hash_size)
+static int hash_equal(const unsigned char *a, const unsigned char *b, int hash_size)
{
if (a && b)
return !memcmp(a, b, hash_size);
diff --git a/reftable/record_test.c b/reftable/record_test.c
index 70ae78feca..2876db7d27 100644
--- a/reftable/record_test.c
+++ b/reftable/record_test.c
@@ -119,15 +119,10 @@ static void test_reftable_ref_record_roundtrip(void)
case REFTABLE_REF_DELETION:
break;
case REFTABLE_REF_VAL1:
- in.u.ref.value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
set_hash(in.u.ref.value.val1, 1);
break;
case REFTABLE_REF_VAL2:
- in.u.ref.value.val2.value =
- reftable_malloc(GIT_SHA1_RAWSZ);
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:
diff --git a/reftable/refname_test.c b/reftable/refname_test.c
index 699e1aea41..b9cc62554e 100644
--- a/reftable/refname_test.c
+++ b/reftable/refname_test.c
@@ -30,7 +30,7 @@ static void test_conflict(void)
struct reftable_write_options opts = { 0 };
struct strbuf buf = STRBUF_INIT;
struct reftable_writer *w =
- reftable_new_writer(&strbuf_add_void, &buf, &opts);
+ reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
struct reftable_ref_record rec = {
.refname = "a/b",
.value_type = REFTABLE_REF_SYMREF,
diff --git a/reftable/reftable-record.h b/reftable/reftable-record.h
index 67104f8fbf..bb6e99acd3 100644
--- a/reftable/reftable-record.h
+++ b/reftable/reftable-record.h
@@ -9,6 +9,7 @@ https://developers.google.com/open-source/licenses/bsd
#ifndef REFTABLE_RECORD_H
#define REFTABLE_RECORD_H
+#include "hash-ll.h"
#include <stdint.h>
/*
@@ -38,10 +39,10 @@ struct reftable_ref_record {
#define REFTABLE_NR_REF_VALUETYPES 4
} value_type;
union {
- uint8_t *val1; /* malloced hash. */
+ unsigned char val1[GIT_MAX_RAWSZ];
struct {
- uint8_t *value; /* first value, malloced hash */
- uint8_t *target_value; /* second value, malloced hash */
+ unsigned char value[GIT_MAX_RAWSZ]; /* first hash */
+ unsigned char target_value[GIT_MAX_RAWSZ]; /* second hash */
} val2;
char *symref; /* referent, malloced 0-terminated string */
} value;
@@ -49,11 +50,11 @@ 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(const struct reftable_ref_record *rec);
+const unsigned char *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(const struct reftable_ref_record *rec);
+const unsigned char *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);
diff --git a/reftable/reftable-writer.h b/reftable/reftable-writer.h
index db8de197f6..7c7cae5f99 100644
--- a/reftable/reftable-writer.h
+++ b/reftable/reftable-writer.h
@@ -88,6 +88,7 @@ struct reftable_stats {
/* reftable_new_writer creates a new writer */
struct reftable_writer *
reftable_new_writer(ssize_t (*writer_func)(void *, const void *, size_t),
+ int (*flush_func)(void *),
void *writer_arg, struct reftable_write_options *opts);
/* Set the range of update indices for the records we will add. When writing a
diff --git a/reftable/stack.c b/reftable/stack.c
index 16bab82063..a1dd79fc06 100644
--- a/reftable/stack.c
+++ b/reftable/stack.c
@@ -8,6 +8,7 @@ https://developers.google.com/open-source/licenses/bsd
#include "stack.h"
+#include "../write-or-die.h"
#include "system.h"
#include "merged.h"
#include "reader.h"
@@ -16,7 +17,6 @@ https://developers.google.com/open-source/licenses/bsd
#include "reftable-record.h"
#include "reftable-merged.h"
#include "writer.h"
-
#include "tempfile.h"
static int stack_try_add(struct reftable_stack *st,
@@ -47,6 +47,13 @@ static ssize_t reftable_fd_write(void *arg, const void *data, size_t sz)
return write_in_full(*fdp, data, sz);
}
+static int reftable_fd_flush(void *arg)
+{
+ int *fdp = (int *)arg;
+
+ return fsync_component(FSYNC_COMPONENT_REFERENCE, *fdp);
+}
+
int reftable_new_stack(struct reftable_stack **dest, const char *dir,
struct reftable_write_options config)
{
@@ -66,6 +73,7 @@ int reftable_new_stack(struct reftable_stack **dest, const char *dir,
strbuf_addstr(&list_file_name, "/tables.list");
p->list_file = strbuf_detach(&list_file_name, NULL);
+ p->list_fd = -1;
p->reftable_dir = xstrdup(dir);
p->config = config;
@@ -175,6 +183,12 @@ void reftable_stack_destroy(struct reftable_stack *st)
st->readers_len = 0;
FREE_AND_NULL(st->readers);
}
+
+ if (st->list_fd >= 0) {
+ close(st->list_fd);
+ st->list_fd = -1;
+ }
+
FREE_AND_NULL(st->list_file);
FREE_AND_NULL(st->reftable_dir);
reftable_free(st);
@@ -304,69 +318,134 @@ static int tv_cmp(struct timeval *a, struct timeval *b)
static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
int reuse_open)
{
- struct timeval deadline = { 0 };
- int err = gettimeofday(&deadline, NULL);
+ char **names = NULL, **names_after = NULL;
+ struct timeval deadline;
int64_t delay = 0;
- int tries = 0;
- if (err < 0)
- return err;
+ int tries = 0, err;
+ int fd = -1;
+ err = gettimeofday(&deadline, NULL);
+ if (err < 0)
+ goto out;
deadline.tv_sec += 3;
+
while (1) {
- char **names = NULL;
- char **names_after = NULL;
- struct timeval now = { 0 };
- int err = gettimeofday(&now, NULL);
- int err2 = 0;
- if (err < 0) {
- return err;
- }
+ struct timeval now;
+
+ err = gettimeofday(&now, NULL);
+ if (err < 0)
+ goto out;
- /* Only look at deadlines after the first few times. This
- simplifies debugging in GDB */
+ /*
+ * Only look at deadlines after the first few times. This
+ * simplifies debugging in GDB.
+ */
tries++;
- if (tries > 3 && tv_cmp(&now, &deadline) >= 0) {
- break;
- }
+ if (tries > 3 && tv_cmp(&now, &deadline) >= 0)
+ goto out;
- err = read_lines(st->list_file, &names);
- if (err < 0) {
- free_names(names);
- return err;
- }
- err = reftable_stack_reload_once(st, names, reuse_open);
- if (err == 0) {
- free_names(names);
- break;
- }
- if (err != REFTABLE_NOT_EXIST_ERROR) {
- free_names(names);
- return err;
- }
+ fd = open(st->list_file, O_RDONLY);
+ if (fd < 0) {
+ if (errno != ENOENT) {
+ err = REFTABLE_IO_ERROR;
+ goto out;
+ }
- /* err == REFTABLE_NOT_EXIST_ERROR can be caused by a concurrent
- writer. Check if there was one by checking if the name list
- changed.
- */
- err2 = read_lines(st->list_file, &names_after);
- if (err2 < 0) {
- free_names(names);
- return err2;
+ names = reftable_calloc(sizeof(char *));
+ } else {
+ err = fd_read_lines(fd, &names);
+ if (err < 0)
+ goto out;
}
+ err = reftable_stack_reload_once(st, names, reuse_open);
+ if (!err)
+ break;
+ if (err != REFTABLE_NOT_EXIST_ERROR)
+ goto out;
+
+ /*
+ * REFTABLE_NOT_EXIST_ERROR can be caused by a concurrent
+ * writer. Check if there was one by checking if the name list
+ * changed.
+ */
+ err = read_lines(st->list_file, &names_after);
+ if (err < 0)
+ goto out;
if (names_equal(names_after, names)) {
- free_names(names);
- free_names(names_after);
- return err;
+ err = REFTABLE_NOT_EXIST_ERROR;
+ goto out;
}
+
free_names(names);
+ names = NULL;
free_names(names_after);
+ names_after = NULL;
+ close(fd);
+ fd = -1;
delay = delay + (delay * rand()) / RAND_MAX + 1;
sleep_millisec(delay);
}
- return 0;
+out:
+ /*
+ * Invalidate the stat cache. It is sufficient to only close the file
+ * descriptor and keep the cached stat info because we never use the
+ * latter when the former is negative.
+ */
+ if (st->list_fd >= 0) {
+ close(st->list_fd);
+ st->list_fd = -1;
+ }
+
+ /*
+ * Cache stat information in case it provides a useful signal to us.
+ * According to POSIX, "The st_ino and st_dev fields taken together
+ * uniquely identify the file within the system." That being said,
+ * Windows is not POSIX compliant and we do not have these fields
+ * available. So the information we have there is insufficient to
+ * determine whether two file descriptors point to the same file.
+ *
+ * While we could fall back to using other signals like the file's
+ * mtime, those are not sufficient to avoid races. We thus refrain from
+ * using the stat cache on such systems and fall back to the secondary
+ * caching mechanism, which is to check whether contents of the file
+ * have changed.
+ *
+ * On other systems which are POSIX compliant we must keep the file
+ * descriptor open. This is to avoid a race condition where two
+ * processes access the reftable stack at the same point in time:
+ *
+ * 1. A reads the reftable stack and caches its stat info.
+ *
+ * 2. B updates the stack, appending a new table to "tables.list".
+ * This will both use a new inode and result in a different file
+ * size, thus invalidating A's cache in theory.
+ *
+ * 3. B decides to auto-compact the stack and merges two tables. The
+ * file size now matches what A has cached again. Furthermore, the
+ * filesystem may decide to recycle the inode number of the file
+ * we have replaced in (2) because it is not in use anymore.
+ *
+ * 4. A reloads the reftable stack. Neither the inode number nor the
+ * file size changed. If the timestamps did not change either then
+ * we think the cached copy of our stack is up-to-date.
+ *
+ * By keeping the file descriptor open the inode number cannot be
+ * recycled, mitigating the race.
+ */
+ if (!err && fd >= 0 && !fstat(fd, &st->list_st) &&
+ st->list_st.st_dev && st->list_st.st_ino) {
+ st->list_fd = fd;
+ fd = -1;
+ }
+
+ if (fd >= 0)
+ close(fd);
+ free_names(names);
+ free_names(names_after);
+ return err;
}
/* -1 = error
@@ -375,8 +454,44 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
static int stack_uptodate(struct reftable_stack *st)
{
char **names = NULL;
- int err = read_lines(st->list_file, &names);
+ int err;
int i = 0;
+
+ /*
+ * When we have cached stat information available then we use it to
+ * verify whether the file has been rewritten.
+ *
+ * Note that we explicitly do not want to use `stat_validity_check()`
+ * and friends here because they may end up not comparing the `st_dev`
+ * and `st_ino` fields. These functions thus cannot guarantee that we
+ * indeed still have the same file.
+ */
+ if (st->list_fd >= 0) {
+ struct stat list_st;
+
+ if (stat(st->list_file, &list_st) < 0) {
+ /*
+ * It's fine for "tables.list" to not exist. In that
+ * case, we have to refresh when the loaded stack has
+ * any readers.
+ */
+ if (errno == ENOENT)
+ return !!st->readers_len;
+ return REFTABLE_IO_ERROR;
+ }
+
+ /*
+ * When "tables.list" refers to the same file we can assume
+ * that it didn't change. This is because we always use
+ * rename(3P) to update the file and never write to it
+ * directly.
+ */
+ if (st->list_st.st_dev == list_st.st_dev &&
+ st->list_st.st_ino == list_st.st_ino)
+ return 0;
+ }
+
+ err = read_lines(st->list_file, &names);
if (err < 0)
return err;
@@ -425,9 +540,6 @@ int reftable_stack_add(struct reftable_stack *st,
return err;
}
- if (!st->disable_auto_compact)
- return reftable_stack_auto_compact(st);
-
return 0;
}
@@ -548,6 +660,9 @@ int reftable_addition_commit(struct reftable_addition *add)
goto done;
}
+ fsync_component_or_die(FSYNC_COMPONENT_REFERENCE, lock_file_fd,
+ get_tempfile_path(add->lock_file));
+
err = rename_tempfile(&add->lock_file, add->stack->list_file);
if (err < 0) {
err = REFTABLE_IO_ERROR;
@@ -562,7 +677,7 @@ int reftable_addition_commit(struct reftable_addition *add)
add->new_tables = NULL;
add->new_tables_len = 0;
- err = reftable_stack_reload(add->stack);
+ err = reftable_stack_reload_maybe_reuse(add->stack, 1);
if (err)
goto done;
@@ -642,7 +757,7 @@ int reftable_addition_add(struct reftable_addition *add,
goto done;
}
}
- wr = reftable_new_writer(reftable_fd_write, &tab_fd,
+ wr = reftable_new_writer(reftable_fd_write, reftable_fd_flush, &tab_fd,
&add->stack->config);
err = write_table(wr, arg);
if (err < 0)
@@ -734,7 +849,13 @@ static int stack_compact_locked(struct reftable_stack *st, int first, int last,
strbuf_addstr(temp_tab, ".temp.XXXXXX");
tab_fd = mkstemp(temp_tab->buf);
- wr = reftable_new_writer(reftable_fd_write, &tab_fd, &st->config);
+ if (st->config.default_permissions &&
+ chmod(temp_tab->buf, st->config.default_permissions) < 0) {
+ err = REFTABLE_IO_ERROR;
+ goto done;
+ }
+
+ wr = reftable_new_writer(reftable_fd_write, reftable_fd_flush, &tab_fd, &st->config);
err = stack_write_compact(st, wr, first, last, config);
if (err < 0)
@@ -801,18 +922,16 @@ static int stack_write_compact(struct reftable_stack *st,
err = 0;
break;
}
- if (err < 0) {
- break;
- }
+ if (err < 0)
+ goto done;
if (first == 0 && reftable_ref_record_is_deletion(&ref)) {
continue;
}
err = reftable_writer_add_ref(wr, &ref);
- if (err < 0) {
- break;
- }
+ if (err < 0)
+ goto done;
entries++;
}
reftable_iterator_destroy(&it);
@@ -827,9 +946,8 @@ static int stack_write_compact(struct reftable_stack *st,
err = 0;
break;
}
- if (err < 0) {
- break;
- }
+ if (err < 0)
+ goto done;
if (first == 0 && reftable_log_record_is_deletion(&log)) {
continue;
}
@@ -845,9 +963,8 @@ static int stack_write_compact(struct reftable_stack *st,
}
err = reftable_writer_add_log(wr, &log);
- if (err < 0) {
- break;
- }
+ if (err < 0)
+ goto done;
entries++;
}
@@ -1015,6 +1132,14 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
unlink(new_table_path.buf);
goto done;
}
+
+ err = fsync_component(FSYNC_COMPONENT_REFERENCE, lock_file_fd);
+ if (err < 0) {
+ err = REFTABLE_IO_ERROR;
+ unlink(new_table_path.buf);
+ goto done;
+ }
+
err = close(lock_file_fd);
lock_file_fd = -1;
if (err < 0) {
diff --git a/reftable/stack.h b/reftable/stack.h
index f57005846e..c1e3efa899 100644
--- a/reftable/stack.h
+++ b/reftable/stack.h
@@ -14,7 +14,10 @@ https://developers.google.com/open-source/licenses/bsd
#include "reftable-stack.h"
struct reftable_stack {
+ struct stat list_st;
char *list_file;
+ int list_fd;
+
char *reftable_dir;
int disable_auto_compact;
diff --git a/reftable/stack_test.c b/reftable/stack_test.c
index 82280c2fd5..5089392f7b 100644
--- a/reftable/stack_test.c
+++ b/reftable/stack_test.c
@@ -443,15 +443,16 @@ static void test_reftable_stack_add(void)
int err = 0;
struct reftable_write_options cfg = {
.exact_log_message = 1,
+ .default_permissions = 0660,
};
struct reftable_stack *st = NULL;
char *dir = get_tmp_dir(__LINE__);
-
struct reftable_ref_record refs[2] = { { NULL } };
struct reftable_log_record logs[2] = { { NULL } };
+ struct strbuf path = STRBUF_INIT;
+ struct stat stat_result;
int N = ARRAY_SIZE(refs);
-
err = reftable_new_stack(&st, dir, cfg);
EXPECT_ERR(err);
st->disable_auto_compact = 1;
@@ -462,7 +463,6 @@ static void test_reftable_stack_add(void)
refs[i].refname = xstrdup(buf);
refs[i].update_index = i + 1;
refs[i].value_type = REFTABLE_REF_VAL1;
- refs[i].value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
set_test_hash(refs[i].value.val1, i);
logs[i].refname = xstrdup(buf);
@@ -510,12 +510,32 @@ static void test_reftable_stack_add(void)
reftable_log_record_release(&dest);
}
+#ifndef GIT_WINDOWS_NATIVE
+ strbuf_addstr(&path, dir);
+ strbuf_addstr(&path, "/tables.list");
+ err = stat(path.buf, &stat_result);
+ EXPECT(!err);
+ EXPECT((stat_result.st_mode & 0777) == cfg.default_permissions);
+
+ strbuf_reset(&path);
+ strbuf_addstr(&path, dir);
+ strbuf_addstr(&path, "/");
+ /* do not try at home; not an external API for reftable. */
+ strbuf_addstr(&path, st->readers[0]->name);
+ err = stat(path.buf, &stat_result);
+ EXPECT(!err);
+ EXPECT((stat_result.st_mode & 0777) == cfg.default_permissions);
+#else
+ (void) stat_result;
+#endif
+
/* cleanup */
reftable_stack_destroy(st);
for (i = 0; i < N; i++) {
reftable_ref_record_release(&refs[i]);
reftable_log_record_release(&logs[i]);
}
+ strbuf_release(&path);
clear_dir(dir);
}
@@ -599,7 +619,6 @@ static void test_reftable_stack_tombstone(void)
refs[i].update_index = i + 1;
if (i % 2 == 0) {
refs[i].value_type = REFTABLE_REF_VAL1;
- refs[i].value.val1 = reftable_malloc(GIT_SHA1_RAWSZ);
set_test_hash(refs[i].value.val1, i);
}
diff --git a/reftable/test_framework.c b/reftable/test_framework.c
index 04044fc1a0..4066924eee 100644
--- a/reftable/test_framework.c
+++ b/reftable/test_framework.c
@@ -20,3 +20,8 @@ ssize_t strbuf_add_void(void *b, const void *data, size_t sz)
strbuf_add(b, data, sz);
return sz;
}
+
+int noop_flush(void *arg)
+{
+ return 0;
+}
diff --git a/reftable/test_framework.h b/reftable/test_framework.h
index ee44f735ae..687390f9c2 100644
--- a/reftable/test_framework.h
+++ b/reftable/test_framework.h
@@ -56,4 +56,6 @@ void set_test_hash(uint8_t *p, int i);
*/
ssize_t strbuf_add_void(void *b, const void *data, size_t sz);
+int noop_flush(void *);
+
#endif
diff --git a/reftable/writer.c b/reftable/writer.c
index 2e322a5683..92935baa70 100644
--- a/reftable/writer.c
+++ b/reftable/writer.c
@@ -121,6 +121,7 @@ static struct strbuf reftable_empty_strbuf = STRBUF_INIT;
struct reftable_writer *
reftable_new_writer(ssize_t (*writer_func)(void *, const void *, size_t),
+ int (*flush_func)(void *),
void *writer_arg, struct reftable_write_options *opts)
{
struct reftable_writer *wp =
@@ -136,6 +137,7 @@ reftable_new_writer(ssize_t (*writer_func)(void *, const void *, size_t),
wp->write = writer_func;
wp->write_arg = writer_arg;
wp->opts = *opts;
+ wp->flush = flush_func;
writer_reinit_block_writer(wp, BLOCK_TYPE_REF);
return wp;
@@ -432,12 +434,12 @@ static int writer_finish_section(struct reftable_writer *w)
reftable_free(idx);
}
- writer_clear_index(w);
-
err = writer_flush_block(w);
if (err < 0)
return err;
+ writer_clear_index(w);
+
bstats = writer_reftable_block_stats(w, typ);
bstats->index_blocks = w->stats.idx_stats.blocks - before_blocks;
bstats->index_offset = index_start;
@@ -603,6 +605,12 @@ int reftable_writer_close(struct reftable_writer *w)
put_be32(p, crc32(0, footer, p - footer));
p += 4;
+ err = w->flush(w->write_arg);
+ if (err < 0) {
+ err = REFTABLE_IO_ERROR;
+ goto done;
+ }
+
err = padded_write(w, footer, footer_size(writer_version(w)), 0);
if (err < 0)
goto done;
diff --git a/reftable/writer.h b/reftable/writer.h
index 09b88673d9..8d0df9cc52 100644
--- a/reftable/writer.h
+++ b/reftable/writer.h
@@ -16,6 +16,7 @@ https://developers.google.com/open-source/licenses/bsd
struct reftable_writer {
ssize_t (*write)(void *, const void *, size_t);
+ int (*flush)(void *);
void *write_arg;
int pending_padding;
struct strbuf last_key;
diff --git a/remote-curl.c b/remote-curl.c
index cb0182b582..1161dc7fed 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -1446,8 +1446,14 @@ static int stateless_connect(const char *service_name)
* establish a stateless connection, otherwise we need to tell the
* client to fallback to using other transport helper functions to
* complete their request.
+ *
+ * The "git-upload-archive" service is a read-only operation. Fallback
+ * to use "git-upload-pack" service to discover protocol version.
*/
- discover = discover_refs(service_name, 0);
+ if (!strcmp(service_name, "git-upload-archive"))
+ discover = discover_refs("git-upload-pack", 0);
+ else
+ discover = discover_refs(service_name, 0);
if (discover->version != protocol_v2) {
printf("fallback\n");
fflush(stdout);
@@ -1485,9 +1491,11 @@ static int stateless_connect(const char *service_name)
/*
* Dump the capability listing that we got from the server earlier
- * during the info/refs request.
+ * during the info/refs request. This does not work with the
+ * "git-upload-archive" service.
*/
- write_or_die(rpc.in, discover->buf, discover->len);
+ if (strcmp(service_name, "git-upload-archive"))
+ write_or_die(rpc.in, discover->buf, discover->len);
/* Until we see EOF keep sending POSTs */
while (1) {
diff --git a/repository.c b/repository.c
index a7679ceeaa..7aacb51b65 100644
--- a/repository.c
+++ b/repository.c
@@ -104,6 +104,11 @@ void repo_set_hash_algo(struct repository *repo, int hash_algo)
repo->hash_algo = &hash_algos[hash_algo];
}
+void repo_set_ref_storage_format(struct repository *repo, unsigned int format)
+{
+ repo->ref_storage_format = format;
+}
+
/*
* Attempt to resolve and set the provided 'gitdir' for repository 'repo'.
* Return 0 upon success and a non-zero value upon failure.
@@ -184,6 +189,7 @@ int repo_init(struct repository *repo,
goto error;
repo_set_hash_algo(repo, format.hash_algo);
+ repo_set_ref_storage_format(repo, format.ref_storage_format);
repo->repository_format_worktree_config = format.worktree_config;
/* take ownership of format.partial_clone */
@@ -256,8 +262,6 @@ static void repo_clear_path_cache(struct repo_path_cache *cache)
FREE_AND_NULL(cache->merge_rr);
FREE_AND_NULL(cache->merge_mode);
FREE_AND_NULL(cache->merge_head);
- FREE_AND_NULL(cache->merge_autostash);
- FREE_AND_NULL(cache->auto_merge);
FREE_AND_NULL(cache->fetch_head);
FREE_AND_NULL(cache->shallow);
}
diff --git a/repository.h b/repository.h
index 5f18486f64..7a250a6605 100644
--- a/repository.h
+++ b/repository.h
@@ -24,6 +24,9 @@ enum fetch_negotiation_setting {
FETCH_NEGOTIATION_NOOP,
};
+#define REF_STORAGE_FORMAT_UNKNOWN 0
+#define REF_STORAGE_FORMAT_FILES 1
+
struct repo_settings {
int initialized;
@@ -64,8 +67,6 @@ struct repo_path_cache {
char *merge_rr;
char *merge_mode;
char *merge_head;
- char *merge_autostash;
- char *auto_merge;
char *fetch_head;
char *shallow;
};
@@ -160,6 +161,9 @@ struct repository {
/* Repository's current hash algorithm, as serialized on disk. */
const struct git_hash_algo *hash_algo;
+ /* Repository's reference storage format, as serialized on disk. */
+ unsigned int ref_storage_format;
+
/* A unique-id for tracing purposes. */
int trace2_repo_id;
@@ -199,6 +203,7 @@ void repo_set_gitdir(struct repository *repo, const char *root,
const struct set_gitdir_args *extra_args);
void repo_set_worktree(struct repository *repo, const char *path);
void repo_set_hash_algo(struct repository *repo, int algo);
+void repo_set_ref_storage_format(struct repository *repo, unsigned int format);
void initialize_the_repository(void);
RESULT_MUST_BE_USED
int repo_init(struct repository *r, const char *gitdir, const char *worktree);
diff --git a/sequencer.c b/sequencer.c
index 3cc88d8a80..91de546b32 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -474,7 +474,7 @@ static void print_advice(struct repository *r, int show_hint,
* of the commit itself so remove CHERRY_PICK_HEAD
*/
refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD",
- NULL, 0);
+ NULL, REF_NO_DEREF);
return;
}
@@ -1667,7 +1667,7 @@ static int do_commit(struct repository *r,
strbuf_release(&sb);
if (!res) {
refs_delete_ref(get_main_ref_store(r), "",
- "CHERRY_PICK_HEAD", NULL, 0);
+ "CHERRY_PICK_HEAD", NULL, REF_NO_DEREF);
unlink(git_path_merge_msg(r));
if (!is_rebase_i(opts))
print_commit_summary(r, NULL, &oid,
@@ -2406,9 +2406,10 @@ static int do_pick_commit(struct repository *r,
} else if (allow == 2) {
drop_commit = 1;
refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD",
- NULL, 0);
+ NULL, REF_NO_DEREF);
unlink(git_path_merge_msg(r));
- unlink(git_path_auto_merge(r));
+ refs_delete_ref(get_main_ref_store(r), "", "AUTO_MERGE",
+ NULL, REF_NO_DEREF);
fprintf(stderr,
_("dropping %s %s -- patch contents already upstream\n"),
oid_to_hex(&commit->object.oid), msg.subject);
@@ -2802,7 +2803,7 @@ void sequencer_post_commit_cleanup(struct repository *r, int verbose)
if (refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD")) {
if (!refs_delete_ref(get_main_ref_store(r), "",
- "CHERRY_PICK_HEAD", NULL, 0) &&
+ "CHERRY_PICK_HEAD", NULL, REF_NO_DEREF) &&
verbose)
warning(_("cancelling a cherry picking in progress"));
opts.action = REPLAY_PICK;
@@ -2811,14 +2812,15 @@ void sequencer_post_commit_cleanup(struct repository *r, int verbose)
if (refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD")) {
if (!refs_delete_ref(get_main_ref_store(r), "", "REVERT_HEAD",
- NULL, 0) &&
+ NULL, REF_NO_DEREF) &&
verbose)
warning(_("cancelling a revert in progress"));
opts.action = REPLAY_REVERT;
need_cleanup = 1;
}
- unlink(git_path_auto_merge(r));
+ refs_delete_ref(get_main_ref_store(r), "", "AUTO_MERGE",
+ NULL, REF_NO_DEREF);
if (!need_cleanup)
return;
@@ -4116,7 +4118,7 @@ static int do_merge(struct repository *r,
strbuf_release(&ref_name);
refs_delete_ref(get_main_ref_store(r), "", "CHERRY_PICK_HEAD",
- NULL, 0);
+ NULL, REF_NO_DEREF);
rollback_lock_file(&lock);
ret = run_command(&cmd);
@@ -4461,12 +4463,17 @@ static enum todo_command peek_command(struct todo_list *todo_list, int offset)
return -1;
}
-void create_autostash(struct repository *r, const char *path)
+static void create_autostash_internal(struct repository *r,
+ const char *path,
+ const char *refname)
{
struct strbuf buf = STRBUF_INIT;
struct lock_file lock_file = LOCK_INIT;
int fd;
+ if (path && refname)
+ BUG("can only pass path or refname");
+
fd = repo_hold_locked_index(r, &lock_file, 0);
refresh_index(r->index, REFRESH_QUIET, NULL, NULL, NULL);
if (0 <= fd)
@@ -4493,10 +4500,16 @@ void create_autostash(struct repository *r, const char *path)
strbuf_reset(&buf);
strbuf_add_unique_abbrev(&buf, &oid, DEFAULT_ABBREV);
- if (safe_create_leading_directories_const(path))
- die(_("Could not create directory for '%s'"),
- path);
- write_file(path, "%s", oid_to_hex(&oid));
+ if (path) {
+ if (safe_create_leading_directories_const(path))
+ die(_("Could not create directory for '%s'"),
+ path);
+ write_file(path, "%s", oid_to_hex(&oid));
+ } else {
+ refs_update_ref(get_main_ref_store(r), "", refname,
+ &oid, null_oid(), 0, UPDATE_REFS_DIE_ON_ERR);
+ }
+
printf(_("Created autostash: %s\n"), buf.buf);
if (reset_head(r, &ropts) < 0)
die(_("could not reset --hard"));
@@ -4507,6 +4520,16 @@ void create_autostash(struct repository *r, const char *path)
strbuf_release(&buf);
}
+void create_autostash(struct repository *r, const char *path)
+{
+ create_autostash_internal(r, path, NULL);
+}
+
+void create_autostash_ref(struct repository *r, const char *refname)
+{
+ create_autostash_internal(r, NULL, refname);
+}
+
static int apply_save_autostash_oid(const char *stash_oid, int attempt_apply)
{
struct child_process child = CHILD_PROCESS_INIT;
@@ -4584,6 +4607,41 @@ int apply_autostash_oid(const char *stash_oid)
return apply_save_autostash_oid(stash_oid, 1);
}
+static int apply_save_autostash_ref(struct repository *r, const char *refname,
+ int attempt_apply)
+{
+ struct object_id stash_oid;
+ char stash_oid_hex[GIT_MAX_HEXSZ + 1];
+ int flag, ret;
+
+ if (!refs_ref_exists(get_main_ref_store(r), refname))
+ return 0;
+
+ if (!refs_resolve_ref_unsafe(get_main_ref_store(r), refname,
+ RESOLVE_REF_READING, &stash_oid, &flag))
+ return -1;
+ if (flag & REF_ISSYMREF)
+ return error(_("autostash reference is a symref"));
+
+ oid_to_hex_r(stash_oid_hex, &stash_oid);
+ ret = apply_save_autostash_oid(stash_oid_hex, attempt_apply);
+
+ refs_delete_ref(get_main_ref_store(r), "", refname,
+ &stash_oid, REF_NO_DEREF);
+
+ return ret;
+}
+
+int save_autostash_ref(struct repository *r, const char *refname)
+{
+ return apply_save_autostash_ref(r, refname, 0);
+}
+
+int apply_autostash_ref(struct repository *r, const char *refname)
+{
+ return apply_save_autostash_ref(r, refname, 1);
+}
+
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)
@@ -4766,8 +4824,10 @@ static int pick_commits(struct repository *r,
}
unlink(rebase_path_author_script());
unlink(git_path_merge_head(r));
- unlink(git_path_auto_merge(r));
- delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
+ refs_delete_ref(get_main_ref_store(r), "", "AUTO_MERGE",
+ NULL, REF_NO_DEREF);
+ refs_delete_ref(get_main_ref_store(r), "", "REBASE_HEAD",
+ NULL, REF_NO_DEREF);
if (item->command == TODO_BREAK) {
if (!opts->verbose)
@@ -5108,7 +5168,7 @@ static int commit_staged_changes(struct repository *r,
if (refs_ref_exists(get_main_ref_store(r),
"CHERRY_PICK_HEAD") &&
refs_delete_ref(get_main_ref_store(r), "",
- "CHERRY_PICK_HEAD", NULL, 0))
+ "CHERRY_PICK_HEAD", NULL, REF_NO_DEREF))
return error(_("could not remove CHERRY_PICK_HEAD"));
if (unlink(git_path_merge_msg(r)) && errno != ENOENT)
return error_errno(_("could not remove '%s'"),
@@ -5122,7 +5182,8 @@ static int commit_staged_changes(struct repository *r,
return error(_("could not commit staged changes."));
unlink(rebase_path_amend());
unlink(git_path_merge_head(r));
- unlink(git_path_auto_merge(r));
+ refs_delete_ref(get_main_ref_store(r), "", "AUTO_MERGE",
+ NULL, REF_NO_DEREF);
if (final_fixup) {
unlink(rebase_path_fixup_msg());
unlink(rebase_path_squash_msg());
diff --git a/sequencer.h b/sequencer.h
index 913a0f652d..dcef7bb99c 100644
--- a/sequencer.h
+++ b/sequencer.h
@@ -225,9 +225,12 @@ void commit_post_rewrite(struct repository *r,
const struct object_id *new_head);
void create_autostash(struct repository *r, const char *path);
+void create_autostash_ref(struct repository *r, const char *refname);
int save_autostash(const char *path);
+int save_autostash_ref(struct repository *r, const char *refname);
int apply_autostash(const char *path);
int apply_autostash_oid(const char *stash_oid);
+int apply_autostash_ref(struct repository *r, const char *refname);
#define SUMMARY_INITIAL_COMMIT (1 << 0)
#define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)
diff --git a/setup.c b/setup.c
index 0161ce747d..b69b1cbc2a 100644
--- a/setup.c
+++ b/setup.c
@@ -591,6 +591,17 @@ static enum extension_result handle_extension(const char *var,
"extensions.objectformat", value);
data->hash_algo = format;
return EXTENSION_OK;
+ } else if (!strcmp(ext, "refstorage")) {
+ unsigned int format;
+
+ if (!value)
+ return config_error_nonbool(var);
+ format = ref_storage_format_by_name(value);
+ if (format == REF_STORAGE_FORMAT_UNKNOWN)
+ return error(_("invalid value for '%s': '%s'"),
+ "extensions.refstorage", value);
+ data->ref_storage_format = format;
+ return EXTENSION_OK;
}
return EXTENSION_UNKNOWN;
}
@@ -1360,7 +1371,8 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
if (is_git_directory(dir->buf)) {
trace2_data_string("setup", NULL, "implicit-bare-repository", dir->buf);
- if (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT)
+ if (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT &&
+ !ends_with_path_components(dir->buf, ".git"))
return GIT_DIR_DISALLOWED_BARE;
if (!ensure_valid_ownership(NULL, NULL, dir->buf, report))
return GIT_DIR_INVALID_OWNERSHIP;
@@ -1565,6 +1577,8 @@ const char *setup_git_directory_gently(int *nongit_ok)
}
if (startup_info->have_repository) {
repo_set_hash_algo(the_repository, repo_fmt.hash_algo);
+ repo_set_ref_storage_format(the_repository,
+ repo_fmt.ref_storage_format);
the_repository->repository_format_worktree_config =
repo_fmt.worktree_config;
/* take ownership of repo_fmt.partial_clone */
@@ -1658,6 +1672,8 @@ void check_repository_format(struct repository_format *fmt)
check_repository_format_gently(get_git_dir(), fmt, NULL);
startup_info->have_repository = 1;
repo_set_hash_algo(the_repository, fmt->hash_algo);
+ repo_set_ref_storage_format(the_repository,
+ fmt->ref_storage_format);
the_repository->repository_format_worktree_config =
fmt->worktree_config;
the_repository->repository_format_partial_clone =
@@ -1866,12 +1882,15 @@ static int needs_work_tree_config(const char *git_dir, const char *work_tree)
return 1;
}
-void initialize_repository_version(int hash_algo, int reinit)
+void initialize_repository_version(int hash_algo,
+ unsigned int ref_storage_format,
+ int reinit)
{
char repo_version_string[10];
int repo_version = GIT_REPO_VERSION;
- if (hash_algo != GIT_HASH_SHA1)
+ if (hash_algo != GIT_HASH_SHA1 ||
+ ref_storage_format != REF_STORAGE_FORMAT_FILES)
repo_version = GIT_REPO_VERSION_READ;
/* This forces creation of new config file */
@@ -1884,6 +1903,10 @@ void initialize_repository_version(int hash_algo, int reinit)
hash_algos[hash_algo].name);
else if (reinit)
git_config_set_gently("extensions.objectformat", NULL);
+
+ if (ref_storage_format != REF_STORAGE_FORMAT_FILES)
+ git_config_set("extensions.refstorage",
+ ref_storage_format_to_name(ref_storage_format));
}
static int is_reinit(void)
@@ -1898,27 +1921,14 @@ static int is_reinit(void)
return ret;
}
-void create_reference_database(const char *initial_branch, int quiet)
+void create_reference_database(unsigned int ref_storage_format,
+ const char *initial_branch, int quiet)
{
struct strbuf err = STRBUF_INIT;
int reinit = is_reinit();
- /*
- * We need to create a "refs" dir in any case so that older versions of
- * Git can tell that this is a repository. This serves two main purposes:
- *
- * - Clients will know to stop walking the parent-directory chain when
- * detecting the Git repository. Otherwise they may end up detecting
- * a Git repository in a parent directory instead.
- *
- * - Instead of failing to detect a repository with unknown reference
- * format altogether, old clients will print an error saying that
- * they do not understand the reference format extension.
- */
- safe_create_dir(git_path("refs"), 1);
- adjust_shared_perm(git_path("refs"));
-
- if (refs_init_db(&err))
+ repo_set_ref_storage_format(the_repository, ref_storage_format);
+ if (refs_init_db(get_main_ref_store(the_repository), 0, &err))
die("failed to set up refs db: %s", err.buf);
/*
@@ -2023,7 +2033,7 @@ static int create_default_files(const char *template_path,
adjust_shared_perm(get_git_dir());
}
- initialize_repository_version(fmt->hash_algo, 0);
+ initialize_repository_version(fmt->hash_algo, fmt->ref_storage_format, 0);
/* Check filemode trustability */
path = git_path_buf(&buf, "config");
@@ -2136,8 +2146,29 @@ static void validate_hash_algorithm(struct repository_format *repo_fmt, int hash
}
}
+static void validate_ref_storage_format(struct repository_format *repo_fmt,
+ unsigned int format)
+{
+ const char *name = getenv("GIT_DEFAULT_REF_FORMAT");
+
+ if (repo_fmt->version >= 0 &&
+ format != REF_STORAGE_FORMAT_UNKNOWN &&
+ format != repo_fmt->ref_storage_format) {
+ die(_("attempt to reinitialize repository with different reference storage format"));
+ } else if (format != REF_STORAGE_FORMAT_UNKNOWN) {
+ repo_fmt->ref_storage_format = format;
+ } else if (name) {
+ format = ref_storage_format_by_name(name);
+ if (format == REF_STORAGE_FORMAT_UNKNOWN)
+ die(_("unknown ref storage format '%s'"), name);
+ repo_fmt->ref_storage_format = format;
+ }
+}
+
int init_db(const char *git_dir, const char *real_git_dir,
- const char *template_dir, int hash, const char *initial_branch,
+ const char *template_dir, int hash,
+ unsigned int ref_storage_format,
+ const char *initial_branch,
int init_shared_repository, unsigned int flags)
{
int reinit;
@@ -2180,13 +2211,22 @@ int init_db(const char *git_dir, const char *real_git_dir,
check_repository_format(&repo_fmt);
validate_hash_algorithm(&repo_fmt, hash);
+ validate_ref_storage_format(&repo_fmt, ref_storage_format);
reinit = create_default_files(template_dir, original_git_dir,
&repo_fmt, prev_bare_repository,
init_shared_repository);
+ /*
+ * Now that we have set up both the hash algorithm and the ref storage
+ * format we can update the repository's settings accordingly.
+ */
+ repo_set_hash_algo(the_repository, repo_fmt.hash_algo);
+ repo_set_ref_storage_format(the_repository, repo_fmt.ref_storage_format);
+
if (!(flags & INIT_DB_SKIP_REFDB))
- create_reference_database(initial_branch, flags & INIT_DB_QUIET);
+ create_reference_database(repo_fmt.ref_storage_format,
+ initial_branch, flags & INIT_DB_QUIET);
create_object_directory();
if (get_shared_repository()) {
diff --git a/setup.h b/setup.h
index 3f0f17c351..3599aec93c 100644
--- a/setup.h
+++ b/setup.h
@@ -115,6 +115,7 @@ struct repository_format {
int worktree_config;
int is_bare;
int hash_algo;
+ unsigned int ref_storage_format;
int sparse_index;
char *work_tree;
struct string_list unknown_extensions;
@@ -131,6 +132,7 @@ struct repository_format {
.version = -1, \
.is_bare = -1, \
.hash_algo = GIT_HASH_SHA1, \
+ .ref_storage_format = REF_STORAGE_FORMAT_FILES, \
.unknown_extensions = STRING_LIST_INIT_DUP, \
.v1_only_extensions = STRING_LIST_INIT_DUP, \
}
@@ -175,10 +177,14 @@ void check_repository_format(struct repository_format *fmt);
int init_db(const char *git_dir, const char *real_git_dir,
const char *template_dir, int hash_algo,
+ unsigned int ref_storage_format,
const char *initial_branch, int init_shared_repository,
unsigned int flags);
-void initialize_repository_version(int hash_algo, int reinit);
-void create_reference_database(const char *initial_branch, int quiet);
+void initialize_repository_version(int hash_algo,
+ unsigned int ref_storage_format,
+ int reinit);
+void create_reference_database(unsigned int ref_storage_format,
+ const char *initial_branch, int quiet);
/*
* NOTE NOTE NOTE!!
diff --git a/sideband.c b/sideband.c
index 6cbfd391c4..266a67342b 100644
--- a/sideband.c
+++ b/sideband.c
@@ -69,7 +69,10 @@ void list_config_color_sideband_slots(struct string_list *list, const char *pref
* of the line. This should be called for a single line only, which is
* passed as the first N characters of the SRC array.
*
- * NEEDSWORK: use "size_t n" instead for clarity.
+ * It is fine to use "int n" here instead of "size_t n" as all calls to this
+ * function pass an 'int' parameter. Additionally, the buffer involved in
+ * storing these 'int' values takes input from a packet via the pkt-line
+ * interface, which is capable of transferring only 64kB at a time.
*/
static void maybe_colorize_sideband(struct strbuf *dest, const char *src, int n)
{
diff --git a/strvec.h b/strvec.h
index 9f55c8766b..4715d3e51f 100644
--- a/strvec.h
+++ b/strvec.h
@@ -4,8 +4,8 @@
/**
* The strvec API allows one to dynamically build and store
* NULL-terminated arrays of strings. A strvec maintains the invariant that the
- * `items` member always points to a non-NULL array, and that the array is
- * always NULL-terminated at the element pointed to by `items[nr]`. This
+ * `v` member always points to a non-NULL array, and that the array is
+ * always NULL-terminated at the element pointed to by `v[nr]`. This
* makes the result suitable for passing to functions expecting to receive
* argv from main().
*
@@ -22,7 +22,7 @@ extern const char *empty_strvec[];
/**
* A single array. This should be initialized by assignment from
- * `STRVEC_INIT`, or by calling `strvec_init`. The `items`
+ * `STRVEC_INIT`, or by calling `strvec_init`. The `v`
* member contains the actual array; the `nr` member contains the
* number of elements in the array, not including the terminating
* NULL.
@@ -80,7 +80,7 @@ void strvec_split(struct strvec *, const char *);
void strvec_clear(struct strvec *);
/**
- * Disconnect the `items` member from the `strvec` struct and
+ * Disconnect the `v` member from the `strvec` struct and
* return it. The caller is responsible for freeing the memory used
* by the array, and by the strings it references. After detaching,
* the `strvec` is in a reinitialized state and can be pushed
diff --git a/submodule-config.c b/submodule-config.c
index f4dd482abc..54130f6a38 100644
--- a/submodule-config.c
+++ b/submodule-config.c
@@ -14,6 +14,8 @@
#include "parse-options.h"
#include "thread-utils.h"
#include "tree-walk.h"
+#include "url.h"
+#include "urlmatch.h"
/*
* submodule cache lookup structure
@@ -228,6 +230,144 @@ in_component:
return 0;
}
+static int starts_with_dot_slash(const char *const path)
+{
+ return path_match_flags(path, PATH_MATCH_STARTS_WITH_DOT_SLASH |
+ PATH_MATCH_XPLATFORM);
+}
+
+static int starts_with_dot_dot_slash(const char *const path)
+{
+ return path_match_flags(path, PATH_MATCH_STARTS_WITH_DOT_DOT_SLASH |
+ PATH_MATCH_XPLATFORM);
+}
+
+static int submodule_url_is_relative(const char *url)
+{
+ return starts_with_dot_slash(url) || starts_with_dot_dot_slash(url);
+}
+
+/*
+ * Count directory components that a relative submodule URL should chop
+ * from the remote_url it is to be resolved against.
+ *
+ * In other words, this counts "../" components at the start of a
+ * submodule URL.
+ *
+ * Returns the number of directory components to chop and writes a
+ * pointer to the next character of url after all leading "./" and
+ * "../" components to out.
+ */
+static int count_leading_dotdots(const char *url, const char **out)
+{
+ int result = 0;
+ while (1) {
+ if (starts_with_dot_dot_slash(url)) {
+ result++;
+ url += strlen("../");
+ continue;
+ }
+ if (starts_with_dot_slash(url)) {
+ url += strlen("./");
+ continue;
+ }
+ *out = url;
+ return result;
+ }
+}
+/*
+ * Check whether a transport is implemented by git-remote-curl.
+ *
+ * If it is, returns 1 and writes the URL that would be passed to
+ * git-remote-curl to the "out" parameter.
+ *
+ * Otherwise, returns 0 and leaves "out" untouched.
+ *
+ * Examples:
+ * http::https://example.com/repo.git -> 1, https://example.com/repo.git
+ * https://example.com/repo.git -> 1, https://example.com/repo.git
+ * git://example.com/repo.git -> 0
+ *
+ * This is for use in checking for previously exploitable bugs that
+ * required a submodule URL to be passed to git-remote-curl.
+ */
+static int url_to_curl_url(const char *url, const char **out)
+{
+ /*
+ * We don't need to check for case-aliases, "http.exe", and so
+ * on because in the default configuration, is_transport_allowed
+ * prevents URLs with those schemes from being cloned
+ * automatically.
+ */
+ if (skip_prefix(url, "http::", out) ||
+ skip_prefix(url, "https::", out) ||
+ skip_prefix(url, "ftp::", out) ||
+ skip_prefix(url, "ftps::", out))
+ return 1;
+ if (starts_with(url, "http://") ||
+ starts_with(url, "https://") ||
+ starts_with(url, "ftp://") ||
+ starts_with(url, "ftps://")) {
+ *out = url;
+ return 1;
+ }
+ return 0;
+}
+
+int check_submodule_url(const char *url)
+{
+ const char *curl_url;
+
+ if (looks_like_command_line_option(url))
+ return -1;
+
+ if (submodule_url_is_relative(url) || starts_with(url, "git://")) {
+ char *decoded;
+ const char *next;
+ int has_nl;
+
+ /*
+ * This could be appended to an http URL and url-decoded;
+ * check for malicious characters.
+ */
+ decoded = url_decode(url);
+ has_nl = !!strchr(decoded, '\n');
+
+ free(decoded);
+ if (has_nl)
+ return -1;
+
+ /*
+ * URLs which escape their root via "../" can overwrite
+ * the host field and previous components, resolving to
+ * URLs like https::example.com/submodule.git and
+ * https:///example.com/submodule.git that were
+ * susceptible to CVE-2020-11008.
+ */
+ if (count_leading_dotdots(url, &next) > 0 &&
+ (*next == ':' || *next == '/'))
+ return -1;
+ }
+
+ else if (url_to_curl_url(url, &curl_url)) {
+ int ret = 0;
+ char *normalized = url_normalize(curl_url, NULL);
+ if (normalized) {
+ char *decoded = url_decode(normalized);
+ if (strchr(decoded, '\n'))
+ ret = -1;
+ free(normalized);
+ free(decoded);
+ } else {
+ ret = -1;
+ }
+
+ return ret;
+ }
+
+ return 0;
+}
+
static int name_and_item_from_var(const char *var, struct strbuf *name,
struct strbuf *item)
{
diff --git a/submodule-config.h b/submodule-config.h
index 958f320ac6..b6133af71b 100644
--- a/submodule-config.h
+++ b/submodule-config.h
@@ -89,6 +89,9 @@ int config_set_in_gitmodules_file_gently(const char *key, const char *value);
*/
int check_submodule_name(const char *name);
+/* Returns 0 if the URL valid per RFC3986 and -1 otherwise. */
+int check_submodule_url(const char *url);
+
/*
* Note: these helper functions exist solely to maintain backward
* compatibility with 'fetch' and 'update_clone' storing configuration in
diff --git a/t/README b/t/README
index 36463d0742..621d3b8c09 100644
--- a/t/README
+++ b/t/README
@@ -479,6 +479,9 @@ GIT_TEST_DEFAULT_HASH=<hash-algo> specifies which hash algorithm to
use in the test scripts. Recognized values for <hash-algo> are "sha1"
and "sha256".
+GIT_TEST_DEFAULT_REF_FORMAT=<format> specifies which ref storage format
+to use in the test scripts. Recognized values for <format> are "files".
+
GIT_TEST_NO_WRITE_REV_INDEX=<boolean>, when true disables the
'pack.writeReverseIndex' setting.
diff --git a/t/helper/test-ctype.c b/t/helper/test-ctype.c
deleted file mode 100644
index e5659df40b..0000000000
--- a/t/helper/test-ctype.c
+++ /dev/null
@@ -1,70 +0,0 @@
-#include "test-tool.h"
-
-static int rc;
-
-static void report_error(const char *class, int ch)
-{
- printf("%s classifies char %d (0x%02x) wrongly\n", class, ch, ch);
- rc = 1;
-}
-
-static int is_in(const char *s, int ch)
-{
- /*
- * We can't find NUL using strchr. Accept it as the first
- * character in the spec -- there are no empty classes.
- */
- if (ch == '\0')
- return ch == *s;
- if (*s == '\0')
- s++;
- return !!strchr(s, ch);
-}
-
-#define TEST_CLASS(t,s) { \
- int i; \
- for (i = 0; i < 256; i++) { \
- if (is_in(s, i) != t(i)) \
- report_error(#t, i); \
- } \
- if (t(EOF)) \
- report_error(#t, EOF); \
-}
-
-#define DIGIT "0123456789"
-#define LOWER "abcdefghijklmnopqrstuvwxyz"
-#define UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-#define PUNCT "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
-#define ASCII \
- "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" \
- "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" \
- "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f" \
- "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f" \
- "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f" \
- "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f" \
- "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f" \
- "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
-#define CNTRL \
- "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" \
- "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" \
- "\x7f"
-
-int cmd__ctype(int argc UNUSED, const char **argv UNUSED)
-{
- TEST_CLASS(isdigit, DIGIT);
- TEST_CLASS(isspace, " \n\r\t");
- TEST_CLASS(isalpha, LOWER UPPER);
- TEST_CLASS(isalnum, LOWER UPPER DIGIT);
- TEST_CLASS(is_glob_special, "*?[\\");
- TEST_CLASS(is_regex_special, "$()*+.?[\\^{|");
- TEST_CLASS(is_pathspec_magic, "!\"#%&',-/:;<=>@_`~");
- TEST_CLASS(isascii, ASCII);
- TEST_CLASS(islower, LOWER);
- TEST_CLASS(isupper, UPPER);
- TEST_CLASS(iscntrl, CNTRL);
- TEST_CLASS(ispunct, PUNCT);
- TEST_CLASS(isxdigit, DIGIT "abcdefABCDEF");
- TEST_CLASS(isprint, LOWER UPPER DIGIT PUNCT " ");
-
- return rc;
-}
diff --git a/t/helper/test-pkt-line.c b/t/helper/test-pkt-line.c
index 77e99c37df..4daa82f00f 100644
--- a/t/helper/test-pkt-line.c
+++ b/t/helper/test-pkt-line.c
@@ -3,6 +3,7 @@
#include "pkt-line.h"
#include "sideband.h"
#include "write-or-die.h"
+#include "parse-options.h"
static void pack_line(const char *line)
{
@@ -65,12 +66,33 @@ static void unpack(void)
}
}
-static void unpack_sideband(void)
+static void unpack_sideband(int argc, const char **argv)
{
struct packet_reader reader;
- packet_reader_init(&reader, 0, NULL, 0,
- PACKET_READ_GENTLE_ON_EOF |
- PACKET_READ_CHOMP_NEWLINE);
+ int options = PACKET_READ_GENTLE_ON_EOF;
+ int chomp_newline = 1;
+ int reader_use_sideband = 0;
+ const char *const unpack_sideband_usage[] = {
+ "test_tool unpack_sideband [options...]", NULL
+ };
+ struct option cmd_options[] = {
+ OPT_BOOL(0, "reader-use-sideband", &reader_use_sideband,
+ "set use_sideband bit for packet reader (Default: off)"),
+ OPT_BOOL(0, "chomp-newline", &chomp_newline,
+ "chomp newline in packet (Default: on)"),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, "", cmd_options, unpack_sideband_usage,
+ 0);
+ if (argc > 0)
+ usage_msg_opt(_("too many arguments"), unpack_sideband_usage,
+ cmd_options);
+
+ if (chomp_newline)
+ options |= PACKET_READ_CHOMP_NEWLINE;
+ packet_reader_init(&reader, 0, NULL, 0, options);
+ reader.use_sideband = reader_use_sideband;
while (packet_reader_read(&reader) != PACKET_READ_EOF) {
int band;
@@ -80,6 +102,17 @@ static void unpack_sideband(void)
case PACKET_READ_EOF:
break;
case PACKET_READ_NORMAL:
+ /*
+ * When the "use_sideband" field of the reader is turned
+ * on, sideband packets other than the payload have been
+ * parsed and consumed in packet_reader_read(), and only
+ * the payload arrives here.
+ */
+ if (reader.use_sideband) {
+ write_or_die(1, reader.line, reader.pktlen - 1);
+ break;
+ }
+
band = reader.line[0] & 0xff;
if (band < 1 || band > 2)
continue; /* skip non-sideband packets */
@@ -98,15 +131,31 @@ static void unpack_sideband(void)
static int send_split_sideband(void)
{
+ const char *foo = "Foo.\n";
+ const char *bar = "Bar.\n";
const char *part1 = "Hello,";
const char *primary = "\001primary: regular output\n";
const char *part2 = " world!\n";
+ /* Each sideband message has a trailing newline character. */
+ send_sideband(1, 2, foo, strlen(foo), LARGE_PACKET_MAX);
+ send_sideband(1, 2, bar, strlen(bar), LARGE_PACKET_MAX);
+
+ /*
+ * One sideband message is divided into part1 and part2
+ * by the primary message.
+ */
send_sideband(1, 2, part1, strlen(part1), LARGE_PACKET_MAX);
packet_write(1, primary, strlen(primary));
send_sideband(1, 2, part2, strlen(part2), LARGE_PACKET_MAX);
packet_response_end(1);
+ /*
+ * We use unpack_sideband() to consume packets. A flush packet
+ * is required to end parsing.
+ */
+ packet_flush(1);
+
return 0;
}
@@ -127,7 +176,7 @@ int cmd__pkt_line(int argc, const char **argv)
else if (!strcmp(argv[1], "unpack"))
unpack();
else if (!strcmp(argv[1], "unpack-sideband"))
- unpack_sideband();
+ unpack_sideband(argc - 1, argv + 1);
else if (!strcmp(argv[1], "send-split-sideband"))
send_split_sideband();
else if (!strcmp(argv[1], "receive-sideband"))
diff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c
index e9a444ddba..4acae41bb9 100644
--- a/t/helper/test-read-midx.c
+++ b/t/helper/test-read-midx.c
@@ -6,6 +6,7 @@
#include "pack-bitmap.h"
#include "packfile.h"
#include "setup.h"
+#include "gettext.h"
static int read_midx_file(const char *object_dir, int show_objects)
{
@@ -79,7 +80,7 @@ static int read_midx_checksum(const char *object_dir)
static int read_midx_preferred_pack(const char *object_dir)
{
struct multi_pack_index *midx = NULL;
- struct bitmap_index *bitmap = NULL;
+ uint32_t preferred_pack;
setup_git_directory();
@@ -87,23 +88,45 @@ static int read_midx_preferred_pack(const char *object_dir)
if (!midx)
return 1;
- bitmap = prepare_bitmap_git(the_repository);
- if (!bitmap)
+ if (midx_preferred_pack(midx, &preferred_pack) < 0) {
+ warning(_("could not determine MIDX preferred pack"));
return 1;
- if (!bitmap_is_midx(bitmap)) {
- free_bitmap_index(bitmap);
+ }
+
+ printf("%s\n", midx->pack_names[preferred_pack]);
+ return 0;
+}
+
+static int read_midx_bitmapped_packs(const char *object_dir)
+{
+ struct multi_pack_index *midx = NULL;
+ struct bitmapped_pack pack;
+ uint32_t i;
+
+ setup_git_directory();
+
+ midx = load_multi_pack_index(object_dir, 1);
+ if (!midx)
return 1;
+
+ for (i = 0; i < midx->num_packs; i++) {
+ if (nth_bitmapped_pack(the_repository, midx, &pack, i) < 0)
+ return 1;
+
+ printf("%s\n", pack_basename(pack.p));
+ printf(" bitmap_pos: %"PRIuMAX"\n", (uintmax_t)pack.bitmap_pos);
+ printf(" bitmap_nr: %"PRIuMAX"\n", (uintmax_t)pack.bitmap_nr);
}
- printf("%s\n", midx->pack_names[midx_preferred_pack(bitmap)]);
- free_bitmap_index(bitmap);
+ close_midx(midx);
+
return 0;
}
int cmd__read_midx(int argc, const char **argv)
{
if (!(argc == 2 || argc == 3))
- usage("read-midx [--show-objects|--checksum|--preferred-pack] <object-dir>");
+ usage("read-midx [--show-objects|--checksum|--preferred-pack|--bitmap] <object-dir>");
if (!strcmp(argv[1], "--show-objects"))
return read_midx_file(argv[2], 1);
@@ -111,5 +134,7 @@ int cmd__read_midx(int argc, const char **argv)
return read_midx_checksum(argv[2]);
else if (!strcmp(argv[1], "--preferred-pack"))
return read_midx_preferred_pack(argv[2]);
+ else if (!strcmp(argv[1], "--bitmap"))
+ return read_midx_bitmapped_packs(argv[2]);
return read_midx_file(argv[1], 0);
}
diff --git a/t/helper/test-submodule.c b/t/helper/test-submodule.c
index 50c154d037..7197969a08 100644
--- a/t/helper/test-submodule.c
+++ b/t/helper/test-submodule.c
@@ -9,12 +9,19 @@
#include "submodule.h"
#define TEST_TOOL_CHECK_NAME_USAGE \
- "test-tool submodule check-name <name>"
+ "test-tool submodule check-name"
static const char *submodule_check_name_usage[] = {
TEST_TOOL_CHECK_NAME_USAGE,
NULL
};
+#define TEST_TOOL_CHECK_URL_USAGE \
+ "test-tool submodule check-url"
+static const char *submodule_check_url_usage[] = {
+ TEST_TOOL_CHECK_URL_USAGE,
+ NULL
+};
+
#define TEST_TOOL_IS_ACTIVE_USAGE \
"test-tool submodule is-active <name>"
static const char *submodule_is_active_usage[] = {
@@ -31,31 +38,26 @@ static const char *submodule_resolve_relative_url_usage[] = {
static const char *submodule_usage[] = {
TEST_TOOL_CHECK_NAME_USAGE,
+ TEST_TOOL_CHECK_URL_USAGE,
TEST_TOOL_IS_ACTIVE_USAGE,
TEST_TOOL_RESOLVE_RELATIVE_URL_USAGE,
NULL
};
+typedef int (*check_fn_t)(const char *);
+
/*
- * Exit non-zero if any of the submodule names given on the command line is
- * invalid. If no names are given, filter stdin to print only valid names
- * (which is primarily intended for testing).
+ * Apply 'check_fn' to each line of stdin, printing values that pass the check
+ * to stdout.
*/
-static int check_name(int argc, const char **argv)
+static int check_submodule(check_fn_t check_fn)
{
- if (argc > 1) {
- while (*++argv) {
- if (check_submodule_name(*argv) < 0)
- return 1;
- }
- } else {
- struct strbuf buf = STRBUF_INIT;
- while (strbuf_getline(&buf, stdin) != EOF) {
- if (!check_submodule_name(buf.buf))
- printf("%s\n", buf.buf);
- }
- strbuf_release(&buf);
+ struct strbuf buf = STRBUF_INIT;
+ while (strbuf_getline(&buf, stdin) != EOF) {
+ if (!check_fn(buf.buf))
+ printf("%s\n", buf.buf);
}
+ strbuf_release(&buf);
return 0;
}
@@ -69,7 +71,20 @@ static int cmd__submodule_check_name(int argc, const char **argv)
if (argc)
usage_with_options(submodule_check_name_usage, options);
- return check_name(argc, argv);
+ return check_submodule(check_submodule_name);
+}
+
+static int cmd__submodule_check_url(int argc, const char **argv)
+{
+ struct option options[] = {
+ OPT_END()
+ };
+ argc = parse_options(argc, argv, "test-tools", options,
+ submodule_check_url_usage, 0);
+ if (argc)
+ usage_with_options(submodule_check_url_usage, options);
+
+ return check_submodule(check_submodule_url);
}
static int cmd__submodule_is_active(int argc, const char **argv)
@@ -195,6 +210,7 @@ static int cmd__submodule_config_writeable(int argc, const char **argv UNUSED)
static struct test_cmd cmds[] = {
{ "check-name", cmd__submodule_check_name },
+ { "check-url", cmd__submodule_check_url },
{ "is-active", cmd__submodule_is_active },
{ "resolve-relative-url", cmd__submodule_resolve_relative_url},
{ "config-list", cmd__submodule_config_list },
diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c
index 37ba996539..33b9501c21 100644
--- a/t/helper/test-tool.c
+++ b/t/helper/test-tool.c
@@ -19,7 +19,6 @@ static struct test_cmd cmds[] = {
{ "config", cmd__config },
{ "crontab", cmd__crontab },
{ "csprng", cmd__csprng },
- { "ctype", cmd__ctype },
{ "date", cmd__date },
{ "delta", cmd__delta },
{ "dir-iterator", cmd__dir_iterator },
diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h
index 8a1a7c63da..b72f07ded9 100644
--- a/t/helper/test-tool.h
+++ b/t/helper/test-tool.h
@@ -12,7 +12,6 @@ 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);
int cmd__dir_iterator(int argc, const char **argv);
diff --git a/t/perf/p5332-multi-pack-reuse.sh b/t/perf/p5332-multi-pack-reuse.sh
new file mode 100755
index 0000000000..5c6c575d62
--- /dev/null
+++ b/t/perf/p5332-multi-pack-reuse.sh
@@ -0,0 +1,81 @@
+#!/bin/sh
+
+test_description='tests pack performance with multi-pack reuse'
+
+. ./perf-lib.sh
+. "${TEST_DIRECTORY}/perf/lib-pack.sh"
+
+packdir=.git/objects/pack
+
+test_perf_large_repo
+
+find_pack () {
+ for idx in $packdir/pack-*.idx
+ do
+ if git show-index <$idx | grep -q "$1"
+ then
+ basename $idx
+ fi || return 1
+ done
+}
+
+repack_into_n_chunks () {
+ git repack -adk &&
+
+ test "$1" -eq 1 && return ||
+
+ find $packdir -type f | sort >packs.before &&
+
+ # partition the repository into $1 chunks of consecutive commits, and
+ # then create $1 packs with the objects reachable from each chunk
+ # (excluding any objects reachable from the previous chunks)
+ sz="$(($(git rev-list --count --all) / $1))"
+ for rev in $(git rev-list --all | awk "NR % $sz == 0" | tac)
+ do
+ pack="$(echo "$rev" | git pack-objects --revs \
+ --honor-pack-keep --delta-base-offset $packdir/pack)" &&
+ touch $packdir/pack-$pack.keep || return 1
+ done
+
+ # grab any remaining objects not packed by the previous step(s)
+ git pack-objects --revs --all --honor-pack-keep --delta-base-offset \
+ $packdir/pack &&
+
+ find $packdir -type f | sort >packs.after &&
+
+ # and install the whole thing
+ for f in $(comm -12 packs.before packs.after)
+ do
+ rm -f "$f" || return 1
+ done
+ rm -fr $packdir/*.keep
+}
+
+for nr_packs in 1 10 100
+do
+ test_expect_success "create $nr_packs-pack scenario" '
+ repack_into_n_chunks $nr_packs
+ '
+
+ test_expect_success "setup bitmaps for $nr_packs-pack scenario" '
+ find $packdir -type f -name "*.idx" | sed -e "s/.*\/\(.*\)$/+\1/g" |
+ git multi-pack-index write --stdin-packs --bitmap \
+ --preferred-pack="$(find_pack $(git rev-parse HEAD))"
+ '
+
+ for reuse in single multi
+ do
+ test_perf "clone for $nr_packs-pack scenario ($reuse-pack reuse)" "
+ git for-each-ref --format='%(objectname)' refs/heads refs/tags >in &&
+ git -c pack.allowPackReuse=$reuse pack-objects \
+ --revs --delta-base-offset --use-bitmap-index \
+ --stdout <in >result
+ "
+
+ test_size "clone size for $nr_packs-pack scenario ($reuse-pack reuse)" '
+ wc -c <result
+ '
+ done
+done
+
+test_done
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index 2b78e3be47..b131d665db 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -532,6 +532,76 @@ test_expect_success 'init rejects attempts to initialize with different hash' '
test_must_fail git -C sha256 init --object-format=sha1
'
+test_expect_success DEFAULT_REPO_FORMAT 'extensions.refStorage is not allowed with repo version 0' '
+ test_when_finished "rm -rf refstorage" &&
+ git init refstorage &&
+ git -C refstorage config extensions.refStorage files &&
+ test_must_fail git -C refstorage rev-parse 2>err &&
+ grep "repo version is 0, but v1-only extension found" err
+'
+
+test_expect_success DEFAULT_REPO_FORMAT 'extensions.refStorage with files backend' '
+ test_when_finished "rm -rf refstorage" &&
+ git init refstorage &&
+ git -C refstorage config core.repositoryformatversion 1 &&
+ git -C refstorage config extensions.refStorage files &&
+ test_commit -C refstorage A &&
+ git -C refstorage rev-parse --verify HEAD
+'
+
+test_expect_success DEFAULT_REPO_FORMAT 'extensions.refStorage with unknown backend' '
+ test_when_finished "rm -rf refstorage" &&
+ git init refstorage &&
+ git -C refstorage config core.repositoryformatversion 1 &&
+ git -C refstorage config extensions.refStorage garbage &&
+ test_must_fail git -C refstorage rev-parse 2>err &&
+ grep "invalid value for ${SQ}extensions.refstorage${SQ}: ${SQ}garbage${SQ}" err
+'
+
+test_expect_success DEFAULT_REPO_FORMAT 'init with GIT_DEFAULT_REF_FORMAT=files' '
+ test_when_finished "rm -rf refformat" &&
+ GIT_DEFAULT_REF_FORMAT=files git init refformat &&
+ echo 0 >expect &&
+ git -C refformat config core.repositoryformatversion >actual &&
+ test_cmp expect actual &&
+ test_must_fail git -C refformat config extensions.refstorage
+'
+
+test_expect_success 'init with GIT_DEFAULT_REF_FORMAT=garbage' '
+ test_when_finished "rm -rf refformat" &&
+ cat >expect <<-EOF &&
+ fatal: unknown ref storage format ${SQ}garbage${SQ}
+ EOF
+ test_must_fail env GIT_DEFAULT_REF_FORMAT=garbage git init refformat 2>err &&
+ test_cmp expect err
+'
+
+test_expect_success 'init with --ref-format=files' '
+ test_when_finished "rm -rf refformat" &&
+ git init --ref-format=files refformat &&
+ echo files >expect &&
+ git -C refformat rev-parse --show-ref-format >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 're-init with same format' '
+ test_when_finished "rm -rf refformat" &&
+ git init --ref-format=files refformat &&
+ git init --ref-format=files refformat &&
+ echo files >expect &&
+ git -C refformat rev-parse --show-ref-format >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'init with --ref-format=garbage' '
+ test_when_finished "rm -rf refformat" &&
+ cat >expect <<-EOF &&
+ fatal: unknown ref storage format ${SQ}garbage${SQ}
+ EOF
+ test_must_fail git init --ref-format=garbage refformat 2>err &&
+ test_cmp expect err
+'
+
test_expect_success MINGW 'core.hidedotfiles = false' '
git config --global core.hidedotfiles false &&
rm -rf newdir &&
diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh
index aee2298f01..774b52c298 100755
--- a/t/t0003-attributes.sh
+++ b/t/t0003-attributes.sh
@@ -19,6 +19,20 @@ attr_check () {
test_must_be_empty err
}
+attr_check_object_mode_basic () {
+ path="$1" &&
+ expect="$2" &&
+ check_opts="$3" &&
+ git check-attr $check_opts builtin_objectmode -- "$path" >actual 2>err &&
+ echo "$path: builtin_objectmode: $expect" >expect &&
+ test_cmp expect actual
+}
+
+attr_check_object_mode () {
+ attr_check_object_mode_basic "$@" &&
+ test_must_be_empty err
+}
+
attr_check_quote () {
path="$1" quoted_path="$2" expect="$3" &&
@@ -558,4 +572,66 @@ test_expect_success EXPENSIVE 'large attributes file ignored in index' '
test_cmp expect err
'
+test_expect_success 'builtin object mode attributes work (dir and regular paths)' '
+ >normal &&
+ attr_check_object_mode normal 100644 &&
+ mkdir dir &&
+ attr_check_object_mode dir 040000
+'
+
+test_expect_success POSIXPERM 'builtin object mode attributes work (executable)' '
+ >exec &&
+ chmod +x exec &&
+ attr_check_object_mode exec 100755
+'
+
+test_expect_success SYMLINKS 'builtin object mode attributes work (symlinks)' '
+ ln -s to_sym sym &&
+ attr_check_object_mode sym 120000
+'
+
+test_expect_success 'native object mode attributes work with --cached' '
+ >normal &&
+ git add normal &&
+ empty_blob=$(git rev-parse :normal) &&
+ git update-index --index-info <<-EOF &&
+ 100755 $empty_blob 0 exec
+ 120000 $empty_blob 0 symlink
+ EOF
+ attr_check_object_mode normal 100644 --cached &&
+ attr_check_object_mode exec 100755 --cached &&
+ attr_check_object_mode symlink 120000 --cached
+'
+
+test_expect_success 'check object mode attributes work for submodules' '
+ mkdir sub &&
+ (
+ cd sub &&
+ git init &&
+ mv .git .real &&
+ echo "gitdir: .real" >.git &&
+ test_commit first
+ ) &&
+ attr_check_object_mode sub 160000 &&
+ attr_check_object_mode sub unspecified --cached &&
+ git add sub &&
+ attr_check_object_mode sub 160000 --cached
+'
+
+test_expect_success 'we do not allow user defined builtin_* attributes' '
+ echo "foo* builtin_foo" >.gitattributes &&
+ git add .gitattributes 2>actual &&
+ echo "builtin_foo is not a valid attribute name: .gitattributes:1" >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'user defined builtin_objectmode values are ignored' '
+ echo "foo* builtin_objectmode=12345" >.gitattributes &&
+ git add .gitattributes &&
+ >foo_1 &&
+ attr_check_object_mode_basic foo_1 100644 &&
+ echo "builtin_objectmode is not a valid attribute name: .gitattributes:1" >expect &&
+ test_cmp expect err
+'
+
test_done
diff --git a/t/t0018-advice.sh b/t/t0018-advice.sh
index c13057a4ca..0dcfb760a2 100755
--- a/t/t0018-advice.sh
+++ b/t/t0018-advice.sh
@@ -17,7 +17,6 @@ test_expect_success 'advice should be printed when config variable is unset' '
test_expect_success 'advice should be printed when config variable is set to true' '
cat >expect <<-\EOF &&
hint: This is a piece of advice
- hint: Disable this message with "git config advice.nestedTag false"
EOF
test_config advice.nestedTag true &&
test-tool advise "This is a piece of advice" 2>actual &&
diff --git a/t/t0024-crlf-archive.sh b/t/t0024-crlf-archive.sh
index a34de56420..a7f4de4a43 100755
--- a/t/t0024-crlf-archive.sh
+++ b/t/t0024-crlf-archive.sh
@@ -9,7 +9,7 @@ test_expect_success setup '
git config core.autocrlf true &&
- printf "CRLF line ending\r\nAnd another\r\n" > sample &&
+ printf "CRLF line ending\r\nAnd another\r\n" >sample &&
git add sample &&
test_tick &&
@@ -19,8 +19,9 @@ test_expect_success setup '
test_expect_success 'tar archive' '
- git archive --format=tar HEAD |
- ( mkdir untarred && cd untarred && "$TAR" -xf - ) &&
+ git archive --format=tar HEAD >test.tar &&
+ mkdir untarred &&
+ "$TAR" xf test.tar -C untarred &&
test_cmp sample untarred/sample
@@ -30,7 +31,11 @@ test_expect_success UNZIP 'zip archive' '
git archive --format=zip HEAD >test.zip &&
- ( mkdir unzipped && cd unzipped && "$GIT_UNZIP" ../test.zip ) &&
+ mkdir unzipped &&
+ (
+ cd unzipped &&
+ "$GIT_UNZIP" ../test.zip
+ ) &&
test_cmp sample unzipped/sample
diff --git a/t/t0035-safe-bare-repository.sh b/t/t0035-safe-bare-repository.sh
index 038b8b788d..8048856379 100755
--- a/t/t0035-safe-bare-repository.sh
+++ b/t/t0035-safe-bare-repository.sh
@@ -78,4 +78,12 @@ test_expect_success 'no trace when GIT_DIR is explicitly provided' '
expect_accepted_explicit "$pwd/outer-repo/bare-repo"
'
+test_expect_success 'no trace when "bare repository" is .git' '
+ expect_accepted_implicit -C outer-repo/.git
+'
+
+test_expect_success 'no trace when "bare repository" is a subdir of .git' '
+ expect_accepted_implicit -C outer-repo/.git/objects
+'
+
test_done
diff --git a/t/t0070-fundamental.sh b/t/t0070-fundamental.sh
index 487bc8d905..0ecec2ba71 100755
--- a/t/t0070-fundamental.sh
+++ b/t/t0070-fundamental.sh
@@ -9,10 +9,6 @@ Verify wrappers and compatibility functions.
TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
-test_expect_success 'character classes (isspace, isalpha etc.)' '
- test-tool ctype
-'
-
test_expect_success 'mktemp to nonexistent directory prints filename' '
test_must_fail test-tool mktemp doesnotexist/testXXXXXX 2>err &&
grep "doesnotexist/test" err
@@ -53,4 +49,62 @@ test_expect_success 'missing sideband designator is reported' '
test_grep "missing sideband" err
'
+test_expect_success 'unpack-sideband: --no-chomp-newline' '
+ test_when_finished "rm -f expect-out expect-err" &&
+ test-tool pkt-line send-split-sideband >split-sideband &&
+ test-tool pkt-line unpack-sideband \
+ --no-chomp-newline <split-sideband >out 2>err &&
+ cat >expect-out <<-EOF &&
+ primary: regular output
+ EOF
+ cat >expect-err <<-EOF &&
+ Foo.
+ Bar.
+ Hello, world!
+ EOF
+ test_cmp expect-out out &&
+ test_cmp expect-err err
+'
+
+test_expect_success 'unpack-sideband: --chomp-newline (default)' '
+ test_when_finished "rm -f expect-out expect-err" &&
+ test-tool pkt-line send-split-sideband >split-sideband &&
+ test-tool pkt-line unpack-sideband \
+ --chomp-newline <split-sideband >out 2>err &&
+ printf "primary: regular output" >expect-out &&
+ printf "Foo.Bar.Hello, world!" >expect-err &&
+ test_cmp expect-out out &&
+ test_cmp expect-err err
+'
+
+test_expect_success 'unpack-sideband: packet_reader_read() consumes sideband, no chomp payload' '
+ test_when_finished "rm -f expect-out expect-err" &&
+ test-tool pkt-line send-split-sideband >split-sideband &&
+ test-tool pkt-line unpack-sideband \
+ --reader-use-sideband \
+ --no-chomp-newline <split-sideband >out 2>err &&
+ cat >expect-out <<-EOF &&
+ primary: regular output
+ EOF
+ printf "remote: Foo. \n" >expect-err &&
+ printf "remote: Bar. \n" >>expect-err &&
+ printf "remote: Hello, world! \n" >>expect-err &&
+ test_cmp expect-out out &&
+ test_cmp expect-err err
+'
+
+test_expect_success 'unpack-sideband: packet_reader_read() consumes sideband, chomp payload' '
+ test_when_finished "rm -f expect-out expect-err" &&
+ test-tool pkt-line send-split-sideband >split-sideband &&
+ test-tool pkt-line unpack-sideband \
+ --reader-use-sideband \
+ --chomp-newline <split-sideband >out 2>err &&
+ printf "primary: regular output" >expect-out &&
+ printf "remote: Foo. \n" >expect-err &&
+ printf "remote: Bar. \n" >>expect-err &&
+ printf "remote: Hello, world! \n" >>expect-err &&
+ test_cmp expect-out out &&
+ test_cmp expect-err err
+'
+
test_done
diff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh
new file mode 100755
index 0000000000..e6a5f1868f
--- /dev/null
+++ b/t/t0600-reffiles-backend.sh
@@ -0,0 +1,384 @@
+#!/bin/sh
+
+test_description='Test reffiles backend'
+
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+
+if ! test_have_prereq REFFILES
+then
+ skip_all='skipping reffiles specific tests'
+ test_done
+fi
+
+test_expect_success 'setup' '
+ git commit --allow-empty -m Initial &&
+ C=$(git rev-parse HEAD) &&
+ git commit --allow-empty -m Second &&
+ D=$(git rev-parse HEAD) &&
+ git commit --allow-empty -m Third &&
+ E=$(git rev-parse HEAD)
+'
+
+test_expect_success 'empty directory should not fool rev-parse' '
+ prefix=refs/e-rev-parse &&
+ git update-ref $prefix/foo $C &&
+ git pack-refs --all &&
+ mkdir -p .git/$prefix/foo/bar/baz &&
+ echo "$C" >expected &&
+ git rev-parse $prefix/foo >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'empty directory should not fool for-each-ref' '
+ prefix=refs/e-for-each-ref &&
+ git update-ref $prefix/foo $C &&
+ git for-each-ref $prefix >expected &&
+ git pack-refs --all &&
+ mkdir -p .git/$prefix/foo/bar/baz &&
+ git for-each-ref $prefix >actual &&
+ test_cmp expected actual
+'
+
+test_expect_success 'empty directory should not fool create' '
+ prefix=refs/e-create &&
+ mkdir -p .git/$prefix/foo/bar/baz &&
+ printf "create %s $C\n" $prefix/foo |
+ git update-ref --stdin
+'
+
+test_expect_success 'empty directory should not fool verify' '
+ prefix=refs/e-verify &&
+ git update-ref $prefix/foo $C &&
+ git pack-refs --all &&
+ mkdir -p .git/$prefix/foo/bar/baz &&
+ printf "verify %s $C\n" $prefix/foo |
+ git update-ref --stdin
+'
+
+test_expect_success 'empty directory should not fool 1-arg update' '
+ prefix=refs/e-update-1 &&
+ git update-ref $prefix/foo $C &&
+ git pack-refs --all &&
+ mkdir -p .git/$prefix/foo/bar/baz &&
+ printf "update %s $D\n" $prefix/foo |
+ git update-ref --stdin
+'
+
+test_expect_success 'empty directory should not fool 2-arg update' '
+ prefix=refs/e-update-2 &&
+ git update-ref $prefix/foo $C &&
+ git pack-refs --all &&
+ mkdir -p .git/$prefix/foo/bar/baz &&
+ printf "update %s $D $C\n" $prefix/foo |
+ git update-ref --stdin
+'
+
+test_expect_success 'empty directory should not fool 0-arg delete' '
+ prefix=refs/e-delete-0 &&
+ git update-ref $prefix/foo $C &&
+ git pack-refs --all &&
+ mkdir -p .git/$prefix/foo/bar/baz &&
+ printf "delete %s\n" $prefix/foo |
+ git update-ref --stdin
+'
+
+test_expect_success 'empty directory should not fool 1-arg delete' '
+ prefix=refs/e-delete-1 &&
+ git update-ref $prefix/foo $C &&
+ git pack-refs --all &&
+ mkdir -p .git/$prefix/foo/bar/baz &&
+ printf "delete %s $C\n" $prefix/foo |
+ git update-ref --stdin
+'
+
+test_expect_success 'non-empty directory blocks create' '
+ prefix=refs/ne-create &&
+ mkdir -p .git/$prefix/foo/bar &&
+ : >.git/$prefix/foo/bar/baz.lock &&
+ test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $SQ$prefix/foo$SQ: there is a non-empty directory $SQ.git/$prefix/foo$SQ blocking reference $SQ$prefix/foo$SQ
+ EOF
+ printf "%s\n" "update $prefix/foo $C" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ
+ EOF
+ printf "%s\n" "update $prefix/foo $D $C" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err
+'
+
+test_expect_success 'broken reference blocks create' '
+ prefix=refs/broken-create &&
+ mkdir -p .git/$prefix &&
+ echo "gobbledigook" >.git/$prefix/foo &&
+ test_when_finished "rm -f .git/$prefix/foo" &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken
+ EOF
+ printf "%s\n" "update $prefix/foo $C" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken
+ EOF
+ printf "%s\n" "update $prefix/foo $D $C" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err
+'
+
+test_expect_success 'non-empty directory blocks indirect create' '
+ prefix=refs/ne-indirect-create &&
+ git symbolic-ref $prefix/symref $prefix/foo &&
+ mkdir -p .git/$prefix/foo/bar &&
+ : >.git/$prefix/foo/bar/baz.lock &&
+ test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $SQ$prefix/symref$SQ: there is a non-empty directory $SQ.git/$prefix/foo$SQ blocking reference $SQ$prefix/foo$SQ
+ EOF
+ printf "%s\n" "update $prefix/symref $C" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ
+ EOF
+ printf "%s\n" "update $prefix/symref $D $C" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err
+'
+
+test_expect_success 'broken reference blocks indirect create' '
+ prefix=refs/broken-indirect-create &&
+ git symbolic-ref $prefix/symref $prefix/foo &&
+ echo "gobbledigook" >.git/$prefix/foo &&
+ test_when_finished "rm -f .git/$prefix/foo" &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken
+ EOF
+ printf "%s\n" "update $prefix/symref $C" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err &&
+ cat >expected <<-EOF &&
+ fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken
+ EOF
+ printf "%s\n" "update $prefix/symref $D $C" |
+ test_must_fail git update-ref --stdin 2>output.err &&
+ test_cmp expected output.err
+'
+
+test_expect_success 'no bogus intermediate values during delete' '
+ prefix=refs/slow-transaction &&
+ # Set up a reference with differing loose and packed versions:
+ git update-ref $prefix/foo $C &&
+ git pack-refs --all &&
+ git update-ref $prefix/foo $D &&
+ # Now try to update the reference, but hold the `packed-refs` lock
+ # for a while to see what happens while the process is blocked:
+ : >.git/packed-refs.lock &&
+ test_when_finished "rm -f .git/packed-refs.lock" &&
+ {
+ # Note: the following command is intentionally run in the
+ # background. We increase the timeout so that `update-ref`
+ # attempts to acquire the `packed-refs` lock for much longer
+ # than it takes for us to do the check then delete it:
+ git -c core.packedrefstimeout=30000 update-ref -d $prefix/foo &
+ } &&
+ pid2=$! &&
+ # Give update-ref plenty of time to get to the point where it tries
+ # to lock packed-refs:
+ sleep 1 &&
+ # Make sure that update-ref did not complete despite the lock:
+ kill -0 $pid2 &&
+ # Verify that the reference still has its old value:
+ sha1=$(git rev-parse --verify --quiet $prefix/foo || echo undefined) &&
+ case "$sha1" in
+ $D)
+ # This is what we hope for; it means that nothing
+ # user-visible has changed yet.
+ : ;;
+ undefined)
+ # This is not correct; it means the deletion has happened
+ # already even though update-ref should not have been
+ # able to acquire the lock yet.
+ echo "$prefix/foo deleted prematurely" &&
+ break
+ ;;
+ $C)
+ # This value should never be seen. Probably the loose
+ # reference has been deleted but the packed reference
+ # is still there:
+ echo "$prefix/foo incorrectly observed to be C" &&
+ break
+ ;;
+ *)
+ # WTF?
+ echo "unexpected value observed for $prefix/foo: $sha1" &&
+ break
+ ;;
+ esac >out &&
+ rm -f .git/packed-refs.lock &&
+ wait $pid2 &&
+ test_must_be_empty out &&
+ test_must_fail git rev-parse --verify --quiet $prefix/foo
+'
+
+test_expect_success 'delete fails cleanly if packed-refs file is locked' '
+ prefix=refs/locked-packed-refs &&
+ # Set up a reference with differing loose and packed versions:
+ git update-ref $prefix/foo $C &&
+ git pack-refs --all &&
+ git update-ref $prefix/foo $D &&
+ git for-each-ref $prefix >unchanged &&
+ # Now try to delete it while the `packed-refs` lock is held:
+ : >.git/packed-refs.lock &&
+ test_when_finished "rm -f .git/packed-refs.lock" &&
+ test_must_fail git update-ref -d $prefix/foo >out 2>err &&
+ git for-each-ref $prefix >actual &&
+ test_grep "Unable to create $SQ.*packed-refs.lock$SQ: " err &&
+ test_cmp unchanged actual
+'
+
+test_expect_success 'delete fails cleanly if packed-refs.new write fails' '
+ # Setup and expectations are similar to the test above.
+ prefix=refs/failed-packed-refs &&
+ git update-ref $prefix/foo $C &&
+ git pack-refs --all &&
+ git update-ref $prefix/foo $D &&
+ git for-each-ref $prefix >unchanged &&
+ # This should not happen in practice, but it is an easy way to get a
+ # reliable error (we open with create_tempfile(), which uses O_EXCL).
+ : >.git/packed-refs.new &&
+ test_when_finished "rm -f .git/packed-refs.new" &&
+ test_must_fail git update-ref -d $prefix/foo &&
+ git for-each-ref $prefix >actual &&
+ test_cmp unchanged actual
+'
+
+RWT="test-tool ref-store worktree:wt"
+RMAIN="test-tool ref-store worktree:main"
+
+test_expect_success 'setup worktree' '
+ test_commit first &&
+ git worktree add -b wt-main wt &&
+ (
+ cd wt &&
+ test_commit second
+ )
+'
+
+# Some refs (refs/bisect/*, pseudorefs) are kept per worktree, so they should
+# only appear in the for-each-reflog output if it is called from the correct
+# worktree, which is exercised in this test. This test is poorly written for
+# mulitple reasons: 1) it creates invalidly formatted log entres. 2) it uses
+# direct FS access for creating the reflogs. 3) PSEUDO-WT and refs/bisect/random
+# do not create reflogs by default, so it is not testing a realistic scenario.
+test_expect_success 'for_each_reflog()' '
+ echo $ZERO_OID > .git/logs/PSEUDO-MAIN &&
+ mkdir -p .git/logs/refs/bisect &&
+ echo $ZERO_OID > .git/logs/refs/bisect/random &&
+
+ echo $ZERO_OID > .git/worktrees/wt/logs/PSEUDO-WT &&
+ mkdir -p .git/worktrees/wt/logs/refs/bisect &&
+ echo $ZERO_OID > .git/worktrees/wt/logs/refs/bisect/wt-random &&
+
+ $RWT for-each-reflog | cut -d" " -f 2- | sort >actual &&
+ cat >expected <<-\EOF &&
+ HEAD 0x1
+ PSEUDO-WT 0x0
+ refs/bisect/wt-random 0x0
+ refs/heads/main 0x0
+ refs/heads/wt-main 0x0
+ EOF
+ test_cmp expected actual &&
+
+ $RMAIN for-each-reflog | cut -d" " -f 2- | sort >actual &&
+ cat >expected <<-\EOF &&
+ HEAD 0x1
+ PSEUDO-MAIN 0x0
+ refs/bisect/random 0x0
+ refs/heads/main 0x0
+ refs/heads/wt-main 0x0
+ EOF
+ test_cmp expected actual
+'
+
+# Triggering the bug detected by this test requires a newline to fall
+# exactly BUFSIZ-1 bytes from the end of the file. We don't know
+# what that value is, since it's platform dependent. However, if
+# we choose some value N, we also catch any D which divides N evenly
+# (since we will read backwards in chunks of D). So we choose 8K,
+# which catches glibc (with an 8K BUFSIZ) and *BSD (1K).
+#
+# 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' '
+ git checkout -b reflogskip &&
+ zf=$(test_oid zero_2) &&
+ ident="abc <xyz> 0000000001 +0000" &&
+ for i in $(test_seq 1 75); do
+ printf "$zf%02d $zf%02d %s\t" $i $(($i+1)) "$ident" &&
+ if test $i = 75; then
+ for j in $(test_seq 1 89); do
+ printf X || return 1
+ done
+ else
+ printf X
+ fi &&
+ printf "\n" || return 1
+ done >.git/logs/refs/heads/reflogskip &&
+ git rev-parse reflogskip@{73} >actual &&
+ echo ${zf}03 >expect &&
+ test_cmp expect actual
+'
+
+# This test takes a lock on an individual ref; this is not supported in
+# reftable.
+test_expect_success 'reflog expire operates on symref not referrent' '
+ git branch --create-reflog the_symref &&
+ git branch --create-reflog referrent &&
+ git update-ref referrent HEAD &&
+ git symbolic-ref refs/heads/the_symref refs/heads/referrent &&
+ test_when_finished "rm -f .git/refs/heads/referrent.lock" &&
+ touch .git/refs/heads/referrent.lock &&
+ git reflog expire --expire=all the_symref
+'
+
+test_expect_success 'empty reflog' '
+ test_when_finished "rm -rf empty" &&
+ git init empty &&
+ test_commit -C empty A &&
+ >empty/.git/logs/refs/heads/foo &&
+ git -C empty reflog expire --all 2>err &&
+ test_must_be_empty err
+'
+
+test_expect_success SYMLINKS 'ref resolution not confused by broken symlinks' '
+ ln -s does-not-exist .git/refs/heads/broken &&
+ test_must_fail git rev-parse --verify broken
+'
+
+test_expect_success 'log diagnoses bogus HEAD hash' '
+ git init empty &&
+ test_when_finished "rm -rf empty" &&
+ echo 1234abcd >empty/.git/refs/heads/main &&
+ test_must_fail git -C empty log 2>stderr &&
+ test_grep broken stderr
+'
+
+test_expect_success 'log diagnoses bogus HEAD symref' '
+ git init empty &&
+ test-tool -C empty ref-store main create-symref HEAD refs/heads/invalid.lock &&
+ test_must_fail git -C empty log 2>stderr &&
+ test_grep broken stderr &&
+ test_must_fail git -C empty log --default totally-bogus 2>stderr &&
+ test_grep broken stderr
+'
+
+test_done
diff --git a/t/t3210-pack-refs.sh b/t/t0601-reffiles-pack-refs.sh
index c0f1f9cfb7..c309d2bae8 100755
--- a/t/t3210-pack-refs.sh
+++ b/t/t0601-reffiles-pack-refs.sh
@@ -15,10 +15,10 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
-if test_have_prereq !REFFILES
+if ! test_have_prereq REFFILES
then
- skip_all='skipping files-backend specific pack-refs tests'
- test_done
+ skip_all='skipping reffiles specific tests'
+ test_done
fi
test_expect_success 'enable reflogs' '
@@ -32,6 +32,14 @@ test_expect_success 'prepare a trivial repository' '
HEAD=$(git rev-parse --verify HEAD)
'
+test_expect_success 'pack_refs(PACK_REFS_ALL | PACK_REFS_PRUNE)' '
+ N=`find .git/refs -type f | wc -l` &&
+ test "$N" != 0 &&
+ test-tool ref-store main pack-refs PACK_REFS_PRUNE,PACK_REFS_ALL &&
+ N=`find .git/refs -type f` &&
+ test -z "$N"
+'
+
SHA1=
test_expect_success 'see if git show-ref works as expected' '
@@ -300,4 +308,54 @@ test_expect_success SYMLINKS 'pack symlinked packed-refs' '
test "$(test_readlink .git/packed-refs)" = "my-deviant-packed-refs"
'
+# The 'packed-refs' file is stored directly in .git/. This means it is global
+# to the repository, and can only contain refs that are shared across all
+# worktrees.
+test_expect_success 'refs/worktree must not be packed' '
+ test_commit initial &&
+ test_commit wt1 &&
+ test_commit wt2 &&
+ git worktree add wt1 wt1 &&
+ git worktree add wt2 wt2 &&
+ git checkout initial &&
+ git update-ref refs/worktree/foo HEAD &&
+ git -C wt1 update-ref refs/worktree/foo HEAD &&
+ git -C wt2 update-ref refs/worktree/foo HEAD &&
+ git pack-refs --all &&
+ test_path_is_missing .git/refs/tags/wt1 &&
+ test_path_is_file .git/refs/worktree/foo &&
+ test_path_is_file .git/worktrees/wt1/refs/worktree/foo &&
+ test_path_is_file .git/worktrees/wt2/refs/worktree/foo
+'
+
+# 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_tick && git commit --allow-empty -m one &&
+ recoverable=$(git rev-parse HEAD) &&
+ test_tick && git commit --allow-empty -m two &&
+ missing=$(git rev-parse HEAD) &&
+ rm -f .git/refs/heads/main &&
+ cat >.git/packed-refs <<-EOF &&
+ $missing refs/heads/main
+ $recoverable refs/heads/other
+ EOF
+ echo $missing >expect &&
+ git rev-parse refs/heads/main >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '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' '
+ git update-ref -d refs/heads/other &&
+ git rev-parse refs/heads/main >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh
index 271c5e4fd3..e0c6482797 100755
--- a/t/t1006-cat-file.sh
+++ b/t/t1006-cat-file.sh
@@ -1100,6 +1100,42 @@ test_expect_success 'cat-file --batch="batman" with --batch-all-objects will wor
cmp expect actual
'
+test_expect_success 'cat-file %(objectsize:disk) with --batch-all-objects' '
+ # our state has both loose and packed objects,
+ # so find both for our expected output
+ {
+ find .git/objects/?? -type f |
+ awk -F/ "{ print \$0, \$3\$4 }" |
+ while read path oid
+ do
+ size=$(test_file_size "$path") &&
+ echo "$oid $size" ||
+ return 1
+ done &&
+ rawsz=$(test_oid rawsz) &&
+ find .git/objects/pack -name "*.idx" |
+ while read idx
+ do
+ git show-index <"$idx" >idx.raw &&
+ sort -nr <idx.raw >idx.sorted &&
+ packsz=$(test_file_size "${idx%.idx}.pack") &&
+ end=$((packsz - rawsz)) &&
+ while read start oid rest
+ do
+ size=$((end - start)) &&
+ end=$start &&
+ echo "$oid $size" ||
+ return 1
+ done <idx.sorted ||
+ return 1
+ done
+ } >expect.raw &&
+ sort <expect.raw >expect &&
+ git cat-file --batch-all-objects \
+ --batch-check="%(objectname) %(objectsize:disk)" >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'set up replacement object' '
orig=$(git rev-parse HEAD) &&
git cat-file commit $orig >orig &&
diff --git a/t/t1401-symbolic-ref.sh b/t/t1401-symbolic-ref.sh
index 3241d35917..5c60d6f812 100755
--- a/t/t1401-symbolic-ref.sh
+++ b/t/t1401-symbolic-ref.sh
@@ -106,9 +106,8 @@ test_expect_success LONG_REF 'we can parse long symbolic ref' '
'
test_expect_success 'symbolic-ref reports failure in exit code' '
- test_when_finished "rm -f .git/HEAD.lock" &&
- >.git/HEAD.lock &&
- test_must_fail git symbolic-ref HEAD refs/heads/whatever
+ # Create d/f conflict to simulate failure.
+ test_must_fail git symbolic-ref refs/heads refs/heads/foo
'
test_expect_success 'symbolic-ref writes reflog entry' '
diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh
index ec1957b709..d0a8f7b121 100755
--- a/t/t1403-show-ref.sh
+++ b/t/t1403-show-ref.sh
@@ -262,9 +262,9 @@ test_expect_success '--exists with non-commit object' '
test_expect_success '--exists with directory fails with generic error' '
cat >expect <<-EOF &&
- error: failed to look up reference: Is a directory
+ error: reference does not exist
EOF
- test_expect_code 1 git show-ref --exists refs/heads 2>err &&
+ test_expect_code 2 git show-ref --exists refs/heads 2>err &&
test_cmp expect err
'
diff --git a/t/t1404-update-ref-errors.sh b/t/t1404-update-ref-errors.sh
index 0369beea33..00b7013705 100755
--- a/t/t1404-update-ref-errors.sh
+++ b/t/t1404-update-ref-errors.sh
@@ -191,78 +191,6 @@ test_expect_success 'one new ref is a simple prefix of another' '
'
-test_expect_success REFFILES 'empty directory should not fool rev-parse' '
- prefix=refs/e-rev-parse &&
- git update-ref $prefix/foo $C &&
- git pack-refs --all &&
- mkdir -p .git/$prefix/foo/bar/baz &&
- echo "$C" >expected &&
- git rev-parse $prefix/foo >actual &&
- test_cmp expected actual
-'
-
-test_expect_success REFFILES 'empty directory should not fool for-each-ref' '
- prefix=refs/e-for-each-ref &&
- git update-ref $prefix/foo $C &&
- git for-each-ref $prefix >expected &&
- git pack-refs --all &&
- mkdir -p .git/$prefix/foo/bar/baz &&
- git for-each-ref $prefix >actual &&
- test_cmp expected actual
-'
-
-test_expect_success REFFILES 'empty directory should not fool create' '
- prefix=refs/e-create &&
- mkdir -p .git/$prefix/foo/bar/baz &&
- printf "create %s $C\n" $prefix/foo |
- git update-ref --stdin
-'
-
-test_expect_success REFFILES 'empty directory should not fool verify' '
- prefix=refs/e-verify &&
- git update-ref $prefix/foo $C &&
- git pack-refs --all &&
- mkdir -p .git/$prefix/foo/bar/baz &&
- printf "verify %s $C\n" $prefix/foo |
- git update-ref --stdin
-'
-
-test_expect_success REFFILES 'empty directory should not fool 1-arg update' '
- prefix=refs/e-update-1 &&
- git update-ref $prefix/foo $C &&
- git pack-refs --all &&
- mkdir -p .git/$prefix/foo/bar/baz &&
- printf "update %s $D\n" $prefix/foo |
- git update-ref --stdin
-'
-
-test_expect_success REFFILES 'empty directory should not fool 2-arg update' '
- prefix=refs/e-update-2 &&
- git update-ref $prefix/foo $C &&
- git pack-refs --all &&
- mkdir -p .git/$prefix/foo/bar/baz &&
- printf "update %s $D $C\n" $prefix/foo |
- git update-ref --stdin
-'
-
-test_expect_success REFFILES 'empty directory should not fool 0-arg delete' '
- prefix=refs/e-delete-0 &&
- git update-ref $prefix/foo $C &&
- git pack-refs --all &&
- mkdir -p .git/$prefix/foo/bar/baz &&
- printf "delete %s\n" $prefix/foo |
- git update-ref --stdin
-'
-
-test_expect_success REFFILES 'empty directory should not fool 1-arg delete' '
- prefix=refs/e-delete-1 &&
- git update-ref $prefix/foo $C &&
- git pack-refs --all &&
- mkdir -p .git/$prefix/foo/bar/baz &&
- printf "delete %s $C\n" $prefix/foo |
- git update-ref --stdin
-'
-
test_expect_success REFFILES 'D/F conflict prevents add long + delete short' '
df_test refs/df-al-ds --add-del foo/bar foo
'
@@ -468,169 +396,4 @@ test_expect_success 'incorrect old value blocks indirect no-deref delete' '
test_cmp expected output.err
'
-test_expect_success REFFILES 'non-empty directory blocks create' '
- prefix=refs/ne-create &&
- mkdir -p .git/$prefix/foo/bar &&
- : >.git/$prefix/foo/bar/baz.lock &&
- test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" &&
- cat >expected <<-EOF &&
- fatal: cannot lock ref $SQ$prefix/foo$SQ: there is a non-empty directory $SQ.git/$prefix/foo$SQ blocking reference $SQ$prefix/foo$SQ
- EOF
- printf "%s\n" "update $prefix/foo $C" |
- test_must_fail git update-ref --stdin 2>output.err &&
- test_cmp expected output.err &&
- cat >expected <<-EOF &&
- fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ
- EOF
- printf "%s\n" "update $prefix/foo $D $C" |
- test_must_fail git update-ref --stdin 2>output.err &&
- test_cmp expected output.err
-'
-
-test_expect_success REFFILES 'broken reference blocks create' '
- prefix=refs/broken-create &&
- mkdir -p .git/$prefix &&
- echo "gobbledigook" >.git/$prefix/foo &&
- test_when_finished "rm -f .git/$prefix/foo" &&
- cat >expected <<-EOF &&
- fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken
- EOF
- printf "%s\n" "update $prefix/foo $C" |
- test_must_fail git update-ref --stdin 2>output.err &&
- test_cmp expected output.err &&
- cat >expected <<-EOF &&
- fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken
- EOF
- printf "%s\n" "update $prefix/foo $D $C" |
- test_must_fail git update-ref --stdin 2>output.err &&
- test_cmp expected output.err
-'
-
-test_expect_success REFFILES 'non-empty directory blocks indirect create' '
- prefix=refs/ne-indirect-create &&
- git symbolic-ref $prefix/symref $prefix/foo &&
- mkdir -p .git/$prefix/foo/bar &&
- : >.git/$prefix/foo/bar/baz.lock &&
- test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" &&
- cat >expected <<-EOF &&
- fatal: cannot lock ref $SQ$prefix/symref$SQ: there is a non-empty directory $SQ.git/$prefix/foo$SQ blocking reference $SQ$prefix/foo$SQ
- EOF
- printf "%s\n" "update $prefix/symref $C" |
- test_must_fail git update-ref --stdin 2>output.err &&
- test_cmp expected output.err &&
- cat >expected <<-EOF &&
- fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ
- EOF
- printf "%s\n" "update $prefix/symref $D $C" |
- test_must_fail git update-ref --stdin 2>output.err &&
- test_cmp expected output.err
-'
-
-test_expect_success REFFILES 'broken reference blocks indirect create' '
- prefix=refs/broken-indirect-create &&
- git symbolic-ref $prefix/symref $prefix/foo &&
- echo "gobbledigook" >.git/$prefix/foo &&
- test_when_finished "rm -f .git/$prefix/foo" &&
- cat >expected <<-EOF &&
- fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken
- EOF
- printf "%s\n" "update $prefix/symref $C" |
- test_must_fail git update-ref --stdin 2>output.err &&
- test_cmp expected output.err &&
- cat >expected <<-EOF &&
- fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken
- EOF
- printf "%s\n" "update $prefix/symref $D $C" |
- test_must_fail git update-ref --stdin 2>output.err &&
- test_cmp expected output.err
-'
-
-test_expect_success REFFILES 'no bogus intermediate values during delete' '
- prefix=refs/slow-transaction &&
- # Set up a reference with differing loose and packed versions:
- git update-ref $prefix/foo $C &&
- git pack-refs --all &&
- git update-ref $prefix/foo $D &&
- # Now try to update the reference, but hold the `packed-refs` lock
- # for a while to see what happens while the process is blocked:
- : >.git/packed-refs.lock &&
- test_when_finished "rm -f .git/packed-refs.lock" &&
- {
- # Note: the following command is intentionally run in the
- # background. We increase the timeout so that `update-ref`
- # attempts to acquire the `packed-refs` lock for much longer
- # than it takes for us to do the check then delete it:
- git -c core.packedrefstimeout=30000 update-ref -d $prefix/foo &
- } &&
- pid2=$! &&
- # Give update-ref plenty of time to get to the point where it tries
- # to lock packed-refs:
- sleep 1 &&
- # Make sure that update-ref did not complete despite the lock:
- kill -0 $pid2 &&
- # Verify that the reference still has its old value:
- sha1=$(git rev-parse --verify --quiet $prefix/foo || echo undefined) &&
- case "$sha1" in
- $D)
- # This is what we hope for; it means that nothing
- # user-visible has changed yet.
- : ;;
- undefined)
- # This is not correct; it means the deletion has happened
- # already even though update-ref should not have been
- # able to acquire the lock yet.
- echo "$prefix/foo deleted prematurely" &&
- break
- ;;
- $C)
- # This value should never be seen. Probably the loose
- # reference has been deleted but the packed reference
- # is still there:
- echo "$prefix/foo incorrectly observed to be C" &&
- break
- ;;
- *)
- # WTF?
- echo "unexpected value observed for $prefix/foo: $sha1" &&
- break
- ;;
- esac >out &&
- rm -f .git/packed-refs.lock &&
- wait $pid2 &&
- test_must_be_empty out &&
- test_must_fail git rev-parse --verify --quiet $prefix/foo
-'
-
-test_expect_success REFFILES 'delete fails cleanly if packed-refs file is locked' '
- prefix=refs/locked-packed-refs &&
- # Set up a reference with differing loose and packed versions:
- git update-ref $prefix/foo $C &&
- git pack-refs --all &&
- git update-ref $prefix/foo $D &&
- git for-each-ref $prefix >unchanged &&
- # Now try to delete it while the `packed-refs` lock is held:
- : >.git/packed-refs.lock &&
- test_when_finished "rm -f .git/packed-refs.lock" &&
- test_must_fail git update-ref -d $prefix/foo >out 2>err &&
- git for-each-ref $prefix >actual &&
- test_grep "Unable to create $SQ.*packed-refs.lock$SQ: " err &&
- test_cmp unchanged actual
-'
-
-test_expect_success REFFILES 'delete fails cleanly if packed-refs.new write fails' '
- # Setup and expectations are similar to the test above.
- prefix=refs/failed-packed-refs &&
- git update-ref $prefix/foo $C &&
- git pack-refs --all &&
- git update-ref $prefix/foo $D &&
- git for-each-ref $prefix >unchanged &&
- # This should not happen in practice, but it is an easy way to get a
- # reliable error (we open with create_tempfile(), which uses O_EXCL).
- : >.git/packed-refs.new &&
- test_when_finished "rm -f .git/packed-refs.new" &&
- test_must_fail git update-ref -d $prefix/foo &&
- git for-each-ref $prefix >actual &&
- test_cmp unchanged actual
-'
-
test_done
diff --git a/t/t1405-main-ref-store.sh b/t/t1405-main-ref-store.sh
index e4627cf1b6..976bd71efb 100755
--- a/t/t1405-main-ref-store.sh
+++ b/t/t1405-main-ref-store.sh
@@ -15,14 +15,6 @@ test_expect_success 'setup' '
test_commit one
'
-test_expect_success REFFILES 'pack_refs(PACK_REFS_ALL | PACK_REFS_PRUNE)' '
- N=`find .git/refs -type f | wc -l` &&
- test "$N" != 0 &&
- $RUN pack-refs PACK_REFS_PRUNE,PACK_REFS_ALL &&
- N=`find .git/refs -type f` &&
- test -z "$N"
-'
-
test_expect_success 'create_symref(FOO, refs/heads/main)' '
$RUN create-symref FOO refs/heads/main nothing &&
echo refs/heads/main >expected &&
@@ -112,7 +104,7 @@ test_expect_success 'delete_reflog(HEAD)' '
test_must_fail git reflog exists HEAD
'
-test_expect_success REFFILES 'create-reflog(HEAD)' '
+test_expect_success 'create-reflog(HEAD)' '
$RUN create-reflog HEAD &&
git reflog exists HEAD
'
diff --git a/t/t1407-worktree-ref-store.sh b/t/t1407-worktree-ref-store.sh
index 05b1881c59..48b1c92a41 100755
--- a/t/t1407-worktree-ref-store.sh
+++ b/t/t1407-worktree-ref-store.sh
@@ -53,41 +53,4 @@ test_expect_success 'create_symref(FOO, refs/heads/main)' '
test_cmp expected actual
'
-# Some refs (refs/bisect/*, pseudorefs) are kept per worktree, so they should
-# only appear in the for-each-reflog output if it is called from the correct
-# worktree, which is exercised in this test. This test is poorly written (and
-# therefore marked REFFILES) for mulitple reasons: 1) it creates invalidly
-# formatted log entres. 2) it uses direct FS access for creating the reflogs. 3)
-# PSEUDO-WT and refs/bisect/random do not create reflogs by default, so it is
-# not testing a realistic scenario.
-test_expect_success REFFILES 'for_each_reflog()' '
- echo $ZERO_OID > .git/logs/PSEUDO-MAIN &&
- mkdir -p .git/logs/refs/bisect &&
- echo $ZERO_OID > .git/logs/refs/bisect/random &&
-
- echo $ZERO_OID > .git/worktrees/wt/logs/PSEUDO-WT &&
- mkdir -p .git/worktrees/wt/logs/refs/bisect &&
- echo $ZERO_OID > .git/worktrees/wt/logs/refs/bisect/wt-random &&
-
- $RWT for-each-reflog | cut -d" " -f 2- | sort >actual &&
- cat >expected <<-\EOF &&
- HEAD 0x1
- PSEUDO-WT 0x0
- refs/bisect/wt-random 0x0
- refs/heads/main 0x0
- refs/heads/wt-main 0x0
- EOF
- test_cmp expected actual &&
-
- $RMAIN for-each-reflog | cut -d" " -f 2- | sort >actual &&
- cat >expected <<-\EOF &&
- HEAD 0x1
- PSEUDO-MAIN 0x0
- refs/bisect/random 0x0
- refs/heads/main 0x0
- refs/heads/wt-main 0x0
- EOF
- test_cmp expected actual
-'
-
test_done
diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh
index a0ff8d51f0..d2f5f42e67 100755
--- a/t/t1410-reflog.sh
+++ b/t/t1410-reflog.sh
@@ -354,36 +354,6 @@ test_expect_success 'stale dirs do not cause d/f conflicts (reflogs off)' '
test_must_be_empty actual
'
-# Triggering the bug detected by this test requires a newline to fall
-# exactly BUFSIZ-1 bytes from the end of the file. We don't know
-# what that value is, since it's platform dependent. However, if
-# we choose some value N, we also catch any D which divides N evenly
-# (since we will read backwards in chunks of D). So we choose 8K,
-# which catches glibc (with an 8K BUFSIZ) and *BSD (1K).
-#
-# 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 REFFILES,SHA1 'parsing reverse reflogs at BUFSIZ boundaries' '
- git checkout -b reflogskip &&
- zf=$(test_oid zero_2) &&
- ident="abc <xyz> 0000000001 +0000" &&
- for i in $(test_seq 1 75); do
- printf "$zf%02d $zf%02d %s\t" $i $(($i+1)) "$ident" &&
- if test $i = 75; then
- for j in $(test_seq 1 89); do
- printf X || return 1
- done
- else
- printf X
- fi &&
- printf "\n" || return 1
- done >.git/logs/refs/heads/reflogskip &&
- git rev-parse reflogskip@{73} >actual &&
- echo ${zf}03 >expect &&
- test_cmp expect actual
-'
-
test_expect_success 'no segfaults for reflog containing non-commit sha1s' '
git update-ref --create-reflog -m "Creating ref" \
refs/tests/tree-in-reflog HEAD &&
@@ -397,18 +367,6 @@ test_expect_failure 'reflog with non-commit entries displays all entries' '
test_line_count = 3 actual
'
-# This test takes a lock on an individual ref; this is not supported in
-# reftable.
-test_expect_success REFFILES 'reflog expire operates on symref not referrent' '
- git branch --create-reflog the_symref &&
- git branch --create-reflog referrent &&
- git update-ref referrent HEAD &&
- git symbolic-ref refs/heads/the_symref refs/heads/referrent &&
- test_when_finished "rm -f .git/refs/heads/referrent.lock" &&
- touch .git/refs/heads/referrent.lock &&
- git reflog expire --expire=all the_symref
-'
-
test_expect_success 'continue walking past root commits' '
git init orphanage &&
(
diff --git a/t/t1414-reflog-walk.sh b/t/t1414-reflog-walk.sh
index ea64cecf47..be6c3f472c 100755
--- a/t/t1414-reflog-walk.sh
+++ b/t/t1414-reflog-walk.sh
@@ -121,13 +121,12 @@ test_expect_success 'min/max age uses entry date to limit' '
# Create a situation where the reflog and ref database disagree about the latest
# state of HEAD.
-test_expect_success REFFILES 'walk prefers reflog to ref tip' '
+test_expect_success 'walk prefers reflog to ref tip' '
+ test_commit A &&
+ test_commit B &&
+ git reflog delete HEAD@{0} &&
head=$(git rev-parse HEAD) &&
- one=$(git rev-parse one) &&
- ident="$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE" &&
- echo "$head $one $ident broken reflog entry" >>.git/logs/HEAD &&
-
- echo $one >expect &&
+ git rev-parse A >expect &&
git log -g --format=%H -1 >actual &&
test_cmp expect actual
'
diff --git a/t/t1415-worktree-refs.sh b/t/t1415-worktree-refs.sh
index 3b531842dd..eb4eec8bec 100755
--- a/t/t1415-worktree-refs.sh
+++ b/t/t1415-worktree-refs.sh
@@ -17,17 +17,6 @@ test_expect_success 'setup' '
git -C wt2 update-ref refs/worktree/foo HEAD
'
-# The 'packed-refs' file is stored directly in .git/. This means it is global
-# to the repository, and can only contain refs that are shared across all
-# worktrees.
-test_expect_success REFFILES 'refs/worktree must not be packed' '
- git pack-refs --all &&
- test_path_is_missing .git/refs/tags/wt1 &&
- test_path_is_file .git/refs/worktree/foo &&
- test_path_is_file .git/worktrees/wt1/refs/worktree/foo &&
- test_path_is_file .git/worktrees/wt2/refs/worktree/foo
-'
-
test_expect_success 'refs/worktree are per-worktree' '
test_cmp_rev worktree/foo initial &&
( cd wt1 && test_cmp_rev worktree/foo wt1 ) &&
diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh
index 3f9e7f62e4..a669e592f1 100755
--- a/t/t1500-rev-parse.sh
+++ b/t/t1500-rev-parse.sh
@@ -208,6 +208,23 @@ test_expect_success 'rev-parse --show-object-format in repo' '
grep "unknown mode for --show-object-format: squeamish-ossifrage" err
'
+test_expect_success 'rev-parse --show-ref-format' '
+ test_detect_ref_format >expect &&
+ git rev-parse --show-ref-format >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'rev-parse --show-ref-format with invalid storage' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ git config extensions.refstorage broken &&
+ test_must_fail git rev-parse --show-ref-format 2>err &&
+ grep "error: invalid value for ${SQ}extensions.refstorage${SQ}: ${SQ}broken${SQ}" err
+ )
+'
+
test_expect_success '--show-toplevel from subdir of working tree' '
pwd >expect &&
git -C sub/dir rev-parse --show-toplevel >actual &&
diff --git a/t/t1503-rev-parse-verify.sh b/t/t1503-rev-parse-verify.sh
index bc136833c1..79df65ec7f 100755
--- a/t/t1503-rev-parse-verify.sh
+++ b/t/t1503-rev-parse-verify.sh
@@ -144,11 +144,6 @@ test_expect_success 'main@{n} for various n' '
test_must_fail git rev-parse --verify main@{$Np1}
'
-test_expect_success SYMLINKS,REFFILES 'ref resolution not confused by broken symlinks' '
- ln -s does-not-exist .git/refs/heads/broken &&
- test_must_fail git rev-parse --verify broken
-'
-
test_expect_success 'options can appear after --verify' '
git rev-parse --verify HEAD >expect &&
git rev-parse --verify -q HEAD >actual &&
diff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh
index 947d1587ac..a5c7358eea 100755
--- a/t/t2017-checkout-orphan.sh
+++ b/t/t2017-checkout-orphan.sh
@@ -86,7 +86,7 @@ test_expect_success '--orphan makes reflog by default' '
git rev-parse --verify delta@{0}
'
-test_expect_success REFFILES '--orphan does not make reflog when core.logAllRefUpdates = false' '
+test_expect_success '--orphan does not make reflog when core.logAllRefUpdates = false' '
git checkout main &&
git config core.logAllRefUpdates false &&
git checkout --orphan epsilon &&
diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh
index 3742971105..c28c04133c 100755
--- a/t/t2400-worktree-add.sh
+++ b/t/t2400-worktree-add.sh
@@ -490,7 +490,8 @@ test_expect_success 'put a worktree under rebase' '
cd under-rebase &&
set_fake_editor &&
FAKE_LINES="edit 1" git rebase -i HEAD^ &&
- git worktree list | grep "under-rebase.*detached HEAD"
+ git worktree list >actual &&
+ grep "under-rebase.*detached HEAD" actual
)
'
@@ -531,7 +532,8 @@ test_expect_success 'checkout a branch under bisect' '
git bisect start &&
git bisect bad &&
git bisect good HEAD~2 &&
- git worktree list | grep "under-bisect.*detached HEAD" &&
+ git worktree list >actual &&
+ grep "under-bisect.*detached HEAD" actual &&
test_must_fail git worktree add new-bisect under-bisect &&
! test -d new-bisect
)
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index 6a316f081e..de7d3014e4 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -519,7 +519,7 @@ EOF
mv .git/config .git/config-saved
-test_expect_success SHA1 'git branch -m q q2 without config should succeed' '
+test_expect_success DEFAULT_REPO_FORMAT 'git branch -m q q2 without config should succeed' '
git branch -m q q2 &&
git branch -m q2 q
'
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
index 34faeac3f1..3319240515 100755
--- a/t/t3903-stash.sh
+++ b/t/t3903-stash.sh
@@ -200,7 +200,7 @@ test_expect_success 'drop stash reflog updates refs/stash' '
test_cmp expect actual
'
-test_expect_success REFFILES 'drop stash reflog updates refs/stash with rewrite' '
+test_expect_success 'drop stash reflog updates refs/stash with rewrite' '
git init repo &&
(
cd repo &&
@@ -213,16 +213,16 @@ test_expect_success REFFILES 'drop stash reflog updates refs/stash with rewrite'
new_oid="$(git -C repo rev-parse stash@{0})" &&
cat >expect <<-EOF &&
- $(test_oid zero) $old_oid
- $old_oid $new_oid
+ $new_oid
+ $old_oid
EOF
- cut -d" " -f1-2 repo/.git/logs/refs/stash >actual &&
+ git -C repo reflog show refs/stash --format=%H >actual &&
test_cmp expect actual &&
git -C repo stash drop stash@{1} &&
- cut -d" " -f1-2 repo/.git/logs/refs/stash >actual &&
+ git -C repo reflog show refs/stash --format=%H >actual &&
cat >expect <<-EOF &&
- $(test_oid zero) $new_oid
+ $new_oid
EOF
test_cmp expect actual
'
diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh
index 85be1367de..49c042a38a 100755
--- a/t/t4001-diff-rename.sh
+++ b/t/t4001-diff-rename.sh
@@ -286,4 +286,28 @@ test_expect_success 'basename similarity vs best similarity' '
test_cmp expected actual
'
+test_expect_success 'last line matters too' '
+ {
+ test_write_lines a 0 1 2 3 4 5 6 7 8 9 &&
+ printf "git ignores final up to 63 characters if not newline terminated"
+ } >no-final-lf &&
+ git add no-final-lf &&
+ git commit -m "original version of file with no final newline" &&
+
+ # Change ONLY the first character of the whole file
+ {
+ test_write_lines b 0 1 2 3 4 5 6 7 8 9 &&
+ printf "git ignores final up to 63 characters if not newline terminated"
+ } >no-final-lf &&
+ git add no-final-lf &&
+ git mv no-final-lf still-absent-final-lf &&
+ git commit -a -m "rename no-final-lf -> still-absent-final-lf" &&
+ git diff-tree -r -M --name-status HEAD^ HEAD >actual &&
+ sed -e "s/^R[0-9]* /R /" actual >actual.munged &&
+ cat >expected <<-\EOF &&
+ R no-final-lf still-absent-final-lf
+ EOF
+ test_cmp expected actual.munged
+'
+
test_done
diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh
index cb094241ec..1e3b2dbea4 100755
--- a/t/t4013-diff-various.sh
+++ b/t/t4013-diff-various.sh
@@ -663,4 +663,10 @@ test_expect_success 'diff --default-prefix overrides diff.mnemonicprefix' '
check_prefix actual a/file0 b/file0
'
+test_expect_success 'diff --no-renames cannot be abbreviated' '
+ test_expect_code 129 git diff --no-rename >actual 2>error &&
+ test_must_be_empty actual &&
+ grep "invalid option: --no-rename" error
+'
+
test_done
diff --git a/t/t4053-diff-no-index.sh b/t/t4053-diff-no-index.sh
index 5ce345d309..651ec77660 100755
--- a/t/t4053-diff-no-index.sh
+++ b/t/t4053-diff-no-index.sh
@@ -205,6 +205,18 @@ test_expect_success POSIXPERM,SYMLINKS 'diff --no-index normalizes: mode not lik
test_cmp expected actual
'
+test_expect_success POSIXPERM 'external diff with mode-only change' '
+ echo content >not-executable &&
+ echo content >executable &&
+ chmod +x executable &&
+ echo executable executable $(test_oid zero) 100755 \
+ not-executable $(test_oid zero) 100644 not-executable \
+ >expect &&
+ test_expect_code 1 git -c diff.external=echo diff \
+ --no-index executable not-executable >actual &&
+ test_cmp expect actual
+'
+
test_expect_success "diff --no-index treats '-' as stdin" '
cat >expect <<-EOF &&
diff --git a/- b/a/1
diff --git a/t/t4129-apply-samemode.sh b/t/t4129-apply-samemode.sh
index e7a7295f1b..2775bfadd8 100755
--- a/t/t4129-apply-samemode.sh
+++ b/t/t4129-apply-samemode.sh
@@ -41,7 +41,8 @@ test_expect_success FILEMODE 'same mode (index only)' '
chmod +x file &&
git add file &&
git apply --cached patch-0.txt &&
- git ls-files -s file | grep "^100755"
+ git ls-files -s file >ls-files-output &&
+ test_grep "^100755" ls-files-output
'
test_expect_success FILEMODE 'mode update (no index)' '
@@ -60,7 +61,8 @@ test_expect_success FILEMODE 'mode update (with index)' '
test_expect_success FILEMODE 'mode update (index only)' '
git reset --hard &&
git apply --cached patch-1.txt &&
- git ls-files -s file | grep "^100755"
+ git ls-files -s file >ls-files-output &&
+ test_grep "^100755" ls-files-output
'
test_expect_success FILEMODE 'empty mode is rejected' '
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index ddd205f98a..60fe60d761 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -2255,23 +2255,6 @@ test_expect_success 'log on empty repo fails' '
test_grep does.not.have.any.commits stderr
'
-test_expect_success REFFILES 'log diagnoses bogus HEAD hash' '
- git init empty &&
- test_when_finished "rm -rf empty" &&
- echo 1234abcd >empty/.git/refs/heads/main &&
- test_must_fail git -C empty log 2>stderr &&
- test_grep broken stderr
-'
-
-test_expect_success REFFILES 'log diagnoses bogus HEAD symref' '
- git init empty &&
- test-tool -C empty ref-store main create-symref HEAD refs/heads/invalid.lock &&
- test_must_fail git -C empty log 2>stderr &&
- test_grep broken stderr &&
- test_must_fail git -C empty log --default totally-bogus 2>stderr &&
- test_grep broken stderr
-'
-
test_expect_success 'log does not default to HEAD when rev input is given' '
git log --branches=does-not-exist >actual &&
test_must_be_empty actual
diff --git a/t/t5003-archive-zip.sh b/t/t5003-archive-zip.sh
index fc499cdff0..961c6aac25 100755
--- a/t/t5003-archive-zip.sh
+++ b/t/t5003-archive-zip.sh
@@ -239,4 +239,38 @@ check_zip with_untracked2
check_added with_untracked2 untracked one/untracked
check_added with_untracked2 untracked two/untracked
+# Test remote archive over HTTP protocol.
+#
+# Note: this should be the last part of this test suite, because
+# by including lib-httpd.sh, the test may end early if httpd tests
+# should not be run.
+#
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+test_expect_success "setup for HTTP protocol" '
+ cp -R bare.git "$HTTPD_DOCUMENT_ROOT_PATH/bare.git" &&
+ git -C "$HTTPD_DOCUMENT_ROOT_PATH/bare.git" \
+ config http.uploadpack true &&
+ set_askpass user@host pass@host
+'
+
+setup_askpass_helper
+
+test_expect_success 'remote archive does not work with protocol v1' '
+ test_must_fail git -c protocol.version=1 archive \
+ --remote="$HTTPD_URL/auth/smart/bare.git" \
+ --output=remote-http.zip HEAD >actual 2>&1 &&
+ cat >expect <<-EOF &&
+ fatal: can${SQ}t connect to subservice git-upload-archive
+ EOF
+ test_cmp expect actual
+'
+
+test_expect_success 'archive remote http repository' '
+ git archive --remote="$HTTPD_URL/auth/smart/bare.git" \
+ --output=remote-http.zip HEAD &&
+ test_cmp_bin d.zip remote-http.zip
+'
+
test_done
diff --git a/t/t5312-prune-corruption.sh b/t/t5312-prune-corruption.sh
index 230cb38712..d8d2e30468 100755
--- a/t/t5312-prune-corruption.sh
+++ b/t/t5312-prune-corruption.sh
@@ -111,30 +111,4 @@ test_expect_success 'pack-refs does not silently delete broken loose ref' '
test_cmp expect actual
'
-# 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 REFFILES 'create packed-refs file with broken ref' '
- rm -f .git/refs/heads/main &&
- cat >.git/packed-refs <<-EOF &&
- $missing refs/heads/main
- $recoverable refs/heads/other
- EOF
- echo $missing >expect &&
- git rev-parse refs/heads/main >actual &&
- test_cmp expect actual
-'
-
-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 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
-'
-
test_done
diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh
index c20aafe99a..dd09134db0 100755
--- a/t/t5319-multi-pack-index.sh
+++ b/t/t5319-multi-pack-index.sh
@@ -1171,4 +1171,39 @@ test_expect_success 'reader notices out-of-bounds fanout' '
test_cmp expect err
'
+test_expect_success 'bitmapped packs are stored via the BTMP chunk' '
+ test_when_finished "rm -fr repo" &&
+ git init repo &&
+ (
+ cd repo &&
+
+ for i in 1 2 3 4 5
+ do
+ test_commit "$i" &&
+ git repack -d || return 1
+ done &&
+
+ find $objdir/pack -type f -name "*.idx" | xargs -n 1 basename |
+ sort >packs &&
+
+ git multi-pack-index write --stdin-packs <packs &&
+ test_must_fail test-tool read-midx --bitmap $objdir 2>err &&
+ cat >expect <<-\EOF &&
+ error: MIDX does not contain the BTMP chunk
+ EOF
+ test_cmp expect err &&
+
+ git multi-pack-index write --stdin-packs --bitmap \
+ --preferred-pack="$(head -n1 <packs)" <packs &&
+ test-tool read-midx --bitmap $objdir >actual &&
+ for i in $(test_seq $(wc -l <packs))
+ do
+ sed -ne "${i}s/\.idx$/\.pack/p" packs &&
+ echo " bitmap_pos: $((($i - 1) * 3))" &&
+ echo " bitmap_nr: 3" || return 1
+ done >expect &&
+ test_cmp expect actual
+ )
+'
+
test_done
diff --git a/t/t5332-multi-pack-reuse.sh b/t/t5332-multi-pack-reuse.sh
new file mode 100755
index 0000000000..2ba788b042
--- /dev/null
+++ b/t/t5332-multi-pack-reuse.sh
@@ -0,0 +1,203 @@
+#!/bin/sh
+
+test_description='pack-objects multi-pack reuse'
+
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-bitmap.sh
+
+objdir=.git/objects
+packdir=$objdir/pack
+
+test_pack_reused () {
+ test_trace2_data pack-objects pack-reused "$1"
+}
+
+test_packs_reused () {
+ test_trace2_data pack-objects packs-reused "$1"
+}
+
+
+# pack_position <object> </path/to/pack.idx
+pack_position () {
+ git show-index >objects &&
+ grep "$1" objects | cut -d" " -f1
+}
+
+test_expect_success 'preferred pack is reused for single-pack reuse' '
+ test_config pack.allowPackReuse single &&
+
+ for i in A B
+ do
+ test_commit "$i" &&
+ git repack -d || return 1
+ done &&
+
+ git multi-pack-index write --bitmap &&
+
+ : >trace2.txt &&
+ GIT_TRACE2_EVENT="$PWD/trace2.txt" \
+ git pack-objects --stdout --revs --all >/dev/null &&
+
+ test_pack_reused 3 <trace2.txt &&
+ test_packs_reused 1 <trace2.txt
+'
+
+test_expect_success 'enable multi-pack reuse' '
+ git config pack.allowPackReuse multi
+'
+
+test_expect_success 'reuse all objects from subset of bitmapped packs' '
+ test_commit C &&
+ git repack -d &&
+
+ git multi-pack-index write --bitmap &&
+
+ cat >in <<-EOF &&
+ $(git rev-parse C)
+ ^$(git rev-parse A)
+ EOF
+
+ : >trace2.txt &&
+ GIT_TRACE2_EVENT="$PWD/trace2.txt" \
+ git pack-objects --stdout --revs <in >/dev/null &&
+
+ test_pack_reused 6 <trace2.txt &&
+ test_packs_reused 2 <trace2.txt
+'
+
+test_expect_success 'reuse all objects from all packs' '
+ : >trace2.txt &&
+ GIT_TRACE2_EVENT="$PWD/trace2.txt" \
+ git pack-objects --stdout --revs --all >/dev/null &&
+
+ test_pack_reused 9 <trace2.txt &&
+ test_packs_reused 3 <trace2.txt
+'
+
+test_expect_success 'reuse objects from first pack with middle gap' '
+ for i in D E F
+ do
+ test_commit "$i" || return 1
+ done &&
+
+ # Set "pack.window" to zero to ensure that we do not create any
+ # deltas, which could alter the amount of pack reuse we perform
+ # (if, for e.g., we are not sending one or more bases).
+ D="$(git -c pack.window=0 pack-objects --all --unpacked $packdir/pack)" &&
+
+ d_pos="$(pack_position $(git rev-parse D) <$packdir/pack-$D.idx)" &&
+ e_pos="$(pack_position $(git rev-parse E) <$packdir/pack-$D.idx)" &&
+ f_pos="$(pack_position $(git rev-parse F) <$packdir/pack-$D.idx)" &&
+
+ # commits F, E, and D, should appear in that order at the
+ # beginning of the pack
+ test $f_pos -lt $e_pos &&
+ test $e_pos -lt $d_pos &&
+
+ # Ensure that the pack we are constructing sorts ahead of any
+ # other packs in lexical/bitmap order by choosing it as the
+ # preferred pack.
+ git multi-pack-index write --bitmap --preferred-pack="pack-$D.idx" &&
+
+ cat >in <<-EOF &&
+ $(git rev-parse E)
+ ^$(git rev-parse D)
+ EOF
+
+ : >trace2.txt &&
+ GIT_TRACE2_EVENT="$PWD/trace2.txt" \
+ git pack-objects --stdout --delta-base-offset --revs <in >/dev/null &&
+
+ test_pack_reused 3 <trace2.txt &&
+ test_packs_reused 1 <trace2.txt
+'
+
+test_expect_success 'reuse objects from middle pack with middle gap' '
+ rm -fr $packdir/multi-pack-index* &&
+
+ # Ensure that the pack we are constructing sort into any
+ # position *but* the first one, by choosing a different pack as
+ # the preferred one.
+ git multi-pack-index write --bitmap --preferred-pack="pack-$A.idx" &&
+
+ cat >in <<-EOF &&
+ $(git rev-parse E)
+ ^$(git rev-parse D)
+ EOF
+
+ : >trace2.txt &&
+ GIT_TRACE2_EVENT="$PWD/trace2.txt" \
+ git pack-objects --stdout --delta-base-offset --revs <in >/dev/null &&
+
+ test_pack_reused 3 <trace2.txt &&
+ test_packs_reused 1 <trace2.txt
+'
+
+test_expect_success 'omit delta with uninteresting base (same pack)' '
+ git repack -adk &&
+
+ test_seq 32 >f &&
+ git add f &&
+ test_tick &&
+ git commit -m "delta" &&
+ delta="$(git rev-parse HEAD)" &&
+
+ test_seq 64 >f &&
+ test_tick &&
+ git commit -a -m "base" &&
+ base="$(git rev-parse HEAD)" &&
+
+ test_commit other &&
+
+ git repack -d &&
+
+ have_delta "$(git rev-parse $delta:f)" "$(git rev-parse $base:f)" &&
+
+ git multi-pack-index write --bitmap &&
+
+ cat >in <<-EOF &&
+ $(git rev-parse other)
+ ^$base
+ EOF
+
+ : >trace2.txt &&
+ GIT_TRACE2_EVENT="$PWD/trace2.txt" \
+ git pack-objects --stdout --delta-base-offset --revs <in >/dev/null &&
+
+ # We can only reuse the 3 objects corresponding to "other" from
+ # the latest pack.
+ #
+ # This is because even though we want "delta", we do not want
+ # "base", meaning that we have to inflate the delta/base-pair
+ # corresponding to the blob in commit "delta", which bypasses
+ # the pack-reuse mechanism.
+ #
+ # The remaining objects from the other pack are similarly not
+ # reused because their objects are on the uninteresting side of
+ # the query.
+ test_pack_reused 3 <trace2.txt &&
+ test_packs_reused 1 <trace2.txt
+'
+
+test_expect_success 'omit delta from uninteresting base (cross pack)' '
+ cat >in <<-EOF &&
+ $(git rev-parse $base)
+ ^$(git rev-parse $delta)
+ EOF
+
+ P="$(git pack-objects --revs $packdir/pack <in)" &&
+
+ git multi-pack-index write --bitmap --preferred-pack="pack-$P.idx" &&
+
+ : >trace2.txt &&
+ GIT_TRACE2_EVENT="$PWD/trace2.txt" \
+ git pack-objects --stdout --delta-base-offset --all >/dev/null &&
+
+ packs_nr="$(find $packdir -type f -name "pack-*.pack" | wc -l)" &&
+ objects_nr="$(git rev-list --count --all --objects)" &&
+
+ test_pack_reused $(($objects_nr - 1)) <trace2.txt &&
+ test_packs_reused $packs_nr <trace2.txt
+'
+
+test_done
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 79592a3b0a..33d34d5ae9 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -803,7 +803,8 @@ test_expect_success 'fetch.writeCommitGraph with submodules' '
cd super-clone &&
rm -rf .git/objects/info &&
git -c fetch.writeCommitGraph=true fetch origin &&
- test_path_is_file .git/objects/info/commit-graphs/commit-graph-chain
+ test_path_is_file .git/objects/info/commit-graphs/commit-graph-chain &&
+ git -c fetch.writeCommitGraph=true fetch --recurse-submodules origin
)
'
diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh
index a95841dc36..25772c85c5 100755
--- a/t/t5514-fetch-multiple.sh
+++ b/t/t5514-fetch-multiple.sh
@@ -24,6 +24,15 @@ setup_repository () {
)
}
+setup_test_clone () {
+ test_dir="$1" &&
+ git clone one "$test_dir" &&
+ for r in one two three
+ do
+ git -C "$test_dir" remote add "$r" "../$r" || return 1
+ done
+}
+
test_expect_success setup '
setup_repository one &&
setup_repository two &&
@@ -209,4 +218,156 @@ test_expect_success 'git fetch --multiple --jobs=0 picks a default' '
git fetch --multiple --jobs=0)
'
+create_fetch_all_expect () {
+ cat >expect <<-\EOF
+ one/main
+ one/side
+ origin/HEAD -> origin/main
+ origin/main
+ origin/side
+ three/another
+ three/main
+ three/side
+ two/another
+ two/main
+ two/side
+ EOF
+}
+
+for fetch_all in true false
+do
+ test_expect_success "git fetch --all (works with fetch.all = $fetch_all)" '
+ test_dir="test_fetch_all_$fetch_all" &&
+ setup_test_clone "$test_dir" &&
+ (
+ cd "$test_dir" &&
+ git config fetch.all $fetch_all &&
+ git fetch --all &&
+ create_fetch_all_expect &&
+ git branch -r >actual &&
+ test_cmp expect actual
+ )
+ '
+done
+
+test_expect_success 'git fetch (fetch all remotes with fetch.all = true)' '
+ setup_test_clone test9 &&
+ (
+ cd test9 &&
+ git config fetch.all true &&
+ git fetch &&
+ git branch -r >actual &&
+ create_fetch_all_expect &&
+ test_cmp expect actual
+ )
+'
+
+create_fetch_one_expect () {
+ cat >expect <<-\EOF
+ one/main
+ one/side
+ origin/HEAD -> origin/main
+ origin/main
+ origin/side
+ EOF
+}
+
+test_expect_success 'git fetch one (explicit remote overrides fetch.all)' '
+ setup_test_clone test10 &&
+ (
+ cd test10 &&
+ git config fetch.all true &&
+ git fetch one &&
+ create_fetch_one_expect &&
+ git branch -r >actual &&
+ test_cmp expect actual
+ )
+'
+
+create_fetch_two_as_origin_expect () {
+ cat >expect <<-\EOF
+ origin/HEAD -> origin/main
+ origin/another
+ origin/main
+ origin/side
+ EOF
+}
+
+test_expect_success 'git config fetch.all false (fetch only default remote)' '
+ setup_test_clone test11 &&
+ (
+ cd test11 &&
+ git config fetch.all false &&
+ git remote set-url origin ../two &&
+ git fetch &&
+ create_fetch_two_as_origin_expect &&
+ git branch -r >actual &&
+ test_cmp expect actual
+ )
+'
+
+for fetch_all in true false
+do
+ test_expect_success "git fetch --no-all (fetch only default remote with fetch.all = $fetch_all)" '
+ test_dir="test_no_all_fetch_all_$fetch_all" &&
+ setup_test_clone "$test_dir" &&
+ (
+ cd "$test_dir" &&
+ git config fetch.all $fetch_all &&
+ git remote set-url origin ../two &&
+ git fetch --no-all &&
+ create_fetch_two_as_origin_expect &&
+ git branch -r >actual &&
+ test_cmp expect actual
+ )
+ '
+done
+
+test_expect_success 'git fetch --no-all (fetch only default remote without fetch.all)' '
+ setup_test_clone test12 &&
+ (
+ cd test12 &&
+ git config --unset-all fetch.all || true &&
+ git remote set-url origin ../two &&
+ git fetch --no-all &&
+ create_fetch_two_as_origin_expect &&
+ git branch -r >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'git fetch --all --no-all (fetch only default remote)' '
+ setup_test_clone test13 &&
+ (
+ cd test13 &&
+ git remote set-url origin ../two &&
+ git fetch --all --no-all &&
+ create_fetch_two_as_origin_expect &&
+ git branch -r >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'git fetch --no-all one (fetch only explicit remote)' '
+ setup_test_clone test14 &&
+ (
+ cd test14 &&
+ git fetch --no-all one &&
+ create_fetch_one_expect &&
+ git branch -r >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'git fetch --no-all --all (fetch all remotes)' '
+ setup_test_clone test15 &&
+ (
+ cd test15 &&
+ git fetch --no-all --all &&
+ create_fetch_all_expect &&
+ git branch -r >actual &&
+ test_cmp expect actual
+ )
+'
+
test_done
diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh
index df758e187d..71428f3d5c 100755
--- a/t/t5541-http-push-smart.sh
+++ b/t/t5541-http-push-smart.sh
@@ -232,8 +232,9 @@ test_expect_success 'push --atomic fails on server-side errors' '
test_config -C "$d" http.receivepack true &&
up="$HTTPD_URL"/smart/atomic-branches.git &&
- # break ref updates for other on the remote site
- mkdir "$d/refs/heads/other.lock" &&
+ # Create d/f conflict to break ref updates for other on the remote site.
+ git -C "$d" update-ref -d refs/heads/other &&
+ git -C "$d" update-ref refs/heads/other/conflict HEAD &&
# add the new commit to other
git branch -f other collateral &&
@@ -241,18 +242,9 @@ test_expect_success 'push --atomic fails on server-side errors' '
# --atomic should cause entire push to be rejected
test_must_fail git push --atomic "$up" atomic other 2>output &&
- # the new branch should not have been created upstream
- test_must_fail git -C "$d" show-ref --verify refs/heads/atomic &&
-
- # upstream should still reflect atomic2, the last thing we pushed
- # successfully
- git rev-parse atomic2 >expected &&
- # ...to other.
- git -C "$d" rev-parse refs/heads/other >actual &&
- test_cmp expected actual &&
-
- # the new branch should not have been created upstream
+ # The atomic and other branches should not be created upstream.
test_must_fail git -C "$d" show-ref --verify refs/heads/atomic &&
+ test_must_fail git -C "$d" show-ref --verify refs/heads/other &&
# the failed refs should be indicated to the user
grep "^ ! .*rejected.* other -> other .*atomic transaction failed" output &&
diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh
index e069737b80..a623a1058c 100755
--- a/t/t5551-http-fetch-smart.sh
+++ b/t/t5551-http-fetch-smart.sh
@@ -733,4 +733,22 @@ test_expect_success 'no empty path components' '
! grep "//" log
'
+test_expect_success 'tag following always works over v0 http' '
+ upstream=$HTTPD_DOCUMENT_ROOT_PATH/tags &&
+ git init "$upstream" &&
+ (
+ cd "$upstream" &&
+ git commit --allow-empty -m base &&
+ git tag not-annotated &&
+ git tag -m foo annotated
+ ) &&
+ git init tags &&
+ git -C tags -c protocol.version=0 \
+ fetch --depth 1 $HTTPD_URL/smart/tags \
+ refs/tags/annotated:refs/tags/annotated &&
+ git -C "$upstream" for-each-ref refs/tags >expect &&
+ git -C tags for-each-ref >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index 47eae641f0..fb1b9c686d 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -157,6 +157,23 @@ test_expect_success 'clone --mirror does not repeat tags' '
'
+test_expect_success 'clone with files ref format' '
+ test_when_finished "rm -rf ref-storage" &&
+ git clone --ref-format=files --mirror src ref-storage &&
+ echo files >expect &&
+ git -C ref-storage rev-parse --show-ref-format >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'clone with garbage ref format' '
+ cat >expect <<-EOF &&
+ fatal: unknown ref storage format ${SQ}garbage${SQ}
+ EOF
+ test_must_fail git clone --ref-format=garbage --mirror src ref-storage 2>err &&
+ test_cmp expect err &&
+ test_path_is_missing ref-storage
+'
+
test_expect_success 'clone to destination with trailing /' '
git clone src target-1/ &&
diff --git a/t/t6113-rev-list-bitmap-filters.sh b/t/t6113-rev-list-bitmap-filters.sh
index 86c70521f1..459f0d7412 100755
--- a/t/t6113-rev-list-bitmap-filters.sh
+++ b/t/t6113-rev-list-bitmap-filters.sh
@@ -4,6 +4,8 @@ test_description='rev-list combining bitmaps and filters'
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-bitmap.sh
+TEST_PASSES_SANITIZE_LEAK=true
+
test_expect_success 'set up bitmapped repo' '
# one commit will have bitmaps, the other will not
test_commit one &&
diff --git a/t/t6135-pathspec-with-attrs.sh b/t/t6135-pathspec-with-attrs.sh
index 00b84ecba6..120dcd74a5 100755
--- a/t/t6135-pathspec-with-attrs.sh
+++ b/t/t6135-pathspec-with-attrs.sh
@@ -393,4 +393,31 @@ test_expect_success 'reading from .gitattributes in a subdirectory (3)' '
test_cmp expect actual
'
+test_expect_success POSIXPERM 'pathspec with builtin_objectmode attr can be used' '
+ >mode_exec_file_1 &&
+
+ git status -s ":(attr:builtin_objectmode=100644)mode_exec_*" >actual &&
+ echo ?? mode_exec_file_1 >expect &&
+ test_cmp expect actual &&
+
+ git add mode_exec_file_1 &&
+ chmod +x mode_exec_file_1 &&
+ git status -s ":(attr:builtin_objectmode=100755)mode_exec_*" >actual &&
+ echo AM mode_exec_file_1 >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success POSIXPERM 'builtin_objectmode attr can be excluded' '
+ >mode_1_regular &&
+ >mode_1_exec &&
+ chmod +x mode_1_exec &&
+ git status -s ":(exclude,attr:builtin_objectmode=100644)" "mode_1_*" >actual &&
+ echo ?? mode_1_exec >expect &&
+ test_cmp expect actual &&
+
+ git status -s ":(exclude,attr:builtin_objectmode=100755)" "mode_1_*" >actual &&
+ echo ?? mode_1_regular >expect &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t6406-merge-attr.sh b/t/t6406-merge-attr.sh
index 72f8c1722f..156a1efacf 100755
--- a/t/t6406-merge-attr.sh
+++ b/t/t6406-merge-attr.sh
@@ -42,11 +42,15 @@ test_expect_success setup '
#!/bin/sh
orig="$1" ours="$2" theirs="$3" exit="$4" path=$5
+ orig_name="$6" our_name="$7" their_name="$8"
(
echo "orig is $orig"
echo "ours is $ours"
echo "theirs is $theirs"
echo "path is $path"
+ echo "orig_name is $orig_name"
+ echo "our_name is $our_name"
+ echo "their_name is $their_name"
echo "=== orig ==="
cat "$orig"
echo "=== ours ==="
@@ -121,7 +125,7 @@ test_expect_success 'custom merge backend' '
git reset --hard anchor &&
git config --replace-all \
- merge.custom.driver "./custom-merge %O %A %B 0 %P" &&
+ merge.custom.driver "./custom-merge %O %A %B 0 %P %S %X %Y" &&
git config --replace-all \
merge.custom.name "custom merge driver for testing" &&
@@ -132,7 +136,8 @@ test_expect_success 'custom merge backend' '
o=$(git unpack-file main^:text) &&
a=$(git unpack-file side^:text) &&
b=$(git unpack-file main:text) &&
- sh -c "./custom-merge $o $a $b 0 text" &&
+ base_revid=$(git rev-parse --short main^) &&
+ sh -c "./custom-merge $o $a $b 0 text $base_revid HEAD main" &&
sed -e 1,3d $a >check-2 &&
cmp check-1 check-2 &&
rm -f $o $a $b
@@ -142,7 +147,7 @@ test_expect_success 'custom merge backend' '
git reset --hard anchor &&
git config --replace-all \
- merge.custom.driver "./custom-merge %O %A %B 1 %P" &&
+ merge.custom.driver "./custom-merge %O %A %B 1 %P %S %X %Y" &&
git config --replace-all \
merge.custom.name "custom merge driver for testing" &&
@@ -159,7 +164,8 @@ test_expect_success 'custom merge backend' '
o=$(git unpack-file main^:text) &&
a=$(git unpack-file anchor:text) &&
b=$(git unpack-file main:text) &&
- sh -c "./custom-merge $o $a $b 0 text" &&
+ base_revid=$(git rev-parse --short main^) &&
+ sh -c "./custom-merge $o $a $b 0 text $base_revid HEAD main" &&
sed -e 1,3d $a >check-2 &&
cmp check-1 check-2 &&
sed -e 1,3d -e 4q $a >check-3 &&
@@ -173,7 +179,7 @@ test_expect_success !WINDOWS 'custom merge driver that is killed with a signal'
git reset --hard anchor &&
git config --replace-all \
- merge.custom.driver "./custom-merge %O %A %B 0 %P" &&
+ merge.custom.driver "./custom-merge %O %A %B 0 %P %S %X %Y" &&
git config --replace-all \
merge.custom.name "custom merge driver for testing" &&
diff --git a/t/t7450-bad-git-dotfiles.sh b/t/t7450-bad-git-dotfiles.sh
index 35a31acd4d..46d4fb0354 100755
--- a/t/t7450-bad-git-dotfiles.sh
+++ b/t/t7450-bad-git-dotfiles.sh
@@ -45,6 +45,32 @@ test_expect_success 'check names' '
test_cmp expect actual
'
+test_expect_success 'check urls' '
+ cat >expect <<-\EOF &&
+ ./bar/baz/foo.git
+ https://example.com/foo.git
+ http://example.com:80/deeper/foo.git
+ EOF
+
+ test-tool submodule check-url >actual <<-\EOF &&
+ ./bar/baz/foo.git
+ https://example.com/foo.git
+ http://example.com:80/deeper/foo.git
+ -a./foo
+ ../../..//test/foo.git
+ ../../../../../:localhost:8080/foo.git
+ ..\../.\../:example.com/foo.git
+ ./%0ahost=example.com/foo.git
+ https://one.example.com/evil?%0ahost=two.example.com
+ https:///example.com/foo.git
+ http://example.com:test/foo.git
+ https::example.com/foo.git
+ http:::example.com/foo.git
+ EOF
+
+ test_cmp expect actual
+'
+
test_expect_success 'create innocent subrepo' '
git init innocent &&
git -C innocent commit --allow-empty -m foo
diff --git a/t/t7501-commit-basic-functionality.sh b/t/t7501-commit-basic-functionality.sh
index 3d8500a52e..bced44a0fc 100755
--- a/t/t7501-commit-basic-functionality.sh
+++ b/t/t7501-commit-basic-functionality.sh
@@ -3,8 +3,7 @@
# Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>
#
-# FIXME: Test the various index usages, -i and -o, test reflog,
-# signoff
+# FIXME: Test the various index usages, test reflog
test_description='git commit'
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
@@ -92,6 +91,34 @@ test_expect_success '--long fails with nothing to commit' '
test_must_fail git commit -m initial --long
'
+test_expect_success 'fail to commit untracked file (even with --include/--only)' '
+ echo content >baz &&
+ error="error: pathspec .baz. did not match any file(s) known to git" &&
+
+ test_must_fail git commit -m "baz" baz 2>err &&
+ test_grep -e "$error" err &&
+
+ test_must_fail git commit --only -m "baz" baz 2>err &&
+ test_grep -e "$error" err &&
+
+ # TODO: as for --include, the below command will fail because
+ # nothing is staged. If something was staged, it would not fail
+ # even though the provided pathspec does not match any tracked
+ # path. (However, the untracked paths that match the pathspec are
+ # not committed and only the staged changes get committed.)
+ # In either cases, no error is returned to stderr like in (--only
+ # and without --only/--include) cases. In a similar manner,
+ # "git add -u baz" also does not error out.
+ #
+ # Therefore, the below test is just to document the current behavior
+ # and is not an endorsement to the current behavior, and we may
+ # want to fix this. And when that happens, this test should be
+ # updated accordingly.
+
+ test_must_fail git commit --include -m "baz" baz 2>err &&
+ test_must_be_empty err
+'
+
test_expect_success 'setup: non-initial commit' '
echo bongo bongo bongo >file &&
git commit -m next -a
@@ -117,6 +144,51 @@ test_expect_success '--long with stuff to commit returns ok' '
git commit -m next -a --long
'
+for opt in "" "-o" "--only"
+do
+ test_expect_success 'exclude additional staged changes when given pathspec' '
+ echo content >>file &&
+ echo content >>baz &&
+ git add baz &&
+ git commit $opt -m "file" file &&
+
+ git diff --name-only >actual &&
+ test_must_be_empty actual &&
+
+ test_write_lines baz >expect &&
+ git diff --name-only --cached >actual &&
+ test_cmp expect actual &&
+
+ test_write_lines file >expect &&
+ git diff --name-only HEAD^ HEAD >actual &&
+ test_cmp expect actual
+ '
+done
+
+test_expect_success '-i/--include includes staged changes' '
+ echo content >>file &&
+ echo content >>baz &&
+ git add file &&
+
+ # baz is in the index, therefore, it will be committed
+ git commit --include -m "file and baz" baz &&
+
+ git diff --name-only HEAD >remaining &&
+ test_must_be_empty remaining &&
+
+ test_write_lines baz file >expect &&
+ git diff --name-only HEAD^ HEAD >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success '--include and --only do not mix' '
+ test_when_finished "git reset --hard" &&
+ echo content >>file &&
+ echo content >>baz &&
+ test_must_fail git commit --include --only -m "file baz" file baz 2>actual &&
+ test_grep -e "fatal: options .-i/--include. and .-o/--only. cannot be used together" actual
+'
+
test_expect_success 'commit message from non-existing file' '
echo more bongo: bongo bongo bongo bongo >file &&
test_must_fail git commit -F gah -a
@@ -389,6 +461,28 @@ test_expect_success 'amend commit to fix date' '
'
+test_expect_success 'amend commit to add signoff' '
+
+ test_commit "msg" file content &&
+ git commit --amend --signoff &&
+ test_commit_message HEAD <<-EOF
+ msg
+
+ Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+ EOF
+'
+
+test_expect_success 'amend does not add signoff if it already exists' '
+
+ test_commit --signoff "tenor" file newcontent &&
+ git commit --amend --signoff &&
+ test_commit_message HEAD <<-EOF
+ tenor
+
+ Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+ EOF
+'
+
test_expect_success 'commit mentions forced date in output' '
git commit --amend --date=2010-01-02T03:04:05 >output &&
grep "Date: *Sat Jan 2 03:04:05 2010" output
diff --git a/t/t7527-builtin-fsmonitor.sh b/t/t7527-builtin-fsmonitor.sh
index 78503158fd..363f9dc0e4 100755
--- a/t/t7527-builtin-fsmonitor.sh
+++ b/t/t7527-builtin-fsmonitor.sh
@@ -978,7 +978,7 @@ test_expect_success !UNICODE_COMPOSITION_SENSITIVE 'Unicode nfc/nfd' '
mkdir test_unicode/nfd &&
mkdir test_unicode/nfd/d_${utf8_nfd} &&
- git -C test_unicode fsmonitor--daemon stop &&
+ test-tool -C test_unicode fsmonitor-client query --token 0 &&
if test_have_prereq UNICODE_NFC_PRESERVED
then
diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh
index 00d29871e6..0943dfa18a 100755
--- a/t/t7900-maintenance.sh
+++ b/t/t7900-maintenance.sh
@@ -67,6 +67,51 @@ test_expect_success 'maintenance.auto config option' '
test_subcommand ! git maintenance run --auto --quiet <false
'
+test_expect_success 'register uses XDG_CONFIG_HOME config if it exists' '
+ test_when_finished rm -r .config/git/config &&
+ (
+ XDG_CONFIG_HOME=.config &&
+ export XDG_CONFIG_HOME &&
+ mkdir -p $XDG_CONFIG_HOME/git &&
+ >$XDG_CONFIG_HOME/git/config &&
+ git maintenance register &&
+ git config --file=$XDG_CONFIG_HOME/git/config --get maintenance.repo >actual &&
+ pwd >expect &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'register does not need XDG_CONFIG_HOME config to exist' '
+ test_when_finished git maintenance unregister &&
+ test_path_is_missing $XDG_CONFIG_HOME/git/config &&
+ git maintenance register &&
+ git config --global --get maintenance.repo >actual &&
+ pwd >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'unregister uses XDG_CONFIG_HOME config if it exists' '
+ test_when_finished rm -r .config/git/config &&
+ (
+ XDG_CONFIG_HOME=.config &&
+ export XDG_CONFIG_HOME &&
+ mkdir -p $XDG_CONFIG_HOME/git &&
+ >$XDG_CONFIG_HOME/git/config &&
+ git maintenance register &&
+ git maintenance unregister &&
+ test_must_fail git config --file=$XDG_CONFIG_HOME/git/config --get maintenance.repo >actual &&
+ test_must_be_empty actual
+ )
+'
+
+test_expect_success 'unregister does not need XDG_CONFIG_HOME config to exist' '
+ test_path_is_missing $XDG_CONFIG_HOME/git/config &&
+ git maintenance register &&
+ git maintenance unregister &&
+ test_must_fail git config --global --get maintenance.repo >actual &&
+ test_must_be_empty actual
+'
+
test_expect_success 'maintenance.<task>.enabled' '
git config maintenance.gc.enabled false &&
git config maintenance.commit-graph.enabled true &&
diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh
index 0333065d4d..7679780fb8 100755
--- a/t/t9500-gitweb-standalone-no-errors.sh
+++ b/t/t9500-gitweb-standalone-no-errors.sh
@@ -627,6 +627,7 @@ test_expect_success \
test_expect_success 'setup' '
version=$(git config core.repositoryformatversion) &&
algo=$(test_might_fail git config extensions.objectformat) &&
+ refstorage=$(test_might_fail git config extensions.refstorage) &&
cat >.git/config <<-\EOF &&
# testing noval and alternate separator
[gitweb]
@@ -637,6 +638,10 @@ test_expect_success 'setup' '
if test -n "$algo"
then
git config extensions.objectformat "$algo"
+ fi &&
+ if test -n "$refstorage"
+ then
+ git config extensions.refstorage "$refstorage"
fi
'
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index aa9a614de3..35eb534fdd 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -5,6 +5,12 @@
test_description='test bash completion'
+# The Bash completion scripts must not print anything to either stdout or
+# stderr, which we try to verify. When tracing is enabled without support for
+# BASH_XTRACEFD this assertion will fail, so we have to mark the test as
+# untraceable with such ancient Bash versions.
+test_untraceable=UnfortunatelyYes
+
. ./lib-bash.sh
complete ()
@@ -87,9 +93,11 @@ test_completion ()
else
sed -e 's/Z$//' |sort >expected
fi &&
- run_completion "$1" &&
+ run_completion "$1" >"$TRASH_DIRECTORY"/bash-completion-output 2>&1 &&
sort out >out_sorted &&
- test_cmp expected out_sorted
+ test_cmp expected out_sorted &&
+ test_must_be_empty "$TRASH_DIRECTORY"/bash-completion-output &&
+ rm "$TRASH_DIRECTORY"/bash-completion-output
}
# Test __gitcomp.
@@ -1925,6 +1933,14 @@ test_expect_success 'git checkout - --orphan with branch already provided comple
EOF
'
+test_expect_success 'git restore completes modified files' '
+ test_commit A a.file &&
+ echo B >a.file &&
+ test_completion "git restore a." <<-\EOF
+ a.file
+ EOF
+'
+
test_expect_success 'teardown after ref completion' '
git branch -d matching-branch &&
git tag -d matching-tag &&
@@ -2720,4 +2736,31 @@ test_expect_success '__git_complete' '
test_must_fail __git_complete ga missing
'
+test_expect_success '__git_pseudoref_exists' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ (
+ cd repo &&
+ sane_unset __git_repo_path &&
+
+ # HEAD should exist, even if it points to an unborn branch.
+ __git_pseudoref_exists HEAD >output 2>&1 &&
+ test_must_be_empty output &&
+
+ # HEAD points to an existing branch, so it should exist.
+ test_commit A &&
+ __git_pseudoref_exists HEAD >output 2>&1 &&
+ test_must_be_empty output &&
+
+ # CHERRY_PICK_HEAD does not exist, so the existence check should fail.
+ ! __git_pseudoref_exists CHERRY_PICK_HEAD >output 2>&1 &&
+ test_must_be_empty output &&
+
+ # CHERRY_PICK_HEAD points to a commit, so it should exist.
+ git update-ref CHERRY_PICK_HEAD A &&
+ __git_pseudoref_exists CHERRY_PICK_HEAD >output 2>&1 &&
+ test_must_be_empty output
+ )
+'
+
test_done
diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh
index 5eb57914ab..b5eaf7fdc1 100644
--- a/t/test-lib-functions.sh
+++ b/t/test-lib-functions.sh
@@ -1659,6 +1659,11 @@ test_detect_hash () {
test_hash_algo="${GIT_TEST_DEFAULT_HASH:-sha1}"
}
+# Detect the hash algorithm in use.
+test_detect_ref_format () {
+ echo "${GIT_TEST_DEFAULT_REF_FORMAT:-files}"
+}
+
# Load common hash metadata and common placeholder object IDs for use with
# test_oid.
test_oid_init () {
@@ -1874,6 +1879,20 @@ test_region () {
return 0
}
+# Check that the given data fragment was included as part of the
+# trace2-format trace on stdin.
+#
+# test_trace2_data <category> <key> <value>
+#
+# For example, to look for trace2_data_intmax("pack-objects", repo,
+# "reused", N) in an invocation of "git pack-objects", run:
+#
+# GIT_TRACE2_EVENT="$(pwd)/trace.txt" git pack-objects ... &&
+# test_trace2_data pack-objects reused N <trace2.txt
+test_trace2_data () {
+ grep -e '"category":"'"$1"'","key":"'"$2"'","value":"'"$3"'"'
+}
+
# Given a GIT_TRACE2_EVENT log over stdin, writes to stdout a list of URLs
# sent to git-remote-https child processes.
test_remote_https_urls() {
diff --git a/t/test-lib-github-workflow-markup.sh b/t/test-lib-github-workflow-markup.sh
index 970c6538cb..33405c90d7 100644
--- a/t/test-lib-github-workflow-markup.sh
+++ b/t/test-lib-github-workflow-markup.sh
@@ -42,8 +42,8 @@ finalize_test_case_output () {
fixed)
echo >>$github_markup_output "::notice::fixed: $this_test.$test_count $1"
;;
- ok)
- # Exit without printing the "ok" tests
+ ok|broken)
+ # Exit without printing the "ok" or ""broken" tests
return
;;
esac
diff --git a/t/test-lib.sh b/t/test-lib.sh
index 876b99562a..fc93aa57e6 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -542,6 +542,8 @@ export EDITOR
GIT_DEFAULT_HASH="${GIT_TEST_DEFAULT_HASH:-sha1}"
export GIT_DEFAULT_HASH
+GIT_DEFAULT_REF_FORMAT="${GIT_TEST_DEFAULT_REF_FORMAT:-files}"
+export GIT_DEFAULT_REF_FORMAT
GIT_TEST_MERGE_ALGORITHM="${GIT_TEST_MERGE_ALGORITHM:-ort}"
export GIT_TEST_MERGE_ALGORITHM
@@ -1745,7 +1747,14 @@ parisc* | hppa*)
;;
esac
-test_set_prereq REFFILES
+case "$GIT_DEFAULT_REF_FORMAT" in
+files)
+ test_set_prereq REFFILES;;
+*)
+ echo 2>&1 "error: unknown ref format $GIT_DEFAULT_REF_FORMAT"
+ exit 1
+ ;;
+esac
( COLUMNS=1 && test $COLUMNS = 1 ) && test_set_prereq COLUMNS_CAN_BE_1
test -z "$NO_CURL" && test_set_prereq LIBCURL
@@ -1936,6 +1945,10 @@ test_lazy_prereq SHA1 '
esac
'
+test_lazy_prereq DEFAULT_REPO_FORMAT '
+ test_have_prereq SHA1,REFFILES
+'
+
# Ensure that no test accidentally triggers a Git command
# that runs the actual maintenance scheduler, affecting a user's
# system permanently.
diff --git a/t/unit-tests/t-ctype.c b/t/unit-tests/t-ctype.c
new file mode 100644
index 0000000000..f315489984
--- /dev/null
+++ b/t/unit-tests/t-ctype.c
@@ -0,0 +1,80 @@
+#include "test-lib.h"
+
+static int is_in(const char *s, int ch)
+{
+ /*
+ * We can't find NUL using strchr. Accept it as the first
+ * character in the spec -- there are no empty classes.
+ */
+ if (ch == '\0')
+ return ch == *s;
+ if (*s == '\0')
+ s++;
+ return !!strchr(s, ch);
+}
+
+/* Macro to test a character type */
+#define TEST_CTYPE_FUNC(func, string) \
+static void test_ctype_##func(void) { \
+ for (int i = 0; i < 256; i++) { \
+ if (!check_int(func(i), ==, is_in(string, i))) \
+ test_msg(" i: 0x%02x", i); \
+ } \
+ if (!check(!func(EOF))) \
+ test_msg(" i: 0x%02x (EOF)", EOF); \
+}
+
+#define TEST_CHAR_CLASS(class) TEST(test_ctype_##class(), #class " works")
+
+#define DIGIT "0123456789"
+#define LOWER "abcdefghijklmnopqrstuvwxyz"
+#define UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+#define PUNCT "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
+#define ASCII \
+ "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" \
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" \
+ "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f" \
+ "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f" \
+ "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f" \
+ "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f" \
+ "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f" \
+ "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
+#define CNTRL \
+ "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" \
+ "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" \
+ "\x7f"
+
+TEST_CTYPE_FUNC(isdigit, DIGIT)
+TEST_CTYPE_FUNC(isspace, " \n\r\t")
+TEST_CTYPE_FUNC(isalpha, LOWER UPPER)
+TEST_CTYPE_FUNC(isalnum, LOWER UPPER DIGIT)
+TEST_CTYPE_FUNC(is_glob_special, "*?[\\")
+TEST_CTYPE_FUNC(is_regex_special, "$()*+.?[\\^{|")
+TEST_CTYPE_FUNC(is_pathspec_magic, "!\"#%&',-/:;<=>@_`~")
+TEST_CTYPE_FUNC(isascii, ASCII)
+TEST_CTYPE_FUNC(islower, LOWER)
+TEST_CTYPE_FUNC(isupper, UPPER)
+TEST_CTYPE_FUNC(iscntrl, CNTRL)
+TEST_CTYPE_FUNC(ispunct, PUNCT)
+TEST_CTYPE_FUNC(isxdigit, DIGIT "abcdefABCDEF")
+TEST_CTYPE_FUNC(isprint, LOWER UPPER DIGIT PUNCT " ")
+
+int cmd_main(int argc, const char **argv) {
+ /* Run all character type tests */
+ TEST_CHAR_CLASS(isspace);
+ TEST_CHAR_CLASS(isdigit);
+ TEST_CHAR_CLASS(isalpha);
+ TEST_CHAR_CLASS(isalnum);
+ TEST_CHAR_CLASS(is_glob_special);
+ TEST_CHAR_CLASS(is_regex_special);
+ TEST_CHAR_CLASS(is_pathspec_magic);
+ TEST_CHAR_CLASS(isascii);
+ TEST_CHAR_CLASS(islower);
+ TEST_CHAR_CLASS(isupper);
+ TEST_CHAR_CLASS(iscntrl);
+ TEST_CHAR_CLASS(ispunct);
+ TEST_CHAR_CLASS(isxdigit);
+ TEST_CHAR_CLASS(isprint);
+
+ return test_done();
+}
diff --git a/transport-helper.c b/transport-helper.c
index e34a8f47cf..dd6002b393 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -17,6 +17,7 @@
#include "refspec.h"
#include "transport-internal.h"
#include "protocol.h"
+#include "packfile.h"
static int debug;
@@ -432,6 +433,8 @@ static int fetch_with_fetch(struct transport *transport,
warning(_("%s unexpectedly said: '%s'"), data->name, buf.buf);
}
strbuf_release(&buf);
+
+ reprepare_packed_git(the_repository);
return 0;
}
@@ -626,7 +629,8 @@ static int process_connect_service(struct transport *transport,
ret = run_connect(transport, &cmdbuf);
} else if (data->stateless_connect &&
(get_protocol_version_config() == protocol_v2) &&
- !strcmp("git-upload-pack", name)) {
+ (!strcmp("git-upload-pack", name) ||
+ !strcmp("git-upload-archive", name))) {
strbuf_addf(&cmdbuf, "stateless-connect %s\n", name);
ret = run_connect(transport, &cmdbuf);
if (ret)
@@ -643,6 +647,7 @@ static int process_connect(struct transport *transport,
struct helper_data *data = transport->data;
const char *name;
const char *exec;
+ int ret;
name = for_push ? "git-receive-pack" : "git-upload-pack";
if (for_push)
@@ -650,7 +655,10 @@ static int process_connect(struct transport *transport,
else
exec = data->transport_options.uploadpack;
- return process_connect_service(transport, name, exec);
+ ret = process_connect_service(transport, name, exec);
+ if (ret)
+ do_take_over(transport);
+ return ret;
}
static int connect_helper(struct transport *transport, const char *name,
@@ -660,14 +668,14 @@ static int connect_helper(struct transport *transport, const char *name,
/* Get_helper so connect is inited. */
get_helper(transport);
- if (!data->connect)
- die(_("operation not supported by protocol"));
if (!process_connect_service(transport, name, exec))
die(_("can't connect to subservice %s"), name);
fd[0] = data->helper->out;
fd[1] = data->helper->in;
+
+ do_take_over(transport);
return 0;
}
@@ -682,10 +690,8 @@ static int fetch_refs(struct transport *transport,
get_helper(transport);
- if (process_connect(transport, 0)) {
- do_take_over(transport);
+ if (process_connect(transport, 0))
return transport->vtable->fetch_refs(transport, nr_heads, to_fetch);
- }
/*
* If we reach here, then the server, the client, and/or the transport
@@ -1142,10 +1148,8 @@ static int push_refs(struct transport *transport,
{
struct helper_data *data = transport->data;
- if (process_connect(transport, 1)) {
- do_take_over(transport);
+ if (process_connect(transport, 1))
return transport->vtable->push_refs(transport, remote_refs, flags);
- }
if (!remote_refs) {
fprintf(stderr,
@@ -1186,11 +1190,9 @@ static struct ref *get_refs_list(struct transport *transport, int for_push,
{
get_helper(transport);
- if (process_connect(transport, for_push)) {
- do_take_over(transport);
+ if (process_connect(transport, for_push))
return transport->vtable->get_refs_list(transport, for_push,
transport_options);
- }
return get_refs_list_using_list(transport, for_push);
}
@@ -1274,10 +1276,8 @@ static int get_bundle_uri(struct transport *transport)
{
get_helper(transport);
- if (process_connect(transport, 0)) {
- do_take_over(transport);
+ if (process_connect(transport, 0))
return transport->vtable->get_bundle_uri(transport);
- }
return -1;
}
diff --git a/transport.c b/transport.c
index bd7899e9bf..df518ead70 100644
--- a/transport.c
+++ b/transport.c
@@ -1467,6 +1467,7 @@ int transport_push(struct repository *r,
if (porcelain && !push_ret)
puts("Done");
else if (!quiet && !ret && !transport_refs_pushed(remote_refs))
+ /* stable plumbing output; do not modify or localize */
fprintf(stderr, "Everything up-to-date\n");
done:
diff --git a/worktree.c b/worktree.c
index 1399d452ac..b02a05a74a 100644
--- a/worktree.c
+++ b/worktree.c
@@ -12,18 +12,23 @@
#include "wt-status.h"
#include "config.h"
+void free_worktree(struct worktree *worktree)
+{
+ if (!worktree)
+ return;
+ free(worktree->path);
+ free(worktree->id);
+ free(worktree->head_ref);
+ free(worktree->lock_reason);
+ free(worktree->prune_reason);
+ free(worktree);
+}
+
void free_worktrees(struct worktree **worktrees)
{
int i = 0;
-
- for (i = 0; worktrees[i]; i++) {
- free(worktrees[i]->path);
- free(worktrees[i]->id);
- free(worktrees[i]->head_ref);
- free(worktrees[i]->lock_reason);
- free(worktrees[i]->prune_reason);
- free(worktrees[i]);
- }
+ for (i = 0; worktrees[i]; i++)
+ free_worktree(worktrees[i]);
free (worktrees);
}
@@ -51,7 +56,7 @@ static void add_head_info(struct worktree *wt)
/**
* get the main worktree
*/
-static struct worktree *get_main_worktree(void)
+static struct worktree *get_main_worktree(int skip_reading_head)
{
struct worktree *worktree = NULL;
struct strbuf worktree_path = STRBUF_INIT;
@@ -70,11 +75,13 @@ static struct worktree *get_main_worktree(void)
*/
worktree->is_bare = (is_bare_repository_cfg == 1) ||
is_bare_repository();
- add_head_info(worktree);
+ if (!skip_reading_head)
+ add_head_info(worktree);
return worktree;
}
-static struct worktree *get_linked_worktree(const char *id)
+struct worktree *get_linked_worktree(const char *id,
+ int skip_reading_head)
{
struct worktree *worktree = NULL;
struct strbuf path = STRBUF_INIT;
@@ -93,7 +100,8 @@ static struct worktree *get_linked_worktree(const char *id)
CALLOC_ARRAY(worktree, 1);
worktree->path = strbuf_detach(&worktree_path, NULL);
worktree->id = xstrdup(id);
- add_head_info(worktree);
+ if (!skip_reading_head)
+ add_head_info(worktree);
done:
strbuf_release(&path);
@@ -118,7 +126,14 @@ static void mark_current_worktree(struct worktree **worktrees)
free(git_dir);
}
-struct worktree **get_worktrees(void)
+/*
+ * NEEDSWORK: This function exists so that we can look up metadata of a
+ * worktree without trying to access any of its internals like the refdb. It
+ * would be preferable to instead have a corruption-tolerant function for
+ * retrieving worktree metadata that could be used when the worktree is known
+ * to not be in a healthy state, e.g. when creating or repairing it.
+ */
+static struct worktree **get_worktrees_internal(int skip_reading_head)
{
struct worktree **list = NULL;
struct strbuf path = STRBUF_INIT;
@@ -128,7 +143,7 @@ struct worktree **get_worktrees(void)
ALLOC_ARRAY(list, alloc);
- list[counter++] = get_main_worktree();
+ list[counter++] = get_main_worktree(skip_reading_head);
strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
dir = opendir(path.buf);
@@ -137,7 +152,7 @@ struct worktree **get_worktrees(void)
while ((d = readdir_skip_dot_and_dotdot(dir)) != NULL) {
struct worktree *linked = NULL;
- if ((linked = get_linked_worktree(d->d_name))) {
+ if ((linked = get_linked_worktree(d->d_name, skip_reading_head))) {
ALLOC_GROW(list, counter + 1, alloc);
list[counter++] = linked;
}
@@ -151,6 +166,11 @@ struct worktree **get_worktrees(void)
return list;
}
+struct worktree **get_worktrees(void)
+{
+ return get_worktrees_internal(0);
+}
+
const char *get_worktree_git_dir(const struct worktree *wt)
{
if (!wt)
@@ -591,7 +611,7 @@ static void repair_noop(int iserr UNUSED,
void repair_worktrees(worktree_repair_fn fn, void *cb_data)
{
- struct worktree **worktrees = get_worktrees();
+ struct worktree **worktrees = get_worktrees_internal(1);
struct worktree **wt = worktrees + 1; /* +1 skips main worktree */
if (!fn)
diff --git a/worktree.h b/worktree.h
index ce45b66de9..f14784a2ff 100644
--- a/worktree.h
+++ b/worktree.h
@@ -58,6 +58,13 @@ struct worktree *find_worktree(struct worktree **list,
const char *arg);
/*
+ * Look up the worktree corresponding to `id`, or NULL of no such worktree
+ * exists.
+ */
+struct worktree *get_linked_worktree(const char *id,
+ int skip_reading_head);
+
+/*
* Return the worktree corresponding to `path`, or NULL if no such worktree
* exists.
*/
@@ -135,6 +142,11 @@ void repair_worktrees(worktree_repair_fn, void *cb_data);
void repair_worktree_at_path(const char *, worktree_repair_fn, void *cb_data);
/*
+ * Free up the memory for a worktree.
+ */
+void free_worktree(struct worktree *);
+
+/*
* Free up the memory for worktree(s)
*/
void free_worktrees(struct worktree **);
diff --git a/write-or-die.c b/write-or-die.c
index 42a2dc73cd..3942152865 100644
--- a/write-or-die.c
+++ b/write-or-die.c
@@ -19,20 +19,17 @@
void maybe_flush_or_die(FILE *f, const char *desc)
{
static int skip_stdout_flush = -1;
- struct stat st;
- char *cp;
if (f == stdout) {
if (skip_stdout_flush < 0) {
- /* NEEDSWORK: make this a normal Boolean */
- cp = getenv("GIT_FLUSH");
- if (cp)
- skip_stdout_flush = (atoi(cp) == 0);
- else if ((fstat(fileno(stdout), &st) == 0) &&
- S_ISREG(st.st_mode))
- skip_stdout_flush = 1;
- else
- skip_stdout_flush = 0;
+ skip_stdout_flush = git_env_bool("GIT_FLUSH", -1);
+ if (skip_stdout_flush < 0) {
+ struct stat st;
+ if (fstat(fileno(stdout), &st))
+ skip_stdout_flush = 0;
+ else
+ skip_stdout_flush = S_ISREG(st.st_mode);
+ }
}
if (skip_stdout_flush && !ferror(f))
return;