diff options
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 @@ -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; @@ -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 && @@ -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; } @@ -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()); @@ -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; } @@ -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", ¤t_rev)) + if (refs_read_ref(get_main_ref_store(the_repository), no_checkout ? "BISECT_HEAD" : "HEAD", ¤t_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); @@ -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" @@ -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 @@ -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; } @@ -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); @@ -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; @@ -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) @@ -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), + ...) @@ -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; +} @@ -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); @@ -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; } } @@ -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); @@ -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; @@ -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"); @@ -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; } @@ -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 @@ -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 @@ -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); @@ -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; } @@ -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; @@ -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); } @@ -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; @@ -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)) @@ -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; @@ -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); @@ -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; } @@ -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, ¤t_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(¤t_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(¤t_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(¤t_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(¤t_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; @@ -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); @@ -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")); @@ -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) @@ -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("ed, 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); @@ -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) @@ -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; } @@ -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(); +} @@ -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); +} @@ -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); } @@ -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); |