aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/check-whitespace.yml67
-rw-r--r--.github/workflows/coverity.yml2
-rw-r--r--.github/workflows/main.yml3
-rw-r--r--.gitlab-ci.yml18
-rw-r--r--Documentation/RelNotes/2.39.4.txt79
-rw-r--r--Documentation/RelNotes/2.40.2.txt7
-rw-r--r--Documentation/RelNotes/2.41.1.txt7
-rw-r--r--Documentation/RelNotes/2.42.2.txt7
-rw-r--r--Documentation/RelNotes/2.43.4.txt7
-rw-r--r--Documentation/RelNotes/2.44.1.txt8
-rw-r--r--Documentation/RelNotes/2.45.1.txt8
-rw-r--r--Documentation/RelNotes/2.46.0.txt89
-rw-r--r--Documentation/SubmittingPatches125
-rw-r--r--Documentation/config.txt3
-rw-r--r--Documentation/fsck-msgids.txt12
-rw-r--r--Documentation/git-config.txt219
-rw-r--r--Documentation/git-gui.txt2
-rw-r--r--Documentation/git-merge-tree.txt5
-rw-r--r--Documentation/git-tag.txt16
-rw-r--r--Documentation/git-update-index.txt1
-rw-r--r--Documentation/git-upload-pack.txt31
-rw-r--r--Documentation/git.txt49
-rw-r--r--Documentation/githooks.txt14
-rw-r--r--Documentation/glossary-content.txt3
-rw-r--r--Makefile11
-rw-r--r--add-interactive.c17
-rw-r--r--advice.c7
-rw-r--r--attr.c55
-rw-r--r--bisect.c25
-rw-r--r--blame.c4
-rw-r--r--branch.c7
-rw-r--r--builtin/am.c38
-rw-r--r--builtin/bisect.c44
-rw-r--r--builtin/blame.c9
-rw-r--r--builtin/branch.c51
-rw-r--r--builtin/bundle.c5
-rw-r--r--builtin/checkout.c35
-rw-r--r--builtin/clone.c87
-rw-r--r--builtin/commit.c20
-rw-r--r--builtin/config.c512
-rw-r--r--builtin/describe.c3
-rw-r--r--builtin/diff.c9
-rw-r--r--builtin/fast-import.c16
-rw-r--r--builtin/fetch.c22
-rw-r--r--builtin/fsck.c11
-rw-r--r--builtin/gc.c3
-rw-r--r--builtin/interpret-trailers.c12
-rw-r--r--builtin/log.c6
-rw-r--r--builtin/merge.c34
-rw-r--r--builtin/name-rev.c5
-rw-r--r--builtin/notes.c26
-rw-r--r--builtin/pack-objects.c10
-rw-r--r--builtin/pull.c2
-rw-r--r--builtin/rebase.c18
-rw-r--r--builtin/receive-pack.c16
-rw-r--r--builtin/reflog.c25
-rw-r--r--builtin/remote.c37
-rw-r--r--builtin/repack.c7
-rw-r--r--builtin/replace.c11
-rw-r--r--builtin/reset.c13
-rw-r--r--builtin/rev-parse.c30
-rw-r--r--builtin/shortlog.c2
-rw-r--r--builtin/show-branch.c22
-rw-r--r--builtin/show-ref.c19
-rw-r--r--builtin/stash.c23
-rw-r--r--builtin/submodule--helper.c93
-rw-r--r--builtin/symbolic-ref.c13
-rw-r--r--builtin/tag.c50
-rw-r--r--builtin/update-index.c2
-rw-r--r--builtin/update-ref.c22
-rw-r--r--builtin/upload-pack.c2
-rw-r--r--builtin/worktree.c21
-rw-r--r--bundle-uri.c12
-rw-r--r--bundle.c2
-rwxr-xr-xci/check-whitespace.sh95
-rwxr-xr-xci/install-dependencies.sh2
-rwxr-xr-xci/lib.sh10
-rwxr-xr-xci/run-build-and-tests.sh2
-rwxr-xr-xci/run-test-slice.sh2
-rw-r--r--color.c21
-rw-r--r--color.h3
-rw-r--r--commit-graph.c3
-rw-r--r--commit.c3
-rw-r--r--compat/mingw.c2
-rw-r--r--compat/precompose_utf8.c10
-rw-r--r--compat/precompose_utf8.h1
-rw-r--r--compat/regex/regcomp.c12
-rw-r--r--compat/regex/regex_internal.c4
-rw-r--r--compat/regex/regexec.c10
-rw-r--r--config.c32
-rw-r--r--config.h2
-rw-r--r--contrib/buildsystems/CMakeLists.txt3
-rw-r--r--contrib/coccinelle/refs.cocci103
-rw-r--r--copy.c61
-rw-r--r--copy.h14
-rw-r--r--delta-islands.c3
-rw-r--r--diff.c1
-rw-r--r--dir.c12
-rw-r--r--dir.h7
-rw-r--r--entry.c16
-rw-r--r--environment.h7
-rw-r--r--fetch-pack.c11
-rw-r--r--fmt-merge-msg.c4
-rw-r--r--fsck.c56
-rw-r--r--fsck.h12
-rw-r--r--git-compat-util.h1
-rwxr-xr-xgit-p4.py24
-rw-r--r--git.c11
-rw-r--r--help.c5
-rw-r--r--hook.c53
-rw-r--r--http-backend.c13
-rw-r--r--log-tree.c9
-rw-r--r--ls-refs.c10
-rw-r--r--mergetools/vimdiff2
-rw-r--r--midx-write.c3
-rw-r--r--negotiator/default.c3
-rw-r--r--negotiator/skipping.c3
-rw-r--r--notes-cache.c6
-rw-r--r--notes-merge.c2
-rw-r--r--notes-utils.c7
-rw-r--r--notes.c5
-rw-r--r--oss-fuzz/fuzz-commit-graph.c1
-rw-r--r--parse-options-cb.c3
-rw-r--r--path.c55
-rw-r--r--path.h1
-rw-r--r--promisor-remote.c10
-rw-r--r--reachable.c5
-rw-r--r--read-cache.c72
-rw-r--r--ref-filter.c35
-rw-r--r--reflog-walk.c27
-rw-r--r--reflog.c20
-rw-r--r--refs.c309
-rw-r--r--refs.h311
-rw-r--r--refs/debug.c13
-rw-r--r--refs/files-backend.c212
-rw-r--r--refs/packed-backend.c1
-rw-r--r--refs/refs-internal.h40
-rw-r--r--refs/reftable-backend.c202
-rw-r--r--remote-curl.c19
-rw-r--r--remote.c38
-rw-r--r--repository.c22
-rw-r--r--reset.c29
-rw-r--r--revision.c27
-rw-r--r--scalar.c10
-rw-r--r--sequencer.c97
-rw-r--r--server-info.c3
-rw-r--r--setup.c146
-rw-r--r--setup.h14
-rw-r--r--shallow.c16
-rw-r--r--strbuf.c1
-rw-r--r--submodule.c95
-rw-r--r--submodule.h5
-rw-r--r--t/Makefile14
-rw-r--r--t/helper/test-example-tap.c (renamed from t/unit-tests/t-basic.c)5
-rw-r--r--t/helper/test-path-utils.c11
-rw-r--r--t/helper/test-ref-store.c2
-rw-r--r--t/helper/test-run-command.c28
-rw-r--r--t/helper/test-tool.c1
-rw-r--r--t/helper/test-tool.h1
-rw-r--r--t/lib-chunk.sh3
-rwxr-xr-xt/run-test.sh18
-rwxr-xr-xt/t0000-basic.sh28
-rwxr-xr-xt/t0003-attributes.sh35
-rwxr-xr-xt/t0018-advice.sh71
-rwxr-xr-xt/t0033-safe-directory.sh24
-rwxr-xr-xt/t0040-parse-options.sh17
-rwxr-xr-xt/t0050-filesystem.sh11
-rwxr-xr-xt/t0060-path-utils.sh41
-rwxr-xr-xt/t0080-unit-test-output.sh24
-rwxr-xr-xt/t0411-clone-from-partial.sh78
-rw-r--r--t/t0450/txt-help-mismatches1
-rwxr-xr-xt/t0600-reffiles-backend.sh2
-rwxr-xr-xt/t1300-config.sh432
-rwxr-xr-xt/t1404-update-ref-errors.sh2
-rwxr-xr-xt/t1416-ref-transaction-hooks.sh23
-rwxr-xr-xt/t1450-fsck.sh37
-rwxr-xr-xt/t1500-rev-parse.sh6
-rwxr-xr-xt/t1700-split-index.sh2
-rwxr-xr-xt/t1800-hook.sh15
-rwxr-xr-xt/t4026-color.sh26
-rwxr-xr-xt/t4046-diff-unmerged.sh8
-rwxr-xr-xt/t4202-log.sh2
-rwxr-xr-xt/t5001-archive-attr.sh3
-rwxr-xr-xt/t5510-fetch.sh40
-rwxr-xr-xt/t5550-http-fetch-dumb.sh15
-rwxr-xr-xt/t5601-clone.sh66
-rwxr-xr-xt/t7004-tag.sh114
-rwxr-xr-xt/t7400-submodule-basic.sh31
-rwxr-xr-xt/t7406-submodule-update.sh48
-rwxr-xr-xt/t7423-submodule-symlinks.sh67
-rwxr-xr-xt/t7450-bad-git-dotfiles.sh34
-rwxr-xr-xt/t9001-send-email.sh2
-rwxr-xr-xt/t9118-git-svn-funky-branch-names.sh2
-rwxr-xr-xt/t9210-scalar.sh38
-rwxr-xr-xt/t9902-completion.sh2
-rw-r--r--t/unit-tests/t-trailer.c315
-rw-r--r--trailer.c179
-rw-r--r--trailer.h102
-rw-r--r--transport-helper.c29
-rw-r--r--transport.c16
-rw-r--r--upload-pack.c20
-rw-r--r--walker.c8
-rw-r--r--wt-status.c22
203 files changed, 5046 insertions, 1903 deletions
diff --git a/.github/workflows/check-whitespace.yml b/.github/workflows/check-whitespace.yml
index a241a63428..d0a78fc426 100644
--- a/.github/workflows/check-whitespace.yml
+++ b/.github/workflows/check-whitespace.yml
@@ -26,66 +26,7 @@ jobs:
- name: git log --check
id: check_out
run: |
- baseSha=${{github.event.pull_request.base.sha}}
- problems=()
- commit=
- commitText=
- commitTextmd=
- goodparent=
- while read dash sha etc
- do
- case "${dash}" in
- "---")
- if test -z "${commit}"
- then
- goodparent=${sha}
- fi
- commit="${sha}"
- commitText="${sha} ${etc}"
- commitTextmd="[${sha}](https://github.com/${{ github.repository }}/commit/${sha}) ${etc}"
- ;;
- "")
- ;;
- *)
- if test -n "${commit}"
- then
- problems+=("1) --- ${commitTextmd}")
- echo ""
- echo "--- ${commitText}"
- commit=
- fi
- case "${dash}" in
- *:[1-9]*:) # contains file and line number information
- dashend=${dash#*:}
- problems+=("[${dash}](https://github.com/${{ github.repository }}/blob/${{github.event.pull_request.head.ref}}/${dash%%:*}#L${dashend%:}) ${sha} ${etc}")
- ;;
- *)
- problems+=("\`${dash} ${sha} ${etc}\`")
- ;;
- esac
- echo "${dash} ${sha} ${etc}"
- ;;
- esac
- done <<< $(git log --check --pretty=format:"---% h% s" ${baseSha}..)
-
- if test ${#problems[*]} -gt 0
- then
- if test -z "${commit}"
- then
- goodparent=${baseSha: 0:7}
- fi
- echo "🛑 Please review the Summary output for further information."
- echo "### :x: A whitespace issue was found in one or more of the commits." >$GITHUB_STEP_SUMMARY
- echo "" >>$GITHUB_STEP_SUMMARY
- echo "Run these commands to correct the problem:" >>$GITHUB_STEP_SUMMARY
- echo "1. \`git rebase --whitespace=fix ${goodparent}\`" >>$GITHUB_STEP_SUMMARY
- echo "1. \`git push --force\`" >>$GITHUB_STEP_SUMMARY
- echo " " >>$GITHUB_STEP_SUMMARY
- echo "Errors:" >>$GITHUB_STEP_SUMMARY
- for i in "${problems[@]}"
- do
- echo "${i}" >>$GITHUB_STEP_SUMMARY
- done
-
- exit 2
- fi
+ ./ci/check-whitespace.sh \
+ "${{github.event.pull_request.base.sha}}" \
+ "$GITHUB_STEP_SUMMARY" \
+ "https://github.com/${{github.repository}}"
diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml
index 53cf12fe04..48341e81f4 100644
--- a/.github/workflows/coverity.yml
+++ b/.github/workflows/coverity.yml
@@ -45,7 +45,7 @@ jobs:
- run: ci/install-dependencies.sh
if: contains(matrix.os, 'ubuntu') || contains(matrix.os, 'macos')
env:
- runs_on_pool: ${{ matrix.os }}
+ distro: ${{ matrix.os }}
# The Coverity site says the tool is usually updated twice yearly, so the
# MD5 of download can be used to determine whether there's been an update.
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 5838986895..13cc0fe807 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -284,8 +284,7 @@ jobs:
cc: clang
pool: macos-13
- jobname: osx-gcc
- cc: gcc
- cc_package: gcc-13
+ cc: gcc-13
pool: macos-13
- jobname: linux-gcc-default
cc: gcc
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ba65f50aac..f676959ca0 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -95,6 +95,15 @@ test:osx:
- t/failed-test-artifacts
when: on_failure
+test:fuzz-smoke-tests:
+ image: ubuntu:latest
+ variables:
+ CC: clang
+ before_script:
+ - ./ci/install-dependencies.sh
+ script:
+ - ./ci/run-build-and-minimal-fuzzers.sh
+
static-analysis:
image: ubuntu:22.04
variables:
@@ -104,3 +113,12 @@ static-analysis:
script:
- ./ci/run-static-analysis.sh
- ./ci/check-directional-formatting.bash
+
+check-whitespace:
+ image: ubuntu:latest
+ before_script:
+ - ./ci/install-dependencies.sh
+ script:
+ - ./ci/check-whitespace.sh "$CI_MERGE_REQUEST_TARGET_BRANCH_SHA"
+ rules:
+ - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
diff --git a/Documentation/RelNotes/2.39.4.txt b/Documentation/RelNotes/2.39.4.txt
new file mode 100644
index 0000000000..7f54521fea
--- /dev/null
+++ b/Documentation/RelNotes/2.39.4.txt
@@ -0,0 +1,79 @@
+Git v2.39.4 Release Notes
+=========================
+
+This addresses the security issues CVE-2024-32002, CVE-2024-32004,
+CVE-2024-32020 and CVE-2024-32021.
+
+This release also backports fixes necessary to let the CI builds pass
+successfully.
+
+Fixes since v2.39.3
+-------------------
+
+ * CVE-2024-32002:
+
+ Recursive clones on case-insensitive filesystems that support symbolic
+ links are susceptible to case confusion that can be exploited to
+ execute just-cloned code during the clone operation.
+
+ * CVE-2024-32004:
+
+ Repositories can be configured to execute arbitrary code during local
+ clones. To address this, the ownership checks introduced in v2.30.3
+ are now extended to cover cloning local repositories.
+
+ * CVE-2024-32020:
+
+ Local clones may end up hardlinking files into the target repository's
+ object database when source and target repository reside on the same
+ disk. If the source repository is owned by a different user, then
+ those hardlinked files may be rewritten at any point in time by the
+ untrusted user.
+
+ * CVE-2024-32021:
+
+ When cloning a local source repository that contains symlinks via the
+ filesystem, Git may create hardlinks to arbitrary user-readable files
+ on the same filesystem as the target repository in the objects/
+ directory.
+
+ * CVE-2024-32465:
+
+ It is supposed to be safe to clone untrusted repositories, even those
+ unpacked from zip archives or tarballs originating from untrusted
+ sources, but Git can be tricked to run arbitrary code as part of the
+ clone.
+
+ * Defense-in-depth: submodule: require the submodule path to contain
+ directories only.
+
+ * Defense-in-depth: clone: when symbolic links collide with directories, keep
+ the latter.
+
+ * Defense-in-depth: clone: prevent hooks from running during a clone.
+
+ * Defense-in-depth: core.hooksPath: add some protection while cloning.
+
+ * Defense-in-depth: fsck: warn about symlink pointing inside a gitdir.
+
+ * Various fix-ups on HTTP tests.
+
+ * Test update.
+
+ * HTTP Header redaction code has been adjusted for a newer version of
+ cURL library that shows its traces differently from earlier
+ versions.
+
+ * Fix was added to work around a regression in libcURL 8.7.0 (which has
+ already been fixed in their tip of the tree).
+
+ * Replace macos-12 used at GitHub CI with macos-13.
+
+ * ci(linux-asan/linux-ubsan): let's save some time
+
+ * Tests with LSan from time to time seem to emit harmless message that makes
+ our tests unnecessarily flakey; we work it around by filtering the
+ uninteresting output.
+
+ * Update GitHub Actions jobs to avoid warnings against using deprecated
+ version of Node.js.
diff --git a/Documentation/RelNotes/2.40.2.txt b/Documentation/RelNotes/2.40.2.txt
new file mode 100644
index 0000000000..646a2cc3eb
--- /dev/null
+++ b/Documentation/RelNotes/2.40.2.txt
@@ -0,0 +1,7 @@
+Git v2.40.2 Release Notes
+=========================
+
+This release merges up the fix that appears in v2.39.4 to address
+the security issues CVE-2024-32002, CVE-2024-32004, CVE-2024-32020,
+CVE-2024-32021 and CVE-2024-32465; see the release notes for that
+version for details.
diff --git a/Documentation/RelNotes/2.41.1.txt b/Documentation/RelNotes/2.41.1.txt
new file mode 100644
index 0000000000..9fb4c218b2
--- /dev/null
+++ b/Documentation/RelNotes/2.41.1.txt
@@ -0,0 +1,7 @@
+Git v2.41.1 Release Notes
+=========================
+
+This release merges up the fix that appears in v2.39.4 and v2.40.2
+to address the security issues CVE-2024-32002, CVE-2024-32004,
+CVE-2024-32020, CVE-2024-32021 and CVE-2024-32465; see the release
+notes for these versions for details.
diff --git a/Documentation/RelNotes/2.42.2.txt b/Documentation/RelNotes/2.42.2.txt
new file mode 100644
index 0000000000..dbf761a01d
--- /dev/null
+++ b/Documentation/RelNotes/2.42.2.txt
@@ -0,0 +1,7 @@
+Git v2.42.2 Release Notes
+=========================
+
+This release merges up the fix that appears in v2.39.4, v2.40.2
+and v2.41.1 to address the security issues CVE-2024-32002,
+CVE-2024-32004, CVE-2024-32020, CVE-2024-32021 and CVE-2024-32465;
+see the release notes for these versions for details.
diff --git a/Documentation/RelNotes/2.43.4.txt b/Documentation/RelNotes/2.43.4.txt
new file mode 100644
index 0000000000..0a842515ff
--- /dev/null
+++ b/Documentation/RelNotes/2.43.4.txt
@@ -0,0 +1,7 @@
+Git v2.43.4 Release Notes
+=========================
+
+This release merges up the fix that appears in v2.39.4, v2.40.2,
+v2.41.1 and v2.42.2 to address the security issues CVE-2024-32002,
+CVE-2024-32004, CVE-2024-32020, CVE-2024-32021 and CVE-2024-32465;
+see the release notes for these versions for details.
diff --git a/Documentation/RelNotes/2.44.1.txt b/Documentation/RelNotes/2.44.1.txt
new file mode 100644
index 0000000000..b5135c3281
--- /dev/null
+++ b/Documentation/RelNotes/2.44.1.txt
@@ -0,0 +1,8 @@
+Git v2.44.1 Release Notes
+=========================
+
+This release merges up the fix that appears in v2.39.4, v2.40.2,
+v2.41.1, v2.42.2 and v2.43.4 to address the security issues
+CVE-2024-32002, CVE-2024-32004, CVE-2024-32020, CVE-2024-32021
+and CVE-2024-32465; see the release notes for these versions
+for details.
diff --git a/Documentation/RelNotes/2.45.1.txt b/Documentation/RelNotes/2.45.1.txt
new file mode 100644
index 0000000000..3b0d60cfa3
--- /dev/null
+++ b/Documentation/RelNotes/2.45.1.txt
@@ -0,0 +1,8 @@
+Git v2.45.1 Release Notes
+=========================
+
+This release merges up the fix that appears in v2.39.4,
+v2.40.2, v2.41.1, v2.42.2, v2.43.4 and v2.44.1 to address the
+security issues CVE-2024-32002, CVE-2024-32004, CVE-2024-32020,
+CVE-2024-32021 and CVE-2024-32465; see the release notes for
+these versions for details.
diff --git a/Documentation/RelNotes/2.46.0.txt b/Documentation/RelNotes/2.46.0.txt
index 5838476df1..a65261fd7e 100644
--- a/Documentation/RelNotes/2.46.0.txt
+++ b/Documentation/RelNotes/2.46.0.txt
@@ -10,7 +10,32 @@ UI, Workflows & Features
* The "--rfc" option of "git format-patch" learned to take an
optional string value to be used in place of "RFC" to tweak the
"[PATCH]" on the subject header.
- (merge ce36894509 jc/format-patch-rfc-more later to maint).
+
+ * The credential helper protocol, together with the HTTP layer, have
+ been enhanced to support authentication schemes different from
+ username & password pair, like Bearer and NTLM.
+
+ * Command line completion script (in contrib/) learned to complete
+ "git symbolic-ref" a bit better (you need to enable plumbing
+ commands to be completed with GIT_COMPLETION_SHOW_ALL_COMMANDS).
+
+ * When the user responds to a prompt given by "git add -p" with an
+ unsupported command, list of available commands were given, which
+ was too much if the user knew what they wanted to type but merely
+ made a typo. Now the user gets a much shorter error message.
+
+ * The color parsing code learned to handle 12-bit RGB colors, spelled
+ as "#RGB" (in addition to "#RRGGBB" that is already supported).
+
+ * The operation mode options (like "--get") the "git config" command
+ uses have been deprecated and replaced with subcommands (like "git
+ config get").
+
+ * "git tag" learned the "--trailer" option to futz with the trailers
+ in the same way as "git commit" does.
+
+ * A new global "--no-advice" option can be used to disable all advice
+ messages, which is meant to be used only in scripts.
Performance, Internal Implementation, Development Support etc.
@@ -23,6 +48,29 @@ Performance, Internal Implementation, Development Support etc.
that are used in fuzzer tests, to make sure at least they build
without bitrot, in Linux CI runs.
+ * Code to write out reftable has seen some optimization and
+ simplification.
+
+ * Tests to ensure interoperability between reftable written by jgit
+ and our code have been added and enabled in CI.
+
+ * The singleton index_state instance "the_index" has been eliminated
+ by always instantiating "the_repository" and replacing references
+ to "the_index" with references to its .index member.
+
+ * Git-GUI has a new maintainer, Johannes Sixt.
+ (merge e18ad8eb26 jc/git-gui-maintainer-update later to maint).
+
+ * The "test-tool" has been taught to run testsuite tests in parallel,
+ bypassing the need to use the "prove" tool.
+
+ * The "whitespace check" task that was enabled for GitHub Actions CI
+ has been ported to GitLab CI.
+
+ * The refs API lost functions that implicitly assumes to work on the
+ primary ref_store by forcing the callers to pass a ref_store as an
+ argument.
+
Fixes since v2.45
-----------------
@@ -52,4 +100,43 @@ Fixes since v2.45
errored out. Now it keeps going.
(merge c75662bfc9 js/for-each-repo-keep-going later to maint).
+ * zsh can pretend to be a normal shell pretty well except for some
+ glitches that we tickle in some of our scripts. Work them around
+ so that "vimdiff" and our test suite works well enough with it.
+ (merge fedd5c79ff bc/zsh-compatibility later to maint).
+
+ * Command line completion support for zsh (in contrib/) has been
+ updated to stop exposing internal state to end-user shell
+ interaction.
+ (merge 3c20acdf46 dk/zsh-git-repo-path-fix later to maint).
+
+ * Tests that try to corrupt in-repository files in chunked format did
+ not work well on macOS due to its broken "mv", which has been
+ worked around.
+ (merge 861dc19ba8 jc/test-workaround-broken-mv later to maint).
+
+ * The maximum size of attribute files is enforced more consistently.
+ (merge c793f9cb08 tb/attr-limits later to maint).
+
+ * Unbreak CI jobs so that we do not attempt to use Python 2 that has
+ been removed from the platform.
+ (merge 5ca0c455f1 ps/ci-python-2-deprecation later to maint).
+
+ * Git 2.43 started using the tree of HEAD as the source of attributes
+ in a bare repository, which has severe performance implications.
+ For now, revert the change, without ripping out a more explicit
+ support for the attr.tree configuration variable.
+ (merge 51441e6460 jc/no-default-attr-tree-in-bare later to maint).
+
+ * The "--exit-code" option of "git diff" command learned to work with
+ the "--ext-diff" option.
+ (merge 11be65cfa4 rs/external-diff-with-exit-code later to maint).
+
* Other code cleanup, docfix, build fix, etc.
+ (merge 4cf6e7bf5e jt/doc-submitting-rerolled-series later to maint).
+ (merge a5a4cb7b27 rs/diff-parseopts-cleanup later to maint).
+ (merge 395c130fd8 ma/win32-unix-domain-socket later to maint).
+ (merge 7df2405b38 jk/ci-macos-gcc13-fix later to maint).
+ (merge 55702c543e fa/p4-error later to maint).
+ (merge 2566a77774 vd/doc-merge-tree-x-option later to maint).
+ (merge b64b0df9da ds/scalar-reconfigure-all-fix later to maint).
diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
index 384893be1c..20f4311e54 100644
--- a/Documentation/SubmittingPatches
+++ b/Documentation/SubmittingPatches
@@ -7,6 +7,73 @@ Here are some guidelines for contributing back to this
project. There is also a link:MyFirstContribution.html[step-by-step tutorial]
available which covers many of these same guidelines.
+[[patch-flow]]
+=== A typical life cycle of a patch series
+
+To help us understand the reason behind various guidelines given later
+in the document, first let's understand how the life cycle of a
+typical patch series for this project goes.
+
+. You come up with an itch. You code it up. You do not need any
+ pre-authorization from the project to do so.
++
+Your patches will be reviewed by other contributors on the mailing
+list, and the reviews will be done to assess the merit of various
+things, like the general idea behind your patch (including "is it
+solving a problem worth solving in the first place?"), the reason
+behind the design of the solution, and the actual implementation.
+The guidelines given here are there to help your patches by making
+them easier to understand by the reviewers.
+
+. You send the patches to the list and cc people who may need to know
+ about the change. Your goal is *not* necessarily to convince others
+ that what you are building is good. Your goal is to get help in
+ coming up with a solution for the "itch" that is better than what
+ you can build alone.
++
+The people who may need to know are the ones who worked on the code
+you are touching. These people happen to be the ones who are
+most likely to be knowledgeable enough to help you, but
+they have no obligation to help you (i.e. you ask them for help,
+you don't demand). +git log -p {litdd} _$area_you_are_modifying_+ would
+help you find out who they are.
+
+. You get comments and suggestions for improvements. You may even get
+ them in an "on top of your change" patch form. You are expected to
+ respond to them with "Reply-All" on the mailing list, while taking
+ them into account while preparing an updated set of patches.
+
+. Polish, refine, and re-send your patches to the list and to the people
+ who spent their time to improve your patch. Go back to step (2).
+
+. While the above iterations improve your patches, the maintainer may
+ pick the patches up from the list and queue them to the `seen`
+ branch, in order to make it easier for people to play with it
+ without having to pick up and apply the patches to their trees
+ themselves. Being in `seen` has no other meaning. Specifically, it
+ does not mean the patch was "accepted" in any way.
+
+. When the discussion reaches a consensus that the latest iteration of
+ the patches are in good enough shape, the maintainer includes the
+ topic in the "What's cooking" report that are sent out a few times a
+ week to the mailing list, marked as "Will merge to 'next'." This
+ decision is primarily made by the maintainer with help from those
+ who participated in the review discussion.
+
+. After the patches are merged to the 'next' branch, the discussion
+ can still continue to further improve them by adding more patches on
+ top, but by the time a topic gets merged to 'next', it is expected
+ that everybody agrees that the scope and the basic direction of the
+ topic are appropriate, so such an incremental updates are limited to
+ small corrections and polishing. After a topic cooks for some time
+ (like 7 calendar days) in 'next' without needing further tweaks on
+ top, it gets merged to the 'master' branch and wait to become part
+ of the next major release.
+
+In the following sections, many techniques and conventions are listed
+to help your patches get reviewed effectively in such a life cycle.
+
+
[[choose-starting-point]]
=== Choose a starting point.
@@ -192,8 +259,9 @@ reasons:
which case, they can explain why they extend your code to cover
files, too).
-The goal of your log message is to convey the _why_ behind your
-change to help future developers.
+The goal of your log message is to convey the _why_ behind your change
+to help future developers. The reviewers will also make sure that
+your proposed log message will serve this purpose well.
The first line of the commit message should be a short description (50
characters is the soft limit, see DISCUSSION in linkgit:git-commit[1]),
@@ -545,9 +613,9 @@ not a text/plain, it's something else.
Some parts of the system have dedicated maintainers with their own
repositories.
-- `git-gui/` comes from git-gui project, maintained by Pratyush Yadav:
+- `git-gui/` comes from git-gui project, maintained by Johannes Sixt:
- https://github.com/prati0100/git-gui.git
+ https://github.com/j6t/git-gui
- `gitk-git/` comes from Paul Mackerras's gitk project:
@@ -562,55 +630,6 @@ repositories.
Patches to these parts should be based on their trees.
-[[patch-flow]]
-== An ideal patch flow
-
-Here is an ideal patch flow for this project the current maintainer
-suggests to the contributors:
-
-. You come up with an itch. You code it up.
-
-. Send it to the list and cc people who may need to know about
- the change.
-+
-The people who may need to know are the ones whose code you
-are butchering. These people happen to be the ones who are
-most likely to be knowledgeable enough to help you, but
-they have no obligation to help you (i.e. you ask for help,
-don't demand). +git log -p {litdd} _$area_you_are_modifying_+ would
-help you find out who they are.
-
-. You get comments and suggestions for improvements. You may
- even get them in an "on top of your change" patch form.
-
-. Polish, refine, and re-send to the list and the people who
- spend their time to improve your patch. Go back to step (2).
-
-. The list forms consensus that the last round of your patch is
- good. Send it to the maintainer and cc the list.
-
-. A topic branch is created with the patch and is merged to `next`,
- and cooked further and eventually graduates to `master`.
-
-In any time between the (2)-(3) cycle, the maintainer may pick it up
-from the list and queue it to `seen`, in order to make it easier for
-people to play with it without having to pick up and apply the patch to
-their trees themselves.
-
-[[patch-status]]
-== Know the status of your patch after submission
-
-* You can use Git itself to find out when your patch is merged in
- master. `git pull --rebase` will automatically skip already-applied
- patches, and will let you know. This works only if you rebase on top
- of the branch in which your patch has been merged (i.e. it will not
- tell you if your patch is merged in `seen` if you rebase on top of
- master).
-
-* Read the Git mailing list, the maintainer regularly posts messages
- entitled "What's cooking in git.git" giving
- the status of various proposed changes.
-
== GitHub CI[[GHCI]]
With an account at GitHub, you can use GitHub CI to test your changes
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 70b448b132..6f649c997c 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -316,7 +316,8 @@ terminals, this is usually not the same as setting to "white black".
Colors may also be given as numbers between 0 and 255; these use ANSI
256-color mode (but note that not all terminals may support this). If
your terminal supports it, you may also specify 24-bit RGB values as
-hex, like `#ff0ab3`.
+hex, like `#ff0ab3`, or 12-bit RGB values like `#f1b`, which is
+equivalent to the 24-bit color `#ff11bb`.
+
The accepted attributes are `bold`, `dim`, `ul`, `blink`, `reverse`,
`italic`, and `strike` (for crossed-out or "strikethrough" letters).
diff --git a/Documentation/fsck-msgids.txt b/Documentation/fsck-msgids.txt
index f643585a34..5edc06c658 100644
--- a/Documentation/fsck-msgids.txt
+++ b/Documentation/fsck-msgids.txt
@@ -164,6 +164,18 @@
`nullSha1`::
(WARN) Tree contains entries pointing to a null sha1.
+`symlinkPointsToGitDir`::
+ (WARN) Symbolic link points inside a gitdir.
+
+`symlinkTargetBlob`::
+ (ERROR) A non-blob found instead of a symbolic link's target.
+
+`symlinkTargetLength`::
+ (WARN) Symbolic link target longer than maximum path length.
+
+`symlinkTargetMissing`::
+ (ERROR) Unable to read symbolic link target's blob.
+
`treeNotSorted`::
(ERROR) A tree is not properly sorted.
diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt
index ac61113fcc..65c645d461 100644
--- a/Documentation/git-config.txt
+++ b/Documentation/git-config.txt
@@ -9,21 +9,14 @@ git-config - Get and set repository or global options
SYNOPSIS
--------
[verse]
-'git config' [<file-option>] [--type=<type>] [--comment=<message>] [--fixed-value] [--show-origin] [--show-scope] [-z|--null] <name> [<value> [<value-pattern>]]
-'git config' [<file-option>] [--type=<type>] [--comment=<message>] --add <name> <value>
-'git config' [<file-option>] [--type=<type>] [--comment=<message>] [--fixed-value] --replace-all <name> <value> [<value-pattern>]
-'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] [--fixed-value] --get <name> [<value-pattern>]
-'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] [--fixed-value] --get-all <name> [<value-pattern>]
-'git config' [<file-option>] [--type=<type>] [--show-origin] [--show-scope] [-z|--null] [--fixed-value] [--name-only] --get-regexp <name-regex> [<value-pattern>]
-'git config' [<file-option>] [--type=<type>] [-z|--null] --get-urlmatch <name> <URL>
-'git config' [<file-option>] [--fixed-value] --unset <name> [<value-pattern>]
-'git config' [<file-option>] [--fixed-value] --unset-all <name> [<value-pattern>]
-'git config' [<file-option>] --rename-section <old-name> <new-name>
-'git config' [<file-option>] --remove-section <name>
-'git config' [<file-option>] [--show-origin] [--show-scope] [-z|--null] [--name-only] -l | --list
-'git config' [<file-option>] --get-color <name> [<default>]
+'git config list' [<file-option>] [<display-option>] [--includes]
+'git config get' [<file-option>] [<display-option>] [--includes] [--all] [--regexp=<regexp>] [--value=<value>] [--fixed-value] [--default=<default>] <name>
+'git config set' [<file-option>] [--type=<type>] [--all] [--value=<value>] [--fixed-value] <name> <value>
+'git config unset' [<file-option>] [--all] [--value=<value>] [--fixed-value] <name> <value>
+'git config rename-section' [<file-option>] <old-name> <new-name>
+'git config remove-section' [<file-option>] <name>
+'git config edit' [<file-option>]
'git config' [<file-option>] --get-colorbool <name> [<stdout-is-tty>]
-'git config' [<file-option>] -e | --edit
DESCRIPTION
-----------
@@ -31,7 +24,7 @@ You can query/set/replace/unset options with this command. The name is
actually the section and the key separated by a dot, and the value will be
escaped.
-Multiple lines can be added to an option by using the `--add` option.
+Multiple lines can be added to an option by using the `--append` option.
If you want to update or unset an option which can occur on multiple
lines, a `value-pattern` (which is an extended regular expression,
unless the `--fixed-value` option is given) needs to be given. Only the
@@ -74,6 +67,42 @@ On success, the command returns the exit code 0.
A list of all available configuration variables can be obtained using the
`git help --config` command.
+COMMANDS
+--------
+
+list::
+ List all variables set in config file, along with their values.
+
+get::
+ Emits the value of the specified key. If key is present multiple times
+ in the configuration, emits the last value. If `--all` is specified,
+ emits all values associated with key. Returns error code 1 if key is
+ not present.
+
+set::
+ Set value for one or more config options. By default, this command
+ refuses to write multi-valued config options. Passing `--all` will
+ replace all multi-valued config options with the new value, whereas
+ `--value=` will replace all config options whose values match the given
+ pattern.
+
+unset::
+ Unset value for one or more config options. By default, this command
+ refuses to unset multi-valued keys. Passing `--all` will unset all
+ multi-valued config options, whereas `--value` will unset all config
+ options whose values match the given pattern.
+
+rename-section::
+ Rename the given section to a new name.
+
+remove-section::
+ Remove the given section from the configuration file.
+
+edit::
+ Opens an editor to modify the specified config file; either
+ `--system`, `--global`, `--local` (default), `--worktree`, or
+ `--file <config-file>`.
+
[[OPTIONS]]
OPTIONS
-------
@@ -82,10 +111,9 @@ OPTIONS
Default behavior is to replace at most one line. This replaces
all lines matching the key (and optionally the `value-pattern`).
---add::
+--append::
Adds a new line to the option without altering any existing
- values. This is the same as providing '^$' as the `value-pattern`
- in `--replace-all`.
+ values. This is the same as providing '--value=^$' in `set`.
--comment <message>::
Append a comment at the end of new or modified lines.
@@ -99,22 +127,16 @@ OPTIONS
not contain linefeed characters (no multi-line comments are
permitted).
---get::
- Get the value for a given key (optionally filtered by a regex
- matching the value). Returns error code 1 if the key was not
- found and the last value if multiple key values were found.
+--all::
+ With `get`, return all values for a multi-valued key.
---get-all::
- Like get, but returns all values for a multi-valued key.
+---regexp::
+ With `get`, interpret the name as a regular expression. Regular
+ expression matching is currently case-sensitive and done against a
+ canonicalized version of the key in which section and variable names
+ are lowercased, but subsection names are not.
---get-regexp::
- Like --get-all, but interprets the name as a regular expression and
- writes out the key names. Regular expression matching is currently
- case-sensitive and done against a canonicalized version of the key
- in which section and variable names are lowercased, but subsection
- names are not.
-
---get-urlmatch <name> <URL>::
+--url=<URL>::
When given a two-part <name> as <section>.<key>, the value for
<section>.<URL>.<key> whose <URL> part matches the best to the
given URL is returned (if no such key exists, the value for
@@ -178,22 +200,6 @@ See also <<FILES>>.
section in linkgit:gitrevisions[7] for a more complete list of
ways to spell blob names.
---remove-section::
- Remove the given section from the configuration file.
-
---rename-section::
- Rename the given section to a new name.
-
---unset::
- Remove the line matching the key from config file.
-
---unset-all::
- Remove all lines matching the key from config file.
-
--l::
---list::
- List all variables set in config file, along with their values.
-
--fixed-value::
When used with the `value-pattern` argument, treat `value-pattern` as
an exact string instead of a regular expression. This will restrict
@@ -248,8 +254,8 @@ Valid `<type>`'s include:
contain line breaks.
--name-only::
- Output only the names of config variables for `--list` or
- `--get-regexp`.
+ Output only the names of config variables for `list` or
+ `get`.
--show-origin::
Augment the output of all queried config options with the
@@ -273,23 +279,6 @@ Valid `<type>`'s include:
When the color setting for `name` is undefined, the command uses
`color.ui` as fallback.
---get-color <name> [<default>]::
-
- Find the color configured for `name` (e.g. `color.diff.new`) and
- output it as the ANSI color escape sequence to the standard
- output. The optional `default` parameter is used instead, if
- there is no color configured for `name`.
-+
-`--type=color [--default=<default>]` is preferred over `--get-color`
-(but note that `--get-color` will omit the trailing newline printed by
-`--type=color`).
-
--e::
---edit::
- Opens an editor to modify the specified config file; either
- `--system`, `--global`, `--local` (default), `--worktree`, or
- `--file <config-file>`.
-
--[no-]includes::
Respect `include.*` directives in config files when looking up
values. Defaults to `off` when a specific file is given (e.g.,
@@ -297,14 +286,64 @@ Valid `<type>`'s include:
config files.
--default <value>::
- When using `--get`, and the requested variable is not found, behave as if
+ When using `get`, and the requested variable is not found, behave as if
<value> were the value assigned to that variable.
+DEPRECATED MODES
+----------------
+
+The following modes have been deprecated in favor of subcommands. It is
+recommended to migrate to the new syntax.
+
+'git config <name>'::
+ Replaced by `git config get <name>`.
+
+'git config <name> <value> [<value-pattern>]'::
+ Replaced by `git config set [--value=<pattern>] <name> <value>`.
+
+-l::
+--list::
+ Replaced by `git config list`.
+
+--get <name> [<value-pattern>]::
+ Replaced by `git config get [--value=<pattern>] <name>`.
+
+--get-all <name> [<value-pattern>]::
+ Replaced by `git config get [--value=<pattern>] --all --show-names <name>`.
+
+--get-regexp <name-regexp>::
+ Replaced by `git config get --all --show-names --regexp <name-regexp>`.
+
+--get-urlmatch <name> <URL>::
+ Replaced by `git config get --all --show-names --url=<URL> <name>`.
+
+--get-color <name> [<default>]::
+ Replaced by `git config get --type=color [--default=<default>] <name>`.
+
+--add <name> <value>::
+ Replaced by `git config set --append <name> <value>`.
+
+--unset <name> [<value-pattern>]::
+ Replaced by `git config unset [--value=<pattern>] <name>`.
+
+--unset-all <name> [<value-pattern>]::
+ Replaced by `git config unset [--value=<pattern>] --all <name>`.
+
+--rename-section <old-name> <new-name>::
+ Replaced by `git config rename-section <old-name> <new-name>`.
+
+--remove-section <name>::
+ Replaced by `git config remove-section <name>`.
+
+-e::
+--edit::
+ Replaced by `git config edit`.
+
CONFIGURATION
-------------
`pager.config` is only respected when listing configuration, i.e., when
-using `--list` or any of the `--get-*` which may return multiple results.
-The default is to use a pager.
+using `list` or `get` which may return multiple results. The default is to use
+a pager.
[[FILES]]
FILES
@@ -346,8 +385,8 @@ precedence over values read earlier. When multiple values are taken then all
values of a key from all files will be used.
By default, options are only written to the repository specific
-configuration file. Note that this also affects options like `--replace-all`
-and `--unset`. *'git config' will only ever change one file at a time*.
+configuration file. Note that this also affects options like `set`
+and `unset`. *'git config' will only ever change one file at a time*.
You can limit which configuration sources are read from or written to by
specifying the path of a file with the `--file` option, or by specifying a
@@ -482,7 +521,7 @@ Given a .git/config like this:
you can set the filemode to true with
------------
-% git config core.filemode true
+% git config set core.filemode true
------------
The hypothetical proxy command entries actually have a postfix to discern
@@ -490,7 +529,7 @@ what URL they apply to. Here is how to change the entry for kernel.org
to "ssh".
------------
-% git config core.gitproxy '"ssh" for kernel.org' 'for kernel.org$'
+% git config set --value='for kernel.org$' core.gitproxy '"ssh" for kernel.org'
------------
This makes sure that only the key/value pair for kernel.org is replaced.
@@ -498,7 +537,7 @@ This makes sure that only the key/value pair for kernel.org is replaced.
To delete the entry for renames, do
------------
-% git config --unset diff.renames
+% git config unset diff.renames
------------
If you want to delete an entry for a multivar (like core.gitproxy above),
@@ -507,51 +546,45 @@ you have to provide a regex matching the value of exactly one line.
To query the value for a given key, do
------------
-% git config --get core.filemode
-------------
-
-or
-
-------------
-% git config core.filemode
+% git config get core.filemode
------------
or, to query a multivar:
------------
-% git config --get core.gitproxy "for kernel.org$"
+% git config get --value="for kernel.org$" core.gitproxy
------------
If you want to know all the values for a multivar, do:
------------
-% git config --get-all core.gitproxy
+% git config get --all --show-names core.gitproxy
------------
If you like to live dangerously, you can replace *all* core.gitproxy by a
new one with
------------
-% git config --replace-all core.gitproxy ssh
+% git config set --all core.gitproxy ssh
------------
However, if you really only want to replace the line for the default proxy,
i.e. the one without a "for ..." postfix, do something like this:
------------
-% git config core.gitproxy ssh '! for '
+% git config set --value='! for ' core.gitproxy ssh
------------
To actually match only values with an exclamation mark, you have to
------------
-% git config section.key value '[!]'
+% git config set --value='[!]' section.key value
------------
To add a new proxy, without altering any of the existing ones, use
------------
-% git config --add core.gitproxy '"proxy-command" for example.com'
+% git config set --append core.gitproxy '"proxy-command" for example.com'
------------
An example to use customized color from the configuration in your
@@ -559,8 +592,8 @@ script:
------------
#!/bin/sh
-WS=$(git config --get-color color.diff.whitespace "blue reverse")
-RESET=$(git config --get-color "" "reset")
+WS=$(git config get --type=color --default="blue reverse" color.diff.whitespace)
+RESET=$(git config get --type=color --default="reset" "")
echo "${WS}your whitespace color or blue reverse${RESET}"
------------
@@ -568,11 +601,11 @@ For URLs in `https://weak.example.com`, `http.sslVerify` is set to
false, while it is set to `true` for all others:
------------
-% git config --type=bool --get-urlmatch http.sslverify https://good.example.com
+% git config get --type=bool --url=https://good.example.com http.sslverify
true
-% git config --type=bool --get-urlmatch http.sslverify https://weak.example.com
+% git config get --type=bool --url=https://weak.example.com http.sslverify
false
-% git config --get-urlmatch http https://weak.example.com
+% git config get --url=https://weak.example.com http
http.cookieFile /tmp/cookie.txt
http.sslverify false
------------
diff --git a/Documentation/git-gui.txt b/Documentation/git-gui.txt
index e8f3ccb433..f5b02ef114 100644
--- a/Documentation/git-gui.txt
+++ b/Documentation/git-gui.txt
@@ -114,7 +114,7 @@ of end users.
The official repository of the 'git gui' project can be found at:
- https://github.com/prati0100/git-gui.git/
+ https://github.com/j6t/git-gui
GIT
---
diff --git a/Documentation/git-merge-tree.txt b/Documentation/git-merge-tree.txt
index dd388fa21d..84cb2edf6d 100644
--- a/Documentation/git-merge-tree.txt
+++ b/Documentation/git-merge-tree.txt
@@ -72,6 +72,11 @@ OPTIONS
As the merge-base is provided directly, <branch1> and <branch2> do not need
to specify commits; trees are enough.
+-X<option>::
+--strategy-option=<option>::
+ Pass the merge strategy-specific option through to the merge strategy.
+ See linkgit:git-merge[1] for details.
+
[[OUTPUT]]
OUTPUT
------
diff --git a/Documentation/git-tag.txt b/Documentation/git-tag.txt
index 5fe519c31e..4494729f5e 100644
--- a/Documentation/git-tag.txt
+++ b/Documentation/git-tag.txt
@@ -10,6 +10,7 @@ SYNOPSIS
--------
[verse]
'git tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]
+ [(--trailer <token>[(=|:)<value>])...]
<tagname> [<commit> | <object>]
'git tag' -d <tagname>...
'git tag' [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]
@@ -31,8 +32,8 @@ creates a 'tag' object, and requires a tag message. Unless
`-m <msg>` or `-F <file>` is given, an editor is started for the user to type
in the tag message.
-If `-m <msg>` or `-F <file>` is given and `-a`, `-s`, and `-u <key-id>`
-are absent, `-a` is implied.
+If `-m <msg>` or `-F <file>` or `--trailer <token>[=<value>]` is given
+and `-a`, `-s`, and `-u <key-id>` are absent, `-a` is implied.
Otherwise, a tag reference that points directly at the given object
(i.e., a lightweight tag) is created.
@@ -178,6 +179,17 @@ This option is only applicable when listing tags without annotation lines.
Implies `-a` if none of `-a`, `-s`, or `-u <key-id>`
is given.
+--trailer <token>[(=|:)<value>]::
+ Specify a (<token>, <value>) pair that should be applied as a
+ trailer. (e.g. `git tag --trailer "Custom-Key: value"`
+ will add a "Custom-Key" trailer to the tag message.)
+ The `trailer.*` configuration variables
+ (linkgit:git-interpret-trailers[1]) can be used to define if
+ a duplicated trailer is omitted, where in the run of trailers
+ each trailer would appear, and other details.
+ The trailers can be extracted in `git tag --list`, using
+ `--format="%(trailers)"` placeholder.
+
-e::
--edit::
The message taken from file with `-F` and command line with
diff --git a/Documentation/git-update-index.txt b/Documentation/git-update-index.txt
index 8c47890a6a..7128aed540 100644
--- a/Documentation/git-update-index.txt
+++ b/Documentation/git-update-index.txt
@@ -25,6 +25,7 @@ SYNOPSIS
[--really-refresh] [--unresolve] [--again | -g]
[--info-only] [--index-info]
[-z] [--stdin] [--index-version <n>]
+ [--show-index-version]
[--verbose]
[--] [<file>...]
diff --git a/Documentation/git-upload-pack.txt b/Documentation/git-upload-pack.txt
index 7ad60bc348..516d1639d9 100644
--- a/Documentation/git-upload-pack.txt
+++ b/Documentation/git-upload-pack.txt
@@ -55,6 +55,37 @@ ENVIRONMENT
admins may need to configure some transports to allow this
variable to be passed. See the discussion in linkgit:git[1].
+`GIT_NO_LAZY_FETCH`::
+ When cloning or fetching from a partial repository (i.e., one
+ itself cloned with `--filter`), the server-side `upload-pack`
+ may need to fetch extra objects from its upstream in order to
+ complete the request. By default, `upload-pack` will refuse to
+ perform such a lazy fetch, because `git fetch` may run arbitrary
+ commands specified in configuration and hooks of the source
+ repository (and `upload-pack` tries to be safe to run even in
+ untrusted `.git` directories).
++
+This is implemented by having `upload-pack` internally set the
+`GIT_NO_LAZY_FETCH` variable to `1`. If you want to override it
+(because you are fetching from a partial clone, and you are sure
+you trust it), you can explicitly set `GIT_NO_LAZY_FETCH` to
+`0`.
+
+SECURITY
+--------
+
+Most Git commands should not be run in an untrusted `.git` directory
+(see the section `SECURITY` in linkgit:git[1]). `upload-pack` tries to
+avoid any dangerous configuration options or hooks from the repository
+it's serving, making it safe to clone an untrusted directory and run
+commands on the resulting clone.
+
+For an extra level of safety, you may be able to run `upload-pack` as an
+alternate user. The details will be platform dependent, but on many
+systems you can run:
+
+ git clone --no-local --upload-pack='sudo -u nobody git-upload-pack' ...
+
SEE ALSO
--------
linkgit:gitnamespaces[7]
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 7a1b112a3e..a31a70acca 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -11,9 +11,10 @@ SYNOPSIS
[verse]
'git' [-v | --version] [-h | --help] [-C <path>] [-c <name>=<value>]
[--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
- [-p|--paginate|-P|--no-pager] [--no-replace-objects] [--bare]
- [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
- [--config-env=<name>=<envvar>] <command> [<args>]
+ [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--no-lazy-fetch]
+ [--no-optional-locks] [--no-advice] [--bare] [--git-dir=<path>]
+ [--work-tree=<path>] [--namespace=<name>] [--config-env=<name>=<envvar>]
+ <command> [<args>]
DESCRIPTION
-----------
@@ -186,6 +187,13 @@ If you just want to run git as if it was started in `<path>` then use
This is equivalent to setting the `GIT_NO_LAZY_FETCH`
environment variable to `1`.
+--no-optional-locks::
+ Do not perform optional operations that require locks. This is
+ equivalent to setting the `GIT_OPTIONAL_LOCKS` to `0`.
+
+--no-advice::
+ Disable all advice hints from being printed.
+
--literal-pathspecs::
Treat pathspecs literally (i.e. no globbing, no pathspec magic).
This is equivalent to setting the `GIT_LITERAL_PATHSPECS` environment
@@ -207,10 +215,6 @@ If you just want to run git as if it was started in `<path>` then use
Add "icase" magic to all pathspec. This is equivalent to setting
the `GIT_ICASE_PATHSPECS` environment variable to `1`.
---no-optional-locks::
- Do not perform optional operations that require locks. This is
- equivalent to setting the `GIT_OPTIONAL_LOCKS` to `0`.
-
--list-cmds=<group>[,<group>...]::
List commands by group. This is an internal/experimental
option and may change or be removed in the future. Supported
@@ -1067,6 +1071,37 @@ The index is also capable of storing multiple entries (called "stages")
for a given pathname. These stages are used to hold the various
unmerged version of a file when a merge is in progress.
+SECURITY
+--------
+
+Some configuration options and hook files may cause Git to run arbitrary
+shell commands. Because configuration and hooks are not copied using
+`git clone`, it is generally safe to clone remote repositories with
+untrusted content, inspect them with `git log`, and so on.
+
+However, it is not safe to run Git commands in a `.git` directory (or
+the working tree that surrounds it) when that `.git` directory itself
+comes from an untrusted source. The commands in its config and hooks
+are executed in the usual way.
+
+By default, Git will refuse to run when the repository is owned by
+someone other than the user running the command. See the entry for
+`safe.directory` in linkgit:git-config[1]. While this can help protect
+you in a multi-user environment, note that you can also acquire
+untrusted repositories that are owned by you (for example, if you
+extract a zip file or tarball from an untrusted source). In such cases,
+you'd need to "sanitize" the untrusted repository first.
+
+If you have an untrusted `.git` directory, you should first clone it
+with `git clone --no-local` to obtain a clean copy. Git does restrict
+the set of options and hooks that will be run by `upload-pack`, which
+handles the server side of a clone or fetch, but beware that the
+surface area for attack against `upload-pack` is large, so this does
+carry some risk. The safest thing is to serve the repository as an
+unprivileged user (either via linkgit:git-daemon[1], ssh, or using
+other tools to change user ids). See the discussion in the `SECURITY`
+section of linkgit:git-upload-pack[1].
+
FURTHER DOCUMENTATION
---------------------
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index ee9b92c90d..06e997131b 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -486,7 +486,7 @@ reference-transaction
This hook is invoked by any Git command that performs reference
updates. It executes whenever a reference transaction is prepared,
committed or aborted and may thus get called multiple times. The hook
-does not cover symbolic references (but that may change in the future).
+also supports symbolic reference updates.
The hook takes exactly one argument, which is the current state the
given reference transaction is in:
@@ -503,16 +503,20 @@ given reference transaction is in:
For each reference update that was added to the transaction, the hook
receives on standard input a line of the format:
- <old-oid> SP <new-oid> SP <ref-name> LF
+ <old-value> SP <new-value> SP <ref-name> LF
-where `<old-oid>` is the old object name passed into the reference
-transaction, `<new-oid>` is the new object name to be stored in the
+where `<old-value>` is the old object name passed into the reference
+transaction, `<new-value>` is the new object name to be stored in the
ref and `<ref-name>` is the full name of the ref. When force updating
the reference regardless of its current value or when the reference is
-to be created anew, `<old-oid>` is the all-zeroes object name. To
+to be created anew, `<old-value>` is the all-zeroes object name. To
distinguish these cases, you can inspect the current value of
`<ref-name>` via `git rev-parse`.
+For symbolic reference updates the `<old_value>` and `<new-value>`
+fields could denote references instead of objects. A reference will be
+denoted with a 'ref:' prefix, like `ref:<ref-target>`.
+
The exit status of the hook is ignored for any state except for the
"prepared" state. In the "prepared" state, a non-zero exit status will
cause the transaction to be aborted. The hook will not be called with
diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt
index d71b199955..1272809e13 100644
--- a/Documentation/glossary-content.txt
+++ b/Documentation/glossary-content.txt
@@ -576,7 +576,8 @@ The most notable example is `HEAD`.
[[def_refspec]]refspec::
A "refspec" is used by <<def_fetch,fetch>> and
<<def_push,push>> to describe the mapping between remote
- <<def_ref,ref>> and local ref.
+ <<def_ref,ref>> and local ref. See linkgit:git-fetch[1] or
+ linkgit:git-push[1] for details.
[[def_remote]]remote repository::
A <<def_repository,repository>> which is used to track the same
diff --git a/Makefile b/Makefile
index 0285db5630..8f4432ae57 100644
--- a/Makefile
+++ b/Makefile
@@ -794,6 +794,7 @@ TEST_BUILTINS_OBJS += test-dump-split-index.o
TEST_BUILTINS_OBJS += test-dump-untracked-cache.o
TEST_BUILTINS_OBJS += test-env-helper.o
TEST_BUILTINS_OBJS += test-example-decorate.o
+TEST_BUILTINS_OBJS += test-example-tap.o
TEST_BUILTINS_OBJS += test-find-pack.o
TEST_BUILTINS_OBJS += test-fsmonitor-client.o
TEST_BUILTINS_OBJS += test-genrandom.o
@@ -1333,11 +1334,11 @@ THIRD_PARTY_SOURCES += compat/regex/%
THIRD_PARTY_SOURCES += sha1collisiondetection/%
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-mem-pool
UNIT_TEST_PROGRAMS += t-prio-queue
+UNIT_TEST_PROGRAMS += t-strbuf
+UNIT_TEST_PROGRAMS += t-trailer
UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
UNIT_TEST_OBJS = $(patsubst %,$(UNIT_TEST_DIR)/%.o,$(UNIT_TEST_PROGRAMS))
UNIT_TEST_OBJS += $(UNIT_TEST_DIR)/test-lib.o
@@ -3235,7 +3236,7 @@ perf: all
.PRECIOUS: $(TEST_OBJS)
-t/helper/test-tool$X: $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS))
+t/helper/test-tool$X: $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS)) $(UNIT_TEST_DIR)/test-lib.o
t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS) $(REFTABLE_TEST_LIB)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)
@@ -3888,5 +3889,5 @@ $(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o $(UNIT_TEST_DIR)/
.PHONY: build-unit-tests unit-tests
build-unit-tests: $(UNIT_TEST_PROGS)
-unit-tests: $(UNIT_TEST_PROGS)
+unit-tests: $(UNIT_TEST_PROGS) t/helper/test-tool$X
$(MAKE) -C t/ unit-tests
diff --git a/add-interactive.c b/add-interactive.c
index e17602b5e4..b5d6cd689a 100644
--- a/add-interactive.c
+++ b/add-interactive.c
@@ -532,8 +532,9 @@ static int get_modified_files(struct repository *r,
size_t *binary_count)
{
struct object_id head_oid;
- int is_initial = !resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
- &head_oid, NULL);
+ int is_initial = !refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ "HEAD", RESOLVE_REF_READING,
+ &head_oid, NULL);
struct collection_status s = { 0 };
int i;
@@ -761,8 +762,10 @@ static int run_revert(struct add_i_state *s, const struct pathspec *ps,
size_t count, i, j;
struct object_id oid;
- int is_initial = !resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &oid,
- NULL);
+ int is_initial = !refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ "HEAD", RESOLVE_REF_READING,
+ &oid,
+ NULL);
struct lock_file index_lock;
const char **paths;
struct tree *tree;
@@ -990,8 +993,10 @@ static int run_diff(struct add_i_state *s, const struct pathspec *ps,
ssize_t count, i;
struct object_id oid;
- int is_initial = !resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &oid,
- NULL);
+ int is_initial = !refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ "HEAD", RESOLVE_REF_READING,
+ &oid,
+ NULL);
if (get_modified_files(s->r, INDEX_ONLY, files, ps, NULL, NULL) < 0)
return -1;
diff --git a/advice.c b/advice.c
index 75111191ad..0a122c2020 100644
--- a/advice.c
+++ b/advice.c
@@ -2,6 +2,7 @@
#include "advice.h"
#include "config.h"
#include "color.h"
+#include "environment.h"
#include "gettext.h"
#include "help.h"
#include "string-list.h"
@@ -127,6 +128,12 @@ void advise(const char *advice, ...)
int advice_enabled(enum advice_type type)
{
int enabled = advice_setting[type].level != ADVICE_LEVEL_DISABLED;
+ static int globally_enabled = -1;
+
+ if (globally_enabled < 0)
+ globally_enabled = git_env_bool(GIT_ADVICE_ENVIRONMENT, 1);
+ if (!globally_enabled)
+ return 0;
if (type == ADVICE_PUSH_UPDATE_REJECTED)
return enabled &&
diff --git a/attr.c b/attr.c
index 679e42258c..f3dd2de12d 100644
--- a/attr.c
+++ b/attr.c
@@ -765,8 +765,8 @@ static struct attr_stack *read_attr_from_file(const char *path, unsigned flags)
return res;
}
-static struct attr_stack *read_attr_from_buf(char *buf, const char *path,
- unsigned flags)
+static struct attr_stack *read_attr_from_buf(char *buf, size_t length,
+ const char *path, unsigned flags)
{
struct attr_stack *res;
char *sp;
@@ -774,6 +774,11 @@ static struct attr_stack *read_attr_from_buf(char *buf, const char *path,
if (!buf)
return NULL;
+ if (length >= ATTR_MAX_FILE_SIZE) {
+ warning(_("ignoring overly large gitattributes blob '%s'"), path);
+ free(buf);
+ return NULL;
+ }
CALLOC_ARRAY(res, 1);
for (sp = buf; *sp;) {
@@ -813,7 +818,7 @@ static struct attr_stack *read_attr_from_blob(struct index_state *istate,
return NULL;
}
- return read_attr_from_buf(buf, path, flags);
+ return read_attr_from_buf(buf, sz, path, flags);
}
static struct attr_stack *read_attr_from_index(struct index_state *istate,
@@ -860,13 +865,7 @@ static struct attr_stack *read_attr_from_index(struct index_state *istate,
stack = read_attr_from_blob(istate, &istate->cache[sparse_dir_pos]->oid, relative_path, flags);
} else {
buf = read_blob_data_from_index(istate, path, &size);
- if (!buf)
- return NULL;
- if (size >= ATTR_MAX_FILE_SIZE) {
- warning(_("ignoring overly large gitattributes blob '%s'"), path);
- return NULL;
- }
- stack = read_attr_from_buf(buf, path, flags);
+ stack = read_attr_from_buf(buf, size, path, flags);
}
return stack;
}
@@ -1206,15 +1205,16 @@ static void collect_some_attrs(struct index_state *istate,
}
static const char *default_attr_source_tree_object_name;
-static int ignore_bad_attr_tree;
void set_git_attr_source(const char *tree_object_name)
{
default_attr_source_tree_object_name = xstrdup(tree_object_name);
}
-static void compute_default_attr_source(struct object_id *attr_source)
+static int compute_default_attr_source(struct object_id *attr_source)
{
+ int ignore_bad_attr_tree = 0;
+
if (!default_attr_source_tree_object_name)
default_attr_source_tree_object_name = getenv(GIT_ATTR_SOURCE_ENVIRONMENT);
@@ -1223,29 +1223,34 @@ static void compute_default_attr_source(struct object_id *attr_source)
ignore_bad_attr_tree = 1;
}
- if (!default_attr_source_tree_object_name &&
- startup_info->have_repository &&
- is_bare_repository()) {
- default_attr_source_tree_object_name = "HEAD";
- ignore_bad_attr_tree = 1;
- }
+ if (!default_attr_source_tree_object_name)
+ return 0;
- if (!default_attr_source_tree_object_name || !is_null_oid(attr_source))
- return;
+ if (!startup_info->have_repository) {
+ if (!ignore_bad_attr_tree)
+ die(_("cannot use --attr-source or GIT_ATTR_SOURCE without repo"));
+ return 0;
+ }
if (repo_get_oid_treeish(the_repository,
default_attr_source_tree_object_name,
- attr_source) && !ignore_bad_attr_tree)
- die(_("bad --attr-source or GIT_ATTR_SOURCE"));
+ attr_source)) {
+ if (!ignore_bad_attr_tree)
+ die(_("bad --attr-source or GIT_ATTR_SOURCE"));
+ return 0;
+ }
+
+ return 1;
}
static struct object_id *default_attr_source(void)
{
static struct object_id attr_source;
+ static int has_attr_source = -1;
- if (is_null_oid(&attr_source))
- compute_default_attr_source(&attr_source);
- if (is_null_oid(&attr_source))
+ if (has_attr_source < 0)
+ has_attr_source = compute_default_attr_source(&attr_source);
+ if (!has_attr_source)
return NULL;
return &attr_source;
}
diff --git a/bisect.c b/bisect.c
index 29aae879b8..4ea703bec1 100644
--- a/bisect.c
+++ b/bisect.c
@@ -469,7 +469,8 @@ static int register_ref(const char *refname, const struct object_id *oid,
static int read_bisect_refs(void)
{
- return for_each_ref_in("refs/bisect/", register_ref, NULL);
+ return refs_for_each_ref_in(get_main_ref_store(the_repository),
+ "refs/bisect/", register_ref, NULL);
}
static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES")
@@ -709,7 +710,7 @@ static enum bisect_error error_if_skipped_commits(struct commit_list *tried,
static int is_expected_rev(const struct object_id *oid)
{
struct object_id expected_oid;
- if (read_ref("BISECT_EXPECTED_REV", &expected_oid))
+ if (refs_read_ref(get_main_ref_store(the_repository), "BISECT_EXPECTED_REV", &expected_oid))
return 0;
return oideq(oid, &expected_oid);
}
@@ -721,11 +722,14 @@ enum bisect_error bisect_checkout(const struct object_id *bisect_rev,
struct pretty_print_context pp = {0};
struct strbuf commit_msg = STRBUF_INIT;
- update_ref(NULL, "BISECT_EXPECTED_REV", bisect_rev, NULL, 0, UPDATE_REFS_DIE_ON_ERR);
+ refs_update_ref(get_main_ref_store(the_repository), NULL,
+ "BISECT_EXPECTED_REV", bisect_rev, NULL, 0,
+ UPDATE_REFS_DIE_ON_ERR);
if (no_checkout) {
- update_ref(NULL, "BISECT_HEAD", bisect_rev, NULL, 0,
- UPDATE_REFS_DIE_ON_ERR);
+ refs_update_ref(get_main_ref_store(the_repository), NULL,
+ "BISECT_HEAD", bisect_rev, NULL, 0,
+ UPDATE_REFS_DIE_ON_ERR);
} else {
struct child_process cmd = CHILD_PROCESS_INIT;
@@ -1027,7 +1031,8 @@ enum bisect_error bisect_next_all(struct repository *r, const char *prefix)
* If no_checkout is non-zero, the bisection process does not
* checkout the trial commit but instead simply updates BISECT_HEAD.
*/
- int no_checkout = ref_exists("BISECT_HEAD");
+ int no_checkout = refs_ref_exists(get_main_ref_store(the_repository),
+ "BISECT_HEAD");
unsigned bisect_flags = 0;
read_bisect_terms(&term_bad, &term_good);
@@ -1178,10 +1183,14 @@ int bisect_clean_state(void)
/* There may be some refs packed during bisection */
struct string_list refs_for_removal = STRING_LIST_INIT_NODUP;
- for_each_ref_in("refs/bisect", mark_for_removal, (void *) &refs_for_removal);
+ refs_for_each_ref_in(get_main_ref_store(the_repository),
+ "refs/bisect", mark_for_removal,
+ (void *) &refs_for_removal);
string_list_append(&refs_for_removal, xstrdup("BISECT_HEAD"));
string_list_append(&refs_for_removal, xstrdup("BISECT_EXPECTED_REV"));
- result = delete_refs("bisect: remove", &refs_for_removal, REF_NO_DEREF);
+ result = refs_delete_refs(get_main_ref_store(the_repository),
+ "bisect: remove", &refs_for_removal,
+ REF_NO_DEREF);
refs_for_removal.strdup_strings = 1;
string_list_clear(&refs_for_removal, 0);
unlink_or_warn(git_path_bisect_ancestors_ok());
diff --git a/blame.c b/blame.c
index 1a16d4eb6a..33586b9777 100644
--- a/blame.c
+++ b/blame.c
@@ -2700,7 +2700,7 @@ static struct commit *dwim_reverse_initial(struct rev_info *revs,
return NULL;
/* Do we have HEAD? */
- if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
+ if (!refs_resolve_ref_unsafe(get_main_ref_store(the_repository), "HEAD", RESOLVE_REF_READING, &head_oid, NULL))
return NULL;
head_commit = lookup_commit_reference_gently(revs->repo,
&head_oid, 1);
@@ -2803,7 +2803,7 @@ void setup_scoreboard(struct blame_scoreboard *sb,
if (sb->final) {
parent_oid = &sb->final->object.oid;
} else {
- if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
+ if (!refs_resolve_ref_unsafe(get_main_ref_store(the_repository), "HEAD", RESOLVE_REF_READING, &head_oid, NULL))
die("no such ref: HEAD");
parent_oid = &head_oid;
}
diff --git a/branch.c b/branch.c
index e4a738fc7b..df5d24fec6 100644
--- a/branch.c
+++ b/branch.c
@@ -377,7 +377,7 @@ int validate_branchname(const char *name, struct strbuf *ref)
exit(code);
}
- return ref_exists(ref->buf);
+ return refs_ref_exists(get_main_ref_store(the_repository), ref->buf);
}
static int initialized_checked_out_branches;
@@ -623,11 +623,12 @@ void create_branch(struct repository *r,
msg = xstrfmt("branch: Reset to %s", start_name);
else
msg = xstrfmt("branch: Created from %s", start_name);
- transaction = ref_transaction_begin(&err);
+ transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
+ &err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf,
&oid, forcing ? NULL : null_oid(),
- 0, msg, &err) ||
+ NULL, NULL, 0, msg, &err) ||
ref_transaction_commit(transaction, &err))
die("%s", err.buf);
ref_transaction_free(transaction);
diff --git a/builtin/am.c b/builtin/am.c
index 4db2bc3c2f..36839029d2 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1001,7 +1001,8 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
if (mkdir(state->dir, 0777) < 0 && errno != EEXIST)
die_errno(_("failed to create directory '%s'"), state->dir);
- delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
+ refs_delete_ref(get_main_ref_store(the_repository), NULL,
+ "REBASE_HEAD", NULL, REF_NO_DEREF);
if (split_mail(state, patch_format, paths, keep_cr) < 0) {
am_destroy(state);
@@ -1081,12 +1082,15 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
if (!repo_get_oid(the_repository, "HEAD", &curr_head)) {
write_state_text(state, "abort-safety", oid_to_hex(&curr_head));
if (!state->rebasing)
- update_ref("am", "ORIG_HEAD", &curr_head, NULL, 0,
- UPDATE_REFS_DIE_ON_ERR);
+ refs_update_ref(get_main_ref_store(the_repository),
+ "am", "ORIG_HEAD", &curr_head, NULL,
+ 0,
+ UPDATE_REFS_DIE_ON_ERR);
} else {
write_state_text(state, "abort-safety", "");
if (!state->rebasing)
- delete_ref(NULL, "ORIG_HEAD", NULL, 0);
+ refs_delete_ref(get_main_ref_store(the_repository),
+ NULL, "ORIG_HEAD", NULL, 0);
}
/*
@@ -1119,7 +1123,8 @@ static void am_next(struct am_state *state)
oidclr(&state->orig_commit);
unlink(am_path(state, "original-commit"));
- delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
+ refs_delete_ref(get_main_ref_store(the_repository), NULL,
+ "REBASE_HEAD", NULL, REF_NO_DEREF);
if (!repo_get_oid(the_repository, "HEAD", &head))
write_state_text(state, "abort-safety", oid_to_hex(&head));
@@ -1466,8 +1471,9 @@ static int parse_mail_rebase(struct am_state *state, const char *mail)
oidcpy(&state->orig_commit, &commit_oid);
write_state_text(state, "original-commit", oid_to_hex(&commit_oid));
- update_ref("am", "REBASE_HEAD", &commit_oid,
- NULL, REF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR);
+ refs_update_ref(get_main_ref_store(the_repository), "am",
+ "REBASE_HEAD", &commit_oid,
+ NULL, REF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR);
return 0;
}
@@ -1697,8 +1703,9 @@ static void do_commit(const struct am_state *state)
strbuf_addf(&sb, "%s: %.*s", reflog_msg, linelen(state->msg),
state->msg);
- update_ref(sb.buf, "HEAD", &commit, old_oid, 0,
- UPDATE_REFS_DIE_ON_ERR);
+ refs_update_ref(get_main_ref_store(the_repository), sb.buf, "HEAD",
+ &commit, old_oid, 0,
+ UPDATE_REFS_DIE_ON_ERR);
if (state->rebasing) {
FILE *fp = xfopen(am_path(state, "rewritten"), "a");
@@ -2175,7 +2182,8 @@ static void am_abort(struct am_state *state)
am_rerere_clear();
- curr_branch = resolve_refdup("HEAD", 0, &curr_head, NULL);
+ curr_branch = refs_resolve_refdup(get_main_ref_store(the_repository),
+ "HEAD", 0, &curr_head, NULL);
has_curr_head = curr_branch && !is_null_oid(&curr_head);
if (!has_curr_head)
oidcpy(&curr_head, the_hash_algo->empty_tree);
@@ -2188,11 +2196,13 @@ static void am_abort(struct am_state *state)
die(_("failed to clean index"));
if (has_orig_head)
- update_ref("am --abort", "HEAD", &orig_head,
- has_curr_head ? &curr_head : NULL, 0,
- UPDATE_REFS_DIE_ON_ERR);
+ refs_update_ref(get_main_ref_store(the_repository),
+ "am --abort", "HEAD", &orig_head,
+ has_curr_head ? &curr_head : NULL, 0,
+ UPDATE_REFS_DIE_ON_ERR);
else if (curr_branch)
- delete_ref(NULL, curr_branch, NULL, REF_NO_DEREF);
+ refs_delete_ref(get_main_ref_store(the_repository), NULL,
+ curr_branch, NULL, REF_NO_DEREF);
free(curr_branch);
am_destroy(state);
diff --git a/builtin/bisect.c b/builtin/bisect.c
index f69c3f7e43..a58432b9d9 100644
--- a/builtin/bisect.c
+++ b/builtin/bisect.c
@@ -243,7 +243,7 @@ static int bisect_reset(const char *commit)
strbuf_addstr(&branch, commit);
}
- if (branch.len && !ref_exists("BISECT_HEAD")) {
+ if (branch.len && !refs_ref_exists(get_main_ref_store(the_repository), "BISECT_HEAD")) {
struct child_process cmd = CHILD_PROCESS_INIT;
cmd.git_cmd = 1;
@@ -302,8 +302,8 @@ static int bisect_write(const char *state, const char *rev,
goto finish;
}
- if (update_ref(NULL, tag.buf, &oid, NULL, 0,
- UPDATE_REFS_MSG_ON_ERR)) {
+ if (refs_update_ref(get_main_ref_store(the_repository), NULL, tag.buf, &oid, NULL, 0,
+ UPDATE_REFS_MSG_ON_ERR)) {
res = -1;
goto finish;
}
@@ -416,11 +416,12 @@ static void bisect_status(struct bisect_state *state,
char *bad_ref = xstrfmt("refs/bisect/%s", terms->term_bad);
char *good_glob = xstrfmt("%s-*", terms->term_good);
- if (ref_exists(bad_ref))
+ if (refs_ref_exists(get_main_ref_store(the_repository), bad_ref))
state->nr_bad = 1;
- for_each_glob_ref_in(inc_nr, good_glob, "refs/bisect/",
- (void *) &state->nr_good);
+ refs_for_each_glob_ref_in(get_main_ref_store(the_repository), inc_nr,
+ good_glob, "refs/bisect/",
+ (void *) &state->nr_good);
free(good_glob);
free(bad_ref);
@@ -574,9 +575,11 @@ static int prepare_revs(struct bisect_terms *terms, struct rev_info *revs)
reset_revision_walk();
repo_init_revisions(the_repository, revs, NULL);
setup_revisions(0, NULL, revs, NULL);
- for_each_glob_ref_in(add_bisect_ref, bad, "refs/bisect/", &cb);
+ refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
+ add_bisect_ref, bad, "refs/bisect/", &cb);
cb.object_flags = UNINTERESTING;
- for_each_glob_ref_in(add_bisect_ref, good, "refs/bisect/", &cb);
+ refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
+ add_bisect_ref, good, "refs/bisect/", &cb);
if (prepare_revision_walk(revs))
res = error(_("revision walk setup failed\n"));
@@ -636,7 +639,7 @@ static int bisect_successful(struct bisect_terms *terms)
char *bad_ref = xstrfmt("refs/bisect/%s",terms->term_bad);
int res;
- read_ref(bad_ref, &oid);
+ refs_read_ref(get_main_ref_store(the_repository), bad_ref, &oid);
commit = lookup_commit_reference_by_name(bad_ref);
repo_format_commit_message(the_repository, commit, "%s", &commit_name,
&pp);
@@ -779,7 +782,8 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
/*
* Verify HEAD
*/
- head = resolve_ref_unsafe("HEAD", 0, &head_oid, &flags);
+ head = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ "HEAD", 0, &head_oid, &flags);
if (!head)
if (repo_get_oid(the_repository, "HEAD", &head_oid))
return error(_("bad HEAD - I need a HEAD"));
@@ -838,8 +842,8 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,
res = error(_("invalid ref: '%s'"), start_head.buf);
goto finish;
}
- if (update_ref(NULL, "BISECT_HEAD", &oid, NULL, 0,
- UPDATE_REFS_MSG_ON_ERR)) {
+ if (refs_update_ref(get_main_ref_store(the_repository), NULL, "BISECT_HEAD", &oid, NULL, 0,
+ UPDATE_REFS_MSG_ON_ERR)) {
res = BISECT_FAILED;
goto finish;
}
@@ -972,7 +976,7 @@ static enum bisect_error bisect_state(struct bisect_terms *terms, int argc,
oid_array_append(&revs, &commit->object.oid);
}
- if (read_ref("BISECT_EXPECTED_REV", &expected))
+ if (refs_read_ref(get_main_ref_store(the_repository), "BISECT_EXPECTED_REV", &expected))
verify_expected = 0; /* Ignore invalid file contents */
for (i = 0; i < revs.nr; i++) {
@@ -982,7 +986,9 @@ static enum bisect_error bisect_state(struct bisect_terms *terms, int argc,
}
if (verify_expected && !oideq(&revs.oid[i], &expected)) {
unlink_or_warn(git_path_bisect_ancestors_ok());
- delete_ref(NULL, "BISECT_EXPECTED_REV", NULL, REF_NO_DEREF);
+ refs_delete_ref(get_main_ref_store(the_repository),
+ NULL, "BISECT_EXPECTED_REV", NULL,
+ REF_NO_DEREF);
verify_expected = 0;
}
}
@@ -1179,13 +1185,15 @@ static int verify_good(const struct bisect_terms *terms, const char *command)
struct object_id good_rev;
struct object_id current_rev;
char *good_glob = xstrfmt("%s-*", terms->term_good);
- int no_checkout = ref_exists("BISECT_HEAD");
+ int no_checkout = refs_ref_exists(get_main_ref_store(the_repository),
+ "BISECT_HEAD");
- for_each_glob_ref_in(get_first_good, good_glob, "refs/bisect/",
- &good_rev);
+ refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
+ get_first_good, good_glob, "refs/bisect/",
+ &good_rev);
free(good_glob);
- if (read_ref(no_checkout ? "BISECT_HEAD" : "HEAD", &current_rev))
+ if (refs_read_ref(get_main_ref_store(the_repository), no_checkout ? "BISECT_HEAD" : "HEAD", &current_rev))
return -1;
res = bisect_checkout(&good_rev, no_checkout);
diff --git a/builtin/blame.c b/builtin/blame.c
index 9aa74680a3..fadba1a530 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -915,7 +915,6 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
struct range_set ranges;
unsigned int range_i;
long anchor;
- const int hexsz = the_hash_algo->hexsz;
long num_lines = 0;
const char *str_usage = cmd_is_annotate ? annotate_usage : blame_usage;
const char **opt_usage = cmd_is_annotate ? annotate_opt_usage : blame_opt_usage;
@@ -973,11 +972,11 @@ parse_done:
} else if (show_progress < 0)
show_progress = isatty(2);
- if (0 < abbrev && abbrev < hexsz)
+ if (0 < abbrev && abbrev < (int)the_hash_algo->hexsz)
/* one more abbrev length is needed for the boundary commit */
abbrev++;
else if (!abbrev)
- abbrev = hexsz;
+ abbrev = the_hash_algo->hexsz;
if (revs_file && read_ancestry(revs_file))
die_errno("reading graft file '%s' failed", revs_file);
@@ -1093,8 +1092,8 @@ parse_done:
struct commit *head_commit;
struct object_id head_oid;
- if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
- &head_oid, NULL) ||
+ if (!refs_resolve_ref_unsafe(get_main_ref_store(the_repository), "HEAD", RESOLVE_REF_READING,
+ &head_oid, NULL) ||
!(head_commit = lookup_commit_reference_gently(revs.repo,
&head_oid, 1)))
die("no such ref: HEAD");
diff --git a/builtin/branch.c b/builtin/branch.c
index dd3e3a7dc0..48cac74f97 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -148,8 +148,8 @@ static int branch_merged(int kind, const char *name,
if (upstream &&
(reference_name = reference_name_to_free =
- resolve_refdup(upstream, RESOLVE_REF_READING,
- &oid, NULL)) != NULL)
+ refs_resolve_refdup(get_main_ref_store(the_repository), upstream, RESOLVE_REF_READING,
+ &oid, NULL)) != NULL)
reference_rev = lookup_commit_reference(the_repository,
&oid);
}
@@ -272,21 +272,24 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
}
}
- target = resolve_refdup(name,
- RESOLVE_REF_READING
- | RESOLVE_REF_NO_RECURSE
- | RESOLVE_REF_ALLOW_BAD_NAME,
- &oid, &flags);
+ target = refs_resolve_refdup(get_main_ref_store(the_repository),
+ name,
+ RESOLVE_REF_READING
+ | RESOLVE_REF_NO_RECURSE
+ | RESOLVE_REF_ALLOW_BAD_NAME,
+ &oid, &flags);
if (!target) {
if (remote_branch) {
error(_("remote-tracking branch '%s' not found"), bname.buf);
} else {
char *virtual_name = mkpathdup(fmt_remotes, bname.buf);
- char *virtual_target = resolve_refdup(virtual_name,
- RESOLVE_REF_READING
- | RESOLVE_REF_NO_RECURSE
- | RESOLVE_REF_ALLOW_BAD_NAME,
- &oid, &flags);
+ char *virtual_target = refs_resolve_refdup(get_main_ref_store(the_repository),
+ virtual_name,
+ RESOLVE_REF_READING
+ | RESOLVE_REF_NO_RECURSE
+ | RESOLVE_REF_ALLOW_BAD_NAME,
+ &oid,
+ &flags);
FREE_AND_NULL(virtual_name);
if (virtual_target)
@@ -317,13 +320,13 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
free(target);
}
- if (delete_refs(NULL, &refs_to_delete, REF_NO_DEREF))
+ if (refs_delete_refs(get_main_ref_store(the_repository), NULL, &refs_to_delete, REF_NO_DEREF))
ret = 1;
for_each_string_list_item(item, &refs_to_delete) {
char *describe_ref = item->util;
char *name = item->string;
- if (!ref_exists(name)) {
+ if (!refs_ref_exists(get_main_ref_store(the_repository), name)) {
char *refname = name + branch_name_pos;
if (!quiet)
printf(remote_branch
@@ -499,7 +502,8 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
static void print_current_branch_name(void)
{
int flags;
- const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, &flags);
+ const char *refname = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ "HEAD", 0, NULL, &flags);
const char *shortname;
if (!refname)
die(_("could not resolve HEAD"));
@@ -555,7 +559,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,
continue;
refs = get_worktree_ref_store(worktrees[i]);
- if (refs_create_symref(refs, "HEAD", newref, logmsg))
+ if (refs_update_symref(refs, "HEAD", newref, logmsg))
ret = error(_("HEAD of working tree %s is not updated"),
worktrees[i]->path);
}
@@ -580,7 +584,7 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
* Bad name --- this could be an attempt to rename a
* ref that we used to allow to be created by accident.
*/
- if (ref_exists(oldref.buf))
+ if (refs_ref_exists(get_main_ref_store(the_repository), oldref.buf))
recovery = 1;
else {
int code = die_message(_("invalid branch name: '%s'"), oldname);
@@ -601,7 +605,7 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
}
}
- if ((copy || !(oldref_usage & IS_HEAD)) && !ref_exists(oldref.buf)) {
+ if ((copy || !(oldref_usage & IS_HEAD)) && !refs_ref_exists(get_main_ref_store(the_repository), oldref.buf)) {
if (oldref_usage & IS_HEAD)
die(_("no commit on branch '%s' yet"), oldname);
else
@@ -632,9 +636,9 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
oldref.buf, newref.buf);
if (!copy && !(oldref_usage & IS_ORPHAN) &&
- rename_ref(oldref.buf, newref.buf, logmsg.buf))
+ refs_rename_ref(get_main_ref_store(the_repository), oldref.buf, newref.buf, logmsg.buf))
die(_("branch rename failed"));
- if (copy && copy_existing_ref(oldref.buf, newref.buf, logmsg.buf))
+ if (copy && refs_copy_existing_ref(get_main_ref_store(the_repository), oldref.buf, newref.buf, logmsg.buf))
die(_("branch copy failed"));
if (recovery) {
@@ -786,7 +790,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
track = git_branch_track;
- head = resolve_refdup("HEAD", 0, &head_oid, NULL);
+ head = refs_resolve_refdup(get_main_ref_store(the_repository), "HEAD",
+ 0, &head_oid, NULL);
if (!head)
die(_("failed to resolve HEAD as a valid ref"));
if (!strcmp(head, "HEAD"))
@@ -891,7 +896,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
}
strbuf_addf(&branch_ref, "refs/heads/%s", branch_name);
- if (!ref_exists(branch_ref.buf))
+ if (!refs_ref_exists(get_main_ref_store(the_repository), branch_ref.buf))
error((!argc || branch_checked_out(branch_ref.buf))
? _("no commit on branch '%s' yet")
: _("no branch named '%s'"),
@@ -936,7 +941,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
die(_("no such branch '%s'"), argv[0]);
}
- if (!ref_exists(branch->refname)) {
+ if (!refs_ref_exists(get_main_ref_store(the_repository), branch->refname)) {
if (!argc || branch_checked_out(branch->refname))
die(_("no commit on branch '%s' yet"), branch->name);
die(_("branch '%s' does not exist"), branch->name);
diff --git a/builtin/bundle.c b/builtin/bundle.c
index 3ad11dc5d0..d5d41a8f67 100644
--- a/builtin/bundle.c
+++ b/builtin/bundle.c
@@ -140,6 +140,11 @@ static int cmd_bundle_verify(int argc, const char **argv, const char *prefix) {
builtin_bundle_verify_usage, options, &bundle_file);
/* bundle internals use argv[1] as further parameters */
+ if (!startup_info->have_repository) {
+ ret = error(_("need a repository to verify a bundle"));
+ goto cleanup;
+ }
+
if ((bundle_fd = open_bundle(bundle_file, &header, &name)) < 0) {
ret = 1;
goto cleanup;
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 8a1d13b399..f90a4ca4b7 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -645,7 +645,8 @@ static int checkout_paths(const struct checkout_opts *opts,
rollback_lock_file(&lock_file);
}
- read_ref_full("HEAD", 0, &rev, NULL);
+ refs_read_ref_full(get_main_ref_store(the_repository), "HEAD", 0,
+ &rev, NULL);
head = lookup_commit_reference_gently(the_repository, &rev, 1);
errs |= post_checkout_hook(head, head, 0);
@@ -957,7 +958,8 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
int ret;
struct strbuf err = STRBUF_INIT;
- ret = safe_create_reflog(refname, &err);
+ ret = refs_create_reflog(get_main_ref_store(the_repository),
+ refname, &err);
if (ret) {
fprintf(stderr, _("Can not do reflog for '%s': %s\n"),
opts->new_orphan_branch, err.buf);
@@ -998,8 +1000,10 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
if (!strcmp(new_branch_info->name, "HEAD") && !new_branch_info->path && !opts->force_detach) {
/* Nothing to do. */
} else if (opts->force_detach || !new_branch_info->path) { /* No longer on any branch. */
- update_ref(msg.buf, "HEAD", &new_branch_info->commit->object.oid, NULL,
- REF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR);
+ refs_update_ref(get_main_ref_store(the_repository), msg.buf,
+ "HEAD", &new_branch_info->commit->object.oid,
+ NULL,
+ REF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR);
if (!opts->quiet) {
if (old_branch_info->path &&
advice_enabled(ADVICE_DETACHED_HEAD) && !opts->force_detach)
@@ -1007,7 +1011,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
describe_detached_head(_("HEAD is now at"), new_branch_info->commit);
}
} else if (new_branch_info->path) { /* Switch branches. */
- if (create_symref("HEAD", new_branch_info->path, msg.buf) < 0)
+ if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", new_branch_info->path, msg.buf) < 0)
die(_("unable to update HEAD"));
if (!opts->quiet) {
if (old_branch_info->path && !strcmp(new_branch_info->path, old_branch_info->path)) {
@@ -1028,8 +1032,9 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
}
}
if (old_branch_info->path && old_branch_info->name) {
- if (!ref_exists(old_branch_info->path) && reflog_exists(old_branch_info->path))
- delete_reflog(old_branch_info->path);
+ if (!refs_ref_exists(get_main_ref_store(the_repository), old_branch_info->path) && refs_reflog_exists(get_main_ref_store(the_repository), old_branch_info->path))
+ refs_delete_reflog(get_main_ref_store(the_repository),
+ old_branch_info->path);
}
}
remove_branch_state(the_repository, !opts->quiet);
@@ -1128,7 +1133,8 @@ static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne
object->flags &= ~UNINTERESTING;
add_pending_object(&revs, object, oid_to_hex(&object->oid));
- for_each_ref(add_pending_uninteresting_ref, &revs);
+ refs_for_each_ref(get_main_ref_store(the_repository),
+ add_pending_uninteresting_ref, &revs);
if (new_commit)
add_pending_oid(&revs, "HEAD",
&new_commit->object.oid,
@@ -1158,7 +1164,8 @@ static int switch_branches(const struct checkout_opts *opts,
trace2_cmd_mode("branch");
memset(&old_branch_info, 0, sizeof(old_branch_info));
- old_branch_info.path = resolve_refdup("HEAD", 0, &rev, &flag);
+ old_branch_info.path = refs_resolve_refdup(get_main_ref_store(the_repository),
+ "HEAD", 0, &rev, &flag);
if (old_branch_info.path)
old_branch_info.commit = lookup_commit_reference_gently(the_repository, &rev, 1);
if (!(flag & REF_ISSYMREF))
@@ -1246,7 +1253,7 @@ static void setup_new_branch_info_and_source_tree(
setup_branch_path(new_branch_info);
if (!check_refname_format(new_branch_info->path, 0) &&
- !read_ref(new_branch_info->path, &branch_rev))
+ !refs_read_ref(get_main_ref_store(the_repository), new_branch_info->path, &branch_rev))
oidcpy(rev, &branch_rev);
else
/* not an existing branch */
@@ -1465,7 +1472,8 @@ static int switch_unborn_to_new_branch(const struct checkout_opts *opts)
if (!opts->new_branch)
die(_("You are on a branch yet to be born"));
strbuf_addf(&branch_ref, "refs/heads/%s", opts->new_branch);
- status = create_symref("HEAD", branch_ref.buf, "checkout -b");
+ status = refs_update_symref(get_main_ref_store(the_repository),
+ "HEAD", branch_ref.buf, "checkout -b");
strbuf_release(&branch_ref);
if (!opts->quiet)
fprintf(stderr, _("Switched to a new branch '%s'\n"),
@@ -1552,7 +1560,8 @@ static void die_if_switching_to_a_branch_in_use(struct checkout_opts *opts,
if (opts->ignore_other_worktrees)
return;
- head_ref = resolve_refdup("HEAD", 0, NULL, &flags);
+ head_ref = refs_resolve_refdup(get_main_ref_store(the_repository),
+ "HEAD", 0, NULL, &flags);
if (head_ref && (!(flags & REF_ISSYMREF) || strcmp(head_ref, full_ref)))
die_if_checked_out(full_ref, 1);
free(head_ref);
@@ -1633,7 +1642,7 @@ static int checkout_branch(struct checkout_opts *opts,
struct object_id rev;
int flag;
- if (!read_ref_full("HEAD", 0, &rev, &flag) &&
+ if (!refs_read_ref_full(get_main_ref_store(the_repository), "HEAD", 0, &rev, &flag) &&
(flag & REF_ISSYMREF) && is_null_oid(&rev))
return switch_unborn_to_new_branch(opts);
}
diff --git a/builtin/clone.c b/builtin/clone.c
index 93fdfc945a..23993b905b 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -328,7 +328,20 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
int src_len, dest_len;
struct dir_iterator *iter;
int iter_status;
- struct strbuf realpath = STRBUF_INIT;
+
+ /*
+ * Refuse copying directories by default which aren't owned by us. The
+ * code that performs either the copying or hardlinking is not prepared
+ * to handle various edge cases where an adversary may for example
+ * racily swap out files for symlinks. This can cause us to
+ * inadvertently use the wrong source file.
+ *
+ * Furthermore, even if we were prepared to handle such races safely,
+ * creating hardlinks across user boundaries is an inherently unsafe
+ * operation as the hardlinked files can be rewritten at will by the
+ * potentially-untrusted user. We thus refuse to do so by default.
+ */
+ die_upon_dubious_ownership(NULL, NULL, src_repo);
mkdir_if_missing(dest->buf, 0777);
@@ -376,9 +389,27 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
if (unlink(dest->buf) && errno != ENOENT)
die_errno(_("failed to unlink '%s'"), dest->buf);
if (!option_no_hardlinks) {
- strbuf_realpath(&realpath, src->buf, 1);
- if (!link(realpath.buf, dest->buf))
+ if (!link(src->buf, dest->buf)) {
+ struct stat st;
+
+ /*
+ * Sanity-check whether the created hardlink
+ * actually links to the expected file now. This
+ * catches time-of-check-time-of-use bugs in
+ * case the source file was meanwhile swapped.
+ */
+ if (lstat(dest->buf, &st))
+ die(_("hardlink cannot be checked at '%s'"), dest->buf);
+ if (st.st_mode != iter->st.st_mode ||
+ st.st_ino != iter->st.st_ino ||
+ st.st_dev != iter->st.st_dev ||
+ st.st_size != iter->st.st_size ||
+ st.st_uid != iter->st.st_uid ||
+ st.st_gid != iter->st.st_gid)
+ die(_("hardlink different from source at '%s'"), dest->buf);
+
continue;
+ }
if (option_local > 0)
die_errno(_("failed to create link '%s'"), dest->buf);
option_no_hardlinks = 1;
@@ -391,8 +422,6 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
strbuf_setlen(src, src_len);
die(_("failed to iterate over '%s'"), src->buf);
}
-
- strbuf_release(&realpath);
}
static void clone_local(const char *src_repo, const char *dest_repo)
@@ -538,7 +567,8 @@ static void write_remote_refs(const struct ref *local_refs)
struct ref_transaction *t;
struct strbuf err = STRBUF_INIT;
- t = ref_transaction_begin(&err);
+ t = ref_store_transaction_begin(get_main_ref_store(the_repository),
+ &err);
if (!t)
die("%s", err.buf);
@@ -569,8 +599,9 @@ static void write_followtags(const struct ref *refs, const char *msg)
OBJECT_INFO_QUICK |
OBJECT_INFO_SKIP_FETCH_OBJECT))
continue;
- update_ref(msg, ref->name, &ref->old_oid, NULL, 0,
- UPDATE_REFS_DIE_ON_ERR);
+ refs_update_ref(get_main_ref_store(the_repository), msg,
+ ref->name, &ref->old_oid, NULL, 0,
+ UPDATE_REFS_DIE_ON_ERR);
}
}
@@ -622,9 +653,9 @@ static void update_remote_refs(const struct ref *refs,
struct strbuf head_ref = STRBUF_INIT;
strbuf_addstr(&head_ref, branch_top);
strbuf_addstr(&head_ref, "HEAD");
- if (create_symref(head_ref.buf,
- remote_head_points_at->peer_ref->name,
- msg) < 0)
+ if (refs_update_symref(get_main_ref_store(the_repository), head_ref.buf,
+ remote_head_points_at->peer_ref->name,
+ msg) < 0)
die(_("unable to update %s"), head_ref.buf);
strbuf_release(&head_ref);
}
@@ -636,33 +667,36 @@ static void update_head(const struct ref *our, const struct ref *remote,
const char *head;
if (our && skip_prefix(our->name, "refs/heads/", &head)) {
/* Local default branch link */
- if (create_symref("HEAD", our->name, NULL) < 0)
+ if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", our->name, NULL) < 0)
die(_("unable to update HEAD"));
if (!option_bare) {
- update_ref(msg, "HEAD", &our->old_oid, NULL, 0,
- UPDATE_REFS_DIE_ON_ERR);
+ refs_update_ref(get_main_ref_store(the_repository),
+ msg, "HEAD", &our->old_oid, NULL, 0,
+ UPDATE_REFS_DIE_ON_ERR);
install_branch_config(0, head, remote_name, our->name);
}
} else if (our) {
struct commit *c = lookup_commit_reference(the_repository,
&our->old_oid);
/* --branch specifies a non-branch (i.e. tags), detach HEAD */
- update_ref(msg, "HEAD", &c->object.oid, NULL, REF_NO_DEREF,
- UPDATE_REFS_DIE_ON_ERR);
+ refs_update_ref(get_main_ref_store(the_repository), msg,
+ "HEAD", &c->object.oid, NULL, REF_NO_DEREF,
+ UPDATE_REFS_DIE_ON_ERR);
} else if (remote) {
/*
* We know remote HEAD points to a non-branch, or
* HEAD points to a branch but we don't know which one.
* Detach HEAD in all these cases.
*/
- update_ref(msg, "HEAD", &remote->old_oid, NULL, REF_NO_DEREF,
- UPDATE_REFS_DIE_ON_ERR);
+ refs_update_ref(get_main_ref_store(the_repository), msg,
+ "HEAD", &remote->old_oid, NULL, REF_NO_DEREF,
+ UPDATE_REFS_DIE_ON_ERR);
} else if (unborn && skip_prefix(unborn, "refs/heads/", &head)) {
/*
* Unborn head from remote; same as "our" case above except
* that we have no ref to update.
*/
- if (create_symref("HEAD", unborn, NULL) < 0)
+ if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", unborn, NULL) < 0)
die(_("unable to update HEAD"));
if (!option_bare)
install_branch_config(0, head, remote_name, unborn);
@@ -703,7 +737,8 @@ static int checkout(int submodule_progress, int filter_submodules)
if (option_no_checkout)
return 0;
- head = resolve_refdup("HEAD", RESOLVE_REF_READING, &oid, NULL);
+ head = refs_resolve_refdup(get_main_ref_store(the_repository), "HEAD",
+ RESOLVE_REF_READING, &oid, NULL);
if (!head) {
warning(_("remote HEAD refers to nonexistent ref, "
"unable to checkout"));
@@ -937,6 +972,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
int hash_algo;
unsigned int ref_storage_format = REF_STORAGE_FORMAT_UNKNOWN;
const int do_not_override_repo_unix_permissions = -1;
+ const char *template_dir;
+ char *template_dir_dup = NULL;
struct transport_ls_refs_options transport_ls_refs_options =
TRANSPORT_LS_REFS_OPTIONS_INIT;
@@ -956,6 +993,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
usage_msg_opt(_("You must specify a repository to clone."),
builtin_clone_usage, builtin_clone_options);
+ xsetenv("GIT_CLONE_PROTECTION_ACTIVE", "true", 0 /* allow user override */);
+ template_dir = get_template_dir(option_template);
+ if (*template_dir && !is_absolute_path(template_dir))
+ template_dir = template_dir_dup =
+ absolute_pathdup(template_dir);
+ xsetenv("GIT_CLONE_TEMPLATE_DIR", template_dir, 1);
+
if (option_depth || option_since || option_not.nr)
deepen = 1;
if (option_single_branch == -1)
@@ -1117,7 +1161,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
* repository, and reference backends may persist that information into
* their on-disk data structures.
*/
- init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN,
+ init_db(git_dir, real_git_dir, template_dir, GIT_HASH_UNKNOWN,
ref_storage_format, NULL,
do_not_override_repo_unix_permissions, INIT_DB_QUIET | INIT_DB_SKIP_REFDB);
@@ -1506,6 +1550,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
free(dir);
free(path);
free(repo_to_free);
+ free(template_dir_dup);
junk_mode = JUNK_LEAVE_ALL;
transport_ls_refs_options_release(&transport_ls_refs_options);
diff --git a/builtin/commit.c b/builtin/commit.c
index c2943055ef..78bfae2164 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -37,6 +37,7 @@
#include "commit-reach.h"
#include "commit-graph.h"
#include "pretty.h"
+#include "trailer.h"
static const char * const builtin_commit_usage[] = {
N_("git commit [-a | --interactive | --patch] [-s] [-v] [-u<mode>] [--amend]\n"
@@ -141,14 +142,6 @@ static struct strbuf message = STRBUF_INIT;
static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
-static int opt_pass_trailer(const struct option *opt, const char *arg, int unset)
-{
- BUG_ON_OPT_NEG(unset);
-
- strvec_pushl(opt->value, "--trailer", arg, NULL);
- return 0;
-}
-
static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
{
enum wt_status_format *value = (enum wt_status_format *)opt->value;
@@ -1037,14 +1030,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
fclose(s->fp);
if (trailer_args.nr) {
- struct child_process run_trailer = CHILD_PROCESS_INIT;
-
- strvec_pushl(&run_trailer.args, "interpret-trailers",
- "--in-place", "--no-divider",
- git_path_commit_editmsg(), NULL);
- strvec_pushv(&run_trailer.args, trailer_args.v);
- run_trailer.git_cmd = 1;
- if (run_command(&run_trailer))
+ if (amend_file_with_trailers(git_path_commit_editmsg(), &trailer_args))
die(_("unable to pass trailers to --trailers"));
strvec_clear(&trailer_args);
}
@@ -1672,7 +1658,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
OPT_STRING(0, "fixup", &fixup_message, N_("[(amend|reword):]commit"), N_("use autosquash formatted message to fixup or amend/reword specified commit")),
OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
- OPT_CALLBACK_F(0, "trailer", &trailer_args, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG, opt_pass_trailer),
+ OPT_PASSTHRU_ARGV(0, "trailer", &trailer_args, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG),
OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")),
OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
diff --git a/builtin/config.c b/builtin/config.c
index 0015620dde..80aa9d8a66 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -16,7 +16,49 @@
#include "worktree.h"
static const char *const builtin_config_usage[] = {
- N_("git config [<options>]"),
+ N_("git config list [<file-option>] [<display-option>] [--includes]"),
+ N_("git config get [<file-option>] [<display-option>] [--includes] [--all] [--regexp=<regexp>] [--value=<value>] [--fixed-value] [--default=<default>] <name>"),
+ N_("git config set [<file-option>] [--type=<type>] [--all] [--value=<value>] [--fixed-value] <name> <value>"),
+ N_("git config unset [<file-option>] [--all] [--value=<value>] [--fixed-value] <name> <value>"),
+ N_("git config rename-section [<file-option>] <old-name> <new-name>"),
+ N_("git config remove-section [<file-option>] <name>"),
+ N_("git config edit [<file-option>]"),
+ N_("git config [<file-option>] --get-colorbool <name> [<stdout-is-tty>]"),
+ NULL
+};
+
+static const char *const builtin_config_list_usage[] = {
+ N_("git config list [<file-option>] [<display-option>] [--includes]"),
+ NULL
+};
+
+static const char *const builtin_config_get_usage[] = {
+ N_("git config get [<file-option>] [<display-option>] [--includes] [--all] [--regexp=<regexp>] [--value=<value>] [--fixed-value] [--default=<default>] <name>"),
+ NULL
+};
+
+static const char *const builtin_config_set_usage[] = {
+ N_("git config set [<file-option>] [--type=<type>] [--comment=<message>] [--all] [--value=<value>] [--fixed-value] <name> <value>"),
+ NULL
+};
+
+static const char *const builtin_config_unset_usage[] = {
+ N_("git config unset [<file-option>] [--all] [--value=<value>] [--fixed-value] <name> <value>"),
+ NULL
+};
+
+static const char *const builtin_config_rename_section_usage[] = {
+ N_("git config rename-section [<file-option>] <old-name> <new-name>"),
+ NULL
+};
+
+static const char *const builtin_config_remove_section_usage[] = {
+ N_("git config remove-section [<file-option>] <name>"),
+ NULL
+};
+
+static const char *const builtin_config_edit_usage[] = {
+ N_("git config edit [<file-option>]"),
NULL
};
@@ -33,6 +75,7 @@ static char delim = '=';
static char key_delim = ' ';
static char term = '\n';
+static parse_opt_subcommand_fn *subcommand;
static int use_global_config, use_system_config, use_local_config;
static int use_worktree_config;
static struct git_config_source given_config_source;
@@ -44,7 +87,7 @@ static struct config_options config_options;
static int show_origin;
static int show_scope;
static int fixed_value;
-static const char *comment;
+static const char *comment_arg;
#define ACTION_GET (1<<0)
#define ACTION_GET_ALL (1<<1)
@@ -135,54 +178,6 @@ static int option_parse_type(const struct option *opt, const char *arg,
return 0;
}
-static struct option builtin_config_options[] = {
- OPT_GROUP(N_("Config file location")),
- OPT_BOOL(0, "global", &use_global_config, N_("use global config file")),
- OPT_BOOL(0, "system", &use_system_config, N_("use system config file")),
- OPT_BOOL(0, "local", &use_local_config, N_("use repository config file")),
- OPT_BOOL(0, "worktree", &use_worktree_config, N_("use per-worktree config file")),
- OPT_STRING('f', "file", &given_config_source.file, N_("file"), N_("use given config file")),
- OPT_STRING(0, "blob", &given_config_source.blob, N_("blob-id"), N_("read config from given blob object")),
- OPT_GROUP(N_("Action")),
- OPT_BIT(0, "get", &actions, N_("get value: name [value-pattern]"), ACTION_GET),
- OPT_BIT(0, "get-all", &actions, N_("get all values: key [value-pattern]"), ACTION_GET_ALL),
- OPT_BIT(0, "get-regexp", &actions, N_("get values for regexp: name-regex [value-pattern]"), ACTION_GET_REGEXP),
- OPT_BIT(0, "get-urlmatch", &actions, N_("get value specific for the URL: section[.var] URL"), ACTION_GET_URLMATCH),
- OPT_BIT(0, "replace-all", &actions, N_("replace all matching variables: name value [value-pattern]"), ACTION_REPLACE_ALL),
- OPT_BIT(0, "add", &actions, N_("add a new variable: name value"), ACTION_ADD),
- OPT_BIT(0, "unset", &actions, N_("remove a variable: name [value-pattern]"), ACTION_UNSET),
- OPT_BIT(0, "unset-all", &actions, N_("remove all matches: name [value-pattern]"), ACTION_UNSET_ALL),
- OPT_BIT(0, "rename-section", &actions, N_("rename section: old-name new-name"), ACTION_RENAME_SECTION),
- OPT_BIT(0, "remove-section", &actions, N_("remove a section: name"), ACTION_REMOVE_SECTION),
- OPT_BIT('l', "list", &actions, N_("list all"), ACTION_LIST),
- OPT_BOOL(0, "fixed-value", &fixed_value, N_("use string equality when comparing values to 'value-pattern'")),
- OPT_BIT('e', "edit", &actions, N_("open an editor"), ACTION_EDIT),
- OPT_BIT(0, "get-color", &actions, N_("find the color configured: slot [default]"), ACTION_GET_COLOR),
- OPT_BIT(0, "get-colorbool", &actions, N_("find the color setting: slot [stdout-is-tty]"), ACTION_GET_COLORBOOL),
- OPT_GROUP(N_("Type")),
- OPT_CALLBACK('t', "type", &type, N_("type"), N_("value is given this type"), option_parse_type),
- OPT_CALLBACK_VALUE(0, "bool", &type, N_("value is \"true\" or \"false\""), TYPE_BOOL),
- OPT_CALLBACK_VALUE(0, "int", &type, N_("value is decimal number"), TYPE_INT),
- OPT_CALLBACK_VALUE(0, "bool-or-int", &type, N_("value is --bool or --int"), TYPE_BOOL_OR_INT),
- OPT_CALLBACK_VALUE(0, "bool-or-str", &type, N_("value is --bool or string"), TYPE_BOOL_OR_STR),
- OPT_CALLBACK_VALUE(0, "path", &type, N_("value is a path (file or directory name)"), TYPE_PATH),
- OPT_CALLBACK_VALUE(0, "expiry-date", &type, N_("value is an expiry date"), TYPE_EXPIRY_DATE),
- OPT_GROUP(N_("Other")),
- OPT_BOOL('z', "null", &end_nul, N_("terminate values with NUL byte")),
- OPT_BOOL(0, "name-only", &omit_values, N_("show variable names only")),
- OPT_BOOL(0, "includes", &respect_includes_opt, N_("respect include directives on lookup")),
- OPT_BOOL(0, "show-origin", &show_origin, N_("show origin of config (file, standard input, blob, command line)")),
- OPT_BOOL(0, "show-scope", &show_scope, N_("show scope of config (worktree, local, global, system, command)")),
- OPT_STRING(0, "default", &default_value, N_("value"), N_("with --get, use default value when missing entry")),
- OPT_STRING(0, "comment", &comment, N_("value"), N_("human-readable comment string (# will be prepended as needed)")),
- OPT_END(),
-};
-
-static NORETURN void usage_builtin_config(void)
-{
- usage_with_options(builtin_config_usage, builtin_config_options);
-}
-
static void check_argc(int argc, int min, int max)
{
if (argc >= min && argc <= max)
@@ -671,20 +666,8 @@ static char *default_user_config(void)
return strbuf_detach(&buf, NULL);
}
-int cmd_config(int argc, const char **argv, const char *prefix)
+static void handle_config_location(const char *prefix)
{
- int nongit = !startup_info->have_repository;
- char *value = NULL;
- int flags = 0;
- int ret = 0;
- struct key_value_info default_kvi = KVI_INIT;
-
- given_config_source.file = xstrdup_or_null(getenv(CONFIG_ENVIRONMENT));
-
- argc = parse_options(argc, argv, prefix, builtin_config_options,
- builtin_config_usage,
- PARSE_OPT_STOP_AT_NON_OPTION);
-
if (use_global_config + use_system_config + use_local_config +
use_worktree_config +
!!given_config_source.file + !!given_config_source.blob > 1) {
@@ -692,14 +675,13 @@ int cmd_config(int argc, const char **argv, const char *prefix)
usage_builtin_config();
}
- if (nongit) {
+ if (!startup_info->have_repository) {
if (use_local_config)
die(_("--local can only be used inside a git repository"));
if (given_config_source.blob)
die(_("--blob can only be used inside a git repository"));
if (use_worktree_config)
die(_("--worktree can only be used inside a git repository"));
-
}
if (given_config_source.file &&
@@ -753,26 +735,384 @@ int cmd_config(int argc, const char **argv, const char *prefix)
config_options.respect_includes = !given_config_source.file;
else
config_options.respect_includes = respect_includes_opt;
- if (!nongit) {
+ if (startup_info->have_repository) {
config_options.commondir = get_git_common_dir();
config_options.git_dir = get_git_dir();
}
+}
+static void handle_nul(void) {
if (end_nul) {
term = '\0';
delim = '\n';
key_delim = '\n';
}
+}
+
+#define CONFIG_LOCATION_OPTIONS \
+ OPT_GROUP(N_("Config file location")), \
+ OPT_BOOL(0, "global", &use_global_config, N_("use global config file")), \
+ OPT_BOOL(0, "system", &use_system_config, N_("use system config file")), \
+ OPT_BOOL(0, "local", &use_local_config, N_("use repository config file")), \
+ OPT_BOOL(0, "worktree", &use_worktree_config, N_("use per-worktree config file")), \
+ OPT_STRING('f', "file", &given_config_source.file, N_("file"), N_("use given config file")), \
+ OPT_STRING(0, "blob", &given_config_source.blob, N_("blob-id"), N_("read config from given blob object"))
+
+#define CONFIG_TYPE_OPTIONS \
+ OPT_GROUP(N_("Type")), \
+ OPT_CALLBACK('t', "type", &type, N_("type"), N_("value is given this type"), option_parse_type), \
+ OPT_CALLBACK_VALUE(0, "bool", &type, N_("value is \"true\" or \"false\""), TYPE_BOOL), \
+ OPT_CALLBACK_VALUE(0, "int", &type, N_("value is decimal number"), TYPE_INT), \
+ OPT_CALLBACK_VALUE(0, "bool-or-int", &type, N_("value is --bool or --int"), TYPE_BOOL_OR_INT), \
+ OPT_CALLBACK_VALUE(0, "bool-or-str", &type, N_("value is --bool or string"), TYPE_BOOL_OR_STR), \
+ OPT_CALLBACK_VALUE(0, "path", &type, N_("value is a path (file or directory name)"), TYPE_PATH), \
+ OPT_CALLBACK_VALUE(0, "expiry-date", &type, N_("value is an expiry date"), TYPE_EXPIRY_DATE)
+
+#define CONFIG_DISPLAY_OPTIONS \
+ OPT_GROUP(N_("Display options")), \
+ OPT_BOOL('z', "null", &end_nul, N_("terminate values with NUL byte")), \
+ OPT_BOOL(0, "name-only", &omit_values, N_("show variable names only")), \
+ OPT_BOOL(0, "show-origin", &show_origin, N_("show origin of config (file, standard input, blob, command line)")), \
+ OPT_BOOL(0, "show-scope", &show_scope, N_("show scope of config (worktree, local, global, system, command)"))
+
+static struct option builtin_config_options[] = {
+ CONFIG_LOCATION_OPTIONS,
+ OPT_GROUP(N_("Action")),
+ OPT_CMDMODE(0, "get", &actions, N_("get value: name [<value-pattern>]"), ACTION_GET),
+ OPT_CMDMODE(0, "get-all", &actions, N_("get all values: key [<value-pattern>]"), ACTION_GET_ALL),
+ OPT_CMDMODE(0, "get-regexp", &actions, N_("get values for regexp: name-regex [<value-pattern>]"), ACTION_GET_REGEXP),
+ OPT_CMDMODE(0, "get-urlmatch", &actions, N_("get value specific for the URL: section[.var] URL"), ACTION_GET_URLMATCH),
+ OPT_CMDMODE(0, "replace-all", &actions, N_("replace all matching variables: name value [<value-pattern>]"), ACTION_REPLACE_ALL),
+ OPT_CMDMODE(0, "add", &actions, N_("add a new variable: name value"), ACTION_ADD),
+ OPT_CMDMODE(0, "unset", &actions, N_("remove a variable: name [<value-pattern>]"), ACTION_UNSET),
+ OPT_CMDMODE(0, "unset-all", &actions, N_("remove all matches: name [<value-pattern>]"), ACTION_UNSET_ALL),
+ OPT_CMDMODE(0, "rename-section", &actions, N_("rename section: old-name new-name"), ACTION_RENAME_SECTION),
+ OPT_CMDMODE(0, "remove-section", &actions, N_("remove a section: name"), ACTION_REMOVE_SECTION),
+ OPT_CMDMODE('l', "list", &actions, N_("list all"), ACTION_LIST),
+ OPT_CMDMODE('e', "edit", &actions, N_("open an editor"), ACTION_EDIT),
+ OPT_CMDMODE(0, "get-color", &actions, N_("find the color configured: slot [<default>]"), ACTION_GET_COLOR),
+ OPT_CMDMODE(0, "get-colorbool", &actions, N_("find the color setting: slot [<stdout-is-tty>]"), ACTION_GET_COLORBOOL),
+ CONFIG_TYPE_OPTIONS,
+ CONFIG_DISPLAY_OPTIONS,
+ OPT_GROUP(N_("Other")),
+ OPT_STRING(0, "default", &default_value, N_("value"), N_("with --get, use default value when missing entry")),
+ OPT_STRING(0, "comment", &comment_arg, N_("value"), N_("human-readable comment string (# will be prepended as needed)")),
+ OPT_BOOL(0, "fixed-value", &fixed_value, N_("use string equality when comparing values to 'value-pattern'")),
+ OPT_BOOL(0, "includes", &respect_includes_opt, N_("respect include directives on lookup")),
+ OPT_END(),
+};
+
+static NORETURN void usage_builtin_config(void)
+{
+ usage_with_options(builtin_config_usage, builtin_config_options);
+}
+
+static int cmd_config_list(int argc, const char **argv, const char *prefix)
+{
+ struct option opts[] = {
+ CONFIG_LOCATION_OPTIONS,
+ CONFIG_DISPLAY_OPTIONS,
+ OPT_GROUP(N_("Other")),
+ OPT_BOOL(0, "includes", &respect_includes_opt, N_("respect include directives on lookup")),
+ OPT_END(),
+ };
+
+ argc = parse_options(argc, argv, prefix, opts, builtin_config_list_usage, 0);
+ check_argc(argc, 0, 0);
+
+ handle_config_location(prefix);
+ handle_nul();
+
+ setup_auto_pager("config", 1);
+
+ if (config_with_options(show_all_config, NULL,
+ &given_config_source, the_repository,
+ &config_options) < 0) {
+ if (given_config_source.file)
+ die_errno(_("unable to read config file '%s'"),
+ given_config_source.file);
+ else
+ die(_("error processing config file(s)"));
+ }
+
+ return 0;
+}
+
+static int cmd_config_get(int argc, const char **argv, const char *prefix)
+{
+ const char *value_pattern = NULL, *url = NULL;
+ int flags = 0;
+ struct option opts[] = {
+ CONFIG_LOCATION_OPTIONS,
+ CONFIG_TYPE_OPTIONS,
+ OPT_GROUP(N_("Filter options")),
+ OPT_BOOL(0, "all", &do_all, N_("return all values for multi-valued config options")),
+ OPT_BOOL(0, "regexp", &use_key_regexp, N_("interpret the name as a regular expression")),
+ OPT_STRING(0, "value", &value_pattern, N_("pattern"), N_("show config with values matching the pattern")),
+ OPT_BIT(0, "fixed-value", &flags, N_("use string equality when comparing values to value pattern"), CONFIG_FLAGS_FIXED_VALUE),
+ OPT_STRING(0, "url", &url, N_("URL"), N_("show config matching the given URL")),
+ CONFIG_DISPLAY_OPTIONS,
+ OPT_BOOL(0, "show-names", &show_keys, N_("show config keys in addition to their values")),
+ OPT_GROUP(N_("Other")),
+ OPT_BOOL(0, "includes", &respect_includes_opt, N_("respect include directives on lookup")),
+ OPT_STRING(0, "default", &default_value, N_("value"), N_("use default value when missing entry")),
+ OPT_END(),
+ };
+
+ argc = parse_options(argc, argv, prefix, opts, builtin_config_get_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+ check_argc(argc, 1, 1);
+
+ if ((flags & CONFIG_FLAGS_FIXED_VALUE) && !value_pattern)
+ die(_("--fixed-value only applies with 'value-pattern'"));
+ if (default_value && (do_all || url))
+ die(_("--default= cannot be used with --all or --url="));
+ if (url && (do_all || use_key_regexp || value_pattern))
+ die(_("--url= cannot be used with --all, --regexp or --value"));
+
+ handle_config_location(prefix);
+ handle_nul();
+
+ setup_auto_pager("config", 1);
+
+ if (url)
+ return get_urlmatch(argv[0], url);
+ return get_value(argv[0], value_pattern, flags);
+}
+
+static int cmd_config_set(int argc, const char **argv, const char *prefix)
+{
+ const char *value_pattern = NULL, *comment_arg = NULL;
+ char *comment = NULL;
+ int flags = 0, append = 0;
+ struct option opts[] = {
+ CONFIG_LOCATION_OPTIONS,
+ CONFIG_TYPE_OPTIONS,
+ OPT_GROUP(N_("Filter")),
+ OPT_BIT(0, "all", &flags, N_("replace multi-valued config option with new value"), CONFIG_FLAGS_MULTI_REPLACE),
+ OPT_STRING(0, "value", &value_pattern, N_("pattern"), N_("show config with values matching the pattern")),
+ OPT_BIT(0, "fixed-value", &flags, N_("use string equality when comparing values to value pattern"), CONFIG_FLAGS_FIXED_VALUE),
+ OPT_GROUP(N_("Other")),
+ OPT_STRING(0, "comment", &comment_arg, N_("value"), N_("human-readable comment string (# will be prepended as needed)")),
+ OPT_BOOL(0, "append", &append, N_("add a new line without altering any existing values")),
+ OPT_END(),
+ };
+ struct key_value_info default_kvi = KVI_INIT;
+ char *value;
+ int ret;
+
+ argc = parse_options(argc, argv, prefix, opts, builtin_config_set_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+ check_write();
+ check_argc(argc, 2, 2);
+
+ if ((flags & CONFIG_FLAGS_FIXED_VALUE) && !value_pattern)
+ die(_("--fixed-value only applies with --value=<pattern>"));
+ if (append && value_pattern)
+ die(_("--append cannot be used with --value=<pattern>"));
+ if (append)
+ value_pattern = CONFIG_REGEX_NONE;
+
+ comment = git_config_prepare_comment_string(comment_arg);
+
+ handle_config_location(prefix);
+
+ value = normalize_value(argv[0], argv[1], &default_kvi);
+
+ if ((flags & CONFIG_FLAGS_MULTI_REPLACE) || value_pattern) {
+ ret = git_config_set_multivar_in_file_gently(given_config_source.file,
+ argv[0], value, value_pattern,
+ comment, flags);
+ } else {
+ ret = git_config_set_in_file_gently(given_config_source.file,
+ argv[0], comment, value);
+ if (ret == CONFIG_NOTHING_SET)
+ error(_("cannot overwrite multiple values with a single value\n"
+ " Use a regexp, --add or --replace-all to change %s."), argv[0]);
+ }
+
+ free(comment);
+ free(value);
+ return ret;
+}
+
+static int cmd_config_unset(int argc, const char **argv, const char *prefix)
+{
+ const char *value_pattern = NULL;
+ int flags = 0;
+ struct option opts[] = {
+ CONFIG_LOCATION_OPTIONS,
+ OPT_GROUP(N_("Filter")),
+ OPT_BIT(0, "all", &flags, N_("replace multi-valued config option with new value"), CONFIG_FLAGS_MULTI_REPLACE),
+ OPT_STRING(0, "value", &value_pattern, N_("pattern"), N_("show config with values matching the pattern")),
+ OPT_BIT(0, "fixed-value", &flags, N_("use string equality when comparing values to value pattern"), CONFIG_FLAGS_FIXED_VALUE),
+ OPT_END(),
+ };
+
+ argc = parse_options(argc, argv, prefix, opts, builtin_config_unset_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+ check_write();
+ check_argc(argc, 1, 1);
+
+ if ((flags & CONFIG_FLAGS_FIXED_VALUE) && !value_pattern)
+ die(_("--fixed-value only applies with 'value-pattern'"));
+
+ handle_config_location(prefix);
+
+ if ((flags & CONFIG_FLAGS_MULTI_REPLACE) || value_pattern)
+ return git_config_set_multivar_in_file_gently(given_config_source.file,
+ argv[0], NULL, value_pattern,
+ NULL, flags);
+ else
+ return git_config_set_in_file_gently(given_config_source.file, argv[0],
+ NULL, NULL);
+}
+
+static int cmd_config_rename_section(int argc, const char **argv, const char *prefix)
+{
+ struct option opts[] = {
+ CONFIG_LOCATION_OPTIONS,
+ OPT_END(),
+ };
+ int ret;
+
+ argc = parse_options(argc, argv, prefix, opts, builtin_config_rename_section_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+ check_write();
+ check_argc(argc, 2, 2);
+
+ handle_config_location(prefix);
+
+ ret = git_config_rename_section_in_file(given_config_source.file,
+ argv[0], argv[1]);
+ if (ret < 0)
+ return ret;
+ else if (!ret)
+ die(_("no such section: %s"), argv[0]);
+
+ return 0;
+}
+
+static int cmd_config_remove_section(int argc, const char **argv, const char *prefix)
+{
+ struct option opts[] = {
+ CONFIG_LOCATION_OPTIONS,
+ OPT_END(),
+ };
+ int ret;
+
+ argc = parse_options(argc, argv, prefix, opts, builtin_config_remove_section_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+ check_write();
+ check_argc(argc, 1, 1);
+
+ handle_config_location(prefix);
+
+ ret = git_config_rename_section_in_file(given_config_source.file,
+ argv[0], NULL);
+ if (ret < 0)
+ return ret;
+ else if (!ret)
+ die(_("no such section: %s"), argv[0]);
+
+ return 0;
+}
+
+static int show_editor(void)
+{
+ char *config_file;
+
+ if (!given_config_source.file && !startup_info->have_repository)
+ die(_("not in a git directory"));
+ if (given_config_source.use_stdin)
+ die(_("editing stdin is not supported"));
+ if (given_config_source.blob)
+ die(_("editing blobs is not supported"));
+ git_config(git_default_config, NULL);
+ config_file = given_config_source.file ?
+ xstrdup(given_config_source.file) :
+ git_pathdup("config");
+ if (use_global_config) {
+ int fd = open(config_file, O_CREAT | O_EXCL | O_WRONLY, 0666);
+ if (fd >= 0) {
+ char *content = default_user_config();
+ write_str_in_full(fd, content);
+ free(content);
+ close(fd);
+ }
+ else if (errno != EEXIST)
+ die_errno(_("cannot create configuration file %s"), config_file);
+ }
+ launch_editor(config_file, NULL, NULL);
+ free(config_file);
+
+ return 0;
+}
+
+static int cmd_config_edit(int argc, const char **argv, const char *prefix)
+{
+ struct option opts[] = {
+ CONFIG_LOCATION_OPTIONS,
+ OPT_END(),
+ };
+
+ argc = parse_options(argc, argv, prefix, opts, builtin_config_edit_usage, 0);
+ check_write();
+ check_argc(argc, 0, 0);
+
+ handle_config_location(prefix);
+
+ return show_editor();
+}
+
+static struct option builtin_subcommand_options[] = {
+ OPT_SUBCOMMAND("list", &subcommand, cmd_config_list),
+ OPT_SUBCOMMAND("get", &subcommand, cmd_config_get),
+ OPT_SUBCOMMAND("set", &subcommand, cmd_config_set),
+ OPT_SUBCOMMAND("unset", &subcommand, cmd_config_unset),
+ OPT_SUBCOMMAND("rename-section", &subcommand, cmd_config_rename_section),
+ OPT_SUBCOMMAND("remove-section", &subcommand, cmd_config_remove_section),
+ OPT_SUBCOMMAND("edit", &subcommand, cmd_config_edit),
+ OPT_END(),
+};
+
+int cmd_config(int argc, const char **argv, const char *prefix)
+{
+ char *value = NULL, *comment = NULL;
+ int flags = 0;
+ int ret = 0;
+ struct key_value_info default_kvi = KVI_INIT;
+
+ given_config_source.file = xstrdup_or_null(getenv(CONFIG_ENVIRONMENT));
+
+ /*
+ * This is somewhat hacky: we first parse the command line while
+ * keeping all args intact in order to determine whether a subcommand
+ * has been specified. If so, we re-parse it a second time, but this
+ * time we drop KEEP_ARGV0. This is so that we don't munge the command
+ * line in case no subcommand was given, which would otherwise confuse
+ * us when parsing the legacy-style modes that don't use subcommands.
+ */
+ argc = parse_options(argc, argv, prefix, builtin_subcommand_options, builtin_config_usage,
+ PARSE_OPT_SUBCOMMAND_OPTIONAL|PARSE_OPT_KEEP_ARGV0|PARSE_OPT_KEEP_UNKNOWN_OPT);
+ if (subcommand) {
+ argc = parse_options(argc, argv, prefix, builtin_subcommand_options, builtin_config_usage,
+ PARSE_OPT_SUBCOMMAND_OPTIONAL|PARSE_OPT_KEEP_UNKNOWN_OPT);
+ return subcommand(argc, argv, prefix);
+ }
+
+ argc = parse_options(argc, argv, prefix, builtin_config_options,
+ builtin_config_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+
+ handle_config_location(prefix);
+ handle_nul();
if ((actions & (ACTION_GET_COLOR|ACTION_GET_COLORBOOL)) && type) {
error(_("--get-color and variable type are incoherent"));
usage_builtin_config();
}
- if (HAS_MULTI_BITS(actions)) {
- error(_("only one action at a time"));
- usage_builtin_config();
- }
if (actions == 0)
switch (argc) {
case 1: actions = ACTION_GET; break;
@@ -799,7 +1139,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
usage_builtin_config();
}
- if (comment &&
+ if (comment_arg &&
!(actions & (ACTION_ADD|ACTION_SET|ACTION_SET_ALL|ACTION_REPLACE_ALL))) {
error(_("--comment is only applicable to add/set/replace operations"));
usage_builtin_config();
@@ -841,7 +1181,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
flags |= CONFIG_FLAGS_FIXED_VALUE;
}
- comment = git_config_prepare_comment_string(comment);
+ comment = git_config_prepare_comment_string(comment_arg);
if (actions & PAGING_ACTIONS)
setup_auto_pager("config", 1);
@@ -859,32 +1199,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
}
}
else if (actions == ACTION_EDIT) {
- char *config_file;
-
- check_argc(argc, 0, 0);
- if (!given_config_source.file && nongit)
- die(_("not in a git directory"));
- if (given_config_source.use_stdin)
- die(_("editing stdin is not supported"));
- if (given_config_source.blob)
- die(_("editing blobs is not supported"));
- git_config(git_default_config, NULL);
- config_file = given_config_source.file ?
- xstrdup(given_config_source.file) :
- git_pathdup("config");
- if (use_global_config) {
- int fd = open(config_file, O_CREAT | O_EXCL | O_WRONLY, 0666);
- if (fd >= 0) {
- char *content = default_user_config();
- write_str_in_full(fd, content);
- free(content);
- close(fd);
- }
- else if (errno != EEXIST)
- die_errno(_("cannot create configuration file %s"), config_file);
- }
- launch_editor(config_file, NULL, NULL);
- free(config_file);
+ ret = show_editor();
}
else if (actions == ACTION_SET) {
check_write();
@@ -993,6 +1308,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
return get_colorbool(argv[0], argc == 2);
}
+ free(comment);
free(value);
return ret;
}
diff --git a/builtin/describe.c b/builtin/describe.c
index c0e3301e3c..82aca00c80 100644
--- a/builtin/describe.c
+++ b/builtin/describe.c
@@ -637,7 +637,8 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
}
hashmap_init(&names, commit_name_neq, NULL, 0);
- for_each_rawref(get_name, NULL);
+ refs_for_each_rawref(get_main_ref_store(the_repository), get_name,
+ NULL);
if (!hashmap_get_size(&names) && !always)
die(_("No names found, cannot describe anything."));
diff --git a/builtin/diff.c b/builtin/diff.c
index efc37483b3..9b6cdabe15 100644
--- a/builtin/diff.c
+++ b/builtin/diff.c
@@ -465,6 +465,15 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
no_index = DIFF_NO_INDEX_IMPLICIT;
}
+ /*
+ * When operating outside of a Git repository we need to have a hash
+ * algorithm at hand so that we can generate the blob hashes. We
+ * default to SHA1 here, but may eventually want to change this to be
+ * configurable via a command line option.
+ */
+ if (nongit)
+ repo_set_hash_algo(the_repository, GIT_HASH_SHA1);
+
init_diff_ui_defaults();
git_config(git_diff_ui_config, NULL);
prefix = precompose_argv_prefix(argc, argv, prefix);
diff --git a/builtin/fast-import.c b/builtin/fast-import.c
index dc5a9d32dd..d1c0243d04 100644
--- a/builtin/fast-import.c
+++ b/builtin/fast-import.c
@@ -1604,10 +1604,11 @@ static int update_branch(struct branch *b)
if (is_null_oid(&b->oid)) {
if (b->delete)
- delete_ref(NULL, b->name, NULL, 0);
+ refs_delete_ref(get_main_ref_store(the_repository),
+ NULL, b->name, NULL, 0);
return 0;
}
- if (read_ref(b->name, &old_oid))
+ if (refs_read_ref(get_main_ref_store(the_repository), b->name, &old_oid))
oidclr(&old_oid);
if (!force_update && !is_null_oid(&old_oid)) {
struct commit *old_cmit, *new_cmit;
@@ -1631,10 +1632,11 @@ static int update_branch(struct branch *b)
return -1;
}
}
- transaction = ref_transaction_begin(&err);
+ transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
+ &err);
if (!transaction ||
ref_transaction_update(transaction, b->name, &b->oid, &old_oid,
- 0, msg, &err) ||
+ NULL, NULL, 0, msg, &err) ||
ref_transaction_commit(transaction, &err)) {
ref_transaction_free(transaction);
error("%s", err.buf);
@@ -1665,7 +1667,8 @@ static void dump_tags(void)
struct strbuf err = STRBUF_INIT;
struct ref_transaction *transaction;
- transaction = ref_transaction_begin(&err);
+ transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
+ &err);
if (!transaction) {
failure |= error("%s", err.buf);
goto cleanup;
@@ -1675,7 +1678,8 @@ static void dump_tags(void)
strbuf_addf(&ref_name, "refs/tags/%s", t->name);
if (ref_transaction_update(transaction, ref_name.buf,
- &t->oid, NULL, 0, msg, &err)) {
+ &t->oid, NULL, NULL, NULL,
+ 0, msg, &err)) {
failure |= error("%s", err.buf);
goto cleanup;
}
diff --git a/builtin/fetch.c b/builtin/fetch.c
index 5857d860db..75255dc600 100644
--- a/builtin/fetch.c
+++ b/builtin/fetch.c
@@ -340,7 +340,8 @@ static void find_non_local_tags(const struct ref *refs,
refname_hash_init(&remote_refs);
create_fetch_oidset(head, &fetch_oids);
- for_each_ref(add_one_refname, &existing_refs);
+ refs_for_each_ref(get_main_ref_store(the_repository), add_one_refname,
+ &existing_refs);
/*
* If we already have a transaction, then we need to filter out all
@@ -614,7 +615,9 @@ static struct ref *get_ref_map(struct remote *remote,
if (!existing_refs_populated) {
refname_hash_init(&existing_refs);
- for_each_ref(add_one_refname, &existing_refs);
+ refs_for_each_ref(get_main_ref_store(the_repository),
+ add_one_refname,
+ &existing_refs);
existing_refs_populated = 1;
}
@@ -659,7 +662,8 @@ static int s_update_ref(const char *action,
* lifecycle.
*/
if (!transaction) {
- transaction = our_transaction = ref_transaction_begin(&err);
+ transaction = our_transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
+ &err);
if (!transaction) {
ret = STORE_REF_ERROR_OTHER;
goto out;
@@ -668,7 +672,7 @@ static int s_update_ref(const char *action,
ret = ref_transaction_update(transaction, ref->name, &ref->new_oid,
check_old ? &ref->old_oid : NULL,
- 0, msg, &err);
+ NULL, NULL, 0, msg, &err);
if (ret) {
ret = STORE_REF_ERROR_OTHER;
goto out;
@@ -1393,7 +1397,9 @@ static int prune_refs(struct display_state *display_state,
for (ref = stale_refs; ref; ref = ref->next)
string_list_append(&refnames, ref->name);
- result = delete_refs("fetch: prune", &refnames, 0);
+ result = refs_delete_refs(get_main_ref_store(the_repository),
+ "fetch: prune", &refnames,
+ 0);
string_list_clear(&refnames, 0);
}
}
@@ -1479,7 +1485,8 @@ static void add_negotiation_tips(struct git_transport_options *smart_options)
continue;
}
old_nr = oids->nr;
- for_each_glob_ref(add_oid, s, oids);
+ refs_for_each_glob_ref(get_main_ref_store(the_repository),
+ add_oid, s, oids);
if (old_nr == oids->nr)
warning("ignoring --negotiation-tip=%s because it does not match any refs",
s);
@@ -1655,7 +1662,8 @@ static int do_fetch(struct transport *transport,
config->display_format);
if (atomic_fetch) {
- transaction = ref_transaction_begin(&err);
+ transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
+ &err);
if (!transaction) {
retcode = -1;
goto cleanup;
diff --git a/builtin/fsck.c b/builtin/fsck.c
index f892487c9b..d13a226c2e 100644
--- a/builtin/fsck.c
+++ b/builtin/fsck.c
@@ -514,7 +514,9 @@ static int fsck_handle_reflog(const char *logname, void *cb_data)
struct strbuf refname = STRBUF_INIT;
strbuf_worktree_ref(cb_data, &refname, logname);
- for_each_reflog_ent(refname.buf, fsck_handle_reflog_ent, refname.buf);
+ refs_for_each_reflog_ent(get_main_ref_store(the_repository),
+ refname.buf, fsck_handle_reflog_ent,
+ refname.buf);
strbuf_release(&refname);
return 0;
}
@@ -563,7 +565,8 @@ static void get_default_heads(void)
const char *head_points_at;
struct object_id head_oid;
- for_each_rawref(fsck_handle_ref, NULL);
+ refs_for_each_rawref(get_main_ref_store(the_repository),
+ fsck_handle_ref, NULL);
worktrees = get_worktrees();
for (p = worktrees; *p; p++) {
@@ -712,7 +715,9 @@ static int fsck_head_link(const char *head_ref_name,
if (verbose)
fprintf_ln(stderr, _("Checking %s link"), head_ref_name);
- *head_points_at = resolve_ref_unsafe(head_ref_name, 0, head_oid, NULL);
+ *head_points_at = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ head_ref_name, 0, head_oid,
+ NULL);
if (!*head_points_at) {
errors_found |= ERROR_REFS;
return error(_("invalid %s"), head_ref_name);
diff --git a/builtin/gc.c b/builtin/gc.c
index d3b5ca9bb1..054fca7835 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -907,7 +907,8 @@ static int should_write_commit_graph(void)
if (data.limit < 0)
return 1;
- result = for_each_ref(dfs_on_ref, &data);
+ result = refs_for_each_ref(get_main_ref_store(the_repository),
+ dfs_on_ref, &data);
repo_clear_commit_marks(the_repository, SEEN);
diff --git a/builtin/interpret-trailers.c b/builtin/interpret-trailers.c
index 8768bfea3c..1d969494cf 100644
--- a/builtin/interpret-trailers.c
+++ b/builtin/interpret-trailers.c
@@ -141,7 +141,7 @@ static void interpret_trailers(const struct process_trailer_options *opts,
LIST_HEAD(head);
struct strbuf sb = STRBUF_INIT;
struct strbuf trailer_block = STRBUF_INIT;
- struct trailer_info info;
+ struct trailer_info *info;
FILE *outfile = stdout;
trailer_config_init();
@@ -151,13 +151,13 @@ static void interpret_trailers(const struct process_trailer_options *opts,
if (opts->in_place)
outfile = create_in_place_tempfile(file);
- parse_trailers(opts, &info, sb.buf, &head);
+ info = parse_trailers(opts, sb.buf, &head);
/* Print the lines before the trailers */
if (!opts->only_trailers)
- fwrite(sb.buf, 1, info.trailer_block_start, outfile);
+ fwrite(sb.buf, 1, trailer_block_start(info), outfile);
- if (!opts->only_trailers && !info.blank_line_before_trailer)
+ if (!opts->only_trailers && !blank_line_before_trailer_block(info))
fprintf(outfile, "\n");
@@ -178,8 +178,8 @@ static void interpret_trailers(const struct process_trailer_options *opts,
/* Print the lines after the trailers as is */
if (!opts->only_trailers)
- fwrite(sb.buf + info.trailer_block_end, 1, sb.len - info.trailer_block_end, outfile);
- trailer_info_release(&info);
+ fwrite(sb.buf + trailer_block_end(info), 1, sb.len - trailer_block_end(info), outfile);
+ trailer_info_release(info);
if (opts->in_place)
if (rename_tempfile(&trailers_tempfile, file))
diff --git a/builtin/log.c b/builtin/log.c
index 4da7399905..b17dd8b40a 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -2226,8 +2226,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (check_head) {
const char *ref, *v;
- ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
- NULL, NULL);
+ ref = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ "HEAD",
+ RESOLVE_REF_READING,
+ NULL, NULL);
if (ref && skip_prefix(ref, "refs/heads/", &v))
branch_name = xstrdup(v);
else
diff --git a/builtin/merge.c b/builtin/merge.c
index 6a6d379885..e4bd65eeba 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -448,8 +448,10 @@ static void finish(struct commit *head_commit,
if (verbosity >= 0 && !merge_msg.len)
printf(_("No merge message -- not updating HEAD\n"));
else {
- update_ref(reflog_message.buf, "HEAD", new_head, head,
- 0, UPDATE_REFS_DIE_ON_ERR);
+ refs_update_ref(get_main_ref_store(the_repository),
+ reflog_message.buf, "HEAD", new_head,
+ head,
+ 0, UPDATE_REFS_DIE_ON_ERR);
/*
* We ignore errors in 'gc --auto', since the
* user should see them.
@@ -546,7 +548,7 @@ static void merge_name(const char *remote, struct strbuf *msg)
struct strbuf truname = STRBUF_INIT;
strbuf_addf(&truname, "refs/heads/%s", remote);
strbuf_setlen(&truname, truname.len - len);
- if (ref_exists(truname.buf)) {
+ if (refs_ref_exists(get_main_ref_store(the_repository), truname.buf)) {
strbuf_addf(msg,
"%s\t\tbranch '%s'%s of .\n",
oid_to_hex(&remote_head->object.oid),
@@ -1251,7 +1253,7 @@ static int merging_a_throwaway_tag(struct commit *commit)
*/
tag_ref = xstrfmt("refs/tags/%s",
((struct tag *)merge_remote_util(commit)->obj)->tag);
- if (!read_ref(tag_ref, &oid) &&
+ if (!refs_read_ref(get_main_ref_store(the_repository), tag_ref, &oid) &&
oideq(&oid, &merge_remote_util(commit)->obj->oid))
is_throwaway_tag = 0;
else
@@ -1283,7 +1285,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
* Check if we are _not_ on a detached HEAD, i.e. if there is a
* current branch.
*/
- branch = branch_to_free = resolve_refdup("HEAD", 0, &head_oid, NULL);
+ branch = branch_to_free = refs_resolve_refdup(get_main_ref_store(the_repository),
+ "HEAD", 0, &head_oid,
+ NULL);
if (branch)
skip_prefix(branch, "refs/heads/", &branch);
@@ -1324,8 +1328,10 @@ 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_ref("MERGE_AUTOSTASH", &stash_oid))
- delete_ref("", "MERGE_AUTOSTASH", &stash_oid, REF_NO_DEREF);
+ if (!refs_read_ref(get_main_ref_store(the_repository), "MERGE_AUTOSTASH", &stash_oid))
+ refs_delete_ref(get_main_ref_store(the_repository),
+ "", "MERGE_AUTOSTASH", &stash_oid,
+ REF_NO_DEREF);
/* Invoke 'git reset --merge' */
ret = cmd_reset(nargc, nargv, prefix);
@@ -1378,7 +1384,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
else
die(_("You have not concluded your merge (MERGE_HEAD exists)."));
}
- if (ref_exists("CHERRY_PICK_HEAD")) {
+ if (refs_ref_exists(get_main_ref_store(the_repository), "CHERRY_PICK_HEAD")) {
if (advice_enabled(ADVICE_RESOLVE_CONFLICT))
die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n"
"Please, commit your changes before you merge."));
@@ -1449,8 +1455,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
remote_head_oid = &remoteheads->item->object.oid;
read_empty(remote_head_oid);
- update_ref("initial pull", "HEAD", remote_head_oid, NULL, 0,
- UPDATE_REFS_DIE_ON_ERR);
+ refs_update_ref(get_main_ref_store(the_repository),
+ "initial pull", "HEAD", remote_head_oid, NULL,
+ 0,
+ UPDATE_REFS_DIE_ON_ERR);
goto done;
}
@@ -1530,8 +1538,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
free(list);
}
- update_ref("updating ORIG_HEAD", "ORIG_HEAD",
- &head_commit->object.oid, NULL, 0, UPDATE_REFS_DIE_ON_ERR);
+ refs_update_ref(get_main_ref_store(the_repository),
+ "updating ORIG_HEAD", "ORIG_HEAD",
+ &head_commit->object.oid, NULL, 0,
+ UPDATE_REFS_DIE_ON_ERR);
if (remoteheads && !common) {
/* No common ancestors found. */
diff --git a/builtin/name-rev.c b/builtin/name-rev.c
index ad9930c831..70e9ec4e47 100644
--- a/builtin/name-rev.c
+++ b/builtin/name-rev.c
@@ -296,7 +296,8 @@ static void add_to_tip_table(const struct object_id *oid, const char *refname,
char *short_refname = NULL;
if (shorten_unambiguous)
- short_refname = shorten_unambiguous_ref(refname, 0);
+ short_refname = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+ refname, 0);
else if (skip_prefix(refname, "refs/heads/", &refname))
; /* refname already advanced */
else
@@ -647,7 +648,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
adjust_cutoff_timestamp_for_slop();
- for_each_ref(name_ref, &data);
+ refs_for_each_ref(get_main_ref_store(the_repository), name_ref, &data);
name_tips(&string_pool);
if (annotate_stdin) {
diff --git a/builtin/notes.c b/builtin/notes.c
index cb011303e6..7f80b3449b 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -794,9 +794,9 @@ static int merge_abort(struct notes_merge_options *o)
* notes_merge_abort() to remove .git/NOTES_MERGE_WORKTREE.
*/
- if (delete_ref(NULL, "NOTES_MERGE_PARTIAL", NULL, 0))
+ if (refs_delete_ref(get_main_ref_store(the_repository), NULL, "NOTES_MERGE_PARTIAL", NULL, 0))
ret += error(_("failed to delete ref NOTES_MERGE_PARTIAL"));
- if (delete_ref(NULL, "NOTES_MERGE_REF", NULL, REF_NO_DEREF))
+ if (refs_delete_ref(get_main_ref_store(the_repository), NULL, "NOTES_MERGE_REF", NULL, REF_NO_DEREF))
ret += error(_("failed to delete ref NOTES_MERGE_REF"));
if (notes_merge_abort(o))
ret += error(_("failed to remove 'git notes merge' worktree"));
@@ -834,7 +834,8 @@ static int merge_commit(struct notes_merge_options *o)
init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0);
o->local_ref = local_ref_to_free =
- resolve_refdup("NOTES_MERGE_REF", 0, &oid, NULL);
+ refs_resolve_refdup(get_main_ref_store(the_repository),
+ "NOTES_MERGE_REF", 0, &oid, NULL);
if (!o->local_ref)
die(_("failed to resolve NOTES_MERGE_REF"));
@@ -847,9 +848,10 @@ static int merge_commit(struct notes_merge_options *o)
&pretty_ctx);
strbuf_trim(&msg);
strbuf_insertstr(&msg, 0, "notes: ");
- update_ref(msg.buf, o->local_ref, &oid,
- is_null_oid(&parent_oid) ? NULL : &parent_oid,
- 0, UPDATE_REFS_DIE_ON_ERR);
+ refs_update_ref(get_main_ref_store(the_repository), msg.buf,
+ o->local_ref, &oid,
+ is_null_oid(&parent_oid) ? NULL : &parent_oid,
+ 0, UPDATE_REFS_DIE_ON_ERR);
free_notes(t);
strbuf_release(&msg);
@@ -961,14 +963,16 @@ static int merge(int argc, const char **argv, const char *prefix)
if (result >= 0) /* Merge resulted (trivially) in result_oid */
/* Update default notes ref with new commit */
- update_ref(msg.buf, default_notes_ref(), &result_oid, NULL, 0,
- UPDATE_REFS_DIE_ON_ERR);
+ refs_update_ref(get_main_ref_store(the_repository), msg.buf,
+ default_notes_ref(), &result_oid, NULL, 0,
+ UPDATE_REFS_DIE_ON_ERR);
else { /* Merge has unresolved conflicts */
struct worktree **worktrees;
const struct worktree *wt;
/* Update .git/NOTES_MERGE_PARTIAL with partial merge result */
- update_ref(msg.buf, "NOTES_MERGE_PARTIAL", &result_oid, NULL,
- 0, UPDATE_REFS_DIE_ON_ERR);
+ refs_update_ref(get_main_ref_store(the_repository), msg.buf,
+ "NOTES_MERGE_PARTIAL", &result_oid, NULL,
+ 0, UPDATE_REFS_DIE_ON_ERR);
/* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
worktrees = get_worktrees();
wt = find_shared_symref(worktrees, "NOTES_MERGE_REF",
@@ -977,7 +981,7 @@ static int merge(int argc, const char **argv, const char *prefix)
die(_("a notes merge into %s is already in-progress at %s"),
default_notes_ref(), wt->path);
free_worktrees(worktrees);
- if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
+ if (refs_update_symref(get_main_ref_store(the_repository), "NOTES_MERGE_REF", default_notes_ref(), NULL))
die(_("failed to store link to current notes ref (%s)"),
default_notes_ref());
fprintf(stderr, _("Automatic notes merge failed. Fix conflicts in %s "
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index baf0090fc8..cd2396896d 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -939,7 +939,8 @@ static struct object_entry **compute_write_order(void)
/*
* Mark objects that are at the tip of tags.
*/
- for_each_tag_ref(mark_tagged, NULL);
+ refs_for_each_tag_ref(get_main_ref_store(the_repository), mark_tagged,
+ NULL);
if (use_delta_islands) {
max_layers = compute_pack_layers(&to_pack);
@@ -4093,7 +4094,9 @@ static void mark_bitmap_preferred_tips(void)
return;
for_each_string_list_item(item, preferred_tips) {
- for_each_ref_in(item->string, mark_bitmap_preferred_tip, NULL);
+ refs_for_each_ref_in(get_main_ref_store(the_repository),
+ item->string, mark_bitmap_preferred_tip,
+ NULL);
}
}
@@ -4588,7 +4591,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
}
cleanup_preferred_base();
if (include_tag && nr_result)
- for_each_tag_ref(add_ref_tag, NULL);
+ refs_for_each_tag_ref(get_main_ref_store(the_repository),
+ add_ref_tag, NULL);
stop_progress(&progress_state);
trace2_region_leave("pack-objects", "enumerate-objects",
the_repository);
diff --git a/builtin/pull.c b/builtin/pull.c
index 66869210db..d622202bce 100644
--- a/builtin/pull.c
+++ b/builtin/pull.c
@@ -611,7 +611,7 @@ static int pull_into_void(const struct object_id *merge_head,
merge_head, 0))
return 1;
- if (update_ref("initial pull", "HEAD", merge_head, curr_head, 0, UPDATE_REFS_DIE_ON_ERR))
+ if (refs_update_ref(get_main_ref_store(the_repository), "initial pull", "HEAD", merge_head, curr_head, 0, UPDATE_REFS_DIE_ON_ERR))
return 1;
return 0;
diff --git a/builtin/rebase.c b/builtin/rebase.c
index fe17d562a8..0466d9414a 100644
--- a/builtin/rebase.c
+++ b/builtin/rebase.c
@@ -251,7 +251,7 @@ static int init_basic_state(struct replay_opts *opts, const char *head_name,
if (!is_directory(merge_dir()) && mkdir_in_gitdir(merge_dir()))
return error_errno(_("could not create temporary %s"), merge_dir());
- delete_reflog("REBASE_HEAD");
+ refs_delete_reflog(get_main_ref_store(the_repository), "REBASE_HEAD");
interactive = fopen(path_interactive(), "w");
if (!interactive)
@@ -513,8 +513,10 @@ static int finish_rebase(struct rebase_options *opts)
struct strbuf dir = STRBUF_INIT;
int ret = 0;
- delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF);
- delete_ref(NULL, "AUTO_MERGE", NULL, REF_NO_DEREF);
+ refs_delete_ref(get_main_ref_store(the_repository), NULL,
+ "REBASE_HEAD", NULL, REF_NO_DEREF);
+ refs_delete_ref(get_main_ref_store(the_repository), NULL,
+ "AUTO_MERGE", NULL, REF_NO_DEREF);
apply_autostash(state_dir_path("autostash", opts));
/*
* We ignore errors in 'git maintenance run --auto', since the
@@ -1622,7 +1624,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
/* Is it a local branch? */
strbuf_reset(&buf);
strbuf_addf(&buf, "refs/heads/%s", branch_name);
- if (!read_ref(buf.buf, &branch_oid)) {
+ if (!refs_read_ref(get_main_ref_store(the_repository), buf.buf, &branch_oid)) {
die_if_checked_out(buf.buf, 1);
options.head_name = xstrdup(buf.buf);
options.orig_head =
@@ -1639,8 +1641,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
} else if (argc == 0) {
/* Do not need to switch branches, we are already on it. */
options.head_name =
- xstrdup_or_null(resolve_ref_unsafe("HEAD", 0, NULL,
- &flags));
+ xstrdup_or_null(refs_resolve_ref_unsafe(get_main_ref_store(the_repository), "HEAD", 0, NULL,
+ &flags));
if (!options.head_name)
die(_("No such ref: %s"), "HEAD");
if (flags & REF_ISSYMREF) {
@@ -1734,7 +1736,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (!(options.flags & REBASE_NO_QUIET))
; /* be quiet */
else if (!strcmp(branch_name, "HEAD") &&
- resolve_ref_unsafe("HEAD", 0, NULL, &flag))
+ refs_resolve_ref_unsafe(get_main_ref_store(the_repository), "HEAD", 0, NULL, &flag))
puts(_("HEAD is up to date."));
else
printf(_("Current branch %s is up to date.\n"),
@@ -1744,7 +1746,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
} else if (!(options.flags & REBASE_NO_QUIET))
; /* be quiet */
else if (!strcmp(branch_name, "HEAD") &&
- resolve_ref_unsafe("HEAD", 0, NULL, &flag))
+ refs_resolve_ref_unsafe(get_main_ref_store(the_repository), "HEAD", 0, NULL, &flag))
puts(_("HEAD is up to date, rebase forced."));
else
printf(_("Current branch %s is up to date, rebase "
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index e8d7df14b6..be8969a84a 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1566,7 +1566,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
struct strbuf err = STRBUF_INIT;
if (!parse_object(the_repository, old_oid)) {
old_oid = NULL;
- if (ref_exists(name)) {
+ if (refs_ref_exists(get_main_ref_store(the_repository), name)) {
rp_warning("allowing deletion of corrupt ref");
} else {
rp_warning("deleting a non-existent ref");
@@ -1595,6 +1595,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
if (ref_transaction_update(transaction,
namespaced_name,
new_oid, old_oid,
+ NULL, NULL,
0, "push",
&err)) {
rp_error("%s", err.buf);
@@ -1693,7 +1694,8 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
int flag;
strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name);
- dst_name = resolve_ref_unsafe(buf.buf, 0, NULL, &flag);
+ dst_name = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ buf.buf, 0, NULL, &flag);
check_aliased_update_internal(cmd, list, dst_name, flag);
strbuf_release(&buf);
}
@@ -1829,7 +1831,8 @@ static void execute_commands_non_atomic(struct command *commands,
if (!should_process_cmd(cmd) || cmd->run_proc_receive)
continue;
- transaction = ref_transaction_begin(&err);
+ transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
+ &err);
if (!transaction) {
rp_error("%s", err.buf);
strbuf_reset(&err);
@@ -1857,7 +1860,8 @@ static void execute_commands_atomic(struct command *commands,
struct strbuf err = STRBUF_INIT;
const char *reported_error = "atomic push failure";
- transaction = ref_transaction_begin(&err);
+ transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
+ &err);
if (!transaction) {
rp_error("%s", err.buf);
strbuf_reset(&err);
@@ -1983,7 +1987,9 @@ static void execute_commands(struct command *commands,
check_aliased_updates(commands);
free(head_name_to_free);
- head_name = head_name_to_free = resolve_refdup("HEAD", 0, NULL, NULL);
+ head_name = head_name_to_free = refs_resolve_refdup(get_main_ref_store(the_repository),
+ "HEAD", 0, NULL,
+ NULL);
if (run_proc_receive &&
run_proc_receive_hook(commands, push_options))
diff --git a/builtin/reflog.c b/builtin/reflog.c
index 060eb3377e..b4650cea16 100644
--- a/builtin/reflog.c
+++ b/builtin/reflog.c
@@ -364,11 +364,12 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
};
set_reflog_expiry_param(&cb.cmd, item->string);
- status |= reflog_expire(item->string, flags,
- reflog_expiry_prepare,
- should_prune_fn,
- reflog_expiry_cleanup,
- &cb);
+ status |= refs_reflog_expire(get_main_ref_store(the_repository),
+ item->string, flags,
+ reflog_expiry_prepare,
+ should_prune_fn,
+ reflog_expiry_cleanup,
+ &cb);
}
string_list_clear(&collected.reflogs, 0);
}
@@ -382,11 +383,12 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
continue;
}
set_reflog_expiry_param(&cb.cmd, ref);
- status |= reflog_expire(ref, flags,
- reflog_expiry_prepare,
- should_prune_fn,
- reflog_expiry_cleanup,
- &cb);
+ status |= refs_reflog_expire(get_main_ref_store(the_repository),
+ ref, flags,
+ reflog_expiry_prepare,
+ should_prune_fn,
+ reflog_expiry_cleanup,
+ &cb);
free(ref);
}
return status;
@@ -437,7 +439,8 @@ static int cmd_reflog_exists(int argc, const char **argv, const char *prefix)
refname = argv[0];
if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
die(_("invalid ref format: %s"), refname);
- return !reflog_exists(refname);
+ return !refs_reflog_exists(get_main_ref_store(the_repository),
+ refname);
}
/*
diff --git a/builtin/remote.c b/builtin/remote.c
index 8412d12fa5..d52b1c0e10 100644
--- a/builtin/remote.c
+++ b/builtin/remote.c
@@ -240,7 +240,7 @@ static int add(int argc, const char **argv, const char *prefix)
strbuf_reset(&buf2);
strbuf_addf(&buf2, "refs/remotes/%s/%s", name, master);
- if (create_symref(buf.buf, buf2.buf, "remote add"))
+ if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote add"))
return error(_("Could not setup master '%s'"), master);
}
@@ -376,7 +376,7 @@ static int get_ref_states(const struct ref *remote_refs, struct ref_states *stat
for (ref = fetch_map; ref; ref = ref->next) {
if (omit_name_by_refspec(ref->name, &states->remote->fetch))
string_list_append(&states->skipped, abbrev_branch(ref->name));
- else if (!ref->peer_ref || !ref_exists(ref->peer_ref->name))
+ else if (!ref->peer_ref || !refs_ref_exists(get_main_ref_store(the_repository), ref->peer_ref->name))
string_list_append(&states->new_refs, abbrev_branch(ref->name));
else
string_list_append(&states->tracked, abbrev_branch(ref->name));
@@ -598,8 +598,9 @@ static int read_remote_branches(const char *refname,
strbuf_addf(&buf, "refs/remotes/%s/", rename->old_name);
if (starts_with(refname, buf.buf)) {
item = string_list_append(rename->remote_branches, refname);
- symref = resolve_ref_unsafe(refname, RESOLVE_REF_READING,
- NULL, &flag);
+ symref = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ refname, RESOLVE_REF_READING,
+ NULL, &flag);
if (symref && (flag & REF_ISSYMREF)) {
item->util = xstrdup(symref);
rename->symrefs_nr++;
@@ -789,7 +790,8 @@ static int mv(int argc, const char **argv, const char *prefix)
* First remove symrefs, then rename the rest, finally create
* the new symrefs.
*/
- for_each_ref(read_remote_branches, &rename);
+ refs_for_each_ref(get_main_ref_store(the_repository),
+ read_remote_branches, &rename);
if (show_progress) {
/*
* Count symrefs twice, since "renaming" them is done by
@@ -805,7 +807,7 @@ static int mv(int argc, const char **argv, const char *prefix)
if (refs_read_symbolic_ref(get_main_ref_store(the_repository), item->string,
&referent))
continue;
- if (delete_ref(NULL, item->string, NULL, REF_NO_DEREF))
+ if (refs_delete_ref(get_main_ref_store(the_repository), NULL, item->string, NULL, REF_NO_DEREF))
die(_("deleting '%s' failed"), item->string);
strbuf_release(&referent);
@@ -823,7 +825,7 @@ static int mv(int argc, const char **argv, const char *prefix)
strbuf_reset(&buf2);
strbuf_addf(&buf2, "remote: renamed %s to %s",
item->string, buf.buf);
- if (rename_ref(item->string, buf.buf, buf2.buf))
+ if (refs_rename_ref(get_main_ref_store(the_repository), item->string, buf.buf, buf2.buf))
die(_("renaming '%s' failed"), item->string);
display_progress(progress, ++refs_renamed_nr);
}
@@ -843,7 +845,7 @@ static int mv(int argc, const char **argv, const char *prefix)
strbuf_reset(&buf3);
strbuf_addf(&buf3, "remote: renamed %s to %s",
item->string, buf.buf);
- if (create_symref(buf.buf, buf2.buf, buf3.buf))
+ if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf))
die(_("creating '%s' failed"), buf.buf);
display_progress(progress, ++refs_renamed_nr);
}
@@ -917,11 +919,14 @@ static int rm(int argc, const char **argv, const char *prefix)
* refs, which are invalidated when deleting a branch.
*/
cb_data.remote = remote;
- result = for_each_ref(add_branch_for_removal, &cb_data);
+ result = refs_for_each_ref(get_main_ref_store(the_repository),
+ add_branch_for_removal, &cb_data);
strbuf_release(&buf);
if (!result)
- result = delete_refs("remote: remove", &branches, REF_NO_DEREF);
+ result = refs_delete_refs(get_main_ref_store(the_repository),
+ "remote: remove", &branches,
+ REF_NO_DEREF);
string_list_clear(&branches, 0);
if (skipped.nr) {
@@ -1010,7 +1015,8 @@ static int get_remote_ref_states(const char *name,
get_push_ref_states(remote_refs, states);
transport_disconnect(transport);
} else {
- for_each_ref(append_ref_to_tracked_list, states);
+ refs_for_each_ref(get_main_ref_store(the_repository),
+ append_ref_to_tracked_list, states);
string_list_sort(&states->tracked);
get_push_ref_states_noquery(states);
}
@@ -1407,7 +1413,7 @@ static int set_head(int argc, const char **argv, const char *prefix)
head_name = xstrdup(states.heads.items[0].string);
free_remote_ref_states(&states);
} else if (opt_d && !opt_a && argc == 1) {
- if (delete_ref(NULL, buf.buf, NULL, REF_NO_DEREF))
+ if (refs_delete_ref(get_main_ref_store(the_repository), NULL, buf.buf, NULL, REF_NO_DEREF))
result |= error(_("Could not delete %s"), buf.buf);
} else
usage_with_options(builtin_remote_sethead_usage, options);
@@ -1415,9 +1421,9 @@ static int set_head(int argc, const char **argv, const char *prefix)
if (head_name) {
strbuf_addf(&buf2, "refs/remotes/%s/%s", argv[0], head_name);
/* make sure it's valid */
- if (!ref_exists(buf2.buf))
+ if (!refs_ref_exists(get_main_ref_store(the_repository), buf2.buf))
result |= error(_("Not a valid ref: %s"), buf2.buf);
- else if (create_symref(buf.buf, buf2.buf, "remote set-head"))
+ else if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, "remote set-head"))
result |= error(_("Could not setup %s"), buf.buf);
else if (opt_a)
printf("%s/HEAD set to %s\n", argv[0], head_name);
@@ -1457,7 +1463,8 @@ static int prune_remote(const char *remote, int dry_run)
string_list_sort(&refs_to_prune);
if (!dry_run)
- result |= delete_refs("remote: prune", &refs_to_prune, 0);
+ result |= refs_delete_refs(get_main_ref_store(the_repository),
+ "remote: prune", &refs_to_prune, 0);
for_each_string_list_item(item, &states.stale) {
const char *refname = item->util;
diff --git a/builtin/repack.c b/builtin/repack.c
index 15e4cccc45..43491a4cbf 100644
--- a/builtin/repack.c
+++ b/builtin/repack.c
@@ -706,11 +706,14 @@ static void midx_snapshot_refs(struct tempfile *f)
data.preferred = 1;
for_each_string_list_item(item, preferred)
- for_each_ref_in(item->string, midx_snapshot_ref_one, &data);
+ refs_for_each_ref_in(get_main_ref_store(the_repository),
+ item->string,
+ midx_snapshot_ref_one, &data);
data.preferred = 0;
}
- for_each_ref(midx_snapshot_ref_one, &data);
+ refs_for_each_ref(get_main_ref_store(the_repository),
+ midx_snapshot_ref_one, &data);
if (close_tempfile_gently(f)) {
int save_errno = errno;
diff --git a/builtin/replace.c b/builtin/replace.c
index da59600ad2..f46ff57691 100644
--- a/builtin/replace.c
+++ b/builtin/replace.c
@@ -130,7 +130,7 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn)
strbuf_addstr(&ref, oid_to_hex(&oid));
full_hex = ref.buf + base_len;
- if (read_ref(ref.buf, &oid)) {
+ if (refs_read_ref(get_main_ref_store(the_repository), ref.buf, &oid)) {
error(_("replace ref '%s' not found"), full_hex);
had_error = 1;
continue;
@@ -145,7 +145,7 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn)
static int delete_replace_ref(const char *name, const char *ref,
const struct object_id *oid)
{
- if (delete_ref(NULL, ref, oid, 0))
+ if (refs_delete_ref(get_main_ref_store(the_repository), NULL, ref, oid, 0))
return 1;
printf_ln(_("Deleted replace ref '%s'"), name);
return 0;
@@ -163,7 +163,7 @@ static int check_ref_valid(struct object_id *object,
if (check_refname_format(ref->buf, 0))
return error(_("'%s' is not a valid ref name"), ref->buf);
- if (read_ref(ref->buf, prev))
+ if (refs_read_ref(get_main_ref_store(the_repository), ref->buf, prev))
oidclr(prev);
else if (!force)
return error(_("replace ref '%s' already exists"), ref->buf);
@@ -198,10 +198,11 @@ static int replace_object_oid(const char *object_ref,
return -1;
}
- transaction = ref_transaction_begin(&err);
+ transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
+ &err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf, repl, &prev,
- 0, NULL, &err) ||
+ NULL, NULL, 0, NULL, &err) ||
ref_transaction_commit(transaction, &err))
res = error("%s", err.buf);
diff --git a/builtin/reset.c b/builtin/reset.c
index b6dacf9678..5f941fb3a2 100644
--- a/builtin/reset.c
+++ b/builtin/reset.c
@@ -307,13 +307,16 @@ static int reset_refs(const char *rev, const struct object_id *oid)
if (!repo_get_oid(the_repository, "HEAD", &oid_orig)) {
orig = &oid_orig;
set_reflog_message(&msg, "updating ORIG_HEAD", NULL);
- update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0,
- UPDATE_REFS_MSG_ON_ERR);
+ refs_update_ref(get_main_ref_store(the_repository), msg.buf,
+ "ORIG_HEAD", orig, old_orig, 0,
+ UPDATE_REFS_MSG_ON_ERR);
} else if (old_orig)
- delete_ref(NULL, "ORIG_HEAD", old_orig, 0);
+ refs_delete_ref(get_main_ref_store(the_repository), NULL,
+ "ORIG_HEAD", old_orig, 0);
set_reflog_message(&msg, "updating HEAD", rev);
- update_ref_status = update_ref(msg.buf, "HEAD", oid, orig, 0,
- UPDATE_REFS_MSG_ON_ERR);
+ update_ref_status = refs_update_ref(get_main_ref_store(the_repository),
+ msg.buf, "HEAD", oid, orig, 0,
+ UPDATE_REFS_MSG_ON_ERR);
strbuf_release(&msg);
return update_ref_status;
}
diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c
index af79538632..1e2919fd81 100644
--- a/builtin/rev-parse.c
+++ b/builtin/rev-parse.c
@@ -160,8 +160,9 @@ static void show_rev(int type, const struct object_id *oid, const char *name)
case 1: /* happy */
if (abbrev_ref) {
char *old = full;
- full = shorten_unambiguous_ref(full,
- abbrev_ref_strict);
+ full = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+ full,
+ abbrev_ref_strict);
free(old);
}
show_with_type(type, full);
@@ -599,9 +600,12 @@ static int opt_with_value(const char *arg, const char *opt, const char **value)
static void handle_ref_opt(const char *pattern, const char *prefix)
{
if (pattern)
- for_each_glob_ref_in(show_reference, pattern, prefix, NULL);
+ refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
+ show_reference, pattern, prefix,
+ NULL);
else
- for_each_ref_in(prefix, show_reference, NULL);
+ refs_for_each_ref_in(get_main_ref_store(the_repository),
+ prefix, show_reference, NULL);
clear_ref_exclusions(&ref_excludes);
}
@@ -687,7 +691,6 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
const char *name = NULL;
struct object_context unused;
struct strbuf buf = STRBUF_INIT;
- const int hexsz = the_hash_algo->hexsz;
int seen_end_of_options = 0;
enum format_type format = FORMAT_DEFAULT;
@@ -863,8 +866,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
abbrev = strtoul(arg, NULL, 10);
if (abbrev < MINIMUM_ABBREV)
abbrev = MINIMUM_ABBREV;
- else if (hexsz <= abbrev)
- abbrev = hexsz;
+ else if ((int)the_hash_algo->hexsz <= abbrev)
+ abbrev = the_hash_algo->hexsz;
continue;
}
if (!strcmp(arg, "--sq")) {
@@ -898,7 +901,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
continue;
}
if (!strcmp(arg, "--all")) {
- for_each_ref(show_reference, NULL);
+ refs_for_each_ref(get_main_ref_store(the_repository),
+ show_reference, NULL);
clear_ref_exclusions(&ref_excludes);
continue;
}
@@ -908,8 +912,14 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
continue;
}
if (!strcmp(arg, "--bisect")) {
- for_each_fullref_in("refs/bisect/bad", show_reference, NULL);
- for_each_fullref_in("refs/bisect/good", anti_reference, NULL);
+ refs_for_each_fullref_in(get_main_ref_store(the_repository),
+ "refs/bisect/bad",
+ NULL, show_reference,
+ NULL);
+ refs_for_each_fullref_in(get_main_ref_store(the_repository),
+ "refs/bisect/good",
+ NULL, anti_reference,
+ NULL);
continue;
}
if (opt_with_value(arg, "--branches", &arg)) {
diff --git a/builtin/shortlog.c b/builtin/shortlog.c
index 3c7cd2d6ef..d4daf31e22 100644
--- a/builtin/shortlog.c
+++ b/builtin/shortlog.c
@@ -435,7 +435,7 @@ parse_done:
usage_with_options(shortlog_usage, options);
}
- if (setup_revisions(argc, argv, &rev, NULL) != 1) {
+ if (!nongit && setup_revisions(argc, argv, &rev, NULL) != 1) {
error(_("unrecognized argument: %s"), argv[1]);
usage_with_options(shortlog_usage, options);
}
diff --git a/builtin/show-branch.c b/builtin/show-branch.c
index b01ec761d2..d72f4cb98d 100644
--- a/builtin/show-branch.c
+++ b/builtin/show-branch.c
@@ -479,13 +479,15 @@ static void snarf_refs(int head, int remotes)
if (head) {
int orig_cnt = ref_name_cnt;
- for_each_ref(append_head_ref, NULL);
+ refs_for_each_ref(get_main_ref_store(the_repository),
+ append_head_ref, NULL);
sort_ref_range(orig_cnt, ref_name_cnt);
}
if (remotes) {
int orig_cnt = ref_name_cnt;
- for_each_ref(append_remote_ref, NULL);
+ refs_for_each_ref(get_main_ref_store(the_repository),
+ append_remote_ref, NULL);
sort_ref_range(orig_cnt, ref_name_cnt);
}
}
@@ -549,7 +551,8 @@ static void append_one_rev(const char *av)
match_ref_pattern = av;
match_ref_slash = count_slashes(av);
- for_each_ref(append_matching_ref, NULL);
+ refs_for_each_ref(get_main_ref_store(the_repository),
+ append_matching_ref, NULL);
if (saved_matches == ref_name_cnt &&
ref_name_cnt < MAX_REVS)
error(_("no matching refs with %s"), av);
@@ -740,9 +743,11 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
if (ac == 0) {
static const char *fake_av[2];
- fake_av[0] = resolve_refdup("HEAD",
- RESOLVE_REF_READING, &oid,
- NULL);
+ fake_av[0] = refs_resolve_refdup(get_main_ref_store(the_repository),
+ "HEAD",
+ RESOLVE_REF_READING,
+ &oid,
+ NULL);
fake_av[1] = NULL;
av = fake_av;
ac = 1;
@@ -815,8 +820,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
snarf_refs(all_heads, all_remotes);
}
- head = resolve_refdup("HEAD", RESOLVE_REF_READING,
- &head_oid, NULL);
+ head = refs_resolve_refdup(get_main_ref_store(the_repository), "HEAD",
+ RESOLVE_REF_READING,
+ &head_oid, NULL);
if (with_current_branch && head) {
int has_head = 0;
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 1c15421e60..151ef35134 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -129,7 +129,8 @@ static int cmd_show_ref__exclude_existing(const struct exclude_existing_options
char buf[1024];
int patternlen = opts->pattern ? strlen(opts->pattern) : 0;
- for_each_ref(add_existing, &existing_refs);
+ refs_for_each_ref(get_main_ref_store(the_repository), add_existing,
+ &existing_refs);
while (fgets(buf, sizeof(buf), stdin)) {
char *ref;
int len = strlen(buf);
@@ -173,7 +174,7 @@ static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,
struct object_id oid;
if ((starts_with(*refs, "refs/") || refname_is_safe(*refs)) &&
- !read_ref(*refs, &oid)) {
+ !refs_read_ref(get_main_ref_store(the_repository), *refs, &oid)) {
show_one(show_one_opts, *refs, &oid);
}
else if (!show_one_opts->quiet)
@@ -205,14 +206,20 @@ static int cmd_show_ref__patterns(const struct patterns_options *opts,
show_ref_data.patterns = patterns;
if (opts->show_head)
- head_ref(show_ref, &show_ref_data);
+ refs_head_ref(get_main_ref_store(the_repository), show_ref,
+ &show_ref_data);
if (opts->heads_only || opts->tags_only) {
if (opts->heads_only)
- for_each_fullref_in("refs/heads/", show_ref, &show_ref_data);
+ refs_for_each_fullref_in(get_main_ref_store(the_repository),
+ "refs/heads/", NULL,
+ show_ref, &show_ref_data);
if (opts->tags_only)
- for_each_fullref_in("refs/tags/", show_ref, &show_ref_data);
+ refs_for_each_fullref_in(get_main_ref_store(the_repository),
+ "refs/tags/", NULL, show_ref,
+ &show_ref_data);
} else {
- for_each_ref(show_ref, &show_ref_data);
+ refs_for_each_ref(get_main_ref_store(the_repository),
+ show_ref, &show_ref_data);
}
if (!show_ref_data.found_match)
return 1;
diff --git a/builtin/stash.c b/builtin/stash.c
index bf2834fddd..7859bc0866 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -195,7 +195,7 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv)
commit = argv[0];
if (!commit) {
- if (!ref_exists(ref_stash)) {
+ if (!refs_ref_exists(get_main_ref_store(the_repository), ref_stash)) {
fprintf_ln(stderr, _("No stash entries found."));
return -1;
}
@@ -243,7 +243,8 @@ static int do_clear_stash(void)
if (repo_get_oid(the_repository, ref_stash, &obj))
return 0;
- return delete_ref(NULL, ref_stash, &obj, 0);
+ return refs_delete_ref(get_main_ref_store(the_repository), NULL,
+ ref_stash, &obj, 0);
}
static int clear_stash(int argc, const char **argv, const char *prefix)
@@ -686,7 +687,8 @@ static int reject_reflog_ent(struct object_id *ooid UNUSED,
static int reflog_is_empty(const char *refname)
{
- return !for_each_reflog_ent(refname, reject_reflog_ent, NULL);
+ return !refs_for_each_reflog_ent(get_main_ref_store(the_repository),
+ refname, reject_reflog_ent, NULL);
}
static int do_drop_stash(struct stash_info *info, int quiet)
@@ -823,7 +825,7 @@ static int list_stash(int argc, const char **argv, const char *prefix)
git_stash_list_usage,
PARSE_OPT_KEEP_UNKNOWN_OPT);
- if (!ref_exists(ref_stash))
+ if (!refs_ref_exists(get_main_ref_store(the_repository), ref_stash))
return 0;
cp.git_cmd = 1;
@@ -997,10 +999,10 @@ static int do_store_stash(const struct object_id *w_commit, const char *stash_ms
if (!stash_msg)
stash_msg = "Created via \"git stash store\".";
- if (update_ref(stash_msg, ref_stash, w_commit, NULL,
- REF_FORCE_CREATE_REFLOG,
- quiet ? UPDATE_REFS_QUIET_ON_ERR :
- UPDATE_REFS_MSG_ON_ERR)) {
+ if (refs_update_ref(get_main_ref_store(the_repository), stash_msg, ref_stash, w_commit, NULL,
+ REF_FORCE_CREATE_REFLOG,
+ quiet ? UPDATE_REFS_QUIET_ON_ERR :
+ UPDATE_REFS_MSG_ON_ERR)) {
if (!quiet) {
fprintf_ln(stderr, _("Cannot update %s with %s"),
ref_stash, oid_to_hex(w_commit));
@@ -1383,7 +1385,8 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b
goto done;
}
- branch_ref = resolve_ref_unsafe("HEAD", 0, NULL, &flags);
+ branch_ref = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ "HEAD", 0, NULL, &flags);
if (flags & REF_ISSYMREF)
skip_prefix(branch_ref, "refs/heads/", &branch_name);
head_short_sha1 = repo_find_unique_abbrev(the_repository,
@@ -1565,7 +1568,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q
goto done;
}
- if (!reflog_exists(ref_stash) && do_clear_stash()) {
+ if (!refs_reflog_exists(get_main_ref_store(the_repository), ref_stash) && do_clear_stash()) {
ret = -1;
if (!quiet)
fprintf_ln(stderr, _("Cannot initialize stash"));
diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c
index 93f4b9d726..e604cb5ddb 100644
--- a/builtin/submodule--helper.c
+++ b/builtin/submodule--helper.c
@@ -302,6 +302,9 @@ static void runcommand_in_submodule_cb(const struct cache_entry *list_item,
struct child_process cp = CHILD_PROCESS_INIT;
char *displaypath;
+ if (validate_submodule_path(path) < 0)
+ exit(128);
+
displaypath = get_submodule_displaypath(path, info->prefix,
info->super_prefix);
@@ -633,6 +636,9 @@ static void status_submodule(const char *path, const struct object_id *ce_oid,
.free_removed_argv_elements = 1,
};
+ if (validate_submodule_path(path) < 0)
+ exit(128);
+
if (!submodule_from_path(the_repository, null_oid(), path))
die(_("no submodule mapping found in .gitmodules for path '%s'"),
path);
@@ -1237,6 +1243,9 @@ static void sync_submodule(const char *path, const char *prefix,
if (!is_submodule_active(the_repository, path))
return;
+ if (validate_submodule_path(path) < 0)
+ exit(128);
+
sub = submodule_from_path(the_repository, null_oid(), path);
if (sub && sub->url) {
@@ -1380,6 +1389,9 @@ static void deinit_submodule(const char *path, const char *prefix,
struct strbuf sb_config = STRBUF_INIT;
char *sub_git_dir = xstrfmt("%s/.git", path);
+ if (validate_submodule_path(path) < 0)
+ exit(128);
+
sub = submodule_from_path(the_repository, null_oid(), path);
if (!sub || !sub->name)
@@ -1661,16 +1673,42 @@ static char *clone_submodule_sm_gitdir(const char *name)
return sm_gitdir;
}
+static int dir_contains_only_dotgit(const char *path)
+{
+ DIR *dir = opendir(path);
+ struct dirent *e;
+ int ret = 1;
+
+ if (!dir)
+ return 0;
+
+ e = readdir_skip_dot_and_dotdot(dir);
+ if (!e)
+ ret = 0;
+ else if (strcmp(DEFAULT_GIT_DIR_ENVIRONMENT, e->d_name) ||
+ (e = readdir_skip_dot_and_dotdot(dir))) {
+ error("unexpected item '%s' in '%s'", e->d_name, path);
+ ret = 0;
+ }
+
+ closedir(dir);
+ return ret;
+}
+
static int clone_submodule(const struct module_clone_data *clone_data,
struct string_list *reference)
{
char *p;
char *sm_gitdir = clone_submodule_sm_gitdir(clone_data->name);
char *sm_alternate = NULL, *error_strategy = NULL;
+ struct stat st;
struct child_process cp = CHILD_PROCESS_INIT;
const char *clone_data_path = clone_data->path;
char *to_free = NULL;
+ if (validate_submodule_path(clone_data_path) < 0)
+ exit(128);
+
if (!is_absolute_path(clone_data->path))
clone_data_path = to_free = xstrfmt("%s/%s", get_git_work_tree(),
clone_data->path);
@@ -1680,6 +1718,10 @@ static int clone_submodule(const struct module_clone_data *clone_data,
"git dir"), sm_gitdir);
if (!file_exists(sm_gitdir)) {
+ if (clone_data->require_init && !stat(clone_data_path, &st) &&
+ !is_empty_dir(clone_data_path))
+ die(_("directory not empty: '%s'"), clone_data_path);
+
if (safe_create_leading_directories_const(sm_gitdir) < 0)
die(_("could not create directory '%s'"), sm_gitdir);
@@ -1724,10 +1766,18 @@ static int clone_submodule(const struct module_clone_data *clone_data,
if(run_command(&cp))
die(_("clone of '%s' into submodule path '%s' failed"),
clone_data->url, clone_data_path);
+
+ if (clone_data->require_init && !stat(clone_data_path, &st) &&
+ !dir_contains_only_dotgit(clone_data_path)) {
+ char *dot_git = xstrfmt("%s/.git", clone_data_path);
+ unlink(dot_git);
+ free(dot_git);
+ die(_("directory not empty: '%s'"), clone_data_path);
+ }
} else {
char *path;
- if (clone_data->require_init && !access(clone_data_path, X_OK) &&
+ if (clone_data->require_init && !stat(clone_data_path, &st) &&
!is_empty_dir(clone_data_path))
die(_("directory not empty: '%s'"), clone_data_path);
if (safe_create_leading_directories_const(clone_data_path) < 0)
@@ -1737,6 +1787,23 @@ static int clone_submodule(const struct module_clone_data *clone_data,
free(path);
}
+ /*
+ * We already performed this check at the beginning of this function,
+ * before cloning the objects. This tries to detect racy behavior e.g.
+ * in parallel clones, where another process could easily have made the
+ * gitdir nested _after_ it was created.
+ *
+ * To prevent further harm coming from this unintentionally-nested
+ * gitdir, let's disable it by deleting the `HEAD` file.
+ */
+ if (validate_submodule_git_dir(sm_gitdir, clone_data->name) < 0) {
+ char *head = xstrfmt("%s/HEAD", sm_gitdir);
+ unlink(head);
+ free(head);
+ die(_("refusing to create/use '%s' in another submodule's "
+ "git dir"), sm_gitdir);
+ }
+
connect_work_tree_and_git_dir(clone_data_path, sm_gitdir, 0);
p = git_pathdup_submodule(clone_data_path, "config");
@@ -2389,7 +2456,9 @@ static int remote_submodule_branch(const char *path, const char **branch)
}
if (!strcmp(*branch, ".")) {
- const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
+ const char *refname = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ "HEAD", 0, NULL,
+ NULL);
if (!refname)
return die_message(_("No such ref: %s"), "HEAD");
@@ -2516,6 +2585,9 @@ static int update_submodule(struct update_data *update_data)
{
int ret;
+ if (validate_submodule_path(update_data->sm_path) < 0)
+ return -1;
+
ret = determine_submodule_update_strategy(the_repository,
update_data->just_cloned,
update_data->sm_path,
@@ -2623,12 +2695,21 @@ static int update_submodules(struct update_data *update_data)
for (i = 0; i < suc.update_clone_nr; i++) {
struct update_clone_data ucd = suc.update_clone[i];
- int code;
+ int code = 128;
oidcpy(&update_data->oid, &ucd.oid);
update_data->just_cloned = ucd.just_cloned;
update_data->sm_path = ucd.sub->path;
+ /*
+ * Verify that the submodule path does not contain any
+ * symlinks; if it does, it might have been tampered with.
+ * TODO: allow exempting it via
+ * `safe.submodule.path` or something
+ */
+ if (validate_submodule_path(update_data->sm_path) < 0)
+ goto fail;
+
code = ensure_core_worktree(update_data->sm_path);
if (code)
goto fail;
@@ -2795,7 +2876,8 @@ static int push_check(int argc, const char **argv, const char *prefix UNUSED)
argv++;
argc--;
/* Get the submodule's head ref and determine if it is detached */
- head = resolve_refdup("HEAD", 0, &head_oid, NULL);
+ head = refs_resolve_refdup(get_main_ref_store(the_repository), "HEAD",
+ 0, &head_oid, NULL);
if (!head)
die(_("Failed to resolve HEAD as a valid ref."));
if (!strcmp(head, "HEAD"))
@@ -3355,6 +3437,9 @@ static int module_add(int argc, const char **argv, const char *prefix)
normalize_path_copy(add_data.sm_path, add_data.sm_path);
strip_dir_trailing_slashes(add_data.sm_path);
+ if (validate_submodule_path(add_data.sm_path) < 0)
+ exit(128);
+
die_on_index_match(add_data.sm_path, force);
die_on_repo_without_commits(add_data.sm_path);
diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c
index c9defe4d2e..81abdd170f 100644
--- a/builtin/symbolic-ref.c
+++ b/builtin/symbolic-ref.c
@@ -18,7 +18,8 @@ static int check_symref(const char *HEAD, int quiet, int shorten, int recurse, i
const char *refname;
resolve_flags = (recurse ? 0 : RESOLVE_REF_NO_RECURSE);
- refname = resolve_ref_unsafe(HEAD, resolve_flags, NULL, &flag);
+ refname = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ HEAD, resolve_flags, NULL, &flag);
if (!refname)
die("No such ref: %s", HEAD);
@@ -31,7 +32,9 @@ static int check_symref(const char *HEAD, int quiet, int shorten, int recurse, i
if (print) {
char *to_free = NULL;
if (shorten)
- refname = to_free = shorten_unambiguous_ref(refname, 0);
+ refname = to_free = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+ refname,
+ 0);
puts(refname);
free(to_free);
}
@@ -66,7 +69,8 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
die("Cannot delete %s, not a symbolic ref", argv[0]);
if (!strcmp(argv[0], "HEAD"))
die("deleting '%s' is not allowed", argv[0]);
- return delete_ref(NULL, argv[0], NULL, REF_NO_DEREF);
+ return refs_delete_ref(get_main_ref_store(the_repository),
+ NULL, argv[0], NULL, REF_NO_DEREF);
}
switch (argc) {
@@ -79,7 +83,8 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
die("Refusing to point HEAD outside of refs/");
if (check_refname_format(argv[1], REFNAME_ALLOW_ONELEVEL) < 0)
die("Refusing to set '%s' to invalid ref '%s'", argv[0], argv[1]);
- ret = !!create_symref(argv[0], argv[1], msg);
+ ret = !!refs_update_symref(get_main_ref_store(the_repository),
+ argv[0], argv[1], msg);
break;
default:
usage_with_options(git_symbolic_ref_usage, options);
diff --git a/builtin/tag.c b/builtin/tag.c
index 9a33cb50b4..6e2c0cf342 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -28,9 +28,11 @@
#include "date.h"
#include "write-or-die.h"
#include "object-file-convert.h"
+#include "trailer.h"
static const char * const git_tag_usage[] = {
N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>] [-e]\n"
+ " [(--trailer <token>[(=|:)<value>])...]\n"
" <tagname> [<commit> | <object>]"),
N_("git tag -d <tagname>..."),
N_("git tag [-n[<num>]] -l [--contains <commit>] [--no-contains <commit>]\n"
@@ -87,7 +89,7 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn,
for (p = argv; *p; p++) {
strbuf_reset(&ref);
strbuf_addf(&ref, "refs/tags/%s", *p);
- if (read_ref(ref.buf, &oid)) {
+ if (refs_read_ref(get_main_ref_store(the_repository), ref.buf, &oid)) {
error(_("tag '%s' not found."), *p);
had_error = 1;
continue;
@@ -116,13 +118,13 @@ static int delete_tags(const char **argv)
struct string_list_item *item;
result = for_each_tag_name(argv, collect_tags, (void *)&refs_to_delete);
- if (delete_refs(NULL, &refs_to_delete, REF_NO_DEREF))
+ if (refs_delete_refs(get_main_ref_store(the_repository), NULL, &refs_to_delete, REF_NO_DEREF))
result = 1;
for_each_string_list_item(item, &refs_to_delete) {
const char *name = item->string;
struct object_id *oid = item->util;
- if (!ref_exists(name))
+ if (!refs_ref_exists(get_main_ref_store(the_repository), name))
printf(_("Deleted tag '%s' (was %s)\n"),
item->string + 10,
repo_find_unique_abbrev(the_repository, oid, DEFAULT_ABBREV));
@@ -290,10 +292,12 @@ static const char message_advice_nested_tag[] =
static void create_tag(const struct object_id *object, const char *object_ref,
const char *tag,
struct strbuf *buf, struct create_tag_options *opt,
- struct object_id *prev, struct object_id *result, char *path)
+ struct object_id *prev, struct object_id *result,
+ struct strvec *trailer_args, char *path)
{
enum object_type type;
struct strbuf header = STRBUF_INIT;
+ int should_edit;
type = oid_object_info(the_repository, object, NULL);
if (type <= OBJ_NONE)
@@ -313,13 +317,15 @@ static void create_tag(const struct object_id *object, const char *object_ref,
tag,
git_committer_info(IDENT_STRICT));
- if (!opt->message_given || opt->use_editor) {
+ should_edit = opt->use_editor || !opt->message_given;
+ if (should_edit || trailer_args->nr) {
int fd;
/* write the template message before editing: */
fd = xopen(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
- if (opt->message_given) {
+ if (opt->message_given && buf->len) {
+ strbuf_complete(buf, '\n');
write_or_die(fd, buf->buf, buf->len);
strbuf_reset(buf);
} else if (!is_null_oid(prev)) {
@@ -338,10 +344,19 @@ static void create_tag(const struct object_id *object, const char *object_ref,
}
close(fd);
- if (launch_editor(path, buf, NULL)) {
- fprintf(stderr,
- _("Please supply the message using either -m or -F option.\n"));
- exit(1);
+ if (trailer_args->nr && amend_file_with_trailers(path, trailer_args))
+ die(_("unable to pass trailers to --trailers"));
+
+ if (should_edit) {
+ if (launch_editor(path, buf, NULL)) {
+ fprintf(stderr,
+ _("Please supply the message using either -m or -F option.\n"));
+ exit(1);
+ }
+ } else if (trailer_args->nr) {
+ strbuf_reset(buf);
+ if (strbuf_read_file(buf, path, 0) < 0)
+ die_errno(_("failed to read '%s'"), path);
}
}
@@ -463,6 +478,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
struct ref_sorting *sorting;
struct string_list sorting_options = STRING_LIST_INIT_DUP;
struct ref_format format = REF_FORMAT_INIT;
+ struct strvec trailer_args = STRVEC_INIT;
int icase = 0;
int edit_flag = 0;
struct option options[] = {
@@ -479,6 +495,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
OPT_CALLBACK_F('m', "message", &msg, N_("message"),
N_("tag message"), PARSE_OPT_NONEG, parse_msg_arg),
OPT_FILENAME('F', "file", &msgfile, N_("read message from file")),
+ OPT_PASSTHRU_ARGV(0, "trailer", &trailer_args, N_("trailer"),
+ N_("add custom trailer(s)"), PARSE_OPT_NONEG),
OPT_BOOL('e', "edit", &edit_flag, N_("force edit of tag message")),
OPT_BOOL('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")),
OPT_CLEANUP(&cleanup_arg),
@@ -548,7 +566,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
opt.sign = 1;
set_signing_key(keyid);
}
- create_tag_object = (opt.sign || annotate || msg.given || msgfile);
+ create_tag_object = (opt.sign || annotate || msg.given || msgfile ||
+ edit_flag || trailer_args.nr);
if ((create_tag_object || force) && (cmdmode != 0))
usage_with_options(git_tag_usage, options);
@@ -630,7 +649,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
if (strbuf_check_tag_ref(&ref, tag))
die(_("'%s' is not a valid tag name."), tag);
- if (read_ref(ref.buf, &prev))
+ if (refs_read_ref(get_main_ref_store(the_repository), ref.buf, &prev))
oidclr(&prev);
else if (!force)
die(_("tag '%s' already exists"), tag);
@@ -654,12 +673,14 @@ int cmd_tag(int argc, const char **argv, const char *prefix)
opt.sign = 1;
path = git_pathdup("TAG_EDITMSG");
create_tag(&object, object_ref, tag, &buf, &opt, &prev, &object,
- path);
+ &trailer_args, path);
}
- transaction = ref_transaction_begin(&err);
+ transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
+ &err);
if (!transaction ||
ref_transaction_update(transaction, ref.buf, &object, &prev,
+ NULL, NULL,
create_reflog ? REF_FORCE_CREATE_REFLOG : 0,
reflog_msg.buf, &err) ||
ref_transaction_commit(transaction, &err)) {
@@ -686,6 +707,7 @@ cleanup:
strbuf_release(&reflog_msg);
strbuf_release(&msg.buf);
strbuf_release(&err);
+ strvec_clear(&trailer_args);
free(msgfile);
return ret;
}
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 6321810006..20aa1c4c68 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -682,7 +682,7 @@ static int do_reupdate(const char **paths,
PATHSPEC_PREFER_CWD,
prefix, paths);
- if (read_ref("HEAD", &head_oid))
+ if (refs_read_ref(get_main_ref_store(the_repository), "HEAD", &head_oid))
/* If there is no HEAD, that means it is an initial
* commit. Update everything in the index.
*/
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index e46afbc46d..6cda1c08aa 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -204,6 +204,7 @@ static void parse_cmd_update(struct ref_transaction *transaction,
if (ref_transaction_update(transaction, refname,
&new_oid, have_old ? &old_oid : NULL,
+ NULL, NULL,
update_flags | create_reflog_flag,
msg, &err))
die("%s", err.buf);
@@ -397,7 +398,8 @@ static void update_refs_stdin(void)
struct ref_transaction *transaction;
int i, j;
- transaction = ref_transaction_begin(&err);
+ transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
+ &err);
if (!transaction)
die("%s", err.buf);
@@ -464,7 +466,8 @@ static void update_refs_stdin(void)
* get a "start".
*/
state = cmd->state;
- transaction = ref_transaction_begin(&err);
+ transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
+ &err);
if (!transaction)
die("%s", err.buf);
@@ -571,11 +574,14 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
* For purposes of backwards compatibility, we treat
* NULL_SHA1 as "don't care" here:
*/
- return delete_ref(msg, refname,
- (oldval && !is_null_oid(&oldoid)) ? &oldoid : NULL,
- default_flags);
+ return refs_delete_ref(get_main_ref_store(the_repository),
+ msg, refname,
+ (oldval && !is_null_oid(&oldoid)) ? &oldoid : NULL,
+ default_flags);
else
- return update_ref(msg, refname, &oid, oldval ? &oldoid : NULL,
- default_flags | create_reflog_flag,
- UPDATE_REFS_DIE_ON_ERR);
+ return refs_update_ref(get_main_ref_store(the_repository),
+ msg, refname, &oid,
+ oldval ? &oldoid : NULL,
+ default_flags | create_reflog_flag,
+ UPDATE_REFS_DIE_ON_ERR);
}
diff --git a/builtin/upload-pack.c b/builtin/upload-pack.c
index 15afb97260..46d93278d9 100644
--- a/builtin/upload-pack.c
+++ b/builtin/upload-pack.c
@@ -9,6 +9,7 @@
#include "upload-pack.h"
#include "serve.h"
#include "commit.h"
+#include "environment.h"
static const char * const upload_pack_usage[] = {
N_("git-upload-pack [--[no-]strict] [--timeout=<n>] [--stateless-rpc]\n"
@@ -39,6 +40,7 @@ int cmd_upload_pack(int argc, const char **argv, const char *prefix)
packet_trace_identity("upload-pack");
disable_replace_refs();
save_commit_buffer = 0;
+ xsetenv(NO_LAZY_FETCH_ENVIRONMENT, "1", 0);
argc = parse_options(argc, argv, prefix, options, upload_pack_usage, 0);
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 7c6c72536b..7e0868df72 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -433,7 +433,7 @@ static int add_worktree(const char *path, const char *refname,
/* is 'refname' a branch or commit? */
if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) &&
- ref_exists(symref.buf)) {
+ refs_ref_exists(get_main_ref_store(the_repository), symref.buf)) {
is_branch = 1;
if (!opts->force)
die_if_checked_out(symref.buf, 0);
@@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,
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);
+ ret = refs_update_symref(wt_refs, "HEAD", symref.buf, NULL);
if (ret)
goto done;
@@ -605,7 +605,7 @@ static void print_preparing_worktree_line(int detach,
} else {
struct strbuf s = STRBUF_INIT;
if (!detach && !strbuf_check_branch_ref(&s, branch) &&
- ref_exists(s.buf))
+ refs_ref_exists(get_main_ref_store(the_repository), s.buf))
fprintf_ln(stderr, _("Preparing worktree (checking out '%s')"),
branch);
else {
@@ -647,9 +647,9 @@ static int first_valid_ref(const char *refname UNUSED,
*/
static int can_use_local_refs(const struct add_opts *opts)
{
- if (head_ref(first_valid_ref, NULL)) {
+ if (refs_head_ref(get_main_ref_store(the_repository), first_valid_ref, NULL)) {
return 1;
- } else if (for_each_branch_ref(first_valid_ref, NULL)) {
+ } else if (refs_for_each_branch_ref(get_main_ref_store(the_repository), first_valid_ref, NULL)) {
if (!opts->quiet) {
struct strbuf path = STRBUF_INIT;
struct strbuf contents = STRBUF_INIT;
@@ -689,7 +689,7 @@ static int can_use_remote_refs(const struct add_opts *opts)
{
if (!guess_remote) {
return 0;
- } else if (for_each_remote_ref(first_valid_ref, NULL)) {
+ } else if (refs_for_each_remote_ref(get_main_ref_store(the_repository), first_valid_ref, NULL)) {
return 1;
} else if (!opts->force && remote_get(NULL)) {
die(_("No local or remote refs exist despite at least one remote\n"
@@ -747,7 +747,8 @@ static const char *dwim_branch(const char *path, const char **new_branch)
UNLEAK(branchname);
branch_exists = !strbuf_check_branch_ref(&ref, branchname) &&
- ref_exists(ref.buf);
+ refs_ref_exists(get_main_ref_store(the_repository),
+ ref.buf);
strbuf_release(&ref);
if (branch_exists)
return branchname;
@@ -838,7 +839,7 @@ static int add(int ac, const char **av, const char *prefix)
if (!opts.force &&
!strbuf_check_branch_ref(&symref, new_branch) &&
- ref_exists(symref.buf))
+ refs_ref_exists(get_main_ref_store(the_repository), symref.buf))
die_if_checked_out(symref.buf, 0);
strbuf_release(&symref);
}
@@ -974,7 +975,9 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len)
if (wt->is_detached)
strbuf_addstr(&sb, "(detached HEAD)");
else if (wt->head_ref) {
- char *ref = shorten_unambiguous_ref(wt->head_ref, 0);
+ char *ref = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+ wt->head_ref,
+ 0);
strbuf_addf(&sb, "[%s]", ref);
free(ref);
} else
diff --git a/bundle-uri.c b/bundle-uri.c
index ca32050a78..91b3319a5c 100644
--- a/bundle-uri.c
+++ b/bundle-uri.c
@@ -395,11 +395,13 @@ static int unbundle_from_file(struct repository *r, const char *file)
strbuf_setlen(&bundle_ref, bundle_prefix_len);
strbuf_addstr(&bundle_ref, branch_name);
- has_old = !read_ref(bundle_ref.buf, &old_oid);
- update_ref("fetched bundle", bundle_ref.buf, oid,
- has_old ? &old_oid : NULL,
- REF_SKIP_OID_VERIFICATION,
- UPDATE_REFS_MSG_ON_ERR);
+ has_old = !refs_read_ref(get_main_ref_store(the_repository),
+ bundle_ref.buf, &old_oid);
+ refs_update_ref(get_main_ref_store(the_repository),
+ "fetched bundle", bundle_ref.buf, oid,
+ has_old ? &old_oid : NULL,
+ REF_SKIP_OID_VERIFICATION,
+ UPDATE_REFS_MSG_ON_ERR);
}
bundle_header_release(&header);
diff --git a/bundle.c b/bundle.c
index a9744da255..95367c2d0a 100644
--- a/bundle.c
+++ b/bundle.c
@@ -389,7 +389,7 @@ static int write_bundle_refs(int bundle_fd, struct rev_info *revs)
if (repo_dwim_ref(the_repository, e->name, strlen(e->name),
&oid, &ref, 0) != 1)
goto skip_write_ref;
- if (read_ref_full(e->name, RESOLVE_REF_READING, &oid, &flag))
+ if (refs_read_ref_full(get_main_ref_store(the_repository), e->name, RESOLVE_REF_READING, &oid, &flag))
flag = 0;
display_ref = (flag & REF_ISSYMREF) ? e->name : ref;
diff --git a/ci/check-whitespace.sh b/ci/check-whitespace.sh
new file mode 100755
index 0000000000..db399097a5
--- /dev/null
+++ b/ci/check-whitespace.sh
@@ -0,0 +1,95 @@
+#!/usr/bin/env bash
+#
+# Check that commits after a specified point do not contain new or modified
+# lines with whitespace errors. An optional formatted summary can be generated
+# by providing an output file path and url as additional arguments.
+#
+
+baseCommit=$1
+outputFile=$2
+url=$3
+
+if test "$#" -ne 1 && test "$#" -ne 3
+then
+ echo "USAGE: $0 <BASE_COMMIT> [<OUTPUT_FILE> <URL>]"
+ exit 1
+fi
+
+problems=()
+commit=
+commitText=
+commitTextmd=
+goodParent=
+
+while read dash sha etc
+do
+ case "${dash}" in
+ "---") # Line contains commit information.
+ if test -z "${goodParent}"
+ then
+ # Assume the commit has no whitespace errors until detected otherwise.
+ goodParent=${sha}
+ fi
+
+ commit="${sha}"
+ commitText="${sha} ${etc}"
+ commitTextmd="[${sha}](${url}/commit/${sha}) ${etc}"
+ ;;
+ "")
+ ;;
+ *) # Line contains whitespace error information for current commit.
+ if test -n "${goodParent}"
+ then
+ problems+=("1) --- ${commitTextmd}")
+ echo ""
+ echo "--- ${commitText}"
+ goodParent=
+ fi
+
+ case "${dash}" in
+ *:[1-9]*:) # contains file and line number information
+ dashend=${dash#*:}
+ problems+=("[${dash}](${url}/blob/${commit}/${dash%%:*}#L${dashend%:}) ${sha} ${etc}")
+ ;;
+ *)
+ problems+=("\`${dash} ${sha} ${etc}\`")
+ ;;
+ esac
+ echo "${dash} ${sha} ${etc}"
+ ;;
+ esac
+done <<< "$(git log --check --pretty=format:"---% h% s" "${baseCommit}"..)"
+
+if test ${#problems[*]} -gt 0
+then
+ if test -z "${goodParent}"
+ then
+ goodParent=${baseCommit: 0:7}
+ fi
+
+ echo "A whitespace issue was found in onen of more of the commits."
+ echo "Run the following command to resolve whitespace issues:"
+ echo "git rebase --whitespace=fix ${goodParent}"
+
+ # If target output file is provided, write formatted output.
+ if test -n "$outputFile"
+ then
+ echo "🛑 Please review the Summary output for further information."
+ (
+ echo "### :x: A whitespace issue was found in one or more of the commits."
+ echo ""
+ echo "Run these commands to correct the problem:"
+ echo "1. \`git rebase --whitespace=fix ${goodParent}\`"
+ echo "1. \`git push --force\`"
+ echo ""
+ echo "Errors:"
+
+ for i in "${problems[@]}"
+ do
+ echo "${i}"
+ done
+ ) >"$outputFile"
+ fi
+
+ exit 2
+fi
diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh
index c196e56762..2e7688ae8b 100755
--- a/ci/install-dependencies.sh
+++ b/ci/install-dependencies.sh
@@ -69,8 +69,6 @@ macos-*)
export HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALL_CLEANUP=1
# Uncomment this if you want to run perf tests:
# brew install gnu-time
- test -z "$BREW_INSTALL_PACKAGES" ||
- brew install $BREW_INSTALL_PACKAGES
brew link --force gettext
mkdir -p "$CUSTOM_PATH"
diff --git a/ci/lib.sh b/ci/lib.sh
index 473a2d0348..1f4059b1b8 100755
--- a/ci/lib.sh
+++ b/ci/lib.sh
@@ -18,7 +18,7 @@ elif test true = "$GITLAB_CI"
then
begin_group () {
need_to_end_group=t
- printf "\e[0Ksection_start:$(date +%s):$(echo "$1" | tr ' ' _)\r\e[0K$1\n"
+ printf "\e[0Ksection_start:$(date +%s):$(echo "$1" | tr ' ' _)[collapsed=true]\r\e[0K$1\n"
trap "end_group '$1'" EXIT
set -x
}
@@ -325,9 +325,13 @@ ubuntu-*)
break
fi
- PYTHON_PACKAGE=python2
- if test "$jobname" = linux-gcc
+ # Python 2 is end of life, and Ubuntu 23.04 and newer don't actually
+ # have it anymore. We thus only test with Python 2 on older LTS
+ # releases.
+ if "$distro" = "ubuntu-20.04"
then
+ PYTHON_PACKAGE=python2
+ else
PYTHON_PACKAGE=python3
fi
MAKEFLAGS="$MAKEFLAGS PYTHON_PATH=/usr/bin/$PYTHON_PACKAGE"
diff --git a/ci/run-build-and-tests.sh b/ci/run-build-and-tests.sh
index c192bd613c..98dda42045 100755
--- a/ci/run-build-and-tests.sh
+++ b/ci/run-build-and-tests.sh
@@ -53,8 +53,6 @@ if test -n "$run_tests"
then
group "Run tests" make test ||
handle_failed_tests
- group "Run unit tests" \
- make DEFAULT_UNIT_TEST_TARGET=unit-tests-prove unit-tests
fi
check_unignored_build_artifacts
diff --git a/ci/run-test-slice.sh b/ci/run-test-slice.sh
index ae8094382f..e167e646f7 100755
--- a/ci/run-test-slice.sh
+++ b/ci/run-test-slice.sh
@@ -17,7 +17,7 @@ handle_failed_tests
# We only have one unit test at the moment, so run it in the first slice
if [ "$1" == "0" ] ; then
- group "Run unit tests" make --quiet -C t unit-tests-prove
+ group "Run unit tests" make --quiet -C t unit-tests-test-tool
fi
check_unignored_build_artifacts
diff --git a/color.c b/color.c
index f663c06ac4..227a5ab2f4 100644
--- a/color.c
+++ b/color.c
@@ -64,12 +64,16 @@ static int match_word(const char *word, int len, const char *match)
return !strncasecmp(word, match, len) && !match[len];
}
-static int get_hex_color(const char *in, unsigned char *out)
+static int get_hex_color(const char **inp, int width, unsigned char *out)
{
+ const char *in = *inp;
unsigned int val;
- val = (hexval(in[0]) << 4) | hexval(in[1]);
+
+ assert(width == 1 || width == 2);
+ val = (hexval(in[0]) << 4) | hexval(in[width - 1]);
if (val & ~0xff)
return -1;
+ *inp += width;
*out = val;
return 0;
}
@@ -135,11 +139,14 @@ static int parse_color(struct color *out, const char *name, int len)
return 0;
}
- /* Try a 24-bit RGB value */
- if (len == 7 && name[0] == '#') {
- if (!get_hex_color(name + 1, &out->red) &&
- !get_hex_color(name + 3, &out->green) &&
- !get_hex_color(name + 5, &out->blue)) {
+ /* Try a 24- or 12-bit RGB value prefixed with '#' */
+ if ((len == 7 || len == 4) && name[0] == '#') {
+ int width_per_color = (len == 7) ? 2 : 1;
+ const char *color = name + 1;
+
+ if (!get_hex_color(&color, width_per_color, &out->red) &&
+ !get_hex_color(&color, width_per_color, &out->green) &&
+ !get_hex_color(&color, width_per_color, &out->blue)) {
out->type = COLOR_RGB;
return 0;
}
diff --git a/color.h b/color.h
index bb28343be2..7ed259a35b 100644
--- a/color.h
+++ b/color.h
@@ -112,7 +112,8 @@ int want_color_fd(int fd, int var);
* Translate a Git color from 'value' into a string that the terminal can
* interpret and store it into 'dst'. The Git color values are of the form
* "foreground [background] [attr]" where fore- and background can be a color
- * name ("red"), a RGB code (#0xFF0000) or a 256-color-mode from the terminal.
+ * name ("red"), a RGB code (#FF0000 or #F00) or a 256-color-mode from the
+ * terminal.
*/
int color_parse(const char *value, char *dst);
int color_parse_mem(const char *value, int len, char *dst);
diff --git a/commit-graph.c b/commit-graph.c
index 45417d7412..c4c156ff52 100644
--- a/commit-graph.c
+++ b/commit-graph.c
@@ -1845,7 +1845,8 @@ int write_commit_graph_reachable(struct object_directory *odb,
data.progress = start_delayed_progress(
_("Collecting referenced commits"), 0);
- for_each_ref(add_ref_to_set, &data);
+ refs_for_each_ref(get_main_ref_store(the_repository), add_ref_to_set,
+ &data);
stop_progress(&data.progress);
diff --git a/commit.c b/commit.c
index 1a479a997c..1d08951007 100644
--- a/commit.c
+++ b/commit.c
@@ -1070,7 +1070,8 @@ struct commit *get_fork_point(const char *refname, struct commit *commit)
memset(&revs, 0, sizeof(revs));
revs.initial = 1;
- for_each_reflog_ent(full_refname, collect_one_reflog_ent, &revs);
+ refs_for_each_reflog_ent(get_main_ref_store(the_repository),
+ full_refname, collect_one_reflog_ent, &revs);
if (!revs.nr)
add_one_commit(&oid, &revs);
diff --git a/compat/mingw.c b/compat/mingw.c
index 4876344b5b..6b06ea540f 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -3159,6 +3159,7 @@ int uname(struct utsname *buf)
return 0;
}
+#ifndef NO_UNIX_SOCKETS
int mingw_have_unix_sockets(void)
{
SC_HANDLE scm, srvc;
@@ -3177,3 +3178,4 @@ int mingw_have_unix_sockets(void)
}
return ret;
}
+#endif
diff --git a/compat/precompose_utf8.c b/compat/precompose_utf8.c
index 0bd5c24250..5a7c90c90d 100644
--- a/compat/precompose_utf8.c
+++ b/compat/precompose_utf8.c
@@ -94,6 +94,16 @@ const char *precompose_string_if_needed(const char *in)
return in;
}
+void precompose_strbuf_if_needed(struct strbuf *sb)
+{
+ char *buf_prec = (char *)precompose_string_if_needed(sb->buf);
+ if (buf_prec != sb->buf) {
+ size_t buf_prec_len = strlen(buf_prec);
+ free(strbuf_detach(sb, NULL));
+ strbuf_attach(sb, buf_prec, buf_prec_len, buf_prec_len + 1);
+ }
+}
+
const char *precompose_argv_prefix(int argc, const char **argv, const char *prefix)
{
int i = 0;
diff --git a/compat/precompose_utf8.h b/compat/precompose_utf8.h
index fea06cf28a..7c3cfcadb0 100644
--- a/compat/precompose_utf8.h
+++ b/compat/precompose_utf8.h
@@ -30,6 +30,7 @@ typedef struct {
const char *precompose_argv_prefix(int argc, const char **argv, const char *prefix);
const char *precompose_string_if_needed(const char *in);
+void precompose_strbuf_if_needed(struct strbuf *sb);
void probe_utf8_pathname_composition(void);
PREC_DIR *precompose_utf8_opendir(const char *dirname);
diff --git a/compat/regex/regcomp.c b/compat/regex/regcomp.c
index d1bc09e49b..2bc0f1187a 100644
--- a/compat/regex/regcomp.c
+++ b/compat/regex/regcomp.c
@@ -868,7 +868,7 @@ init_dfa (re_dfa_t *dfa, size_t pat_len)
if (table_size > pat_len)
break;
- dfa->state_table = calloc (sizeof (struct re_state_table_entry), table_size);
+ dfa->state_table = calloc (table_size, sizeof (struct re_state_table_entry));
dfa->state_hash_mask = table_size - 1;
dfa->mb_cur_max = MB_CUR_MAX;
@@ -936,7 +936,7 @@ init_dfa (re_dfa_t *dfa, size_t pat_len)
{
int i, j, ch;
- dfa->sb_char = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1);
+ dfa->sb_char = (re_bitset_ptr_t) calloc (1, sizeof (bitset_t));
if (BE (dfa->sb_char == NULL, 0))
return REG_ESPACE;
@@ -3079,9 +3079,9 @@ parse_bracket_exp (re_string_t *regexp, re_dfa_t *dfa, re_token_t *token,
_NL_COLLATE_SYMB_EXTRAMB);
}
#endif
- sbcset = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1);
+ sbcset = (re_bitset_ptr_t) calloc (1, sizeof (bitset_t));
#ifdef RE_ENABLE_I18N
- mbcset = (re_charset_t *) calloc (sizeof (re_charset_t), 1);
+ mbcset = (re_charset_t *) calloc (1, sizeof (re_charset_t));
#endif /* RE_ENABLE_I18N */
#ifdef RE_ENABLE_I18N
if (BE (sbcset == NULL || mbcset == NULL, 0))
@@ -3626,9 +3626,9 @@ build_charclass_op (re_dfa_t *dfa, RE_TRANSLATE_TYPE trans,
re_token_t br_token;
bin_tree_t *tree;
- sbcset = (re_bitset_ptr_t) calloc (sizeof (bitset_t), 1);
+ sbcset = (re_bitset_ptr_t) calloc (1, sizeof (bitset_t));
#ifdef RE_ENABLE_I18N
- mbcset = (re_charset_t *) calloc (sizeof (re_charset_t), 1);
+ mbcset = (re_charset_t *) calloc (1, sizeof (re_charset_t));
#endif /* RE_ENABLE_I18N */
#ifdef RE_ENABLE_I18N
diff --git a/compat/regex/regex_internal.c b/compat/regex/regex_internal.c
index ec51cf3446..ec5cc5d2dd 100644
--- a/compat/regex/regex_internal.c
+++ b/compat/regex/regex_internal.c
@@ -1628,7 +1628,7 @@ create_ci_newstate (const re_dfa_t *dfa, const re_node_set *nodes,
reg_errcode_t err;
re_dfastate_t *newstate;
- newstate = (re_dfastate_t *) calloc (sizeof (re_dfastate_t), 1);
+ newstate = (re_dfastate_t *) calloc (1, sizeof (re_dfastate_t));
if (BE (newstate == NULL, 0))
return NULL;
err = re_node_set_init_copy (&newstate->nodes, nodes);
@@ -1678,7 +1678,7 @@ create_cd_newstate (const re_dfa_t *dfa, const re_node_set *nodes,
reg_errcode_t err;
re_dfastate_t *newstate;
- newstate = (re_dfastate_t *) calloc (sizeof (re_dfastate_t), 1);
+ newstate = (re_dfastate_t *) calloc (1, sizeof (re_dfastate_t));
if (BE (newstate == NULL, 0))
return NULL;
err = re_node_set_init_copy (&newstate->nodes, nodes);
diff --git a/compat/regex/regexec.c b/compat/regex/regexec.c
index 49358ae475..e92be5741d 100644
--- a/compat/regex/regexec.c
+++ b/compat/regex/regexec.c
@@ -2796,8 +2796,8 @@ get_subexp (re_match_context_t *mctx, int bkref_node, int bkref_str_idx)
continue; /* No. */
if (sub_top->path == NULL)
{
- sub_top->path = calloc (sizeof (state_array_t),
- sl_str - sub_top->str_idx + 1);
+ sub_top->path = calloc (sl_str - sub_top->str_idx + 1,
+ sizeof (state_array_t));
if (sub_top->path == NULL)
return REG_ESPACE;
}
@@ -3361,7 +3361,7 @@ build_trtable (const re_dfa_t *dfa, re_dfastate_t *state)
if (ndests == 0)
{
state->trtable = (re_dfastate_t **)
- calloc (sizeof (re_dfastate_t *), SBC_MAX);
+ calloc (SBC_MAX, sizeof (re_dfastate_t *));
return 1;
}
return 0;
@@ -3457,7 +3457,7 @@ out_free:
discern by looking at the character code: allocate a
256-entry transition table. */
trtable = state->trtable =
- (re_dfastate_t **) calloc (sizeof (re_dfastate_t *), SBC_MAX);
+ (re_dfastate_t **) calloc (SBC_MAX, sizeof (re_dfastate_t *));
if (BE (trtable == NULL, 0))
goto out_free;
@@ -3488,7 +3488,7 @@ out_free:
transition tables, one starting at trtable[0] and one
starting at trtable[SBC_MAX]. */
trtable = state->word_trtable =
- (re_dfastate_t **) calloc (sizeof (re_dfastate_t *), 2 * SBC_MAX);
+ (re_dfastate_t **) calloc (2 * SBC_MAX, sizeof (re_dfastate_t *));
if (BE (trtable == NULL, 0))
goto out_free;
diff --git a/config.c b/config.c
index ae3652b08f..d57996240b 100644
--- a/config.c
+++ b/config.c
@@ -303,7 +303,8 @@ static int include_by_branch(const char *cond, size_t cond_len)
int ret;
struct strbuf pattern = STRBUF_INIT;
const char *refname = !the_repository->gitdir ?
- NULL : resolve_ref_unsafe("HEAD", 0, NULL, &flags);
+ NULL : refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ "HEAD", 0, NULL, &flags);
const char *shortname;
if (!refname || !(flags & REF_ISSYMREF) ||
@@ -1416,8 +1417,19 @@ static int git_default_core_config(const char *var, const char *value,
if (!strcmp(var, "core.attributesfile"))
return git_config_pathname(&git_attributes_file, var, value);
- if (!strcmp(var, "core.hookspath"))
+ if (!strcmp(var, "core.hookspath")) {
+ if (ctx->kvi && ctx->kvi->scope == CONFIG_SCOPE_LOCAL &&
+ git_env_bool("GIT_CLONE_PROTECTION_ACTIVE", 0))
+ die(_("active `core.hooksPath` found in the local "
+ "repository config:\n\t%s\nFor security "
+ "reasons, this is disallowed by default.\nIf "
+ "this is intentional and the hook should "
+ "actually be run, please\nrun the command "
+ "again with "
+ "`GIT_CLONE_PROTECTION_ACTIVE=false`"),
+ value);
return git_config_pathname(&git_hooks_path, var, value);
+ }
if (!strcmp(var, "core.bare")) {
is_bare_repository_cfg = git_config_bool(var, value);
@@ -3182,14 +3194,10 @@ void git_config_set(const char *key, const char *value)
trace2_cmd_set_config(key, value);
}
-/*
- * The ownership rule is that the caller will own the string
- * if it receives a piece of memory different from what it passed
- * as the parameter.
- */
-const char *git_config_prepare_comment_string(const char *comment)
+char *git_config_prepare_comment_string(const char *comment)
{
size_t leading_blanks;
+ char *prepared;
if (!comment)
return NULL;
@@ -3210,13 +3218,13 @@ const char *git_config_prepare_comment_string(const char *comment)
leading_blanks = strspn(comment, " \t");
if (leading_blanks && comment[leading_blanks] == '#')
- ; /* use it as-is */
+ prepared = xstrdup(comment); /* use it as-is */
else if (comment[0] == '#')
- comment = xstrfmt(" %s", comment);
+ prepared = xstrfmt(" %s", comment);
else
- comment = xstrfmt(" # %s", comment);
+ prepared = xstrfmt(" # %s", comment);
- return comment;
+ return prepared;
}
static void validate_comment_string(const char *comment)
diff --git a/config.h b/config.h
index f4966e3749..db8b608064 100644
--- a/config.h
+++ b/config.h
@@ -338,7 +338,7 @@ void git_config_set_multivar(const char *, const char *, const char *, unsigned)
int repo_config_set_multivar_gently(struct repository *, const char *, const char *, const char *, unsigned);
int git_config_set_multivar_in_file_gently(const char *, const char *, const char *, const char *, const char *, unsigned);
-const char *git_config_prepare_comment_string(const char *);
+char *git_config_prepare_comment_string(const char *);
/**
* takes four parameters:
diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt
index 804629c525..2f9c33585c 100644
--- a/contrib/buildsystems/CMakeLists.txt
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -1005,10 +1005,11 @@ endforeach()
#test-tool
parse_makefile_for_sources(test-tool_SOURCES "TEST_BUILTINS_OBJS")
+add_library(test-lib OBJECT ${CMAKE_SOURCE_DIR}/t/unit-tests/test-lib.c)
list(TRANSFORM test-tool_SOURCES PREPEND "${CMAKE_SOURCE_DIR}/t/helper/")
add_executable(test-tool ${CMAKE_SOURCE_DIR}/t/helper/test-tool.c ${test-tool_SOURCES} ${test-reftable_SOURCES})
-target_link_libraries(test-tool common-main)
+target_link_libraries(test-tool test-lib common-main)
set_target_properties(test-fake-ssh test-tool
PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/t/helper)
diff --git a/contrib/coccinelle/refs.cocci b/contrib/coccinelle/refs.cocci
new file mode 100644
index 0000000000..31d9cad8f3
--- /dev/null
+++ b/contrib/coccinelle/refs.cocci
@@ -0,0 +1,103 @@
+// Migrate "refs.h" to not rely on `the_repository` implicitly anymore.
+@@
+@@
+(
+- resolve_ref_unsafe
++ refs_resolve_ref_unsafe
+|
+- resolve_refdup
++ refs_resolve_refdup
+|
+- read_ref_full
++ refs_read_ref_full
+|
+- read_ref
++ refs_read_ref
+|
+- ref_exists
++ refs_ref_exists
+|
+- head_ref
++ refs_head_ref
+|
+- for_each_ref
++ refs_for_each_ref
+|
+- for_each_ref_in
++ refs_for_each_ref_in
+|
+- for_each_fullref_in
++ refs_for_each_fullref_in
+|
+- for_each_tag_ref
++ refs_for_each_tag_ref
+|
+- for_each_branch_ref
++ refs_for_each_branch_ref
+|
+- for_each_remote_ref
++ refs_for_each_remote_ref
+|
+- for_each_glob_ref
++ refs_for_each_glob_ref
+|
+- for_each_glob_ref_in
++ refs_for_each_glob_ref_in
+|
+- head_ref_namespaced
++ refs_head_ref_namespaced
+|
+- for_each_namespaced_ref
++ refs_for_each_namespaced_ref
+|
+- for_each_rawref
++ refs_for_each_rawref
+|
+- safe_create_reflog
++ refs_create_reflog
+|
+- reflog_exists
++ refs_reflog_exists
+|
+- delete_ref
++ refs_delete_ref
+|
+- delete_refs
++ refs_delete_refs
+|
+- delete_reflog
++ refs_delete_reflog
+|
+- for_each_reflog_ent
++ refs_for_each_reflog_ent
+|
+- for_each_reflog_ent_reverse
++ refs_for_each_reflog_ent_reverse
+|
+- for_each_reflog
++ refs_for_each_reflog
+|
+- shorten_unambiguous_ref
++ refs_shorten_unambiguous_ref
+|
+- rename_ref
++ refs_rename_ref
+|
+- copy_existing_ref
++ refs_copy_existing_ref
+|
+- create_symref
++ refs_create_symref
+|
+- ref_transaction_begin
++ ref_store_transaction_begin
+|
+- update_ref
++ refs_update_ref
+|
+- reflog_expire
++ refs_reflog_expire
+)
+ (
++ get_main_ref_store(the_repository),
+ ...)
diff --git a/copy.c b/copy.c
index 23d84c6c1d..3df156f6ce 100644
--- a/copy.c
+++ b/copy.c
@@ -1,6 +1,9 @@
#include "git-compat-util.h"
#include "copy.h"
#include "path.h"
+#include "gettext.h"
+#include "strbuf.h"
+#include "abspath.h"
int copy_fd(int ifd, int ofd)
{
@@ -67,3 +70,61 @@ int copy_file_with_time(const char *dst, const char *src, int mode)
return copy_times(dst, src);
return status;
}
+
+static int do_symlinks_match(const char *path1, const char *path2)
+{
+ struct strbuf buf1 = STRBUF_INIT, buf2 = STRBUF_INIT;
+ int ret = 0;
+
+ if (!strbuf_readlink(&buf1, path1, 0) &&
+ !strbuf_readlink(&buf2, path2, 0))
+ ret = !strcmp(buf1.buf, buf2.buf);
+
+ strbuf_release(&buf1);
+ strbuf_release(&buf2);
+ return ret;
+}
+
+int do_files_match(const char *path1, const char *path2)
+{
+ struct stat st1, st2;
+ int fd1 = -1, fd2 = -1, ret = 1;
+ char buf1[8192], buf2[8192];
+
+ if ((fd1 = open_nofollow(path1, O_RDONLY)) < 0 ||
+ fstat(fd1, &st1) || !S_ISREG(st1.st_mode)) {
+ if (fd1 < 0 && errno == ELOOP)
+ /* maybe this is a symbolic link? */
+ return do_symlinks_match(path1, path2);
+ ret = 0;
+ } else if ((fd2 = open_nofollow(path2, O_RDONLY)) < 0 ||
+ fstat(fd2, &st2) || !S_ISREG(st2.st_mode)) {
+ ret = 0;
+ }
+
+ if (ret)
+ /* to match, neither must be executable, or both */
+ ret = !(st1.st_mode & 0111) == !(st2.st_mode & 0111);
+
+ if (ret)
+ ret = st1.st_size == st2.st_size;
+
+ while (ret) {
+ ssize_t len1 = read_in_full(fd1, buf1, sizeof(buf1));
+ ssize_t len2 = read_in_full(fd2, buf2, sizeof(buf2));
+
+ if (len1 < 0 || len2 < 0 || len1 != len2)
+ ret = 0; /* read error or different file size */
+ else if (!len1) /* len2 is also 0; hit EOF on both */
+ break; /* ret is still true */
+ else
+ ret = !memcmp(buf1, buf2, len1);
+ }
+
+ if (fd1 >= 0)
+ close(fd1);
+ if (fd2 >= 0)
+ close(fd2);
+
+ return ret;
+}
diff --git a/copy.h b/copy.h
index 2af77cba86..057259a3a7 100644
--- a/copy.h
+++ b/copy.h
@@ -7,4 +7,18 @@ int copy_fd(int ifd, int ofd);
int copy_file(const char *dst, const char *src, int mode);
int copy_file_with_time(const char *dst, const char *src, int mode);
+/*
+ * Compare the file mode and contents of two given files.
+ *
+ * If both files are actually symbolic links, the function returns 1 if the link
+ * targets are identical or 0 if they are not.
+ *
+ * If any of the two files cannot be accessed or in case of read failures, this
+ * function returns 0.
+ *
+ * If the file modes and contents are identical, the function returns 1,
+ * otherwise it returns 0.
+ */
+int do_files_match(const char *path1, const char *path2);
+
#endif /* COPY_H */
diff --git a/delta-islands.c b/delta-islands.c
index f7e079425f..4ac3c10551 100644
--- a/delta-islands.c
+++ b/delta-islands.c
@@ -488,7 +488,8 @@ void load_delta_islands(struct repository *r, int progress)
git_config(island_config_callback, &ild);
ild.remote_islands = kh_init_str();
- for_each_ref(find_island_for_ref, &ild);
+ refs_for_each_ref(get_main_ref_store(the_repository),
+ find_island_for_ref, &ild);
free_config_regexes(&ild);
deduplicate_islands(ild.remote_islands, r);
free_remote_islands(ild.remote_islands);
diff --git a/diff.c b/diff.c
index 108c187577..ded9ac70df 100644
--- a/diff.c
+++ b/diff.c
@@ -4555,6 +4555,7 @@ static void run_diff_cmd(const char *pgm,
o, complete_rewrite);
} else {
fprintf(o->file, "* Unmerged path %s\n", name);
+ o->found_changes = 1;
}
}
diff --git a/dir.c b/dir.c
index 20ebe4cba2..2d83f3311a 100644
--- a/dir.c
+++ b/dir.c
@@ -100,6 +100,18 @@ int fspathncmp(const char *a, const char *b, size_t count)
return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);
}
+int paths_collide(const char *a, const char *b)
+{
+ size_t len_a = strlen(a), len_b = strlen(b);
+
+ if (len_a == len_b)
+ return fspatheq(a, b);
+
+ if (len_a < len_b)
+ return is_dir_sep(b[len_a]) && !fspathncmp(a, b, len_a);
+ return is_dir_sep(a[len_b]) && !fspathncmp(a, b, len_b);
+}
+
unsigned int fspathhash(const char *str)
{
return ignore_case ? strihash(str) : strhash(str);
diff --git a/dir.h b/dir.h
index 45a7b9ec5f..b9e8e96128 100644
--- a/dir.h
+++ b/dir.h
@@ -549,6 +549,13 @@ int fspathncmp(const char *a, const char *b, size_t count);
unsigned int fspathhash(const char *str);
/*
+ * Reports whether paths collide. This may be because the paths differ only in
+ * case on a case-sensitive filesystem, or that one path refers to a symlink
+ * that collides with one of the parent directories of the other.
+ */
+int paths_collide(const char *a, const char *b);
+
+/*
* The prefix part of pattern must not contains wildcards.
*/
struct pathspec_item;
diff --git a/entry.c b/entry.c
index f918a3a78e..b8c257f6f9 100644
--- a/entry.c
+++ b/entry.c
@@ -460,7 +460,7 @@ static void mark_colliding_entries(const struct checkout *state,
continue;
if ((trust_ino && !match_stat_data(&dup->ce_stat_data, st)) ||
- (!trust_ino && !fspathcmp(ce->name, dup->name))) {
+ paths_collide(ce->name, dup->name)) {
dup->ce_flags |= CE_MATCHED;
break;
}
@@ -547,6 +547,20 @@ int checkout_entry_ca(struct cache_entry *ce, struct conv_attrs *ca,
/* If it is a gitlink, leave it alone! */
if (S_ISGITLINK(ce->ce_mode))
return 0;
+ /*
+ * We must avoid replacing submodules' leading
+ * directories with symbolic links, lest recursive
+ * clones can write into arbitrary locations.
+ *
+ * Technically, this logic is not limited
+ * to recursive clones, or for that matter to
+ * submodules' paths colliding with symbolic links'
+ * paths. Yet it strikes a balance in favor of
+ * simplicity, and if paths are colliding, we might
+ * just as well keep the directories during a clone.
+ */
+ if (state->clone && S_ISLNK(ce->ce_mode))
+ return 0;
remove_subtree(&path);
} else if (unlink(path.buf))
return error_errno("unable to unlink old '%s'", path.buf);
diff --git a/environment.h b/environment.h
index 05fd94d7be..0b2d457f07 100644
--- a/environment.h
+++ b/environment.h
@@ -58,6 +58,13 @@ const char *getenv_safe(struct strvec *argv, const char *name);
#define GIT_ATTR_SOURCE_ENVIRONMENT "GIT_ATTR_SOURCE"
/*
+ * Environment variable used to propagate the --no-advice global option to the
+ * advice_enabled() helper, even when run in a subprocess.
+ * This is an internal variable that should not be set by the user.
+ */
+#define GIT_ADVICE_ENVIRONMENT "GIT_ADVICE"
+
+/*
* Environment variable used in handshaking the wire protocol.
* Contains a colon ':' separated list of keys with optional values
* 'key[=value]'. Presence of unknown keys and values must be
diff --git a/fetch-pack.c b/fetch-pack.c
index 091f9a80a9..7d2aef21ad 100644
--- a/fetch-pack.c
+++ b/fetch-pack.c
@@ -290,7 +290,8 @@ static void mark_tips(struct fetch_negotiator *negotiator,
int i;
if (!negotiation_tips) {
- for_each_rawref(rev_list_insert_ref_oid, negotiator);
+ refs_for_each_rawref(get_main_ref_store(the_repository),
+ rev_list_insert_ref_oid, negotiator);
return;
}
@@ -732,11 +733,6 @@ static void mark_alternate_complete(struct fetch_negotiator *negotiator UNUSED,
mark_complete(&obj->oid);
}
-struct loose_object_iter {
- struct oidset *loose_object_set;
- struct ref *refs;
-};
-
/*
* Mark recent commits available locally and reachable from a local ref as
* COMPLETE.
@@ -793,7 +789,8 @@ static void mark_complete_and_common_ref(struct fetch_negotiator *negotiator,
*/
trace2_region_enter("fetch-pack", "mark_complete_local_refs", NULL);
if (!args->deepen) {
- for_each_rawref(mark_complete_oid, NULL);
+ refs_for_each_rawref(get_main_ref_store(the_repository),
+ mark_complete_oid, NULL);
for_each_cached_alternate(NULL, mark_alternate_complete);
commit_list_sort_by_date(&complete);
if (cutoff)
diff --git a/fmt-merge-msg.c b/fmt-merge-msg.c
index ae201e21db..7d144b803a 100644
--- a/fmt-merge-msg.c
+++ b/fmt-merge-msg.c
@@ -661,7 +661,9 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
/* learn the commit that we merge into and the current branch name */
current_branch = current_branch_to_free =
- resolve_refdup("HEAD", RESOLVE_REF_READING, &head_oid, NULL);
+ refs_resolve_refdup(get_main_ref_store(the_repository),
+ "HEAD", RESOLVE_REF_READING, &head_oid,
+ NULL);
if (!current_branch)
die("No current branch");
diff --git a/fsck.c b/fsck.c
index 78af29d264..8ef962199f 100644
--- a/fsck.c
+++ b/fsck.c
@@ -658,6 +658,8 @@ static int fsck_tree(const struct object_id *tree_oid,
retval += report(options, tree_oid, OBJ_TREE,
FSCK_MSG_MAILMAP_SYMLINK,
".mailmap is a symlink");
+ oidset_insert(&options->symlink_targets_found,
+ entry_oid);
}
if ((backslash = strchr(name, '\\'))) {
@@ -1166,6 +1168,56 @@ static int fsck_blob(const struct object_id *oid, const char *buf,
}
}
+ if (oidset_contains(&options->symlink_targets_found, oid)) {
+ const char *ptr = buf;
+ const struct object_id *reported = NULL;
+
+ oidset_insert(&options->symlink_targets_done, oid);
+
+ if (!buf || size > PATH_MAX) {
+ /*
+ * A missing buffer here is a sign that the caller found the
+ * blob too gigantic to load into memory. Let's just consider
+ * that an error.
+ */
+ return report(options, oid, OBJ_BLOB,
+ FSCK_MSG_SYMLINK_TARGET_LENGTH,
+ "symlink target too long");
+ }
+
+ while (!reported && ptr) {
+ const char *p = ptr;
+ char c, *slash = strchrnul(ptr, '/');
+ char *backslash = memchr(ptr, '\\', slash - ptr);
+
+ c = *slash;
+ *slash = '\0';
+
+ while (!reported && backslash) {
+ *backslash = '\0';
+ if (is_ntfs_dotgit(p))
+ ret |= report(options, reported = oid, OBJ_BLOB,
+ FSCK_MSG_SYMLINK_POINTS_TO_GIT_DIR,
+ "symlink target points to git dir");
+ *backslash = '\\';
+ p = backslash + 1;
+ backslash = memchr(p, '\\', slash - p);
+ }
+ if (!reported && is_ntfs_dotgit(p))
+ ret |= report(options, reported = oid, OBJ_BLOB,
+ FSCK_MSG_SYMLINK_POINTS_TO_GIT_DIR,
+ "symlink target points to git dir");
+
+ if (!reported && is_hfs_dotgit(ptr))
+ ret |= report(options, reported = oid, OBJ_BLOB,
+ FSCK_MSG_SYMLINK_POINTS_TO_GIT_DIR,
+ "symlink target points to git dir");
+
+ *slash = c;
+ ptr = c ? slash + 1 : NULL;
+ }
+ }
+
return ret;
}
@@ -1264,6 +1316,10 @@ int fsck_finish(struct fsck_options *options)
FSCK_MSG_GITATTRIBUTES_MISSING, FSCK_MSG_GITATTRIBUTES_BLOB,
options, ".gitattributes");
+ ret |= fsck_blobs(&options->symlink_targets_found, &options->symlink_targets_done,
+ FSCK_MSG_SYMLINK_TARGET_MISSING, FSCK_MSG_SYMLINK_TARGET_BLOB,
+ options, "<symlink-target>");
+
return ret;
}
diff --git a/fsck.h b/fsck.h
index e3adf9d911..17fa2dda5d 100644
--- a/fsck.h
+++ b/fsck.h
@@ -64,6 +64,8 @@ enum fsck_msg_type {
FUNC(GITATTRIBUTES_LARGE, ERROR) \
FUNC(GITATTRIBUTES_LINE_LENGTH, ERROR) \
FUNC(GITATTRIBUTES_BLOB, ERROR) \
+ FUNC(SYMLINK_TARGET_MISSING, ERROR) \
+ FUNC(SYMLINK_TARGET_BLOB, ERROR) \
/* warnings */ \
FUNC(EMPTY_NAME, WARN) \
FUNC(FULL_PATHNAME, WARN) \
@@ -74,6 +76,8 @@ enum fsck_msg_type {
FUNC(ZERO_PADDED_FILEMODE, WARN) \
FUNC(NUL_IN_COMMIT, WARN) \
FUNC(LARGE_PATHNAME, WARN) \
+ FUNC(SYMLINK_TARGET_LENGTH, WARN) \
+ FUNC(SYMLINK_POINTS_TO_GIT_DIR, WARN) \
/* infos (reported as warnings, but ignored by default) */ \
FUNC(BAD_FILEMODE, INFO) \
FUNC(GITMODULES_PARSE, INFO) \
@@ -141,6 +145,8 @@ struct fsck_options {
struct oidset gitmodules_done;
struct oidset gitattributes_found;
struct oidset gitattributes_done;
+ struct oidset symlink_targets_found;
+ struct oidset symlink_targets_done;
kh_oid_map_t *object_names;
};
@@ -150,6 +156,8 @@ struct fsck_options {
.gitmodules_done = OIDSET_INIT, \
.gitattributes_found = OIDSET_INIT, \
.gitattributes_done = OIDSET_INIT, \
+ .symlink_targets_found = OIDSET_INIT, \
+ .symlink_targets_done = OIDSET_INIT, \
.error_func = fsck_error_function \
}
#define FSCK_OPTIONS_STRICT { \
@@ -158,6 +166,8 @@ struct fsck_options {
.gitmodules_done = OIDSET_INIT, \
.gitattributes_found = OIDSET_INIT, \
.gitattributes_done = OIDSET_INIT, \
+ .symlink_targets_found = OIDSET_INIT, \
+ .symlink_targets_done = OIDSET_INIT, \
.error_func = fsck_error_function, \
}
#define FSCK_OPTIONS_MISSING_GITMODULES { \
@@ -166,6 +176,8 @@ struct fsck_options {
.gitmodules_done = OIDSET_INIT, \
.gitattributes_found = OIDSET_INIT, \
.gitattributes_done = OIDSET_INIT, \
+ .symlink_targets_found = OIDSET_INIT, \
+ .symlink_targets_done = OIDSET_INIT, \
.error_func = fsck_error_cb_print_missing_gitmodules, \
}
diff --git a/git-compat-util.h b/git-compat-util.h
index ca7678a379..892e1f9067 100644
--- a/git-compat-util.h
+++ b/git-compat-util.h
@@ -344,6 +344,7 @@ static inline const char *precompose_string_if_needed(const char *in)
return in;
}
+#define precompose_strbuf_if_needed(a)
#define probe_utf8_pathname_composition()
#endif
diff --git a/git-p4.py b/git-p4.py
index 28ab12c72b..f1ab31d540 100755
--- a/git-p4.py
+++ b/git-p4.py
@@ -3253,17 +3253,19 @@ class P4Sync(Command, P4UserMap):
if self.stream_have_file_info:
if "depotFile" in self.stream_file:
f = self.stream_file["depotFile"]
- # force a failure in fast-import, else an empty
- # commit will be made
- self.gitStream.write("\n")
- self.gitStream.write("die-now\n")
- self.gitStream.close()
- # ignore errors, but make sure it exits first
- self.importProcess.wait()
- if f:
- die("Error from p4 print for %s: %s" % (f, err))
- else:
- die("Error from p4 print: %s" % err)
+ try:
+ # force a failure in fast-import, else an empty
+ # commit will be made
+ self.gitStream.write("\n")
+ self.gitStream.write("die-now\n")
+ self.gitStream.close()
+ # ignore errors, but make sure it exits first
+ self.importProcess.wait()
+ finally:
+ if f:
+ die("Error from p4 print for %s: %s" % (f, err))
+ else:
+ die("Error from p4 print: %s" % err)
if 'depotFile' in marshalled and self.stream_have_file_info:
# start of a new file - output the old one first
diff --git a/git.c b/git.c
index 654d615a18..637c61ca9c 100644
--- a/git.c
+++ b/git.c
@@ -36,9 +36,10 @@ struct cmd_struct {
const char git_usage_string[] =
N_("git [-v | --version] [-h | --help] [-C <path>] [-c <name>=<value>]\n"
" [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n"
- " [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]\n"
- " [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\n"
- " [--config-env=<name>=<envvar>] <command> [<args>]");
+ " [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--no-lazy-fetch]\n"
+ " [--no-optional-locks] [--no-advice] [--bare] [--git-dir=<path>]\n"
+ " [--work-tree=<path>] [--namespace=<name>] [--config-env=<name>=<envvar>]\n"
+ " <command> [<args>]");
const char git_more_info_string[] =
N_("'git help -a' and 'git help -g' list available subcommands and some\n"
@@ -337,6 +338,10 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)
setenv(GIT_ATTR_SOURCE_ENVIRONMENT, cmd, 1);
if (envchanged)
*envchanged = 1;
+ } else if (!strcmp(cmd, "--no-advice")) {
+ setenv(GIT_ADVICE_ENVIRONMENT, "0", 1);
+ if (envchanged)
+ *envchanged = 1;
} else {
fprintf(stderr, _("unknown option: %s\n"), cmd);
usage(git_usage_string);
diff --git a/help.c b/help.c
index 2dbe57b413..1d057aa607 100644
--- a/help.c
+++ b/help.c
@@ -800,7 +800,7 @@ static int append_similar_ref(const char *refname,
if (starts_with(refname, "refs/remotes/") &&
!strcmp(branch, cb->base_ref))
string_list_append_nodup(cb->similar_refs,
- shorten_unambiguous_ref(refname, 1));
+ refs_shorten_unambiguous_ref(get_main_ref_store(the_repository), refname, 1));
return 0;
}
@@ -811,7 +811,8 @@ static struct string_list guess_refs(const char *ref)
ref_cb.base_ref = ref;
ref_cb.similar_refs = &similar_refs;
- for_each_ref(append_similar_ref, &ref_cb);
+ refs_for_each_ref(get_main_ref_store(the_repository),
+ append_similar_ref, &ref_cb);
return similar_refs;
}
diff --git a/hook.c b/hook.c
index f6306d72b3..eebc4d4473 100644
--- a/hook.c
+++ b/hook.c
@@ -7,25 +7,56 @@
#include "run-command.h"
#include "config.h"
#include "strbuf.h"
+#include "environment.h"
+#include "setup.h"
+#include "copy.h"
+
+static int identical_to_template_hook(const char *name, const char *path)
+{
+ const char *env = getenv("GIT_CLONE_TEMPLATE_DIR");
+ const char *template_dir = get_template_dir(env && *env ? env : NULL);
+ struct strbuf template_path = STRBUF_INIT;
+ int found_template_hook, ret;
+
+ strbuf_addf(&template_path, "%s/hooks/%s", template_dir, name);
+ found_template_hook = access(template_path.buf, X_OK) >= 0;
+#ifdef STRIP_EXTENSION
+ if (!found_template_hook) {
+ strbuf_addstr(&template_path, STRIP_EXTENSION);
+ found_template_hook = access(template_path.buf, X_OK) >= 0;
+ }
+#endif
+ if (!found_template_hook)
+ return 0;
+
+ ret = do_files_match(template_path.buf, path);
+
+ strbuf_release(&template_path);
+ return ret;
+}
const char *find_hook(const char *name)
{
static struct strbuf path = STRBUF_INIT;
+ int found_hook;
+
strbuf_reset(&path);
strbuf_git_path(&path, "hooks/%s", name);
- if (access(path.buf, X_OK) < 0) {
+ found_hook = access(path.buf, X_OK) >= 0;
+#ifdef STRIP_EXTENSION
+ if (!found_hook) {
int err = errno;
-#ifdef STRIP_EXTENSION
strbuf_addstr(&path, STRIP_EXTENSION);
- if (access(path.buf, X_OK) >= 0)
- return path.buf;
- if (errno == EACCES)
- err = errno;
+ found_hook = access(path.buf, X_OK) >= 0;
+ if (!found_hook)
+ errno = err;
+ }
#endif
- if (err == EACCES && advice_enabled(ADVICE_IGNORED_HOOK)) {
+ if (!found_hook) {
+ if (errno == EACCES && advice_enabled(ADVICE_IGNORED_HOOK)) {
static struct string_list advise_given = STRING_LIST_INIT_DUP;
if (!string_list_lookup(&advise_given, name)) {
@@ -39,6 +70,14 @@ const char *find_hook(const char *name)
}
return NULL;
}
+ if (!git_hooks_path && git_env_bool("GIT_CLONE_PROTECTION_ACTIVE", 0) &&
+ !identical_to_template_hook(name, path.buf))
+ die(_("active `%s` hook found during `git clone`:\n\t%s\n"
+ "For security reasons, this is disallowed by default.\n"
+ "If this is intentional and the hook should actually "
+ "be run, please\nrun the command again with "
+ "`GIT_CLONE_PROTECTION_ACTIVE=false`"),
+ name, path.buf);
return path.buf;
}
diff --git a/http-backend.c b/http-backend.c
index 1ed1e29d07..5b65287ac9 100644
--- a/http-backend.c
+++ b/http-backend.c
@@ -559,7 +559,8 @@ static void get_info_refs(struct strbuf *hdr, char *arg UNUSED)
} else {
select_getanyfile(hdr);
- for_each_namespaced_ref(NULL, show_text_ref, &buf);
+ refs_for_each_namespaced_ref(get_main_ref_store(the_repository),
+ NULL, show_text_ref, &buf);
send_strbuf(hdr, "text/plain", &buf);
}
strbuf_release(&buf);
@@ -571,9 +572,10 @@ static int show_head_ref(const char *refname, const struct object_id *oid,
struct strbuf *buf = cb_data;
if (flag & REF_ISSYMREF) {
- const char *target = resolve_ref_unsafe(refname,
- RESOLVE_REF_READING,
- NULL, NULL);
+ const char *target = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ refname,
+ RESOLVE_REF_READING,
+ NULL, NULL);
if (target)
strbuf_addf(buf, "ref: %s\n", strip_namespace(target));
@@ -589,7 +591,8 @@ static void get_head(struct strbuf *hdr, char *arg UNUSED)
struct strbuf buf = STRBUF_INIT;
select_getanyfile(hdr);
- head_ref_namespaced(show_head_ref, &buf);
+ refs_head_ref_namespaced(get_main_ref_store(the_repository),
+ show_head_ref, &buf);
send_strbuf(hdr, "text/plain", &buf);
strbuf_release(&buf);
}
diff --git a/log-tree.c b/log-tree.c
index 16031b44e7..41416de4e3 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -232,8 +232,10 @@ void load_ref_decorations(struct decoration_filter *filter, int flags)
}
decoration_loaded = 1;
decoration_flags = flags;
- for_each_ref(add_ref_decoration, filter);
- head_ref(add_ref_decoration, filter);
+ refs_for_each_ref(get_main_ref_store(the_repository),
+ add_ref_decoration, filter);
+ refs_head_ref(get_main_ref_store(the_repository),
+ add_ref_decoration, filter);
for_each_commit_graft(add_graft_decoration, filter);
}
}
@@ -277,7 +279,8 @@ static const struct name_decoration *current_pointed_by_HEAD(const struct name_d
return NULL;
/* Now resolve and find the matching current branch */
- branch_name = resolve_ref_unsafe("HEAD", 0, NULL, &rru_flags);
+ branch_name = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ "HEAD", 0, NULL, &rru_flags);
if (!branch_name || !(rru_flags & REF_ISSYMREF))
return NULL;
diff --git a/ls-refs.c b/ls-refs.c
index 819cbefee3..8e3ffff811 100644
--- a/ls-refs.c
+++ b/ls-refs.c
@@ -95,9 +95,11 @@ static int send_ref(const char *refname, const struct object_id *oid,
strbuf_addf(&data->buf, "unborn %s", refname_nons);
if (data->symrefs && flag & REF_ISSYMREF) {
struct object_id unused;
- const char *symref_target = resolve_ref_unsafe(refname, 0,
- &unused,
- &flag);
+ const char *symref_target = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ refname,
+ 0,
+ &unused,
+ &flag);
if (!symref_target)
die("'%s' is a symref but it is not?", refname);
@@ -126,7 +128,7 @@ static void send_possibly_unborn_head(struct ls_refs_data *data)
int oid_is_null;
strbuf_addf(&namespaced, "%sHEAD", get_git_namespace());
- if (!resolve_ref_unsafe(namespaced.buf, 0, &oid, &flag))
+ if (!refs_resolve_ref_unsafe(get_main_ref_store(the_repository), namespaced.buf, 0, &oid, &flag))
return; /* bad ref */
oid_is_null = is_null_oid(&oid);
if (!oid_is_null ||
diff --git a/mergetools/vimdiff b/mergetools/vimdiff
index 734d15a03b..f8ad6b35d4 100644
--- a/mergetools/vimdiff
+++ b/mergetools/vimdiff
@@ -325,7 +325,7 @@ gen_cmd () {
fi
# If this is a single window diff with all the buffers
- if ! echo "$tab" | grep ",\|/" >/dev/null
+ if ! echo "$tab" | grep -E ",|/" >/dev/null
then
CMD="$CMD | silent execute 'bufdo diffthis'"
fi
diff --git a/midx-write.c b/midx-write.c
index 65e69d2de7..9d096d5a28 100644
--- a/midx-write.c
+++ b/midx-write.c
@@ -755,7 +755,8 @@ static struct commit **find_commits_for_midx_bitmap(uint32_t *indexed_commits_nr
read_refs_snapshot(refs_snapshot, &revs);
} else {
setup_revisions(0, NULL, &revs, NULL);
- for_each_ref(add_ref_to_pending, &revs);
+ refs_for_each_ref(get_main_ref_store(the_repository),
+ add_ref_to_pending, &revs);
}
/*
diff --git a/negotiator/default.c b/negotiator/default.c
index 9a5b696327..518b3c43b2 100644
--- a/negotiator/default.c
+++ b/negotiator/default.c
@@ -192,6 +192,7 @@ void default_negotiator_init(struct fetch_negotiator *negotiator)
ns->rev_list.compare = compare_commits_by_commit_date;
if (marked)
- for_each_ref(clear_marks, NULL);
+ refs_for_each_ref(get_main_ref_store(the_repository),
+ clear_marks, NULL);
marked = 1;
}
diff --git a/negotiator/skipping.c b/negotiator/skipping.c
index 5b91520430..b7e008c2fd 100644
--- a/negotiator/skipping.c
+++ b/negotiator/skipping.c
@@ -261,6 +261,7 @@ void skipping_negotiator_init(struct fetch_negotiator *negotiator)
data->rev_list.compare = compare;
if (marked)
- for_each_ref(clear_marks, NULL);
+ refs_for_each_ref(get_main_ref_store(the_repository),
+ clear_marks, NULL);
marked = 1;
}
diff --git a/notes-cache.c b/notes-cache.c
index 0e1d5b1ac7..038db01ca0 100644
--- a/notes-cache.c
+++ b/notes-cache.c
@@ -17,7 +17,7 @@ static int notes_cache_match_validity(struct repository *r,
struct strbuf msg = STRBUF_INIT;
int ret;
- if (read_ref(ref, &oid) < 0)
+ if (refs_read_ref(get_main_ref_store(the_repository), ref, &oid) < 0)
return 0;
commit = lookup_commit_reference_gently(r, &oid, 1);
@@ -66,8 +66,8 @@ int notes_cache_write(struct notes_cache *c)
if (commit_tree(c->validity, strlen(c->validity), &tree_oid, NULL,
&commit_oid, NULL, NULL) < 0)
return -1;
- if (update_ref("update notes cache", c->tree.update_ref, &commit_oid,
- NULL, 0, UPDATE_REFS_QUIET_ON_ERR) < 0)
+ if (refs_update_ref(get_main_ref_store(the_repository), "update notes cache", c->tree.update_ref, &commit_oid,
+ NULL, 0, UPDATE_REFS_QUIET_ON_ERR) < 0)
return -1;
return 0;
diff --git a/notes-merge.c b/notes-merge.c
index 51282934ae..6a9a139b12 100644
--- a/notes-merge.c
+++ b/notes-merge.c
@@ -562,7 +562,7 @@ int notes_merge(struct notes_merge_options *o,
o->local_ref, o->remote_ref);
/* Dereference o->local_ref into local_sha1 */
- if (read_ref_full(o->local_ref, 0, &local_oid, NULL))
+ if (refs_read_ref_full(get_main_ref_store(the_repository), o->local_ref, 0, &local_oid, NULL))
die("Failed to resolve local notes ref '%s'", o->local_ref);
else if (!check_refname_format(o->local_ref, 0) &&
is_null_oid(&local_oid))
diff --git a/notes-utils.c b/notes-utils.c
index 6197a5a455..e33aa86c4b 100644
--- a/notes-utils.c
+++ b/notes-utils.c
@@ -23,7 +23,7 @@ void create_notes_commit(struct repository *r,
if (!parents) {
/* Deduce parent commit from t->ref */
struct object_id parent_oid;
- if (!read_ref(t->ref, &parent_oid)) {
+ if (!refs_read_ref(get_main_ref_store(the_repository), t->ref, &parent_oid)) {
struct commit *parent = lookup_commit(r, &parent_oid);
if (repo_parse_commit(r, parent))
die("Failed to find/parse commit %s", t->ref);
@@ -55,8 +55,9 @@ void commit_notes(struct repository *r, struct notes_tree *t, const char *msg)
create_notes_commit(r, t, NULL, buf.buf, buf.len, &commit_oid);
strbuf_insertstr(&buf, 0, "notes: ");
- update_ref(buf.buf, t->update_ref, &commit_oid, NULL, 0,
- UPDATE_REFS_DIE_ON_ERR);
+ refs_update_ref(get_main_ref_store(the_repository), buf.buf,
+ t->update_ref, &commit_oid, NULL, 0,
+ UPDATE_REFS_DIE_ON_ERR);
strbuf_release(&buf);
}
diff --git a/notes.c b/notes.c
index fed1eda80c..53ca25c814 100644
--- a/notes.c
+++ b/notes.c
@@ -945,7 +945,8 @@ void string_list_add_refs_by_glob(struct string_list *list, const char *glob)
{
assert(list->strdup_strings);
if (has_glob_specials(glob)) {
- for_each_glob_ref(string_list_add_one_ref, glob, list);
+ refs_for_each_glob_ref(get_main_ref_store(the_repository),
+ string_list_add_one_ref, glob, list);
} else {
struct object_id oid;
if (repo_get_oid(the_repository, glob, &oid))
@@ -1029,7 +1030,7 @@ void init_notes(struct notes_tree *t, const char *notes_ref,
if (flags & NOTES_INIT_EMPTY ||
repo_get_oid_treeish(the_repository, notes_ref, &object_oid))
return;
- if (flags & NOTES_INIT_WRITABLE && read_ref(notes_ref, &object_oid))
+ if (flags & NOTES_INIT_WRITABLE && refs_read_ref(get_main_ref_store(the_repository), notes_ref, &object_oid))
die("Cannot use notes ref %s", notes_ref);
if (get_tree_entry(the_repository, &object_oid, "", &oid, &mode))
die("Failed to read notes tree referenced by %s (%s)",
diff --git a/oss-fuzz/fuzz-commit-graph.c b/oss-fuzz/fuzz-commit-graph.c
index fe15e2c225..75e668a057 100644
--- a/oss-fuzz/fuzz-commit-graph.c
+++ b/oss-fuzz/fuzz-commit-graph.c
@@ -19,6 +19,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
* touching the disk to keep the individual fuzz-test cases as fast as
* possible.
*/
+ repo_set_hash_algo(the_repository, GIT_HASH_SHA1);
the_repository->settings.commit_graph_generation_version = 2;
the_repository->settings.commit_graph_read_changed_paths = 1;
g = parse_commit_graph(&the_repository->settings, (void *)data, size);
diff --git a/parse-options-cb.c b/parse-options-cb.c
index bdc7fae497..d99d688d3c 100644
--- a/parse-options-cb.c
+++ b/parse-options-cb.c
@@ -7,6 +7,7 @@
#include "environment.h"
#include "gettext.h"
#include "object-name.h"
+#include "setup.h"
#include "string-list.h"
#include "strvec.h"
#include "oid-array.h"
@@ -29,7 +30,7 @@ int parse_opt_abbrev_cb(const struct option *opt, const char *arg, int unset)
opt->long_name);
if (v && v < MINIMUM_ABBREV)
v = MINIMUM_ABBREV;
- else if (v > the_hash_algo->hexsz)
+ else if (startup_info->have_repository && v > the_hash_algo->hexsz)
v = the_hash_algo->hexsz;
}
*(int *)(opt->value) = v;
diff --git a/path.c b/path.c
index 67229edb9c..adfb3d3eb7 100644
--- a/path.c
+++ b/path.c
@@ -5,7 +5,6 @@
#include "abspath.h"
#include "environment.h"
#include "gettext.h"
-#include "hex.h"
#include "repository.h"
#include "strbuf.h"
#include "string-list.h"
@@ -647,58 +646,6 @@ void strbuf_git_common_path(struct strbuf *sb,
va_end(args);
}
-int validate_headref(const char *path)
-{
- struct stat st;
- char buffer[256];
- const char *refname;
- struct object_id oid;
- int fd;
- ssize_t len;
-
- if (lstat(path, &st) < 0)
- return -1;
-
- /* Make sure it is a "refs/.." symlink */
- if (S_ISLNK(st.st_mode)) {
- len = readlink(path, buffer, sizeof(buffer)-1);
- if (len >= 5 && !memcmp("refs/", buffer, 5))
- return 0;
- return -1;
- }
-
- /*
- * Anything else, just open it and try to see if it is a symbolic ref.
- */
- fd = open(path, O_RDONLY);
- if (fd < 0)
- return -1;
- len = read_in_full(fd, buffer, sizeof(buffer)-1);
- close(fd);
-
- if (len < 0)
- return -1;
- buffer[len] = '\0';
-
- /*
- * Is it a symbolic ref?
- */
- if (skip_prefix(buffer, "ref:", &refname)) {
- while (isspace(*refname))
- refname++;
- if (starts_with(refname, "refs/"))
- return 0;
- }
-
- /*
- * Is this a detached HEAD?
- */
- if (!get_oid_hex(buffer, &oid))
- return 0;
-
- return -1;
-}
-
static struct passwd *getpw_str(const char *username, size_t len)
{
struct passwd *pw;
@@ -829,6 +776,7 @@ const char *enter_repo(const char *path, int strict)
if (!suffix[i])
return NULL;
gitfile = read_gitfile(used_path.buf);
+ die_upon_dubious_ownership(gitfile, NULL, used_path.buf);
if (gitfile) {
strbuf_reset(&used_path);
strbuf_addstr(&used_path, gitfile);
@@ -839,6 +787,7 @@ const char *enter_repo(const char *path, int strict)
}
else {
const char *gitfile = read_gitfile(path);
+ die_upon_dubious_ownership(gitfile, NULL, path);
if (gitfile)
path = gitfile;
if (chdir(path))
diff --git a/path.h b/path.h
index ea96487b29..c3bc8617bd 100644
--- a/path.h
+++ b/path.h
@@ -173,7 +173,6 @@ const char *git_path_fetch_head(struct repository *r);
const char *git_path_shallow(struct repository *r);
int ends_with_path_components(const char *path, const char *components);
-int validate_headref(const char *ref);
int calc_shared_perm(int mode);
int adjust_shared_perm(const char *path);
diff --git a/promisor-remote.c b/promisor-remote.c
index ac3aa1e365..b414922c44 100644
--- a/promisor-remote.c
+++ b/promisor-remote.c
@@ -8,6 +8,7 @@
#include "transport.h"
#include "strvec.h"
#include "packfile.h"
+#include "environment.h"
struct promisor_remote_config {
struct promisor_remote *promisors;
@@ -23,6 +24,15 @@ static int fetch_objects(struct repository *repo,
int i;
FILE *child_in;
+ if (git_env_bool(NO_LAZY_FETCH_ENVIRONMENT, 0)) {
+ static int warning_shown;
+ if (!warning_shown) {
+ warning_shown = 1;
+ warning(_("lazy fetching disabled; some objects may not be available"));
+ }
+ return -1;
+ }
+
child.git_cmd = 1;
child.in = -1;
if (repo != the_repository)
diff --git a/reachable.c b/reachable.c
index 3b85add243..1224b30008 100644
--- a/reachable.c
+++ b/reachable.c
@@ -363,10 +363,11 @@ void mark_reachable_objects(struct rev_info *revs, int mark_reflog,
add_index_objects_to_pending(revs, 0);
/* Add all external refs */
- for_each_ref(add_one_ref, revs);
+ refs_for_each_ref(get_main_ref_store(the_repository), add_one_ref,
+ revs);
/* detached HEAD is not included in the list above */
- head_ref(add_one_ref, revs);
+ refs_head_ref(get_main_ref_store(the_repository), add_one_ref, revs);
other_head_refs(add_one_ref, revs);
/* rebase autostash and orig-head */
diff --git a/read-cache.c b/read-cache.c
index e1723ad796..a6db25a16d 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -1116,19 +1116,32 @@ static int has_dir_name(struct index_state *istate,
istate->cache[istate->cache_nr - 1]->name,
&len_eq_last);
if (cmp_last > 0) {
- if (len_eq_last == 0) {
+ if (name[len_eq_last] != '/') {
/*
* The entry sorts AFTER the last one in the
- * index and their paths have no common prefix,
- * so there cannot be a F/D conflict.
+ * index.
+ *
+ * If there were a conflict with "file", then our
+ * name would start with "file/" and the last index
+ * entry would start with "file" but not "file/".
+ *
+ * The next character after common prefix is
+ * not '/', so there can be no conflict.
*/
return retval;
} else {
/*
* The entry sorts AFTER the last one in the
- * index, but has a common prefix. Fall through
- * to the loop below to disect the entry's path
- * and see where the difference is.
+ * index, and the next character after common
+ * prefix is '/'.
+ *
+ * Either the last index entry is a file in
+ * conflict with this entry, or it has a name
+ * which sorts between this entry and the
+ * potential conflicting file.
+ *
+ * In both cases, we fall through to the loop
+ * below and let the regular search code handle it.
*/
}
} else if (cmp_last == 0) {
@@ -1152,53 +1165,6 @@ static int has_dir_name(struct index_state *istate,
}
len = slash - name;
- if (cmp_last > 0) {
- /*
- * (len + 1) is a directory boundary (including
- * the trailing slash). And since the loop is
- * decrementing "slash", the first iteration is
- * the longest directory prefix; subsequent
- * iterations consider parent directories.
- */
-
- if (len + 1 <= len_eq_last) {
- /*
- * The directory prefix (including the trailing
- * slash) also appears as a prefix in the last
- * entry, so the remainder cannot collide (because
- * strcmp said the whole path was greater).
- *
- * EQ: last: xxx/A
- * this: xxx/B
- *
- * LT: last: xxx/file_A
- * this: xxx/file_B
- */
- return retval;
- }
-
- if (len > len_eq_last) {
- /*
- * This part of the directory prefix (excluding
- * the trailing slash) is longer than the known
- * equal portions, so this sub-directory cannot
- * collide with a file.
- *
- * GT: last: xxxA
- * this: xxxB/file
- */
- return retval;
- }
-
- /*
- * This is a possible collision. Fall through and
- * let the regular search code handle it.
- *
- * last: xxx
- * this: xxx/file
- */
- }
-
pos = index_name_stage_pos(istate, name, len, stage, EXPAND_SPARSE);
if (pos >= 0) {
/*
diff --git a/ref-filter.c b/ref-filter.c
index 59ad6f54dd..31cc096644 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -895,7 +895,9 @@ static int head_atom_parser(struct ref_format *format UNUSED,
{
if (arg)
return err_no_arg(err, "HEAD");
- atom->u.head = resolve_refdup("HEAD", RESOLVE_REF_READING, NULL, NULL);
+ atom->u.head = refs_resolve_refdup(get_main_ref_store(the_repository),
+ "HEAD", RESOLVE_REF_READING, NULL,
+ NULL);
return 0;
}
@@ -2135,7 +2137,9 @@ static const char *rstrip_ref_components(const char *refname, int len)
static const char *show_ref(struct refname_atom *atom, const char *refname)
{
if (atom->option == R_SHORT)
- return shorten_unambiguous_ref(refname, warn_ambiguous_refs);
+ return refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+ refname,
+ warn_ambiguous_refs);
else if (atom->option == R_LSTRIP)
return lstrip_ref_components(refname, atom->lstrip);
else if (atom->option == R_RSTRIP)
@@ -2338,8 +2342,10 @@ static int populate_value(struct ref_array_item *ref, struct strbuf *err)
CALLOC_ARRAY(ref->value, used_atom_cnt);
if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) {
- ref->symref = resolve_refdup(ref->refname, RESOLVE_REF_READING,
- NULL, NULL);
+ ref->symref = refs_resolve_refdup(get_main_ref_store(the_repository),
+ ref->refname,
+ RESOLVE_REF_READING,
+ NULL, NULL);
if (!ref->symref)
ref->symref = xstrdup("");
}
@@ -2640,7 +2646,8 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,
* prefixes like "refs/heads/" etc. are stripped off,
* so we have to look at everything:
*/
- return for_each_fullref_in("", cb, cb_data);
+ return refs_for_each_fullref_in(get_main_ref_store(the_repository),
+ "", NULL, cb, cb_data);
}
if (filter->ignore_case) {
@@ -2649,7 +2656,8 @@ static int for_each_fullref_in_pattern(struct ref_filter *filter,
* so just return everything and let the caller
* sort it out.
*/
- return for_each_fullref_in("", cb, cb_data);
+ return refs_for_each_fullref_in(get_main_ref_store(the_repository),
+ "", NULL, cb, cb_data);
}
if (!filter->name_patterns[0]) {
@@ -3060,11 +3068,17 @@ static int do_filter_refs(struct ref_filter *filter, unsigned int type, each_ref
* of filter_ref_kind().
*/
if (filter->kind == FILTER_REFS_BRANCHES)
- ret = for_each_fullref_in("refs/heads/", fn, cb_data);
+ ret = refs_for_each_fullref_in(get_main_ref_store(the_repository),
+ "refs/heads/", NULL,
+ fn, cb_data);
else if (filter->kind == FILTER_REFS_REMOTES)
- ret = for_each_fullref_in("refs/remotes/", fn, cb_data);
+ ret = refs_for_each_fullref_in(get_main_ref_store(the_repository),
+ "refs/remotes/", NULL,
+ fn, cb_data);
else if (filter->kind == FILTER_REFS_TAGS)
- ret = for_each_fullref_in("refs/tags/", fn, cb_data);
+ ret = refs_for_each_fullref_in(get_main_ref_store(the_repository),
+ "refs/tags/", NULL, fn,
+ cb_data);
else if (filter->kind & FILTER_REFS_REGULAR)
ret = for_each_fullref_in_pattern(filter, fn, cb_data);
@@ -3074,7 +3088,8 @@ static int do_filter_refs(struct ref_filter *filter, unsigned int type, each_ref
*/
if (!ret && (filter->kind != FILTER_REFS_KIND_MASK) &&
(filter->kind & FILTER_REFS_DETACHED_HEAD))
- head_ref(fn, cb_data);
+ refs_head_ref(get_main_ref_store(the_repository), fn,
+ cb_data);
}
clear_contains_cache(&filter->internal.contains_cache);
diff --git a/reflog-walk.c b/reflog-walk.c
index 66484f4f32..f11b97e889 100644
--- a/reflog-walk.c
+++ b/reflog-walk.c
@@ -67,24 +67,32 @@ static struct complete_reflogs *read_complete_reflog(const char *ref)
struct complete_reflogs *reflogs =
xcalloc(1, sizeof(struct complete_reflogs));
reflogs->ref = xstrdup(ref);
- for_each_reflog_ent(ref, read_one_reflog, reflogs);
+ refs_for_each_reflog_ent(get_main_ref_store(the_repository), ref,
+ read_one_reflog, reflogs);
if (reflogs->nr == 0) {
const char *name;
void *name_to_free;
- name = name_to_free = resolve_refdup(ref, RESOLVE_REF_READING,
- NULL, NULL);
+ name = name_to_free = refs_resolve_refdup(get_main_ref_store(the_repository),
+ ref,
+ RESOLVE_REF_READING,
+ NULL, NULL);
if (name) {
- for_each_reflog_ent(name, read_one_reflog, reflogs);
+ refs_for_each_reflog_ent(get_main_ref_store(the_repository),
+ name, read_one_reflog,
+ reflogs);
free(name_to_free);
}
}
if (reflogs->nr == 0) {
char *refname = xstrfmt("refs/%s", ref);
- for_each_reflog_ent(refname, read_one_reflog, reflogs);
+ refs_for_each_reflog_ent(get_main_ref_store(the_repository),
+ refname, read_one_reflog, reflogs);
if (reflogs->nr == 0) {
free(refname);
refname = xstrfmt("refs/heads/%s", ref);
- for_each_reflog_ent(refname, read_one_reflog, reflogs);
+ refs_for_each_reflog_ent(get_main_ref_store(the_repository),
+ refname, read_one_reflog,
+ reflogs);
}
free(refname);
}
@@ -174,7 +182,8 @@ int add_reflog_for_walk(struct reflog_walk_info *info,
else {
if (*branch == '\0') {
free(branch);
- branch = resolve_refdup("HEAD", 0, NULL, NULL);
+ branch = refs_resolve_refdup(get_main_ref_store(the_repository),
+ "HEAD", 0, NULL, NULL);
if (!branch)
die("no current branch");
@@ -236,7 +245,9 @@ void get_reflog_selector(struct strbuf *sb,
if (shorten) {
if (!commit_reflog->reflogs->short_ref)
commit_reflog->reflogs->short_ref
- = shorten_unambiguous_ref(commit_reflog->reflogs->ref, 0);
+ = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+ commit_reflog->reflogs->ref,
+ 0);
printed_ref = commit_reflog->reflogs->short_ref;
} else {
printed_ref = commit_reflog->reflogs->ref;
diff --git a/reflog.c b/reflog.c
index 647f3ca398..8861c2d606 100644
--- a/reflog.c
+++ b/reflog.c
@@ -343,7 +343,8 @@ void reflog_expiry_prepare(const char *refname,
case UE_ALWAYS:
return;
case UE_HEAD:
- for_each_ref(push_tip_to_list, &cb->tips);
+ refs_for_each_ref(get_main_ref_store(the_repository),
+ push_tip_to_list, &cb->tips);
for (elem = cb->tips; elem; elem = elem->next)
commit_list_insert(elem->item, &cb->mark_list);
break;
@@ -416,19 +417,22 @@ int reflog_delete(const char *rev, enum expire_reflog_flags flags, int verbose)
recno = strtoul(spec + 2, &ep, 10);
if (*ep == '}') {
cmd.recno = -recno;
- for_each_reflog_ent(ref, count_reflog_ent, &cmd);
+ refs_for_each_reflog_ent(get_main_ref_store(the_repository),
+ ref, count_reflog_ent, &cmd);
} else {
cmd.expire_total = approxidate(spec + 2);
- for_each_reflog_ent(ref, count_reflog_ent, &cmd);
+ refs_for_each_reflog_ent(get_main_ref_store(the_repository),
+ ref, count_reflog_ent, &cmd);
cmd.expire_total = 0;
}
cb.cmd = cmd;
- status |= reflog_expire(ref, flags,
- reflog_expiry_prepare,
- should_prune_fn,
- reflog_expiry_cleanup,
- &cb);
+ status |= refs_reflog_expire(get_main_ref_store(the_repository), ref,
+ flags,
+ reflog_expiry_prepare,
+ should_prune_fn,
+ reflog_expiry_cleanup,
+ &cb);
cleanup:
free(ref);
diff --git a/refs.c b/refs.c
index 55d2e0b2cb..32e91ff740 100644
--- a/refs.c
+++ b/refs.c
@@ -384,14 +384,6 @@ char *refs_resolve_refdup(struct ref_store *refs,
return xstrdup_or_null(result);
}
-char *resolve_refdup(const char *refname, int resolve_flags,
- struct object_id *oid, int *flags)
-{
- return refs_resolve_refdup(get_main_ref_store(the_repository),
- refname, resolve_flags,
- oid, flags);
-}
-
/* The argument to for_each_filter_refs */
struct for_each_ref_filter {
const char *pattern;
@@ -400,19 +392,18 @@ struct for_each_ref_filter {
void *cb_data;
};
-int read_ref_full(const char *refname, int resolve_flags, struct object_id *oid, int *flags)
+int refs_read_ref_full(struct ref_store *refs, const char *refname,
+ int resolve_flags, struct object_id *oid, int *flags)
{
- struct ref_store *refs = get_main_ref_store(the_repository);
-
if (refs_resolve_ref_unsafe(refs, refname, resolve_flags,
oid, flags))
return 0;
return -1;
}
-int read_ref(const char *refname, struct object_id *oid)
+int refs_read_ref(struct ref_store *refs, const char *refname, struct object_id *oid)
{
- return read_ref_full(refname, RESOLVE_REF_READING, oid, NULL);
+ return refs_read_ref_full(refs, refname, RESOLVE_REF_READING, oid, NULL);
}
int refs_ref_exists(struct ref_store *refs, const char *refname)
@@ -421,11 +412,6 @@ int refs_ref_exists(struct ref_store *refs, const char *refname)
NULL, NULL);
}
-int ref_exists(const char *refname)
-{
- return refs_ref_exists(get_main_ref_store(the_repository), refname);
-}
-
static int for_each_filter_refs(const char *refname,
const struct object_id *oid,
int flags, void *data)
@@ -477,7 +463,8 @@ static int warn_if_dangling_symref(const char *refname,
if (!(flags & REF_ISSYMREF))
return 0;
- resolves_to = resolve_ref_unsafe(refname, 0, NULL, NULL);
+ resolves_to = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ refname, 0, NULL, NULL);
if (!resolves_to
|| (d->refname
? strcmp(resolves_to, d->refname)
@@ -498,7 +485,8 @@ void warn_dangling_symref(FILE *fp, const char *msg_fmt, const char *refname)
data.refname = refname;
data.refnames = NULL;
data.msg_fmt = msg_fmt;
- for_each_rawref(warn_if_dangling_symref, &data);
+ refs_for_each_rawref(get_main_ref_store(the_repository),
+ warn_if_dangling_symref, &data);
}
void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct string_list *refnames)
@@ -509,7 +497,8 @@ void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct string_li
data.refname = NULL;
data.refnames = refnames;
data.msg_fmt = msg_fmt;
- for_each_rawref(warn_if_dangling_symref, &data);
+ refs_for_each_rawref(get_main_ref_store(the_repository),
+ warn_if_dangling_symref, &data);
}
int refs_for_each_tag_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
@@ -517,32 +506,17 @@ int refs_for_each_tag_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
return refs_for_each_ref_in(refs, "refs/tags/", fn, cb_data);
}
-int for_each_tag_ref(each_ref_fn fn, void *cb_data)
-{
- return refs_for_each_tag_ref(get_main_ref_store(the_repository), fn, cb_data);
-}
-
int refs_for_each_branch_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
{
return refs_for_each_ref_in(refs, "refs/heads/", fn, cb_data);
}
-int for_each_branch_ref(each_ref_fn fn, void *cb_data)
-{
- return refs_for_each_branch_ref(get_main_ref_store(the_repository), fn, cb_data);
-}
-
int refs_for_each_remote_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
{
return refs_for_each_ref_in(refs, "refs/remotes/", fn, cb_data);
}
-int for_each_remote_ref(each_ref_fn fn, void *cb_data)
-{
- return refs_for_each_remote_ref(get_main_ref_store(the_repository), fn, cb_data);
-}
-
-int head_ref_namespaced(each_ref_fn fn, void *cb_data)
+int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_data)
{
struct strbuf buf = STRBUF_INIT;
int ret = 0;
@@ -550,7 +524,7 @@ int head_ref_namespaced(each_ref_fn fn, void *cb_data)
int flag;
strbuf_addf(&buf, "%sHEAD", get_git_namespace());
- if (!read_ref_full(buf.buf, RESOLVE_REF_READING, &oid, &flag))
+ if (!refs_read_ref_full(refs, buf.buf, RESOLVE_REF_READING, &oid, &flag))
ret = fn(buf.buf, &oid, flag, cb_data);
strbuf_release(&buf);
@@ -583,8 +557,8 @@ void normalize_glob_ref(struct string_list_item *item, const char *prefix,
strbuf_release(&normalized_pattern);
}
-int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
- const char *prefix, void *cb_data)
+int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,
+ const char *pattern, const char *prefix, void *cb_data)
{
struct strbuf real_pattern = STRBUF_INIT;
struct for_each_ref_filter filter;
@@ -607,15 +581,16 @@ int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
filter.prefix = prefix;
filter.fn = fn;
filter.cb_data = cb_data;
- ret = for_each_ref(for_each_filter_refs, &filter);
+ ret = refs_for_each_ref(refs, for_each_filter_refs, &filter);
strbuf_release(&real_pattern);
return ret;
}
-int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data)
+int refs_for_each_glob_ref(struct ref_store *refs, each_ref_fn fn,
+ const char *pattern, void *cb_data)
{
- return for_each_glob_ref_in(fn, pattern, NULL, cb_data);
+ return refs_for_each_glob_ref_in(refs, fn, pattern, NULL, cb_data);
}
const char *prettify_refname(const char *name)
@@ -991,13 +966,6 @@ int refs_delete_ref(struct ref_store *refs, const char *msg,
return 0;
}
-int delete_ref(const char *msg, const char *refname,
- const struct object_id *old_oid, unsigned int flags)
-{
- return refs_delete_ref(get_main_ref_store(the_repository), msg, refname,
- old_oid, flags);
-}
-
static void copy_reflog_msg(struct strbuf *sb, const char *msg)
{
char c;
@@ -1190,11 +1158,6 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,
return tr;
}
-struct ref_transaction *ref_transaction_begin(struct strbuf *err)
-{
- return ref_store_transaction_begin(get_main_ref_store(the_repository), err);
-}
-
void ref_transaction_free(struct ref_transaction *transaction)
{
size_t i;
@@ -1217,6 +1180,8 @@ void ref_transaction_free(struct ref_transaction *transaction)
for (i = 0; i < transaction->nr; i++) {
free(transaction->updates[i]->msg);
+ free((char *)transaction->updates[i]->new_target);
+ free((char *)transaction->updates[i]->old_target);
free(transaction->updates[i]);
}
free(transaction->updates);
@@ -1228,6 +1193,7 @@ struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target, const char *old_target,
const char *msg)
{
struct ref_update *update;
@@ -1235,16 +1201,24 @@ struct ref_update *ref_transaction_add_update(
if (transaction->state != REF_TRANSACTION_OPEN)
BUG("update called for transaction that is not open");
+ if (old_oid && old_target)
+ BUG("only one of old_oid and old_target should be non NULL");
+ if (new_oid && new_target)
+ BUG("only one of new_oid and new_target should be non NULL");
+
FLEX_ALLOC_STR(update, refname, refname);
ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
transaction->updates[transaction->nr++] = update;
update->flags = flags;
- if (flags & REF_HAVE_NEW)
+ update->new_target = xstrdup_or_null(new_target);
+ update->old_target = xstrdup_or_null(old_target);
+ if ((flags & REF_HAVE_NEW) && new_oid)
oidcpy(&update->new_oid, new_oid);
- if (flags & REF_HAVE_OLD)
+ if ((flags & REF_HAVE_OLD) && old_oid)
oidcpy(&update->old_oid, old_oid);
+
update->msg = normalize_reflog_message(msg);
return update;
}
@@ -1253,6 +1227,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target,
+ const char *old_target,
unsigned int flags, const char *msg,
struct strbuf *err)
{
@@ -1278,9 +1254,11 @@ int ref_transaction_update(struct ref_transaction *transaction,
flags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;
flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
+ flags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);
ref_transaction_add_update(transaction, refname, flags,
- new_oid, old_oid, msg);
+ new_oid, old_oid, new_target,
+ old_target, msg);
return 0;
}
@@ -1295,7 +1273,8 @@ int ref_transaction_create(struct ref_transaction *transaction,
return 1;
}
return ref_transaction_update(transaction, refname, new_oid,
- null_oid(), flags, msg, err);
+ null_oid(), NULL, NULL, flags,
+ msg, err);
}
int ref_transaction_delete(struct ref_transaction *transaction,
@@ -1308,7 +1287,8 @@ int ref_transaction_delete(struct ref_transaction *transaction,
BUG("delete called with old_oid set to zeros");
return ref_transaction_update(transaction, refname,
null_oid(), old_oid,
- flags, msg, err);
+ NULL, NULL, flags,
+ msg, err);
}
int ref_transaction_verify(struct ref_transaction *transaction,
@@ -1321,6 +1301,7 @@ int ref_transaction_verify(struct ref_transaction *transaction,
BUG("verify called with old_oid set to NULL");
return ref_transaction_update(transaction, refname,
NULL, old_oid,
+ NULL, NULL,
flags, NULL, err);
}
@@ -1335,8 +1316,8 @@ int refs_update_ref(struct ref_store *refs, const char *msg,
t = ref_store_transaction_begin(refs, &err);
if (!t ||
- ref_transaction_update(t, refname, new_oid, old_oid, flags, msg,
- &err) ||
+ ref_transaction_update(t, refname, new_oid, old_oid, NULL, NULL,
+ flags, msg, &err) ||
ref_transaction_commit(t, &err)) {
ret = 1;
ref_transaction_free(t);
@@ -1363,15 +1344,6 @@ int refs_update_ref(struct ref_store *refs, const char *msg,
return 0;
}
-int update_ref(const char *msg, const char *refname,
- const struct object_id *new_oid,
- const struct object_id *old_oid,
- unsigned int flags, enum action_on_err onerr)
-{
- return refs_update_ref(get_main_ref_store(the_repository), msg, refname, new_oid,
- old_oid, flags, onerr);
-}
-
/*
* Check that the string refname matches a rule of the form
* "{prefix}%.*s{suffix}". So "foo/bar/baz" would match the rule
@@ -1473,12 +1445,6 @@ char *refs_shorten_unambiguous_ref(struct ref_store *refs,
return xstrdup(refname);
}
-char *shorten_unambiguous_ref(const char *refname, int strict)
-{
- return refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
- refname, strict);
-}
-
int parse_hide_refs_config(const char *var, const char *value, const char *section,
struct strvec *hide_refs)
{
@@ -1597,11 +1563,6 @@ int refs_head_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
return 0;
}
-int head_ref(each_ref_fn fn, void *cb_data)
-{
- return refs_head_ref(get_main_ref_store(the_repository), fn, cb_data);
-}
-
struct ref_iterator *refs_ref_iterator_begin(
struct ref_store *refs,
const char *prefix,
@@ -1696,28 +1657,12 @@ int refs_for_each_ref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
return do_for_each_ref(refs, "", NULL, fn, 0, 0, cb_data);
}
-int for_each_ref(each_ref_fn fn, void *cb_data)
-{
- return refs_for_each_ref(get_main_ref_store(the_repository), fn, cb_data);
-}
-
int refs_for_each_ref_in(struct ref_store *refs, const char *prefix,
each_ref_fn fn, void *cb_data)
{
return do_for_each_ref(refs, prefix, NULL, fn, strlen(prefix), 0, cb_data);
}
-int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
-{
- return refs_for_each_ref_in(get_main_ref_store(the_repository), prefix, fn, cb_data);
-}
-
-int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data)
-{
- return do_for_each_ref(get_main_ref_store(the_repository),
- prefix, NULL, fn, 0, 0, cb_data);
-}
-
int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
const char **exclude_patterns,
each_ref_fn fn, void *cb_data)
@@ -1733,14 +1678,14 @@ int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_dat
DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
}
-int for_each_namespaced_ref(const char **exclude_patterns,
- each_ref_fn fn, void *cb_data)
+int refs_for_each_namespaced_ref(struct ref_store *refs,
+ const char **exclude_patterns,
+ each_ref_fn fn, void *cb_data)
{
struct strbuf buf = STRBUF_INIT;
int ret;
strbuf_addf(&buf, "%srefs/", get_git_namespace());
- ret = do_for_each_ref(get_main_ref_store(the_repository),
- buf.buf, exclude_patterns, fn, 0, 0, cb_data);
+ ret = do_for_each_ref(refs, buf.buf, exclude_patterns, fn, 0, 0, cb_data);
strbuf_release(&buf);
return ret;
}
@@ -1751,11 +1696,6 @@ int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data)
DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
}
-int for_each_rawref(each_ref_fn fn, void *cb_data)
-{
- return refs_for_each_rawref(get_main_ref_store(the_repository), fn, cb_data);
-}
-
int refs_for_each_include_root_refs(struct ref_store *refs, each_ref_fn fn,
void *cb_data)
{
@@ -2021,13 +1961,6 @@ int refs_init_db(struct ref_store *refs, int flags, struct strbuf *err)
return refs->be->init_db(refs, flags, err);
}
-const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
- struct object_id *oid, int *flags)
-{
- return refs_resolve_ref_unsafe(get_main_ref_store(the_repository), refname,
- resolve_flags, oid, flags);
-}
-
int resolve_gitlink_ref(const char *submodule, const char *refname,
struct object_id *oid)
{
@@ -2266,26 +2199,27 @@ int peel_iterated_oid(const struct object_id *base, struct object_id *peeled)
return peel_object(base, peeled) ? -1 : 0;
}
-int refs_create_symref(struct ref_store *refs,
- const char *ref_target,
- const char *refs_heads_master,
- const char *logmsg)
+int refs_update_symref(struct ref_store *refs, const char *ref,
+ const char *target, const char *logmsg)
{
- char *msg;
- int retval;
+ struct ref_transaction *transaction;
+ struct strbuf err = STRBUF_INIT;
+ int ret = 0;
- msg = normalize_reflog_message(logmsg);
- retval = refs->be->create_symref(refs, ref_target, refs_heads_master,
- msg);
- free(msg);
- return retval;
-}
+ transaction = ref_store_transaction_begin(refs, &err);
+ if (!transaction ||
+ ref_transaction_update(transaction, ref, NULL, NULL,
+ target, NULL, REF_NO_DEREF,
+ logmsg, &err) ||
+ ref_transaction_commit(transaction, &err)) {
+ ret = error("%s", err.buf);
+ }
-int create_symref(const char *ref_target, const char *refs_heads_master,
- const char *logmsg)
-{
- return refs_create_symref(get_main_ref_store(the_repository), ref_target,
- refs_heads_master, logmsg);
+ strbuf_release(&err);
+ if (transaction)
+ ref_transaction_free(transaction);
+
+ return ret;
}
int ref_update_reject_duplicates(struct string_list *refnames,
@@ -2338,10 +2272,22 @@ static int run_transaction_hook(struct ref_transaction *transaction,
struct ref_update *update = transaction->updates[i];
strbuf_reset(&buf);
- strbuf_addf(&buf, "%s %s %s\n",
- oid_to_hex(&update->old_oid),
- oid_to_hex(&update->new_oid),
- update->refname);
+
+ if (!(update->flags & REF_HAVE_OLD))
+ strbuf_addf(&buf, "%s ", oid_to_hex(null_oid()));
+ else if (update->old_target)
+ strbuf_addf(&buf, "ref:%s ", update->old_target);
+ else
+ strbuf_addf(&buf, "%s ", oid_to_hex(&update->old_oid));
+
+ if (!(update->flags & REF_HAVE_NEW))
+ strbuf_addf(&buf, "%s ", oid_to_hex(null_oid()));
+ else if (update->new_target)
+ strbuf_addf(&buf, "ref:%s ", update->new_target);
+ else
+ strbuf_addf(&buf, "%s ", oid_to_hex(&update->new_oid));
+
+ strbuf_addf(&buf, "%s\n", update->refname);
if (write_in_full(proc.in, buf.buf, buf.len) < 0) {
if (errno != EPIPE) {
@@ -2581,11 +2527,6 @@ int refs_for_each_reflog(struct ref_store *refs, each_reflog_fn fn, void *cb_dat
do_for_each_reflog_helper, &hp);
}
-int for_each_reflog(each_reflog_fn fn, void *cb_data)
-{
- return refs_for_each_reflog(get_main_ref_store(the_repository), fn, cb_data);
-}
-
int refs_for_each_reflog_ent_reverse(struct ref_store *refs,
const char *refname,
each_reflog_ent_fn fn,
@@ -2595,58 +2536,28 @@ int refs_for_each_reflog_ent_reverse(struct ref_store *refs,
fn, cb_data);
}
-int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn,
- void *cb_data)
-{
- return refs_for_each_reflog_ent_reverse(get_main_ref_store(the_repository),
- refname, fn, cb_data);
-}
-
int refs_for_each_reflog_ent(struct ref_store *refs, const char *refname,
each_reflog_ent_fn fn, void *cb_data)
{
return refs->be->for_each_reflog_ent(refs, refname, fn, cb_data);
}
-int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn,
- void *cb_data)
-{
- return refs_for_each_reflog_ent(get_main_ref_store(the_repository), refname,
- fn, cb_data);
-}
-
int refs_reflog_exists(struct ref_store *refs, const char *refname)
{
return refs->be->reflog_exists(refs, refname);
}
-int reflog_exists(const char *refname)
-{
- return refs_reflog_exists(get_main_ref_store(the_repository), refname);
-}
-
int refs_create_reflog(struct ref_store *refs, const char *refname,
struct strbuf *err)
{
return refs->be->create_reflog(refs, refname, err);
}
-int safe_create_reflog(const char *refname, struct strbuf *err)
-{
- return refs_create_reflog(get_main_ref_store(the_repository), refname,
- err);
-}
-
int refs_delete_reflog(struct ref_store *refs, const char *refname)
{
return refs->be->delete_reflog(refs, refname);
}
-int delete_reflog(const char *refname)
-{
- return refs_delete_reflog(get_main_ref_store(the_repository), refname);
-}
-
int refs_reflog_expire(struct ref_store *refs,
const char *refname,
unsigned int flags,
@@ -2660,19 +2571,6 @@ int refs_reflog_expire(struct ref_store *refs,
cleanup_fn, policy_cb_data);
}
-int reflog_expire(const char *refname,
- unsigned int flags,
- reflog_expiry_prepare_fn prepare_fn,
- reflog_expiry_should_prune_fn should_prune_fn,
- reflog_expiry_cleanup_fn cleanup_fn,
- void *policy_cb_data)
-{
- return refs_reflog_expire(get_main_ref_store(the_repository),
- refname, flags,
- prepare_fn, should_prune_fn,
- cleanup_fn, policy_cb_data);
-}
-
int initial_ref_transaction_commit(struct ref_transaction *transaction,
struct strbuf *err)
{
@@ -2751,12 +2649,6 @@ out:
return ret;
}
-int delete_refs(const char *msg, struct string_list *refnames,
- unsigned int flags)
-{
- return refs_delete_refs(get_main_ref_store(the_repository), msg, refnames, flags);
-}
-
int refs_rename_ref(struct ref_store *refs, const char *oldref,
const char *newref, const char *logmsg)
{
@@ -2769,11 +2661,6 @@ int refs_rename_ref(struct ref_store *refs, const char *oldref,
return retval;
}
-int rename_ref(const char *oldref, const char *newref, const char *logmsg)
-{
- return refs_rename_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
-}
-
int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
const char *newref, const char *logmsg)
{
@@ -2786,7 +2673,37 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
return retval;
}
-int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg)
+const char *ref_update_original_update_refname(struct ref_update *update)
+{
+ while (update->parent_update)
+ update = update->parent_update;
+
+ return update->refname;
+}
+
+int ref_update_has_null_new_value(struct ref_update *update)
+{
+ return !update->new_target && is_null_oid(&update->new_oid);
+}
+
+int ref_update_check_old_target(const char *referent, struct ref_update *update,
+ struct strbuf *err)
{
- return refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
+ if (!update->old_target)
+ BUG("called without old_target set");
+
+ if (!strcmp(referent, update->old_target))
+ return 0;
+
+ if (!strcmp(referent, ""))
+ strbuf_addf(err, "verifying symref target: '%s': "
+ "reference is missing but expected %s",
+ ref_update_original_update_refname(update),
+ update->old_target);
+ else
+ strbuf_addf(err, "verifying symref target: '%s': "
+ "is at %s but expected %s",
+ ref_update_original_update_refname(update),
+ referent, update->old_target);
+ return -1;
}
diff --git a/refs.h b/refs.h
index d278775e08..fb419ab2ed 100644
--- a/refs.h
+++ b/refs.h
@@ -72,18 +72,14 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,
struct object_id *oid,
int *flags);
-const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
- struct object_id *oid, int *flags);
-
char *refs_resolve_refdup(struct ref_store *refs,
const char *refname, int resolve_flags,
struct object_id *oid, int *flags);
-char *resolve_refdup(const char *refname, int resolve_flags,
- struct object_id *oid, int *flags);
-int read_ref_full(const char *refname, int resolve_flags,
- struct object_id *oid, int *flags);
-int read_ref(const char *refname, struct object_id *oid);
+int refs_read_ref_full(struct ref_store *refs, const char *refname,
+ int resolve_flags, struct object_id *oid, int *flags);
+
+int refs_read_ref(struct ref_store *refs, const char *refname, struct object_id *oid);
int refs_read_symbolic_ref(struct ref_store *ref_store, const char *refname,
struct strbuf *referent);
@@ -114,8 +110,6 @@ int refs_verify_refname_available(struct ref_store *refs,
int refs_ref_exists(struct ref_store *refs, const char *refname);
-int ref_exists(const char *refname);
-
int should_autocreate_reflog(const char *refname);
int is_branch(const char *refname);
@@ -330,18 +324,6 @@ int refs_for_each_branch_ref(struct ref_store *refs,
int refs_for_each_remote_ref(struct ref_store *refs,
each_ref_fn fn, void *cb_data);
-/* just iterates the head ref. */
-int head_ref(each_ref_fn fn, void *cb_data);
-
-/* iterates all refs. */
-int for_each_ref(each_ref_fn fn, void *cb_data);
-
-/**
- * iterates all refs which have a defined prefix and strips that prefix from
- * the passed variable refname.
- */
-int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data);
-
/*
* references matching any pattern in "exclude_patterns" are omitted from the
* result set on a best-effort basis.
@@ -349,7 +331,6 @@ int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data);
int refs_for_each_fullref_in(struct ref_store *refs, const char *prefix,
const char **exclude_patterns,
each_ref_fn fn, void *cb_data);
-int for_each_fullref_in(const char *prefix, each_ref_fn fn, void *cb_data);
/**
* iterate all refs in "patterns" by partitioning patterns into disjoint sets
@@ -369,28 +350,27 @@ int refs_for_each_fullref_in_prefixes(struct ref_store *refs,
/**
* iterate refs from the respective area.
*/
-int for_each_tag_ref(each_ref_fn fn, void *cb_data);
-int for_each_branch_ref(each_ref_fn fn, void *cb_data);
-int for_each_remote_ref(each_ref_fn fn, void *cb_data);
int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data);
/* iterates all refs that match the specified glob pattern. */
-int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data);
+int refs_for_each_glob_ref(struct ref_store *refs, each_ref_fn fn,
+ const char *pattern, void *cb_data);
-int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
- const char *prefix, void *cb_data);
+int refs_for_each_glob_ref_in(struct ref_store *refs, each_ref_fn fn,
+ const char *pattern, const char *prefix, void *cb_data);
+
+int refs_head_ref_namespaced(struct ref_store *refs, each_ref_fn fn, void *cb_data);
-int head_ref_namespaced(each_ref_fn fn, void *cb_data);
/*
* references matching any pattern in "exclude_patterns" are omitted from the
* result set on a best-effort basis.
*/
-int for_each_namespaced_ref(const char **exclude_patterns,
- each_ref_fn fn, void *cb_data);
+int refs_for_each_namespaced_ref(struct ref_store *refs,
+ const char **exclude_patterns,
+ each_ref_fn fn, void *cb_data);
/* can be used to learn about broken ref and symref */
int refs_for_each_rawref(struct ref_store *refs, each_ref_fn fn, void *cb_data);
-int for_each_rawref(each_ref_fn fn, void *cb_data);
/*
* Iterates over all refs including root refs, i.e. pseudorefs and HEAD.
@@ -446,7 +426,6 @@ int refs_pack_refs(struct ref_store *refs, struct pack_refs_opts *opts);
*/
int refs_create_reflog(struct ref_store *refs, const char *refname,
struct strbuf *err);
-int safe_create_reflog(const char *refname, struct strbuf *err);
/**
* Reads log for the value of ref during at_time (in which case "cnt" should be
@@ -470,7 +449,6 @@ int read_ref_at(struct ref_store *refs,
/** Check if a particular reflog exists */
int refs_reflog_exists(struct ref_store *refs, const char *refname);
-int reflog_exists(const char *refname);
/*
* Delete the specified reference. If old_oid is non-NULL, then
@@ -484,8 +462,6 @@ int refs_delete_ref(struct ref_store *refs, const char *msg,
const char *refname,
const struct object_id *old_oid,
unsigned int flags);
-int delete_ref(const char *msg, const char *refname,
- const struct object_id *old_oid, unsigned int flags);
/*
* Delete the specified references. If there are any problems, emit
@@ -495,12 +471,9 @@ int delete_ref(const char *msg, const char *refname,
*/
int refs_delete_refs(struct ref_store *refs, const char *msg,
struct string_list *refnames, unsigned int flags);
-int delete_refs(const char *msg, struct string_list *refnames,
- unsigned int flags);
/** Delete a reflog */
int refs_delete_reflog(struct ref_store *refs, const char *refname);
-int delete_reflog(const char *refname);
/*
* Callback to process a reflog entry found by the iteration functions (see
@@ -546,17 +519,7 @@ int refs_for_each_reflog_ent_reverse(struct ref_store *refs,
void *cb_data);
/*
- * Iterate over reflog entries in the log for `refname` in the main ref store.
- */
-
-/* oldest entry first */
-int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data);
-
-/* youngest entry first */
-int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data);
-
-/*
- * The signature for the callback function for the {refs_,}for_each_reflog()
+ * The signature for the callback function for the refs_for_each_reflog()
* functions below. The memory pointed to by the refname argument is only
* guaranteed to be valid for the duration of a single callback invocation.
*/
@@ -567,7 +530,6 @@ typedef int each_reflog_fn(const char *refname, void *cb_data);
* and returns the value. Reflog file order is unspecified.
*/
int refs_for_each_reflog(struct ref_store *refs, each_reflog_fn fn, void *cb_data);
-int for_each_reflog(each_reflog_fn fn, void *cb_data);
#define REFNAME_ALLOW_ONELEVEL 1
#define REFNAME_REFSPEC_PATTERN 2
@@ -592,23 +554,17 @@ const char *prettify_refname(const char *refname);
char *refs_shorten_unambiguous_ref(struct ref_store *refs,
const char *refname, int strict);
-char *shorten_unambiguous_ref(const char *refname, int strict);
/** rename ref, return 0 on success **/
int refs_rename_ref(struct ref_store *refs, const char *oldref,
const char *newref, const char *logmsg);
-int rename_ref(const char *oldref, const char *newref,
- const char *logmsg);
/** copy ref, return 0 on success **/
int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
const char *newref, const char *logmsg);
-int copy_existing_ref(const char *oldref, const char *newref,
- const char *logmsg);
-int refs_create_symref(struct ref_store *refs, const char *refname,
+int refs_update_symref(struct ref_store *refs, const char *refname,
const char *target, const char *logmsg);
-int create_symref(const char *refname, const char *target, const char *logmsg);
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
@@ -622,7 +578,6 @@ enum action_on_err {
*/
struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,
struct strbuf *err);
-struct ref_transaction *ref_transaction_begin(struct strbuf *err);
/*
* Reference transaction updates
@@ -648,6 +603,16 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
* before the update. A copy of this value is made in the
* transaction.
*
+ * new_target -- the target reference that the reference will be
+ * updated to point to. If the reference is a regular reference,
+ * it will be converted to a symbolic reference. Cannot be set
+ * together with `new_oid`. A copy of this value is made in the
+ * transaction.
+ *
+ * old_target -- the reference that the reference must be pointing to.
+ * Canont be set together with `old_oid`. A copy of this value is
+ * made in the transaction.
+ *
* flags -- flags affecting the update, passed to
* update_ref_lock(). Possible flags: REF_NO_DEREF,
* REF_FORCE_CREATE_REFLOG. See those constants for more
@@ -713,7 +678,11 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);
* beforehand. The old value is checked after the lock is taken to
* prevent races. If the old value doesn't agree with old_oid, the
* whole transaction fails. If old_oid is NULL, then the previous
- * value is not checked.
+ * value is not checked. If `old_target` is not NULL, treat the reference
+ * as a symbolic ref and validate that its target before the update is
+ * `old_target`. If the `new_target` is not NULL, then the reference
+ * will be updated to a symbolic ref which targets `new_target`.
+ * Together, these allow us to update between regular refs and symrefs.
*
* See the above comment "Reference transaction updates" for more
* information.
@@ -722,6 +691,8 @@ int ref_transaction_update(struct ref_transaction *transaction,
const char *refname,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target,
+ const char *old_target,
unsigned int flags, const char *msg,
struct strbuf *err);
@@ -853,9 +824,6 @@ void ref_transaction_free(struct ref_transaction *transaction);
int refs_update_ref(struct ref_store *refs, const char *msg, const char *refname,
const struct object_id *new_oid, const struct object_id *old_oid,
unsigned int flags, enum action_on_err onerr);
-int update_ref(const char *msg, const char *refname,
- const struct object_id *new_oid, const struct object_id *old_oid,
- unsigned int flags, enum action_on_err onerr);
int parse_hide_refs_config(const char *var, const char *value, const char *,
struct strvec *);
@@ -913,7 +881,7 @@ enum expire_reflog_flags {
/*
* The following interface is used for reflog expiration. The caller
- * calls reflog_expire(), supplying it with three callback functions,
+ * calls refs_reflog_expire(), supplying it with three callback functions,
* of the following types. The callback functions define the
* expiration policy that is desired.
*
@@ -950,12 +918,6 @@ int refs_reflog_expire(struct ref_store *refs,
reflog_expiry_should_prune_fn should_prune_fn,
reflog_expiry_cleanup_fn cleanup_fn,
void *policy_cb_data);
-int reflog_expire(const char *refname,
- unsigned int flags,
- reflog_expiry_prepare_fn prepare_fn,
- reflog_expiry_should_prune_fn should_prune_fn,
- reflog_expiry_cleanup_fn cleanup_fn,
- void *policy_cb_data);
struct ref_store *get_main_ref_store(struct repository *r);
@@ -1054,4 +1016,211 @@ void update_ref_namespace(enum ref_namespace namespace, char *ref);
int is_pseudoref(struct ref_store *refs, const char *refname);
int is_headref(struct ref_store *refs, const char *refname);
+/*
+ * The following functions have been removed in Git v2.45 in favor of functions
+ * that receive a `ref_store` as parameter. The intent of this section is
+ * merely to help patch authors of in-flight series to have a reference what
+ * they should be migrating to. The section will be removed in Git v2.46.
+ */
+#if 0
+static char *resolve_refdup(const char *refname, int resolve_flags,
+ struct object_id *oid, int *flags)
+{
+ return refs_resolve_refdup(get_main_ref_store(the_repository),
+ refname, resolve_flags,
+ oid, flags);
+}
+
+static int read_ref_full(const char *refname, int resolve_flags,
+ struct object_id *oid, int *flags)
+{
+ return refs_read_ref_full(get_main_ref_store(the_repository), refname,
+ resolve_flags, oid, flags);
+}
+
+static int read_ref(const char *refname, struct object_id *oid)
+{
+ return refs_read_ref(get_main_ref_store(the_repository), refname, oid);
+}
+
+static int ref_exists(const char *refname)
+{
+ return refs_ref_exists(get_main_ref_store(the_repository), refname);
+}
+
+static int for_each_tag_ref(each_ref_fn fn, void *cb_data)
+{
+ return refs_for_each_tag_ref(get_main_ref_store(the_repository), fn, cb_data);
+}
+
+static int for_each_branch_ref(each_ref_fn fn, void *cb_data)
+{
+ return refs_for_each_branch_ref(get_main_ref_store(the_repository), fn, cb_data);
+}
+
+static int for_each_remote_ref(each_ref_fn fn, void *cb_data)
+{
+ return refs_for_each_remote_ref(get_main_ref_store(the_repository), fn, cb_data);
+}
+
+static int head_ref_namespaced(each_ref_fn fn, void *cb_data)
+{
+ return refs_head_ref_namespaced(get_main_ref_store(the_repository),
+ fn, cb_data);
+}
+
+static int for_each_glob_ref_in(each_ref_fn fn, const char *pattern,
+ const char *prefix, void *cb_data)
+{
+ return refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
+ fn, pattern, prefix, cb_data);
+}
+
+static int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data)
+{
+ return refs_for_each_glob_ref(get_main_ref_store(the_repository),
+ fn, pattern, cb_data);
+}
+
+static int delete_ref(const char *msg, const char *refname,
+ const struct object_id *old_oid, unsigned int flags)
+{
+ return refs_delete_ref(get_main_ref_store(the_repository), msg, refname,
+ old_oid, flags);
+}
+
+static struct ref_transaction *ref_transaction_begin(struct strbuf *err)
+{
+ return ref_store_transaction_begin(get_main_ref_store(the_repository), err);
+}
+
+static int update_ref(const char *msg, const char *refname,
+ const struct object_id *new_oid,
+ const struct object_id *old_oid,
+ unsigned int flags, enum action_on_err onerr)
+{
+ return refs_update_ref(get_main_ref_store(the_repository), msg, refname, new_oid,
+ old_oid, flags, onerr);
+}
+
+static char *shorten_unambiguous_ref(const char *refname, int strict)
+{
+ return refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+ refname, strict);
+}
+
+static int head_ref(each_ref_fn fn, void *cb_data)
+{
+ return refs_head_ref(get_main_ref_store(the_repository), fn, cb_data);
+}
+
+static int for_each_ref(each_ref_fn fn, void *cb_data)
+{
+ return refs_for_each_ref(get_main_ref_store(the_repository), fn, cb_data);
+}
+
+static int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
+{
+ return refs_for_each_ref_in(get_main_ref_store(the_repository), prefix, fn, cb_data);
+}
+
+static int for_each_fullref_in(const char *prefix,
+ const char **exclude_patterns,
+ each_ref_fn fn, void *cb_data)
+{
+ return refs_for_each_fullref_in(get_main_ref_store(the_repository),
+ prefix, exclude_patterns, fn, cb_data);
+}
+
+static int for_each_namespaced_ref(const char **exclude_patterns,
+ each_ref_fn fn, void *cb_data)
+{
+ return refs_for_each_namespaced_ref(get_main_ref_store(the_repository),
+ exclude_patterns, fn, cb_data);
+}
+
+static int for_each_rawref(each_ref_fn fn, void *cb_data)
+{
+ return refs_for_each_rawref(get_main_ref_store(the_repository), fn, cb_data);
+}
+
+static const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
+ struct object_id *oid, int *flags)
+{
+ return refs_resolve_ref_unsafe(get_main_ref_store(the_repository), refname,
+ resolve_flags, oid, flags);
+}
+
+static int create_symref(const char *ref_target, const char *refs_heads_master,
+ const char *logmsg)
+{
+ return refs_create_symref(get_main_ref_store(the_repository), ref_target,
+ refs_heads_master, logmsg);
+}
+
+static int for_each_reflog(each_reflog_fn fn, void *cb_data)
+{
+ return refs_for_each_reflog(get_main_ref_store(the_repository), fn, cb_data);
+}
+
+static int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn,
+ void *cb_data)
+{
+ return refs_for_each_reflog_ent_reverse(get_main_ref_store(the_repository),
+ refname, fn, cb_data);
+}
+
+static int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn,
+ void *cb_data)
+{
+ return refs_for_each_reflog_ent(get_main_ref_store(the_repository), refname,
+ fn, cb_data);
+}
+
+static int reflog_exists(const char *refname)
+{
+ return refs_reflog_exists(get_main_ref_store(the_repository), refname);
+}
+
+static int safe_create_reflog(const char *refname, struct strbuf *err)
+{
+ return refs_create_reflog(get_main_ref_store(the_repository), refname,
+ err);
+}
+
+static int delete_reflog(const char *refname)
+{
+ return refs_delete_reflog(get_main_ref_store(the_repository), refname);
+}
+
+static int reflog_expire(const char *refname,
+ unsigned int flags,
+ reflog_expiry_prepare_fn prepare_fn,
+ reflog_expiry_should_prune_fn should_prune_fn,
+ reflog_expiry_cleanup_fn cleanup_fn,
+ void *policy_cb_data)
+{
+ return refs_reflog_expire(get_main_ref_store(the_repository),
+ refname, flags,
+ prepare_fn, should_prune_fn,
+ cleanup_fn, policy_cb_data);
+}
+
+static int delete_refs(const char *msg, struct string_list *refnames,
+ unsigned int flags)
+{
+ return refs_delete_refs(get_main_ref_store(the_repository), msg, refnames, flags);
+}
+
+static int rename_ref(const char *oldref, const char *newref, const char *logmsg)
+{
+ return refs_rename_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
+}
+
+static int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg)
+{
+ return refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);
+}
+#endif
+
#endif /* REFS_H */
diff --git a/refs/debug.c b/refs/debug.c
index c7531b17f0..8be316bb67 100644
--- a/refs/debug.c
+++ b/refs/debug.c
@@ -131,18 +131,6 @@ static int debug_pack_refs(struct ref_store *ref_store, struct pack_refs_opts *o
return res;
}
-static int debug_create_symref(struct ref_store *ref_store,
- const char *ref_name, const char *target,
- const char *logmsg)
-{
- struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
- int res = drefs->refs->be->create_symref(drefs->refs, ref_name, target,
- logmsg);
- trace_printf_key(&trace_refs, "create_symref: %s -> %s \"%s\": %d\n", ref_name,
- target, logmsg, res);
- return res;
-}
-
static int debug_rename_ref(struct ref_store *ref_store, const char *oldref,
const char *newref, const char *logmsg)
{
@@ -441,7 +429,6 @@ struct ref_storage_be refs_be_debug = {
.initial_transaction_commit = debug_initial_transaction_commit,
.pack_refs = debug_pack_refs,
- .create_symref = debug_create_symref,
.rename_ref = debug_rename_ref,
.copy_ref = debug_copy_ref,
diff --git a/refs/files-backend.c b/refs/files-backend.c
index a098d14ea0..3dce0c2a34 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -794,8 +794,10 @@ retry:
*/
if (refs_verify_refname_available(
refs->packed_ref_store, refname,
- extras, NULL, err))
+ extras, NULL, err)) {
+ ret = TRANSACTION_NAME_CONFLICT;
goto error_return;
+ }
}
ret = 0;
@@ -1198,7 +1200,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)
ref_transaction_add_update(
transaction, r->name,
REF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,
- null_oid(), &r->oid, NULL);
+ null_oid(), &r->oid, NULL, NULL, NULL);
if (ref_transaction_commit(transaction, &err))
goto cleanup;
@@ -1292,7 +1294,7 @@ static int files_pack_refs(struct ref_store *ref_store,
* packed-refs transaction:
*/
if (ref_transaction_update(transaction, iter->refname,
- iter->oid, NULL,
+ iter->oid, NULL, NULL, NULL,
REF_NO_DEREF, NULL, &err))
die("failure preparing to create packed reference %s: %s",
iter->refname, err.buf);
@@ -1903,66 +1905,23 @@ static int create_ref_symlink(struct ref_lock *lock, const char *target)
return ret;
}
-static void update_symref_reflog(struct files_ref_store *refs,
- struct ref_lock *lock, const char *refname,
- const char *target, const char *logmsg)
+static int create_symref_lock(struct files_ref_store *refs,
+ struct ref_lock *lock, const char *refname,
+ const char *target, struct strbuf *err)
{
- struct strbuf err = STRBUF_INIT;
- struct object_id new_oid;
-
- if (logmsg &&
- refs_resolve_ref_unsafe(&refs->base, target,
- RESOLVE_REF_READING, &new_oid, NULL) &&
- files_log_ref_write(refs, refname, &lock->old_oid,
- &new_oid, logmsg, 0, &err)) {
- error("%s", err.buf);
- strbuf_release(&err);
- }
-}
-
-static int create_symref_locked(struct files_ref_store *refs,
- struct ref_lock *lock, const char *refname,
- const char *target, const char *logmsg)
-{
- if (prefer_symlink_refs && !create_ref_symlink(lock, target)) {
- update_symref_reflog(refs, lock, refname, target, logmsg);
- return 0;
+ if (!fdopen_lock_file(&lock->lk, "w")) {
+ strbuf_addf(err, "unable to fdopen %s: %s",
+ get_lock_file_path(&lock->lk), strerror(errno));
+ return -1;
}
- if (!fdopen_lock_file(&lock->lk, "w"))
- return error("unable to fdopen %s: %s",
+ if (fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target) < 0) {
+ strbuf_addf(err, "unable to write to %s: %s",
get_lock_file_path(&lock->lk), strerror(errno));
-
- update_symref_reflog(refs, lock, refname, target, logmsg);
-
- /* no error check; commit_ref will check ferror */
- fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target);
- if (commit_ref(lock) < 0)
- return error("unable to write symref for %s: %s", refname,
- strerror(errno));
- return 0;
-}
-
-static int files_create_symref(struct ref_store *ref_store,
- const char *refname, const char *target,
- const char *logmsg)
-{
- struct files_ref_store *refs =
- files_downcast(ref_store, REF_STORE_WRITE, "create_symref");
- struct strbuf err = STRBUF_INIT;
- struct ref_lock *lock;
- int ret;
-
- lock = lock_ref_oid_basic(refs, refname, &err);
- if (!lock) {
- error("%s", err.buf);
- strbuf_release(&err);
return -1;
}
- ret = create_symref_locked(refs, lock, refname, target, logmsg);
- unlock_ref(lock);
- return ret;
+ return 0;
}
static int files_reflog_exists(struct ref_store *ref_store,
@@ -2309,7 +2268,7 @@ static int split_head_update(struct ref_update *update,
transaction, "HEAD",
update->flags | REF_LOG_ONLY | REF_NO_DEREF,
&update->new_oid, &update->old_oid,
- update->msg);
+ NULL, NULL, update->msg);
/*
* Add "HEAD". This insertion is O(N) in the transaction
@@ -2371,8 +2330,9 @@ static int split_symref_update(struct ref_update *update,
new_update = ref_transaction_add_update(
transaction, referent, new_flags,
- &update->new_oid, &update->old_oid,
- update->msg);
+ update->new_target ? NULL : &update->new_oid,
+ update->old_target ? NULL : &update->old_oid,
+ update->new_target, update->old_target, update->msg);
new_update->parent_update = update;
@@ -2401,17 +2361,6 @@ static int split_symref_update(struct ref_update *update,
}
/*
- * Return the refname under which update was originally requested.
- */
-static const char *original_update_refname(struct ref_update *update)
-{
- while (update->parent_update)
- update = update->parent_update;
-
- return update->refname;
-}
-
-/*
* Check whether the REF_HAVE_OLD and old_oid values stored in update
* are consistent with oid, which is the reference's current value. If
* everything is OK, return 0; otherwise, write an error message to
@@ -2427,16 +2376,16 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,
if (is_null_oid(&update->old_oid))
strbuf_addf(err, "cannot lock ref '%s': "
"reference already exists",
- original_update_refname(update));
+ ref_update_original_update_refname(update));
else if (is_null_oid(oid))
strbuf_addf(err, "cannot lock ref '%s': "
"reference is missing but expected %s",
- original_update_refname(update),
+ ref_update_original_update_refname(update),
oid_to_hex(&update->old_oid));
else
strbuf_addf(err, "cannot lock ref '%s': "
"is at %s but expected %s",
- original_update_refname(update),
+ ref_update_original_update_refname(update),
oid_to_hex(oid),
oid_to_hex(&update->old_oid));
@@ -2471,7 +2420,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
files_assert_main_repository(refs, "lock_ref_for_update");
- if ((update->flags & REF_HAVE_NEW) && is_null_oid(&update->new_oid))
+ if ((update->flags & REF_HAVE_NEW) && ref_update_has_null_new_value(update))
update->flags |= REF_DELETING;
if (head_ref) {
@@ -2490,7 +2439,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
reason = strbuf_detach(err, NULL);
strbuf_addf(err, "cannot lock ref '%s': %s",
- original_update_refname(update), reason);
+ ref_update_original_update_refname(update), reason);
free(reason);
goto out;
}
@@ -2510,11 +2459,18 @@ static int lock_ref_for_update(struct files_ref_store *refs,
if (update->flags & REF_HAVE_OLD) {
strbuf_addf(err, "cannot lock ref '%s': "
"error reading reference",
- original_update_refname(update));
+ ref_update_original_update_refname(update));
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
- } else if (check_old_oid(update, &lock->old_oid, err)) {
+ }
+
+ if (update->old_target) {
+ if (ref_update_check_old_target(referent.buf, update, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
+ } else if (check_old_oid(update, &lock->old_oid, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
@@ -2535,7 +2491,17 @@ static int lock_ref_for_update(struct files_ref_store *refs,
} else {
struct ref_update *parent_update;
- if (check_old_oid(update, &lock->old_oid, err)) {
+ /*
+ * Even if the ref is a regular ref, if `old_target` is set, we
+ * check the referent value. Ideally `old_target` should only
+ * be set for symrefs, but we're strict about its usage.
+ */
+ if (update->old_target) {
+ if (ref_update_check_old_target(referent.buf, update, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
+ } else if (check_old_oid(update, &lock->old_oid, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto out;
}
@@ -2553,9 +2519,28 @@ static int lock_ref_for_update(struct files_ref_store *refs,
}
}
- if ((update->flags & REF_HAVE_NEW) &&
- !(update->flags & REF_DELETING) &&
- !(update->flags & REF_LOG_ONLY)) {
+ if (update->new_target && !(update->flags & REF_LOG_ONLY)) {
+ if (create_symref_lock(refs, lock, update->refname,
+ update->new_target, err)) {
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
+
+ if (close_ref_gently(lock)) {
+ strbuf_addf(err, "couldn't close '%s.lock'",
+ update->refname);
+ ret = TRANSACTION_GENERIC_ERROR;
+ goto out;
+ }
+
+ /*
+ * Once we have created the symref lock, the commit
+ * phase of the transaction only needs to commit the lock.
+ */
+ update->flags |= REF_NEEDS_COMMIT;
+ } else if ((update->flags & REF_HAVE_NEW) &&
+ !(update->flags & REF_DELETING) &&
+ !(update->flags & REF_LOG_ONLY)) {
if (!(update->type & REF_ISSYMREF) &&
oideq(&lock->old_oid, &update->new_oid)) {
/*
@@ -2763,7 +2748,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
packed_transaction, update->refname,
REF_HAVE_NEW | REF_NO_DEREF,
&update->new_oid, NULL,
- NULL);
+ NULL, NULL, NULL);
}
}
@@ -2818,6 +2803,43 @@ cleanup:
return ret;
}
+static int parse_and_write_reflog(struct files_ref_store *refs,
+ struct ref_update *update,
+ struct ref_lock *lock,
+ struct strbuf *err)
+{
+ if (update->new_target) {
+ /*
+ * We want to get the resolved OID for the target, to ensure
+ * that the correct value is added to the reflog.
+ */
+ if (!refs_resolve_ref_unsafe(&refs->base, update->new_target,
+ RESOLVE_REF_READING,
+ &update->new_oid, NULL)) {
+ /*
+ * TODO: currently we skip creating reflogs for dangling
+ * symref updates. It would be nice to capture this as
+ * zero oid updates however.
+ */
+ return 0;
+ }
+ }
+
+ if (files_log_ref_write(refs, lock->ref_name, &lock->old_oid,
+ &update->new_oid, update->msg, update->flags, err)) {
+ char *old_msg = strbuf_detach(err, NULL);
+
+ strbuf_addf(err, "cannot update the ref '%s': %s",
+ lock->ref_name, old_msg);
+ free(old_msg);
+ unlock_ref(lock);
+ update->backend_data = NULL;
+ return -1;
+ }
+
+ return 0;
+}
+
static int files_transaction_finish(struct ref_store *ref_store,
struct ref_transaction *transaction,
struct strbuf *err)
@@ -2848,23 +2870,20 @@ static int files_transaction_finish(struct ref_store *ref_store,
if (update->flags & REF_NEEDS_COMMIT ||
update->flags & REF_LOG_ONLY) {
- if (files_log_ref_write(refs,
- lock->ref_name,
- &lock->old_oid,
- &update->new_oid,
- update->msg, update->flags,
- err)) {
- char *old_msg = strbuf_detach(err, NULL);
-
- strbuf_addf(err, "cannot update the ref '%s': %s",
- lock->ref_name, old_msg);
- free(old_msg);
- unlock_ref(lock);
- update->backend_data = NULL;
+ if (parse_and_write_reflog(refs, update, lock, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
}
}
+
+ /*
+ * We try creating a symlink, if that succeeds we continue to the
+ * next update. If not, we try and create a regular symref.
+ */
+ if (update->new_target && prefer_symlink_refs)
+ if (!create_ref_symlink(lock, update->new_target))
+ continue;
+
if (update->flags & REF_NEEDS_COMMIT) {
clear_loose_ref_cache(refs);
if (commit_ref(lock)) {
@@ -3048,7 +3067,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,
ref_transaction_add_update(packed_transaction, update->refname,
update->flags & ~REF_HAVE_OLD,
&update->new_oid, &update->old_oid,
- NULL);
+ NULL, NULL, NULL);
}
if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
@@ -3291,7 +3310,6 @@ struct ref_storage_be refs_be_files = {
.initial_transaction_commit = files_initial_transaction_commit,
.pack_refs = files_pack_refs,
- .create_symref = files_create_symref,
.rename_ref = files_rename_ref,
.copy_ref = files_copy_ref,
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index 4e826c05ff..a937e7dbfc 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -1714,7 +1714,6 @@ struct ref_storage_be refs_be_packed = {
.initial_transaction_commit = packed_initial_transaction_commit,
.pack_refs = packed_pack_refs,
- .create_symref = NULL,
.rename_ref = NULL,
.copy_ref = NULL,
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index 56641aa57a..53a6c5d842 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -125,6 +125,19 @@ struct ref_update {
struct object_id old_oid;
/*
+ * If set, point the reference to this value. This can also be
+ * used to convert regular references to become symbolic refs.
+ * Cannot be set together with `new_oid`.
+ */
+ const char *new_target;
+
+ /*
+ * If set, check that the reference previously pointed to this
+ * value. Cannot be set together with `old_oid`.
+ */
+ const char *old_target;
+
+ /*
* One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,
* REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.
*/
@@ -173,6 +186,7 @@ struct ref_update *ref_transaction_add_update(
const char *refname, unsigned int flags,
const struct object_id *new_oid,
const struct object_id *old_oid,
+ const char *new_target, const char *old_target,
const char *msg);
/*
@@ -552,10 +566,6 @@ typedef int ref_transaction_commit_fn(struct ref_store *refs,
typedef int pack_refs_fn(struct ref_store *ref_store,
struct pack_refs_opts *opts);
-typedef int create_symref_fn(struct ref_store *ref_store,
- const char *ref_target,
- const char *refs_heads_master,
- const char *logmsg);
typedef int rename_ref_fn(struct ref_store *ref_store,
const char *oldref, const char *newref,
const char *logmsg);
@@ -676,7 +686,6 @@ struct ref_storage_be {
ref_transaction_commit_fn *initial_transaction_commit;
pack_refs_fn *pack_refs;
- create_symref_fn *create_symref;
rename_ref_fn *rename_ref;
copy_ref_fn *copy_ref;
@@ -735,4 +744,25 @@ void base_ref_store_init(struct ref_store *refs, struct repository *repo,
*/
struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store);
+/*
+ * Return the refname under which update was originally requested.
+ */
+const char *ref_update_original_update_refname(struct ref_update *update);
+
+/*
+ * Helper function to check if the new value is null, this
+ * takes into consideration that the update could be a regular
+ * ref or a symbolic ref.
+ */
+int ref_update_has_null_new_value(struct ref_update *update);
+
+/*
+ * Check whether the old_target values stored in update are consistent
+ * with the referent, which is the symbolic reference's current value.
+ * If everything is OK, return 0; otherwise, write an error message to
+ * err and return -1.
+ */
+int ref_update_check_old_target(const char *referent, struct ref_update *update,
+ struct strbuf *err);
+
#endif /* REFS_REFS_INTERNAL_H */
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index 010ef811b6..98cebbcf39 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -579,16 +579,6 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,
return ret;
}
-/*
- * Return the refname under which update was originally requested.
- */
-static const char *original_update_refname(struct ref_update *update)
-{
- while (update->parent_update)
- update = update->parent_update;
- return update->refname;
-}
-
struct reftable_transaction_update {
struct ref_update *update;
struct object_id current_oid;
@@ -827,7 +817,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
new_update = ref_transaction_add_update(
transaction, "HEAD",
u->flags | REF_LOG_ONLY | REF_NO_DEREF,
- &u->new_oid, &u->old_oid, u->msg);
+ &u->new_oid, &u->old_oid, NULL, NULL, u->msg);
string_list_insert(&affected_refnames, new_update->refname);
}
@@ -854,7 +844,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
* There is no need to write the reference deletion
* when the reference in question doesn't exist.
*/
- if (u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) {
+ if ((u->flags & REF_HAVE_NEW) && !ref_update_has_null_new_value(u)) {
ret = queue_transaction_update(refs, tx_data, u,
&current_oid, err);
if (ret)
@@ -867,7 +857,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
/* The reference does not exist, but we expected it to. */
strbuf_addf(err, _("cannot lock ref '%s': "
"unable to resolve reference '%s'"),
- original_update_refname(u), u->refname);
+ ref_update_original_update_refname(u), u->refname);
ret = -1;
goto done;
}
@@ -905,8 +895,10 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
* intertwined with the locking in files-backend.c.
*/
new_update = ref_transaction_add_update(
- transaction, referent.buf, new_flags,
- &u->new_oid, &u->old_oid, u->msg);
+ transaction, referent.buf, new_flags,
+ &u->new_oid, &u->old_oid, u->new_target,
+ u->old_target, u->msg);
+
new_update->parent_update = u;
/*
@@ -936,20 +928,25 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
* individual refs. But the error messages match what the files
* backend returns, which keeps our tests happy.
*/
- if (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {
+ if (u->old_target) {
+ if (ref_update_check_old_target(referent.buf, u, err)) {
+ ret = -1;
+ goto done;
+ }
+ } else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {
if (is_null_oid(&u->old_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
- "reference already exists"),
- original_update_refname(u));
+ "reference already exists"),
+ ref_update_original_update_refname(u));
else if (is_null_oid(&current_oid))
strbuf_addf(err, _("cannot lock ref '%s': "
- "reference is missing but expected %s"),
- original_update_refname(u),
+ "reference is missing but expected %s"),
+ ref_update_original_update_refname(u),
oid_to_hex(&u->old_oid));
else
strbuf_addf(err, _("cannot lock ref '%s': "
- "is at %s but expected %s"),
- original_update_refname(u),
+ "is at %s but expected %s"),
+ ref_update_original_update_refname(u),
oid_to_hex(&current_oid),
oid_to_hex(&u->old_oid));
ret = -1;
@@ -1047,7 +1044,9 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
* - `core.logAllRefUpdates` tells us to create the reflog for
* the given ref.
*/
- if (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {
+ if ((u->flags & REF_HAVE_NEW) &&
+ !(u->type & REF_ISSYMREF) &&
+ ref_update_has_null_new_value(u)) {
struct reftable_log_record log = {0};
struct reftable_iterator it = {0};
@@ -1088,24 +1087,52 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
(u->flags & REF_FORCE_CREATE_REFLOG ||
should_write_log(&arg->refs->base, u->refname))) {
struct reftable_log_record *log;
+ int create_reflog = 1;
+
+ if (u->new_target) {
+ if (!refs_resolve_ref_unsafe(&arg->refs->base, u->new_target,
+ RESOLVE_REF_READING, &u->new_oid, NULL)) {
+ /*
+ * TODO: currently we skip creating reflogs for dangling
+ * symref updates. It would be nice to capture this as
+ * zero oid updates however.
+ */
+ create_reflog = 0;
+ }
+ }
- ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
- log = &logs[logs_nr++];
- memset(log, 0, sizeof(*log));
-
- fill_reftable_log_record(log, &committer_ident);
- log->update_index = ts;
- log->refname = xstrdup(u->refname);
- memcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);
- memcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);
- log->value.update.message =
- xstrndup(u->msg, arg->refs->write_options.block_size / 2);
+ if (create_reflog) {
+ ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
+ log = &logs[logs_nr++];
+ memset(log, 0, sizeof(*log));
+
+ fill_reftable_log_record(log, &committer_ident);
+ log->update_index = ts;
+ log->refname = xstrdup(u->refname);
+ memcpy(log->value.update.new_hash,
+ u->new_oid.hash, GIT_MAX_RAWSZ);
+ memcpy(log->value.update.old_hash,
+ tx_update->current_oid.hash, GIT_MAX_RAWSZ);
+ log->value.update.message =
+ xstrndup(u->msg, arg->refs->write_options.block_size / 2);
+ }
}
if (u->flags & REF_LOG_ONLY)
continue;
- if (u->flags & REF_HAVE_NEW && is_null_oid(&u->new_oid)) {
+ if (u->new_target) {
+ struct reftable_ref_record ref = {
+ .refname = (char *)u->refname,
+ .value_type = REFTABLE_REF_SYMREF,
+ .value.symref = (char *)u->new_target,
+ .update_index = ts,
+ };
+
+ ret = reftable_writer_add_ref(writer, &ref);
+ if (ret < 0)
+ goto done;
+ } else if ((u->flags & REF_HAVE_NEW) && ref_update_has_null_new_value(u)) {
struct reftable_ref_record ref = {
.refname = (char *)u->refname,
.update_index = ts,
@@ -1237,110 +1264,6 @@ struct write_create_symref_arg {
const char *logmsg;
};
-static int write_create_symref_table(struct reftable_writer *writer, void *cb_data)
-{
- struct write_create_symref_arg *create = cb_data;
- uint64_t ts = reftable_stack_next_update_index(create->stack);
- struct reftable_ref_record ref = {
- .refname = (char *)create->refname,
- .value_type = REFTABLE_REF_SYMREF,
- .value.symref = (char *)create->target,
- .update_index = ts,
- };
- struct ident_split committer_ident = {0};
- struct reftable_log_record log = {0};
- struct object_id new_oid;
- struct object_id old_oid;
- const char *committer_info;
- int ret;
-
- reftable_writer_set_limits(writer, ts, ts);
-
- ret = refs_verify_refname_available(&create->refs->base, create->refname,
- NULL, NULL, create->err);
- if (ret < 0)
- return ret;
-
- ret = reftable_writer_add_ref(writer, &ref);
- if (ret)
- return ret;
-
- /*
- * Note that it is important to try and resolve the reference before we
- * write the log entry. This is because `should_write_log()` will munge
- * `core.logAllRefUpdates`, which is undesirable when we create a new
- * repository because it would be written into the config. As HEAD will
- * not resolve for new repositories this ordering will ensure that this
- * never happens.
- */
- if (!create->logmsg ||
- !refs_resolve_ref_unsafe(&create->refs->base, create->target,
- RESOLVE_REF_READING, &new_oid, NULL) ||
- !should_write_log(&create->refs->base, create->refname))
- return 0;
-
- committer_info = git_committer_info(0);
- if (split_ident_line(&committer_ident, committer_info, strlen(committer_info)))
- BUG("failed splitting committer info");
-
- fill_reftable_log_record(&log, &committer_ident);
- log.refname = xstrdup(create->refname);
- log.update_index = ts;
- log.value.update.message = xstrndup(create->logmsg,
- create->refs->write_options.block_size / 2);
- memcpy(log.value.update.new_hash, new_oid.hash, GIT_MAX_RAWSZ);
- if (refs_resolve_ref_unsafe(&create->refs->base, create->refname,
- RESOLVE_REF_READING, &old_oid, NULL))
- memcpy(log.value.update.old_hash, old_oid.hash, GIT_MAX_RAWSZ);
-
- ret = reftable_writer_add_log(writer, &log);
- reftable_log_record_release(&log);
- return ret;
-}
-
-static int reftable_be_create_symref(struct ref_store *ref_store,
- const char *refname,
- const char *target,
- const char *logmsg)
-{
- struct reftable_ref_store *refs =
- reftable_be_downcast(ref_store, REF_STORE_WRITE, "create_symref");
- struct reftable_stack *stack = stack_for(refs, refname, &refname);
- struct strbuf err = STRBUF_INIT;
- struct write_create_symref_arg arg = {
- .refs = refs,
- .stack = stack,
- .refname = refname,
- .target = target,
- .logmsg = logmsg,
- .err = &err,
- };
- int ret;
-
- ret = refs->err;
- if (ret < 0)
- goto done;
-
- ret = reftable_stack_reload(stack);
- if (ret)
- goto done;
-
- ret = reftable_stack_add(stack, &write_create_symref_table, &arg);
-
-done:
- assert(ret != REFTABLE_API_ERROR);
- if (ret) {
- if (err.len)
- error("%s", err.buf);
- else
- error("unable to write symref for %s: %s", refname,
- reftable_error_str(ret));
- }
-
- strbuf_release(&err);
- return ret;
-}
-
struct write_copy_arg {
struct reftable_ref_store *refs;
struct reftable_stack *stack;
@@ -2255,7 +2178,6 @@ struct ref_storage_be refs_be_reftable = {
.initial_transaction_commit = reftable_be_initial_transaction_commit,
.pack_refs = reftable_be_pack_refs,
- .create_symref = reftable_be_create_symref,
.rename_ref = reftable_be_rename_ref,
.copy_ref = reftable_be_copy_ref,
diff --git a/remote-curl.c b/remote-curl.c
index cae98384da..6008d7e87c 100644
--- a/remote-curl.c
+++ b/remote-curl.c
@@ -266,12 +266,23 @@ static struct ref *parse_git_refs(struct discovery *heads, int for_push)
return list;
}
+/*
+ * Try to detect the hash algorithm used by the remote repository when using
+ * the dumb HTTP transport. As dumb transports cannot tell us the object hash
+ * directly have to derive it from the advertised ref lengths.
+ */
static const struct git_hash_algo *detect_hash_algo(struct discovery *heads)
{
const char *p = memchr(heads->buf, '\t', heads->len);
int algo;
+
+ /*
+ * In case the remote has no refs we have no way to reliably determine
+ * the object hash used by that repository. In that case we simply fall
+ * back to SHA1, which may or may not be correct.
+ */
if (!p)
- return the_hash_algo;
+ return &hash_algos[GIT_HASH_SHA1];
algo = hash_algo_by_length((p - heads->buf) / 2);
if (algo == GIT_HASH_UNKNOWN)
@@ -295,6 +306,12 @@ static struct ref *parse_info_refs(struct discovery *heads)
"is this a git repository?",
transport_anonymize_url(url.buf));
+ /*
+ * Set the repository's hash algo to whatever we have just detected.
+ * This ensures that we can correctly parse the remote references.
+ */
+ repo_set_hash_algo(the_repository, hash_algo_by_ptr(options.hash_algo));
+
data = heads->buf;
start = NULL;
mid = data;
diff --git a/remote.c b/remote.c
index 2b650b813b..ec8c158e60 100644
--- a/remote.c
+++ b/remote.c
@@ -1198,8 +1198,10 @@ static char *guess_ref(const char *name, struct ref *peer)
{
struct strbuf buf = STRBUF_INIT;
- const char *r = resolve_ref_unsafe(peer->name, RESOLVE_REF_READING,
- NULL, NULL);
+ const char *r = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ peer->name,
+ RESOLVE_REF_READING,
+ NULL, NULL);
if (!r)
return NULL;
@@ -1316,9 +1318,10 @@ static int match_explicit(struct ref *src, struct ref *dst,
if (!dst_value) {
int flag;
- dst_value = resolve_ref_unsafe(matched_src->name,
- RESOLVE_REF_READING,
- NULL, &flag);
+ dst_value = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ matched_src->name,
+ RESOLVE_REF_READING,
+ NULL, &flag);
if (!dst_value ||
((flag & REF_ISSYMREF) &&
!starts_with(dst_value, "refs/heads/")))
@@ -1882,7 +1885,7 @@ const char *branch_get_upstream(struct branch *branch, struct strbuf *err)
* or because it is not a real branch, and get_branch
* auto-vivified it?
*/
- if (!ref_exists(branch->refname))
+ if (!refs_ref_exists(get_main_ref_store(the_repository), branch->refname))
return error_buf(err, _("no such branch: '%s'"),
branch->name);
return error_buf(err,
@@ -2168,13 +2171,13 @@ static int stat_branch_pair(const char *branch_name, const char *base,
struct strvec argv = STRVEC_INIT;
/* Cannot stat if what we used to build on no longer exists */
- if (read_ref(base, &oid))
+ if (refs_read_ref(get_main_ref_store(the_repository), base, &oid))
return -1;
theirs = lookup_commit_reference(the_repository, &oid);
if (!theirs)
return -1;
- if (read_ref(branch_name, &oid))
+ if (refs_read_ref(get_main_ref_store(the_repository), branch_name, &oid))
return -1;
ours = lookup_commit_reference(the_repository, &oid);
if (!ours)
@@ -2278,7 +2281,8 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb,
upstream_is_gone = 1;
}
- base = shorten_unambiguous_ref(full_base, 0);
+ base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+ full_base, 0);
if (upstream_is_gone) {
strbuf_addf(sb,
_("Your branch is based on '%s', but the upstream is gone.\n"),
@@ -2358,7 +2362,8 @@ struct ref *get_local_heads(void)
{
struct ref *local_refs = NULL, **local_tail = &local_refs;
- for_each_ref(one_local_ref, &local_tail);
+ refs_for_each_ref(get_main_ref_store(the_repository), one_local_ref,
+ &local_tail);
return local_refs;
}
@@ -2468,7 +2473,8 @@ struct ref *get_stale_heads(struct refspec *rs, struct ref *fetch_map)
for (ref = fetch_map; ref; ref = ref->next)
string_list_append(&ref_names, ref->name);
string_list_sort(&ref_names);
- for_each_ref(get_stale_heads_cb, &info);
+ refs_for_each_ref(get_main_ref_store(the_repository),
+ get_stale_heads_cb, &info);
string_list_clear(&ref_names, 0);
return stale_refs;
}
@@ -2553,7 +2559,7 @@ static int remote_tracking(struct remote *remote, const char *refname,
dst = apply_refspecs(&remote->fetch, refname);
if (!dst)
return -1; /* no tracking ref for refname at remote */
- if (read_ref(dst, oid))
+ if (refs_read_ref(get_main_ref_store(the_repository), dst, oid))
return -1; /* we know what the tracking ref is but we cannot read it */
*dst_refname = dst;
@@ -2659,12 +2665,16 @@ static int is_reachable_in_reflog(const char *local, const struct ref *remote)
* Get the timestamp from the latest entry
* of the remote-tracking ref's reflog.
*/
- for_each_reflog_ent_reverse(remote->tracking_ref, peek_reflog, &date);
+ refs_for_each_reflog_ent_reverse(get_main_ref_store(the_repository),
+ remote->tracking_ref, peek_reflog,
+ &date);
cb.remote_commit = commit;
cb.local_commits = &arr;
cb.remote_reflog_timestamp = date;
- ret = for_each_reflog_ent_reverse(local, check_and_collect_until, &cb);
+ ret = refs_for_each_reflog_ent_reverse(get_main_ref_store(the_repository),
+ local, check_and_collect_until,
+ &cb);
/* We found an entry in the reflog. */
if (ret > 0)
diff --git a/repository.c b/repository.c
index 2118f563e3..e4192af986 100644
--- a/repository.c
+++ b/repository.c
@@ -26,26 +26,6 @@ void initialize_repository(struct repository *repo)
repo->parsed_objects = parsed_object_pool_new();
ALLOC_ARRAY(repo->index, 1);
index_state_init(repo->index, repo);
-
- /*
- * Unfortunately, we need to keep this hack around for the time being:
- *
- * - Not setting up the hash algorithm for `the_repository` leads to
- * crashes because `the_hash_algo` is a macro that expands to
- * `the_repository->hash_algo`. So if Git commands try to access
- * `the_hash_algo` without a Git directory we crash.
- *
- * - Setting up the hash algorithm to be SHA1 by default breaks other
- * commands when running with SHA256.
- *
- * This is another point in case why having global state is a bad idea.
- * Eventually, we should remove this hack and stop setting the hash
- * algorithm in this function altogether. Instead, it should only ever
- * be set via our repository setup procedures. But that requires more
- * work.
- */
- if (repo == the_repository)
- repo_set_hash_algo(repo, GIT_HASH_SHA1);
}
static void expand_base_dir(char **out, const char *in,
@@ -302,6 +282,8 @@ void repo_clear(struct repository *repo)
parsed_object_pool_clear(repo->parsed_objects);
FREE_AND_NULL(repo->parsed_objects);
+ FREE_AND_NULL(repo->settings.fsmonitor);
+
if (repo->config) {
git_configset_clear(repo->config);
FREE_AND_NULL(repo->config);
diff --git a/reset.c b/reset.c
index d619cb7115..937f11c0f4 100644
--- a/reset.c
+++ b/reset.c
@@ -47,11 +47,13 @@ static int update_refs(const struct reset_head_opts *opts,
strbuf_addstr(&msg, "updating ORIG_HEAD");
reflog_orig_head = msg.buf;
}
- update_ref(reflog_orig_head, "ORIG_HEAD",
- orig_head ? orig_head : head,
- old_orig, 0, UPDATE_REFS_MSG_ON_ERR);
+ refs_update_ref(get_main_ref_store(the_repository),
+ reflog_orig_head, "ORIG_HEAD",
+ orig_head ? orig_head : head,
+ old_orig, 0, UPDATE_REFS_MSG_ON_ERR);
} else if (old_orig)
- delete_ref(NULL, "ORIG_HEAD", old_orig, 0);
+ refs_delete_ref(get_main_ref_store(the_repository),
+ NULL, "ORIG_HEAD", old_orig, 0);
}
if (!reflog_head) {
@@ -60,16 +62,19 @@ static int update_refs(const struct reset_head_opts *opts,
reflog_head = msg.buf;
}
if (!switch_to_branch)
- ret = update_ref(reflog_head, "HEAD", oid, head,
- detach_head ? REF_NO_DEREF : 0,
- UPDATE_REFS_MSG_ON_ERR);
+ ret = refs_update_ref(get_main_ref_store(the_repository),
+ reflog_head, "HEAD", oid, head,
+ detach_head ? REF_NO_DEREF : 0,
+ UPDATE_REFS_MSG_ON_ERR);
else {
- ret = update_ref(reflog_branch ? reflog_branch : reflog_head,
- switch_to_branch, oid, NULL, 0,
- UPDATE_REFS_MSG_ON_ERR);
+ ret = refs_update_ref(get_main_ref_store(the_repository),
+ reflog_branch ? reflog_branch : reflog_head,
+ switch_to_branch, oid, NULL, 0,
+ UPDATE_REFS_MSG_ON_ERR);
if (!ret)
- ret = create_symref("HEAD", switch_to_branch,
- reflog_head);
+ ret = refs_update_symref(get_main_ref_store(the_repository),
+ "HEAD", switch_to_branch,
+ reflog_head);
}
if (!ret && run_hook)
run_hooks_l("post-checkout",
diff --git a/revision.c b/revision.c
index 7e45f765d9..7ddf0f151a 100644
--- a/revision.c
+++ b/revision.c
@@ -1738,7 +1738,8 @@ void add_reflogs_to_pending(struct rev_info *revs, unsigned flags)
cb.all_revs = revs;
cb.all_flags = flags;
cb.wt = NULL;
- for_each_reflog(handle_one_reflog, &cb);
+ refs_for_each_reflog(get_main_ref_store(the_repository),
+ handle_one_reflog, &cb);
if (!revs->single_worktree)
add_other_reflogs_to_pending(&cb);
@@ -1979,9 +1980,9 @@ static const char *lookup_other_head(struct object_id *oid)
};
for (i = 0; i < ARRAY_SIZE(other_head); i++)
- if (!read_ref_full(other_head[i],
- RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
- oid, NULL)) {
+ if (!refs_read_ref_full(get_main_ref_store(the_repository), other_head[i],
+ RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
+ oid, NULL)) {
if (is_null_oid(oid))
die(_("%s exists but is a symbolic ref"), other_head[i]);
return other_head[i];
@@ -2789,7 +2790,8 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
} else if ((argcount = parse_long_opt("glob", argv, &optarg))) {
struct all_refs_cb cb;
init_all_refs_cb(&cb, revs, *flags);
- for_each_glob_ref(handle_one_ref, optarg, &cb);
+ refs_for_each_glob_ref(get_main_ref_store(the_repository),
+ handle_one_ref, optarg, &cb);
clear_ref_exclusions(&revs->ref_excludes);
return argcount;
} else if ((argcount = parse_long_opt("exclude", argv, &optarg))) {
@@ -2804,7 +2806,9 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
return error(_("options '%s' and '%s' cannot be used together"),
"--exclude-hidden", "--branches");
init_all_refs_cb(&cb, revs, *flags);
- for_each_glob_ref_in(handle_one_ref, optarg, "refs/heads/", &cb);
+ refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
+ handle_one_ref, optarg,
+ "refs/heads/", &cb);
clear_ref_exclusions(&revs->ref_excludes);
} else if (skip_prefix(arg, "--tags=", &optarg)) {
struct all_refs_cb cb;
@@ -2812,7 +2816,9 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
return error(_("options '%s' and '%s' cannot be used together"),
"--exclude-hidden", "--tags");
init_all_refs_cb(&cb, revs, *flags);
- for_each_glob_ref_in(handle_one_ref, optarg, "refs/tags/", &cb);
+ refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
+ handle_one_ref, optarg,
+ "refs/tags/", &cb);
clear_ref_exclusions(&revs->ref_excludes);
} else if (skip_prefix(arg, "--remotes=", &optarg)) {
struct all_refs_cb cb;
@@ -2820,7 +2826,9 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
return error(_("options '%s' and '%s' cannot be used together"),
"--exclude-hidden", "--remotes");
init_all_refs_cb(&cb, revs, *flags);
- for_each_glob_ref_in(handle_one_ref, optarg, "refs/remotes/", &cb);
+ refs_for_each_glob_ref_in(get_main_ref_store(the_repository),
+ handle_one_ref, optarg,
+ "refs/remotes/", &cb);
clear_ref_exclusions(&revs->ref_excludes);
} else if (!strcmp(arg, "--reflog")) {
add_reflogs_to_pending(revs, *flags);
@@ -2911,7 +2919,8 @@ static void NORETURN diagnose_missing_default(const char *def)
int flags;
const char *refname;
- refname = resolve_ref_unsafe(def, 0, NULL, &flags);
+ refname = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ def, 0, NULL, &flags);
if (!refname || !(flags & REF_ISSYMREF) || (flags & REF_ISBROKEN))
die(_("your current branch appears to be broken"));
diff --git a/scalar.c b/scalar.c
index fb2940c2a0..7234049a1b 100644
--- a/scalar.c
+++ b/scalar.c
@@ -645,7 +645,6 @@ static int cmd_reconfigure(int argc, const char **argv)
};
struct string_list scalar_repos = STRING_LIST_INIT_DUP;
int i, res = 0;
- struct repository r = { NULL };
struct strbuf commondir = STRBUF_INIT, gitdir = STRBUF_INIT;
argc = parse_options(argc, argv, NULL, options,
@@ -665,6 +664,7 @@ static int cmd_reconfigure(int argc, const char **argv)
for (i = 0; i < scalar_repos.nr; i++) {
int succeeded = 0;
+ struct repository *old_repo, r = { NULL };
const char *dir = scalar_repos.items[i].string;
strbuf_reset(&commondir);
@@ -712,13 +712,17 @@ static int cmd_reconfigure(int argc, const char **argv)
git_config_clear();
+ if (repo_init(&r, gitdir.buf, commondir.buf))
+ goto loop_end;
+
+ old_repo = the_repository;
the_repository = &r;
- r.commondir = commondir.buf;
- r.gitdir = gitdir.buf;
if (set_recommended_config(1) >= 0)
succeeded = 1;
+ the_repository = old_repo;
+
loop_end:
if (!succeeded) {
res = -1;
diff --git a/sequencer.c b/sequencer.c
index 88de4dc20f..aa2a239835 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -266,7 +266,7 @@ static struct update_ref_record *init_update_ref_record(const char *ref)
oidcpy(&rec->after, null_oid());
/* This may fail, but that's fine, we will keep the null OID. */
- read_ref(ref, &rec->before);
+ refs_read_ref(get_main_ref_store(the_repository), ref, &rec->before);
return rec;
}
@@ -359,35 +359,32 @@ static const char *get_todo_path(const struct replay_opts *opts)
static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,
size_t ignore_footer)
{
- struct process_trailer_options opts = PROCESS_TRAILER_OPTIONS_INIT;
- struct trailer_info info;
- size_t i;
+ struct trailer_iterator iter;
+ size_t i = 0;
int found_sob = 0, found_sob_last = 0;
char saved_char;
- opts.no_divider = 1;
-
if (ignore_footer) {
saved_char = sb->buf[sb->len - ignore_footer];
sb->buf[sb->len - ignore_footer] = '\0';
}
- trailer_info_get(&opts, sb->buf, &info);
+ trailer_iterator_init(&iter, sb->buf);
if (ignore_footer)
sb->buf[sb->len - ignore_footer] = saved_char;
- if (info.trailer_block_start == info.trailer_block_end)
- return 0;
+ while (trailer_iterator_advance(&iter)) {
+ i++;
+ if (sob && !strncmp(iter.raw, sob->buf, sob->len))
+ found_sob = i;
+ }
+ trailer_iterator_release(&iter);
- for (i = 0; i < info.trailer_nr; i++)
- if (sob && !strncmp(info.trailers[i], sob->buf, sob->len)) {
- found_sob = 1;
- if (i == info.trailer_nr - 1)
- found_sob_last = 1;
- }
+ if (!i)
+ return 0;
- trailer_info_release(&info);
+ found_sob_last = (int)i == found_sob;
if (found_sob_last)
return 3;
@@ -440,7 +437,7 @@ int sequencer_remove_state(struct replay_opts *opts)
char *eol = strchr(p, '\n');
if (eol)
*eol = '\0';
- if (delete_ref("(rebase) cleanup", p, NULL, 0) < 0) {
+ if (refs_delete_ref(get_main_ref_store(the_repository), "(rebase) cleanup", p, NULL, 0) < 0) {
warning(_("could not delete '%s'"), p);
ret = -1;
}
@@ -661,11 +658,12 @@ static int fast_forward_to(struct repository *r,
strbuf_addf(&sb, "%s: fast-forward", action_name(opts));
- transaction = ref_transaction_begin(&err);
+ transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
+ &err);
if (!transaction ||
ref_transaction_update(transaction, "HEAD",
to, unborn && !is_rebase_i(opts) ?
- null_oid() : from,
+ null_oid() : from, NULL, NULL,
0, sb.buf, &err) ||
ref_transaction_commit(transaction, &err)) {
ref_transaction_free(transaction);
@@ -841,11 +839,12 @@ static int is_index_unchanged(struct repository *r)
struct index_state *istate = r->index;
const char *head_name;
- if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL)) {
+ if (!refs_resolve_ref_unsafe(get_main_ref_store(the_repository), "HEAD", RESOLVE_REF_READING, &head_oid, NULL)) {
/* Check to see if this is an unborn branch */
- head_name = resolve_ref_unsafe("HEAD",
- RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
- &head_oid, NULL);
+ head_name = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ "HEAD",
+ RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
+ &head_oid, NULL);
if (!head_name ||
!starts_with(head_name, "refs/heads/") ||
!is_null_oid(&head_oid))
@@ -1294,11 +1293,12 @@ int update_head_with_reflog(const struct commit *old_head,
strbuf_addch(&sb, '\n');
}
- transaction = ref_transaction_begin(err);
+ transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
+ err);
if (!transaction ||
ref_transaction_update(transaction, "HEAD", new_head,
old_head ? &old_head->object.oid : null_oid(),
- 0, sb.buf, err) ||
+ NULL, NULL, 0, sb.buf, err) ||
ref_transaction_commit(transaction, err)) {
ret = -1;
}
@@ -1720,8 +1720,8 @@ out:
static int write_rebase_head(struct object_id *oid)
{
- if (update_ref("rebase", "REBASE_HEAD", oid,
- NULL, REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
+ if (refs_update_ref(get_main_ref_store(the_repository), "rebase", "REBASE_HEAD", oid,
+ NULL, REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
return error(_("could not update %s"), "REBASE_HEAD");
return 0;
@@ -2455,12 +2455,12 @@ static int do_pick_commit(struct repository *r,
if ((command == TODO_PICK || command == TODO_REWORD ||
command == TODO_EDIT) && !opts->no_commit &&
(res == 0 || res == 1) &&
- update_ref(NULL, "CHERRY_PICK_HEAD", &commit->object.oid, NULL,
- REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
+ refs_update_ref(get_main_ref_store(the_repository), NULL, "CHERRY_PICK_HEAD", &commit->object.oid, NULL,
+ REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
res = -1;
if (command == TODO_REVERT && ((opts->no_commit && res == 0) || res == 1) &&
- update_ref(NULL, "REVERT_HEAD", &commit->object.oid, NULL,
- REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
+ refs_update_ref(get_main_ref_store(the_repository), NULL, "REVERT_HEAD", &commit->object.oid, NULL,
+ REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR))
res = -1;
if (res) {
@@ -3364,7 +3364,7 @@ static int rollback_single_pick(struct repository *r)
if (!refs_ref_exists(get_main_ref_store(r), "CHERRY_PICK_HEAD") &&
!refs_ref_exists(get_main_ref_store(r), "REVERT_HEAD"))
return error(_("no cherry-pick or revert in progress"));
- if (read_ref_full("HEAD", 0, &head_oid, NULL))
+ if (refs_read_ref_full(get_main_ref_store(the_repository), "HEAD", 0, &head_oid, NULL))
return error(_("cannot resolve HEAD"));
if (is_null_oid(&head_oid))
return error(_("cannot abort from a branch yet to be born"));
@@ -3375,7 +3375,7 @@ static int skip_single_pick(void)
{
struct object_id head;
- if (read_ref_full("HEAD", 0, &head, NULL))
+ if (refs_read_ref_full(get_main_ref_store(the_repository), "HEAD", 0, &head, NULL))
return error(_("cannot resolve HEAD"));
return reset_merge(&head);
}
@@ -3832,8 +3832,9 @@ static int do_label(struct repository *r, const char *name, int len)
} else if (repo_get_oid(r, "HEAD", &head_oid)) {
error(_("could not read HEAD"));
ret = -1;
- } else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,
- NULL, 0, msg.buf, &err) < 0 ||
+ } else if (ref_transaction_update(transaction, ref_name.buf,
+ &head_oid, NULL, NULL, NULL,
+ 0, msg.buf, &err) < 0 ||
ref_transaction_commit(transaction, &err)) {
error("%s", err.buf);
ret = -1;
@@ -3891,7 +3892,7 @@ static struct commit *lookup_label(struct repository *r, const char *label,
strbuf_reset(buf);
strbuf_addf(buf, "refs/rewritten/%.*s", len, label);
- if (!read_ref(buf->buf, &oid)) {
+ if (!refs_read_ref(get_main_ref_store(the_repository), buf->buf, &oid)) {
commit = lookup_commit_object(r, &oid);
} else {
/* fall back to non-rewritten ref or commit */
@@ -3987,9 +3988,10 @@ static int do_reset(struct repository *r,
ret = error(_("could not write index"));
if (!ret)
- ret = update_ref(reflog_message(opts, "reset", "'%.*s'",
- len, name), "HEAD", &oid,
- NULL, 0, UPDATE_REFS_MSG_ON_ERR);
+ ret = refs_update_ref(get_main_ref_store(the_repository), reflog_message(opts, "reset", "'%.*s'",
+ len, name),
+ "HEAD", &oid,
+ NULL, 0, UPDATE_REFS_MSG_ON_ERR);
cleanup:
free((void *)desc.buffer);
if (ret < 0)
@@ -4471,7 +4473,7 @@ static int do_update_ref(struct repository *r, const char *refname)
for_each_string_list_item(item, &list) {
if (!strcmp(item->string, refname)) {
struct update_ref_record *rec = item->util;
- if (read_ref("HEAD", &rec->after))
+ if (refs_read_ref(get_main_ref_store(the_repository), "HEAD", &rec->after))
return -1;
break;
}
@@ -5031,15 +5033,15 @@ cleanup_head_ref:
}
msg = reflog_message(opts, "finish", "%s onto %s",
head_ref.buf, buf.buf);
- if (update_ref(msg, head_ref.buf, &head, &orig,
- REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {
+ if (refs_update_ref(get_main_ref_store(the_repository), msg, head_ref.buf, &head, &orig,
+ REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) {
res = error(_("could not update %s"),
head_ref.buf);
goto cleanup_head_ref;
}
msg = reflog_message(opts, "finish", "returning to %s",
head_ref.buf);
- if (create_symref("HEAD", head_ref.buf, msg)) {
+ if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", head_ref.buf, msg)) {
res = error(_("could not update HEAD to %s"),
head_ref.buf);
goto cleanup_head_ref;
@@ -6209,10 +6211,11 @@ static int add_decorations_to_list(const struct commit *commit,
struct todo_add_branch_context *ctx)
{
const struct name_decoration *decoration = get_name_decoration(&commit->object);
- const char *head_ref = resolve_ref_unsafe("HEAD",
- RESOLVE_REF_READING,
- NULL,
- NULL);
+ const char *head_ref = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ "HEAD",
+ RESOLVE_REF_READING,
+ NULL,
+ NULL);
while (decoration) {
struct todo_item *item;
diff --git a/server-info.c b/server-info.c
index e2fe0f9143..6feaa457c5 100644
--- a/server-info.c
+++ b/server-info.c
@@ -175,7 +175,8 @@ static int add_info_ref(const char *path, const struct object_id *oid,
static int generate_info_refs(struct update_info_ctx *uic)
{
- return for_each_ref(add_info_ref, uic);
+ return refs_for_each_ref(get_main_ref_store(the_repository),
+ add_info_ref, uic);
}
static int update_info_refs(int force)
diff --git a/setup.c b/setup.c
index f4b32f76e3..9b721ea2a9 100644
--- a/setup.c
+++ b/setup.c
@@ -4,6 +4,7 @@
#include "environment.h"
#include "exec-cmd.h"
#include "gettext.h"
+#include "hex.h"
#include "object-name.h"
#include "refs.h"
#include "repository.h"
@@ -16,6 +17,7 @@
#include "quote.h"
#include "trace2.h"
#include "worktree.h"
+#include "exec-cmd.h"
static int inside_git_dir = -1;
static int inside_work_tree = -1;
@@ -341,6 +343,58 @@ int get_common_dir_noenv(struct strbuf *sb, const char *gitdir)
return ret;
}
+static int validate_headref(const char *path)
+{
+ struct stat st;
+ char buffer[256];
+ const char *refname;
+ struct object_id oid;
+ int fd;
+ ssize_t len;
+
+ if (lstat(path, &st) < 0)
+ return -1;
+
+ /* Make sure it is a "refs/.." symlink */
+ if (S_ISLNK(st.st_mode)) {
+ len = readlink(path, buffer, sizeof(buffer)-1);
+ if (len >= 5 && !memcmp("refs/", buffer, 5))
+ return 0;
+ return -1;
+ }
+
+ /*
+ * Anything else, just open it and try to see if it is a symbolic ref.
+ */
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return -1;
+ len = read_in_full(fd, buffer, sizeof(buffer)-1);
+ close(fd);
+
+ if (len < 0)
+ return -1;
+ buffer[len] = '\0';
+
+ /*
+ * Is it a symbolic ref?
+ */
+ if (skip_prefix(buffer, "ref:", &refname)) {
+ while (isspace(*refname))
+ refname++;
+ if (starts_with(refname, "refs/"))
+ return 0;
+ }
+
+ /*
+ * Is this a detached HEAD?
+ */
+ if (get_oid_hex_any(buffer, &oid) != GIT_HASH_UNKNOWN)
+ return 0;
+
+ return -1;
+}
+
/*
* Test if it looks like we're at a git directory.
* We want to see:
@@ -1220,6 +1274,27 @@ static int ensure_valid_ownership(const char *gitfile,
return data.is_safe;
}
+void die_upon_dubious_ownership(const char *gitfile, const char *worktree,
+ const char *gitdir)
+{
+ struct strbuf report = STRBUF_INIT, quoted = STRBUF_INIT;
+ const char *path;
+
+ if (ensure_valid_ownership(gitfile, worktree, gitdir, &report))
+ return;
+
+ strbuf_complete(&report, '\n');
+ path = gitfile ? gitfile : gitdir;
+ sq_quote_buf_pretty(&quoted, path);
+
+ die(_("detected dubious ownership in repository at '%s'\n"
+ "%s"
+ "To add an exception for this directory, call:\n"
+ "\n"
+ "\tgit config --global --add safe.directory %s"),
+ path, report.buf, quoted.buf);
+}
+
static int allowed_bare_repo_cb(const char *key, const char *value,
const struct config_context *ctx UNUSED,
void *d)
@@ -1781,6 +1856,57 @@ int daemonize(void)
#endif
}
+struct template_dir_cb_data {
+ char *path;
+ int initialized;
+};
+
+static int template_dir_cb(const char *key, const char *value,
+ const struct config_context *ctx, void *d)
+{
+ struct template_dir_cb_data *data = d;
+
+ if (strcmp(key, "init.templatedir"))
+ return 0;
+
+ if (!value) {
+ data->path = NULL;
+ } else {
+ char *path = NULL;
+
+ FREE_AND_NULL(data->path);
+ if (!git_config_pathname((const char **)&path, key, value))
+ data->path = path ? path : xstrdup(value);
+ }
+
+ return 0;
+}
+
+const char *get_template_dir(const char *option_template)
+{
+ const char *template_dir = option_template;
+
+ if (!template_dir)
+ template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
+ if (!template_dir) {
+ static struct template_dir_cb_data data;
+
+ if (!data.initialized) {
+ git_protected_config(template_dir_cb, &data);
+ data.initialized = 1;
+ }
+ template_dir = data.path;
+ }
+ if (!template_dir) {
+ static char *dir;
+
+ if (!dir)
+ dir = system_path(DEFAULT_GIT_TEMPLATE_DIR);
+ template_dir = dir;
+ }
+ return template_dir;
+}
+
#ifdef NO_TRUSTABLE_FILEMODE
#define TEST_FILEMODE 0
#else
@@ -1856,8 +1982,9 @@ static void copy_templates_1(struct strbuf *path, struct strbuf *template_path,
}
}
-static void copy_templates(const char *template_dir, const char *init_template_dir)
+static void copy_templates(const char *option_template)
{
+ const char *template_dir = get_template_dir(option_template);
struct strbuf path = STRBUF_INIT;
struct strbuf template_path = STRBUF_INIT;
size_t template_len;
@@ -1866,16 +1993,8 @@ static void copy_templates(const char *template_dir, const char *init_template_d
DIR *dir;
char *to_free = NULL;
- if (!template_dir)
- template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
- if (!template_dir)
- template_dir = init_template_dir;
- if (!template_dir)
- template_dir = to_free = system_path(DEFAULT_GIT_TEMPLATE_DIR);
- if (!template_dir[0]) {
- free(to_free);
+ if (!template_dir || !*template_dir)
return;
- }
strbuf_addstr(&template_path, template_dir);
strbuf_complete(&template_path, '/');
@@ -2001,7 +2120,7 @@ void create_reference_database(unsigned int ref_storage_format,
die(_("invalid initial branch name: '%s'"),
initial_branch);
- if (create_symref("HEAD", ref, NULL) < 0)
+ if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", ref, NULL) < 0)
exit(1);
free(ref);
}
@@ -2023,7 +2142,6 @@ static int create_default_files(const char *template_path,
char *path;
int reinit;
int filemode;
- const char *init_template_dir = NULL;
const char *work_tree = get_git_work_tree();
/*
@@ -2035,9 +2153,7 @@ static int create_default_files(const char *template_path,
* values (since we've just potentially changed what's available on
* disk).
*/
- git_config_get_pathname("init.templatedir", &init_template_dir);
- copy_templates(template_path, init_template_dir);
- free((char *)init_template_dir);
+ copy_templates(template_path);
git_config_clear();
reset_shared_repository();
git_config(git_default_config, NULL);
diff --git a/setup.h b/setup.h
index d88bb37aaf..b3fd3bf45a 100644
--- a/setup.h
+++ b/setup.h
@@ -41,6 +41,18 @@ const char *read_gitfile_gently(const char *path, int *return_error_code);
const char *resolve_gitdir_gently(const char *suspect, int *return_error_code);
#define resolve_gitdir(path) resolve_gitdir_gently((path), NULL)
+/*
+ * Check if a repository is safe and die if it is not, by verifying the
+ * ownership of the worktree (if any), the git directory, and the gitfile (if
+ * any).
+ *
+ * Exemptions for known-safe repositories can be added via `safe.directory`
+ * config settings; for non-bare repositories, their worktree needs to be
+ * added, for bare ones their git directory.
+ */
+void die_upon_dubious_ownership(const char *gitfile, const char *worktree,
+ const char *gitdir);
+
void setup_work_tree(void);
/*
@@ -172,6 +184,8 @@ int verify_repository_format(const struct repository_format *format,
*/
void check_repository_format(struct repository_format *fmt);
+const char *get_template_dir(const char *option_template);
+
#define INIT_DB_QUIET (1 << 0)
#define INIT_DB_EXIST_OK (1 << 1)
#define INIT_DB_SKIP_REFDB (1 << 2)
diff --git a/shallow.c b/shallow.c
index 7ff50dd0da..a0b181ba8a 100644
--- a/shallow.c
+++ b/shallow.c
@@ -678,8 +678,10 @@ void assign_shallow_commits_to_refs(struct shallow_info *info,
* connect to old refs. If not (e.g. force ref updates) it'll
* have to go down to the current shallow commits.
*/
- head_ref(mark_uninteresting, NULL);
- for_each_ref(mark_uninteresting, NULL);
+ refs_head_ref(get_main_ref_store(the_repository), mark_uninteresting,
+ NULL);
+ refs_for_each_ref(get_main_ref_store(the_repository),
+ mark_uninteresting, NULL);
/* Mark potential bottoms so we won't go out of bound */
for (i = 0; i < nr_shallow; i++) {
@@ -782,8 +784,8 @@ static void post_assign_shallow(struct shallow_info *info,
info->nr_theirs = dst;
memset(&ca, 0, sizeof(ca));
- head_ref(add_ref, &ca);
- for_each_ref(add_ref, &ca);
+ refs_head_ref(get_main_ref_store(the_repository), add_ref, &ca);
+ refs_for_each_ref(get_main_ref_store(the_repository), add_ref, &ca);
/* Remove unreachable shallow commits from "ours" */
for (i = dst = 0; i < info->nr_ours; i++) {
@@ -822,8 +824,10 @@ int delayed_reachability_test(struct shallow_info *si, int c)
struct commit_array ca;
memset(&ca, 0, sizeof(ca));
- head_ref(add_ref, &ca);
- for_each_ref(add_ref, &ca);
+ refs_head_ref(get_main_ref_store(the_repository),
+ add_ref, &ca);
+ refs_for_each_ref(get_main_ref_store(the_repository),
+ add_ref, &ca);
si->commits = ca.commits;
si->nr_commits = ca.nr;
}
diff --git a/strbuf.c b/strbuf.c
index 0d929e4e19..d5b4b3903a 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -592,6 +592,7 @@ int strbuf_getcwd(struct strbuf *sb)
strbuf_grow(sb, guessed_len);
if (getcwd(sb->buf, sb->alloc)) {
strbuf_setlen(sb, strlen(sb->buf));
+ precompose_strbuf_if_needed(sb);
return 0;
}
diff --git a/submodule.c b/submodule.c
index ce2d032521..f6313cd99f 100644
--- a/submodule.c
+++ b/submodule.c
@@ -1015,6 +1015,9 @@ static int submodule_has_commits(struct repository *r,
.super_oid = super_oid
};
+ if (validate_submodule_path(path) < 0)
+ exit(128);
+
oid_array_for_each_unique(commits, check_has_commit, &has_commit);
if (has_commit.result) {
@@ -1137,6 +1140,9 @@ static int push_submodule(const char *path,
const struct string_list *push_options,
int dry_run)
{
+ if (validate_submodule_path(path) < 0)
+ exit(128);
+
if (for_each_remote_ref_submodule(path, has_remote, NULL) > 0) {
struct child_process cp = CHILD_PROCESS_INIT;
strvec_push(&cp.args, "push");
@@ -1186,6 +1192,9 @@ static void submodule_push_check(const char *path, const char *head,
struct child_process cp = CHILD_PROCESS_INIT;
int i;
+ if (validate_submodule_path(path) < 0)
+ exit(128);
+
strvec_push(&cp.args, "submodule--helper");
strvec_push(&cp.args, "push-check");
strvec_push(&cp.args, head);
@@ -1233,7 +1242,8 @@ int push_unpushed_submodules(struct repository *r,
char *head;
struct object_id head_oid;
- head = resolve_refdup("HEAD", 0, &head_oid, NULL);
+ head = refs_resolve_refdup(get_main_ref_store(the_repository),
+ "HEAD", 0, &head_oid, NULL);
if (!head)
die(_("Failed to resolve HEAD as a valid ref."));
@@ -1271,7 +1281,8 @@ static int append_oid_to_array(const char *ref UNUSED,
void check_for_new_submodule_commits(struct object_id *oid)
{
if (!initialized_fetch_ref_tips) {
- for_each_ref(append_oid_to_array, &ref_tips_before_fetch);
+ refs_for_each_ref(get_main_ref_store(the_repository),
+ append_oid_to_array, &ref_tips_before_fetch);
initialized_fetch_ref_tips = 1;
}
@@ -1517,6 +1528,9 @@ static struct fetch_task *fetch_task_create(struct submodule_parallel_fetch *spf
struct fetch_task *task = xmalloc(sizeof(*task));
memset(task, 0, sizeof(*task));
+ if (validate_submodule_path(path) < 0)
+ exit(128);
+
task->sub = submodule_from_path(spf->r, treeish_name, path);
if (!task->sub) {
@@ -1878,6 +1892,9 @@ unsigned is_submodule_modified(const char *path, int ignore_untracked)
const char *git_dir;
int ignore_cp_exit_code = 0;
+ if (validate_submodule_path(path) < 0)
+ exit(128);
+
strbuf_addf(&buf, "%s/.git", path);
git_dir = read_gitfile(buf.buf);
if (!git_dir)
@@ -1954,6 +1971,9 @@ int submodule_uses_gitfile(const char *path)
struct strbuf buf = STRBUF_INIT;
const char *git_dir;
+ if (validate_submodule_path(path) < 0)
+ exit(128);
+
strbuf_addf(&buf, "%s/.git", path);
git_dir = read_gitfile(buf.buf);
if (!git_dir) {
@@ -1993,6 +2013,9 @@ int bad_to_remove_submodule(const char *path, unsigned flags)
struct strbuf buf = STRBUF_INIT;
int ret = 0;
+ if (validate_submodule_path(path) < 0)
+ exit(128);
+
if (!file_exists(path) || is_empty_dir(path))
return 0;
@@ -2043,6 +2066,9 @@ void submodule_unset_core_worktree(const struct submodule *sub)
{
struct strbuf config_path = STRBUF_INIT;
+ if (validate_submodule_path(sub->path) < 0)
+ exit(128);
+
submodule_name_to_gitdir(&config_path, the_repository, sub->name);
strbuf_addstr(&config_path, "/config");
@@ -2057,6 +2083,9 @@ static int submodule_has_dirty_index(const struct submodule *sub)
{
struct child_process cp = CHILD_PROCESS_INIT;
+ if (validate_submodule_path(sub->path) < 0)
+ exit(128);
+
prepare_submodule_repo_env(&cp.env);
cp.git_cmd = 1;
@@ -2074,6 +2103,10 @@ static int submodule_has_dirty_index(const struct submodule *sub)
static void submodule_reset_index(const char *path, const char *super_prefix)
{
struct child_process cp = CHILD_PROCESS_INIT;
+
+ if (validate_submodule_path(path) < 0)
+ exit(128);
+
prepare_submodule_repo_env(&cp.env);
cp.git_cmd = 1;
@@ -2137,10 +2170,27 @@ int submodule_move_head(const char *path, const char *super_prefix,
if (!submodule_uses_gitfile(path))
absorb_git_dir_into_superproject(path,
super_prefix);
+ else {
+ char *dotgit = xstrfmt("%s/.git", path);
+ char *git_dir = xstrdup(read_gitfile(dotgit));
+
+ free(dotgit);
+ if (validate_submodule_git_dir(git_dir,
+ sub->name) < 0)
+ die(_("refusing to create/use '%s' in "
+ "another submodule's git dir"),
+ git_dir);
+ free(git_dir);
+ }
} else {
struct strbuf gitdir = STRBUF_INIT;
submodule_name_to_gitdir(&gitdir, the_repository,
sub->name);
+ if (validate_submodule_git_dir(gitdir.buf,
+ sub->name) < 0)
+ die(_("refusing to create/use '%s' in another "
+ "submodule's git dir"),
+ gitdir.buf);
connect_work_tree_and_git_dir(path, gitdir.buf, 0);
strbuf_release(&gitdir);
@@ -2261,6 +2311,34 @@ int validate_submodule_git_dir(char *git_dir, const char *submodule_name)
return 0;
}
+int validate_submodule_path(const char *path)
+{
+ char *p = xstrdup(path);
+ struct stat st;
+ int i, ret = 0;
+ char sep;
+
+ for (i = 0; !ret && p[i]; i++) {
+ if (!is_dir_sep(p[i]))
+ continue;
+
+ sep = p[i];
+ p[i] = '\0';
+ /* allow missing components, but no symlinks */
+ ret = lstat(p, &st) || !S_ISLNK(st.st_mode) ? 0 : -1;
+ p[i] = sep;
+ if (ret)
+ error(_("expected '%.*s' in submodule path '%s' not to "
+ "be a symbolic link"), i, p, p);
+ }
+ if (!lstat(p, &st) && S_ISLNK(st.st_mode))
+ ret = error(_("expected submodule path '%s' not to be a "
+ "symbolic link"), p);
+ free(p);
+ return ret;
+}
+
+
/*
* Embeds a single submodules git directory into the superprojects git dir,
* non recursively.
@@ -2272,6 +2350,9 @@ static void relocate_single_git_dir_into_superproject(const char *path,
struct strbuf new_gitdir = STRBUF_INIT;
const struct submodule *sub;
+ if (validate_submodule_path(path) < 0)
+ exit(128);
+
if (submodule_uses_worktrees(path))
die(_("relocate_gitdir for submodule '%s' with "
"more than one worktree not supported"), path);
@@ -2313,6 +2394,9 @@ static void absorb_git_dir_into_superproject_recurse(const char *path,
struct child_process cp = CHILD_PROCESS_INIT;
+ if (validate_submodule_path(path) < 0)
+ exit(128);
+
cp.dir = path;
cp.git_cmd = 1;
cp.no_stdin = 1;
@@ -2337,6 +2421,10 @@ void absorb_git_dir_into_superproject(const char *path,
int err_code;
const char *sub_git_dir;
struct strbuf gitdir = STRBUF_INIT;
+
+ if (validate_submodule_path(path) < 0)
+ exit(128);
+
strbuf_addf(&gitdir, "%s/.git", path);
sub_git_dir = resolve_gitdir_gently(gitdir.buf, &err_code);
@@ -2479,6 +2567,9 @@ int submodule_to_gitdir(struct strbuf *buf, const char *submodule)
const char *git_dir;
int ret = 0;
+ if (validate_submodule_path(submodule) < 0)
+ exit(128);
+
strbuf_reset(buf);
strbuf_addstr(buf, submodule);
strbuf_complete(buf, '/');
diff --git a/submodule.h b/submodule.h
index c55a25ca37..b50d29eba4 100644
--- a/submodule.h
+++ b/submodule.h
@@ -148,6 +148,11 @@ void submodule_name_to_gitdir(struct strbuf *buf, struct repository *r,
*/
int validate_submodule_git_dir(char *git_dir, const char *submodule_name);
+/*
+ * Make sure that the given submodule path does not follow symlinks.
+ */
+int validate_submodule_path(const char *path);
+
#define SUBMODULE_MOVE_HEAD_DRY_RUN (1<<0)
#define SUBMODULE_MOVE_HEAD_FORCE (1<<1)
int submodule_move_head(const char *path, const char *super_prefix,
diff --git a/t/Makefile b/t/Makefile
index 2d95046f26..b2eb9f770b 100644
--- a/t/Makefile
+++ b/t/Makefile
@@ -48,7 +48,8 @@ CHAINLINTTESTS = $(sort $(patsubst chainlint/%.test,%,$(wildcard chainlint/*.tes
CHAINLINT = '$(PERL_PATH_SQ)' chainlint.pl
UNIT_TEST_SOURCES = $(wildcard unit-tests/t-*.c)
UNIT_TEST_PROGRAMS = $(patsubst unit-tests/%.c,unit-tests/bin/%$(X),$(UNIT_TEST_SOURCES))
-UNIT_TESTS = $(sort $(filter-out unit-tests/bin/t-basic%,$(UNIT_TEST_PROGRAMS)))
+UNIT_TESTS = $(sort $(UNIT_TEST_PROGRAMS))
+UNIT_TESTS_NO_DIR = $(notdir $(UNIT_TESTS))
# `test-chainlint` (which is a dependency of `test-lint`, `test` and `prove`)
# checks all tests in all scripts via a single invocation, so tell individual
@@ -67,7 +68,7 @@ failed:
test -z "$$failed" || $(MAKE) $$failed
prove: pre-clean check-chainlint $(TEST_LINT)
- @echo "*** prove ***"; $(CHAINLINTSUPPRESS) $(PROVE) --exec '$(TEST_SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS)
+ @echo "*** prove (shell & unit tests) ***"; $(CHAINLINTSUPPRESS) TEST_SHELL_PATH='$(TEST_SHELL_PATH_SQ)' $(PROVE) --exec ./run-test.sh $(GIT_PROVE_OPTS) $(T) $(UNIT_TESTS) :: $(GIT_TEST_OPTS)
$(MAKE) clean-except-prove-cache
$(T):
@@ -76,7 +77,7 @@ $(T):
$(UNIT_TESTS):
@echo "*** $@ ***"; $@
-.PHONY: unit-tests unit-tests-raw unit-tests-prove
+.PHONY: unit-tests unit-tests-raw unit-tests-prove unit-tests-test-tool
unit-tests: $(DEFAULT_UNIT_TEST_TARGET)
unit-tests-raw: $(UNIT_TESTS)
@@ -84,6 +85,13 @@ unit-tests-raw: $(UNIT_TESTS)
unit-tests-prove:
@echo "*** prove - unit tests ***"; $(PROVE) $(GIT_PROVE_OPTS) $(UNIT_TESTS)
+unit-tests-test-tool:
+ @echo "*** test-tool - unit tests **"
+ ( \
+ cd unit-tests/bin && \
+ ../../helper/test-tool$X run-command testsuite $(UNIT_TESTS_NO_DIR)\
+ )
+
pre-clean:
$(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)'
diff --git a/t/unit-tests/t-basic.c b/t/helper/test-example-tap.c
index fda1ae59a6..d072ad559f 100644
--- a/t/unit-tests/t-basic.c
+++ b/t/helper/test-example-tap.c
@@ -1,4 +1,5 @@
-#include "test-lib.h"
+#include "test-tool.h"
+#include "t/unit-tests/test-lib.h"
/*
* The purpose of this "unit test" is to verify a few invariants of the unit
@@ -69,7 +70,7 @@ static void t_empty(void)
; /* empty */
}
-int cmd_main(int argc, const char **argv)
+int cmd__example_tap(int argc, const char **argv)
{
test_res = TEST(check_res = check_int(1, ==, 1), "passing test");
TEST(t_res(1), "passing test and assertion return 1");
diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c
index 70396fa384..023ed2e1a7 100644
--- a/t/helper/test-path-utils.c
+++ b/t/helper/test-path-utils.c
@@ -7,6 +7,7 @@
#include "string-list.h"
#include "trace.h"
#include "utf8.h"
+#include "copy.h"
/*
* A "string_list_each_func_t" function that normalizes an entry from
@@ -500,6 +501,16 @@ int cmd__path_utils(int argc, const char **argv)
return !!res;
}
+ if (argc == 4 && !strcmp(argv[1], "do_files_match")) {
+ int ret = do_files_match(argv[2], argv[3]);
+
+ if (ret)
+ printf("equal\n");
+ else
+ printf("different\n");
+ return !ret;
+ }
+
fprintf(stderr, "%s: unknown function name: %s\n", argv[0],
argv[1] ? argv[1] : "(there was none)");
return 1;
diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c
index 82bbf6e2e6..4651e4ced7 100644
--- a/t/helper/test-ref-store.c
+++ b/t/helper/test-ref-store.c
@@ -118,7 +118,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)
const char *target = notnull(*argv++, "target");
const char *logmsg = *argv++;
- return refs_create_symref(refs, refname, target, logmsg);
+ return refs_update_symref(refs, refname, target, logmsg);
}
static struct flag_definition transaction_flags[] = {
diff --git a/t/helper/test-run-command.c b/t/helper/test-run-command.c
index c0ed8722c8..61eb1175fe 100644
--- a/t/helper/test-run-command.c
+++ b/t/helper/test-run-command.c
@@ -65,6 +65,7 @@ struct testsuite {
struct string_list tests, failed;
int next;
int quiet, immediate, verbose, verbose_log, trace, write_junit_xml;
+ const char *shell_path;
};
#define TESTSUITE_INIT { \
.tests = STRING_LIST_INIT_DUP, \
@@ -80,7 +81,9 @@ static int next_test(struct child_process *cp, struct strbuf *err, void *cb,
return 0;
test = suite->tests.items[suite->next++].string;
- strvec_pushl(&cp->args, "sh", test, NULL);
+ if (suite->shell_path)
+ strvec_push(&cp->args, suite->shell_path);
+ strvec_push(&cp->args, test);
if (suite->quiet)
strvec_push(&cp->args, "--quiet");
if (suite->immediate)
@@ -155,6 +158,8 @@ static int testsuite(int argc, const char **argv)
.task_finished = test_finished,
.data = &suite,
};
+ struct strbuf progpath = STRBUF_INIT;
+ size_t path_prefix_len;
argc = parse_options(argc, argv, NULL, options,
testsuite_usage, PARSE_OPT_STOP_AT_NON_OPTION);
@@ -162,26 +167,36 @@ static int testsuite(int argc, const char **argv)
if (max_jobs <= 0)
max_jobs = online_cpus();
+ /*
+ * If we run without a shell, execute the programs directly from CWD.
+ */
+ suite.shell_path = getenv("TEST_SHELL_PATH");
+ if (!suite.shell_path)
+ strbuf_addstr(&progpath, "./");
+ path_prefix_len = progpath.len;
+
dir = opendir(".");
if (!dir)
die("Could not open the current directory");
while ((d = readdir(dir))) {
const char *p = d->d_name;
- if (*p != 't' || !isdigit(p[1]) || !isdigit(p[2]) ||
- !isdigit(p[3]) || !isdigit(p[4]) || p[5] != '-' ||
- !ends_with(p, ".sh"))
+ if (!strcmp(p, ".") || !strcmp(p, ".."))
continue;
/* No pattern: match all */
if (!argc) {
- string_list_append(&suite.tests, p);
+ strbuf_setlen(&progpath, path_prefix_len);
+ strbuf_addstr(&progpath, p);
+ string_list_append(&suite.tests, progpath.buf);
continue;
}
for (i = 0; i < argc; i++)
if (!wildmatch(argv[i], p, 0)) {
- string_list_append(&suite.tests, p);
+ strbuf_setlen(&progpath, path_prefix_len);
+ strbuf_addstr(&progpath, p);
+ string_list_append(&suite.tests, progpath.buf);
break;
}
}
@@ -208,6 +223,7 @@ static int testsuite(int argc, const char **argv)
string_list_clear(&suite.tests, 0);
string_list_clear(&suite.failed, 0);
+ strbuf_release(&progpath);
return ret;
}
diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c
index 80a946b847..f6fd0fe491 100644
--- a/t/helper/test-tool.c
+++ b/t/helper/test-tool.c
@@ -30,6 +30,7 @@ static struct test_cmd cmds[] = {
{ "dump-untracked-cache", cmd__dump_untracked_cache },
{ "env-helper", cmd__env_helper },
{ "example-decorate", cmd__example_decorate },
+ { "example-tap", cmd__example_tap },
{ "find-pack", cmd__find_pack },
{ "fsmonitor-client", cmd__fsmonitor_client },
{ "genrandom", cmd__genrandom },
diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h
index 2808b92419..868f33453c 100644
--- a/t/helper/test-tool.h
+++ b/t/helper/test-tool.h
@@ -24,6 +24,7 @@ int cmd__dump_untracked_cache(int argc, const char **argv);
int cmd__dump_reftable(int argc, const char **argv);
int cmd__env_helper(int argc, const char **argv);
int cmd__example_decorate(int argc, const char **argv);
+int cmd__example_tap(int argc, const char **argv);
int cmd__find_pack(int argc, const char **argv);
int cmd__fsmonitor_client(int argc, const char **argv);
int cmd__genrandom(int argc, const char **argv);
diff --git a/t/lib-chunk.sh b/t/lib-chunk.sh
index a7cd9c3c6d..9f01df190b 100644
--- a/t/lib-chunk.sh
+++ b/t/lib-chunk.sh
@@ -13,5 +13,6 @@ corrupt_chunk_file () {
fn=$1; shift
perl "$TEST_DIRECTORY"/lib-chunk/corrupt-chunk-file.pl \
"$@" <"$fn" >"$fn.tmp" &&
- mv "$fn.tmp" "$fn"
+ # some vintages of macOS 'mv' fails to overwrite a read-only file.
+ mv -f "$fn.tmp" "$fn"
}
diff --git a/t/run-test.sh b/t/run-test.sh
new file mode 100755
index 0000000000..13c353b91b
--- /dev/null
+++ b/t/run-test.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+# A simple wrapper to run shell tests via TEST_SHELL_PATH,
+# or exec unit tests directly.
+
+case "$1" in
+*.sh)
+ if test -z "${TEST_SHELL_PATH}"
+ then
+ echo >&2 "ERROR: TEST_SHELL_PATH is empty or not set"
+ exit 1
+ fi
+ exec "${TEST_SHELL_PATH}" "$@"
+ ;;
+*)
+ exec "$@"
+ ;;
+esac
diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh
index 6e300be2ac..98b81e4d63 100755
--- a/t/t0000-basic.sh
+++ b/t/t0000-basic.sh
@@ -1201,6 +1201,34 @@ test_expect_success 'very long name in the index handled sanely' '
test $len = 4098
'
+# D/F conflict checking uses an optimization when adding to the end.
+# make sure it does not get confused by `a-` sorting _between_
+# `a` and `a/`.
+test_expect_success 'more update-index D/F conflicts' '
+ # empty the index to make sure our entry is last
+ git read-tree --empty &&
+ cacheinfo=100644,$(test_oid empty_blob) &&
+ git update-index --add --cacheinfo $cacheinfo,path5/a &&
+
+ test_must_fail git update-index --add --cacheinfo $cacheinfo,path5/a/file &&
+ test_must_fail git update-index --add --cacheinfo $cacheinfo,path5/a/b/file &&
+ test_must_fail git update-index --add --cacheinfo $cacheinfo,path5/a/b/c/file &&
+
+ # "a-" sorts between "a" and "a/"
+ git update-index --add --cacheinfo $cacheinfo,path5/a- &&
+
+ test_must_fail git update-index --add --cacheinfo $cacheinfo,path5/a/file &&
+ test_must_fail git update-index --add --cacheinfo $cacheinfo,path5/a/b/file &&
+ test_must_fail git update-index --add --cacheinfo $cacheinfo,path5/a/b/c/file &&
+
+ cat >expected <<-\EOF &&
+ path5/a
+ path5/a-
+ EOF
+ git ls-files >actual &&
+ test_cmp expected actual
+'
+
test_expect_success 'test_must_fail on a failing git command' '
test_must_fail git notacommand
'
diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh
index 774b52c298..66ccb5889d 100755
--- a/t/t0003-attributes.sh
+++ b/t/t0003-attributes.sh
@@ -398,13 +398,19 @@ test_expect_success 'bad attr source defaults to reading .gitattributes file' '
)
'
-test_expect_success 'bare repo defaults to reading .gitattributes from HEAD' '
+test_expect_success 'bare repo no longer defaults to reading .gitattributes from HEAD' '
test_when_finished rm -rf test bare_with_gitattribute &&
git init test &&
test_commit -C test gitattributes .gitattributes "f/path test=val" &&
git clone --bare test bare_with_gitattribute &&
- echo "f/path: test: val" >expect &&
+
+ echo "f/path: test: unspecified" >expect &&
git -C bare_with_gitattribute check-attr test -- f/path >actual &&
+ test_cmp expect actual &&
+
+ echo "f/path: test: val" >expect &&
+ git -C bare_with_gitattribute -c attr.tree=HEAD \
+ check-attr test -- f/path >actual &&
test_cmp expect actual
'
@@ -428,6 +434,21 @@ test_expect_success 'precedence of --attr-source, GIT_ATTR_SOURCE, then attr.tre
)
'
+test_expect_success 'diff without repository with attr source' '
+ mkdir -p "$TRASH_DIRECTORY/outside/nongit" &&
+ (
+ cd "$TRASH_DIRECTORY/outside/nongit" &&
+ GIT_CEILING_DIRECTORIES="$TRASH_DIRECTORY/outside" &&
+ export GIT_CEILING_DIRECTORIES &&
+ touch file &&
+ cat >expect <<-EOF &&
+ fatal: cannot use --attr-source or GIT_ATTR_SOURCE without repo
+ EOF
+ test_must_fail env GIT_ATTR_SOURCE=HEAD git grep --no-index foo file 2>err &&
+ test_cmp expect err
+ )
+'
+
test_expect_success 'bare repository: with --source' '
(
cd bare.git &&
@@ -572,6 +593,16 @@ test_expect_success EXPENSIVE 'large attributes file ignored in index' '
test_cmp expect err
'
+test_expect_success EXPENSIVE 'large attributes blob ignored' '
+ test_when_finished "git update-index --remove .gitattributes" &&
+ blob=$(dd if=/dev/zero bs=1048576 count=101 2>/dev/null | git hash-object -w --stdin) &&
+ git update-index --add --cacheinfo 100644,$blob,.gitattributes &&
+ tree="$(git write-tree)" &&
+ git check-attr --cached --all --source="$tree" path >/dev/null 2>err &&
+ echo "warning: ignoring overly large gitattributes blob ${SQ}.gitattributes${SQ}" >expect &&
+ test_cmp expect err
+'
+
test_expect_success 'builtin object mode attributes work (dir and regular paths)' '
>normal &&
attr_check_object_mode normal 100644 &&
diff --git a/t/t0018-advice.sh b/t/t0018-advice.sh
index 0dcfb760a2..29306b367c 100755
--- a/t/t0018-advice.sh
+++ b/t/t0018-advice.sh
@@ -2,6 +2,9 @@
test_description='Test advise_if_enabled functionality'
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=trunk
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
@@ -29,4 +32,72 @@ test_expect_success 'advice should not be printed when config variable is set to
test_must_be_empty actual
'
+test_expect_success 'advice should not be printed when --no-advice is used' '
+ q_to_tab >expect <<-\EOF &&
+ On branch trunk
+
+ No commits yet
+
+ Untracked files:
+ QREADME
+
+ nothing added to commit but untracked files present
+ EOF
+
+ test_when_finished "rm -fr advice-test" &&
+ git init advice-test &&
+ (
+ cd advice-test &&
+ >README &&
+ git --no-advice status
+ ) >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'advice should not be printed when GIT_ADVICE is set to false' '
+ q_to_tab >expect <<-\EOF &&
+ On branch trunk
+
+ No commits yet
+
+ Untracked files:
+ QREADME
+
+ nothing added to commit but untracked files present
+ EOF
+
+ test_when_finished "rm -fr advice-test" &&
+ git init advice-test &&
+ (
+ cd advice-test &&
+ >README &&
+ GIT_ADVICE=false git status
+ ) >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'advice should be printed when GIT_ADVICE is set to true' '
+ q_to_tab >expect <<-\EOF &&
+ On branch trunk
+
+ No commits yet
+
+ Untracked files:
+ (use "git add <file>..." to include in what will be committed)
+ QREADME
+
+ nothing added to commit but untracked files present (use "git add" to track)
+ EOF
+
+ test_when_finished "rm -fr advice-test" &&
+ git init advice-test &&
+ (
+ cd advice-test &&
+ >README &&
+ GIT_ADVICE=true git status
+ ) >actual &&
+ cat actual > /tmp/actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t0033-safe-directory.sh b/t/t0033-safe-directory.sh
index dc3496897a..11c3e8f28e 100755
--- a/t/t0033-safe-directory.sh
+++ b/t/t0033-safe-directory.sh
@@ -80,4 +80,28 @@ test_expect_success 'safe.directory in included file' '
git status
'
+test_expect_success 'local clone of unowned repo refused in unsafe directory' '
+ test_when_finished "rm -rf source" &&
+ git init source &&
+ (
+ sane_unset GIT_TEST_ASSUME_DIFFERENT_OWNER &&
+ test_commit -C source initial
+ ) &&
+ test_must_fail git clone --local source target &&
+ test_path_is_missing target
+'
+
+test_expect_success 'local clone of unowned repo accepted in safe directory' '
+ test_when_finished "rm -rf source" &&
+ git init source &&
+ (
+ sane_unset GIT_TEST_ASSUME_DIFFERENT_OWNER &&
+ test_commit -C source initial
+ ) &&
+ test_must_fail git clone --local source target &&
+ git config --global --add safe.directory "$(pwd)/source/.git" &&
+ git clone --local source target &&
+ test_path_is_dir target
+'
+
test_done
diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh
index 8bb2a8b453..45a773642f 100755
--- a/t/t0040-parse-options.sh
+++ b/t/t0040-parse-options.sh
@@ -176,6 +176,23 @@ test_expect_success 'long options' '
test_cmp expect output
'
+test_expect_success 'abbreviate to something longer than SHA1 length' '
+ cat >expect <<-EOF &&
+ boolean: 0
+ integer: 0
+ magnitude: 0
+ timestamp: 0
+ string: (not set)
+ abbrev: 100
+ verbose: -1
+ quiet: 0
+ dry run: no
+ file: (not set)
+ EOF
+ test-tool parse-options --abbrev=100 >output &&
+ test_cmp expect output
+'
+
test_expect_success 'missing required value' '
cat >expect <<-\EOF &&
error: switch `s'\'' requires a value
diff --git a/t/t0050-filesystem.sh b/t/t0050-filesystem.sh
index 325eb1c3cd..925734c819 100755
--- a/t/t0050-filesystem.sh
+++ b/t/t0050-filesystem.sh
@@ -156,4 +156,15 @@ test_expect_success CASE_INSENSITIVE_FS 'checkout with no pathspec and a case in
)
'
+test_expect_success 'git ls-files under NFD' '
+ (
+ mkdir -p "somewhere/$aumlcdiar" &&
+ mypwd=$PWD &&
+ cd "somewhere/$aumlcdiar" &&
+ git init &&
+ git --literal-pathspecs ls-files "$mypwd/somewhere/$aumlcdiar" 2>err &&
+ >expected &&
+ test_cmp expected err
+ )
+'
test_done
diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh
index 0afa3d0d31..85686ee15d 100755
--- a/t/t0060-path-utils.sh
+++ b/t/t0060-path-utils.sh
@@ -610,4 +610,45 @@ test_expect_success !VALGRIND,RUNTIME_PREFIX,CAN_EXEC_IN_PWD '%(prefix)/ works'
test_cmp expect actual
'
+test_expect_success 'do_files_match()' '
+ test_seq 0 10 >0-10.txt &&
+ test_seq -1 10 >-1-10.txt &&
+ test_seq 1 10 >1-10.txt &&
+ test_seq 1 9 >1-9.txt &&
+ test_seq 0 8 >0-8.txt &&
+
+ test-tool path-utils do_files_match 0-10.txt 0-10.txt >out &&
+
+ assert_fails() {
+ test_must_fail \
+ test-tool path-utils do_files_match "$1" "$2" >out &&
+ grep different out
+ } &&
+
+ assert_fails 0-8.txt 1-9.txt &&
+ assert_fails -1-10.txt 0-10.txt &&
+ assert_fails 1-10.txt 1-9.txt &&
+ assert_fails 1-10.txt .git &&
+ assert_fails does-not-exist 1-10.txt &&
+
+ if test_have_prereq FILEMODE
+ then
+ cp 0-10.txt 0-10.x &&
+ chmod a+x 0-10.x &&
+ assert_fails 0-10.txt 0-10.x
+ fi &&
+
+ if test_have_prereq SYMLINKS
+ then
+ ln -sf 0-10.txt symlink &&
+ ln -s 0-10.txt another-symlink &&
+ ln -s over-the-ocean yet-another-symlink &&
+ ln -s "$PWD/0-10.txt" absolute-symlink &&
+ assert_fails 0-10.txt symlink &&
+ test-tool path-utils do_files_match symlink another-symlink &&
+ assert_fails symlink yet-another-symlink &&
+ assert_fails symlink absolute-symlink
+ fi
+'
+
test_done
diff --git a/t/t0080-unit-test-output.sh b/t/t0080-unit-test-output.sh
index 6657c114a3..7bbb065d58 100755
--- a/t/t0080-unit-test-output.sh
+++ b/t/t0080-unit-test-output.sh
@@ -9,50 +9,50 @@ test_expect_success 'TAP output from unit tests' '
cat >expect <<-EOF &&
ok 1 - passing test
ok 2 - passing test and assertion return 1
- # check "1 == 2" failed at t/unit-tests/t-basic.c:76
+ # check "1 == 2" failed at t/helper/test-example-tap.c:77
# left: 1
# right: 2
not ok 3 - failing test
ok 4 - failing test and assertion return 0
not ok 5 - passing TEST_TODO() # TODO
ok 6 - passing TEST_TODO() returns 1
- # todo check ${SQ}check(x)${SQ} succeeded at t/unit-tests/t-basic.c:25
+ # todo check ${SQ}check(x)${SQ} succeeded at t/helper/test-example-tap.c:26
not ok 7 - failing TEST_TODO()
ok 8 - failing TEST_TODO() returns 0
- # check "0" failed at t/unit-tests/t-basic.c:30
+ # check "0" failed at t/helper/test-example-tap.c:31
# skipping test - missing prerequisite
- # skipping check ${SQ}1${SQ} at t/unit-tests/t-basic.c:32
+ # skipping check ${SQ}1${SQ} at t/helper/test-example-tap.c:33
ok 9 - test_skip() # SKIP
ok 10 - skipped test returns 1
# skipping test - missing prerequisite
ok 11 - test_skip() inside TEST_TODO() # SKIP
ok 12 - test_skip() inside TEST_TODO() returns 1
- # check "0" failed at t/unit-tests/t-basic.c:48
+ # check "0" failed at t/helper/test-example-tap.c:49
not ok 13 - TEST_TODO() after failing check
ok 14 - TEST_TODO() after failing check returns 0
- # check "0" failed at t/unit-tests/t-basic.c:56
+ # check "0" failed at t/helper/test-example-tap.c:57
not ok 15 - failing check after TEST_TODO()
ok 16 - failing check after TEST_TODO() returns 0
- # check "!strcmp("\thello\\\\", "there\"\n")" failed at t/unit-tests/t-basic.c:61
+ # check "!strcmp("\thello\\\\", "there\"\n")" failed at t/helper/test-example-tap.c:62
# left: "\011hello\\\\"
# right: "there\"\012"
- # check "!strcmp("NULL", NULL)" failed at t/unit-tests/t-basic.c:62
+ # check "!strcmp("NULL", NULL)" failed at t/helper/test-example-tap.c:63
# left: "NULL"
# right: NULL
- # check "${SQ}a${SQ} == ${SQ}\n${SQ}" failed at t/unit-tests/t-basic.c:63
+ # check "${SQ}a${SQ} == ${SQ}\n${SQ}" failed at t/helper/test-example-tap.c:64
# left: ${SQ}a${SQ}
# right: ${SQ}\012${SQ}
- # check "${SQ}\\\\${SQ} == ${SQ}\\${SQ}${SQ}" failed at t/unit-tests/t-basic.c:64
+ # check "${SQ}\\\\${SQ} == ${SQ}\\${SQ}${SQ}" failed at t/helper/test-example-tap.c:65
# left: ${SQ}\\\\${SQ}
# right: ${SQ}\\${SQ}${SQ}
not ok 17 - messages from failing string and char comparison
- # BUG: test has no checks at t/unit-tests/t-basic.c:91
+ # BUG: test has no checks at t/helper/test-example-tap.c:92
not ok 18 - test with no checks
ok 19 - test with no checks returns 0
1..19
EOF
- ! "$GIT_BUILD_DIR"/t/unit-tests/bin/t-basic >actual &&
+ ! test-tool example-tap >actual &&
test_cmp expect actual
'
diff --git a/t/t0411-clone-from-partial.sh b/t/t0411-clone-from-partial.sh
new file mode 100755
index 0000000000..c98d501869
--- /dev/null
+++ b/t/t0411-clone-from-partial.sh
@@ -0,0 +1,78 @@
+#!/bin/sh
+
+test_description='check that local clone does not fetch from promisor remotes'
+
+. ./test-lib.sh
+
+test_expect_success 'create evil repo' '
+ git init tmp &&
+ test_commit -C tmp a &&
+ git -C tmp config uploadpack.allowfilter 1 &&
+ git clone --filter=blob:none --no-local --no-checkout tmp evil &&
+ rm -rf tmp &&
+
+ git -C evil config remote.origin.uploadpack \"\$TRASH_DIRECTORY/fake-upload-pack\" &&
+ write_script fake-upload-pack <<-\EOF &&
+ echo >&2 "fake-upload-pack running"
+ >"$TRASH_DIRECTORY/script-executed"
+ exit 1
+ EOF
+ export TRASH_DIRECTORY &&
+
+ # empty shallow file disables local clone optimization
+ >evil/.git/shallow
+'
+
+test_expect_success 'local clone must not fetch from promisor remote and execute script' '
+ rm -f script-executed &&
+ test_must_fail git clone \
+ --upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \
+ evil clone1 2>err &&
+ test_grep "detected dubious ownership" err &&
+ test_grep ! "fake-upload-pack running" err &&
+ test_path_is_missing script-executed
+'
+
+test_expect_success 'clone from file://... must not fetch from promisor remote and execute script' '
+ rm -f script-executed &&
+ test_must_fail git clone \
+ --upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \
+ "file://$(pwd)/evil" clone2 2>err &&
+ test_grep "detected dubious ownership" err &&
+ test_grep ! "fake-upload-pack running" err &&
+ test_path_is_missing script-executed
+'
+
+test_expect_success 'fetch from file://... must not fetch from promisor remote and execute script' '
+ rm -f script-executed &&
+ test_must_fail git fetch \
+ --upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \
+ "file://$(pwd)/evil" 2>err &&
+ test_grep "detected dubious ownership" err &&
+ test_grep ! "fake-upload-pack running" err &&
+ test_path_is_missing script-executed
+'
+
+test_expect_success 'pack-objects should fetch from promisor remote and execute script' '
+ rm -f script-executed &&
+ echo "HEAD" | test_must_fail git -C evil pack-objects --revs --stdout >/dev/null 2>err &&
+ test_grep "fake-upload-pack running" err &&
+ test_path_is_file script-executed
+'
+
+test_expect_success 'clone from promisor remote does not lazy-fetch by default' '
+ rm -f script-executed &&
+ test_must_fail git clone evil no-lazy 2>err &&
+ test_grep "lazy fetching disabled" err &&
+ test_path_is_missing script-executed
+'
+
+test_expect_success 'promisor lazy-fetching can be re-enabled' '
+ rm -f script-executed &&
+ test_must_fail env GIT_NO_LAZY_FETCH=0 \
+ git clone evil lazy-ok 2>err &&
+ test_grep "fake-upload-pack running" err &&
+ test_path_is_file script-executed
+'
+
+test_done
diff --git a/t/t0450/txt-help-mismatches b/t/t0450/txt-help-mismatches
index a0777acd66..28003f18c9 100644
--- a/t/t0450/txt-help-mismatches
+++ b/t/t0450/txt-help-mismatches
@@ -10,7 +10,6 @@ checkout
checkout-index
clone
column
-config
credential
credential-cache
credential-store
diff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh
index a390cffc80..92f570313d 100755
--- a/t/t0600-reffiles-backend.sh
+++ b/t/t0600-reffiles-backend.sh
@@ -424,7 +424,7 @@ test_expect_success SYMLINKS 'git branch -m with symlinked .git/refs' '
test_when_finished "rm -rf subdir" &&
git init --bare subdir &&
- rm -rfv subdir/refs subdir/objects subdir/packed-refs &&
+ rm -rf subdir/refs subdir/objects subdir/packed-refs &&
ln -s ../.git/refs subdir/refs &&
ln -s ../.git/objects subdir/objects &&
ln -s ../.git/packed-refs subdir/packed-refs &&
diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index 9b65d9eaf5..f3c4d28e06 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -11,6 +11,34 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
TEST_PASSES_SANITIZE_LEAK=true
. ./test-lib.sh
+for mode in legacy subcommands
+do
+
+case "$mode" in
+legacy)
+ mode_prefix="--"
+ mode_get=""
+ mode_get_all="--get-all"
+ mode_get_regexp="--get-regexp"
+ mode_set=""
+ mode_replace_all="--replace-all"
+ mode_unset="--unset"
+ mode_unset_all="--unset-all"
+ ;;
+subcommands)
+ mode_prefix=""
+ mode_get="get"
+ mode_get_all="get --all"
+ mode_get_regexp="get --regexp --all --show-names"
+ mode_set="set"
+ mode_replace_all="set --all"
+ mode_unset="unset"
+ mode_unset_all="unset --all"
+ ;;
+*)
+ BUG "unknown mode $mode";;
+esac
+
test_expect_success 'setup whitespace config' '
sed -e "s/^|//" \
-e "s/[$]$//" \
@@ -112,7 +140,7 @@ cat > expect << EOF
penguin = little blue
EOF
test_expect_success 'initial' '
- git config section.penguin "little blue" &&
+ git config ${mode_set} section.penguin "little blue" &&
test_cmp expect .git/config
'
@@ -122,7 +150,7 @@ cat > expect << EOF
Movie = BadPhysics
EOF
test_expect_success 'mixed case' '
- git config Section.Movie BadPhysics &&
+ git config ${mode_set} Section.Movie BadPhysics &&
test_cmp expect .git/config
'
@@ -134,7 +162,7 @@ cat > expect << EOF
WhatEver = Second
EOF
test_expect_success 'similar section' '
- git config Sections.WhatEver Second &&
+ git config ${mode_set} Sections.WhatEver Second &&
test_cmp expect .git/config
'
@@ -147,7 +175,7 @@ cat > expect << EOF
WhatEver = Second
EOF
test_expect_success 'uppercase section' '
- git config SECTION.UPPERCASE true &&
+ git config ${mode_set} SECTION.UPPERCASE true &&
test_cmp expect .git/config
'
@@ -174,8 +202,8 @@ EOF
test_expect_success 'append comments' '
git config --replace-all --comment="Pygoscelis papua" section.penguin gentoo &&
- git config --comment="find fish" section.disposition peckish &&
- git config --comment="#abc" section.foo bar &&
+ git config ${mode_set} --comment="find fish" section.disposition peckish &&
+ git config ${mode_set} --comment="#abc" section.foo bar &&
git config --comment="and comment" section.spsp value &&
git config --comment=" # and comment" section.htsp value &&
@@ -184,7 +212,7 @@ test_expect_success 'append comments' '
'
test_expect_success 'Prohibited LF in comment' '
- test_must_fail git config --comment="a${LF}b" section.k v
+ test_must_fail git config ${mode_set} --comment="a${LF}b" section.k v
'
test_expect_success 'non-match result' 'test_cmp expect .git/config'
@@ -235,7 +263,7 @@ foo = bar
EOF
test_expect_success 'unset with cont. lines' '
- git config --unset beta.baz
+ git config ${mode_unset} beta.baz
'
cat > expect <<\EOF
@@ -262,7 +290,7 @@ EOF
cp .git/config .git/config2
test_expect_success 'multiple unset' '
- git config --unset-all beta.haha
+ git config ${mode_unset_all} beta.haha
'
cat > expect << EOF
@@ -281,14 +309,14 @@ test_expect_success 'multiple unset is correct' '
cp .git/config2 .git/config
test_expect_success '--replace-all missing value' '
- test_must_fail git config --replace-all beta.haha &&
+ test_must_fail git config ${mode_replace_all} beta.haha &&
test_cmp .git/config2 .git/config
'
rm .git/config2
test_expect_success '--replace-all' '
- git config --replace-all beta.haha gamma
+ git config ${mode_replace_all} beta.haha gamma
'
cat > expect << EOF
@@ -315,7 +343,7 @@ noIndent= sillyValue ; 'nother silly comment
[nextSection] noNewline = ouch
EOF
test_expect_success 'really mean test' '
- git config beta.haha alpha &&
+ git config ${mode_set} beta.haha alpha &&
test_cmp expect .git/config
'
@@ -330,7 +358,7 @@ noIndent= sillyValue ; 'nother silly comment
nonewline = wow
EOF
test_expect_success 'really really mean test' '
- git config nextsection.nonewline wow &&
+ git config ${mode_set} nextsection.nonewline wow &&
test_cmp expect .git/config
'
@@ -348,7 +376,7 @@ noIndent= sillyValue ; 'nother silly comment
nonewline = wow
EOF
test_expect_success 'unset' '
- git config --unset beta.haha &&
+ git config ${mode_unset} beta.haha &&
test_cmp expect .git/config
'
@@ -384,7 +412,7 @@ test_expect_success 'multi-valued get-all returns all' '
wow
wow2 for me
EOF
- git config --get-all nextsection.nonewline >actual &&
+ git config ${mode_get_all} nextsection.nonewline >actual &&
test_cmp expect actual
'
@@ -404,11 +432,11 @@ test_expect_success 'multivar replace' '
'
test_expect_success 'ambiguous unset' '
- test_must_fail git config --unset nextsection.nonewline
+ test_must_fail git config ${mode_unset} nextsection.nonewline
'
test_expect_success 'invalid unset' '
- test_must_fail git config --unset somesection.nonewline
+ test_must_fail git config ${mode_unset} somesection.nonewline
'
cat > expect << EOF
@@ -422,7 +450,12 @@ noIndent= sillyValue ; 'nother silly comment
EOF
test_expect_success 'multivar unset' '
- git config --unset nextsection.nonewline "wow3$" &&
+ case "$mode" in
+ legacy)
+ git config --unset nextsection.nonewline "wow3$";;
+ subcommands)
+ git config unset --value="wow3$" nextsection.nonewline;;
+ esac &&
test_cmp expect .git/config
'
@@ -460,11 +493,11 @@ version.1.2.3eX.alpha=beta
EOF
test_expect_success 'working --list' '
- git config --list > output &&
+ git config ${mode_prefix}list > output &&
test_cmp expect output
'
test_expect_success '--list without repo produces empty output' '
- git --git-dir=nonexistent config --list >output &&
+ git --git-dir=nonexistent config ${mode_prefix}list >output &&
test_must_be_empty output
'
@@ -476,7 +509,7 @@ version.1.2.3eX.alpha
EOF
test_expect_success '--name-only --list' '
- git config --name-only --list >output &&
+ git config ${mode_prefix}list --name-only >output &&
test_cmp expect output
'
@@ -486,7 +519,7 @@ nextsection.nonewline wow2 for me
EOF
test_expect_success '--get-regexp' '
- git config --get-regexp in >output &&
+ git config ${mode_get_regexp} in >output &&
test_cmp expect output
'
@@ -496,7 +529,7 @@ nextsection.nonewline
EOF
test_expect_success '--name-only --get-regexp' '
- git config --name-only --get-regexp in >output &&
+ git config ${mode_get_regexp} --name-only in >output &&
test_cmp expect output
'
@@ -507,7 +540,7 @@ EOF
test_expect_success '--add' '
git config --add nextsection.nonewline "wow4 for you" &&
- git config --get-all nextsection.nonewline > output &&
+ git config ${mode_get_all} nextsection.nonewline > output &&
test_cmp expect output
'
@@ -529,21 +562,21 @@ test_expect_success 'get variable with empty value' '
echo novalue.variable > expect
test_expect_success 'get-regexp variable with no value' '
- git config --get-regexp novalue > output &&
+ git config ${mode_get_regexp} novalue > output &&
test_cmp expect output
'
echo 'novalue.variable true' > expect
test_expect_success 'get-regexp --bool variable with no value' '
- git config --bool --get-regexp novalue > output &&
+ git config ${mode_get_regexp} --bool novalue > output &&
test_cmp expect output
'
echo 'emptyvalue.variable ' > expect
test_expect_success 'get-regexp variable with empty value' '
- git config --get-regexp emptyvalue > output &&
+ git config ${mode_get_regexp} emptyvalue > output &&
test_cmp expect output
'
@@ -614,17 +647,17 @@ ein.bahn=strasse
EOF
test_expect_success 'alternative GIT_CONFIG' '
- GIT_CONFIG=other-config git config --list >output &&
+ GIT_CONFIG=other-config git config ${mode_prefix}list >output &&
test_cmp expect output
'
test_expect_success 'alternative GIT_CONFIG (--file)' '
- git config --file other-config --list >output &&
+ git config ${mode_prefix}list --file other-config >output &&
test_cmp expect output
'
test_expect_success 'alternative GIT_CONFIG (--file=-)' '
- git config --file - --list <other-config >output &&
+ git config ${mode_prefix}list --file - <other-config >output &&
test_cmp expect output
'
@@ -633,10 +666,11 @@ test_expect_success 'setting a value in stdin is an error' '
'
test_expect_success 'editing stdin is an error' '
- test_must_fail git config --file - --edit
+ test_must_fail git config ${mode_prefix}edit --file -
'
test_expect_success 'refer config from subdirectory' '
+ test_when_finished "rm -r x" &&
mkdir x &&
test_cmp_config -C x strasse --file=../other-config --get ein.bahn
'
@@ -665,7 +699,7 @@ weird
EOF
test_expect_success 'rename section' '
- git config --rename-section branch.eins branch.zwei
+ git config ${mode_prefix}rename-section branch.eins branch.zwei
'
cat > expect << EOF
@@ -684,7 +718,7 @@ test_expect_success 'rename succeeded' '
'
test_expect_success 'rename non-existing section' '
- test_must_fail git config --rename-section \
+ test_must_fail git config ${mode_prefix}rename-section \
branch."world domination" branch.drei
'
@@ -693,7 +727,7 @@ test_expect_success 'rename succeeded' '
'
test_expect_success 'rename another section' '
- git config --rename-section branch."1 234 blabl/a" branch.drei
+ git config ${mode_prefix}rename-section branch."1 234 blabl/a" branch.drei
'
cat > expect << EOF
@@ -716,7 +750,7 @@ cat >> .git/config << EOF
EOF
test_expect_success 'rename a section with a var on the same line' '
- git config --rename-section branch.vier branch.zwei
+ git config ${mode_prefix}rename-section branch.vier branch.zwei
'
cat > expect << EOF
@@ -737,11 +771,11 @@ test_expect_success 'rename succeeded' '
'
test_expect_success 'renaming empty section name is rejected' '
- test_must_fail git config --rename-section branch.zwei ""
+ test_must_fail git config ${mode_prefix}rename-section branch.zwei ""
'
test_expect_success 'renaming to bogus section is rejected' '
- test_must_fail git config --rename-section branch.zwei "bogus name"
+ test_must_fail git config ${mode_prefix}rename-section branch.zwei "bogus name"
'
test_expect_success 'renaming a section with a long line' '
@@ -750,7 +784,7 @@ test_expect_success 'renaming a section with a long line' '
printf " c = d %1024s [a] e = f\\n" " " &&
printf "[a] g = h\\n"
} >y &&
- git config -f y --rename-section a xyz &&
+ git config ${mode_prefix}rename-section -f y a xyz &&
test_must_fail git config -f y b.e
'
@@ -760,7 +794,7 @@ test_expect_success 'renaming an embedded section with a long line' '
printf " c = d %1024s [a] [foo] e = f\\n" " " &&
printf "[a] g = h\\n"
} >y &&
- git config -f y --rename-section a xyz &&
+ git config ${mode_prefix}rename-section -f y a xyz &&
test_must_fail git config -f y foo.e
'
@@ -770,7 +804,7 @@ test_expect_success 'renaming a section with an overly-long line' '
printf " c = d %525000s e" " " &&
printf "[a] g = h\\n"
} >y &&
- test_must_fail git config -f y --rename-section a xyz 2>err &&
+ test_must_fail git config ${mode_prefix}rename-section -f y a xyz 2>err &&
grep "refusing to work with overly long line in .y. on line 2" err
'
@@ -779,7 +813,7 @@ cat >> .git/config << EOF
EOF
test_expect_success 'remove section' '
- git config --remove-section branch.zwei
+ git config ${mode_prefix}remove-section branch.zwei
'
cat > expect << EOF
@@ -803,16 +837,16 @@ EOF
test_expect_success 'section ending' '
rm -f .git/config &&
- git config gitcvs.enabled true &&
- git config gitcvs.ext.dbname %Ggitcvs1.%a.%m.sqlite &&
- git config gitcvs.dbname %Ggitcvs2.%a.%m.sqlite &&
+ git config ${mode_set} gitcvs.enabled true &&
+ git config ${mode_set} gitcvs.ext.dbname %Ggitcvs1.%a.%m.sqlite &&
+ git config ${mode_set} gitcvs.dbname %Ggitcvs2.%a.%m.sqlite &&
test_cmp expect .git/config
'
test_expect_success numbers '
- git config kilo.gram 1k &&
- git config mega.ton 1m &&
+ git config ${mode_set} kilo.gram 1k &&
+ git config ${mode_set} mega.ton 1m &&
echo 1024 >expect &&
echo 1048576 >>expect &&
git config --int --get kilo.gram >actual &&
@@ -821,20 +855,20 @@ test_expect_success numbers '
'
test_expect_success '--int is at least 64 bits' '
- git config giga.watts 121g &&
+ git config ${mode_set} giga.watts 121g &&
echo >expect &&
test_cmp_config 129922760704 --int --get giga.watts
'
test_expect_success 'invalid unit' '
- git config aninvalid.unit "1auto" &&
+ git config ${mode_set} aninvalid.unit "1auto" &&
test_cmp_config 1auto aninvalid.unit &&
test_must_fail git config --int --get aninvalid.unit 2>actual &&
test_grep "bad numeric config value .1auto. for .aninvalid.unit. in file .git/config: invalid unit" actual
'
test_expect_success 'invalid unit boolean' '
- git config commit.gpgsign "1true" &&
+ git config ${mode_set} commit.gpgsign "1true" &&
test_cmp_config 1true commit.gpgsign &&
test_must_fail git config --bool --get commit.gpgsign 2>actual &&
test_grep "bad boolean config value .1true. for .commit.gpgsign." actual
@@ -847,7 +881,7 @@ test_expect_success 'line number is reported correctly' '
'
test_expect_success 'invalid stdin config' '
- echo "[broken" | test_must_fail git config --list --file - >output 2>&1 &&
+ echo "[broken" | test_must_fail git config ${mode_prefix}list --file - >output 2>&1 &&
test_grep "bad config line 1 in standard input" output
'
@@ -864,14 +898,14 @@ EOF
test_expect_success bool '
- git config bool.true1 01 &&
- git config bool.true2 -1 &&
- git config bool.true3 YeS &&
- git config bool.true4 true &&
- git config bool.false1 000 &&
- git config bool.false2 "" &&
- git config bool.false3 nO &&
- git config bool.false4 FALSE &&
+ git config ${mode_set} bool.true1 01 &&
+ git config ${mode_set} bool.true2 -1 &&
+ git config ${mode_set} bool.true3 YeS &&
+ git config ${mode_set} bool.true4 true &&
+ git config ${mode_set} bool.false1 000 &&
+ git config ${mode_set} bool.false2 "" &&
+ git config ${mode_set} bool.false3 nO &&
+ git config ${mode_set} bool.false4 FALSE &&
rm -f result &&
for i in 1 2 3 4
do
@@ -882,7 +916,7 @@ test_expect_success bool '
test_expect_success 'invalid bool (--get)' '
- git config bool.nobool foobar &&
+ git config ${mode_set} bool.nobool foobar &&
test_must_fail git config --bool --get bool.nobool'
test_expect_success 'invalid bool (set)' '
@@ -1071,7 +1105,7 @@ test_expect_success 'get --expiry-date' '
test_expect_success 'get --type=color' '
rm .git/config &&
- git config foo.color "red" &&
+ git config ${mode_set} foo.color "red" &&
git config --get --type=color foo.color >actual.raw &&
test_decode_color <actual.raw >actual &&
echo "<RED>" >expect &&
@@ -1108,18 +1142,18 @@ cat > expect << EOF
EOF
test_expect_success 'quoting' '
rm -f .git/config &&
- git config quote.leading " test" &&
- git config quote.ending "test " &&
- git config quote.semicolon "test;test" &&
- git config quote.hash "test#test" &&
+ git config ${mode_set} quote.leading " test" &&
+ git config ${mode_set} quote.ending "test " &&
+ git config ${mode_set} quote.semicolon "test;test" &&
+ git config ${mode_set} quote.hash "test#test" &&
test_cmp expect .git/config
'
test_expect_success 'key with newline' '
- test_must_fail git config "key.with
+ test_must_fail git config ${mode_get} "key.with
newline" 123'
-test_expect_success 'value with newline' 'git config key.sub value.with\\\
+test_expect_success 'value with newline' 'git config ${mode_set} key.sub value.with\\\
newline'
cat > .git/config <<\EOF
@@ -1139,7 +1173,7 @@ section.quotecont=cont;inued
EOF
test_expect_success 'value continued on next line' '
- git config --list > result &&
+ git config ${mode_prefix}list > result &&
test_cmp expect result
'
@@ -1163,14 +1197,14 @@ Qsection.sub=section.val4
Qsection.sub=section.val5Q
EOF
test_expect_success '--null --list' '
- git config --null --list >result.raw &&
+ git config ${mode_prefix}list --null >result.raw &&
nul_to_q <result.raw >result &&
echo >>result &&
test_cmp expect result
'
test_expect_success '--null --get-regexp' '
- git config --null --get-regexp "val[0-9]" >result.raw &&
+ git config ${mode_get_regexp} --null "val[0-9]" >result.raw &&
nul_to_q <result.raw >result &&
echo >>result &&
test_cmp expect result
@@ -1178,26 +1212,27 @@ test_expect_success '--null --get-regexp' '
test_expect_success 'inner whitespace kept verbatim, spaces only' '
echo "foo bar" >expect &&
- git config section.val "foo bar" &&
- git config --get section.val >actual &&
+ git config ${mode_set} section.val "foo bar" &&
+ git config ${mode_get} section.val >actual &&
test_cmp expect actual
'
test_expect_success 'inner whitespace kept verbatim, horizontal tabs only' '
echo "fooQQbar" | q_to_tab >expect &&
- git config section.val "$(cat expect)" &&
- git config --get section.val >actual &&
+ git config ${mode_set} section.val "$(cat expect)" &&
+ git config ${mode_get} section.val >actual &&
test_cmp expect actual
'
test_expect_success 'inner whitespace kept verbatim, horizontal tabs and spaces' '
echo "foo Q bar" | q_to_tab >expect &&
- git config section.val "$(cat expect)" &&
- git config --get section.val >actual &&
+ git config ${mode_set} section.val "$(cat expect)" &&
+ git config ${mode_get} section.val >actual &&
test_cmp expect actual
'
test_expect_success SYMLINKS 'symlinked configuration' '
+ test_when_finished "rm myconfig" &&
ln -s notyet myconfig &&
git config --file=myconfig test.frotz nitfol &&
test -h myconfig &&
@@ -1218,10 +1253,11 @@ test_expect_success SYMLINKS 'symlinked configuration' '
'
test_expect_success SYMLINKS 'symlink to nonexistent configuration' '
+ test_when_finished "rm linktonada linktolinktonada" &&
ln -s doesnotexist linktonada &&
ln -s linktonada linktolinktonada &&
- test_must_fail git config --file=linktonada --list &&
- test_must_fail git config --file=linktolinktonada --list
+ test_must_fail git config ${mode_prefix}list --file=linktonada &&
+ test_must_fail git config ${mode_prefix}list --file=linktolinktonada
'
test_expect_success 'check split_cmdline return' '
@@ -1229,12 +1265,12 @@ test_expect_success 'check split_cmdline return' '
git init repo &&
(
cd repo &&
- git config alias.split-cmdline-fix "echo \"" &&
+ git config ${mode_set} 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 \"" &&
+ git config ${mode_set} branch.main.mergeoptions "echo \"" &&
test_must_fail git merge main
)
'
@@ -1266,18 +1302,18 @@ test_expect_success 'git -c can represent empty string' '
'
test_expect_success 'key sanity-checking' '
- test_must_fail git config foo=bar &&
- test_must_fail git config foo=.bar &&
- test_must_fail git config foo.ba=r &&
- test_must_fail git config foo.1bar &&
- test_must_fail git config foo."ba
+ test_must_fail git config ${mode_get} foo=bar &&
+ test_must_fail git config ${mode_get} foo=.bar &&
+ test_must_fail git config ${mode_get} foo.ba=r &&
+ test_must_fail git config ${mode_get} foo.1bar &&
+ test_must_fail git config ${mode_get} foo."ba
z".bar &&
- test_must_fail git config . false &&
- test_must_fail git config .foo false &&
- test_must_fail git config foo. false &&
- test_must_fail git config .foo. false &&
- git config foo.bar true &&
- git config foo."ba =z".bar false
+ test_must_fail git config ${mode_set} . false &&
+ test_must_fail git config ${mode_set} .foo false &&
+ test_must_fail git config ${mode_set} foo. false &&
+ test_must_fail git config ${mode_set} .foo. false &&
+ git config ${mode_set} foo.bar true &&
+ git config ${mode_set} foo."ba =z".bar false
'
test_expect_success 'git -c works with aliases of builtins' '
@@ -1319,7 +1355,7 @@ test_expect_success 'git -c complains about empty key and value' '
'
test_expect_success 'multiple git -c appends config' '
- test_config alias.x "!git -c x.two=2 config --get-regexp ^x\.*" &&
+ test_config alias.x "!git -c x.two=2 config ${mode_get_regexp} ^x\.*" &&
cat >expect <<-\EOF &&
x.one 1
x.two 2
@@ -1478,14 +1514,14 @@ do
done
test_expect_success 'git -c is not confused by empty environment' '
- GIT_CONFIG_PARAMETERS="" git -c x.one=1 config --list
+ GIT_CONFIG_PARAMETERS="" git -c x.one=1 config ${mode_prefix}list
'
test_expect_success 'GIT_CONFIG_PARAMETERS handles old-style entries' '
v="${SQ}key.one=foo${SQ}" &&
v="$v ${SQ}key.two=bar${SQ}" &&
v="$v ${SQ}key.ambiguous=section.whatever=value${SQ}" &&
- GIT_CONFIG_PARAMETERS=$v git config --get-regexp "key.*" >actual &&
+ GIT_CONFIG_PARAMETERS=$v git config ${mode_get_regexp} "key.*" >actual &&
cat >expect <<-EOF &&
key.one foo
key.two bar
@@ -1498,7 +1534,7 @@ test_expect_success 'GIT_CONFIG_PARAMETERS handles new-style entries' '
v="${SQ}key.one${SQ}=${SQ}foo${SQ}" &&
v="$v ${SQ}key.two${SQ}=${SQ}bar${SQ}" &&
v="$v ${SQ}key.ambiguous=section.whatever${SQ}=${SQ}value${SQ}" &&
- GIT_CONFIG_PARAMETERS=$v git config --get-regexp "key.*" >actual &&
+ GIT_CONFIG_PARAMETERS=$v git config ${mode_get_regexp} "key.*" >actual &&
cat >expect <<-EOF &&
key.one foo
key.two bar
@@ -1512,7 +1548,7 @@ test_expect_success 'old and new-style entries can mix' '
v="$v ${SQ}key.newone${SQ}=${SQ}newfoo${SQ}" &&
v="$v ${SQ}key.oldtwo=oldbar${SQ}" &&
v="$v ${SQ}key.newtwo${SQ}=${SQ}newbar${SQ}" &&
- GIT_CONFIG_PARAMETERS=$v git config --get-regexp "key.*" >actual &&
+ GIT_CONFIG_PARAMETERS=$v git config ${mode_get_regexp} "key.*" >actual &&
cat >expect <<-EOF &&
key.oldone oldfoo
key.newone newfoo
@@ -1525,7 +1561,7 @@ test_expect_success 'old and new-style entries can mix' '
test_expect_success 'old and new bools with ambiguous subsection' '
v="${SQ}key.with=equals.oldbool${SQ}" &&
v="$v ${SQ}key.with=equals.newbool${SQ}=" &&
- GIT_CONFIG_PARAMETERS=$v git config --get-regexp "key.*" >actual &&
+ GIT_CONFIG_PARAMETERS=$v git config ${mode_get_regexp} "key.*" >actual &&
cat >expect <<-EOF &&
key.with equals.oldbool
key.with=equals.newbool
@@ -1539,7 +1575,7 @@ test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '
env.two two
EOF
GIT_CONFIG_PARAMETERS="${SQ}env.one=one${SQ} ${SQ}env.two=two${SQ}" \
- git config --get-regexp "env.*" >actual &&
+ git config ${mode_get_regexp} "env.*" >actual &&
test_cmp expect actual &&
cat >expect <<-EOF &&
@@ -1547,12 +1583,12 @@ test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '
env.two two
EOF
GIT_CONFIG_PARAMETERS="${SQ}env.one=one${SQ}\\$SQ$SQ$SQ ${SQ}env.two=two${SQ}" \
- git config --get-regexp "env.*" >actual &&
+ git config ${mode_get_regexp} "env.*" >actual &&
test_cmp expect actual &&
test_must_fail env \
GIT_CONFIG_PARAMETERS="${SQ}env.one=one${SQ}\\$SQ ${SQ}env.two=two${SQ}" \
- git config --get-regexp "env.*"
+ git config ${mode_get_regexp} "env.*"
'
test_expect_success 'git --config-env=key=envvar support' '
@@ -1600,7 +1636,7 @@ test_expect_success 'git -c and --config-env work together' '
ENVVAR=env-value git \
-c bar.cmd=cmd-value \
--config-env=bar.env=ENVVAR \
- config --get-regexp "^bar.*" >actual &&
+ config ${mode_get_regexp} "^bar.*" >actual &&
test_cmp expect actual
'
@@ -1628,7 +1664,7 @@ test_expect_success 'git config handles environment config pairs' '
GIT_CONFIG_COUNT=2 \
GIT_CONFIG_KEY_0="pair.one" GIT_CONFIG_VALUE_0="foo" \
GIT_CONFIG_KEY_1="pair.two" GIT_CONFIG_VALUE_1="bar" \
- git config --get-regexp "pair.*" >actual &&
+ git config ${mode_get_regexp} "pair.*" >actual &&
cat >expect <<-EOF &&
pair.one foo
pair.two bar
@@ -1638,7 +1674,7 @@ test_expect_success 'git config handles environment config pairs' '
test_expect_success 'git config ignores pairs without count' '
test_must_fail env GIT_CONFIG_KEY_0="pair.one" GIT_CONFIG_VALUE_0="value" \
- git config pair.one 2>error &&
+ git config ${mode_get} pair.one 2>error &&
test_must_be_empty error
'
@@ -1646,7 +1682,7 @@ test_expect_success 'git config ignores pairs exceeding count' '
GIT_CONFIG_COUNT=1 \
GIT_CONFIG_KEY_0="pair.one" GIT_CONFIG_VALUE_0="value" \
GIT_CONFIG_KEY_1="pair.two" GIT_CONFIG_VALUE_1="value" \
- git config --get-regexp "pair.*" >actual 2>error &&
+ git config ${mode_get_regexp} "pair.*" >actual 2>error &&
cat >expect <<-EOF &&
pair.one value
EOF
@@ -1657,43 +1693,43 @@ test_expect_success 'git config ignores pairs exceeding count' '
test_expect_success 'git config ignores pairs with zero count' '
test_must_fail env \
GIT_CONFIG_COUNT=0 GIT_CONFIG_KEY_0="pair.one" GIT_CONFIG_VALUE_0="value" \
- git config pair.one 2>error &&
+ git config ${mode_get} pair.one 2>error &&
test_must_be_empty error
'
test_expect_success 'git config ignores pairs with empty count' '
test_must_fail env \
GIT_CONFIG_COUNT= GIT_CONFIG_KEY_0="pair.one" GIT_CONFIG_VALUE_0="value" \
- git config pair.one 2>error &&
+ git config ${mode_get} pair.one 2>error &&
test_must_be_empty error
'
test_expect_success 'git config fails with invalid count' '
- test_must_fail env GIT_CONFIG_COUNT=10a git config --list 2>error &&
+ test_must_fail env GIT_CONFIG_COUNT=10a git config ${mode_prefix}list 2>error &&
test_grep "bogus count" error &&
- test_must_fail env GIT_CONFIG_COUNT=9999999999999999 git config --list 2>error &&
+ test_must_fail env GIT_CONFIG_COUNT=9999999999999999 git config ${mode_prefix}list 2>error &&
test_grep "too many entries" error
'
test_expect_success 'git config fails with missing config key' '
test_must_fail env GIT_CONFIG_COUNT=1 GIT_CONFIG_VALUE_0="value" \
- git config --list 2>error &&
+ git config ${mode_prefix}list 2>error &&
test_grep "missing config key" error
'
test_expect_success 'git config fails with missing config value' '
test_must_fail env GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0="pair.one" \
- git config --list 2>error &&
+ git config ${mode_prefix}list 2>error &&
test_grep "missing config value" error
'
test_expect_success 'git config fails with invalid config pair key' '
test_must_fail env GIT_CONFIG_COUNT=1 \
GIT_CONFIG_KEY_0= GIT_CONFIG_VALUE_0=value \
- git config --list &&
+ git config ${mode_prefix}list &&
test_must_fail env GIT_CONFIG_COUNT=1 \
GIT_CONFIG_KEY_0=missing-section GIT_CONFIG_VALUE_0=value \
- git config --list
+ git config ${mode_prefix}list
'
test_expect_success 'environment overrides config file' '
@@ -1703,7 +1739,7 @@ test_expect_success 'environment overrides config file' '
one = value
EOF
GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=pair.one GIT_CONFIG_VALUE_0=override \
- git config pair.one >actual &&
+ git config ${mode_get} pair.one >actual &&
cat >expect <<-EOF &&
override
EOF
@@ -1713,7 +1749,7 @@ test_expect_success 'environment overrides config file' '
test_expect_success 'GIT_CONFIG_PARAMETERS overrides environment config' '
GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=pair.one GIT_CONFIG_VALUE_0=value \
GIT_CONFIG_PARAMETERS="${SQ}pair.one=override${SQ}" \
- git config pair.one >actual &&
+ git config ${mode_get} pair.one >actual &&
cat >expect <<-EOF &&
override
EOF
@@ -1732,8 +1768,8 @@ test_expect_success 'command line overrides environment config' '
test_expect_success 'git config --edit works' '
git config -f tmp test.value no &&
echo test.value=yes >expect &&
- GIT_EDITOR="echo [test]value=yes >" git config -f tmp --edit &&
- git config -f tmp --list >actual &&
+ GIT_EDITOR="echo [test]value=yes >" git config ${mode_prefix}edit -f tmp &&
+ git config ${mode_prefix}list -f tmp >actual &&
test_cmp expect actual
'
@@ -1741,8 +1777,8 @@ test_expect_success 'git config --edit respects core.editor' '
git config -f tmp test.value no &&
echo test.value=yes >expect &&
test_config core.editor "echo [test]value=yes >" &&
- git config -f tmp --edit &&
- git config -f tmp --list >actual &&
+ git config ${mode_prefix}edit -f tmp &&
+ git config ${mode_prefix}list -f tmp >actual &&
test_cmp expect actual
'
@@ -1788,20 +1824,28 @@ test_expect_success 'urlmatch' '
test_expect_code 1 git config --bool --get-urlmatch doesnt.exist https://good.example.com >actual &&
test_must_be_empty actual &&
+ test_expect_code 1 git config get --url=https://good.example.com --bool doesnt.exist >actual &&
+ test_must_be_empty actual &&
echo true >expect &&
git config --bool --get-urlmatch http.SSLverify https://good.example.com >actual &&
test_cmp expect actual &&
+ git config get --bool --url=https://good.example.com http.SSLverify >actual &&
+ test_cmp expect actual &&
echo false >expect &&
git config --bool --get-urlmatch http.sslverify https://weak.example.com >actual &&
test_cmp expect actual &&
+ git config get --bool --url=https://weak.example.com http.sslverify >actual &&
+ test_cmp expect actual &&
{
echo http.cookiefile /tmp/cookie.txt &&
echo http.sslverify false
} >expect &&
git config --get-urlmatch HTTP https://weak.example.com >actual &&
+ test_cmp expect actual &&
+ git config get --url=https://weak.example.com HTTP >actual &&
test_cmp expect actual
'
@@ -1817,6 +1861,8 @@ test_expect_success 'urlmatch with --show-scope' '
local http.sslverify false
EOF
git config --get-urlmatch --show-scope HTTP https://weak.example.com >actual &&
+ test_cmp expect actual &&
+ git config get --url=https://weak.example.com --show-scope HTTP >actual &&
test_cmp expect actual
'
@@ -1849,45 +1895,67 @@ test_expect_success 'urlmatch favors more specific URLs' '
echo http.cookiefile /tmp/root.txt >expect &&
git config --get-urlmatch HTTP https://example.com >actual &&
test_cmp expect actual &&
+ git config get --url=https://example.com HTTP >actual &&
+ test_cmp expect actual &&
echo http.cookiefile /tmp/subdirectory.txt >expect &&
git config --get-urlmatch HTTP https://example.com/subdirectory >actual &&
test_cmp expect actual &&
+ git config get --url=https://example.com/subdirectory HTTP >actual &&
+ test_cmp expect actual &&
echo http.cookiefile /tmp/subdirectory.txt >expect &&
git config --get-urlmatch HTTP https://example.com/subdirectory/nested >actual &&
test_cmp expect actual &&
+ git config get --url=https://example.com/subdirectory/nested HTTP >actual &&
+ test_cmp expect actual &&
echo http.cookiefile /tmp/user.txt >expect &&
git config --get-urlmatch HTTP https://user@example.com/ >actual &&
test_cmp expect actual &&
+ git config get --url=https://user@example.com/ HTTP >actual &&
+ test_cmp expect actual &&
echo http.cookiefile /tmp/subdirectory.txt >expect &&
git config --get-urlmatch HTTP https://averylonguser@example.com/subdirectory >actual &&
test_cmp expect actual &&
+ git config get --url=https://averylonguser@example.com/subdirectory HTTP >actual &&
+ test_cmp expect actual &&
echo http.cookiefile /tmp/preceding.txt >expect &&
git config --get-urlmatch HTTP https://preceding.example.com >actual &&
test_cmp expect actual &&
+ git config get --url=https://preceding.example.com HTTP >actual &&
+ test_cmp expect actual &&
echo http.cookiefile /tmp/wildcard.txt >expect &&
git config --get-urlmatch HTTP https://wildcard.example.com >actual &&
test_cmp expect actual &&
+ git config get --url=https://wildcard.example.com HTTP >actual &&
+ test_cmp expect actual &&
echo http.cookiefile /tmp/sub.txt >expect &&
git config --get-urlmatch HTTP https://sub.example.com/wildcardwithsubdomain >actual &&
test_cmp expect actual &&
+ git config get --url=https://sub.example.com/wildcardwithsubdomain HTTP >actual &&
+ test_cmp expect actual &&
echo http.cookiefile /tmp/trailing.txt >expect &&
git config --get-urlmatch HTTP https://trailing.example.com >actual &&
test_cmp expect actual &&
+ git config get --url=https://trailing.example.com HTTP >actual &&
+ test_cmp expect actual &&
echo http.cookiefile /tmp/sub.txt >expect &&
git config --get-urlmatch HTTP https://user@sub.example.com >actual &&
test_cmp expect actual &&
+ git config get --url=https://user@sub.example.com HTTP >actual &&
+ test_cmp expect actual &&
echo http.cookiefile /tmp/multiwildcard.txt >expect &&
git config --get-urlmatch HTTP https://wildcard.example.org >actual &&
+ test_cmp expect actual &&
+ git config get --url=https://wildcard.example.org HTTP >actual &&
test_cmp expect actual
'
@@ -1954,7 +2022,7 @@ test_expect_success '--unset last key removes section (except if commented)' '
# please be careful when you update the above variable
EOF
- git config --unset section.key &&
+ git config ${mode_unset} section.key &&
test_cmp expect .git/config &&
cat >.git/config <<-\EOF &&
@@ -1967,7 +2035,7 @@ test_expect_success '--unset last key removes section (except if commented)' '
[next-section]
EOF
- git config --unset section.key &&
+ git config ${mode_unset} section.key &&
test_cmp expect .git/config &&
q_to_tab >.git/config <<-\EOF &&
@@ -1977,7 +2045,7 @@ test_expect_success '--unset last key removes section (except if commented)' '
[two]
key = true
EOF
- git config --unset two.key &&
+ git config ${mode_unset} two.key &&
! grep two .git/config &&
q_to_tab >.git/config <<-\EOF &&
@@ -1987,7 +2055,7 @@ test_expect_success '--unset last key removes section (except if commented)' '
[one]
key = true
EOF
- git config --unset-all one.key &&
+ git config ${mode_unset_all} one.key &&
test_line_count = 0 .git/config &&
q_to_tab >.git/config <<-\EOF &&
@@ -1997,7 +2065,7 @@ test_expect_success '--unset last key removes section (except if commented)' '
[two]
Qkey = true
EOF
- git config --unset two.key &&
+ git config ${mode_unset} two.key &&
grep two .git/config &&
q_to_tab >.git/config <<-\EOF &&
@@ -2009,8 +2077,8 @@ test_expect_success '--unset last key removes section (except if commented)' '
[TWO "subsection"]
[one]
EOF
- git config --unset two.subsection.key &&
- test "not [two subsection]" = "$(git config one.key)" &&
+ git config ${mode_unset} two.subsection.key &&
+ test "not [two subsection]" = "$(git config ${mode_get} one.key)" &&
test_line_count = 3 .git/config
'
@@ -2021,7 +2089,7 @@ test_expect_success '--unset-all removes section if empty & uncommented' '
key = value2
EOF
- git config --unset-all section.key &&
+ git config ${mode_unset_all} section.key &&
test_line_count = 0 .git/config
'
@@ -2044,7 +2112,7 @@ test_expect_success POSIXPERM,PERL 'preserves existing permissions' '
git config imap.pass Hunter2 &&
perl -e \
"die q(badset) if ((stat(q(.git/config)))[2] & 07777) != 0600" &&
- git config --rename-section imap pop &&
+ git config ${mode_prefix}rename-section imap pop &&
perl -e \
"die q(badrename) if ((stat(q(.git/config)))[2] & 07777) != 0600"
'
@@ -2093,7 +2161,7 @@ test_expect_success '--show-origin with --list' '
command line: user.cmdline=true
EOF
GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=user.environ GIT_CONFIG_VALUE_0=true\
- git -c user.cmdline=true config --list --show-origin >output &&
+ git -c user.cmdline=true config ${mode_prefix}list --show-origin >output &&
test_cmp expect output
'
@@ -2110,7 +2178,7 @@ test_expect_success '--show-origin with --list --null' '
includeQcommand line:Quser.cmdline
trueQ
EOF
- git -c user.cmdline=true config --null --list --show-origin >output.raw &&
+ git -c user.cmdline=true config ${mode_prefix}list --null --show-origin >output.raw &&
nul_to_q <output.raw >output &&
# The here-doc above adds a newline that the --null output would not
# include. Add it here to make the two comparable.
@@ -2124,7 +2192,7 @@ test_expect_success '--show-origin with single file' '
file:.git/config user.override=local
file:.git/config include.path=../include/relative.include
EOF
- git config --local --list --show-origin >output &&
+ git config ${mode_prefix}list --local --show-origin >output &&
test_cmp expect output
'
@@ -2133,7 +2201,7 @@ test_expect_success '--show-origin with --get-regexp' '
file:$HOME/.gitconfig user.global true
file:.git/config user.local true
EOF
- git config --show-origin --get-regexp "user\.[g|l].*" >output &&
+ git config ${mode_get_regexp} --show-origin "user\.[g|l].*" >output &&
test_cmp expect output
'
@@ -2141,7 +2209,7 @@ test_expect_success '--show-origin getting a single key' '
cat >expect <<-\EOF &&
file:.git/config local
EOF
- git config --show-origin user.override >output &&
+ git config ${mode_get} --show-origin user.override >output &&
test_cmp expect output
'
@@ -2162,7 +2230,7 @@ test_expect_success !MINGW '--show-origin escape special file name characters' '
cat >expect <<-\EOF &&
file:"file\" (dq) and spaces.conf" user.custom=true
EOF
- git config --file "$WEIRDLY_NAMED_FILE" --show-origin --list >output &&
+ git config ${mode_prefix}list --file "$WEIRDLY_NAMED_FILE" --show-origin >output &&
test_cmp expect output
'
@@ -2170,7 +2238,7 @@ test_expect_success '--show-origin stdin' '
cat >expect <<-\EOF &&
standard input: user.custom=true
EOF
- git config --file - --show-origin --list <"$CUSTOM_CONFIG_FILE" >output &&
+ git config ${mode_prefix}list --file - --show-origin <"$CUSTOM_CONFIG_FILE" >output &&
test_cmp expect output
'
@@ -2197,7 +2265,7 @@ test_expect_success '--show-origin blob' '
cat >expect <<-EOF &&
blob:$blob user.custom=true
EOF
- git config --blob=$blob --show-origin --list >output &&
+ git config ${mode_prefix}list --blob=$blob --show-origin >output &&
test_cmp expect output
)
'
@@ -2213,7 +2281,7 @@ test_expect_success '--show-origin blob ref' '
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 &&
+ git config ${mode_prefix}list --blob=main:custom.conf --show-origin >output &&
test_cmp expect output
)
'
@@ -2239,13 +2307,14 @@ test_expect_success '--show-scope with --list' '
worktree user.worktree=true
command user.cmdline=true
EOF
+ test_when_finished "git worktree remove wt1" &&
git worktree add wt1 &&
# We need these to test for worktree scope, but outside of this
# test, this is just noise
test_config core.repositoryformatversion 1 &&
test_config extensions.worktreeConfig true &&
git config --worktree user.worktree true &&
- git -c user.cmdline=true config --list --show-scope >output &&
+ git -c user.cmdline=true config ${mode_prefix}list --show-scope >output &&
test_cmp expect output
'
@@ -2254,7 +2323,7 @@ test_expect_success !MINGW '--show-scope with --blob' '
cat >expect <<-EOF &&
command user.custom=true
EOF
- git config --blob=$blob --show-scope --list >output &&
+ git config ${mode_prefix}list --blob=$blob --show-scope >output &&
test_cmp expect output
'
@@ -2264,7 +2333,7 @@ test_expect_success '--show-scope with --local' '
local user.override=local
local include.path=../include/relative.include
EOF
- git config --local --list --show-scope >output &&
+ git config ${mode_prefix}list --local --show-scope >output &&
test_cmp expect output
'
@@ -2272,7 +2341,7 @@ test_expect_success '--show-scope getting a single value' '
cat >expect <<-\EOF &&
local true
EOF
- git config --show-scope --get user.local >output &&
+ git config ${mode_get} --show-scope user.local >output &&
test_cmp expect output
'
@@ -2288,7 +2357,7 @@ test_expect_success '--show-scope with --show-origin' '
local file:.git/../include/relative.include user.relative=include
command command line: user.cmdline=true
EOF
- git -c user.cmdline=true config --list --show-origin --show-scope >output &&
+ git -c user.cmdline=true config ${mode_prefix}list --show-origin --show-scope >output &&
test_cmp expect output
'
@@ -2329,7 +2398,7 @@ test_expect_success 'override global and system config' '
global home.config=true
local local.config=true
EOF
- git config --show-scope --list >output &&
+ git config ${mode_prefix}list --show-scope >output &&
test_cmp expect output &&
cat >expect <<-EOF &&
@@ -2338,20 +2407,20 @@ test_expect_success 'override global and system config' '
local local.config=true
EOF
GIT_CONFIG_NOSYSTEM=false GIT_CONFIG_SYSTEM=custom-system-config GIT_CONFIG_GLOBAL=custom-global-config \
- git config --show-scope --list >output &&
+ git config ${mode_prefix}list --show-scope >output &&
test_cmp expect output &&
cat >expect <<-EOF &&
local local.config=true
EOF
GIT_CONFIG_NOSYSTEM=false GIT_CONFIG_SYSTEM=/dev/null GIT_CONFIG_GLOBAL=/dev/null \
- git config --show-scope --list >output &&
+ git config ${mode_prefix}list --show-scope >output &&
test_cmp expect output
'
test_expect_success 'override global and system config with missing file' '
- test_must_fail env GIT_CONFIG_GLOBAL=does-not-exist GIT_CONFIG_SYSTEM=/dev/null git config --global --list &&
- test_must_fail env GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=does-not-exist git config --system --list &&
+ test_must_fail env GIT_CONFIG_GLOBAL=does-not-exist GIT_CONFIG_SYSTEM=/dev/null git config ${mode_prefix}list --global &&
+ test_must_fail env GIT_CONFIG_GLOBAL=/dev/null GIT_CONFIG_SYSTEM=does-not-exist git config ${mode_prefix}list --system &&
GIT_CONFIG_GLOBAL=does-not-exist GIT_CONFIG_SYSTEM=does-not-exist git version
'
@@ -2467,7 +2536,7 @@ test_expect_success '--replace-all does not invent newlines' '
[abc]
Qkey = b
EOF
- git config --replace-all abc.key b &&
+ git config ${mode_replace_all} abc.key b &&
test_cmp expect .git/config
'
@@ -2478,7 +2547,7 @@ test_expect_success 'set all config with value-pattern' '
# no match => add new entry
cp initial config &&
git config --file=config abc.key two a+ &&
- git config --file=config --list >actual &&
+ git config ${mode_prefix}list --file=config >actual &&
cat >expect <<-\EOF &&
abc.key=one
abc.key=two
@@ -2491,7 +2560,7 @@ test_expect_success 'set all config with value-pattern' '
# multiple values, no match => add
git config --file=config abc.key three a+ &&
- git config --file=config --list >actual &&
+ git config ${mode_prefix}list --file=config >actual &&
cat >expect <<-\EOF &&
abc.key=one
abc.key=two
@@ -2501,7 +2570,7 @@ test_expect_success 'set all config with value-pattern' '
# single match => replace
git config --file=config abc.key four h+ &&
- git config --file=config --list >actual &&
+ git config ${mode_prefix}list --file=config >actual &&
cat >expect <<-\EOF &&
abc.key=one
abc.key=two
@@ -2516,7 +2585,7 @@ test_expect_success '--replace-all and value-pattern' '
git config --file=config --add abc.key two &&
git config --file=config --add abc.key three &&
git config --file=config --replace-all abc.key four "o+" &&
- git config --file=config --list >actual &&
+ git config ${mode_prefix}list --file=config >actual &&
cat >expect <<-\EOF &&
abc.key=four
abc.key=three
@@ -2532,20 +2601,20 @@ test_expect_success 'refuse --fixed-value for incompatible actions' '
test_must_fail git config --file=config --fixed-value --add dev.null bogus &&
test_must_fail git config --file=config --fixed-value --get-urlmatch dev.null bogus &&
test_must_fail git config --file=config --fixed-value --get-urlmatch dev.null bogus &&
- test_must_fail git config --file=config --fixed-value --rename-section dev null &&
- test_must_fail git config --file=config --fixed-value --remove-section dev &&
- test_must_fail git config --file=config --fixed-value --list &&
+ test_must_fail git config ${mode_prefix}rename-section --file=config --fixed-value dev null &&
+ test_must_fail git config ${mode_prefix}remove-section --file=config --fixed-value dev &&
+ test_must_fail git config ${mode_prefix}list --file=config --fixed-value &&
test_must_fail git config --file=config --fixed-value --get-color dev.null &&
test_must_fail git config --file=config --fixed-value --get-colorbool dev.null &&
# These modes complain when --fixed-value has no value-pattern
- test_must_fail git config --file=config --fixed-value dev.null bogus &&
- test_must_fail git config --file=config --fixed-value --replace-all dev.null bogus &&
- test_must_fail git config --file=config --fixed-value --get dev.null &&
- test_must_fail git config --file=config --fixed-value --get-all dev.null &&
- test_must_fail git config --file=config --fixed-value --get-regexp "dev.*" &&
- test_must_fail git config --file=config --fixed-value --unset dev.null &&
- test_must_fail git config --file=config --fixed-value --unset-all dev.null
+ test_must_fail git config ${mode_set} --file=config --fixed-value dev.null bogus &&
+ test_must_fail git config ${mode_replace_all} --file=config --fixed-value dev.null bogus &&
+ test_must_fail git config ${mode_prefix}get --file=config --fixed-value dev.null &&
+ test_must_fail git config ${mode_get_all} --file=config --fixed-value dev.null &&
+ test_must_fail git config ${mode_get_regexp} --file=config --fixed-value "dev.*" &&
+ test_must_fail git config ${mode_unset} --file=config --fixed-value dev.null &&
+ test_must_fail git config ${mode_unset_all} --file=config --fixed-value dev.null
'
test_expect_success '--fixed-value uses exact string matching' '
@@ -2555,7 +2624,7 @@ test_expect_success '--fixed-value uses exact string matching' '
cp initial config &&
git config --file=config fixed.test bogus "$META" &&
- git config --file=config --list >actual &&
+ git config ${mode_prefix}list --file=config >actual &&
cat >expect <<-EOF &&
fixed.test=$META
fixed.test=bogus
@@ -2564,7 +2633,7 @@ test_expect_success '--fixed-value uses exact string matching' '
cp initial config &&
git config --file=config --fixed-value fixed.test bogus "$META" &&
- git config --file=config --list >actual &&
+ git config ${mode_prefix}list --file=config >actual &&
cat >expect <<-\EOF &&
fixed.test=bogus
EOF
@@ -2573,16 +2642,21 @@ test_expect_success '--fixed-value uses exact string matching' '
cp initial config &&
test_must_fail git config --file=config --unset fixed.test "$META" &&
git config --file=config --fixed-value --unset fixed.test "$META" &&
- test_must_fail git config --file=config fixed.test &&
+ test_must_fail git config ${mode_get} --file=config fixed.test &&
+
+ cp initial config &&
+ test_must_fail git config unset --file=config --value="$META" fixed.test &&
+ git config unset --file=config --fixed-value --value="$META" fixed.test &&
+ test_must_fail git config ${mode_get} --file=config fixed.test &&
cp initial config &&
test_must_fail git config --file=config --unset-all fixed.test "$META" &&
git config --file=config --fixed-value --unset-all fixed.test "$META" &&
- test_must_fail git config --file=config fixed.test &&
+ test_must_fail git config ${mode_get} --file=config fixed.test &&
cp initial config &&
- git config --file=config --replace-all fixed.test bogus "$META" &&
- git config --file=config --list >actual &&
+ git config --file=config fixed.test bogus "$META" &&
+ git config ${mode_prefix}list --file=config >actual &&
cat >expect <<-EOF &&
fixed.test=$META
fixed.test=bogus
@@ -2590,7 +2664,7 @@ test_expect_success '--fixed-value uses exact string matching' '
test_cmp expect actual &&
git config --file=config --fixed-value --replace-all fixed.test bogus "$META" &&
- git config --file=config --list >actual &&
+ git config ${mode_prefix}list --file=config >actual &&
cat >expect <<-EOF &&
fixed.test=bogus
fixed.test=bogus
@@ -2605,18 +2679,27 @@ test_expect_success '--get and --get-all with --fixed-value' '
git config --file=config --add fixed.test "$META" &&
git config --file=config --get fixed.test bogus &&
+ git config get --file=config --value=bogus fixed.test &&
test_must_fail git config --file=config --get fixed.test "$META" &&
+ test_must_fail git config get --file=config --value="$META" fixed.test &&
git config --file=config --get --fixed-value fixed.test "$META" &&
+ git config get --file=config --fixed-value --value="$META" fixed.test &&
test_must_fail git config --file=config --get --fixed-value fixed.test non-existent &&
git config --file=config --get-all fixed.test bogus &&
+ git config get --all --file=config --value=bogus fixed.test &&
test_must_fail git config --file=config --get-all fixed.test "$META" &&
+ test_must_fail git config get --all --file=config --value="$META" fixed.test &&
git config --file=config --get-all --fixed-value fixed.test "$META" &&
+ git config get --all --file=config --value="$META" --fixed-value fixed.test &&
test_must_fail git config --file=config --get-all --fixed-value fixed.test non-existent &&
git config --file=config --get-regexp fixed+ bogus &&
+ git config get --regexp --file=config --value=bogus fixed+ &&
test_must_fail git config --file=config --get-regexp fixed+ "$META" &&
+ test_must_fail git config get --regexp --file=config --value="$META" fixed+ &&
git config --file=config --get-regexp --fixed-value fixed+ "$META" &&
+ git config get --regexp --file=config --fixed-value --value="$META" fixed+ &&
test_must_fail git config --file=config --get-regexp --fixed-value fixed+ non-existent
'
@@ -2738,4 +2821,19 @@ test_expect_success 'includeIf.hasconfig:remote.*.url forbids remote url in such
grep "fatal: remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote.*.url" err
'
+test_expect_success 'negated mode causes failure' '
+ test_must_fail git config --no-get 2>err &&
+ grep "unknown option \`no-get${SQ}" err
+'
+
+test_expect_success 'specifying multiple modes causes failure' '
+ cat >expect <<-EOF &&
+ error: options ${SQ}--get-all${SQ} and ${SQ}--get${SQ} cannot be used together
+ EOF
+ test_must_fail git config --get --get-all 2>err &&
+ test_cmp expect err
+'
+
+done
+
test_done
diff --git a/t/t1404-update-ref-errors.sh b/t/t1404-update-ref-errors.sh
index 98e9158bd2..67ebd81a4c 100755
--- a/t/t1404-update-ref-errors.sh
+++ b/t/t1404-update-ref-errors.sh
@@ -100,7 +100,7 @@ df_test() {
printf "%s\n" "delete $delname" "create $addname $D"
fi >commands &&
test_must_fail git update-ref --stdin <commands 2>output.err &&
- grep "fatal:\( cannot lock ref $SQ$addname$SQ:\)\? $SQ$delref$SQ exists; cannot create $SQ$addref$SQ" output.err &&
+ grep -E "fatal:( cannot lock ref $SQ$addname$SQ:)? $SQ$delref$SQ exists; cannot create $SQ$addref$SQ" output.err &&
printf "%s\n" "$C $delref" >expected-refs &&
git for-each-ref --format="%(objectname) %(refname)" $prefix/r >actual-refs &&
test_cmp expected-refs actual-refs
diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh
index 2092488090..067fd57290 100755
--- a/t/t1416-ref-transaction-hooks.sh
+++ b/t/t1416-ref-transaction-hooks.sh
@@ -134,4 +134,27 @@ test_expect_success 'interleaving hook calls succeed' '
test_cmp expect target-repo.git/actual
'
+test_expect_success 'hook captures git-symbolic-ref updates' '
+ test_when_finished "rm actual" &&
+
+ test_hook reference-transaction <<-\EOF &&
+ echo "$*" >>actual
+ while read -r line
+ do
+ printf "%s\n" "$line"
+ done >>actual
+ EOF
+
+ git symbolic-ref refs/heads/symref refs/heads/main &&
+
+ cat >expect <<-EOF &&
+ prepared
+ $ZERO_OID ref:refs/heads/main refs/heads/symref
+ committed
+ $ZERO_OID ref:refs/heads/main refs/heads/symref
+ EOF
+
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh
index 8a456b1142..173b4fafad 100755
--- a/t/t1450-fsck.sh
+++ b/t/t1450-fsck.sh
@@ -1060,4 +1060,41 @@ test_expect_success 'fsck reports problems in current worktree index without fil
test_cmp expect actual
'
+test_expect_success 'fsck warning on symlink target with excessive length' '
+ symlink_target=$(printf "pattern %032769d" 1 | git hash-object -w --stdin) &&
+ test_when_finished "remove_object $symlink_target" &&
+ tree=$(printf "120000 blob %s\t%s\n" $symlink_target symlink | git mktree) &&
+ test_when_finished "remove_object $tree" &&
+ cat >expected <<-EOF &&
+ warning in blob $symlink_target: symlinkTargetLength: symlink target too long
+ EOF
+ git fsck --no-dangling >actual 2>&1 &&
+ test_cmp expected actual
+'
+
+test_expect_success 'fsck warning on symlink target pointing inside git dir' '
+ gitdir=$(printf ".git" | git hash-object -w --stdin) &&
+ ntfs_gitdir=$(printf "GIT~1" | git hash-object -w --stdin) &&
+ hfs_gitdir=$(printf ".${u200c}git" | git hash-object -w --stdin) &&
+ inside_gitdir=$(printf "nested/.git/config" | git hash-object -w --stdin) &&
+ benign_target=$(printf "legit/config" | git hash-object -w --stdin) &&
+ tree=$(printf "120000 blob %s\t%s\n" \
+ $benign_target benign_target \
+ $gitdir gitdir \
+ $hfs_gitdir hfs_gitdir \
+ $inside_gitdir inside_gitdir \
+ $ntfs_gitdir ntfs_gitdir |
+ git mktree) &&
+ for o in $gitdir $ntfs_gitdir $hfs_gitdir $inside_gitdir $benign_target $tree
+ do
+ test_when_finished "remove_object $o" || return 1
+ done &&
+ printf "warning in blob %s: symlinkPointsToGitDir: symlink target points to git dir\n" \
+ $gitdir $hfs_gitdir $inside_gitdir $ntfs_gitdir |
+ sort >expected &&
+ git fsck --no-dangling >actual 2>&1 &&
+ sort actual >actual.sorted &&
+ test_cmp expected actual.sorted
+'
+
test_done
diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh
index a669e592f1..30c31918fd 100755
--- a/t/t1500-rev-parse.sh
+++ b/t/t1500-rev-parse.sh
@@ -304,4 +304,10 @@ test_expect_success 'rev-parse --bisect includes bad, excludes good' '
test_cmp expect actual
'
+test_expect_success '--short= truncates to the actual hash length' '
+ git rev-parse HEAD >expect &&
+ git rev-parse --short=100 HEAD >actual &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t1700-split-index.sh b/t/t1700-split-index.sh
index a7b7263b35..ac4a5b2734 100755
--- a/t/t1700-split-index.sh
+++ b/t/t1700-split-index.sh
@@ -527,7 +527,7 @@ test_expect_success 'reading split index at alternate location' '
# ... and, for backwards compatibility, in the current GIT_DIR
# as well.
- mv -v ./reading-alternate-location/.git/sharedindex.* .git &&
+ mv ./reading-alternate-location/.git/sharedindex.* .git &&
GIT_INDEX_FILE=./reading-alternate-location/.git/index \
git ls-files --cached >actual &&
test_cmp expect actual
diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh
index 8b0234cf2d..1894ebeb0e 100755
--- a/t/t1800-hook.sh
+++ b/t/t1800-hook.sh
@@ -185,4 +185,19 @@ test_expect_success 'stdin to hooks' '
test_cmp expect actual
'
+test_expect_success 'clone protections' '
+ test_config core.hooksPath "$(pwd)/my-hooks" &&
+ mkdir -p my-hooks &&
+ write_script my-hooks/test-hook <<-\EOF &&
+ echo Hook ran $1
+ EOF
+
+ git hook run test-hook 2>err &&
+ test_grep "Hook ran" err &&
+ test_must_fail env GIT_CLONE_PROTECTION_ACTIVE=true \
+ git hook run test-hook 2>err &&
+ test_grep "active .core.hooksPath" err &&
+ test_grep ! "Hook ran" err
+'
+
test_done
diff --git a/t/t4026-color.sh b/t/t4026-color.sh
index cc3f60d468..b05f2a9b60 100755
--- a/t/t4026-color.sh
+++ b/t/t4026-color.sh
@@ -96,8 +96,8 @@ test_expect_success '256 colors' '
color "254 bold 255" "[1;38;5;254;48;5;255m"
'
-test_expect_success '24-bit colors' '
- color "#ff00ff black" "[38;2;255;0;255;40m"
+test_expect_success 'RGB colors' '
+ color "#ff00ff #0f0" "[38;2;255;0;255;48;2;0;255;0m"
'
test_expect_success '"default" foreground' '
@@ -112,7 +112,7 @@ test_expect_success '"default" can be combined with attributes' '
color "default default no-reverse bold" "[1;27;39;49m"
'
-test_expect_success '"normal" yields no color at all"' '
+test_expect_success '"normal" yields no color at all' '
color "normal black" "[40m"
'
@@ -140,6 +140,26 @@ test_expect_success 'extra character after attribute' '
invalid_color "dimX"
'
+test_expect_success 'non-hex character in RGB color' '
+ invalid_color "#x23456" &&
+ invalid_color "#1x3456" &&
+ invalid_color "#12x456" &&
+ invalid_color "#123x56" &&
+ invalid_color "#1234x6" &&
+ invalid_color "#12345x" &&
+ invalid_color "#x23" &&
+ invalid_color "#1x3" &&
+ invalid_color "#12x"
+'
+
+test_expect_success 'wrong number of letters in RGB color' '
+ invalid_color "#1" &&
+ invalid_color "#23" &&
+ invalid_color "#789a" &&
+ invalid_color "#bcdef" &&
+ invalid_color "#1234567"
+'
+
test_expect_success 'unknown color slots are ignored (diff)' '
git config color.diff.nosuchslotwilleverbedefined white &&
git diff --color
diff --git a/t/t4046-diff-unmerged.sh b/t/t4046-diff-unmerged.sh
index fb8c51746e..afda629c98 100755
--- a/t/t4046-diff-unmerged.sh
+++ b/t/t4046-diff-unmerged.sh
@@ -98,4 +98,12 @@ test_expect_success 'diff --stat' '
test_cmp diff-stat.expect diff-stat.actual
'
+test_expect_success 'diff --quiet' '
+ test_expect_code 1 git diff --cached --quiet
+'
+
+test_expect_success 'diff --quiet --ignore-all-space' '
+ test_expect_code 1 git diff --cached --quiet --ignore-all-space
+'
+
test_done
diff --git a/t/t4202-log.sh b/t/t4202-log.sh
index 60fe60d761..86c695eb0a 100755
--- a/t/t4202-log.sh
+++ b/t/t4202-log.sh
@@ -2022,7 +2022,7 @@ test_expect_success GPGSM 'log --graph --show-signature x509' '
test_expect_success GPGSSH 'log --graph --show-signature ssh' '
test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
git log --graph --show-signature -n1 signed-ssh >actual &&
- grep "${GOOD_SIGNATURE_TRUSTED}" actual
+ grep "${GPGSSH_GOOD_SIGNATURE_TRUSTED}" actual
'
test_expect_success GPGSSH,GPGSSH_VERIFYTIME 'log shows failure on expired signature key' '
diff --git a/t/t5001-archive-attr.sh b/t/t5001-archive-attr.sh
index eaf959d8f6..7310774af5 100755
--- a/t/t5001-archive-attr.sh
+++ b/t/t5001-archive-attr.sh
@@ -133,7 +133,8 @@ test_expect_success 'git archive vs. bare' '
'
test_expect_success 'git archive with worktree attributes, bare' '
- (cd bare && git archive --worktree-attributes HEAD) >bare-worktree.tar &&
+ (cd bare &&
+ git -c attr.tree=HEAD archive --worktree-attributes HEAD) >bare-worktree.tar &&
(mkdir bare-worktree && cd bare-worktree && "$TAR" xf -) <bare-worktree.tar
'
diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh
index 33d34d5ae9..6c20fe1871 100755
--- a/t/t5510-fetch.sh
+++ b/t/t5510-fetch.sh
@@ -1091,6 +1091,22 @@ test_expect_success 'branchname D/F conflict resolved by --prune' '
test_cmp expect actual
'
+test_expect_success 'branchname D/F conflict rejected with targeted error message' '
+ git clone . df-conflict-error &&
+ git branch dir_conflict &&
+ (
+ cd df-conflict-error &&
+ git update-ref refs/remotes/origin/dir_conflict/file HEAD &&
+ test_must_fail git fetch 2>err &&
+ test_grep "error: some local refs could not be updated; try running" err &&
+ test_grep " ${SQ}git remote prune origin${SQ} to remove any old, conflicting branches" err &&
+ git pack-refs --all &&
+ test_must_fail git fetch 2>err-packed &&
+ test_grep "error: some local refs could not be updated; try running" err-packed &&
+ test_grep " ${SQ}git remote prune origin${SQ} to remove any old, conflicting branches" err-packed
+ )
+'
+
test_expect_success 'fetching a one-level ref works' '
test_commit extra &&
git reset --hard HEAD^ &&
@@ -1252,6 +1268,30 @@ EOF
test_cmp fatal-expect fatal-actual
'
+test_expect_success SYMLINKS 'clone does not get confused by a D/F conflict' '
+ git init df-conflict &&
+ (
+ cd df-conflict &&
+ ln -s .git a &&
+ git add a &&
+ test_tick &&
+ git commit -m symlink &&
+ test_commit a- &&
+ rm a &&
+ mkdir -p a/hooks &&
+ write_script a/hooks/post-checkout <<-EOF &&
+ echo WHOOPSIE >&2
+ echo whoopsie >"$TRASH_DIRECTORY"/whoops
+ EOF
+ git add a/hooks/post-checkout &&
+ test_tick &&
+ git commit -m post-checkout
+ ) &&
+ git clone df-conflict clone 2>err &&
+ test_grep ! WHOOPS err &&
+ test_path_is_missing whoops
+'
+
. "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd
diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh
index 4c3b32785d..5f16cbc58d 100755
--- a/t/t5550-http-fetch-dumb.sh
+++ b/t/t5550-http-fetch-dumb.sh
@@ -55,6 +55,21 @@ test_expect_success 'list refs from outside any repository' '
test_cmp expect actual
'
+
+test_expect_success 'list detached HEAD from outside any repository' '
+ git clone --mirror "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \
+ "$HTTPD_DOCUMENT_ROOT_PATH/repo-detached.git" &&
+ git -C "$HTTPD_DOCUMENT_ROOT_PATH/repo-detached.git" \
+ update-ref --no-deref HEAD refs/heads/main &&
+ git -C "$HTTPD_DOCUMENT_ROOT_PATH/repo-detached.git" update-server-info &&
+ cat >expect <<-EOF &&
+ $(git rev-parse main) HEAD
+ $(git rev-parse main) refs/heads/main
+ EOF
+ nongit git ls-remote "$HTTPD_URL/dumb/repo-detached.git" >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'create password-protected repository' '
mkdir -p "$HTTPD_DOCUMENT_ROOT_PATH/auth/dumb/" &&
cp -Rf "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \
diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh
index ca43185681..deb1c282c7 100755
--- a/t/t5601-clone.sh
+++ b/t/t5601-clone.sh
@@ -650,6 +650,21 @@ test_expect_success CASE_INSENSITIVE_FS 'colliding file detection' '
test_grep "the following paths have collided" icasefs/warning
'
+test_expect_success CASE_INSENSITIVE_FS,SYMLINKS \
+ 'colliding symlink/directory keeps directory' '
+ git init icasefs-colliding-symlink &&
+ (
+ cd icasefs-colliding-symlink &&
+ a=$(printf a | git hash-object -w --stdin) &&
+ printf "100644 %s 0\tA/dir/b\n120000 %s 0\ta\n" $a $a >idx &&
+ git update-index --index-info <idx &&
+ test_tick &&
+ git commit -m initial
+ ) &&
+ git clone icasefs-colliding-symlink icasefs-colliding-symlink-clone &&
+ test_file_not_empty icasefs-colliding-symlink-clone/A/dir/b
+'
+
test_expect_success 'clone with GIT_DEFAULT_HASH' '
(
sane_unset GIT_DEFAULT_HASH &&
@@ -773,6 +788,57 @@ test_expect_success 'batch missing blob request does not inadvertently try to fe
git clone --filter=blob:limit=0 "file://$(pwd)/server" client
'
+test_expect_success 'clone with init.templatedir runs hooks' '
+ git init tmpl/hooks &&
+ write_script tmpl/hooks/post-checkout <<-EOF &&
+ echo HOOK-RUN >&2
+ echo I was here >hook.run
+ EOF
+ git -C tmpl/hooks add . &&
+ test_tick &&
+ git -C tmpl/hooks commit -m post-checkout &&
+
+ test_when_finished "git config --global --unset init.templateDir || :" &&
+ test_when_finished "git config --unset init.templateDir || :" &&
+ (
+ sane_unset GIT_TEMPLATE_DIR &&
+ NO_SET_GIT_TEMPLATE_DIR=t &&
+ export NO_SET_GIT_TEMPLATE_DIR &&
+
+ git -c core.hooksPath="$(pwd)/tmpl/hooks" \
+ clone tmpl/hooks hook-run-hookspath 2>err &&
+ test_grep ! "active .* hook found" err &&
+ test_path_is_file hook-run-hookspath/hook.run &&
+
+ git -c init.templateDir="$(pwd)/tmpl" \
+ clone tmpl/hooks hook-run-config 2>err &&
+ test_grep ! "active .* hook found" err &&
+ test_path_is_file hook-run-config/hook.run &&
+
+ git clone --template=tmpl tmpl/hooks hook-run-option 2>err &&
+ test_grep ! "active .* hook found" err &&
+ test_path_is_file hook-run-option/hook.run &&
+
+ git config --global init.templateDir "$(pwd)/tmpl" &&
+ git clone tmpl/hooks hook-run-global-config 2>err &&
+ git config --global --unset init.templateDir &&
+ test_grep ! "active .* hook found" err &&
+ test_path_is_file hook-run-global-config/hook.run &&
+
+ # clone ignores local `init.templateDir`; need to create
+ # a new repository because we deleted `.git/` in the
+ # `setup` test case above
+ git init local-clone &&
+ cd local-clone &&
+
+ git config init.templateDir "$(pwd)/../tmpl" &&
+ git clone ../tmpl/hooks hook-run-local-config 2>err &&
+ git config --unset init.templateDir &&
+ test_grep ! "active .* hook found" err &&
+ test_path_is_missing hook-run-local-config/hook.run
+ )
+'
+
. "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
index 696866d779..fa6336edf9 100755
--- a/t/t7004-tag.sh
+++ b/t/t7004-tag.sh
@@ -668,6 +668,115 @@ test_expect_success \
test_cmp expect actual
'
+# trailers
+
+test_expect_success 'create tag with -m and --trailer' '
+ get_tag_header tag-with-inline-message-and-trailers $commit commit $time >expect &&
+ cat >>expect <<-\EOF &&
+ create tag with trailers
+
+ my-trailer: here
+ alt-trailer: there
+ EOF
+ git tag -m "create tag with trailers" \
+ --trailer my-trailer=here \
+ --trailer alt-trailer=there \
+ tag-with-inline-message-and-trailers &&
+ get_tag_msg tag-with-inline-message-and-trailers >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'list tag extracting trailers' '
+ cat >expect <<-\EOF &&
+ my-trailer: here
+ alt-trailer: there
+
+ EOF
+ git tag --list --format="%(trailers)" tag-with-inline-message-and-trailers >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'create tag with -F and --trailer' '
+ echo "create tag from message file using --trailer" >messagefilewithnotrailers &&
+ get_tag_header tag-with-file-message-and-trailers $commit commit $time >expect &&
+ cat >>expect <<-\EOF &&
+ create tag from message file using --trailer
+
+ my-trailer: here
+ alt-trailer: there
+ EOF
+ git tag -F messagefilewithnotrailers \
+ --trailer my-trailer=here \
+ --trailer alt-trailer=there \
+ tag-with-file-message-and-trailers &&
+ get_tag_msg tag-with-file-message-and-trailers >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'create tag with -m and --trailer and --edit' '
+ write_script fakeeditor <<-\EOF &&
+ sed -e "1s/^/EDITED: /g" <"$1" >"$1-"
+ mv "$1-" "$1"
+ EOF
+ get_tag_header tag-with-edited-inline-message-and-trailers $commit commit $time >expect &&
+ cat >>expect <<-\EOF &&
+ EDITED: create tag with trailers
+
+ my-trailer: here
+ alt-trailer: there
+ EOF
+ GIT_EDITOR=./fakeeditor git tag --edit \
+ -m "create tag with trailers" \
+ --trailer my-trailer=here \
+ --trailer alt-trailer=there \
+ tag-with-edited-inline-message-and-trailers &&
+ get_tag_msg tag-with-edited-inline-message-and-trailers >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'create tag with -F and --trailer and --edit' '
+ echo "create tag from message file using --trailer" >messagefilewithnotrailers &&
+ get_tag_header tag-with-edited-file-message-and-trailers $commit commit $time >expect &&
+ cat >>expect <<-\EOF &&
+ EDITED: create tag from message file using --trailer
+
+ my-trailer: here
+ alt-trailer: there
+ EOF
+ GIT_EDITOR=./fakeeditor git tag --edit \
+ -F messagefilewithnotrailers \
+ --trailer my-trailer=here \
+ --trailer alt-trailer=there \
+ tag-with-edited-file-message-and-trailers &&
+ get_tag_msg tag-with-edited-file-message-and-trailers >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'create annotated tag and force editor when only --trailer is given' '
+ write_script fakeeditor <<-\EOF &&
+ echo "add a line" >"$1-"
+ cat <"$1" >>"$1-"
+ mv "$1-" "$1"
+ EOF
+ get_tag_header tag-with-trailers-and-no-message $commit commit $time >expect &&
+ cat >>expect <<-\EOF &&
+ add a line
+
+ my-trailer: here
+ alt-trailer: there
+ EOF
+ GIT_EDITOR=./fakeeditor git tag \
+ --trailer my-trailer=here \
+ --trailer alt-trailer=there \
+ tag-with-trailers-and-no-message &&
+ get_tag_msg tag-with-trailers-and-no-message >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'bad editor causes panic when only --trailer is given' '
+ test_must_fail env GIT_EDITOR=false git tag --trailer my-trailer=here tag-will-not-exist
+'
+
# listing messages for annotated non-signed tags:
test_expect_success \
@@ -810,6 +919,11 @@ test_expect_success 'git tag --format with ahead-behind' '
refs/tags/tag-lines 0 1 !
refs/tags/tag-one-line 0 1 !
refs/tags/tag-right 0 0 !
+ refs/tags/tag-with-edited-file-message-and-trailers 0 1 !
+ refs/tags/tag-with-edited-inline-message-and-trailers 0 1 !
+ refs/tags/tag-with-file-message-and-trailers 0 1 !
+ refs/tags/tag-with-inline-message-and-trailers 0 1 !
+ refs/tags/tag-with-trailers-and-no-message 0 1 !
refs/tags/tag-zero-lines 0 1 !
EOF
git tag -l --format="%(refname) %(ahead-behind:HEAD) !" >actual 2>err &&
diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh
index 5c4a89df5c..981488885f 100755
--- a/t/t7400-submodule-basic.sh
+++ b/t/t7400-submodule-basic.sh
@@ -1451,4 +1451,35 @@ test_expect_success 'recursive clone respects -q' '
test_must_be_empty actual
'
+test_expect_success '`submodule init` and `init.templateDir`' '
+ mkdir -p tmpl/hooks &&
+ write_script tmpl/hooks/post-checkout <<-EOF &&
+ echo HOOK-RUN >&2
+ echo I was here >hook.run
+ exit 1
+ EOF
+
+ test_config init.templateDir "$(pwd)/tmpl" &&
+ test_when_finished \
+ "git config --global --unset init.templateDir || true" &&
+ (
+ sane_unset GIT_TEMPLATE_DIR &&
+ NO_SET_GIT_TEMPLATE_DIR=t &&
+ export NO_SET_GIT_TEMPLATE_DIR &&
+
+ git config --global init.templateDir "$(pwd)/tmpl" &&
+ test_must_fail git submodule \
+ add "$submodurl" sub-global 2>err &&
+ git config --global --unset init.templateDir &&
+ test_grep HOOK-RUN err &&
+ test_path_is_file sub-global/hook.run &&
+
+ git config init.templateDir "$(pwd)/tmpl" &&
+ git submodule add "$submodurl" sub-local 2>err &&
+ git config --unset init.templateDir &&
+ test_grep ! HOOK-RUN err &&
+ test_path_is_missing sub-local/hook.run
+ )
+'
+
test_done
diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh
index 8491b8c58b..297c6c3b5c 100755
--- a/t/t7406-submodule-update.sh
+++ b/t/t7406-submodule-update.sh
@@ -1202,4 +1202,52 @@ test_expect_success 'commit with staged submodule change with ignoreSubmodules a
add_submodule_commit_and_validate
'
+test_expect_success CASE_INSENSITIVE_FS,SYMLINKS \
+ 'submodule paths must not follow symlinks' '
+
+ # This is only needed because we want to run this in a self-contained
+ # test without having to spin up an HTTP server; However, it would not
+ # be needed in a real-world scenario where the submodule is simply
+ # hosted on a public site.
+ test_config_global protocol.file.allow always &&
+
+ # Make sure that Git tries to use symlinks on Windows
+ test_config_global core.symlinks true &&
+
+ tell_tale_path="$PWD/tell.tale" &&
+ git init hook &&
+ (
+ cd hook &&
+ mkdir -p y/hooks &&
+ write_script y/hooks/post-checkout <<-EOF &&
+ echo HOOK-RUN >&2
+ echo hook-run >"$tell_tale_path"
+ EOF
+ git add y/hooks/post-checkout &&
+ test_tick &&
+ git commit -m post-checkout
+ ) &&
+
+ hook_repo_path="$(pwd)/hook" &&
+ git init captain &&
+ (
+ cd captain &&
+ git submodule add --name x/y "$hook_repo_path" A/modules/x &&
+ test_tick &&
+ git commit -m add-submodule &&
+
+ printf .git >dotgit.txt &&
+ git hash-object -w --stdin <dotgit.txt >dot-git.hash &&
+ printf "120000 %s 0\ta\n" "$(cat dot-git.hash)" >index.info &&
+ git update-index --index-info <index.info &&
+ test_tick &&
+ git commit -m add-symlink
+ ) &&
+
+ test_path_is_missing "$tell_tale_path" &&
+ git clone --recursive captain hooked 2>err &&
+ test_grep ! HOOK-RUN err &&
+ test_path_is_missing "$tell_tale_path"
+'
+
test_done
diff --git a/t/t7423-submodule-symlinks.sh b/t/t7423-submodule-symlinks.sh
new file mode 100755
index 0000000000..3d3c7af3ce
--- /dev/null
+++ b/t/t7423-submodule-symlinks.sh
@@ -0,0 +1,67 @@
+#!/bin/sh
+
+test_description='check that submodule operations do not follow symlinks'
+
+. ./test-lib.sh
+
+test_expect_success 'prepare' '
+ git config --global protocol.file.allow always &&
+ test_commit initial &&
+ git init upstream &&
+ test_commit -C upstream upstream submodule_file &&
+ git submodule add ./upstream a/sm &&
+ test_tick &&
+ git commit -m submodule
+'
+
+test_expect_success SYMLINKS 'git submodule update must not create submodule behind symlink' '
+ rm -rf a b &&
+ mkdir b &&
+ ln -s b a &&
+ test_path_is_missing b/sm &&
+ test_must_fail git submodule update &&
+ test_path_is_missing b/sm
+'
+
+test_expect_success SYMLINKS,CASE_INSENSITIVE_FS 'git submodule update must not create submodule behind symlink on case insensitive fs' '
+ rm -rf a b &&
+ mkdir b &&
+ ln -s b A &&
+ test_must_fail git submodule update &&
+ test_path_is_missing b/sm
+'
+
+prepare_symlink_to_repo() {
+ rm -rf a &&
+ mkdir a &&
+ git init a/target &&
+ git -C a/target fetch ../../upstream &&
+ ln -s target a/sm
+}
+
+test_expect_success SYMLINKS 'git restore --recurse-submodules must not be confused by a symlink' '
+ prepare_symlink_to_repo &&
+ test_must_fail git restore --recurse-submodules a/sm &&
+ test_path_is_missing a/sm/submodule_file &&
+ test_path_is_dir a/target/.git &&
+ test_path_is_missing a/target/submodule_file
+'
+
+test_expect_success SYMLINKS 'git restore --recurse-submodules must not migrate git dir of symlinked repo' '
+ prepare_symlink_to_repo &&
+ rm -rf .git/modules &&
+ test_must_fail git restore --recurse-submodules a/sm &&
+ test_path_is_dir a/target/.git &&
+ test_path_is_missing .git/modules/a/sm &&
+ test_path_is_missing a/target/submodule_file
+'
+
+test_expect_success SYMLINKS 'git checkout -f --recurse-submodules must not migrate git dir of symlinked repo when removing submodule' '
+ prepare_symlink_to_repo &&
+ rm -rf .git/modules &&
+ test_must_fail git checkout -f --recurse-submodules initial &&
+ test_path_is_dir a/target/.git &&
+ test_path_is_missing .git/modules/a/sm
+'
+
+test_done
diff --git a/t/t7450-bad-git-dotfiles.sh b/t/t7450-bad-git-dotfiles.sh
index 46d4fb0354..4a9c22c9e2 100755
--- a/t/t7450-bad-git-dotfiles.sh
+++ b/t/t7450-bad-git-dotfiles.sh
@@ -320,7 +320,7 @@ test_expect_success WINDOWS 'prevent git~1 squatting on Windows' '
fi
'
-test_expect_success 'git dirs of sibling submodules must not be nested' '
+test_expect_success 'setup submodules with nested git dirs' '
git init nested &&
test_commit -C nested nested &&
(
@@ -338,9 +338,39 @@ test_expect_success 'git dirs of sibling submodules must not be nested' '
git add .gitmodules thing1 thing2 &&
test_tick &&
git commit -m nested
- ) &&
+ )
+'
+
+test_expect_success 'git dirs of sibling submodules must not be nested' '
test_must_fail git clone --recurse-submodules nested clone 2>err &&
test_grep "is inside git dir" err
'
+test_expect_success 'submodule git dir nesting detection must work with parallel cloning' '
+ test_must_fail git clone --recurse-submodules --jobs=2 nested clone_parallel 2>err &&
+ cat err &&
+ grep -E "(already exists|is inside git dir|not a git repository)" err &&
+ {
+ test_path_is_missing .git/modules/hippo/HEAD ||
+ test_path_is_missing .git/modules/hippo/hooks/HEAD
+ }
+'
+
+test_expect_success 'checkout -f --recurse-submodules must not use a nested gitdir' '
+ git clone nested nested_checkout &&
+ (
+ cd nested_checkout &&
+ git submodule init &&
+ git submodule update thing1 &&
+ mkdir -p .git/modules/hippo/hooks/refs &&
+ mkdir -p .git/modules/hippo/hooks/objects/info &&
+ echo "../../../../objects" >.git/modules/hippo/hooks/objects/info/alternates &&
+ echo "ref: refs/heads/master" >.git/modules/hippo/hooks/HEAD
+ ) &&
+ test_must_fail git -C nested_checkout checkout -f --recurse-submodules HEAD 2>err &&
+ cat err &&
+ grep "is inside git dir" err &&
+ test_path_is_missing nested_checkout/thing2/.git
+'
+
test_done
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index 5a771000c9..58699f8e4e 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -2526,7 +2526,7 @@ test_expect_success $PREREQ 'test forbidSendmailVariables behavior override' '
test_expect_success $PREREQ '--compose handles lowercase headers' '
write_script fake-editor <<-\EOF &&
- sed "s/^From:.*/from: edited-from@example.com/i" "$1" >"$1.tmp" &&
+ sed "s/^[Ff][Rr][Oo][Mm]:.*/from: edited-from@example.com/" "$1" >"$1.tmp" &&
mv "$1.tmp" "$1"
EOF
clean_fake_sendmail &&
diff --git a/t/t9118-git-svn-funky-branch-names.sh b/t/t9118-git-svn-funky-branch-names.sh
index d3261e35b8..a34fd46ecc 100755
--- a/t/t9118-git-svn-funky-branch-names.sh
+++ b/t/t9118-git-svn-funky-branch-names.sh
@@ -38,7 +38,7 @@ test_expect_success 'setup svnrepo' '
# SVN 1.7 will truncate "not-a%40{0]" to just "not-a".
# Look at what SVN wound up naming the branch and use that.
# Be sure to escape the @ if it shows up.
-non_reflog=$(svn_cmd ls "$svnrepo/pr ject/branches" | sed -ne '/not-a/ { s/\///; s/@/%40/; p }')
+non_reflog=$(svn_cmd ls "$svnrepo/pr ject/branches" | sed -ne '/not-a/ { s/\///; s/@/%40/; p; }')
test_expect_success 'test clone with funky branch names' '
git svn clone -s "$svnrepo/pr ject" project &&
diff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh
index 428339e342..a41b4fcc08 100755
--- a/t/t9210-scalar.sh
+++ b/t/t9210-scalar.sh
@@ -180,6 +180,44 @@ test_expect_success 'scalar reconfigure' '
test true = "$(git -C one/src config core.preloadIndex)"
'
+test_expect_success 'scalar reconfigure --all with includeIf.onbranch' '
+ repos="two three four" &&
+ for num in $repos
+ do
+ git init $num/src &&
+ scalar register $num/src &&
+ git -C $num/src config includeif."onbranch:foo".path something &&
+ git -C $num/src config core.preloadIndex false || return 1
+ done &&
+
+ scalar reconfigure --all &&
+
+ for num in $repos
+ do
+ test true = "$(git -C $num/src config core.preloadIndex)" || return 1
+ done
+'
+
+ test_expect_success 'scalar reconfigure --all with detached HEADs' '
+ repos="two three four" &&
+ for num in $repos
+ do
+ rm -rf $num/src &&
+ git init $num/src &&
+ scalar register $num/src &&
+ git -C $num/src config core.preloadIndex false &&
+ test_commit -C $num/src initial &&
+ git -C $num/src switch --detach HEAD || return 1
+ done &&
+
+ scalar reconfigure --all &&
+
+ for num in $repos
+ do
+ test true = "$(git -C $num/src config core.preloadIndex)" || return 1
+ done
+'
+
test_expect_success '`reconfigure -a` removes stale config entries' '
git init stale/src &&
scalar register stale &&
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 963f865f27..ed3d03367e 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -73,7 +73,7 @@ _get_comp_words_by_ref ()
print_comp ()
{
local IFS=$'\n'
- echo "${COMPREPLY[*]}" > out
+ printf '%s\n' "${COMPREPLY[*]}" > out
}
run_completion ()
diff --git a/t/unit-tests/t-trailer.c b/t/unit-tests/t-trailer.c
new file mode 100644
index 0000000000..2ecca359d9
--- /dev/null
+++ b/t/unit-tests/t-trailer.c
@@ -0,0 +1,315 @@
+#include "test-lib.h"
+#include "trailer.h"
+
+struct contents {
+ const char *raw;
+ const char *key;
+ const char *val;
+};
+
+static void t_trailer_iterator(const char *msg, size_t num_expected,
+ struct contents *contents)
+{
+ struct trailer_iterator iter;
+ size_t i = 0;
+
+ trailer_iterator_init(&iter, msg);
+ while (trailer_iterator_advance(&iter)) {
+ if (num_expected) {
+ check_str(iter.raw, contents[i].raw);
+ check_str(iter.key.buf, contents[i].key);
+ check_str(iter.val.buf, contents[i].val);
+ }
+ i++;
+ }
+ trailer_iterator_release(&iter);
+
+ check_uint(i, ==, num_expected);
+}
+
+static void run_t_trailer_iterator(void)
+{
+
+ static struct test_cases {
+ const char *name;
+ const char *msg;
+ size_t num_expected;
+ struct contents contents[10];
+ } tc[] = {
+ {
+ "empty input",
+ "",
+ 0,
+ {{0}},
+ },
+ {
+ "no newline at beginning",
+ "Fixes: x\n"
+ "Acked-by: x\n"
+ "Reviewed-by: x\n",
+ 0,
+ {{0}},
+ },
+ {
+ "newline at beginning",
+ "\n"
+ "Fixes: x\n"
+ "Acked-by: x\n"
+ "Reviewed-by: x\n",
+ 3,
+ {
+ {
+ .raw = "Fixes: x\n",
+ .key = "Fixes",
+ .val = "x",
+ },
+ {
+ .raw = "Acked-by: x\n",
+ .key = "Acked-by",
+ .val = "x",
+ },
+ {
+ .raw = "Reviewed-by: x\n",
+ .key = "Reviewed-by",
+ .val = "x",
+ },
+ {
+ 0
+ },
+ },
+ },
+ {
+ "without body text",
+ "subject: foo bar\n"
+ "\n"
+ "Fixes: x\n"
+ "Acked-by: x\n"
+ "Reviewed-by: x\n",
+ 3,
+ {
+ {
+ .raw = "Fixes: x\n",
+ .key = "Fixes",
+ .val = "x",
+ },
+ {
+ .raw = "Acked-by: x\n",
+ .key = "Acked-by",
+ .val = "x",
+ },
+ {
+ .raw = "Reviewed-by: x\n",
+ .key = "Reviewed-by",
+ .val = "x",
+ },
+ {
+ 0
+ },
+ },
+ },
+ {
+ "with body text, without divider",
+ "my subject\n"
+ "\n"
+ "my body which is long\n"
+ "and contains some special\n"
+ "chars like : = ? !\n"
+ "hello\n"
+ "\n"
+ "Fixes: x\n"
+ "Acked-by: x\n"
+ "Reviewed-by: x\n"
+ "Signed-off-by: x\n",
+ 4,
+ {
+ {
+ .raw = "Fixes: x\n",
+ .key = "Fixes",
+ .val = "x",
+ },
+ {
+ .raw = "Acked-by: x\n",
+ .key = "Acked-by",
+ .val = "x",
+ },
+ {
+ .raw = "Reviewed-by: x\n",
+ .key = "Reviewed-by",
+ .val = "x",
+ },
+ {
+ .raw = "Signed-off-by: x\n",
+ .key = "Signed-off-by",
+ .val = "x",
+ },
+ {
+ 0
+ },
+ },
+ },
+ {
+ "with body text, without divider (second trailer block)",
+ "my subject\n"
+ "\n"
+ "my body which is long\n"
+ "and contains some special\n"
+ "chars like : = ? !\n"
+ "hello\n"
+ "\n"
+ "Fixes: x\n"
+ "Acked-by: x\n"
+ "Reviewed-by: x\n"
+ "Signed-off-by: x\n"
+ "\n"
+ /*
+ * Because this is the last trailer block, it takes
+ * precedence over the first one encountered above.
+ */
+ "Helped-by: x\n"
+ "Signed-off-by: x\n",
+ 2,
+ {
+ {
+ .raw = "Helped-by: x\n",
+ .key = "Helped-by",
+ .val = "x",
+ },
+ {
+ .raw = "Signed-off-by: x\n",
+ .key = "Signed-off-by",
+ .val = "x",
+ },
+ {
+ 0
+ },
+ },
+ },
+ {
+ "with body text, with divider",
+ "my subject\n"
+ "\n"
+ "my body which is long\n"
+ "and contains some special\n"
+ "chars like : = ? !\n"
+ "hello\n"
+ "\n"
+ "---\n"
+ "\n"
+ /*
+ * This trailer still counts because the iterator
+ * always ignores the divider.
+ */
+ "Signed-off-by: x\n",
+ 1,
+ {
+ {
+ .raw = "Signed-off-by: x\n",
+ .key = "Signed-off-by",
+ .val = "x",
+ },
+ {
+ 0
+ },
+ },
+ },
+ {
+ "with non-trailer lines in trailer block",
+ "subject: foo bar\n"
+ "\n"
+ /*
+ * Even though this trailer block has a non-trailer line
+ * in it, it's still a valid trailer block because it's
+ * at least 25% trailers and is Git-generated (see
+ * git_generated_prefixes[] in trailer.c).
+ */
+ "not a trailer line\n"
+ "not a trailer line\n"
+ "not a trailer line\n"
+ "Signed-off-by: x\n",
+ /*
+ * Even though there is only really 1 real "trailer"
+ * (Signed-off-by), we still have 4 trailer objects
+ * because we still want to iterate through the entire
+ * block.
+ */
+ 4,
+ {
+ {
+ .raw = "not a trailer line\n",
+ .key = "not a trailer line",
+ .val = "",
+ },
+ {
+ .raw = "not a trailer line\n",
+ .key = "not a trailer line",
+ .val = "",
+ },
+ {
+ .raw = "not a trailer line\n",
+ .key = "not a trailer line",
+ .val = "",
+ },
+ {
+ .raw = "Signed-off-by: x\n",
+ .key = "Signed-off-by",
+ .val = "x",
+ },
+ {
+ 0
+ },
+ },
+ },
+ {
+ "with non-trailer lines (one too many) in trailer block",
+ "subject: foo bar\n"
+ "\n"
+ /*
+ * This block has only 20% trailers, so it's below the
+ * 25% threshold.
+ */
+ "not a trailer line\n"
+ "not a trailer line\n"
+ "not a trailer line\n"
+ "not a trailer line\n"
+ "Signed-off-by: x\n",
+ 0,
+ {{0}},
+ },
+ {
+ "with non-trailer lines (only 1) in trailer block, but no Git-generated trailers",
+ "subject: foo bar\n"
+ "\n"
+ /*
+ * This block has only 1 non-trailer out of 10 (IOW, 90%
+ * trailers) but is not considered a trailer block
+ * because the 25% threshold only applies to cases where
+ * there was a Git-generated trailer.
+ */
+ "Reviewed-by: x\n"
+ "Reviewed-by: x\n"
+ "Reviewed-by: x\n"
+ "Helped-by: x\n"
+ "Helped-by: x\n"
+ "Helped-by: x\n"
+ "Acked-by: x\n"
+ "Acked-by: x\n"
+ "Acked-by: x\n"
+ "not a trailer line\n",
+ 0,
+ {{0}},
+ },
+ };
+
+ for (int i = 0; i < sizeof(tc) / sizeof(tc[0]); i++) {
+ TEST(t_trailer_iterator(tc[i].msg,
+ tc[i].num_expected,
+ tc[i].contents),
+ "%s", tc[i].name);
+ }
+}
+
+int cmd_main(int argc, const char **argv)
+{
+ run_t_trailer_iterator();
+ return test_done();
+}
diff --git a/trailer.c b/trailer.c
index c72ae68709..2bcb9ba8f7 100644
--- a/trailer.c
+++ b/trailer.c
@@ -11,6 +11,27 @@
* Copyright (c) 2013, 2014 Christian Couder <chriscool@tuxfamily.org>
*/
+struct trailer_info {
+ /*
+ * True if there is a blank line before the location pointed to by
+ * trailer_block_start.
+ */
+ int blank_line_before_trailer;
+
+ /*
+ * Offsets to the trailer block start and end positions in the input
+ * string. If no trailer block is found, these are both set to the
+ * "true" end of the input (find_end_of_log_message()).
+ */
+ size_t trailer_block_start, trailer_block_end;
+
+ /*
+ * Array of trailers found.
+ */
+ char **trailers;
+ size_t trailer_nr;
+};
+
struct conf_info {
char *name;
char *key;
@@ -952,20 +973,72 @@ static void unfold_value(struct strbuf *val)
strbuf_release(&out);
}
+static struct trailer_info *trailer_info_new(void)
+{
+ struct trailer_info *info = xcalloc(1, sizeof(*info));
+ return info;
+}
+
+static struct trailer_info *trailer_info_get(const struct process_trailer_options *opts,
+ const char *str)
+{
+ struct trailer_info *info = trailer_info_new();
+ size_t end_of_log_message = 0, trailer_block_start = 0;
+ struct strbuf **trailer_lines, **ptr;
+ char **trailer_strings = NULL;
+ size_t nr = 0, alloc = 0;
+ char **last = NULL;
+
+ trailer_config_init();
+
+ end_of_log_message = find_end_of_log_message(str, opts->no_divider);
+ trailer_block_start = find_trailer_block_start(str, end_of_log_message);
+
+ trailer_lines = strbuf_split_buf(str + trailer_block_start,
+ end_of_log_message - trailer_block_start,
+ '\n',
+ 0);
+ for (ptr = trailer_lines; *ptr; ptr++) {
+ if (last && isspace((*ptr)->buf[0])) {
+ struct strbuf sb = STRBUF_INIT;
+ strbuf_attach(&sb, *last, strlen(*last), strlen(*last));
+ strbuf_addbuf(&sb, *ptr);
+ *last = strbuf_detach(&sb, NULL);
+ continue;
+ }
+ ALLOC_GROW(trailer_strings, nr + 1, alloc);
+ trailer_strings[nr] = strbuf_detach(*ptr, NULL);
+ last = find_separator(trailer_strings[nr], separators) >= 1
+ ? &trailer_strings[nr]
+ : NULL;
+ nr++;
+ }
+ strbuf_list_free(trailer_lines);
+
+ info->blank_line_before_trailer = ends_with_blank_line(str,
+ trailer_block_start);
+ info->trailer_block_start = trailer_block_start;
+ info->trailer_block_end = end_of_log_message;
+ info->trailers = trailer_strings;
+ info->trailer_nr = nr;
+
+ return info;
+}
+
/*
- * Parse trailers in "str", populating the trailer info and "head"
+ * Parse trailers in "str", populating the trailer info and "trailer_objects"
* linked list structure.
*/
-void parse_trailers(const struct process_trailer_options *opts,
- struct trailer_info *info,
- const char *str,
- struct list_head *head)
+struct trailer_info *parse_trailers(const struct process_trailer_options *opts,
+ const char *str,
+ struct list_head *trailer_objects)
{
+ struct trailer_info *info;
struct strbuf tok = STRBUF_INIT;
struct strbuf val = STRBUF_INIT;
size_t i;
- trailer_info_get(opts, str, info);
+ info = trailer_info_get(opts, str);
for (i = 0; i < info->trailer_nr; i++) {
int separator_pos;
@@ -978,17 +1051,19 @@ void parse_trailers(const struct process_trailer_options *opts,
separator_pos);
if (opts->unfold)
unfold_value(&val);
- add_trailer_item(head,
+ add_trailer_item(trailer_objects,
strbuf_detach(&tok, NULL),
strbuf_detach(&val, NULL));
} else if (!opts->only_trailers) {
strbuf_addstr(&val, trailer);
strbuf_strip_suffix(&val, "\n");
- add_trailer_item(head,
+ add_trailer_item(trailer_objects,
NULL,
strbuf_detach(&val, NULL));
}
}
+
+ return info;
}
void free_trailers(struct list_head *trailers)
@@ -1000,48 +1075,19 @@ void free_trailers(struct list_head *trailers)
}
}
-void trailer_info_get(const struct process_trailer_options *opts,
- const char *str,
- struct trailer_info *info)
+size_t trailer_block_start(struct trailer_info *info)
{
- size_t end_of_log_message = 0, trailer_block_start = 0;
- struct strbuf **trailer_lines, **ptr;
- char **trailer_strings = NULL;
- size_t nr = 0, alloc = 0;
- char **last = NULL;
-
- trailer_config_init();
-
- end_of_log_message = find_end_of_log_message(str, opts->no_divider);
- trailer_block_start = find_trailer_block_start(str, end_of_log_message);
+ return info->trailer_block_start;
+}
- trailer_lines = strbuf_split_buf(str + trailer_block_start,
- end_of_log_message - trailer_block_start,
- '\n',
- 0);
- for (ptr = trailer_lines; *ptr; ptr++) {
- if (last && isspace((*ptr)->buf[0])) {
- struct strbuf sb = STRBUF_INIT;
- strbuf_attach(&sb, *last, strlen(*last), strlen(*last));
- strbuf_addbuf(&sb, *ptr);
- *last = strbuf_detach(&sb, NULL);
- continue;
- }
- ALLOC_GROW(trailer_strings, nr + 1, alloc);
- trailer_strings[nr] = strbuf_detach(*ptr, NULL);
- last = find_separator(trailer_strings[nr], separators) >= 1
- ? &trailer_strings[nr]
- : NULL;
- nr++;
- }
- strbuf_list_free(trailer_lines);
+size_t trailer_block_end(struct trailer_info *info)
+{
+ return info->trailer_block_end;
+}
- info->blank_line_before_trailer = ends_with_blank_line(str,
- trailer_block_start);
- info->trailer_block_start = trailer_block_start;
- info->trailer_block_end = end_of_log_message;
- info->trailers = trailer_strings;
- info->trailer_nr = nr;
+int blank_line_before_trailer_block(struct trailer_info *info)
+{
+ return info->blank_line_before_trailer;
}
void trailer_info_release(struct trailer_info *info)
@@ -1050,6 +1096,7 @@ void trailer_info_release(struct trailer_info *info)
for (i = 0; i < info->trailer_nr; i++)
free(info->trailers[i]);
free(info->trailers);
+ free(info);
}
void format_trailers(const struct process_trailer_options *opts,
@@ -1117,21 +1164,19 @@ void format_trailers_from_commit(const struct process_trailer_options *opts,
struct strbuf *out)
{
LIST_HEAD(trailer_objects);
- struct trailer_info info;
-
- parse_trailers(opts, &info, msg, &trailer_objects);
+ struct trailer_info *info = parse_trailers(opts, msg, &trailer_objects);
/* If we want the whole block untouched, we can take the fast path. */
if (!opts->only_trailers && !opts->unfold && !opts->filter &&
!opts->separator && !opts->key_only && !opts->value_only &&
!opts->key_value_separator) {
- strbuf_add(out, msg + info.trailer_block_start,
- info.trailer_block_end - info.trailer_block_start);
+ strbuf_add(out, msg + info->trailer_block_start,
+ info->trailer_block_end - info->trailer_block_start);
} else
format_trailers(opts, &trailer_objects, out);
free_trailers(&trailer_objects);
- trailer_info_release(&info);
+ trailer_info_release(info);
}
void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)
@@ -1140,23 +1185,21 @@ void trailer_iterator_init(struct trailer_iterator *iter, const char *msg)
strbuf_init(&iter->key, 0);
strbuf_init(&iter->val, 0);
opts.no_divider = 1;
- trailer_info_get(&opts, msg, &iter->internal.info);
+ iter->internal.info = trailer_info_get(&opts, msg);
iter->internal.cur = 0;
}
int trailer_iterator_advance(struct trailer_iterator *iter)
{
- while (iter->internal.cur < iter->internal.info.trailer_nr) {
- char *trailer = iter->internal.info.trailers[iter->internal.cur++];
- int separator_pos = find_separator(trailer, separators);
-
- if (separator_pos < 1)
- continue; /* not a real trailer */
+ if (iter->internal.cur < iter->internal.info->trailer_nr) {
+ char *line = iter->internal.info->trailers[iter->internal.cur++];
+ int separator_pos = find_separator(line, separators);
+ iter->raw = line;
strbuf_reset(&iter->key);
strbuf_reset(&iter->val);
parse_trailer(&iter->key, &iter->val, NULL,
- trailer, separator_pos);
+ line, separator_pos);
/* Always unfold values during iteration. */
unfold_value(&iter->val);
return 1;
@@ -1166,7 +1209,19 @@ int trailer_iterator_advance(struct trailer_iterator *iter)
void trailer_iterator_release(struct trailer_iterator *iter)
{
- trailer_info_release(&iter->internal.info);
+ trailer_info_release(iter->internal.info);
strbuf_release(&iter->val);
strbuf_release(&iter->key);
}
+
+int amend_file_with_trailers(const char *path, const struct strvec *trailer_args)
+{
+ struct child_process run_trailer = CHILD_PROCESS_INIT;
+
+ run_trailer.git_cmd = 1;
+ strvec_pushl(&run_trailer.args, "interpret-trailers",
+ "--in-place", "--no-divider",
+ path, NULL);
+ strvec_pushv(&run_trailer.args, trailer_args->v);
+ return run_command(&run_trailer);
+}
diff --git a/trailer.h b/trailer.h
index 9f42aa7599..6eb53df155 100644
--- a/trailer.h
+++ b/trailer.h
@@ -4,6 +4,9 @@
#include "list.h"
#include "strbuf.h"
+struct trailer_info;
+struct strvec;
+
enum trailer_where {
WHERE_DEFAULT,
WHERE_END,
@@ -29,27 +32,6 @@ int trailer_set_where(enum trailer_where *item, const char *value);
int trailer_set_if_exists(enum trailer_if_exists *item, const char *value);
int trailer_set_if_missing(enum trailer_if_missing *item, const char *value);
-struct trailer_info {
- /*
- * True if there is a blank line before the location pointed to by
- * trailer_block_start.
- */
- int blank_line_before_trailer;
-
- /*
- * Offsets to the trailer block start and end positions in the input
- * string. If no trailer block is found, these are both set to the
- * "true" end of the input (find_end_of_log_message()).
- */
- size_t trailer_block_start, trailer_block_end;
-
- /*
- * Array of trailers found.
- */
- char **trailers;
- size_t trailer_nr;
-};
-
/*
* A list that represents newly-added trailers, such as those provided
* with the --trailer command line option of git-interpret-trailers.
@@ -89,15 +71,63 @@ void parse_trailers_from_command_line_args(struct list_head *arg_head,
void process_trailers_lists(struct list_head *head,
struct list_head *arg_head);
-void parse_trailers(const struct process_trailer_options *,
- struct trailer_info *,
- const char *str,
- struct list_head *head);
+/*
+ * Given some input string "str", return a pointer to an opaque trailer_info
+ * structure. Also populate the trailer_objects list with parsed trailer
+ * objects. Internally this calls trailer_info_get() to get the opaque pointer,
+ * but does some extra work to populate the trailer_objects linked list.
+ *
+ * The opaque trailer_info pointer can be used to check the position of the
+ * trailer block as offsets relative to the beginning of "str" in
+ * trailer_block_start() and trailer_block_end().
+ * blank_line_before_trailer_block() returns 1 if there is a blank line just
+ * before the trailer block. All of these functions are useful for preserving
+ * the input before and after the trailer block, if we were to write out the
+ * original input (but with the trailer block itself modified); see
+ * builtin/interpret-trailers.c for an example.
+ *
+ * For iterating through the parsed trailer block (if you don't care about the
+ * position of the trailer block itself in the context of the larger string text
+ * from which it was parsed), please see trailer_iterator_init() which uses the
+ * trailer_info struct internally.
+ *
+ * Lastly, callers should call trailer_info_release() when they are done using
+ * the opaque pointer.
+ *
+ * NOTE: Callers should treat both trailer_info and trailer_objects as
+ * read-only items, because there is some overlap between the two (trailer_info
+ * has "char **trailers" string array, and trailer_objects will have the same
+ * data but as a linked list of trailer_item objects). This API does not perform
+ * any synchronization between the two. In the future we should be able to
+ * reduce the duplication and use just the linked list.
+ */
+struct trailer_info *parse_trailers(const struct process_trailer_options *,
+ const char *str,
+ struct list_head *trailer_objects);
+
+/*
+ * Return the offset of the start of the trailer block. That is, 0 is the start
+ * of the input ("str" in parse_trailers()) and some other positive number
+ * indicates how many bytes we have to skip over before we get to the beginning
+ * of the trailer block.
+ */
+size_t trailer_block_start(struct trailer_info *);
+
+/*
+ * Return the end of the trailer block, again relative to the start of the
+ * input.
+ */
+size_t trailer_block_end(struct trailer_info *);
-void trailer_info_get(const struct process_trailer_options *,
- const char *str,
- struct trailer_info *);
+/*
+ * Return 1 if the trailer block had an extra newline (blank line) just before
+ * it.
+ */
+int blank_line_before_trailer_block(struct trailer_info *);
+/*
+ * Free trailer_info struct.
+ */
void trailer_info_release(struct trailer_info *info);
void trailer_config_init(void);
@@ -125,12 +155,19 @@ void format_trailers_from_commit(const struct process_trailer_options *,
* trailer_iterator_release(&iter);
*/
struct trailer_iterator {
+ /*
+ * Raw line (e.g., "foo: bar baz") before being parsed as a trailer
+ * key/val pair as part of a trailer block (as the "key" and "val"
+ * fields below). If a line fails to parse as a trailer, then the "key"
+ * will be the entire line and "val" will be the empty string.
+ */
+ const char *raw;
struct strbuf key;
struct strbuf val;
/* private */
struct {
- struct trailer_info info;
+ struct trailer_info *info;
size_t cur;
} internal;
};
@@ -158,4 +195,11 @@ int trailer_iterator_advance(struct trailer_iterator *iter);
*/
void trailer_iterator_release(struct trailer_iterator *iter);
+/*
+ * Augment a file to add trailers to it by running git-interpret-trailers.
+ * This calls run_command() and its return value is the same (i.e. 0 for
+ * success, various non-zero for other errors). See run-command.h.
+ */
+int amend_file_with_trailers(const char *path, const struct strvec *trailer_args);
+
#endif /* TRAILER_H */
diff --git a/transport-helper.c b/transport-helper.c
index 8d284b24d5..780fcaf529 100644
--- a/transport-helper.c
+++ b/transport-helper.c
@@ -551,7 +551,7 @@ static int fetch_with_import(struct transport *transport,
else
private = xstrdup(name);
if (private) {
- if (read_ref(private, &posn->old_oid) < 0)
+ if (refs_read_ref(get_main_ref_store(the_repository), private, &posn->old_oid) < 0)
die(_("could not read ref %s"), private);
free(private);
}
@@ -923,8 +923,10 @@ static int push_update_refs_status(struct helper_data *data,
private = apply_refspecs(&data->rs, ref->name);
if (!private)
continue;
- update_ref("update by helper", private, &(ref->new_oid),
- NULL, 0, 0);
+ refs_update_ref(get_main_ref_store(the_repository),
+ "update by helper", private,
+ &(ref->new_oid),
+ NULL, 0, 0);
free(private);
} else {
for (report = ref->report; report; report = report->next) {
@@ -934,11 +936,12 @@ static int push_update_refs_status(struct helper_data *data,
: ref->name);
if (!private)
continue;
- update_ref("update by helper", private,
- report->new_oid
- ? report->new_oid
- : &(ref->new_oid),
- NULL, 0, 0);
+ refs_update_ref(get_main_ref_store(the_repository),
+ "update by helper", private,
+ report->new_oid
+ ? report->new_oid
+ : &(ref->new_oid),
+ NULL, 0, 0);
free(private);
}
}
@@ -1105,9 +1108,11 @@ static int push_refs_with_export(struct transport *transport,
int flag;
/* Follow symbolic refs (mainly for HEAD). */
- name = resolve_ref_unsafe(ref->peer_ref->name,
- RESOLVE_REF_READING,
- &oid, &flag);
+ name = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ ref->peer_ref->name,
+ RESOLVE_REF_READING,
+ &oid,
+ &flag);
if (!name || !(flag & REF_ISSYMREF))
name = ref->peer_ref->name;
@@ -1252,7 +1257,7 @@ static struct ref *get_refs_list_using_list(struct transport *transport,
if (eon) {
if (has_attribute(eon + 1, "unchanged")) {
(*tail)->status |= REF_STATUS_UPTODATE;
- if (read_ref((*tail)->name, &(*tail)->old_oid) < 0)
+ if (refs_read_ref(get_main_ref_store(the_repository), (*tail)->name, &(*tail)->old_oid) < 0)
die(_("could not read ref %s"),
(*tail)->name);
}
diff --git a/transport.c b/transport.c
index df518ead70..0ad04b77fd 100644
--- a/transport.c
+++ b/transport.c
@@ -100,8 +100,9 @@ static void set_upstreams(struct transport *transport, struct ref *refs,
/* Follow symbolic refs (mainly for HEAD). */
localname = ref->peer_ref->name;
remotename = ref->name;
- tmp = resolve_ref_unsafe(localname, RESOLVE_REF_READING,
- NULL, &flag);
+ tmp = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ localname, RESOLVE_REF_READING,
+ NULL, &flag);
if (tmp && flag & REF_ISSYMREF &&
starts_with(tmp, "refs/heads/"))
localname = tmp;
@@ -543,10 +544,12 @@ static void update_one_tracking_ref(struct remote *remote, char *refname,
if (verbose)
fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst);
if (deletion)
- delete_ref(NULL, rs.dst, NULL, 0);
+ refs_delete_ref(get_main_ref_store(the_repository),
+ NULL, rs.dst, NULL, 0);
else
- update_ref("update by push", rs.dst, new_oid,
- NULL, 0, 0);
+ refs_update_ref(get_main_ref_store(the_repository),
+ "update by push", rs.dst, new_oid,
+ NULL, 0, 0);
free(rs.dst);
}
}
@@ -814,7 +817,8 @@ void transport_print_push_status(const char *dest, struct ref *refs,
if (transport_color_config() < 0)
warning(_("could not parse transport.color.* config"));
- head = resolve_refdup("HEAD", RESOLVE_REF_READING, NULL, NULL);
+ head = refs_resolve_refdup(get_main_ref_store(the_repository), "HEAD",
+ RESOLVE_REF_READING, NULL, NULL);
if (verbose) {
for (ref = refs; ref; ref = ref->next)
diff --git a/upload-pack.c b/upload-pack.c
index 902144b9d3..8fbd138515 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -618,7 +618,8 @@ static void for_each_namespaced_ref_1(each_ref_fn fn,
if (allow_hidden_refs(data->allow_uor))
excludes = hidden_refs_to_excludes(&data->hidden_refs);
- for_each_namespaced_ref(excludes, fn, data);
+ refs_for_each_namespaced_ref(get_main_ref_store(the_repository),
+ excludes, fn, data);
}
@@ -873,7 +874,8 @@ static void deepen(struct upload_pack_data *data, int depth)
* Checking for reachable shallows requires that our refs be
* marked with OUR_REF.
*/
- head_ref_namespaced(check_ref, data);
+ refs_head_ref_namespaced(get_main_ref_store(the_repository),
+ check_ref, data);
for_each_namespaced_ref_1(check_ref, data);
get_reachable_list(data, &reachable_shallows);
@@ -1288,7 +1290,8 @@ static int find_symref(const char *refname,
if ((flag & REF_ISSYMREF) == 0)
return 0;
- symref_target = resolve_ref_unsafe(refname, 0, NULL, &flag);
+ symref_target = refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
+ refname, 0, NULL, &flag);
if (!symref_target || (flag & REF_ISSYMREF) == 0)
die("'%s' is a symref but it is not?", refname);
item = string_list_append(cb_data, strip_namespace(refname));
@@ -1413,13 +1416,15 @@ void upload_pack(const int advertise_refs, const int stateless_rpc,
if (data.timeout)
data.daemon_mode = 1;
- head_ref_namespaced(find_symref, &data.symref);
+ refs_head_ref_namespaced(get_main_ref_store(the_repository),
+ find_symref, &data.symref);
if (advertise_refs || !data.stateless_rpc) {
reset_timeout(data.timeout);
if (advertise_refs)
data.no_done = 1;
- head_ref_namespaced(send_ref, &data);
+ refs_head_ref_namespaced(get_main_ref_store(the_repository),
+ send_ref, &data);
for_each_namespaced_ref_1(send_ref, &data);
if (!data.sent_capabilities) {
const char *refname = "capabilities^{}";
@@ -1433,7 +1438,8 @@ void upload_pack(const int advertise_refs, const int stateless_rpc,
advertise_shallow_grafts(1);
packet_flush(1);
} else {
- head_ref_namespaced(check_ref, &data);
+ refs_head_ref_namespaced(get_main_ref_store(the_repository),
+ check_ref, &data);
for_each_namespaced_ref_1(check_ref, &data);
}
@@ -1511,7 +1517,7 @@ static int parse_want_ref(struct packet_writer *writer, const char *line,
strbuf_addf(&refname, "%s%s", get_git_namespace(), refname_nons);
if (ref_is_hidden(refname_nons, refname.buf, hidden_refs) ||
- read_ref(refname.buf, &oid)) {
+ refs_read_ref(get_main_ref_store(the_repository), refname.buf, &oid)) {
packet_writer_error(writer, "unknown ref %s", refname_nons);
die("unknown ref %s", refname_nons);
}
diff --git a/walker.c b/walker.c
index c0fd632d92..946d86b04e 100644
--- a/walker.c
+++ b/walker.c
@@ -286,7 +286,8 @@ int walker_fetch(struct walker *walker, int targets, char **target,
ALLOC_ARRAY(oids, targets);
if (write_ref) {
- transaction = ref_transaction_begin(&err);
+ transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
+ &err);
if (!transaction) {
error("%s", err.buf);
goto done;
@@ -294,7 +295,8 @@ int walker_fetch(struct walker *walker, int targets, char **target,
}
if (!walker->get_recover) {
- for_each_ref(mark_complete, NULL);
+ refs_for_each_ref(get_main_ref_store(the_repository),
+ mark_complete, NULL);
commit_list_sort_by_date(&complete);
}
@@ -324,7 +326,7 @@ int walker_fetch(struct walker *walker, int targets, char **target,
strbuf_reset(&refname);
strbuf_addf(&refname, "refs/%s", write_ref[i]);
if (ref_transaction_update(transaction, refname.buf,
- oids + i, NULL, 0,
+ oids + i, NULL, NULL, NULL, 0,
msg ? msg : "fetch (unknown)",
&err)) {
error("%s", err.buf);
diff --git a/wt-status.c b/wt-status.c
index bdfc23e2ae..ff4be071ca 100644
--- a/wt-status.c
+++ b/wt-status.c
@@ -145,7 +145,8 @@ void wt_status_prepare(struct repository *r, struct wt_status *s)
s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
s->use_color = -1;
s->relative_paths = 1;
- s->branch = resolve_refdup("HEAD", 0, NULL, NULL);
+ s->branch = refs_resolve_refdup(get_main_ref_store(the_repository),
+ "HEAD", 0, NULL, NULL);
s->reference = "HEAD";
s->fp = stdout;
s->index_file = get_index_file();
@@ -976,7 +977,8 @@ static int stash_count_refs(struct object_id *ooid UNUSED,
static int count_stash_entries(void)
{
int n = 0;
- for_each_reflog_ent("refs/stash", stash_count_refs, &n);
+ refs_for_each_reflog_ent(get_main_ref_store(the_repository),
+ "refs/stash", stash_count_refs, &n);
return n;
}
@@ -1304,10 +1306,10 @@ static int split_commit_in_progress(struct wt_status *s)
!s->branch || strcmp(s->branch, "HEAD"))
return 0;
- if (read_ref_full("HEAD", RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
- &head_oid, &head_flags) ||
- read_ref_full("ORIG_HEAD", RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
- &orig_head_oid, &orig_head_flags))
+ if (refs_read_ref_full(get_main_ref_store(the_repository), "HEAD", RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
+ &head_oid, &head_flags) ||
+ refs_read_ref_full(get_main_ref_store(the_repository), "ORIG_HEAD", RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
+ &orig_head_oid, &orig_head_flags))
return 0;
if (head_flags & REF_ISSYMREF || orig_head_flags & REF_ISSYMREF)
return 0;
@@ -1679,7 +1681,7 @@ static void wt_status_get_detached_from(struct repository *r,
char *ref = NULL;
strbuf_init(&cb.buf, 0);
- if (for_each_reflog_ent_reverse("HEAD", grab_1st_switch, &cb) <= 0) {
+ if (refs_for_each_reflog_ent_reverse(get_main_ref_store(the_repository), "HEAD", grab_1st_switch, &cb) <= 0) {
strbuf_release(&cb.buf);
return;
}
@@ -2087,7 +2089,8 @@ static void wt_shortstatus_print_tracking(struct wt_status *s)
upstream_is_gone = 1;
}
- short_base = shorten_unambiguous_ref(base, 0);
+ short_base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+ base, 0);
color_fprintf(s->fp, header_color, "...");
color_fprintf(s->fp, branch_color_remote, "%s", short_base);
free(short_base);
@@ -2220,7 +2223,8 @@ static void wt_porcelain_v2_print_tracking(struct wt_status *s)
ab_info = stat_tracking_info(branch, &nr_ahead, &nr_behind,
&base, 0, s->ahead_behind_flags);
if (base) {
- base = shorten_unambiguous_ref(base, 0);
+ base = refs_shorten_unambiguous_ref(get_main_ref_store(the_repository),
+ base, 0);
fprintf(s->fp, "# branch.upstream %s%c", base, eol);
free((char *)base);