]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'rj/advice-disable-how-to-disable'
authorJunio C Hamano <gitster@pobox.com>
Tue, 30 Jan 2024 21:34:12 +0000 (13:34 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 30 Jan 2024 21:34:12 +0000 (13:34 -0800)
All conditional "advice" messages show how to turn them off, which
becomes repetitive.  Setting advice.* configuration explicitly on
now omits the instruction part.

* rj/advice-disable-how-to-disable:
  advice: allow disabling the automatic hint in advise_if_enabled()

151 files changed:
.github/workflows/main.yml
.gitlab-ci.yml
Documentation/CodingGuidelines
Documentation/MyFirstContribution.txt
Documentation/RelNotes/2.44.0.txt
Documentation/SubmittingPatches
Documentation/config/extensions.txt
Documentation/config/fetch.txt
Documentation/config/pack.txt
Documentation/config/rebase.txt
Documentation/fetch-options.txt
Documentation/git-bisect.txt
Documentation/git-branch.txt
Documentation/git-clone.txt
Documentation/git-init.txt
Documentation/git-rebase.txt
Documentation/git-rev-parse.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/gitformat-pack.txt
Documentation/glossary-content.txt
Documentation/ref-storage-format.txt [new file with mode: 0644]
Documentation/rev-list-options.txt
Documentation/technical/repository-version.txt
Makefile
README.md
attr.c
branch.c
builtin/clone.c
builtin/commit.c
builtin/config.c
builtin/fetch.c
builtin/gc.c
builtin/index-pack.c
builtin/init-db.c
builtin/merge.c
builtin/pack-objects.c
builtin/rebase.c
builtin/rev-parse.c
builtin/send-pack.c
builtin/show-ref.c
builtin/var.c
builtin/worktree.c
ci/install-dependencies.sh
ci/install-docker-dependencies.sh
ci/lib.sh
ci/print-test-failures.sh
ci/run-build-and-minimal-fuzzers.sh [new file with mode: 0755]
commit-graph.c
compat/mingw.c
config.c
config.h
config.mak.uname
contrib/completion/git-completion.bash
contrib/completion/git-prompt.sh
diffcore-delta.c
ewah/bitmap.c
ewah/ewok.h
fsck.c
git-compat-util.h
git-p4.py
gitweb/gitweb.perl
http-push.c
merge-ort.c
midx.c
midx.h
neue [new file with mode: 0644]
oss-fuzz/dummy-cmd-main.c [new file with mode: 0644]
pack-bitmap-write.c
pack-bitmap.c
pack-bitmap.h
pack-objects.c
pack-objects.h
pack-revindex.c
pack-revindex.h
parse-options.c
path.c
path.h
pkt-line.c
pkt-line.h
refs.c
refs.h
refs/debug.c
refs/files-backend.c
refs/packed-backend.c
refs/refs-internal.h
reftable/block_test.c
reftable/blocksource.c
reftable/merged.c
reftable/merged_test.c
reftable/readwrite_test.c
reftable/record.c
reftable/record_test.c
reftable/reftable-record.h
reftable/stack.c
reftable/stack.h
reftable/stack_test.c
reftable/writer.c
repository.c
repository.h
sequencer.c
sequencer.h
setup.c
setup.h
sideband.c
strvec.h
submodule-config.c
submodule-config.h
t/README
t/helper/test-ctype.c [deleted file]
t/helper/test-pkt-line.c
t/helper/test-read-midx.c
t/helper/test-submodule.c
t/helper/test-tool.c
t/helper/test-tool.h
t/perf/p5332-multi-pack-reuse.sh [new file with mode: 0755]
t/t0001-init.sh
t/t0003-attributes.sh
t/t0024-crlf-archive.sh
t/t0070-fundamental.sh
t/t1006-cat-file.sh
t/t1401-symbolic-ref.sh
t/t1403-show-ref.sh
t/t1500-rev-parse.sh
t/t2400-worktree-add.sh
t/t3200-branch.sh
t/t4001-diff-rename.sh
t/t4013-diff-various.sh
t/t4129-apply-samemode.sh
t/t5319-multi-pack-index.sh
t/t5332-multi-pack-reuse.sh [new file with mode: 0755]
t/t5510-fetch.sh
t/t5514-fetch-multiple.sh
t/t5541-http-push-smart.sh
t/t5601-clone.sh
t/t6113-rev-list-bitmap-filters.sh
t/t6135-pathspec-with-attrs.sh
t/t7450-bad-git-dotfiles.sh
t/t7501-commit-basic-functionality.sh
t/t7527-builtin-fsmonitor.sh
t/t7900-maintenance.sh
t/t9500-gitweb-standalone-no-errors.sh
t/t9902-completion.sh
t/test-lib-functions.sh
t/test-lib-github-workflow-markup.sh
t/test-lib.sh
t/unit-tests/t-ctype.c [new file with mode: 0644]
transport.c
worktree.c
worktree.h
write-or-die.c

index 9fdbd5402898bf1c8a188bfd24262109c96e5e0f..4d97da57ec4e7b8f9276527f24cc068ee786f179 100644 (file)
@@ -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
index cd98bcb18aa38aca55f577466f7d44f06c50177f..43bfbd8834707d861a3fae8a27207257febc9a90 100644 (file)
@@ -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
index 8ed517a5ca0f5e50865f0bcbee29f18663d3bbf7..578587a47155e929457e12862cd648c9fdf81acd 100644 (file)
@@ -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.
 
index 279f6a3e7ca4678f01196e9f60cdea372d0cc4f1..f06563e98174604d339e3ded16b762e13b41534a 100644 (file)
@@ -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]
index e58095fc8daebec8acf8936418d8f9f5eed5d2a9..8dcc97cfa88309042f074a1d9b6c3560c81dfa77 100644 (file)
@@ -32,7 +32,36 @@ 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.
 
 
 Performance, Internal Implementation, Development Support etc.
@@ -64,6 +93,27 @@ 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.
+
 
 Fixes since v2.43
 -----------------
@@ -154,6 +204,27 @@ 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).
+
  * 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 +249,17 @@ 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).
index bce7f97815cb52a5ebbba4289b4b5013929b99ee..e734a3f0f175795f0633e93c07afd13f9183efc8 100644 (file)
@@ -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
index bccaec7a963679f8262d7c8d056fa4429b9586d1..66db0e15da7db82819de941626b7abc5983862c5 100644 (file)
@@ -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
index aea5b97477b64ef664b6b35faad78b2eb618db9a..d7dc461bd16ad782933a40e060c523b063cb1aca 100644 (file)
@@ -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
index f50df9dbce89891f0c6d4a5563c5ab73be4325df..9c630863e6ff18372172111619d4ad2a54f3264f 100644 (file)
@@ -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
index d59576dbb23f8d54638870e006c78ab16a283790..c6187ab28b2fb731fad341c7d3c193872558645d 100644 (file)
@@ -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
index a1d6633a4f15b719a33615ca0ce2805ec2fa0b3e..54ebb4452e997f8c62c9dd2421948086249102bd 100644 (file)
@@ -1,5 +1,6 @@
---all::
-       Fetch all remotes.
+--[no-]all::
+       Fetch all remotes. This overrides the configuration variable
+       `fetch.all`.
 
 -a::
 --append::
index aa02e462244ee0ad2836412cb2351fc43e9c43ad..8e01f1d6189d523fc4c5a196c64349117ea389fe 100644 (file)
@@ -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
index 4395aa935438aaac47cfe41f5db7a1eadafe8a25..0b08442932354fcdcbb321f0c45b80a3a5bc4fdf 100644 (file)
@@ -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
index c37c4a37f7412c0b712d14067c2d7c7d975933c5..6e43eb9c205371548655c5abae5e59bb963c959a 100644 (file)
@@ -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.
index 6f0d2973bf444a510ae4989dee9aa1e360eee247..e8dc645bb59a8664865c9eed00698ee4374ea4a3 100644 (file)
@@ -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
index 1dd6555f66b5543aa851a1b73d6ca5ec86a48d93..06206521fc322b04d6c874193d215e60d2368836 100644 (file)
@@ -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"
index 912fab9f5e00b6e286b1d80a8164d76224408682..546faf9017723581ea8e13bea6f5f90b00372403 100644 (file)
@@ -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
 ~~~~~~~~~~~~~
index bf9e6af6954759c2254b3533a286ffc0c2b82388..da4e8d1303a07c85964acfbd564ed4c521140cbb 100644 (file)
@@ -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.
index 8c1793c14880439da7103bc07f0f89a11137fc86..201bdf5edbd1badb08e73d32062b1c61ce38a4c3 100644 (file)
@@ -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
 -------
 
index 9fcb29a9c844cc2c2efb1cb1767bd8f146270352..d6ae229be5685950da9a8cff4bbe215c62e0c17c 100644 (file)
@@ -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
index f7d98c11e3dd0821094ae82cb488606a0de14732..d71b199955e15578c1392448cc5db0ce01240766 100644 (file)
@@ -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 (file)
index 0000000..1a65cac
--- /dev/null
@@ -0,0 +1 @@
+* `files` for loose files with packed-refs. This is the default.
index 2bf239ff0309aa0e45436c06788f49c094bfac8f..a583b52c612aece1f7d1fd24086c0f7961eecceb 100644 (file)
@@ -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.
index 045a76756fcf47401dc61cb55f37df1deff480d7..27be3741e6040ed9f9915f504793b4ad978d40ad 100644 (file)
@@ -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).
index 15990ff3122eb61676e4bc39477804e77d04367b..0f748a52e6b5b500a00ced3b9d9e6617ddd8293c 100644 (file)
--- 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)
 
index 2c3de2f9c803798e9eb3bc31184b3d86ba69d60b..665ce5f5a83647619fba9157fa9b0141ae8b228b 100644 (file)
--- 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/attr.c b/attr.c
index e62876dfd3e9beae50d18da63f15285d5974b8ec..679e42258c2b205bfceb9d065295c47c375442d4 100644 (file)
--- 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;
        }
 }
index 534594f7f8006d11ebb9b8d10712b6fc6191a043..6719a181bd1f03af21b92d8be71a93142ef700e7 100644 (file)
--- 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)
index 0605fa79aa2fd53433f633b8b8e592098766a43f..bad1b70ce8255156cf4745aca5e120371d39642d 100644 (file)
@@ -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
index 65196a28278bd5afb1bbc1e68864376225868abe..6d1fa71676f735b7ef1c3ab7bd56b767348ed992 100644 (file)
@@ -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);
index 11a4d4ef1411222f3750c760b68efd10180148c0..08fe36d4997d896b1378d25bd899d32af3679f64 100644 (file)
@@ -708,30 +708,11 @@ 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)
-                       /*
-                        * It is unknown if HOME/.gitconfig exists, so
-                        * we do not know if we should write to XDG
-                        * location; error out even if XDG_CONFIG_HOME
-                        * is set and points at a sane location.
-                        */
+               given_config_source.file = git_global_config();
+               if (!given_config_source.file)
                        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 +741,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
index 119f1a72ac6605b65937af3b7cbc651b4c695c0d..3aedfd1bb6361c6bbfd651970f9e9767d0595734 100644 (file)
@@ -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 */
index 7c11d5ebef052a46cd06b411960fb1c2b3d6ac08..cb80ced6cb5c65d70859a3a6f57cff3d886084b3 100644 (file)
@@ -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))
index 0841b6940a3ddf5c71beba29d092881b3fd8b044..1ea87e01f2905ed70e3a17a847278ec2b7b68ec3 100644 (file)
@@ -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();
index b89814a6f87da30fc1721ecef02637f61600cd32..0170469b849e5e5ad52cf8586f4540088b0edd61 100644 (file)
@@ -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);
 }
index ebbe05033e98bc796ceeb6c70e6259b8c76c18f2..8f819781cc34a11dc2c9e3d9b56d0df39f57b9a4 100644 (file)
@@ -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)
index 5c8bfe1035feca3518d2ab6b2515c03adf9d75eb..d8c2128a97928229e93212461027b700d219ec6a 100644 (file)
@@ -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);
 
index 995818c28d2604d8bc3435e4947f90d581a87d59..5b086f651a6fce7d97132da8ff36c80f00648db1 100644 (file)
@@ -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
index 917f12244059a67b74b43c80ec59b8503b61700f..d08987646a0a533ad81480ae4667b577a335f9f7 100644 (file)
@@ -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))
index b7183be9709fd40a54162685825166945935e522..3df9eaad092babdbbfc47f1c5441fc43a4ccd182 100644 (file)
@@ -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;
index aaa2c39b2f636c6a031dc98f237c83d59b754f31..79955c2856eacea0398a7fd9e90f34c790bbd8cd 100644 (file)
@@ -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 {
index 8cf7dd9e2e5cef1d9238c22ed64ea07440d31528..cf5567208a277a62b872a1846919060fb6e276c3 100644 (file)
@@ -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);
index cac83a94197dd60054d5266de8cc1b5186ea2b35..6d7da11746a25ade659a208db13fe8c0bcd4e36c 100644 (file)
@@ -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,20 +495,32 @@ 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;
 }
 
index 4f407530d3057552177883ad6b088f0a65b37b9a..b4e22de3cb9c351098cc57105c12e6242d1a4384 100755 (executable)
@@ -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
index 48c43f0f90774e65fcc8b388c6a52444fde50506..eb2c9e1eca73d3beb19ad0d20087f50af13fc62a 100755 (executable)
@@ -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 &&
index c749b21366b95092b144611dd9182b30bf3cb4a8..d5dd2f269762a239704bf2795c3e3919790ba6a2 100755 (executable)
--- 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
 
index c33ad4e3a2220a97f6e0708afe915010f4f44561..b1f80aeac345dd70746b02b6ca1b5282a0c2a4aa 100755 (executable)
@@ -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 (executable)
index 0000000..8ba486f
--- /dev/null
@@ -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
index bba316913cc9c270e061092000c605014dc97904..45417d7412202e54d21de0ae49941536eb2c82b4 100644 (file)
@@ -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);
 
index 42053c1f656bd8b73696b751f30177ce9e544d59..238a84ddbaa1b4c7563f6950eca469949d77fa68 100644 (file)
@@ -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);
                }
        }
 
index 9ff6ae1cb903a0690295a7d5bb452ef5f35c12e1..3cfeb3d8bd99f4ca15d0f3a06cd4b1fe932f7f47 100644 (file)
--- 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,
index 14f881ecfaf6b264b6d120205ab509f2ee5930f2..5dba984f770e4e96d322874351a70d0f7d0ee8ba 100644 (file)
--- 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);
 
index 3bb03f423a08102f837c52dcbe7a3b581104e0cb..dacc95172dcdcbd85086d9dacf57d2be588c90e1 100644 (file)
@@ -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
index 8c40ade49411bb883799d79983ad33a696651aa8..6662db221df9d5088566a283ca7d784548920ace 100644 (file)
@@ -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
@@ -2966,7 +2967,6 @@ _git_reset ()
 
 _git_restore ()
 {
-       __git_find_repo_path
        case "$prev" in
        -s)
                __git_complete_refs
@@ -2995,7 +2995,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
index 2c030050aea1c67472570a9d86d69d55e31e7714..71f179cba3fbda3bc93de461649cf75bb08c6653 100644 (file)
@@ -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
index 4927ab8fb0c91554b849e3046e424cccc7eff325..ba6cbee76ba0180db352d4baf0653af201304046 100644 (file)
@@ -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;
 }
index 7b525b1ecd896e02dfa71cdf3aab75496d998fac..ac7e0af622a8fc74b678b57f464698d1bc9a45e7 100644 (file)
@@ -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;
index 7eb8b9b63013daa70c91687108307d38a9eb1e09..c11d76c6f336930e0ea540a5b40c594bdb9f5be2 100644 (file)
@@ -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/fsck.c b/fsck.c
index 1ad02fcdfabea74dc7f3d1c214400516d327a421..8ded0a473a47fb45c223d8736f58c599b66cbb93 100644 (file)
--- 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;
index 603c97e3b3f096adda2893af0dc2e5ab96af9f46..7c2a6538e5afea607f3d9a1c09cc6aea5539d8de 100644 (file)
@@ -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)
index 0eb3bb4c47dcf12a9f1d98e1b76a3681197ac52e..3ea1c405e55ac9e0081f4b5c544dbc97d04fe507 100755 (executable)
--- a/git-p4.py
+++ b/git-p4.py
@@ -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".
index fc6d5dd522bf4f4c9869e4e14ea5a07409155784..ccd14e0e30c14ef1da31d09cf6949e70b75cf5e0 100755 (executable)
@@ -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;
        }
index b4d0b2a6aa381f4edb1bd890de4a4be1d10ab8fb..12d111374107a7a071ac90d035c5172af2b63c79 100644 (file)
@@ -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 "
index 77ba7f3020c8c112bf2e6bff9dae78d4d0ca847b..d72fd04f581fbb99732d1e5c7b48a1d4ae83efc0 100644 (file)
@@ -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"
@@ -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 1d14661dade4a686b1b18c351c55275b2a388ee3..85e1c2cd1287b34e91d9dcccc74d330c93ff809c 100644 (file)
--- 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 eb57a37519ce018fdd7aa9ba0d9d6c11003d5452..b374a7afafb867a9ef9f0fd9913ec2552914859c 100644 (file)
--- 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 (file)
index 0000000..e69de29
diff --git a/oss-fuzz/dummy-cmd-main.c b/oss-fuzz/dummy-cmd-main.c
new file mode 100644 (file)
index 0000000..071cb23
--- /dev/null
@@ -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;
+}
index be4733e3bdcff59c135569d696b72e1451e53898..990a9498d731942bf2b01580a8ac77d08ba04d5f 100644 (file)
@@ -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;
 }
index 0260890341b5a36af3ee1ec248282e2f1f1ebc53..229a11fb00fc20257385aca4cd653c26f8ce6b31 100644 (file)
@@ -338,7 +338,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 +393,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 +1285,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 +1841,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 +1852,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(packpos);
-       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 +1873,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 +1924,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)
+static void reuse_partial_packfile_from_bitmap_1(struct bitmap_index *bitmap_git,
+                                                struct bitmapped_pack *pack,
+                                                struct bitmap *reuse)
 {
-       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)
-{
-       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);
 
-       reuse = bitmap_word_alloc(i);
-       memset(reuse->words, 0xFF, i * sizeof(eword_t));
+                       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;
 
-       for (; i < result->word_alloc; ++i) {
-               eword_t word = result->words[i];
-               size_t pos = (i * BITS_IN_EWORD);
+                       if (bitmap_is_midx(bitmap_git)) {
+                               uint32_t midx_pos;
+                               off_t ofs;
 
-               for (offset = 0; offset < BITS_IN_EWORD; ++offset) {
-                       if ((word >> offset) == 0)
-                               break;
+                               midx_pos = pack_pos_to_midx(bitmap_git->midx, bit_pos);
+                               ofs = nth_midxed_offset(bitmap_git->midx, midx_pos);
 
-                       offset += ewah_bit_ctz64(word >> offset);
-                       if (try_partial_reuse(pack, pos + offset,
-                                             reuse, &w_curs) < 0) {
+                               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);
+                       }
+
+                       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 +2023,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 +2121,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,
index 5273a6a019708c8295be8e4fa5ca11db342f3a40..c7dea13217a00ef544d16a45d0f78de8f0e8ebc0 100644 (file)
@@ -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 *);
index f403ca6986a9d4c68715c1e1b24462d462fb8aa9..a9d9855063aea85f4b1b8f70f301c644c5d5e225 100644 (file)
@@ -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)
 {
index 0d78db40cb2f11fcbc4b3c2bbb2d23624ef3f22e..b9898a4e64b8b4d53b21ea776c16f79d9794efef 100644 (file)
@@ -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)
index acf1dd9786cd3c2a0cb410804721488b6d1d92b4..a7624d8be8e58e0807cf0b2dc5e3bae52ac53af9 100644 (file)
@@ -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);
+}
index 6dd47efea10ec69a3438f0a804a71bf13cd91c37..422c2487ae32d8a4500f4a151ca673364fed96a0 100644 (file)
@@ -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
index 4ce2b7ca1689eeb374fa2e122e1cd2436421d405..63a99dea6ef06b19bbb679a3ddd76087abc2a028 100644 (file)
@@ -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 67e2690efef897b406d46eab6202b52fc65a55d0..0fb527918b77652dbba2e6b0bee651fcc0fddfe9 100644 (file)
--- 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 639372edd9ee36127e3b89084f89ee59d12ff60f..b3233c51fa0f1acdf87368db4a7d9c3f0955c964 100644 (file)
--- 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);
 
index 236dd3a3ee1522331595959e5b68b4f00c9c6275..24479eae4dbe2a44b15a10f86b15bfc6f865bb34 100644 (file)
@@ -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;
        }
 
index aedef56286f4dde8968af3e4a9a33f05609f4e5c..3b33cc64f34dcc3447f6a6f42f12451dbc1a77a9 100644 (file)
@@ -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 2f58a3460af27372454317096c795ebc8d4c79a2..c633abf2847cf1b05eec9c9b4d0c817cf78618b6 100644 (file)
--- a/refs.c
+++ b/refs.c
 /*
  * 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 ff113bb12a2512c8f2caf9f0bd8e1e471490a5c0..303c5fac4d08b9798fafb6d4c667e9a94bf91e72 100644 (file)
--- 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
index 83b7a0ba655a50e83430da8ad3d80885cc68bd99..634681ca44e39b86129b2bbd305aa4c1771c3328 100644 (file)
@@ -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,
index 6734f2a309ebad096e1c4df994f17d9af6c10445..75dcc21ecb5ab83cadd37a899a252618a0c66df0 100644 (file)
@@ -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,
index e46906e612285a9412099e703a3715ca882eba75..a499a91c7e0ac94e45a7c28264647802522b65c4 100644 (file)
@@ -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,
index 4af83bf9a5e4d400939dfb61af28995aef33d6cc..82219829b011d12fcb4a27577baa41225ff812f7 100644 (file)
@@ -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;
index c00bbc8aed50f02d21ed06c046bed3c5d4c2f17f..dedb05c7d8c0560898b03aba93043c7efa46aeea 100644 (file)
@@ -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);
index a1ea3044292ef2032485bad261ffa5ed178421bc..8c41e3c70f36aa0aab704fd8d296825f6e342a3d 100644 (file)
@@ -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;
index 574394092d1c2241e794cda58564382a1e8b42dc..c258ce953e81d7df5774a03250b056280beccbf3 100644 (file)
@@ -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;
 }
 
index 0d6e0d4bf572f46683a1a9a3c6e1c128ed51f928..46908f738f770f4be920a65df85b96178ddbc70d 100644 (file)
@@ -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 },
                },
        };
 
index 79cd4e4c28abc4dbbad43f219b11fe4c0b28e5c3..b8a32240164d6a5c29789fb1730a630127f085d5 100644 (file)
@@ -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);
@@ -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);
                }
        }
@@ -674,11 +667,10 @@ 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};
        struct reftable_ref_record ref = {
                .update_index = 1,
                .value_type = REFTABLE_REF_VAL1,
-               .value.val1 = hash,
+               .value.val1 = {42},
        };
        int err;
        int i;
@@ -710,11 +702,10 @@ 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};
        struct reftable_ref_record ref = {
                .update_index = 1,
                .value_type = REFTABLE_REF_VAL1,
-               .value.val1 = hash,
+               .value.val1 = {42},
        };
        int err;
        int i;
@@ -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, &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;
 }
index fbaa1fbef56c52fed8391c7bcef2efd035eb588b..5c3fbb7b2a1e95ad418066df0aa96235116f5329 100644 (file)
@@ -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);
index 70ae78feca4f11cdb50f4fdcb08d90118bf487df..2876db7d2708aa36d861181775de74c966862c2d 100644 (file)
@@ -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:
index 67104f8fbfecd7984127d29dd1ae3fa9bb263394..bb6e99acd3151ec75a7a2dc60e930064b7322e60 100644 (file)
@@ -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);
index 16bab82063d2b3e228abe05b3a8a26ffd310767c..bf3869ce70dc7af837d29aabb985ee71e3e0c757 100644 (file)
@@ -66,6 +66,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 +176,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 +311,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;
 
-               /* Only look at deadlines after the first few times. This
-                  simplifies debugging in GDB */
+               err = gettimeofday(&now, NULL);
+               if (err < 0)
+                       goto out;
+
+               /*
+                * 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 +447,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 +533,6 @@ int reftable_stack_add(struct reftable_stack *st,
                return err;
        }
 
-       if (!st->disable_auto_compact)
-               return reftable_stack_auto_compact(st);
-
        return 0;
 }
 
@@ -562,7 +667,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;
 
@@ -801,18 +906,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 +930,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 +947,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++;
        }
 
index f57005846e561272054883e856d7a1dd14863a19..c1e3efa89960b124c8560662d5dd97257964a908 100644 (file)
@@ -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;
 
index 82280c2fd586e2464561765575127051dd77c15b..289e902146470035e4dd0109af6b8436e97d40d4 100644 (file)
@@ -462,7 +462,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);
@@ -599,7 +598,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);
                }
 
index 2e322a5683d081eea60fc85dfc4d792ce89b7a93..ee4590e20f84dd442fde4f7506e27e568f9ad509 100644 (file)
@@ -432,12 +432,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;
index a7679ceeaa45ce7a20094f9a29a82987290ad354..7aacb51b65cca69ec6acd0c879dd0aa5b15978b3 100644 (file)
@@ -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);
 }
index 5f18486f6465c44b44abb2ded74bcdda14ba7bd9..7a250a6605cc8e5a3ae661584122bf1f90f16747 100644 (file)
@@ -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);
index 3cc88d8a8004d91eb2a404214f4ac136a845d61f..91de546b323e30022e0428628b594ac9174f2ae5 100644 (file)
@@ -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());
index 913a0f652d9ab356bc066f162fa1e5197f6900eb..dcef7bb99c08b0f8e5a29905f8f3a4d6d2d34a45 100644 (file)
@@ -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 0161ce747dfd041a67494433b25fee1e5d0ab9f8..f4ad53d5454847951f2c55d02e9418484fdadac2 100644 (file)
--- 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;
 }
@@ -1565,6 +1576,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 +1671,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 +1881,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 +1902,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 +1920,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 +2032,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 +2145,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 +2210,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 3f0f17c351cbc54348ef1abe6b6e74a54d98ec56..3599aec93c5ac0b72aafaf3cdcb030831cc53e3b 100644 (file)
--- 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!!
index 6cbfd391c47fb531740eb4b963a11b77f8abe29d..266a67342be7245ae3c1dfae5a1a5ad6647f95df 100644 (file)
@@ -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)
 {
index 9f55c8766ba9de77437275b8796f8e348fe35e03..4715d3e51f873e7b6618a80ebc79f2d2b4ebaab7 100644 (file)
--- 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
index f4dd482abc95e7c1668623be7c24ec163745e189..54130f6a38572b613d4b7ee8ae1cf3bc6035055d 100644 (file)
@@ -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)
 {
index 958f320ac6ce08a0b0ac856415a173d5fa7c2600..b6133af71b00d61abdafc3aa736fe4f4018695cf 100644 (file)
@@ -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
index 36463d074259e3805e6de6d41d30bc3632b369f5..621d3b8c095441a8a8985b7f12363e26f8ab4d98 100644 (file)
--- 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 (file)
index e5659df..0000000
+++ /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;
-}
index 77e99c37df0cc00579083b53760accbd0888ffd1..4daa82f00fcb5f0b178aa912033c906fe14c7718 100644 (file)
@@ -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"))
index e9a444ddba55b49c9c47ceb3b27a31d3e022a8dc..4acae41bb993c883970227f394a61603a6e7152d 100644 (file)
@@ -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);
 }
index 50c154d0370bfbdd91b70747133977a752f2b504..7197969a08149699a14c758ceef1223e494b7c35 100644 (file)
@@ -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 },
index 37ba996539b72f66316722cc73094d44aef6e61c..33b9501c2112a758c1f662890d31fcbce93b854f 100644 (file)
@@ -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 },
index 8a1a7c63daa4dabf1b92d35420f2b2f5c36b136c..b72f07ded9946b207b0c20da9886d7842cc5ac5c 100644 (file)
@@ -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 (executable)
index 0000000..5c6c575
--- /dev/null
@@ -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
index 2b78e3be4795096aa3d9eeb5f7b5c3dc3a24f717..b131d665db40e9858d4a3f9c13ab71eb77d6cc5f 100755 (executable)
@@ -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 &&
index aee2298f01331acdf3d73c1657f1a29ed1c8616b..774b52c2980ca10e6183cf74ad10427f00c2514b 100755 (executable)
@@ -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
index a34de5642073d1323b37b7c82f56edfe798fcfdd..a7f4de4a43ffa022ae56ebf14999cbe81900a4a9 100755 (executable)
@@ -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
 
index 487bc8d9054c908f97bc376005c3fd1eedd107b9..0ecec2ba71116959fd2efceac7fc46683d1a3e91 100755 (executable)
@@ -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
index 271c5e4fd38cb367b302de00ae49cfdf454d2ae6..e0c6482797e1203a9e97ec27714d5ea48aaf1b6c 100755 (executable)
@@ -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 &&
index 3241d3591799415e423e4f6155e4111de778a964..5c60d6f812dc20c1c86f5125b5feabb4ce1df9ce 100755 (executable)
@@ -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' '
index ec1957b709e0863f62912caafe96ebddf6a53d5b..d0a8f7b121cd874562fbf0759670520f4b275b30 100755 (executable)
@@ -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
 '
 
index 3f9e7f62e458c4db06a686700a15c5e08f47fad9..a669e592f1d95ca7e0dc575697342326a7a6654f 100755 (executable)
@@ -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 &&
index 3742971105c15268a3c8c41642ff98a75be4f1bf..c28c04133c8a1c953406c5bdea0e83c8cb82870f 100755 (executable)
@@ -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
        )
index 6a316f081ee2267ebd0c545220b4de3e6e527101..de7d3014e4fb914012db343d9efd85a2818677b4 100755 (executable)
@@ -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
 '
index 85be1367de6a3f73e1162ffc03563ec7b9a9eb5a..49c042a38ae987fa95f201bd3a00f55e4adeffab 100755 (executable)
@@ -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
index cb094241ec899f53a4ec65f37f0c4227281722d7..1e3b2dbea48488ecb3a68a8dba82d57b6fbca2a7 100755 (executable)
@@ -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
index e7a7295f1b687d1ed916e80e431e78c4c67657b7..2775bfadd8619b2a5e2440209c46c0658205ddcd 100755 (executable)
@@ -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' '
index c20aafe99a848414397c6c1974c2663775ce8f35..dd09134db03638fd01e73d89acbac45cceaa6e46 100755 (executable)
@@ -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 (executable)
index 0000000..2ba788b
--- /dev/null
@@ -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
index 79592a3b0addcbee8015ee0ddf843c9ccedf6935..33d34d5ae9e953e29f6dba8badb16922072cd76c 100755 (executable)
@@ -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
        )
 '
 
index a95841dc368329286a798982ffac30b7321e02df..25772c85c5a9a89bf247cfa41cf9d8b127572cd8 100755 (executable)
@@ -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
index df758e187dfdc2fc869ad5cb747d895a145d5e91..71428f3d5c760a9bdca7524e15cb9747e92cfd17 100755 (executable)
@@ -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 &&
index 47eae641f018055f2200ef6e2581d8ac60364cdb..fb1b9c686db24a30f9dbd2f6f38f29bfc4624676 100755 (executable)
@@ -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/ &&
index 86c70521f1db7184e70c1eb6baf17883ecc6609f..459f0d741225522276b84a9a65caaa2e2a985bde 100755 (executable)
@@ -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 &&
index 00b84ecba660a1efb25d60fa82365f617d842387..120dcd74a51434303b8bd7e71d10a9cb120bf574 100755 (executable)
@@ -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
index 35a31acd4d7e26bc3bf8f42853d80593488c9227..46d4fb0354b13d585260587d78d27d1e9c545f1c 100755 (executable)
@@ -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
index 3d8500a52e50c004544c6df62cf123b8aeb62a99..bced44a0fc915f430ccc41d54fbd8bc48df2cef8 100755 (executable)
@@ -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
index 78503158fd699d2df75e8fbbbaf164a129f2578b..363f9dc0e41b2686aa9892d5e21e153ca54727b9 100755 (executable)
@@ -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
index 00d29871e659ccb34ef7204017ab07dbefa83c84..0943dfa18a3f97b7e351346d089efdd878f6ca68 100755 (executable)
@@ -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 &&
index 0333065d4d60a0113cb5ac1de391a4a997d2eaa8..7679780fb87b4f14e9c0f5a22708b1e8925a4d53 100755 (executable)
@@ -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
 '
 
index aa9a614de33a17f886b7517daeabad5f48bdc42c..35eb534fdda2735786faf1fa62bb399ddcc52f27 100755 (executable)
@@ -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
index 5eb57914abc493b6cb261b676b27d935f1b919ae..b5eaf7fdc1186d4420388c355e794bfdba783f2f 100644 (file)
@@ -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() {
index 970c6538cba7a8d9465b6774b23e367c4b257d88..33405c90d740d4c4e4fac93cfa8a713939a39348 100644 (file)
@@ -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
index 876b99562a323ee351d4afe0fe2afbca34fb46bc..fc93aa57e6116d5602b17a63f41bd4b8746289e2 100644 (file)
@@ -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 (file)
index 0000000..f315489
--- /dev/null
@@ -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();
+}
index bd7899e9bf5550b2b333dbbcf85a5b98c7813ad2..df518ead70c387dd354dd69a5ee4af8a3ab0d560 100644 (file)
@@ -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:
index 1399d452accaa3d0bdadd759c95fa7fd704f0a95..b02a05a74a341157fa8dff7da22936127bebf18e 100644 (file)
 #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)
index ce45b66de9e82d4d619b6f74e0e77c8bd5ab3f53..f14784a2ff871c69e5b5e28466439201721407b2 100644 (file)
@@ -57,6 +57,13 @@ struct worktree *find_worktree(struct worktree **list,
                               const char *prefix,
                               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.
@@ -134,6 +141,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)
  */
index 42a2dc73cd3f18638445780e7fa9aa9b67ba871c..39421528653e29cbd724153edf94d2613ea1d6b4 100644 (file)
 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;