]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'cp/unit-test-prio-queue'
authorJunio C Hamano <gitster@pobox.com>
Thu, 8 Feb 2024 21:20:33 +0000 (13:20 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 8 Feb 2024 21:20:33 +0000 (13:20 -0800)
The priority queue test has been migrated to the unit testing
framework.

* cp/unit-test-prio-queue:
  tests: move t0009-prio-queue.sh to the new unit testing framework

145 files changed:
.cirrus.yml
.github/workflows/main.yml
.gitlab-ci.yml
CODE_OF_CONDUCT.md
Documentation/MyFirstContribution.txt
Documentation/RelNotes/2.44.0.txt
Documentation/config/advice.txt
Documentation/config/fetch.txt
Documentation/fetch-options.txt
Documentation/git-bisect.txt
Documentation/git-branch.txt
Documentation/git-ls-files.txt
Documentation/git-rebase.txt
Documentation/gitattributes.txt
Documentation/glossary-content.txt
Documentation/rev-list-options.txt
Makefile
README.md
advice.c
advice.h
branch.c
builtin/branch.c
builtin/commit.c
builtin/config.c
builtin/fetch.c
builtin/gc.c
builtin/merge.c
builtin/rebase.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
contrib/subtree/git-subtree.sh
contrib/subtree/t/t7900-subtree.sh
diff.c
diffcore-delta.c
fetch-pack.c
fsck.c
git-p4.py
gitweb/gitweb.perl
http-backend.c
http-push.c
merge-ll.c
merge-ort.c
oss-fuzz/dummy-cmd-main.c [new file with mode: 0644]
pack-bitmap.c
parse-options.c
path.c
path.h
refs.c
refs.h
refs/debug.c
refs/files-backend.c
refs/packed-backend.c
refs/refs-internal.h
reftable/blocksource.c
reftable/merged_test.c
reftable/pq_test.c
reftable/readwrite_test.c
reftable/refname_test.c
reftable/reftable-writer.h
reftable/stack.c
reftable/stack.h
reftable/stack_test.c
reftable/test_framework.c
reftable/test_framework.h
reftable/writer.c
reftable/writer.h
remote-curl.c
repository.c
repository.h
sequencer.c
sequencer.h
setup.c
shared.mak
strvec.h
submodule-config.c
submodule-config.h
t/helper/test-ctype.c [deleted file]
t/helper/test-submodule.c
t/helper/test-tool.c
t/helper/test-tool.h
t/t0018-advice.sh
t/t0024-crlf-archive.sh
t/t0035-safe-bare-repository.sh
t/t0070-fundamental.sh
t/t0080-unit-test-output.sh
t/t0091-bugreport.sh
t/t0600-reffiles-backend.sh [new file with mode: 0755]
t/t0601-reffiles-pack-refs.sh [moved from t/t3210-pack-refs.sh with 81% similarity]
t/t1300-config.sh
t/t1301-shared-repo.sh
t/t1302-repo-version.sh
t/t1401-symbolic-ref.sh
t/t1403-show-ref.sh
t/t1404-update-ref-errors.sh
t/t1405-main-ref-store.sh
t/t1407-worktree-ref-store.sh
t/t1409-avoid-packing-refs.sh
t/t1410-reflog.sh
t/t1414-reflog-walk.sh
t/t1415-worktree-refs.sh
t/t1419-exclude-refs.sh
t/t1503-rev-parse-verify.sh
t/t2017-checkout-orphan.sh
t/t2400-worktree-add.sh
t/t3903-stash.sh
t/t4001-diff-rename.sh
t/t4013-diff-various.sh
t/t4053-diff-no-index.sh
t/t4129-apply-samemode.sh
t/t4202-log.sh
t/t5003-archive-zip.sh
t/t5312-prune-corruption.sh
t/t5332-multi-pack-reuse.sh
t/t5514-fetch-multiple.sh
t/t5526-fetch-submodules.sh
t/t5541-http-push-smart.sh
t/t5551-http-fetch-smart.sh
t/t6113-rev-list-bitmap-filters.sh
t/t6406-merge-attr.sh
t/t7450-bad-git-dotfiles.sh
t/t7501-commit-basic-functionality.sh
t/t7527-builtin-fsmonitor.sh
t/t7900-maintenance.sh
t/t9902-completion.sh
t/test-lib-github-workflow-markup.sh
t/test-lib.sh
t/unit-tests/t-ctype.c [new file with mode: 0644]
transport-helper.c
transport.c
worktree.c
worktree.h

index b6280692d2f21aaa17548985979279594c94323e..77346a4929d4eb6af398cbf0761016d8c6d34347 100644 (file)
@@ -1,7 +1,7 @@
 env:
   CIRRUS_CLONE_DEPTH: 1
 
-freebsd_12_task:
+freebsd_task:
   env:
     GIT_PROVE_OPTS: "--timer --jobs 10"
     GIT_TEST_OPTS: "--no-chain-lint --no-bin-wrappers"
@@ -9,7 +9,7 @@ freebsd_12_task:
     DEFAULT_TEST_TARGET: prove
     DEVELOPER: 1
   freebsd_instance:
-    image_family: freebsd-12-3
+    image_family: freebsd-13-2
     memory: 2G
   install_script:
     pkg install -y gettext gmake perl5
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 0215b1fd4c05e668f37973549384aae24dcc65cf..e58917c50a96dc9457a983bbf2b46ed00f2f3e90 100644 (file)
@@ -130,11 +130,11 @@ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
 version 2.0, available at
 [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
 
-Community Impact Guidelines were inspired by 
+Community Impact Guidelines were inspired by
 [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
 
 For answers to common questions about this code of conduct, see the FAQ at
-[https://www.contributor-covenant.org/faq][FAQ]. Translations are available 
+[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
 at [https://www.contributor-covenant.org/translations][translations].
 
 [homepage]: https://www.contributor-covenant.org
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 2b9d2bd3c260be8e114fc751cf5215a2e7269a88..8615306eed400afa395db4dac07d2febb1bdf3a7 100644 (file)
@@ -38,6 +38,52 @@ UI, Workflows & Features
    useful in magic pathspec, e.g., ":(attr:builtin_objectmode=100755)"
    to limit to executables.
 
+ * "git fetch" learned to pay attention to "fetch.all" configuration
+   variable, which pretends as if "--all" was passed from the command
+   line when no remote parameter was given.
+
+ * In addition to (rather cryptic) Security Identifiers, show username
+   and domain in the error message when we barf on mismatch between
+   the Git directory and the current user on Windows.
+
+ * The error message given when "git branch -d branch" fails due to
+   commits unique to the branch has been split into an error and a new
+   conditional advice message.
+
+ * When given an existing but unreadable file as a configuration file,
+   gitweb behaved as if the file did not exist at all, but now it
+   errors out.  This is a change that may break backward compatibility.
+
+ * When $HOME/.gitignore is missing but XDG config file available, we
+   should write into the latter, not former.  "git gc" and "git
+   maintenance" wrote into a wrong "global config" file, which have
+   been corrected.
+
+ * Define "special ref" as a very narrow set that consists of
+   FETCH_HEAD and MERGE_HEAD, and clarify everything else that used to
+   be classified as such are actually just pseudorefs.
+
+ * All conditional "advice" messages show how to turn them off, which
+   becomes repetitive.  Setting advice.* configuration explicitly on
+   now omits the instruction part.
+
+ * The "disable repository discovery of a bare repository" check,
+   triggered by setting safe.bareRepository configuration variable to
+   'explicit', has been loosened to exclude the ".git/" directory inside
+   a non-bare repository from the check.  So you can do "cd .git &&
+   git cmd" to run a Git command that works on a bare repository without
+   explicitly specifying $GIT_DIR now.
+
+ * The completion script (in contrib/) learned more options that can
+   be used with "git log".
+
+ * The labels on conflict markers for the common ancestor, our version,
+   and the other version are available to custom 3-way merge driver
+   via %S, %X, and %Y placeholders.
+
+ * The write codepath for the reftable data learned to honor
+   core.fsync configuration.
+
 
 Performance, Internal Implementation, Development Support etc.
 
@@ -72,6 +118,29 @@ Performance, Internal Implementation, Development Support etc.
    single, primary, pack in a repository with multiple packfiles.  It
    has been extended to allow reuse from other packfiles, too.
 
+ * Comment updates to help developers not to attempt to modify
+   messages from plumbing commands that must stay constant.
+
+   It might make sense to reassess the plumbing needs every few years,
+   but that should be done as a separate effort.
+
+ * Move test-ctype helper to the unit-test framework.
+
+ * Instead of manually creating refs/ hierarchy on disk upon a
+   creation of a secondary worktree, which is only usable via the
+   files backend, use the refs API to populate it.
+
+ * CI for GitLab learned to drive macOS jobs.
+
+ * A few tests to "git commit -o <pathspec>" and "git commit -i
+   <pathspec>" has been added.
+
+ * Tests on ref API are moved around to prepare for reftable.
+
+ * The Makefile often had to say "-L$(path) -R$(path)" that repeats
+   the path to the same library directory for link time and runtime.
+   A Makefile template is used to reduce such repetition.
+
 
 Fixes since v2.43
 -----------------
@@ -173,6 +242,48 @@ Fixes since v2.43
    data from commit-graph too early, which has been corrected.
    (merge d70f554cdf jk/commit-graph-slab-clear-fix later to maint).
 
+ * Update to a new feature recently added, "git show-ref --exists".
+   (merge 0aabeaa562 tc/show-ref-exists-fix later to maint).
+
+ * oss-fuzz tests are built and run in CI.
+   (merge c4a9cf1df3 js/oss-fuzz-build-in-ci later to maint).
+
+ * Rename detection logic ignored the final line of a file if it is an
+   incomplete line.
+   (merge 1c5bc6971e en/diffcore-delta-final-line-fix later to maint).
+
+ * GitHub CI update.
+   (merge 0188b2c8e0 pb/ci-github-skip-logs-for-broken-tests later to maint).
+
+ * "git diff --no-rename A B" did not disable rename detection but did
+   not trigger an error from the command line parser.
+   (merge 457f96252f rs/parse-options-with-keep-unknown-abbrev-fix later to maint).
+
+ * "git archive --remote=<remote>" learned to talk over the smart
+   http (aka stateless) transport.
+   (merge 176cd68634 jx/remote-archive-over-smart-http later to maint).
+
+ * Fetching via protocol v0 over Smart HTTP transport sometimes failed
+   to correctly auto-follow tags.
+   (merge fba732c462 jk/fetch-auto-tag-following-fix later to maint).
+
+ * The documentation for the --exclude-per-directory option marked it
+   as deprecated, which confused readers into thinking there may be a
+   plan to remove it in the future, which was not our intention.
+   (merge 0009542cab jc/ls-files-doc-update later to maint).
+
+ * "git diff --no-index file1 file2" segfaulted while invoking the
+   external diff driver, which has been corrected.
+   (merge 85a9a63c92 jk/diff-external-with-no-index later to maint).
+
+ * Rewrite //-comments to /* comments */ in files whose comments
+   prevalently use the latter.
+   (merge de65079d7b jc/comment-style-fixes later to maint).
+
+ * Cirrus CI jobs started breaking because we specified version of
+   FreeBSD that is no longer available, which has been corrected.
+   (merge 81fffb66d3 cb/use-freebsd-13-2-at-cirrus-ci 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).
@@ -204,3 +315,16 @@ Fixes since v2.43
    (merge 03bcc93769 cp/sideband-array-index-comment-fix later to maint).
    (merge 993d38a066 jk/index-pack-lsan-false-positive-fix later to maint).
    (merge 25aec06326 ib/rebase-reschedule-doc later to maint).
+   (merge 5aea3955bc rj/clarify-branch-doc-m later to maint).
+   (merge 9cce3be2df bk/bisect-doc-fix later to maint).
+   (merge 8f50984cf4 ne/doc-filter-blob-limit-fix later to maint).
+   (merge f10b0989b8 la/strvec-comment-fix later to maint).
+   (merge 8430b438f6 vd/fsck-submodule-url-test later to maint).
+   (merge f10031fadd nb/rebase-x-shell-docfix later to maint).
+   (merge af3d2c160f jc/majordomo-to-subspace later to maint).
+   (merge ee9895b0ff sd/negotiate-trace-fix later to maint).
+   (merge 976d0251ce jc/coc-whitespace-fix later to maint).
+   (merge 9023198280 jt/p4-spell-re-with-raw-string later to maint).
+   (merge 36c9c44fa4 tb/pack-bitmap-drop-unused-struct-member later to maint).
+   (merge 19ed0dff8f js/win32-retry-pipe-write-on-enospc later to maint).
+   (merge 3cb4384683 jc/t0091-with-unknown-git later to maint).
index 4d7e5d875998fe1c3aa20c0bb5efdcf9fb6fad91..c7ea70f2e2e9d281912567bfb0408200e40fe11b 100644 (file)
@@ -1,30 +1,63 @@
 advice.*::
        These variables control various optional help messages designed to
-       aid new users. All 'advice.*' variables default to 'true', and you
-       can tell Git that you do not need help by setting these to 'false':
+       aid new users.  When left unconfigured, Git will give the message
+       alongside instructions on how to squelch it.  You can tell Git
+       that you do not need the help message by setting these to 'false':
 +
 --
+       addEmbeddedRepo::
+               Advice on what to do when you've accidentally added one
+               git repo inside of another.
+       addEmptyPathspec::
+               Advice shown if a user runs the add command without providing
+               the pathspec parameter.
+       addIgnoredFile::
+               Advice shown if a user attempts to add an ignored file to
+               the index.
+       amWorkDir::
+               Advice that shows the location of the patch file when
+               linkgit:git-am[1] fails to apply it.
        ambiguousFetchRefspec::
                Advice shown when a fetch refspec for multiple remotes maps to
                the same remote-tracking branch namespace and causes branch
                tracking set-up to fail.
+       checkoutAmbiguousRemoteBranchName::
+               Advice shown when the argument to
+               linkgit:git-checkout[1] and linkgit:git-switch[1]
+               ambiguously resolves to a
+               remote tracking branch on more than one remote in
+               situations where an unambiguous argument would have
+               otherwise caused a remote-tracking branch to be
+               checked out. See the `checkout.defaultRemote`
+               configuration variable for how to set a given remote
+               to be used by default in some situations where this
+               advice would be printed.
+       commitBeforeMerge::
+               Advice shown when linkgit:git-merge[1] refuses to
+               merge to avoid overwriting local changes.
+       detachedHead::
+               Advice shown when you used
+               linkgit:git-switch[1] or linkgit:git-checkout[1]
+               to move to the detached HEAD state, to instruct how to
+               create a local branch after the fact.
+       diverging::
+               Advice shown when a fast-forward is not possible.
        fetchShowForcedUpdates::
                Advice shown when linkgit:git-fetch[1] takes a long time
                to calculate forced updates after ref updates, or to warn
                that the check is disabled.
-       pushUpdateRejected::
-               Set this variable to 'false' if you want to disable
-               'pushNonFFCurrent', 'pushNonFFMatching', 'pushAlreadyExists',
-               'pushFetchFirst', 'pushNeedsForce', and 'pushRefNeedsUpdate'
-               simultaneously.
-       pushNonFFCurrent::
-               Advice shown when linkgit:git-push[1] fails due to a
-               non-fast-forward update to the current branch.
-       pushNonFFMatching::
-               Advice shown when you ran linkgit:git-push[1] and pushed
-               'matching refs' explicitly (i.e. you used ':', or
-               specified a refspec that isn't your current branch) and
-               it resulted in a non-fast-forward error.
+       forceDeleteBranch::
+               Advice shown when a user tries to delete a not fully merged
+               branch without the force option set.
+       ignoredHook::
+               Advice shown if a hook is ignored because the hook is not
+               set as executable.
+       implicitIdentity::
+               Advice on how to set your identity configuration when
+               your information is guessed from the system username and
+               domain name.
+       nestedTag::
+               Advice shown if a user attempts to recursively tag a tag object.
        pushAlreadyExists::
                Shown when linkgit:git-push[1] rejects an update that
                does not qualify for fast-forwarding (e.g., a tag.)
@@ -37,6 +70,18 @@ advice.*::
                tries to overwrite a remote ref that points at an
                object that is not a commit-ish, or make the remote
                ref point at an object that is not a commit-ish.
+       pushNonFFCurrent::
+               Advice shown when linkgit:git-push[1] fails due to a
+               non-fast-forward update to the current branch.
+       pushNonFFMatching::
+               Advice shown when you ran linkgit:git-push[1] and pushed
+               'matching refs' explicitly (i.e. you used ':', or
+               specified a refspec that isn't your current branch) and
+               it resulted in a non-fast-forward error.
+       pushRefNeedsUpdate::
+               Shown when linkgit:git-push[1] rejects a forced update of
+               a branch when its remote-tracking ref has updates that we
+               do not have locally.
        pushUnqualifiedRefname::
                Shown when linkgit:git-push[1] gives up trying to
                guess based on the source and destination refs what
@@ -44,10 +89,23 @@ advice.*::
                we can still suggest that the user push to either
                refs/heads/* or refs/tags/* based on the type of the
                source object.
-       pushRefNeedsUpdate::
-               Shown when linkgit:git-push[1] rejects a forced update of
-               a branch when its remote-tracking ref has updates that we
-               do not have locally.
+       pushUpdateRejected::
+               Set this variable to 'false' if you want to disable
+               'pushNonFFCurrent', 'pushNonFFMatching', 'pushAlreadyExists',
+               'pushFetchFirst', 'pushNeedsForce', and 'pushRefNeedsUpdate'
+               simultaneously.
+       resetNoRefresh::
+               Advice to consider using the `--no-refresh` option to
+               linkgit:git-reset[1] when the command takes more than 2 seconds
+               to refresh the index after reset.
+       resolveConflict::
+               Advice shown by various commands when conflicts
+               prevent the operation from being performed.
+       rmHints::
+               In case of failure in the output of linkgit:git-rm[1],
+               show directions on how to proceed from the current state.
+       sequencerInUse::
+               Advice shown when a sequencer command is already in progress.
        skippedCherryPicks::
                Shown when linkgit:git-rebase[1] skips a commit that has already
                been cherry-picked onto the upstream branch.
@@ -68,76 +126,22 @@ advice.*::
                Advise to consider using the `-u` option to linkgit:git-status[1]
                when the command takes more than 2 seconds to enumerate untracked
                files.
-       commitBeforeMerge::
-               Advice shown when linkgit:git-merge[1] refuses to
-               merge to avoid overwriting local changes.
-       resetNoRefresh::
-               Advice to consider using the `--no-refresh` option to
-               linkgit:git-reset[1] when the command takes more than 2 seconds
-               to refresh the index after reset.
-       resolveConflict::
-               Advice shown by various commands when conflicts
-               prevent the operation from being performed.
-       sequencerInUse::
-               Advice shown when a sequencer command is already in progress.
-       implicitIdentity::
-               Advice on how to set your identity configuration when
-               your information is guessed from the system username and
-               domain name.
-       detachedHead::
-               Advice shown when you used
-               linkgit:git-switch[1] or linkgit:git-checkout[1]
-               to move to the detached HEAD state, to instruct how to
-               create a local branch after the fact.
-       suggestDetachingHead::
-               Advice shown when linkgit:git-switch[1] refuses to detach HEAD
-               without the explicit `--detach` option.
-       checkoutAmbiguousRemoteBranchName::
-               Advice shown when the argument to
-               linkgit:git-checkout[1] and linkgit:git-switch[1]
-               ambiguously resolves to a
-               remote tracking branch on more than one remote in
-               situations where an unambiguous argument would have
-               otherwise caused a remote-tracking branch to be
-               checked out. See the `checkout.defaultRemote`
-               configuration variable for how to set a given remote
-               to be used by default in some situations where this
-               advice would be printed.
-       amWorkDir::
-               Advice that shows the location of the patch file when
-               linkgit:git-am[1] fails to apply it.
-       rmHints::
-               In case of failure in the output of linkgit:git-rm[1],
-               show directions on how to proceed from the current state.
-       addEmbeddedRepo::
-               Advice on what to do when you've accidentally added one
-               git repo inside of another.
-       ignoredHook::
-               Advice shown if a hook is ignored because the hook is not
-               set as executable.
-       waitingForEditor::
-               Print a message to the terminal whenever Git is waiting for
-               editor input from the user.
-       nestedTag::
-               Advice shown if a user attempts to recursively tag a tag object.
        submoduleAlternateErrorStrategyDie::
                Advice shown when a submodule.alternateErrorStrategy option
                configured to "die" causes a fatal error.
        submodulesNotUpdated::
                Advice shown when a user runs a submodule command that fails
                because `git submodule update --init` was not run.
-       addIgnoredFile::
-               Advice shown if a user attempts to add an ignored file to
-               the index.
-       addEmptyPathspec::
-               Advice shown if a user runs the add command without providing
-               the pathspec parameter.
+       suggestDetachingHead::
+               Advice shown when linkgit:git-switch[1] refuses to detach HEAD
+               without the explicit `--detach` option.
        updateSparsePath::
                Advice shown when either linkgit:git-add[1] or linkgit:git-rm[1]
                is asked to update index entries outside the current sparse
                checkout.
-       diverging::
-               Advice shown when a fast-forward is not possible.
+       waitingForEditor::
+               Print a message to the terminal whenever Git is waiting for
+               editor input from the user.
        worktreeAddOrphan::
                Advice shown when a user tries to create a worktree from an
                invalid reference, to instruct how to create a new unborn
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 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 f65a8cd91d4bb40e986c3610b6f1c2b89c719ace..d08c7da8f495b3e19fb763535ff742585da9ce3e 100644 (file)
@@ -119,8 +119,10 @@ OPTIONS
 
 --exclude-per-directory=<file>::
        Read additional exclude patterns that apply only to the
-       directory and its subdirectories in <file>.  Deprecated; use
-       --exclude-standard instead.
+       directory and its subdirectories in <file>.  If you are
+       trying to emulate the way Porcelain commands work, using
+       the `--exclude-standard` option instead is easier and more
+       thorough.
 
 --exclude-standard::
        Add the standard Git exclusions: .git/info/exclude, .gitignore
@@ -298,9 +300,8 @@ traversing the directory tree and finding files to show when the
 flags --others or --ignored are specified.  linkgit:gitignore[5]
 specifies the format of exclude patterns.
 
-Generally, you should just use --exclude-standard, but for historical
-reasons the exclude patterns can be specified from the following
-places, in order:
+These exclude patterns can be specified from the following places,
+in order:
 
   1. The command-line flag --exclude=<pattern> specifies a
      single pattern.  Patterns are ordered in the same order
@@ -322,6 +323,18 @@ top of the directory tree.  A pattern read from a file specified
 by --exclude-per-directory is relative to the directory that the
 pattern file appears in.
 
+Generally, you should be able to use `--exclude-standard` when you
+want the exclude rules applied the same way as what Porcelain
+commands do.  To emulate what `--exclude-standard` specifies, you
+can give `--exclude-per-directory=.gitignore`, and then specify:
+
+  1. The file specified by the `core.excludesfile` configuration
+     variable, if exists, or the `$XDG_CONFIG_HOME/git/ignore` file.
+
+  2. The `$GIT_DIR/info/exclude` file.
+
+via the `--exclude-from=` option.
+
 SEE ALSO
 --------
 linkgit:git-read-tree[1], linkgit:gitignore[5]
index 8a8d32161b4455feead4bd43e251e43041a24dd0..06206521fc322b04d6c874193d215e60d2368836 100644 (file)
@@ -966,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 201bdf5edbd1badb08e73d32062b1c61ce38a4c3..4338d023d9a313edae0bae8b2ac8087a3d15a003 100644 (file)
@@ -1137,11 +1137,11 @@ The `merge.*.name` variable gives the driver a human-readable
 name.
 
 The `merge.*.driver` variable's value is used to construct a
-command to run to merge ancestor's version (`%O`), current
+command to run to common ancestor's version (`%O`), current
 version (`%A`) and the other branches' version (`%B`).  These
 three tokens are replaced with the names of temporary files that
 hold the contents of these versions when the command line is
-built. Additionally, %L will be replaced with the conflict marker
+built. Additionally, `%L` will be replaced with the conflict marker
 size (see below).
 
 The merge driver is expected to leave the result of the merge in
@@ -1159,8 +1159,9 @@ When left unspecified, the driver itself is used for both
 internal merge and the final merge.
 
 The merge driver can learn the pathname in which the merged result
-will be stored via placeholder `%P`.
-
+will be stored via placeholder `%P`. The conflict labels to be used
+for the common ancestor, local head and other head can be passed by
+using '%S', '%X' and '%Y` respectively.
 
 `conflict-marker-size`
 ^^^^^^^^^^^^^^^^^^^^^^
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
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 2d649ec186dad021d6d98322113933ada3b37f9c..3882deed5056f8ed0e944a2612e387d36c2096ff 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
@@ -1341,6 +1344,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_PROGRAMS += t-prio-queue
 UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
 UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
@@ -1586,7 +1590,7 @@ endif
 
 ifdef LIBPCREDIR
        BASIC_CFLAGS += -I$(LIBPCREDIR)/include
-       EXTLIBS += -L$(LIBPCREDIR)/$(lib) $(CC_LD_DYNPATH)$(LIBPCREDIR)/$(lib)
+       EXTLIBS += $(call libpath_template,$(LIBPCREDIR)/$(lib))
 endif
 
 ifdef HAVE_ALLOCA_H
@@ -1606,7 +1610,7 @@ else
        ifdef CURLDIR
                # Try "-Wl,-rpath=$(CURLDIR)/$(lib)" in such a case.
                CURL_CFLAGS = -I$(CURLDIR)/include
-               CURL_LIBCURL = -L$(CURLDIR)/$(lib) $(CC_LD_DYNPATH)$(CURLDIR)/$(lib)
+               CURL_LIBCURL = $(call libpath_template,$(CURLDIR)/$(lib))
        else
                CURL_CFLAGS =
                CURL_LIBCURL =
@@ -1642,7 +1646,7 @@ else
        ifndef NO_EXPAT
                ifdef EXPATDIR
                        BASIC_CFLAGS += -I$(EXPATDIR)/include
-                       EXPAT_LIBEXPAT = -L$(EXPATDIR)/$(lib) $(CC_LD_DYNPATH)$(EXPATDIR)/$(lib) -lexpat
+                       EXPAT_LIBEXPAT = $(call libpath_template,$(EXPATDIR)/$(lib)) -lexpat
                else
                        EXPAT_LIBEXPAT = -lexpat
                endif
@@ -1655,7 +1659,7 @@ IMAP_SEND_LDFLAGS += $(OPENSSL_LINK) $(OPENSSL_LIBSSL) $(LIB_4_CRYPTO)
 
 ifdef ZLIB_PATH
        BASIC_CFLAGS += -I$(ZLIB_PATH)/include
-       EXTLIBS += -L$(ZLIB_PATH)/$(lib) $(CC_LD_DYNPATH)$(ZLIB_PATH)/$(lib)
+       EXTLIBS += $(call libpath_template,$(ZLIB_PATH)/$(lib))
 endif
 EXTLIBS += -lz
 
@@ -1663,7 +1667,7 @@ ifndef NO_OPENSSL
        OPENSSL_LIBSSL = -lssl
        ifdef OPENSSLDIR
                BASIC_CFLAGS += -I$(OPENSSLDIR)/include
-               OPENSSL_LINK = -L$(OPENSSLDIR)/$(lib) $(CC_LD_DYNPATH)$(OPENSSLDIR)/$(lib)
+               OPENSSL_LINK = $(call libpath_template,$(OPENSSLDIR)/$(lib))
        else
                OPENSSL_LINK =
        endif
@@ -1690,7 +1694,7 @@ ifndef NO_ICONV
        ifdef NEEDS_LIBICONV
                ifdef ICONVDIR
                        BASIC_CFLAGS += -I$(ICONVDIR)/include
-                       ICONV_LINK = -L$(ICONVDIR)/$(lib) $(CC_LD_DYNPATH)$(ICONVDIR)/$(lib)
+                       ICONV_LINK = $(call libpath_template,$(ICONVDIR)/$(lib))
                else
                        ICONV_LINK =
                endif
@@ -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.
 
index 50c79443ba749fc56437806c21a20755c1988714..6e9098ff08935a2670b468d1fe1a36f3c268c055 100644 (file)
--- a/advice.c
+++ b/advice.c
@@ -33,52 +33,56 @@ static const char *advise_get_color(enum color_advice ix)
        return "";
 }
 
+enum advice_level {
+       ADVICE_LEVEL_NONE = 0,
+       ADVICE_LEVEL_DISABLED,
+       ADVICE_LEVEL_ENABLED,
+};
+
 static struct {
        const char *key;
-       int enabled;
+       enum advice_level level;
 } advice_setting[] = {
-       [ADVICE_ADD_EMBEDDED_REPO]                      = { "addEmbeddedRepo", 1 },
-       [ADVICE_ADD_EMPTY_PATHSPEC]                     = { "addEmptyPathspec", 1 },
-       [ADVICE_ADD_IGNORED_FILE]                       = { "addIgnoredFile", 1 },
-       [ADVICE_AM_WORK_DIR]                            = { "amWorkDir", 1 },
-       [ADVICE_AMBIGUOUS_FETCH_REFSPEC]                = { "ambiguousFetchRefspec", 1 },
-       [ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME]  = { "checkoutAmbiguousRemoteBranchName", 1 },
-       [ADVICE_COMMIT_BEFORE_MERGE]                    = { "commitBeforeMerge", 1 },
-       [ADVICE_DETACHED_HEAD]                          = { "detachedHead", 1 },
-       [ADVICE_SUGGEST_DETACHING_HEAD]                 = { "suggestDetachingHead", 1 },
-       [ADVICE_DIVERGING]                              = { "diverging", 1 },
-       [ADVICE_FETCH_SHOW_FORCED_UPDATES]              = { "fetchShowForcedUpdates", 1 },
-       [ADVICE_GRAFT_FILE_DEPRECATED]                  = { "graftFileDeprecated", 1 },
-       [ADVICE_IGNORED_HOOK]                           = { "ignoredHook", 1 },
-       [ADVICE_IMPLICIT_IDENTITY]                      = { "implicitIdentity", 1 },
-       [ADVICE_NESTED_TAG]                             = { "nestedTag", 1 },
-       [ADVICE_OBJECT_NAME_WARNING]                    = { "objectNameWarning", 1 },
-       [ADVICE_PUSH_ALREADY_EXISTS]                    = { "pushAlreadyExists", 1 },
-       [ADVICE_PUSH_FETCH_FIRST]                       = { "pushFetchFirst", 1 },
-       [ADVICE_PUSH_NEEDS_FORCE]                       = { "pushNeedsForce", 1 },
-       [ADVICE_PUSH_REF_NEEDS_UPDATE]                  = { "pushRefNeedsUpdate", 1 },
-
-       /* make this an alias for backward compatibility */
-       [ADVICE_PUSH_UPDATE_REJECTED_ALIAS]             = { "pushNonFastForward", 1 },
-
-       [ADVICE_PUSH_NON_FF_CURRENT]                    = { "pushNonFFCurrent", 1 },
-       [ADVICE_PUSH_NON_FF_MATCHING]                   = { "pushNonFFMatching", 1 },
-       [ADVICE_PUSH_UNQUALIFIED_REF_NAME]              = { "pushUnqualifiedRefName", 1 },
-       [ADVICE_PUSH_UPDATE_REJECTED]                   = { "pushUpdateRejected", 1 },
-       [ADVICE_RESET_NO_REFRESH_WARNING]               = { "resetNoRefresh", 1 },
-       [ADVICE_RESOLVE_CONFLICT]                       = { "resolveConflict", 1 },
-       [ADVICE_RM_HINTS]                               = { "rmHints", 1 },
-       [ADVICE_SEQUENCER_IN_USE]                       = { "sequencerInUse", 1 },
-       [ADVICE_SET_UPSTREAM_FAILURE]                   = { "setUpstreamFailure", 1 },
-       [ADVICE_SKIPPED_CHERRY_PICKS]                   = { "skippedCherryPicks", 1 },
-       [ADVICE_STATUS_AHEAD_BEHIND_WARNING]            = { "statusAheadBehindWarning", 1 },
-       [ADVICE_STATUS_HINTS]                           = { "statusHints", 1 },
-       [ADVICE_STATUS_U_OPTION]                        = { "statusUoption", 1 },
-       [ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { "submoduleAlternateErrorStrategyDie", 1 },
-       [ADVICE_SUBMODULES_NOT_UPDATED]                 = { "submodulesNotUpdated", 1 },
-       [ADVICE_UPDATE_SPARSE_PATH]                     = { "updateSparsePath", 1 },
-       [ADVICE_WAITING_FOR_EDITOR]                     = { "waitingForEditor", 1 },
-       [ADVICE_WORKTREE_ADD_ORPHAN]                    = { "worktreeAddOrphan", 1 },
+       [ADVICE_ADD_EMBEDDED_REPO]                      = { "addEmbeddedRepo" },
+       [ADVICE_ADD_EMPTY_PATHSPEC]                     = { "addEmptyPathspec" },
+       [ADVICE_ADD_IGNORED_FILE]                       = { "addIgnoredFile" },
+       [ADVICE_AMBIGUOUS_FETCH_REFSPEC]                = { "ambiguousFetchRefspec" },
+       [ADVICE_AM_WORK_DIR]                            = { "amWorkDir" },
+       [ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME]  = { "checkoutAmbiguousRemoteBranchName" },
+       [ADVICE_COMMIT_BEFORE_MERGE]                    = { "commitBeforeMerge" },
+       [ADVICE_DETACHED_HEAD]                          = { "detachedHead" },
+       [ADVICE_DIVERGING]                              = { "diverging" },
+       [ADVICE_FETCH_SHOW_FORCED_UPDATES]              = { "fetchShowForcedUpdates" },
+       [ADVICE_FORCE_DELETE_BRANCH]                    = { "forceDeleteBranch" },
+       [ADVICE_GRAFT_FILE_DEPRECATED]                  = { "graftFileDeprecated" },
+       [ADVICE_IGNORED_HOOK]                           = { "ignoredHook" },
+       [ADVICE_IMPLICIT_IDENTITY]                      = { "implicitIdentity" },
+       [ADVICE_NESTED_TAG]                             = { "nestedTag" },
+       [ADVICE_OBJECT_NAME_WARNING]                    = { "objectNameWarning" },
+       [ADVICE_PUSH_ALREADY_EXISTS]                    = { "pushAlreadyExists" },
+       [ADVICE_PUSH_FETCH_FIRST]                       = { "pushFetchFirst" },
+       [ADVICE_PUSH_NEEDS_FORCE]                       = { "pushNeedsForce" },
+       [ADVICE_PUSH_NON_FF_CURRENT]                    = { "pushNonFFCurrent" },
+       [ADVICE_PUSH_NON_FF_MATCHING]                   = { "pushNonFFMatching" },
+       [ADVICE_PUSH_REF_NEEDS_UPDATE]                  = { "pushRefNeedsUpdate" },
+       [ADVICE_PUSH_UNQUALIFIED_REF_NAME]              = { "pushUnqualifiedRefName" },
+       [ADVICE_PUSH_UPDATE_REJECTED]                   = { "pushUpdateRejected" },
+       [ADVICE_PUSH_UPDATE_REJECTED_ALIAS]             = { "pushNonFastForward" }, /* backwards compatibility */
+       [ADVICE_RESET_NO_REFRESH_WARNING]               = { "resetNoRefresh" },
+       [ADVICE_RESOLVE_CONFLICT]                       = { "resolveConflict" },
+       [ADVICE_RM_HINTS]                               = { "rmHints" },
+       [ADVICE_SEQUENCER_IN_USE]                       = { "sequencerInUse" },
+       [ADVICE_SET_UPSTREAM_FAILURE]                   = { "setUpstreamFailure" },
+       [ADVICE_SKIPPED_CHERRY_PICKS]                   = { "skippedCherryPicks" },
+       [ADVICE_STATUS_AHEAD_BEHIND_WARNING]            = { "statusAheadBehindWarning" },
+       [ADVICE_STATUS_HINTS]                           = { "statusHints" },
+       [ADVICE_STATUS_U_OPTION]                        = { "statusUoption" },
+       [ADVICE_SUBMODULES_NOT_UPDATED]                 = { "submodulesNotUpdated" },
+       [ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { "submoduleAlternateErrorStrategyDie" },
+       [ADVICE_SUGGEST_DETACHING_HEAD]                 = { "suggestDetachingHead" },
+       [ADVICE_UPDATE_SPARSE_PATH]                     = { "updateSparsePath" },
+       [ADVICE_WAITING_FOR_EDITOR]                     = { "waitingForEditor" },
+       [ADVICE_WORKTREE_ADD_ORPHAN]                    = { "worktreeAddOrphan" },
 };
 
 static const char turn_off_instructions[] =
@@ -118,13 +122,13 @@ void advise(const char *advice, ...)
 
 int advice_enabled(enum advice_type type)
 {
-       switch(type) {
-       case ADVICE_PUSH_UPDATE_REJECTED:
-               return advice_setting[ADVICE_PUSH_UPDATE_REJECTED].enabled &&
-                      advice_setting[ADVICE_PUSH_UPDATE_REJECTED_ALIAS].enabled;
-       default:
-               return advice_setting[type].enabled;
-       }
+       int enabled = advice_setting[type].level != ADVICE_LEVEL_DISABLED;
+
+       if (type == ADVICE_PUSH_UPDATE_REJECTED)
+               return enabled &&
+                      advice_enabled(ADVICE_PUSH_UPDATE_REJECTED_ALIAS);
+
+       return enabled;
 }
 
 void advise_if_enabled(enum advice_type type, const char *advice, ...)
@@ -135,7 +139,8 @@ void advise_if_enabled(enum advice_type type, const char *advice, ...)
                return;
 
        va_start(params, advice);
-       vadvise(advice, 1, advice_setting[type].key, params);
+       vadvise(advice, !advice_setting[type].level, advice_setting[type].key,
+               params);
        va_end(params);
 }
 
@@ -164,7 +169,9 @@ int git_default_advice_config(const char *var, const char *value)
        for (i = 0; i < ARRAY_SIZE(advice_setting); i++) {
                if (strcasecmp(k, advice_setting[i].key))
                        continue;
-               advice_setting[i].enabled = git_config_bool(var, value);
+               advice_setting[i].level = git_config_bool(var, value)
+                                         ? ADVICE_LEVEL_ENABLED
+                                         : ADVICE_LEVEL_DISABLED;
                return 0;
        }
 
index 2affbe142616de0d329c9aaaaee9ca355fb73adf..9d4f49ae38bcfe3b0bdf9999cf9d66ee6a95739e 100644 (file)
--- a/advice.h
+++ b/advice.h
@@ -10,18 +10,18 @@ struct string_list;
  * Add the new config variable to Documentation/config/advice.txt.
  * Call advise_if_enabled to print your advice.
  */
- enum advice_type {
+enum advice_type {
        ADVICE_ADD_EMBEDDED_REPO,
        ADVICE_ADD_EMPTY_PATHSPEC,
        ADVICE_ADD_IGNORED_FILE,
-       ADVICE_AM_WORK_DIR,
        ADVICE_AMBIGUOUS_FETCH_REFSPEC,
+       ADVICE_AM_WORK_DIR,
        ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,
        ADVICE_COMMIT_BEFORE_MERGE,
        ADVICE_DETACHED_HEAD,
        ADVICE_DIVERGING,
-       ADVICE_SUGGEST_DETACHING_HEAD,
        ADVICE_FETCH_SHOW_FORCED_UPDATES,
+       ADVICE_FORCE_DELETE_BRANCH,
        ADVICE_GRAFT_FILE_DEPRECATED,
        ADVICE_IGNORED_HOOK,
        ADVICE_IMPLICIT_IDENTITY,
@@ -32,23 +32,24 @@ struct string_list;
        ADVICE_PUSH_NEEDS_FORCE,
        ADVICE_PUSH_NON_FF_CURRENT,
        ADVICE_PUSH_NON_FF_MATCHING,
+       ADVICE_PUSH_REF_NEEDS_UPDATE,
        ADVICE_PUSH_UNQUALIFIED_REF_NAME,
-       ADVICE_PUSH_UPDATE_REJECTED_ALIAS,
        ADVICE_PUSH_UPDATE_REJECTED,
-       ADVICE_PUSH_REF_NEEDS_UPDATE,
+       ADVICE_PUSH_UPDATE_REJECTED_ALIAS,
        ADVICE_RESET_NO_REFRESH_WARNING,
        ADVICE_RESOLVE_CONFLICT,
        ADVICE_RM_HINTS,
        ADVICE_SEQUENCER_IN_USE,
        ADVICE_SET_UPSTREAM_FAILURE,
+       ADVICE_SKIPPED_CHERRY_PICKS,
        ADVICE_STATUS_AHEAD_BEHIND_WARNING,
        ADVICE_STATUS_HINTS,
        ADVICE_STATUS_U_OPTION,
-       ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,
        ADVICE_SUBMODULES_NOT_UPDATED,
+       ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE,
+       ADVICE_SUGGEST_DETACHING_HEAD,
        ADVICE_UPDATE_SPARSE_PATH,
        ADVICE_WAITING_FOR_EDITOR,
-       ADVICE_SKIPPED_CHERRY_PICKS,
        ADVICE_WORKTREE_ADD_ORPHAN,
 };
 
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 0a32d1b6c851d4cf07bb02be9376d61a8d38e5d3..cfb63cce5fb9dff64106907947d0df25a2c25489 100644 (file)
@@ -24,6 +24,7 @@
 #include "ref-filter.h"
 #include "worktree.h"
 #include "help.h"
+#include "advice.h"
 #include "commit-reach.h"
 
 static const char * const builtin_branch_usage[] = {
@@ -190,9 +191,10 @@ static int check_branch_commit(const char *branchname, const char *refname,
                return -1;
        }
        if (!force && !branch_merged(kinds, branchname, rev, head_rev)) {
-               error(_("the branch '%s' is not fully merged.\n"
-                     "If you are sure you want to delete it, "
-                     "run 'git branch -D %s'"), branchname, branchname);
+               error(_("the branch '%s' is not fully merged"), branchname);
+               advise_if_enabled(ADVICE_FORCE_DELETE_BRANCH,
+                                 _("If you are sure you want to delete it, "
+                                 "run 'git branch -D %s'"), branchname);
                return -1;
        }
        return 0;
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..b55bfae7d66df0cea54313f677e1a924a4a579b3 100644 (file)
@@ -708,10 +708,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
        }
 
        if (use_global_config) {
-               char *user_config, *xdg_config;
-
-               git_global_config(&user_config, &xdg_config);
-               if (!user_config)
+               given_config_source.file = git_global_config();
+               if (!given_config_source.file)
                        /*
                         * It is unknown if HOME/.gitconfig exists, so
                         * we do not know if we should write to XDG
@@ -719,19 +717,8 @@ int cmd_config(int argc, const char **argv, const char *prefix)
                         * is set and points at a sane location.
                         */
                        die(_("$HOME not set"));
-
                given_config_source.scope = CONFIG_SCOPE_GLOBAL;
-
-               if (access_or_warn(user_config, R_OK, 0) &&
-                   xdg_config && !access_or_warn(xdg_config, R_OK, 0)) {
-                       given_config_source.file = xdg_config;
-                       free(user_config);
-               } else {
-                       given_config_source.file = user_config;
-                       free(xdg_config);
-               }
-       }
-       else if (use_system_config) {
+       } else if (use_system_config) {
                given_config_source.file = git_system_config();
                given_config_source.scope = CONFIG_SCOPE_SYSTEM;
        } else if (use_local_config) {
@@ -760,7 +747,6 @@ int cmd_config(int argc, const char **argv, const char *prefix)
                given_config_source.scope = CONFIG_SCOPE_COMMAND;
        }
 
-
        if (respect_includes_opt == -1)
                config_options.respect_includes = !given_config_source.file;
        else
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 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 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 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..9c76b62b02da037db84ef633100cc6d9136eb0cf 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;
 }
 
@@ -851,21 +848,21 @@ static int add(int ac, const char **av, const char *prefix)
                const char *s = worktree_basename(path, &n);
                new_branch = xstrndup(s, n);
        } else if (opts.orphan) {
-               // No-op
+               ; /* no-op */
        } else if (opts.detach) {
-               // Check HEAD
+               /* Check HEAD */
                if (!strcmp(branch, "HEAD"))
                        can_use_local_refs(&opts);
        } else if (ac < 2 && new_branch) {
-               // DWIM: Infer --orphan when repo has no refs.
+               /* DWIM: Infer --orphan when repo has no refs. */
                opts.orphan = dwim_orphan(&opts, !!opt_track, 0);
        } else if (ac < 2) {
-               // DWIM: Guess branch name from path.
+               /* DWIM: Guess branch name from path. */
                const char *s = dwim_branch(path, &new_branch);
                if (s)
                        branch = s;
 
-               // DWIM: Infer --orphan when repo has no refs.
+               /* DWIM: Infer --orphan when repo has no refs. */
                opts.orphan = (!s) && dwim_orphan(&opts, !!opt_track, 1);
        } else if (ac == 2) {
                struct object_id oid;
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 f86c5e9f942f3bc5d2f0341a8ee8d2f6a6cd0dde..45417d7412202e54d21de0ae49941536eb2c82b4 100644 (file)
@@ -2619,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..320fb99a90e1db6006135e9798f7300f6fa29798 100644 (file)
@@ -707,13 +707,24 @@ ssize_t mingw_write(int fd, const void *buf, size_t len)
 {
        ssize_t result = write(fd, buf, len);
 
-       if (result < 0 && errno == EINVAL && buf) {
+       if (result < 0 && (errno == EINVAL || errno == ENOSPC) && buf) {
+               int orig = errno;
+
                /* check if fd is a pipe */
                HANDLE h = (HANDLE) _get_osfhandle(fd);
-               if (GetFileType(h) == FILE_TYPE_PIPE)
+               if (GetFileType(h) != FILE_TYPE_PIPE)
+                       errno = orig;
+               else if (orig == EINVAL)
                        errno = EPIPE;
-               else
-                       errno = EINVAL;
+               else {
+                       DWORD buf_size;
+
+                       if (!GetNamedPipeInfo(h, NULL, NULL, &buf_size, NULL))
+                               buf_size = 4096;
+                       if (len > buf_size)
+                               return write(fd, buf, buf_size);
+                       errno = orig;
+               }
        }
 
        return result;
@@ -2684,6 +2695,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 +2800,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..c3408d414370d32d7defa2d1e98a32ee6b4fddfc 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
@@ -1807,7 +1808,7 @@ __git_diff_common_options="--stat --numstat --shortstat --summary
                        --output= --output-indicator-context=
                        --output-indicator-new= --output-indicator-old=
                        --ws-error-highlight=
-                       --pickaxe-all --pickaxe-regex
+                       --pickaxe-all --pickaxe-regex --patch-with-raw
 "
 
 # Options for diff/difftool
@@ -2071,6 +2072,16 @@ __git_log_common_options="
        --min-age= --until= --before=
        --min-parents= --max-parents=
        --no-min-parents --no-max-parents
+       --alternate-refs --ancestry-path
+       --author-date-order --basic-regexp
+       --bisect --boundary --exclude-first-parent-only
+       --exclude-hidden --extended-regexp
+       --fixed-strings --grep-reflog
+       --ignore-missing --left-only --perl-regexp
+       --reflog --regexp-ignore-case --remove-empty
+       --right-only --show-linear-break
+       --show-notes-by-default --show-pulls
+       --since-as-filter --single-worktree
 "
 # Options that go well for log and gitk (not shortlog)
 __git_log_gitk_options="
@@ -2086,6 +2097,7 @@ __git_log_shortlog_options="
 # Options accepted by log and show
 __git_log_show_options="
        --diff-merges --diff-merges= --no-diff-merges --dd --remerge-diff
+       --encoding=
 "
 
 __git_diff_merges_opts="off none on first-parent 1 separate m combined c dense-combined cc remerge r"
@@ -2169,6 +2181,8 @@ _git_log ()
                        --no-walk --no-walk= --do-walk
                        --parents --children
                        --expand-tabs --expand-tabs= --no-expand-tabs
+                       --clear-decorations --decorate-refs=
+                       --decorate-refs-exclude=
                        $merge
                        $__git_diff_common_options
                        "
@@ -2966,7 +2980,6 @@ _git_reset ()
 
 _git_restore ()
 {
-       __git_find_repo_path
        case "$prev" in
        -s)
                __git_complete_refs
@@ -2995,7 +3008,6 @@ __git_revert_inprogress_options=$__git_sequencer_inprogress_options
 
 _git_revert ()
 {
-       __git_find_repo_path
        if __git_pseudoref_exists REVERT_HEAD; then
                __gitcomp "$__git_revert_inprogress_options"
                return
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 3028029ac2ddf67608dad1f6538b37a347aebe29..5dab3f506c6e0877740f7d3990388b3975e877c5 100755 (executable)
@@ -787,6 +787,22 @@ ensure_valid_ref_format () {
                die "fatal: '$1' does not look like a ref"
 }
 
+# Usage: check if a commit from another subtree should be
+# ignored from processing for splits
+should_ignore_subtree_split_commit () {
+       assert test $# = 1
+       local rev="$1"
+       if test -n "$(git log -1 --grep="git-subtree-dir:" $rev)"
+       then
+               if test -z "$(git log -1 --grep="git-subtree-mainline:" $rev)" &&
+                       test -z "$(git log -1 --grep="git-subtree-dir: $arg_prefix$" $rev)"
+               then
+                       return 0
+               fi
+       fi
+       return 1
+}
+
 # Usage: process_split_commit REV PARENTS
 process_split_commit () {
        assert test $# = 2
@@ -972,7 +988,19 @@ cmd_split () {
        eval "$grl" |
        while read rev parents
        do
-               process_split_commit "$rev" "$parents"
+               if should_ignore_subtree_split_commit "$rev"
+               then
+                       continue
+               fi
+               parsedparents=''
+               for parent in $parents
+               do
+                       if ! should_ignore_subtree_split_commit "$parent"
+                       then
+                               parsedparents="$parsedparents$parent "
+                       fi
+               done
+               process_split_commit "$rev" "$parsedparents"
        done || exit $?
 
        latest_new=$(cache_get latest_new) || exit $?
index 49a21dd7c9c553f80a905ca8333b93fbe05fcc67..ca4df5be83245fe9a37a0b46737b61071f3b78b0 100755 (executable)
@@ -385,6 +385,46 @@ test_expect_success 'split sub dir/ with --rejoin' '
        )
 '
 
+# Tests that commits from other subtrees are not processed as
+# part of a split.
+#
+# This test performs the following:
+# - Creates Repo with subtrees 'subA' and 'subB'
+# - Creates commits in the repo including changes to subtrees
+# - Runs the following 'split' and commit' commands in order:
+#      - Perform 'split' on subtree A
+#      - Perform 'split' on subtree B
+#      - Create new commits with changes to subtree A and B
+#      - Perform split on subtree A
+#      - Check that the commits in subtree B are not processed
+#                      as part of the subtree A split
+test_expect_success 'split with multiple subtrees' '
+       subtree_test_create_repo "$test_count" &&
+       subtree_test_create_repo "$test_count/subA" &&
+       subtree_test_create_repo "$test_count/subB" &&
+       test_create_commit "$test_count" main1 &&
+       test_create_commit "$test_count/subA" subA1 &&
+       test_create_commit "$test_count/subA" subA2 &&
+       test_create_commit "$test_count/subA" subA3 &&
+       test_create_commit "$test_count/subB" subB1 &&
+       git -C "$test_count" fetch ./subA HEAD &&
+       git -C "$test_count" subtree add --prefix=subADir FETCH_HEAD &&
+       git -C "$test_count" fetch ./subB HEAD &&
+       git -C "$test_count" subtree add --prefix=subBDir FETCH_HEAD &&
+       test_create_commit "$test_count" subADir/main-subA1 &&
+       test_create_commit "$test_count" subBDir/main-subB1 &&
+       git -C "$test_count" subtree split --prefix=subADir \
+               --squash --rejoin -m "Sub A Split 1" &&
+       git -C "$test_count" subtree split --prefix=subBDir \
+               --squash --rejoin -m "Sub B Split 1" &&
+       test_create_commit "$test_count" subADir/main-subA2 &&
+       test_create_commit "$test_count" subBDir/main-subB2 &&
+       git -C "$test_count" subtree split --prefix=subADir \
+               --squash --rejoin -m "Sub A Split 2" &&
+       test "$(git -C "$test_count" subtree split --prefix=subBDir \
+               --squash --rejoin -d -m "Sub B Split 1" 2>&1 | grep -w "\[1\]")" = ""
+'
+
 test_expect_success 'split sub dir/ with --rejoin from scratch' '
        subtree_test_create_repo "$test_count" &&
        test_create_commit "$test_count" main1 &&
diff --git a/diff.c b/diff.c
index a89a6a6128ad68de5d29d47061386f21d91a2337..ccfa1fca0d05fa0148a53e05497efaf21cc4d916 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -4384,7 +4384,8 @@ static void run_external_diff(const char *pgm,
                add_external_diff_name(o->repo, &cmd.args, two);
                if (other) {
                        strvec_push(&cmd.args, other);
-                       strvec_push(&cmd.args, xfrm_msg);
+                       if (xfrm_msg)
+                               strvec_push(&cmd.args, xfrm_msg);
                }
        }
 
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 5b8aa0adc77621504619b831bdd842b83f4867a1..091f9a80a9ee72593c5079b48f15ea22dbd59d4c 100644 (file)
@@ -2216,7 +2216,7 @@ void negotiate_using_fetch(const struct oid_array *negotiation_tips,
                                           the_repository, "%d",
                                           negotiation_round);
        }
-       trace2_region_enter("fetch-pack", "negotiate_using_fetch", the_repository);
+       trace2_region_leave("fetch-pack", "negotiate_using_fetch", the_repository);
        trace2_data_intmax("negotiate_using_fetch", the_repository,
                           "total_rounds", negotiation_round);
        clear_common_flag(acked_commits);
diff --git a/fsck.c b/fsck.c
index 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 0eb3bb4c47dcf12a9f1d98e1b76a3681197ac52e..28ab12c72b6561bf6f583391acf8daa300a6211b 100755 (executable)
--- a/git-p4.py
+++ b/git-p4.py
@@ -689,8 +689,8 @@ def setP4ExecBit(file, mode):
 
     if not isModeExec(mode):
         p4Type = getP4OpenedType(file)
-        p4Type = re.sub('^([cku]?)x(.*)', '\\1\\2', p4Type)
-        p4Type = re.sub('(.*?\+.*?)x(.*?)', '\\1\\2', p4Type)
+        p4Type = re.sub(r'^([cku]?)x(.*)', r'\1\2', p4Type)
+        p4Type = re.sub(r'(.*?\+.*?)x(.*?)', r'\1\2', p4Type)
         if p4Type[-1] == "+":
             p4Type = p4Type[0:-1]
 
@@ -701,7 +701,7 @@ def getP4OpenedType(file):
     """Returns the perforce file type for the given file."""
 
     result = p4_read_pipe(["opened", wildcard_encode(file)])
-    match = re.match(".*\((.+)\)( \*exclusive\*)?\r?$", result)
+    match = re.match(r".*\((.+)\)( \*exclusive\*)?\r?$", result)
     if match:
         return match.group(1)
     else:
@@ -757,7 +757,7 @@ def parseDiffTreeEntry(entry):
 
     global _diff_tree_pattern
     if not _diff_tree_pattern:
-        _diff_tree_pattern = re.compile(':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)')
+        _diff_tree_pattern = re.compile(r':(\d+) (\d+) (\w+) (\w+) ([A-Z])(\d+)?\t(.*?)((\t(.*))|$)')
 
     match = _diff_tree_pattern.match(entry)
     if match:
@@ -918,9 +918,9 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None, skip_info=False,
             if len(result) > 0:
                 data = result[0].get('data')
                 if data:
-                    m = re.search('Too many rows scanned \(over (\d+)\)', data)
+                    m = re.search(r'Too many rows scanned \(over (\d+)\)', data)
                     if not m:
-                        m = re.search('Request too large \(over (\d+)\)', data)
+                        m = re.search(r'Request too large \(over (\d+)\)', data)
 
                     if m:
                         limit = int(m.group(1))
@@ -1452,7 +1452,7 @@ def wildcard_encode(path):
 
 
 def wildcard_present(path):
-    m = re.search("[*#@%]", path)
+    m = re.search(r"[*#@%]", path)
     return m is not None
 
 
@@ -3048,7 +3048,7 @@ class P4Sync(Command, P4UserMap):
             # Preserve everything in relative path name except leading
             # //depot/; just look at first prefix as they all should
             # be in the same depot.
-            depot = re.sub("^(//[^/]+/).*", r'\1', prefixes[0])
+            depot = re.sub(r"^(//[^/]+/).*", r'\1', prefixes[0])
             if p4PathStartsWith(path, depot):
                 path = path[len(depot):]
 
@@ -3603,7 +3603,7 @@ class P4Sync(Command, P4UserMap):
                     commitFound = True
                 else:
                     gitCommit = read_pipe(["git", "rev-list", "--max-count=1",
-                        "--reverse", ":/\[git-p4:.*change = %d\]" % changelist], ignore_error=True)
+                        "--reverse", r":/\[git-p4:.*change = %d\]" % changelist], ignore_error=True)
                     if len(gitCommit) == 0:
                         print("importing label %s: could not find git commit for changelist %d" % (name, changelist))
                     else:
@@ -4182,7 +4182,7 @@ class P4Sync(Command, P4UserMap):
                 if len(self.changesFile) == 0:
                     revision = "#head"
 
-            p = re.sub("\.\.\.$", "", p)
+            p = re.sub(r"\.\.\.$", "", p)
             if not p.endswith("/"):
                 p += "/"
 
@@ -4251,7 +4251,8 @@ class P4Sync(Command, P4UserMap):
         if self.tempBranches != []:
             for branch in self.tempBranches:
                 read_pipe(["git", "update-ref", "-d", branch])
-            os.rmdir(os.path.join(os.environ.get("GIT_DIR", ".git"), self.tempBranchLocation))
+            if len(read_pipe(["git", "for-each-ref", self.tempBranchLocation])) > 0:
+                   die("There are unexpected temporary branches")
 
         # Create a symbolic ref p4/HEAD pointing to p4/<branch> to allow
         # a convenient shortcut refname "p4".
@@ -4291,7 +4292,7 @@ class P4Rebase(Command):
             die("Cannot find upstream branchpoint for rebase")
 
         # the branchpoint may be p4/foo~3, so strip off the parent
-        upstream = re.sub("~[0-9]+$", "", upstream)
+        upstream = re.sub(r"~[0-9]+$", "", upstream)
 
         print("Rebasing the current branch onto %s" % upstream)
         oldHead = read_pipe(["git", "rev-parse", "HEAD"]).strip()
@@ -4320,8 +4321,8 @@ class P4Clone(P4Sync):
     def defaultDestination(self, args):
         # TODO: use common prefix of args?
         depotPath = args[0]
-        depotDir = re.sub("(@[^@]*)$", "", depotPath)
-        depotDir = re.sub("(#[^#]*)$", "", depotDir)
+        depotDir = re.sub(r"(@[^@]*)$", "", depotPath)
+        depotDir = re.sub(r"(#[^#]*)$", "", depotDir)
         depotDir = re.sub(r"\.\.\.$", "", depotDir)
         depotDir = re.sub(r"/$", "", depotDir)
         return os.path.split(depotDir)[1]
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 ff07b87e6461446610c35708a39f22a5e5b36aff..1ed1e29d0706bfe51e2074cabe393e0ed6eba7c7 100644 (file)
@@ -38,6 +38,7 @@ struct rpc_service {
 static struct rpc_service rpc_service[] = {
        { "upload-pack", "uploadpack", 1, 1 },
        { "receive-pack", "receivepack", 0, -1 },
+       { "upload-archive", "uploadarchive", 0, -1 },
 };
 
 static struct string_list *get_parameters(void)
@@ -639,10 +640,15 @@ static void check_content_type(struct strbuf *hdr, const char *accepted_type)
 
 static void service_rpc(struct strbuf *hdr, char *service_name)
 {
-       const char *argv[] = {NULL, "--stateless-rpc", ".", NULL};
+       struct strvec argv = STRVEC_INIT;
        struct rpc_service *svc = select_service(hdr, service_name);
        struct strbuf buf = STRBUF_INIT;
 
+       strvec_push(&argv, svc->name);
+       if (strcmp(service_name, "git-upload-archive"))
+               strvec_push(&argv, "--stateless-rpc");
+       strvec_push(&argv, ".");
+
        strbuf_reset(&buf);
        strbuf_addf(&buf, "application/x-git-%s-request", svc->name);
        check_content_type(hdr, buf.buf);
@@ -655,9 +661,9 @@ static void service_rpc(struct strbuf *hdr, char *service_name)
 
        end_headers(hdr);
 
-       argv[0] = svc->name;
-       run_service(argv, svc->buffer_input);
+       run_service(argv.v, svc->buffer_input);
        strbuf_release(&buf);
+       strvec_clear(&argv);
 }
 
 static int dead;
@@ -723,6 +729,7 @@ static struct service_cmd {
        {"GET", "/objects/pack/pack-[0-9a-f]{64}\\.idx$", get_idx_file},
 
        {"POST", "/git-upload-pack$", service_rpc},
+       {"POST", "/git-upload-archive$", service_rpc},
        {"POST", "/git-receive-pack$", service_rpc}
 };
 
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 1df58ebaac08582d042268f5a07cf5cbd9d7b1c3..5ffb045efb9d0f030fcf4a7ce24508a7016f91e5 100644 (file)
@@ -185,9 +185,9 @@ static void create_temp(mmfile_t *src, char *path, size_t len)
 static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn,
                        mmbuffer_t *result,
                        const char *path,
-                       mmfile_t *orig, const char *orig_name UNUSED,
-                       mmfile_t *src1, const char *name1 UNUSED,
-                       mmfile_t *src2, const char *name2 UNUSED,
+                       mmfile_t *orig, const char *orig_name,
+                       mmfile_t *src1, const char *name1,
+                       mmfile_t *src2, const char *name2,
                        const struct ll_merge_options *opts,
                        int marker_size)
 {
@@ -222,6 +222,12 @@ static enum ll_merge_result ll_ext_merge(const struct ll_merge_driver *fn,
                        strbuf_addf(&cmd, "%d", marker_size);
                else if (skip_prefix(format, "P", &format))
                        sq_quote_buf(&cmd, path);
+               else if (skip_prefix(format, "S", &format))
+                       sq_quote_buf(&cmd, orig_name ? orig_name : "");
+               else if (skip_prefix(format, "X", &format))
+                       sq_quote_buf(&cmd, name1 ? name1 : "");
+               else if (skip_prefix(format, "Y", &format))
+                       sq_quote_buf(&cmd, name2 ? name2 : "");
                else
                        strbuf_addch(&cmd, '%');
        }
@@ -315,7 +321,12 @@ static int read_merge_config(const char *var, const char *value,
                 *    %B - temporary file name for the other branches' version.
                 *    %L - conflict marker length
                 *    %P - the original path (safely quoted for the shell)
+                *    %S - the revision for the merge base
+                *    %X - the revision for our version
+                *    %Y - the revision for their version
                 *
+                * If the file is not named indentically in all versions, then each
+                * revision is joined with the corresponding path, separated by a colon.
                 * The external merge driver should write the results in the
                 * file named by %A, and signal that it has done with zero exit
                 * status.
index 77ba7f3020c8c112bf2e6bff9dae78d4d0ca847b..8617babee41cb59aa5b3dac97a55a53f92e7bc20 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"
@@ -2641,7 +2642,7 @@ static void apply_directory_rename_modifications(struct merge_options *opt,
                        oidcpy(&ci->stages[i].oid, null_oid());
                }
 
-               // Now we want to focus on new_ci, so reassign ci to it
+               /* Now we want to focus on new_ci, so reassign ci to it. */
                ci = new_ci;
        }
 
@@ -4659,9 +4660,6 @@ void merge_switch_to_result(struct merge_options *opt,
 {
        assert(opt->priv == NULL);
        if (result->clean >= 0 && update_worktree_and_index) {
-               const char *filename;
-               FILE *fp;
-
                trace2_region_enter("merge", "checkout", opt->repo);
                if (checkout(opt, head, result->tree)) {
                        /* failure to function */
@@ -4687,10 +4685,17 @@ void merge_switch_to_result(struct merge_options *opt,
                trace2_region_leave("merge", "record_conflicted", opt->repo);
 
                trace2_region_enter("merge", "write_auto_merge", opt->repo);
-               filename = git_path_auto_merge(opt->repo);
-               fp = xfopen(filename, "w");
-               fprintf(fp, "%s\n", oid_to_hex(&result->tree->object.oid));
-               fclose(fp);
+               if (refs_update_ref(get_main_ref_store(opt->repo), "", "AUTO_MERGE",
+                                   &result->tree->object.oid, NULL, REF_NO_DEREF,
+                                   UPDATE_REFS_MSG_ON_ERR)) {
+                       /* failure to function */
+                       opt->priv = NULL;
+                       result->clean = -1;
+                       merge_finalize(opt, result);
+                       trace2_region_leave("merge", "write_auto_merge",
+                                           opt->repo);
+                       return;
+               }
                trace2_region_leave("merge", "write_auto_merge", opt->repo);
        }
        if (display_update_msgs)
diff --git a/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 229a11fb00fc20257385aca4cd653c26f8ce6b31..2baeabacee13d4ec5311261499fe10d8f0a882e5 100644 (file)
@@ -51,13 +51,6 @@ struct bitmap_index {
        struct packed_git *pack;
        struct multi_pack_index *midx;
 
-       /*
-        * Mark the first `reuse_objects` in the packfile as reused:
-        * they will be sent as-is without using them for repacking
-        * calculations
-        */
-       uint32_t reuse_objects;
-
        /* mmapped buffer of the whole bitmap index */
        unsigned char *map;
        size_t map_size; /* size of the mmaped buffer */
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);
 
diff --git a/refs.c b/refs.c
index 20e8f1ff1f114ab0dff0927a4456fbca639c6fb3..c633abf2847cf1b05eec9c9b4d0c817cf78618b6 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -1839,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.
@@ -1853,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;
@@ -1997,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,
diff --git a/refs.h b/refs.h
index 11b3b6cceafc1898eaf14cf80782d32061292ae9..303c5fac4d08b9798fafb6d4c667e9a94bf91e72 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -126,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 b9775f2c37c7eb5e9de275fc3d6ad2125802eeef..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;
 }
index b288fc97dba7b4986ccef25cdd1608eb39e349b9..75dcc21ecb5ab83cadd37a899a252618a0c66df0 100644 (file)
@@ -3218,21 +3218,46 @@ 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;
index bf04b933815955bc13109faf3e7916fc769fff15..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. */
index 8e9f04cc670baeddd68b933ee41d11062b2fbe32..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,
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 46908f738f770f4be920a65df85b96178ddbc70d..bf090b474ed5c69b8ef973b86914f36fdd206318 100644 (file)
@@ -42,7 +42,7 @@ static void write_test_table(struct strbuf *buf,
                }
        }
 
-       w = reftable_new_writer(&strbuf_add_void, buf, &opts);
+       w = reftable_new_writer(&strbuf_add_void, &noop_flush, buf, &opts);
        reftable_writer_set_limits(w, min, max);
 
        for (i = 0; i < n; i++) {
@@ -70,7 +70,7 @@ static void write_test_log_table(struct strbuf *buf,
                .exact_log_message = 1,
        };
        struct reftable_writer *w = NULL;
-       w = reftable_new_writer(&strbuf_add_void, buf, &opts);
+       w = reftable_new_writer(&strbuf_add_void, &noop_flush, buf, &opts);
        reftable_writer_set_limits(w, update_index, update_index);
 
        for (i = 0; i < n; i++) {
@@ -412,7 +412,7 @@ static void test_default_write_opts(void)
        struct reftable_write_options opts = { 0 };
        struct strbuf buf = STRBUF_INIT;
        struct reftable_writer *w =
-               reftable_new_writer(&strbuf_add_void, &buf, &opts);
+               reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
 
        struct reftable_ref_record rec = {
                .refname = "master",
index 011b5c75028a9d8dbad15f23ddb99d39e09ca9cb..c202eff84801820b28c56ddb7de28a2d041f3c08 100644 (file)
@@ -60,7 +60,7 @@ static void test_pq(void)
                if (last) {
                        EXPECT(strcmp(last, rec->u.ref.refname) < 0);
                }
-               // this is names[i], so don't dealloc.
+               /* this is names[i], so don't dealloc. */
                last = rec->u.ref.refname;
                rec->u.ref.refname = NULL;
                reftable_record_release(rec);
index b8a32240164d6a5c29789fb1730a630127f085d5..6b99daeaf2a9b912bbf70277413a486c19e06bbc 100644 (file)
@@ -51,7 +51,7 @@ static void write_table(char ***names, struct strbuf *buf, int N,
                .hash_id = hash_id,
        };
        struct reftable_writer *w =
-               reftable_new_writer(&strbuf_add_void, buf, &opts);
+               reftable_new_writer(&strbuf_add_void, &noop_flush, buf, &opts);
        struct reftable_ref_record ref = { NULL };
        int i = 0, n;
        struct reftable_log_record log = { NULL };
@@ -130,7 +130,7 @@ static void test_log_buffer_size(void)
                                           .message = "commit: 9\n",
                                   } } };
        struct reftable_writer *w =
-               reftable_new_writer(&strbuf_add_void, &buf, &opts);
+               reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
 
        /* This tests buffer extension for log compression. Must use a random
           hash, to ensure that the compressed part is larger than the original.
@@ -171,7 +171,7 @@ static void test_log_overflow(void)
                                           .message = msg,
                                   } } };
        struct reftable_writer *w =
-               reftable_new_writer(&strbuf_add_void, &buf, &opts);
+               reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
 
        uint8_t hash1[GIT_SHA1_RAWSZ]  = {1}, hash2[GIT_SHA1_RAWSZ] = { 2 };
 
@@ -202,7 +202,7 @@ static void test_log_write_read(void)
        struct reftable_block_source source = { NULL };
        struct strbuf buf = STRBUF_INIT;
        struct reftable_writer *w =
-               reftable_new_writer(&strbuf_add_void, &buf, &opts);
+               reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
        const struct reftable_stats *stats = NULL;
        reftable_writer_set_limits(w, 0, N);
        for (i = 0; i < N; i++) {
@@ -294,7 +294,7 @@ static void test_log_zlib_corruption(void)
        struct reftable_block_source source = { 0 };
        struct strbuf buf = STRBUF_INIT;
        struct reftable_writer *w =
-               reftable_new_writer(&strbuf_add_void, &buf, &opts);
+               reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
        const struct reftable_stats *stats = NULL;
        uint8_t hash1[GIT_SHA1_RAWSZ] = { 1 };
        uint8_t hash2[GIT_SHA1_RAWSZ] = { 2 };
@@ -535,7 +535,7 @@ static void test_table_refs_for(int indexed)
 
        struct strbuf buf = STRBUF_INIT;
        struct reftable_writer *w =
-               reftable_new_writer(&strbuf_add_void, &buf, &opts);
+               reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
 
        struct reftable_iterator it = { NULL };
        int j;
@@ -628,7 +628,7 @@ static void test_write_empty_table(void)
        struct reftable_write_options opts = { 0 };
        struct strbuf buf = STRBUF_INIT;
        struct reftable_writer *w =
-               reftable_new_writer(&strbuf_add_void, &buf, &opts);
+               reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
        struct reftable_block_source source = { NULL };
        struct reftable_reader *rd = NULL;
        struct reftable_ref_record rec = { NULL };
@@ -666,7 +666,7 @@ 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);
+               reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
        struct reftable_ref_record ref = {
                .update_index = 1,
                .value_type = REFTABLE_REF_VAL1,
@@ -701,7 +701,7 @@ 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);
+               reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
        struct reftable_ref_record ref = {
                .update_index = 1,
                .value_type = REFTABLE_REF_VAL1,
@@ -735,7 +735,7 @@ static void test_write_empty_key(void)
        struct reftable_write_options opts = { 0 };
        struct strbuf buf = STRBUF_INIT;
        struct reftable_writer *w =
-               reftable_new_writer(&strbuf_add_void, &buf, &opts);
+               reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
        struct reftable_ref_record ref = {
                .refname = "",
                .update_index = 1,
@@ -758,7 +758,7 @@ static void test_write_key_order(void)
        struct reftable_write_options opts = { 0 };
        struct strbuf buf = STRBUF_INIT;
        struct reftable_writer *w =
-               reftable_new_writer(&strbuf_add_void, &buf, &opts);
+               reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
        struct reftable_ref_record refs[2] = {
                {
                        .refname = "b",
@@ -801,7 +801,7 @@ static void test_write_multiple_indices(void)
        struct reftable_reader *reader;
        int err, i;
 
-       writer = reftable_new_writer(&strbuf_add_void, &writer_buf, &opts);
+       writer = reftable_new_writer(&strbuf_add_void, &noop_flush, &writer_buf, &opts);
        reftable_writer_set_limits(writer, 1, 1);
        for (i = 0; i < 100; i++) {
                struct reftable_ref_record ref = {
index 699e1aea4122d66a8a00451521f7d902858474c2..b9cc62554ea1569a66e8fc08be40e04452e3032c 100644 (file)
@@ -30,7 +30,7 @@ static void test_conflict(void)
        struct reftable_write_options opts = { 0 };
        struct strbuf buf = STRBUF_INIT;
        struct reftable_writer *w =
-               reftable_new_writer(&strbuf_add_void, &buf, &opts);
+               reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts);
        struct reftable_ref_record rec = {
                .refname = "a/b",
                .value_type = REFTABLE_REF_SYMREF,
index db8de197f6c42a0de203fc1fbca8174f7f4d2938..7c7cae5f99b7cd4c2be74707bb550366e1088a35 100644 (file)
@@ -88,6 +88,7 @@ struct reftable_stats {
 /* reftable_new_writer creates a new writer */
 struct reftable_writer *
 reftable_new_writer(ssize_t (*writer_func)(void *, const void *, size_t),
+                   int (*flush_func)(void *),
                    void *writer_arg, struct reftable_write_options *opts);
 
 /* Set the range of update indices for the records we will add. When writing a
index 7ffeb3ee1074bc59aa094fabbbce24d1f3cfaf9a..a1dd79fc06160004148f1bb610709d07e139b295 100644 (file)
@@ -8,6 +8,7 @@ https://developers.google.com/open-source/licenses/bsd
 
 #include "stack.h"
 
+#include "../write-or-die.h"
 #include "system.h"
 #include "merged.h"
 #include "reader.h"
@@ -16,7 +17,6 @@ https://developers.google.com/open-source/licenses/bsd
 #include "reftable-record.h"
 #include "reftable-merged.h"
 #include "writer.h"
-
 #include "tempfile.h"
 
 static int stack_try_add(struct reftable_stack *st,
@@ -47,6 +47,13 @@ static ssize_t reftable_fd_write(void *arg, const void *data, size_t sz)
        return write_in_full(*fdp, data, sz);
 }
 
+static int reftable_fd_flush(void *arg)
+{
+       int *fdp = (int *)arg;
+
+       return fsync_component(FSYNC_COMPONENT_REFERENCE, *fdp);
+}
+
 int reftable_new_stack(struct reftable_stack **dest, const char *dir,
                       struct reftable_write_options config)
 {
@@ -66,6 +73,7 @@ int reftable_new_stack(struct reftable_stack **dest, const char *dir,
        strbuf_addstr(&list_file_name, "/tables.list");
 
        p->list_file = strbuf_detach(&list_file_name, NULL);
+       p->list_fd = -1;
        p->reftable_dir = xstrdup(dir);
        p->config = config;
 
@@ -175,6 +183,12 @@ void reftable_stack_destroy(struct reftable_stack *st)
                st->readers_len = 0;
                FREE_AND_NULL(st->readers);
        }
+
+       if (st->list_fd >= 0) {
+               close(st->list_fd);
+               st->list_fd = -1;
+       }
+
        FREE_AND_NULL(st->list_file);
        FREE_AND_NULL(st->reftable_dir);
        reftable_free(st);
@@ -304,69 +318,134 @@ static int tv_cmp(struct timeval *a, struct timeval *b)
 static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
                                             int reuse_open)
 {
-       struct timeval deadline = { 0 };
-       int err = gettimeofday(&deadline, NULL);
+       char **names = NULL, **names_after = NULL;
+       struct timeval deadline;
        int64_t delay = 0;
-       int tries = 0;
-       if (err < 0)
-               return err;
+       int tries = 0, err;
+       int fd = -1;
 
+       err = gettimeofday(&deadline, NULL);
+       if (err < 0)
+               goto out;
        deadline.tv_sec += 3;
+
        while (1) {
-               char **names = NULL;
-               char **names_after = NULL;
-               struct timeval now = { 0 };
-               int err = gettimeofday(&now, NULL);
-               int err2 = 0;
-               if (err < 0) {
-                       return err;
-               }
+               struct timeval now;
+
+               err = gettimeofday(&now, NULL);
+               if (err < 0)
+                       goto out;
 
-               /* Only look at deadlines after the first few times. This
-                  simplifies debugging in GDB */
+               /*
+                * Only look at deadlines after the first few times. This
+                * simplifies debugging in GDB.
+                */
                tries++;
-               if (tries > 3 && tv_cmp(&now, &deadline) >= 0) {
-                       break;
-               }
+               if (tries > 3 && tv_cmp(&now, &deadline) >= 0)
+                       goto out;
 
-               err = read_lines(st->list_file, &names);
-               if (err < 0) {
-                       free_names(names);
-                       return err;
-               }
-               err = reftable_stack_reload_once(st, names, reuse_open);
-               if (err == 0) {
-                       free_names(names);
-                       break;
-               }
-               if (err != REFTABLE_NOT_EXIST_ERROR) {
-                       free_names(names);
-                       return err;
-               }
+               fd = open(st->list_file, O_RDONLY);
+               if (fd < 0) {
+                       if (errno != ENOENT) {
+                               err = REFTABLE_IO_ERROR;
+                               goto out;
+                       }
 
-               /* err == REFTABLE_NOT_EXIST_ERROR can be caused by a concurrent
-                  writer. Check if there was one by checking if the name list
-                  changed.
-               */
-               err2 = read_lines(st->list_file, &names_after);
-               if (err2 < 0) {
-                       free_names(names);
-                       return err2;
+                       names = reftable_calloc(sizeof(char *));
+               } else {
+                       err = fd_read_lines(fd, &names);
+                       if (err < 0)
+                               goto out;
                }
 
+               err = reftable_stack_reload_once(st, names, reuse_open);
+               if (!err)
+                       break;
+               if (err != REFTABLE_NOT_EXIST_ERROR)
+                       goto out;
+
+               /*
+                * REFTABLE_NOT_EXIST_ERROR can be caused by a concurrent
+                * writer. Check if there was one by checking if the name list
+                * changed.
+                */
+               err = read_lines(st->list_file, &names_after);
+               if (err < 0)
+                       goto out;
                if (names_equal(names_after, names)) {
-                       free_names(names);
-                       free_names(names_after);
-                       return err;
+                       err = REFTABLE_NOT_EXIST_ERROR;
+                       goto out;
                }
+
                free_names(names);
+               names = NULL;
                free_names(names_after);
+               names_after = NULL;
+               close(fd);
+               fd = -1;
 
                delay = delay + (delay * rand()) / RAND_MAX + 1;
                sleep_millisec(delay);
        }
 
-       return 0;
+out:
+       /*
+        * Invalidate the stat cache. It is sufficient to only close the file
+        * descriptor and keep the cached stat info because we never use the
+        * latter when the former is negative.
+        */
+       if (st->list_fd >= 0) {
+               close(st->list_fd);
+               st->list_fd = -1;
+       }
+
+       /*
+        * Cache stat information in case it provides a useful signal to us.
+        * According to POSIX, "The st_ino and st_dev fields taken together
+        * uniquely identify the file within the system." That being said,
+        * Windows is not POSIX compliant and we do not have these fields
+        * available. So the information we have there is insufficient to
+        * determine whether two file descriptors point to the same file.
+        *
+        * While we could fall back to using other signals like the file's
+        * mtime, those are not sufficient to avoid races. We thus refrain from
+        * using the stat cache on such systems and fall back to the secondary
+        * caching mechanism, which is to check whether contents of the file
+        * have changed.
+        *
+        * On other systems which are POSIX compliant we must keep the file
+        * descriptor open. This is to avoid a race condition where two
+        * processes access the reftable stack at the same point in time:
+        *
+        *   1. A reads the reftable stack and caches its stat info.
+        *
+        *   2. B updates the stack, appending a new table to "tables.list".
+        *      This will both use a new inode and result in a different file
+        *      size, thus invalidating A's cache in theory.
+        *
+        *   3. B decides to auto-compact the stack and merges two tables. The
+        *      file size now matches what A has cached again. Furthermore, the
+        *      filesystem may decide to recycle the inode number of the file
+        *      we have replaced in (2) because it is not in use anymore.
+        *
+        *   4. A reloads the reftable stack. Neither the inode number nor the
+        *      file size changed. If the timestamps did not change either then
+        *      we think the cached copy of our stack is up-to-date.
+        *
+        * By keeping the file descriptor open the inode number cannot be
+        * recycled, mitigating the race.
+        */
+       if (!err && fd >= 0 && !fstat(fd, &st->list_st) &&
+           st->list_st.st_dev && st->list_st.st_ino) {
+               st->list_fd = fd;
+               fd = -1;
+       }
+
+       if (fd >= 0)
+               close(fd);
+       free_names(names);
+       free_names(names_after);
+       return err;
 }
 
 /* -1 = error
@@ -375,8 +454,44 @@ static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st,
 static int stack_uptodate(struct reftable_stack *st)
 {
        char **names = NULL;
-       int err = read_lines(st->list_file, &names);
+       int err;
        int i = 0;
+
+       /*
+        * When we have cached stat information available then we use it to
+        * verify whether the file has been rewritten.
+        *
+        * Note that we explicitly do not want to use `stat_validity_check()`
+        * and friends here because they may end up not comparing the `st_dev`
+        * and `st_ino` fields. These functions thus cannot guarantee that we
+        * indeed still have the same file.
+        */
+       if (st->list_fd >= 0) {
+               struct stat list_st;
+
+               if (stat(st->list_file, &list_st) < 0) {
+                       /*
+                        * It's fine for "tables.list" to not exist. In that
+                        * case, we have to refresh when the loaded stack has
+                        * any readers.
+                        */
+                       if (errno == ENOENT)
+                               return !!st->readers_len;
+                       return REFTABLE_IO_ERROR;
+               }
+
+               /*
+                * When "tables.list" refers to the same file we can assume
+                * that it didn't change. This is because we always use
+                * rename(3P) to update the file and never write to it
+                * directly.
+                */
+               if (st->list_st.st_dev == list_st.st_dev &&
+                   st->list_st.st_ino == list_st.st_ino)
+                       return 0;
+       }
+
+       err = read_lines(st->list_file, &names);
        if (err < 0)
                return err;
 
@@ -545,6 +660,9 @@ int reftable_addition_commit(struct reftable_addition *add)
                goto done;
        }
 
+       fsync_component_or_die(FSYNC_COMPONENT_REFERENCE, lock_file_fd,
+                              get_tempfile_path(add->lock_file));
+
        err = rename_tempfile(&add->lock_file, add->stack->list_file);
        if (err < 0) {
                err = REFTABLE_IO_ERROR;
@@ -559,7 +677,7 @@ int reftable_addition_commit(struct reftable_addition *add)
        add->new_tables = NULL;
        add->new_tables_len = 0;
 
-       err = reftable_stack_reload(add->stack);
+       err = reftable_stack_reload_maybe_reuse(add->stack, 1);
        if (err)
                goto done;
 
@@ -639,7 +757,7 @@ int reftable_addition_add(struct reftable_addition *add,
                        goto done;
                }
        }
-       wr = reftable_new_writer(reftable_fd_write, &tab_fd,
+       wr = reftable_new_writer(reftable_fd_write, reftable_fd_flush, &tab_fd,
                                 &add->stack->config);
        err = write_table(wr, arg);
        if (err < 0)
@@ -731,7 +849,13 @@ static int stack_compact_locked(struct reftable_stack *st, int first, int last,
        strbuf_addstr(temp_tab, ".temp.XXXXXX");
 
        tab_fd = mkstemp(temp_tab->buf);
-       wr = reftable_new_writer(reftable_fd_write, &tab_fd, &st->config);
+       if (st->config.default_permissions &&
+           chmod(temp_tab->buf, st->config.default_permissions) < 0) {
+               err = REFTABLE_IO_ERROR;
+               goto done;
+       }
+
+       wr = reftable_new_writer(reftable_fd_write, reftable_fd_flush, &tab_fd, &st->config);
 
        err = stack_write_compact(st, wr, first, last, config);
        if (err < 0)
@@ -1008,6 +1132,14 @@ static int stack_compact_range(struct reftable_stack *st, int first, int last,
                unlink(new_table_path.buf);
                goto done;
        }
+
+       err = fsync_component(FSYNC_COMPONENT_REFERENCE, lock_file_fd);
+       if (err < 0) {
+               err = REFTABLE_IO_ERROR;
+               unlink(new_table_path.buf);
+               goto done;
+       }
+
        err = close(lock_file_fd);
        lock_file_fd = -1;
        if (err < 0) {
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 289e902146470035e4dd0109af6b8436e97d40d4..5089392f7beb3fb52e971228ece7635676985b88 100644 (file)
@@ -443,15 +443,16 @@ static void test_reftable_stack_add(void)
        int err = 0;
        struct reftable_write_options cfg = {
                .exact_log_message = 1,
+               .default_permissions = 0660,
        };
        struct reftable_stack *st = NULL;
        char *dir = get_tmp_dir(__LINE__);
-
        struct reftable_ref_record refs[2] = { { NULL } };
        struct reftable_log_record logs[2] = { { NULL } };
+       struct strbuf path = STRBUF_INIT;
+       struct stat stat_result;
        int N = ARRAY_SIZE(refs);
 
-
        err = reftable_new_stack(&st, dir, cfg);
        EXPECT_ERR(err);
        st->disable_auto_compact = 1;
@@ -509,12 +510,32 @@ static void test_reftable_stack_add(void)
                reftable_log_record_release(&dest);
        }
 
+#ifndef GIT_WINDOWS_NATIVE
+       strbuf_addstr(&path, dir);
+       strbuf_addstr(&path, "/tables.list");
+       err = stat(path.buf, &stat_result);
+       EXPECT(!err);
+       EXPECT((stat_result.st_mode & 0777) == cfg.default_permissions);
+
+       strbuf_reset(&path);
+       strbuf_addstr(&path, dir);
+       strbuf_addstr(&path, "/");
+       /* do not try at home; not an external API for reftable. */
+       strbuf_addstr(&path, st->readers[0]->name);
+       err = stat(path.buf, &stat_result);
+       EXPECT(!err);
+       EXPECT((stat_result.st_mode & 0777) == cfg.default_permissions);
+#else
+       (void) stat_result;
+#endif
+
        /* cleanup */
        reftable_stack_destroy(st);
        for (i = 0; i < N; i++) {
                reftable_ref_record_release(&refs[i]);
                reftable_log_record_release(&logs[i]);
        }
+       strbuf_release(&path);
        clear_dir(dir);
 }
 
index 04044fc1a0fb5ffa143264527b882afb752fc2c6..4066924eee45dc6b4fe5b353f7fe54c9dc9f7d7e 100644 (file)
@@ -20,3 +20,8 @@ ssize_t strbuf_add_void(void *b, const void *data, size_t sz)
        strbuf_add(b, data, sz);
        return sz;
 }
+
+int noop_flush(void *arg)
+{
+       return 0;
+}
index ee44f735aea51d03fc3400a9340ccbd7af881f9e..687390f9c239a397652a9a4c9a0bbb7a3510d232 100644 (file)
@@ -56,4 +56,6 @@ void set_test_hash(uint8_t *p, int i);
  */
 ssize_t strbuf_add_void(void *b, const void *data, size_t sz);
 
+int noop_flush(void *);
+
 #endif
index ee4590e20f84dd442fde4f7506e27e568f9ad509..92935baa7036924de241a2adc8eca3640b0fc376 100644 (file)
@@ -121,6 +121,7 @@ static struct strbuf reftable_empty_strbuf = STRBUF_INIT;
 
 struct reftable_writer *
 reftable_new_writer(ssize_t (*writer_func)(void *, const void *, size_t),
+                   int (*flush_func)(void *),
                    void *writer_arg, struct reftable_write_options *opts)
 {
        struct reftable_writer *wp =
@@ -136,6 +137,7 @@ reftable_new_writer(ssize_t (*writer_func)(void *, const void *, size_t),
        wp->write = writer_func;
        wp->write_arg = writer_arg;
        wp->opts = *opts;
+       wp->flush = flush_func;
        writer_reinit_block_writer(wp, BLOCK_TYPE_REF);
 
        return wp;
@@ -603,6 +605,12 @@ int reftable_writer_close(struct reftable_writer *w)
        put_be32(p, crc32(0, footer, p - footer));
        p += 4;
 
+       err = w->flush(w->write_arg);
+       if (err < 0) {
+               err = REFTABLE_IO_ERROR;
+               goto done;
+       }
+
        err = padded_write(w, footer, footer_size(writer_version(w)), 0);
        if (err < 0)
                goto done;
index 09b88673d9757536c3c0f5122f991703eb94a06e..8d0df9cc528dba79677716b6b6760b93c7dad057 100644 (file)
@@ -16,6 +16,7 @@ https://developers.google.com/open-source/licenses/bsd
 
 struct reftable_writer {
        ssize_t (*write)(void *, const void *, size_t);
+       int (*flush)(void *);
        void *write_arg;
        int pending_padding;
        struct strbuf last_key;
index cb0182b582a5cb4d13718d4a47cfb07c078464a0..1161dc7fed689259141ae2eb5403d84b906027d3 100644 (file)
@@ -1446,8 +1446,14 @@ static int stateless_connect(const char *service_name)
         * establish a stateless connection, otherwise we need to tell the
         * client to fallback to using other transport helper functions to
         * complete their request.
+        *
+        * The "git-upload-archive" service is a read-only operation. Fallback
+        * to use "git-upload-pack" service to discover protocol version.
         */
-       discover = discover_refs(service_name, 0);
+       if (!strcmp(service_name, "git-upload-archive"))
+               discover = discover_refs("git-upload-pack", 0);
+       else
+               discover = discover_refs(service_name, 0);
        if (discover->version != protocol_v2) {
                printf("fallback\n");
                fflush(stdout);
@@ -1485,9 +1491,11 @@ static int stateless_connect(const char *service_name)
 
        /*
         * Dump the capability listing that we got from the server earlier
-        * during the info/refs request.
+        * during the info/refs request. This does not work with the
+        * "git-upload-archive" service.
         */
-       write_or_die(rpc.in, discover->buf, discover->len);
+       if (strcmp(service_name, "git-upload-archive"))
+               write_or_die(rpc.in, discover->buf, discover->len);
 
        /* Until we see EOF keep sending POSTs */
        while (1) {
index d7d24d416ae4373c9283ec6bd5171b7cfdd1e759..7aacb51b65cca69ec6acd0c879dd0aa5b15978b3 100644 (file)
@@ -262,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 f5269b3730c602a79adbde479f4622f124156b60..7a250a6605cc8e5a3ae661584122bf1f90f16747 100644 (file)
@@ -67,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;
 };
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 b38702718fbc8db7bb1ba718ecd69b7c99da80e4..b69b1cbc2adb41aa5f4df8b1aff88761ea2cba5c 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -1371,7 +1371,8 @@ static enum discovery_result setup_git_directory_gently_1(struct strbuf *dir,
 
                if (is_git_directory(dir->buf)) {
                        trace2_data_string("setup", NULL, "implicit-bare-repository", dir->buf);
-                       if (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT)
+                       if (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT &&
+                           !ends_with_path_components(dir->buf, ".git"))
                                return GIT_DIR_DISALLOWED_BARE;
                        if (!ensure_valid_ownership(NULL, NULL, dir->buf, report))
                                return GIT_DIR_INVALID_OWNERSHIP;
@@ -1926,23 +1927,8 @@ void create_reference_database(unsigned int ref_storage_format,
        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"));
-
        repo_set_ref_storage_format(the_repository, ref_storage_format);
-       if (refs_init_db(&err))
+       if (refs_init_db(get_main_ref_store(the_repository), 0, &err))
                die("failed to set up refs db: %s", err.buf);
 
        /*
index aeb80fc4d5a33e1b5c6f2e1bbdf4cc3279802eef..29bebd30d8acbce9f50661cef48ecdbae1e41f5a 100644 (file)
@@ -108,3 +108,11 @@ endif
 define mkdir_p_parent_template
 $(if $(wildcard $(@D)),,$(QUIET_MKDIR_P_PARENT)$(shell mkdir -p $(@D)))
 endef
+
+## Getting sick of writing -L$(SOMELIBDIR) $(CC_LD_DYNPATH)$(SOMELIBDIR)?
+## Write $(call libpath_template,$(SOMELIBDIR)) instead, perhaps?
+## With CC_LD_DYNPATH set to either an empty string or to "-L", the
+## the directory is not shown the second time.
+define libpath_template
+-L$(1) $(if $(filter-out -L,$(CC_LD_DYNPATH)),$(CC_LD_DYNPATH)$(1))
+endef
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
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 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 ccc1d3674f8e043b5ba7fd4aa92029bd18c3032f..482a1e58a4b6ec922f1d5abd39848acda79a78bf 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 8a8926f8bc28539156e567b1ead3bcea837f1a28..b1be7cfcf593d0d34ac5f851738ae2fbdf93478f 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);
index c13057a4ca3be22e431771fd60d5ab0325349bdc..0dcfb760a2ba922570d12e17cc1ff6297bdab913 100755 (executable)
@@ -17,7 +17,6 @@ test_expect_success 'advice should be printed when config variable is unset' '
 test_expect_success 'advice should be printed when config variable is set to true' '
        cat >expect <<-\EOF &&
        hint: This is a piece of advice
-       hint: Disable this message with "git config advice.nestedTag false"
        EOF
        test_config advice.nestedTag true &&
        test-tool advise "This is a piece of advice" 2>actual &&
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 038b8b788d7dea688c2efa807e1d1856b8f33c13..804885637954a57ba85e139591e6a0f5a5167034 100755 (executable)
@@ -78,4 +78,12 @@ test_expect_success 'no trace when GIT_DIR is explicitly provided' '
        expect_accepted_explicit "$pwd/outer-repo/bare-repo"
 '
 
+test_expect_success 'no trace when "bare repository" is .git' '
+       expect_accepted_implicit -C outer-repo/.git
+'
+
+test_expect_success 'no trace when "bare repository" is a subdir of .git' '
+       expect_accepted_implicit -C outer-repo/.git/objects
+'
+
 test_done
index f18f9284a5bf3961a3f5373f887b808e55cd9b75..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
index 961b54b06cf875f222ff6281a65e84b895c159cb..6657c114a36c7ea650a974a2f556b7f87435c251 100755 (executable)
@@ -2,6 +2,7 @@
 
 test_description='Test the output of the unit test framework'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success 'TAP output from unit tests' '
index 8798feefe3f6700ee90d3854299802a60536df7d..fca39048fe8840099c2d7a393e91dfd3a6683029 100755 (executable)
@@ -39,9 +39,9 @@ test_expect_success 'sanity check "System Info" section' '
 
        sed -ne "/^\[System Info\]$/,/^$/p" <git-bugreport-format.txt >system &&
 
-       # The beginning should match "git version --build-info" verbatim,
+       # The beginning should match "git version --build-options" verbatim,
        # but rather than checking bit-for-bit equality, just test some basics.
-       grep "git version [0-9]." system &&
+       grep "git version " system &&
        grep "shell-path: ." system &&
 
        # After the version, there should be some more info.
diff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh
new file mode 100755 (executable)
index 0000000..e6a5f18
--- /dev/null
@@ -0,0 +1,384 @@
+#!/bin/sh
+
+test_description='Test reffiles backend'
+
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+
+if ! test_have_prereq REFFILES
+then
+       skip_all='skipping reffiles specific tests'
+       test_done
+fi
+
+test_expect_success 'setup' '
+       git commit --allow-empty -m Initial &&
+       C=$(git rev-parse HEAD) &&
+       git commit --allow-empty -m Second &&
+       D=$(git rev-parse HEAD) &&
+       git commit --allow-empty -m Third &&
+       E=$(git rev-parse HEAD)
+'
+
+test_expect_success 'empty directory should not fool rev-parse' '
+       prefix=refs/e-rev-parse &&
+       git update-ref $prefix/foo $C &&
+       git pack-refs --all &&
+       mkdir -p .git/$prefix/foo/bar/baz &&
+       echo "$C" >expected &&
+       git rev-parse $prefix/foo >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'empty directory should not fool for-each-ref' '
+       prefix=refs/e-for-each-ref &&
+       git update-ref $prefix/foo $C &&
+       git for-each-ref $prefix >expected &&
+       git pack-refs --all &&
+       mkdir -p .git/$prefix/foo/bar/baz &&
+       git for-each-ref $prefix >actual &&
+       test_cmp expected actual
+'
+
+test_expect_success 'empty directory should not fool create' '
+       prefix=refs/e-create &&
+       mkdir -p .git/$prefix/foo/bar/baz &&
+       printf "create %s $C\n" $prefix/foo |
+       git update-ref --stdin
+'
+
+test_expect_success 'empty directory should not fool verify' '
+       prefix=refs/e-verify &&
+       git update-ref $prefix/foo $C &&
+       git pack-refs --all &&
+       mkdir -p .git/$prefix/foo/bar/baz &&
+       printf "verify %s $C\n" $prefix/foo |
+       git update-ref --stdin
+'
+
+test_expect_success 'empty directory should not fool 1-arg update' '
+       prefix=refs/e-update-1 &&
+       git update-ref $prefix/foo $C &&
+       git pack-refs --all &&
+       mkdir -p .git/$prefix/foo/bar/baz &&
+       printf "update %s $D\n" $prefix/foo |
+       git update-ref --stdin
+'
+
+test_expect_success 'empty directory should not fool 2-arg update' '
+       prefix=refs/e-update-2 &&
+       git update-ref $prefix/foo $C &&
+       git pack-refs --all &&
+       mkdir -p .git/$prefix/foo/bar/baz &&
+       printf "update %s $D $C\n" $prefix/foo |
+       git update-ref --stdin
+'
+
+test_expect_success 'empty directory should not fool 0-arg delete' '
+       prefix=refs/e-delete-0 &&
+       git update-ref $prefix/foo $C &&
+       git pack-refs --all &&
+       mkdir -p .git/$prefix/foo/bar/baz &&
+       printf "delete %s\n" $prefix/foo |
+       git update-ref --stdin
+'
+
+test_expect_success 'empty directory should not fool 1-arg delete' '
+       prefix=refs/e-delete-1 &&
+       git update-ref $prefix/foo $C &&
+       git pack-refs --all &&
+       mkdir -p .git/$prefix/foo/bar/baz &&
+       printf "delete %s $C\n" $prefix/foo |
+       git update-ref --stdin
+'
+
+test_expect_success 'non-empty directory blocks create' '
+       prefix=refs/ne-create &&
+       mkdir -p .git/$prefix/foo/bar &&
+       : >.git/$prefix/foo/bar/baz.lock &&
+       test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" &&
+       cat >expected <<-EOF &&
+       fatal: cannot lock ref $SQ$prefix/foo$SQ: there is a non-empty directory $SQ.git/$prefix/foo$SQ blocking reference $SQ$prefix/foo$SQ
+       EOF
+       printf "%s\n" "update $prefix/foo $C" |
+       test_must_fail git update-ref --stdin 2>output.err &&
+       test_cmp expected output.err &&
+       cat >expected <<-EOF &&
+       fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ
+       EOF
+       printf "%s\n" "update $prefix/foo $D $C" |
+       test_must_fail git update-ref --stdin 2>output.err &&
+       test_cmp expected output.err
+'
+
+test_expect_success 'broken reference blocks create' '
+       prefix=refs/broken-create &&
+       mkdir -p .git/$prefix &&
+       echo "gobbledigook" >.git/$prefix/foo &&
+       test_when_finished "rm -f .git/$prefix/foo" &&
+       cat >expected <<-EOF &&
+       fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken
+       EOF
+       printf "%s\n" "update $prefix/foo $C" |
+       test_must_fail git update-ref --stdin 2>output.err &&
+       test_cmp expected output.err &&
+       cat >expected <<-EOF &&
+       fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken
+       EOF
+       printf "%s\n" "update $prefix/foo $D $C" |
+       test_must_fail git update-ref --stdin 2>output.err &&
+       test_cmp expected output.err
+'
+
+test_expect_success 'non-empty directory blocks indirect create' '
+       prefix=refs/ne-indirect-create &&
+       git symbolic-ref $prefix/symref $prefix/foo &&
+       mkdir -p .git/$prefix/foo/bar &&
+       : >.git/$prefix/foo/bar/baz.lock &&
+       test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" &&
+       cat >expected <<-EOF &&
+       fatal: cannot lock ref $SQ$prefix/symref$SQ: there is a non-empty directory $SQ.git/$prefix/foo$SQ blocking reference $SQ$prefix/foo$SQ
+       EOF
+       printf "%s\n" "update $prefix/symref $C" |
+       test_must_fail git update-ref --stdin 2>output.err &&
+       test_cmp expected output.err &&
+       cat >expected <<-EOF &&
+       fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ
+       EOF
+       printf "%s\n" "update $prefix/symref $D $C" |
+       test_must_fail git update-ref --stdin 2>output.err &&
+       test_cmp expected output.err
+'
+
+test_expect_success 'broken reference blocks indirect create' '
+       prefix=refs/broken-indirect-create &&
+       git symbolic-ref $prefix/symref $prefix/foo &&
+       echo "gobbledigook" >.git/$prefix/foo &&
+       test_when_finished "rm -f .git/$prefix/foo" &&
+       cat >expected <<-EOF &&
+       fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken
+       EOF
+       printf "%s\n" "update $prefix/symref $C" |
+       test_must_fail git update-ref --stdin 2>output.err &&
+       test_cmp expected output.err &&
+       cat >expected <<-EOF &&
+       fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken
+       EOF
+       printf "%s\n" "update $prefix/symref $D $C" |
+       test_must_fail git update-ref --stdin 2>output.err &&
+       test_cmp expected output.err
+'
+
+test_expect_success 'no bogus intermediate values during delete' '
+       prefix=refs/slow-transaction &&
+       # Set up a reference with differing loose and packed versions:
+       git update-ref $prefix/foo $C &&
+       git pack-refs --all &&
+       git update-ref $prefix/foo $D &&
+       # Now try to update the reference, but hold the `packed-refs` lock
+       # for a while to see what happens while the process is blocked:
+       : >.git/packed-refs.lock &&
+       test_when_finished "rm -f .git/packed-refs.lock" &&
+       {
+               # Note: the following command is intentionally run in the
+               # background. We increase the timeout so that `update-ref`
+               # attempts to acquire the `packed-refs` lock for much longer
+               # than it takes for us to do the check then delete it:
+               git -c core.packedrefstimeout=30000 update-ref -d $prefix/foo &
+       } &&
+       pid2=$! &&
+       # Give update-ref plenty of time to get to the point where it tries
+       # to lock packed-refs:
+       sleep 1 &&
+       # Make sure that update-ref did not complete despite the lock:
+       kill -0 $pid2 &&
+       # Verify that the reference still has its old value:
+       sha1=$(git rev-parse --verify --quiet $prefix/foo || echo undefined) &&
+       case "$sha1" in
+       $D)
+               # This is what we hope for; it means that nothing
+               # user-visible has changed yet.
+               : ;;
+       undefined)
+               # This is not correct; it means the deletion has happened
+               # already even though update-ref should not have been
+               # able to acquire the lock yet.
+               echo "$prefix/foo deleted prematurely" &&
+               break
+               ;;
+       $C)
+               # This value should never be seen. Probably the loose
+               # reference has been deleted but the packed reference
+               # is still there:
+               echo "$prefix/foo incorrectly observed to be C" &&
+               break
+               ;;
+       *)
+               # WTF?
+               echo "unexpected value observed for $prefix/foo: $sha1" &&
+               break
+               ;;
+       esac >out &&
+       rm -f .git/packed-refs.lock &&
+       wait $pid2 &&
+       test_must_be_empty out &&
+       test_must_fail git rev-parse --verify --quiet $prefix/foo
+'
+
+test_expect_success 'delete fails cleanly if packed-refs file is locked' '
+       prefix=refs/locked-packed-refs &&
+       # Set up a reference with differing loose and packed versions:
+       git update-ref $prefix/foo $C &&
+       git pack-refs --all &&
+       git update-ref $prefix/foo $D &&
+       git for-each-ref $prefix >unchanged &&
+       # Now try to delete it while the `packed-refs` lock is held:
+       : >.git/packed-refs.lock &&
+       test_when_finished "rm -f .git/packed-refs.lock" &&
+       test_must_fail git update-ref -d $prefix/foo >out 2>err &&
+       git for-each-ref $prefix >actual &&
+       test_grep "Unable to create $SQ.*packed-refs.lock$SQ: " err &&
+       test_cmp unchanged actual
+'
+
+test_expect_success 'delete fails cleanly if packed-refs.new write fails' '
+       # Setup and expectations are similar to the test above.
+       prefix=refs/failed-packed-refs &&
+       git update-ref $prefix/foo $C &&
+       git pack-refs --all &&
+       git update-ref $prefix/foo $D &&
+       git for-each-ref $prefix >unchanged &&
+       # This should not happen in practice, but it is an easy way to get a
+       # reliable error (we open with create_tempfile(), which uses O_EXCL).
+       : >.git/packed-refs.new &&
+       test_when_finished "rm -f .git/packed-refs.new" &&
+       test_must_fail git update-ref -d $prefix/foo &&
+       git for-each-ref $prefix >actual &&
+       test_cmp unchanged actual
+'
+
+RWT="test-tool ref-store worktree:wt"
+RMAIN="test-tool ref-store worktree:main"
+
+test_expect_success 'setup worktree' '
+       test_commit first &&
+       git worktree add -b wt-main wt &&
+       (
+               cd wt &&
+               test_commit second
+       )
+'
+
+# Some refs (refs/bisect/*, pseudorefs) are kept per worktree, so they should
+# only appear in the for-each-reflog output if it is called from the correct
+# worktree, which is exercised in this test. This test is poorly written for
+# mulitple reasons: 1) it creates invalidly formatted log entres. 2) it uses
+# direct FS access for creating the reflogs. 3) PSEUDO-WT and refs/bisect/random
+# do not create reflogs by default, so it is not testing a realistic scenario.
+test_expect_success 'for_each_reflog()' '
+       echo $ZERO_OID > .git/logs/PSEUDO-MAIN &&
+       mkdir -p     .git/logs/refs/bisect &&
+       echo $ZERO_OID > .git/logs/refs/bisect/random &&
+
+       echo $ZERO_OID > .git/worktrees/wt/logs/PSEUDO-WT &&
+       mkdir -p     .git/worktrees/wt/logs/refs/bisect &&
+       echo $ZERO_OID > .git/worktrees/wt/logs/refs/bisect/wt-random &&
+
+       $RWT for-each-reflog | cut -d" " -f 2- | sort >actual &&
+       cat >expected <<-\EOF &&
+       HEAD 0x1
+       PSEUDO-WT 0x0
+       refs/bisect/wt-random 0x0
+       refs/heads/main 0x0
+       refs/heads/wt-main 0x0
+       EOF
+       test_cmp expected actual &&
+
+       $RMAIN for-each-reflog | cut -d" " -f 2- | sort >actual &&
+       cat >expected <<-\EOF &&
+       HEAD 0x1
+       PSEUDO-MAIN 0x0
+       refs/bisect/random 0x0
+       refs/heads/main 0x0
+       refs/heads/wt-main 0x0
+       EOF
+       test_cmp expected actual
+'
+
+# Triggering the bug detected by this test requires a newline to fall
+# exactly BUFSIZ-1 bytes from the end of the file. We don't know
+# what that value is, since it's platform dependent. However, if
+# we choose some value N, we also catch any D which divides N evenly
+# (since we will read backwards in chunks of D). So we choose 8K,
+# which catches glibc (with an 8K BUFSIZ) and *BSD (1K).
+#
+# Each line is 114 characters, so we need 75 to still have a few before the
+# last 8K. The 89-character padding on the final entry lines up our
+# newline exactly.
+test_expect_success SHA1 'parsing reverse reflogs at BUFSIZ boundaries' '
+       git checkout -b reflogskip &&
+       zf=$(test_oid zero_2) &&
+       ident="abc <xyz> 0000000001 +0000" &&
+       for i in $(test_seq 1 75); do
+               printf "$zf%02d $zf%02d %s\t" $i $(($i+1)) "$ident" &&
+               if test $i = 75; then
+                       for j in $(test_seq 1 89); do
+                               printf X || return 1
+                       done
+               else
+                       printf X
+               fi &&
+               printf "\n" || return 1
+       done >.git/logs/refs/heads/reflogskip &&
+       git rev-parse reflogskip@{73} >actual &&
+       echo ${zf}03 >expect &&
+       test_cmp expect actual
+'
+
+# This test takes a lock on an individual ref; this is not supported in
+# reftable.
+test_expect_success 'reflog expire operates on symref not referrent' '
+       git branch --create-reflog the_symref &&
+       git branch --create-reflog referrent &&
+       git update-ref referrent HEAD &&
+       git symbolic-ref refs/heads/the_symref refs/heads/referrent &&
+       test_when_finished "rm -f .git/refs/heads/referrent.lock" &&
+       touch .git/refs/heads/referrent.lock &&
+       git reflog expire --expire=all the_symref
+'
+
+test_expect_success 'empty reflog' '
+       test_when_finished "rm -rf empty" &&
+       git init empty &&
+       test_commit -C empty A &&
+       >empty/.git/logs/refs/heads/foo &&
+       git -C empty reflog expire --all 2>err &&
+       test_must_be_empty err
+'
+
+test_expect_success SYMLINKS 'ref resolution not confused by broken symlinks' '
+       ln -s does-not-exist .git/refs/heads/broken &&
+       test_must_fail git rev-parse --verify broken
+'
+
+test_expect_success 'log diagnoses bogus HEAD hash' '
+       git init empty &&
+       test_when_finished "rm -rf empty" &&
+       echo 1234abcd >empty/.git/refs/heads/main &&
+       test_must_fail git -C empty log 2>stderr &&
+       test_grep broken stderr
+'
+
+test_expect_success 'log diagnoses bogus HEAD symref' '
+       git init empty &&
+       test-tool -C empty ref-store main create-symref HEAD refs/heads/invalid.lock &&
+       test_must_fail git -C empty log 2>stderr &&
+       test_grep broken stderr &&
+       test_must_fail git -C empty log --default totally-bogus 2>stderr &&
+       test_grep broken stderr
+'
+
+test_done
similarity index 81%
rename from t/t3210-pack-refs.sh
rename to t/t0601-reffiles-pack-refs.sh
index 7f4e98db7db24363a451fa4b1ae8412b6e1277c3..c309d2bae8a19816907b81d82cef9099b2fa21e9 100755 (executable)
@@ -15,6 +15,12 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
+if ! test_have_prereq REFFILES
+then
+       skip_all='skipping reffiles specific tests'
+       test_done
+fi
+
 test_expect_success 'enable reflogs' '
        git config core.logallrefupdates true
 '
@@ -26,6 +32,14 @@ test_expect_success 'prepare a trivial repository' '
        HEAD=$(git rev-parse --verify HEAD)
 '
 
+test_expect_success 'pack_refs(PACK_REFS_ALL | PACK_REFS_PRUNE)' '
+       N=`find .git/refs -type f | wc -l` &&
+       test "$N" != 0 &&
+       test-tool ref-store main pack-refs PACK_REFS_PRUNE,PACK_REFS_ALL &&
+       N=`find .git/refs -type f` &&
+       test -z "$N"
+'
+
 SHA1=
 
 test_expect_success 'see if git show-ref works as expected' '
@@ -294,4 +308,54 @@ test_expect_success SYMLINKS 'pack symlinked packed-refs' '
        test "$(test_readlink .git/packed-refs)" = "my-deviant-packed-refs"
 '
 
+# The 'packed-refs' file is stored directly in .git/. This means it is global
+# to the repository, and can only contain refs that are shared across all
+# worktrees.
+test_expect_success 'refs/worktree must not be packed' '
+       test_commit initial &&
+       test_commit wt1 &&
+       test_commit wt2 &&
+       git worktree add wt1 wt1 &&
+       git worktree add wt2 wt2 &&
+       git checkout initial &&
+       git update-ref refs/worktree/foo HEAD &&
+       git -C wt1 update-ref refs/worktree/foo HEAD &&
+       git -C wt2 update-ref refs/worktree/foo HEAD &&
+       git pack-refs --all &&
+       test_path_is_missing .git/refs/tags/wt1 &&
+       test_path_is_file .git/refs/worktree/foo &&
+       test_path_is_file .git/worktrees/wt1/refs/worktree/foo &&
+       test_path_is_file .git/worktrees/wt2/refs/worktree/foo
+'
+
+# we do not want to count on running pack-refs to
+# actually pack it, as it is perfectly reasonable to
+# skip processing a broken ref
+test_expect_success 'create packed-refs file with broken ref' '
+       test_tick && git commit --allow-empty -m one &&
+       recoverable=$(git rev-parse HEAD) &&
+       test_tick && git commit --allow-empty -m two &&
+       missing=$(git rev-parse HEAD) &&
+       rm -f .git/refs/heads/main &&
+       cat >.git/packed-refs <<-EOF &&
+       $missing refs/heads/main
+       $recoverable refs/heads/other
+       EOF
+       echo $missing >expect &&
+       git rev-parse refs/heads/main >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'pack-refs does not silently delete broken packed ref' '
+       git pack-refs --all --prune &&
+       git rev-parse refs/heads/main >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'pack-refs does not drop broken refs during deletion' '
+       git update-ref -d refs/heads/other &&
+       git rev-parse refs/heads/main >actual &&
+       test_cmp expect actual
+'
+
 test_done
index f4e27521344920ca5cf85b20a263487e2d794b52..31c38786870849e7a815f32a08933f059c9c8ffb 100755 (executable)
@@ -1098,15 +1098,20 @@ test_expect_success SYMLINKS 'symlink to nonexistent configuration' '
        test_must_fail git config --file=linktolinktonada --list
 '
 
-test_expect_success 'check split_cmdline return' "
-       git config alias.split-cmdline-fix 'echo \"' &&
-       test_must_fail git split-cmdline-fix &&
-       echo foo > foo &&
-       git add foo &&
-       git commit -m 'initial commit' &&
-       git config branch.main.mergeoptions 'echo \"' &&
-       test_must_fail git merge main
-"
+test_expect_success 'check split_cmdline return' '
+       test_when_finished "rm -rf repo" &&
+       git init repo &&
+       (
+               cd repo &&
+               git config alias.split-cmdline-fix "echo \"" &&
+               test_must_fail git split-cmdline-fix &&
+               echo foo >foo &&
+               git add foo &&
+               git commit -m "initial commit" &&
+               git config branch.main.mergeoptions "echo \"" &&
+               test_must_fail git merge main
+       )
+'
 
 test_expect_success 'git -c "key=value" support' '
        cat >expect <<-\EOF &&
@@ -1157,10 +1162,16 @@ test_expect_success 'git -c works with aliases of builtins' '
 '
 
 test_expect_success 'aliases can be CamelCased' '
-       test_config alias.CamelCased "rev-parse HEAD" &&
-       git CamelCased >out &&
-       git rev-parse HEAD >expect &&
-       test_cmp expect out
+       test_when_finished "rm -rf repo" &&
+       git init repo &&
+       (
+               cd repo &&
+               test_commit A &&
+               git config alias.CamelCased "rev-parse HEAD" &&
+               git CamelCased >out &&
+               git rev-parse HEAD >expect &&
+               test_cmp expect out
+       )
 '
 
 test_expect_success 'git -c does not split values on equals' '
@@ -2009,11 +2020,11 @@ test_expect_success '--show-origin getting a single key' '
 '
 
 test_expect_success 'set up custom config file' '
-       CUSTOM_CONFIG_FILE="custom.conf" &&
-       cat >"$CUSTOM_CONFIG_FILE" <<-\EOF
+       cat >"custom.conf" <<-\EOF &&
        [user]
                custom = true
        EOF
+       CUSTOM_CONFIG_FILE="$(test-tool path-utils real_path custom.conf)"
 '
 
 test_expect_success !MINGW 'set up custom config file with special name characters' '
@@ -2052,22 +2063,33 @@ test_expect_success '--show-origin stdin with file include' '
 '
 
 test_expect_success '--show-origin blob' '
-       blob=$(git hash-object -w "$CUSTOM_CONFIG_FILE") &&
-       cat >expect <<-EOF &&
-       blob:$blob      user.custom=true
-       EOF
-       git config --blob=$blob --show-origin --list >output &&
-       test_cmp expect output
+       test_when_finished "rm -rf repo" &&
+       git init repo &&
+       (
+               cd repo &&
+               blob=$(git hash-object -w "$CUSTOM_CONFIG_FILE") &&
+               cat >expect <<-EOF &&
+               blob:$blob      user.custom=true
+               EOF
+               git config --blob=$blob --show-origin --list >output &&
+               test_cmp expect output
+       )
 '
 
 test_expect_success '--show-origin blob ref' '
-       cat >expect <<-\EOF &&
-       blob:main:custom.conf   user.custom=true
-       EOF
-       git add "$CUSTOM_CONFIG_FILE" &&
-       git commit -m "new config file" &&
-       git config --blob=main:"$CUSTOM_CONFIG_FILE" --show-origin --list >output &&
-       test_cmp expect output
+       test_when_finished "rm -rf repo" &&
+       git init repo &&
+       (
+               cd repo &&
+               cat >expect <<-\EOF &&
+               blob:main:custom.conf   user.custom=true
+               EOF
+               cp "$CUSTOM_CONFIG_FILE" custom.conf &&
+               git add custom.conf &&
+               git commit -m "new config file" &&
+               git config --blob=main:custom.conf --show-origin --list >output &&
+               test_cmp expect output
+       )
 '
 
 test_expect_success '--show-origin with --default' '
index e5a0d65caa3e4cc363319826f37a4254bfc99b33..8e2c01e76027f77d8de730816d6d83c22e953e57 100755 (executable)
@@ -137,7 +137,7 @@ test_expect_success POSIXPERM 'info/refs respects umask in unshared repo' '
        test_cmp expect actual
 '
 
-test_expect_success POSIXPERM 'git reflog expire honors core.sharedRepository' '
+test_expect_success REFFILES,POSIXPERM 'git reflog expire honors core.sharedRepository' '
        umask 077 &&
        git config core.sharedRepository group &&
        git reflog expire --all &&
index 179474fa651e159bf68714620ab4f1be2c259fe5..42caa0d2978b58755d43b3361589c6e7691c34db 100755 (executable)
@@ -9,10 +9,6 @@ TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
 test_expect_success 'setup' '
-       test_oid_cache <<-\EOF &&
-       version sha1:0
-       version sha256:1
-       EOF
        cat >test.patch <<-\EOF &&
        diff --git a/test.txt b/test.txt
        new file mode 100644
@@ -28,7 +24,12 @@ test_expect_success 'setup' '
 '
 
 test_expect_success 'gitdir selection on normal repos' '
-       test_oid version >expect &&
+       if test_have_prereq DEFAULT_REPO_FORMAT
+       then
+               echo 0
+       else
+               echo 1
+       fi >expect &&
        git config core.repositoryformatversion >actual &&
        git -C test config core.repositoryformatversion >actual2 &&
        test_cmp expect actual &&
@@ -79,8 +80,13 @@ mkconfig () {
 
 while read outcome version extensions; do
        test_expect_success "$outcome version=$version $extensions" "
-               mkconfig $version $extensions >.git/config &&
-               check_${outcome}
+               test_when_finished 'rm -rf extensions' &&
+               git init extensions &&
+               (
+                       cd extensions &&
+                       mkconfig $version $extensions >.git/config &&
+                       check_${outcome}
+               )
        "
 done <<\EOF
 allow 0
@@ -94,7 +100,8 @@ allow 1 noop-v1
 EOF
 
 test_expect_success 'precious-objects allowed' '
-       mkconfig 1 preciousObjects >.git/config &&
+       git config core.repositoryFormatVersion 1 &&
+       git config extensions.preciousObjects 1 &&
        check_allow
 '
 
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 0369beea33b1aff41662390245fdf2ccf21b1cfb..00b70137053dfcb7351f9a5f8e16b0cadb07f51c 100755 (executable)
@@ -191,78 +191,6 @@ test_expect_success 'one new ref is a simple prefix of another' '
 
 '
 
-test_expect_success REFFILES 'empty directory should not fool rev-parse' '
-       prefix=refs/e-rev-parse &&
-       git update-ref $prefix/foo $C &&
-       git pack-refs --all &&
-       mkdir -p .git/$prefix/foo/bar/baz &&
-       echo "$C" >expected &&
-       git rev-parse $prefix/foo >actual &&
-       test_cmp expected actual
-'
-
-test_expect_success REFFILES 'empty directory should not fool for-each-ref' '
-       prefix=refs/e-for-each-ref &&
-       git update-ref $prefix/foo $C &&
-       git for-each-ref $prefix >expected &&
-       git pack-refs --all &&
-       mkdir -p .git/$prefix/foo/bar/baz &&
-       git for-each-ref $prefix >actual &&
-       test_cmp expected actual
-'
-
-test_expect_success REFFILES 'empty directory should not fool create' '
-       prefix=refs/e-create &&
-       mkdir -p .git/$prefix/foo/bar/baz &&
-       printf "create %s $C\n" $prefix/foo |
-       git update-ref --stdin
-'
-
-test_expect_success REFFILES 'empty directory should not fool verify' '
-       prefix=refs/e-verify &&
-       git update-ref $prefix/foo $C &&
-       git pack-refs --all &&
-       mkdir -p .git/$prefix/foo/bar/baz &&
-       printf "verify %s $C\n" $prefix/foo |
-       git update-ref --stdin
-'
-
-test_expect_success REFFILES 'empty directory should not fool 1-arg update' '
-       prefix=refs/e-update-1 &&
-       git update-ref $prefix/foo $C &&
-       git pack-refs --all &&
-       mkdir -p .git/$prefix/foo/bar/baz &&
-       printf "update %s $D\n" $prefix/foo |
-       git update-ref --stdin
-'
-
-test_expect_success REFFILES 'empty directory should not fool 2-arg update' '
-       prefix=refs/e-update-2 &&
-       git update-ref $prefix/foo $C &&
-       git pack-refs --all &&
-       mkdir -p .git/$prefix/foo/bar/baz &&
-       printf "update %s $D $C\n" $prefix/foo |
-       git update-ref --stdin
-'
-
-test_expect_success REFFILES 'empty directory should not fool 0-arg delete' '
-       prefix=refs/e-delete-0 &&
-       git update-ref $prefix/foo $C &&
-       git pack-refs --all &&
-       mkdir -p .git/$prefix/foo/bar/baz &&
-       printf "delete %s\n" $prefix/foo |
-       git update-ref --stdin
-'
-
-test_expect_success REFFILES 'empty directory should not fool 1-arg delete' '
-       prefix=refs/e-delete-1 &&
-       git update-ref $prefix/foo $C &&
-       git pack-refs --all &&
-       mkdir -p .git/$prefix/foo/bar/baz &&
-       printf "delete %s $C\n" $prefix/foo |
-       git update-ref --stdin
-'
-
 test_expect_success REFFILES 'D/F conflict prevents add long + delete short' '
        df_test refs/df-al-ds --add-del foo/bar foo
 '
@@ -468,169 +396,4 @@ test_expect_success 'incorrect old value blocks indirect no-deref delete' '
        test_cmp expected output.err
 '
 
-test_expect_success REFFILES 'non-empty directory blocks create' '
-       prefix=refs/ne-create &&
-       mkdir -p .git/$prefix/foo/bar &&
-       : >.git/$prefix/foo/bar/baz.lock &&
-       test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" &&
-       cat >expected <<-EOF &&
-       fatal: cannot lock ref $SQ$prefix/foo$SQ: there is a non-empty directory $SQ.git/$prefix/foo$SQ blocking reference $SQ$prefix/foo$SQ
-       EOF
-       printf "%s\n" "update $prefix/foo $C" |
-       test_must_fail git update-ref --stdin 2>output.err &&
-       test_cmp expected output.err &&
-       cat >expected <<-EOF &&
-       fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ
-       EOF
-       printf "%s\n" "update $prefix/foo $D $C" |
-       test_must_fail git update-ref --stdin 2>output.err &&
-       test_cmp expected output.err
-'
-
-test_expect_success REFFILES 'broken reference blocks create' '
-       prefix=refs/broken-create &&
-       mkdir -p .git/$prefix &&
-       echo "gobbledigook" >.git/$prefix/foo &&
-       test_when_finished "rm -f .git/$prefix/foo" &&
-       cat >expected <<-EOF &&
-       fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken
-       EOF
-       printf "%s\n" "update $prefix/foo $C" |
-       test_must_fail git update-ref --stdin 2>output.err &&
-       test_cmp expected output.err &&
-       cat >expected <<-EOF &&
-       fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken
-       EOF
-       printf "%s\n" "update $prefix/foo $D $C" |
-       test_must_fail git update-ref --stdin 2>output.err &&
-       test_cmp expected output.err
-'
-
-test_expect_success REFFILES 'non-empty directory blocks indirect create' '
-       prefix=refs/ne-indirect-create &&
-       git symbolic-ref $prefix/symref $prefix/foo &&
-       mkdir -p .git/$prefix/foo/bar &&
-       : >.git/$prefix/foo/bar/baz.lock &&
-       test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" &&
-       cat >expected <<-EOF &&
-       fatal: cannot lock ref $SQ$prefix/symref$SQ: there is a non-empty directory $SQ.git/$prefix/foo$SQ blocking reference $SQ$prefix/foo$SQ
-       EOF
-       printf "%s\n" "update $prefix/symref $C" |
-       test_must_fail git update-ref --stdin 2>output.err &&
-       test_cmp expected output.err &&
-       cat >expected <<-EOF &&
-       fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ
-       EOF
-       printf "%s\n" "update $prefix/symref $D $C" |
-       test_must_fail git update-ref --stdin 2>output.err &&
-       test_cmp expected output.err
-'
-
-test_expect_success REFFILES 'broken reference blocks indirect create' '
-       prefix=refs/broken-indirect-create &&
-       git symbolic-ref $prefix/symref $prefix/foo &&
-       echo "gobbledigook" >.git/$prefix/foo &&
-       test_when_finished "rm -f .git/$prefix/foo" &&
-       cat >expected <<-EOF &&
-       fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken
-       EOF
-       printf "%s\n" "update $prefix/symref $C" |
-       test_must_fail git update-ref --stdin 2>output.err &&
-       test_cmp expected output.err &&
-       cat >expected <<-EOF &&
-       fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken
-       EOF
-       printf "%s\n" "update $prefix/symref $D $C" |
-       test_must_fail git update-ref --stdin 2>output.err &&
-       test_cmp expected output.err
-'
-
-test_expect_success REFFILES 'no bogus intermediate values during delete' '
-       prefix=refs/slow-transaction &&
-       # Set up a reference with differing loose and packed versions:
-       git update-ref $prefix/foo $C &&
-       git pack-refs --all &&
-       git update-ref $prefix/foo $D &&
-       # Now try to update the reference, but hold the `packed-refs` lock
-       # for a while to see what happens while the process is blocked:
-       : >.git/packed-refs.lock &&
-       test_when_finished "rm -f .git/packed-refs.lock" &&
-       {
-               # Note: the following command is intentionally run in the
-               # background. We increase the timeout so that `update-ref`
-               # attempts to acquire the `packed-refs` lock for much longer
-               # than it takes for us to do the check then delete it:
-               git -c core.packedrefstimeout=30000 update-ref -d $prefix/foo &
-       } &&
-       pid2=$! &&
-       # Give update-ref plenty of time to get to the point where it tries
-       # to lock packed-refs:
-       sleep 1 &&
-       # Make sure that update-ref did not complete despite the lock:
-       kill -0 $pid2 &&
-       # Verify that the reference still has its old value:
-       sha1=$(git rev-parse --verify --quiet $prefix/foo || echo undefined) &&
-       case "$sha1" in
-       $D)
-               # This is what we hope for; it means that nothing
-               # user-visible has changed yet.
-               : ;;
-       undefined)
-               # This is not correct; it means the deletion has happened
-               # already even though update-ref should not have been
-               # able to acquire the lock yet.
-               echo "$prefix/foo deleted prematurely" &&
-               break
-               ;;
-       $C)
-               # This value should never be seen. Probably the loose
-               # reference has been deleted but the packed reference
-               # is still there:
-               echo "$prefix/foo incorrectly observed to be C" &&
-               break
-               ;;
-       *)
-               # WTF?
-               echo "unexpected value observed for $prefix/foo: $sha1" &&
-               break
-               ;;
-       esac >out &&
-       rm -f .git/packed-refs.lock &&
-       wait $pid2 &&
-       test_must_be_empty out &&
-       test_must_fail git rev-parse --verify --quiet $prefix/foo
-'
-
-test_expect_success REFFILES 'delete fails cleanly if packed-refs file is locked' '
-       prefix=refs/locked-packed-refs &&
-       # Set up a reference with differing loose and packed versions:
-       git update-ref $prefix/foo $C &&
-       git pack-refs --all &&
-       git update-ref $prefix/foo $D &&
-       git for-each-ref $prefix >unchanged &&
-       # Now try to delete it while the `packed-refs` lock is held:
-       : >.git/packed-refs.lock &&
-       test_when_finished "rm -f .git/packed-refs.lock" &&
-       test_must_fail git update-ref -d $prefix/foo >out 2>err &&
-       git for-each-ref $prefix >actual &&
-       test_grep "Unable to create $SQ.*packed-refs.lock$SQ: " err &&
-       test_cmp unchanged actual
-'
-
-test_expect_success REFFILES 'delete fails cleanly if packed-refs.new write fails' '
-       # Setup and expectations are similar to the test above.
-       prefix=refs/failed-packed-refs &&
-       git update-ref $prefix/foo $C &&
-       git pack-refs --all &&
-       git update-ref $prefix/foo $D &&
-       git for-each-ref $prefix >unchanged &&
-       # This should not happen in practice, but it is an easy way to get a
-       # reliable error (we open with create_tempfile(), which uses O_EXCL).
-       : >.git/packed-refs.new &&
-       test_when_finished "rm -f .git/packed-refs.new" &&
-       test_must_fail git update-ref -d $prefix/foo &&
-       git for-each-ref $prefix >actual &&
-       test_cmp unchanged actual
-'
-
 test_done
index e4627cf1b61f0b3128a79e28f15a9d5e5693e30c..976bd71efb561d7a374b801ef087edc51f8de01a 100755 (executable)
@@ -15,14 +15,6 @@ test_expect_success 'setup' '
        test_commit one
 '
 
-test_expect_success REFFILES 'pack_refs(PACK_REFS_ALL | PACK_REFS_PRUNE)' '
-       N=`find .git/refs -type f | wc -l` &&
-       test "$N" != 0 &&
-       $RUN pack-refs PACK_REFS_PRUNE,PACK_REFS_ALL &&
-       N=`find .git/refs -type f` &&
-       test -z "$N"
-'
-
 test_expect_success 'create_symref(FOO, refs/heads/main)' '
        $RUN create-symref FOO refs/heads/main nothing &&
        echo refs/heads/main >expected &&
@@ -112,7 +104,7 @@ test_expect_success 'delete_reflog(HEAD)' '
        test_must_fail git reflog exists HEAD
 '
 
-test_expect_success REFFILES 'create-reflog(HEAD)' '
+test_expect_success 'create-reflog(HEAD)' '
        $RUN create-reflog HEAD &&
        git reflog exists HEAD
 '
index 05b1881c5911780b53a3d882dbe1a4dfae0034d8..48b1c92a41450b25645d6cd782aa86c8e630164b 100755 (executable)
@@ -53,41 +53,4 @@ test_expect_success 'create_symref(FOO, refs/heads/main)' '
        test_cmp expected actual
 '
 
-# Some refs (refs/bisect/*, pseudorefs) are kept per worktree, so they should
-# only appear in the for-each-reflog output if it is called from the correct
-# worktree, which is exercised in this test. This test is poorly written (and
-# therefore marked REFFILES) for mulitple reasons: 1) it creates invalidly
-# formatted log entres. 2) it uses direct FS access for creating the reflogs. 3)
-# PSEUDO-WT and refs/bisect/random do not create reflogs by default, so it is
-# not testing a realistic scenario.
-test_expect_success REFFILES 'for_each_reflog()' '
-       echo $ZERO_OID > .git/logs/PSEUDO-MAIN &&
-       mkdir -p     .git/logs/refs/bisect &&
-       echo $ZERO_OID > .git/logs/refs/bisect/random &&
-
-       echo $ZERO_OID > .git/worktrees/wt/logs/PSEUDO-WT &&
-       mkdir -p     .git/worktrees/wt/logs/refs/bisect &&
-       echo $ZERO_OID > .git/worktrees/wt/logs/refs/bisect/wt-random &&
-
-       $RWT for-each-reflog | cut -d" " -f 2- | sort >actual &&
-       cat >expected <<-\EOF &&
-       HEAD 0x1
-       PSEUDO-WT 0x0
-       refs/bisect/wt-random 0x0
-       refs/heads/main 0x0
-       refs/heads/wt-main 0x0
-       EOF
-       test_cmp expected actual &&
-
-       $RMAIN for-each-reflog | cut -d" " -f 2- | sort >actual &&
-       cat >expected <<-\EOF &&
-       HEAD 0x1
-       PSEUDO-MAIN 0x0
-       refs/bisect/random 0x0
-       refs/heads/main 0x0
-       refs/heads/wt-main 0x0
-       EOF
-       test_cmp expected actual
-'
-
 test_done
index f23c0152a858265e4a8c6b0c068e00823aadb444..7748973733e6adab07117192accabf7698189681 100755 (executable)
@@ -5,6 +5,12 @@ test_description='avoid rewriting packed-refs unnecessarily'
 TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
+if test_have_prereq !REFFILES
+then
+  skip_all='skipping files-backend specific pack-refs tests'
+  test_done
+fi
+
 # Add an identifying mark to the packed-refs file header line. This
 # shouldn't upset readers, and it should be omitted if the file is
 # ever rewritten.
index a0ff8d51f04b2d9ad34060a4f42290577d6673f7..d2f5f42e67440d856fd8f1752c75b20a16278e13 100755 (executable)
@@ -354,36 +354,6 @@ test_expect_success 'stale dirs do not cause d/f conflicts (reflogs off)' '
        test_must_be_empty actual
 '
 
-# Triggering the bug detected by this test requires a newline to fall
-# exactly BUFSIZ-1 bytes from the end of the file. We don't know
-# what that value is, since it's platform dependent. However, if
-# we choose some value N, we also catch any D which divides N evenly
-# (since we will read backwards in chunks of D). So we choose 8K,
-# which catches glibc (with an 8K BUFSIZ) and *BSD (1K).
-#
-# Each line is 114 characters, so we need 75 to still have a few before the
-# last 8K. The 89-character padding on the final entry lines up our
-# newline exactly.
-test_expect_success REFFILES,SHA1 'parsing reverse reflogs at BUFSIZ boundaries' '
-       git checkout -b reflogskip &&
-       zf=$(test_oid zero_2) &&
-       ident="abc <xyz> 0000000001 +0000" &&
-       for i in $(test_seq 1 75); do
-               printf "$zf%02d $zf%02d %s\t" $i $(($i+1)) "$ident" &&
-               if test $i = 75; then
-                       for j in $(test_seq 1 89); do
-                               printf X || return 1
-                       done
-               else
-                       printf X
-               fi &&
-               printf "\n" || return 1
-       done >.git/logs/refs/heads/reflogskip &&
-       git rev-parse reflogskip@{73} >actual &&
-       echo ${zf}03 >expect &&
-       test_cmp expect actual
-'
-
 test_expect_success 'no segfaults for reflog containing non-commit sha1s' '
        git update-ref --create-reflog -m "Creating ref" \
                refs/tests/tree-in-reflog HEAD &&
@@ -397,18 +367,6 @@ test_expect_failure 'reflog with non-commit entries displays all entries' '
        test_line_count = 3 actual
 '
 
-# This test takes a lock on an individual ref; this is not supported in
-# reftable.
-test_expect_success REFFILES 'reflog expire operates on symref not referrent' '
-       git branch --create-reflog the_symref &&
-       git branch --create-reflog referrent &&
-       git update-ref referrent HEAD &&
-       git symbolic-ref refs/heads/the_symref refs/heads/referrent &&
-       test_when_finished "rm -f .git/refs/heads/referrent.lock" &&
-       touch .git/refs/heads/referrent.lock &&
-       git reflog expire --expire=all the_symref
-'
-
 test_expect_success 'continue walking past root commits' '
        git init orphanage &&
        (
index ea64cecf47bf14277c79cc54e954c64022e0519a..be6c3f472c176525b64383b726ad7b05b664accd 100755 (executable)
@@ -121,13 +121,12 @@ test_expect_success 'min/max age uses entry date to limit' '
 
 # Create a situation where the reflog and ref database disagree about the latest
 # state of HEAD.
-test_expect_success REFFILES 'walk prefers reflog to ref tip' '
+test_expect_success 'walk prefers reflog to ref tip' '
+       test_commit A &&
+       test_commit B &&
+       git reflog delete HEAD@{0} &&
        head=$(git rev-parse HEAD) &&
-       one=$(git rev-parse one) &&
-       ident="$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE" &&
-       echo "$head $one $ident broken reflog entry" >>.git/logs/HEAD &&
-
-       echo $one >expect &&
+       git rev-parse A >expect &&
        git log -g --format=%H -1 >actual &&
        test_cmp expect actual
 '
index 3b531842dd4c864b45bfd89c36e14f9327d6b29e..eb4eec8becbfa64efcde4e866334363a866c01a2 100755 (executable)
@@ -17,17 +17,6 @@ test_expect_success 'setup' '
        git -C wt2 update-ref refs/worktree/foo HEAD
 '
 
-# The 'packed-refs' file is stored directly in .git/. This means it is global
-# to the repository, and can only contain refs that are shared across all
-# worktrees.
-test_expect_success REFFILES 'refs/worktree must not be packed' '
-       git pack-refs --all &&
-       test_path_is_missing .git/refs/tags/wt1 &&
-       test_path_is_file .git/refs/worktree/foo &&
-       test_path_is_file .git/worktrees/wt1/refs/worktree/foo &&
-       test_path_is_file .git/worktrees/wt2/refs/worktree/foo
-'
-
 test_expect_success 'refs/worktree are per-worktree' '
        test_cmp_rev worktree/foo initial &&
        ( cd wt1 && test_cmp_rev worktree/foo wt1 ) &&
index 5d8c86b657399e0d8852909a33491449f1eaefa9..13595744190b54e539a581f2409ab3be5e3932d3 100755 (executable)
@@ -8,6 +8,12 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 
+if test_have_prereq !REFFILES
+then
+       skip_all='skipping `git for-each-ref --exclude` tests; need files backend'
+       test_done
+fi
+
 for_each_ref__exclude () {
        GIT_TRACE2_PERF=1 test-tool ref-store main \
                for-each-ref--exclude "$@" >actual.raw
index bc136833c1098d1c4e43d4b05cfadc7bef50b24c..79df65ec7f619baa053b2aa50d5ec08557cb820a 100755 (executable)
@@ -144,11 +144,6 @@ test_expect_success 'main@{n} for various n' '
        test_must_fail git rev-parse --verify main@{$Np1}
 '
 
-test_expect_success SYMLINKS,REFFILES 'ref resolution not confused by broken symlinks' '
-       ln -s does-not-exist .git/refs/heads/broken &&
-       test_must_fail git rev-parse --verify broken
-'
-
 test_expect_success 'options can appear after --verify' '
        git rev-parse --verify HEAD >expect &&
        git rev-parse --verify -q HEAD >actual &&
index 947d1587ac8be9a2ab333049a2196c4e211185e6..a5c7358eeabefb3dacc94ae94e2c61d200381ac5 100755 (executable)
@@ -86,7 +86,7 @@ test_expect_success '--orphan makes reflog by default' '
        git rev-parse --verify delta@{0}
 '
 
-test_expect_success REFFILES '--orphan does not make reflog when core.logAllRefUpdates = false' '
+test_expect_success '--orphan does not make reflog when core.logAllRefUpdates = false' '
        git checkout main &&
        git config core.logAllRefUpdates false &&
        git checkout --orphan epsilon &&
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 34faeac3f1cde42633fc5a8b8b67cbf14d57edaf..3319240515536f5c42846583df60930938409538 100755 (executable)
@@ -200,7 +200,7 @@ test_expect_success 'drop stash reflog updates refs/stash' '
        test_cmp expect actual
 '
 
-test_expect_success REFFILES 'drop stash reflog updates refs/stash with rewrite' '
+test_expect_success 'drop stash reflog updates refs/stash with rewrite' '
        git init repo &&
        (
                cd repo &&
@@ -213,16 +213,16 @@ test_expect_success REFFILES 'drop stash reflog updates refs/stash with rewrite'
        new_oid="$(git -C repo rev-parse stash@{0})" &&
 
        cat >expect <<-EOF &&
-       $(test_oid zero) $old_oid
-       $old_oid $new_oid
+       $new_oid
+       $old_oid
        EOF
-       cut -d" " -f1-2 repo/.git/logs/refs/stash >actual &&
+       git -C repo reflog show refs/stash --format=%H >actual &&
        test_cmp expect actual &&
 
        git -C repo stash drop stash@{1} &&
-       cut -d" " -f1-2 repo/.git/logs/refs/stash >actual &&
+       git -C repo reflog show refs/stash --format=%H >actual &&
        cat >expect <<-EOF &&
-       $(test_oid zero) $new_oid
+       $new_oid
        EOF
        test_cmp expect actual
 '
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 5ce345d309eb70425b9c04cdbfa57d1611675af8..651ec776606bb0990edc40bebf13ccc4937bb0ce 100755 (executable)
@@ -205,6 +205,18 @@ test_expect_success POSIXPERM,SYMLINKS 'diff --no-index normalizes: mode not lik
        test_cmp expected actual
 '
 
+test_expect_success POSIXPERM 'external diff with mode-only change' '
+       echo content >not-executable &&
+       echo content >executable &&
+       chmod +x executable &&
+       echo executable executable $(test_oid zero) 100755 \
+               not-executable $(test_oid zero) 100644 not-executable \
+               >expect &&
+       test_expect_code 1 git -c diff.external=echo diff \
+               --no-index executable not-executable >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success "diff --no-index treats '-' as stdin" '
        cat >expect <<-EOF &&
        diff --git a/- b/a/1
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 ddd205f98abf7f8d58555761eaf20006bb8433e8..60fe60d7610e2a8141c58385b2319c7fa43a5de3 100755 (executable)
@@ -2255,23 +2255,6 @@ test_expect_success 'log on empty repo fails' '
        test_grep does.not.have.any.commits stderr
 '
 
-test_expect_success REFFILES 'log diagnoses bogus HEAD hash' '
-       git init empty &&
-       test_when_finished "rm -rf empty" &&
-       echo 1234abcd >empty/.git/refs/heads/main &&
-       test_must_fail git -C empty log 2>stderr &&
-       test_grep broken stderr
-'
-
-test_expect_success REFFILES 'log diagnoses bogus HEAD symref' '
-       git init empty &&
-       test-tool -C empty ref-store main create-symref HEAD refs/heads/invalid.lock &&
-       test_must_fail git -C empty log 2>stderr &&
-       test_grep broken stderr &&
-       test_must_fail git -C empty log --default totally-bogus 2>stderr &&
-       test_grep broken stderr
-'
-
 test_expect_success 'log does not default to HEAD when rev input is given' '
        git log --branches=does-not-exist >actual &&
        test_must_be_empty actual
index fc499cdff01d01a6079221e78b50a7924e5ecb71..961c6aac2561354f6ad57270c1362fe73c850924 100755 (executable)
@@ -239,4 +239,38 @@ check_zip with_untracked2
 check_added with_untracked2 untracked one/untracked
 check_added with_untracked2 untracked two/untracked
 
+# Test remote archive over HTTP protocol.
+#
+# Note: this should be the last part of this test suite, because
+# by including lib-httpd.sh, the test may end early if httpd tests
+# should not be run.
+#
+. "$TEST_DIRECTORY"/lib-httpd.sh
+start_httpd
+
+test_expect_success "setup for HTTP protocol" '
+       cp -R bare.git "$HTTPD_DOCUMENT_ROOT_PATH/bare.git" &&
+       git -C "$HTTPD_DOCUMENT_ROOT_PATH/bare.git" \
+               config http.uploadpack true &&
+       set_askpass user@host pass@host
+'
+
+setup_askpass_helper
+
+test_expect_success 'remote archive does not work with protocol v1' '
+       test_must_fail git -c protocol.version=1 archive \
+               --remote="$HTTPD_URL/auth/smart/bare.git" \
+               --output=remote-http.zip HEAD >actual 2>&1 &&
+       cat >expect <<-EOF &&
+       fatal: can${SQ}t connect to subservice git-upload-archive
+       EOF
+       test_cmp expect actual
+'
+
+test_expect_success 'archive remote http repository' '
+       git archive --remote="$HTTPD_URL/auth/smart/bare.git" \
+               --output=remote-http.zip HEAD &&
+       test_cmp_bin d.zip remote-http.zip
+'
+
 test_done
index 230cb3871223e1f28482c841093cb7a0e6873754..d8d2e304687b2a9e6d9313d0daa8a77848410f41 100755 (executable)
@@ -111,30 +111,4 @@ test_expect_success 'pack-refs does not silently delete broken loose ref' '
        test_cmp expect actual
 '
 
-# we do not want to count on running pack-refs to
-# actually pack it, as it is perfectly reasonable to
-# skip processing a broken ref
-test_expect_success REFFILES 'create packed-refs file with broken ref' '
-       rm -f .git/refs/heads/main &&
-       cat >.git/packed-refs <<-EOF &&
-       $missing refs/heads/main
-       $recoverable refs/heads/other
-       EOF
-       echo $missing >expect &&
-       git rev-parse refs/heads/main >actual &&
-       test_cmp expect actual
-'
-
-test_expect_success REFFILES 'pack-refs does not silently delete broken packed ref' '
-       git pack-refs --all --prune &&
-       git rev-parse refs/heads/main >actual &&
-       test_cmp expect actual
-'
-
-test_expect_success REFFILES  'pack-refs does not drop broken refs during deletion' '
-       git update-ref -d refs/heads/other &&
-       git rev-parse refs/heads/main >actual &&
-       test_cmp expect actual
-'
-
 test_done
index 2ba788b0421b6a7dcaf8f8ff420f5ec97cdab72b..99145327a6a21f733a5a6fcbd5ccafb1b4259088 100755 (executable)
@@ -2,6 +2,7 @@
 
 test_description='pack-objects multi-pack reuse'
 
+TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/lib-bitmap.sh
 
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 7ab220fa3135a1ecbaa8f69a5db8a9b1298f4ce1..5e566205ba4b95830bd97c97b0092cb4a3356304 100755 (executable)
@@ -771,7 +771,7 @@ test_expect_success 'fetching submodule into a broken repository' '
        git -C dst fetch --recurse-submodules &&
 
        # Break the receiving submodule
-       test-tool -C dst/sub ref-store main delete-refs REF_NO_DEREF msg HEAD &&
+       rm -r dst/sub/.git/objects &&
 
        # NOTE: without the fix the following tests will recurse forever!
        # They should terminate with an error.
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 e069737b80b7bd906afa1963f7b818ffac8ed2ca..a623a1058cd2acddcfca4b6712dcfa41e56dbb67 100755 (executable)
@@ -733,4 +733,22 @@ test_expect_success 'no empty path components' '
        ! grep "//" log
 '
 
+test_expect_success 'tag following always works over v0 http' '
+       upstream=$HTTPD_DOCUMENT_ROOT_PATH/tags &&
+       git init "$upstream" &&
+       (
+               cd "$upstream" &&
+               git commit --allow-empty -m base &&
+               git tag not-annotated &&
+               git tag -m foo annotated
+       ) &&
+       git init tags &&
+       git -C tags -c protocol.version=0 \
+               fetch --depth 1 $HTTPD_URL/smart/tags \
+               refs/tags/annotated:refs/tags/annotated &&
+       git -C "$upstream" for-each-ref refs/tags >expect &&
+       git -C tags for-each-ref >actual &&
+       test_cmp expect actual
+'
+
 test_done
index 459f0d741225522276b84a9a65caaa2e2a985bde..a9656a1ec8a6d71814a5f17157eb0e2acbd4bf98 100755 (executable)
@@ -1,10 +1,11 @@
 #!/bin/sh
 
 test_description='rev-list combining bitmaps and filters'
+
+TEST_PASSES_SANITIZE_LEAK=true
 . ./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
index 72f8c1722ff76d332529450b4115d7afb39ed5ec..156a1efacfeabcba3cff34d1044efd522183f13d 100755 (executable)
@@ -42,11 +42,15 @@ test_expect_success setup '
        #!/bin/sh
 
        orig="$1" ours="$2" theirs="$3" exit="$4" path=$5
+       orig_name="$6" our_name="$7" their_name="$8"
        (
                echo "orig is $orig"
                echo "ours is $ours"
                echo "theirs is $theirs"
                echo "path is $path"
+               echo "orig_name is $orig_name"
+               echo "our_name is $our_name"
+               echo "their_name is $their_name"
                echo "=== orig ==="
                cat "$orig"
                echo "=== ours ==="
@@ -121,7 +125,7 @@ test_expect_success 'custom merge backend' '
 
        git reset --hard anchor &&
        git config --replace-all \
-       merge.custom.driver "./custom-merge %O %A %B 0 %P" &&
+       merge.custom.driver "./custom-merge %O %A %B 0 %P %S %X %Y" &&
        git config --replace-all \
        merge.custom.name "custom merge driver for testing" &&
 
@@ -132,7 +136,8 @@ test_expect_success 'custom merge backend' '
        o=$(git unpack-file main^:text) &&
        a=$(git unpack-file side^:text) &&
        b=$(git unpack-file main:text) &&
-       sh -c "./custom-merge $o $a $b 0 text" &&
+       base_revid=$(git rev-parse --short main^) &&
+       sh -c "./custom-merge $o $a $b 0 text $base_revid HEAD main" &&
        sed -e 1,3d $a >check-2 &&
        cmp check-1 check-2 &&
        rm -f $o $a $b
@@ -142,7 +147,7 @@ test_expect_success 'custom merge backend' '
 
        git reset --hard anchor &&
        git config --replace-all \
-       merge.custom.driver "./custom-merge %O %A %B 1 %P" &&
+       merge.custom.driver "./custom-merge %O %A %B 1 %P %S %X %Y" &&
        git config --replace-all \
        merge.custom.name "custom merge driver for testing" &&
 
@@ -159,7 +164,8 @@ test_expect_success 'custom merge backend' '
        o=$(git unpack-file main^:text) &&
        a=$(git unpack-file anchor:text) &&
        b=$(git unpack-file main:text) &&
-       sh -c "./custom-merge $o $a $b 0 text" &&
+       base_revid=$(git rev-parse --short main^) &&
+       sh -c "./custom-merge $o $a $b 0 text $base_revid HEAD main" &&
        sed -e 1,3d $a >check-2 &&
        cmp check-1 check-2 &&
        sed -e 1,3d -e 4q $a >check-3 &&
@@ -173,7 +179,7 @@ test_expect_success !WINDOWS 'custom merge driver that is killed with a signal'
 
        git reset --hard anchor &&
        git config --replace-all \
-       merge.custom.driver "./custom-merge %O %A %B 0 %P" &&
+       merge.custom.driver "./custom-merge %O %A %B 0 %P %S %X %Y" &&
        git config --replace-all \
        merge.custom.name "custom merge driver for testing" &&
 
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 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 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 fc93aa57e6116d5602b17a63f41bd4b8746289e2..042f557a6fd39c2da29ae357261f35120ee4e9e2 100644 (file)
@@ -1297,6 +1297,11 @@ test_done () {
                EOF
        fi
 
+       if test -z "$passes_sanitize_leak" && test_bool_env TEST_PASSES_SANITIZE_LEAK false
+       then
+               BAIL_OUT "Please, set TEST_PASSES_SANITIZE_LEAK before sourcing test-lib.sh"
+       fi
+
        if test "$test_fixed" != 0
        then
                say_color error "# $test_fixed known breakage(s) vanished; please update test(s)"
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 e34a8f47cfbf5cba150e1aa7c6267fc2b1c080c0..dd6002b393738df81c7ecfc85deb4ed0abe4943c 100644 (file)
@@ -17,6 +17,7 @@
 #include "refspec.h"
 #include "transport-internal.h"
 #include "protocol.h"
+#include "packfile.h"
 
 static int debug;
 
@@ -432,6 +433,8 @@ static int fetch_with_fetch(struct transport *transport,
                        warning(_("%s unexpectedly said: '%s'"), data->name, buf.buf);
        }
        strbuf_release(&buf);
+
+       reprepare_packed_git(the_repository);
        return 0;
 }
 
@@ -626,7 +629,8 @@ static int process_connect_service(struct transport *transport,
                ret = run_connect(transport, &cmdbuf);
        } else if (data->stateless_connect &&
                   (get_protocol_version_config() == protocol_v2) &&
-                  !strcmp("git-upload-pack", name)) {
+                  (!strcmp("git-upload-pack", name) ||
+                   !strcmp("git-upload-archive", name))) {
                strbuf_addf(&cmdbuf, "stateless-connect %s\n", name);
                ret = run_connect(transport, &cmdbuf);
                if (ret)
@@ -643,6 +647,7 @@ static int process_connect(struct transport *transport,
        struct helper_data *data = transport->data;
        const char *name;
        const char *exec;
+       int ret;
 
        name = for_push ? "git-receive-pack" : "git-upload-pack";
        if (for_push)
@@ -650,7 +655,10 @@ static int process_connect(struct transport *transport,
        else
                exec = data->transport_options.uploadpack;
 
-       return process_connect_service(transport, name, exec);
+       ret = process_connect_service(transport, name, exec);
+       if (ret)
+               do_take_over(transport);
+       return ret;
 }
 
 static int connect_helper(struct transport *transport, const char *name,
@@ -660,14 +668,14 @@ static int connect_helper(struct transport *transport, const char *name,
 
        /* Get_helper so connect is inited. */
        get_helper(transport);
-       if (!data->connect)
-               die(_("operation not supported by protocol"));
 
        if (!process_connect_service(transport, name, exec))
                die(_("can't connect to subservice %s"), name);
 
        fd[0] = data->helper->out;
        fd[1] = data->helper->in;
+
+       do_take_over(transport);
        return 0;
 }
 
@@ -682,10 +690,8 @@ static int fetch_refs(struct transport *transport,
 
        get_helper(transport);
 
-       if (process_connect(transport, 0)) {
-               do_take_over(transport);
+       if (process_connect(transport, 0))
                return transport->vtable->fetch_refs(transport, nr_heads, to_fetch);
-       }
 
        /*
         * If we reach here, then the server, the client, and/or the transport
@@ -1142,10 +1148,8 @@ static int push_refs(struct transport *transport,
 {
        struct helper_data *data = transport->data;
 
-       if (process_connect(transport, 1)) {
-               do_take_over(transport);
+       if (process_connect(transport, 1))
                return transport->vtable->push_refs(transport, remote_refs, flags);
-       }
 
        if (!remote_refs) {
                fprintf(stderr,
@@ -1186,11 +1190,9 @@ static struct ref *get_refs_list(struct transport *transport, int for_push,
 {
        get_helper(transport);
 
-       if (process_connect(transport, for_push)) {
-               do_take_over(transport);
+       if (process_connect(transport, for_push))
                return transport->vtable->get_refs_list(transport, for_push,
                                                        transport_options);
-       }
 
        return get_refs_list_using_list(transport, for_push);
 }
@@ -1274,10 +1276,8 @@ static int get_bundle_uri(struct transport *transport)
 {
        get_helper(transport);
 
-       if (process_connect(transport, 0)) {
-               do_take_over(transport);
+       if (process_connect(transport, 0))
                return transport->vtable->get_bundle_uri(transport);
-       }
 
        return -1;
 }
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 dbc670d369588caedafa646401b037675d4f05af..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);
 }
 
@@ -75,8 +80,8 @@ static struct worktree *get_main_worktree(int skip_reading_head)
        return worktree;
 }
 
-static struct worktree *get_linked_worktree(const char *id,
-                                           int skip_reading_head)
+struct worktree *get_linked_worktree(const char *id,
+                                    int skip_reading_head)
 {
        struct worktree *worktree = NULL;
        struct strbuf path = STRBUF_INIT;
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)
  */