diff options
184 files changed, 5084 insertions, 2028 deletions
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 3428773b09..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 @@ -303,7 +302,7 @@ jobs: CC: ${{matrix.vector.cc}} CC_PACKAGE: ${{matrix.vector.cc_package}} jobname: ${{matrix.vector.jobname}} - runs_on_pool: ${{matrix.vector.pool}} + distro: ${{matrix.vector.pool}} runs-on: ${{matrix.vector.pool}} steps: - uses: actions/checkout@v4 @@ -342,12 +341,16 @@ jobs: vector: - jobname: linux-musl image: alpine + distro: alpine-latest - jobname: linux32 image: daald/ubuntu32:xenial + distro: ubuntu32-16.04 - jobname: pedantic image: fedora + distro: fedora-latest env: jobname: ${{matrix.vector.jobname}} + distro: ${{matrix.vector.distro}} runs-on: ubuntu-latest container: ${{matrix.vector.image}} steps: @@ -355,7 +358,7 @@ jobs: if: matrix.vector.jobname != 'linux32' - uses: actions/checkout@v1 # cannot be upgraded because Node.js Actions aren't supported in this container if: matrix.vector.jobname == 'linux32' - - run: ci/install-docker-dependencies.sh + - run: ci/install-dependencies.sh - run: ci/run-build-and-tests.sh - name: print test failures if: failure() && env.FAILED_TEST_ARTIFACTS != '' diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 619bf729fa..f676959ca0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,8 +9,10 @@ workflow: test:linux: image: $image + variables: + CUSTOM_PATH: "/custom" before_script: - - ./ci/install-docker-dependencies.sh + - ./ci/install-dependencies.sh script: - useradd builder --create-home - chown -R builder "${CI_PROJECT_DIR}" @@ -93,12 +95,21 @@ 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: jobname: StaticAnalysis before_script: - - ./ci/install-docker-dependencies.sh + - ./ci/install-dependencies.sh script: - ./ci/run-static-analysis.sh - ./ci/check-directional-formatting.bash diff --git a/Documentation/MyFirstContribution.txt b/Documentation/MyFirstContribution.txt index f06563e981..e41654c00a 100644 --- a/Documentation/MyFirstContribution.txt +++ b/Documentation/MyFirstContribution.txt @@ -1116,6 +1116,15 @@ $ git send-email --to=target@example.com psuh/*.patch NOTE: Check `git help send-email` for some other options which you may find valuable, such as changing the Reply-to address or adding more CC and BCC lines. +:contrib-scripts: footnoteref:[contrib-scripts,Scripts under `contrib/` are + +not part of the core `git` binary and must be called directly. Clone the Git + +codebase and run `perl contrib/contacts/git-contacts`.] + +NOTE: If you're not sure whom to CC, running `contrib/contacts/git-contacts` can +list potential reviewers. In addition, you can do `git send-email +--cc-cmd='perl contrib/contacts/git-contacts' feature/*.patch`{contrib-scripts} to +automatically pass this list of emails to `send-email`. + NOTE: When you are sending a real patch, it will go to git@vger.kernel.org - but please don't send your patchset from the tutorial to the real mailing list! For now, you can send it to yourself, to make sure you understand how it will look. 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 new file mode 100644 index 0000000000..966ad69a5d --- /dev/null +++ b/Documentation/RelNotes/2.46.0.txt @@ -0,0 +1,117 @@ +Git v2.46 Release Notes +======================= + +Backward Compatibility Notes + + (None at this moment) + +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. + + * 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). + + +Performance, Internal Implementation, Development Support etc. + + * Advertise "git contacts", a tool for newcomers to find people to + ask review for their patches, a bit more in our developer + documentation. + + * In addition to building the objects needed, try to link the objects + 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). + + +Fixes since v2.45 +----------------- + + * "git rebase --signoff" used to forget that it needs to add a + sign-off to the resulting commit when told to continue after a + conflict stops its operation. + (merge a6c2654f83 pw/rebase-m-signoff-fix later to maint). + + * The procedure to build multi-pack-index got confused by the + replace-refs mechanism, which has been corrected by disabling the + latter. + (merge 93e2ae1c95 xx/disable-replace-when-building-midx later to maint). + + * The "-k" and "--rfc" options of "format-patch" will now error out + when used together, as one tells us not to add anything to the + title of the commit, and the other one tells us to add "RFC" in + addition to "PATCH". + (merge cadcf58085 ds/format-patch-rfc-and-k later to maint). + + * "git stash -S" did not handle binary files correctly, which has + been corrected. + (merge 5fb7686409 aj/stash-staged-fix later to maint). + + * A scheduled "git maintenance" job is expected to work on all + repositories it knows about, but it stopped at the first one that + 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). + + * 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). diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index c647c7e1b4..0690ae2140 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -397,17 +397,57 @@ letter. [[send-patches]] === Sending your patches. +==== Choosing your reviewers + :security-ml: footnoteref:[security-ml,The Git Security mailing list: git-security@googlegroups.com] -Before sending any patches, please note that patches that may be +NOTE: Patches that may be security relevant should be submitted privately to the Git Security mailing list{security-ml}, instead of the public mailing list. -Learn to use format-patch and send-email if possible. These commands +:contrib-scripts: footnoteref:[contrib-scripts,Scripts under `contrib/` are + +not part of the core `git` binary and must be called directly. Clone the Git + +codebase and run `perl contrib/contacts/git-contacts`.] + +Send your patch with "To:" set to the mailing list, with "cc:" listing +people who are involved in the area you are touching (the `git-contacts` +script in `contrib/contacts/`{contrib-scripts} can help to +identify them), to solicit comments and reviews. Also, when you made +trial merges of your topic to `next` and `seen`, you may have noticed +work by others conflicting with your changes. There is a good possibility +that these people may know the area you are touching well. + +If you are using `send-email`, you can feed it the output of `git-contacts` like +this: + +.... + git send-email --cc-cmd='perl contrib/contacts/git-contacts' feature/*.patch +.... + +:current-maintainer: footnote:[The current maintainer: gitster@pobox.com] +:git-ml: footnote:[The mailing list: git@vger.kernel.org] + +After the list reached a consensus that it is a good idea to apply the +patch, re-send it with "To:" set to the maintainer{current-maintainer} +and "cc:" the list{git-ml} for inclusion. This is especially relevant +when the maintainer did not heavily participate in the discussion and +instead left the review to trusted others. + +Do not forget to add trailers such as `Acked-by:`, `Reviewed-by:` and +`Tested-by:` lines as necessary to credit people who helped your +patch, and "cc:" them when sending such a final version for inclusion. + +==== `format-patch` and `send-email` + +Learn to use `format-patch` and `send-email` if possible. These commands are optimized for the workflow of sending patches, avoiding many ways your existing e-mail client (often optimized for "multipart/*" MIME type e-mails) might render your patches unusable. +NOTE: Here we outline the procedure using `format-patch` and +`send-email`, but you can instead use GitGitGadget to send in your +patches (see link:MyFirstContribution.html[MyFirstContribution]). + People on the Git mailing list need to be able to read and comment on the changes you are submitting. It is important for a developer to be able to "quote" your changes, using standard @@ -415,10 +455,12 @@ e-mail tools, so that they may comment on specific portions of your code. For this reason, each patch should be submitted "inline" in a separate message. -Multiple related patches should be grouped into their own e-mail -thread to help readers find all parts of the series. To that end, -send them as replies to either an additional "cover letter" message -(see below), the first patch, or the respective preceding patch. +All subsequent versions of a patch series and other related patches should be +grouped into their own e-mail thread to help readers find all parts of the +series. To that end, send them as replies to either an additional "cover +letter" message (see below), the first patch, or the respective preceding patch. +Here is a link:MyFirstContribution.html#v2-git-send-email[step-by-step guide] on +how to submit updated versions of a patch series. If your log message (including your name on the `Signed-off-by` trailer) is not writable in ASCII, make sure that @@ -498,42 +540,14 @@ patch, format it as "multipart/signed", not a text/plain message that starts with `-----BEGIN PGP SIGNED MESSAGE-----`. That is not a text/plain, it's something else. -:security-ml-ref: footnoteref:[security-ml] - -As mentioned at the beginning of the section, patches that may be -security relevant should not be submitted to the public mailing list -mentioned below, but should instead be sent privately to the Git -Security mailing list{security-ml-ref}. - -Send your patch with "To:" set to the mailing list, with "cc:" listing -people who are involved in the area you are touching (the `git -contacts` command in `contrib/contacts/` can help to -identify them), to solicit comments and reviews. Also, when you made -trial merges of your topic to `next` and `seen`, you may have noticed -work by others conflicting with your changes. There is a good possibility -that these people may know the area you are touching well. - -:current-maintainer: footnote:[The current maintainer: gitster@pobox.com] -:git-ml: footnote:[The mailing list: git@vger.kernel.org] - -After the list reached a consensus that it is a good idea to apply the -patch, re-send it with "To:" set to the maintainer{current-maintainer} -and "cc:" the list{git-ml} for inclusion. This is especially relevant -when the maintainer did not heavily participate in the discussion and -instead left the review to trusted others. - -Do not forget to add trailers such as `Acked-by:`, `Reviewed-by:` and -`Tested-by:` lines as necessary to credit people who helped your -patch, and "cc:" them when sending such a final version for inclusion. - == Subsystems with dedicated maintainers 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: 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-credential.txt b/Documentation/git-credential.txt index 918a0aa42b..e41493292f 100644 --- a/Documentation/git-credential.txt +++ b/Documentation/git-credential.txt @@ -8,7 +8,7 @@ git-credential - Retrieve and store user credentials SYNOPSIS -------- ------------------ -'git credential' (fill|approve|reject) +'git credential' (fill|approve|reject|capability) ------------------ DESCRIPTION @@ -41,6 +41,9 @@ If the action is `reject`, git-credential will send the description to any configured credential helpers, which may erase any stored credentials matching the description. +If the action is `capability`, git-credential will announce any capabilities +it supports to standard output. + If the action is `approve` or `reject`, no output should be emitted. TYPICAL USE OF GIT CREDENTIAL @@ -111,7 +114,9 @@ attribute per line. Each attribute is specified by a key-value pair, separated by an `=` (equals) sign, followed by a newline. The key may contain any bytes except `=`, newline, or NUL. The value may -contain any bytes except newline or NUL. +contain any bytes except newline or NUL. A line, including the trailing +newline, may not exceed 65535 bytes in order to allow implementations to +parse efficiently. Attributes with keys that end with C-style array brackets `[]` can have multiple values. Each instance of a multi-valued attribute forms an @@ -178,6 +183,61 @@ empty string. Components which are missing from the URL (e.g., there is no username in the example above) will be left unset. +`authtype`:: + This indicates that the authentication scheme in question should be used. + Common values for HTTP and HTTPS include `basic`, `bearer`, and `digest`, + although the latter is insecure and should not be used. If `credential` + is used, this may be set to an arbitrary string suitable for the protocol in + question (usually HTTP). ++ +This value should not be sent unless the appropriate capability (see below) is +provided on input. + +`credential`:: + The pre-encoded credential, suitable for the protocol in question (usually + HTTP). If this key is sent, `authtype` is mandatory, and `username` and + `password` are not used. For HTTP, Git concatenates the `authtype` value and + this value with a single space to determine the `Authorization` header. ++ +This value should not be sent unless the appropriate capability (see below) is +provided on input. + +`ephemeral`:: + This boolean value indicates, if true, that the value in the `credential` + field should not be saved by the credential helper because its usefulness is + limited in time. For example, an HTTP Digest `credential` value is computed + using a nonce and reusing it will not result in successful authentication. + This may also be used for situations with short duration (e.g., 24-hour) + credentials. The default value is false. ++ +The credential helper will still be invoked with `store` or `erase` so that it +can determine whether the operation was successful. ++ +This value should not be sent unless the appropriate capability (see below) is +provided on input. + +`state[]`:: + This value provides an opaque state that will be passed back to this helper + if it is called again. Each different credential helper may specify this + once. The value should include a prefix unique to the credential helper and + should ignore values that don't match its prefix. ++ +This value should not be sent unless the appropriate capability (see below) is +provided on input. + +`continue`:: + This is a boolean value, which, if enabled, indicates that this + authentication is a non-final part of a multistage authentication step. This + is common in protocols such as NTLM and Kerberos, where two rounds of client + authentication are required, and setting this flag allows the credential + helper to implement the multistage authentication step. This flag should + only be sent if a further stage is required; that is, if another round of + authentication is expected. ++ +This value should not be sent unless the appropriate capability (see below) is +provided on input. This attribute is 'one-way' from a credential helper to +pass information to Git (or other programs invoking `git credential`). + `wwwauth[]`:: When an HTTP response is received by Git that includes one or more @@ -189,7 +249,45 @@ attribute 'wwwauth[]', where the order of the attributes is the same as they appear in the HTTP response. This attribute is 'one-way' from Git to pass additional information to credential helpers. -Unrecognised attributes are silently discarded. +`capability[]`:: + This signals that Git, or the helper, as appropriate, supports the capability + in question. This can be used to provide better, more specific data as part + of the protocol. A `capability[]` directive must precede any value depending + on it and these directives _should_ be the first item announced in the + protocol. ++ +There are two currently supported capabilities. The first is `authtype`, which +indicates that the `authtype`, `credential`, and `ephemeral` values are +understood. The second is `state`, which indicates that the `state[]` and +`continue` values are understood. ++ +It is not obligatory to use the additional features just because the capability +is supported, but they should not be provided without the capability. + +Unrecognised attributes and capabilities are silently discarded. + +[[CAPA-IOFMT]] +CAPABILITY INPUT/OUTPUT FORMAT +------------------------------ + +For `git credential capability`, the format is slightly different. First, a +`version 0` announcement is made to indicate the current version of the +protocol, and then each capability is announced with a line like `capability +authtype`. Credential helpers may also implement this format, again with the +`capability` argument. Additional lines may be added in the future; callers +should ignore lines which they don't understand. + +Because this is a new part of the credential helper protocol, older versions of +Git, as well as some credential helpers, may not support it. If a non-zero +exit status is received, or if the first line doesn't start with the word +`version` and a space, callers should assume that no capabilities are supported. + +The intention of this format is to differentiate it from the credential output +in an unambiguous way. It is possible to use very simple credential helpers +(e.g., inline shell scripts) which always produce identical output. Using a +distinct format allows users to continue to use this syntax without having to +worry about correctly implementing capability advertisements or accidentally +confusing callers querying for capabilities. GIT --- diff --git a/Documentation/git-for-each-repo.txt b/Documentation/git-for-each-repo.txt index 94bd19da26..abe3527aac 100644 --- a/Documentation/git-for-each-repo.txt +++ b/Documentation/git-for-each-repo.txt @@ -42,6 +42,15 @@ These config values are loaded from system, global, and local Git config, as available. If `git for-each-repo` is run in a directory that is not a Git repository, then only the system and global config is used. +--keep-going:: + Continue with the remaining repositories if the command failed + on a repository. The exit code will still indicate that the + overall operation was not successful. ++ +Note that the exact exit code of the failing command is not passed +through as the exit code of the `for-each-repo` command: If the command +failed in any of the specified repositories, the overall exit code will +be 1. SUBPROCESS BEHAVIOR ------------------- diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 728bb3821c..369af2c4a7 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -20,7 +20,7 @@ SYNOPSIS [--in-reply-to=<message-id>] [--suffix=.<sfx>] [--ignore-if-in-upstream] [--always] [--cover-from-description=<mode>] - [--rfc] [--subject-prefix=<subject-prefix>] + [--rfc[=<rfc>]] [--subject-prefix=<subject-prefix>] [(--reroll-count|-v) <n>] [--to=<email>] [--cc=<email>] [--[no-]cover-letter] [--quiet] @@ -238,10 +238,21 @@ the patches (with a value of e.g. "PATCH my-project"). value of the `format.filenameMaxLength` configuration variable, or 64 if unconfigured. ---rfc:: - Prepends "RFC" to the subject prefix (producing "RFC PATCH" by - default). RFC means "Request For Comments"; use this when sending - an experimental patch for discussion rather than application. +--rfc[=<rfc>]:: + Prepends the string _<rfc>_ (defaults to "RFC") to + the subject prefix. As the subject prefix defaults to + "PATCH", you'll get "RFC PATCH" by default. ++ +RFC means "Request For Comments"; use this when sending +an experimental patch for discussion rather than application. +"--rfc=WIP" may also be a useful way to indicate that a patch +is not complete yet ("WIP" stands for "Work In Progress"). ++ +If the convention of the receiving community for a particular extra +string is to have it _after_ the subject prefix, the string _<rfc>_ +can be prefixed with a dash ("`-`") to signal that the the rest of +the _<rfc>_ string should be appended to the subject prefix instead, +e.g., `--rfc='-(WIP)'` results in "PATCH (WIP)". -v <n>:: --reroll-count=<n>:: 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-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-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..024a01df6c 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -1067,6 +1067,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/glossary-content.txt b/Documentation/glossary-content.txt index d71b199955..1272809e13 100644 --- a/Documentation/glossary-content.txt +++ b/Documentation/glossary-content.txt @@ -576,7 +576,8 @@ The most notable example is `HEAD`. [[def_refspec]]refspec:: A "refspec" is used by <<def_fetch,fetch>> and <<def_push,push>> to describe the mapping between remote - <<def_ref,ref>> and local ref. + <<def_ref,ref>> and local ref. See linkgit:git-fetch[1] or + linkgit:git-push[1] for details. [[def_remote]]remote repository:: A <<def_repository,repository>> which is used to track the same diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 433cf41e59..814cdcf95d 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v2.45.0 +DEF_VER=v2.45.GIT LF=' ' @@ -409,6 +409,9 @@ include shared.mak # to the "<name>" of the corresponding `compat/fsmonitor/fsm-settings-<name>.c` # that implements the `fsm_os_settings__*()` routines. # +# Define LINK_FUZZ_PROGRAMS if you want `make all` to also build the fuzz test +# programs in oss-fuzz/. +# # === Optional library: libintl === # # Define NO_GETTEXT if you don't want Git output to be translated. @@ -752,23 +755,6 @@ SCRIPTS = $(SCRIPT_SH_GEN) \ ETAGS_TARGET = TAGS -# If you add a new fuzzer, please also make sure to run it in -# ci/run-build-and-minimal-fuzzers.sh so that we make sure it still links and -# runs in the future. -FUZZ_OBJS += oss-fuzz/dummy-cmd-main.o -FUZZ_OBJS += oss-fuzz/fuzz-commit-graph.o -FUZZ_OBJS += oss-fuzz/fuzz-config.o -FUZZ_OBJS += oss-fuzz/fuzz-date.o -FUZZ_OBJS += oss-fuzz/fuzz-pack-headers.o -FUZZ_OBJS += oss-fuzz/fuzz-pack-idx.o -.PHONY: fuzz-objs -fuzz-objs: $(FUZZ_OBJS) - -# Always build fuzz objects even if not testing, to prevent bit-rot. -all:: $(FUZZ_OBJS) - -FUZZ_PROGRAMS += $(patsubst %.o,%,$(filter-out %dummy-cmd-main.o,$(FUZZ_OBJS))) - # Empty... EXTRA_PROGRAMS = @@ -808,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 @@ -1347,7 +1334,6 @@ 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 @@ -2373,6 +2359,29 @@ ifndef NO_TCLTK endif $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) SHELL_PATH='$(SHELL_PATH_SQ)' PERL_PATH='$(PERL_PATH_SQ)' +# If you add a new fuzzer, please also make sure to run it in +# ci/run-build-and-minimal-fuzzers.sh so that we make sure it still links and +# runs in the future. +FUZZ_OBJS += oss-fuzz/dummy-cmd-main.o +FUZZ_OBJS += oss-fuzz/fuzz-commit-graph.o +FUZZ_OBJS += oss-fuzz/fuzz-config.o +FUZZ_OBJS += oss-fuzz/fuzz-date.o +FUZZ_OBJS += oss-fuzz/fuzz-pack-headers.o +FUZZ_OBJS += oss-fuzz/fuzz-pack-idx.o +.PHONY: fuzz-objs +fuzz-objs: $(FUZZ_OBJS) + +# Always build fuzz objects even if not testing, to prevent bit-rot. +all:: $(FUZZ_OBJS) + +FUZZ_PROGRAMS += $(patsubst %.o,%,$(filter-out %dummy-cmd-main.o,$(FUZZ_OBJS))) + +# Build fuzz programs when possible, even without the necessary fuzzing support, +# to prevent bit-rot. +ifdef LINK_FUZZ_PROGRAMS +all:: $(FUZZ_PROGRAMS) +endif + please_set_SHELL_PATH_to_a_more_modern_shell: @$$(:) @@ -2656,7 +2665,6 @@ REFTABLE_OBJS += reftable/merged.o REFTABLE_OBJS += reftable/pq.o REFTABLE_OBJS += reftable/reader.o REFTABLE_OBJS += reftable/record.o -REFTABLE_OBJS += reftable/refname.o REFTABLE_OBJS += reftable/generic.o REFTABLE_OBJS += reftable/stack.o REFTABLE_OBJS += reftable/tree.o @@ -2669,7 +2677,6 @@ REFTABLE_TEST_OBJS += reftable/merged_test.o REFTABLE_TEST_OBJS += reftable/pq_test.o REFTABLE_TEST_OBJS += reftable/record_test.o REFTABLE_TEST_OBJS += reftable/readwrite_test.o -REFTABLE_TEST_OBJS += reftable/refname_test.o REFTABLE_TEST_OBJS += reftable/stack_test.o REFTABLE_TEST_OBJS += reftable/test_framework.o REFTABLE_TEST_OBJS += reftable/tree_test.o @@ -3228,7 +3235,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) @@ -3858,22 +3865,22 @@ cover_db_html: cover_db # # An example command to build against libFuzzer from LLVM 11.0.0: # -# make CC=clang CXX=clang++ \ +# make CC=clang FUZZ_CXX=clang++ \ # CFLAGS="-fsanitize=fuzzer-no-link,address" \ # LIB_FUZZING_ENGINE="-fsanitize=fuzzer,address" \ # fuzz-all # +FUZZ_CXX ?= $(CC) FUZZ_CXXFLAGS ?= $(ALL_CFLAGS) .PHONY: fuzz-all +fuzz-all: $(FUZZ_PROGRAMS) $(FUZZ_PROGRAMS): %: %.o oss-fuzz/dummy-cmd-main.o $(GITLIBS) GIT-LDFLAGS - $(QUIET_LINK)$(CXX) $(FUZZ_CXXFLAGS) -o $@ $(ALL_LDFLAGS) \ + $(QUIET_LINK)$(FUZZ_CXX) $(FUZZ_CXXFLAGS) -o $@ $(ALL_LDFLAGS) \ -Wl,--allow-multiple-definition \ $(filter %.o,$^) $(filter %.a,$^) $(LIBS) $(LIB_FUZZING_ENGINE) -fuzz-all: $(FUZZ_PROGRAMS) - $(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o $(UNIT_TEST_DIR)/test-lib.o $(GITLIBS) GIT-LDFLAGS $(call mkdir_p_parent_template) $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \ @@ -3881,5 +3888,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 @@ -1 +1 @@ -Documentation/RelNotes/2.45.0.txt
\ No newline at end of file +Documentation/RelNotes/2.46.0.txt
\ No newline at end of file diff --git a/add-patch.c b/add-patch.c index 0997d4af73..2252895c28 100644 --- a/add-patch.c +++ b/add-patch.c @@ -293,10 +293,9 @@ static void err(struct add_p_state *s, const char *fmt, ...) va_list args; va_start(args, fmt); - fputs(s->s.error_color, stderr); - vfprintf(stderr, fmt, args); - fputs(s->s.reset_color, stderr); - fputc('\n', stderr); + fputs(s->s.error_color, stdout); + vprintf(fmt, args); + puts(s->s.reset_color); va_end(args); } @@ -1326,7 +1325,7 @@ static int apply_for_checkout(struct add_p_state *s, struct strbuf *diff, err(s, _("Nothing was applied.\n")); } else /* As a last resort, show the diff to the user */ - fwrite(diff->buf, diff->len, 1, stderr); + fwrite(diff->buf, diff->len, 1, stdout); return 0; } @@ -1668,7 +1667,7 @@ soft_increment: } } else if (s->answer.buf[0] == 'p') { rendered_hunk_index = -1; - } else { + } else if (s->answer.buf[0] == '?') { const char *p = _(help_patch_remainder), *eol = p; color_fprintf(stdout, s->s.help_color, "%s", @@ -1692,6 +1691,9 @@ soft_increment: color_fprintf_ln(stdout, s->s.help_color, "%.*s", (int)(eol - p), p); } + } else { + err(s, _("Unknown command '%s' (use '?' for help)"), + s->answer.buf); } } @@ -1778,9 +1780,9 @@ int run_add_p(struct repository *r, enum add_p_mode mode, break; if (s.file_diff_nr == 0) - fprintf(stderr, _("No changes.\n")); + err(&s, _("No changes.")); else if (binary_count == s.file_diff_nr) - fprintf(stderr, _("Only binary files changed.\n")); + err(&s, _("Only binary files changed.")); add_p_state_clear(&s); return 0; @@ -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; } @@ -1223,13 +1222,6 @@ 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 || !is_null_oid(attr_source)) return; diff --git a/builtin/add.c b/builtin/add.c index b7d3ff1e28..3dfcfc5fba 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -3,7 +3,7 @@ * * Copyright (C) 2006 Linus Torvalds */ -#define USE_THE_INDEX_VARIABLE + #include "builtin.h" #include "advice.h" #include "config.h" @@ -40,20 +40,20 @@ static int chmod_pathspec(struct pathspec *pathspec, char flip, int show_only) { int i, ret = 0; - for (i = 0; i < the_index.cache_nr; i++) { - struct cache_entry *ce = the_index.cache[i]; + for (i = 0; i < the_repository->index->cache_nr; i++) { + struct cache_entry *ce = the_repository->index->cache[i]; int err; if (!include_sparse && (ce_skip_worktree(ce) || - !path_in_sparse_checkout(ce->name, &the_index))) + !path_in_sparse_checkout(ce->name, the_repository->index))) continue; - if (pathspec && !ce_path_match(&the_index, ce, pathspec, NULL)) + if (pathspec && !ce_path_match(the_repository->index, ce, pathspec, NULL)) continue; if (!show_only) - err = chmod_index_entry(&the_index, ce, flip); + err = chmod_index_entry(the_repository->index, ce, flip); else err = S_ISREG(ce->ce_mode) ? 0 : -1; @@ -68,20 +68,20 @@ static int renormalize_tracked_files(const struct pathspec *pathspec, int flags) { int i, retval = 0; - for (i = 0; i < the_index.cache_nr; i++) { - struct cache_entry *ce = the_index.cache[i]; + for (i = 0; i < the_repository->index->cache_nr; i++) { + struct cache_entry *ce = the_repository->index->cache[i]; if (!include_sparse && (ce_skip_worktree(ce) || - !path_in_sparse_checkout(ce->name, &the_index))) + !path_in_sparse_checkout(ce->name, the_repository->index))) continue; if (ce_stage(ce)) continue; /* do not touch unmerged paths */ if (!S_ISREG(ce->ce_mode) && !S_ISLNK(ce->ce_mode)) continue; /* do not touch non blobs */ - if (pathspec && !ce_path_match(&the_index, ce, pathspec, NULL)) + if (pathspec && !ce_path_match(the_repository->index, ce, pathspec, NULL)) continue; - retval |= add_file_to_index(&the_index, ce->name, + retval |= add_file_to_index(the_repository->index, ce->name, flags | ADD_CACHE_RENORMALIZE); } @@ -100,11 +100,11 @@ static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec, i = dir->nr; while (--i >= 0) { struct dir_entry *entry = *src++; - if (dir_path_match(&the_index, entry, pathspec, prefix, seen)) + if (dir_path_match(the_repository->index, entry, pathspec, prefix, seen)) *dst++ = entry; } dir->nr = dst - dir->entries; - add_pathspec_matches_against_index(pathspec, &the_index, seen, + add_pathspec_matches_against_index(pathspec, the_repository->index, seen, PS_IGNORE_SKIP_WORKTREE); return seen; } @@ -119,14 +119,14 @@ static int refresh(int verbose, const struct pathspec *pathspec) (verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET); seen = xcalloc(pathspec->nr, 1); - refresh_index(&the_index, flags, pathspec, seen, + refresh_index(the_repository->index, flags, pathspec, seen, _("Unstaged changes after refreshing the index:")); for (i = 0; i < pathspec->nr; i++) { if (!seen[i]) { const char *path = pathspec->items[i].original; if (matches_skip_worktree(pathspec, i, &skip_worktree_seen) || - !path_in_sparse_checkout(path, &the_index)) { + !path_in_sparse_checkout(path, the_repository->index)) { string_list_append(&only_match_skip_worktree, pathspec->items[i].original); } else { @@ -338,12 +338,12 @@ static int add_files(struct dir_struct *dir, int flags) for (i = 0; i < dir->nr; i++) { if (!include_sparse && - !path_in_sparse_checkout(dir->entries[i]->name, &the_index)) { + !path_in_sparse_checkout(dir->entries[i]->name, the_repository->index)) { string_list_append(&matched_sparse_paths, dir->entries[i]->name); continue; } - if (add_file_to_index(&the_index, dir->entries[i]->name, flags)) { + if (add_file_to_index(the_repository->index, dir->entries[i]->name, flags)) { if (!ignore_add_errors) die(_("adding files failed")); exit_status = 1; @@ -461,8 +461,8 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (repo_read_index_preload(the_repository, &pathspec, 0) < 0) die(_("index file corrupt")); - die_in_unpopulated_submodule(&the_index, prefix); - die_path_inside_submodule(&the_index, &pathspec); + die_in_unpopulated_submodule(the_repository->index, prefix); + die_path_inside_submodule(the_repository->index, &pathspec); if (add_new_files) { int baselen; @@ -474,7 +474,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) } /* This picks up the paths that are not tracked */ - baselen = fill_directory(&dir, &the_index, &pathspec); + baselen = fill_directory(&dir, the_repository->index, &pathspec); if (pathspec.nr) seen = prune_directory(&dir, &pathspec, baselen); } @@ -491,7 +491,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (!seen) seen = find_pathspecs_matching_against_index(&pathspec, - &the_index, PS_IGNORE_SKIP_WORKTREE); + the_repository->index, PS_IGNORE_SKIP_WORKTREE); /* * file_exists() assumes exact match @@ -527,8 +527,8 @@ int cmd_add(int argc, const char **argv, const char *prefix) !file_exists(path)) { if (ignore_missing) { int dtype = DT_UNKNOWN; - if (is_excluded(&dir, &the_index, path, &dtype)) - dir_add_ignored(&dir, &the_index, + if (is_excluded(&dir, the_repository->index, path, &dtype)) + dir_add_ignored(&dir, the_repository->index, path, pathspec.items[i].len); } else die(_("pathspec '%s' did not match any files"), @@ -569,7 +569,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) end_odb_transaction(); finish: - if (write_locked_index(&the_index, &lock_file, + if (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK | SKIP_IF_UNCHANGED)) die(_("unable to write new index file")); diff --git a/builtin/am.c b/builtin/am.c index 022cae2e8d..4db2bc3c2f 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -3,7 +3,7 @@ * * Based on git-am.sh by Junio C Hamano. */ -#define USE_THE_INDEX_VARIABLE + #include "builtin.h" #include "abspath.h" #include "advice.h" @@ -1536,8 +1536,8 @@ static int run_apply(const struct am_state *state, const char *index_file) if (index_file) { /* Reload index as apply_all_patches() will have modified it. */ - discard_index(&the_index); - read_index_from(&the_index, index_file, get_git_dir()); + discard_index(the_repository->index); + read_index_from(the_repository->index, index_file, get_git_dir()); } return 0; @@ -1579,10 +1579,10 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa if (build_fake_ancestor(state, index_path)) return error("could not build fake ancestor"); - discard_index(&the_index); - read_index_from(&the_index, index_path, get_git_dir()); + discard_index(the_repository->index); + read_index_from(the_repository->index, index_path, get_git_dir()); - if (write_index_as_tree(&orig_tree, &the_index, index_path, 0, NULL)) + if (write_index_as_tree(&orig_tree, the_repository->index, index_path, 0, NULL)) return error(_("Repository lacks necessary blobs to fall back on 3-way merge.")); say(state, stdout, _("Using index info to reconstruct a base tree...")); @@ -1608,12 +1608,12 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa return error(_("Did you hand edit your patch?\n" "It does not apply to blobs recorded in its index.")); - if (write_index_as_tree(&their_tree, &the_index, index_path, 0, NULL)) + if (write_index_as_tree(&their_tree, the_repository->index, index_path, 0, NULL)) return error("could not write tree"); say(state, stdout, _("Falling back to patching base and 3-way merge...")); - discard_index(&the_index); + discard_index(the_repository->index); repo_read_index(the_repository); /* @@ -1660,7 +1660,7 @@ static void do_commit(const struct am_state *state) if (!state->no_verify && run_hooks("pre-applypatch")) exit(1); - if (write_index_as_tree(&tree, &the_index, get_index_file(), 0, NULL)) + if (write_index_as_tree(&tree, the_repository->index, get_index_file(), 0, NULL)) die(_("git write-tree failed to write a tree")); if (!repo_get_oid_commit(the_repository, "HEAD", &parent)) { @@ -1948,7 +1948,7 @@ static void am_resolve(struct am_state *state, int allow_empty) } } - if (unmerged_index(&the_index)) { + if (unmerged_index(the_repository->index)) { printf_ln(_("You still have unmerged paths in your index.\n" "You should 'git add' each file with resolved conflicts to mark them as such.\n" "You might run `git rm` on a file to accept \"deleted by them\" for it.")); @@ -1987,12 +1987,12 @@ static int fast_forward_to(struct tree *head, struct tree *remote, int reset) repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR); - refresh_index(&the_index, REFRESH_QUIET, NULL, NULL, NULL); + refresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL); memset(&opts, 0, sizeof(opts)); opts.head_idx = 1; - opts.src_index = &the_index; - opts.dst_index = &the_index; + opts.src_index = the_repository->index; + opts.dst_index = the_repository->index; opts.update = 1; opts.merge = 1; opts.reset = reset ? UNPACK_RESET_PROTECT_UNTRACKED : 0; @@ -2006,7 +2006,7 @@ static int fast_forward_to(struct tree *head, struct tree *remote, int reset) return -1; } - if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) + if (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); return 0; @@ -2029,8 +2029,8 @@ static int merge_tree(struct tree *tree) memset(&opts, 0, sizeof(opts)); opts.head_idx = 1; - opts.src_index = &the_index; - opts.dst_index = &the_index; + opts.src_index = the_repository->index; + opts.dst_index = the_repository->index; opts.merge = 1; opts.fn = oneway_merge; init_tree_desc(&t[0], &tree->object.oid, tree->buffer, tree->size); @@ -2040,7 +2040,7 @@ static int merge_tree(struct tree *tree) return -1; } - if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) + if (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); return 0; @@ -2068,7 +2068,7 @@ static int clean_index(const struct object_id *head, const struct object_id *rem if (fast_forward_to(head_tree, head_tree, 1)) return -1; - if (write_index_as_tree(&index, &the_index, get_index_file(), 0, NULL)) + if (write_index_as_tree(&index, the_repository->index, get_index_file(), 0, NULL)) return -1; index_tree = parse_tree_indirect(&index); diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 0c948f40fb..43a1d7ac49 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -3,7 +3,7 @@ * * Copyright (C) Linus Torvalds, 2005 */ -#define USE_THE_INDEX_VARIABLE + #include "builtin.h" #include "config.h" #include "convert.h" @@ -77,7 +77,7 @@ static int filter_object(const char *path, unsigned mode, struct checkout_metadata meta; init_checkout_metadata(&meta, NULL, NULL, oid); - if (convert_to_working_tree(&the_index, path, *buf, *size, &strbuf, &meta)) { + if (convert_to_working_tree(the_repository->index, path, *buf, *size, &strbuf, &meta)) { free(*buf); *size = strbuf.len; *buf = strbuf_detach(&strbuf, NULL); diff --git a/builtin/check-attr.c b/builtin/check-attr.c index c1da1d184e..9376810710 100644 --- a/builtin/check-attr.c +++ b/builtin/check-attr.c @@ -1,4 +1,3 @@ -#define USE_THE_INDEX_VARIABLE #include "builtin.h" #include "config.h" #include "attr.h" @@ -71,9 +70,9 @@ static void check_attr(const char *prefix, struct attr_check *check, prefix_path(prefix, prefix ? strlen(prefix) : 0, file); if (collect_all) { - git_all_attrs(&the_index, full_path, check); + git_all_attrs(the_repository->index, full_path, check); } else { - git_check_attr(&the_index, full_path, check); + git_check_attr(the_repository->index, full_path, check); } output_attr(check, file); diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c index 906cd96753..6c43430ec4 100644 --- a/builtin/check-ignore.c +++ b/builtin/check-ignore.c @@ -1,4 +1,3 @@ -#define USE_THE_INDEX_VARIABLE #include "builtin.h" #include "config.h" #include "dir.h" @@ -95,21 +94,21 @@ static int check_ignore(struct dir_struct *dir, PATHSPEC_KEEP_ORDER, prefix, argv); - die_path_inside_submodule(&the_index, &pathspec); + die_path_inside_submodule(the_repository->index, &pathspec); /* * look for pathspecs matching entries in the index, since these * should not be ignored, in order to be consistent with * 'git status', 'git add' etc. */ - seen = find_pathspecs_matching_against_index(&pathspec, &the_index, + seen = find_pathspecs_matching_against_index(&pathspec, the_repository->index, PS_HEED_SKIP_WORKTREE); for (i = 0; i < pathspec.nr; i++) { full_path = pathspec.items[i].match; pattern = NULL; if (!seen[i]) { int dtype = DT_UNKNOWN; - pattern = last_matching_pattern(dir, &the_index, + pattern = last_matching_pattern(dir, the_repository->index, full_path, &dtype); if (!verbose && pattern && pattern->flags & PATTERN_FLAG_NEGATIVE) diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index 2e086a204d..29e744d11b 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -4,7 +4,7 @@ * Copyright (C) 2005 Linus Torvalds * */ -#define USE_THE_INDEX_VARIABLE + #include "builtin.h" #include "config.h" #include "gettext.h" @@ -69,7 +69,7 @@ static void write_tempfile_record(const char *name, const char *prefix) static int checkout_file(const char *name, const char *prefix) { int namelen = strlen(name); - int pos = index_name_pos(&the_index, name, namelen); + int pos = index_name_pos(the_repository->index, name, namelen); int has_same_name = 0; int is_file = 0; int is_skipped = 1; @@ -79,8 +79,8 @@ static int checkout_file(const char *name, const char *prefix) if (pos < 0) pos = -pos - 1; - while (pos < the_index.cache_nr) { - struct cache_entry *ce = the_index.cache[pos]; + while (pos <the_repository->index->cache_nr) { + struct cache_entry *ce =the_repository->index->cache[pos]; if (ce_namelen(ce) != namelen || memcmp(ce->name, name, namelen)) break; @@ -140,8 +140,8 @@ static int checkout_all(const char *prefix, int prefix_length) int i, errs = 0; struct cache_entry *last_ce = NULL; - for (i = 0; i < the_index.cache_nr ; i++) { - struct cache_entry *ce = the_index.cache[i]; + for (i = 0; i < the_repository->index->cache_nr ; i++) { + struct cache_entry *ce = the_repository->index->cache[i]; if (S_ISSPARSEDIR(ce->ce_mode)) { if (!ce_skip_worktree(ce)) @@ -154,8 +154,8 @@ static int checkout_all(const char *prefix, int prefix_length) * first entry inside the expanded sparse directory). */ if (ignore_skip_worktree) { - ensure_full_index(&the_index); - ce = the_index.cache[i]; + ensure_full_index(the_repository->index); + ce = the_repository->index->cache[i]; } } @@ -260,7 +260,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_checkout_index_options, builtin_checkout_index_usage, 0); - state.istate = &the_index; + state.istate = the_repository->index; state.force = force; state.quiet = quiet; state.not_new = not_new; @@ -280,7 +280,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) */ if (index_opt && !state.base_dir_len && !to_tempfile) { state.refresh_cache = 1; - state.istate = &the_index; + state.istate = the_repository->index; repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR); } @@ -339,7 +339,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) return 1; if (is_lock_file_locked(&lock_file) && - write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) + write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK)) die("Unable to write new index file"); return 0; } diff --git a/builtin/checkout.c b/builtin/checkout.c index 71e6036aab..8a1d13b399 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -1,4 +1,3 @@ -#define USE_THE_INDEX_VARIABLE #include "builtin.h" #include "advice.h" #include "branch.h" @@ -146,7 +145,7 @@ static int update_some(const struct object_id *oid, struct strbuf *base, return READ_TREE_RECURSIVE; len = base->len + strlen(pathname); - ce = make_empty_cache_entry(&the_index, len); + ce = make_empty_cache_entry(the_repository->index, len); oidcpy(&ce->oid, oid); memcpy(ce->name, base->buf, base->len); memcpy(ce->name + base->len, pathname, len - base->len); @@ -159,9 +158,9 @@ static int update_some(const struct object_id *oid, struct strbuf *base, * entry in place. Whether it is UPTODATE or not, checkout_entry will * do the right thing. */ - pos = index_name_pos(&the_index, ce->name, ce->ce_namelen); + pos = index_name_pos(the_repository->index, ce->name, ce->ce_namelen); if (pos >= 0) { - struct cache_entry *old = the_index.cache[pos]; + struct cache_entry *old = the_repository->index->cache[pos]; if (ce->ce_mode == old->ce_mode && !ce_intent_to_add(old) && oideq(&ce->oid, &old->oid)) { @@ -171,7 +170,7 @@ static int update_some(const struct object_id *oid, struct strbuf *base, } } - add_index_entry(&the_index, ce, + add_index_entry(the_repository->index, ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); return 0; } @@ -190,8 +189,8 @@ static int read_tree_some(struct tree *tree, const struct pathspec *pathspec) static int skip_same_name(const struct cache_entry *ce, int pos) { - while (++pos < the_index.cache_nr && - !strcmp(the_index.cache[pos]->name, ce->name)) + while (++pos < the_repository->index->cache_nr && + !strcmp(the_repository->index->cache[pos]->name, ce->name)) ; /* skip */ return pos; } @@ -199,9 +198,9 @@ static int skip_same_name(const struct cache_entry *ce, int pos) static int check_stage(int stage, const struct cache_entry *ce, int pos, int overlay_mode) { - while (pos < the_index.cache_nr && - !strcmp(the_index.cache[pos]->name, ce->name)) { - if (ce_stage(the_index.cache[pos]) == stage) + while (pos < the_repository->index->cache_nr && + !strcmp(the_repository->index->cache[pos]->name, ce->name)) { + if (ce_stage(the_repository->index->cache[pos]) == stage) return 0; pos++; } @@ -218,8 +217,8 @@ static int check_stages(unsigned stages, const struct cache_entry *ce, int pos) unsigned seen = 0; const char *name = ce->name; - while (pos < the_index.cache_nr) { - ce = the_index.cache[pos]; + while (pos < the_repository->index->cache_nr) { + ce = the_repository->index->cache[pos]; if (strcmp(name, ce->name)) break; seen |= (1 << ce_stage(ce)); @@ -235,10 +234,10 @@ static int checkout_stage(int stage, const struct cache_entry *ce, int pos, const struct checkout *state, int *nr_checkouts, int overlay_mode) { - while (pos < the_index.cache_nr && - !strcmp(the_index.cache[pos]->name, ce->name)) { - if (ce_stage(the_index.cache[pos]) == stage) - return checkout_entry(the_index.cache[pos], state, + while (pos < the_repository->index->cache_nr && + !strcmp(the_repository->index->cache[pos]->name, ce->name)) { + if (ce_stage(the_repository->index->cache[pos]) == stage) + return checkout_entry(the_repository->index->cache[pos], state, NULL, nr_checkouts); pos++; } @@ -256,7 +255,7 @@ static int checkout_merged(int pos, const struct checkout *state, int *nr_checkouts, struct mem_pool *ce_mem_pool, int conflict_style) { - struct cache_entry *ce = the_index.cache[pos]; + struct cache_entry *ce = the_repository->index->cache[pos]; const char *path = ce->name; mmfile_t ancestor, ours, theirs; enum ll_merge_result merge_status; @@ -269,7 +268,7 @@ static int checkout_merged(int pos, const struct checkout *state, int renormalize = 0; memset(threeway, 0, sizeof(threeway)); - while (pos < the_index.cache_nr) { + while (pos < the_repository->index->cache_nr) { int stage; stage = ce_stage(ce); if (!stage || strcmp(path, ce->name)) @@ -278,7 +277,7 @@ static int checkout_merged(int pos, const struct checkout *state, if (stage == 2) mode = create_ce_mode(ce->ce_mode); pos++; - ce = the_index.cache[pos]; + ce = the_repository->index->cache[pos]; } if (is_null_oid(&threeway[1]) || is_null_oid(&threeway[2])) return error(_("path '%s' does not have necessary versions"), path); @@ -356,7 +355,7 @@ static void mark_ce_for_checkout_overlay(struct cache_entry *ce, * match_pathspec() for _all_ entries when * opts->source_tree != NULL. */ - if (ce_path_match(&the_index, ce, &opts->pathspec, ps_matched)) + if (ce_path_match(the_repository->index, ce, &opts->pathspec, ps_matched)) ce->ce_flags |= CE_MATCHED; } @@ -367,7 +366,7 @@ static void mark_ce_for_checkout_no_overlay(struct cache_entry *ce, ce->ce_flags &= ~CE_MATCHED; if (!opts->ignore_skipworktree && ce_skip_worktree(ce)) return; - if (ce_path_match(&the_index, ce, &opts->pathspec, ps_matched)) { + if (ce_path_match(the_repository->index, ce, &opts->pathspec, ps_matched)) { ce->ce_flags |= CE_MATCHED; if (opts->source_tree && !(ce->ce_flags & CE_UPDATE)) /* @@ -391,7 +390,7 @@ static int checkout_worktree(const struct checkout_opts *opts, state.force = 1; state.refresh_cache = 1; - state.istate = &the_index; + state.istate = the_repository->index; mem_pool_init(&ce_mem_pool, 0); get_parallel_checkout_configs(&pc_workers, &pc_threshold); @@ -404,8 +403,8 @@ static int checkout_worktree(const struct checkout_opts *opts, if (pc_workers > 1) init_parallel_checkout(); - for (pos = 0; pos < the_index.cache_nr; pos++) { - struct cache_entry *ce = the_index.cache[pos]; + for (pos = 0; pos < the_repository->index->cache_nr; pos++) { + struct cache_entry *ce = the_repository->index->cache[pos]; if (ce->ce_flags & CE_MATCHED) { if (!ce_stage(ce)) { errs |= checkout_entry(ce, &state, @@ -429,7 +428,7 @@ static int checkout_worktree(const struct checkout_opts *opts, errs |= run_parallel_checkout(&state, pc_workers, pc_threshold, NULL, NULL); mem_pool_discard(&ce_mem_pool, should_validate_cache_entries()); - remove_marked_cache_entries(&the_index, 1); + remove_marked_cache_entries(the_repository->index, 1); remove_scheduled_dirs(); errs |= finish_delayed_checkout(&state, opts->show_progress); @@ -571,7 +570,7 @@ static int checkout_paths(const struct checkout_opts *opts, if (opts->source_tree) read_tree_some(opts->source_tree, &opts->pathspec); if (opts->merge) - unmerge_index(&the_index, &opts->pathspec, CE_MATCHED); + unmerge_index(the_repository->index, &opts->pathspec, CE_MATCHED); ps_matched = xcalloc(opts->pathspec.nr, 1); @@ -579,13 +578,13 @@ static int checkout_paths(const struct checkout_opts *opts, * Make sure all pathspecs participated in locating the paths * to be checked out. */ - for (pos = 0; pos < the_index.cache_nr; pos++) + for (pos = 0; pos < the_repository->index->cache_nr; pos++) if (opts->overlay_mode) - mark_ce_for_checkout_overlay(the_index.cache[pos], + mark_ce_for_checkout_overlay(the_repository->index->cache[pos], ps_matched, opts); else - mark_ce_for_checkout_no_overlay(the_index.cache[pos], + mark_ce_for_checkout_no_overlay(the_repository->index->cache[pos], ps_matched, opts); @@ -596,8 +595,8 @@ static int checkout_paths(const struct checkout_opts *opts, free(ps_matched); /* Any unmerged paths? */ - for (pos = 0; pos < the_index.cache_nr; pos++) { - const struct cache_entry *ce = the_index.cache[pos]; + for (pos = 0; pos < the_repository->index->cache_nr; pos++) { + const struct cache_entry *ce = the_repository->index->cache[pos]; if (ce->ce_flags & CE_MATCHED) { if (!ce_stage(ce)) continue; @@ -622,7 +621,7 @@ static int checkout_paths(const struct checkout_opts *opts, if (opts->checkout_worktree) errs |= checkout_worktree(opts, new_branch_info); else - remove_marked_cache_entries(&the_index, 1); + remove_marked_cache_entries(the_repository->index, 1); /* * Allow updating the index when checking out from the index. @@ -634,7 +633,7 @@ static int checkout_paths(const struct checkout_opts *opts, checkout_index = opts->checkout_index; if (checkout_index) { - if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) + if (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); } else { /* @@ -703,8 +702,8 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o, opts.merge = 1; opts.fn = oneway_merge; opts.verbose_update = o->show_progress; - opts.src_index = &the_index; - opts.dst_index = &the_index; + opts.src_index = the_repository->index; + opts.dst_index = the_repository->index; init_checkout_metadata(&opts.meta, info->refname, info->commit ? &info->commit->object.oid : null_oid(), NULL); @@ -756,12 +755,12 @@ static void init_topts(struct unpack_trees_options *topts, int merge, { memset(topts, 0, sizeof(*topts)); topts->head_idx = -1; - topts->src_index = &the_index; - topts->dst_index = &the_index; + topts->src_index = the_repository->index; + topts->dst_index = the_repository->index; setup_unpack_trees_porcelain(topts, "checkout"); - topts->initial_checkout = is_index_unborn(&the_index); + topts->initial_checkout = is_index_unborn(the_repository->index); topts->update = 1; topts->merge = 1; topts->quiet = merge && old_commit; @@ -783,7 +782,7 @@ static int merge_working_tree(const struct checkout_opts *opts, if (repo_read_index_preload(the_repository, NULL, 0) < 0) return error(_("index file corrupt")); - resolve_undo_clear_index(&the_index); + resolve_undo_clear_index(the_repository->index); if (opts->new_orphan_branch && opts->orphan_from_empty_tree) { if (new_branch_info->commit) BUG("'switch --orphan' should never accept a commit as starting point"); @@ -807,9 +806,9 @@ static int merge_working_tree(const struct checkout_opts *opts, struct unpack_trees_options topts; const struct object_id *old_commit_oid; - refresh_index(&the_index, REFRESH_QUIET, NULL, NULL, NULL); + refresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL); - if (unmerged_index(&the_index)) { + if (unmerged_index(the_repository->index)) { error(_("you need to resolve your current index first")); return 1; } @@ -919,10 +918,10 @@ static int merge_working_tree(const struct checkout_opts *opts, } } - if (!cache_tree_fully_valid(the_index.cache_tree)) - cache_tree_update(&the_index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR); + if (!cache_tree_fully_valid(the_repository->index->cache_tree)) + cache_tree_update(the_repository->index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR); - if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) + if (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); if (!opts->discard_changes && !opts->quiet && new_branch_info->commit) diff --git a/builtin/clean.c b/builtin/clean.c index 29efe84153..ded5a91534 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -6,7 +6,6 @@ * Based on git-clean.sh by Pavel Roskin */ -#define USE_THE_INDEX_VARIABLE #include "builtin.h" #include "abspath.h" #include "config.h" @@ -714,7 +713,7 @@ static int filter_by_patterns_cmd(void) for_each_string_list_item(item, &del_list) { int dtype = DT_UNKNOWN; - if (is_excluded(&dir, &the_index, item->string, &dtype)) { + if (is_excluded(&dir, the_repository->index, item->string, &dtype)) { *item->string = '\0'; changed++; } @@ -1021,7 +1020,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) PATHSPEC_PREFER_CWD, prefix, argv); - fill_directory(&dir, &the_index, &pathspec); + fill_directory(&dir, the_repository->index, &pathspec); correct_untracked_entries(&dir); for (i = 0; i < dir.nr; i++) { @@ -1029,7 +1028,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) struct stat st; const char *rel; - if (!index_name_is_other(&the_index, ent->name, ent->len)) + if (!index_name_is_other(the_repository->index, ent->name, ent->len)) continue; if (lstat(ent->name, &st)) diff --git a/builtin/clone.c b/builtin/clone.c index 74ec14542e..0e7cf198f5 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -8,7 +8,6 @@ * Clone a repository into a different directory that does not yet exist. */ -#define USE_THE_INDEX_VARIABLE #include "builtin.h" #include "abspath.h" #include "advice.h" @@ -329,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); @@ -377,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; @@ -392,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) @@ -731,8 +759,8 @@ static int checkout(int submodule_progress, int filter_submodules) opts.preserve_ignored = 0; opts.fn = oneway_merge; opts.verbose_update = (option_verbosity >= 0); - opts.src_index = &the_index; - opts.dst_index = &the_index; + opts.src_index = the_repository->index; + opts.dst_index = the_repository->index; init_checkout_metadata(&opts.meta, head, &oid, NULL); tree = parse_tree_indirect(&oid); @@ -746,7 +774,7 @@ static int checkout(int submodule_progress, int filter_submodules) free(head); - if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) + if (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); err |= run_hooks_l("post-checkout", oid_to_hex(null_oid()), @@ -938,6 +966,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; @@ -957,6 +987,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) @@ -1118,7 +1155,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); @@ -1507,6 +1544,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 6e1484446b..78bfae2164 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -5,7 +5,6 @@ * Based on git-commit.sh by Junio C Hamano and Linus Torvalds */ -#define USE_THE_INDEX_VARIABLE #include "builtin.h" #include "advice.h" #include "config.h" @@ -38,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" @@ -142,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; @@ -266,19 +258,19 @@ static int list_paths(struct string_list *list, const char *with_tree, if (with_tree) { char *max_prefix = common_prefix(pattern); - overlay_tree_on_index(&the_index, with_tree, max_prefix); + overlay_tree_on_index(the_repository->index, with_tree, max_prefix); free(max_prefix); } /* TODO: audit for interaction with sparse-index. */ - ensure_full_index(&the_index); - for (i = 0; i < the_index.cache_nr; i++) { - const struct cache_entry *ce = the_index.cache[i]; + ensure_full_index(the_repository->index); + for (i = 0; i < the_repository->index->cache_nr; i++) { + const struct cache_entry *ce = the_repository->index->cache[i]; struct string_list_item *item; if (ce->ce_flags & CE_UPDATE) continue; - if (!ce_path_match(&the_index, ce, pattern, m)) + if (!ce_path_match(the_repository->index, ce, pattern, m)) continue; item = string_list_insert(list, ce->name); if (ce_skip_worktree(ce)) @@ -302,10 +294,10 @@ static void add_remove_files(struct string_list *list) continue; if (!lstat(p->string, &st)) { - if (add_to_index(&the_index, p->string, &st, 0)) + if (add_to_index(the_repository->index, p->string, &st, 0)) die(_("updating files failed")); } else - remove_file_from_index(&the_index, p->string); + remove_file_from_index(the_repository->index, p->string); } } @@ -316,7 +308,7 @@ static void create_base_index(const struct commit *current_head) struct tree_desc t; if (!current_head) { - discard_index(&the_index); + discard_index(the_repository->index); return; } @@ -324,8 +316,8 @@ static void create_base_index(const struct commit *current_head) opts.head_idx = 1; opts.index_only = 1; opts.merge = 1; - opts.src_index = &the_index; - opts.dst_index = &the_index; + opts.src_index = the_repository->index; + opts.dst_index = the_repository->index; opts.fn = oneway_merge; tree = parse_tree_indirect(¤t_head->object.oid); @@ -344,7 +336,7 @@ static void refresh_cache_or_die(int refresh_flags) * refresh_flags contains REFRESH_QUIET, so the only errors * are for unmerged entries. */ - if (refresh_index(&the_index, refresh_flags | REFRESH_IN_PORCELAIN, NULL, NULL, NULL)) + if (refresh_index(the_repository->index, refresh_flags | REFRESH_IN_PORCELAIN, NULL, NULL, NULL)) die_resolve_conflict("commit"); } @@ -393,7 +385,7 @@ static const char *prepare_index(const char **argv, const char *prefix, refresh_cache_or_die(refresh_flags); - if (write_locked_index(&the_index, &index_lock, 0)) + if (write_locked_index(the_repository->index, &index_lock, 0)) die(_("unable to create temporary index")); old_repo_index_file = the_repository->index_file; @@ -412,13 +404,13 @@ static const char *prepare_index(const char **argv, const char *prefix, unsetenv(INDEX_ENVIRONMENT); FREE_AND_NULL(old_index_env); - discard_index(&the_index); - read_index_from(&the_index, get_lock_file_path(&index_lock), + discard_index(the_repository->index); + read_index_from(the_repository->index, get_lock_file_path(&index_lock), get_git_dir()); - if (cache_tree_update(&the_index, WRITE_TREE_SILENT) == 0) { + if (cache_tree_update(the_repository->index, WRITE_TREE_SILENT) == 0) { if (reopen_lock_file(&index_lock) < 0) die(_("unable to write index file")); - if (write_locked_index(&the_index, &index_lock, 0)) + if (write_locked_index(the_repository->index, &index_lock, 0)) die(_("unable to update temporary index")); } else warning(_("Failed to update main cache tree")); @@ -450,8 +442,8 @@ static const char *prepare_index(const char **argv, const char *prefix, exit(128); refresh_cache_or_die(refresh_flags); - cache_tree_update(&the_index, WRITE_TREE_SILENT); - if (write_locked_index(&the_index, &index_lock, 0)) + cache_tree_update(the_repository->index, WRITE_TREE_SILENT); + if (write_locked_index(the_repository->index, &index_lock, 0)) die(_("unable to write new index file")); commit_style = COMMIT_NORMAL; ret = get_lock_file_path(&index_lock); @@ -472,10 +464,10 @@ static const char *prepare_index(const char **argv, const char *prefix, repo_hold_locked_index(the_repository, &index_lock, LOCK_DIE_ON_ERROR); refresh_cache_or_die(refresh_flags); - if (the_index.cache_changed - || !cache_tree_fully_valid(the_index.cache_tree)) - cache_tree_update(&the_index, WRITE_TREE_SILENT); - if (write_locked_index(&the_index, &index_lock, + if (the_repository->index->cache_changed + || !cache_tree_fully_valid(the_repository->index->cache_tree)) + cache_tree_update(the_repository->index, WRITE_TREE_SILENT); + if (write_locked_index(the_repository->index, &index_lock, COMMIT_LOCK | SKIP_IF_UNCHANGED)) die(_("unable to write new index file")); commit_style = COMMIT_AS_IS; @@ -516,15 +508,15 @@ static const char *prepare_index(const char **argv, const char *prefix, if (list_paths(&partial, !current_head ? NULL : "HEAD", &pathspec)) exit(1); - discard_index(&the_index); + discard_index(the_repository->index); if (repo_read_index(the_repository) < 0) die(_("cannot read the index")); repo_hold_locked_index(the_repository, &index_lock, LOCK_DIE_ON_ERROR); add_remove_files(&partial); - refresh_index(&the_index, REFRESH_QUIET, NULL, NULL, NULL); - cache_tree_update(&the_index, WRITE_TREE_SILENT); - if (write_locked_index(&the_index, &index_lock, 0)) + refresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL); + cache_tree_update(the_repository->index, WRITE_TREE_SILENT); + if (write_locked_index(the_repository->index, &index_lock, 0)) die(_("unable to write new index file")); hold_lock_file_for_update(&false_lock, @@ -534,14 +526,14 @@ static const char *prepare_index(const char **argv, const char *prefix, create_base_index(current_head); add_remove_files(&partial); - refresh_index(&the_index, REFRESH_QUIET, NULL, NULL, NULL); + refresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL); - if (write_locked_index(&the_index, &false_lock, 0)) + if (write_locked_index(the_repository->index, &false_lock, 0)) die(_("unable to write temporary index file")); - discard_index(&the_index); + discard_index(the_repository->index); ret = get_lock_file_path(&false_lock); - read_index_from(&the_index, ret, get_git_dir()); + read_index_from(the_repository->index, ret, get_git_dir()); out: string_list_clear(&partial, 0); clear_pathspec(&pathspec); @@ -999,7 +991,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, struct object_id oid; const char *parent = "HEAD"; - if (!the_index.initialized && repo_read_index(the_repository) < 0) + if (!the_repository->index->initialized && repo_read_index(the_repository) < 0) die(_("Cannot read index")); if (amend) @@ -1009,11 +1001,11 @@ static int prepare_to_commit(const char *index_file, const char *prefix, int i, ita_nr = 0; /* TODO: audit for interaction with sparse-index. */ - ensure_full_index(&the_index); - for (i = 0; i < the_index.cache_nr; i++) - if (ce_intent_to_add(the_index.cache[i])) + ensure_full_index(the_repository->index); + for (i = 0; i < the_repository->index->cache_nr; i++) + if (ce_intent_to_add(the_repository->index->cache[i])) ita_nr++; - committable = the_index.cache_nr - ita_nr > 0; + committable = the_repository->index->cache_nr - ita_nr > 0; } else { /* * Unless the user did explicitly request a submodule @@ -1038,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); } @@ -1081,11 +1066,11 @@ static int prepare_to_commit(const char *index_file, const char *prefix, * and could have updated it. We must do this before we invoke * the editor and after we invoke run_status above. */ - discard_index(&the_index); + discard_index(the_repository->index); } - read_index_from(&the_index, index_file, get_git_dir()); + read_index_from(the_repository->index, index_file, get_git_dir()); - if (cache_tree_update(&the_index, 0)) { + if (cache_tree_update(the_repository->index, 0)) { error(_("Error building trees")); return 0; } @@ -1586,7 +1571,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) status_format != STATUS_FORMAT_PORCELAIN_V2) progress_flag = REFRESH_PROGRESS; repo_read_index(the_repository); - refresh_index(&the_index, + refresh_index(the_repository->index, REFRESH_QUIET|REFRESH_UNMERGED|progress_flag, &s.pathspec, NULL, NULL); @@ -1673,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")), @@ -1856,7 +1841,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) append_merge_tag_headers(parents, &tail); } - if (commit_tree_extended(sb.buf, sb.len, &the_index.cache_tree->oid, + if (commit_tree_extended(sb.buf, sb.len, &the_repository->index->cache_tree->oid, parents, &oid, author_ident.buf, NULL, sign_commit, extra)) { rollback_index_files(); 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/credential-cache--daemon.c b/builtin/credential-cache--daemon.c index 17f929dede..4952b22547 100644 --- a/builtin/credential-cache--daemon.c +++ b/builtin/credential-cache--daemon.c @@ -115,7 +115,9 @@ static int read_request(FILE *fh, struct credential *c, return error("client sent bogus timeout line: %s", item.buf); *timeout = atoi(p); - if (credential_read(c, fh) < 0) + credential_set_all_capabilities(c, CREDENTIAL_OP_INITIAL); + + if (credential_read(c, fh, CREDENTIAL_OP_HELPER) < 0) return -1; return 0; } @@ -131,8 +133,18 @@ static void serve_one_client(FILE *in, FILE *out) else if (!strcmp(action.buf, "get")) { struct credential_cache_entry *e = lookup_credential(&c); if (e) { - fprintf(out, "username=%s\n", e->item.username); - fprintf(out, "password=%s\n", e->item.password); + e->item.capa_authtype.request_initial = 1; + e->item.capa_authtype.request_helper = 1; + + fprintf(out, "capability[]=authtype\n"); + if (e->item.username) + fprintf(out, "username=%s\n", e->item.username); + if (e->item.password) + fprintf(out, "password=%s\n", e->item.password); + if (credential_has_capability(&c.capa_authtype, CREDENTIAL_OP_HELPER) && e->item.authtype) + fprintf(out, "authtype=%s\n", e->item.authtype); + if (credential_has_capability(&c.capa_authtype, CREDENTIAL_OP_HELPER) && e->item.credential) + fprintf(out, "credential=%s\n", e->item.credential); if (e->item.password_expiry_utc != TIME_MAX) fprintf(out, "password_expiry_utc=%"PRItime"\n", e->item.password_expiry_utc); @@ -157,8 +169,10 @@ static void serve_one_client(FILE *in, FILE *out) else if (!strcmp(action.buf, "store")) { if (timeout < 0) warning("cache client didn't specify a timeout"); - else if (!c.username || !c.password) + else if ((!c.username || !c.password) && (!c.authtype && !c.credential)) warning("cache client gave us a partial credential"); + else if (c.ephemeral) + warning("not storing ephemeral credential"); else { remove_credential(&c, 0); cache_credential(&c, timeout); diff --git a/builtin/credential-cache.c b/builtin/credential-cache.c index bef120b537..3db8df70a9 100644 --- a/builtin/credential-cache.c +++ b/builtin/credential-cache.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "credential.h" #include "gettext.h" #include "parse-options.h" #include "path.h" @@ -127,6 +128,13 @@ static char *get_socket_path(void) return socket; } +static void announce_capabilities(void) +{ + struct credential c = CREDENTIAL_INIT; + c.capa_authtype.request_initial = 1; + credential_announce_capabilities(&c, stdout); +} + int cmd_credential_cache(int argc, const char **argv, const char *prefix) { char *socket_path = NULL; @@ -163,6 +171,8 @@ int cmd_credential_cache(int argc, const char **argv, const char *prefix) do_cache(socket_path, op, timeout, FLAG_RELAY); else if (!strcmp(op, "store")) do_cache(socket_path, op, timeout, FLAG_RELAY|FLAG_SPAWN); + else if (!strcmp(op, "capability")) + announce_capabilities(); else ; /* ignore unknown operation */ diff --git a/builtin/credential-store.c b/builtin/credential-store.c index 4a492411bb..494c809332 100644 --- a/builtin/credential-store.c +++ b/builtin/credential-store.c @@ -205,7 +205,7 @@ int cmd_credential_store(int argc, const char **argv, const char *prefix) if (!fns.nr) die("unable to set up default path; use --file"); - if (credential_read(&c, stdin) < 0) + if (credential_read(&c, stdin, CREDENTIAL_OP_HELPER) < 0) die("unable to read credential"); if (!strcmp(op, "get")) diff --git a/builtin/credential.c b/builtin/credential.c index 7010752987..5100d441f2 100644 --- a/builtin/credential.c +++ b/builtin/credential.c @@ -17,15 +17,24 @@ int cmd_credential(int argc, const char **argv, const char *prefix UNUSED) usage(usage_msg); op = argv[1]; - if (credential_read(&c, stdin) < 0) + if (!strcmp(op, "capability")) { + credential_set_all_capabilities(&c, CREDENTIAL_OP_INITIAL); + credential_announce_capabilities(&c, stdout); + return 0; + } + + if (credential_read(&c, stdin, CREDENTIAL_OP_INITIAL) < 0) die("unable to read credential from stdin"); if (!strcmp(op, "fill")) { - credential_fill(&c); - credential_write(&c, stdout); + credential_fill(&c, 0); + credential_next_state(&c); + credential_write(&c, stdout, CREDENTIAL_OP_RESPONSE); } else if (!strcmp(op, "approve")) { + credential_set_all_capabilities(&c, CREDENTIAL_OP_HELPER); credential_approve(&c); } else if (!strcmp(op, "reject")) { + credential_set_all_capabilities(&c, CREDENTIAL_OP_HELPER); credential_reject(&c); } else { usage(usage_msg); diff --git a/builtin/describe.c b/builtin/describe.c index d6c77a714f..c0e3301e3c 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -1,4 +1,3 @@ -#define USE_THE_INDEX_VARIABLE #include "builtin.h" #include "config.h" #include "environment.h" @@ -674,7 +673,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix) prepare_repo_settings(the_repository); the_repository->settings.command_requires_full_index = 0; repo_read_index(the_repository); - refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, + refresh_index(the_repository->index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL); fd = repo_hold_locked_index(the_repository, &index_lock, 0); diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index a8e68ce8ef..0d3c611aac 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -1,4 +1,3 @@ -#define USE_THE_INDEX_VARIABLE #include "builtin.h" #include "config.h" #include "diff.h" @@ -206,7 +205,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) opt->diffopt.rotate_to_strict = 0; opt->diffopt.no_free = 1; if (opt->diffopt.detect_rename) { - if (!the_index.cache) + if (the_repository->index->cache) repo_read_index(the_repository); opt->diffopt.setup |= DIFF_SETUP_USE_SIZE_CACHE; } diff --git a/builtin/diff.c b/builtin/diff.c index 6e196e0c7d..efc37483b3 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -3,7 +3,7 @@ * * Copyright (c) 2006 Junio C Hamano */ -#define USE_THE_INDEX_VARIABLE + #include "builtin.h" #include "config.h" #include "ewah/ewok.h" @@ -239,9 +239,9 @@ static void refresh_index_quietly(void) fd = repo_hold_locked_index(the_repository, &lock_file, 0); if (fd < 0) return; - discard_index(&the_index); + discard_index(the_repository->index); repo_read_index(the_repository); - refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, + refresh_index(the_repository->index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL); repo_update_index_if_able(the_repository, &lock_file); } diff --git a/builtin/difftool.c b/builtin/difftool.c index a3c72b8258..a130faae4f 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -11,7 +11,7 @@ * * Copyright (C) 2016 Johannes Schindelin */ -#define USE_THE_INDEX_VARIABLE + #include "builtin.h" #include "abspath.h" #include "config.h" @@ -117,7 +117,7 @@ static int use_wt_file(const char *workdir, const char *name, int fd = open(buf.buf, O_RDONLY); if (fd >= 0 && - !index_fd(&the_index, &wt_oid, fd, &st, OBJ_BLOB, name, 0)) { + !index_fd(the_repository->index, &wt_oid, fd, &st, OBJ_BLOB, name, 0)) { if (is_null_oid(oid)) { oidcpy(oid, &wt_oid); use = 1; diff --git a/builtin/for-each-repo.c b/builtin/for-each-repo.c index 28186b30f5..c4fa41fda9 100644 --- a/builtin/for-each-repo.c +++ b/builtin/for-each-repo.c @@ -32,6 +32,7 @@ static int run_command_on_repo(const char *path, int argc, const char ** argv) int cmd_for_each_repo(int argc, const char **argv, const char *prefix) { static const char *config_key = NULL; + int keep_going = 0; int i, result = 0; const struct string_list *values; int err; @@ -39,6 +40,8 @@ int cmd_for_each_repo(int argc, const char **argv, const char *prefix) const struct option options[] = { OPT_STRING(0, "config", &config_key, N_("config"), N_("config key storing a list of repository paths")), + OPT_BOOL(0, "keep-going", &keep_going, + N_("keep going even if command fails in a repository")), OPT_END() }; @@ -55,8 +58,14 @@ int cmd_for_each_repo(int argc, const char **argv, const char *prefix) else if (err) return 0; - for (i = 0; !result && i < values->nr; i++) - result = run_command_on_repo(values->items[i].string, argc, argv); + for (i = 0; i < values->nr; i++) { + int ret = run_command_on_repo(values->items[i].string, argc, argv); + if (ret) { + if (!keep_going) + return ret; + result = 1; + } + } return result; } diff --git a/builtin/gc.c b/builtin/gc.c index d187cec1ea..d3b5ca9bb1 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -1870,6 +1870,7 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit "<string>%s/git</string>\n" "<string>--exec-path=%s</string>\n" "<string>for-each-repo</string>\n" + "<string>--keep-going</string>\n" "<string>--config=maintenance.repo</string>\n" "<string>maintenance</string>\n" "<string>run</string>\n" @@ -2112,7 +2113,7 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority "<Actions Context=\"Author\">\n" "<Exec>\n" "<Command>\"%s\\headless-git.exe\"</Command>\n" - "<Arguments>--exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%s</Arguments>\n" + "<Arguments>--exec-path=\"%s\" for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%s</Arguments>\n" "</Exec>\n" "</Actions>\n" "</Task>\n"; @@ -2257,7 +2258,7 @@ static int crontab_update_schedule(int run_maintenance, int fd) "# replaced in the future by a Git command.\n\n"); strbuf_addf(&line_format, - "%%d %%s * * %%s \"%s/git\" --exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%%s\n", + "%%d %%s * * %%s \"%s/git\" --exec-path=\"%s\" for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%%s\n", exec_path, exec_path); fprintf(cron_in, line_format.buf, minute, "1-23", "*", "hourly"); fprintf(cron_in, line_format.buf, minute, "0", "1-6", "daily"); @@ -2458,7 +2459,7 @@ static int systemd_timer_write_service_template(const char *exec_path) "\n" "[Service]\n" "Type=oneshot\n" - "ExecStart=\"%s/git\" --exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%%i\n" + "ExecStart=\"%s/git\" --exec-path=\"%s\" for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=%%i\n" "LockPersonality=yes\n" "MemoryDenyWriteExecute=yes\n" "NoNewPrivileges=yes\n" diff --git a/builtin/log.c b/builtin/log.c index c0a8bb95e9..4da7399905 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -1494,6 +1494,19 @@ static int subject_prefix_callback(const struct option *opt, const char *arg, return 0; } +static int rfc_callback(const struct option *opt, const char *arg, + int unset) +{ + const char **rfc = opt->value; + + *rfc = opt->value; + if (unset) + *rfc = NULL; + else + *rfc = arg ? arg : "RFC"; + return 0; +} + static int numbered_cmdline_opt = 0; static int numbered_callback(const struct option *opt, const char *arg, @@ -1907,8 +1920,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) struct strbuf rdiff2 = STRBUF_INIT; struct strbuf rdiff_title = STRBUF_INIT; struct strbuf sprefix = STRBUF_INIT; + const char *rfc = NULL; int creation_factor = -1; - int rfc = 0; const struct option builtin_format_patch_options[] = { OPT_CALLBACK_F('n', "numbered", &numbered, NULL, @@ -1932,7 +1945,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("mark the series as Nth re-roll")), OPT_INTEGER(0, "filename-max-length", &fmt_patch_name_max, N_("max length of output filename")), - OPT_BOOL(0, "rfc", &rfc, N_("use [RFC PATCH] instead of [PATCH]")), + OPT_CALLBACK_F(0, "rfc", &rfc, N_("rfc"), + N_("add <rfc> (default 'RFC') before 'PATCH'"), + PARSE_OPT_OPTARG, rfc_callback), OPT_STRING(0, "cover-from-description", &cover_from_description_arg, N_("cover-from-description-mode"), N_("generate parts of a cover letter based on a branch's description")), @@ -2050,8 +2065,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (cover_from_description_arg) cover_from_description_mode = parse_cover_from_description(cover_from_description_arg); - if (rfc) - strbuf_insertstr(&sprefix, 0, "RFC "); + if (rfc && rfc[0]) { + subject_prefix = 1; + if (rfc[0] == '-') + strbuf_addf(&sprefix, " %s", rfc + 1); + else + strbuf_insertf(&sprefix, 0, "%s ", rfc); + } if (reroll_count) { strbuf_addf(&sprefix, " v%s", reroll_count); diff --git a/builtin/merge-index.c b/builtin/merge-index.c index 270d5f644a..0fabe3f6bb 100644 --- a/builtin/merge-index.c +++ b/builtin/merge-index.c @@ -1,4 +1,3 @@ -#define USE_THE_INDEX_VARIABLE #include "builtin.h" #include "hex.h" #include "read-cache-ll.h" @@ -18,11 +17,11 @@ static int merge_entry(int pos, const char *path) char ownbuf[4][60]; struct child_process cmd = CHILD_PROCESS_INIT; - if (pos >= the_index.cache_nr) + if (pos >= the_repository->index->cache_nr) die("git merge-index: %s not in the cache", path); found = 0; do { - const struct cache_entry *ce = the_index.cache[pos]; + const struct cache_entry *ce = the_repository->index->cache[pos]; int stage = ce_stage(ce); if (strcmp(ce->name, path)) @@ -32,7 +31,7 @@ static int merge_entry(int pos, const char *path) xsnprintf(ownbuf[stage], sizeof(ownbuf[stage]), "%o", ce->ce_mode); arguments[stage] = hexbuf[stage]; arguments[stage + 4] = ownbuf[stage]; - } while (++pos < the_index.cache_nr); + } while (++pos < the_repository->index->cache_nr); if (!found) die("git merge-index: %s not in the cache", path); @@ -51,7 +50,7 @@ static int merge_entry(int pos, const char *path) static void merge_one_path(const char *path) { - int pos = index_name_pos(&the_index, path, strlen(path)); + int pos = index_name_pos(the_repository->index, path, strlen(path)); /* * If it already exists in the cache as stage0, it's @@ -65,9 +64,9 @@ static void merge_all(void) { int i; /* TODO: audit for interaction with sparse-index. */ - ensure_full_index(&the_index); - for (i = 0; i < the_index.cache_nr; i++) { - const struct cache_entry *ce = the_index.cache[i]; + ensure_full_index(the_repository->index); + for (i = 0; i < the_repository->index->cache_nr; i++) { + const struct cache_entry *ce = the_repository->index->cache[i]; if (!ce_stage(ce)) continue; i += merge_entry(i, ce->name)-1; @@ -89,7 +88,7 @@ int cmd_merge_index(int argc, const char **argv, const char *prefix UNUSED) repo_read_index(the_repository); /* TODO: audit for interaction with sparse-index. */ - ensure_full_index(&the_index); + ensure_full_index(the_repository->index); i = 1; if (!strcmp(argv[i], "-o")) { diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index 8bdb439131..1082d919fd 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -1,4 +1,3 @@ -#define USE_THE_INDEX_VARIABLE #include "builtin.h" #include "tree-walk.h" #include "xdiff-interface.h" @@ -364,7 +363,7 @@ static void trivial_merge_trees(struct tree_desc t[3], const char *base) setup_traverse_info(&info, base); info.fn = threeway_callback; - traverse_trees(&the_index, 3, t, &info); + traverse_trees(the_repository->index, 3, t, &info); } static void *get_tree_descriptor(struct repository *r, diff --git a/builtin/merge.c b/builtin/merge.c index 6f4fec87fc..6a6d379885 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -6,7 +6,6 @@ * Based on git-merge.sh by Junio C Hamano. */ -#define USE_THE_INDEX_VARIABLE #include "builtin.h" #include "abspath.h" #include "advice.h" @@ -300,7 +299,7 @@ static int save_state(struct object_id *stash) int rc = -1; fd = repo_hold_locked_index(the_repository, &lock_file, 0); - refresh_index(&the_index, REFRESH_QUIET, NULL, NULL, NULL); + refresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL); if (0 <= fd) repo_update_index_if_able(the_repository, &lock_file); rollback_lock_file(&lock_file); @@ -372,7 +371,7 @@ static void restore_state(const struct object_id *head, run_command(&cmd); refresh_cache: - discard_index(&the_index); + discard_index(the_repository->index); if (repo_read_index(the_repository) < 0) die(_("could not read index")); } @@ -657,8 +656,8 @@ static int read_tree_trivial(struct object_id *common, struct object_id *head, memset(&opts, 0, sizeof(opts)); opts.head_idx = 2; - opts.src_index = &the_index; - opts.dst_index = &the_index; + opts.src_index = the_repository->index; + opts.dst_index = the_repository->index; opts.update = 1; opts.verbose_update = 1; opts.trivial_merges_only = 1; @@ -674,7 +673,7 @@ static int read_tree_trivial(struct object_id *common, struct object_id *head, if (!trees[nr_trees++]) return -1; opts.fn = threeway_merge; - cache_tree_free(&the_index.cache_tree); + cache_tree_free(&the_repository->index->cache_tree); for (i = 0; i < nr_trees; i++) { parse_tree(trees[i]); init_tree_desc(t+i, &trees[i]->object.oid, @@ -687,7 +686,7 @@ static int read_tree_trivial(struct object_id *common, struct object_id *head, static void write_tree_trivial(struct object_id *oid) { - if (write_index_as_tree(oid, &the_index, get_index_file(), 0, NULL)) + if (write_index_as_tree(oid, the_repository->index, get_index_file(), 0, NULL)) die(_("git write-tree failed to write a tree")); } @@ -745,7 +744,7 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, rollback_lock_file(&lock); return 2; } - if (write_locked_index(&the_index, &lock, + if (write_locked_index(the_repository->index, &lock, COMMIT_LOCK | SKIP_IF_UNCHANGED)) die(_("unable to write %s"), get_index_file()); return clean ? 0 : 1; @@ -768,8 +767,8 @@ static int count_unmerged_entries(void) { int i, ret = 0; - for (i = 0; i < the_index.cache_nr; i++) - if (ce_stage(the_index.cache[i])) + for (i = 0; i < the_repository->index->cache_nr; i++) + if (ce_stage(the_repository->index->cache[i])) ret++; return ret; @@ -843,9 +842,9 @@ static void prepare_to_commit(struct commit_list *remoteheads) * the editor and after we invoke run_status above. */ if (invoked_hook) - discard_index(&the_index); + discard_index(the_repository->index); } - read_index_from(&the_index, index_file, get_git_dir()); + read_index_from(the_repository->index, index_file, get_git_dir()); strbuf_addbuf(&msg, &merge_msg); if (squash) BUG("the control must not reach here under --squash"); @@ -957,7 +956,7 @@ static int suggest_conflicts(void) * Thus, we will get the cleanup mode which is returned when we _are_ * using an editor. */ - append_conflicts_hint(&the_index, &msgbuf, + append_conflicts_hint(the_repository->index, &msgbuf, get_cleanup_mode(cleanup_arg, 1)); fputs(msgbuf.buf, fp); strbuf_release(&msgbuf); @@ -1386,7 +1385,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) else die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).")); } - resolve_undo_clear_index(&the_index); + resolve_undo_clear_index(the_repository->index); if (option_edit < 0) option_edit = default_edit_option(); @@ -1595,7 +1594,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) * We are not doing octopus, not fast-forward, and have * only one common. */ - refresh_index(&the_index, REFRESH_QUIET, NULL, NULL, NULL); + refresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL); if (allow_trivial && fast_forward != FF_ONLY) { /* * Must first ensure that index matches HEAD before @@ -1784,6 +1783,6 @@ done: } strbuf_release(&buf); free(branch_to_free); - discard_index(&the_index); + discard_index(the_repository->index); return ret; } diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c index a72aebecaa..8360932d2e 100644 --- a/builtin/multi-pack-index.c +++ b/builtin/multi-pack-index.c @@ -8,6 +8,7 @@ #include "strbuf.h" #include "trace2.h" #include "object-store-ll.h" +#include "replace-object.h" #define BUILTIN_MIDX_WRITE_USAGE \ N_("git multi-pack-index [<options>] write [--preferred-pack=<pack>]" \ @@ -273,6 +274,8 @@ int cmd_multi_pack_index(int argc, const char **argv, }; struct option *options = parse_options_concat(builtin_multi_pack_index_options, common_opts); + disable_replace_refs(); + git_config(git_default_config, NULL); if (the_repository && diff --git a/builtin/mv.c b/builtin/mv.c index 22e64fc290..74aa9746aa 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -3,7 +3,7 @@ * * Copyright (C) 2006 Johannes Schindelin */ -#define USE_THE_INDEX_VARIABLE + #include "builtin.h" #include "abspath.h" #include "advice.h" @@ -95,9 +95,9 @@ static void prepare_move_submodule(const char *src, int first, const char **submodule_gitfile) { struct strbuf submodule_dotgit = STRBUF_INIT; - if (!S_ISGITLINK(the_index.cache[first]->ce_mode)) + if (!S_ISGITLINK(the_repository->index->cache[first]->ce_mode)) die(_("Directory %s is in index and no submodule?"), src); - if (!is_staging_gitmodules_ok(&the_index)) + if (!is_staging_gitmodules_ok(the_repository->index)) die(_("Please stage your changes to .gitmodules or stash them to proceed")); strbuf_addf(&submodule_dotgit, "%s/.git", src); *submodule_gitfile = read_gitfile(submodule_dotgit.buf); @@ -114,13 +114,13 @@ static int index_range_of_same_dir(const char *src, int length, const char *src_w_slash = add_slash(src); int first, last, len_w_slash = length + 1; - first = index_name_pos(&the_index, src_w_slash, len_w_slash); + first = index_name_pos(the_repository->index, src_w_slash, len_w_slash); if (first >= 0) die(_("%.*s is in index"), len_w_slash, src_w_slash); first = -1 - first; - for (last = first; last < the_index.cache_nr; last++) { - const char *path = the_index.cache[last]->name; + for (last = first; last < the_repository->index->cache_nr; last++) { + const char *path = the_repository->index->cache[last]->name; if (strncmp(path, src_w_slash, len_w_slash)) break; } @@ -144,14 +144,14 @@ static int empty_dir_has_sparse_contents(const char *name) const char *with_slash = add_slash(name); int length = strlen(with_slash); - int pos = index_name_pos(&the_index, with_slash, length); + int pos = index_name_pos(the_repository->index, with_slash, length); const struct cache_entry *ce; if (pos < 0) { pos = -pos - 1; - if (pos >= the_index.cache_nr) + if (pos >= the_repository->index->cache_nr) goto free_return; - ce = the_index.cache[pos]; + ce = the_repository->index->cache[pos]; if (strncmp(with_slash, ce->name, length)) goto free_return; if (ce_skip_worktree(ce)) @@ -223,7 +223,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) S_ISDIR(st.st_mode)) { destination = internal_prefix_pathspec(dst_w_slash, argv, argc, DUP_BASENAME); } else { - if (!path_in_sparse_checkout(dst_w_slash, &the_index) && + if (!path_in_sparse_checkout(dst_w_slash, the_repository->index) && empty_dir_has_sparse_contents(dst_w_slash)) { destination = internal_prefix_pathspec(dst_w_slash, argv, argc, DUP_BASENAME); dst_mode = SKIP_WORKTREE_DIR; @@ -239,7 +239,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) * is deprecated at this point) sparse-checkout. As * SPARSE here is only considering cone-mode situation. */ - if (!path_in_cone_mode_sparse_checkout(destination[0], &the_index)) + if (!path_in_cone_mode_sparse_checkout(destination[0], the_repository->index)) dst_mode = SPARSE; } } @@ -263,10 +263,10 @@ int cmd_mv(int argc, const char **argv, const char *prefix) int pos; const struct cache_entry *ce; - pos = index_name_pos(&the_index, src, length); + pos = index_name_pos(the_repository->index, src, length); if (pos < 0) { const char *src_w_slash = add_slash(src); - if (!path_in_sparse_checkout(src_w_slash, &the_index) && + if (!path_in_sparse_checkout(src_w_slash, the_repository->index) && empty_dir_has_sparse_contents(src)) { modes[i] |= SKIP_WORKTREE_DIR; goto dir_check; @@ -276,7 +276,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) bad = _("bad source"); goto act_on_entry; } - ce = the_index.cache[pos]; + ce = the_repository->index->cache[pos]; if (!ce_skip_worktree(ce)) { bad = _("bad source"); goto act_on_entry; @@ -286,7 +286,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) goto act_on_entry; } /* Check if dst exists in index */ - if (index_name_pos(&the_index, dst, strlen(dst)) < 0) { + if (index_name_pos(the_repository->index, dst, strlen(dst)) < 0) { modes[i] |= SPARSE; goto act_on_entry; } @@ -311,7 +311,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) dir_check: if (S_ISDIR(st.st_mode)) { int j, dst_len, n; - int first = index_name_pos(&the_index, src, length), last; + int first = index_name_pos(the_repository->index, src, length), last; if (first >= 0) { prepare_move_submodule(src, first, @@ -339,7 +339,7 @@ dir_check: dst_len = strlen(dst); for (j = 0; j < last - first; j++) { - const struct cache_entry *ce = the_index.cache[first + j]; + const struct cache_entry *ce = the_repository->index->cache[first + j]; const char *path = ce->name; source[argc + j] = path; destination[argc + j] = @@ -351,7 +351,7 @@ dir_check: argc += last - first; goto act_on_entry; } - if (!(ce = index_file_exists(&the_index, src, length, 0))) { + if (!(ce = index_file_exists(the_repository->index, src, length, 0))) { bad = _("not under version control"); goto act_on_entry; } @@ -387,7 +387,7 @@ dir_check: if (ignore_sparse && (dst_mode & (SKIP_WORKTREE_DIR | SPARSE)) && - index_entry_exists(&the_index, dst, strlen(dst))) { + index_entry_exists(the_repository->index, dst, strlen(dst))) { bad = _("destination exists in the index"); if (force) { if (verbose) @@ -404,12 +404,12 @@ dir_check: * option as a way to have a successful run. */ if (!ignore_sparse && - !path_in_sparse_checkout(src, &the_index)) { + !path_in_sparse_checkout(src, the_repository->index)) { string_list_append(&only_match_skip_worktree, src); skip_sparse = 1; } if (!ignore_sparse && - !path_in_sparse_checkout(dst, &the_index)) { + !path_in_sparse_checkout(dst, the_repository->index)) { string_list_append(&only_match_skip_worktree, dst); skip_sparse = 1; } @@ -449,7 +449,7 @@ remove_entry: int pos; int sparse_and_dirty = 0; struct checkout state = CHECKOUT_INIT; - state.istate = &the_index; + state.istate = the_repository->index; if (force) state.force = 1; @@ -476,14 +476,14 @@ remove_entry: if (mode & (WORKING_DIRECTORY | SKIP_WORKTREE_DIR)) continue; - pos = index_name_pos(&the_index, src, strlen(src)); + pos = index_name_pos(the_repository->index, src, strlen(src)); assert(pos >= 0); if (!(mode & SPARSE) && !lstat(src, &st)) - sparse_and_dirty = ie_modified(&the_index, - the_index.cache[pos], + sparse_and_dirty = ie_modified(the_repository->index, + the_repository->index->cache[pos], &st, 0); - rename_index_entry_at(&the_index, pos, dst); + rename_index_entry_at(the_repository->index, pos, dst); if (ignore_sparse && core_apply_sparse_checkout && @@ -495,11 +495,11 @@ remove_entry: * should be added in a future patch. */ if ((mode & SPARSE) && - path_in_sparse_checkout(dst, &the_index)) { + path_in_sparse_checkout(dst, the_repository->index)) { /* from out-of-cone to in-cone */ - int dst_pos = index_name_pos(&the_index, dst, + int dst_pos = index_name_pos(the_repository->index, dst, strlen(dst)); - struct cache_entry *dst_ce = the_index.cache[dst_pos]; + struct cache_entry *dst_ce = the_repository->index->cache[dst_pos]; dst_ce->ce_flags &= ~CE_SKIP_WORKTREE; @@ -507,11 +507,11 @@ remove_entry: die(_("cannot checkout %s"), dst_ce->name); } else if ((dst_mode & (SKIP_WORKTREE_DIR | SPARSE)) && !(mode & SPARSE) && - !path_in_sparse_checkout(dst, &the_index)) { + !path_in_sparse_checkout(dst, the_repository->index)) { /* from in-cone to out-of-cone */ - int dst_pos = index_name_pos(&the_index, dst, + int dst_pos = index_name_pos(the_repository->index, dst, strlen(dst)); - struct cache_entry *dst_ce = the_index.cache[dst_pos]; + struct cache_entry *dst_ce = the_repository->index->cache[dst_pos]; /* * if src is clean, it will suffice to remove it @@ -559,9 +559,9 @@ remove_entry: advise_on_moving_dirty_path(&dirty_paths); if (gitmodules_modified) - stage_updated_gitmodules(&the_index); + stage_updated_gitmodules(the_repository->index); - if (write_locked_index(&the_index, &lock_file, + if (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK | SKIP_IF_UNCHANGED)) die(_("Unable to write new index file")); diff --git a/builtin/pull.c b/builtin/pull.c index 72cbb76d52..66869210db 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -5,7 +5,7 @@ * * Fetch one or more remote refs and merge it/them into the current HEAD. */ -#define USE_THE_INDEX_VARIABLE + #include "builtin.h" #include "advice.h" #include "config.h" @@ -1044,7 +1044,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (opt_autostash == -1) opt_autostash = config_autostash; - if (is_null_oid(&orig_head) && !is_index_unborn(&the_index)) + if (is_null_oid(&orig_head) && !is_index_unborn(the_repository->index)) die(_("Updating an unborn branch with changes added to the index.")); if (!opt_autostash) diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 6f89cec0fb..a8cf8504b8 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -4,7 +4,6 @@ * Copyright (C) Linus Torvalds, 2005 */ -#define USE_THE_INDEX_VARIABLE #include "builtin.h" #include "config.h" #include "gettext.h" @@ -159,8 +158,8 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix) memset(&opts, 0, sizeof(opts)); opts.head_idx = -1; - opts.src_index = &the_index; - opts.dst_index = &the_index; + opts.src_index = the_repository->index; + opts.dst_index = the_repository->index; git_config(git_read_tree_config, NULL); @@ -197,7 +196,7 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix) die(_("You need to resolve your current index first")); stage = opts.merge = 1; } - resolve_undo_clear_index(&the_index); + resolve_undo_clear_index(the_repository->index); for (i = 0; i < argc; i++) { const char *arg = argv[i]; @@ -225,7 +224,7 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix) setup_work_tree(); if (opts.skip_sparse_checkout) - ensure_full_index(&the_index); + ensure_full_index(the_repository->index); if (opts.merge) { switch (stage - 1) { @@ -237,7 +236,7 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix) break; case 2: opts.fn = twoway_merge; - opts.initial_checkout = is_index_unborn(&the_index); + opts.initial_checkout = is_index_unborn(the_repository->index); break; case 3: default: @@ -258,7 +257,7 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix) if (nr_trees == 1 && !opts.prefix) opts.skip_cache_tree_update = 1; - cache_tree_free(&the_index.cache_tree); + cache_tree_free(&the_repository->index->cache_tree); for (i = 0; i < nr_trees; i++) { struct tree *tree = trees[i]; if (parse_tree(tree) < 0) @@ -282,7 +281,7 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix) the_repository->index, trees[0]); - if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) + if (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK)) die("unable to write new index file"); return 0; } diff --git a/builtin/rebase.c b/builtin/rebase.c index 891f28468e..fe17d562a8 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -4,7 +4,6 @@ * Copyright (c) 2018 Pratik Karki */ -#define USE_THE_INDEX_VARIABLE #include "builtin.h" #include "abspath.h" #include "environment.h" @@ -295,7 +294,7 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags) if (ret) error(_("could not generate todo list")); else { - discard_index(&the_index); + discard_index(the_repository->index); if (todo_list_parse_insn_buffer(the_repository, todo_list.buf.buf, &todo_list)) BUG("unusable todo list"); diff --git a/builtin/replay.c b/builtin/replay.c index 6bc4b47f09..6bf0691f15 100644 --- a/builtin/replay.c +++ b/builtin/replay.c @@ -2,7 +2,6 @@ * "git replay" builtin command */ -#define USE_THE_INDEX_VARIABLE #include "git-compat-util.h" #include "builtin.h" diff --git a/builtin/reset.c b/builtin/reset.c index 1d62ff6332..b6dacf9678 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -7,7 +7,7 @@ * * Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano */ -#define USE_THE_INDEX_VARIABLE + #include "builtin.h" #include "advice.h" #include "config.h" @@ -66,8 +66,8 @@ static int reset_index(const char *ref, const struct object_id *oid, int reset_t memset(&opts, 0, sizeof(opts)); opts.head_idx = 1; - opts.src_index = &the_index; - opts.dst_index = &the_index; + opts.src_index = the_repository->index; + opts.dst_index = the_repository->index; opts.fn = oneway_merge; opts.merge = 1; init_checkout_metadata(&opts.meta, ref, oid, NULL); @@ -159,11 +159,11 @@ static void update_index_from_diff(struct diff_queue_struct *q, struct cache_entry *ce; if (!is_in_reset_tree && !intent_to_add) { - remove_file_from_index(&the_index, one->path); + remove_file_from_index(the_repository->index, one->path); continue; } - ce = make_cache_entry(&the_index, one->mode, &one->oid, one->path, + ce = make_cache_entry(the_repository->index, one->mode, &one->oid, one->path, 0, 0); /* @@ -174,9 +174,9 @@ static void update_index_from_diff(struct diff_queue_struct *q, * if this entry is outside the sparse cone - this is necessary * to properly construct the reset sparse directory. */ - pos = index_name_pos(&the_index, one->path, strlen(one->path)); - if ((pos >= 0 && ce_skip_worktree(the_index.cache[pos])) || - (pos < 0 && !path_in_sparse_checkout(one->path, &the_index))) + pos = index_name_pos(the_repository->index, one->path, strlen(one->path)); + if ((pos >= 0 && ce_skip_worktree(the_repository->index->cache[pos])) || + (pos < 0 && !path_in_sparse_checkout(one->path, the_repository->index))) ce->ce_flags |= CE_SKIP_WORKTREE; if (!ce) @@ -186,7 +186,7 @@ static void update_index_from_diff(struct diff_queue_struct *q, ce->ce_flags |= CE_INTENT_TO_ADD; set_object_name_for_intent_to_add_entry(ce); } - add_index_entry(&the_index, ce, + add_index_entry(the_repository->index, ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE); } } @@ -208,8 +208,8 @@ static int read_from_tree(const struct pathspec *pathspec, opt.change = diff_change; opt.add_remove = diff_addremove; - if (pathspec->nr && pathspec_needs_expanded_index(&the_index, pathspec)) - ensure_full_index(&the_index); + if (pathspec->nr && pathspec_needs_expanded_index(the_repository->index, pathspec)) + ensure_full_index(the_repository->index); if (do_diff_cache(tree_oid, &opt)) return 1; @@ -235,7 +235,7 @@ static void set_reflog_message(struct strbuf *sb, const char *action, static void die_if_unmerged_cache(int reset_type) { - if (is_merge() || unmerged_index(&the_index)) + if (is_merge() || unmerged_index(the_repository->index)) die(_("Cannot do a %s reset in the middle of a merge."), _(reset_type_names[reset_type])); @@ -470,12 +470,12 @@ int cmd_reset(int argc, const char **argv, const char *prefix) update_ref_status = 1; goto cleanup; } - the_index.updated_skipworktree = 1; + the_repository->index->updated_skipworktree = 1; if (!no_refresh && get_git_work_tree()) { uint64_t t_begin, t_delta_in_ms; t_begin = getnanotime(); - refresh_index(&the_index, flags, NULL, NULL, + refresh_index(the_repository->index, flags, NULL, NULL, _("Unstaged changes after reset:")); t_delta_in_ms = (getnanotime() - t_begin) / 1000000; if (!quiet && advice_enabled(ADVICE_RESET_NO_REFRESH_WARNING) && t_delta_in_ms > REFRESH_INDEX_DELAY_WARNING_IN_MS) { @@ -501,7 +501,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) free(ref); } - if (write_locked_index(&the_index, &lock, COMMIT_LOCK)) + if (write_locked_index(the_repository->index, &lock, COMMIT_LOCK)) die(_("Could not write new index file.")); } @@ -516,7 +516,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) if (!pathspec.nr) remove_branch_state(the_repository, 0); - discard_index(&the_index); + discard_index(the_repository->index); cleanup: clear_pathspec(&pathspec); diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 624182e507..af79538632 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -3,7 +3,7 @@ * * Copyright (C) Linus Torvalds, 2005 */ -#define USE_THE_INDEX_VARIABLE + #include "builtin.h" #include "abspath.h" #include "config.h" @@ -1049,8 +1049,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (!strcmp(arg, "--shared-index-path")) { if (repo_read_index(the_repository) < 0) die(_("Could not read the index")); - if (the_index.split_index) { - const struct object_id *oid = &the_index.split_index->base_oid; + if (the_repository->index->split_index) { + const struct object_id *oid = &the_repository->index->split_index->base_oid; const char *path = git_path("sharedindex.%s", oid_to_hex(oid)); print_path(path, prefix, format, DEFAULT_RELATIVE); } diff --git a/builtin/rm.c b/builtin/rm.c index fd130cea2d..d195c16e74 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -3,7 +3,7 @@ * * Copyright (C) Linus Torvalds 2006 */ -#define USE_THE_INDEX_VARIABLE + #include "builtin.h" #include "advice.h" #include "config.h" @@ -41,8 +41,8 @@ static int get_ours_cache_pos(const char *path, int pos) { int i = -pos - 1; - while ((i < the_index.cache_nr) && !strcmp(the_index.cache[i]->name, path)) { - if (ce_stage(the_index.cache[i]) == 2) + while ((i < the_repository->index->cache_nr) && !strcmp(the_repository->index->cache[i]->name, path)) { + if (ce_stage(the_repository->index->cache[i]) == 2) return i; i++; } @@ -78,13 +78,13 @@ static void submodules_absorb_gitdir_if_needed(void) int pos; const struct cache_entry *ce; - pos = index_name_pos(&the_index, name, strlen(name)); + pos = index_name_pos(the_repository->index, name, strlen(name)); if (pos < 0) { pos = get_ours_cache_pos(name, pos); if (pos < 0) continue; } - ce = the_index.cache[pos]; + ce = the_repository->index->cache[pos]; if (!S_ISGITLINK(ce->ce_mode) || !file_exists(ce->name) || @@ -122,7 +122,7 @@ static int check_local_mod(struct object_id *head, int index_only) int local_changes = 0; int staged_changes = 0; - pos = index_name_pos(&the_index, name, strlen(name)); + pos = index_name_pos(the_repository->index, name, strlen(name)); if (pos < 0) { /* * Skip unmerged entries except for populated submodules @@ -132,11 +132,11 @@ static int check_local_mod(struct object_id *head, int index_only) if (pos < 0) continue; - if (!S_ISGITLINK(the_index.cache[pos]->ce_mode) || + if (!S_ISGITLINK(the_repository->index->cache[pos]->ce_mode) || is_empty_dir(name)) continue; } - ce = the_index.cache[pos]; + ce = the_repository->index->cache[pos]; if (lstat(ce->name, &st) < 0) { if (!is_missing_file_error(errno)) @@ -173,7 +173,7 @@ static int check_local_mod(struct object_id *head, int index_only) * Is the index different from the file in the work tree? * If it's a submodule, is its work tree modified? */ - if (ie_match_stat(&the_index, ce, &st, 0) || + if (ie_match_stat(the_repository->index, ce, &st, 0) || (S_ISGITLINK(ce->ce_mode) && bad_to_remove_submodule(ce->name, SUBMODULE_REMOVAL_DIE_ON_ERROR | @@ -301,27 +301,27 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (repo_read_index(the_repository) < 0) die(_("index file corrupt")); - refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &pathspec, NULL, NULL); + refresh_index(the_repository->index, REFRESH_QUIET|REFRESH_UNMERGED, &pathspec, NULL, NULL); seen = xcalloc(pathspec.nr, 1); - if (pathspec_needs_expanded_index(&the_index, &pathspec)) - ensure_full_index(&the_index); + if (pathspec_needs_expanded_index(the_repository->index, &pathspec)) + ensure_full_index(the_repository->index); - for (i = 0; i < the_index.cache_nr; i++) { - const struct cache_entry *ce = the_index.cache[i]; + for (i = 0; i < the_repository->index->cache_nr; i++) { + const struct cache_entry *ce = the_repository->index->cache[i]; if (!include_sparse && (ce_skip_worktree(ce) || - !path_in_sparse_checkout(ce->name, &the_index))) + !path_in_sparse_checkout(ce->name, the_repository->index))) continue; - if (!ce_path_match(&the_index, ce, &pathspec, seen)) + if (!ce_path_match(the_repository->index, ce, &pathspec, seen)) continue; ALLOC_GROW(list.entry, list.nr + 1, list.alloc); list.entry[list.nr].name = xstrdup(ce->name); list.entry[list.nr].is_submodule = S_ISGITLINK(ce->ce_mode); if (list.entry[list.nr++].is_submodule && - !is_staging_gitmodules_ok(&the_index)) + !is_staging_gitmodules_ok(the_repository->index)) die(_("please stage your changes to .gitmodules or stash them to proceed")); } @@ -391,7 +391,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (!quiet) printf("rm '%s'\n", path); - if (remove_file_from_index(&the_index, path)) + if (remove_file_from_index(the_repository->index, path)) die(_("git rm: unable to remove %s"), path); } @@ -432,10 +432,10 @@ int cmd_rm(int argc, const char **argv, const char *prefix) } strbuf_release(&buf); if (gitmodules_modified) - stage_updated_gitmodules(&the_index); + stage_updated_gitmodules(the_repository->index); } - if (write_locked_index(&the_index, &lock_file, + if (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK | SKIP_IF_UNCHANGED)) die(_("Unable to write new index file")); diff --git a/builtin/stash.c b/builtin/stash.c index 062be1fbc0..bf2834fddd 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -1,4 +1,3 @@ -#define USE_THE_INDEX_VARIABLE #include "builtin.h" #include "abspath.h" #include "config.h" @@ -273,7 +272,7 @@ static int reset_tree(struct object_id *i_tree, int update, int reset) struct lock_file lock_file = LOCK_INIT; repo_read_index_preload(the_repository, NULL, 0); - if (refresh_index(&the_index, REFRESH_QUIET, NULL, NULL, NULL)) + if (refresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL)) return -1; repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR); @@ -287,8 +286,8 @@ static int reset_tree(struct object_id *i_tree, int update, int reset) init_tree_desc(t, &tree->object.oid, tree->buffer, tree->size); opts.head_idx = 1; - opts.src_index = &the_index; - opts.dst_index = &the_index; + opts.src_index = the_repository->index; + opts.dst_index = the_repository->index; opts.merge = 1; opts.reset = reset ? UNPACK_RESET_PROTECT_UNTRACKED : 0; opts.update = update; @@ -299,7 +298,7 @@ static int reset_tree(struct object_id *i_tree, int update, int reset) if (unpack_trees(nr_trees, t, &opts)) return -1; - if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) + if (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK)) return error(_("unable to write new index file")); return 0; @@ -430,7 +429,7 @@ static void unstage_changes_unless_new(struct object_id *orig_tree) state.force = 1; state.quiet = 1; state.refresh_cache = 1; - state.istate = &the_index; + state.istate = the_repository->index; /* * Step 1: get a difference between orig_tree (which corresponding @@ -454,7 +453,7 @@ static void unstage_changes_unless_new(struct object_id *orig_tree) /* Look up the path's position in the current index. */ p = diff_queued_diff.queue[i]; - pos = index_name_pos(&the_index, p->two->path, + pos = index_name_pos(the_repository->index, p->two->path, strlen(p->two->path)); /* @@ -465,10 +464,10 @@ static void unstage_changes_unless_new(struct object_id *orig_tree) * path, but left it out of the working tree, then clear the * SKIP_WORKTREE bit and write it to the working tree. */ - if (pos >= 0 && ce_skip_worktree(the_index.cache[pos])) { + if (pos >= 0 && ce_skip_worktree(the_repository->index->cache[pos])) { struct stat st; - ce = the_index.cache[pos]; + ce = the_repository->index->cache[pos]; if (!lstat(ce->name, &st)) { /* Conflicting path present; relocate it */ struct strbuf new_path = STRBUF_INIT; @@ -504,12 +503,12 @@ static void unstage_changes_unless_new(struct object_id *orig_tree) if (pos < 0) option = ADD_CACHE_OK_TO_ADD; - ce = make_cache_entry(&the_index, + ce = make_cache_entry(the_repository->index, p->one->mode, &p->one->oid, p->one->path, 0, 0); - add_index_entry(&the_index, ce, option); + add_index_entry(the_repository->index, ce, option); } } diff_flush(&diff_opts); @@ -518,7 +517,7 @@ static void unstage_changes_unless_new(struct object_id *orig_tree) * Step 4: write the new index to disk */ repo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR); - if (write_locked_index(&the_index, &lock, + if (write_locked_index(the_repository->index, &lock, COMMIT_LOCK | SKIP_IF_UNCHANGED)) die(_("could not write index")); } @@ -539,7 +538,7 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, NULL, NULL, NULL)) return error(_("could not write index")); - if (write_index_as_tree(&c_tree, &the_index, get_index_file(), 0, + if (write_index_as_tree(&c_tree, the_repository->index, get_index_file(), 0, NULL)) return error(_("cannot apply a stash in the middle of a merge")); @@ -562,14 +561,14 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, return error(_("conflicts in index. " "Try without --index.")); - discard_index(&the_index); + discard_index(the_repository->index); repo_read_index(the_repository); - if (write_index_as_tree(&index_tree, &the_index, + if (write_index_as_tree(&index_tree, the_repository->index, get_index_file(), 0, NULL)) return error(_("could not save index tree")); reset_head(); - discard_index(&the_index); + discard_index(the_repository->index); repo_read_index(the_repository); } } @@ -875,8 +874,8 @@ static void diff_include_untracked(const struct stash_info *info, struct diff_op } unpack_tree_opt.head_idx = -1; - unpack_tree_opt.src_index = &the_index; - unpack_tree_opt.dst_index = &the_index; + unpack_tree_opt.src_index = the_repository->index; + unpack_tree_opt.dst_index = the_repository->index; unpack_tree_opt.merge = 1; unpack_tree_opt.fn = stash_worktree_untracked_merge; @@ -1205,8 +1204,8 @@ static int stash_staged(struct stash_info *info, struct strbuf *out_patch, } cp_diff_tree.git_cmd = 1; - strvec_pushl(&cp_diff_tree.args, "diff-tree", "-p", "-U1", "HEAD", - oid_to_hex(&info->w_tree), "--", NULL); + strvec_pushl(&cp_diff_tree.args, "diff-tree", "-p", "--binary", + "-U1", "HEAD", oid_to_hex(&info->w_tree), "--", NULL); if (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) { ret = -1; goto done; @@ -1395,7 +1394,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b strbuf_addf(&commit_tree_label, "index on %s\n", msg.buf); commit_list_insert(head_commit, &parents); - if (write_index_as_tree(&info->i_tree, &the_index, get_index_file(), 0, + if (write_index_as_tree(&info->i_tree, the_repository->index, get_index_file(), 0, NULL) || commit_tree(commit_tree_label.buf, commit_tree_label.len, &info->i_tree, parents, &info->i_commit, NULL, NULL)) { @@ -1540,9 +1539,9 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q char *ps_matched = xcalloc(ps->nr, 1); /* TODO: audit for interaction with sparse-index. */ - ensure_full_index(&the_index); - for (i = 0; i < the_index.cache_nr; i++) - ce_path_match(&the_index, the_index.cache[i], ps, + ensure_full_index(the_repository->index); + for (i = 0; i < the_repository->index->cache_nr; i++) + ce_path_match(the_repository->index, the_repository->index->cache[i], ps, ps_matched); if (report_path_error(ps_matched, ps)) { @@ -1612,7 +1611,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q goto done; } } - discard_index(&the_index); + discard_index(the_repository->index); if (ps->nr) { struct child_process cp_add = CHILD_PROCESS_INIT; struct child_process cp_diff = CHILD_PROCESS_INIT; diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index e4e18adb57..c968ca793b 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -1,4 +1,3 @@ -#define USE_THE_INDEX_VARIABLE #include "builtin.h" #include "abspath.h" #include "environment.h" @@ -207,18 +206,18 @@ static int module_list_compute(const char **argv, if (repo_read_index(the_repository) < 0) die(_("index file corrupt")); - for (i = 0; i < the_index.cache_nr; i++) { - const struct cache_entry *ce = the_index.cache[i]; + for (i = 0; i < the_repository->index->cache_nr; i++) { + const struct cache_entry *ce = the_repository->index->cache[i]; - if (!match_pathspec(&the_index, pathspec, ce->name, ce_namelen(ce), + if (!match_pathspec(the_repository->index, pathspec, ce->name, ce_namelen(ce), 0, ps_matched, 1) || !S_ISGITLINK(ce->ce_mode)) continue; ALLOC_GROW(list->entries, list->nr + 1, list->alloc); list->entries[list->nr++] = ce; - while (i + 1 < the_index.cache_nr && - !strcmp(ce->name, the_index.cache[i + 1]->name)) + while (i + 1 < the_repository->index->cache_nr && + !strcmp(ce->name, the_repository->index->cache[i + 1]->name)) /* * Skip entries with the same name in different stages * to make sure an entry is returned only once. @@ -303,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); @@ -634,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); @@ -907,7 +912,7 @@ static void generate_submodule_summary(struct summary_cb *info, int fd = open(p->sm_path, O_RDONLY); if (fd < 0 || fstat(fd, &st) < 0 || - index_fd(&the_index, &p->oid_dst, fd, &st, OBJ_BLOB, + index_fd(the_repository->index, &p->oid_dst, fd, &st, OBJ_BLOB, p->sm_path, 0)) error(_("couldn't hash object from '%s'"), p->sm_path); } else { @@ -1238,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) { @@ -1381,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) @@ -1662,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); @@ -1681,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); @@ -1725,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) @@ -1738,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"); @@ -2517,6 +2583,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, @@ -2624,12 +2693,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; @@ -3243,21 +3321,21 @@ static void die_on_index_match(const char *path, int force) char *ps_matched = xcalloc(ps.nr, 1); /* TODO: audit for interaction with sparse-index. */ - ensure_full_index(&the_index); + ensure_full_index(the_repository->index); /* * Since there is only one pathspec, we just need to * check ps_matched[0] to know if a cache entry matched. */ - for (i = 0; i < the_index.cache_nr; i++) { - ce_path_match(&the_index, the_index.cache[i], &ps, + for (i = 0; i < the_repository->index->cache_nr; i++) { + ce_path_match(the_repository->index, the_repository->index->cache[i], &ps, ps_matched); if (ps_matched[0]) { if (!force) die(_("'%s' already exists in the index"), path); - if (!S_ISGITLINK(the_index.cache[i]->ce_mode)) + if (!S_ISGITLINK(the_repository->index->cache[i]->ce_mode)) die(_("'%s' already exists in the index " "and is not a submodule"), path); break; @@ -3356,6 +3434,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/tag.c b/builtin/tag.c index 9a33cb50b4..01b19a5b6c 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" @@ -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); @@ -654,7 +673,7 @@ 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); @@ -686,6 +705,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 7bcaa1476c..6321810006 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -3,7 +3,7 @@ * * Copyright (C) Linus Torvalds, 2005 */ -#define USE_THE_INDEX_VARIABLE + #include "builtin.h" #include "bulk-checkin.h" #include "config.h" @@ -247,16 +247,16 @@ done: static int mark_ce_flags(const char *path, int flag, int mark) { int namelen = strlen(path); - int pos = index_name_pos(&the_index, path, namelen); + int pos = index_name_pos(the_repository->index, path, namelen); if (0 <= pos) { - mark_fsmonitor_invalid(&the_index, the_index.cache[pos]); + mark_fsmonitor_invalid(the_repository->index, the_repository->index->cache[pos]); if (mark) - the_index.cache[pos]->ce_flags |= flag; + the_repository->index->cache[pos]->ce_flags |= flag; else - the_index.cache[pos]->ce_flags &= ~flag; - the_index.cache[pos]->ce_flags |= CE_UPDATE_IN_BASE; - cache_tree_invalidate_path(&the_index, path); - the_index.cache_changed |= CE_ENTRY_CHANGED; + the_repository->index->cache[pos]->ce_flags &= ~flag; + the_repository->index->cache[pos]->ce_flags |= CE_UPDATE_IN_BASE; + cache_tree_invalidate_path(the_repository->index, path); + the_repository->index->cache_changed |= CE_ENTRY_CHANGED; return 0; } return -1; @@ -266,7 +266,7 @@ static int remove_one_path(const char *path) { if (!allow_remove) return error("%s: does not exist and --remove not passed", path); - if (remove_file_from_index(&the_index, path)) + if (remove_file_from_index(the_repository->index, path)) return error("%s: cannot remove from the index", path); return 0; } @@ -291,24 +291,24 @@ static int add_one_path(const struct cache_entry *old, const char *path, int len struct cache_entry *ce; /* Was the old index entry already up-to-date? */ - if (old && !ce_stage(old) && !ie_match_stat(&the_index, old, st, 0)) + if (old && !ce_stage(old) && !ie_match_stat(the_repository->index, old, st, 0)) return 0; - ce = make_empty_cache_entry(&the_index, len); + ce = make_empty_cache_entry(the_repository->index, len); memcpy(ce->name, path, len); ce->ce_flags = create_ce_flags(0); ce->ce_namelen = len; - fill_stat_cache_info(&the_index, ce, st); + fill_stat_cache_info(the_repository->index, ce, st); ce->ce_mode = ce_mode_from_stat(old, st->st_mode); - if (index_path(&the_index, &ce->oid, path, st, + if (index_path(the_repository->index, &ce->oid, path, st, info_only ? 0 : HASH_WRITE_OBJECT)) { discard_cache_entry(ce); return -1; } option = allow_add ? ADD_CACHE_OK_TO_ADD : 0; option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0; - if (add_index_entry(&the_index, ce, option)) { + if (add_index_entry(the_repository->index, ce, option)) { discard_cache_entry(ce); return error("%s: cannot add to the index - missing --add option?", path); } @@ -341,11 +341,11 @@ static int add_one_path(const struct cache_entry *old, const char *path, int len static int process_directory(const char *path, int len, struct stat *st) { struct object_id oid; - int pos = index_name_pos(&the_index, path, len); + int pos = index_name_pos(the_repository->index, path, len); /* Exact match: file or existing gitlink */ if (pos >= 0) { - const struct cache_entry *ce = the_index.cache[pos]; + const struct cache_entry *ce = the_repository->index->cache[pos]; if (S_ISGITLINK(ce->ce_mode)) { /* Do nothing to the index if there is no HEAD! */ @@ -360,8 +360,8 @@ static int process_directory(const char *path, int len, struct stat *st) /* Inexact match: is there perhaps a subdirectory match? */ pos = -pos-1; - while (pos < the_index.cache_nr) { - const struct cache_entry *ce = the_index.cache[pos++]; + while (pos < the_repository->index->cache_nr) { + const struct cache_entry *ce = the_repository->index->cache[pos++]; if (strncmp(ce->name, path, len)) break; @@ -391,8 +391,8 @@ static int process_path(const char *path, struct stat *st, int stat_errno) if (has_symlink_leading_path(path, len)) return error("'%s' is beyond a symbolic link", path); - pos = index_name_pos(&the_index, path, len); - ce = pos < 0 ? NULL : the_index.cache[pos]; + pos = index_name_pos(the_repository->index, path, len); + ce = pos < 0 ? NULL : the_repository->index->cache[pos]; if (ce && ce_skip_worktree(ce)) { /* * working directory version is assumed "good" @@ -400,7 +400,7 @@ static int process_path(const char *path, struct stat *st, int stat_errno) * On the other hand, removing it from index should work */ if (!ignore_skip_worktree_entries && allow_remove && - remove_file_from_index(&the_index, path)) + remove_file_from_index(the_repository->index, path)) return error("%s: cannot remove from the index", path); return 0; } @@ -428,7 +428,7 @@ static int add_cacheinfo(unsigned int mode, const struct object_id *oid, return error("Invalid path '%s'", path); len = strlen(path); - ce = make_empty_cache_entry(&the_index, len); + ce = make_empty_cache_entry(the_repository->index, len); oidcpy(&ce->oid, oid); memcpy(ce->name, path, len); @@ -439,7 +439,7 @@ static int add_cacheinfo(unsigned int mode, const struct object_id *oid, ce->ce_flags |= CE_VALID; option = allow_add ? ADD_CACHE_OK_TO_ADD : 0; option |= allow_replace ? ADD_CACHE_OK_TO_REPLACE : 0; - if (add_index_entry(&the_index, ce, option)) + if (add_index_entry(the_repository->index, ce, option)) return error("%s: cannot add to the index - missing --add option?", path); report("add '%s'", path); @@ -451,11 +451,11 @@ static void chmod_path(char flip, const char *path) int pos; struct cache_entry *ce; - pos = index_name_pos(&the_index, path, strlen(path)); + pos = index_name_pos(the_repository->index, path, strlen(path)); if (pos < 0) goto fail; - ce = the_index.cache[pos]; - if (chmod_index_entry(&the_index, ce, flip) < 0) + ce = the_repository->index->cache[pos]; + if (chmod_index_entry(the_repository->index, ce, flip) < 0) goto fail; report("chmod %cx '%s'", flip, path); @@ -498,7 +498,7 @@ static void update_one(const char *path) } if (force_remove) { - if (remove_file_from_index(&the_index, path)) + if (remove_file_from_index(the_repository->index, path)) die("git update-index: unable to remove %s", path); report("remove '%s'", path); return; @@ -581,7 +581,7 @@ static void read_index_info(int nul_term_line) if (!mode) { /* mode == 0 means there is no such path -- remove */ - if (remove_file_from_index(&the_index, path_name)) + if (remove_file_from_index(the_repository->index, path_name)) die("git update-index: unable to remove %s", ptr); } @@ -622,12 +622,12 @@ static struct cache_entry *read_one_ent(const char *which, error("%s: not in %s branch.", path, which); return NULL; } - if (!the_index.sparse_index && mode == S_IFDIR) { + if (!the_repository->index->sparse_index && mode == S_IFDIR) { if (which) error("%s: not a blob in %s branch.", path, which); return NULL; } - ce = make_empty_cache_entry(&the_index, namelen); + ce = make_empty_cache_entry(the_repository->index, namelen); oidcpy(&ce->oid, &oid); memcpy(ce->name, path, namelen); @@ -642,12 +642,12 @@ static int unresolve_one(const char *path) struct string_list_item *item; int res = 0; - if (!the_index.resolve_undo) + if (!the_repository->index->resolve_undo) return res; - item = string_list_lookup(the_index.resolve_undo, path); + item = string_list_lookup(the_repository->index->resolve_undo, path); if (!item) return res; /* no resolve-undo record for the path */ - res = unmerge_index_entry(&the_index, path, item->util, 0); + res = unmerge_index_entry(the_repository->index, path, item->util, 0); FREE_AND_NULL(item->util); return res; } @@ -688,13 +688,13 @@ static int do_reupdate(const char **paths, */ has_head = 0; redo: - for (pos = 0; pos < the_index.cache_nr; pos++) { - const struct cache_entry *ce = the_index.cache[pos]; + for (pos = 0; pos < the_repository->index->cache_nr; pos++) { + const struct cache_entry *ce = the_repository->index->cache[pos]; struct cache_entry *old = NULL; int save_nr; char *path; - if (ce_stage(ce) || !ce_path_match(&the_index, ce, &pathspec, NULL)) + if (ce_stage(ce) || !ce_path_match(the_repository->index, ce, &pathspec, NULL)) continue; if (has_head) old = read_one_ent(NULL, &head_oid, @@ -710,7 +710,7 @@ static int do_reupdate(const char **paths, * to process each path individually */ if (S_ISSPARSEDIR(ce->ce_mode)) { - ensure_full_index(&the_index); + ensure_full_index(the_repository->index); goto redo; } @@ -718,12 +718,12 @@ static int do_reupdate(const char **paths, * path anymore, in which case, under 'allow_remove', * or worse yet 'allow_replace', active_nr may decrease. */ - save_nr = the_index.cache_nr; + save_nr = the_repository->index->cache_nr; path = xstrdup(ce->name); update_one(path); free(path); discard_cache_entry(old); - if (save_nr != the_index.cache_nr) + if (save_nr != the_repository->index->cache_nr) goto redo; } clear_pathspec(&pathspec); @@ -739,9 +739,9 @@ static int refresh(struct refresh_params *o, unsigned int flag) { setup_work_tree(); repo_read_index(the_repository); - *o->has_errors |= refresh_index(&the_index, o->flags | flag, NULL, + *o->has_errors |= refresh_index(the_repository->index, o->flags | flag, NULL, NULL, NULL); - if (has_racy_timestamp(&the_index)) { + if (has_racy_timestamp(the_repository->index)) { /* * Even if nothing else has changed, updating the file * increases the chance that racy timestamps become @@ -750,7 +750,7 @@ static int refresh(struct refresh_params *o, unsigned int flag) * refresh_index() as these are no actual errors. * cmd_status() does the same. */ - the_index.cache_changed |= SOMETHING_CHANGED; + the_repository->index->cache_changed |= SOMETHING_CHANGED; } return 0; } @@ -787,7 +787,7 @@ static int resolve_undo_clear_callback(const struct option *opt UNUSED, { BUG_ON_OPT_NEG(unset); BUG_ON_OPT_ARG(arg); - resolve_undo_clear_index(&the_index); + resolve_undo_clear_index(the_repository->index); return 0; } @@ -888,7 +888,7 @@ static enum parse_opt_result unresolve_callback( *has_errors = do_unresolve(ctx->argc, ctx->argv, prefix, prefix ? strlen(prefix) : 0); if (*has_errors) - the_index.cache_changed = 0; + the_repository->index->cache_changed = 0; ctx->argv += ctx->argc - 1; ctx->argc = 1; @@ -909,7 +909,7 @@ static enum parse_opt_result reupdate_callback( setup_work_tree(); *has_errors = do_reupdate(ctx->argv + 1, prefix); if (*has_errors) - the_index.cache_changed = 0; + the_repository->index->cache_changed = 0; ctx->argv += ctx->argc - 1; ctx->argc = 1; @@ -1056,7 +1056,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) if (entries < 0) die("cache corrupted"); - the_index.updated_skipworktree = 1; + the_repository->index->updated_skipworktree = 1; /* * Custom copy of parse_options() because we want to handle @@ -1111,18 +1111,18 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) getline_fn = nul_term_line ? strbuf_getline_nul : strbuf_getline_lf; if (preferred_index_format) { if (preferred_index_format < 0) { - printf(_("%d\n"), the_index.version); + printf(_("%d\n"), the_repository->index->version); } else if (preferred_index_format < INDEX_FORMAT_LB || INDEX_FORMAT_UB < preferred_index_format) { die("index-version %d not in range: %d..%d", preferred_index_format, INDEX_FORMAT_LB, INDEX_FORMAT_UB); } else { - if (the_index.version != preferred_index_format) - the_index.cache_changed |= SOMETHING_CHANGED; + if (the_repository->index->version != preferred_index_format) + the_repository->index->cache_changed |= SOMETHING_CHANGED; report(_("index-version: was %d, set to %d"), - the_index.version, preferred_index_format); - the_index.version = preferred_index_format; + the_repository->index->version, preferred_index_format); + the_repository->index->version = preferred_index_format; } } @@ -1159,16 +1159,16 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) warning(_("core.splitIndex is set to false; " "remove or change it, if you really want to " "enable split index")); - if (the_index.split_index) - the_index.cache_changed |= SPLIT_INDEX_ORDERED; + if (the_repository->index->split_index) + the_repository->index->cache_changed |= SPLIT_INDEX_ORDERED; else - add_split_index(&the_index); + add_split_index(the_repository->index); } else if (!split_index) { if (git_config_get_split_index() == 1) warning(_("core.splitIndex is set to true; " "remove or change it, if you really want to " "disable split index")); - remove_split_index(&the_index); + remove_split_index(the_repository->index); } prepare_repo_settings(r); @@ -1180,7 +1180,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) warning(_("core.untrackedCache is set to true; " "remove or change it, if you really want to " "disable the untracked cache")); - remove_untracked_cache(&the_index); + remove_untracked_cache(the_repository->index); report(_("Untracked cache disabled")); break; case UC_TEST: @@ -1192,7 +1192,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) warning(_("core.untrackedCache is set to false; " "remove or change it, if you really want to " "enable the untracked cache")); - add_untracked_cache(&the_index); + add_untracked_cache(the_repository->index); report(_("Untracked cache enabled for '%s'"), get_git_work_tree()); break; default: @@ -1222,7 +1222,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) "set it if you really want to " "enable fsmonitor")); } - add_fsmonitor(&the_index); + add_fsmonitor(the_repository->index); report(_("fsmonitor enabled")); } else if (!fsmonitor) { enum fsmonitor_mode fsm_mode = fsm_settings__get_mode(r); @@ -1230,17 +1230,17 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) warning(_("core.fsmonitor is set; " "remove it if you really want to " "disable fsmonitor")); - remove_fsmonitor(&the_index); + remove_fsmonitor(the_repository->index); report(_("fsmonitor disabled")); } - if (the_index.cache_changed || force_write) { + if (the_repository->index->cache_changed || force_write) { if (newfd < 0) { if (refresh_args.flags & REFRESH_QUIET) exit(128); unable_to_lock_die(get_index_file(), lock_error); } - if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) + if (write_locked_index(the_repository->index, &lock_file, COMMIT_LOCK)) die("Unable to write new index file"); } 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/write-tree.c b/builtin/write-tree.c index 66e83d0ecb..8c75b4609b 100644 --- a/builtin/write-tree.c +++ b/builtin/write-tree.c @@ -3,7 +3,7 @@ * * Copyright (C) Linus Torvalds, 2005 */ -#define USE_THE_INDEX_VARIABLE + #include "builtin.h" #include "config.h" #include "environment.h" @@ -44,8 +44,8 @@ int cmd_write_tree(int argc, const char **argv, const char *cmd_prefix) prepare_repo_settings(the_repository); the_repository->settings.command_requires_full_index = 0; - ret = write_index_as_tree(&oid, &the_index, get_index_file(), flags, - tree_prefix); + ret = write_index_as_tree(&oid, the_repository->index, get_index_file(), + flags, tree_prefix); switch (ret) { case 0: printf("%s\n", oid_to_hex(&oid)); diff --git a/ci/install-dependencies.sh b/ci/install-dependencies.sh index b4e22de3cb..2e7688ae8b 100755 --- a/ci/install-dependencies.sh +++ b/ci/install-dependencies.sh @@ -1,49 +1,81 @@ -#!/usr/bin/env bash +#!/bin/sh # # Install dependencies required to build and test Git on Linux and macOS # . ${0%/*}/lib.sh +begin_group "Install dependencies" + P4WHENCE=https://cdist2.perforce.com/perforce/r21.2 LFSWHENCE=https://github.com/github/git-lfs/releases/download/v$LINUX_GIT_LFS_VERSION -UBUNTU_COMMON_PKGS="make libssl-dev libcurl4-openssl-dev libexpat-dev - tcl tk gettext zlib1g-dev perl-modules liberror-perl libauthen-sasl-perl - libemail-valid-perl libio-socket-ssl-perl libnet-smtp-ssl-perl" +JGITWHENCE=https://repo.eclipse.org/content/groups/releases//org/eclipse/jgit/org.eclipse.jgit.pgm/6.8.0.202311291450-r/org.eclipse.jgit.pgm-6.8.0.202311291450-r.sh -case "$runs_on_pool" in +# Make sudo a no-op and execute the command directly when running as root. +# While using sudo would be fine on most platforms when we are root already, +# some platforms like e.g. Alpine Linux do not have sudo available by default +# and would thus break. +if test "$(id -u)" -eq 0 +then + sudo () { + "$@" + } +fi + +case "$distro" in +alpine-*) + apk add --update shadow sudo build-base curl-dev openssl-dev expat-dev gettext \ + pcre2-dev python3 musl-libintl perl-utils ncurses \ + apache2 apache2-http2 apache2-proxy apache2-ssl apache2-webdav apr-util-dbd_sqlite3 \ + bash cvs gnupg perl-cgi perl-dbd-sqlite >/dev/null + ;; +fedora-*) + dnf -yq update >/dev/null && + dnf -yq install make gcc findutils diffutils perl python3 gettext zlib-devel expat-devel openssl-devel curl-devel pcre2-devel >/dev/null + ;; ubuntu-*) + # Required so that apt doesn't wait for user input on certain packages. + export DEBIAN_FRONTEND=noninteractive + sudo apt-get -q update - sudo apt-get -q -y install language-pack-is libsvn-perl apache2 \ - $UBUNTU_COMMON_PKGS $CC_PACKAGE $PYTHON_PACKAGE - mkdir --parents "$P4_PATH" - pushd "$P4_PATH" - wget --quiet "$P4WHENCE/bin.linux26x86_64/p4d" - wget --quiet "$P4WHENCE/bin.linux26x86_64/p4" - chmod u+x p4d - chmod u+x p4 - popd - mkdir --parents "$GIT_LFS_PATH" - pushd "$GIT_LFS_PATH" - wget --quiet "$LFSWHENCE/git-lfs-linux-amd64-$LINUX_GIT_LFS_VERSION.tar.gz" - tar --extract --gunzip --file "git-lfs-linux-amd64-$LINUX_GIT_LFS_VERSION.tar.gz" - cp git-lfs-$LINUX_GIT_LFS_VERSION/git-lfs . - popd + sudo apt-get -q -y install \ + language-pack-is libsvn-perl apache2 cvs cvsps git gnupg subversion \ + make libssl-dev libcurl4-openssl-dev libexpat-dev wget sudo default-jre \ + tcl tk gettext zlib1g-dev perl-modules liberror-perl libauthen-sasl-perl \ + libemail-valid-perl libio-socket-ssl-perl libnet-smtp-ssl-perl libdbd-sqlite3-perl libcgi-pm-perl \ + ${CC_PACKAGE:-${CC:-gcc}} $PYTHON_PACKAGE + + mkdir --parents "$CUSTOM_PATH" + wget --quiet --directory-prefix="$CUSTOM_PATH" \ + "$P4WHENCE/bin.linux26x86_64/p4d" "$P4WHENCE/bin.linux26x86_64/p4" + chmod a+x "$CUSTOM_PATH/p4d" "$CUSTOM_PATH/p4" + + wget --quiet "$LFSWHENCE/git-lfs-linux-amd64-$LINUX_GIT_LFS_VERSION.tar.gz" + tar -xzf "git-lfs-linux-amd64-$LINUX_GIT_LFS_VERSION.tar.gz" \ + -C "$CUSTOM_PATH" --strip-components=1 "git-lfs-$LINUX_GIT_LFS_VERSION/git-lfs" + rm "git-lfs-linux-amd64-$LINUX_GIT_LFS_VERSION.tar.gz" + + wget --quiet "$JGITWHENCE" --output-document="$CUSTOM_PATH/jgit" + chmod a+x "$CUSTOM_PATH/jgit" + ;; +ubuntu32-*) + sudo linux32 --32bit i386 sh -c ' + apt update >/dev/null && + apt install -y build-essential libcurl4-openssl-dev \ + libssl-dev libexpat-dev gettext python >/dev/null + ' ;; 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 "$P4_PATH" - pushd "$P4_PATH" - wget -q "$P4WHENCE/bin.macosx1015x86_64/helix-core-server.tgz" && - tar -xf helix-core-server.tgz && - sudo xattr -d com.apple.quarantine p4 p4d 2>/dev/null || true - popd + mkdir -p "$CUSTOM_PATH" + wget -q "$P4WHENCE/bin.macosx1015x86_64/helix-core-server.tgz" && + tar -xf helix-core-server.tgz -C "$CUSTOM_PATH" p4 p4d && + sudo xattr -d com.apple.quarantine "$CUSTOM_PATH/p4" "$CUSTOM_PATH/p4d" 2>/dev/null || true + rm helix-core-server.tgz if test -n "$CC_PACKAGE" then @@ -72,10 +104,6 @@ Documentation) test -n "$ALREADY_HAVE_ASCIIDOCTOR" || sudo gem install --version 1.5.8 asciidoctor ;; -linux-gcc-default) - sudo apt-get -q update - sudo apt-get -q -y install $UBUNTU_COMMON_PKGS - ;; esac if type p4d >/dev/null 2>&1 && type p4 >/dev/null 2>&1 @@ -87,6 +115,7 @@ then else echo >&2 "WARNING: perforce wasn't installed, see above for clues why" fi + if type git-lfs >/dev/null 2>&1 then echo "$(tput setaf 6)Git-LFS Version$(tput sgr0)" @@ -94,3 +123,13 @@ then else echo >&2 "WARNING: git-lfs wasn't installed, see above for clues why" fi + +if type jgit >/dev/null 2>&1 +then + echo "$(tput setaf 6)JGit Version$(tput sgr0)" + jgit version +else + echo >&2 "WARNING: JGit wasn't installed, see above for clues why" +fi + +end_group "Install dependencies" diff --git a/ci/install-docker-dependencies.sh b/ci/install-docker-dependencies.sh deleted file mode 100755 index eb2c9e1eca..0000000000 --- a/ci/install-docker-dependencies.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/sh -# -# Install dependencies required to build and test Git inside container -# - -. ${0%/*}/lib.sh - -begin_group "Install dependencies" - -case "$jobname" in -linux32) - linux32 --32bit i386 sh -c ' - apt update >/dev/null && - apt install -y build-essential libcurl4-openssl-dev \ - libssl-dev libexpat-dev gettext python >/dev/null - ' - ;; -linux-musl) - apk add --update shadow sudo build-base curl-dev openssl-dev expat-dev gettext \ - pcre2-dev python3 musl-libintl perl-utils ncurses \ - apache2 apache2-http2 apache2-proxy apache2-ssl apache2-webdav apr-util-dbd_sqlite3 \ - bash cvs gnupg perl-cgi perl-dbd-sqlite >/dev/null - ;; -linux-*|StaticAnalysis) - # Required so that apt doesn't wait for user input on certain packages. - export DEBIAN_FRONTEND=noninteractive - - apt update -q && - apt install -q -y sudo git make language-pack-is libsvn-perl apache2 libssl-dev \ - libcurl4-openssl-dev libexpat-dev tcl tk gettext zlib1g-dev \ - perl-modules liberror-perl libauthen-sasl-perl libemail-valid-perl \ - libdbd-sqlite3-perl libio-socket-ssl-perl libnet-smtp-ssl-perl ${CC_PACKAGE:-${CC:-gcc}} \ - apache2 cvs cvsps gnupg libcgi-pm-perl subversion - - if test "$jobname" = StaticAnalysis - then - apt install -q -y coccinelle - fi - ;; -pedantic) - dnf -yq update >/dev/null && - dnf -yq install make gcc findutils diffutils perl python3 gettext zlib-devel expat-devel openssl-devel curl-devel pcre2-devel >/dev/null - ;; -esac - -end_group "Install dependencies" @@ -279,7 +279,7 @@ then cache_dir="$HOME/none" - runs_on_pool=$(echo "$CI_JOB_IMAGE" | tr : -) + distro=$(echo "$CI_JOB_IMAGE" | tr : -) JOBS=$(nproc) else echo "Could not identify CI type" >&2 @@ -318,16 +318,20 @@ export DEFAULT_TEST_TARGET=prove export GIT_TEST_CLONE_2GB=true export SKIP_DASHED_BUILT_INS=YesPlease -case "$runs_on_pool" in +case "$distro" in ubuntu-*) if test "$jobname" = "linux-gcc-default" then 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" @@ -340,10 +344,6 @@ ubuntu-*) # image. # Keep that in mind when you encounter a broken OS X build! export LINUX_GIT_LFS_VERSION="1.5.2" - - P4_PATH="$HOME/custom/p4" - GIT_LFS_PATH="$HOME/custom/git-lfs" - export PATH="$GIT_LFS_PATH:$P4_PATH:$PATH" ;; macos-*) MAKEFLAGS="$MAKEFLAGS PYTHON_PATH=$(which python3)" @@ -351,12 +351,12 @@ macos-*) then MAKEFLAGS="$MAKEFLAGS APPLE_COMMON_CRYPTO_SHA1=Yes" fi - - P4_PATH="$HOME/custom/p4" - export PATH="$P4_PATH:$PATH" ;; esac +CUSTOM_PATH="${CUSTOM_PATH:-$HOME/path}" +export PATH="$CUSTOM_PATH:$PATH" + case "$jobname" in linux32) CC=gcc diff --git a/ci/run-build-and-minimal-fuzzers.sh b/ci/run-build-and-minimal-fuzzers.sh index a51076d18d..797d65c661 100755 --- a/ci/run-build-and-minimal-fuzzers.sh +++ b/ci/run-build-and-minimal-fuzzers.sh @@ -7,7 +7,7 @@ group "Build fuzzers" make \ CC=clang \ - CXX=clang++ \ + FUZZ_CXX=clang++ \ CFLAGS="-fsanitize=fuzzer-no-link,address" \ LIB_FUZZING_ENGINE="-fsanitize=fuzzer,address" \ fuzz-all 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/common-main.c b/common-main.c index 033778b3c5..b86f40600f 100644 --- a/common-main.c +++ b/common-main.c @@ -48,7 +48,7 @@ int main(int argc, const char **argv) setlocale(LC_CTYPE, ""); git_setup_gettext(); - initialize_the_repository(); + initialize_repository(the_repository); attr_start(); 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 @@ -1416,8 +1416,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 +3193,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 +3217,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/config.mak.uname b/config.mak.uname index a7607a5676..85d63821ec 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -68,6 +68,7 @@ ifeq ($(uname_S),Linux) ifneq ($(findstring .el7.,$(uname_R)),) BASIC_CFLAGS += -std=c99 endif + LINK_FUZZ_PROGRAMS = YesPlease endif ifeq ($(uname_S),GNU/kFreeBSD) HAVE_ALLOCA_H = YesPlease 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/completion/git-completion.bash b/contrib/completion/git-completion.bash index 75193ded4b..5c0ddeb3d4 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -31,15 +31,29 @@ # Note that "git" is optional --- '!f() { : commit; ...}; f' would complete # just like the 'git commit' command. # -# If you have a command that is not part of git, but you would still -# like completion, you can use __git_complete: +# To add completion for git subcommands that are implemented in external +# scripts, define a function of the form '_git_${subcommand}' while replacing +# all dashes with underscores, and the main git completion will make use of it. +# For example, to add completion for 'git do-stuff' (which could e.g. live +# in /usr/bin/git-do-stuff), name the completion function '_git_do_stuff'. +# See _git_show, _git_bisect etc. below for more examples. +# +# If you have a shell command that is not part of git (and is not called as a +# git subcommand), but you would still like git-style completion for it, use +# __git_complete. For example, to use the same completion as for 'git log' also +# for the 'gl' command: # # __git_complete gl git_log # -# Or if it's a main command (i.e. git or gitk): +# Or if the 'gk' command should be completed the same as 'gitk': # # __git_complete gk gitk # +# The second parameter of __git_complete gives the completion function; it is +# resolved as a function named "$2", or "__$2_main", or "_$2" in that order. +# In the examples above, the actual functions used for completion will be +# _git_log and __gitk_main. +# # Compatible with bash 3.2.57. # # You can set the following environment variables to influence the behavior of @@ -3581,6 +3595,17 @@ _git_svn () fi } +_git_symbolic_ref () { + case "$cur" in + --*) + __gitcomp_builtin symbolic-ref + return + ;; + esac + + __git_complete_refs +} + _git_tag () { local i c="$__git_cmd_idx" f=0 diff --git a/contrib/completion/git-completion.zsh b/contrib/completion/git-completion.zsh index cac6f61881..f5877bd7a1 100644 --- a/contrib/completion/git-completion.zsh +++ b/contrib/completion/git-completion.zsh @@ -272,6 +272,7 @@ _git () { local _ret=1 local cur cword prev + local __git_repo_path cur=${words[CURRENT]} prev=${words[CURRENT-1]} @@ -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/credential.c b/credential.c index 18098bd35e..758528b291 100644 --- a/credential.c +++ b/credential.c @@ -25,13 +25,64 @@ void credential_clear(struct credential *c) free(c->path); free(c->username); free(c->password); + free(c->credential); free(c->oauth_refresh_token); + free(c->authtype); string_list_clear(&c->helpers, 0); strvec_clear(&c->wwwauth_headers); + strvec_clear(&c->state_headers); + strvec_clear(&c->state_headers_to_send); credential_init(c); } +void credential_next_state(struct credential *c) +{ + strvec_clear(&c->state_headers_to_send); + SWAP(c->state_headers, c->state_headers_to_send); +} + +void credential_clear_secrets(struct credential *c) +{ + FREE_AND_NULL(c->password); + FREE_AND_NULL(c->credential); +} + +static void credential_set_capability(struct credential_capability *capa, + enum credential_op_type op_type) +{ + switch (op_type) { + case CREDENTIAL_OP_INITIAL: + capa->request_initial = 1; + break; + case CREDENTIAL_OP_HELPER: + capa->request_helper = 1; + break; + case CREDENTIAL_OP_RESPONSE: + capa->response = 1; + break; + } +} + + +void credential_set_all_capabilities(struct credential *c, + enum credential_op_type op_type) +{ + credential_set_capability(&c->capa_authtype, op_type); + credential_set_capability(&c->capa_state, op_type); +} + +static void announce_one(struct credential_capability *cc, const char *name, FILE *fp) { + if (cc->request_initial) + fprintf(fp, "capability %s\n", name); +} + +void credential_announce_capabilities(struct credential *c, FILE *fp) { + fprintf(fp, "version 0\n"); + announce_one(&c->capa_authtype, "authtype", fp); + announce_one(&c->capa_state, "state", fp); +} + int credential_match(const struct credential *want, const struct credential *have, int match_password) { @@ -40,7 +91,8 @@ int credential_match(const struct credential *want, CHECK(host) && CHECK(path) && CHECK(username) && - (!match_password || CHECK(password)); + (!match_password || CHECK(password)) && + (!match_password || CHECK(credential)); #undef CHECK } @@ -208,7 +260,26 @@ static void credential_getpass(struct credential *c) PROMPT_ASKPASS); } -int credential_read(struct credential *c, FILE *fp) +int credential_has_capability(const struct credential_capability *capa, + enum credential_op_type op_type) +{ + /* + * We're checking here if each previous step indicated that we had the + * capability. If it did, then we want to pass it along; conversely, if + * it did not, we don't want to report that to our caller. + */ + switch (op_type) { + case CREDENTIAL_OP_HELPER: + return capa->request_initial; + case CREDENTIAL_OP_RESPONSE: + return capa->request_initial && capa->request_helper; + default: + return 0; + } +} + +int credential_read(struct credential *c, FILE *fp, + enum credential_op_type op_type) { struct strbuf line = STRBUF_INIT; @@ -233,6 +304,9 @@ int credential_read(struct credential *c, FILE *fp) } else if (!strcmp(key, "password")) { free(c->password); c->password = xstrdup(value); + } else if (!strcmp(key, "credential")) { + free(c->credential); + c->credential = xstrdup(value); } else if (!strcmp(key, "protocol")) { free(c->protocol); c->protocol = xstrdup(value); @@ -242,8 +316,19 @@ int credential_read(struct credential *c, FILE *fp) } else if (!strcmp(key, "path")) { free(c->path); c->path = xstrdup(value); + } else if (!strcmp(key, "ephemeral")) { + c->ephemeral = !!git_config_bool("ephemeral", value); } else if (!strcmp(key, "wwwauth[]")) { strvec_push(&c->wwwauth_headers, value); + } else if (!strcmp(key, "state[]")) { + strvec_push(&c->state_headers, value); + } else if (!strcmp(key, "capability[]")) { + if (!strcmp(value, "authtype")) + credential_set_capability(&c->capa_authtype, op_type); + else if (!strcmp(value, "state")) + credential_set_capability(&c->capa_state, op_type); + } else if (!strcmp(key, "continue")) { + c->multistage = !!git_config_bool("continue", value); } else if (!strcmp(key, "password_expiry_utc")) { errno = 0; c->password_expiry_utc = parse_timestamp(value, NULL, 10); @@ -252,6 +337,9 @@ int credential_read(struct credential *c, FILE *fp) } else if (!strcmp(key, "oauth_refresh_token")) { free(c->oauth_refresh_token); c->oauth_refresh_token = xstrdup(value); + } else if (!strcmp(key, "authtype")) { + free(c->authtype); + c->authtype = xstrdup(value); } else if (!strcmp(key, "url")) { credential_from_url(c, value); } else if (!strcmp(key, "quit")) { @@ -280,8 +368,20 @@ static void credential_write_item(FILE *fp, const char *key, const char *value, fprintf(fp, "%s=%s\n", key, value); } -void credential_write(const struct credential *c, FILE *fp) +void credential_write(const struct credential *c, FILE *fp, + enum credential_op_type op_type) { + if (credential_has_capability(&c->capa_authtype, op_type)) + credential_write_item(fp, "capability[]", "authtype", 0); + if (credential_has_capability(&c->capa_state, op_type)) + credential_write_item(fp, "capability[]", "state", 0); + + if (credential_has_capability(&c->capa_authtype, op_type)) { + credential_write_item(fp, "authtype", c->authtype, 0); + credential_write_item(fp, "credential", c->credential, 0); + if (c->ephemeral) + credential_write_item(fp, "ephemeral", "1", 0); + } credential_write_item(fp, "protocol", c->protocol, 1); credential_write_item(fp, "host", c->host, 1); credential_write_item(fp, "path", c->path, 0); @@ -295,6 +395,12 @@ void credential_write(const struct credential *c, FILE *fp) } for (size_t i = 0; i < c->wwwauth_headers.nr; i++) credential_write_item(fp, "wwwauth[]", c->wwwauth_headers.v[i], 0); + if (credential_has_capability(&c->capa_state, op_type)) { + if (c->multistage) + credential_write_item(fp, "continue", "1", 0); + for (size_t i = 0; i < c->state_headers_to_send.nr; i++) + credential_write_item(fp, "state[]", c->state_headers_to_send.v[i], 0); + } } static int run_credential_helper(struct credential *c, @@ -317,14 +423,14 @@ static int run_credential_helper(struct credential *c, fp = xfdopen(helper.in, "w"); sigchain_push(SIGPIPE, SIG_IGN); - credential_write(c, fp); + credential_write(c, fp, want_output ? CREDENTIAL_OP_HELPER : CREDENTIAL_OP_RESPONSE); fclose(fp); sigchain_pop(SIGPIPE); if (want_output) { int r; fp = xfdopen(helper.out, "r"); - r = credential_read(c, fp); + r = credential_read(c, fp, CREDENTIAL_OP_HELPER); fclose(fp); if (r < 0) { finish_command(&helper); @@ -357,14 +463,19 @@ static int credential_do(struct credential *c, const char *helper, return r; } -void credential_fill(struct credential *c) +void credential_fill(struct credential *c, int all_capabilities) { int i; - if (c->username && c->password) + if ((c->username && c->password) || c->credential) return; + credential_next_state(c); + c->multistage = 0; + credential_apply_config(c); + if (all_capabilities) + credential_set_all_capabilities(c, CREDENTIAL_OP_INITIAL); for (i = 0; i < c->helpers.nr; i++) { credential_do(c, c->helpers.items[i].string, "get"); @@ -374,15 +485,17 @@ void credential_fill(struct credential *c) /* Reset expiry to maintain consistency */ c->password_expiry_utc = TIME_MAX; } - if (c->username && c->password) + if ((c->username && c->password) || c->credential) { + strvec_clear(&c->wwwauth_headers); return; + } if (c->quit) die("credential helper '%s' told us to quit", c->helpers.items[i].string); } credential_getpass(c); - if (!c->username && !c->password) + if (!c->username && !c->password && !c->credential) die("unable to get password from user"); } @@ -392,9 +505,11 @@ void credential_approve(struct credential *c) if (c->approved) return; - if (!c->username || !c->password || c->password_expiry_utc < time(NULL)) + if (((!c->username || !c->password) && !c->credential) || c->password_expiry_utc < time(NULL)) return; + credential_next_state(c); + credential_apply_config(c); for (i = 0; i < c->helpers.nr; i++) @@ -406,6 +521,8 @@ void credential_reject(struct credential *c) { int i; + credential_next_state(c); + credential_apply_config(c); for (i = 0; i < c->helpers.nr; i++) @@ -413,6 +530,7 @@ void credential_reject(struct credential *c) FREE_AND_NULL(c->username); FREE_AND_NULL(c->password); + FREE_AND_NULL(c->credential); FREE_AND_NULL(c->oauth_refresh_token); c->password_expiry_utc = TIME_MAX; c->approved = 0; diff --git a/credential.h b/credential.h index acc41adf54..af8c287ff2 100644 --- a/credential.h +++ b/credential.h @@ -93,6 +93,27 @@ * ----------------------------------------------------------------------- */ +/* + * These values define the kind of operation we're performing and the + * capabilities at each stage. The first is either an external request (via git + * credential fill) or an internal request (e.g., via the HTTP) code. The + * second is the call to the credential helper, and the third is the response + * we're providing. + * + * At each stage, we will emit the capability only if the previous stage + * supported it. + */ +enum credential_op_type { + CREDENTIAL_OP_INITIAL = 1, + CREDENTIAL_OP_HELPER = 2, + CREDENTIAL_OP_RESPONSE = 3, +}; + +struct credential_capability { + unsigned request_initial:1, + request_helper:1, + response:1; +}; /** * This struct represents a single username/password combination @@ -124,6 +145,16 @@ struct credential { struct strvec wwwauth_headers; /** + * A `strvec` of state headers received from credential helpers. + */ + struct strvec state_headers; + + /** + * A `strvec` of state headers to send to credential helpers. + */ + struct strvec state_headers_to_send; + + /** * Internal use only. Keeps track of if we previously matched against a * WWW-Authenticate header line in order to re-fold future continuation * lines into one value. @@ -131,24 +162,38 @@ struct credential { unsigned header_is_last_match:1; unsigned approved:1, + ephemeral:1, configured:1, + multistage: 1, quit:1, use_http_path:1, username_from_proto:1; + struct credential_capability capa_authtype; + struct credential_capability capa_state; + char *username; char *password; + char *credential; char *protocol; char *host; char *path; char *oauth_refresh_token; timestamp_t password_expiry_utc; + + /** + * The authorization scheme to use. If this is NULL, libcurl is free to + * negotiate any scheme it likes. + */ + char *authtype; }; #define CREDENTIAL_INIT { \ .helpers = STRING_LIST_INIT_DUP, \ .password_expiry_utc = TIME_MAX, \ .wwwauth_headers = STRVEC_INIT, \ + .state_headers = STRVEC_INIT, \ + .state_headers_to_send = STRVEC_INIT, \ } /* Initialize a credential structure, setting all fields to empty. */ @@ -167,8 +212,11 @@ void credential_clear(struct credential *); * returns, the username and password fields of the credential are * guaranteed to be non-NULL. If an error occurs, the function will * die(). + * + * If all_capabilities is set, this is an internal user that is prepared + * to deal with all known capabilities, and we should advertise that fact. */ -void credential_fill(struct credential *); +void credential_fill(struct credential *, int all_capabilities); /** * Inform the credential subsystem that the provided credentials @@ -191,8 +239,46 @@ void credential_approve(struct credential *); */ void credential_reject(struct credential *); -int credential_read(struct credential *, FILE *); -void credential_write(const struct credential *, FILE *); +/** + * Enable all of the supported credential flags in this credential. + */ +void credential_set_all_capabilities(struct credential *c, + enum credential_op_type op_type); + +/** + * Clear the secrets in this credential, but leave other data intact. + * + * This is useful for resetting credentials in preparation for a subsequent + * stage of filling. + */ +void credential_clear_secrets(struct credential *c); + +/** + * Print a list of supported capabilities and version numbers to standard + * output. + */ +void credential_announce_capabilities(struct credential *c, FILE *fp); + +/** + * Prepares the credential for the next iteration of the helper protocol by + * updating the state headers to send with the ones read by the last iteration + * of the protocol. + * + * Except for internal callers, this should be called exactly once between + * reading credentials with `credential_fill` and writing them. + */ +void credential_next_state(struct credential *c); + +/** + * Return true if the capability is enabled for an operation of op_type. + */ +int credential_has_capability(const struct credential_capability *capa, + enum credential_op_type op_type); + +int credential_read(struct credential *, FILE *, + enum credential_op_type); +void credential_write(const struct credential *, FILE *, + enum credential_op_type); /* * Parse a url into a credential struct, replacing any existing contents. diff --git a/diff-lib.c b/diff-lib.c index 683f11e509..12b1541478 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -660,7 +660,6 @@ int do_diff_cache(const struct object_id *tree_oid, struct diff_options *opt) repo_init_revisions(opt->repo, &revs, NULL); copy_pathspec(&revs.prune_data, &opt->pathspec); - diff_setup_done(&revs.diffopt); revs.diffopt = *opt; if (diff_cache(&revs, tree_oid, NULL, 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); @@ -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, \ } @@ -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 @@ -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; } @@ -128,7 +128,6 @@ static unsigned long empty_auth_useless = | CURLAUTH_DIGEST; static struct curl_slist *pragma_header; -static struct curl_slist *no_pragma_header; static struct string_list extra_http_headers = STRING_LIST_INIT_DUP; static struct curl_slist *host_resolutions; @@ -299,6 +298,11 @@ size_t fwrite_null(char *ptr UNUSED, size_t eltsize UNUSED, size_t nmemb, return nmemb; } +static struct curl_slist *object_request_headers(void) +{ + return curl_slist_append(http_copy_default_headers(), "Pragma:"); +} + static void closedown_active_slot(struct active_request_slot *slot) { active_requests--; @@ -557,18 +561,34 @@ static int curl_empty_auth_enabled(void) return 0; } +struct curl_slist *http_append_auth_header(const struct credential *c, + struct curl_slist *headers) +{ + if (c->authtype && c->credential) { + struct strbuf auth = STRBUF_INIT; + strbuf_addf(&auth, "Authorization: %s %s", + c->authtype, c->credential); + headers = curl_slist_append(headers, auth.buf); + strbuf_release(&auth); + } + return headers; +} + static void init_curl_http_auth(CURL *result) { - if (!http_auth.username || !*http_auth.username) { + if ((!http_auth.username || !*http_auth.username) && + (!http_auth.credential || !*http_auth.credential)) { if (curl_empty_auth_enabled()) curl_easy_setopt(result, CURLOPT_USERPWD, ":"); return; } - credential_fill(&http_auth); + credential_fill(&http_auth, 1); - curl_easy_setopt(result, CURLOPT_USERNAME, http_auth.username); - curl_easy_setopt(result, CURLOPT_PASSWORD, http_auth.password); + if (http_auth.password) { + curl_easy_setopt(result, CURLOPT_USERNAME, http_auth.username); + curl_easy_setopt(result, CURLOPT_PASSWORD, http_auth.password); + } } /* *var must be free-able */ @@ -582,17 +602,22 @@ static void var_override(const char **var, char *value) static void set_proxyauth_name_password(CURL *result) { + if (proxy_auth.password) { curl_easy_setopt(result, CURLOPT_PROXYUSERNAME, proxy_auth.username); curl_easy_setopt(result, CURLOPT_PROXYPASSWORD, proxy_auth.password); + } else if (proxy_auth.authtype && proxy_auth.credential) { + curl_easy_setopt(result, CURLOPT_PROXYHEADER, + http_append_auth_header(&proxy_auth, NULL)); + } } static void init_curl_proxy_auth(CURL *result) { if (proxy_auth.username) { - if (!proxy_auth.password) - credential_fill(&proxy_auth); + if (!proxy_auth.password && !proxy_auth.credential) + credential_fill(&proxy_auth, 1); set_proxyauth_name_password(result); } @@ -626,7 +651,7 @@ static int has_cert_password(void) cert_auth.host = xstrdup(""); cert_auth.username = xstrdup(""); cert_auth.path = xstrdup(ssl_cert); - credential_fill(&cert_auth); + credential_fill(&cert_auth, 0); } return 1; } @@ -641,7 +666,7 @@ static int has_proxy_cert_password(void) proxy_cert_auth.host = xstrdup(""); proxy_cert_auth.username = xstrdup(""); proxy_cert_auth.path = xstrdup(http_proxy_ssl_cert); - credential_fill(&proxy_cert_auth); + credential_fill(&proxy_cert_auth, 0); } return 1; } @@ -1275,8 +1300,6 @@ void http_init(struct remote *remote, const char *url, int proactive_auth) pragma_header = curl_slist_append(http_copy_default_headers(), "Pragma: no-cache"); - no_pragma_header = curl_slist_append(http_copy_default_headers(), - "Pragma:"); { char *http_max_requests = getenv("GIT_HTTP_MAX_REQUESTS"); @@ -1360,9 +1383,6 @@ void http_cleanup(void) curl_slist_free_all(pragma_header); pragma_header = NULL; - curl_slist_free_all(no_pragma_header); - no_pragma_header = NULL; - curl_slist_free_all(host_resolutions); host_resolutions = NULL; @@ -1470,7 +1490,7 @@ struct active_request_slot *get_active_slot(void) curl_easy_setopt(slot->curl, CURLOPT_IPRESOLVE, git_curl_ipresolve); curl_easy_setopt(slot->curl, CURLOPT_HTTPAUTH, http_auth_methods); - if (http_auth.password || curl_empty_auth_enabled()) + if (http_auth.password || http_auth.credential || curl_empty_auth_enabled()) init_curl_http_auth(slot->curl); return slot; @@ -1759,7 +1779,12 @@ static int handle_curl_result(struct slot_results *results) } else if (missing_target(results)) return HTTP_MISSING_TARGET; else if (results->http_code == 401) { - if (http_auth.username && http_auth.password) { + if ((http_auth.username && http_auth.password) ||\ + (http_auth.authtype && http_auth.credential)) { + if (http_auth.multistage) { + credential_clear_secrets(&http_auth); + return HTTP_REAUTH; + } credential_reject(&http_auth); return HTTP_NOAUTH; } else { @@ -2067,11 +2092,15 @@ static int http_request(const char *url, /* Add additional headers here */ if (options && options->extra_headers) { const struct string_list_item *item; - for_each_string_list_item(item, options->extra_headers) { - headers = curl_slist_append(headers, item->string); + if (options && options->extra_headers) { + for_each_string_list_item(item, options->extra_headers) { + headers = curl_slist_append(headers, item->string); + } } } + headers = http_append_auth_header(&http_auth, headers); + curl_easy_setopt(slot->curl, CURLOPT_URL, url); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(slot->curl, CURLOPT_ENCODING, ""); @@ -2153,6 +2182,7 @@ static int http_request_reauth(const char *url, void *result, int target, struct http_get_options *options) { + int i = 3; int ret = http_request(url, result, target, options); if (ret != HTTP_OK && ret != HTTP_REAUTH) @@ -2166,35 +2196,35 @@ static int http_request_reauth(const char *url, } } - if (ret != HTTP_REAUTH) - return ret; - - /* - * The previous request may have put cruft into our output stream; we - * should clear it out before making our next request. - */ - switch (target) { - case HTTP_REQUEST_STRBUF: - strbuf_reset(result); - break; - case HTTP_REQUEST_FILE: - if (fflush(result)) { - error_errno("unable to flush a file"); - return HTTP_START_FAILED; - } - rewind(result); - if (ftruncate(fileno(result), 0) < 0) { - error_errno("unable to truncate a file"); - return HTTP_START_FAILED; + while (ret == HTTP_REAUTH && --i) { + /* + * The previous request may have put cruft into our output stream; we + * should clear it out before making our next request. + */ + switch (target) { + case HTTP_REQUEST_STRBUF: + strbuf_reset(result); + break; + case HTTP_REQUEST_FILE: + if (fflush(result)) { + error_errno("unable to flush a file"); + return HTTP_START_FAILED; + } + rewind(result); + if (ftruncate(fileno(result), 0) < 0) { + error_errno("unable to truncate a file"); + return HTTP_START_FAILED; + } + break; + default: + BUG("Unknown http_request target"); } - break; - default: - BUG("Unknown http_request target"); - } - credential_fill(&http_auth); + credential_fill(&http_auth, 1); - return http_request(url, result, target, options); + ret = http_request(url, result, target, options); + } + return ret; } int http_get_strbuf(const char *url, @@ -2371,6 +2401,7 @@ void release_http_pack_request(struct http_pack_request *preq) } preq->slot = NULL; strbuf_release(&preq->tmpfile); + curl_slist_free_all(preq->headers); free(preq->url); free(preq); } @@ -2455,11 +2486,11 @@ struct http_pack_request *new_direct_http_pack_request( } preq->slot = get_active_slot(); + preq->headers = object_request_headers(); curl_easy_setopt(preq->slot->curl, CURLOPT_WRITEDATA, preq->packfile); curl_easy_setopt(preq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite); curl_easy_setopt(preq->slot->curl, CURLOPT_URL, preq->url); - curl_easy_setopt(preq->slot->curl, CURLOPT_HTTPHEADER, - no_pragma_header); + curl_easy_setopt(preq->slot->curl, CURLOPT_HTTPHEADER, preq->headers); /* * If there is data present from a previous transfer attempt, @@ -2625,13 +2656,14 @@ struct http_object_request *new_http_object_request(const char *base_url, } freq->slot = get_active_slot(); + freq->headers = object_request_headers(); curl_easy_setopt(freq->slot->curl, CURLOPT_WRITEDATA, freq); curl_easy_setopt(freq->slot->curl, CURLOPT_FAILONERROR, 0); curl_easy_setopt(freq->slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file); curl_easy_setopt(freq->slot->curl, CURLOPT_ERRORBUFFER, freq->errorstr); curl_easy_setopt(freq->slot->curl, CURLOPT_URL, freq->url); - curl_easy_setopt(freq->slot->curl, CURLOPT_HTTPHEADER, no_pragma_header); + curl_easy_setopt(freq->slot->curl, CURLOPT_HTTPHEADER, freq->headers); /* * If we have successfully processed data from a previous fetch @@ -2719,5 +2751,6 @@ void release_http_object_request(struct http_object_request *freq) release_active_slot(freq->slot); freq->slot = NULL; } + curl_slist_free_all(freq->headers); strbuf_release(&freq->tmpfile); } @@ -175,6 +175,9 @@ int http_get_file(const char *url, const char *filename, int http_fetch_ref(const char *base, struct ref *ref); +struct curl_slist *http_append_auth_header(const struct credential *c, + struct curl_slist *headers); + /* Helpers for fetching packs */ int http_get_info_packs(const char *base_url, struct packed_git **packs_head); @@ -196,6 +199,7 @@ struct http_pack_request { FILE *packfile; struct strbuf tmpfile; struct active_request_slot *slot; + struct curl_slist *headers; }; struct http_pack_request *new_http_pack_request( @@ -229,6 +233,7 @@ struct http_object_request { int zret; int rename; struct active_request_slot *slot; + struct curl_slist *headers; }; struct http_object_request *new_http_object_request( diff --git a/imap-send.c b/imap-send.c index 0afd088d8a..c0130d0e02 100644 --- a/imap-send.c +++ b/imap-send.c @@ -917,7 +917,7 @@ static void server_fill_credential(struct imap_server_conf *srvc, struct credent cred->username = xstrdup_or_null(srvc->user); cred->password = xstrdup_or_null(srvc->pass); - credential_fill(cred); + credential_fill(cred, 1); if (!srvc->user) srvc->user = xstrdup(cred->username); diff --git a/mergetools/vimdiff b/mergetools/vimdiff index 97e376329b..734d15a03b 100644 --- a/mergetools/vimdiff +++ b/mergetools/vimdiff @@ -72,7 +72,6 @@ gen_cmd_aux () { nested=0 nested_min=100 - # Step 1: # # Increase/decrease "start"/"end" indices respectively to get rid of @@ -87,7 +86,7 @@ gen_cmd_aux () { IFS=# for c in $(echo "$LAYOUT" | sed 's:.:&#:g') do - if test "$c" = " " + if test -z "$c" || test "$c" = " " then continue fi diff --git a/oss-fuzz/fuzz-commit-graph.c b/oss-fuzz/fuzz-commit-graph.c index 2992079dd9..fe15e2c225 100644 --- a/oss-fuzz/fuzz-commit-graph.c +++ b/oss-fuzz/fuzz-commit-graph.c @@ -11,7 +11,8 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { struct commit_graph *g; - initialize_the_repository(); + initialize_repository(the_repository); + /* * Initialize the_repository with commit-graph settings that would * normally be read from the repository's gitdir. We want to avoid @@ -829,6 +829,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 +840,7 @@ const char *enter_repo(const char *path, int strict) } else { const char *gitfile = read_gitfile(path); + die_upon_dubious_ownership(gitfile, NULL, path); if (gitfile) path = gitfile; if (chdir(path)) diff --git a/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/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/refs/reftable-backend.c b/refs/reftable-backend.c index 1cda48c504..010ef811b6 100644 --- a/refs/reftable-backend.c +++ b/refs/reftable-backend.c @@ -172,32 +172,30 @@ static int should_write_log(struct ref_store *refs, const char *refname) } } -static void fill_reftable_log_record(struct reftable_log_record *log) +static void fill_reftable_log_record(struct reftable_log_record *log, const struct ident_split *split) { - const char *info = git_committer_info(0); - struct ident_split split = {0}; + const char *tz_begin; int sign = 1; - if (split_ident_line(&split, info, strlen(info))) - BUG("failed splitting committer info"); - reftable_log_record_release(log); log->value_type = REFTABLE_LOG_UPDATE; log->value.update.name = - xstrndup(split.name_begin, split.name_end - split.name_begin); + xstrndup(split->name_begin, split->name_end - split->name_begin); log->value.update.email = - xstrndup(split.mail_begin, split.mail_end - split.mail_begin); - log->value.update.time = atol(split.date_begin); - if (*split.tz_begin == '-') { + xstrndup(split->mail_begin, split->mail_end - split->mail_begin); + log->value.update.time = atol(split->date_begin); + + tz_begin = split->tz_begin; + if (*tz_begin == '-') { sign = -1; - split.tz_begin++; + tz_begin++; } - if (*split.tz_begin == '+') { + if (*tz_begin == '+') { sign = 1; - split.tz_begin++; + tz_begin++; } - log->value.update.tz_offset = sign * atoi(split.tz_begin); + log->value.update.tz_offset = sign * atoi(tz_begin); } static int read_ref_without_reload(struct reftable_stack *stack, @@ -1021,9 +1019,15 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data reftable_stack_merged_table(arg->stack); uint64_t ts = reftable_stack_next_update_index(arg->stack); struct reftable_log_record *logs = NULL; + struct ident_split committer_ident = {0}; size_t logs_nr = 0, logs_alloc = 0, i; + const char *committer_info; int ret = 0; + committer_info = git_committer_info(0); + if (split_ident_line(&committer_ident, committer_info, strlen(committer_info))) + BUG("failed splitting committer info"); + QSORT(arg->updates, arg->updates_nr, transaction_update_cmp); reftable_writer_set_limits(writer, ts, ts); @@ -1089,7 +1093,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data log = &logs[logs_nr++]; memset(log, 0, sizeof(*log)); - fill_reftable_log_record(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); @@ -1227,6 +1231,7 @@ out: struct write_create_symref_arg { struct reftable_ref_store *refs; struct reftable_stack *stack; + struct strbuf *err; const char *refname; const char *target; const char *logmsg; @@ -1242,13 +1247,20 @@ static int write_create_symref_table(struct reftable_writer *writer, void *cb_da .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; @@ -1267,7 +1279,11 @@ static int write_create_symref_table(struct reftable_writer *writer, void *cb_da !should_write_log(&create->refs->base, create->refname)) return 0; - fill_reftable_log_record(&log); + 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, @@ -1290,12 +1306,14 @@ static int reftable_be_create_symref(struct ref_store *ref_store, 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; @@ -1311,9 +1329,15 @@ static int reftable_be_create_symref(struct ref_store *ref_store, done: assert(ret != REFTABLE_API_ERROR); - if (ret) - error("unable to write symref for %s: %s", refname, - reftable_error_str(ret)); + 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; } @@ -1335,10 +1359,16 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data) struct reftable_log_record old_log = {0}, *logs = NULL; struct reftable_iterator it = {0}; struct string_list skip = STRING_LIST_INIT_NODUP; + struct ident_split committer_ident = {0}; struct strbuf errbuf = STRBUF_INIT; size_t logs_nr = 0, logs_alloc = 0, i; + const char *committer_info; int ret; + committer_info = git_committer_info(0); + if (split_ident_line(&committer_ident, committer_info, strlen(committer_info))) + BUG("failed splitting committer info"); + if (reftable_stack_read_ref(arg->stack, arg->oldname, &old_ref)) { ret = error(_("refname %s not found"), arg->oldname); goto done; @@ -1361,7 +1391,8 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data) /* * Verify that the new refname is available. */ - string_list_insert(&skip, arg->oldname); + if (arg->delete_old) + string_list_insert(&skip, arg->oldname); ret = refs_verify_refname_available(&arg->refs->base, arg->newname, NULL, &skip, &errbuf); if (ret < 0) { @@ -1412,7 +1443,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data) ALLOC_GROW(logs, logs_nr + 1, logs_alloc); memset(&logs[logs_nr], 0, sizeof(logs[logs_nr])); - fill_reftable_log_record(&logs[logs_nr]); + fill_reftable_log_record(&logs[logs_nr], &committer_ident); logs[logs_nr].refname = (char *)arg->newname; logs[logs_nr].update_index = deletion_ts; logs[logs_nr].value.update.message = @@ -1444,7 +1475,7 @@ static int write_copy_table(struct reftable_writer *writer, void *cb_data) */ ALLOC_GROW(logs, logs_nr + 1, logs_alloc); memset(&logs[logs_nr], 0, sizeof(logs[logs_nr])); - fill_reftable_log_record(&logs[logs_nr]); + fill_reftable_log_record(&logs[logs_nr], &committer_ident); logs[logs_nr].refname = (char *)arg->newname; logs[logs_nr].update_index = creation_ts; logs[logs_nr].value.update.message = diff --git a/reftable/block.c b/reftable/block.c index 3e87460cba..5942cb4053 100644 --- a/reftable/block.c +++ b/reftable/block.c @@ -76,6 +76,10 @@ void block_writer_init(struct block_writer *bw, uint8_t typ, uint8_t *buf, bw->entries = 0; bw->restart_len = 0; bw->last_key.len = 0; + if (!bw->zstream) { + REFTABLE_CALLOC_ARRAY(bw->zstream, 1); + deflateInit(bw->zstream, 9); + } } uint8_t block_writer_type(struct block_writer *bw) @@ -139,39 +143,52 @@ int block_writer_finish(struct block_writer *w) w->next += 2; put_be24(w->buf + 1 + w->header_off, w->next); + /* + * Log records are stored zlib-compressed. Note that the compression + * also spans over the restart points we have just written. + */ if (block_writer_type(w) == BLOCK_TYPE_LOG) { int block_header_skip = 4 + w->header_off; - uLongf src_len = w->next - block_header_skip; - uLongf dest_cap = src_len * 1.001 + 12; - uint8_t *compressed; - - REFTABLE_ALLOC_ARRAY(compressed, dest_cap); - - while (1) { - uLongf out_dest_len = dest_cap; - int zresult = compress2(compressed, &out_dest_len, - w->buf + block_header_skip, - src_len, 9); - if (zresult == Z_BUF_ERROR && dest_cap < LONG_MAX) { - dest_cap *= 2; - compressed = - reftable_realloc(compressed, dest_cap); - if (compressed) - continue; - } - - if (Z_OK != zresult) { - reftable_free(compressed); - return REFTABLE_ZLIB_ERROR; - } - - memcpy(w->buf + block_header_skip, compressed, - out_dest_len); - w->next = out_dest_len + block_header_skip; - reftable_free(compressed); - break; - } + uLongf src_len = w->next - block_header_skip, compressed_len; + int ret; + + ret = deflateReset(w->zstream); + if (ret != Z_OK) + return REFTABLE_ZLIB_ERROR; + + /* + * Precompute the upper bound of how many bytes the compressed + * data may end up with. Combined with `Z_FINISH`, `deflate()` + * is guaranteed to return `Z_STREAM_END`. + */ + compressed_len = deflateBound(w->zstream, src_len); + REFTABLE_ALLOC_GROW(w->compressed, compressed_len, w->compressed_cap); + + w->zstream->next_out = w->compressed; + w->zstream->avail_out = compressed_len; + w->zstream->next_in = w->buf + block_header_skip; + w->zstream->avail_in = src_len; + + /* + * We want to perform all decompression in a single step, which + * is why we can pass Z_FINISH here. As we have precomputed the + * deflated buffer's size via `deflateBound()` this function is + * guaranteed to succeed according to the zlib documentation. + */ + ret = deflate(w->zstream, Z_FINISH); + if (ret != Z_STREAM_END) + return REFTABLE_ZLIB_ERROR; + + /* + * Overwrite the uncompressed data we have already written and + * adjust the `next` pointer to point right after the + * compressed data. + */ + memcpy(w->buf + block_header_skip, w->compressed, + w->zstream->total_out); + w->next = w->zstream->total_out + block_header_skip; } + return w->next; } @@ -514,7 +531,10 @@ done: void block_writer_release(struct block_writer *bw) { + deflateEnd(bw->zstream); + FREE_AND_NULL(bw->zstream); FREE_AND_NULL(bw->restarts); + FREE_AND_NULL(bw->compressed); strbuf_release(&bw->last_key); /* the block is not owned. */ } diff --git a/reftable/block.h b/reftable/block.h index ea4384a7e2..e91f3d2790 100644 --- a/reftable/block.h +++ b/reftable/block.h @@ -18,6 +18,10 @@ https://developers.google.com/open-source/licenses/bsd * allocation overhead. */ struct block_writer { + z_stream *zstream; + unsigned char *compressed; + size_t compressed_cap; + uint8_t *buf; uint32_t block_size; diff --git a/reftable/error.c b/reftable/error.c index cfb7a0fda4..a25f28a43e 100644 --- a/reftable/error.c +++ b/reftable/error.c @@ -27,8 +27,6 @@ const char *reftable_error_str(int err) return "misuse of the reftable API"; case REFTABLE_ZLIB_ERROR: return "zlib failure"; - case REFTABLE_NAME_CONFLICT: - return "file/directory conflict"; case REFTABLE_EMPTY_TABLE_ERROR: return "wrote empty table"; case REFTABLE_REFNAME_ERROR: diff --git a/reftable/refname.c b/reftable/refname.c deleted file mode 100644 index bbfde15754..0000000000 --- a/reftable/refname.c +++ /dev/null @@ -1,206 +0,0 @@ -/* - Copyright 2020 Google LLC - - Use of this source code is governed by a BSD-style - license that can be found in the LICENSE file or at - https://developers.google.com/open-source/licenses/bsd -*/ - -#include "system.h" -#include "reftable-error.h" -#include "basics.h" -#include "refname.h" -#include "reftable-iterator.h" - -struct refname_needle_lesseq_args { - char **haystack; - const char *needle; -}; - -static int refname_needle_lesseq(size_t k, void *_args) -{ - struct refname_needle_lesseq_args *args = _args; - return strcmp(args->needle, args->haystack[k]) <= 0; -} - -static int modification_has_ref(struct modification *mod, const char *name) -{ - struct reftable_ref_record ref = { NULL }; - int err = 0; - - if (mod->add_len > 0) { - struct refname_needle_lesseq_args args = { - .haystack = mod->add, - .needle = name, - }; - size_t idx = binsearch(mod->add_len, refname_needle_lesseq, &args); - if (idx < mod->add_len && !strcmp(mod->add[idx], name)) - return 0; - } - - if (mod->del_len > 0) { - struct refname_needle_lesseq_args args = { - .haystack = mod->del, - .needle = name, - }; - size_t idx = binsearch(mod->del_len, refname_needle_lesseq, &args); - if (idx < mod->del_len && !strcmp(mod->del[idx], name)) - return 1; - } - - err = reftable_table_read_ref(&mod->tab, name, &ref); - reftable_ref_record_release(&ref); - return err; -} - -static void modification_release(struct modification *mod) -{ - /* don't delete the strings themselves; they're owned by ref records. - */ - FREE_AND_NULL(mod->add); - FREE_AND_NULL(mod->del); - mod->add_len = 0; - mod->del_len = 0; -} - -static int modification_has_ref_with_prefix(struct modification *mod, - const char *prefix) -{ - struct reftable_iterator it = { NULL }; - struct reftable_ref_record ref = { NULL }; - int err = 0; - - if (mod->add_len > 0) { - struct refname_needle_lesseq_args args = { - .haystack = mod->add, - .needle = prefix, - }; - size_t idx = binsearch(mod->add_len, refname_needle_lesseq, &args); - if (idx < mod->add_len && - !strncmp(prefix, mod->add[idx], strlen(prefix))) - goto done; - } - err = reftable_table_seek_ref(&mod->tab, &it, prefix); - if (err) - goto done; - - while (1) { - err = reftable_iterator_next_ref(&it, &ref); - if (err) - goto done; - - if (mod->del_len > 0) { - struct refname_needle_lesseq_args args = { - .haystack = mod->del, - .needle = ref.refname, - }; - size_t idx = binsearch(mod->del_len, refname_needle_lesseq, &args); - if (idx < mod->del_len && - !strcmp(ref.refname, mod->del[idx])) - continue; - } - - if (strncmp(ref.refname, prefix, strlen(prefix))) { - err = 1; - goto done; - } - err = 0; - goto done; - } - -done: - reftable_ref_record_release(&ref); - reftable_iterator_destroy(&it); - return err; -} - -static int validate_refname(const char *name) -{ - while (1) { - char *next = strchr(name, '/'); - if (!*name) { - return REFTABLE_REFNAME_ERROR; - } - if (!next) { - return 0; - } - if (next - name == 0 || (next - name == 1 && *name == '.') || - (next - name == 2 && name[0] == '.' && name[1] == '.')) - return REFTABLE_REFNAME_ERROR; - name = next + 1; - } - return 0; -} - -int validate_ref_record_addition(struct reftable_table tab, - struct reftable_ref_record *recs, size_t sz) -{ - struct modification mod = { - .tab = tab, - .add = reftable_calloc(sz, sizeof(*mod.add)), - .del = reftable_calloc(sz, sizeof(*mod.del)), - }; - int i = 0; - int err = 0; - for (; i < sz; i++) { - if (reftable_ref_record_is_deletion(&recs[i])) { - mod.del[mod.del_len++] = recs[i].refname; - } else { - mod.add[mod.add_len++] = recs[i].refname; - } - } - - err = modification_validate(&mod); - modification_release(&mod); - return err; -} - -static void strbuf_trim_component(struct strbuf *sl) -{ - while (sl->len > 0) { - int is_slash = (sl->buf[sl->len - 1] == '/'); - strbuf_setlen(sl, sl->len - 1); - if (is_slash) - break; - } -} - -int modification_validate(struct modification *mod) -{ - struct strbuf slashed = STRBUF_INIT; - int err = 0; - int i = 0; - for (; i < mod->add_len; i++) { - err = validate_refname(mod->add[i]); - if (err) - goto done; - strbuf_reset(&slashed); - strbuf_addstr(&slashed, mod->add[i]); - strbuf_addstr(&slashed, "/"); - - err = modification_has_ref_with_prefix(mod, slashed.buf); - if (err == 0) { - err = REFTABLE_NAME_CONFLICT; - goto done; - } - if (err < 0) - goto done; - - strbuf_reset(&slashed); - strbuf_addstr(&slashed, mod->add[i]); - while (slashed.len) { - strbuf_trim_component(&slashed); - err = modification_has_ref(mod, slashed.buf); - if (err == 0) { - err = REFTABLE_NAME_CONFLICT; - goto done; - } - if (err < 0) - goto done; - } - } - err = 0; -done: - strbuf_release(&slashed); - return err; -} diff --git a/reftable/refname.h b/reftable/refname.h deleted file mode 100644 index a24b40fcb4..0000000000 --- a/reftable/refname.h +++ /dev/null @@ -1,29 +0,0 @@ -/* - Copyright 2020 Google LLC - - Use of this source code is governed by a BSD-style - license that can be found in the LICENSE file or at - https://developers.google.com/open-source/licenses/bsd -*/ -#ifndef REFNAME_H -#define REFNAME_H - -#include "reftable-record.h" -#include "reftable-generic.h" - -struct modification { - struct reftable_table tab; - - char **add; - size_t add_len; - - char **del; - size_t del_len; -}; - -int validate_ref_record_addition(struct reftable_table tab, - struct reftable_ref_record *recs, size_t sz); - -int modification_validate(struct modification *mod); - -#endif diff --git a/reftable/refname_test.c b/reftable/refname_test.c deleted file mode 100644 index b9cc62554e..0000000000 --- a/reftable/refname_test.c +++ /dev/null @@ -1,101 +0,0 @@ -/* -Copyright 2020 Google LLC - -Use of this source code is governed by a BSD-style -license that can be found in the LICENSE file or at -https://developers.google.com/open-source/licenses/bsd -*/ - -#include "basics.h" -#include "block.h" -#include "blocksource.h" -#include "reader.h" -#include "record.h" -#include "refname.h" -#include "reftable-error.h" -#include "reftable-writer.h" -#include "system.h" - -#include "test_framework.h" -#include "reftable-tests.h" - -struct testcase { - char *add; - char *del; - int error_code; -}; - -static void test_conflict(void) -{ - struct reftable_write_options opts = { 0 }; - struct strbuf buf = STRBUF_INIT; - struct reftable_writer *w = - reftable_new_writer(&strbuf_add_void, &noop_flush, &buf, &opts); - struct reftable_ref_record rec = { - .refname = "a/b", - .value_type = REFTABLE_REF_SYMREF, - .value.symref = "destination", /* make sure it's not a symref. - */ - .update_index = 1, - }; - int err; - int i; - struct reftable_block_source source = { NULL }; - struct reftable_reader *rd = NULL; - struct reftable_table tab = { NULL }; - struct testcase cases[] = { - { "a/b/c", NULL, REFTABLE_NAME_CONFLICT }, - { "b", NULL, 0 }, - { "a", NULL, REFTABLE_NAME_CONFLICT }, - { "a", "a/b", 0 }, - - { "p/", NULL, REFTABLE_REFNAME_ERROR }, - { "p//q", NULL, REFTABLE_REFNAME_ERROR }, - { "p/./q", NULL, REFTABLE_REFNAME_ERROR }, - { "p/../q", NULL, REFTABLE_REFNAME_ERROR }, - - { "a/b/c", "a/b", 0 }, - { NULL, "a//b", 0 }, - }; - reftable_writer_set_limits(w, 1, 1); - - err = reftable_writer_add_ref(w, &rec); - EXPECT_ERR(err); - - err = reftable_writer_close(w); - EXPECT_ERR(err); - reftable_writer_free(w); - - block_source_from_strbuf(&source, &buf); - err = reftable_new_reader(&rd, &source, "filename"); - EXPECT_ERR(err); - - reftable_table_from_reader(&tab, rd); - - for (i = 0; i < ARRAY_SIZE(cases); i++) { - struct modification mod = { - .tab = tab, - }; - - if (cases[i].add) { - mod.add = &cases[i].add; - mod.add_len = 1; - } - if (cases[i].del) { - mod.del = &cases[i].del; - mod.del_len = 1; - } - - err = modification_validate(&mod); - EXPECT(err == cases[i].error_code); - } - - reftable_reader_free(rd); - strbuf_release(&buf); -} - -int refname_test_main(int argc, const char *argv[]) -{ - RUN_TEST(test_conflict); - return 0; -} diff --git a/reftable/reftable-error.h b/reftable/reftable-error.h index e9b07c9f36..6368cd9ed9 100644 --- a/reftable/reftable-error.h +++ b/reftable/reftable-error.h @@ -48,9 +48,6 @@ enum reftable_error { /* Wrote a table without blocks. */ REFTABLE_EMPTY_TABLE_ERROR = -8, - /* Dir/file conflict. */ - REFTABLE_NAME_CONFLICT = -9, - /* Invalid ref name. */ REFTABLE_REFNAME_ERROR = -10, diff --git a/reftable/reftable-tests.h b/reftable/reftable-tests.h index 0019cbcfa4..114cc3d053 100644 --- a/reftable/reftable-tests.h +++ b/reftable/reftable-tests.h @@ -14,7 +14,6 @@ int block_test_main(int argc, const char **argv); int merged_test_main(int argc, const char **argv); int pq_test_main(int argc, const char **argv); int record_test_main(int argc, const char **argv); -int refname_test_main(int argc, const char **argv); int readwrite_test_main(int argc, const char **argv); int stack_test_main(int argc, const char **argv); int tree_test_main(int argc, const char **argv); diff --git a/reftable/reftable-writer.h b/reftable/reftable-writer.h index 155bf0bbe2..b601a69a40 100644 --- a/reftable/reftable-writer.h +++ b/reftable/reftable-writer.h @@ -38,10 +38,6 @@ struct reftable_write_options { /* Default mode for creating files. If unset, use 0666 (+umask) */ unsigned int default_permissions; - /* boolean: do not check ref names for validity or dir/file conflicts. - */ - unsigned skip_name_check : 1; - /* boolean: copy log messages exactly. If unset, check that the message * is a single line, and add '\n' if missing. */ diff --git a/reftable/stack.c b/reftable/stack.c index 80266bcbab..a59ebe038d 100644 --- a/reftable/stack.c +++ b/reftable/stack.c @@ -12,8 +12,8 @@ https://developers.google.com/open-source/licenses/bsd #include "system.h" #include "merged.h" #include "reader.h" -#include "refname.h" #include "reftable-error.h" +#include "reftable-generic.h" #include "reftable-record.h" #include "reftable-merged.h" #include "writer.h" @@ -27,8 +27,6 @@ static int stack_write_compact(struct reftable_stack *st, struct reftable_writer *wr, size_t first, size_t last, struct reftable_log_expiry_config *config); -static int stack_check_addition(struct reftable_stack *st, - const char *new_tab_name); static void reftable_addition_close(struct reftable_addition *add); static int reftable_stack_reload_maybe_reuse(struct reftable_stack *st, int reuse_open); @@ -787,10 +785,6 @@ int reftable_addition_add(struct reftable_addition *add, goto done; } - err = stack_check_addition(add->stack, get_tempfile_path(tab_file)); - if (err < 0) - goto done; - if (wr->min_update_index < add->next_update_index) { err = REFTABLE_API_ERROR; goto done; @@ -1355,65 +1349,6 @@ done: return err; } -static int stack_check_addition(struct reftable_stack *st, - const char *new_tab_name) -{ - int err = 0; - struct reftable_block_source src = { NULL }; - struct reftable_reader *rd = NULL; - struct reftable_table tab = { NULL }; - struct reftable_ref_record *refs = NULL; - struct reftable_iterator it = { NULL }; - int cap = 0; - int len = 0; - int i = 0; - - if (st->config.skip_name_check) - return 0; - - err = reftable_block_source_from_file(&src, new_tab_name); - if (err < 0) - goto done; - - err = reftable_new_reader(&rd, &src, new_tab_name); - if (err < 0) - goto done; - - err = reftable_reader_seek_ref(rd, &it, ""); - if (err > 0) { - err = 0; - goto done; - } - if (err < 0) - goto done; - - while (1) { - struct reftable_ref_record ref = { NULL }; - err = reftable_iterator_next_ref(&it, &ref); - if (err > 0) - break; - if (err < 0) - goto done; - - REFTABLE_ALLOC_GROW(refs, len + 1, cap); - refs[len++] = ref; - } - - reftable_table_from_merged_table(&tab, reftable_stack_merged_table(st)); - - err = validate_ref_record_addition(tab, refs, len); - -done: - for (i = 0; i < len; i++) { - reftable_ref_record_release(&refs[i]); - } - - free(refs); - reftable_iterator_destroy(&it); - reftable_reader_free(rd); - return err; -} - static int is_table_name(const char *s) { const char *dot = strrchr(s, '.'); diff --git a/reftable/stack_test.c b/reftable/stack_test.c index 1df3ffce52..7889f818d1 100644 --- a/reftable/stack_test.c +++ b/reftable/stack_test.c @@ -396,44 +396,6 @@ static void test_reftable_stack_auto_compaction_fails_gracefully(void) clear_dir(dir); } -static void test_reftable_stack_validate_refname(void) -{ - struct reftable_write_options cfg = { 0 }; - struct reftable_stack *st = NULL; - int err; - char *dir = get_tmp_dir(__LINE__); - - int i; - struct reftable_ref_record ref = { - .refname = "a/b", - .update_index = 1, - .value_type = REFTABLE_REF_SYMREF, - .value.symref = "master", - }; - char *additions[] = { "a", "a/b/c" }; - - err = reftable_new_stack(&st, dir, cfg); - EXPECT_ERR(err); - - err = reftable_stack_add(st, &write_test_ref, &ref); - EXPECT_ERR(err); - - for (i = 0; i < ARRAY_SIZE(additions); i++) { - struct reftable_ref_record ref = { - .refname = additions[i], - .update_index = 1, - .value_type = REFTABLE_REF_SYMREF, - .value.symref = "master", - }; - - err = reftable_stack_add(st, &write_test_ref, &ref); - EXPECT(err == REFTABLE_NAME_CONFLICT); - } - - reftable_stack_destroy(st); - clear_dir(dir); -} - static int write_error(struct reftable_writer *wr, void *arg) { return *((int *)arg); @@ -1105,7 +1067,6 @@ int stack_test_main(int argc, const char *argv[]) RUN_TEST(test_reftable_stack_auto_compaction_fails_gracefully); RUN_TEST(test_reftable_stack_update_index_check); RUN_TEST(test_reftable_stack_uptodate); - RUN_TEST(test_reftable_stack_validate_refname); RUN_TEST(test_suggest_compaction_segment); RUN_TEST(test_suggest_compaction_segment_nothing); return 0; diff --git a/reftable/writer.c b/reftable/writer.c index 1d9ff0fbfa..10eccaaa07 100644 --- a/reftable/writer.c +++ b/reftable/writer.c @@ -109,7 +109,7 @@ static void writer_reinit_block_writer(struct reftable_writer *w, uint8_t typ) block_start = header_size(writer_version(w)); } - strbuf_release(&w->last_key); + strbuf_reset(&w->last_key); block_writer_init(&w->block_writer_data, typ, w->block, w->opts.block_size, block_start, hash_size(w->opts.hash_id)); @@ -149,11 +149,21 @@ void reftable_writer_set_limits(struct reftable_writer *w, uint64_t min, w->max_update_index = max; } +static void writer_release(struct reftable_writer *w) +{ + if (w) { + reftable_free(w->block); + w->block = NULL; + block_writer_release(&w->block_writer_data); + w->block_writer = NULL; + writer_clear_index(w); + strbuf_release(&w->last_key); + } +} + void reftable_writer_free(struct reftable_writer *w) { - if (!w) - return; - reftable_free(w->block); + writer_release(w); reftable_free(w); } @@ -209,7 +219,8 @@ static int writer_add_record(struct reftable_writer *w, struct reftable_record *rec) { struct strbuf key = STRBUF_INIT; - int err = -1; + int err; + reftable_record_key(rec, &key); if (strbuf_cmp(&w->last_key, &key) >= 0) { err = REFTABLE_API_ERROR; @@ -218,27 +229,42 @@ static int writer_add_record(struct reftable_writer *w, strbuf_reset(&w->last_key); strbuf_addbuf(&w->last_key, &key); - if (!w->block_writer) { + if (!w->block_writer) writer_reinit_block_writer(w, reftable_record_type(rec)); - } - assert(block_writer_type(w->block_writer) == reftable_record_type(rec)); + if (block_writer_type(w->block_writer) != reftable_record_type(rec)) + BUG("record of type %d added to writer of type %d", + reftable_record_type(rec), block_writer_type(w->block_writer)); - if (block_writer_add(w->block_writer, rec) == 0) { + /* + * Try to add the record to the writer. If this succeeds then we're + * done. Otherwise the block writer may have hit the block size limit + * and needs to be flushed. + */ + if (!block_writer_add(w->block_writer, rec)) { err = 0; goto done; } + /* + * The current block is full, so we need to flush and reinitialize the + * writer to start writing the next block. + */ err = writer_flush_block(w); - if (err < 0) { + if (err < 0) goto done; - } - writer_reinit_block_writer(w, reftable_record_type(rec)); + + /* + * Try to add the record to the writer again. If this still fails then + * the record does not fit into the block size. + * + * TODO: it would be great to have `block_writer_add()` return proper + * error codes so that we don't have to second-guess the failure + * mode here. + */ err = block_writer_add(w->block_writer, rec); - if (err == -1) { - /* we are writing into memory, so an error can only mean it - * doesn't fit. */ + if (err) { err = REFTABLE_ENTRY_TOO_BIG_ERROR; goto done; } @@ -452,7 +478,7 @@ static int writer_finish_section(struct reftable_writer *w) bstats->max_index_level = max_level; /* Reinit lastKey, as the next section can start with any key. */ - w->last_key.len = 0; + strbuf_reset(&w->last_key); return 0; } @@ -627,74 +653,87 @@ int reftable_writer_close(struct reftable_writer *w) } done: - /* free up memory. */ - block_writer_release(&w->block_writer_data); - writer_clear_index(w); - strbuf_release(&w->last_key); + writer_release(w); return err; } static void writer_clear_index(struct reftable_writer *w) { - for (size_t i = 0; i < w->index_len; i++) + for (size_t i = 0; w->index && i < w->index_len; i++) strbuf_release(&w->index[i].last_key); FREE_AND_NULL(w->index); w->index_len = 0; w->index_cap = 0; } -static const int debug = 0; - static int writer_flush_nonempty_block(struct reftable_writer *w) { + struct reftable_index_record index_record = { + .last_key = STRBUF_INIT, + }; uint8_t typ = block_writer_type(w->block_writer); - struct reftable_block_stats *bstats = - writer_reftable_block_stats(w, typ); - uint64_t block_typ_off = (bstats->blocks == 0) ? w->next : 0; - int raw_bytes = block_writer_finish(w->block_writer); - int padding = 0; - int err = 0; - struct reftable_index_record ir = { .last_key = STRBUF_INIT }; + struct reftable_block_stats *bstats; + int raw_bytes, padding = 0, err; + uint64_t block_typ_off; + + /* + * Finish the current block. This will cause the block writer to emit + * restart points and potentially compress records in case we are + * writing a log block. + * + * Note that this is still happening in memory. + */ + raw_bytes = block_writer_finish(w->block_writer); if (raw_bytes < 0) return raw_bytes; - if (!w->opts.unpadded && typ != BLOCK_TYPE_LOG) { + /* + * By default, all records except for log records are padded to the + * block size. + */ + if (!w->opts.unpadded && typ != BLOCK_TYPE_LOG) padding = w->opts.block_size - raw_bytes; - } - if (block_typ_off > 0) { + bstats = writer_reftable_block_stats(w, typ); + block_typ_off = (bstats->blocks == 0) ? w->next : 0; + if (block_typ_off > 0) bstats->offset = block_typ_off; - } - bstats->entries += w->block_writer->entries; bstats->restarts += w->block_writer->restart_len; bstats->blocks++; w->stats.blocks++; - if (debug) { - fprintf(stderr, "block %c off %" PRIu64 " sz %d (%d)\n", typ, - w->next, raw_bytes, - get_be24(w->block + w->block_writer->header_off + 1)); - } - - if (w->next == 0) { + /* + * If this is the first block we're writing to the table then we need + * to also write the reftable header. + */ + if (!w->next) writer_write_header(w, w->block); - } err = padded_write(w, w->block, raw_bytes, padding); if (err < 0) return err; + /* + * Add an index record for every block that we're writing. If we end up + * having more than a threshold of index records we will end up writing + * an index section in `writer_finish_section()`. Each index record + * contains the last record key of the block it is indexing as well as + * the offset of that block. + * + * Note that this also applies when flushing index blocks, in which + * case we will end up with a multi-level index. + */ REFTABLE_ALLOC_GROW(w->index, w->index_len + 1, w->index_cap); - - ir.offset = w->next; - strbuf_reset(&ir.last_key); - strbuf_addbuf(&ir.last_key, &w->block_writer->last_key); - w->index[w->index_len] = ir; - + index_record.offset = w->next; + strbuf_reset(&index_record.last_key); + strbuf_addbuf(&index_record.last_key, &w->block_writer->last_key); + w->index[w->index_len] = index_record; w->index_len++; + w->next += padding + raw_bytes; w->block_writer = NULL; + return 0; } diff --git a/remote-curl.c b/remote-curl.c index 0b6d7815fd..cae98384da 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -889,7 +889,7 @@ static curl_off_t xcurl_off_t(size_t len) static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_received) { struct active_request_slot *slot; - struct curl_slist *headers = http_copy_default_headers(); + struct curl_slist *headers = NULL; int use_gzip = rpc->gzip_request; char *gzip_body = NULL; size_t gzip_size = 0; @@ -922,20 +922,24 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece do { err = probe_rpc(rpc, &results); if (err == HTTP_REAUTH) - credential_fill(&http_auth); + credential_fill(&http_auth, 0); } while (err == HTTP_REAUTH); if (err != HTTP_OK) return -1; - if (results.auth_avail & CURLAUTH_GSSNEGOTIATE) + if (results.auth_avail & CURLAUTH_GSSNEGOTIATE || http_auth.authtype) needs_100_continue = 1; } +retry: + headers = http_copy_default_headers(); headers = curl_slist_append(headers, rpc->hdr_content_type); headers = curl_slist_append(headers, rpc->hdr_accept); headers = curl_slist_append(headers, needs_100_continue ? "Expect: 100-continue" : "Expect:"); + headers = http_append_auth_header(&http_auth, headers); + /* Add Accept-Language header */ if (rpc->hdr_accept_language) headers = curl_slist_append(headers, rpc->hdr_accept_language); @@ -944,7 +948,6 @@ static int post_rpc(struct rpc_state *rpc, int stateless_connect, int flush_rece if (rpc->protocol_header) headers = curl_slist_append(headers, rpc->protocol_header); -retry: slot = get_active_slot(); curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0); @@ -1041,7 +1044,8 @@ retry: rpc->any_written = 0; err = run_slot(slot, NULL); if (err == HTTP_REAUTH && !large_request) { - credential_fill(&http_auth); + credential_fill(&http_auth, 0); + curl_slist_free_all(headers); goto retry; } if (err != HTTP_OK) diff --git a/repository.c b/repository.c index e15b416944..c50849fd83 100644 --- a/repository.c +++ b/repository.c @@ -1,8 +1,3 @@ -/* - * not really _using_ the compat macros, just make sure the_index - * declaration matches the definition in this file. - */ -#define USE_THE_INDEX_VARIABLE #include "git-compat-util.h" #include "abspath.h" #include "repository.h" @@ -22,21 +17,35 @@ /* The main repository */ static struct repository the_repo; -struct repository *the_repository; -struct index_state the_index; +struct repository *the_repository = &the_repo; -void initialize_the_repository(void) +void initialize_repository(struct repository *repo) { - the_repository = &the_repo; - - the_repo.index = &the_index; - the_repo.objects = raw_object_store_new(); - the_repo.remote_state = remote_state_new(); - the_repo.parsed_objects = parsed_object_pool_new(); - - index_state_init(&the_index, the_repository); + repo->objects = raw_object_store_new(); + repo->remote_state = remote_state_new(); + repo->parsed_objects = parsed_object_pool_new(); + ALLOC_ARRAY(repo->index, 1); + index_state_init(repo->index, repo); - repo_set_hash_algo(&the_repo, GIT_HASH_SHA1); + /* + * 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, @@ -188,9 +197,7 @@ int repo_init(struct repository *repo, struct repository_format format = REPOSITORY_FORMAT_INIT; memset(repo, 0, sizeof(*repo)); - repo->objects = raw_object_store_new(); - repo->parsed_objects = parsed_object_pool_new(); - repo->remote_state = remote_state_new(); + initialize_repository(repo); if (repo_init_gitdir(repo, gitdir)) goto error; @@ -295,6 +302,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); @@ -307,8 +316,7 @@ void repo_clear(struct repository *repo) if (repo->index) { discard_index(repo->index); - if (repo->index != &the_index) - FREE_AND_NULL(repo->index); + FREE_AND_NULL(repo->index); } if (repo->promisor_remote_config) { diff --git a/repository.h b/repository.h index 268436779c..41ed22543a 100644 --- a/repository.h +++ b/repository.h @@ -187,9 +187,6 @@ struct repository { }; extern struct repository *the_repository; -#ifdef USE_THE_INDEX_VARIABLE -extern struct index_state the_index; -#endif /* * Define a custom repository layout. Any field can be NULL, which @@ -210,7 +207,7 @@ void repo_set_worktree(struct repository *repo, const char *path); void repo_set_hash_algo(struct repository *repo, int algo); void repo_set_compat_hash_algo(struct repository *repo, int compat_algo); void repo_set_ref_storage_format(struct repository *repo, unsigned int format); -void initialize_the_repository(void); +void initialize_repository(struct repository *repo); RESULT_MUST_BE_USED int repo_init(struct repository *r, const char *gitdir, const char *worktree); diff --git a/sequencer.c b/sequencer.c index 2c19846385..88de4dc20f 100644 --- a/sequencer.c +++ b/sequencer.c @@ -207,6 +207,46 @@ static GIT_PATH_FUNC(rebase_path_no_reschedule_failed_exec, "rebase-merge/no-res static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, "rebase-merge/drop_redundant_commits") static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, "rebase-merge/keep_redundant_commits") +/* + * A 'struct replay_ctx' represents the private state of the sequencer. + */ +struct replay_ctx { + /* + * The commit message that will be used except at the end of a + * chain of fixup and squash commands. + */ + struct strbuf message; + /* + * The list of completed fixup and squash commands in the + * current chain. + */ + struct strbuf current_fixups; + /* + * Stores the reflog message that will be used when creating a + * commit. Points to a static buffer and should not be free()'d. + */ + const char *reflog_message; + /* + * The number of completed fixup and squash commands in the + * current chain. + */ + int current_fixup_count; + /* + * Whether message contains a commit message. + */ + unsigned have_message :1; +}; + +struct replay_ctx* replay_ctx_new(void) +{ + struct replay_ctx *ctx = xcalloc(1, sizeof(*ctx)); + + strbuf_init(&ctx->current_fixups, 0); + strbuf_init(&ctx->message, 0); + + return ctx; +} + /** * A 'struct update_refs_record' represents a value in the update-refs * list. We use a string_list to map refs to these (before, after) pairs. @@ -366,17 +406,26 @@ static const char *gpg_sign_opt_quoted(struct replay_opts *opts) return buf.buf; } +static void replay_ctx_release(struct replay_ctx *ctx) +{ + strbuf_release(&ctx->current_fixups); + strbuf_release(&ctx->message); +} + void replay_opts_release(struct replay_opts *opts) { + struct replay_ctx *ctx = opts->ctx; + free(opts->gpg_sign); free(opts->reflog_action); free(opts->default_strategy); free(opts->strategy); strvec_clear (&opts->xopts); - strbuf_release(&opts->current_fixups); if (opts->revs) release_revisions(opts->revs); free(opts->revs); + replay_ctx_release(ctx); + free(opts->ctx); } int sequencer_remove_state(struct replay_opts *opts) @@ -1084,6 +1133,7 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts, unsigned int flags) { + struct replay_ctx *ctx = opts->ctx; struct child_process cmd = CHILD_PROCESS_INIT; if ((flags & CLEANUP_MSG) && (flags & VERBATIM_MSG)) @@ -1101,7 +1151,7 @@ static int run_git_commit(const char *defmsg, gpg_opt, gpg_opt); } - strvec_pushf(&cmd.env, GIT_REFLOG_ACTION "=%s", opts->reflog_message); + strvec_pushf(&cmd.env, GIT_REFLOG_ACTION "=%s", ctx->reflog_message); if (opts->committer_date_is_author_date) strvec_pushf(&cmd.env, "GIT_COMMITTER_DATE=%s", @@ -1487,6 +1537,7 @@ static int try_to_commit(struct repository *r, struct replay_opts *opts, unsigned int flags, struct object_id *oid) { + struct replay_ctx *ctx = opts->ctx; struct object_id tree; struct commit *current_head = NULL; struct commit_list *parents = NULL; @@ -1648,7 +1699,7 @@ static int try_to_commit(struct repository *r, goto out; } - if (update_head_with_reflog(current_head, oid, opts->reflog_message, + if (update_head_with_reflog(current_head, oid, ctx->reflog_message, msg, &err)) { res = error("%s", err.buf); goto out; @@ -1878,10 +1929,10 @@ static void add_commented_lines(struct strbuf *buf, const void *str, size_t len) } /* Does the current fixup chain contain a squash command? */ -static int seen_squash(struct replay_opts *opts) +static int seen_squash(struct replay_ctx *ctx) { - return starts_with(opts->current_fixups.buf, "squash") || - strstr(opts->current_fixups.buf, "\nsquash"); + return starts_with(ctx->current_fixups.buf, "squash") || + strstr(ctx->current_fixups.buf, "\nsquash"); } static void update_comment_bufs(struct strbuf *buf1, struct strbuf *buf2, int n) @@ -1957,6 +2008,7 @@ static int append_squash_message(struct strbuf *buf, const char *body, enum todo_command command, struct replay_opts *opts, unsigned flag) { + struct replay_ctx *ctx = opts->ctx; const char *fixup_msg; size_t commented_len = 0, fixup_off; /* @@ -1965,13 +2017,13 @@ static int append_squash_message(struct strbuf *buf, const char *body, * squashing commit messages. */ if (starts_with(body, "amend!") || - ((command == TODO_SQUASH || seen_squash(opts)) && + ((command == TODO_SQUASH || seen_squash(ctx)) && (starts_with(body, "squash!") || starts_with(body, "fixup!")))) commented_len = commit_subject_length(body); strbuf_addf(buf, "\n%s ", comment_line_str); strbuf_addf(buf, _(nth_commit_msg_fmt), - ++opts->current_fixup_count + 1); + ++ctx->current_fixup_count + 1); strbuf_addstr(buf, "\n\n"); strbuf_add_commented_lines(buf, body, commented_len, comment_line_str); /* buf->buf may be reallocated so store an offset into the buffer */ @@ -1979,7 +2031,7 @@ static int append_squash_message(struct strbuf *buf, const char *body, strbuf_addstr(buf, body + commented_len); /* fixup -C after squash behaves like squash */ - if (is_fixup_flag(command, flag) && !seen_squash(opts)) { + if (is_fixup_flag(command, flag) && !seen_squash(ctx)) { /* * We're replacing the commit message so we need to * append the Signed-off-by: trailer if the user @@ -2013,12 +2065,13 @@ static int update_squash_messages(struct repository *r, struct replay_opts *opts, unsigned flag) { + struct replay_ctx *ctx = opts->ctx; struct strbuf buf = STRBUF_INIT; int res = 0; const char *message, *body; const char *encoding = get_commit_output_encoding(); - if (opts->current_fixup_count > 0) { + if (ctx->current_fixup_count > 0) { struct strbuf header = STRBUF_INIT; char *eol; @@ -2031,10 +2084,10 @@ static int update_squash_messages(struct repository *r, strbuf_addf(&header, "%s ", comment_line_str); strbuf_addf(&header, _(combined_commit_msg_fmt), - opts->current_fixup_count + 2); + ctx->current_fixup_count + 2); strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len); strbuf_release(&header); - if (is_fixup_flag(command, flag) && !seen_squash(opts)) + if (is_fixup_flag(command, flag) && !seen_squash(ctx)) update_squash_message_for_fixup(&buf); } else { struct object_id head; @@ -2081,7 +2134,7 @@ static int update_squash_messages(struct repository *r, } else if (command == TODO_FIXUP) { strbuf_addf(&buf, "\n%s ", comment_line_str); strbuf_addf(&buf, _(skip_nth_commit_msg_fmt), - ++opts->current_fixup_count + 1); + ++ctx->current_fixup_count + 1); strbuf_addstr(&buf, "\n\n"); strbuf_add_commented_lines(&buf, body, strlen(body), comment_line_str); @@ -2095,12 +2148,12 @@ static int update_squash_messages(struct repository *r, strbuf_release(&buf); if (!res) { - strbuf_addf(&opts->current_fixups, "%s%s %s", - opts->current_fixups.len ? "\n" : "", + strbuf_addf(&ctx->current_fixups, "%s%s %s", + ctx->current_fixups.len ? "\n" : "", command_to_string(command), oid_to_hex(&commit->object.oid)); - res = write_message(opts->current_fixups.buf, - opts->current_fixups.len, + res = write_message(ctx->current_fixups.buf, + ctx->current_fixups.len, rebase_path_current_fixups(), 0); } @@ -2178,6 +2231,7 @@ static int do_pick_commit(struct repository *r, struct replay_opts *opts, int final_fixup, int *check_todo) { + struct replay_ctx *ctx = opts->ctx; unsigned int flags = should_edit(opts) ? EDIT_MSG : 0; const char *msg_file = should_edit(opts) ? NULL : git_path_merge_msg(r); struct object_id head; @@ -2185,7 +2239,6 @@ static int do_pick_commit(struct repository *r, const char *base_label, *next_label; char *author = NULL; struct commit_message msg = { NULL, NULL, NULL, NULL }; - struct strbuf msgbuf = STRBUF_INIT; int res, unborn = 0, reword = 0, allow, drop_commit; enum todo_command command = item->command; struct commit *commit = item->commit; @@ -2284,7 +2337,7 @@ static int do_pick_commit(struct repository *r, next = parent; next_label = msg.parent_label; if (opts->commit_use_reference) { - strbuf_addstr(&msgbuf, + strbuf_addstr(&ctx->message, "# *** SAY WHY WE ARE REVERTING ON THE TITLE LINE ***"); } else if (skip_prefix(msg.subject, "Revert \"", &orig_subject) && /* @@ -2293,21 +2346,21 @@ static int do_pick_commit(struct repository *r, * thus requiring excessive complexity to deal with. */ !starts_with(orig_subject, "Revert \"")) { - strbuf_addstr(&msgbuf, "Reapply \""); - strbuf_addstr(&msgbuf, orig_subject); + strbuf_addstr(&ctx->message, "Reapply \""); + strbuf_addstr(&ctx->message, orig_subject); } else { - strbuf_addstr(&msgbuf, "Revert \""); - strbuf_addstr(&msgbuf, msg.subject); - strbuf_addstr(&msgbuf, "\""); + strbuf_addstr(&ctx->message, "Revert \""); + strbuf_addstr(&ctx->message, msg.subject); + strbuf_addstr(&ctx->message, "\""); } - strbuf_addstr(&msgbuf, "\n\nThis reverts commit "); - refer_to_commit(opts, &msgbuf, commit); + strbuf_addstr(&ctx->message, "\n\nThis reverts commit "); + refer_to_commit(opts, &ctx->message, commit); if (commit->parents && commit->parents->next) { - strbuf_addstr(&msgbuf, ", reversing\nchanges made to "); - refer_to_commit(opts, &msgbuf, parent); + strbuf_addstr(&ctx->message, ", reversing\nchanges made to "); + refer_to_commit(opts, &ctx->message, parent); } - strbuf_addstr(&msgbuf, ".\n"); + strbuf_addstr(&ctx->message, ".\n"); } else { const char *p; @@ -2316,21 +2369,22 @@ static int do_pick_commit(struct repository *r, next = commit; next_label = msg.label; - /* Append the commit log message to msgbuf. */ + /* Append the commit log message to ctx->message. */ if (find_commit_subject(msg.message, &p)) - strbuf_addstr(&msgbuf, p); + strbuf_addstr(&ctx->message, p); if (opts->record_origin) { - strbuf_complete_line(&msgbuf); - if (!has_conforming_footer(&msgbuf, NULL, 0)) - strbuf_addch(&msgbuf, '\n'); - strbuf_addstr(&msgbuf, cherry_picked_prefix); - strbuf_addstr(&msgbuf, oid_to_hex(&commit->object.oid)); - strbuf_addstr(&msgbuf, ")\n"); + strbuf_complete_line(&ctx->message); + if (!has_conforming_footer(&ctx->message, NULL, 0)) + strbuf_addch(&ctx->message, '\n'); + strbuf_addstr(&ctx->message, cherry_picked_prefix); + strbuf_addstr(&ctx->message, oid_to_hex(&commit->object.oid)); + strbuf_addstr(&ctx->message, ")\n"); } if (!is_fixup(command)) author = get_author(msg.message); } + ctx->have_message = 1; if (command == TODO_REWORD) reword = 1; @@ -2361,7 +2415,7 @@ static int do_pick_commit(struct repository *r, } if (opts->signoff && !is_fixup(command)) - append_signoff(&msgbuf, 0, 0); + append_signoff(&ctx->message, 0, 0); if (is_rebase_i(opts) && write_author_script(msg.message) < 0) res = -1; @@ -2370,17 +2424,17 @@ static int do_pick_commit(struct repository *r, !strcmp(opts->strategy, "ort") || command == TODO_REVERT) { res = do_recursive_merge(r, base, next, base_label, next_label, - &head, &msgbuf, opts); + &head, &ctx->message, opts); if (res < 0) goto leave; - res |= write_message(msgbuf.buf, msgbuf.len, + res |= write_message(ctx->message.buf, ctx->message.len, git_path_merge_msg(r), 0); } else { struct commit_list *common = NULL; struct commit_list *remotes = NULL; - res = write_message(msgbuf.buf, msgbuf.len, + res = write_message(ctx->message.buf, ctx->message.len, git_path_merge_msg(r), 0); commit_list_insert(base, &common); @@ -2458,14 +2512,13 @@ fast_forward_edit: unlink(rebase_path_fixup_msg()); unlink(rebase_path_squash_msg()); unlink(rebase_path_current_fixups()); - strbuf_reset(&opts->current_fixups); - opts->current_fixup_count = 0; + strbuf_reset(&ctx->current_fixups); + ctx->current_fixup_count = 0; } leave: free_message(commit, &msg); free(author); - strbuf_release(&msgbuf); update_abort_safety_file(); return res; @@ -2846,12 +2899,14 @@ void sequencer_post_commit_cleanup(struct repository *r, int verbose) NULL, REF_NO_DEREF); if (!need_cleanup) - return; + goto out; if (!have_finished_the_last_pick()) - return; + goto out; sequencer_remove_state(&opts); +out: + replay_opts_release(&opts); } static void todo_list_write_total_nr(struct todo_list *todo_list) @@ -3022,6 +3077,8 @@ static void read_strategy_opts(struct replay_opts *opts, struct strbuf *buf) static int read_populate_opts(struct replay_opts *opts) { + struct replay_ctx *ctx = opts->ctx; + if (is_rebase_i(opts)) { struct strbuf buf = STRBUF_INIT; int ret = 0; @@ -3081,13 +3138,13 @@ static int read_populate_opts(struct replay_opts *opts) read_strategy_opts(opts, &buf); strbuf_reset(&buf); - if (read_oneliner(&opts->current_fixups, + if (read_oneliner(&ctx->current_fixups, rebase_path_current_fixups(), READ_ONELINER_SKIP_IF_EMPTY)) { - const char *p = opts->current_fixups.buf; - opts->current_fixup_count = 1; + const char *p = ctx->current_fixups.buf; + ctx->current_fixup_count = 1; while ((p = strchr(p, '\n'))) { - opts->current_fixup_count++; + ctx->current_fixup_count++; p++; } } @@ -3608,13 +3665,24 @@ static int error_with_patch(struct repository *r, struct replay_opts *opts, int exit_code, int to_amend) { - if (commit) { - if (make_patch(r, commit, opts)) + struct replay_ctx *ctx = opts->ctx; + + /* + * Write the commit message to be used by "git rebase + * --continue". If a "fixup" or "squash" command has conflicts + * then we will have already written rebase_path_message() in + * error_failed_squash(). If an "edit" command was + * fast-forwarded then we don't have a message in ctx->message + * and rely on make_patch() to write rebase_path_message() + * instead. + */ + if (ctx->have_message && !file_exists(rebase_path_message()) && + write_message(ctx->message.buf, ctx->message.len, + rebase_path_message(), 0)) + return error(_("could not write commit message file")); + + if (commit && make_patch(r, commit, opts)) return -1; - } else if (copy_file(rebase_path_message(), - git_path_merge_msg(r), 0666)) - return error(_("unable to copy '%s' to '%s'"), - git_path_merge_msg(r), rebase_path_message()); if (to_amend) { if (intend_to_amend()) @@ -3936,6 +4004,7 @@ static int do_merge(struct repository *r, const char *arg, int arg_len, int flags, int *check_todo, struct replay_opts *opts) { + struct replay_ctx *ctx = opts->ctx; int run_commit_flags = 0; struct strbuf ref_name = STRBUF_INIT; struct commit *head_commit, *merge_commit, *i; @@ -4064,40 +4133,31 @@ static int do_merge(struct repository *r, write_author_script(message); find_commit_subject(message, &body); len = strlen(body); - ret = write_message(body, len, git_path_merge_msg(r), 0); + strbuf_add(&ctx->message, body, len); repo_unuse_commit_buffer(r, commit, message); - if (ret) { - error_errno(_("could not write '%s'"), - git_path_merge_msg(r)); - goto leave_merge; - } } else { struct strbuf buf = STRBUF_INIT; - int len; strbuf_addf(&buf, "author %s", git_author_info(0)); write_author_script(buf.buf); - strbuf_reset(&buf); + strbuf_release(&buf); if (oneline_offset < arg_len) { - p = arg + oneline_offset; - len = arg_len - oneline_offset; + strbuf_add(&ctx->message, arg + oneline_offset, + arg_len - oneline_offset); } else { - strbuf_addf(&buf, "Merge %s '%.*s'", + strbuf_addf(&ctx->message, "Merge %s '%.*s'", to_merge->next ? "branches" : "branch", merge_arg_len, arg); - p = buf.buf; - len = buf.len; - } - - ret = write_message(p, len, git_path_merge_msg(r), 0); - strbuf_release(&buf); - if (ret) { - error_errno(_("could not write '%s'"), - git_path_merge_msg(r)); - goto leave_merge; } } + ctx->have_message = 1; + if (write_message(ctx->message.buf, ctx->message.len, + git_path_merge_msg(r), 0)) { + ret = error_errno(_("could not write '%s'"), + git_path_merge_msg(r)); + goto leave_merge; + } if (strategy || to_merge->next) { /* Octopus merge */ @@ -4758,11 +4818,12 @@ static int pick_one_commit(struct repository *r, struct replay_opts *opts, int *check_todo, int* reschedule) { + struct replay_ctx *ctx = opts->ctx; int res; struct todo_item *item = todo_list->items + todo_list->current; const char *arg = todo_item_get_arg(todo_list, item); if (is_rebase_i(opts)) - opts->reflog_message = reflog_message( + ctx->reflog_message = reflog_message( opts, command_to_string(item->command), NULL); res = do_pick_commit(r, item, opts, is_final_fixup(todo_list), @@ -4819,9 +4880,10 @@ static int pick_commits(struct repository *r, struct todo_list *todo_list, struct replay_opts *opts) { + struct replay_ctx *ctx = opts->ctx; int res = 0, reschedule = 0; - opts->reflog_message = sequencer_reflog_action(opts); + ctx->reflog_message = sequencer_reflog_action(opts); if (opts->allow_ff) assert(!(opts->signoff || opts->no_commit || opts->record_origin || should_edit(opts) || @@ -4871,6 +4933,8 @@ static int pick_commits(struct repository *r, return stopped_at_head(r); } } + strbuf_reset(&ctx->message); + ctx->have_message = 0; if (item->command <= TODO_SQUASH) { res = pick_one_commit(r, todo_list, opts, &check_todo, &reschedule); @@ -5076,6 +5140,7 @@ static int commit_staged_changes(struct repository *r, struct replay_opts *opts, struct todo_list *todo_list) { + struct replay_ctx *ctx = opts->ctx; unsigned int flags = ALLOW_EMPTY | EDIT_MSG; unsigned int final_fixup = 0, is_clean; @@ -5112,7 +5177,7 @@ static int commit_staged_changes(struct repository *r, * the commit message and if there was a squash, let the user * edit it. */ - if (!is_clean || !opts->current_fixup_count) + if (!is_clean || !ctx->current_fixup_count) ; /* this is not the final fixup */ else if (!oideq(&head, &to_amend) || !file_exists(rebase_path_stopped_sha())) { @@ -5121,20 +5186,20 @@ static int commit_staged_changes(struct repository *r, unlink(rebase_path_fixup_msg()); unlink(rebase_path_squash_msg()); unlink(rebase_path_current_fixups()); - strbuf_reset(&opts->current_fixups); - opts->current_fixup_count = 0; + strbuf_reset(&ctx->current_fixups); + ctx->current_fixup_count = 0; } } else { /* we are in a fixup/squash chain */ - const char *p = opts->current_fixups.buf; - int len = opts->current_fixups.len; + const char *p = ctx->current_fixups.buf; + int len = ctx->current_fixups.len; - opts->current_fixup_count--; + ctx->current_fixup_count--; if (!len) BUG("Incorrect current_fixups:\n%s", p); while (len && p[len - 1] != '\n') len--; - strbuf_setlen(&opts->current_fixups, len); + strbuf_setlen(&ctx->current_fixups, len); if (write_message(p, len, rebase_path_current_fixups(), 0) < 0) return error(_("could not write file: '%s'"), @@ -5151,7 +5216,7 @@ static int commit_staged_changes(struct repository *r, * actually need to re-commit with a cleaned up commit * message. */ - if (opts->current_fixup_count > 0 && + if (ctx->current_fixup_count > 0 && !is_fixup(peek_command(todo_list, 0))) { final_fixup = 1; /* @@ -5224,20 +5289,21 @@ static int commit_staged_changes(struct repository *r, unlink(rebase_path_fixup_msg()); unlink(rebase_path_squash_msg()); } - if (opts->current_fixup_count > 0) { + if (ctx->current_fixup_count > 0) { /* * Whether final fixup or not, we just cleaned up the commit * message... */ unlink(rebase_path_current_fixups()); - strbuf_reset(&opts->current_fixups); - opts->current_fixup_count = 0; + strbuf_reset(&ctx->current_fixups); + ctx->current_fixup_count = 0; } return 0; } int sequencer_continue(struct repository *r, struct replay_opts *opts) { + struct replay_ctx *ctx = opts->ctx; struct todo_list todo_list = TODO_LIST_INIT; int res; @@ -5257,7 +5323,7 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts) unlink(rebase_path_dropped()); } - opts->reflog_message = reflog_message(opts, "continue", NULL); + ctx->reflog_message = reflog_message(opts, "continue", NULL); if (commit_staged_changes(r, opts, &todo_list)) { res = -1; goto release_todo_list; @@ -5309,7 +5375,7 @@ static int single_pick(struct repository *r, TODO_PICK : TODO_REVERT; item.commit = cmit; - opts->reflog_message = sequencer_reflog_action(opts); + opts->ctx->reflog_message = sequencer_reflog_action(opts); return do_pick_commit(r, &item, opts, 0, &check_todo); } diff --git a/sequencer.h b/sequencer.h index 437eabd38a..a309ddd712 100644 --- a/sequencer.h +++ b/sequencer.h @@ -31,6 +31,9 @@ enum commit_msg_cleanup_mode { COMMIT_MSG_CLEANUP_ALL }; +struct replay_ctx; +struct replay_ctx* replay_ctx_new(void); + struct replay_opts { enum replay_action action; @@ -68,10 +71,6 @@ struct replay_opts { /* Reflog */ char *reflog_action; - /* Used by fixup/squash */ - struct strbuf current_fixups; - int current_fixup_count; - /* placeholder commit for -i --root */ struct object_id squash_onto; int have_squash_onto; @@ -80,13 +79,13 @@ struct replay_opts { struct rev_info *revs; /* Private use */ - const char *reflog_message; + struct replay_ctx *ctx; }; #define REPLAY_OPTS_INIT { \ .edit = -1, \ .action = -1, \ - .current_fixups = STRBUF_INIT, \ .xopts = STRVEC_INIT, \ + .ctx = replay_ctx_new(), \ } /* @@ -16,6 +16,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; @@ -1220,6 +1221,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 +1803,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 +1929,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 +1940,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, '/'); @@ -2023,7 +2089,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 +2100,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) diff --git a/submodule.c b/submodule.c index ce2d032521..05d6db9d3d 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); @@ -1517,6 +1526,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 +1890,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 +1969,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 +2011,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 +2064,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 +2081,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 +2101,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 +2168,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 +2309,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 +2348,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 +2392,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 +2419,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 +2565,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/helper/test-cache-tree.c b/t/helper/test-cache-tree.c index e7236392c8..dc89ecfd71 100644 --- a/t/helper/test-cache-tree.c +++ b/t/helper/test-cache-tree.c @@ -1,4 +1,3 @@ -#define USE_THE_INDEX_VARIABLE #include "test-tool.h" #include "gettext.h" #include "hex.h" @@ -38,29 +37,29 @@ int cmd__cache_tree(int argc, const char **argv) if (repo_read_index(the_repository) < 0) die(_("unable to read index file")); - oidcpy(&oid, &the_index.cache_tree->oid); + oidcpy(&oid, &the_repository->index->cache_tree->oid); tree = parse_tree_indirect(&oid); if (!tree) die(_("not a tree object: %s"), oid_to_hex(&oid)); if (empty) { /* clear the cache tree & allocate a new one */ - cache_tree_free(&the_index.cache_tree); - the_index.cache_tree = cache_tree(); + cache_tree_free(&the_repository->index->cache_tree); + the_repository->index->cache_tree = cache_tree(); } else if (invalidate_qty) { /* invalidate the specified number of unique paths */ - float f_interval = (float)the_index.cache_nr / invalidate_qty; + float f_interval = (float)the_repository->index->cache_nr / invalidate_qty; int interval = f_interval < 1.0 ? 1 : (int)f_interval; - for (i = 0; i < invalidate_qty && i * interval < the_index.cache_nr; i++) - cache_tree_invalidate_path(&the_index, the_index.cache[i * interval]->name); + for (i = 0; i < invalidate_qty && i * interval < the_repository->index->cache_nr; i++) + cache_tree_invalidate_path(the_repository->index, the_repository->index->cache[i * interval]->name); } if (argc != 1) usage_with_options(test_cache_tree_usage, options); else if (!strcmp(argv[0], "prime")) - prime_cache_tree(the_repository, &the_index, tree); + prime_cache_tree(the_repository, the_repository->index, tree); else if (!strcmp(argv[0], "update")) - cache_tree_update(&the_index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR); + cache_tree_update(the_repository->index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR); /* use "control" subcommand to specify no-op */ else if (!!strcmp(argv[0], "control")) die(_("Unhandled subcommand '%s'"), argv[0]); diff --git a/t/helper/test-dump-cache-tree.c b/t/helper/test-dump-cache-tree.c index c38f546e4f..02b0b46c3f 100644 --- a/t/helper/test-dump-cache-tree.c +++ b/t/helper/test-dump-cache-tree.c @@ -1,4 +1,3 @@ -#define USE_THE_INDEX_VARIABLE #include "test-tool.h" #include "hash.h" #include "hex.h" @@ -68,10 +67,10 @@ int cmd__dump_cache_tree(int ac UNUSED, const char **av UNUSED) setup_git_directory(); if (repo_read_index(the_repository) < 0) die("unable to read index file"); - istate = the_index; + istate = *the_repository->index; istate.cache_tree = another; cache_tree_update(&istate, WRITE_TREE_DRY_RUN); - ret = dump_cache_tree(the_index.cache_tree, another, ""); + ret = dump_cache_tree(the_repository->index->cache_tree, another, ""); cache_tree_free(&another); return ret; diff --git a/t/helper/test-dump-split-index.c b/t/helper/test-dump-split-index.c index f29d18ef94..f472691a3c 100644 --- a/t/helper/test-dump-split-index.c +++ b/t/helper/test-dump-split-index.c @@ -1,4 +1,3 @@ -#define USE_THE_INDEX_VARIABLE #include "test-tool.h" #include "hex.h" #include "read-cache-ll.h" @@ -19,16 +18,16 @@ int cmd__dump_split_index(int ac UNUSED, const char **av) setup_git_directory(); - do_read_index(&the_index, av[1], 1); - printf("own %s\n", oid_to_hex(&the_index.oid)); - si = the_index.split_index; + do_read_index(the_repository->index, av[1], 1); + printf("own %s\n", oid_to_hex(&the_repository->index->oid)); + si = the_repository->index->split_index; if (!si) { printf("not a split index\n"); return 0; } printf("base %s\n", oid_to_hex(&si->base_oid)); - for (i = 0; i < the_index.cache_nr; i++) { - struct cache_entry *ce = the_index.cache[i]; + for (i = 0; i < the_repository->index->cache_nr; i++) { + struct cache_entry *ce = the_repository->index->cache[i]; printf("%06o %s %d\t%s\n", ce->ce_mode, oid_to_hex(&ce->oid), ce_stage(ce), ce->name); } diff --git a/t/helper/test-dump-untracked-cache.c b/t/helper/test-dump-untracked-cache.c index b4af9712fe..9ff67c3967 100644 --- a/t/helper/test-dump-untracked-cache.c +++ b/t/helper/test-dump-untracked-cache.c @@ -1,4 +1,3 @@ -#define USE_THE_INDEX_VARIABLE #include "test-tool.h" #include "dir.h" #include "hex.h" @@ -56,7 +55,7 @@ int cmd__dump_untracked_cache(int ac UNUSED, const char **av UNUSED) setup_git_directory(); if (repo_read_index(the_repository) < 0) die("unable to read index file"); - uc = the_index.untracked; + uc = the_repository->index->untracked; if (!uc) { printf("no untracked cache\n"); return 0; 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-lazy-init-name-hash.c b/t/helper/test-lazy-init-name-hash.c index 187a115d57..5f33bb7b8f 100644 --- a/t/helper/test-lazy-init-name-hash.c +++ b/t/helper/test-lazy-init-name-hash.c @@ -1,4 +1,3 @@ -#define USE_THE_INDEX_VARIABLE #include "test-tool.h" #include "environment.h" #include "name-hash.h" @@ -40,22 +39,22 @@ static void dump_run(void) repo_read_index(the_repository); if (single) { - test_lazy_init_name_hash(&the_index, 0); + test_lazy_init_name_hash(the_repository->index, 0); } else { - int nr_threads_used = test_lazy_init_name_hash(&the_index, 1); + int nr_threads_used = test_lazy_init_name_hash(the_repository->index, 1); if (!nr_threads_used) die("non-threaded code path used"); } - hashmap_for_each_entry(&the_index.dir_hash, &iter_dir, dir, + hashmap_for_each_entry(&the_repository->index->dir_hash, &iter_dir, dir, ent /* member name */) printf("dir %08x %7d %s\n", dir->ent.hash, dir->nr, dir->name); - hashmap_for_each_entry(&the_index.name_hash, &iter_cache, ce, + hashmap_for_each_entry(&the_repository->index->name_hash, &iter_cache, ce, ent /* member name */) printf("name %08x %s\n", ce->ent.hash, ce->name); - discard_index(&the_index); + discard_index(the_repository->index); } /* @@ -74,7 +73,7 @@ static uint64_t time_runs(int try_threaded) t0 = getnanotime(); repo_read_index(the_repository); t1 = getnanotime(); - nr_threads_used = test_lazy_init_name_hash(&the_index, try_threaded); + nr_threads_used = test_lazy_init_name_hash(the_repository->index, try_threaded); t2 = getnanotime(); sum += (t2 - t1); @@ -86,16 +85,16 @@ static uint64_t time_runs(int try_threaded) printf("%f %f %d multi %d\n", ((double)(t1 - t0))/1000000000, ((double)(t2 - t1))/1000000000, - the_index.cache_nr, + the_repository->index->cache_nr, nr_threads_used); else printf("%f %f %d single\n", ((double)(t1 - t0))/1000000000, ((double)(t2 - t1))/1000000000, - the_index.cache_nr); + the_repository->index->cache_nr); fflush(stdout); - discard_index(&the_index); + discard_index(the_repository->index); } avg = sum / count; @@ -120,8 +119,8 @@ static void analyze_run(void) int nr; repo_read_index(the_repository); - cache_nr_limit = the_index.cache_nr; - discard_index(&the_index); + cache_nr_limit = the_repository->index->cache_nr; + discard_index(the_repository->index); nr = analyze; while (1) { @@ -135,22 +134,22 @@ static void analyze_run(void) for (i = 0; i < count; i++) { repo_read_index(the_repository); - the_index.cache_nr = nr; /* cheap truncate of index */ + the_repository->index->cache_nr = nr; /* cheap truncate of index */ t1s = getnanotime(); - test_lazy_init_name_hash(&the_index, 0); + test_lazy_init_name_hash(the_repository->index, 0); t2s = getnanotime(); sum_single += (t2s - t1s); - the_index.cache_nr = cache_nr_limit; - discard_index(&the_index); + the_repository->index->cache_nr = cache_nr_limit; + discard_index(the_repository->index); repo_read_index(the_repository); - the_index.cache_nr = nr; /* cheap truncate of index */ + the_repository->index->cache_nr = nr; /* cheap truncate of index */ t1m = getnanotime(); - nr_threads_used = test_lazy_init_name_hash(&the_index, 1); + nr_threads_used = test_lazy_init_name_hash(the_repository->index, 1); t2m = getnanotime(); sum_multi += (t2m - t1m); - the_index.cache_nr = cache_nr_limit; - discard_index(&the_index); + the_repository->index->cache_nr = cache_nr_limit; + discard_index(the_repository->index); if (!nr_threads_used) printf(" [size %8d] [single %f] non-threaded code path used\n", 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-read-cache.c b/t/helper/test-read-cache.c index 1acd362346..e803c43ece 100644 --- a/t/helper/test-read-cache.c +++ b/t/helper/test-read-cache.c @@ -1,4 +1,3 @@ -#define USE_THE_INDEX_VARIABLE #include "test-tool.h" #include "config.h" #include "read-cache-ll.h" @@ -10,7 +9,7 @@ int cmd__read_cache(int argc, const char **argv) int i, cnt = 1; const char *name = NULL; - initialize_the_repository(); + initialize_repository(the_repository); if (argc > 1 && skip_prefix(argv[1], "--print-and-refresh=", &name)) { argc--; @@ -27,16 +26,16 @@ int cmd__read_cache(int argc, const char **argv) if (name) { int pos; - refresh_index(&the_index, REFRESH_QUIET, + refresh_index(the_repository->index, REFRESH_QUIET, NULL, NULL, NULL); - pos = index_name_pos(&the_index, name, strlen(name)); + pos = index_name_pos(the_repository->index, name, strlen(name)); if (pos < 0) die("%s not in index", name); printf("%s is%s up to date\n", name, - ce_uptodate(the_index.cache[pos]) ? "" : " not"); + ce_uptodate(the_repository->index->cache[pos]) ? "" : " not"); write_file(name, "%d\n", i); } - discard_index(&the_index); + discard_index(the_repository->index); } return 0; } diff --git a/t/helper/test-reftable.c b/t/helper/test-reftable.c index 00237ef0d9..bae731669c 100644 --- a/t/helper/test-reftable.c +++ b/t/helper/test-reftable.c @@ -13,7 +13,6 @@ int cmd__reftable(int argc, const char **argv) readwrite_test_main(argc, argv); merged_test_main(argc, argv); stack_test_main(argc, argv); - refname_test_main(argc, argv); return 0; } 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-scrap-cache-tree.c b/t/helper/test-scrap-cache-tree.c index 0a816a96e2..737cbe475b 100644 --- a/t/helper/test-scrap-cache-tree.c +++ b/t/helper/test-scrap-cache-tree.c @@ -1,4 +1,3 @@ -#define USE_THE_INDEX_VARIABLE #include "test-tool.h" #include "lockfile.h" #include "read-cache-ll.h" @@ -15,9 +14,9 @@ int cmd__scrap_cache_tree(int ac UNUSED, const char **av UNUSED) repo_hold_locked_index(the_repository, &index_lock, LOCK_DIE_ON_ERROR); if (repo_read_index(the_repository) < 0) die("unable to read index file"); - cache_tree_free(&the_index.cache_tree); - the_index.cache_tree = NULL; - if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) + cache_tree_free(&the_repository->index->cache_tree); + the_repository->index->cache_tree = NULL; + if (write_locked_index(the_repository->index, &index_lock, COMMIT_LOCK)) die("unable to write index file"); return 0; } 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/helper/test-write-cache.c b/t/helper/test-write-cache.c index f084034d38..7e3da380a9 100644 --- a/t/helper/test-write-cache.c +++ b/t/helper/test-write-cache.c @@ -1,4 +1,3 @@ -#define USE_THE_INDEX_VARIABLE #include "test-tool.h" #include "lockfile.h" #include "read-cache-ll.h" @@ -16,7 +15,7 @@ int cmd__write_cache(int argc, const char **argv) for (i = 0; i < cnt; i++) { repo_hold_locked_index(the_repository, &index_lock, LOCK_DIE_ON_ERROR); - if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) + if (write_locked_index(the_repository->index, &index_lock, COMMIT_LOCK)) die("unable to write index file"); } 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/lib-credential.sh b/t/lib-credential.sh index 44799c0d38..58b9c74060 100644 --- a/t/lib-credential.sh +++ b/t/lib-credential.sh @@ -538,6 +538,129 @@ helper_test_oauth_refresh_token() { ' } +helper_test_authtype() { + HELPER=$1 + + test_expect_success "helper ($HELPER) stores authtype and credential" ' + check approve $HELPER <<-\EOF + capability[]=authtype + authtype=Bearer + credential=random-token + protocol=https + host=git.example.com + EOF + ' + + test_expect_success "helper ($HELPER) gets authtype and credential" ' + check fill $HELPER <<-\EOF + capability[]=authtype + protocol=https + host=git.example.com + -- + capability[]=authtype + authtype=Bearer + credential=random-token + protocol=https + host=git.example.com + -- + EOF + ' + + test_expect_success "helper ($HELPER) stores authtype and credential with username" ' + check approve $HELPER <<-\EOF + capability[]=authtype + authtype=Bearer + credential=other-token + protocol=https + host=git.example.com + username=foobar + EOF + ' + + test_expect_success "helper ($HELPER) gets authtype and credential with username" ' + check fill $HELPER <<-\EOF + capability[]=authtype + protocol=https + host=git.example.com + username=foobar + -- + capability[]=authtype + authtype=Bearer + credential=other-token + protocol=https + host=git.example.com + username=foobar + -- + EOF + ' + + test_expect_success "helper ($HELPER) does not get authtype and credential with different username" ' + check fill $HELPER <<-\EOF + capability[]=authtype + protocol=https + host=git.example.com + username=barbaz + -- + protocol=https + host=git.example.com + username=barbaz + password=askpass-password + -- + askpass: Password for '\''https://barbaz@git.example.com'\'': + EOF + ' + + test_expect_success "helper ($HELPER) does not store ephemeral authtype and credential" ' + check approve $HELPER <<-\EOF && + capability[]=authtype + authtype=Bearer + credential=git2-token + protocol=https + host=git2.example.com + ephemeral=1 + EOF + + check fill $HELPER <<-\EOF + capability[]=authtype + protocol=https + host=git2.example.com + -- + protocol=https + host=git2.example.com + username=askpass-username + password=askpass-password + -- + askpass: Username for '\''https://git2.example.com'\'': + askpass: Password for '\''https://askpass-username@git2.example.com'\'': + EOF + ' + + test_expect_success "helper ($HELPER) does not store ephemeral username and password" ' + check approve $HELPER <<-\EOF && + capability[]=authtype + protocol=https + host=git2.example.com + user=barbaz + password=secret + ephemeral=1 + EOF + + check fill $HELPER <<-\EOF + capability[]=authtype + protocol=https + host=git2.example.com + -- + protocol=https + host=git2.example.com + username=askpass-username + password=askpass-password + -- + askpass: Username for '\''https://git2.example.com'\'': + askpass: Password for '\''https://askpass-username@git2.example.com'\'': + EOF + ' +} + write_script askpass <<\EOF echo >&2 askpass: $* what=$(echo $1 | cut -d" " -f1 | tr A-Z a-z | tr -cd a-z) diff --git a/t/lib-httpd/nph-custom-auth.sh b/t/lib-httpd/nph-custom-auth.sh index f5345e775e..d408d2caad 100644 --- a/t/lib-httpd/nph-custom-auth.sh +++ b/t/lib-httpd/nph-custom-auth.sh @@ -19,21 +19,30 @@ CHALLENGE_FILE=custom-auth.challenge # if test -n "$HTTP_AUTHORIZATION" && \ - grep -Fqsx "${HTTP_AUTHORIZATION}" "$VALID_CREDS_FILE" + grep -Fqs "creds=${HTTP_AUTHORIZATION}" "$VALID_CREDS_FILE" then + idno=$(grep -F "creds=${HTTP_AUTHORIZATION}" "$VALID_CREDS_FILE" | sed -e 's/^id=\([a-z0-9-][a-z0-9-]*\) .*$/\1/') + status=$(sed -ne "s/^id=$idno.*status=\\([0-9][0-9][0-9]\\).*\$/\\1/p" "$CHALLENGE_FILE" | head -n1) # Note that although git-http-backend returns a status line, it # does so using a CGI 'Status' header. Because this script is an # No Parsed Headers (NPH) script, we must return a real HTTP # status line. # This is only a test script, so we don't bother to check for # the actual status from git-http-backend and always return 200. - echo 'HTTP/1.1 200 OK' - exec "$GIT_EXEC_PATH"/git-http-backend + echo "HTTP/1.1 $status Nonspecific Reason Phrase" + if test "$status" -eq 200 + then + exec "$GIT_EXEC_PATH"/git-http-backend + else + sed -ne "s/^id=$idno.*response=//p" "$CHALLENGE_FILE" + echo + exit + fi fi echo 'HTTP/1.1 401 Authorization Required' if test -f "$CHALLENGE_FILE" then - cat "$CHALLENGE_FILE" + sed -ne 's/^id=default.*response=//p' "$CHALLENGE_FILE" fi echo 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..71f082836a 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 ' @@ -572,6 +578,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/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/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/t0068-for-each-repo.sh b/t/t0068-for-each-repo.sh index 4b90b74d5d..95019e01ed 100755 --- a/t/t0068-for-each-repo.sh +++ b/t/t0068-for-each-repo.sh @@ -59,4 +59,20 @@ test_expect_success 'error on NULL value for config keys' ' test_cmp expect actual ' +test_expect_success '--keep-going' ' + git config keep.going non-existing && + git config --add keep.going . && + + test_must_fail git for-each-repo --config=keep.going \ + -- branch >out 2>err && + test_grep "cannot change to .*non-existing" err && + test_must_be_empty out && + + test_must_fail git for-each-repo --config=keep.going --keep-going \ + -- branch >out 2>err && + test_grep "cannot change to .*non-existing" err && + git branch >expect && + test_cmp expect out +' + 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/t0300-credentials.sh b/t/t0300-credentials.sh index 400f6bdbca..432f029d48 100755 --- a/t/t0300-credentials.sh +++ b/t/t0300-credentials.sh @@ -12,7 +12,13 @@ test_expect_success 'setup helper scripts' ' IFS== while read key value; do echo >&2 "$whoami: $key=$value" - eval "$key=$value" + if test -z "${key%%*\[\]}" + then + key=${key%%\[\]} + eval "$key=\"\$$key $value\"" + else + eval "$key=$value" + fi done IFS=$OIFS EOF @@ -35,6 +41,30 @@ test_expect_success 'setup helper scripts' ' test -z "$pass" || echo password=$pass EOF + write_script git-credential-verbatim-cred <<-\EOF && + authtype=$1; shift + credential=$1; shift + . ./dump + echo capability[]=authtype + echo capability[]=state + test -z "${capability##*authtype*}" || exit 0 + test -z "$authtype" || echo authtype=$authtype + test -z "$credential" || echo credential=$credential + test -z "${capability##*state*}" || exit 0 + echo state[]=verbatim-cred:foo + EOF + + write_script git-credential-verbatim-ephemeral <<-\EOF && + authtype=$1; shift + credential=$1; shift + . ./dump + echo capability[]=authtype + test -z "${capability##*authtype*}" || exit 0 + test -z "$authtype" || echo authtype=$authtype + test -z "$credential" || echo credential=$credential + echo "ephemeral=1" + EOF + write_script git-credential-verbatim-with-expiry <<-\EOF && user=$1; shift pass=$1; shift @@ -64,6 +94,67 @@ test_expect_success 'credential_fill invokes helper' ' EOF ' +test_expect_success 'credential_fill invokes helper with credential' ' + check fill "verbatim-cred Bearer token" <<-\EOF + capability[]=authtype + protocol=http + host=example.com + -- + capability[]=authtype + authtype=Bearer + credential=token + protocol=http + host=example.com + -- + verbatim-cred: get + verbatim-cred: capability[]=authtype + verbatim-cred: protocol=http + verbatim-cred: host=example.com + EOF +' + +test_expect_success 'credential_fill invokes helper with ephemeral credential' ' + check fill "verbatim-ephemeral Bearer token" <<-\EOF + capability[]=authtype + protocol=http + host=example.com + -- + capability[]=authtype + authtype=Bearer + credential=token + ephemeral=1 + protocol=http + host=example.com + -- + verbatim-ephemeral: get + verbatim-ephemeral: capability[]=authtype + verbatim-ephemeral: protocol=http + verbatim-ephemeral: host=example.com + EOF +' +test_expect_success 'credential_fill invokes helper with credential and state' ' + check fill "verbatim-cred Bearer token" <<-\EOF + capability[]=authtype + capability[]=state + protocol=http + host=example.com + -- + capability[]=authtype + capability[]=state + authtype=Bearer + credential=token + protocol=http + host=example.com + state[]=verbatim-cred:foo + -- + verbatim-cred: get + verbatim-cred: capability[]=authtype + verbatim-cred: capability[]=state + verbatim-cred: protocol=http + verbatim-cred: host=example.com + EOF +' + test_expect_success 'credential_fill invokes multiple helpers' ' check fill useless "verbatim foo bar" <<-\EOF protocol=http @@ -83,6 +174,45 @@ test_expect_success 'credential_fill invokes multiple helpers' ' EOF ' +test_expect_success 'credential_fill response does not get capabilities when helpers are incapable' ' + check fill useless "verbatim foo bar" <<-\EOF + capability[]=authtype + capability[]=state + protocol=http + host=example.com + -- + protocol=http + host=example.com + username=foo + password=bar + -- + useless: get + useless: capability[]=authtype + useless: capability[]=state + useless: protocol=http + useless: host=example.com + verbatim: get + verbatim: capability[]=authtype + verbatim: capability[]=state + verbatim: protocol=http + verbatim: host=example.com + EOF +' + +test_expect_success 'credential_fill response does not get capabilities when caller is incapable' ' + check fill "verbatim-cred Bearer token" <<-\EOF + protocol=http + host=example.com + -- + protocol=http + host=example.com + -- + verbatim-cred: get + verbatim-cred: protocol=http + verbatim-cred: host=example.com + EOF +' + test_expect_success 'credential_fill stops when we get a full response' ' check fill "verbatim one two" "verbatim three four" <<-\EOF protocol=http @@ -99,6 +229,25 @@ test_expect_success 'credential_fill stops when we get a full response' ' EOF ' +test_expect_success 'credential_fill thinks a credential is a full response' ' + check fill "verbatim-cred Bearer token" "verbatim three four" <<-\EOF + capability[]=authtype + protocol=http + host=example.com + -- + capability[]=authtype + authtype=Bearer + credential=token + protocol=http + host=example.com + -- + verbatim-cred: get + verbatim-cred: capability[]=authtype + verbatim-cred: protocol=http + verbatim-cred: host=example.com + EOF +' + test_expect_success 'credential_fill continues through partial response' ' check fill "verbatim one \"\"" "verbatim two three" <<-\EOF protocol=http @@ -175,6 +324,20 @@ test_expect_success 'credential_fill passes along metadata' ' EOF ' +test_expect_success 'credential_fill produces no credential without capability' ' + check fill "verbatim-cred Bearer token" <<-\EOF + protocol=http + host=example.com + -- + protocol=http + host=example.com + -- + verbatim-cred: get + verbatim-cred: protocol=http + verbatim-cred: host=example.com + EOF +' + test_expect_success 'credential_approve calls all helpers' ' check approve useless "verbatim one two" <<-\EOF protocol=http diff --git a/t/t0301-credential-cache.sh b/t/t0301-credential-cache.sh index f2c146fa2a..c10e35905e 100755 --- a/t/t0301-credential-cache.sh +++ b/t/t0301-credential-cache.sh @@ -39,6 +39,7 @@ test_atexit 'git credential-cache exit' helper_test cache helper_test_password_expiry_utc cache helper_test_oauth_refresh_token cache +helper_test_authtype cache test_expect_success 'socket defaults to ~/.cache/git/credential/socket' ' test_when_finished " 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 64214340e7..a390cffc80 100755 --- a/t/t0600-reffiles-backend.sh +++ b/t/t0600-reffiles-backend.sh @@ -4,16 +4,12 @@ test_description='Test reffiles backend' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +GIT_TEST_DEFAULT_REF_FORMAT=files +export GIT_TEST_DEFAULT_REF_FORMAT TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh -if ! test_have_prereq REFFILES -then - skip_all='skipping reffiles specific tests' - test_done -fi - test_expect_success 'setup' ' git commit --allow-empty -m Initial && C=$(git rev-parse HEAD) && diff --git a/t/t0601-reffiles-pack-refs.sh b/t/t0601-reffiles-pack-refs.sh index 7d4ab0b91a..60a544b8ee 100755 --- a/t/t0601-reffiles-pack-refs.sh +++ b/t/t0601-reffiles-pack-refs.sh @@ -9,18 +9,15 @@ test_description='git pack-refs should not change the branch semantic This test runs git pack-refs and git show-ref and checks that the branch semantic is still the same. ' + GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +GIT_TEST_DEFAULT_REF_FORMAT=files +export GIT_TEST_DEFAULT_REF_FORMAT TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh -if ! test_have_prereq REFFILES -then - skip_all='skipping reffiles specific tests' - test_done -fi - test_expect_success 'enable reflogs' ' git config core.logallrefupdates true ' diff --git a/t/t0610-reftable-basics.sh b/t/t0610-reftable-basics.sh index 178791e086..cc5bbfd732 100755 --- a/t/t0610-reftable-basics.sh +++ b/t/t0610-reftable-basics.sh @@ -4,17 +4,14 @@ # test_description='reftable basics' + GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +GIT_TEST_DEFAULT_REF_FORMAT=reftable +export GIT_TEST_DEFAULT_REF_FORMAT . ./test-lib.sh -if ! test_have_prereq REFTABLE -then - skip_all='skipping reftable tests; set GIT_TEST_DEFAULT_REF_FORMAT=reftable' - test_done -fi - INVALID_OID=$(test_oid 001) test_expect_success 'init: creates basic reftable structures' ' @@ -81,8 +78,8 @@ test_expect_success 'init: reinitializing reftable with files backend fails' ' ' test_expect_perms () { - local perms="$1" - local file="$2" + local perms="$1" && + local file="$2" && local actual="$(ls -l "$file")" && case "$actual" in @@ -286,7 +283,7 @@ test_expect_success 'ref transaction: creating symbolic ref fails with F/D confl git init repo && test_commit -C repo A && cat >expect <<-EOF && - error: unable to write symref for refs/heads: file/directory conflict + error: ${SQ}refs/heads/main${SQ} exists; cannot create ${SQ}refs/heads${SQ} EOF test_must_fail git -C repo symbolic-ref refs/heads refs/heads/foo 2>err && test_cmp expect err @@ -854,6 +851,39 @@ test_expect_success 'reflog: updates via HEAD update HEAD reflog' ' ) ' +test_expect_success 'branch: copying branch with D/F conflict' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit A && + git branch branch && + cat >expect <<-EOF && + error: ${SQ}refs/heads/branch${SQ} exists; cannot create ${SQ}refs/heads/branch/moved${SQ} + fatal: branch copy failed + EOF + test_must_fail git branch -c branch branch/moved 2>err && + test_cmp expect err + ) +' + +test_expect_success 'branch: moving branch with D/F conflict' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit A && + git branch branch && + git branch conflict && + cat >expect <<-EOF && + error: ${SQ}refs/heads/conflict${SQ} exists; cannot create ${SQ}refs/heads/conflict/moved${SQ} + fatal: branch rename failed + EOF + test_must_fail git branch -m branch conflict/moved 2>err && + test_cmp expect err + ) +' + test_expect_success 'worktree: adding worktree creates separate stack' ' test_when_finished "rm -rf repo worktree" && git init repo && diff --git a/t/t0612-reftable-jgit-compatibility.sh b/t/t0612-reftable-jgit-compatibility.sh new file mode 100755 index 0000000000..d0d7e80b49 --- /dev/null +++ b/t/t0612-reftable-jgit-compatibility.sh @@ -0,0 +1,132 @@ +#!/bin/sh + +test_description='reftables are compatible with JGit' + +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +GIT_TEST_DEFAULT_REF_FORMAT=reftable +export GIT_TEST_DEFAULT_REF_FORMAT + +# JGit does not support the 'link' DIRC extension. +GIT_TEST_SPLIT_INDEX=0 +export GIT_TEST_SPLIT_INDEX + +. ./test-lib.sh + +if ! test_have_prereq JGIT +then + skip_all='skipping reftable JGit tests; JGit is not present in PATH' + test_done +fi + +if ! test_have_prereq SHA1 +then + skip_all='skipping reftable JGit tests; JGit does not support SHA256 reftables' + test_done +fi + +test_commit_jgit () { + touch "$1" && + jgit add "$1" && + jgit commit -m "$1" +} + +test_same_refs () { + git show-ref --head >cgit.actual && + jgit show-ref >jgit-tabs.actual && + tr "\t" " " <jgit-tabs.actual >jgit.actual && + test_cmp cgit.actual jgit.actual +} + +test_same_ref () { + git rev-parse "$1" >cgit.actual && + jgit rev-parse "$1" >jgit.actual && + test_cmp cgit.actual jgit.actual +} + +test_same_reflog () { + git reflog "$*" >cgit.actual && + jgit reflog "$*" >jgit-newline.actual && + sed '/^$/d' <jgit-newline.actual >jgit.actual && + test_cmp cgit.actual jgit.actual +} + +test_expect_success 'CGit repository can be read by JGit' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit A && + test_same_refs && + test_same_ref HEAD && + test_same_reflog HEAD + ) +' + +test_expect_success 'JGit repository can be read by CGit' ' + test_when_finished "rm -rf repo" && + jgit init repo && + ( + cd repo && + + touch file && + jgit add file && + jgit commit -m "initial commit" && + + # Note that we must convert the ref storage after we have + # written the default branch. Otherwise JGit will end up with + # no HEAD at all. + jgit convert-ref-storage --format=reftable && + + test_same_refs && + test_same_ref HEAD && + # Interestingly, JGit cannot read its own reflog here. CGit can + # though. + printf "%s HEAD@{0}: commit (initial): initial commit" "$(git rev-parse --short HEAD)" >expect && + git reflog HEAD >actual && + test_cmp expect actual + ) +' + +test_expect_success 'mixed writes from JGit and CGit' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + + test_commit A && + test_commit_jgit B && + test_commit C && + test_commit_jgit D && + + test_same_refs && + test_same_ref HEAD && + test_same_reflog HEAD + ) +' + +test_expect_success 'JGit can read multi-level index' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + + test_commit A && + awk " + BEGIN { + print \"start\"; + for (i = 0; i < 10000; i++) + printf \"create refs/heads/branch-%d HEAD\n\", i; + print \"commit\"; + } + " >input && + git update-ref --stdin <input && + + test_same_refs && + test_same_ref refs/heads/branch-1 && + test_same_ref refs/heads/branch-5738 && + test_same_ref refs/heads/branch-9999 + ) +' + +test_done 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/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/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/t3428-rebase-signoff.sh b/t/t3428-rebase-signoff.sh index 1bebd1ce74..6f57aed9fa 100755 --- a/t/t3428-rebase-signoff.sh +++ b/t/t3428-rebase-signoff.sh @@ -5,12 +5,17 @@ test_description='git rebase --signoff This test runs git rebase --signoff and make sure that it works. ' -TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-rebase.sh test_expect_success 'setup' ' git commit --allow-empty -m "Initial empty commit" && test_commit first file a && + test_commit second file && + git checkout -b conflict-branch first && + test_commit file-2 file-2 && + test_commit conflict file && + test_commit third file && ident="$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" && @@ -28,6 +33,22 @@ test_expect_success 'setup' ' Signed-off-by: $ident EOF + # Expected commit message after conflict resolution for rebase --signoff + cat >expected-signed-conflict <<-EOF && + third + + Signed-off-by: $ident + + conflict + + Signed-off-by: $ident + + file-2 + + Signed-off-by: $ident + + EOF + # Expected commit message after rebase without --signoff (or with --no-signoff) cat >expected-unsigned <<-EOF && first @@ -39,8 +60,12 @@ test_expect_success 'setup' ' # We configure an alias to do the rebase --signoff so that # on the next subtest we can show that --no-signoff overrides the alias test_expect_success 'rebase --apply --signoff adds a sign-off line' ' - git rbs --apply HEAD^ && - test_commit_message HEAD expected-signed + test_must_fail git rbs --apply second third && + git checkout --theirs file && + git add file && + git rebase --continue && + git log --format=%B -n3 >actual && + test_cmp expected-signed-conflict actual ' test_expect_success 'rebase --no-signoff does not add a sign-off line' ' @@ -51,28 +76,65 @@ test_expect_success 'rebase --no-signoff does not add a sign-off line' ' test_expect_success 'rebase --exec --signoff adds a sign-off line' ' test_when_finished "rm exec" && - git commit --amend -m "first" && - git rebase --exec "touch exec" --signoff HEAD^ && + git rebase --exec "touch exec" --signoff first^ first && test_path_is_file exec && test_commit_message HEAD expected-signed ' test_expect_success 'rebase --root --signoff adds a sign-off line' ' - git commit --amend -m "first" && + git checkout first && git rebase --root --keep-empty --signoff && test_commit_message HEAD^ expected-initial-signed && test_commit_message HEAD expected-signed ' -test_expect_success 'rebase -i --signoff fails' ' - git commit --amend -m "first" && - git rebase -i --signoff HEAD^ && - test_commit_message HEAD expected-signed +test_expect_success 'rebase -m --signoff adds a sign-off line' ' + test_must_fail git rebase -m --signoff second third && + git checkout --theirs file && + git add file && + GIT_EDITOR="sed -n /Conflicts:/,/^\\\$/p >actual" \ + git rebase --continue && + cat >expect <<-\EOF && + # Conflicts: + # file + + EOF + test_cmp expect actual && + git log --format=%B -n3 >actual && + test_cmp expected-signed-conflict actual ' -test_expect_success 'rebase -m --signoff fails' ' - git commit --amend -m "first" && - git rebase -m --signoff HEAD^ && - test_commit_message HEAD expected-signed +test_expect_success 'rebase -i --signoff adds a sign-off line when editing commit' ' + ( + set_fake_editor && + FAKE_LINES="edit 1 edit 3 edit 2" \ + git rebase -i --signoff first third + ) && + echo a >a && + git add a && + test_must_fail git rebase --continue && + git checkout --ours file && + echo b >a && + git add a file && + git rebase --continue && + echo c >a && + git add a && + git log --format=%B -n3 >actual && + cat >expect <<-EOF && + conflict + + Signed-off-by: $ident + + third + + Signed-off-by: $ident + + file-2 + + Signed-off-by: $ident + + EOF + test_cmp expect actual ' + test_done diff --git a/t/t3434-rebase-i18n.sh b/t/t3434-rebase-i18n.sh index e6fef696bb..a4e482d2cd 100755 --- a/t/t3434-rebase-i18n.sh +++ b/t/t3434-rebase-i18n.sh @@ -71,7 +71,7 @@ test_rebase_continue_update_encode () { git config i18n.commitencoding $new && test_must_fail git rebase -m main && test -f .git/rebase-merge/message && - git stripspace <.git/rebase-merge/message >two.t && + git stripspace -s <.git/rebase-merge/message >two.t && git add two.t && git rebase --continue && compare_msg $msgfile $old $new && diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 04d8333373..28a95a775d 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -8,6 +8,8 @@ TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-terminal.sh +SP=" " + diff_cmp () { for x do @@ -45,17 +47,30 @@ test_expect_success 'warn about add.interactive.useBuiltin' ' cat >expect <<-\EOF && warning: the add.interactive.useBuiltin setting has been removed! See its entry in '\''git help config'\'' for details. - No changes. EOF + echo "No changes." >expect.out && for v in = =true =false do git -c "add.interactive.useBuiltin$v" add -p >out 2>actual && - test_must_be_empty out && + test_cmp expect.out out && test_cmp expect actual || return 1 done ' +test_expect_success 'unknown command' ' + test_when_finished "git reset --hard; rm -f command" && + echo W >command && + git add -N command && + git diff command >expect && + cat >>expect <<-EOF && + (1/1) Stage addition [y,n,q,a,d,e,p,?]? Unknown command ${SQ}W${SQ} (use ${SQ}?${SQ} for help) + (1/1) Stage addition [y,n,q,a,d,e,p,?]?$SP + EOF + git add -p -- command <command >actual 2>&1 && + test_cmp expect actual +' + test_expect_success 'setup (initial)' ' echo content >file && git add file && @@ -232,7 +247,6 @@ test_expect_success 'setup file' ' ' test_expect_success 'setup patch' ' - SP=" " && NULL="" && cat >patch <<-EOF @@ -1,4 +1,4 @@ @@ -335,13 +349,13 @@ test_expect_success 'different prompts for mode change/deleted' ' test_expect_success 'correct message when there is nothing to do' ' git reset --hard && - git add -p 2>err && - test_grep "No changes" err && + git add -p >out && + test_grep "No changes" out && printf "\\0123" >binary && git add binary && printf "\\0abc" >binary && - git add -p 2>err && - test_grep "Only binary files changed" err + git add -p >out && + test_grep "Only binary files changed" out ' test_expect_success 'setup again' ' diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 00db82fb24..a7f71f8126 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -393,6 +393,15 @@ test_expect_success 'stash --staged' ' test bar,bar4 = $(cat file),$(cat file2) ' +test_expect_success 'stash --staged with binary file' ' + printf "\0" >file && + git add file && + git stash --staged && + git stash pop && + printf "\0" >expect && + test_cmp expect file +' + test_expect_success 'dont assume push with non-option args' ' test_must_fail git stash -q drop 2>err && test_grep -e "subcommand wasn'\''t specified; '\''push'\'' can'\''t be assumed due to unexpected token '\''drop'\''" err diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index e37a1411ee..ba85b582c5 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -1368,12 +1368,38 @@ test_expect_success 'empty subject prefix does not have extra space' ' test_cmp expect actual ' -test_expect_success '--rfc' ' +test_expect_success '--rfc and --no-rfc' ' cat >expect <<-\EOF && Subject: [RFC PATCH 1/1] header with . in it EOF git format-patch -n -1 --stdout --rfc >patch && grep "^Subject:" patch >actual && + test_cmp expect actual && + git format-patch -n -1 --stdout --rfc --no-rfc >patch && + sed -e "s/RFC //" expect >expect-raw && + grep "^Subject:" patch >actual && + test_cmp expect-raw actual +' + +test_expect_success '--rfc=WIP and --rfc=' ' + cat >expect <<-\EOF && + Subject: [WIP PATCH 1/1] header with . in it + EOF + git format-patch -n -1 --stdout --rfc=WIP >patch && + grep "^Subject:" patch >actual && + test_cmp expect actual && + git format-patch -n -1 --stdout --rfc --rfc= >patch && + sed -e "s/WIP //" expect >expect-raw && + grep "^Subject:" patch >actual && + test_cmp expect-raw actual +' + +test_expect_success '--rfc=-(WIP) appends' ' + cat >expect <<-\EOF && + Subject: [PATCH (WIP) 1/1] header with . in it + EOF + git format-patch -n -1 --stdout --rfc="-(WIP)" >patch && + grep "^Subject:" patch >actual && test_cmp expect actual ' @@ -1397,6 +1423,27 @@ test_expect_success '--rfc is argument order independent' ' test_cmp expect actual ' +test_expect_success '--subject-prefix="<non-empty>" and -k cannot be used together' ' + echo "fatal: options '\''--subject-prefix/--rfc'\'' and '\''-k'\'' cannot be used together" >expect.err && + test_must_fail git format-patch -1 --stdout --subject-prefix="MYPREFIX" -k >actual.out 2>actual.err && + test_must_be_empty actual.out && + test_cmp expect.err actual.err +' + +test_expect_success '--subject-prefix="" and -k cannot be used together' ' + echo "fatal: options '\''--subject-prefix/--rfc'\'' and '\''-k'\'' cannot be used together" >expect.err && + test_must_fail git format-patch -1 --stdout --subject-prefix="" -k >actual.out 2>actual.err && + test_must_be_empty actual.out && + test_cmp expect.err actual.err +' + +test_expect_success '--rfc and -k cannot be used together' ' + echo "fatal: options '\''--subject-prefix/--rfc'\'' and '\''-k'\'' cannot be used together" >expect.err && + test_must_fail git format-patch -1 --stdout --rfc -k >actual.out 2>actual.err && + test_must_be_empty actual.out && + test_cmp expect.err actual.err +' + test_expect_success '--from=ident notices bogus ident' ' test_must_fail git format-patch -1 --stdout --from=foo >patch ' 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 ffaf69335f..fb8c51746e 100755 --- a/t/t4046-diff-unmerged.sh +++ b/t/t4046-diff-unmerged.sh @@ -20,13 +20,15 @@ test_expect_success setup ' for t in o x do path="$b$o$t" && - case "$path" in ooo) continue ;; esac && - paths="$paths$path " && - p=" $path" && - case "$b" in x) echo "$m1$p" ;; esac && - case "$o" in x) echo "$m2$p" ;; esac && - case "$t" in x) echo "$m3$p" ;; esac || - return 1 + if test "$path" != ooo + then + paths="$paths$path " && + p=" $path" && + case "$b" in x) echo "$m1$p" ;; esac && + case "$o" in x) echo "$m2$p" ;; esac && + case "$t" in x) echo "$m3$p" ;; esac || + return 1 + fi done done done >ls-files-s.expect && 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/t5326-multi-pack-bitmaps.sh b/t/t5326-multi-pack-bitmaps.sh index 5d7d321840..cc7220b6c0 100755 --- a/t/t5326-multi-pack-bitmaps.sh +++ b/t/t5326-multi-pack-bitmaps.sh @@ -434,6 +434,27 @@ test_expect_success 'tagged commits are selected for bitmapping' ' ) ' +test_expect_success 'do not follow replace objects for MIDX bitmap' ' + rm -fr repo && + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + test_commit A && + test_commit B && + git checkout --orphan=orphan A && + test_commit orphan && + + git replace A HEAD && + git repack -ad --write-midx --write-bitmap-index && + + # generating reachability bitmaps with replace refs + # enabled will result in broken clones + git clone --no-local --bare . clone.git + ) +' + corrupt_file () { chmod a+w "$1" && printf "bogus" | dd of="$1" bs=1 seek="12" conv=notrunc diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 33d34d5ae9..9441793d06 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -1252,6 +1252,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/t5563-simple-http-auth.sh b/t/t5563-simple-http-auth.sh index ab8a721ccc..5d5caa3f58 100755 --- a/t/t5563-simple-http-auth.sh +++ b/t/t5563-simple-http-auth.sh @@ -21,9 +21,17 @@ test_expect_success 'setup_credential_helper' ' CREDENTIAL_HELPER="$TRASH_DIRECTORY/bin/git-credential-test-helper" && write_script "$CREDENTIAL_HELPER" <<-\EOF cmd=$1 - teefile=$cmd-query.cred + teefile=$cmd-query-temp.cred catfile=$cmd-reply.cred sed -n -e "/^$/q" -e "p" >>$teefile + state=$(sed -ne "s/^state\[\]=helper://p" "$teefile") + if test -z "$state" + then + mv "$teefile" "$cmd-query.cred" + else + mv "$teefile" "$cmd-query-$state.cred" + catfile="$cmd-reply-$state.cred" + fi if test "$cmd" = "get" then cat $catfile @@ -32,13 +40,15 @@ test_expect_success 'setup_credential_helper' ' ' set_credential_reply () { - cat >"$TRASH_DIRECTORY/$1-reply.cred" + local suffix="$(test -n "$2" && echo "-$2")" + cat >"$TRASH_DIRECTORY/$1-reply$suffix.cred" } expect_credential_query () { - cat >"$TRASH_DIRECTORY/$1-expect.cred" && - test_cmp "$TRASH_DIRECTORY/$1-expect.cred" \ - "$TRASH_DIRECTORY/$1-query.cred" + local suffix="$(test -n "$2" && echo "-$2")" + cat >"$TRASH_DIRECTORY/$1-expect$suffix.cred" && + test_cmp "$TRASH_DIRECTORY/$1-expect$suffix.cred" \ + "$TRASH_DIRECTORY/$1-query$suffix.cred" } per_test_cleanup () { @@ -63,17 +73,20 @@ test_expect_success 'access using basic auth' ' # Basic base64(alice:secret-passwd) cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && - Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== + id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== EOF cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && - WWW-Authenticate: Basic realm="example.com" + id=1 status=200 + id=default response=WWW-Authenticate: Basic realm="example.com" EOF test_config_global credential.helper test-helper && git ls-remote "$HTTPD_URL/custom_auth/repo.git" && expect_credential_query get <<-EOF && + capability[]=authtype + capability[]=state protocol=http host=$HTTPD_DEST wwwauth[]=Basic realm="example.com" @@ -87,6 +100,45 @@ test_expect_success 'access using basic auth' ' EOF ' +test_expect_success 'access using basic auth via authtype' ' + test_when_finished "per_test_cleanup" && + + set_credential_reply get <<-EOF && + capability[]=authtype + authtype=Basic + credential=YWxpY2U6c2VjcmV0LXBhc3N3ZA== + EOF + + # Basic base64(alice:secret-passwd) + cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && + id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== + EOF + + cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && + id=1 status=200 + id=default response=WWW-Authenticate: Basic realm="example.com" + EOF + + test_config_global credential.helper test-helper && + GIT_CURL_VERBOSE=1 git ls-remote "$HTTPD_URL/custom_auth/repo.git" && + + expect_credential_query get <<-EOF && + capability[]=authtype + capability[]=state + protocol=http + host=$HTTPD_DEST + wwwauth[]=Basic realm="example.com" + EOF + + expect_credential_query store <<-EOF + capability[]=authtype + authtype=Basic + credential=YWxpY2U6c2VjcmV0LXBhc3N3ZA== + protocol=http + host=$HTTPD_DEST + EOF +' + test_expect_success 'access using basic auth invalid credentials' ' test_when_finished "per_test_cleanup" && @@ -97,17 +149,20 @@ test_expect_success 'access using basic auth invalid credentials' ' # Basic base64(alice:secret-passwd) cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && - Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== + id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== EOF cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && - WWW-Authenticate: Basic realm="example.com" + id=1 status=200 + id=default response=WWW-Authenticate: Basic realm="example.com" EOF test_config_global credential.helper test-helper && test_must_fail git ls-remote "$HTTPD_URL/custom_auth/repo.git" && expect_credential_query get <<-EOF && + capability[]=authtype + capability[]=state protocol=http host=$HTTPD_DEST wwwauth[]=Basic realm="example.com" @@ -132,19 +187,22 @@ test_expect_success 'access using basic auth with extra challenges' ' # Basic base64(alice:secret-passwd) cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && - Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== + id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== EOF cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && - WWW-Authenticate: FooBar param1="value1" param2="value2" - WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0 - WWW-Authenticate: Basic realm="example.com" + id=1 status=200 + id=default response=WWW-Authenticate: FooBar param1="value1" param2="value2" + id=default response=WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0 + id=default response=WWW-Authenticate: Basic realm="example.com" EOF test_config_global credential.helper test-helper && git ls-remote "$HTTPD_URL/custom_auth/repo.git" && expect_credential_query get <<-EOF && + capability[]=authtype + capability[]=state protocol=http host=$HTTPD_DEST wwwauth[]=FooBar param1="value1" param2="value2" @@ -170,19 +228,22 @@ test_expect_success 'access using basic auth mixed-case wwwauth header name' ' # Basic base64(alice:secret-passwd) cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && - Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== + id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== EOF cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && - www-authenticate: foobar param1="value1" param2="value2" - WWW-AUTHENTICATE: BEARER authorize_uri="id.example.com" p=1 q=0 - WwW-aUtHeNtIcAtE: baSiC realm="example.com" + id=1 status=200 + id=default response=www-authenticate: foobar param1="value1" param2="value2" + id=default response=WWW-AUTHENTICATE: BEARER authorize_uri="id.example.com" p=1 q=0 + id=default response=WwW-aUtHeNtIcAtE: baSiC realm="example.com" EOF test_config_global credential.helper test-helper && git ls-remote "$HTTPD_URL/custom_auth/repo.git" && expect_credential_query get <<-EOF && + capability[]=authtype + capability[]=state protocol=http host=$HTTPD_DEST wwwauth[]=foobar param1="value1" param2="value2" @@ -208,24 +269,27 @@ test_expect_success 'access using basic auth with wwwauth header continuations' # Basic base64(alice:secret-passwd) cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && - Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== + id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== EOF # Note that leading and trailing whitespace is important to correctly # simulate a continuation/folded header. cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && - WWW-Authenticate: FooBar param1="value1" - param2="value2" - WWW-Authenticate: Bearer authorize_uri="id.example.com" - p=1 - q=0 - WWW-Authenticate: Basic realm="example.com" + id=1 status=200 + id=default response=WWW-Authenticate: FooBar param1="value1" + id=default response= param2="value2" + id=default response=WWW-Authenticate: Bearer authorize_uri="id.example.com" + id=default response= p=1 + id=default response= q=0 + id=default response=WWW-Authenticate: Basic realm="example.com" EOF test_config_global credential.helper test-helper && git ls-remote "$HTTPD_URL/custom_auth/repo.git" && expect_credential_query get <<-EOF && + capability[]=authtype + capability[]=state protocol=http host=$HTTPD_DEST wwwauth[]=FooBar param1="value1" param2="value2" @@ -251,26 +315,29 @@ test_expect_success 'access using basic auth with wwwauth header empty continuat # Basic base64(alice:secret-passwd) cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && - Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== + id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== EOF CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" && # Note that leading and trailing whitespace is important to correctly # simulate a continuation/folded header. - printf "WWW-Authenticate: FooBar param1=\"value1\"\r\n" >"$CHALLENGE" && - printf " \r\n" >>"$CHALLENGE" && - printf " param2=\"value2\"\r\n" >>"$CHALLENGE" && - printf "WWW-Authenticate: Bearer authorize_uri=\"id.example.com\"\r\n" >>"$CHALLENGE" && - printf " p=1\r\n" >>"$CHALLENGE" && - printf " \r\n" >>"$CHALLENGE" && - printf " q=0\r\n" >>"$CHALLENGE" && - printf "WWW-Authenticate: Basic realm=\"example.com\"\r\n" >>"$CHALLENGE" && + printf "id=1 status=200\n" >"$CHALLENGE" && + printf "id=default response=WWW-Authenticate: FooBar param1=\"value1\"\r\n" >>"$CHALLENGE" && + printf "id=default response= \r\n" >>"$CHALLENGE" && + printf "id=default response= param2=\"value2\"\r\n" >>"$CHALLENGE" && + printf "id=default response=WWW-Authenticate: Bearer authorize_uri=\"id.example.com\"\r\n" >>"$CHALLENGE" && + printf "id=default response= p=1\r\n" >>"$CHALLENGE" && + printf "id=default response= \r\n" >>"$CHALLENGE" && + printf "id=default response= q=0\r\n" >>"$CHALLENGE" && + printf "id=default response=WWW-Authenticate: Basic realm=\"example.com\"\r\n" >>"$CHALLENGE" && test_config_global credential.helper test-helper && git ls-remote "$HTTPD_URL/custom_auth/repo.git" && expect_credential_query get <<-EOF && + capability[]=authtype + capability[]=state protocol=http host=$HTTPD_DEST wwwauth[]=FooBar param1="value1" param2="value2" @@ -296,22 +363,25 @@ test_expect_success 'access using basic auth with wwwauth header mixed line-endi # Basic base64(alice:secret-passwd) cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && - Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== + id=1 creds=Basic YWxpY2U6c2VjcmV0LXBhc3N3ZA== EOF CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" && # Note that leading and trailing whitespace is important to correctly # simulate a continuation/folded header. - printf "WWW-Authenticate: FooBar param1=\"value1\"\r\n" >"$CHALLENGE" && - printf " \r\n" >>"$CHALLENGE" && - printf "\tparam2=\"value2\"\r\n" >>"$CHALLENGE" && - printf "WWW-Authenticate: Basic realm=\"example.com\"" >>"$CHALLENGE" && + printf "id=1 status=200\n" >"$CHALLENGE" && + printf "id=default response=WWW-Authenticate: FooBar param1=\"value1\"\r\n" >>"$CHALLENGE" && + printf "id=default response= \r\n" >>"$CHALLENGE" && + printf "id=default response=\tparam2=\"value2\"\r\n" >>"$CHALLENGE" && + printf "id=default response=WWW-Authenticate: Basic realm=\"example.com\"" >>"$CHALLENGE" && test_config_global credential.helper test-helper && git ls-remote "$HTTPD_URL/custom_auth/repo.git" && expect_credential_query get <<-EOF && + capability[]=authtype + capability[]=state protocol=http host=$HTTPD_DEST wwwauth[]=FooBar param1="value1" param2="value2" @@ -326,4 +396,166 @@ test_expect_success 'access using basic auth with wwwauth header mixed line-endi EOF ' +test_expect_success 'access using bearer auth' ' + test_when_finished "per_test_cleanup" && + + set_credential_reply get <<-EOF && + capability[]=authtype + authtype=Bearer + credential=YS1naXQtdG9rZW4= + EOF + + # Basic base64(a-git-token) + cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && + id=1 creds=Bearer YS1naXQtdG9rZW4= + EOF + + CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" && + + cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && + id=1 status=200 + id=default response=WWW-Authenticate: FooBar param1="value1" param2="value2" + id=default response=WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0 + id=default response=WWW-Authenticate: Basic realm="example.com" + EOF + + test_config_global credential.helper test-helper && + git ls-remote "$HTTPD_URL/custom_auth/repo.git" && + + expect_credential_query get <<-EOF && + capability[]=authtype + capability[]=state + protocol=http + host=$HTTPD_DEST + wwwauth[]=FooBar param1="value1" param2="value2" + wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0 + wwwauth[]=Basic realm="example.com" + EOF + + expect_credential_query store <<-EOF + capability[]=authtype + authtype=Bearer + credential=YS1naXQtdG9rZW4= + protocol=http + host=$HTTPD_DEST + EOF +' + +test_expect_success 'access using bearer auth with invalid credentials' ' + test_when_finished "per_test_cleanup" && + + set_credential_reply get <<-EOF && + capability[]=authtype + authtype=Bearer + credential=incorrect-token + EOF + + # Basic base64(a-git-token) + cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && + id=1 creds=Bearer YS1naXQtdG9rZW4= + EOF + + CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" && + + cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && + id=1 status=200 + id=default response=WWW-Authenticate: FooBar param1="value1" param2="value2" + id=default response=WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0 + id=default response=WWW-Authenticate: Basic realm="example.com" + EOF + + test_config_global credential.helper test-helper && + test_must_fail git ls-remote "$HTTPD_URL/custom_auth/repo.git" && + + expect_credential_query get <<-EOF && + capability[]=authtype + capability[]=state + protocol=http + host=$HTTPD_DEST + wwwauth[]=FooBar param1="value1" param2="value2" + wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0 + wwwauth[]=Basic realm="example.com" + EOF + + expect_credential_query erase <<-EOF + capability[]=authtype + authtype=Bearer + credential=incorrect-token + protocol=http + host=$HTTPD_DEST + wwwauth[]=FooBar param1="value1" param2="value2" + wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0 + wwwauth[]=Basic realm="example.com" + EOF +' + +test_expect_success 'access using three-legged auth' ' + test_when_finished "per_test_cleanup" && + + set_credential_reply get <<-EOF && + capability[]=authtype + capability[]=state + authtype=Multistage + credential=YS1naXQtdG9rZW4= + state[]=helper:foobar + continue=1 + EOF + + set_credential_reply get foobar <<-EOF && + capability[]=authtype + capability[]=state + authtype=Multistage + credential=YW5vdGhlci10b2tlbg== + state[]=helper:bazquux + EOF + + cat >"$HTTPD_ROOT_PATH/custom-auth.valid" <<-EOF && + id=1 creds=Multistage YS1naXQtdG9rZW4= + id=2 creds=Multistage YW5vdGhlci10b2tlbg== + EOF + + CHALLENGE="$HTTPD_ROOT_PATH/custom-auth.challenge" && + + cat >"$HTTPD_ROOT_PATH/custom-auth.challenge" <<-EOF && + id=1 status=401 response=WWW-Authenticate: Multistage challenge="456" + id=1 status=401 response=WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0 + id=2 status=200 + id=default response=WWW-Authenticate: Multistage challenge="123" + id=default response=WWW-Authenticate: Bearer authorize_uri="id.example.com" p=1 q=0 + EOF + + test_config_global credential.helper test-helper && + git ls-remote "$HTTPD_URL/custom_auth/repo.git" && + + expect_credential_query get <<-EOF && + capability[]=authtype + capability[]=state + protocol=http + host=$HTTPD_DEST + wwwauth[]=Multistage challenge="123" + wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0 + EOF + + expect_credential_query get foobar <<-EOF && + capability[]=authtype + capability[]=state + authtype=Multistage + protocol=http + host=$HTTPD_DEST + wwwauth[]=Multistage challenge="456" + wwwauth[]=Bearer authorize_uri="id.example.com" p=1 q=0 + state[]=helper:foobar + EOF + + expect_credential_query store bazquux <<-EOF + capability[]=authtype + capability[]=state + authtype=Multistage + credential=YW5vdGhlci10b2tlbg== + protocol=http + host=$HTTPD_DEST + state[]=helper:bazquux + EOF +' + test_done 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/t7900-maintenance.sh b/t/t7900-maintenance.sh index 0943dfa18a..8595489ceb 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh @@ -639,9 +639,9 @@ test_expect_success 'start from empty cron table' ' # start registers the repo git config --get --global --fixed-value maintenance.repo "$(pwd)" && - grep "for-each-repo --config=maintenance.repo maintenance run --schedule=daily" cron.txt && - grep "for-each-repo --config=maintenance.repo maintenance run --schedule=hourly" cron.txt && - grep "for-each-repo --config=maintenance.repo maintenance run --schedule=weekly" cron.txt + grep "for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=daily" cron.txt && + grep "for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=hourly" cron.txt && + grep "for-each-repo --keep-going --config=maintenance.repo maintenance run --schedule=weekly" cron.txt ' test_expect_success 'stop from existing schedule' ' diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index 569cf23104..963f865f27 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -2518,6 +2518,29 @@ test_expect_success 'complete tree filename with metacharacters' ' EOF ' +test_expect_success 'symbolic-ref completes builtin options' ' + test_completion "git symbolic-ref --d" <<-\EOF + --delete Z + EOF +' + +test_expect_success 'symbolic-ref completes short ref names' ' + test_completion "git symbolic-ref foo m" <<-\EOF + main Z + mybranch Z + mytag Z + EOF +' + +test_expect_success 'symbolic-ref completes full ref names' ' + test_completion "git symbolic-ref foo refs/" <<-\EOF + refs/heads/main Z + refs/heads/mybranch Z + refs/tags/mytag Z + refs/tags/A Z + EOF +' + test_expect_success PERL 'send-email' ' test_completion "git send-email --cov" <<-\EOF && --cover-from-description=Z @@ -1170,3 +1170,15 @@ void trailer_iterator_release(struct trailer_iterator *iter) 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,8 @@ #include "list.h" #include "strbuf.h" +struct strvec; + enum trailer_where { WHERE_DEFAULT, WHERE_END, @@ -158,4 +160,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 */ |
